core: allow repo ID registry to store metadata (#1003)

Summary:
Our registry was defined to simply be a list of IDs. This is
insufficiently flexible; we want to be able to annotate these IDs with,
e.g., last-updated times (#989). This commit wraps the entries in a
simple object, updating clients appropriately.

Test Plan:

  - Run `node ./bin/sourcecred.js load sourcecred/example-github` with a
    repository registry in the old format, and note that it errors
    appropriately.
  - Run `yarn build` with a repository registry in the old format, and
    note that it errors (“Compat mismatch”).
  - Delete the old registry and re-run the `load` command. Note that it
    runs successfully and outputs a registry. Run `yarn build`; note
    that this works.
  - Load data for two repositories. Run `yarn start`. Note that the list
    of prototypes still works, and that you can navigate to and render
    attributions for individual project pages.
  - Verify that `yarn test --full` passes.

wchargin-branch: repo-id-registry-metadata
This commit is contained in:
William Chargin 2018-11-09 17:28:39 -08:00 committed by GitHub
parent 332e776317
commit 80b458d719
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 65 additions and 39 deletions

View File

@ -37,7 +37,7 @@ function loadRepoRegistry() /*: RepoIdRegistry */ {
} catch (e) {
if (e.code === "ENOENT") {
jsonString = JSON.stringify([
{version: "0.1.0", type: "REPO_ID_REGISTRY"},
{version: "0.2.0", type: "REPO_ID_REGISTRY"},
[],
]);
} else {
@ -46,7 +46,7 @@ function loadRepoRegistry() /*: RepoIdRegistry */ {
}
const json = JSON.parse(jsonString);
const compat = json[0];
if (compat.version !== "0.1.0" || compat.type !== "REPO_ID_REGISTRY") {
if (compat.version !== "0.2.0" || compat.type !== "REPO_ID_REGISTRY") {
throw new Error("Compat mismatch");
}
return json[1];

View File

@ -1 +1 @@
[{"type":"REPO_ID_REGISTRY","version":"0.1.0"},[{"name":"example-github","owner":"sourcecred"}]]
[{"type":"REPO_ID_REGISTRY","version":"0.2.0"},[{"repoId":{"name":"example-github","owner":"sourcecred"}}]]

View File

@ -213,7 +213,7 @@ test_expect_success TWO_REPOS \
test_expect_success TWO_REPOS \
"TWO_REPOS: should have a repo registry loaded into env" '
grep -F "REPO_REGISTRY" out &&
grep -xF "REPO_REGISTRY: [{\"name\":\"example-git\",\"owner\":\"sourcecred\"},{\"name\":\"example-github\",\"owner\":\"sourcecred\"}]" out
grep -xF "REPO_REGISTRY: [{\"repoId\":{\"name\":\"example-git\",\"owner\":\"sourcecred\"}},{\"repoId\":{\"name\":\"example-github\",\"owner\":\"sourcecred\"}}]" out
'
test_expect_success TWO_REPOS \

View File

@ -213,7 +213,7 @@ function addToRepoIdRegistry(repoId) {
} else {
registry = RepoIdRegistry.emptyRegistry();
}
registry = RepoIdRegistry.addRepoId(repoId, registry);
registry = RepoIdRegistry.addRepoId(registry, {repoId});
fs.writeFileSync(outputFile, stringify(RepoIdRegistry.toJSON(registry)));
}

View File

@ -429,7 +429,10 @@ describe("cli/load", () => {
)
.toString();
const registry = RepoIdRegistry.fromJSON(JSON.parse(blob));
expect(registry).toEqual([stringToRepoId("foo/combined")]);
const expected: RepoIdRegistry.RepoIdRegistry = [
{repoId: stringToRepoId("foo/combined")},
];
expect(registry).toEqual(expected);
});
it("appends to an existing registry", async () => {
@ -438,8 +441,8 @@ describe("cli/load", () => {
path.join(sourcecredDirectory, RepoIdRegistry.REPO_ID_REGISTRY_FILE),
JSON.stringify(
RepoIdRegistry.toJSON([
stringToRepoId("previous/one"),
stringToRepoId("previous/two"),
{repoId: stringToRepoId("previous/one")},
{repoId: stringToRepoId("previous/two")},
])
)
);
@ -451,11 +454,12 @@ describe("cli/load", () => {
)
.toString();
const registry = RepoIdRegistry.fromJSON(JSON.parse(blob));
expect(registry).toEqual([
stringToRepoId("previous/one"),
stringToRepoId("previous/two"),
stringToRepoId("foo/combined"),
]);
const expected: RepoIdRegistry.RepoIdRegistry = [
{repoId: stringToRepoId("previous/one")},
{repoId: stringToRepoId("previous/two")},
{repoId: stringToRepoId("foo/combined")},
];
expect(registry).toEqual(expected);
});
});
});

View File

@ -10,9 +10,12 @@ import type {RepoId} from "../core/repoId";
export const REPO_ID_REGISTRY_FILE = "repositoryRegistry.json";
export const REPO_ID_REGISTRY_API = "/api/v1/data/repositoryRegistry.json";
const REPO_ID_REGISTRY_COMPAT = {type: "REPO_ID_REGISTRY", version: "0.1.0"};
const REPO_ID_REGISTRY_COMPAT = {type: "REPO_ID_REGISTRY", version: "0.2.0"};
export type RepoIdRegistry = $ReadOnlyArray<RepoId>;
export type RegistryEntry = {|
+repoId: RepoId,
|};
export type RepoIdRegistry = $ReadOnlyArray<RegistryEntry>;
export type RepoIdRegistryJSON = Compatible<RepoIdRegistry>;
export function toJSON(r: RepoIdRegistry): RepoIdRegistryJSON {
@ -23,8 +26,16 @@ export function fromJSON(j: RepoIdRegistryJSON): RepoIdRegistry {
return fromCompat(REPO_ID_REGISTRY_COMPAT, j);
}
export function addRepoId(r: RepoId, reg: RepoIdRegistry): RepoIdRegistry {
return [...reg.filter((x) => !deepEqual(x, r)), r];
export function addRepoId(
registry: RepoIdRegistry,
entry: RegistryEntry
): RepoIdRegistry {
return [
...registry.filter(
(x: RegistryEntry) => !deepEqual(x.repoId, entry.repoId)
),
entry,
];
}
export function emptyRegistry(): RepoIdRegistry {

View File

@ -20,31 +20,42 @@ describe("core/repoIdRegistry", () => {
checkExample(emptyRegistry());
});
it("nonempty registry", () => {
checkExample([makeRepoId("foo", "bar"), makeRepoId("zoo", "zod")]);
checkExample([
{repoId: makeRepoId("foo", "bar")},
{repoId: makeRepoId("zoo", "zod")},
]);
});
});
describe("addRepoId", () => {
it("adds to empty registry", () => {
expect(addRepoId(makeRepoId("foo", "bar"), emptyRegistry())).toEqual([
makeRepoId("foo", "bar"),
]);
expect(
addRepoId(emptyRegistry(), {repoId: makeRepoId("foo", "bar")})
).toEqual([{repoId: makeRepoId("foo", "bar")}]);
});
it("adds to nonempty registry", () => {
const registry = [makeRepoId("foo", "bar")];
expect(addRepoId(makeRepoId("zoo", "zod"), registry)).toEqual([
makeRepoId("foo", "bar"),
makeRepoId("zoo", "zod"),
const registry = [{repoId: makeRepoId("foo", "bar")}];
expect(addRepoId(registry, {repoId: makeRepoId("zoo", "zod")})).toEqual([
{repoId: makeRepoId("foo", "bar")},
{repoId: makeRepoId("zoo", "zod")},
]);
});
it("adding repoId that is already the last has no effect", () => {
const registry = [makeRepoId("zoo", "zod"), makeRepoId("foo", "bar")];
expect(addRepoId(makeRepoId("foo", "bar"), registry)).toEqual(registry);
const registry = [
{repoId: makeRepoId("zoo", "zod")},
{repoId: makeRepoId("foo", "bar")},
];
expect(addRepoId(registry, {repoId: makeRepoId("foo", "bar")})).toEqual(
registry
);
});
it("adding already-existing repoId shifts it to the end", () => {
const registry = [makeRepoId("zoo", "zod"), makeRepoId("foo", "bar")];
expect(addRepoId(makeRepoId("zoo", "zod"), registry)).toEqual([
makeRepoId("foo", "bar"),
makeRepoId("zoo", "zod"),
const registry = [
{repoId: makeRepoId("zoo", "zod")},
{repoId: makeRepoId("foo", "bar")},
];
expect(addRepoId(registry, {repoId: makeRepoId("zoo", "zod")})).toEqual([
{repoId: makeRepoId("foo", "bar")},
{repoId: makeRepoId("zoo", "zod")},
]);
});
});

View File

@ -26,8 +26,8 @@ export default function makePrototypesPage(
<ul>
{registry.map((x) => (
<li key={stringify(x)}>
<Link to={`/prototypes/${x.owner}/${x.name}/`}>
{`${x.owner}/${x.name}`}
<Link to={`/prototypes/${x.repoId.owner}/${x.repoId.name}/`}>
{`${x.repoId.owner}/${x.repoId.name}`}
</Link>
</li>
))}

View File

@ -47,13 +47,13 @@ function makeRouteData(registry /*: RepoIdRegistry */) /*: RouteData */ {
title: "SourceCred prototype",
navTitle: "Prototype",
},
...registry.map((repo) => ({
path: `/prototypes/${repo.owner}/${repo.name}/`,
...registry.map((entry) => ({
path: `/prototypes/${entry.repoId.owner}/${entry.repoId.name}/`,
contents: {
type: "PAGE",
component: () => require("./ProjectPage").default(repo),
component: () => require("./ProjectPage").default(entry.repoId),
},
title: `${repo.owner}/${repo.name} • SourceCred`,
title: `${entry.repoId.owner}/${entry.repoId.name} • SourceCred`,
navTitle: null,
})),
{

View File

@ -6,8 +6,8 @@ import {makeRouteData} from "./routeData";
describe("homepage/routeData", () => {
function routeData() {
return makeRouteData([
stringToRepoId("sourcecred/example-github"),
stringToRepoId("sourcecred/sourcecred"),
{repoId: stringToRepoId("sourcecred/example-github")},
{repoId: stringToRepoId("sourcecred/sourcecred")},
]);
}