mirror of
https://github.com/status-im/sourcecred.git
synced 2025-01-11 13:14:28 +00:00
Integrate the identity plugin (#1385)
This commit integrates the identity plugin, which was created in #1384. It does this by adding explicit identity fields to the project configuration, which are then applied when loading the graph in `api/load.js`. The actual integration is quite straightforward. Test plan: The underlying logic is thoroughly tested; I added one new test case to verify that it is integrated properly. Since the project compat has changed, I've updated all the snapshots. Prior to merging this PR, I will produce one "integration test", using this code to do identity resolution for a real project (i.e. on the SourceCred instance itself).
This commit is contained in:
parent
9a9f211901
commit
54ece536d3
@ -3,6 +3,7 @@
|
||||
## [Unreleased]
|
||||
|
||||
<!-- Please add new entries just beneath this line. -->
|
||||
- Enable combining different user identities together (#1385)
|
||||
- Add `sourcecred discourse` for loading Discourse servers (#1374)
|
||||
- Breaking: Change output format for the scores command (#1372)
|
||||
- Include top nodes for every type in Timeline Cred (#1358)
|
||||
|
@ -1 +1 @@
|
||||
[{"type":"sourcecred/project","version":"0.2.0"},{"discourseServer":null,"id":"sourcecred-test/example-github","repoIds":[{"name":"example-github","owner":"sourcecred-test"}]}]
|
||||
[{"type":"sourcecred/project","version":"0.3.0"},{"discourseServer":null,"id":"sourcecred-test/example-github","identities":[],"repoIds":[{"name":"example-github","owner":"sourcecred-test"}]}]
|
@ -15,6 +15,7 @@ import {setupProjectDirectory} from "../core/project_io";
|
||||
import {loadDiscourse} from "../plugins/discourse/loadDiscourse";
|
||||
import {type PluginDeclaration} from "../analysis/pluginDeclaration";
|
||||
import * as NullUtil from "../util/null";
|
||||
import {nodeContractions} from "../plugins/identity/nodeContractions";
|
||||
|
||||
export type LoadOptions = {|
|
||||
+project: Project,
|
||||
@ -91,7 +92,16 @@ export async function load(
|
||||
]);
|
||||
|
||||
const pluginGraphs = await Promise.all(pluginGraphPromises);
|
||||
const graph = Graph.merge(pluginGraphs);
|
||||
let graph = Graph.merge(pluginGraphs);
|
||||
const {identities, discourseServer} = project;
|
||||
if (identities.length) {
|
||||
const serverUrl =
|
||||
discourseServer == null ? null : discourseServer.serverUrl;
|
||||
const contractions = nodeContractions(identities, serverUrl);
|
||||
// Only apply contractions if identities have been specified, since it involves
|
||||
// a full Graph copy
|
||||
graph = graph.contractNodes(contractions);
|
||||
}
|
||||
|
||||
const projectDirectory = await setupProjectDirectory(
|
||||
project,
|
||||
|
@ -7,6 +7,7 @@ import fs from "fs-extra";
|
||||
|
||||
import type {Options as LoadGraphOptions} from "../plugins/github/loadGraph";
|
||||
import type {Options as LoadDiscourseOptions} from "../plugins/discourse/loadDiscourse";
|
||||
import {nodeContractions} from "../plugins/identity/nodeContractions";
|
||||
import type {Project} from "../core/project";
|
||||
import {
|
||||
directoryForProjectId,
|
||||
@ -66,6 +67,7 @@ describe("api/load", () => {
|
||||
serverUrl: discourseServerUrl,
|
||||
apiUsername: discourseApiUsername,
|
||||
},
|
||||
identities: [],
|
||||
};
|
||||
deepFreeze(project);
|
||||
const githubToken = "EXAMPLE_TOKEN";
|
||||
@ -222,4 +224,23 @@ describe("api/load", () => {
|
||||
const expectedJSON = discourseGraph().toJSON();
|
||||
expect(graphJSON).toEqual(expectedJSON);
|
||||
});
|
||||
|
||||
it("applies identity transformations, if present in the project", async () => {
|
||||
const {options, taskReporter, sourcecredDirectory} = example();
|
||||
const identity = {username: "identity", aliases: []};
|
||||
const newProject = {...options.project, identities: [identity]};
|
||||
const newOptions = {...options, project: newProject};
|
||||
await load(newOptions, taskReporter);
|
||||
const projectDirectory = directoryForProjectId(
|
||||
project.id,
|
||||
sourcecredDirectory
|
||||
);
|
||||
const graphFile = path.join(projectDirectory, "graph.json");
|
||||
const graphJSON = JSON.parse(await fs.readFile(graphFile));
|
||||
const identityGraph = combinedGraph().contractNodes(
|
||||
nodeContractions([identity], discourseServerUrl)
|
||||
);
|
||||
const expectedJSON = identityGraph.toJSON();
|
||||
expect(graphJSON).toEqual(expectedJSON);
|
||||
});
|
||||
});
|
||||
|
@ -105,6 +105,7 @@ const command: Command = async (args, std) => {
|
||||
id: projectId,
|
||||
repoIds: [],
|
||||
discourseServer: {serverUrl, apiUsername},
|
||||
identities: [],
|
||||
};
|
||||
const taskReporter = new LoggingTaskReporter();
|
||||
let weights = defaultWeights();
|
||||
|
@ -171,7 +171,7 @@ export async function createProject(opts: {|
|
||||
const subproject = await specToProject(spec, NullUtil.get(githubToken));
|
||||
repoIds = repoIds.concat(subproject.repoIds);
|
||||
}
|
||||
return {id: projectId, repoIds, discourseServer};
|
||||
return {id: projectId, repoIds, discourseServer, identities: []};
|
||||
}
|
||||
|
||||
export default genProject;
|
||||
|
@ -14,6 +14,7 @@ import {partialParams} from "../analysis/timeline/params";
|
||||
import {type PluginDeclaration} from "../analysis/pluginDeclaration";
|
||||
import {declaration as discourseDeclaration} from "../plugins/discourse/declaration";
|
||||
import {declaration as githubDeclaration} from "../plugins/github/declaration";
|
||||
import {declaration as identityDeclaration} from "../plugins/identity/declaration";
|
||||
|
||||
function usage(print: (string) => void): void {
|
||||
print(
|
||||
@ -132,6 +133,9 @@ const loadCommand: Command = async (args, std) => {
|
||||
if (project.repoIds.length) {
|
||||
plugins.push(githubDeclaration);
|
||||
}
|
||||
if (project.identities.length) {
|
||||
plugins.push(identityDeclaration);
|
||||
}
|
||||
return {
|
||||
project,
|
||||
params,
|
||||
|
@ -77,6 +77,7 @@ describe("cli/load", () => {
|
||||
id: "foo/bar",
|
||||
repoIds: [makeRepoId("foo", "bar")],
|
||||
discourseServer: null,
|
||||
identities: [],
|
||||
},
|
||||
params: defaultParams(),
|
||||
plugins: [githubDeclaration],
|
||||
@ -102,6 +103,7 @@ describe("cli/load", () => {
|
||||
id: projectId,
|
||||
repoIds: [stringToRepoId(projectId)],
|
||||
discourseServer: null,
|
||||
identities: [],
|
||||
},
|
||||
params: defaultParams(),
|
||||
plugins: [githubDeclaration],
|
||||
@ -140,6 +142,7 @@ describe("cli/load", () => {
|
||||
id: "foo/bar",
|
||||
repoIds: [makeRepoId("foo", "bar")],
|
||||
discourseServer: null,
|
||||
identities: [],
|
||||
},
|
||||
params: partialParams({weights}),
|
||||
plugins: [githubDeclaration],
|
||||
|
@ -3,6 +3,7 @@
|
||||
import base64url from "base64url";
|
||||
import {type RepoId} from "../core/repoId";
|
||||
import {toCompat, fromCompat, type Compatible} from "../util/compat";
|
||||
import {type Identity} from "../plugins/identity/identity";
|
||||
|
||||
export type ProjectId = string;
|
||||
|
||||
@ -29,9 +30,10 @@ export type Project = {|
|
||||
+serverUrl: string,
|
||||
+apiUsername: string,
|
||||
|} | null,
|
||||
+identities: $ReadOnlyArray<Identity>,
|
||||
|};
|
||||
|
||||
const COMPAT_INFO = {type: "sourcecred/project", version: "0.2.0"};
|
||||
const COMPAT_INFO = {type: "sourcecred/project", version: "0.3.0"};
|
||||
|
||||
export type ProjectJSON = Compatible<Project>;
|
||||
|
||||
|
@ -18,11 +18,18 @@ describe("core/project", () => {
|
||||
id: "foo/bar",
|
||||
repoIds: [foobar],
|
||||
discourseServer: null,
|
||||
identities: [],
|
||||
});
|
||||
const p2: Project = deepFreeze({
|
||||
id: "@foo",
|
||||
repoIds: [foobar, foozod],
|
||||
discourseServer: {serverUrl: "https://example.com", apiUsername: "credbot"},
|
||||
identities: [
|
||||
{
|
||||
username: "example",
|
||||
aliases: ["github/example"],
|
||||
},
|
||||
],
|
||||
});
|
||||
describe("to/fro JSON", () => {
|
||||
it("round trip is identity", () => {
|
||||
|
@ -23,11 +23,13 @@ describe("core/project_io", () => {
|
||||
id: "foo/bar",
|
||||
repoIds: [foobar],
|
||||
discourseServer: null,
|
||||
identities: [],
|
||||
});
|
||||
const p2: Project = deepFreeze({
|
||||
id: "@foo",
|
||||
repoIds: [foobar, foozod],
|
||||
discourseServer: {serverUrl: "https://example.com", apiUsername: "credbot"},
|
||||
identities: [{username: "foo", aliases: ["github/foo", "discourse/foo"]}],
|
||||
});
|
||||
|
||||
it("setupProjectDirectory results in a loadable project", async () => {
|
||||
|
@ -37,6 +37,7 @@ export async function specToProject(
|
||||
id: spec,
|
||||
repoIds: [stringToRepoId(spec)],
|
||||
discourseServer: null,
|
||||
identities: [],
|
||||
};
|
||||
return project;
|
||||
} else if (spec.match(ownerSpecMatcher)) {
|
||||
@ -46,6 +47,7 @@ export async function specToProject(
|
||||
id: spec,
|
||||
repoIds: org.repos,
|
||||
discourseServer: null,
|
||||
identities: [],
|
||||
};
|
||||
return project;
|
||||
}
|
||||
|
@ -18,6 +18,7 @@ describe("plugins/github/specToProject", () => {
|
||||
id: spec,
|
||||
repoIds: [stringToRepoId(spec)],
|
||||
discourseServer: null,
|
||||
identities: [],
|
||||
};
|
||||
const actual = await specToProject(spec, "FAKE_TOKEN");
|
||||
expect(expected).toEqual(actual);
|
||||
@ -31,7 +32,12 @@ describe("plugins/github/specToProject", () => {
|
||||
fetchGithubOrg.mockResolvedValueOnce(fakeOrg);
|
||||
const actual = await specToProject(spec, token);
|
||||
expect(fetchGithubOrg).toHaveBeenCalledWith(fakeOrg.name, token);
|
||||
const expected: Project = {id: spec, repoIds: repos, discourseServer: null};
|
||||
const expected: Project = {
|
||||
id: spec,
|
||||
repoIds: repos,
|
||||
discourseServer: null,
|
||||
identities: [],
|
||||
};
|
||||
expect(actual).toEqual(expected);
|
||||
});
|
||||
describe("fails for malformed spec strings", () => {
|
||||
|
Loading…
x
Reference in New Issue
Block a user