Frontend gets plugins from disk, not TimelineCred (#1625)
This commit modifies the frontend so that it now pulls plugin declarations from disk, rather than from the TimelineCred. This will allow us to decouple the TimelineCred from the PluginDeclarations, which is another step towards #1557. As proof that the frontend no longer gets plugins from the TimelineCred, I removed the public `plugins()` method on TimelineCred. Test plan: The frontend is somewhat sketchily tested. `yarn test` passing is good, manual inspection of the frontend is also necessary; I've done this.
This commit is contained in:
parent
aeaa945a27
commit
1073374dc7
|
@ -76,10 +76,6 @@ export class TimelineCred {
|
|||
return this._params;
|
||||
}
|
||||
|
||||
plugins(): $ReadOnlyArray<PluginDeclaration> {
|
||||
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<CredNode> {
|
||||
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));
|
||||
}
|
||||
|
||||
|
|
|
@ -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<Props, State> {
|
|||
);
|
||||
}
|
||||
case "SUCCESS": {
|
||||
const {timelineCred} = loadResult;
|
||||
const {timelineCred, pluginDeclarations} = loadResult;
|
||||
return (
|
||||
<TimelineExplorer
|
||||
initialTimelineCred={timelineCred}
|
||||
projectId={this.props.projectId}
|
||||
pluginDeclarations={pluginDeclarations}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
@ -90,9 +96,24 @@ export async function defaultLoader(
|
|||
return TimelineCred.fromJSON(await response.json());
|
||||
}
|
||||
|
||||
async function fetchPluginDeclarations(): Promise<PluginDeclarations> {
|
||||
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};
|
||||
|
|
|
@ -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<Props, State> {
|
|||
);
|
||||
const weightConfig = (
|
||||
<WeightConfig
|
||||
declarations={this.state.timelineCred.plugins()}
|
||||
declarations={this.props.pluginDeclarations}
|
||||
nodeWeights={this.state.weights.nodeWeights}
|
||||
edgeWeights={this.state.weights.edgeWeights}
|
||||
onNodeWeightChange={(prefix, weight) => {
|
||||
|
@ -201,7 +205,7 @@ export class TimelineExplorer extends React.Component<Props, State> {
|
|||
<option key={"All users"} value={""}>
|
||||
All users
|
||||
</option>
|
||||
{this.state.timelineCred.plugins().map(optionGroup)}
|
||||
{this.props.pluginDeclarations.map(optionGroup)}
|
||||
</select>
|
||||
</label>
|
||||
);
|
||||
|
|
|
@ -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 = (
|
||||
<WeightConfig
|
||||
declarations={declarations}
|
||||
|
|
|
@ -72,6 +72,7 @@ describe("explorer/legacy/App", () => {
|
|||
defaultParams(),
|
||||
[]
|
||||
),
|
||||
pluginDeclarations: [],
|
||||
});
|
||||
},
|
||||
pagerankEvaluated: (loadingState) => {
|
||||
|
@ -86,6 +87,7 @@ describe("explorer/legacy/App", () => {
|
|||
defaultParams(),
|
||||
[]
|
||||
),
|
||||
pluginDeclarations: [],
|
||||
pagerankNodeDecomposition: new Map(),
|
||||
});
|
||||
},
|
||||
|
|
|
@ -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<TimelineCred>;
|
||||
doLoad: (assets: Assets, projectId: string) => Promise<LoadSuccess>;
|
||||
pagerank: (
|
||||
Graph,
|
||||
EdgeEvaluator,
|
||||
|
@ -96,10 +93,7 @@ export class StateTransitionMachine implements StateTransitionMachineInterface {
|
|||
constructor(
|
||||
getState: () => AppState,
|
||||
setState: (AppState) => void,
|
||||
doLoadTimelineCred: (
|
||||
assets: Assets,
|
||||
projectId: string
|
||||
) => Promise<TimelineCred>,
|
||||
doLoad: (assets: Assets, projectId: string) => Promise<LoadSuccess>,
|
||||
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<TimelineCred> {
|
||||
): Promise<LoadSuccess> {
|
||||
const loadResult = await defaultLoader(assets, projectId);
|
||||
if (loadResult.type !== "SUCCESS") {
|
||||
throw new Error(loadResult);
|
||||
}
|
||||
return loadResult.timelineCred;
|
||||
return loadResult;
|
||||
}
|
||||
|
|
|
@ -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<TimelineCred>
|
||||
Promise<LoadSuccess>
|
||||
> = 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();
|
||||
|
|
Loading…
Reference in New Issue