show neighbors in sidebar
This commit is contained in:
parent
7bf02ea60c
commit
58b36250e2
|
@ -5,6 +5,9 @@ from scraper.models import Instance, Edge
|
||||||
|
|
||||||
|
|
||||||
class InstanceListSerializer(serializers.ModelSerializer):
|
class InstanceListSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
Minimal instance details used in the full list of instances.
|
||||||
|
"""
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Instance
|
model = Instance
|
||||||
fields = ('name', 'user_count')
|
fields = ('name', 'user_count')
|
||||||
|
@ -20,6 +23,9 @@ class InstanceListSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class InstanceDetailSerializer(serializers.ModelSerializer):
|
class InstanceDetailSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
Detailed instance view.
|
||||||
|
"""
|
||||||
userCount = serializers.SerializerMethodField()
|
userCount = serializers.SerializerMethodField()
|
||||||
statusCount = serializers.SerializerMethodField()
|
statusCount = serializers.SerializerMethodField()
|
||||||
domainCount = serializers.SerializerMethodField()
|
domainCount = serializers.SerializerMethodField()
|
||||||
|
@ -40,10 +46,15 @@ class InstanceDetailSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = Instance
|
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):
|
class EdgeSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
Used for displaying the graph.
|
||||||
|
"""
|
||||||
id = serializers.SerializerMethodField('get_pk')
|
id = serializers.SerializerMethodField('get_pk')
|
||||||
size = serializers.SerializerMethodField('get_weight')
|
size = serializers.SerializerMethodField('get_weight')
|
||||||
|
|
||||||
|
@ -59,6 +70,9 @@ class EdgeSerializer(serializers.ModelSerializer):
|
||||||
|
|
||||||
|
|
||||||
class NodeSerializer(serializers.ModelSerializer):
|
class NodeSerializer(serializers.ModelSerializer):
|
||||||
|
"""
|
||||||
|
Used for displaying the graph.
|
||||||
|
"""
|
||||||
id = serializers.SerializerMethodField('get_name')
|
id = serializers.SerializerMethodField('get_name')
|
||||||
label = serializers.SerializerMethodField('get_name')
|
label = serializers.SerializerMethodField('get_name')
|
||||||
size = serializers.SerializerMethodField()
|
size = serializers.SerializerMethodField()
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
"@blueprintjs/select": "^3.1.0",
|
"@blueprintjs/select": "^3.1.0",
|
||||||
"classnames": "^2.2.6",
|
"classnames": "^2.2.6",
|
||||||
"cross-fetch": "^2.2.2",
|
"cross-fetch": "^2.2.2",
|
||||||
|
"lodash": "^4.17.10",
|
||||||
"moment": "^2.22.2",
|
"moment": "^2.22.2",
|
||||||
"normalize.css": "^8.0.0",
|
"normalize.css": "^8.0.0",
|
||||||
"react": "^16.4.2",
|
"react": "^16.4.2",
|
||||||
|
@ -29,6 +30,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/classnames": "^2.2.6",
|
"@types/classnames": "^2.2.6",
|
||||||
"@types/jest": "^23.3.1",
|
"@types/jest": "^23.3.1",
|
||||||
|
"@types/lodash": "^4.14.116",
|
||||||
"@types/node": "^10.9.2",
|
"@types/node": "^10.9.2",
|
||||||
"@types/react": "^16.4.12",
|
"@types/react": "^16.4.12",
|
||||||
"@types/react-dom": "^16.0.7",
|
"@types/react-dom": "^16.0.7",
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import { orderBy } from 'lodash';
|
||||||
import * as moment from 'moment';
|
import * as moment from 'moment';
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { connect } from 'react-redux';
|
import { connect } from 'react-redux';
|
||||||
|
@ -5,14 +6,16 @@ import { Dispatch } from 'redux';
|
||||||
import * as sanitize from 'sanitize-html';
|
import * as sanitize from 'sanitize-html';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AnchorButton, Card, Classes, Divider, Elevation, HTMLTable, NonIdealState, Position, Tooltip
|
AnchorButton, Card, Classes, Divider, Elevation, HTMLTable, NonIdealState, Position, Tab, Tabs,
|
||||||
|
Tooltip
|
||||||
} from '@blueprintjs/core';
|
} from '@blueprintjs/core';
|
||||||
import { IconNames } from '@blueprintjs/icons';
|
import { IconNames } from '@blueprintjs/icons';
|
||||||
|
|
||||||
import { selectAndLoadInstance } from '../redux/actions';
|
import { selectAndLoadInstance } from '../redux/actions';
|
||||||
import { IAppState, IInstanceDetails } from '../redux/types';
|
import { IAppState, IGraph, IInstanceDetails } from '../redux/types';
|
||||||
|
|
||||||
interface ISidebarProps {
|
interface ISidebarProps {
|
||||||
|
graph?: IGraph,
|
||||||
instanceName: string | null,
|
instanceName: string | null,
|
||||||
instanceDetails: IInstanceDetails | null,
|
instanceDetails: IInstanceDetails | null,
|
||||||
isLoadingInstanceDetails: boolean;
|
isLoadingInstanceDetails: boolean;
|
||||||
|
@ -40,14 +43,23 @@ class SidebarImpl extends React.Component<ISidebarProps> {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{this.renderHeading()}
|
{this.renderHeading()}
|
||||||
{this.renderDescription()}
|
<Tabs>
|
||||||
{this.renderVersion()}
|
{this.props.instanceDetails.description &&
|
||||||
{this.renderCounts()}
|
<Tab id="description" title="Description" panel={this.renderDescription()} />}
|
||||||
{this.renderPeers()}
|
{this.shouldRenderStats() &&
|
||||||
|
<Tab id="stats" title="Details" panel={this.renderVersionAndCounts()} />}
|
||||||
|
<Tab id="neighbors" title="Neighbors" panel={this.renderNeighbors()} />
|
||||||
|
<Tab id="peers" title="Known peers" panel={this.renderPeers()} />
|
||||||
|
</Tabs>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private shouldRenderStats = () => {
|
||||||
|
const details = this.props.instanceDetails;
|
||||||
|
return details && (details.version || details.userCount || details.statusCount || details.domainCount);
|
||||||
|
}
|
||||||
|
|
||||||
private renderHeading = () => {
|
private renderHeading = () => {
|
||||||
let content: JSX.Element;
|
let content: JSX.Element;
|
||||||
if (!this.props.instanceName) {
|
if (!this.props.instanceName) {
|
||||||
|
@ -80,41 +92,24 @@ class SidebarImpl extends React.Component<ISidebarProps> {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<div>
|
<p className={Classes.RUNNING_TEXT} dangerouslySetInnerHTML={{__html: sanitize(description)}} />
|
||||||
<h4>Description</h4>
|
|
||||||
<div className={Classes.RUNNING_TEXT} dangerouslySetInnerHTML={{__html: sanitize(description)}} />
|
|
||||||
<Divider />
|
|
||||||
</div>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderVersion = () => {
|
private renderVersionAndCounts = () => {
|
||||||
const version = this.props.instanceDetails!.version;
|
const version = this.props.instanceDetails!.version;
|
||||||
if (!version) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<div>
|
|
||||||
<h4>Version</h4>
|
|
||||||
<code className={Classes.CODE}>{version}</code>
|
|
||||||
<Divider />
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private renderCounts = () => {
|
|
||||||
const userCount = this.props.instanceDetails!.userCount;
|
const userCount = this.props.instanceDetails!.userCount;
|
||||||
const statusCount = this.props.instanceDetails!.statusCount;
|
const statusCount = this.props.instanceDetails!.statusCount;
|
||||||
const domainCount = this.props.instanceDetails!.domainCount;
|
const domainCount = this.props.instanceDetails!.domainCount;
|
||||||
const lastUpdated = this.props.instanceDetails!.lastUpdated;
|
const lastUpdated = this.props.instanceDetails!.lastUpdated;
|
||||||
if (!userCount && !statusCount && !domainCount) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h4>Stats</h4>
|
|
||||||
<HTMLTable small={true} striped={true} className="fediverse-sidebar-table">
|
<HTMLTable small={true} striped={true} className="fediverse-sidebar-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td>Version</td>
|
||||||
|
<td>{<code>{version}</code> || "Unknown"}</td>
|
||||||
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Users</td>
|
<td>Users</td>
|
||||||
<td>{userCount || "Unknown"}</td>
|
<td>{userCount || "Unknown"}</td>
|
||||||
|
@ -133,11 +128,50 @@ class SidebarImpl extends React.Component<ISidebarProps> {
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</HTMLTable>
|
</HTMLTable>
|
||||||
<Divider />
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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) => (
|
||||||
|
<tr key={idx}>
|
||||||
|
<td><AnchorButton minimal={true} onClick={this.selectInstance}>{neighborDetails.neighbor}</AnchorButton></td>
|
||||||
|
<td>{neighborDetails.weight.toFixed(4)}</td>
|
||||||
|
</tr>
|
||||||
|
));
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<p className={Classes.TEXT_MUTED}>
|
||||||
|
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.
|
||||||
|
</p>
|
||||||
|
<HTMLTable small={true} striped={true} interactive={false} className="fediverse-sidebar-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Instance</th>
|
||||||
|
<th>Mention ratio</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{neighborRows}
|
||||||
|
</tbody>
|
||||||
|
</HTMLTable>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
private renderPeers = () => {
|
private renderPeers = () => {
|
||||||
const peers = this.props.instanceDetails!.peers;
|
const peers = this.props.instanceDetails!.peers;
|
||||||
if (!peers || peers.length === 0) {
|
if (!peers || peers.length === 0) {
|
||||||
|
@ -145,13 +179,15 @@ class SidebarImpl extends React.Component<ISidebarProps> {
|
||||||
}
|
}
|
||||||
const peerRows = peers.map(instance => (
|
const peerRows = peers.map(instance => (
|
||||||
<tr key={instance.name} onClick={this.selectInstance}>
|
<tr key={instance.name} onClick={this.selectInstance}>
|
||||||
<td>{instance.name}</td>
|
<td><AnchorButton minimal={true} onClick={this.selectInstance}>{instance.name}</AnchorButton></td>
|
||||||
</tr>
|
</tr>
|
||||||
));
|
));
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<h4>Known instances</h4>
|
<p className={Classes.TEXT_MUTED}>
|
||||||
<HTMLTable small={true} striped={true} interactive={true} className="fediverse-sidebar-table">
|
All the instances, past and present, that {this.props.instanceName} knows about.
|
||||||
|
</p>
|
||||||
|
<HTMLTable small={true} striped={true} interactive={false} className="fediverse-sidebar-table">
|
||||||
<tbody>
|
<tbody>
|
||||||
{peerRows}
|
{peerRows}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
@ -221,12 +257,13 @@ class SidebarImpl extends React.Component<ISidebarProps> {
|
||||||
window.open("https://" + this.props.instanceName, "_blank");
|
window.open("https://" + this.props.instanceName, "_blank");
|
||||||
}
|
}
|
||||||
|
|
||||||
private selectInstance = (e: any)=> {
|
private selectInstance = (e: any) => {
|
||||||
this.props.selectAndLoadInstance(e.target.innerText);
|
this.props.selectAndLoadInstance(e.target.innerText);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state: IAppState) => ({
|
const mapStateToProps = (state: IAppState) => ({
|
||||||
|
graph: state.data.graph,
|
||||||
instanceDetails: state.currentInstance.currentInstanceDetails,
|
instanceDetails: state.currentInstance.currentInstanceDetails,
|
||||||
instanceName: state.currentInstance.currentInstanceName,
|
instanceName: state.currentInstance.currentInstanceName,
|
||||||
isLoadingInstanceDetails: state.currentInstance.isLoadingInstanceDetails,
|
isLoadingInstanceDetails: state.currentInstance.isLoadingInstanceDetails,
|
||||||
|
|
|
@ -62,6 +62,10 @@
|
||||||
version "0.0.29"
|
version "0.0.29"
|
||||||
resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee"
|
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":
|
"@types/node@*", "@types/node@^10.9.2":
|
||||||
version "10.9.2"
|
version "10.9.2"
|
||||||
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.9.2.tgz#f0ab8dced5cd6c56b26765e1c0d9e4fdcc9f2a00"
|
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.9.2.tgz#f0ab8dced5cd6c56b26765e1c0d9e4fdcc9f2a00"
|
||||||
|
|
Loading…
Reference in a new issue