Add a WeightsFileManager (#1150)

This adds a WeightsFileManager component that allows the user to save or
load weights in the cred explorer. Clicking the download icon downloads
the weights, clicking the upload icon uploads them.

I also did a slight refactor to the FileUploader so that it no longer
always provides the file upload icon, instead the instantiator passes
children which act as the upload clickable. Seemed more consistent.

Test plan: No tests added, but I manually tested that upload and
download both work.
This commit is contained in:
Dandelion Mané 2019-05-21 04:41:00 +03:00 committed by GitHub
parent fb89559e44
commit a831e05e5f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 59 additions and 5 deletions

View File

@ -2,6 +2,7 @@
## [Unreleased]
- Allow the user to save or upload weight settings (#1150)
- Allow tweaking weights on a per-node basis (#1143)
- Add the `pagerank` command (#1114)
- Add the `clear` command (#1111)

View File

@ -12,6 +12,7 @@ import {type NodeAddressT} from "../core/graph";
import {PagerankTable} from "./pagerankTable/Table";
import {WeightConfig} from "./weights/WeightConfig";
import {WeightsFileManager} from "./weights/WeightsFileManager";
import {type Weights, defaultWeights} from "../analysis/weights";
import {Prefix as GithubPrefix} from "../plugins/github/nodes";
import {
@ -108,6 +109,14 @@ export function createApp(
}}
/>
);
const weightFileManager = (
<WeightsFileManager
weights={this.state.weights}
onWeightsChange={(weights: Weights) => {
this.setState({weights});
}}
/>
);
let pagerankTable;
if (appState.type === "PAGERANK_EVALUATED") {
const adapters = appState.graphWithAdapters.adapters;
@ -117,6 +126,7 @@ export function createApp(
defaultNodeType={userNodeType}
adapters={adapters}
weightConfig={weightConfig}
weightFileManager={weightFileManager}
manualWeights={this.state.weights.nodeManualWeights}
onManualWeightsChange={(addr: NodeAddressT, weight: number) =>
this.setState(({weights}) => {

View File

@ -4,6 +4,7 @@ import React from "react";
import sortBy from "lodash.sortby";
import {WeightConfig} from "../weights/WeightConfig";
import {WeightsFileManager} from "../weights/WeightsFileManager";
import {NodeAddress, type NodeAddressT} from "../../core/graph";
import type {PagerankNodeDecomposition} from "../../analysis/pagerankNodeDecomposition";
import {DynamicExplorerAdapterSet} from "../adapters/explorerAdapterSet";
@ -19,6 +20,7 @@ type PagerankTableProps = {|
+manualWeights: Map<NodeAddressT, number>,
+onManualWeightsChange: (NodeAddressT, number) => void,
+weightConfig: React$Element<typeof WeightConfig>,
+weightFileManager: React$Element<typeof WeightsFileManager>,
|};
type PagerankTableState = {|
selectedNodeTypePrefix: NodeAddressT,
@ -45,6 +47,7 @@ export class PagerankTable extends React.PureComponent<
<div style={{display: "flex"}}>
{this.renderFilterSelect()}
<span style={{flexGrow: 1}} />
{this.props.weightFileManager}
<button
onClick={() => {
this.setState(({showWeightConfig}) => ({

View File

@ -22,12 +22,14 @@ describe("explorer/pagerankTable/Table", () => {
manualWeights,
onManualWeightsChange,
weightConfig,
weightFileManager,
maxEntriesPerList,
} = await example();
const element = shallow(
<PagerankTable
defaultNodeType={defaultNodeType}
weightConfig={weightConfig}
weightFileManager={weightFileManager}
pnd={pnd}
adapters={adapters}
maxEntriesPerList={maxEntriesPerList}

View File

@ -20,6 +20,7 @@ export async function example() {
const manualWeights: Map<NodeAddressT, number> = new Map();
const onManualWeightsChange: (NodeAddressT, number) => void = jest.fn();
const weightConfig: any = <div data-test-weight-config={true} />;
const weightFileManager: any = <div data-test-weight-file-manager={true} />;
const sharedProps: SharedProps = {
adapters,
@ -37,5 +38,6 @@ export async function example() {
manualWeights,
onManualWeightsChange,
weightConfig,
weightFileManager,
};
}

View File

@ -0,0 +1,33 @@
// @flow
import stringify from "json-stable-stringify";
import React from "react";
import {FileUploader} from "../../util/FileUploader";
import {MdFileDownload, MdFileUpload} from "react-icons/md";
import {type Weights, toJSON, fromJSON} from "../../analysis/weights";
export type Props = {|
+weights: Weights,
+onWeightsChange: (Weights) => void,
|};
export class WeightsFileManager extends React.Component<Props> {
render() {
const weightsJSON = stringify(toJSON(this.props.weights));
const onUpload = (json) => this.props.onWeightsChange(fromJSON(json));
return (
<div>
<a
download="weights.json"
title="Download your weights.json"
href={`data:text/json,${weightsJSON}`}
style={{color: "black"}}
>
<MdFileDownload style={{margin: "2px"}} />
</a>
<FileUploader title="Upload weights.json" onUpload={onUpload}>
<MdFileUpload style={{margin: "2px"}} />
</FileUploader>
</div>
);
}
}

View File

@ -1,11 +1,11 @@
// @flow
import React from "react";
import {MdFileUpload} from "react-icons/md";
import React, {type Node} from "react";
export type Props = {|
+onUpload: (mixed) => void,
+onUpload: (any) => void,
+title: string,
+children: Node,
|};
/**
@ -36,7 +36,7 @@ export class FileUploader extends React.Component<Props> {
// over the UI, but ensures that it is still accessible via the keyboard.
// https://snook.ca/archives/html_and_css/hiding-content-for-accessibility
<label title={this.props.title}>
<MdFileUpload />
{this.props.children}
<input
type="file"
accept=".json"

View File

@ -2,6 +2,7 @@
import React from "react";
import Markdown from "react-markdown";
import {MdFileUpload} from "react-icons/md";
import dedent from "./dedent";
import stringify from "json-stable-stringify";
@ -36,7 +37,9 @@ export default class FileUploaderInspectionTest extends React.Component<
`}
/>
<h2>The File Uploader</h2>
<FileUploader onUpload={onUpload} title="FileUploader" />
<FileUploader onUpload={onUpload} title="FileUploader">
<MdFileUpload />
</FileUploader>
<h2>The Uploaded File</h2>
{this.state.json ? (
<pre style={{backgroundColor: "#efefef"}}>{displayContents}</pre>