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

View File

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

View File

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

View File

@ -1,12 +1,9 @@
// @flow
import type {
PluginAdapter as IPluginAdapter,
Renderer as IRenderer,
} from "../../app/pluginAdapter";
import type {PluginAdapter as IPluginAdapter} from "../../app/pluginAdapter";
import {Graph} from "../../core/graph";
import * as N from "./nodes";
import * as E from "./edges";
import {description, edgeVerb} from "./render";
import {description} from "./render";
export async function createPluginAdapter(
repoOwner: string,
@ -33,15 +30,18 @@ class PluginAdapter implements IPluginAdapter {
graph() {
return this._graph;
}
renderer() {
return new Renderer();
}
nodePrefix() {
return N._Prefix.base;
}
edgePrefix() {
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() {
return [
{name: "Blob", prefix: N._Prefix.blob},
@ -50,16 +50,33 @@ class PluginAdapter implements IPluginAdapter {
{name: "Tree entry", prefix: N._Prefix.treeEntry},
];
}
}
class Renderer implements IRenderer {
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);
}
edgeVerb(edgeAddress, direction) {
return edgeVerb(E.fromRaw((edgeAddress: any)), direction);
edgeTypes() {
return [
{
forwardName: "has tree",
backwardName: "owned by",
prefix: E._Prefix.hasTree,
},
{
forwardName: "has parent",
backwardName: "is parent of",
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
import * as N from "./nodes";
import * as E from "./edges";
export function description(address: N.StructuredAddress) {
switch (address.type) {
@ -19,24 +18,3 @@ export function description(address: N.StructuredAddress) {
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
import type {
PluginAdapter as IPluginAdapter,
Renderer as IRenderer,
} from "../../app/pluginAdapter";
import type {PluginAdapter as IPluginAdapter} from "../../app/pluginAdapter";
import {type Graph, NodeAddress} from "../../core/graph";
import {createGraph} from "./createGraph";
import * as N from "./nodes";
import * as E from "./edges";
import {RelationalView} from "./relationalView";
import {description, edgeVerb} from "./render";
import {description} from "./render";
export async function createPluginAdapter(
repoOwner: string,
@ -35,12 +32,19 @@ class PluginAdapter implements IPluginAdapter {
name() {
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() {
return this._graph;
}
renderer() {
return new Renderer(this._view);
}
nodePrefix() {
return N._Prefix.base;
}
@ -57,24 +61,28 @@ class PluginAdapter implements IPluginAdapter {
{name: "User", prefix: N._Prefix.userlike},
];
}
}
class Renderer implements IRenderer {
+_view: RelationalView;
constructor(view) {
this._view = view;
}
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);
}
edgeVerb(edgeAddress, direction) {
return edgeVerb(E.fromRaw((edgeAddress: any)), direction);
edgeTypes() {
return [
{
forwardName: "authors",
backwardName: "is authored by",
prefix: E._Prefix.authors,
},
{
forwardName: "has parent",
backwardName: "has child",
prefix: E._Prefix.hasParent,
},
{
forwardName: "merges",
backwardName: "is merged by",
prefix: E._Prefix.mergedAs,
},
{
forwardName: "references",
backwardName: "is referenced by",
prefix: E._Prefix.references,
},
];
}
}

View File

@ -1,7 +1,6 @@
// @flow
import * as R from "./relationalView";
import * as E from "./edges";
export function description(e: R.Entity) {
const withAuthors = (x: R.AuthoredEntity) => {
@ -25,22 +24,3 @@ export function description(e: R.Entity) {
};
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)}`);
}
}