mirror of
https://github.com/status-im/sourcecred.git
synced 2025-02-10 03:34:15 +00:00
Load plugin data even when hosted at non-root (#679)
Summary: This commit approximately completes the implementation of #643.\* Plugin adapters are now provided an `Assets` object at `load` time, which they can use to resolve their plugin-specific API routes. \* “Approximately” because there are some non-essential pieces of legacy code that should be cleaned up. Test Plan: Unit tests modified, but it would be good to also manually test this. Run `./scripts/build_static_site.sh` to build the site to, say, `/tmp/gateway/`. Then spin up a static HTTP server serving `/tmp/` and navigate to `/gateway/` in the browser. Note that the entire application works. wchargin-branch: use-assets-in-PluginAdapters
This commit is contained in:
parent
b252a6b5de
commit
5ac2494586
@ -3,4 +3,5 @@
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
- Start tracking changes in `CHANGELOG.md`
|
- Start tracking changes in `CHANGELOG.md`
|
||||||
- Aggregate over connection types in the cred explorer (#502)
|
- Aggregate over connection types in the cred explorer (#502)
|
||||||
|
- Support hosting SourceCred instances at arbitrary gateways, not just the root of a domain (#643)
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
import {Graph, type NodeAddressT, type EdgeAddressT} from "../../core/graph";
|
import {Graph, type NodeAddressT, type EdgeAddressT} from "../../core/graph";
|
||||||
import {NodeTrie, EdgeTrie} from "../../core/trie";
|
import {NodeTrie, EdgeTrie} from "../../core/trie";
|
||||||
|
import type {Assets} from "../assets";
|
||||||
import type {Repo} from "../../core/repo";
|
import type {Repo} from "../../core/repo";
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
@ -68,8 +69,8 @@ export class StaticAdapterSet {
|
|||||||
return this._typeEdgeTrie.getLast(x);
|
return this._typeEdgeTrie.getLast(x);
|
||||||
}
|
}
|
||||||
|
|
||||||
load(repo: Repo): Promise<DynamicAdapterSet> {
|
load(assets: Assets, repo: Repo): Promise<DynamicAdapterSet> {
|
||||||
return Promise.all(this._adapters.map((a) => a.load(repo))).then(
|
return Promise.all(this._adapters.map((a) => a.load(assets, repo))).then(
|
||||||
(adapters) => new DynamicAdapterSet(this, adapters)
|
(adapters) => new DynamicAdapterSet(this, adapters)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ import {
|
|||||||
import type {DynamicPluginAdapter} from "./pluginAdapter";
|
import type {DynamicPluginAdapter} from "./pluginAdapter";
|
||||||
import {StaticAdapterSet} from "./adapterSet";
|
import {StaticAdapterSet} from "./adapterSet";
|
||||||
import {FallbackStaticAdapter, FALLBACK_NAME} from "./fallbackAdapter";
|
import {FallbackStaticAdapter, FALLBACK_NAME} from "./fallbackAdapter";
|
||||||
|
import {Assets} from "../assets";
|
||||||
import {makeRepo, type Repo} from "../../core/repo";
|
import {makeRepo, type Repo} from "../../core/repo";
|
||||||
|
|
||||||
describe("app/adapters/adapterSet", () => {
|
describe("app/adapters/adapterSet", () => {
|
||||||
@ -57,8 +58,10 @@ describe("app/adapters/adapterSet", () => {
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
load(_unused_repo: Repo) {
|
load(assets: Assets, repo: Repo) {
|
||||||
return this.loadingMock().then(() => new TestDynamicPluginAdapter());
|
return this.loadingMock(assets, repo).then(
|
||||||
|
() => new TestDynamicPluginAdapter()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -159,7 +162,14 @@ describe("app/adapters/adapterSet", () => {
|
|||||||
it("loads a dynamicAdapterSet", async () => {
|
it("loads a dynamicAdapterSet", async () => {
|
||||||
const {x, sas} = example();
|
const {x, sas} = example();
|
||||||
x.loadingMock.mockResolvedValue();
|
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());
|
expect(das).toEqual(expect.anything());
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -169,7 +179,10 @@ describe("app/adapters/adapterSet", () => {
|
|||||||
const x = new TestStaticPluginAdapter();
|
const x = new TestStaticPluginAdapter();
|
||||||
const sas = new StaticAdapterSet([x]);
|
const sas = new StaticAdapterSet([x]);
|
||||||
x.loadingMock.mockResolvedValue();
|
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};
|
return {x, sas, das};
|
||||||
}
|
}
|
||||||
it("allows retrieval of the original StaticAdapterSet", async () => {
|
it("allows retrieval of the original StaticAdapterSet", async () => {
|
||||||
|
@ -6,6 +6,7 @@ import {
|
|||||||
type NodeAddressT,
|
type NodeAddressT,
|
||||||
EdgeAddress,
|
EdgeAddress,
|
||||||
} from "../../core/graph";
|
} from "../../core/graph";
|
||||||
|
import type {Assets} from "../assets";
|
||||||
import type {Repo} from "../../core/repo";
|
import type {Repo} from "../../core/repo";
|
||||||
|
|
||||||
import type {StaticPluginAdapter, DynamicPluginAdapter} from "./pluginAdapter";
|
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());
|
return Promise.resolve(new FallbackDynamicAdapter());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// @flow
|
// @flow
|
||||||
|
|
||||||
import {Graph, type NodeAddressT, type EdgeAddressT} from "../../core/graph";
|
import {Graph, type NodeAddressT, type EdgeAddressT} from "../../core/graph";
|
||||||
|
import type {Assets} from "../assets";
|
||||||
import type {Repo} from "../../core/repo";
|
import type {Repo} from "../../core/repo";
|
||||||
|
|
||||||
export type EdgeType = {|
|
export type EdgeType = {|
|
||||||
@ -22,7 +23,7 @@ export interface StaticPluginAdapter {
|
|||||||
edgePrefix(): EdgeAddressT;
|
edgePrefix(): EdgeAddressT;
|
||||||
nodeTypes(): NodeType[];
|
nodeTypes(): NodeType[];
|
||||||
edgeTypes(): EdgeType[];
|
edgeTypes(): EdgeType[];
|
||||||
load(repo: Repo): Promise<DynamicPluginAdapter>;
|
load(assets: Assets, repo: Repo): Promise<DynamicPluginAdapter>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DynamicPluginAdapter {
|
export interface DynamicPluginAdapter {
|
||||||
|
@ -91,7 +91,9 @@ export function createApp(
|
|||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
disabled={subType !== "READY_TO_LOAD_GRAPH"}
|
disabled={subType !== "READY_TO_LOAD_GRAPH"}
|
||||||
onClick={() => this.stateTransitionMachine.loadGraph()}
|
onClick={() =>
|
||||||
|
this.stateTransitionMachine.loadGraph(this.props.assets)
|
||||||
|
}
|
||||||
>
|
>
|
||||||
Load graph
|
Load graph
|
||||||
</button>
|
</button>
|
||||||
|
@ -4,6 +4,7 @@ import deepEqual from "lodash.isequal";
|
|||||||
|
|
||||||
import * as NullUtil from "../../util/null";
|
import * as NullUtil from "../../util/null";
|
||||||
import {Graph} from "../../core/graph";
|
import {Graph} from "../../core/graph";
|
||||||
|
import type {Assets} from "../../app/assets";
|
||||||
import type {Repo} from "../../core/repo";
|
import type {Repo} from "../../core/repo";
|
||||||
import {type EdgeEvaluator} from "../../core/attribution/pagerank";
|
import {type EdgeEvaluator} from "../../core/attribution/pagerank";
|
||||||
import {
|
import {
|
||||||
@ -78,7 +79,7 @@ export function initialState(): AppState {
|
|||||||
export interface StateTransitionMachineInterface {
|
export interface StateTransitionMachineInterface {
|
||||||
+setRepo: (Repo) => void;
|
+setRepo: (Repo) => void;
|
||||||
+setEdgeEvaluator: (EdgeEvaluator) => void;
|
+setEdgeEvaluator: (EdgeEvaluator) => void;
|
||||||
+loadGraph: () => Promise<void>;
|
+loadGraph: (Assets) => Promise<void>;
|
||||||
+runPagerank: () => Promise<void>;
|
+runPagerank: () => Promise<void>;
|
||||||
}
|
}
|
||||||
/* In production, instantiate via createStateTransitionMachine; the constructor
|
/* In production, instantiate via createStateTransitionMachine; the constructor
|
||||||
@ -88,7 +89,10 @@ export interface StateTransitionMachineInterface {
|
|||||||
export class StateTransitionMachine implements StateTransitionMachineInterface {
|
export class StateTransitionMachine implements StateTransitionMachineInterface {
|
||||||
getState: () => AppState;
|
getState: () => AppState;
|
||||||
setState: (AppState) => void;
|
setState: (AppState) => void;
|
||||||
loadGraphWithAdapters: (repo: Repo) => Promise<GraphWithAdapters>;
|
loadGraphWithAdapters: (
|
||||||
|
assets: Assets,
|
||||||
|
repo: Repo
|
||||||
|
) => Promise<GraphWithAdapters>;
|
||||||
pagerank: (
|
pagerank: (
|
||||||
Graph,
|
Graph,
|
||||||
EdgeEvaluator,
|
EdgeEvaluator,
|
||||||
@ -98,7 +102,10 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
|
|||||||
constructor(
|
constructor(
|
||||||
getState: () => AppState,
|
getState: () => AppState,
|
||||||
setState: (AppState) => void,
|
setState: (AppState) => void,
|
||||||
loadGraphWithAdapters: (repo: Repo) => Promise<GraphWithAdapters>,
|
loadGraphWithAdapters: (
|
||||||
|
assets: Assets,
|
||||||
|
repo: Repo
|
||||||
|
) => Promise<GraphWithAdapters>,
|
||||||
pagerank: (
|
pagerank: (
|
||||||
Graph,
|
Graph,
|
||||||
EdgeEvaluator,
|
EdgeEvaluator,
|
||||||
@ -160,7 +167,7 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async loadGraph() {
|
async loadGraph(assets: Assets) {
|
||||||
const state = this.getState();
|
const state = this.getState();
|
||||||
if (
|
if (
|
||||||
state.type !== "INITIALIZED" ||
|
state.type !== "INITIALIZED" ||
|
||||||
@ -176,7 +183,7 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
|
|||||||
this.setState(loadingState);
|
this.setState(loadingState);
|
||||||
let newState: ?AppState;
|
let newState: ?AppState;
|
||||||
try {
|
try {
|
||||||
const graphWithAdapters = await this.loadGraphWithAdapters(repo);
|
const graphWithAdapters = await this.loadGraphWithAdapters(assets, repo);
|
||||||
newState = {
|
newState = {
|
||||||
...state,
|
...state,
|
||||||
substate: {
|
substate: {
|
||||||
@ -250,8 +257,9 @@ export type GraphWithAdapters = {|
|
|||||||
+adapters: DynamicAdapterSet,
|
+adapters: DynamicAdapterSet,
|
||||||
|};
|
|};
|
||||||
export async function loadGraphWithAdapters(
|
export async function loadGraphWithAdapters(
|
||||||
|
assets: Assets,
|
||||||
repo: Repo
|
repo: Repo
|
||||||
): Promise<GraphWithAdapters> {
|
): Promise<GraphWithAdapters> {
|
||||||
const adapters = await defaultStaticAdapters().load(repo);
|
const adapters = await defaultStaticAdapters().load(assets, repo);
|
||||||
return {graph: adapters.graph(), adapters};
|
return {graph: adapters.graph(), adapters};
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import {
|
|||||||
} from "./state";
|
} from "./state";
|
||||||
|
|
||||||
import {Graph} from "../../core/graph";
|
import {Graph} from "../../core/graph";
|
||||||
|
import {Assets} from "../assets";
|
||||||
import {makeRepo, type Repo} from "../../core/repo";
|
import {makeRepo, type Repo} from "../../core/repo";
|
||||||
import {type EdgeEvaluator} from "../../core/attribution/pagerank";
|
import {type EdgeEvaluator} from "../../core/attribution/pagerank";
|
||||||
import {StaticAdapterSet, DynamicAdapterSet} from "../adapters/adapterSet";
|
import {StaticAdapterSet, DynamicAdapterSet} from "../adapters/adapterSet";
|
||||||
@ -23,7 +24,10 @@ describe("app/credExplorer/state", () => {
|
|||||||
const setState = (appState) => {
|
const setState = (appState) => {
|
||||||
stateContainer.appState = appState;
|
stateContainer.appState = appState;
|
||||||
};
|
};
|
||||||
const loadGraphMock: (repo: Repo) => Promise<GraphWithAdapters> = jest.fn();
|
const loadGraphMock: (
|
||||||
|
assets: Assets,
|
||||||
|
repo: Repo
|
||||||
|
) => Promise<GraphWithAdapters> = jest.fn();
|
||||||
const pagerankMock: (
|
const pagerankMock: (
|
||||||
Graph,
|
Graph,
|
||||||
EdgeEvaluator,
|
EdgeEvaluator,
|
||||||
@ -196,13 +200,25 @@ describe("app/credExplorer/state", () => {
|
|||||||
];
|
];
|
||||||
for (const b of badStates) {
|
for (const b of badStates) {
|
||||||
const {stm} = example(b);
|
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", () => {
|
it("immediately sets loading status", () => {
|
||||||
const {getState, stm} = example(readyToLoadGraph());
|
const {getState, stm} = example(readyToLoadGraph());
|
||||||
expect(loading(getState())).toBe("NOT_LOADING");
|
expect(loading(getState())).toBe("NOT_LOADING");
|
||||||
stm.loadGraph();
|
stm.loadGraph(new Assets("/my/gateway/"));
|
||||||
expect(loading(getState())).toBe("LOADING");
|
expect(loading(getState())).toBe("LOADING");
|
||||||
expect(getSubstate(getState()).type).toBe("READY_TO_LOAD_GRAPH");
|
expect(getSubstate(getState()).type).toBe("READY_TO_LOAD_GRAPH");
|
||||||
});
|
});
|
||||||
@ -210,7 +226,7 @@ describe("app/credExplorer/state", () => {
|
|||||||
const {getState, stm, loadGraphMock} = example(readyToLoadGraph());
|
const {getState, stm, loadGraphMock} = example(readyToLoadGraph());
|
||||||
const gwa = graphWithAdapters();
|
const gwa = graphWithAdapters();
|
||||||
loadGraphMock.mockResolvedValue(gwa);
|
loadGraphMock.mockResolvedValue(gwa);
|
||||||
await stm.loadGraph();
|
await stm.loadGraph(new Assets("/my/gateway/"));
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const substate = getSubstate(state);
|
const substate = getSubstate(state);
|
||||||
expect(loading(state)).toBe("NOT_LOADING");
|
expect(loading(state)).toBe("NOT_LOADING");
|
||||||
@ -230,7 +246,7 @@ describe("app/credExplorer/state", () => {
|
|||||||
resolve(graphWithAdapters());
|
resolve(graphWithAdapters());
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
await stm.loadGraph();
|
await stm.loadGraph(new Assets("/my/gateway/"));
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const substate = getSubstate(state);
|
const substate = getSubstate(state);
|
||||||
expect(loading(state)).toBe("NOT_LOADING");
|
expect(loading(state)).toBe("NOT_LOADING");
|
||||||
@ -243,7 +259,7 @@ describe("app/credExplorer/state", () => {
|
|||||||
// $ExpectFlowError
|
// $ExpectFlowError
|
||||||
console.error = jest.fn();
|
console.error = jest.fn();
|
||||||
loadGraphMock.mockRejectedValue(error);
|
loadGraphMock.mockRejectedValue(error);
|
||||||
await stm.loadGraph();
|
await stm.loadGraph(new Assets("/my/gateway/"));
|
||||||
const state = getState();
|
const state = getState();
|
||||||
const substate = getSubstate(state);
|
const substate = getSubstate(state);
|
||||||
expect(loading(state)).toBe("FAILED");
|
expect(loading(state)).toBe("FAILED");
|
||||||
|
@ -7,6 +7,7 @@ import {Graph} from "../../core/graph";
|
|||||||
import * as N from "./nodes";
|
import * as N from "./nodes";
|
||||||
import * as E from "./edges";
|
import * as E from "./edges";
|
||||||
import {description} from "./render";
|
import {description} from "./render";
|
||||||
|
import type {Assets} from "../../app/assets";
|
||||||
import type {Repo} from "../../core/repo";
|
import type {Repo} from "../../core/repo";
|
||||||
|
|
||||||
export class StaticPluginAdapter implements IStaticPluginAdapter {
|
export class StaticPluginAdapter implements IStaticPluginAdapter {
|
||||||
@ -76,8 +77,10 @@ export class StaticPluginAdapter implements IStaticPluginAdapter {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
async load(repo: Repo): Promise<IDynamicPluginAdapter> {
|
async load(assets: Assets, repo: Repo): Promise<IDynamicPluginAdapter> {
|
||||||
const url = `/api/v1/data/data/${repo.owner}/${repo.name}/git/graph.json`;
|
const url = assets.resolve(
|
||||||
|
`/api/v1/data/data/${repo.owner}/${repo.name}/git/graph.json`
|
||||||
|
);
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return Promise.reject(response);
|
return Promise.reject(response);
|
||||||
|
@ -9,6 +9,7 @@ import * as N from "./nodes";
|
|||||||
import * as E from "./edges";
|
import * as E from "./edges";
|
||||||
import {RelationalView} from "./relationalView";
|
import {RelationalView} from "./relationalView";
|
||||||
import {description} from "./render";
|
import {description} from "./render";
|
||||||
|
import type {Assets} from "../../app/assets";
|
||||||
import type {Repo} from "../../core/repo";
|
import type {Repo} from "../../core/repo";
|
||||||
|
|
||||||
export class StaticPluginAdapter implements IStaticPluginAdapter {
|
export class StaticPluginAdapter implements IStaticPluginAdapter {
|
||||||
@ -85,8 +86,10 @@ export class StaticPluginAdapter implements IStaticPluginAdapter {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
async load(repo: Repo): Promise<IDynamicPluginAdapater> {
|
async load(assets: Assets, repo: Repo): Promise<IDynamicPluginAdapater> {
|
||||||
const url = `/api/v1/data/data/${repo.owner}/${repo.name}/github/view.json`;
|
const url = assets.resolve(
|
||||||
|
`/api/v1/data/data/${repo.owner}/${repo.name}/github/view.json`
|
||||||
|
);
|
||||||
const response = await fetch(url);
|
const response = await fetch(url);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
return Promise.reject(response);
|
return Promise.reject(response);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user