mirror of
https://github.com/status-im/sourcecred.git
synced 2025-01-23 11:00:11 +00:00
Split initiativeFile from initiativesDirectory (#1748)
As we're looking to add more features to InitiativeFile, the single source file would grow large. Anticipating this we're splitting one type and it's related functions off into a new file.
This commit is contained in:
parent
f362a2409a
commit
7caca360a0
@ -20,7 +20,7 @@ import {
|
||||
contributesToEdgeType,
|
||||
championsEdgeType,
|
||||
} from "./declaration";
|
||||
import {initiativeFileURL} from "./initiativesDirectory";
|
||||
import {initiativeFileURL} from "./initiativeFile";
|
||||
|
||||
function initiativeAddress(initiative: Initiative): NodeAddressT {
|
||||
return addressFromId(initiative.id);
|
||||
|
68
src/plugins/initiatives/initiativeFile.js
Normal file
68
src/plugins/initiatives/initiativeFile.js
Normal file
@ -0,0 +1,68 @@
|
||||
// @flow
|
||||
|
||||
import {type URL} from "../../core/references";
|
||||
import {type NodeAddressT, NodeAddress} from "../../core/graph";
|
||||
import {type Compatible, fromCompat, toCompat} from "../../util/compat";
|
||||
import {initiativeNodeType} from "./declaration";
|
||||
import {type InitiativeWeight, type InitiativeId, createId} from "./initiative";
|
||||
import {type InitiativesDirectory} from "./initiativesDirectory";
|
||||
|
||||
export const INITIATIVE_FILE_SUBTYPE = "INITIATIVE_FILE";
|
||||
|
||||
/**
|
||||
* 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,
|
||||
+weight: InitiativeWeight,
|
||||
+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 = 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* When provided with the initiative NodeAddressT of an InitiativeFile this extracts
|
||||
* the URL from it. Or null when the address is not for an InitiativeFile.
|
||||
*/
|
||||
export function initiativeFileURL(address: NodeAddressT): string | null {
|
||||
const initiativeFilePrefix = NodeAddress.append(
|
||||
initiativeNodeType.prefix,
|
||||
INITIATIVE_FILE_SUBTYPE
|
||||
);
|
||||
|
||||
if (!NodeAddress.hasPrefix(address, initiativeFilePrefix)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parts = NodeAddress.toParts(address);
|
||||
const remoteUrl = parts[4];
|
||||
const fileName = parts[5];
|
||||
return `${remoteUrl}/${fileName}`;
|
||||
}
|
||||
|
||||
// Creates the InitiativeId for an InitiativeFile.
|
||||
export function initiativeFileId(
|
||||
{remoteUrl}: InitiativesDirectory,
|
||||
fileName: string
|
||||
): InitiativeId {
|
||||
return createId(INITIATIVE_FILE_SUBTYPE, remoteUrl, fileName);
|
||||
}
|
83
src/plugins/initiatives/initiativeFile.test.js
Normal file
83
src/plugins/initiatives/initiativeFile.test.js
Normal file
@ -0,0 +1,83 @@
|
||||
// @flow
|
||||
|
||||
import {NodeAddress} from "../../core/graph";
|
||||
import {createId, addressFromId} from "./initiative";
|
||||
import {type InitiativesDirectory} from "./initiativesDirectory";
|
||||
import {
|
||||
type InitiativeFile,
|
||||
fromJSON,
|
||||
toJSON,
|
||||
initiativeFileURL,
|
||||
initiativeFileId,
|
||||
} from "./initiativeFile";
|
||||
|
||||
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"],
|
||||
dependencies: ["http://foo.bar/dep"],
|
||||
references: ["http://foo.bar/ref"],
|
||||
});
|
||||
|
||||
describe("plugins/initiatives/initiativeFile", () => {
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe("initiativeFileURL", () => {
|
||||
it("should return null for a different prefix", () => {
|
||||
// Given
|
||||
const address = NodeAddress.fromParts(["foobar"]);
|
||||
|
||||
// When
|
||||
const url = initiativeFileURL(address);
|
||||
|
||||
// Then
|
||||
expect(url).toEqual(null);
|
||||
});
|
||||
|
||||
it("should detect the correct prefix and create a URL", () => {
|
||||
// Given
|
||||
const remoteUrl = "http://foo.bar/dir";
|
||||
const fileName = "sample.json";
|
||||
const address = addressFromId(
|
||||
createId("INITIATIVE_FILE", remoteUrl, fileName)
|
||||
);
|
||||
|
||||
// When
|
||||
const url = initiativeFileURL(address);
|
||||
|
||||
// Then
|
||||
expect(url).toEqual(`${remoteUrl}/${fileName}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("initiativeFileId", () => {
|
||||
it("should add the correct prefix to a remoteUrl and fileName", () => {
|
||||
// Given
|
||||
const dir: InitiativesDirectory = {
|
||||
localPath: "should-not-be-used",
|
||||
remoteUrl: "http://foo.bar/dir",
|
||||
};
|
||||
const fileName = "sample.json";
|
||||
|
||||
// When
|
||||
const id = initiativeFileId(dir, fileName);
|
||||
|
||||
// Then
|
||||
expect(id).toEqual(createId("INITIATIVE_FILE", dir.remoteUrl, fileName));
|
||||
});
|
||||
});
|
||||
});
|
@ -4,24 +4,23 @@ import path from "path";
|
||||
import fs from "fs-extra";
|
||||
import globby from "globby";
|
||||
import {type URL} from "../../core/references";
|
||||
import {type NodeAddressT, NodeAddress} from "../../core/graph";
|
||||
import {type Compatible, fromCompat, toCompat} from "../../util/compat";
|
||||
import {type NodeAddressT} from "../../core/graph";
|
||||
import {compatReader} from "../../backend/compatIO";
|
||||
import {initiativeNodeType} from "./declaration";
|
||||
import {
|
||||
type ReferenceDetector,
|
||||
MappedReferenceDetector,
|
||||
} from "../../core/references";
|
||||
import {
|
||||
type Initiative,
|
||||
type InitiativeWeight,
|
||||
type InitiativeId,
|
||||
type InitiativeRepository,
|
||||
createId,
|
||||
addressFromId,
|
||||
} from "./initiative";
|
||||
|
||||
export const INITIATIVE_FILE_SUBTYPE = "INITIATIVE_FILE";
|
||||
import {
|
||||
type InitiativeFile,
|
||||
fromJSON,
|
||||
initiativeFileURL,
|
||||
initiativeFileId,
|
||||
} from "./initiativeFile";
|
||||
|
||||
/**
|
||||
* Represents an Initiatives directory.
|
||||
@ -74,64 +73,6 @@ export async function loadDirectory(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
+weight: InitiativeWeight,
|
||||
+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);
|
||||
}
|
||||
|
||||
/**
|
||||
* When provided with the initiative NodeAddressT of an InitiativeFile this extracts
|
||||
* the URL from it. Or null when the address is not for an InitiativeFile.
|
||||
*/
|
||||
export function initiativeFileURL(address: NodeAddressT): string | null {
|
||||
const initiativeFilePrefix = NodeAddress.append(
|
||||
initiativeNodeType.prefix,
|
||||
INITIATIVE_FILE_SUBTYPE
|
||||
);
|
||||
|
||||
if (!NodeAddress.hasPrefix(address, initiativeFilePrefix)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const parts = NodeAddress.toParts(address);
|
||||
const remoteUrl = parts[4];
|
||||
const fileName = parts[5];
|
||||
return `${remoteUrl}/${fileName}`;
|
||||
}
|
||||
|
||||
// Creates the InitiativeId for an InitiativeFile.
|
||||
export function _initiativeFileId(
|
||||
{remoteUrl}: InitiativesDirectory,
|
||||
fileName: string
|
||||
): InitiativeId {
|
||||
return createId(INITIATIVE_FILE_SUBTYPE, remoteUrl, fileName);
|
||||
}
|
||||
|
||||
// Checks the path exists and is a directory.
|
||||
// Returns the absolute path or throws.
|
||||
export async function _validatePath(localPath: string): Promise<string> {
|
||||
@ -209,7 +150,7 @@ export function _convertToInitiatives(
|
||||
const {timestampIso, ...partialInitiativeFile} = initiativeFile;
|
||||
const initiative: Initiative = {
|
||||
...partialInitiativeFile,
|
||||
id: _initiativeFileId(directory, fileName),
|
||||
id: initiativeFileId(directory, fileName),
|
||||
timestampMs: Date.parse(timestampIso),
|
||||
};
|
||||
initiatives.push(initiative);
|
||||
|
@ -4,17 +4,11 @@ import tmp from "tmp";
|
||||
import path from "path";
|
||||
import fs from "fs-extra";
|
||||
import stringify from "json-stable-stringify";
|
||||
import {NodeAddress} from "../../core/graph";
|
||||
import {MappedReferenceDetector} from "../../core/references";
|
||||
import {type Initiative, createId, addressFromId} from "./initiative";
|
||||
import {
|
||||
type InitiativeFile,
|
||||
type InitiativesDirectory,
|
||||
fromJSON,
|
||||
toJSON,
|
||||
initiativeFileURL,
|
||||
loadDirectory,
|
||||
_initiativeFileId,
|
||||
_validatePath,
|
||||
_findFiles,
|
||||
_readFiles,
|
||||
@ -22,6 +16,7 @@ import {
|
||||
_convertToInitiatives,
|
||||
_createReferenceMap,
|
||||
} from "./initiativesDirectory";
|
||||
import {type InitiativeFile} from "./initiativeFile";
|
||||
|
||||
const exampleInitiativeFile = (): InitiativeFile => ({
|
||||
title: "Sample initiative",
|
||||
@ -44,64 +39,6 @@ const exampleInitiative = (remoteUrl: string, fileName: string): Initiative => {
|
||||
};
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
describe("initiativeFileURL", () => {
|
||||
it("should return null for a different prefix", () => {
|
||||
// Given
|
||||
const address = NodeAddress.fromParts(["foobar"]);
|
||||
|
||||
// When
|
||||
const url = initiativeFileURL(address);
|
||||
|
||||
// Then
|
||||
expect(url).toEqual(null);
|
||||
});
|
||||
|
||||
it("should detect the correct prefix and create a URL", () => {
|
||||
// Given
|
||||
const remoteUrl = "http://foo.bar/dir";
|
||||
const fileName = "sample.json";
|
||||
const address = addressFromId(
|
||||
createId("INITIATIVE_FILE", remoteUrl, fileName)
|
||||
);
|
||||
|
||||
// When
|
||||
const url = initiativeFileURL(address);
|
||||
|
||||
// Then
|
||||
expect(url).toEqual(`${remoteUrl}/${fileName}`);
|
||||
});
|
||||
});
|
||||
|
||||
describe("_initiativeFileId", () => {
|
||||
it("should add the correct prefix to a remoteUrl and fileName", () => {
|
||||
// Given
|
||||
const dir: InitiativesDirectory = {
|
||||
localPath: "should-not-be-used",
|
||||
remoteUrl: "http://foo.bar/dir",
|
||||
};
|
||||
const fileName = "sample.json";
|
||||
|
||||
// When
|
||||
const id = _initiativeFileId(dir, fileName);
|
||||
|
||||
// Then
|
||||
expect(id).toEqual(createId("INITIATIVE_FILE", dir.remoteUrl, fileName));
|
||||
});
|
||||
});
|
||||
|
||||
describe("_validatePath", () => {
|
||||
it("should resolve relative paths", async () => {
|
||||
// Given
|
||||
|
Loading…
x
Reference in New Issue
Block a user