From e9b8901aa55c7e97f95309cf8cbe3f93a14f60b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tao=20Bror=20Bojl=C3=A9n?= Date: Fri, 19 Apr 2019 15:29:45 +0100 Subject: [PATCH] don't display quiet nodes on graph --- backend/apiv1/views.py | 6 +- .../migrations/0002_auto_20190419_1346.py | 24 +++ backend/scraper/models.py | 4 +- frontend/src/components/Graph.jsx | 171 +++++++++--------- frontend/src/components/Sidebar.tsx | 16 ++ 5 files changed, 132 insertions(+), 89 deletions(-) create mode 100644 backend/scraper/migrations/0002_auto_20190419_1346.py diff --git a/backend/apiv1/views.py b/backend/apiv1/views.py index 9be587f..eeedb45 100644 --- a/backend/apiv1/views.py +++ b/backend/apiv1/views.py @@ -32,8 +32,6 @@ class NodeView(viewsets.ReadOnlyModelViewSet): """ Endpoint to get a list of the graph's nodes in a SigmaJS-friendly format. """ - queryset = Instance.objects.filter(status='success')\ - .filter(x_coord__isnull=False)\ - .filter(y_coord__isnull=False)\ - .filter(user_count__isnull=False) + queryset = Instance.objects.filter(status='success', x_coord__isnull=False, y_coord__isnull=False, user_count__isnull=False)\ + .exclude(sources__isnull=True, targets__isnull=True) serializer_class = NodeSerializer diff --git a/backend/scraper/migrations/0002_auto_20190419_1346.py b/backend/scraper/migrations/0002_auto_20190419_1346.py new file mode 100644 index 0000000..07d9486 --- /dev/null +++ b/backend/scraper/migrations/0002_auto_20190419_1346.py @@ -0,0 +1,24 @@ +# Generated by Django 2.1.7 on 2019-04-19 13:46 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('scraper', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='edge', + name='source', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='targets', to='scraper.Instance'), + ), + migrations.AlterField( + model_name='edge', + name='target', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sources', to='scraper.Instance'), + ), + ] diff --git a/backend/scraper/models.py b/backend/scraper/models.py index aeb4757..1387630 100644 --- a/backend/scraper/models.py +++ b/backend/scraper/models.py @@ -51,8 +51,8 @@ class Edge(models.Model): It aggregates stats from the asymmetrical PeerRelationship to a symmetrical one that's suitable for serving to the front-end. """ - source = models.ForeignKey(Instance, related_name='+', on_delete=models.CASCADE) - target = models.ForeignKey(Instance, related_name='+', on_delete=models.CASCADE) + source = models.ForeignKey(Instance, related_name='targets', on_delete=models.CASCADE) + target = models.ForeignKey(Instance, related_name='sources', on_delete=models.CASCADE) weight = models.FloatField(blank=True, null=True) # Metadata diff --git a/frontend/src/components/Graph.jsx b/frontend/src/components/Graph.jsx index 204f74f..f78bf5d 100644 --- a/frontend/src/components/Graph.jsx +++ b/frontend/src/components/Graph.jsx @@ -1,102 +1,107 @@ -import * as React from 'react'; -import { connect } from 'react-redux'; -import { Sigma, Filter, ForceAtlas2 } from 'react-sigma'; +import * as React from "react"; +import { connect } from "react-redux"; +import { Sigma, Filter, ForceAtlas2 } from "react-sigma"; -import { selectAndLoadInstance } from '../redux/actions'; -import { ErrorState } from './ErrorState'; +import { selectAndLoadInstance } from "../redux/actions"; +import { ErrorState } from "./ErrorState"; const STYLE = { - bottom: "0", - left: "0", - position: "absolute", - right: "0", - top: "50px", -} + bottom: "0", + left: "0", + position: "absolute", + right: "0", + top: "50px" +}; const DEFAULT_NODE_COLOR = "#CED9E0"; const SELECTED_NODE_COLOR = "#48AFF0"; const SETTINGS = { - defaultEdgeColor: "#5C7080", - defaultLabelColor: "#F5F8FA", - defaultNodeColor: DEFAULT_NODE_COLOR, - drawEdges: true, - drawLabels: true, - edgeColor: "default", - labelColor: "default", - labelThreshold: 10, - maxEdgeSize: 1, - minEdgeSize: 0.3, -} + defaultEdgeColor: "#5C7080", + defaultLabelColor: "#F5F8FA", + defaultNodeColor: DEFAULT_NODE_COLOR, + drawEdges: true, + drawLabels: true, + edgeColor: "default", + labelColor: "default", + labelThreshold: 10, + maxEdgeSize: 1, + minEdgeSize: 0.3 +}; class GraphImpl extends React.Component { + constructor(props) { + super(props); + this.sigmaComponent = React.createRef(); + } - constructor(props) { - super(props); - this.sigmaComponent = React.createRef(); + render() { + let graph = this.props.graph; + if (!graph) { + return ; } + // Check that all nodes have size & coordinates; otherwise the graph will look messed up + const lengthBeforeFilter = graph.nodes.length; + graph = { ...graph, nodes: graph.nodes.filter(n => n.size && n.x && n.y) }; + if (graph.nodes.length !== lengthBeforeFilter) { + // tslint:disable-next-line:no-console + console.error( + "Some nodes were missing details: " + + this.props.graph.nodes.filter(n => !n.size || !n.x || !n.y).map(n => n.label) + ); + return ; + } + return ( + + + + + ); + } - render() { - let graph = this.props.graph; - if (!graph) { - return ; - } - // Check that all nodes have size & coordinates; otherwise the graph will look messed up - const lengthBeforeFilter = graph.nodes.length; - graph = {...graph, nodes: graph.nodes.filter(n => n.size && n.x && n.y)}; - if (graph.nodes.length !== lengthBeforeFilter) { - // tslint:disable-next-line:no-console - console.error("Some nodes were missing details: " + this.props.graph.nodes.filter(n => !n.size || !n.x || !n.y).map(n => n.label)); - return ; - } - return ( - - - - - ) + componentDidUpdate() { + const sigma = this.sigmaComponent && this.sigmaComponent.current.sigma; + // Check if sigma exists s.t. nothing breaks if the graph didn't load (for whatever reason) + if (sigma) { + sigma.graph.nodes().map(this.colorNodes); + sigma.refresh(); } + } - componentDidUpdate() { - const sigma = this.sigmaComponent && this.sigmaComponent.current.sigma; - // Check if sigma exists s.t. nothing breaks if the graph didn't load (for whatever reason) - if (sigma) { - sigma.graph.nodes().map(this.colorNodes); - sigma.refresh(); - } - } + onClickNode = e => { + this.props.selectAndLoadInstance(e.data.node.label); + }; - onClickNode = (e) => { - this.props.selectAndLoadInstance(e.data.node.label); + onClickStage = e => { + // Deselect the instance (unless this was a drag event) + if (!e.data.captor.isDragging) { + this.props.selectAndLoadInstance(null); } + }; - onClickStage = (e) => { - // Deselect the instance (unless this was a drag event) - if (!e.data.captor.isDragging) { - this.props.selectAndLoadInstance(null); - } - } - - colorNodes = (n) => { - if (this.props.currentInstanceName && n.id === this.props.currentInstanceName) { - n.color = SELECTED_NODE_COLOR; - } else { - n.color = DEFAULT_NODE_COLOR; - } + colorNodes = n => { + if (this.props.currentInstanceName && n.id === this.props.currentInstanceName) { + n.color = SELECTED_NODE_COLOR; + } else { + n.color = DEFAULT_NODE_COLOR; } + }; } -const mapStateToProps = (state) => ({ - currentInstanceName: state.currentInstance.currentInstanceName, - graph: state.data.graph, -}) -const mapDispatchToProps = (dispatch) => ({ - selectAndLoadInstance: (instanceName) => dispatch(selectAndLoadInstance(instanceName)), -}) -export const Graph = connect(mapStateToProps, mapDispatchToProps)(GraphImpl) +const mapStateToProps = state => ({ + currentInstanceName: state.currentInstance.currentInstanceName, + graph: state.data.graph +}); +const mapDispatchToProps = dispatch => ({ + selectAndLoadInstance: instanceName => dispatch(selectAndLoadInstance(instanceName)) +}); +export const Graph = connect( + mapStateToProps, + mapDispatchToProps +)(GraphImpl); diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 648f0e2..a6247d8 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -80,6 +80,12 @@ class SidebarImpl extends React.Component { return this.renderMissingDataState(); } else if (this.props.instanceLoadError) { return ; + } else if ( + this.props.graph && + this.props.instanceName && + this.props.graph.nodes.map(n => n.id).indexOf(this.props.instanceName) < 0 + ) { + return this.renderQuietInstanceState(); } return (
@@ -299,6 +305,16 @@ class SidebarImpl extends React.Component { ); }; + private renderQuietInstanceState = () => { + return ( + + ); + }; + private openInstanceLink = () => { window.open("https://" + this.props.instanceName, "_blank"); };