Initial Timeline Explorer
This commit adds a TimelineExplorer for visualizing timeline cred data. The centerpiece is the TimelineCredChart, a d3-based line chart showing how the top users' cred evolved over time. It has features like tooltips, reasonable ticks on the x axis, a legend, and filtering out line segments that stay on the x axis. An inspection test is included, which you can check out here: http://localhost:8080/test/TimelineCredView/ Also, you can run it for any loaded repository at: http://localhost:8080/timeline/$repoOwner/$repoName This commit also includes new dependencies: - recharts (for the charts) - react-markdown (for rendering the Markdown descriptions) - remove-markdown (so the legend will be clean text) - d3-time-format for date axis generation - d3-scale and d3-scale-chromatic for color scales Test plan: The frontend code is mostly untested, in keeping with my observation that the costs of testing the old explorer were really high, and the tests brought little benefit. However, I have manually tested it thoroughly. Also, there is an inspection test for the TimelineCredView (see above).
This commit is contained in:
parent
b106326e0a
commit
5dc7f440ce
|
@ -8,7 +8,11 @@
|
||||||
"chalk": "2.4.2",
|
"chalk": "2.4.2",
|
||||||
"commonmark": "^0.29.0",
|
"commonmark": "^0.29.0",
|
||||||
"d3-array": "^2.2.0",
|
"d3-array": "^2.2.0",
|
||||||
|
"d3-format": "^1.3.2",
|
||||||
|
"d3-scale": "^3.0.0",
|
||||||
|
"d3-scale-chromatic": "^1.3.3",
|
||||||
"d3-time": "^1.0.11",
|
"d3-time": "^1.0.11",
|
||||||
|
"d3-time-format": "^2.1.3",
|
||||||
"express": "^4.16.3",
|
"express": "^4.16.3",
|
||||||
"fs-extra": "8.1.0",
|
"fs-extra": "8.1.0",
|
||||||
"history": "^3.0.0",
|
"history": "^3.0.0",
|
||||||
|
@ -26,6 +30,8 @@
|
||||||
"react-icons": "^3.7.0",
|
"react-icons": "^3.7.0",
|
||||||
"react-markdown": "^4.0.8",
|
"react-markdown": "^4.0.8",
|
||||||
"react-router": "3.2.1",
|
"react-router": "3.2.1",
|
||||||
|
"recharts": "^1.6.2",
|
||||||
|
"remove-markdown": "^0.3.0",
|
||||||
"retry": "^0.12.0",
|
"retry": "^0.12.0",
|
||||||
"rimraf": "^2.6.3",
|
"rimraf": "^2.6.3",
|
||||||
"svg-react-loader": "^0.4.6",
|
"svg-react-loader": "^0.4.6",
|
||||||
|
|
|
@ -0,0 +1,104 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import type {Assets} from "../webutil/assets";
|
||||||
|
import type {RepoId} from "../core/repoId";
|
||||||
|
import {TimelineExplorer} from "./TimelineExplorer";
|
||||||
|
import {TimelineCred} from "../analysis/timeline/timelineCred";
|
||||||
|
import {declaration as githubDeclaration} from "../plugins/github/declaration";
|
||||||
|
import {DEFAULT_CRED_CONFIG} from "../plugins/defaultCredConfig";
|
||||||
|
|
||||||
|
export type Props = {|
|
||||||
|
+assets: Assets,
|
||||||
|
+repoId: RepoId,
|
||||||
|
+loader: Loader,
|
||||||
|
|};
|
||||||
|
|
||||||
|
export type Loader = (assets: Assets, repoId: RepoId) => Promise<LoadResult>;
|
||||||
|
|
||||||
|
export type LoadResult = Loading | LoadSuccess | LoadError;
|
||||||
|
export type Loading = {|+type: "LOADING"|};
|
||||||
|
export type LoadSuccess = {|
|
||||||
|
+type: "SUCCESS",
|
||||||
|
+timelineCred: TimelineCred,
|
||||||
|
|};
|
||||||
|
export type LoadError = {|+type: "ERROR", +error: any|};
|
||||||
|
|
||||||
|
export type State = {|
|
||||||
|
loadResult: LoadResult,
|
||||||
|
|};
|
||||||
|
export class TimelineApp extends React.Component<Props, State> {
|
||||||
|
state = {loadResult: {type: "LOADING"}};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
async load() {
|
||||||
|
const loadResult = await this.props.loader(
|
||||||
|
this.props.assets,
|
||||||
|
this.props.repoId
|
||||||
|
);
|
||||||
|
this.setState({loadResult});
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {loadResult} = this.state;
|
||||||
|
switch (loadResult.type) {
|
||||||
|
case "LOADING": {
|
||||||
|
return (
|
||||||
|
<div style={{width: 900, margin: "0 auto"}}>
|
||||||
|
<h1>Loading...</h1>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "ERROR": {
|
||||||
|
const {error} = loadResult;
|
||||||
|
return (
|
||||||
|
<div style={{width: 900, margin: "0 auto"}}>
|
||||||
|
<h1>Load Error:</h1>
|
||||||
|
<p>
|
||||||
|
{error.status}:{error.statusText}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
case "SUCCESS": {
|
||||||
|
const {timelineCred} = loadResult;
|
||||||
|
return (
|
||||||
|
<TimelineExplorer
|
||||||
|
initialTimelineCred={timelineCred}
|
||||||
|
repoId={this.props.repoId}
|
||||||
|
declarations={[githubDeclaration]}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
throw new Error(`Unexpected load state: ${(loadResult.type: empty)}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function defaultLoader(
|
||||||
|
assets: Assets,
|
||||||
|
repoId: RepoId
|
||||||
|
): Promise<LoadResult> {
|
||||||
|
async function fetchCred(): Promise<TimelineCred> {
|
||||||
|
const url = assets.resolve(
|
||||||
|
`api/v1/data/data/${repoId.owner}/${repoId.name}/cred.json`
|
||||||
|
);
|
||||||
|
const response = await fetch(url);
|
||||||
|
if (!response.ok) {
|
||||||
|
return Promise.reject(response);
|
||||||
|
}
|
||||||
|
return TimelineCred.fromJSON(await response.json(), DEFAULT_CRED_CONFIG);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const timelineCred = await fetchCred();
|
||||||
|
return {type: "SUCCESS", timelineCred};
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return {type: "ERROR", error: e};
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,126 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import removeMd from "remove-markdown";
|
||||||
|
import {schemeCategory10} from "d3-scale-chromatic";
|
||||||
|
import {timeFormat} from "d3-time-format";
|
||||||
|
import {scaleOrdinal} from "d3-scale";
|
||||||
|
import {timeMonth, timeYear} from "d3-time";
|
||||||
|
import {format} from "d3-format";
|
||||||
|
import {
|
||||||
|
LineChart,
|
||||||
|
Line,
|
||||||
|
XAxis,
|
||||||
|
YAxis,
|
||||||
|
CartesianGrid,
|
||||||
|
Tooltip,
|
||||||
|
Legend,
|
||||||
|
} from "recharts";
|
||||||
|
import * as NullUtil from "../util/null";
|
||||||
|
import {type NodeAddressT} from "../core/graph";
|
||||||
|
import {type Interval, TimelineCred} from "../analysis/timeline/timelineCred";
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
timelineCred: TimelineCred,
|
||||||
|
displayedNodes: $ReadOnlyArray<NodeAddressT>,
|
||||||
|
};
|
||||||
|
|
||||||
|
type LineChartDatum = {
|
||||||
|
interval: Interval,
|
||||||
|
// May be null if the node score was filtered out (effectively bc.
|
||||||
|
// that node did not exist yet)
|
||||||
|
score: Map<NodeAddressT, ?number>,
|
||||||
|
};
|
||||||
|
|
||||||
|
// TODO: Make the line chart auto-scale based on its container
|
||||||
|
export const LINE_CHART_WIDTH = 1000;
|
||||||
|
export const LINE_CHART_HEIGHT = 500;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders a line chart showing cred over time for the node addresses in
|
||||||
|
* props.displayedNodes.
|
||||||
|
*
|
||||||
|
* To see a demo, you can check out the TimelineCredView inspection test.
|
||||||
|
* Run `yarn start` and navigate to:
|
||||||
|
* http://localhost:8080/test/TimelineCredView/
|
||||||
|
*/
|
||||||
|
export class TimelineCredChart extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const {timelineCred, displayedNodes} = this.props;
|
||||||
|
const intervals = timelineCred.intervals();
|
||||||
|
const timeDomain = [
|
||||||
|
intervals[0].startTimeMs,
|
||||||
|
intervals[intervals.length - 1].endTimeMs,
|
||||||
|
];
|
||||||
|
const data: LineChartDatum[] = intervals.map((interval, index) => {
|
||||||
|
const score = new Map();
|
||||||
|
for (const node of displayedNodes) {
|
||||||
|
const {cred} = NullUtil.get(timelineCred.credNode(node));
|
||||||
|
const lastScore = index === 0 ? 0 : cred[index - 1];
|
||||||
|
const nextScore = index === intervals.length - 1 ? 0 : cred[index + 1];
|
||||||
|
const thisScore = cred[index];
|
||||||
|
// Filter a score out if it's on the zero line and not going anywhere.
|
||||||
|
// This makes the tooltips a lot cleaner.
|
||||||
|
// (Ideally we would have more control over the tooltips display directly
|
||||||
|
// without munging the data...)
|
||||||
|
const filteredScore =
|
||||||
|
Math.max(lastScore, nextScore, thisScore) < 0.1 ? null : thisScore;
|
||||||
|
score.set(node, filteredScore);
|
||||||
|
}
|
||||||
|
return {score, interval};
|
||||||
|
});
|
||||||
|
const scale = scaleOrdinal(displayedNodes, schemeCategory10);
|
||||||
|
const Lines = displayedNodes.map((n: NodeAddressT) => {
|
||||||
|
const description = NullUtil.get(timelineCred.graph().node(n))
|
||||||
|
.description;
|
||||||
|
const plainDescription = removeMd(description);
|
||||||
|
return (
|
||||||
|
<Line
|
||||||
|
type="monotone"
|
||||||
|
dot={false}
|
||||||
|
key={n}
|
||||||
|
stroke={scale(n)}
|
||||||
|
dataKey={(x) => x.score.get(n)}
|
||||||
|
name={plainDescription}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
const formatMonth = timeFormat("%b");
|
||||||
|
const formatYear = timeFormat("%Y");
|
||||||
|
|
||||||
|
function multiFormat(dateMs) {
|
||||||
|
const date = new Date(dateMs);
|
||||||
|
return timeYear(date) < date ? formatMonth(date) : formatYear(date);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ticks = timeMonth.range(...timeDomain);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<LineChart
|
||||||
|
width={LINE_CHART_WIDTH}
|
||||||
|
height={LINE_CHART_HEIGHT}
|
||||||
|
data={data}
|
||||||
|
>
|
||||||
|
<CartesianGrid strokeDasharray="3 3" />
|
||||||
|
<XAxis
|
||||||
|
dataKey={(x) => x.interval.startTimeMs}
|
||||||
|
type="number"
|
||||||
|
domain={timeDomain}
|
||||||
|
tickFormatter={multiFormat}
|
||||||
|
ticks={ticks}
|
||||||
|
/>
|
||||||
|
<YAxis />
|
||||||
|
{Lines}
|
||||||
|
<Tooltip
|
||||||
|
formatter={format(".1d")}
|
||||||
|
itemSorter={(x) => -x.value}
|
||||||
|
labelFormatter={(v) => {
|
||||||
|
return `Week of ${timeFormat("%B %e, %Y")(v)}`;
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Legend />
|
||||||
|
</LineChart>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,78 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import Markdown from "react-markdown";
|
||||||
|
import {sum} from "d3-array";
|
||||||
|
import {type NodeAddressT} from "../core/graph";
|
||||||
|
import {TimelineCredChart} from "./TimelineCredChart";
|
||||||
|
import {format} from "d3-format";
|
||||||
|
import {TimelineCred} from "../analysis/timeline/timelineCred";
|
||||||
|
|
||||||
|
export type Props = {|
|
||||||
|
+timelineCred: TimelineCred,
|
||||||
|
+selectedNodeFilter: NodeAddressT,
|
||||||
|
|};
|
||||||
|
|
||||||
|
const MAX_ENTRIES_PER_LIST = 100;
|
||||||
|
const DEFAULT_ENTRIES_PER_CHART = 6;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render a view on TimelineCred.
|
||||||
|
*
|
||||||
|
* Takes a TimelineCred instance and a node filter as props. It will display
|
||||||
|
* cred for nodes that match the filter.
|
||||||
|
*
|
||||||
|
* The top `DEFAULT_ENTRIES_PER_CHART` nodes (by total cred across time) will
|
||||||
|
* be rendered in a TimelineCredChart. There is also a table showing the top
|
||||||
|
* `MAX_ENTRIES_PER_LIST` nodes (also by total cred across time).
|
||||||
|
*
|
||||||
|
* For a demo, check out TimelineCredViewInspectionTest by running `yarn start`
|
||||||
|
* and then navigating to:
|
||||||
|
* http://localhost:8080/test/TimelineCredView/
|
||||||
|
*/
|
||||||
|
export class TimelineCredView extends React.Component<Props> {
|
||||||
|
render() {
|
||||||
|
const {selectedNodeFilter, timelineCred} = this.props;
|
||||||
|
const nodes = timelineCred.credSortedNodes(selectedNodeFilter);
|
||||||
|
const tableNodes = nodes.slice(0, MAX_ENTRIES_PER_LIST);
|
||||||
|
const chartNodes = nodes
|
||||||
|
.slice(0, DEFAULT_ENTRIES_PER_CHART)
|
||||||
|
.map((x) => x.node.address);
|
||||||
|
const totalScore = sum(nodes.map((x) => x.total));
|
||||||
|
return (
|
||||||
|
<div style={{width: 1000, margin: "0 auto"}}>
|
||||||
|
<TimelineCredChart
|
||||||
|
timelineCred={timelineCred}
|
||||||
|
displayedNodes={chartNodes}
|
||||||
|
/>
|
||||||
|
<table style={{width: 600, margin: "0 auto", padding: "20px 10px"}}>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Contributor</th>
|
||||||
|
<th style={{textAlign: "right"}}>Cred</th>
|
||||||
|
<th style={{textAlign: "right"}}>% Total</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{tableNodes.map(({node, total}) => {
|
||||||
|
return (
|
||||||
|
<tr key={node.address}>
|
||||||
|
<td>
|
||||||
|
<Markdown
|
||||||
|
renderers={{paragraph: "span"}}
|
||||||
|
source={node.description}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td style={{textAlign: "right"}}>{format(".1d")(total)}</td>
|
||||||
|
<td style={{textAlign: "right"}}>
|
||||||
|
{format(".1%")(total / totalScore)}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,73 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import {timeWeek} from "d3-time";
|
||||||
|
import type {Assets} from "../webutil/assets";
|
||||||
|
import {TimelineCredView} from "./TimelineCredView";
|
||||||
|
import {Graph, NodeAddress} from "../core/graph";
|
||||||
|
import {type Interval, TimelineCred} from "../analysis/timeline/timelineCred";
|
||||||
|
import {type FilteredTimelineCred} from "../analysis/timeline/filterTimelineCred";
|
||||||
|
import {defaultWeights} from "../analysis/weights";
|
||||||
|
import {DEFAULT_CRED_CONFIG} from "../plugins/defaultCredConfig";
|
||||||
|
|
||||||
|
export default class TimelineCredViewInspectiontest extends React.Component<{|
|
||||||
|
+assets: Assets,
|
||||||
|
|}> {
|
||||||
|
intervals(): Interval[] {
|
||||||
|
const startTimeMs = +new Date(2017, 0);
|
||||||
|
const endTimeMs = +new Date(2017, 6);
|
||||||
|
const boundaries = timeWeek.range(startTimeMs, endTimeMs);
|
||||||
|
const result = [];
|
||||||
|
for (let i = 0; i < boundaries.length - 1; i++) {
|
||||||
|
result.push({
|
||||||
|
startTimeMs: +boundaries[i],
|
||||||
|
endTimeMs: +boundaries[i + 1],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
timelineCred(): TimelineCred {
|
||||||
|
const intervals = this.intervals();
|
||||||
|
const users = [
|
||||||
|
["starter", (x) => Math.max(0, 20 - x)],
|
||||||
|
["steady", (_) => 4],
|
||||||
|
["finisher", (x) => (x * x) / 20],
|
||||||
|
["latecomer", (x) => Math.max(0, x - 20)],
|
||||||
|
];
|
||||||
|
|
||||||
|
const graph = new Graph();
|
||||||
|
const addressToCred = new Map();
|
||||||
|
for (const [name, generator] of users) {
|
||||||
|
const address = NodeAddress.fromParts(["foo", name]);
|
||||||
|
graph.addNode({
|
||||||
|
address,
|
||||||
|
description: `[@${name}](https://github.com/${name})`,
|
||||||
|
timestampMs: null,
|
||||||
|
});
|
||||||
|
const scores = intervals.map((_unuesd, i) => generator(i));
|
||||||
|
addressToCred.set(address, scores);
|
||||||
|
}
|
||||||
|
const filteredTimelineCred: FilteredTimelineCred = {
|
||||||
|
intervals,
|
||||||
|
addressToCred,
|
||||||
|
};
|
||||||
|
const params = {alpha: 0.05, intervalDecay: 0.5, weights: defaultWeights()};
|
||||||
|
return new TimelineCred(
|
||||||
|
graph,
|
||||||
|
filteredTimelineCred,
|
||||||
|
params,
|
||||||
|
DEFAULT_CRED_CONFIG
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const selectedNodeFilter = NodeAddress.fromParts(["foo"]);
|
||||||
|
return (
|
||||||
|
<TimelineCredView
|
||||||
|
timelineCred={this.timelineCred()}
|
||||||
|
selectedNodeFilter={selectedNodeFilter}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,160 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
import deepEqual from "lodash.isequal";
|
||||||
|
import {type RepoId} from "../core/repoId";
|
||||||
|
import {type PluginDeclaration} from "../analysis/pluginDeclaration";
|
||||||
|
import {type Weights, copy as weightsCopy} from "../analysis/weights";
|
||||||
|
import {
|
||||||
|
TimelineCred,
|
||||||
|
type TimelineCredParameters,
|
||||||
|
} from "../analysis/timeline/timelineCred";
|
||||||
|
import {TimelineCredView} from "./TimelineCredView";
|
||||||
|
import {WeightConfig} from "./weights/WeightConfig";
|
||||||
|
import {WeightsFileManager} from "./weights/WeightsFileManager";
|
||||||
|
|
||||||
|
export type Props = {
|
||||||
|
repoId: RepoId,
|
||||||
|
initialTimelineCred: TimelineCred,
|
||||||
|
// TODO: Get this info from the TimelineCred
|
||||||
|
declarations: $ReadOnlyArray<PluginDeclaration>,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type State = {
|
||||||
|
timelineCred: TimelineCred,
|
||||||
|
weights: Weights,
|
||||||
|
alpha: number,
|
||||||
|
intervalDecay: number,
|
||||||
|
loading: boolean,
|
||||||
|
showWeightConfig: boolean,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TimelineExplorer allows displaying, exploring, and re-calculating TimelineCred.
|
||||||
|
*
|
||||||
|
* It basically wraps a TimelineCredView with some additional features and options:
|
||||||
|
* - allows changing the weights and re-calculating cred with new weights
|
||||||
|
* - allows saving/loading weights
|
||||||
|
* - displays the RepoId
|
||||||
|
*/
|
||||||
|
export class TimelineExplorer extends React.Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
const timelineCred = props.initialTimelineCred;
|
||||||
|
const {alpha, intervalDecay, weights} = timelineCred.params();
|
||||||
|
this.state = {
|
||||||
|
selectedNodeTypePrefix: timelineCred.config().scoreNodePrefix,
|
||||||
|
timelineCred,
|
||||||
|
alpha,
|
||||||
|
intervalDecay,
|
||||||
|
// Set the weights to a copy, to ensure we don't mutate the weights in the
|
||||||
|
// initialTimelineCred. This enables e.g. disabling the analyze button
|
||||||
|
// when the parameters are unchanged.
|
||||||
|
weights: weightsCopy(weights),
|
||||||
|
loading: false,
|
||||||
|
showWeightConfig: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
params(): TimelineCredParameters {
|
||||||
|
const {alpha, intervalDecay, weights} = this.state;
|
||||||
|
// Set the weights to a copy, to ensure that the weights we pass into e.g.
|
||||||
|
// analyzeCred are a distinct reference from the one we keep in our state.
|
||||||
|
return {alpha, intervalDecay, weights: weightsCopy(weights)};
|
||||||
|
}
|
||||||
|
|
||||||
|
async analyzeCred() {
|
||||||
|
this.setState({loading: true});
|
||||||
|
const timelineCred = await this.state.timelineCred.reanalyze(this.params());
|
||||||
|
this.setState({timelineCred, loading: false});
|
||||||
|
}
|
||||||
|
|
||||||
|
renderConfigurationRow() {
|
||||||
|
const {showWeightConfig} = this.state;
|
||||||
|
const weightFileManager = (
|
||||||
|
<WeightsFileManager
|
||||||
|
weights={this.state.weights}
|
||||||
|
onWeightsChange={(weights: Weights) => {
|
||||||
|
this.setState({weights});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const weightConfig = (
|
||||||
|
<WeightConfig
|
||||||
|
declarations={this.props.declarations}
|
||||||
|
nodeTypeWeights={this.state.weights.nodeTypeWeights}
|
||||||
|
edgeTypeWeights={this.state.weights.edgeTypeWeights}
|
||||||
|
onNodeWeightChange={(prefix, weight) => {
|
||||||
|
this.setState(({weights}) => {
|
||||||
|
weights.nodeTypeWeights.set(prefix, weight);
|
||||||
|
return {weights};
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
onEdgeWeightChange={(prefix, weight) => {
|
||||||
|
this.setState(({weights}) => {
|
||||||
|
weights.edgeTypeWeights.set(prefix, weight);
|
||||||
|
return {weights};
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
const paramsUpToDate = deepEqual(
|
||||||
|
this.params(),
|
||||||
|
this.state.timelineCred.params()
|
||||||
|
);
|
||||||
|
const analyzeButton = (
|
||||||
|
<button
|
||||||
|
disabled={this.state.loading || paramsUpToDate}
|
||||||
|
onClick={() => this.analyzeCred()}
|
||||||
|
>
|
||||||
|
re-compute cred
|
||||||
|
</button>
|
||||||
|
);
|
||||||
|
const {owner, name} = this.props.repoId;
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div style={{marginTop: 30, display: "flex"}}>
|
||||||
|
<span style={{paddingLeft: 30}}>
|
||||||
|
cred for {owner}/{name}
|
||||||
|
<a href={`/prototype/${owner}/${name}/`}>(legacy)</a>
|
||||||
|
</span>
|
||||||
|
<span style={{flexGrow: 1}} />
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
this.setState(({showWeightConfig}) => ({
|
||||||
|
showWeightConfig: !showWeightConfig,
|
||||||
|
}));
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{showWeightConfig
|
||||||
|
? "Hide weight configuration"
|
||||||
|
: "Show weight configuration"}
|
||||||
|
</button>
|
||||||
|
{analyzeButton}
|
||||||
|
</div>
|
||||||
|
{showWeightConfig && (
|
||||||
|
<div style={{marginTop: 10}}>
|
||||||
|
<span>Upload/Download weights:</span>
|
||||||
|
{weightFileManager}
|
||||||
|
{weightConfig}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const timelineCredView = (
|
||||||
|
<TimelineCredView
|
||||||
|
timelineCred={this.state.timelineCred}
|
||||||
|
selectedNodeFilter={this.state.timelineCred.config().scoreNodePrefix}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<div style={{width: 900, margin: "0 auto"}}>
|
||||||
|
{this.renderConfigurationRow()}
|
||||||
|
{timelineCredView}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React, {type ComponentType} from "react";
|
||||||
|
|
||||||
|
import type {RepoId} from "../core/repoId";
|
||||||
|
import type {Assets} from "../webutil/assets";
|
||||||
|
import HomepageTimeline from "./homepageTimeline";
|
||||||
|
|
||||||
|
export default function makeTimelinePage(
|
||||||
|
repoId: RepoId
|
||||||
|
): ComponentType<{|+assets: Assets|}> {
|
||||||
|
return class TimelinePage extends React.Component<{|+assets: Assets|}> {
|
||||||
|
render() {
|
||||||
|
return <HomepageTimeline assets={this.props.assets} repoId={repoId} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
// @flow
|
||||||
|
|
||||||
|
import React from "react";
|
||||||
|
|
||||||
|
import type {Assets} from "../webutil/assets";
|
||||||
|
import {TimelineApp, defaultLoader} from "../explorer/TimelineApp";
|
||||||
|
import type {RepoId} from "../core/repoId";
|
||||||
|
|
||||||
|
export default class TimelineExplorer extends React.Component<{|
|
||||||
|
+assets: Assets,
|
||||||
|
+repoId: RepoId,
|
||||||
|
|}> {
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<TimelineApp
|
||||||
|
assets={this.props.assets}
|
||||||
|
repoId={this.props.repoId}
|
||||||
|
loader={defaultLoader}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -85,6 +85,15 @@ function makeRouteData(registry /*: RepoIdRegistry */) /*: RouteData */ {
|
||||||
title: `${entry.repoId.owner}/${entry.repoId.name} • SourceCred`,
|
title: `${entry.repoId.owner}/${entry.repoId.name} • SourceCred`,
|
||||||
navTitle: null,
|
navTitle: null,
|
||||||
})),
|
})),
|
||||||
|
...registry.map((entry) => ({
|
||||||
|
path: `/timeline/${entry.repoId.owner}/${entry.repoId.name}/`,
|
||||||
|
contents: {
|
||||||
|
type: "PAGE",
|
||||||
|
component: () => require("./TimelinePage").default(entry.repoId),
|
||||||
|
},
|
||||||
|
title: `${entry.repoId.owner}/${entry.repoId.name} • Timeline`,
|
||||||
|
navTitle: null,
|
||||||
|
})),
|
||||||
{
|
{
|
||||||
path: "/discord-invite/",
|
path: "/discord-invite/",
|
||||||
contents: {
|
contents: {
|
||||||
|
@ -99,6 +108,10 @@ function makeRouteData(registry /*: RepoIdRegistry */) /*: RouteData */ {
|
||||||
"FileUploader",
|
"FileUploader",
|
||||||
() => require("../util/FileUploaderInspectionTest").default
|
() => require("../util/FileUploaderInspectionTest").default
|
||||||
),
|
),
|
||||||
|
inspectionTestFor(
|
||||||
|
"TimelineCredView",
|
||||||
|
() => require("../explorer/TimelineCredViewInspectionTest").default
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
exports.makeRouteData = makeRouteData;
|
exports.makeRouteData = makeRouteData;
|
||||||
|
|
224
yarn.lock
224
yarn.lock
|
@ -735,6 +735,13 @@
|
||||||
"@babel/plugin-transform-react-jsx-self" "^7.0.0"
|
"@babel/plugin-transform-react-jsx-self" "^7.0.0"
|
||||||
"@babel/plugin-transform-react-jsx-source" "^7.0.0"
|
"@babel/plugin-transform-react-jsx-source" "^7.0.0"
|
||||||
|
|
||||||
|
"@babel/runtime@^7.1.2":
|
||||||
|
version "7.4.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.4.5.tgz#582bb531f5f9dc67d2fcb682979894f75e253f12"
|
||||||
|
integrity sha512-TuI4qpWZP6lGOGIuGWtp9sPluqYICmbk8T/1vpSysqJxRPkudh/ofFWyqdcMsDf2s7KvDL4/YHgKyvcS3g9CJQ==
|
||||||
|
dependencies:
|
||||||
|
regenerator-runtime "^0.13.2"
|
||||||
|
|
||||||
"@babel/runtime@^7.4.5":
|
"@babel/runtime@^7.4.5":
|
||||||
version "7.5.1"
|
version "7.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.1.tgz#51b56e216e87103ab3f7d6040b464c538e242888"
|
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.5.1.tgz#51b56e216e87103ab3f7d6040b464c538e242888"
|
||||||
|
@ -1614,6 +1621,11 @@ bail@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.4.tgz#7181b66d508aa3055d3f6c13f0a0c720641dde9b"
|
resolved "https://registry.yarnpkg.com/bail/-/bail-1.0.4.tgz#7181b66d508aa3055d3f6c13f0a0c720641dde9b"
|
||||||
integrity sha512-S8vuDB4w6YpRhICUDET3guPlQpaJl7od94tpZ0Fvnyp+MKW/HyDTcRDck+29C9g+d/qQHnddRH3+94kZdrW0Ww==
|
integrity sha512-S8vuDB4w6YpRhICUDET3guPlQpaJl7od94tpZ0Fvnyp+MKW/HyDTcRDck+29C9g+d/qQHnddRH3+94kZdrW0Ww==
|
||||||
|
|
||||||
|
balanced-match@^0.4.2:
|
||||||
|
version "0.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-0.4.2.tgz#cb3f3e3c732dc0f01ee70b403f302e61d7709838"
|
||||||
|
integrity sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=
|
||||||
|
|
||||||
balanced-match@^1.0.0:
|
balanced-match@^1.0.0:
|
||||||
version "1.0.0"
|
version "1.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
|
||||||
|
@ -2065,6 +2077,11 @@ class-utils@^0.3.5:
|
||||||
isobject "^3.0.0"
|
isobject "^3.0.0"
|
||||||
static-extend "^0.1.1"
|
static-extend "^0.1.1"
|
||||||
|
|
||||||
|
classnames@^2.2.5:
|
||||||
|
version "2.2.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.2.6.tgz#43935bffdd291f326dad0a205309b38d00f650ce"
|
||||||
|
integrity sha512-JR/iSQOSt+LQIWwrwEzJ9uk0xfN3mTVYMwt1Ir5mUcSN6pU+V4zQFFaJsclJbPuAUQH+yfWef6tm7l1quW3C8Q==
|
||||||
|
|
||||||
cli-cursor@^2.1.0:
|
cli-cursor@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
|
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5"
|
||||||
|
@ -2307,6 +2324,11 @@ core-js@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
|
resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636"
|
||||||
integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=
|
integrity sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=
|
||||||
|
|
||||||
|
core-js@^2.5.1:
|
||||||
|
version "2.6.9"
|
||||||
|
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
|
||||||
|
integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==
|
||||||
|
|
||||||
core-util-is@1.0.2, core-util-is@~1.0.0:
|
core-util-is@1.0.2, core-util-is@~1.0.0:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||||
|
@ -2470,12 +2492,89 @@ cyclist@~0.2.2:
|
||||||
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
|
resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640"
|
||||||
integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=
|
integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA=
|
||||||
|
|
||||||
d3-array@^2.2.0:
|
d3-array@^1.2.0:
|
||||||
|
version "1.2.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-1.2.4.tgz#635ce4d5eea759f6f605863dbcfc30edc737f71f"
|
||||||
|
integrity sha512-KHW6M86R+FUPYGb3R5XiYjXPq7VzwxZ22buHhAEVG5ztoEcZZMLov530mmccaqA1GghZArjQV46fuc8kUqhhHw==
|
||||||
|
|
||||||
|
"d3-array@^1.2.0 || 2", d3-array@^2.2.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.2.0.tgz#a9e966b8f8d78f0888d98db1fb840fc8da8ac5c7"
|
resolved "https://registry.yarnpkg.com/d3-array/-/d3-array-2.2.0.tgz#a9e966b8f8d78f0888d98db1fb840fc8da8ac5c7"
|
||||||
integrity sha512-eE0QmSh6xToqM3sxHiJYg/QFdNn52ZEgmFE8A8abU8GsHvsIOolqH8B70/8+VGAKm5MlwaExhqR3DLIjOJMLPA==
|
integrity sha512-eE0QmSh6xToqM3sxHiJYg/QFdNn52ZEgmFE8A8abU8GsHvsIOolqH8B70/8+VGAKm5MlwaExhqR3DLIjOJMLPA==
|
||||||
|
|
||||||
d3-time@^1.0.11:
|
d3-collection@1:
|
||||||
|
version "1.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-collection/-/d3-collection-1.0.7.tgz#349bd2aa9977db071091c13144d5e4f16b5b310e"
|
||||||
|
integrity sha512-ii0/r5f4sjKNTfh84Di+DpztYwqKhEyUlKoPrzUFfeSkWxjW49xU2QzO9qrPrNkpdI0XJkfzvmTu8V2Zylln6A==
|
||||||
|
|
||||||
|
d3-color@1:
|
||||||
|
version "1.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-color/-/d3-color-1.2.3.tgz#6c67bb2af6df3cc8d79efcc4d3a3e83e28c8048f"
|
||||||
|
integrity sha512-x37qq3ChOTLd26hnps36lexMRhNXEtVxZ4B25rL0DVdDsGQIJGB18S7y9XDwlDD6MD/ZBzITCf4JjGMM10TZkw==
|
||||||
|
|
||||||
|
d3-format@1, d3-format@^1.3.2:
|
||||||
|
version "1.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-format/-/d3-format-1.3.2.tgz#6a96b5e31bcb98122a30863f7d92365c00603562"
|
||||||
|
integrity sha512-Z18Dprj96ExragQ0DeGi+SYPQ7pPfRMtUXtsg/ChVIKNBCzjO8XYJvRTC1usblx52lqge56V5ect+frYTQc8WQ==
|
||||||
|
|
||||||
|
d3-interpolate@1, d3-interpolate@^1.3.0:
|
||||||
|
version "1.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-interpolate/-/d3-interpolate-1.3.2.tgz#417d3ebdeb4bc4efcc8fd4361c55e4040211fd68"
|
||||||
|
integrity sha512-NlNKGopqaz9qM1PXh9gBF1KSCVh+jSFErrSlD/4hybwoNX/gt1d8CDbDW+3i+5UOHhjC6s6nMvRxcuoMVNgL2w==
|
||||||
|
dependencies:
|
||||||
|
d3-color "1"
|
||||||
|
|
||||||
|
d3-path@1:
|
||||||
|
version "1.0.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-path/-/d3-path-1.0.7.tgz#8de7cd693a75ac0b5480d3abaccd94793e58aae8"
|
||||||
|
integrity sha512-q0cW1RpvA5c5ma2rch62mX8AYaiLX0+bdaSM2wxSU9tXjU4DNvkx9qiUvjkuWCj3p22UO/hlPivujqMiR9PDzA==
|
||||||
|
|
||||||
|
d3-scale-chromatic@^1.3.3:
|
||||||
|
version "1.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-scale-chromatic/-/d3-scale-chromatic-1.3.3.tgz#dad4366f0edcb288f490128979c3c793583ed3c0"
|
||||||
|
integrity sha512-BWTipif1CimXcYfT02LKjAyItX5gKiwxuPRgr4xM58JwlLocWbjPLI7aMEjkcoOQXMkYsmNsvv3d2yl/OKuHHw==
|
||||||
|
dependencies:
|
||||||
|
d3-color "1"
|
||||||
|
d3-interpolate "1"
|
||||||
|
|
||||||
|
d3-scale@^2.1.0:
|
||||||
|
version "2.2.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-2.2.2.tgz#4e880e0b2745acaaddd3ede26a9e908a9e17b81f"
|
||||||
|
integrity sha512-LbeEvGgIb8UMcAa0EATLNX0lelKWGYDQiPdHj+gLblGVhGLyNbaCn3EvrJf0A3Y/uOOU5aD6MTh5ZFCdEwGiCw==
|
||||||
|
dependencies:
|
||||||
|
d3-array "^1.2.0"
|
||||||
|
d3-collection "1"
|
||||||
|
d3-format "1"
|
||||||
|
d3-interpolate "1"
|
||||||
|
d3-time "1"
|
||||||
|
d3-time-format "2"
|
||||||
|
|
||||||
|
d3-scale@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-scale/-/d3-scale-3.0.0.tgz#ddede1278ac3ea2bf3666de6ca625e20bed9b6c9"
|
||||||
|
integrity sha512-ktic5HBFlAZj2CN8CCl/p/JyY8bMQluN7+fA6ICE6yyoMOnSQAZ1Bb8/5LcNpNKMBMJge+5Vv4pWJhARYlQYFw==
|
||||||
|
dependencies:
|
||||||
|
d3-array "^1.2.0 || 2"
|
||||||
|
d3-format "1"
|
||||||
|
d3-interpolate "1"
|
||||||
|
d3-time "1"
|
||||||
|
d3-time-format "2"
|
||||||
|
|
||||||
|
d3-shape@^1.2.0:
|
||||||
|
version "1.3.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-shape/-/d3-shape-1.3.5.tgz#e81aea5940f59f0a79cfccac012232a8987c6033"
|
||||||
|
integrity sha512-VKazVR3phgD+MUCldapHD7P9kcrvPcexeX/PkMJmkUov4JM8IxsSg1DvbYoYich9AtdTsa5nNk2++ImPiDiSxg==
|
||||||
|
dependencies:
|
||||||
|
d3-path "1"
|
||||||
|
|
||||||
|
d3-time-format@2, d3-time-format@^2.1.3:
|
||||||
|
version "2.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/d3-time-format/-/d3-time-format-2.1.3.tgz#ae06f8e0126a9d60d6364eac5b1533ae1bac826b"
|
||||||
|
integrity sha512-6k0a2rZryzGm5Ihx+aFMuO1GgelgIz+7HhB4PH4OEndD5q2zGn1mDfRdNrulspOfR6JXkb2sThhDK41CSK85QA==
|
||||||
|
dependencies:
|
||||||
|
d3-time "1"
|
||||||
|
|
||||||
|
d3-time@1, d3-time@^1.0.11:
|
||||||
version "1.0.11"
|
version "1.0.11"
|
||||||
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.0.11.tgz#1d831a3e25cd189eb256c17770a666368762bbce"
|
resolved "https://registry.yarnpkg.com/d3-time/-/d3-time-1.0.11.tgz#1d831a3e25cd189eb256c17770a666368762bbce"
|
||||||
integrity sha512-Z3wpvhPLW4vEScGeIMUckDW7+3hWKOQfAWg/U7PlWBnQmeKQ00gCUsTtWSYulrKNA7ta8hJ+xXc6MHrMuITwEw==
|
integrity sha512-Z3wpvhPLW4vEScGeIMUckDW7+3hWKOQfAWg/U7PlWBnQmeKQ00gCUsTtWSYulrKNA7ta8hJ+xXc6MHrMuITwEw==
|
||||||
|
@ -2532,6 +2631,11 @@ decamelize@^1.2.0:
|
||||||
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
|
||||||
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
|
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
|
||||||
|
|
||||||
|
decimal.js-light@^2.4.1:
|
||||||
|
version "2.5.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/decimal.js-light/-/decimal.js-light-2.5.0.tgz#ca7faf504c799326df94b0ab920424fdfc125348"
|
||||||
|
integrity sha512-b3VJCbd2hwUpeRGG3Toob+CRo8W22xplipNhP3tN7TSVB/cyMX71P1vM2Xjc9H74uV6dS2hDDmo/rHq8L87Upg==
|
||||||
|
|
||||||
decode-uri-component@^0.2.0:
|
decode-uri-component@^0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
|
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
|
||||||
|
@ -2726,6 +2830,13 @@ doctrine@^3.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
esutils "^2.0.2"
|
esutils "^2.0.2"
|
||||||
|
|
||||||
|
dom-helpers@^3.4.0:
|
||||||
|
version "3.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-3.4.0.tgz#e9b369700f959f62ecde5a6babde4bccd9169af8"
|
||||||
|
integrity sha512-LnuPJ+dwqKDIyotW1VzmOZ5TONUN7CwkCR5hrgawTUbkBGYdeoNLZo6nNfGkCrjtE1nXXaj7iMMpDa8/d9WoIA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/runtime" "^7.1.2"
|
||||||
|
|
||||||
dom-serializer@0, dom-serializer@~0.1.0, dom-serializer@~0.1.1:
|
dom-serializer@0, dom-serializer@~0.1.0, dom-serializer@~0.1.1:
|
||||||
version "0.1.1"
|
version "0.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0"
|
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-0.1.1.tgz#1ec4059e284babed36eec2941d4a970a189ce7c0"
|
||||||
|
@ -5250,6 +5361,11 @@ lodash.clonedeep@^4.5.0:
|
||||||
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
|
||||||
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
|
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
|
||||||
|
|
||||||
|
lodash.debounce@^4.0.8:
|
||||||
|
version "4.0.8"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||||
|
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
|
||||||
|
|
||||||
lodash.defaults@^4.0.1:
|
lodash.defaults@^4.0.1:
|
||||||
version "4.2.0"
|
version "4.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
|
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
|
||||||
|
@ -5320,7 +5436,17 @@ lodash.sortby@^4.7.0:
|
||||||
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
||||||
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
|
integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
|
||||||
|
|
||||||
"lodash@>=3.5 <5", lodash@^4.0.0, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.4, lodash@^4.3.0:
|
lodash.throttle@^4.1.1:
|
||||||
|
version "4.1.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
|
||||||
|
integrity sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=
|
||||||
|
|
||||||
|
"lodash@>=3.5 <5", lodash@^4.15.0, lodash@^4.17.4, lodash@^4.3.0:
|
||||||
|
version "4.17.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
|
||||||
|
integrity sha512-svL3uiZf1RwhH+cWrfZn3A4+U58wbP0tGVTLQPbjplZxZ8ROD9VLuNgsRniTlLe7OlSqR79RUehXgpBW/s0IQw==
|
||||||
|
|
||||||
|
lodash@^4.0.0, lodash@^4.17.11, lodash@^4.17.5, lodash@~4.17.4:
|
||||||
version "4.17.11"
|
version "4.17.11"
|
||||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
|
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d"
|
||||||
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
|
integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==
|
||||||
|
@ -5396,6 +5522,11 @@ markdown-escapes@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.3.tgz#6155e10416efaafab665d466ce598216375195f5"
|
resolved "https://registry.yarnpkg.com/markdown-escapes/-/markdown-escapes-1.0.3.tgz#6155e10416efaafab665d466ce598216375195f5"
|
||||||
integrity sha512-XUi5HJhhV5R74k8/0H2oCbCiYf/u4cO/rX8tnGkRvrqhsr5BRNU6Mg0yt/8UIx1iIS8220BNJsDb7XnILhLepw==
|
integrity sha512-XUi5HJhhV5R74k8/0H2oCbCiYf/u4cO/rX8tnGkRvrqhsr5BRNU6Mg0yt/8UIx1iIS8220BNJsDb7XnILhLepw==
|
||||||
|
|
||||||
|
math-expression-evaluator@^1.2.14:
|
||||||
|
version "1.2.17"
|
||||||
|
resolved "https://registry.yarnpkg.com/math-expression-evaluator/-/math-expression-evaluator-1.2.17.tgz#de819fdbcd84dccd8fae59c6aeb79615b9d266ac"
|
||||||
|
integrity sha1-3oGf282E3M2PrlnGrreWFbnSZqw=
|
||||||
|
|
||||||
md5.js@^1.3.4:
|
md5.js@^1.3.4:
|
||||||
version "1.3.5"
|
version "1.3.5"
|
||||||
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
|
resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f"
|
||||||
|
@ -6518,7 +6649,7 @@ prop-types-exact@^1.2.0:
|
||||||
object.assign "^4.1.0"
|
object.assign "^4.1.0"
|
||||||
reflect.ownkeys "^0.2.0"
|
reflect.ownkeys "^0.2.0"
|
||||||
|
|
||||||
prop-types@^15.5.6, prop-types@^15.6.2, prop-types@^15.7.2:
|
prop-types@^15.5.6, prop-types@^15.6.0, prop-types@^15.6.2, prop-types@^15.7.2:
|
||||||
version "15.7.2"
|
version "15.7.2"
|
||||||
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
|
||||||
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ==
|
||||||
|
@ -6756,6 +6887,11 @@ react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6:
|
||||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
|
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.8.6.tgz#5bbc1e2d29141c9fbdfed456343fe2bc430a6a16"
|
||||||
integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
|
integrity sha512-aUk3bHfZ2bRSVFFbbeVS4i+lNPZr3/WM5jT2J5omUVV1zzcs1nAaf3l51ctA5FFvCRbhrH0bdAsRRQddFJZPtA==
|
||||||
|
|
||||||
|
react-lifecycles-compat@^3.0.4:
|
||||||
|
version "3.0.4"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362"
|
||||||
|
integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA==
|
||||||
|
|
||||||
react-markdown@^4.0.8:
|
react-markdown@^4.0.8:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-4.1.0.tgz#7fdf840ecbabc803f28156f7411c726b58f25a73"
|
resolved "https://registry.yarnpkg.com/react-markdown/-/react-markdown-4.1.0.tgz#7fdf840ecbabc803f28156f7411c726b58f25a73"
|
||||||
|
@ -6769,6 +6905,16 @@ react-markdown@^4.0.8:
|
||||||
unist-util-visit "^1.3.0"
|
unist-util-visit "^1.3.0"
|
||||||
xtend "^4.0.1"
|
xtend "^4.0.1"
|
||||||
|
|
||||||
|
react-resize-detector@^2.3.0:
|
||||||
|
version "2.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-resize-detector/-/react-resize-detector-2.3.0.tgz#57bad1ae26a28a62a2ddb678ba6ffdf8fa2b599c"
|
||||||
|
integrity sha512-oCAddEWWeFWYH5FAcHdBYcZjAw9fMzRUK9sWSx6WvSSOPVRxcHd5zTIGy/mOus+AhN/u6T4TMiWxvq79PywnJQ==
|
||||||
|
dependencies:
|
||||||
|
lodash.debounce "^4.0.8"
|
||||||
|
lodash.throttle "^4.1.1"
|
||||||
|
prop-types "^15.6.0"
|
||||||
|
resize-observer-polyfill "^1.5.0"
|
||||||
|
|
||||||
react-router@3.2.1:
|
react-router@3.2.1:
|
||||||
version "3.2.1"
|
version "3.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/react-router/-/react-router-3.2.1.tgz#b9a3279962bdfbe684c8bd0482b81ef288f0f244"
|
resolved "https://registry.yarnpkg.com/react-router/-/react-router-3.2.1.tgz#b9a3279962bdfbe684c8bd0482b81ef288f0f244"
|
||||||
|
@ -6782,6 +6928,16 @@ react-router@3.2.1:
|
||||||
prop-types "^15.5.6"
|
prop-types "^15.5.6"
|
||||||
warning "^3.0.0"
|
warning "^3.0.0"
|
||||||
|
|
||||||
|
react-smooth@^1.0.0:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-smooth/-/react-smooth-1.0.2.tgz#f7a2d932ece8db898646078c3c97f3e9533e0486"
|
||||||
|
integrity sha512-pIGzL1g9VGAsRsdZQokIK0vrCkcdKtnOnS1gyB2rrowdLy69lNSWoIjCTWAfgbiYvria8tm5hEZqj+jwXMkV4A==
|
||||||
|
dependencies:
|
||||||
|
lodash "~4.17.4"
|
||||||
|
prop-types "^15.6.0"
|
||||||
|
raf "^3.4.0"
|
||||||
|
react-transition-group "^2.5.0"
|
||||||
|
|
||||||
react-test-renderer@^16.0.0-0:
|
react-test-renderer@^16.0.0-0:
|
||||||
version "16.8.6"
|
version "16.8.6"
|
||||||
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.8.6.tgz#188d8029b8c39c786f998aa3efd3ffe7642d5ba1"
|
resolved "https://registry.yarnpkg.com/react-test-renderer/-/react-test-renderer-16.8.6.tgz#188d8029b8c39c786f998aa3efd3ffe7642d5ba1"
|
||||||
|
@ -6792,6 +6948,16 @@ react-test-renderer@^16.0.0-0:
|
||||||
react-is "^16.8.6"
|
react-is "^16.8.6"
|
||||||
scheduler "^0.13.6"
|
scheduler "^0.13.6"
|
||||||
|
|
||||||
|
react-transition-group@^2.5.0:
|
||||||
|
version "2.9.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-2.9.0.tgz#df9cdb025796211151a436c69a8f3b97b5b07c8d"
|
||||||
|
integrity sha512-+HzNTCHpeQyl4MJ/bdE0u6XRMe9+XG/+aL4mCxVN4DnPBQ0/5bfHWPDuOZUzYdMj94daZaZdCCc1Dzt9R/xSSg==
|
||||||
|
dependencies:
|
||||||
|
dom-helpers "^3.4.0"
|
||||||
|
loose-envify "^1.4.0"
|
||||||
|
prop-types "^15.6.2"
|
||||||
|
react-lifecycles-compat "^3.0.4"
|
||||||
|
|
||||||
react@^16.4.1:
|
react@^16.4.1:
|
||||||
version "16.8.6"
|
version "16.8.6"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe"
|
resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe"
|
||||||
|
@ -6874,6 +7040,30 @@ realpath-native@^1.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
util.promisify "^1.0.0"
|
util.promisify "^1.0.0"
|
||||||
|
|
||||||
|
recharts-scale@^0.4.2:
|
||||||
|
version "0.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/recharts-scale/-/recharts-scale-0.4.2.tgz#b66315d985cd9b80d5f7d977a5aab9a305abc354"
|
||||||
|
integrity sha512-p/cKt7j17D1CImLgX2f5+6IXLbRHGUQkogIp06VUoci/XkhOQiGSzUrsD1uRmiI7jha4u8XNFOjkHkzzBPivMg==
|
||||||
|
dependencies:
|
||||||
|
decimal.js-light "^2.4.1"
|
||||||
|
|
||||||
|
recharts@^1.6.2:
|
||||||
|
version "1.6.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/recharts/-/recharts-1.6.2.tgz#4ced884f04b680e8dac5d3e109f99b0e7cfb9b0f"
|
||||||
|
integrity sha512-NqVN8Hq5wrrBthTxQB+iCnZjup1dc+AYRIB6Q9ck9UjdSJTt4PbLepGpudQEYJEN5iIpP/I2vThC4uiTJa7xUQ==
|
||||||
|
dependencies:
|
||||||
|
classnames "^2.2.5"
|
||||||
|
core-js "^2.5.1"
|
||||||
|
d3-interpolate "^1.3.0"
|
||||||
|
d3-scale "^2.1.0"
|
||||||
|
d3-shape "^1.2.0"
|
||||||
|
lodash "^4.17.5"
|
||||||
|
prop-types "^15.6.0"
|
||||||
|
react-resize-detector "^2.3.0"
|
||||||
|
react-smooth "^1.0.0"
|
||||||
|
recharts-scale "^0.4.2"
|
||||||
|
reduce-css-calc "^1.3.0"
|
||||||
|
|
||||||
recursive-readdir@2.2.1:
|
recursive-readdir@2.2.1:
|
||||||
version "2.2.1"
|
version "2.2.1"
|
||||||
resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.1.tgz#90ef231d0778c5ce093c9a48d74e5c5422d13a99"
|
resolved "https://registry.yarnpkg.com/recursive-readdir/-/recursive-readdir-2.2.1.tgz#90ef231d0778c5ce093c9a48d74e5c5422d13a99"
|
||||||
|
@ -6881,6 +7071,22 @@ recursive-readdir@2.2.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
minimatch "3.0.3"
|
minimatch "3.0.3"
|
||||||
|
|
||||||
|
reduce-css-calc@^1.3.0:
|
||||||
|
version "1.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/reduce-css-calc/-/reduce-css-calc-1.3.0.tgz#747c914e049614a4c9cfbba629871ad1d2927716"
|
||||||
|
integrity sha1-dHyRTgSWFKTJz7umKYca0dKSdxY=
|
||||||
|
dependencies:
|
||||||
|
balanced-match "^0.4.2"
|
||||||
|
math-expression-evaluator "^1.2.14"
|
||||||
|
reduce-function-call "^1.0.1"
|
||||||
|
|
||||||
|
reduce-function-call@^1.0.1:
|
||||||
|
version "1.0.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/reduce-function-call/-/reduce-function-call-1.0.2.tgz#5a200bf92e0e37751752fe45b0ab330fd4b6be99"
|
||||||
|
integrity sha1-WiAL+S4ON3UXUv5FsKszD9S2vpk=
|
||||||
|
dependencies:
|
||||||
|
balanced-match "^0.4.2"
|
||||||
|
|
||||||
reflect.ownkeys@^0.2.0:
|
reflect.ownkeys@^0.2.0:
|
||||||
version "0.2.0"
|
version "0.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460"
|
resolved "https://registry.yarnpkg.com/reflect.ownkeys/-/reflect.ownkeys-0.2.0.tgz#749aceec7f3fdf8b63f927a04809e90c5c0b3460"
|
||||||
|
@ -6973,6 +7179,11 @@ remark-parse@^5.0.0:
|
||||||
vfile-location "^2.0.0"
|
vfile-location "^2.0.0"
|
||||||
xtend "^4.0.1"
|
xtend "^4.0.1"
|
||||||
|
|
||||||
|
remove-markdown@^0.3.0:
|
||||||
|
version "0.3.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/remove-markdown/-/remove-markdown-0.3.0.tgz#5e4b667493a93579728f3d52ecc1db9ca505dc98"
|
||||||
|
integrity sha1-XktmdJOpNXlyjz1S7MHbnKUF3Jg=
|
||||||
|
|
||||||
remove-trailing-separator@^1.0.1:
|
remove-trailing-separator@^1.0.1:
|
||||||
version "1.1.0"
|
version "1.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef"
|
||||||
|
@ -7060,6 +7271,11 @@ requires-port@^1.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
|
||||||
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
|
integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=
|
||||||
|
|
||||||
|
resize-observer-polyfill@^1.5.0:
|
||||||
|
version "1.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.1.tgz#0e9020dd3d21024458d4ebd27e23e40269810464"
|
||||||
|
integrity sha512-LwZrotdHOo12nQuZlHEmtuXdqGoOD0OhaxopaNFxWzInpEgaLWoVuAMbTzixuosCx2nEG58ngzW3vxdWoxIgdg==
|
||||||
|
|
||||||
resolve-cwd@^2.0.0:
|
resolve-cwd@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
|
resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a"
|
||||||
|
|
Loading…
Reference in New Issue