Refactor PluginAdapter abstraction (#512)

- PluginAdapters no longer expose a Renderer; instead, the render
methods are inlined on the PluginAdapter. The extra abstraction didn't
provide any lift in the current architecture.

- The edgeVerb function has been removed.

- PluginAdapters now enumerate EdgeTypes. Each has a prefix, and a
forward and a backward name.

Test plan: `yarn travis`, plus manual testing of the frontend and the
weight config.
This commit is contained in:
Dandelion Mané 2018-07-23 15:25:17 -07:00 committed by GitHub
parent 28100275c4
commit 3c14ef8a43
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 105 additions and 111 deletions

View File

@ -32,7 +32,7 @@ export function nodeDescription(
} }
try { try {
return adapter.renderer().nodeDescription(address); return adapter.nodeDescription(address);
} catch (e) { } catch (e) {
const result = NodeAddress.toString(address); const result = NodeAddress.toString(address);
console.error(`Error getting description for ${result}: ${e.message}`); console.error(`Error getting description for ${result}: ${e.message}`);
@ -54,13 +54,15 @@ function edgeVerb(
return result; return result;
} }
try { const edgeType = adapter
return adapter.renderer().edgeVerb(address, direction); .edgeTypes()
} catch (e) { .find(({prefix}) => EdgeAddress.hasPrefix(address, prefix));
if (edgeType == null) {
const result = EdgeAddress.toString(address); const result = EdgeAddress.toString(address);
console.error(`Error getting description for ${result}: ${e.message}`); console.warn(`No edge type for ${result}`);
return result; return result;
} }
return direction === "FORWARD" ? edgeType.forwardName : edgeType.backwardName;
} }
function scoreDisplay(probability: number) { function scoreDisplay(probability: number) {

View File

@ -61,57 +61,66 @@ function example() {
throw new Error("unused"); throw new Error("unused");
}, },
renderer: () => ({ renderer: () => ({
nodeDescription: (x) => `foo: ${NodeAddress.toString(x)}`,
edgeVerb: (_unused_e, direction) => edgeVerb: (_unused_e, direction) =>
direction === "FORWARD" ? "foos" : "is fooed by", direction === "FORWARD" ? "foos" : "is fooed by",
}), }),
nodeDescription: (x) => `foo: ${NodeAddress.toString(x)}`,
nodePrefix: () => NodeAddress.fromParts(["foo"]), nodePrefix: () => NodeAddress.fromParts(["foo"]),
edgePrefix: () => EdgeAddress.fromParts(["foo"]), edgePrefix: () => EdgeAddress.fromParts(["foo"]),
nodeTypes: () => [ nodeTypes: () => [
{name: "alpha", prefix: NodeAddress.fromParts(["foo", "a"])}, {name: "alpha", prefix: NodeAddress.fromParts(["foo", "a"])},
{name: "beta", prefix: NodeAddress.fromParts(["foo", "b"])}, {name: "beta", prefix: NodeAddress.fromParts(["foo", "b"])},
], ],
edgeTypes: () => [
{
prefix: EdgeAddress.fromParts(["foo"]),
forwardName: "foos",
backwardName: "is fooed by",
},
],
}, },
{ {
name: () => "bar", name: () => "bar",
graph: () => { graph: () => {
throw new Error("unused"); throw new Error("unused");
}, },
renderer: () => ({ nodeDescription: (x) => `bar: ${NodeAddress.toString(x)}`,
nodeDescription: (x) => `bar: ${NodeAddress.toString(x)}`,
edgeVerb: (_unused_e, direction) =>
direction === "FORWARD" ? "bars" : "is barred by",
}),
nodePrefix: () => NodeAddress.fromParts(["bar"]), nodePrefix: () => NodeAddress.fromParts(["bar"]),
edgePrefix: () => EdgeAddress.fromParts(["bar"]), edgePrefix: () => EdgeAddress.fromParts(["bar"]),
nodeTypes: () => [ nodeTypes: () => [
{name: "alpha", prefix: NodeAddress.fromParts(["bar", "a"])}, {name: "alpha", prefix: NodeAddress.fromParts(["bar", "a"])},
], ],
edgeTypes: () => [
{
prefix: EdgeAddress.fromParts(["bar"]),
forwardName: "bars",
backwardName: "is barred by",
},
],
}, },
{ {
name: () => "xox", name: () => "xox",
graph: () => { graph: () => {
throw new Error("unused"); throw new Error("unused");
}, },
renderer: () => ({ nodeDescription: (_unused_arg) => `xox node!`,
nodeDescription: (_unused_arg) => `xox node!`,
edgeVerb: (_unused_e, _unused_direction) => `xox'd`,
}),
nodePrefix: () => NodeAddress.fromParts(["xox"]), nodePrefix: () => NodeAddress.fromParts(["xox"]),
edgePrefix: () => EdgeAddress.fromParts(["xox"]), edgePrefix: () => EdgeAddress.fromParts(["xox"]),
nodeTypes: () => [], nodeTypes: () => [],
edgeTypes: () => [],
}, },
{ {
name: () => "unused", name: () => "unused",
graph: () => { graph: () => {
throw new Error("unused"); throw new Error("unused");
}, },
renderer: () => { nodeDescription: () => {
throw new Error("Impossible!"); throw new Error("Unused");
}, },
nodePrefix: () => NodeAddress.fromParts(["unused"]), nodePrefix: () => NodeAddress.fromParts(["unused"]),
edgePrefix: () => EdgeAddress.fromParts(["unused"]), edgePrefix: () => EdgeAddress.fromParts(["unused"]),
nodeTypes: () => [], nodeTypes: () => [],
edgeTypes: () => [],
}, },
]; ];

