Initiatives: replace trackers with InitiativeIds (#1647)

"Trackers" were an idea to let Initiatives be aware of the medium that
declares it. Such as a Discourse topic or file.

With Discourse in mind this was really useful. We could add an automatic
contribution edge, enhance reference detection to "upgrade" a URL pointing
to that Topic to resolve to the Initiative instead, etc.

Using files as the only source of Initiatives this becomes less relevant.
So in the interest of reducing complexity, we'll remove tracker awareness.
This commit is contained in:
Robin van Boven 2020-02-08 08:55:07 +01:00 committed by GitHub
parent 8eb9312277
commit 84e658143e
5 changed files with 37 additions and 84 deletions

View File

@ -11,8 +11,8 @@ import {
} from "../../core/graph";
import type {ReferenceDetector, URL} from "../../core/references";
import type {Initiative, InitiativeRepository} from "./initiative";
import {addressFromId} from "./initiative";
import {
initiativeNodeType,
dependsOnEdgeType,
referencesEdgeType,
contributesToEdgeType,
@ -20,10 +20,7 @@ import {
} from "./declaration";
function initiativeAddress(initiative: Initiative): NodeAddressT {
return NodeAddress.append(
initiativeNodeType.prefix,
...NodeAddress.toParts(initiative.tracker)
);
return addressFromId(initiative.id);
}
function initiativeNode(initiative: Initiative): Node {
@ -72,9 +69,6 @@ export function createGraph(
// Adds the Initiative node.
graph.addNode(initiativeNode(initiative));
// Consider the tracker a contribution.
graph.addEdge(contributionEdge(initiative, initiative.tracker));
// Generic approach to adding edges when the reference detector has a hit.
const edgeHandler = (
urls: $ReadOnlyArray<URL>,

View File

@ -8,7 +8,7 @@ import {
} from "../../core/graph";
import type {ReferenceDetector, URL} from "../../core/references";
import type {Initiative, InitiativeRepository} from "./initiative";
import {topicAddress} from "../discourse/address";
import {createId} from "./initiative";
import {createGraph} from "./createGraph";
import {
initiativeNodeType,
@ -32,10 +32,10 @@ class MockInitiativeRepository implements InitiativeRepository {
this._counter++;
const initiative: Initiative = {
id: createId("TEST_SUBTYPE", String(num)),
title: `Example Initiative ${num}`,
timestampMs: 400 + num,
completed: false,
tracker: topicAddress("https://example.com", num),
dependencies: [],
references: [],
contributions: [],
@ -80,10 +80,11 @@ function exampleNodeAddress(id: number): NodeAddressT {
return NodeAddress.fromParts(["example", String(id)]);
}
function discourseInitiativeAddress(id: number): NodeAddressT {
function testInitiativeAddress(num: number): NodeAddressT {
return NodeAddress.append(
initiativeNodeType.prefix,
...NodeAddress.toParts(topicAddress("https://example.com", id))
"TEST_SUBTYPE",
String(num)
);
}
@ -124,40 +125,12 @@ describe("plugins/initiatives/createGraph", () => {
{
description: "Example Initiative 1",
timestampMs: 401,
address: discourseInitiativeAddress(1),
address: testInitiativeAddress(1),
},
{
description: "Example Initiative 2",
timestampMs: 402,
address: discourseInitiativeAddress(2),
},
]);
});
it("should add the tracker as a contribution edge", () => {
// Given
const {repo, refs} = example();
const i1 = repo.addInitiative();
// When
const graph = createGraph(repo, refs);
// Then
const contributions = Array.from(
graph.edges({
addressPrefix: contributesToEdgeType.prefix,
showDangling: true,
})
);
expect(contributions).toEqual([
{
address: contributionEdgeAddress(
discourseInitiativeAddress(1),
i1.tracker
),
dst: discourseInitiativeAddress(1),
src: i1.tracker,
timestampMs: i1.timestampMs,
address: testInitiativeAddress(2),
},
]);
});
@ -251,10 +224,10 @@ describe("plugins/initiatives/createGraph", () => {
expect(dependencies).toHaveLength(1);
expect(dependencies).toContainEqual({
address: dependencyEdgeAddress(
discourseInitiativeAddress(1),
testInitiativeAddress(1),
exampleNodeAddress(1)
),
src: discourseInitiativeAddress(1),
src: testInitiativeAddress(1),
dst: exampleNodeAddress(1),
timestampMs: 401,
});
@ -282,10 +255,10 @@ describe("plugins/initiatives/createGraph", () => {
expect(references).toHaveLength(1);
expect(references).toContainEqual({
address: referenceEdgeAddress(
discourseInitiativeAddress(1),
testInitiativeAddress(1),
exampleNodeAddress(2)
),
src: discourseInitiativeAddress(1),
src: testInitiativeAddress(1),
dst: exampleNodeAddress(2),
timestampMs: 401,
});
@ -310,14 +283,14 @@ describe("plugins/initiatives/createGraph", () => {
})
);
expect(refs.addressFromUrl).toHaveBeenCalledTimes(2);
expect(contributions).toHaveLength(2);
expect(contributions).toHaveLength(1);
expect(contributions).toContainEqual({
address: contributionEdgeAddress(
discourseInitiativeAddress(1),
testInitiativeAddress(1),
exampleNodeAddress(3)
),
src: exampleNodeAddress(3),
dst: discourseInitiativeAddress(1),
dst: testInitiativeAddress(1),
timestampMs: 401,
});
});
@ -344,11 +317,11 @@ describe("plugins/initiatives/createGraph", () => {
expect(champions).toHaveLength(1);
expect(champions).toContainEqual({
address: championEdgeAddress(
discourseInitiativeAddress(1),
testInitiativeAddress(1),
exampleNodeAddress(4)
),
src: exampleNodeAddress(4),
dst: discourseInitiativeAddress(1),
dst: testInitiativeAddress(1),
timestampMs: 401,
});
});

View File

@ -2,11 +2,13 @@
import type {Topic, Post, CategoryId, TopicId} from "../discourse/fetch";
import type {Initiative, URL, InitiativeRepository} from "./initiative";
import {createId} from "./initiative";
import {
parseCookedHtml,
type HtmlTemplateInitiativePartial,
} from "./htmlTemplate";
import {topicAddress} from "../discourse/address";
export const DISCOURSE_TOPIC_SUBTYPE = "DISCOURSE_TOPIC";
/**
* A subset of queries we need for our plugin.
@ -130,11 +132,10 @@ export function initiativeFromDiscourseTracker(
);
}
const tracker = topicAddress(serverUrl, topic.id);
const partial = parseCookedHtml(openingPost.cooked);
return {
id: createId(DISCOURSE_TOPIC_SUBTYPE, serverUrl, String(topic.id)),
title,
tracker,
timestampMs,
completed: partial.completed,
dependencies: absoluteURLs(serverUrl, partial.dependencies),

View File

@ -6,10 +6,8 @@ import {
DiscourseInitiativeRepository,
type DiscourseQueries,
} from "./discourse";
import {type Initiative} from "./initiative";
import type {ReadRepository} from "../discourse/mirrorRepository";
import type {Topic, Post, CategoryId, TopicId} from "../discourse/fetch";
import {NodeAddress} from "../../core/graph";
import dedent from "../../util/dedent";
function givenParseError(message: string) {
@ -28,13 +26,6 @@ function mockParseCookedHtml(
return jest.fn().mockImplementation(fn);
}
function snapshotInitiative(initiative: Initiative): Object {
return {
...initiative,
tracker: NodeAddress.toParts(initiative.tracker),
};
}
function exampleTopic(overrides?: $Shape<Topic>): Topic {
return {
id: 123,
@ -241,43 +232,39 @@ describe("plugins/initiatives/discourse", () => {
const initiatives = repo.initiatives();
// Then
expect(initiatives.map(snapshotInitiative)).toMatchInlineSnapshot(`
expect(initiatives).toMatchInlineSnapshot(`
Array [
Object {
"champions": Array [],
"completed": false,
"contributions": Array [],
"dependencies": Array [],
"id": Array [
"DISCOURSE_TOPIC",
"https://foo.bar",
"40",
],
"references": Array [
"https://example.org/references/included",
],
"timestampMs": 1571498171951,
"title": "Example initiative",
"tracker": Array [
"sourcecred",
"discourse",
"topic",
"https://foo.bar",
"40",
],
},
Object {
"champions": Array [],
"completed": false,
"contributions": Array [],
"dependencies": Array [],
"id": Array [
"DISCOURSE_TOPIC",
"https://foo.bar",
"42",
],
"references": Array [
"https://example.org/references/included",
],
"timestampMs": 1571498171951,
"title": "Example initiative",
"tracker": Array [
"sourcecred",
"discourse",
"topic",
"https://foo.bar",
"42",
],
},
]
`);
@ -397,7 +384,7 @@ describe("plugins/initiatives/discourse", () => {
expect(initiative.timestampMs).toEqual(firstPost.timestampMs);
});
it("derives the tracker address from topic ID", () => {
it("derives the id from topic ID", () => {
// Given
const serverUrl = "https://foo.bar";
const topic = exampleTopic({
@ -418,10 +405,8 @@ describe("plugins/initiatives/discourse", () => {
);
// Then
expect(NodeAddress.toParts(initiative.tracker)).toEqual([
"sourcecred",
"discourse",
"topic",
expect(initiative.id).toEqual([
"DISCOURSE_TOPIC",
serverUrl,
String(topic.id),
]);

View File

@ -36,10 +36,10 @@ export function addressFromId(id: InitiativeId): NodeAddressT {
* See https://discourse.sourcecred.io/t/write-the-initiatives-plugin/269/6
*/
export type Initiative = {|
+id: InitiativeId,
+title: string,
+timestampMs: number,
+completed: boolean,
+tracker: NodeAddressT,
+dependencies: $ReadOnlyArray<URL>,
+references: $ReadOnlyArray<URL>,
+contributions: $ReadOnlyArray<URL>,