don't display quiet nodes on graph
This commit is contained in:
parent
8d00ba9372
commit
e9b8901aa5
|
@ -32,8 +32,6 @@ class NodeView(viewsets.ReadOnlyModelViewSet):
|
||||||
"""
|
"""
|
||||||
Endpoint to get a list of the graph's nodes in a SigmaJS-friendly format.
|
Endpoint to get a list of the graph's nodes in a SigmaJS-friendly format.
|
||||||
"""
|
"""
|
||||||
queryset = Instance.objects.filter(status='success')\
|
queryset = Instance.objects.filter(status='success', x_coord__isnull=False, y_coord__isnull=False, user_count__isnull=False)\
|
||||||
.filter(x_coord__isnull=False)\
|
.exclude(sources__isnull=True, targets__isnull=True)
|
||||||
.filter(y_coord__isnull=False)\
|
|
||||||
.filter(user_count__isnull=False)
|
|
||||||
serializer_class = NodeSerializer
|
serializer_class = NodeSerializer
|
||||||
|
|
24
backend/scraper/migrations/0002_auto_20190419_1346.py
Normal file
24
backend/scraper/migrations/0002_auto_20190419_1346.py
Normal file
|
@ -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'),
|
||||||
|
),
|
||||||
|
]
|
|
@ -51,8 +51,8 @@ class Edge(models.Model):
|
||||||
It aggregates stats from the asymmetrical PeerRelationship to a symmetrical one that's suitable for serving
|
It aggregates stats from the asymmetrical PeerRelationship to a symmetrical one that's suitable for serving
|
||||||
to the front-end.
|
to the front-end.
|
||||||
"""
|
"""
|
||||||
source = 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='+', on_delete=models.CASCADE)
|
target = models.ForeignKey(Instance, related_name='sources', on_delete=models.CASCADE)
|
||||||
weight = models.FloatField(blank=True, null=True)
|
weight = models.FloatField(blank=True, null=True)
|
||||||
|
|
||||||
# Metadata
|
# Metadata
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
import * as React from 'react';
|
import * as React from "react";
|
||||||
import { connect } from 'react-redux';
|
import { connect } from "react-redux";
|
||||||
import { Sigma, Filter, ForceAtlas2 } from 'react-sigma';
|
import { Sigma, Filter, ForceAtlas2 } from "react-sigma";
|
||||||
|
|
||||||
import { selectAndLoadInstance } from '../redux/actions';
|
import { selectAndLoadInstance } from "../redux/actions";
|
||||||
import { ErrorState } from './ErrorState';
|
import { ErrorState } from "./ErrorState";
|
||||||
|
|
||||||
const STYLE = {
|
const STYLE = {
|
||||||
bottom: "0",
|
bottom: "0",
|
||||||
left: "0",
|
left: "0",
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
right: "0",
|
right: "0",
|
||||||
top: "50px",
|
top: "50px"
|
||||||
}
|
};
|
||||||
const DEFAULT_NODE_COLOR = "#CED9E0";
|
const DEFAULT_NODE_COLOR = "#CED9E0";
|
||||||
const SELECTED_NODE_COLOR = "#48AFF0";
|
const SELECTED_NODE_COLOR = "#48AFF0";
|
||||||
const SETTINGS = {
|
const SETTINGS = {
|
||||||
|
@ -24,11 +24,10 @@ const SETTINGS = {
|
||||||
labelColor: "default",
|
labelColor: "default",
|
||||||
labelThreshold: 10,
|
labelThreshold: 10,
|
||||||
maxEdgeSize: 1,
|
maxEdgeSize: 1,
|
||||||
minEdgeSize: 0.3,
|
minEdgeSize: 0.3
|
||||||
}
|
};
|
||||||
|
|
||||||
class GraphImpl extends React.Component {
|
class GraphImpl extends React.Component {
|
||||||
|
|
||||||
constructor(props) {
|
constructor(props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.sigmaComponent = React.createRef();
|
this.sigmaComponent = React.createRef();
|
||||||
|
@ -41,10 +40,13 @@ class GraphImpl extends React.Component {
|
||||||
}
|
}
|
||||||
// Check that all nodes have size & coordinates; otherwise the graph will look messed up
|
// Check that all nodes have size & coordinates; otherwise the graph will look messed up
|
||||||
const lengthBeforeFilter = graph.nodes.length;
|
const lengthBeforeFilter = graph.nodes.length;
|
||||||
graph = {...graph, nodes: graph.nodes.filter(n => n.size && n.x && n.y)};
|
graph = { ...graph, nodes: graph.nodes.filter(n => n.size && n.x && n.y) };
|
||||||
if (graph.nodes.length !== lengthBeforeFilter) {
|
if (graph.nodes.length !== lengthBeforeFilter) {
|
||||||
// tslint:disable-next-line:no-console
|
// 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));
|
console.error(
|
||||||
|
"Some nodes were missing details: " +
|
||||||
|
this.props.graph.nodes.filter(n => !n.size || !n.x || !n.y).map(n => n.label)
|
||||||
|
);
|
||||||
return <ErrorState />;
|
return <ErrorState />;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
|
@ -58,9 +60,9 @@ class GraphImpl extends React.Component {
|
||||||
ref={this.sigmaComponent}
|
ref={this.sigmaComponent}
|
||||||
>
|
>
|
||||||
<Filter neighborsOf={this.props.currentInstanceName} />
|
<Filter neighborsOf={this.props.currentInstanceName} />
|
||||||
<ForceAtlas2 iterationsPerRender={1} timeout={10000}/>
|
<ForceAtlas2 iterationsPerRender={1} timeout={10000} />
|
||||||
</Sigma>
|
</Sigma>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
componentDidUpdate() {
|
componentDidUpdate() {
|
||||||
|
@ -72,31 +74,34 @@ class GraphImpl extends React.Component {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
onClickNode = (e) => {
|
onClickNode = e => {
|
||||||
this.props.selectAndLoadInstance(e.data.node.label);
|
this.props.selectAndLoadInstance(e.data.node.label);
|
||||||
}
|
};
|
||||||
|
|
||||||
onClickStage = (e) => {
|
onClickStage = e => {
|
||||||
// Deselect the instance (unless this was a drag event)
|
// Deselect the instance (unless this was a drag event)
|
||||||
if (!e.data.captor.isDragging) {
|
if (!e.data.captor.isDragging) {
|
||||||
this.props.selectAndLoadInstance(null);
|
this.props.selectAndLoadInstance(null);
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
colorNodes = (n) => {
|
colorNodes = n => {
|
||||||
if (this.props.currentInstanceName && n.id === this.props.currentInstanceName) {
|
if (this.props.currentInstanceName && n.id === this.props.currentInstanceName) {
|
||||||
n.color = SELECTED_NODE_COLOR;
|
n.color = SELECTED_NODE_COLOR;
|
||||||
} else {
|
} else {
|
||||||
n.color = DEFAULT_NODE_COLOR;
|
n.color = DEFAULT_NODE_COLOR;
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const mapStateToProps = (state) => ({
|
const mapStateToProps = state => ({
|
||||||
currentInstanceName: state.currentInstance.currentInstanceName,
|
currentInstanceName: state.currentInstance.currentInstanceName,
|
||||||
graph: state.data.graph,
|
graph: state.data.graph
|
||||||
})
|
});
|
||||||
const mapDispatchToProps = (dispatch) => ({
|
const mapDispatchToProps = dispatch => ({
|
||||||
selectAndLoadInstance: (instanceName) => dispatch(selectAndLoadInstance(instanceName)),
|
selectAndLoadInstance: instanceName => dispatch(selectAndLoadInstance(instanceName))
|
||||||
})
|
});
|
||||||
export const Graph = connect(mapStateToProps, mapDispatchToProps)(GraphImpl)
|
export const Graph = connect(
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToProps
|
||||||
|
)(GraphImpl);
|
||||||
|
|
|
@ -80,6 +80,12 @@ class SidebarImpl extends React.Component<ISidebarProps, ISidebarState> {
|
||||||
return this.renderMissingDataState();
|
return this.renderMissingDataState();
|
||||||
} else if (this.props.instanceLoadError) {
|
} else if (this.props.instanceLoadError) {
|
||||||
return <ErrorState />;
|
return <ErrorState />;
|
||||||
|
} 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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
|
@ -299,6 +305,16 @@ class SidebarImpl extends React.Component<ISidebarProps, ISidebarState> {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private renderQuietInstanceState = () => {
|
||||||
|
return (
|
||||||
|
<NonIdealState
|
||||||
|
icon={IconNames.CLEAN}
|
||||||
|
title="No interactions"
|
||||||
|
description="Users on this instance have not publicly interacted with any other instances recently. "
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
private openInstanceLink = () => {
|
private openInstanceLink = () => {
|
||||||
window.open("https://" + this.props.instanceName, "_blank");
|
window.open("https://" + this.props.instanceName, "_blank");
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue