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",
|
"timestampIso": "2020-01-08T22:01:57.711Z",
|
||||||
"title": "Initiative A",
|
"title": "Initiative A",
|
||||||
|
"weight": Object {
|
||||||
|
"complete": 100,
|
||||||
|
"incomplete": 0,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
"initiative-B.json" => Object {
|
"initiative-B.json" => Object {
|
||||||
"champions": Array [
|
"champions": Array [
|
||||||
|
@ -35,6 +39,10 @@ Map {
|
||||||
],
|
],
|
||||||
"timestampIso": "2020-01-08T22:01:57.722Z",
|
"timestampIso": "2020-01-08T22:01:57.722Z",
|
||||||
"title": "Initiative B",
|
"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\\"
|
\\"http://foo.bar/A/ref\\"
|
||||||
],
|
],
|
||||||
\\"timestampMs\\": 1578520917711,
|
\\"timestampMs\\": 1578520917711,
|
||||||
\\"title\\": \\"Initiative A\\"
|
\\"title\\": \\"Initiative A\\",
|
||||||
|
\\"weight\\": {
|
||||||
|
\\"complete\\": 100,
|
||||||
|
\\"incomplete\\": 0
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
\\"champions\\": [
|
\\"champions\\": [
|
||||||
|
@ -96,7 +108,11 @@ exports[`plugins/initiatives/initiativesDirectory loadDirectory should handle an
|
||||||
\\"http://foo.bar/B/ref\\"
|
\\"http://foo.bar/B/ref\\"
|
||||||
],
|
],
|
||||||
\\"timestampMs\\": 1578520917722,
|
\\"timestampMs\\": 1578520917722,
|
||||||
\\"title\\": \\"Initiative B\\"
|
\\"title\\": \\"Initiative B\\",
|
||||||
|
\\"weight\\": {
|
||||||
|
\\"complete\\": 69,
|
||||||
|
\\"incomplete\\": 42
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]"
|
]"
|
||||||
`;
|
`;
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Graph,
|
|
||||||
EdgeAddress,
|
EdgeAddress,
|
||||||
NodeAddress,
|
NodeAddress,
|
||||||
type Edge,
|
type Edge,
|
||||||
|
@ -9,6 +8,9 @@ import {
|
||||||
type EdgeAddressT,
|
type EdgeAddressT,
|
||||||
type NodeAddressT,
|
type NodeAddressT,
|
||||||
} from "../../core/graph";
|
} 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 {ReferenceDetector, URL} from "../../core/references";
|
||||||
import type {Initiative, InitiativeRepository} from "./initiative";
|
import type {Initiative, InitiativeRepository} from "./initiative";
|
||||||
import {addressFromId} 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;
|
type EdgeFactoryT = (initiative: Initiative, other: NodeAddressT) => Edge;
|
||||||
|
|
||||||
function edgeFactory(
|
function edgeFactory(
|
||||||
|
@ -63,15 +72,21 @@ const referenceEdge = edgeFactory(referencesEdgeType.prefix, true);
|
||||||
const contributionEdge = edgeFactory(contributesToEdgeType.prefix, false);
|
const contributionEdge = edgeFactory(contributesToEdgeType.prefix, false);
|
||||||
const championEdge = edgeFactory(championsEdgeType.prefix, false);
|
const championEdge = edgeFactory(championsEdgeType.prefix, false);
|
||||||
|
|
||||||
export function createGraph(
|
export function createWeightedGraph(
|
||||||
repo: InitiativeRepository,
|
repo: InitiativeRepository,
|
||||||
refs: ReferenceDetector
|
refs: ReferenceDetector
|
||||||
): Graph {
|
): WeightedGraphT {
|
||||||
const graph = new Graph();
|
const wg = WeightedGraph.empty();
|
||||||
|
const {graph, weights} = wg;
|
||||||
|
|
||||||
for (const initiative of repo.initiatives()) {
|
for (const initiative of repo.initiatives()) {
|
||||||
// Adds the Initiative node.
|
// 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.
|
// Generic approach to adding edges when the reference detector has a hit.
|
||||||
const edgeHandler = (
|
const edgeHandler = (
|
||||||
|
@ -92,5 +107,5 @@ export function createGraph(
|
||||||
edgeHandler(initiative.champions, championEdge);
|
edgeHandler(initiative.champions, championEdge);
|
||||||
}
|
}
|
||||||
|
|
||||||
return graph;
|
return wg;
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,10 +6,11 @@ import {
|
||||||
type EdgeAddressT,
|
type EdgeAddressT,
|
||||||
type NodeAddressT,
|
type NodeAddressT,
|
||||||
} from "../../core/graph";
|
} from "../../core/graph";
|
||||||
|
import * as Weights from "../../core/weights";
|
||||||
import type {ReferenceDetector, URL} from "../../core/references";
|
import type {ReferenceDetector, URL} from "../../core/references";
|
||||||
import type {Initiative, InitiativeRepository} from "./initiative";
|
import type {Initiative, InitiativeRepository} from "./initiative";
|
||||||
import {createId, addressFromId} from "./initiative";
|
import {createId, addressFromId} from "./initiative";
|
||||||
import {createGraph} from "./createGraph";
|
import {createWeightedGraph, initiativeWeight} from "./createGraph";
|
||||||
import {
|
import {
|
||||||
initiativeNodeType,
|
initiativeNodeType,
|
||||||
dependsOnEdgeType,
|
dependsOnEdgeType,
|
||||||
|
@ -18,6 +19,20 @@ import {
|
||||||
championsEdgeType,
|
championsEdgeType,
|
||||||
} from "./declaration";
|
} 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 {
|
class MockInitiativeRepository implements InitiativeRepository {
|
||||||
_counter: number;
|
_counter: number;
|
||||||
_initiatives: Initiative[];
|
_initiatives: Initiative[];
|
||||||
|
@ -31,17 +46,12 @@ class MockInitiativeRepository implements InitiativeRepository {
|
||||||
const num = this._counter;
|
const num = this._counter;
|
||||||
this._counter++;
|
this._counter++;
|
||||||
|
|
||||||
const initiative: Initiative = {
|
const initiative = _createInitiative({
|
||||||
id: createId("TEST_SUBTYPE", String(num)),
|
id: createId("TEST_SUBTYPE", String(num)),
|
||||||
title: `Example Initiative ${num}`,
|
title: `Example Initiative ${num}`,
|
||||||
timestampMs: 400 + num,
|
timestampMs: 400 + num,
|
||||||
completed: false,
|
|
||||||
dependencies: [],
|
|
||||||
references: [],
|
|
||||||
contributions: [],
|
|
||||||
champions: [],
|
|
||||||
...shape,
|
...shape,
|
||||||
};
|
});
|
||||||
|
|
||||||
this._initiatives.push(initiative);
|
this._initiatives.push(initiative);
|
||||||
return initiative;
|
return initiative;
|
||||||
|
@ -107,7 +117,55 @@ const contributionEdgeAddress = edgeAddress(contributesToEdgeType.prefix);
|
||||||
const championEdgeAddress = edgeAddress(championsEdgeType.prefix);
|
const championEdgeAddress = edgeAddress(championsEdgeType.prefix);
|
||||||
|
|
||||||
describe("plugins/initiatives/createGraph", () => {
|
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", () => {
|
it("should add initiative nodes to the graph", () => {
|
||||||
// Given
|
// Given
|
||||||
const {repo, refs} = example();
|
const {repo, refs} = example();
|
||||||
|
@ -115,7 +173,7 @@ describe("plugins/initiatives/createGraph", () => {
|
||||||
repo.addInitiative();
|
repo.addInitiative();
|
||||||
|
|
||||||
// When
|
// When
|
||||||
const graph = createGraph(repo, refs);
|
const {graph, weights} = createWeightedGraph(repo, refs);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
const nodes = Array.from(
|
const nodes = Array.from(
|
||||||
|
@ -133,6 +191,25 @@ describe("plugins/initiatives/createGraph", () => {
|
||||||
address: testInitiativeAddress(2),
|
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", () => {
|
it("should add initiative file urls to the description", () => {
|
||||||
|
@ -145,7 +222,7 @@ describe("plugins/initiatives/createGraph", () => {
|
||||||
repo.addInitiative({id});
|
repo.addInitiative({id});
|
||||||
|
|
||||||
// When
|
// When
|
||||||
const graph = createGraph(repo, refs);
|
const {graph} = createWeightedGraph(repo, refs);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
const node = graph.node(addres);
|
const node = graph.node(addres);
|
||||||
|
@ -163,7 +240,7 @@ describe("plugins/initiatives/createGraph", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// When
|
// When
|
||||||
createGraph(repo, refs);
|
createWeightedGraph(repo, refs);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
expect(refs.addressFromUrl).toHaveBeenCalledWith(
|
expect(refs.addressFromUrl).toHaveBeenCalledWith(
|
||||||
|
@ -179,7 +256,7 @@ describe("plugins/initiatives/createGraph", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// When
|
// When
|
||||||
createGraph(repo, refs);
|
createWeightedGraph(repo, refs);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
expect(refs.addressFromUrl).toHaveBeenCalledWith(
|
expect(refs.addressFromUrl).toHaveBeenCalledWith(
|
||||||
|
@ -195,7 +272,7 @@ describe("plugins/initiatives/createGraph", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// When
|
// When
|
||||||
createGraph(repo, refs);
|
createWeightedGraph(repo, refs);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
expect(refs.addressFromUrl).toHaveBeenCalledWith(
|
expect(refs.addressFromUrl).toHaveBeenCalledWith(
|
||||||
|
@ -211,7 +288,7 @@ describe("plugins/initiatives/createGraph", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// When
|
// When
|
||||||
createGraph(repo, refs);
|
createWeightedGraph(repo, refs);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
expect(refs.addressFromUrl).toHaveBeenCalledWith(
|
expect(refs.addressFromUrl).toHaveBeenCalledWith(
|
||||||
|
@ -230,7 +307,7 @@ describe("plugins/initiatives/createGraph", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// When
|
// When
|
||||||
const graph = createGraph(repo, refs);
|
const {graph} = createWeightedGraph(repo, refs);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
const dependencies = Array.from(
|
const dependencies = Array.from(
|
||||||
|
@ -261,7 +338,7 @@ describe("plugins/initiatives/createGraph", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// When
|
// When
|
||||||
const graph = createGraph(repo, refs);
|
const {graph} = createWeightedGraph(repo, refs);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
const references = Array.from(
|
const references = Array.from(
|
||||||
|
@ -292,7 +369,7 @@ describe("plugins/initiatives/createGraph", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// When
|
// When
|
||||||
const graph = createGraph(repo, refs);
|
const {graph} = createWeightedGraph(repo, refs);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
const contributions = Array.from(
|
const contributions = Array.from(
|
||||||
|
@ -323,7 +400,7 @@ describe("plugins/initiatives/createGraph", () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
// When
|
// When
|
||||||
const graph = createGraph(repo, refs);
|
const {graph} = createWeightedGraph(repo, refs);
|
||||||
|
|
||||||
// Then
|
// Then
|
||||||
const champions = Array.from(
|
const champions = Array.from(
|
||||||
|
|
|
@ -6,6 +6,10 @@
|
||||||
{
|
{
|
||||||
"title": "Initiative A",
|
"title": "Initiative A",
|
||||||
"timestampIso": "2020-01-08T22:01:57.711Z",
|
"timestampIso": "2020-01-08T22:01:57.711Z",
|
||||||
|
"weight": {
|
||||||
|
"incomplete": 0,
|
||||||
|
"complete": 100
|
||||||
|
},
|
||||||
"completed": true,
|
"completed": true,
|
||||||
"champions": ["http://foo.bar/A/champ"],
|
"champions": ["http://foo.bar/A/champ"],
|
||||||
"contributions": ["http://foo.bar/A/contrib"],
|
"contributions": ["http://foo.bar/A/contrib"],
|
||||||
|
|
|
@ -6,6 +6,10 @@
|
||||||
{
|
{
|
||||||
"title": "Initiative B",
|
"title": "Initiative B",
|
||||||
"timestampIso": "2020-01-08T22:01:57.722Z",
|
"timestampIso": "2020-01-08T22:01:57.722Z",
|
||||||
|
"weight": {
|
||||||
|
"incomplete": 42,
|
||||||
|
"complete": 69
|
||||||
|
},
|
||||||
"completed": false,
|
"completed": false,
|
||||||
"champions": ["http://foo.bar/B/champ"],
|
"champions": ["http://foo.bar/B/champ"],
|
||||||
"contributions": ["http://foo.bar/B/contrib"],
|
"contributions": ["http://foo.bar/B/contrib"],
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import {type NodeAddressT, NodeAddress} from "../../core/graph";
|
import {type NodeAddressT, NodeAddress} from "../../core/graph";
|
||||||
|
import {type NodeWeight} from "../../core/weights";
|
||||||
import {initiativeNodeType} from "./declaration";
|
import {initiativeNodeType} from "./declaration";
|
||||||
|
|
||||||
export type URL = string;
|
export type URL = string;
|
||||||
|
@ -21,6 +22,12 @@ export function addressFromId(id: InitiativeId): NodeAddressT {
|
||||||
return NodeAddress.append(initiativeNodeType.prefix, ...id);
|
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.
|
* An intermediate representation of an Initiative.
|
||||||
*
|
*
|
||||||
|
@ -39,6 +46,7 @@ export type Initiative = {|
|
||||||
+id: InitiativeId,
|
+id: InitiativeId,
|
||||||
+title: string,
|
+title: string,
|
||||||
+timestampMs: number,
|
+timestampMs: number,
|
||||||
|
+weight?: InitiativeWeight,
|
||||||
+completed: boolean,
|
+completed: boolean,
|
||||||
+dependencies: $ReadOnlyArray<URL>,
|
+dependencies: $ReadOnlyArray<URL>,
|
||||||
+references: $ReadOnlyArray<URL>,
|
+references: $ReadOnlyArray<URL>,
|
||||||
|
|
|
@ -13,6 +13,7 @@ import {
|
||||||
} from "../../core/references";
|
} from "../../core/references";
|
||||||
import {
|
import {
|
||||||
type Initiative,
|
type Initiative,
|
||||||
|
type InitiativeWeight,
|
||||||
type InitiativeId,
|
type InitiativeId,
|
||||||
type InitiativeRepository,
|
type InitiativeRepository,
|
||||||
type URL,
|
type URL,
|
||||||
|
@ -82,6 +83,7 @@ export async function loadDirectory(
|
||||||
export type InitiativeFile = {|
|
export type InitiativeFile = {|
|
||||||
+title: string,
|
+title: string,
|
||||||
+timestampIso: ISOTimestamp,
|
+timestampIso: ISOTimestamp,
|
||||||
|
+weight: InitiativeWeight,
|
||||||
+completed: boolean,
|
+completed: boolean,
|
||||||
+dependencies: $ReadOnlyArray<URL>,
|
+dependencies: $ReadOnlyArray<URL>,
|
||||||
+references: $ReadOnlyArray<URL>,
|
+references: $ReadOnlyArray<URL>,
|
||||||
|
|
|
@ -26,6 +26,7 @@ import {
|
||||||
const exampleInitiativeFile = (): InitiativeFile => ({
|
const exampleInitiativeFile = (): InitiativeFile => ({
|
||||||
title: "Sample initiative",
|
title: "Sample initiative",
|
||||||
timestampIso: ("2020-01-08T22:01:57.766Z": any),
|
timestampIso: ("2020-01-08T22:01:57.766Z": any),
|
||||||
|
weight: {incomplete: 360, complete: 420},
|
||||||
completed: false,
|
completed: false,
|
||||||
champions: ["http://foo.bar/champ"],
|
champions: ["http://foo.bar/champ"],
|
||||||
contributions: ["http://foo.bar/contrib"],
|
contributions: ["http://foo.bar/contrib"],
|
||||||
|
|
Loading…
Reference in New Issue