Extracted 'static' methods outside of classes for the 'class-methods-use-this' lint rule

This commit is contained in:
Filip Barl
2016-12-07 17:12:51 +01:00
parent 8dce4e8f36
commit 86d61cf0d6
9 changed files with 171 additions and 168 deletions

View File

@@ -33,6 +33,5 @@
"jsx-a11y/no-static-element-interactions": 0,
"react/jsx-no-target-blank": 0,
"react/no-find-dom-node": 0,
"class-methods-use-this": 0,
}
}

View File

@@ -115,6 +115,80 @@ function updateLayout(width, height, nodes, baseOptions) {
}
function centerSelectedNode(props, state) {
let stateNodes = state.nodes;
let stateEdges = state.edges;
if (!stateNodes.has(props.selectedNodeId)) {
return {};
}
const adjacentNodes = props.adjacentNodes;
const adjacentLayoutNodeIds = [];
adjacentNodes.forEach((adjacentId) => {
// filter loopback
if (adjacentId !== props.selectedNodeId) {
adjacentLayoutNodeIds.push(adjacentId);
}
});
// move origin node to center of viewport
const zoomScale = state.scale;
const translate = [state.panTranslateX, state.panTranslateY];
const viewportHalfWidth = ((state.width + props.margins.left) - DETAILS_PANEL_WIDTH) / 2;
const viewportHalfHeight = (state.height + props.margins.top) / 2;
const centerX = (-translate[0] + viewportHalfWidth) / zoomScale;
const centerY = (-translate[1] + viewportHalfHeight) / zoomScale;
stateNodes = stateNodes.mergeIn([props.selectedNodeId], {
x: centerX,
y: centerY
});
// circle layout for adjacent nodes
const adjacentCount = adjacentLayoutNodeIds.length;
const density = radiusDensity(adjacentCount);
const radius = Math.min(state.width, state.height) / density / zoomScale;
const offsetAngle = Math.PI / 4;
stateNodes = stateNodes.map((node, nodeId) => {
const index = adjacentLayoutNodeIds.indexOf(nodeId);
if (index > -1) {
const angle = offsetAngle + ((Math.PI * 2 * index) / adjacentCount);
return node.merge({
x: centerX + (radius * Math.sin(angle)),
y: centerY + (radius * Math.cos(angle))
});
}
return node;
});
// fix all edges for circular nodes
stateEdges = stateEdges.map((edge) => {
if (edge.get('source') === props.selectedNodeId
|| edge.get('target') === props.selectedNodeId
|| _.includes(adjacentLayoutNodeIds, edge.get('source'))
|| _.includes(adjacentLayoutNodeIds, edge.get('target'))) {
const source = stateNodes.get(edge.get('source'));
const target = stateNodes.get(edge.get('target'));
return edge.set('points', fromJS([
{x: source.get('x'), y: source.get('y')},
{x: target.get('x'), y: target.get('y')}
]));
}
return edge;
});
// auto-scale node size for selected nodes
const selectedNodeScale = getNodeScale(adjacentNodes.size, state.width, state.height);
return {
selectedNodeScale,
edges: stateEdges,
nodes: stateNodes
};
}
class NodesChart extends React.Component {
constructor(props, context) {
@@ -180,7 +254,7 @@ class NodesChart extends React.Component {
assign(state, this.restoreLayout(state));
}
if (nextProps.selectedNodeId) {
assign(state, this.centerSelectedNode(nextProps, state));
assign(state, centerSelectedNode(nextProps, state));
}
this.setState(state);
@@ -246,79 +320,6 @@ class NodesChart extends React.Component {
}
}
centerSelectedNode(props, state) {
let stateNodes = state.nodes;
let stateEdges = state.edges;
if (!stateNodes.has(props.selectedNodeId)) {
return {};
}
const adjacentNodes = props.adjacentNodes;
const adjacentLayoutNodeIds = [];
adjacentNodes.forEach((adjacentId) => {
// filter loopback
if (adjacentId !== props.selectedNodeId) {
adjacentLayoutNodeIds.push(adjacentId);
}
});
// move origin node to center of viewport
const zoomScale = state.scale;
const translate = [state.panTranslateX, state.panTranslateY];
const viewportHalfWidth = ((state.width + props.margins.left) - DETAILS_PANEL_WIDTH) / 2;
const viewportHalfHeight = (state.height + props.margins.top) / 2;
const centerX = (-translate[0] + viewportHalfWidth) / zoomScale;
const centerY = (-translate[1] + viewportHalfHeight) / zoomScale;
stateNodes = stateNodes.mergeIn([props.selectedNodeId], {
x: centerX,
y: centerY
});
// circle layout for adjacent nodes
const adjacentCount = adjacentLayoutNodeIds.length;
const density = radiusDensity(adjacentCount);
const radius = Math.min(state.width, state.height) / density / zoomScale;
const offsetAngle = Math.PI / 4;
stateNodes = stateNodes.map((node, nodeId) => {
const index = adjacentLayoutNodeIds.indexOf(nodeId);
if (index > -1) {
const angle = offsetAngle + ((Math.PI * 2 * index) / adjacentCount);
return node.merge({
x: centerX + (radius * Math.sin(angle)),
y: centerY + (radius * Math.cos(angle))
});
}
return node;
});
// fix all edges for circular nodes
stateEdges = stateEdges.map((edge) => {
if (edge.get('source') === props.selectedNodeId
|| edge.get('target') === props.selectedNodeId
|| includes(adjacentLayoutNodeIds, edge.get('source'))
|| includes(adjacentLayoutNodeIds, edge.get('target'))) {
const source = stateNodes.get(edge.get('source'));
const target = stateNodes.get(edge.get('target'));
return edge.set('points', fromJS([
{x: source.get('x'), y: source.get('y')},
{x: target.get('x'), y: target.get('y')}
]));
}
return edge;
});
// auto-scale node size for selected nodes
const selectedNodeScale = getNodeScale(adjacentNodes.size, state.width, state.height);
return {
selectedNodeScale,
edges: stateEdges,
nodes: stateNodes
};
}
restoreLayout(state) {
// undo any pan/zooming that might have happened
this.setZoom(state);

View File

@@ -4,6 +4,21 @@ import classNames from 'classnames';
import { toggleGridMode } from '../actions/app-actions';
function renderItem(icons, label, isSelected, onClick) {
const className = classNames('grid-mode-selector-action', {
'grid-mode-selector-action-selected': isSelected
});
return (
<div
className={className}
onClick={onClick} >
<span className={icons} style={{fontSize: 12}} />
<span>{label}</span>
</div>
);
}
class GridModeSelector extends React.Component {
constructor(props, context) {
@@ -21,28 +36,14 @@ class GridModeSelector extends React.Component {
return this.props.toggleGridMode(false);
}
renderItem(icons, label, isSelected, onClick) {
const className = classNames('grid-mode-selector-action', {
'grid-mode-selector-action-selected': isSelected
});
return (
<div
className={className}
onClick={onClick} >
<span className={icons} style={{fontSize: 12}} />
<span>{label}</span>
</div>
);
}
render() {
const { gridMode } = this.props;
return (
<div className="grid-mode-selector">
<div className="grid-mode-selector-wrapper">
{this.renderItem('fa fa-share-alt', 'Graph', !gridMode, this.disableGridMode)}
{this.renderItem('fa fa-table', 'Table', gridMode, this.enableGridMode)}
{renderItem('fa fa-share-alt', 'Graph', !gridMode, this.disableGridMode)}
{renderItem('fa fa-table', 'Table', gridMode, this.enableGridMode)}
</div>
</div>
);

View File

@@ -6,26 +6,24 @@ import MatchedText from './matched-text';
const SHOW_ROW_COUNT = 2;
const MAX_MATCH_LENGTH = 24;
class MatchedResults extends React.Component {
renderMatch(matches, field) {
const match = matches.get(field);
const text = match.text;
return (
<div className="matched-results-match" key={match.label}>
<div className="matched-results-match-wrapper">
<span className="matched-results-match-label">
{match.label}:
</span>
<MatchedText
text={text} match={match}
maxLength={MAX_MATCH_LENGTH}
truncate={match.truncate} />
</div>
function renderMatch(match) {
return (
<div className="matched-results-match" key={match.label}>
<div className="matched-results-match-wrapper">
<span className="matched-results-match-label">
{match.label}:
</span>
<MatchedText
text={match.text} match={match}
maxLength={MAX_MATCH_LENGTH}
truncate={match.truncate} />
</div>
);
}
</div>
);
}
class MatchedResults extends React.Component {
render() {
const { matches, style } = this.props;
@@ -46,7 +44,7 @@ class MatchedResults extends React.Component {
return (
<div className="matched-results" style={style}>
{matches.keySeq().take(SHOW_ROW_COUNT).map(fieldId => this.renderMatch(matches, fieldId))}
{matches.keySeq().take(SHOW_ROW_COUNT).map(fieldId => renderMatch(matches.get(fieldId)))}
{moreFieldMatches && <div className="matched-results-more" title={moreFieldMatchesTitle}>
{`${moreFieldMatches.size} more matches`}
</div>}

View File

@@ -6,6 +6,16 @@ import MatchedText from '../matched-text';
import NodeDetailsControlButton from './node-details-control-button';
import ShowMore from '../show-more';
function renderControls(controls) {
return (
<div className="node-details-labels-controls">
{sortBy(controls, 'rank').map(control => <NodeDetailsControlButton
nodeId={control.nodeId} control={control} key={control.id} />)}
</div>
);
}
export default class NodeDetailsLabels extends React.Component {
constructor(props, context) {
@@ -15,7 +25,6 @@ export default class NodeDetailsLabels extends React.Component {
limit: this.DEFAULT_LIMIT,
};
this.handleLimitClick = this.handleLimitClick.bind(this);
this.renderControls = this.renderControls.bind(this);
}
handleLimitClick() {
@@ -23,15 +32,6 @@ export default class NodeDetailsLabels extends React.Component {
this.setState({limit});
}
renderControls(controls) {
return (
<div className="node-details-labels-controls">
{sortBy(controls, 'rank').map(control => <NodeDetailsControlButton
nodeId={control.nodeId} control={control} key={control.id} />)}
</div>
);
}
render() {
const { controls, matches = makeMap() } = this.props;
let rows = this.props.rows;
@@ -49,7 +49,7 @@ export default class NodeDetailsLabels extends React.Component {
return (
<div className="node-details-labels">
{controls && this.renderControls(controls)}
{controls && renderControls(controls)}
{rows.map(field => (
<div className="node-details-labels-field" key={field.id}>
<div

View File

@@ -13,6 +13,22 @@ const navbarHeight = 194;
const marginTop = 0;
function renderEmptyTopologyError(show) {
return (
<NodesError faIconClass="fa-circle-thin" hidden={!show}>
<div className="heading">Nothing to show. This can have any of these reasons:</div>
<ul>
<li>We haven&apos;t received any reports from probes recently.
Are the probes properly configured?</li>
<li>There are nodes, but they&apos;re currently hidden. Check the view options
in the bottom-left if they allow for showing hidden nodes.</li>
<li>Containers view only: you&apos;re not running Docker,
or you don&apos;t have any containers.</li>
</ul>
</NodesError>
);
}
class Nodes extends React.Component {
constructor(props, context) {
super(props, context);
@@ -32,22 +48,6 @@ class Nodes extends React.Component {
window.removeEventListener('resize', this.handleResize);
}
renderEmptyTopologyError(show) {
return (
<NodesError faIconClass="fa-circle-thin" hidden={!show}>
<div className="heading">Nothing to show. This can have any of these reasons:</div>
<ul>
<li>We haven&apos;t received any reports from probes recently.
Are the probes properly configured?</li>
<li>There are nodes, but they&apos;re currently hidden. Check the view options
in the bottom-left if they allow for showing hidden nodes.</li>
<li>Containers view only: you&apos;re not running Docker,
or you don&apos;t have any containers.</li>
</ul>
</NodesError>
);
}
render() {
const { topologyEmpty, gridMode, topologiesLoaded, nodesLoaded, topologies,
currentTopology } = this.props;
@@ -60,7 +60,7 @@ class Nodes extends React.Component {
itemType={getNodeType(currentTopology, topologies)}
show={topologiesLoaded && !nodesLoaded} />
</DelayedShow>
{this.renderEmptyTopologyError(topologiesLoaded && nodesLoaded && topologyEmpty)}
{renderEmptyTopologyError(topologiesLoaded && nodesLoaded && topologyEmpty)}
{gridMode ?
<NodesGrid {...this.state} nodeSize="24" margins={CANVAS_MARGINS} /> :

View File

@@ -3,24 +3,25 @@ import { connect } from 'react-redux';
import classNames from 'classnames';
import ReactTooltip from 'react-tooltip';
class Plugins extends React.Component {
renderPlugin({id, label, description, status}) {
const error = status !== 'ok';
const className = classNames({ error });
const title = `Plugin description: ${description}<br />Status: ${status}`;
// Inner span to hold styling so we don't effect the "before:content"
return (
<span className="plugins-plugin" key={id}>
<span className={className} data-tip={title} data-multiline>
{error && <span className="plugins-plugin-icon fa fa-exclamation-circle" />}
{label || id}
</span>
<ReactTooltip class="tooltip" effect="solid" offset={{right: 7}} />
function renderPlugin({id, label, description, status}) {
const error = status !== 'ok';
const className = classNames({ error });
const title = `Plugin description: ${description}<br />Status: ${status}`;
// Inner span to hold styling so we don't effect the "before:content"
return (
<span className="plugins-plugin" key={id}>
<span className={className} data-tip={title} data-multiline>
{error && <span className="plugins-plugin-icon fa fa-exclamation-circle" />}
{label || id}
</span>
);
}
<ReactTooltip class="tooltip" effect="solid" offset={{right: 7}} />
</span>
);
}
class Plugins extends React.Component {
render() {
const hasPlugins = this.props.plugins && this.props.plugins.size > 0;
return (
@@ -29,7 +30,7 @@ class Plugins extends React.Component {
Plugins:
</span>
{hasPlugins && this.props.plugins.toIndexedSeq()
.map(plugin => this.renderPlugin(plugin.toJS()))}
.map(plugin => renderPlugin(plugin.toJS()))}
{!hasPlugins && <span className="plugins-empty">n/a</span>}
</div>
);

View File

@@ -123,7 +123,7 @@ class Terminal extends React.Component {
}
this.socket = null;
const wereConnected = this.state.connected;
if (this.isMounted) {
if (this.isComponentMounted) {
// Calling setState on an unmounted component will throw a warning.
// `connected` will get set to false by `componentWillUnmount`.
this.setState({connected: false});
@@ -176,7 +176,7 @@ class Terminal extends React.Component {
}
componentDidMount() {
this.isMounted = true;
this.isComponentMounted = true;
if (this.props.connect) {
this.mountTerminal();
}
@@ -217,7 +217,7 @@ class Terminal extends React.Component {
}
componentWillUnmount() {
this.isMounted = false;
this.isComponentMounted = false;
this.setState({connected: false});
log('cwu terminal');

View File

@@ -4,6 +4,18 @@ import classnames from 'classnames';
import { clickTopology } from '../actions/app-actions';
function basicTopologyInfo(topology, searchMatchCount) {
const info = [
`Nodes: ${topology.getIn(['stats', 'node_count'])}`,
`Connections: ${topology.getIn(['stats', 'edge_count'])}`
];
if (searchMatchCount) {
info.push(`Search Matches: ${searchMatchCount}`);
}
return info.join('\n');
}
class Topologies extends React.Component {
constructor(props, context) {
@@ -21,7 +33,7 @@ class Topologies extends React.Component {
const topologyId = subTopology.get('id');
const searchMatches = this.props.searchNodeMatches.get(subTopology.get('id'));
const searchMatchCount = searchMatches ? searchMatches.size : 0;
const title = this.renderTitle(subTopology, searchMatchCount);
const title = basicTopologyInfo(subTopology, searchMatchCount);
const className = classnames('topologies-sub-item', {
'topologies-sub-item-active': isActive,
'topologies-sub-item-matched': searchMatchCount
@@ -38,15 +50,6 @@ class Topologies extends React.Component {
);
}
renderTitle(topology, searchMatchCount) {
let title = `Nodes: ${topology.getIn(['stats', 'node_count'])}\n`
+ `Connections: ${topology.getIn(['stats', 'node_count'])}`;
if (searchMatchCount) {
title = `${title}\nSearch Matches: ${searchMatchCount}`;
}
return title;
}
renderTopology(topology) {
const isActive = topology === this.props.currentTopology;
const searchMatches = this.props.searchNodeMatches.get(topology.get('id'));
@@ -56,7 +59,7 @@ class Topologies extends React.Component {
'topologies-item-main-matched': searchMatchCount
});
const topologyId = topology.get('id');
const title = this.renderTitle(topology, searchMatchCount);
const title = basicTopologyInfo(topology, searchMatchCount);
return (
<div className="topologies-item" key={topologyId}>