Initiatives: define weights to allow Cred minting (#1674)
Using a required type of before and after completion weight is a simple way to start minting Cred on Initiatives. It sets expectations by having both states defined in a version controlled file.
This commit is contained in:
parent
660f607011
commit
fee071c031
|
@ -18,6 +18,10 @@ Map {
|
|||
],
|
||||
"timestampIso": "2020-01-08T22:01:57.711Z",
|
||||
"title": "Initiative A",
|
||||
"weight": Object {
|
||||
"complete": 100,
|
||||
"incomplete": 0,
|
||||
},
|
||||
},
|
||||
"initiative-B.json" => Object {
|
||||
"champions": Array [
|
||||
|
@ -35,6 +39,10 @@ Map {
|
|||
],
|
||||
"timestampIso": "2020-01-08T22:01:57.722Z",
|
||||
"title": "Initiative B",
|
||||
"weight": Object {
|
||||
"complete": 69,
|
||||
"incomplete": 42,
|
||||
},
|
||||
},
|
||||
}
|
||||
`;
|
||||
|
@ -74,7 +82,11 @@ exports[`plugins/initiatives/initiativesDirectory loadDirectory should handle an
|
|||
\\"http://foo.bar/A/ref\\"
|
||||
],
|
||||
\\"timestampMs\\": 1578520917711,
|
||||
\\"title\\": \\"Initiative A\\"
|
||||
\\"title\\": \\"Initiative A\\",
|
||||
\\"weight\\": {
|
||||
\\"complete\\": 100,
|
||||
\\"incomplete\\": 0
|
||||
}
|
||||
},
|
||||
{
|
||||
\\"champions\\": [
|
||||
|
@ -96,7 +108,11 @@ exports[`plugins/initiatives/initiativesDirectory loadDirectory should handle an
|
|||
\\"http://foo.bar/B/ref\\"
|
||||
],
|
||||
\\"timestampMs\\": 1578520917722,
|
||||
\\"title\\": \\"Initiative B\\"
|
||||
\\"title\\": \\"Initiative B\\",
|
||||
\\"weight\\": {
|
||||
\\"complete\\": 69,
|
||||
\\"incomplete\\": 42
|
||||
}
|
||||
}
|
||||
]"
|
||||
`;
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// @flow
|
||||
|
||||
import {
|
||||
Graph,
|
||||
EdgeAddress,
|
||||
NodeAddress,
|
||||
type Edge,
|
||||
|
@ -9,6 +8,9 @@ import {
|
|||
type EdgeAddressT,
|
||||
type NodeAddressT,
|
||||
} from "../../core/graph";
|
||||
import {type WeightedGraph as WeightedGraphT} from "../../core/weightedGraph";
|
||||
import * as WeightedGraph from "../../core/weightedGraph";
|
||||
import type {NodeWeight} from "../../core/weights";
|
||||
import type {ReferenceDetector, URL} from "../../core/references";
|
||||
import type {Initiative, InitiativeRepository} from "./initiative";
|
||||
import {addressFromId} from "./initiative";
|
||||
|
@ -35,6 +37,13 @@ function initiativeNode(initiative: Initiative): Node {
|
|||
};
|
||||
}
|
||||
|
||||
export function initiativeWeight(initiative: Initiative): ?NodeWeight {
|
||||
if (!initiative.weight) return;
|
||||
return initiative.completed
|
||||
? initiative.weight.complete
|
||||
: initiative.weight.incomplete;
|
||||
}
|
||||
|
||||
type EdgeFactoryT = (initiative: Initiative, other: NodeAddressT) => Edge;
|
||||
|
||||
function edgeFactory(
|
||||
|
@ -63,15 +72,21 @@ const referenceEdge = edgeFactory(referencesEdgeType.prefix, true);
|
|||
const contributionEdge = edgeFactory(contributesToEdgeType.prefix, false);
|
||||
const championEdge = edgeFactory(championsEdgeType.prefix, false);
|
||||
|
||||
export function createGraph(
|
||||
export function createWeightedGraph(
|
||||
repo: InitiativeRepository,
|
||||
refs: ReferenceDetector
|
||||
): Graph {
|
||||
const graph = new Graph();
|
||||
): WeightedGraphT {
|
||||
const wg = WeightedGraph.empty();
|
||||
const {graph, weights} = wg;
|
||||
|
||||
for (const initiative of repo.initiatives()) {
|
||||
// Adds the Initiative node.
|
||||
graph.addNode(initiativeNode(initiative));
|
||||
const node = initiativeNode(initiative);
|
||||
const weight = initiativeWeight(initiative);
|
||||
graph.addNode(node);
|
||||
if (weight) {
|
||||
weights.nodeWeights.set(node.address, weight);
|
||||
}
|
||||
|
||||
// Generic approach to adding edges when the reference detector has a hit.
|
||||
const edgeHandler = (
|
||||
|
@ -92,5 +107,5 @@ export function createGraph(
|
|||
edgeHandler(initiative.champions, championEdge);
|
||||
}
|
||||
|
||||
return graph;
|
||||
return wg;
|
||||
}
|
||||
|
|
|
@ -6,10 +6,11 @@ import {
|
|||
type EdgeAddressT,
|
||||
type NodeAddressT,
|
||||
} from "../../core/graph";
|
||||
import * as Weights from "../../core/weights";
|
||||
import type {ReferenceDetector, URL} from "../../core/references";
|
||||
import type {Initiative, InitiativeRepository} from "./initiative";
|
||||
import {createId, addressFromId} from "./initiative";
|
||||
import {createGraph} from "./createGraph";
|
||||
import {createWeightedGraph, initiativeWeight} from "./createGraph";
|
||||
import {
|
||||
initiativeNodeType,
|
||||
dependsOnEdgeType,
|
||||
|
@ -18,6 +19,20 @@ import {
|
|||
championsEdgeType,
|
||||
} from "./declaration";
|
||||
|
||||
function _createInitiative(overrides?: $Shape<Initiative>): Initiative {
|
||||
return {
|
||||
id: createId("UNSET_SUBTYPE", "42"),
|
||||
title: "Unset test initiative",
|
||||
timestampMs: 123,
|
||||
completed: false,
|
||||
dependencies: [],
|
||||
references: [],
|
||||
contributions: [],
|
||||
champions: [],
|
||||
...overrides,
|
||||
};
|
||||
}
|
||||
|
||||
class MockInitiativeRepository implements InitiativeRepository {
|
||||
_counter: number;
|
||||
_initiatives: Initiative[];
|
||||
|
@ -31,17 +46,12 @@ class MockInitiativeRepository implements InitiativeRepository {
|
|||
const num = this._counter;
|
||||
this._counter++;
|
||||
|
||||
const initiative: Initiative = {
|
||||
const initiative = _createInitiative({
|
||||
id: createId("TEST_SUBTYPE", String(num)),
|
||||
title: `Example Initiative ${num}`,
|
||||
timestampMs: 400 + num,
|
||||
completed: false,
|
||||
dependencies: [],
|
||||
references: [],
|
||||
contributions: [],
|
||||
champions: [],
|
||||
...shape,
|
||||
};
|
||||
});
|
||||
|
||||
this._initiatives.push(initiative);
|
||||
return initiative;
|
||||
|
@ -107,7 +117,55 @@ const contributionEdgeAddress = edgeAddress(contributesToEdgeType.prefix);
|
|||
const championEdgeAddress = edgeAddress(championsEdgeType.prefix);
|
||||
|
||||
describe("plugins/initiatives/createGraph", () => {
|
||||
describe("createGraph", () => {
|
||||
describe("initiativeWeight", () => {
|
||||
it("should be falsy when the initiative has no weight set", () => {
|
||||
// Given
|
||||
const initiative = _createInitiative({
|
||||
id: createId("TEST_INITIATIVE_WEIGHTS", "41"),
|
||||
title: "No weight set",
|
||||
});
|
||||
|
||||
// When
|
||||
const maybeWeight = initiativeWeight(initiative);
|
||||
|
||||
// Then
|
||||
expect(maybeWeight).toBeFalsy();
|
||||
});
|
||||
|
||||
it("should use the first weight when not completed", () => {
|
||||
// Given
|
||||
const initiative = _createInitiative({
|
||||
id: createId("TEST_INITIATIVE_WEIGHTS", "41"),
|
||||
title: "Weights set, not completed",
|
||||
completed: false,
|
||||
weight: {incomplete: 222, complete: 333},
|
||||
});
|
||||
|
||||
// When
|
||||
const maybeWeight = initiativeWeight(initiative);
|
||||
|
||||
// Then
|
||||
expect(maybeWeight).toEqual(222);
|
||||
});
|
||||
|
||||
it("should use the second weight when completed", () => {
|
||||
// Given
|
||||
const initiative = _createInitiative({
|
||||
id: createId("TEST_INITIATIVE_WEIGHTS", "41"),
|
||||
title: "Weights set, completed",
|
||||
completed: true,
|
||||
weight: {incomplete: 222, complete: 333},
|
||||
});
|
||||
|
||||
// When
|
||||
const maybeWeight = initiativeWeight(initiative);
|
||||
|
||||
// Then
|
||||
expect(maybeWeight).toEqual(333);
|
||||
});
|
||||
});
|
||||
|
||||
describe("createWeightedGraph", () => {
|
||||
it("should add initiative nodes to the graph", () => {
|
||||
// Given
|
||||
const {repo, refs} = example();
|
||||
|
@ -115,7 +173,7 @@ describe("plugins/initiatives/createGraph", () => {
|
|||
repo.addInitiative();
|
||||
|
||||
// When
|
||||
const graph = createGraph(repo, refs);
|
||||
const {graph, weights} = createWeightedGraph(repo, refs);
|
||||
|
||||
// Then
|
||||
const nodes = Array.from(
|
||||
|
@ -133,6 +191,25 @@ describe("plugins/initiatives/createGraph", () => {
|
|||
address: testInitiativeAddress(2),
|
||||
},
|
||||
]);
|
||||
expect(weights).toEqual(Weights.empty());
|
||||
});
|
||||
|
||||
it("should add node weights for initiatives with weights", () => {
|
||||
// Given
|
||||
const {repo, refs} = example();
|
||||
repo.addInitiative({weight: {incomplete: 360, complete: 420}});
|
||||
repo.addInitiative({weight: {incomplete: 42, complete: 69}});
|
||||
repo.addInitiative({
|
||||
weight: {incomplete: 42, complete: 69},
|
||||
completed: true,
|
||||
});
|
||||
|
||||
// When
|
||||
const {weights} = createWeightedGraph(repo, refs);
|
||||
|
||||
// Then
|
||||
expect(weights.edgeWeights.size).toEqual(0);
|
||||
expect([...weights.nodeWeights.values()]).toEqual([360, 42, 69]);
|
||||
});
|
||||
|
||||
it("should add initiative file urls to the description", () => {
|
||||
|
@ -145,7 +222,7 @@ describe("plugins/initiatives/createGraph", () => {
|
|||
repo.addInitiative({id});
|
||||
|
||||
// When
|
||||
const graph = createGraph(repo, refs);
|
||||
const {graph} = createWeightedGraph(repo, refs);
|
||||
|
||||
// Then
|
||||
const node = graph.node(addres);
|
||||
|
@ -163,7 +240,7 @@ describe("plugins/initiatives/createGraph", () => {
|
|||
});
|
||||
|
||||
// When
|
||||
createGraph(repo, refs);
|
||||
createWeightedGraph(repo, refs);
|
||||
|
||||
// Then
|
||||
expect(refs.addressFromUrl).toHaveBeenCalledWith(
|
||||
|
@ -179,7 +256,7 @@ describe("plugins/initiatives/createGraph", () => {
|
|||
});
|
||||
|
||||
// When
|
||||
createGraph(repo, refs);
|
||||
createWeightedGraph(repo, refs);
|
||||
|
||||
// Then
|
||||
expect(refs.addressFromUrl).toHaveBeenCalledWith(
|
||||
|
@ -195,7 +272,7 @@ describe("plugins/initiatives/createGraph", () => {
|
|||
});
|
||||
|
||||
// When
|
||||
createGraph(repo, refs);
|
||||
createWeightedGraph(repo, refs);
|
||||
|
||||
// Then
|
||||
expect(refs.addressFromUrl).toHaveBeenCalledWith(
|
||||
|
@ -211,7 +288,7 @@ describe("plugins/initiatives/createGraph", () => {
|
|||
});
|
||||
|
||||
// When
|
||||
createGraph(repo, refs);
|
||||
createWeightedGraph(repo, refs);
|
||||
|
||||
// Then
|
||||
expect(refs.addressFromUrl).toHaveBeenCalledWith(
|
||||
|
@ -230,7 +307,7 @@ describe("plugins/initiatives/createGraph", () => {
|
|||
});
|
||||
|
||||
// When
|
||||
const graph = createGraph(repo, refs);
|
||||
const {graph} = createWeightedGraph(repo, refs);
|
||||
|
||||
// Then
|
||||
const dependencies = Array.from(
|
||||
|
@ -261,7 +338,7 @@ describe("plugins/initiatives/createGraph", () => {
|
|||
});
|
||||
|
||||
// When
|
||||
const graph = createGraph(repo, refs);
|
||||
const {graph} = createWeightedGraph(repo, refs);
|
||||
|
||||
// Then
|
||||
const references = Array.from(
|
||||
|
@ -292,7 +369,7 @@ describe("plugins/initiatives/createGraph", () => {
|
|||
});
|
||||
|
||||
// When
|
||||
const graph = createGraph(repo, refs);
|
||||
const {graph} = createWeightedGraph(repo, refs);
|
||||
|
||||
// Then
|
||||
const contributions = Array.from(
|
||||
|
@ -323,7 +400,7 @@ describe("plugins/initiatives/createGraph", () => {
|
|||
});
|
||||
|
||||
// When
|
||||
const graph = createGraph(repo, refs);
|
||||
const {graph} = createWeightedGraph(repo, refs);
|
||||
|
||||
// Then
|
||||
const champions = Array.from(
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
{
|
||||
"title": "Initiative A",
|
||||
"timestampIso": "2020-01-08T22:01:57.711Z",
|
||||
"weight": {
|
||||
"incomplete": 0,
|
||||
"complete": 100
|
||||
},
|
||||
"completed": true,
|
||||
"champions": ["http://foo.bar/A/champ"],
|
||||
"contributions": ["http://foo.bar/A/contrib"],
|
||||
|
|
|
@ -6,6 +6,10 @@
|
|||
{
|
||||
"title": "Initiative B",
|
||||
"timestampIso": "2020-01-08T22:01:57.722Z",
|
||||
"weight": {
|
||||
"incomplete": 42,
|
||||
"complete": 69
|
||||
},
|
||||
"completed": false,
|
||||
"champions": ["http://foo.bar/B/champ"],
|
||||
"contributions": ["http://foo.bar/B/contrib"],
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// @flow
|
||||
|
||||
import {type NodeAddressT, NodeAddress} from "../../core/graph";
|
||||
import {type NodeWeight} from "../../core/weights";
|
||||
import {initiativeNodeType} from "./declaration";
|
||||
|
||||
export type URL = string;
|
||||
|
@ -21,6 +22,12 @@ export function addressFromId(id: InitiativeId): NodeAddressT {
|
|||
return NodeAddress.append(initiativeNodeType.prefix, ...id);
|
||||
}
|
||||
|
||||
// A before completion and after completion weight for Initiatives.
|
||||
export type InitiativeWeight = {|
|
||||
+incomplete: NodeWeight,
|
||||
+complete: NodeWeight,
|
||||
|};
|
||||
|
||||
/**
|
||||
* An intermediate representation of an Initiative.
|
||||
*
|
||||
|
@ -39,6 +46,7 @@ export type Initiative = {|
|
|||
+id: InitiativeId,
|
||||
+title: string,
|
||||
+timestampMs: number,
|
||||
+weight?: InitiativeWeight,
|
||||
+completed: boolean,
|
||||
+dependencies: $ReadOnlyArray<URL>,
|
||||
+references: $ReadOnlyArray<URL>,
|
||||
|
|
|
@ -13,6 +13,7 @@ import {
|
|||
} from "../../core/references";
|
||||
import {
|
||||
type Initiative,
|
||||
type InitiativeWeight,
|
||||
type InitiativeId,
|
||||
type InitiativeRepository,
|
||||
type URL,
|
||||
|
@ -82,6 +83,7 @@ export async function loadDirectory(
|
|||
export type InitiativeFile = {|
|
||||
+title: string,
|
||||
+timestampIso: ISOTimestamp,
|
||||
+weight: InitiativeWeight,
|
||||
+completed: boolean,
|
||||
+dependencies: $ReadOnlyArray<URL>,
|
||||
+references: $ReadOnlyArray<URL>,
|
||||
|
|
|
@ -26,6 +26,7 @@ import {
|
|||
const exampleInitiativeFile = (): InitiativeFile => ({
|
||||
title: "Sample initiative",
|
||||
timestampIso: ("2020-01-08T22:01:57.766Z": any),
|
||||
weight: {incomplete: 360, complete: 420},
|
||||
completed: false,
|
||||
champions: ["http://foo.bar/champ"],
|
||||
contributions: ["http://foo.bar/contrib"],
|
||||
|
|
Loading…
Reference in New Issue