Add InitiativesDirectory and InitiativeFile types (#1641)

Based on [forum discussion][1], Initiatives should be tracked in files.

The main issue with storing the existing Initiative type as JSON in a
file, is there's no natural NodeAddress or URL for a file-based tracker.
This type resolves that by using the file name within a directory as a
unique reference and requiring a remoteUrl for referencing. (See #1640)

[1]: https://discourse.sourcecred.io/t/576
This commit is contained in:
Robin van Boven 2020-02-08 09:00:30 +01:00 committed by GitHub
parent 84e658143e
commit f924521fdd
2 changed files with 91 additions and 0 deletions

View File

@ -0,0 +1,63 @@
// @flow
import {type ReferenceDetector} from "../../core/references";
import {type Compatible, fromCompat, toCompat} from "../../util/compat";
import {type InitiativeRepository, type URL} from "./initiative";
/**
* Represents an Initiatives directory.
*
* Initiative directories contain a set of InitiativeFiles in a `*.json` pattern.
* Where the file name is the ID of that Initiative.
* Additionally we require a `remoteUrl` for this directory. We expect this directory
* to be something you can browse online. This allows us to create a ReferenceDetector.
*/
export type InitiativesDirectory = {|
+localPath: string,
+remoteUrl: string,
|};
/**
* Opaque because we only want this file's functions to create these load results.
* However we do allow anyone to consume it's properties.
*/
export opaque type LoadedInitiativesDirectory: {|
+referenceDetector: ReferenceDetector,
+initiatives: InitiativeRepository,
|} = {|
+referenceDetector: ReferenceDetector,
+initiatives: InitiativeRepository,
|};
// Adding below signature to help clarity of this commit.
// TODO: @beanow will implement this in a follow-up.
type _unused_loadDirectoryFunction = (InitiativesDirectory) => Promise<LoadedInitiativesDirectory>;
/**
* Represents a single Initiative using a file as source.
*
* Note: The file name will be used to derive the InitiativeId. So it doesn't
* make sense to use this outside of the context of an InitiativesDirectory.
*/
export type InitiativeFile = {|
+title: string,
+timestampIso: ISOTimestamp,
+completed: boolean,
+dependencies: $ReadOnlyArray<URL>,
+references: $ReadOnlyArray<URL>,
+contributions: $ReadOnlyArray<URL>,
+champions: $ReadOnlyArray<URL>,
|};
// Note: setting this to opaque forces us to convert it to timestampMs.
opaque type ISOTimestamp = string;
const COMPAT_INFO = {type: "sourcecred/initiativeFile", version: "0.1.0"};
export function fromJSON(j: Compatible<any>): InitiativeFile {
return fromCompat(COMPAT_INFO, j);
}
export function toJSON(m: InitiativeFile): Compatible<InitiativeFile> {
return toCompat(COMPAT_INFO, m);
}

View File

@ -0,0 +1,28 @@
// @flow
import {type InitiativeFile, fromJSON, toJSON} from "./initiativesDirectory";
const exampleInitiativeFile = (): InitiativeFile => ({
title: "Sample initiative",
timestampIso: ("2020-01-08T22:01:57.766Z": any),
completed: false,
champions: ["http://foo.bar/champ"],
contributions: ["http://foo.bar/contrib"],
dependencies: ["http://foo.bar/dep"],
references: ["http://foo.bar/ref"],
});
describe("plugins/initiatives/initiativesDirectory", () => {
describe("toJSON/fromJSON", () => {
it("should handle an example round-trip", () => {
// Given
const initiativeFile = exampleInitiativeFile();
// When
const actual = fromJSON(toJSON(initiativeFile));
// Then
expect(actual).toEqual(initiativeFile);
});
});
});