View File

@ -2,19 +2,19 @@
import type {Graph, NodeAddressT, EdgeAddressT} from "../core/graph"; import type {Graph, NodeAddressT, EdgeAddressT} from "../core/graph";
export interface Renderer {
nodeDescription(NodeAddressT): string;
edgeVerb(EdgeAddressT, "FORWARD" | "BACKWARD"): string;
}
export interface PluginAdapter { export interface PluginAdapter {
name(): string; name(): string;
graph(): Graph; graph(): Graph;
renderer(): Renderer;
nodePrefix(): NodeAddressT; nodePrefix(): NodeAddressT;
edgePrefix(): EdgeAddressT; edgePrefix(): EdgeAddressT;
nodeTypes(): Array<{| nodeTypes(): Array<{|
+name: string, +name: string,
+prefix: NodeAddressT, +prefix: NodeAddressT,
|}>; |}>;
nodeDescription(NodeAddressT): string;
edgeTypes(): Array<{|
+forwardName: string,
+backwardName: string,
+prefix: EdgeAddressT,
|}>;
} }

View File

@ -1,12 +1,9 @@
// @flow // @flow
import type { import type {PluginAdapter as IPluginAdapter} from "../../app/pluginAdapter";
PluginAdapter as IPluginAdapter,
Renderer as IRenderer,
} from "../../app/pluginAdapter";
import {Graph} from "../../core/graph"; 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, edgeVerb} from "./render"; import {description} from "./render";
export async function createPluginAdapter( export async function createPluginAdapter(
repoOwner: string, repoOwner: string,
@ -33,15 +30,18 @@ class PluginAdapter implements IPluginAdapter {
graph() { graph() {
return this._graph; return this._graph;
} }
renderer() {
return new Renderer();
}
nodePrefix() { nodePrefix() {
return N._Prefix.base; return N._Prefix.base;
} }
edgePrefix() { edgePrefix() {
return E._Prefix.base; return E._Prefix.base;
} }
nodeDescription(node) {
// This cast is unsound, and might throw at runtime, but won't have
// silent failures or cause problems down the road.
const address = N.fromRaw((node: any));
return description(address);
}
nodeTypes() { nodeTypes() {
return [ return [
{name: "Blob", prefix: N._Prefix.blob}, {name: "Blob", prefix: N._Prefix.blob},
@ -50,16 +50,33 @@ class PluginAdapter implements IPluginAdapter {
{name: "Tree entry", prefix: N._Prefix.treeEntry}, {name: "Tree entry", prefix: N._Prefix.treeEntry},
]; ];
} }
} edgeTypes() {
return [
class Renderer implements IRenderer { {
nodeDescription(node) { forwardName: "has tree",
// This cast is unsound, and might throw at runtime, but won't have backwardName: "owned by",
// silent failures or cause problems down the road. prefix: E._Prefix.hasTree,
const address = N.fromRaw((node: any)); },
return description(address); {
} forwardName: "has parent",
edgeVerb(edgeAddress, direction) { backwardName: "is parent of",
return edgeVerb(E.fromRaw((edgeAddress: any)), direction); prefix: E._Prefix.hasParent,
},
{
forwardName: "includes",
backwardName: "is included by",
prefix: E._Prefix.includes,
},
{
forwardName: "evolves to",
backwardName: "evolves from",
prefix: E._Prefix.becomes,
},
{
forwardName: "has contents",
backwardName: "is contents of",
prefix: E._Prefix.hasContents,
},
];
} }
} }

View File

@ -1,7 +1,6 @@
// @flow // @flow
import * as N from "./nodes"; import * as N from "./nodes";
import * as E from "./edges";
export function description(address: N.StructuredAddress) { export function description(address: N.StructuredAddress) {
switch (address.type) { switch (address.type) {
@ -19,24 +18,3 @@ export function description(address: N.StructuredAddress) {
throw new Error(`unknown type: ${(address.type: empty)}`); throw new Error(`unknown type: ${(address.type: empty)}`);
} }
} }
export function edgeVerb(
address: E.StructuredAddress,
direction: "FORWARD" | "BACKWARD"
) {
const forward = direction === "FORWARD";
switch (address.type) {
case "HAS_TREE":
return forward ? "has tree" : "owned by";
case "HAS_PARENT":
return forward ? "has parent" : "is parent of";
case "INCLUDES":
return forward ? "includes" : "is included by";
case "BECOMES":
return forward ? "evolves to" : "evolves from";
case "HAS_CONTENTS":
return forward ? "has contents" : "is contents of";
default:
throw new Error(`unknown type: ${(address.type: empty)}`);
}
}

View File

@ -1,14 +1,11 @@
// @flow // @flow
import type { import type {PluginAdapter as IPluginAdapter} from "../../app/pluginAdapter";
PluginAdapter as IPluginAdapter,
Renderer as IRenderer,
} from "../../app/pluginAdapter";
import {type Graph, NodeAddress} from "../../core/graph"; import {type Graph, NodeAddress} from "../../core/graph";
import {createGraph} from "./createGraph"; import {createGraph} from "./createGraph";
import * as N from "./nodes"; 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, edgeVerb} from "./render"; import {description} from "./render";
export async function createPluginAdapter( export async function createPluginAdapter(
repoOwner: string, repoOwner: string,
@ -35,12 +32,19 @@ class PluginAdapter implements IPluginAdapter {
name() { name() {
return "GitHub"; return "GitHub";
} }
nodeDescription(node) {
// This cast is unsound, and might throw at runtime, but won't have
// silent failures or cause problems down the road.
const address = N.fromRaw((node: any));
const entity = this._view.entity(address);
if (entity == null) {
throw new Error(`unknown entity: ${NodeAddress.toString(node)}`);
}
return description(entity);
}
graph() { graph() {
return this._graph; return this._graph;
} }
renderer() {
return new Renderer(this._view);
}
nodePrefix() { nodePrefix() {
return N._Prefix.base; return N._Prefix.base;
} }
@ -57,24 +61,28 @@ class PluginAdapter implements IPluginAdapter {
{name: "User", prefix: N._Prefix.userlike}, {name: "User", prefix: N._Prefix.userlike},
]; ];
} }
} edgeTypes() {
return [
class Renderer implements IRenderer { {
+_view: RelationalView; forwardName: "authors",
constructor(view) { backwardName: "is authored by",
this._view = view; prefix: E._Prefix.authors,
} },
nodeDescription(node) { {
// This cast is unsound, and might throw at runtime, but won't have forwardName: "has parent",
// silent failures or cause problems down the road. backwardName: "has child",
const address = N.fromRaw((node: any)); prefix: E._Prefix.hasParent,
const entity = this._view.entity(address); },
if (entity == null) { {
throw new Error(`unknown entity: ${NodeAddress.toString(node)}`); forwardName: "merges",
} backwardName: "is merged by",
return description(entity); prefix: E._Prefix.mergedAs,
} },
edgeVerb(edgeAddress, direction) { {
return edgeVerb(E.fromRaw((edgeAddress: any)), direction); forwardName: "references",
backwardName: "is referenced by",
prefix: E._Prefix.references,
},
];
} }
} }

View File

@ -1,7 +1,6 @@
// @flow // @flow
import * as R from "./relationalView"; import * as R from "./relationalView";
import * as E from "./edges";
export function description(e: R.Entity) { export function description(e: R.Entity) {
const withAuthors = (x: R.AuthoredEntity) => { const withAuthors = (x: R.AuthoredEntity) => {
@ -25,22 +24,3 @@ export function description(e: R.Entity) {
}; };
return R.match(handlers, e); return R.match(handlers, e);
} }
export function edgeVerb(
e: E.StructuredAddress,
direction: "FORWARD" | "BACKWARD"
) {
const forward = direction === "FORWARD";
switch (e.type) {
case "AUTHORS":
return forward ? "authors" : "is authored by";
case "MERGED_AS":
return forward ? "merges" : "is merged by";
case "HAS_PARENT":
return forward ? "has parent" : "has child";
case "REFERENCES":
return forward ? "references" : "is referenced by";
default:
throw new Error(`Unexpected type ${(e.type: empty)}`);
}
}