Remove the artifact plugin (#303)
Given that we are undergoing a major world-changing refactor (#190), all outstanding code needs to be refactored to use the new conventions. We don't actually use the Artifact Plugin yet, and reading the code, it needs non-trivial rewrites to be in sync with the new world. Rather than maintain it now, I am deleting it; we can regain the context when the time is ripe to setup and integrate the plugin. Test plan: Travis passes. `yarn start` produces no references to the artifact editor.
This commit is contained in:
parent
f0fcf02791
commit
c68b78f959
|
@ -3,12 +3,10 @@
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import {BrowserRouter as Router, Route, NavLink} from "react-router-dom";
|
import {BrowserRouter as Router, Route, NavLink} from "react-router-dom";
|
||||||
|
|
||||||
import ArtifactEditor from "../plugins/artifact/editor/App";
|
|
||||||
import CredExplorer from "./credExplorer/App";
|
import CredExplorer from "./credExplorer/App";
|
||||||
|
|
||||||
export default class App extends React.Component<{}> {
|
export default class App extends React.Component<{}> {
|
||||||
render() {
|
render() {
|
||||||
const ARTIFACT_EDITOR_ROUTE = "/plugins/artifact/editor";
|
|
||||||
const CRED_EXPLORER_ROUTE = "/explorer";
|
const CRED_EXPLORER_ROUTE = "/explorer";
|
||||||
return (
|
return (
|
||||||
<Router>
|
<Router>
|
||||||
|
@ -21,16 +19,12 @@ export default class App extends React.Component<{}> {
|
||||||
<li>
|
<li>
|
||||||
<NavLink to={CRED_EXPLORER_ROUTE}>Cred Explorer</NavLink>
|
<NavLink to={CRED_EXPLORER_ROUTE}>Cred Explorer</NavLink>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
|
||||||
<NavLink to={ARTIFACT_EDITOR_ROUTE}>Artifact Editor</NavLink>
|
|
||||||
</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
<Route exact path="/" component={Home} />
|
<Route exact path="/" component={Home} />
|
||||||
<Route path={CRED_EXPLORER_ROUTE} component={CredExplorer} />
|
<Route path={CRED_EXPLORER_ROUTE} component={CredExplorer} />
|
||||||
<Route path={ARTIFACT_EDITOR_ROUTE} component={ArtifactEditor} />
|
|
||||||
</div>
|
</div>
|
||||||
</Router>
|
</Router>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import type {Address} from "../../core/address";
|
|
||||||
import type {Graph} from "../../core/graph";
|
|
||||||
|
|
||||||
export const ARTIFACT_PLUGIN_NAME = "sourcecred/artifact-beta";
|
|
||||||
|
|
||||||
export const ARTIFACT_NODE_TYPE = "ARTIFACT";
|
|
||||||
export type ArtifactNodePayload = {|
|
|
||||||
+name: string,
|
|
||||||
+description: string,
|
|
||||||
|};
|
|
||||||
|
|
||||||
export type NodePayload = ArtifactNodePayload;
|
|
||||||
|
|
||||||
export const INCLUDES_EDGE_TYPE = "INCLUDES";
|
|
||||||
export type IncludesEdgePayload = {|
|
|
||||||
+weight: number, // non-negative
|
|
||||||
|};
|
|
||||||
|
|
||||||
export type EdgePayload = IncludesEdgePayload;
|
|
||||||
|
|
||||||
const NON_SLUG_CHARACTER: RegExp = /[^a-z]/g;
|
|
||||||
|
|
||||||
export function artifactAddress(
|
|
||||||
graph: Graph,
|
|
||||||
repoOwner: string,
|
|
||||||
repoName: string,
|
|
||||||
artifactName: string
|
|
||||||
): Address {
|
|
||||||
const baseName = artifactName.toLowerCase().replace(NON_SLUG_CHARACTER, "-");
|
|
||||||
const baseId = `${repoOwner}/${repoName}/${baseName}`;
|
|
||||||
function address(id) {
|
|
||||||
return {
|
|
||||||
pluginName: ARTIFACT_PLUGIN_NAME,
|
|
||||||
id,
|
|
||||||
type: ARTIFACT_NODE_TYPE,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
let id = baseId;
|
|
||||||
for (let i = 0; graph.node(address(id)) != null; i++) {
|
|
||||||
id = baseId + "-" + i;
|
|
||||||
}
|
|
||||||
return address(id);
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import {Graph} from "../../core/graph";
|
|
||||||
import {artifactAddress} from "./artifactPlugin";
|
|
||||||
|
|
||||||
describe("artifactPlugin", () => {
|
|
||||||
describe("artifactAddress", () => {
|
|
||||||
it("repositoryName included in id", () => {
|
|
||||||
const a = artifactAddress(
|
|
||||||
new Graph(),
|
|
||||||
"not-sourcecred",
|
|
||||||
"not-artifact-plugin",
|
|
||||||
"Sample artifact!"
|
|
||||||
);
|
|
||||||
expect(a.id.startsWith("not-sourcecred/not-artifact-plugin")).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("slugifies the artifact name", () => {
|
|
||||||
const a = artifactAddress(
|
|
||||||
new Graph(),
|
|
||||||
"not-sourcecred",
|
|
||||||
"not-artifact-plugin",
|
|
||||||
"Sample artifact!"
|
|
||||||
);
|
|
||||||
expect(a.id).toEqual(
|
|
||||||
"not-sourcecred/not-artifact-plugin/sample-artifact-"
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("resolves collisions", () => {
|
|
||||||
const g = new Graph();
|
|
||||||
const ids = [];
|
|
||||||
for (let i = 0; i < 3; i++) {
|
|
||||||
const a = artifactAddress(
|
|
||||||
g,
|
|
||||||
"not-sourcecred",
|
|
||||||
"not-artifact-plugin",
|
|
||||||
"Sample artifact!"
|
|
||||||
);
|
|
||||||
ids.push(a.id);
|
|
||||||
g.addNode({
|
|
||||||
address: a,
|
|
||||||
payload: {name: "Sample artifact!", description: ""},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
expect(ids).toEqual([
|
|
||||||
"not-sourcecred/not-artifact-plugin/sample-artifact-",
|
|
||||||
"not-sourcecred/not-artifact-plugin/sample-artifact--0",
|
|
||||||
"not-sourcecred/not-artifact-plugin/sample-artifact--1",
|
|
||||||
]);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,72 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import {StyleSheet, css} from "aphrodite/no-important";
|
|
||||||
|
|
||||||
import "./pluginAdapter";
|
|
||||||
|
|
||||||
import type {Graph, Node} from "../../../core/graph";
|
|
||||||
import type {NodePayload as ArtifactNodePayload} from "../artifactPlugin";
|
|
||||||
import type {Settings} from "./SettingsConfig";
|
|
||||||
import {ArtifactGraphEditor} from "./ArtifactGraphEditor";
|
|
||||||
import {ContributionList} from "./ContributionList";
|
|
||||||
import {GithubGraphFetcher} from "./GithubGraphFetcher";
|
|
||||||
import {SettingsConfig, defaultSettings} from "./SettingsConfig";
|
|
||||||
import standardAdapterSet from "./standardAdapterSet";
|
|
||||||
|
|
||||||
type Props = {};
|
|
||||||
type State = {
|
|
||||||
artifacts: Node<ArtifactNodePayload>[],
|
|
||||||
githubGraph: ?Graph,
|
|
||||||
artifactGraph: ?Graph,
|
|
||||||
settings: Settings,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default class App extends React.Component<Props, State> {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.state = {
|
|
||||||
artifacts: [],
|
|
||||||
githubGraph: null,
|
|
||||||
artifactGraph: null,
|
|
||||||
settings: defaultSettings(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<header className={css(styles.header)}>
|
|
||||||
<h1>Artifact editor</h1>
|
|
||||||
</header>
|
|
||||||
<SettingsConfig
|
|
||||||
onChange={(settings) => {
|
|
||||||
this.setState({settings});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<GithubGraphFetcher
|
|
||||||
settings={this.state.settings}
|
|
||||||
onCreateGraph={(githubGraph) => {
|
|
||||||
this.setState({githubGraph});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ArtifactGraphEditor
|
|
||||||
settings={this.state.settings}
|
|
||||||
onChange={(artifactGraph) => {
|
|
||||||
this.setState({artifactGraph});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ContributionList
|
|
||||||
graph={this.state.githubGraph}
|
|
||||||
adapters={standardAdapterSet}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
|
||||||
header: {
|
|
||||||
color: "#f0f",
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -1,13 +0,0 @@
|
||||||
// @flow
|
|
||||||
import React from "react";
|
|
||||||
import ReactDOM from "react-dom";
|
|
||||||
import App from "./App";
|
|
||||||
|
|
||||||
require("../../../app/testUtil").configureAphrodite();
|
|
||||||
|
|
||||||
// Check that PropTypes check out.
|
|
||||||
it("renders without crashing", () => {
|
|
||||||
const div = document.createElement("div");
|
|
||||||
ReactDOM.render(<App />, div);
|
|
||||||
ReactDOM.unmountComponentAtNode(div);
|
|
||||||
});
|
|
|
@ -1,124 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import type {Node} from "../../../core/graph";
|
|
||||||
import type {Settings} from "./SettingsConfig";
|
|
||||||
import type {NodePayload} from "../artifactPlugin";
|
|
||||||
import {Graph} from "../../../core/graph";
|
|
||||||
import {artifactAddress} from "../artifactPlugin";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
settings: Settings,
|
|
||||||
onChange: (Graph) => void,
|
|
||||||
};
|
|
||||||
type State = {
|
|
||||||
graph: Graph,
|
|
||||||
artifactInProgressName: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
export class ArtifactGraphEditor extends React.Component<Props, State> {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.state = {
|
|
||||||
graph: new Graph(),
|
|
||||||
artifactInProgressName: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.props.onChange(this.state.graph);
|
|
||||||
}
|
|
||||||
|
|
||||||
addArtifact(name: string): void {
|
|
||||||
this.setState(
|
|
||||||
(state) => {
|
|
||||||
const node: Node<NodePayload> = {
|
|
||||||
address: artifactAddress(
|
|
||||||
state.graph,
|
|
||||||
this.props.settings.repoOwner,
|
|
||||||
this.props.settings.repoName,
|
|
||||||
name
|
|
||||||
),
|
|
||||||
payload: {name, description: ""},
|
|
||||||
};
|
|
||||||
return {graph: state.graph.copy().addNode(node)};
|
|
||||||
},
|
|
||||||
() => {
|
|
||||||
this.props.onChange(this.state.graph);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
updateArtifactDescription(
|
|
||||||
oldArtifactNode: Node<NodePayload>,
|
|
||||||
newDescription: string
|
|
||||||
): void {
|
|
||||||
this.setState(
|
|
||||||
(state) => ({
|
|
||||||
graph: state.graph
|
|
||||||
.copy()
|
|
||||||
.removeNode(oldArtifactNode.address)
|
|
||||||
.addNode({
|
|
||||||
address: oldArtifactNode.address,
|
|
||||||
payload: {
|
|
||||||
name: oldArtifactNode.payload.name,
|
|
||||||
description: newDescription,
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
() => {
|
|
||||||
this.props.onChange(this.state.graph);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h2>Artifacts</h2>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Artifact</th>
|
|
||||||
<th>Description</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{this.state.graph.nodes().map((x) => (
|
|
||||||
<tr key={x.address.id}>
|
|
||||||
<td>{x.payload.name}</td>
|
|
||||||
<td>
|
|
||||||
<textarea
|
|
||||||
key={`description-${x.address.id}`}
|
|
||||||
value={x.payload.description}
|
|
||||||
onChange={(e) => {
|
|
||||||
this.updateArtifactDescription(x, e.target.value);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
))}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
<input
|
|
||||||
value={this.state.artifactInProgressName}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = e.target.value;
|
|
||||||
this.setState({
|
|
||||||
artifactInProgressName: value,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
onClick={() => {
|
|
||||||
this.addArtifact(this.state.artifactInProgressName);
|
|
||||||
this.setState({artifactInProgressName: ""});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
Add artifact
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,113 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import {shallow} from "enzyme";
|
|
||||||
|
|
||||||
import {Graph} from "../../../core/graph";
|
|
||||||
import {ArtifactGraphEditor} from "./ArtifactGraphEditor";
|
|
||||||
import {artifactAddress} from "../artifactPlugin";
|
|
||||||
|
|
||||||
require("../../../app/testUtil").configureAphrodite();
|
|
||||||
require("../../../app/testUtil").configureEnzyme();
|
|
||||||
|
|
||||||
describe("ArtifactGraphEditor", () => {
|
|
||||||
function createComponent(onChange) {
|
|
||||||
return (
|
|
||||||
<ArtifactGraphEditor
|
|
||||||
settings={{
|
|
||||||
githubApiToken: "123youdontneedme",
|
|
||||||
repoOwner: "sourcecred",
|
|
||||||
repoName: "artifact-tests",
|
|
||||||
}}
|
|
||||||
onChange={onChange}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
it("invokes its callback after mounting, not construction", () => {
|
|
||||||
const onChange = jest.fn();
|
|
||||||
const component = createComponent(onChange);
|
|
||||||
expect(onChange).not.toHaveBeenCalled();
|
|
||||||
shallow(component);
|
|
||||||
expect(onChange).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("adds an artifact to the list", () => {
|
|
||||||
const onChange = jest.fn();
|
|
||||||
const element = shallow(createComponent(onChange));
|
|
||||||
expect(onChange).toHaveBeenLastCalledWith(new Graph());
|
|
||||||
element
|
|
||||||
.find("input")
|
|
||||||
.simulate("change", {target: {value: "Root artifact!"}});
|
|
||||||
expect(onChange).toHaveBeenCalledTimes(1);
|
|
||||||
element.find("button").simulate("click");
|
|
||||||
expect(
|
|
||||||
element.find("td").filterWhere((x) => x.text() === "Root artifact!")
|
|
||||||
).toHaveLength(1);
|
|
||||||
expect(onChange).toHaveBeenCalledTimes(2);
|
|
||||||
expect(onChange).toHaveBeenLastCalledWith(
|
|
||||||
new Graph().addNode({
|
|
||||||
address: artifactAddress(
|
|
||||||
new Graph(),
|
|
||||||
"sourcecred",
|
|
||||||
"artifact-tests",
|
|
||||||
"Root artifact!"
|
|
||||||
),
|
|
||||||
payload: {
|
|
||||||
name: "Root artifact!",
|
|
||||||
description: "",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("modifies an artifact's description", () => {
|
|
||||||
const onChange = jest.fn();
|
|
||||||
const element = shallow(createComponent(onChange));
|
|
||||||
element
|
|
||||||
.find("input")
|
|
||||||
.simulate("change", {target: {value: "Root artifact!"}});
|
|
||||||
element.find("button").simulate("click");
|
|
||||||
element
|
|
||||||
.find("tr textarea")
|
|
||||||
.simulate("change", {target: {value: "for garlic, carrots, etc."}});
|
|
||||||
expect(onChange).toHaveBeenLastCalledWith(
|
|
||||||
new Graph().addNode({
|
|
||||||
address: artifactAddress(
|
|
||||||
new Graph(),
|
|
||||||
"sourcecred",
|
|
||||||
"artifact-tests",
|
|
||||||
"Root artifact!"
|
|
||||||
),
|
|
||||||
payload: {
|
|
||||||
name: "Root artifact!",
|
|
||||||
description: "for garlic, carrots, etc.",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("does not mutate the graph passed to its callback", () => {
|
|
||||||
const onChange = jest.fn();
|
|
||||||
const element = shallow(createComponent(onChange));
|
|
||||||
const g1 = onChange.mock.calls[0][0];
|
|
||||||
const g1Copy = g1.copy();
|
|
||||||
element
|
|
||||||
.find("input")
|
|
||||||
.simulate("change", {target: {value: "Root artifact!"}});
|
|
||||||
element.find("button").simulate("click");
|
|
||||||
expect(onChange).toHaveBeenCalledTimes(2);
|
|
||||||
const g2 = onChange.mock.calls[1][0];
|
|
||||||
const g2Copy = g2.copy();
|
|
||||||
expect(g1.equals(g1Copy)).toBe(true);
|
|
||||||
expect(g1.equals(g2)).toBe(false);
|
|
||||||
element
|
|
||||||
.find("tr textarea")
|
|
||||||
.simulate("change", {target: {value: "for garlic, carrots, etc."}});
|
|
||||||
expect(onChange).toHaveBeenCalledTimes(3);
|
|
||||||
const g3 = onChange.mock.calls[2][0];
|
|
||||||
expect(g1.equals(g1Copy)).toBe(true);
|
|
||||||
expect(g2.equals(g2Copy)).toBe(true);
|
|
||||||
expect(g3.equals(g2)).toBe(false);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,141 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import type {Node} from "../../../core/graph";
|
|
||||||
import {AdapterSet} from "./adapterSet";
|
|
||||||
import {Graph} from "../../../core/graph";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
graph: ?Graph,
|
|
||||||
adapters: AdapterSet,
|
|
||||||
};
|
|
||||||
type State = {
|
|
||||||
typeFilter: ?{|
|
|
||||||
+pluginName: string,
|
|
||||||
+type: string,
|
|
||||||
|},
|
|
||||||
};
|
|
||||||
|
|
||||||
export class ContributionList extends React.Component<Props, State> {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.state = {
|
|
||||||
typeFilter: null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h2>Contributions</h2>
|
|
||||||
{this.renderFilterSelect()}
|
|
||||||
{this.renderTable()}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderFilterSelect() {
|
|
||||||
if (this.props.graph == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const graph: Graph = this.props.graph;
|
|
||||||
const typesByPlugin: {[pluginName: string]: Set<string>} = {};
|
|
||||||
graph.nodes().forEach((node) => {
|
|
||||||
const adapter = this.props.adapters.getAdapter(node);
|
|
||||||
if (adapter == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!typesByPlugin[adapter.pluginName]) {
|
|
||||||
typesByPlugin[adapter.pluginName] = new Set();
|
|
||||||
}
|
|
||||||
typesByPlugin[adapter.pluginName].add(node.address.type);
|
|
||||||
});
|
|
||||||
function optionGroup(pluginName: string) {
|
|
||||||
const header = (
|
|
||||||
<option key={pluginName} disabled style={{fontWeight: "bold"}}>
|
|
||||||
{pluginName}
|
|
||||||
</option>
|
|
||||||
);
|
|
||||||
const entries = Array.from(typesByPlugin[pluginName])
|
|
||||||
.sort()
|
|
||||||
.map((type) => (
|
|
||||||
<option key={type} value={JSON.stringify({pluginName, type})}>
|
|
||||||
{"\u2003" + type}
|
|
||||||
</option>
|
|
||||||
));
|
|
||||||
return [header, ...entries];
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<label>
|
|
||||||
Filter by contribution type:{" "}
|
|
||||||
<select
|
|
||||||
value={JSON.stringify(this.state.typeFilter)}
|
|
||||||
onChange={(e) => {
|
|
||||||
this.setState({typeFilter: JSON.parse(e.target.value)});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<option value={JSON.stringify(null)}>Show all</option>
|
|
||||||
{Object.keys(typesByPlugin)
|
|
||||||
.sort()
|
|
||||||
.map(optionGroup)}
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderTable() {
|
|
||||||
if (this.props.graph == null) {
|
|
||||||
return <div>(no graph)</div>;
|
|
||||||
} else {
|
|
||||||
const graph: Graph = this.props.graph;
|
|
||||||
const {typeFilter} = this.state;
|
|
||||||
const shouldDisplay: (node: Node<any>) => boolean = typeFilter
|
|
||||||
? (node) => {
|
|
||||||
const adapter = this.props.adapters.getAdapter(node);
|
|
||||||
return (
|
|
||||||
!!adapter &&
|
|
||||||
adapter.pluginName === typeFilter.pluginName &&
|
|
||||||
node.address.type === typeFilter.type
|
|
||||||
);
|
|
||||||
}
|
|
||||||
: (_) => true;
|
|
||||||
return (
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Title</th>
|
|
||||||
<th>Artifact</th>
|
|
||||||
<th>Weight</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
{this.props.graph.nodes().map((node) => {
|
|
||||||
if (!shouldDisplay(node)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
const adapter = this.props.adapters.getAdapter(node);
|
|
||||||
if (adapter == null) {
|
|
||||||
return (
|
|
||||||
<tr key={JSON.stringify(node.address)}>
|
|
||||||
<td colSpan={3}>
|
|
||||||
<i>unknown</i> (plugin: {node.address.pluginName})
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return (
|
|
||||||
<tr key={JSON.stringify(node.address)}>
|
|
||||||
<td>{adapter.extractTitle(graph, node)}</td>
|
|
||||||
<td>[TODO]</td>
|
|
||||||
<td>[TODO]</td>
|
|
||||||
</tr>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,193 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import type {ReactWrapper} from "enzyme";
|
|
||||||
import React from "react";
|
|
||||||
import {shallow} from "enzyme";
|
|
||||||
import enzymeToJSON from "enzyme-to-json";
|
|
||||||
|
|
||||||
import type {Address} from "../../../core/address";
|
|
||||||
import type {Node} from "../../../core/graph";
|
|
||||||
import type {PluginAdapter} from "./pluginAdapter";
|
|
||||||
import {AdapterSet} from "./adapterSet";
|
|
||||||
import {ContributionList} from "./ContributionList";
|
|
||||||
import {Graph} from "../../../core/graph";
|
|
||||||
|
|
||||||
require("../../../app/testUtil").configureAphrodite();
|
|
||||||
require("../../../app/testUtil").configureEnzyme();
|
|
||||||
|
|
||||||
function createTestData(): * {
|
|
||||||
type PayloadA = number;
|
|
||||||
type PayloadB = boolean;
|
|
||||||
type PayloadC = string;
|
|
||||||
|
|
||||||
const PLUGIN_A = "sourcecred/example-plugin-a";
|
|
||||||
const PLUGIN_B = "sourcecred/example-plugin-b";
|
|
||||||
const PLUGIN_C = "sourcecred/example-plugin-c";
|
|
||||||
|
|
||||||
function makeAddress(
|
|
||||||
pluginName: typeof PLUGIN_A | typeof PLUGIN_B | typeof PLUGIN_C,
|
|
||||||
type: string,
|
|
||||||
id: string
|
|
||||||
): Address {
|
|
||||||
return {
|
|
||||||
pluginName,
|
|
||||||
id,
|
|
||||||
type,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const nodeA1 = () => ({
|
|
||||||
address: makeAddress(PLUGIN_A, "small", "one"),
|
|
||||||
payload: (111: PayloadA),
|
|
||||||
});
|
|
||||||
const nodeA2 = () => ({
|
|
||||||
address: makeAddress(PLUGIN_A, "small", "two"),
|
|
||||||
payload: (234: PayloadA),
|
|
||||||
});
|
|
||||||
const nodeA3 = () => ({
|
|
||||||
address: makeAddress(PLUGIN_A, "big", "three"),
|
|
||||||
payload: (616: PayloadA),
|
|
||||||
});
|
|
||||||
const nodeB4 = () => ({
|
|
||||||
address: makeAddress(PLUGIN_B, "very true", "four"),
|
|
||||||
payload: (true: PayloadB),
|
|
||||||
});
|
|
||||||
const nodeC5 = () => ({
|
|
||||||
address: makeAddress(PLUGIN_C, "ctype", "five"),
|
|
||||||
payload: ("I have no adapter :-(": PayloadC),
|
|
||||||
});
|
|
||||||
const edgeA1A2 = () => ({
|
|
||||||
address: makeAddress(PLUGIN_A, "atype", "one-to-two"),
|
|
||||||
payload: null,
|
|
||||||
src: nodeA1().address,
|
|
||||||
dst: nodeA2().address,
|
|
||||||
});
|
|
||||||
const edgeB4A3 = () => ({
|
|
||||||
address: makeAddress(PLUGIN_C, "ctype", "four-to-three"),
|
|
||||||
payload: null,
|
|
||||||
src: nodeB4().address,
|
|
||||||
dst: nodeA3().address,
|
|
||||||
});
|
|
||||||
|
|
||||||
const graph: () => Graph = () =>
|
|
||||||
new Graph()
|
|
||||||
.addNode(nodeA1())
|
|
||||||
.addNode(nodeA2())
|
|
||||||
.addNode(nodeA3())
|
|
||||||
.addNode(nodeB4())
|
|
||||||
.addNode(nodeC5())
|
|
||||||
.addEdge(edgeA1A2())
|
|
||||||
.addEdge(edgeB4A3());
|
|
||||||
|
|
||||||
const adapterA: () => PluginAdapter<PayloadA> = () => ({
|
|
||||||
pluginName: PLUGIN_A,
|
|
||||||
renderer: class RendererA extends React.Component<{
|
|
||||||
graph: Graph,
|
|
||||||
node: Node<PayloadA>,
|
|
||||||
}> {
|
|
||||||
render() {
|
|
||||||
const {graph, node} = this.props;
|
|
||||||
const neighborCount = graph.neighborhood(node.address, {
|
|
||||||
direction: "OUT",
|
|
||||||
}).length;
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
<tt>{node.address.id}</tt> has neighbor count{" "}
|
|
||||||
<strong>{neighborCount}</strong>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
extractTitle(graph: Graph, node: Node<PayloadA>) {
|
|
||||||
return `the number ${String(node.payload)}`;
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const adapterB: () => PluginAdapter<PayloadB> = () => ({
|
|
||||||
pluginName: PLUGIN_B,
|
|
||||||
renderer: class RendererB extends React.Component<{
|
|
||||||
graph: Graph,
|
|
||||||
node: Node<PayloadB>,
|
|
||||||
}> {
|
|
||||||
render() {
|
|
||||||
const {node} = this.props;
|
|
||||||
return (
|
|
||||||
<span>
|
|
||||||
Node <em>{node.address.id}</em>: <strong>{node.payload}</strong>
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
extractTitle(graph: Graph, node: Node<PayloadB>) {
|
|
||||||
return String(node.payload).toUpperCase() + "!";
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const adapters: () => AdapterSet = () => {
|
|
||||||
const result = new AdapterSet();
|
|
||||||
result.addAdapter(adapterA());
|
|
||||||
result.addAdapter(adapterB());
|
|
||||||
return result;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
PLUGIN_A,
|
|
||||||
PLUGIN_B,
|
|
||||||
PLUGIN_C,
|
|
||||||
nodeA1,
|
|
||||||
nodeA2,
|
|
||||||
nodeA3,
|
|
||||||
nodeB4,
|
|
||||||
nodeC5,
|
|
||||||
edgeA1A2,
|
|
||||||
edgeB4A3,
|
|
||||||
graph,
|
|
||||||
adapterA,
|
|
||||||
adapterB,
|
|
||||||
adapters,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
describe("ContributionList", () => {
|
|
||||||
// Render a contribution list with the above test data.
|
|
||||||
function render() {
|
|
||||||
const data = createTestData();
|
|
||||||
const result = shallow(
|
|
||||||
<ContributionList graph={data.graph()} adapters={data.adapters()} />
|
|
||||||
);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Select the unique <option> whose text matches the given patern.
|
|
||||||
function simulateSelect(container: ReactWrapper, pattern: RegExp): void {
|
|
||||||
const targetOption = container
|
|
||||||
.find("option")
|
|
||||||
.filterWhere((x) => pattern.test(x.text()));
|
|
||||||
expect(targetOption).toHaveLength(1);
|
|
||||||
container
|
|
||||||
.find("select")
|
|
||||||
.simulate("change", {target: {value: targetOption.prop("value")}});
|
|
||||||
}
|
|
||||||
|
|
||||||
it("renders some test data in the default state", () => {
|
|
||||||
const result = render();
|
|
||||||
expect(enzymeToJSON(result)).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("updates the node table when a filter is selected", () => {
|
|
||||||
const result = render();
|
|
||||||
simulateSelect(result, /small/);
|
|
||||||
expect(enzymeToJSON(result)).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
|
|
||||||
it("resets the node table when a filter is deselected", () => {
|
|
||||||
const result = render();
|
|
||||||
const originalHtml = result.html();
|
|
||||||
simulateSelect(result, /big/);
|
|
||||||
const intermediateHtml = result.html();
|
|
||||||
simulateSelect(result, /Show all/);
|
|
||||||
const finalHtml = result.html();
|
|
||||||
expect(finalHtml).toEqual(originalHtml);
|
|
||||||
expect(finalHtml).not.toEqual(intermediateHtml);
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,37 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import type {Graph} from "../../../core/graph";
|
|
||||||
import type {Settings} from "./SettingsConfig";
|
|
||||||
import fetchGithubRepo from "../../github/fetchGithubRepo";
|
|
||||||
import {parse} from "../../github/parser";
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
settings: Settings,
|
|
||||||
onCreateGraph: (graph: Graph) => void,
|
|
||||||
};
|
|
||||||
|
|
||||||
export class GithubGraphFetcher extends React.Component<Props> {
|
|
||||||
render() {
|
|
||||||
const {settings} = this.props;
|
|
||||||
const haveSettings =
|
|
||||||
!!settings.githubApiToken && !!settings.repoOwner && !!settings.repoName;
|
|
||||||
return (
|
|
||||||
<button onClick={() => this.fetchGraph()} disabled={!haveSettings}>
|
|
||||||
Fetch GitHub graph
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchGraph() {
|
|
||||||
const {repoOwner, repoName, githubApiToken} = this.props.settings;
|
|
||||||
fetchGithubRepo(repoOwner, repoName, githubApiToken)
|
|
||||||
.then((json) => {
|
|
||||||
return Promise.resolve(parse(json));
|
|
||||||
})
|
|
||||||
.then((graph) => {
|
|
||||||
this.props.onCreateGraph(graph);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import LocalStore from "../../../app/LocalStore";
|
|
||||||
|
|
||||||
export default new LocalStore({version: "1", keyPrefix: "artifact-editor"});
|
|
|
@ -1,89 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import LocalStore from "./LocalStore";
|
|
||||||
|
|
||||||
export type Settings = {
|
|
||||||
githubApiToken: string,
|
|
||||||
repoOwner: string,
|
|
||||||
repoName: string,
|
|
||||||
};
|
|
||||||
|
|
||||||
type Props = {
|
|
||||||
onChange: (Settings) => void,
|
|
||||||
};
|
|
||||||
type State = Settings;
|
|
||||||
|
|
||||||
const LOCAL_STORE_SETTINGS_KEY = "SettingsConfig.settings";
|
|
||||||
|
|
||||||
export function defaultSettings() {
|
|
||||||
return {
|
|
||||||
githubApiToken: "",
|
|
||||||
repoOwner: "",
|
|
||||||
repoName: "",
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export class SettingsConfig extends React.Component<Props, State> {
|
|
||||||
constructor() {
|
|
||||||
super();
|
|
||||||
this.state = defaultSettings();
|
|
||||||
}
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
this.setState(LocalStore.get(LOCAL_STORE_SETTINGS_KEY, this.state), () => {
|
|
||||||
this.props.onChange(this.state);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<label>
|
|
||||||
API token{" "}
|
|
||||||
<input
|
|
||||||
value={this.state.githubApiToken}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = e.target.value;
|
|
||||||
this.setState(
|
|
||||||
{githubApiToken: value},
|
|
||||||
this._updateSettings.bind(this)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<br />
|
|
||||||
<label>
|
|
||||||
Repository owner{" "}
|
|
||||||
<input
|
|
||||||
value={this.state.repoOwner}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = e.target.value;
|
|
||||||
this.setState(
|
|
||||||
{repoOwner: value},
|
|
||||||
this._updateSettings.bind(this)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
<br />
|
|
||||||
<label>
|
|
||||||
Repository name{" "}
|
|
||||||
<input
|
|
||||||
value={this.state.repoName}
|
|
||||||
onChange={(e) => {
|
|
||||||
const value = e.target.value;
|
|
||||||
this.setState({repoName: value}, this._updateSettings.bind(this));
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateSettings() {
|
|
||||||
LocalStore.set(LOCAL_STORE_SETTINGS_KEY, this.state);
|
|
||||||
this.props.onChange(this.state);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,251 +0,0 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`ContributionList renders some test data in the default state 1`] = `
|
|
||||||
<div>
|
|
||||||
<h2>
|
|
||||||
Contributions
|
|
||||||
</h2>
|
|
||||||
<label>
|
|
||||||
Filter by contribution type:
|
|
||||||
|
|
||||||
<select
|
|
||||||
onChange={[Function]}
|
|
||||||
value="null"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
value="null"
|
|
||||||
>
|
|
||||||
Show all
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
disabled={true}
|
|
||||||
key="sourcecred/example-plugin-a"
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"fontWeight": "bold",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
sourcecred/example-plugin-a
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
key="big"
|
|
||||||
value="{\\"pluginName\\":\\"sourcecred/example-plugin-a\\",\\"type\\":\\"big\\"}"
|
|
||||||
>
|
|
||||||
big
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
key="small"
|
|
||||||
value="{\\"pluginName\\":\\"sourcecred/example-plugin-a\\",\\"type\\":\\"small\\"}"
|
|
||||||
>
|
|
||||||
small
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
disabled={true}
|
|
||||||
key="sourcecred/example-plugin-b"
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"fontWeight": "bold",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
sourcecred/example-plugin-b
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
key="very true"
|
|
||||||
value="{\\"pluginName\\":\\"sourcecred/example-plugin-b\\",\\"type\\":\\"very true\\"}"
|
|
||||||
>
|
|
||||||
very true
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
Title
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
Artifact
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
Weight
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr
|
|
||||||
key="{\\"pluginName\\":\\"sourcecred/example-plugin-a\\",\\"id\\":\\"one\\",\\"type\\":\\"small\\"}"
|
|
||||||
>
|
|
||||||
<td>
|
|
||||||
the number 111
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
[TODO]
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
[TODO]
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr
|
|
||||||
key="{\\"pluginName\\":\\"sourcecred/example-plugin-a\\",\\"id\\":\\"two\\",\\"type\\":\\"small\\"}"
|
|
||||||
>
|
|
||||||
<td>
|
|
||||||
the number 234
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
[TODO]
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
[TODO]
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr
|
|
||||||
key="{\\"pluginName\\":\\"sourcecred/example-plugin-a\\",\\"id\\":\\"three\\",\\"type\\":\\"big\\"}"
|
|
||||||
>
|
|
||||||
<td>
|
|
||||||
the number 616
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
[TODO]
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
[TODO]
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr
|
|
||||||
key="{\\"pluginName\\":\\"sourcecred/example-plugin-b\\",\\"id\\":\\"four\\",\\"type\\":\\"very true\\"}"
|
|
||||||
>
|
|
||||||
<td>
|
|
||||||
TRUE!
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
[TODO]
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
[TODO]
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr
|
|
||||||
key="{\\"pluginName\\":\\"sourcecred/example-plugin-c\\",\\"id\\":\\"five\\",\\"type\\":\\"ctype\\"}"
|
|
||||||
>
|
|
||||||
<td
|
|
||||||
colSpan={3}
|
|
||||||
>
|
|
||||||
<i>
|
|
||||||
unknown
|
|
||||||
</i>
|
|
||||||
(plugin:
|
|
||||||
sourcecred/example-plugin-c
|
|
||||||
)
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
|
|
||||||
exports[`ContributionList updates the node table when a filter is selected 1`] = `
|
|
||||||
<div>
|
|
||||||
<h2>
|
|
||||||
Contributions
|
|
||||||
</h2>
|
|
||||||
<label>
|
|
||||||
Filter by contribution type:
|
|
||||||
|
|
||||||
<select
|
|
||||||
onChange={[Function]}
|
|
||||||
value="{\\"pluginName\\":\\"sourcecred/example-plugin-a\\",\\"type\\":\\"small\\"}"
|
|
||||||
>
|
|
||||||
<option
|
|
||||||
value="null"
|
|
||||||
>
|
|
||||||
Show all
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
disabled={true}
|
|
||||||
key="sourcecred/example-plugin-a"
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"fontWeight": "bold",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
sourcecred/example-plugin-a
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
key="big"
|
|
||||||
value="{\\"pluginName\\":\\"sourcecred/example-plugin-a\\",\\"type\\":\\"big\\"}"
|
|
||||||
>
|
|
||||||
big
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
key="small"
|
|
||||||
value="{\\"pluginName\\":\\"sourcecred/example-plugin-a\\",\\"type\\":\\"small\\"}"
|
|
||||||
>
|
|
||||||
small
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
disabled={true}
|
|
||||||
key="sourcecred/example-plugin-b"
|
|
||||||
style={
|
|
||||||
Object {
|
|
||||||
"fontWeight": "bold",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
>
|
|
||||||
sourcecred/example-plugin-b
|
|
||||||
</option>
|
|
||||||
<option
|
|
||||||
key="very true"
|
|
||||||
value="{\\"pluginName\\":\\"sourcecred/example-plugin-b\\",\\"type\\":\\"very true\\"}"
|
|
||||||
>
|
|
||||||
very true
|
|
||||||
</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>
|
|
||||||
Title
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
Artifact
|
|
||||||
</th>
|
|
||||||
<th>
|
|
||||||
Weight
|
|
||||||
</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr
|
|
||||||
key="{\\"pluginName\\":\\"sourcecred/example-plugin-a\\",\\"id\\":\\"one\\",\\"type\\":\\"small\\"}"
|
|
||||||
>
|
|
||||||
<td>
|
|
||||||
the number 111
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
[TODO]
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
[TODO]
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr
|
|
||||||
key="{\\"pluginName\\":\\"sourcecred/example-plugin-a\\",\\"id\\":\\"two\\",\\"type\\":\\"small\\"}"
|
|
||||||
>
|
|
||||||
<td>
|
|
||||||
the number 234
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
[TODO]
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
[TODO]
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
`;
|
|
|
@ -1,20 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import type {Node} from "../../../core/graph";
|
|
||||||
import type {PluginAdapter} from "./pluginAdapter";
|
|
||||||
|
|
||||||
export class AdapterSet {
|
|
||||||
adapters: {[pluginName: string]: PluginAdapter<any>};
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.adapters = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
addAdapter(adapter: PluginAdapter<any>): void {
|
|
||||||
this.adapters[adapter.pluginName] = adapter;
|
|
||||||
}
|
|
||||||
|
|
||||||
getAdapter<NP>(node: Node<NP>): ?PluginAdapter<NP> {
|
|
||||||
return this.adapters[node.address.pluginName];
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,420 +0,0 @@
|
||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`githubPluginAdapter operates on the example repo 1`] = `
|
|
||||||
Array [
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/decentralion",
|
|
||||||
"payload": Object {
|
|
||||||
"login": "decentralion",
|
|
||||||
"subtype": "USER",
|
|
||||||
"url": "https://github.com/decentralion",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
AUTHOR
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "decentralion",
|
|
||||||
"type": "AUTHOR",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github",
|
|
||||||
"payload": Object {
|
|
||||||
"name": "example-github",
|
|
||||||
"owner": "sourcecred",
|
|
||||||
"url": "https://github.com/sourcecred/example-github",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
REPOSITORY
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "sourcecred/example-github",
|
|
||||||
"type": "REPOSITORY",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/issues/1",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "This is just an example issue.",
|
|
||||||
"number": 1,
|
|
||||||
"title": "An example issue.",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/issues/1",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
ISSUE
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "#1: An example issue.",
|
|
||||||
"type": "ISSUE",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/issues/2",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "This issue references another issue, namely #1",
|
|
||||||
"number": 2,
|
|
||||||
"title": "A referencing issue.",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/issues/2",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
ISSUE
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "#2: A referencing issue.",
|
|
||||||
"type": "ISSUE",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/issues/2#issuecomment-373768703",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "It should also be possible to reference by exact url: https://github.com/sourcecred/example-github/issues/6",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/issues/2#issuecomment-373768703",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
COMMENT
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "comment on #2: A referencing issue.",
|
|
||||||
"type": "COMMENT",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/issues/2#issuecomment-373768850",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "We might also reference individual comments directly.
|
|
||||||
https://github.com/sourcecred/example-github/issues/6#issuecomment-373768538",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/issues/2#issuecomment-373768850",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
COMMENT
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "comment on #2: A referencing issue.",
|
|
||||||
"type": "COMMENT",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/issues/2#issuecomment-385576185",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "Here's a PR by direct url: https://github.com/sourcecred/example-github/pull/5",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/issues/2#issuecomment-385576185",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
COMMENT
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "comment on #2: A referencing issue.",
|
|
||||||
"type": "COMMENT",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/issues/2#issuecomment-385576220",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "a PR review by url: https://github.com/sourcecred/example-github/pull/5#pullrequestreview-100313899",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/issues/2#issuecomment-385576220",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
COMMENT
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "comment on #2: A referencing issue.",
|
|
||||||
"type": "COMMENT",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/issues/2#issuecomment-385576248",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "a PR Review Comment by url: https://github.com/sourcecred/example-github/pull/5#discussion_r171460198",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/issues/2#issuecomment-385576248",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
COMMENT
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "comment on #2: A referencing issue.",
|
|
||||||
"type": "COMMENT",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/issues/2#issuecomment-385576273",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "a user by url: https://github.com/wchargin",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/issues/2#issuecomment-385576273",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
COMMENT
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "comment on #2: A referencing issue.",
|
|
||||||
"type": "COMMENT",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/issues/2#issuecomment-385576920",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "Here are several references:
|
|
||||||
#1
|
|
||||||
#2
|
|
||||||
#3
|
|
||||||
|
|
||||||
https://github.com/sourcecred/example-github/pull/5#discussion_r171460198
|
|
||||||
https://github.com/sourcecred/example-github/pull/5#pullrequestreview-100313899
|
|
||||||
",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/issues/2#issuecomment-385576920",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
COMMENT
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "comment on #2: A referencing issue.",
|
|
||||||
"type": "COMMENT",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/issues/2#issuecomment-385576936",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "This comment has no references.",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/issues/2#issuecomment-385576936",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
COMMENT
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "comment on #2: A referencing issue.",
|
|
||||||
"type": "COMMENT",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/issues/4",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "Alas, its life as an open issue had only just begun.",
|
|
||||||
"number": 4,
|
|
||||||
"title": "A closed pull request",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/issues/4",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
ISSUE
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "#4: A closed pull request",
|
|
||||||
"type": "ISSUE",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/issues/6",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "This issue shall shortly have a few comments.",
|
|
||||||
"number": 6,
|
|
||||||
"title": "An issue with comments",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/issues/6",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
ISSUE
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "#6: An issue with comments",
|
|
||||||
"type": "ISSUE",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/issues/6#issuecomment-373768442",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "A wild COMMENT appeared!",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/issues/6#issuecomment-373768442",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
COMMENT
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "comment on #6: An issue with comments",
|
|
||||||
"type": "COMMENT",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/issues/6#issuecomment-373768538",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "And the maintainer said, \\"Let there be comments!\\"",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/issues/6#issuecomment-373768538",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
COMMENT
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "comment on #6: An issue with comments",
|
|
||||||
"type": "COMMENT",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/issues/6#issuecomment-385223316",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "This comment references an #2, which itself references an issue. This comment is thus allows us to test that in-references are not included when requesting a Post's references.",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/issues/6#issuecomment-385223316",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
COMMENT
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "comment on #6: An issue with comments",
|
|
||||||
"type": "COMMENT",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/issues/7",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "Deal with this, naive string display algorithms!!!!!",
|
|
||||||
"number": 7,
|
|
||||||
"title": "An issue with an extremely long title, which even has a VerySuperFragicalisticialiManyCharacterUberLongTriplePlusGood word in it, and should really be truncated intelligently or something",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/issues/7",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
ISSUE
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "#7: An issue with an extremely long title, which even has a VerySuperFragicalisticialiManyCharacterUberLongTriplePlusGood word in it, and should really be truncated intelligently or something",
|
|
||||||
"type": "ISSUE",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/issues/8",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "Issue with Unicode: ȴሲ𣐳楢👍 :heart: 𐤔𐤁𐤀𐤑𐤍𐤉𐤔𐤌𐤄𐤍𐤍 ❤️
|
|
||||||
Issue with Unicode: ȴሲ𣐳楢👍 :heart: 𐤔𐤁𐤀𐤑𐤍𐤉𐤔𐤌𐤄𐤍𐤍 ❤️",
|
|
||||||
"number": 8,
|
|
||||||
"title": "Issue with Unicode: ȴሲ𣐳楢👍 :heart: 𐤔𐤁𐤀𐤑𐤍𐤉𐤔𐤌𐤄𐤍𐤍 ❤️",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/issues/8",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
ISSUE
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "#8: Issue with Unicode: ȴሲ𣐳楢👍 :heart: 𐤔𐤁𐤀𐤑𐤍𐤉𐤔𐤌𐤄𐤍𐤍 ❤️",
|
|
||||||
"type": "ISSUE",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/pull/3",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "Oh look, it's a pull request.",
|
|
||||||
"number": 3,
|
|
||||||
"title": "Add README, merge via PR.",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/pull/3",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
PULL_REQUEST
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "#3: Add README, merge via PR.",
|
|
||||||
"type": "PULL_REQUEST",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/pull/3#issuecomment-369162222",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "It seems apropos to reference something from a pull request comment... eg: #2 ",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/pull/3#issuecomment-369162222",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
COMMENT
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "comment on #3: Add README, merge via PR.",
|
|
||||||
"type": "COMMENT",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/pull/5",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "@wchargin could you please do the following:
|
|
||||||
- add a commit comment
|
|
||||||
- add a review comment requesting some trivial change
|
|
||||||
- i'll change it
|
|
||||||
- then approve the pr",
|
|
||||||
"number": 5,
|
|
||||||
"title": "This pull request will be more contentious. I can feel it...",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/pull/5",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
PULL_REQUEST
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "#5: This pull request will be more contentious. I can feel it...",
|
|
||||||
"type": "PULL_REQUEST",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/pull/5#discussion_r171460198",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "seems a bit capricious",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/pull/5#discussion_r171460198",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
PULL_REQUEST_REVIEW_COMMENT
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "comment on review of #5: This pull request will be more contentious. I can feel it...",
|
|
||||||
"type": "PULL_REQUEST_REVIEW_COMMENT",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/pull/5#pullrequestreview-100313899",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "hmmm.jpg",
|
|
||||||
"state": "CHANGES_REQUESTED",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/pull/5#pullrequestreview-100313899",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
PULL_REQUEST_REVIEW
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "review of #5: This pull request will be more contentious. I can feel it...",
|
|
||||||
"type": "PULL_REQUEST_REVIEW",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/pull/5#pullrequestreview-100314038",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "I'm sold",
|
|
||||||
"state": "APPROVED",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/pull/5#pullrequestreview-100314038",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
PULL_REQUEST_REVIEW
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "review of #5: This pull request will be more contentious. I can feel it...",
|
|
||||||
"type": "PULL_REQUEST_REVIEW",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/sourcecred/example-github/pull/9",
|
|
||||||
"payload": Object {
|
|
||||||
"body": "Nominally paired with @wchargin",
|
|
||||||
"number": 9,
|
|
||||||
"title": "An unmerged pull request",
|
|
||||||
"url": "https://github.com/sourcecred/example-github/pull/9",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
PULL_REQUEST
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "#9: An unmerged pull request",
|
|
||||||
"type": "PULL_REQUEST",
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"id": "https://github.com/wchargin",
|
|
||||||
"payload": Object {
|
|
||||||
"login": "wchargin",
|
|
||||||
"subtype": "USER",
|
|
||||||
"url": "https://github.com/wchargin",
|
|
||||||
},
|
|
||||||
"rendered": <div>
|
|
||||||
type:
|
|
||||||
AUTHOR
|
|
||||||
(details to be implemented)
|
|
||||||
</div>,
|
|
||||||
"title": "wchargin",
|
|
||||||
"type": "AUTHOR",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
`;
|
|
|
@ -1,108 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
|
|
||||||
import {Graph} from "../../../../core/graph";
|
|
||||||
import type {Node} from "../../../../core/graph";
|
|
||||||
import type {
|
|
||||||
NodePayload,
|
|
||||||
NodeType,
|
|
||||||
RepositoryNodePayload,
|
|
||||||
IssueNodePayload,
|
|
||||||
PullRequestNodePayload,
|
|
||||||
CommentNodePayload,
|
|
||||||
PullRequestReviewCommentNodePayload,
|
|
||||||
PullRequestReviewNodePayload,
|
|
||||||
AuthorNodePayload,
|
|
||||||
} from "../../../github/types";
|
|
||||||
import type {PluginAdapter} from "../pluginAdapter";
|
|
||||||
import {PLUGIN_NAME} from "../../../github/pluginName";
|
|
||||||
import {CONTAINS_EDGE_TYPE} from "../../../github/types";
|
|
||||||
|
|
||||||
const adapter: PluginAdapter<NodePayload> = {
|
|
||||||
pluginName: PLUGIN_NAME,
|
|
||||||
|
|
||||||
renderer: class GithubNodeRenderer extends React.Component<{
|
|
||||||
graph: Graph,
|
|
||||||
node: Node<NodePayload>,
|
|
||||||
}> {
|
|
||||||
render() {
|
|
||||||
const type = this.props.node.address.type;
|
|
||||||
return <div>type: {type} (details to be implemented)</div>;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
extractTitle(graph: *, node: Node<NodePayload>): string {
|
|
||||||
// NOTE: If the graph is malformed such that there are containment
|
|
||||||
// cycles, then this function may blow the stack or fail to
|
|
||||||
// terminate. (If necessary, we can fix this by tracking all
|
|
||||||
// previously queried IDs.)
|
|
||||||
function extractParentTitles(node: Node<NodePayload>): string[] {
|
|
||||||
return graph
|
|
||||||
.neighborhood(node.address, {
|
|
||||||
direction: "IN",
|
|
||||||
edgeType: CONTAINS_EDGE_TYPE,
|
|
||||||
})
|
|
||||||
.map(({neighbor}) => {
|
|
||||||
return adapter.extractTitle(graph, graph.node(neighbor));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
function extractRepositoryTitle(node: Node<RepositoryNodePayload>) {
|
|
||||||
return `${node.payload.owner}/${node.payload.name}`;
|
|
||||||
}
|
|
||||||
function extractIssueOrPrTitle(
|
|
||||||
node: Node<IssueNodePayload | PullRequestNodePayload>
|
|
||||||
) {
|
|
||||||
return `#${node.payload.number}: ${node.payload.title}`;
|
|
||||||
}
|
|
||||||
function extractCommentTitle(
|
|
||||||
kind: string,
|
|
||||||
node: Node<CommentNodePayload | PullRequestReviewCommentNodePayload>
|
|
||||||
) {
|
|
||||||
const parentTitles = extractParentTitles(node);
|
|
||||||
if (parentTitles.length === 0) {
|
|
||||||
// Should never happen.
|
|
||||||
return "comment (orphaned)";
|
|
||||||
} else {
|
|
||||||
// Should just be one parent.
|
|
||||||
return `comment on ${parentTitles.join(" and ")}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function extractPRReviewTitle(node: Node<PullRequestReviewNodePayload>) {
|
|
||||||
const parentTitles = extractParentTitles(node);
|
|
||||||
if (parentTitles.length === 0) {
|
|
||||||
// Should never happen.
|
|
||||||
return "pull request review (orphaned)";
|
|
||||||
} else {
|
|
||||||
// Should just be one parent.
|
|
||||||
return `review of ${parentTitles.join(" and ")}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function extractAuthorTitle(node: Node<AuthorNodePayload>) {
|
|
||||||
return node.payload.login;
|
|
||||||
}
|
|
||||||
const anyNode: Node<any> = node;
|
|
||||||
const type: NodeType = (node.address.type: any);
|
|
||||||
switch (type) {
|
|
||||||
case "REPOSITORY":
|
|
||||||
return extractRepositoryTitle(anyNode);
|
|
||||||
case "ISSUE":
|
|
||||||
case "PULL_REQUEST":
|
|
||||||
return extractIssueOrPrTitle(anyNode);
|
|
||||||
case "COMMENT":
|
|
||||||
return extractCommentTitle("comment", anyNode);
|
|
||||||
case "PULL_REQUEST_REVIEW_COMMENT":
|
|
||||||
return extractCommentTitle("review comment", anyNode);
|
|
||||||
case "PULL_REQUEST_REVIEW":
|
|
||||||
return extractPRReviewTitle(anyNode);
|
|
||||||
case "AUTHOR":
|
|
||||||
return extractAuthorTitle(anyNode);
|
|
||||||
default:
|
|
||||||
// eslint-disable-next-line no-unused-expressions
|
|
||||||
(type: empty);
|
|
||||||
throw new Error(`unknown node type: ${node.address.type}`);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export default adapter;
|
|
|
@ -1,36 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import React from "react";
|
|
||||||
import {shallow} from "enzyme";
|
|
||||||
import enzymeToJSON from "enzyme-to-json";
|
|
||||||
import stringify from "json-stable-stringify";
|
|
||||||
|
|
||||||
import {parse} from "../../../github/parser";
|
|
||||||
import exampleRepoData from "../../../github/demoData/example-github.json";
|
|
||||||
import adapter from "./githubPluginAdapter";
|
|
||||||
|
|
||||||
require("../../../../app/testUtil").configureEnzyme();
|
|
||||||
|
|
||||||
describe("githubPluginAdapter", () => {
|
|
||||||
it("operates on the example repo", () => {
|
|
||||||
const graph = parse(exampleRepoData);
|
|
||||||
|
|
||||||
const result = graph
|
|
||||||
.nodes()
|
|
||||||
.map((node) => ({
|
|
||||||
id: node.address.id,
|
|
||||||
payload: node.payload,
|
|
||||||
type: node.address.type,
|
|
||||||
title: adapter.extractTitle(graph, node),
|
|
||||||
rendered: enzymeToJSON(
|
|
||||||
shallow(<adapter.renderer graph={graph} node={node} />)
|
|
||||||
),
|
|
||||||
}))
|
|
||||||
.sort((a, b) => {
|
|
||||||
const ka = stringify(a.id);
|
|
||||||
const kb = stringify(b.id);
|
|
||||||
return ka > kb ? 1 : ka < kb ? -1 : 0;
|
|
||||||
});
|
|
||||||
expect(result).toMatchSnapshot();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,10 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import type {Graph, Node} from "../../../core/graph";
|
|
||||||
import type {ComponentType} from "react";
|
|
||||||
|
|
||||||
export interface PluginAdapter<-NodePayload> {
|
|
||||||
pluginName: string;
|
|
||||||
renderer: $Subtype<ComponentType<{graph: Graph, node: Node<NodePayload>}>>;
|
|
||||||
extractTitle(graph: Graph, node: Node<NodePayload>): string;
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
// @flow
|
|
||||||
|
|
||||||
import {AdapterSet} from "./adapterSet";
|
|
||||||
import githubPluginAdapter from "./adapters/githubPluginAdapter";
|
|
||||||
|
|
||||||
const adapterSet = new AdapterSet();
|
|
||||||
adapterSet.addAdapter(githubPluginAdapter);
|
|
||||||
|
|
||||||
export default adapterSet;
|
|
Loading…
Reference in New Issue