PagerankTable: Replace topLevelFilter with NodeType in props (#1103)

The motivation for this change is to make it easier
to access the selected Node's `name` prop for #576,
in which we plan to show a Card displaying summary
stats for the selected node. With only the `topLevelFilter`
available, it's trickier than it needs to be to find out
a node type's `name`.

Test Plan:
* Yarn test passes.
* Visual/Manual inspection of table doesn't surface any issues.
* Updated `it("filter defaults to defaultNodeFilter if available")`
to `it("selectedNodeType defaults to defaultNodeType if available")`.
* Verified that the above new test is failable in several ways by
mangling the tests to test for the wrong node type and mangling the
code to set the wrong node type.
* Since we factored out 'topLevelFilter' and 'defaultNodeFilter',
running `git grep -i topLevelFilter` and `git grep -i defaultNodeFilter`
turns up empty, just to make sure those terms aren't hanging
around to confuse anybody in the future.
* I don't think changing the `prop` parameter warrants any
additional tests, as the current tests verify that the prop
is passed in correctly.

This was at @decentralion's suggestion, following the Contributing
Guideline's Kent Beck quote of making the easy change to make the
change we were originally after (#576) easier. 🙌
This commit is contained in:
Brian Litwin 2019-02-21 14:27:18 -05:00 committed by GitHub
parent c353efff36
commit 42669cd160
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 35 additions and 38 deletions

View File

@ -20,6 +20,7 @@ import {
initialState,
} from "./state";
import {StaticExplorerAdapterSet} from "./adapters/explorerAdapterSet";
import {userNodeType} from "../plugins/github/declaration";
const credOverviewUrl =
"https://discuss.sourcecred.io/t/a-gentle-introduction-to-cred/20";
@ -91,7 +92,7 @@ export function createApp(
const pnd = appState.pagerankNodeDecomposition;
pagerankTable = (
<PagerankTable
defaultNodeFilter={GithubPrefix.user}
defaultNodeType={userNodeType}
adapters={adapters}
weightedTypes={this.state.weightedTypes}
onWeightedTypesChange={(weightedTypes) =>

View File

@ -4,15 +4,16 @@ import React from "react";
import sortBy from "lodash.sortby";
import * as NullUtil from "../../util/null";
import {type NodeAddressT, NodeAddress} from "../../core/graph";
import {NodeAddress} from "../../core/graph";
import type {PagerankNodeDecomposition} from "../../analysis/pagerankNodeDecomposition";
import {DynamicExplorerAdapterSet} from "../adapters/explorerAdapterSet";
import type {DynamicExplorerAdapter} from "../adapters/explorerAdapter";
import {FALLBACK_NAME} from "../../analysis/fallbackDeclaration";
import type {WeightedTypes} from "../../analysis/weights";
import {WeightConfig} from "../weights/WeightConfig";
import {NodeRowList} from "./Node";
import {type NodeType} from "../../analysis/types";
import {fallbackNodeType} from "../../analysis/fallbackDeclaration";
type PagerankTableProps = {|
+pnd: PagerankNodeDecomposition,
@ -20,10 +21,10 @@ type PagerankTableProps = {|
+weightedTypes: WeightedTypes,
+onWeightedTypesChange: (WeightedTypes) => void,
+maxEntriesPerList: number,
+defaultNodeFilter: ?NodeAddressT,
+defaultNodeType: ?NodeType,
|};
type PagerankTableState = {|
topLevelFilter: NodeAddressT,
selectedNodeType: NodeType,
showWeightConfig: boolean,
|};
export class PagerankTable extends React.PureComponent<
@ -32,20 +33,11 @@ export class PagerankTable extends React.PureComponent<
> {
constructor(props: PagerankTableProps): void {
super();
const {defaultNodeFilter, adapters} = props;
if (defaultNodeFilter != null) {
const nodeTypes = adapters.static().nodeTypes();
if (!nodeTypes.some((x) => x.prefix === defaultNodeFilter)) {
throw new Error(
`invalid defaultNodeFilter ${defaultNodeFilter}: doesn't match any type`
const selectedNodeType = NullUtil.orElse(
props.defaultNodeType,
fallbackNodeType
);
}
}
const topLevelFilter = NullUtil.orElse(
props.defaultNodeFilter,
NodeAddress.empty
);
this.state = {topLevelFilter, showWeightConfig: false};
this.state = {selectedNodeType, showWeightConfig: false};
}
renderConfigurationRow() {
@ -116,9 +108,11 @@ export class PagerankTable extends React.PureComponent<
<label>
<span>Filter by node type: </span>
<select
value={this.state.topLevelFilter}
value={this.state.selectedNodeType.prefix}
onChange={(e) => {
this.setState({topLevelFilter: e.target.value});
const nodePrefix = e.target.value;
const nodeType = adapters.static().typeMatchingNode(nodePrefix);
this.setState({selectedNodeType: nodeType});
}}
>
<option value={NodeAddress.empty}>Show all</option>
@ -138,7 +132,7 @@ export class PagerankTable extends React.PureComponent<
if (pnd == null || adapters == null || maxEntriesPerList == null) {
throw new Error("Impossible.");
}
const topLevelFilter = this.state.topLevelFilter;
const selectedNodeTypePrefix = this.state.selectedNodeType.prefix;
const sharedProps = {pnd, adapters, maxEntriesPerList};
return (
<table
@ -161,7 +155,7 @@ export class PagerankTable extends React.PureComponent<
<NodeRowList
sharedProps={sharedProps}
nodes={Array.from(pnd.keys()).filter((node) =>
NodeAddress.hasPrefix(node, topLevelFilter)
NodeAddress.hasPrefix(node, selectedNodeTypePrefix)
)}
/>
</tbody>

View File

@ -3,7 +3,7 @@
import React from "react";
import {shallow} from "enzyme";
import {NodeAddress, type NodeAddressT} from "../../core/graph";
import {NodeAddress} from "../../core/graph";
import {PagerankTable} from "./Table";
import {example, COLUMNS} from "./sharedTestUtils";
@ -11,17 +11,19 @@ import {NodeRowList} from "./Node";
import {WeightConfig} from "../weights/WeightConfig";
import {defaultWeightsForAdapter} from "../weights/weights";
import {FactorioStaticAdapter} from "../../plugins/demo/explorerAdapter";
import {type NodeType} from "../../analysis/types";
import {fallbackNodeType} from "../../analysis/fallbackDeclaration";
require("../../webutil/testUtil").configureEnzyme();
describe("explorer/pagerankTable/Table", () => {
describe("PagerankTable", () => {
async function setup(defaultNodeFilter?: NodeAddressT) {
async function setup(defaultNodeType?: NodeType) {
const {pnd, adapters, weightedTypes} = await example();
const onWeightedTypesChange = jest.fn();
const maxEntriesPerList = 321;
const element = shallow(
<PagerankTable
defaultNodeFilter={defaultNodeFilter}
defaultNodeType={defaultNodeType}
weightedTypes={weightedTypes}
onWeightedTypesChange={onWeightedTypesChange}
pnd={pnd}
@ -124,20 +126,20 @@ describe("explorer/pagerankTable/Table", () => {
);
expect(actualNodes).not.toHaveLength(0);
});
it("filter defaults to show all if defaultNodeFilter not passed", async () => {
it("filter defaults to show all if defaultNodeType not passed", async () => {
const {element} = await setup();
expect(element.state().topLevelFilter).toEqual(NodeAddress.empty);
expect(element.state().selectedNodeType).toEqual(fallbackNodeType);
});
it("filter defaults to defaultNodeFilter if available", async () => {
const filter = NodeAddress.fromParts(["factorio", "inserter"]);
const {element} = await setup(filter);
expect(element.state().topLevelFilter).toEqual(filter);
});
it("raises an error if defaultNodeFilter doesn't match any node type", async () => {
const badFilter = NodeAddress.fromParts(["doesn't", "exist"]);
await expect(setup(badFilter)).rejects.toThrow(
"invalid defaultNodeFilter"
);
it("selectedNodeType defaults to defaultNodeType if available", async () => {
const nodeType: NodeType = {
name: "testNodeType",
pluralName: "testNodeTypes",
prefix: NodeAddress.empty,
defaultWeight: 1,
description: "test type",
};
const {element} = await setup(nodeType);
expect(element.state().selectedNodeType).toEqual(nodeType);
});
});

View File

@ -45,7 +45,7 @@ const commentNodeType = Object.freeze({
description: "NodeType for a GitHub comment",
});
const userNodeType = Object.freeze({
export const userNodeType = Object.freeze({
name: "User",
pluralName: "Users",
prefix: N.Prefix.user,