From 58b36250e2a44813e81ded55e09fe64d36ea9106 Mon Sep 17 00:00:00 2001 From: Tao Bojlen Date: Mon, 3 Sep 2018 21:30:11 +0200 Subject: [PATCH] show neighbors in sidebar --- apiv1/serializers.py | 16 ++++- frontend/package.json | 2 + frontend/src/components/Sidebar.tsx | 105 +++++++++++++++++++--------- frontend/yarn.lock | 4 ++ 4 files changed, 92 insertions(+), 35 deletions(-) diff --git a/apiv1/serializers.py b/apiv1/serializers.py index 2efcd1d..86f121f 100644 --- a/apiv1/serializers.py +++ b/apiv1/serializers.py @@ -5,6 +5,9 @@ from scraper.models import Instance, Edge class InstanceListSerializer(serializers.ModelSerializer): + """ + Minimal instance details used in the full list of instances. + """ class Meta: model = Instance fields = ('name', 'user_count') @@ -20,6 +23,9 @@ class InstanceListSerializer(serializers.ModelSerializer): class InstanceDetailSerializer(serializers.ModelSerializer): + """ + Detailed instance view. + """ userCount = serializers.SerializerMethodField() statusCount = serializers.SerializerMethodField() domainCount = serializers.SerializerMethodField() @@ -40,10 +46,15 @@ class InstanceDetailSerializer(serializers.ModelSerializer): class Meta: model = Instance - fields = ('name', 'description', 'version', 'userCount', 'statusCount', 'domainCount', 'peers', 'lastUpdated', 'status') + fields = ('name', 'description', 'version', 'userCount', + 'statusCount', 'domainCount', 'peers', 'lastUpdated', + 'status') class EdgeSerializer(serializers.ModelSerializer): + """ + Used for displaying the graph. + """ id = serializers.SerializerMethodField('get_pk') size = serializers.SerializerMethodField('get_weight') @@ -59,6 +70,9 @@ class EdgeSerializer(serializers.ModelSerializer): class NodeSerializer(serializers.ModelSerializer): + """ + Used for displaying the graph. + """ id = serializers.SerializerMethodField('get_name') label = serializers.SerializerMethodField('get_name') size = serializers.SerializerMethodField() diff --git a/frontend/package.json b/frontend/package.json index 2e03cba..c74f031 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,6 +8,7 @@ "@blueprintjs/select": "^3.1.0", "classnames": "^2.2.6", "cross-fetch": "^2.2.2", + "lodash": "^4.17.10", "moment": "^2.22.2", "normalize.css": "^8.0.0", "react": "^16.4.2", @@ -29,6 +30,7 @@ "devDependencies": { "@types/classnames": "^2.2.6", "@types/jest": "^23.3.1", + "@types/lodash": "^4.14.116", "@types/node": "^10.9.2", "@types/react": "^16.4.12", "@types/react-dom": "^16.0.7", diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 2b906a5..a7b77ab 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -1,3 +1,4 @@ +import { orderBy } from 'lodash'; import * as moment from 'moment'; import * as React from 'react'; import { connect } from 'react-redux'; @@ -5,14 +6,16 @@ import { Dispatch } from 'redux'; import * as sanitize from 'sanitize-html'; import { - AnchorButton, Card, Classes, Divider, Elevation, HTMLTable, NonIdealState, Position, Tooltip + AnchorButton, Card, Classes, Divider, Elevation, HTMLTable, NonIdealState, Position, Tab, Tabs, + Tooltip } from '@blueprintjs/core'; import { IconNames } from '@blueprintjs/icons'; import { selectAndLoadInstance } from '../redux/actions'; -import { IAppState, IInstanceDetails } from '../redux/types'; +import { IAppState, IGraph, IInstanceDetails } from '../redux/types'; interface ISidebarProps { + graph?: IGraph, instanceName: string | null, instanceDetails: IInstanceDetails | null, isLoadingInstanceDetails: boolean; @@ -40,14 +43,23 @@ class SidebarImpl extends React.Component { return (
{this.renderHeading()} - {this.renderDescription()} - {this.renderVersion()} - {this.renderCounts()} - {this.renderPeers()} + + {this.props.instanceDetails.description && + } + {this.shouldRenderStats() && + } + + +
); } + private shouldRenderStats = () => { + const details = this.props.instanceDetails; + return details && (details.version || details.userCount || details.statusCount || details.domainCount); + } + private renderHeading = () => { let content: JSX.Element; if (!this.props.instanceName) { @@ -80,41 +92,24 @@ class SidebarImpl extends React.Component { return; } return ( -
-

Description

-
- -
+

) } - private renderVersion = () => { + private renderVersionAndCounts = () => { const version = this.props.instanceDetails!.version; - if (!version) { - return; - } - return ( -

-

Version

- {version} - -
- ) - } - - private renderCounts = () => { const userCount = this.props.instanceDetails!.userCount; const statusCount = this.props.instanceDetails!.statusCount; const domainCount = this.props.instanceDetails!.domainCount; const lastUpdated = this.props.instanceDetails!.lastUpdated; - if (!userCount && !statusCount && !domainCount) { - return; - } return (
-

Stats

+ + Version + {{version} || "Unknown"} + Users {userCount || "Unknown"} @@ -133,11 +128,50 @@ class SidebarImpl extends React.Component { -
) } + private renderNeighbors = () => { + if (!this.props.graph || !this.props.instanceName) { + return; + } + const edges = this.props.graph.edges.filter(e => [e.source, e.target].indexOf(this.props.instanceName!) > -1); + const neighbors: any[] = []; + edges.forEach(e => { + if (e.source === this.props.instanceName) { + neighbors.push({neighbor: e.target, weight: e.size}); + } else { + neighbors.push({neighbor: e.source, weight: e.size}); + } + }) + const neighborRows = orderBy(neighbors, ['weight'], ['desc']).map((neighborDetails: any, idx: number) => ( + + {neighborDetails.neighbor} + {neighborDetails.weight.toFixed(4)} + + )); + return ( +
+

+ The mention ratio is the average of how many times the two instances mention each other per status. + A mention ratio of 1 would mean that every single status contained a mention of a user on the other instance. +

+ + + + Instance + Mention ratio + + + + {neighborRows} + + +
+ ); + } + private renderPeers = () => { const peers = this.props.instanceDetails!.peers; if (!peers || peers.length === 0) { @@ -145,13 +179,15 @@ class SidebarImpl extends React.Component { } const peerRows = peers.map(instance => ( - {instance.name} + {instance.name} )); return (
-

Known instances

- +

+ All the instances, past and present, that {this.props.instanceName} knows about. +

+ {peerRows} @@ -221,12 +257,13 @@ class SidebarImpl extends React.Component { window.open("https://" + this.props.instanceName, "_blank"); } - private selectInstance = (e: any)=> { + private selectInstance = (e: any) => { this.props.selectAndLoadInstance(e.target.innerText); } } const mapStateToProps = (state: IAppState) => ({ + graph: state.data.graph, instanceDetails: state.currentInstance.currentInstanceDetails, instanceName: state.currentInstance.currentInstanceName, isLoadingInstanceDetails: state.currentInstance.isLoadingInstanceDetails, diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 0744700..c86923a 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -62,6 +62,10 @@ version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" +"@types/lodash@^4.14.116": + version "4.14.116" + resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.116.tgz#5ccf215653e3e8c786a58390751033a9adca0eb9" + "@types/node@*", "@types/node@^10.9.2": version "10.9.2" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.9.2.tgz#f0ab8dced5cd6c56b26765e1c0d9e4fdcc9f2a00"