diff --git a/src/analysis/timeline/timelineCred.js b/src/analysis/timeline/timelineCred.js index 5cb9f6b..ce35dd2 100644 --- a/src/analysis/timeline/timelineCred.js +++ b/src/analysis/timeline/timelineCred.js @@ -76,10 +76,6 @@ export class TimelineCred { return this._params; } - plugins(): $ReadOnlyArray { - return this._plugins; - } - /** * Creates a new TimelineCred based on the new Parameters. * Holds the graph and config constant. @@ -151,7 +147,7 @@ export class TimelineCred { * with a type specified as a user type by one of the plugin declarations. */ userNodes(): $ReadOnlyArray { - const userTypes = [].concat(...this.plugins().map((p) => p.userTypes)); + const userTypes = [].concat(...this._plugins.map((p) => p.userTypes)); return this.credSortedNodes(userTypes.map((x) => x.prefix)); } diff --git a/src/explorer/TimelineApp.js b/src/explorer/TimelineApp.js index 6a35b70..744e434 100644 --- a/src/explorer/TimelineApp.js +++ b/src/explorer/TimelineApp.js @@ -5,6 +5,10 @@ import type {Assets} from "../webutil/assets"; import {TimelineExplorer} from "./TimelineExplorer"; import {TimelineCred} from "../analysis/timeline/timelineCred"; import {encodeProjectId, type ProjectId} from "../core/project"; +import { + type PluginDeclarations, + fromJSON as pluginsFromJSON, +} from "../analysis/pluginDeclaration"; export type Props = {| +assets: Assets, @@ -19,6 +23,7 @@ export type Loading = {|+type: "LOADING"|}; export type LoadSuccess = {| +type: "SUCCESS", +timelineCred: TimelineCred, + +pluginDeclarations: PluginDeclarations, |}; export type LoadError = {|+type: "ERROR", +error: any|}; @@ -62,11 +67,12 @@ export class TimelineApp extends React.Component { ); } case "SUCCESS": { - const {timelineCred} = loadResult; + const {timelineCred, pluginDeclarations} = loadResult; return ( ); } @@ -90,9 +96,24 @@ export async function defaultLoader( return TimelineCred.fromJSON(await response.json()); } + async function fetchPluginDeclarations(): Promise { + const encodedId = encodeProjectId(projectId); + const url = assets.resolve( + `api/v1/data/projects/${encodedId}/pluginDeclarations.json` + ); + const response = await fetch(url); + if (!response.ok) { + return Promise.reject(response); + } + return pluginsFromJSON(await response.json()); + } + try { - const timelineCred = await fetchCred(); - return {type: "SUCCESS", timelineCred}; + const [timelineCred, pluginDeclarations] = await Promise.all([ + fetchCred(), + fetchPluginDeclarations(), + ]); + return {type: "SUCCESS", timelineCred, pluginDeclarations}; } catch (e) { console.error(e); return {type: "ERROR", error: e}; diff --git a/src/explorer/TimelineExplorer.js b/src/explorer/TimelineExplorer.js index 4778c19..4fa1944 100644 --- a/src/explorer/TimelineExplorer.js +++ b/src/explorer/TimelineExplorer.js @@ -11,13 +11,17 @@ import {TimelineCredView} from "./TimelineCredView"; import Link from "../webutil/Link"; import {WeightConfig} from "./weights/WeightConfig"; import {WeightsFileManager} from "./weights/WeightsFileManager"; -import {type PluginDeclaration} from "../analysis/pluginDeclaration"; +import { + type PluginDeclarations, + type PluginDeclaration, +} from "../analysis/pluginDeclaration"; import * as NullUtil from "../util/null"; import {format} from "d3-format"; export type Props = { projectId: string, initialTimelineCred: TimelineCred, + pluginDeclarations: PluginDeclarations, }; export type State = { @@ -89,7 +93,7 @@ export class TimelineExplorer extends React.Component { ); const weightConfig = ( { @@ -201,7 +205,7 @@ export class TimelineExplorer extends React.Component { - {this.state.timelineCred.plugins().map(optionGroup)} + {this.props.pluginDeclarations.map(optionGroup)} ); diff --git a/src/explorer/legacy/App.js b/src/explorer/legacy/App.js index 5374003..d414a51 100644 --- a/src/explorer/legacy/App.js +++ b/src/explorer/legacy/App.js @@ -93,7 +93,7 @@ export function createApp( ); let pagerankTable; if (appState.type === "PAGERANK_EVALUATED") { - const declarations = appState.timelineCred.plugins(); + const declarations = appState.pluginDeclarations; const weightConfig = ( { defaultParams(), [] ), + pluginDeclarations: [], }); }, pagerankEvaluated: (loadingState) => { @@ -86,6 +87,7 @@ describe("explorer/legacy/App", () => { defaultParams(), [] ), + pluginDeclarations: [], pagerankNodeDecomposition: new Map(), }); }, diff --git a/src/explorer/legacy/state.js b/src/explorer/legacy/state.js index 013979b..1647664 100644 --- a/src/explorer/legacy/state.js +++ b/src/explorer/legacy/state.js @@ -5,7 +5,7 @@ import deepEqual from "lodash.isequal"; import {Graph, type NodeAddressT} from "../../core/graph"; import type {Assets} from "../../webutil/assets"; import {type EdgeEvaluator} from "../../analysis/pagerank"; -import {defaultLoader} from "../TimelineApp"; +import {defaultLoader, type LoadSuccess} from "../TimelineApp"; import { type PagerankNodeDecomposition, type PagerankOptions, @@ -15,7 +15,10 @@ import {TimelineCred} from "../../analysis/timeline/timelineCred"; import type {Weights} from "../../core/weights"; import {weightsToEdgeEvaluator} from "../../analysis/weightsToEdgeEvaluator"; -import {combineTypes} from "../../analysis/pluginDeclaration"; +import { + combineTypes, + type PluginDeclarations, +} from "../../analysis/pluginDeclaration"; /* This models the UI states of the credExplorer/App as a state machine. @@ -40,11 +43,13 @@ export type ReadyToRunPagerank = {| +type: "READY_TO_RUN_PAGERANK", +projectId: string, +timelineCred: TimelineCred, + +pluginDeclarations: PluginDeclarations, +loading: LoadingState, |}; export type PagerankEvaluated = {| +type: "PAGERANK_EVALUATED", +timelineCred: TimelineCred, + +pluginDeclarations: PluginDeclarations, +projectId: string, +pagerankNodeDecomposition: PagerankNodeDecomposition, +loading: LoadingState, @@ -58,12 +63,7 @@ export function createStateTransitionMachine( getState: () => AppState, setState: (AppState) => void ): StateTransitionMachine { - return new StateTransitionMachine( - getState, - setState, - doLoadTimelineCred, - pagerank - ); + return new StateTransitionMachine(getState, setState, doLoad, pagerank); } // Exported for testing purposes. @@ -83,10 +83,7 @@ export interface StateTransitionMachineInterface { export class StateTransitionMachine implements StateTransitionMachineInterface { getState: () => AppState; setState: (AppState) => void; - doLoadTimelineCred: ( - assets: Assets, - projectId: string - ) => Promise; + doLoad: (assets: Assets, projectId: string) => Promise; pagerank: ( Graph, EdgeEvaluator, @@ -96,10 +93,7 @@ export class StateTransitionMachine implements StateTransitionMachineInterface { constructor( getState: () => AppState, setState: (AppState) => void, - doLoadTimelineCred: ( - assets: Assets, - projectId: string - ) => Promise, + doLoad: (assets: Assets, projectId: string) => Promise, pagerank: ( Graph, EdgeEvaluator, @@ -108,7 +102,7 @@ export class StateTransitionMachine implements StateTransitionMachineInterface { ) { this.getState = getState; this.setState = setState; - this.doLoadTimelineCred = doLoadTimelineCred; + this.doLoad = doLoad; this.pagerank = pagerank; } @@ -124,10 +118,14 @@ export class StateTransitionMachine implements StateTransitionMachineInterface { let newState: ?AppState; let success = true; try { - const timelineCred = await this.doLoadTimelineCred(assets, projectId); + const {pluginDeclarations, timelineCred} = await this.doLoad( + assets, + projectId + ); newState = { type: "READY_TO_RUN_PAGERANK", timelineCred, + pluginDeclarations, projectId, loading: "NOT_LOADING", }; @@ -159,7 +157,7 @@ export class StateTransitionMachine implements StateTransitionMachineInterface { this.setState(loadingState); const graph = state.timelineCred.weightedGraph().graph; let newState: ?AppState; - const types = combineTypes(state.timelineCred.plugins()); + const types = combineTypes(state.pluginDeclarations); try { const pagerankNodeDecomposition = await this.pagerank( graph, @@ -173,6 +171,7 @@ export class StateTransitionMachine implements StateTransitionMachineInterface { type: "PAGERANK_EVALUATED", pagerankNodeDecomposition, timelineCred: state.timelineCred, + pluginDeclarations: state.pluginDeclarations, projectId: state.projectId, loading: "NOT_LOADING", }; @@ -213,13 +212,13 @@ export class StateTransitionMachine implements StateTransitionMachineInterface { } } -export async function doLoadTimelineCred( +export async function doLoad( assets: Assets, projectId: string -): Promise { +): Promise { const loadResult = await defaultLoader(assets, projectId); if (loadResult.type !== "SUCCESS") { throw new Error(loadResult); } - return loadResult.timelineCred; + return loadResult; } diff --git a/src/explorer/legacy/state.test.js b/src/explorer/legacy/state.test.js index 0f5c149..54288ba 100644 --- a/src/explorer/legacy/state.test.js +++ b/src/explorer/legacy/state.test.js @@ -13,6 +13,7 @@ import type { } from "../../analysis/pagerank"; import {TimelineCred} from "../../analysis/timeline/timelineCred"; import {defaultParams} from "../../analysis/timeline/params"; +import {type LoadSuccess} from "../TimelineApp"; describe("explorer/legacy/state", () => { function example(startingState: AppState) { @@ -21,9 +22,9 @@ describe("explorer/legacy/state", () => { const setState = (appState) => { stateContainer.appState = appState; }; - const loadTimelineCredMock: JestMockFn< + const loadMock: JestMockFn< [Assets, string], - Promise + Promise > = jest.fn(); const pagerankMock: JestMockFn< @@ -33,10 +34,10 @@ describe("explorer/legacy/state", () => { const stm = new StateTransitionMachine( getState, setState, - loadTimelineCredMock, + loadMock, pagerankMock ); - return {getState, stm, loadTimelineCredMock, pagerankMock}; + return {getState, stm, loadMock, pagerankMock}; } function readyToLoadGraph(): AppState { return { @@ -57,6 +58,7 @@ describe("explorer/legacy/state", () => { defaultParams(), [] ), + pluginDeclarations: [], }; } function pagerankEvaluated(): AppState { @@ -70,6 +72,7 @@ describe("explorer/legacy/state", () => { defaultParams(), [] ), + pluginDeclarations: [], pagerankNodeDecomposition: pagerankNodeDecomposition(), loading: "NOT_LOADING", }; @@ -92,12 +95,12 @@ describe("explorer/legacy/state", () => { } }); it("passes along the projectId", () => { - const {stm, loadTimelineCredMock} = example(readyToLoadGraph()); - expect(loadTimelineCredMock).toHaveBeenCalledTimes(0); + const {stm, loadMock} = example(readyToLoadGraph()); + expect(loadMock).toHaveBeenCalledTimes(0); const assets = new Assets("/my/gateway/"); stm.loadTimelineCred(assets); - expect(loadTimelineCredMock).toHaveBeenCalledTimes(1); - expect(loadTimelineCredMock).toHaveBeenCalledWith(assets, "foo/bar"); + expect(loadMock).toHaveBeenCalledTimes(1); + expect(loadMock).toHaveBeenCalledWith(assets, "foo/bar"); }); it("immediately sets loading status", () => { const {getState, stm} = example(readyToLoadGraph()); @@ -107,7 +110,7 @@ describe("explorer/legacy/state", () => { expect(getState().type).toBe("READY_TO_LOAD_GRAPH"); }); it("transitions to READY_TO_RUN_PAGERANK on success", async () => { - const {getState, stm, loadTimelineCredMock} = example(readyToLoadGraph()); + const {getState, stm, loadMock} = example(readyToLoadGraph()); const timelineCred = new TimelineCred( WeightedGraph.empty(), @@ -116,7 +119,12 @@ describe("explorer/legacy/state", () => { defaultParams(), [] ); - loadTimelineCredMock.mockReturnValue(Promise.resolve(timelineCred)); + const loadResult = { + type: "SUCCESS", + timelineCred, + pluginDeclarations: [], + }; + loadMock.mockReturnValue(Promise.resolve(loadResult)); const succeeded = await stm.loadTimelineCred(new Assets("/my/gateway/")); expect(succeeded).toBe(true); const state = getState(); @@ -128,11 +136,11 @@ describe("explorer/legacy/state", () => { expect(state.timelineCred).toBe(timelineCred); }); it("sets loading state FAILED on reject", async () => { - const {getState, stm, loadTimelineCredMock} = example(readyToLoadGraph()); + const {getState, stm, loadMock} = example(readyToLoadGraph()); const error = new Error("Oh no!"); // $ExpectFlowError console.error = jest.fn(); - loadTimelineCredMock.mockReturnValue(Promise.reject(error)); + loadMock.mockReturnValue(Promise.reject(error)); const succeeded = await stm.loadTimelineCred(new Assets("/my/gateway/")); expect(succeeded).toBe(false); const state = getState();