diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a15fbd..0205f30 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,4 +3,5 @@ ## [Unreleased] - Start tracking changes in `CHANGELOG.md` - Aggregate over connection types in the cred explorer (#502) +- Support hosting SourceCred instances at arbitrary gateways, not just the root of a domain (#643) diff --git a/src/app/adapters/adapterSet.js b/src/app/adapters/adapterSet.js index 1851b69..adeb1cc 100644 --- a/src/app/adapters/adapterSet.js +++ b/src/app/adapters/adapterSet.js @@ -2,6 +2,7 @@ import {Graph, type NodeAddressT, type EdgeAddressT} from "../../core/graph"; import {NodeTrie, EdgeTrie} from "../../core/trie"; +import type {Assets} from "../assets"; import type {Repo} from "../../core/repo"; import type { @@ -68,8 +69,8 @@ export class StaticAdapterSet { return this._typeEdgeTrie.getLast(x); } - load(repo: Repo): Promise { - return Promise.all(this._adapters.map((a) => a.load(repo))).then( + load(assets: Assets, repo: Repo): Promise { + return Promise.all(this._adapters.map((a) => a.load(assets, repo))).then( (adapters) => new DynamicAdapterSet(this, adapters) ); } diff --git a/src/app/adapters/adapterSet.test.js b/src/app/adapters/adapterSet.test.js index 9fc22cf..92e9cdf 100644 --- a/src/app/adapters/adapterSet.test.js +++ b/src/app/adapters/adapterSet.test.js @@ -9,6 +9,7 @@ import { import type {DynamicPluginAdapter} from "./pluginAdapter"; import {StaticAdapterSet} from "./adapterSet"; import {FallbackStaticAdapter, FALLBACK_NAME} from "./fallbackAdapter"; +import {Assets} from "../assets"; import {makeRepo, type Repo} from "../../core/repo"; describe("app/adapters/adapterSet", () => { @@ -57,8 +58,10 @@ describe("app/adapters/adapterSet", () => { ]; } - load(_unused_repo: Repo) { - return this.loadingMock().then(() => new TestDynamicPluginAdapter()); + load(assets: Assets, repo: Repo) { + return this.loadingMock(assets, repo).then( + () => new TestDynamicPluginAdapter() + ); } } @@ -159,7 +162,14 @@ describe("app/adapters/adapterSet", () => { it("loads a dynamicAdapterSet", async () => { const {x, sas} = example(); x.loadingMock.mockResolvedValue(); - const das = await sas.load(makeRepo("foo", "bar")); + expect(x.loadingMock).toHaveBeenCalledTimes(0); + const assets = new Assets("/my/gateway/"); + const repo = makeRepo("foo", "bar"); + const das = await sas.load(assets, repo); + expect(x.loadingMock).toHaveBeenCalledTimes(1); + expect(x.loadingMock.mock.calls[0]).toHaveLength(2); + expect(x.loadingMock.mock.calls[0][0]).toBe(assets); + expect(x.loadingMock.mock.calls[0][1]).toBe(repo); expect(das).toEqual(expect.anything()); }); }); @@ -169,7 +179,10 @@ describe("app/adapters/adapterSet", () => { const x = new TestStaticPluginAdapter(); const sas = new StaticAdapterSet([x]); x.loadingMock.mockResolvedValue(); - const das = await sas.load(makeRepo("foo", "bar")); + const das = await sas.load( + new Assets("/my/gateway/"), + makeRepo("foo", "bar") + ); return {x, sas, das}; } it("allows retrieval of the original StaticAdapterSet", async () => { diff --git a/src/app/adapters/fallbackAdapter.js b/src/app/adapters/fallbackAdapter.js index 39d7958..ac57522 100644 --- a/src/app/adapters/fallbackAdapter.js +++ b/src/app/adapters/fallbackAdapter.js @@ -6,6 +6,7 @@ import { type NodeAddressT, EdgeAddress, } from "../../core/graph"; +import type {Assets} from "../assets"; import type {Repo} from "../../core/repo"; import type {StaticPluginAdapter, DynamicPluginAdapter} from "./pluginAdapter"; @@ -46,7 +47,7 @@ export class FallbackStaticAdapter implements StaticPluginAdapter { ]; } - load(_unused_repo: Repo) { + load(_unused_assets: Assets, _unused_repo: Repo) { return Promise.resolve(new FallbackDynamicAdapter()); } } diff --git a/src/app/adapters/pluginAdapter.js b/src/app/adapters/pluginAdapter.js index a39e22a..dc4b05c 100644 --- a/src/app/adapters/pluginAdapter.js +++ b/src/app/adapters/pluginAdapter.js @@ -1,6 +1,7 @@ // @flow import {Graph, type NodeAddressT, type EdgeAddressT} from "../../core/graph"; +import type {Assets} from "../assets"; import type {Repo} from "../../core/repo"; export type EdgeType = {| @@ -22,7 +23,7 @@ export interface StaticPluginAdapter { edgePrefix(): EdgeAddressT; nodeTypes(): NodeType[]; edgeTypes(): EdgeType[]; - load(repo: Repo): Promise; + load(assets: Assets, repo: Repo): Promise; } export interface DynamicPluginAdapter { diff --git a/src/app/credExplorer/App.js b/src/app/credExplorer/App.js index d111cbc..abd240e 100644 --- a/src/app/credExplorer/App.js +++ b/src/app/credExplorer/App.js @@ -91,7 +91,9 @@ export function createApp( diff --git a/src/app/credExplorer/state.js b/src/app/credExplorer/state.js index 84ceef6..c30d8f6 100644 --- a/src/app/credExplorer/state.js +++ b/src/app/credExplorer/state.js @@ -4,6 +4,7 @@ import deepEqual from "lodash.isequal"; import * as NullUtil from "../../util/null"; import {Graph} from "../../core/graph"; +import type {Assets} from "../../app/assets"; import type {Repo} from "../../core/repo"; import {type EdgeEvaluator} from "../../core/attribution/pagerank"; import { @@ -78,7 +79,7 @@ export function initialState(): AppState { export interface StateTransitionMachineInterface { +setRepo: (Repo) => void; +setEdgeEvaluator: (EdgeEvaluator) => void; - +loadGraph: () => Promise; + +loadGraph: (Assets) => Promise; +runPagerank: () => Promise; } /* In production, instantiate via createStateTransitionMachine; the constructor @@ -88,7 +89,10 @@ export interface StateTransitionMachineInterface { export class StateTransitionMachine implements StateTransitionMachineInterface { getState: () => AppState; setState: (AppState) => void; - loadGraphWithAdapters: (repo: Repo) => Promise; + loadGraphWithAdapters: ( + assets: Assets, + repo: Repo + ) => Promise; pagerank: ( Graph, EdgeEvaluator, @@ -98,7 +102,10 @@ export class StateTransitionMachine implements StateTransitionMachineInterface { constructor( getState: () => AppState, setState: (AppState) => void, - loadGraphWithAdapters: (repo: Repo) => Promise, + loadGraphWithAdapters: ( + assets: Assets, + repo: Repo + ) => Promise, pagerank: ( Graph, EdgeEvaluator, @@ -160,7 +167,7 @@ export class StateTransitionMachine implements StateTransitionMachineInterface { } } - async loadGraph() { + async loadGraph(assets: Assets) { const state = this.getState(); if ( state.type !== "INITIALIZED" || @@ -176,7 +183,7 @@ export class StateTransitionMachine implements StateTransitionMachineInterface { this.setState(loadingState); let newState: ?AppState; try { - const graphWithAdapters = await this.loadGraphWithAdapters(repo); + const graphWithAdapters = await this.loadGraphWithAdapters(assets, repo); newState = { ...state, substate: { @@ -250,8 +257,9 @@ export type GraphWithAdapters = {| +adapters: DynamicAdapterSet, |}; export async function loadGraphWithAdapters( + assets: Assets, repo: Repo ): Promise { - const adapters = await defaultStaticAdapters().load(repo); + const adapters = await defaultStaticAdapters().load(assets, repo); return {graph: adapters.graph(), adapters}; } diff --git a/src/app/credExplorer/state.test.js b/src/app/credExplorer/state.test.js index a6e53f2..8d1eb1b 100644 --- a/src/app/credExplorer/state.test.js +++ b/src/app/credExplorer/state.test.js @@ -8,6 +8,7 @@ import { } from "./state"; import {Graph} from "../../core/graph"; +import {Assets} from "../assets"; import {makeRepo, type Repo} from "../../core/repo"; import {type EdgeEvaluator} from "../../core/attribution/pagerank"; import {StaticAdapterSet, DynamicAdapterSet} from "../adapters/adapterSet"; @@ -23,7 +24,10 @@ describe("app/credExplorer/state", () => { const setState = (appState) => { stateContainer.appState = appState; }; - const loadGraphMock: (repo: Repo) => Promise = jest.fn(); + const loadGraphMock: ( + assets: Assets, + repo: Repo + ) => Promise = jest.fn(); const pagerankMock: ( Graph, EdgeEvaluator, @@ -196,13 +200,25 @@ describe("app/credExplorer/state", () => { ]; for (const b of badStates) { const {stm} = example(b); - await expect(stm.loadGraph()).rejects.toThrow("incorrect state"); + await expect(stm.loadGraph(new Assets("/my/gateway/"))).rejects.toThrow( + "incorrect state" + ); } }); + it("passes along the adapters and repo", () => { + const {stm, loadGraphMock} = example(readyToLoadGraph()); + expect(loadGraphMock).toHaveBeenCalledTimes(0); + const assets = new Assets("/my/gateway/"); + stm.loadGraph(assets); + expect(loadGraphMock).toHaveBeenCalledTimes(1); + expect(loadGraphMock.mock.calls[0]).toHaveLength(2); + expect(loadGraphMock.mock.calls[0][0]).toBe(assets); + expect(loadGraphMock.mock.calls[0][1]).toEqual(makeRepo("foo", "bar")); + }); it("immediately sets loading status", () => { const {getState, stm} = example(readyToLoadGraph()); expect(loading(getState())).toBe("NOT_LOADING"); - stm.loadGraph(); + stm.loadGraph(new Assets("/my/gateway/")); expect(loading(getState())).toBe("LOADING"); expect(getSubstate(getState()).type).toBe("READY_TO_LOAD_GRAPH"); }); @@ -210,7 +226,7 @@ describe("app/credExplorer/state", () => { const {getState, stm, loadGraphMock} = example(readyToLoadGraph()); const gwa = graphWithAdapters(); loadGraphMock.mockResolvedValue(gwa); - await stm.loadGraph(); + await stm.loadGraph(new Assets("/my/gateway/")); const state = getState(); const substate = getSubstate(state); expect(loading(state)).toBe("NOT_LOADING"); @@ -230,7 +246,7 @@ describe("app/credExplorer/state", () => { resolve(graphWithAdapters()); }) ); - await stm.loadGraph(); + await stm.loadGraph(new Assets("/my/gateway/")); const state = getState(); const substate = getSubstate(state); expect(loading(state)).toBe("NOT_LOADING"); @@ -243,7 +259,7 @@ describe("app/credExplorer/state", () => { // $ExpectFlowError console.error = jest.fn(); loadGraphMock.mockRejectedValue(error); - await stm.loadGraph(); + await stm.loadGraph(new Assets("/my/gateway/")); const state = getState(); const substate = getSubstate(state); expect(loading(state)).toBe("FAILED"); diff --git a/src/plugins/git/pluginAdapter.js b/src/plugins/git/pluginAdapter.js index b8f1054..18b898f 100644 --- a/src/plugins/git/pluginAdapter.js +++ b/src/plugins/git/pluginAdapter.js @@ -7,6 +7,7 @@ import {Graph} from "../../core/graph"; import * as N from "./nodes"; import * as E from "./edges"; import {description} from "./render"; +import type {Assets} from "../../app/assets"; import type {Repo} from "../../core/repo"; export class StaticPluginAdapter implements IStaticPluginAdapter { @@ -76,8 +77,10 @@ export class StaticPluginAdapter implements IStaticPluginAdapter { }, ]; } - async load(repo: Repo): Promise { - const url = `/api/v1/data/data/${repo.owner}/${repo.name}/git/graph.json`; + async load(assets: Assets, repo: Repo): Promise { + const url = assets.resolve( + `/api/v1/data/data/${repo.owner}/${repo.name}/git/graph.json` + ); const response = await fetch(url); if (!response.ok) { return Promise.reject(response); diff --git a/src/plugins/github/pluginAdapter.js b/src/plugins/github/pluginAdapter.js index 337d52e..6507dfe 100644 --- a/src/plugins/github/pluginAdapter.js +++ b/src/plugins/github/pluginAdapter.js @@ -9,6 +9,7 @@ import * as N from "./nodes"; import * as E from "./edges"; import {RelationalView} from "./relationalView"; import {description} from "./render"; +import type {Assets} from "../../app/assets"; import type {Repo} from "../../core/repo"; export class StaticPluginAdapter implements IStaticPluginAdapter { @@ -85,8 +86,10 @@ export class StaticPluginAdapter implements IStaticPluginAdapter { }, ]; } - async load(repo: Repo): Promise { - const url = `/api/v1/data/data/${repo.owner}/${repo.name}/github/view.json`; + async load(assets: Assets, repo: Repo): Promise { + const url = assets.resolve( + `/api/v1/data/data/${repo.owner}/${repo.name}/github/view.json` + ); const response = await fetch(url); if (!response.ok) { return Promise.reject(response);