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:
parent
fb89559e44
commit
a831e05e5f
|
@ -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)
|
||||
|
|
|
@ -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}) => {
|
||||
|
|
|
@ -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}) => ({
|
||||
|
|
|
@ -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}
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue