Merge pull request #2896 from weaveworks/2895-update-eslint-deps

Update eslint dependencies
This commit is contained in:
Filip Barl
2017-10-18 13:42:56 +02:00
committed by GitHub
100 changed files with 1375 additions and 846 deletions

View File

@@ -36,5 +36,8 @@
"react/prefer-stateless-function": 0,
"react/sort-comp": 0,
"react/prop-types": 0,
"jsx-a11y/click-events-have-key-events": 0,
"jsx-a11y/mouse-events-have-key-events": 0,
}
}

View File

@@ -778,6 +778,7 @@ export function resetLocalViewState() {
return (dispatch) => {
dispatch({type: ActionTypes.RESET_LOCAL_VIEW_STATE});
storageSet('scopeViewState', '');
// eslint-disable-next-line prefer-destructuring
window.location.href = window.location.href.split('#')[0];
};
}

View File

@@ -134,13 +134,17 @@ describe('NodesLayout', () => {
},
layoutProps: {
nodes: fromJS({
n1: {id: 'n1', label: 'lold', labelMinor: 'lmold', rank: 'rold'},
n1: {
id: 'n1', label: 'lold', labelMinor: 'lmold', rank: 'rold'
},
}),
edges: fromJS({})
},
layoutProps2: {
nodes: fromJS({
n1: {id: 'n1', label: 'lnew', labelMinor: 'lmnew', rank: 'rnew', x: 111, y: 109},
n1: {
id: 'n1', label: 'lnew', labelMinor: 'lmnew', rank: 'rnew', x: 111, y: 109
},
}),
edges: fromJS({})
}
@@ -184,7 +188,8 @@ describe('NodesLayout', () => {
it('lays out initial nodeset in a rectangle', () => {
const result = NodesLayout.doLayout(
nodeSets.initial4.nodes,
nodeSets.initial4.edges);
nodeSets.initial4.edges
);
// console.log('initial', result.get('nodes'));
nodes = result.nodes.toJS();
@@ -199,7 +204,8 @@ describe('NodesLayout', () => {
it('keeps nodes in rectangle after removing one edge', () => {
let result = NodesLayout.doLayout(
nodeSets.initial4.nodes,
nodeSets.initial4.edges);
nodeSets.initial4.edges
);
options.cachedLayout = result;
options.nodeCache = options.nodeCache.merge(result.nodes);
@@ -221,7 +227,8 @@ describe('NodesLayout', () => {
it('keeps nodes in rectangle after removed edge reappears', () => {
let result = NodesLayout.doLayout(
nodeSets.initial4.nodes,
nodeSets.initial4.edges);
nodeSets.initial4.edges
);
coords = getNodeCoordinates(result.nodes);
options.cachedLayout = result;
@@ -252,7 +259,8 @@ describe('NodesLayout', () => {
it('keeps nodes in rectangle after node disappears', () => {
let result = NodesLayout.doLayout(
nodeSets.initial4.nodes,
nodeSets.initial4.edges);
nodeSets.initial4.edges
);
options.cachedLayout = result;
options.nodeCache = options.nodeCache.merge(result.nodes);
@@ -273,7 +281,8 @@ describe('NodesLayout', () => {
it('keeps nodes in rectangle after removed node reappears', () => {
let result = NodesLayout.doLayout(
nodeSets.initial4.nodes,
nodeSets.initial4.edges);
nodeSets.initial4.edges
);
nodes = result.nodes.toJS();
@@ -313,7 +322,8 @@ describe('NodesLayout', () => {
it('renders single nodes in a square', () => {
const result = NodesLayout.doLayout(
nodeSets.single3.nodes,
nodeSets.single3.edges);
nodeSets.single3.edges
);
nodes = result.nodes.toJS();
@@ -404,7 +414,8 @@ describe('NodesLayout', () => {
expect(NodesLayout.hasNewNodesOfExistingRank(
nodeSets.rank6.nodes,
nodeSets.rank6.edges,
result.nodes)).toBeTruthy();
result.nodes
)).toBeTruthy();
result = NodesLayout.doLayout(
nodeSets.rank6.nodes,

View File

@@ -70,7 +70,9 @@ export default class EdgeContainer extends React.PureComponent {
}
render() {
const { isAnimated, waypoints, scale, ...forwardedProps } = this.props;
const {
isAnimated, waypoints, scale, ...forwardedProps
} = this.props;
const { thickness, waypointsMap } = this.state;
if (!isAnimated) {
@@ -81,9 +83,14 @@ export default class EdgeContainer extends React.PureComponent {
// For the Motion interpolation to work, the waypoints need to be in a map format like
// { x0: 11, y0: 22, x1: 33, y1: 44 } that we convert to the array format when rendering.
<Motion style={{ interpolatedThickness: weakSpring(thickness), ...waypointsMap.toJS() }}>
{({ interpolatedThickness, ...interpolatedWaypoints}) => transformedEdge(
forwardedProps, waypointsMapToArray(fromJS(interpolatedWaypoints)), interpolatedThickness
)}
{
({ interpolatedThickness, ...interpolatedWaypoints}) =>
transformedEdge(
forwardedProps,
waypointsMapToArray(fromJS(interpolatedWaypoints)),
interpolatedThickness
)
}
</Motion>
);
}

View File

@@ -6,7 +6,6 @@ import { enterEdge, leaveEdge } from '../actions/app-actions';
import { encodeIdAttribute, decodeIdAttribute } from '../utils/dom-utils';
class Edge extends React.Component {
constructor(props, context) {
super(props, context);
this.handleMouseEnter = this.handleMouseEnter.bind(this);
@@ -14,13 +13,16 @@ class Edge extends React.Component {
}
render() {
const { id, path, highlighted, focused, thickness, source, target } = this.props;
const {
id, path, highlighted, focused, thickness, source, target
} = this.props;
const shouldRenderMarker = (focused || highlighted) && (source !== target);
const className = classNames('edge', { highlighted });
return (
<g
id={encodeIdAttribute(id)} className={className}
id={encodeIdAttribute(id)}
className={className}
onMouseEnter={this.handleMouseEnter}
onMouseLeave={this.handleMouseLeave}
>

View File

@@ -13,7 +13,9 @@ const transformedNode = (otherProps, { x, y, k }) => (
export default class NodeContainer extends React.PureComponent {
render() {
const { dx, dy, isAnimated, scale, ...forwardedProps } = this.props;
const {
dx, dy, isAnimated, scale, ...forwardedProps
} = this.props;
if (!isAnimated) {
// Show static node for optimized rendering

View File

@@ -23,7 +23,9 @@ import {
import { encodeIdAttribute } from '../utils/dom-utils';
function NodeShape(shapeType, shapeElement, shapeProps, { id, highlighted, color, metric }) {
function NodeShape(shapeType, shapeElement, shapeProps, {
id, highlighted, color, metric
}) {
const { height, hasMetric, formattedValue } = getMetricValue(metric);
const className = classNames('shape', `shape-${shapeType}`, { metrics: hasMetric });
const metricStyle = { fill: getMetricColor(metric) };

View File

@@ -103,8 +103,10 @@ class Node extends React.Component {
}
render() {
const { focused, highlighted, networks, pseudo, rank, label, transform,
exportingGraph, showingNetworks, stack, id, metric } = this.props;
const {
focused, highlighted, networks, pseudo, rank, label, transform,
exportingGraph, showingNetworks, stack, id, metric
} = this.props;
const { hovered } = this.state;
const color = getNodeColor(rank, label, pseudo);

View File

@@ -196,9 +196,14 @@ class NodesChartElements extends React.Component {
const scale = (this.props.selectedScale || 1) * 100000;
return (
<rect
className={className} key="nodes-chart-overlay"
transform={`scale(${scale})`} fill="#fff"
x={-1} y={-1} width={2} height={2}
className={className}
key="nodes-chart-overlay"
transform={`scale(${scale})`}
fill="#fff"
x={-1}
y={-1}
width={2}
height={2}
/>
);
}
@@ -277,6 +282,4 @@ function mapStateToProps(state) {
};
}
export default connect(
mapStateToProps
)(NodesChartElements);
export default connect(mapStateToProps)(NodesChartElements);

View File

@@ -1,8 +1,9 @@
import React from 'react';
import classnames from 'classnames';
export default function NodesError({children, faIconClass, hidden,
mainClassName = 'nodes-chart-error'}) {
const NodesError = ({
children, faIconClass, hidden, mainClassName = 'nodes-chart-error'
}) => {
const className = classnames(mainClassName, {
hide: hidden
});
@@ -18,4 +19,6 @@ export default function NodesError({children, faIconClass, hidden,
{children}
</div>
);
}
};
export default NodesError;

View File

@@ -58,7 +58,9 @@ function getColumns(nodes) {
}
function renderIdCell({ rank, label, labelMinor, pseudo }) {
function renderIdCell({
rank, label, labelMinor, pseudo
}) {
const showSubLabel = Boolean(pseudo) && labelMinor;
const title = showSubLabel ? `${label} (${labelMinor})` : label;
const iconStyle = {
@@ -79,7 +81,6 @@ function renderIdCell({ rank, label, labelMinor, pseudo }) {
class NodesGrid extends React.Component {
constructor(props, context) {
super(props, context);
@@ -101,8 +102,10 @@ class NodesGrid extends React.Component {
}
render() {
const { nodes, height, gridSortedBy, gridSortedDesc, canvasMargins,
searchNodeMatches, searchQuery } = this.props;
const {
nodes, height, gridSortedBy, gridSortedDesc, canvasMargins,
searchNodeMatches, searchQuery
} = this.props;
const cmpStyle = {
height,
marginTop: canvasMargins.top,

View File

@@ -88,7 +88,7 @@ function layoutSingleNodes(layout, opts) {
const graphWidth = layout.graphWidth || layout.width;
const aspectRatio = graphHeight ? graphWidth / graphHeight : 1;
let nodes = layout.nodes;
let { nodes } = layout;
// 0-degree nodes
const singleNodes = nodes.filter(node => node.get('degree') === 0);

View File

@@ -19,28 +19,31 @@ describe('NodeDetails', () => {
});
it('shows n/a when node was not found', () => {
const c = TestUtils.renderIntoDocument(
const c = TestUtils.renderIntoDocument((
<Provider store={configureStore()}>
<NodeDetails notFound />
</Provider>
));
const notFound = TestUtils.findRenderedDOMComponentWithClass(
c,
'node-details-header-notavailable'
);
const notFound = TestUtils.findRenderedDOMComponentWithClass(c,
'node-details-header-notavailable');
expect(notFound).toBeDefined();
});
it('show label of node with title', () => {
nodes = nodes.set(nodeId, Immutable.fromJS({id: nodeId}));
details = {label: 'Node 1'};
const c = TestUtils.renderIntoDocument(
const c = TestUtils.renderIntoDocument((
<Provider store={configureStore()}>
<NodeDetails
nodes={nodes}
topologyId="containers"
nodeId={nodeId} details={details}
/>
nodeId={nodeId}
details={details}
/>
</Provider>
);
));
const title = TestUtils.findRenderedDOMComponentWithClass(c, 'node-details-header-label');
expect(title.title).toBe('Node 1');

View File

@@ -170,9 +170,11 @@ class App extends React.Component {
}
render() {
const { isTableViewMode, isGraphViewMode, isResourceViewMode, showingDetails,
const {
isTableViewMode, isGraphViewMode, isResourceViewMode, showingDetails,
showingHelp, showingNetworkSelector, showingTroubleshootingMenu,
timeTravelTransitioning, showingTimeTravel } = this.props;
timeTravelTransitioning, showingTimeTravel
} = this.props;
const className = classNames('scope-app', { 'time-travel-open': showingTimeTravel });
const isIframe = window !== window.top;
@@ -192,9 +194,11 @@ class App extends React.Component {
<TimeTravel />
<div className="selectors">
<div className="logo">
{!isIframe && <svg width="100%" height="100%" viewBox="0 0 1089 217">
<Logo />
</svg>}
{!isIframe &&
<svg width="100%" height="100%" viewBox="0 0 1089 217">
<Logo />
</svg>
}
</div>
<Search />
<Topologies />
@@ -243,6 +247,4 @@ function mapStateToProps(state) {
};
}
export default connect(
mapStateToProps
)(App);
export default connect(mapStateToProps)(App);

View File

@@ -21,7 +21,7 @@ const CloudLink = ({ alwaysShow, ...props }) => (
<CloudFeature alwaysShow={alwaysShow}>
<LinkWrapper {...props} />
</CloudFeature>
);
);
class LinkWrapper extends React.Component {
constructor(props, context) {
@@ -44,7 +44,7 @@ class LinkWrapper extends React.Component {
if (router && href[0] === '/') {
router.push(href);
} else {
location.href = href;
window.location.href = href;
}
}

View File

@@ -117,7 +117,7 @@ function startPerf(delay) {
export function showingDebugToolbar() {
return (('debugToolbar' in localStorage && JSON.parse(localStorage.debugToolbar))
|| location.pathname.indexOf('debug') > -1);
|| window.location.pathname.indexOf('debug') > -1);
}
@@ -151,7 +151,6 @@ function setAppState(fn) {
class DebugToolbar extends React.Component {
constructor(props, context) {
super(props, context);
this.onChange = this.onChange.bind(this);
@@ -265,7 +264,9 @@ class DebugToolbar extends React.Component {
addInternetNode() {
setTimeout(() => {
this.asyncDispatch(receiveNodesDelta({
add: [{id: INTERNET, label: INTERNET, pseudo: true, labelMinor: 'Outgoing packets', shape: 'cloud'}]
add: [{
id: INTERNET, label: INTERNET, pseudo: true, labelMinor: 'Outgoing packets', shape: 'cloud'
}]
}));
}, 0);
}
@@ -385,6 +386,4 @@ function mapStateToProps(state) {
}
export default connect(
mapStateToProps
)(DebugToolbar);
export default connect(mapStateToProps)(DebugToolbar);

View File

@@ -10,7 +10,6 @@ import {
} from '../constants/styles';
class DetailsCard extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {

View File

@@ -11,8 +11,11 @@ class Details extends React.Component {
<div className="details">
{details.toIndexedSeq().map((obj, index) => (
<DetailsCard
key={obj.id} index={index} cardCount={details.size}
nodeControlStatus={controlStatus.get(obj.id)} {...obj}
key={obj.id}
index={index}
cardCount={details.size}
nodeControlStatus={controlStatus.get(obj.id)}
{...obj}
/>
))}
</div>
@@ -27,6 +30,4 @@ function mapStateToProps(state) {
};
}
export default connect(
mapStateToProps
)(Details);
export default connect(mapStateToProps)(Details);

View File

@@ -3,11 +3,11 @@ import { createDevTools } from 'redux-devtools';
import LogMonitor from 'redux-devtools-log-monitor';
import DockMonitor from 'redux-devtools-dock-monitor';
export default createDevTools(
export default createDevTools((
<DockMonitor
defaultIsVisible={false}
toggleVisibilityKey="ctrl-h"
changePositionKey="ctrl-w">
<LogMonitor />
</DockMonitor>
);
));

View File

@@ -6,7 +6,6 @@ import { DETAILS_PANEL_WIDTH, DETAILS_PANEL_MARGINS } from '../constants/styles'
import Terminal from './terminal';
class EmeddedTerminal extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
@@ -70,6 +69,4 @@ function mapStateToProps(state) {
};
}
export default connect(
mapStateToProps
)(EmeddedTerminal);
export default connect(mapStateToProps)(EmeddedTerminal);

View File

@@ -34,7 +34,9 @@ class Footer extends React.Component {
}
render() {
const { hostname, version, versionUpdate, contrastMode } = this.props;
const {
hostname, version, versionUpdate, contrastMode
} = this.props;
const otherContrastModeTitle = contrastMode
? 'Switch to normal contrast' : 'Switch to high contrast';
@@ -47,13 +49,16 @@ class Footer extends React.Component {
return (
<div className="footer">
<div className="footer-status">
{versionUpdate && <a
className="footer-versionupdate"
title={versionUpdateTitle}
href={versionUpdate.get('downloadUrl')}
target="_blank" rel="noopener noreferrer">
Update available: {versionUpdate.get('version')}
</a>}
{versionUpdate &&
<a
className="footer-versionupdate"
title={versionUpdateTitle}
href={versionUpdate.get('downloadUrl')}
target="_blank"
rel="noopener noreferrer">
Update available: {versionUpdate.get('version')}
</a>
}
<span className="footer-label">Version</span>
{version}
<span className="footer-label">on</span>
@@ -65,25 +70,26 @@ class Footer extends React.Component {
</div>
<div className="footer-tools">
<a
<button
className="footer-icon"
onClick={this.handleRelayoutClick}
title={forceRelayoutTitle}>
<span className="fa fa-refresh" />
</a>
<a onClick={this.handleContrastClick} className="footer-icon" title={otherContrastModeTitle}>
</button>
<button onClick={this.handleContrastClick} className="footer-icon" title={otherContrastModeTitle}>
<span className="fa fa-adjust" />
</a>
<a
</button>
<button
onClick={this.props.toggleTroubleshootingMenu}
className="footer-icon" title="Open troubleshooting menu"
className="footer-icon"
title="Open troubleshooting menu"
href=""
>
<span className="fa fa-bug" />
</a>
<a className="footer-icon" onClick={this.props.toggleHelp} title="Show help">
</button>
<button className="footer-icon" onClick={this.props.toggleHelp} title="Show help">
<span className="fa fa-question" />
</a>
</button>
</div>
</div>

View File

@@ -116,14 +116,21 @@ function renderSearchPanel() {
function renderFieldsPanel(currentTopologyName, searchableFields) {
const none = <span style={{fontStyle: 'italic'}}>None</span>;
const none = (
<span style={{fontStyle: 'italic'}}>None</span>
);
const currentTopology = (
<span className="help-panel-fields-current-topology">
{currentTopologyName}
</span>
);
return (
<div className="help-panel-fields">
<h2>Fields and Metrics</h2>
<p>
Searchable fields and metrics in the <br />
currently selected <span className="help-panel-fields-current-topology">
{currentTopologyName}</span> topology:
currently selected {currentTopology} topology:
</p>
<div className="help-panel-fields-fields">
<div className="help-panel-fields-fields-column">
@@ -150,7 +157,9 @@ function renderFieldsPanel(currentTopologyName, searchableFields) {
}
function HelpPanel({ currentTopologyName, searchableFields, onClickClose, canvasMargins }) {
function HelpPanel({
currentTopologyName, searchableFields, onClickClose, canvasMargins
}) {
return (
<div className="help-panel-wrapper">
<div className="help-panel" style={{marginTop: canvasMargins.top}}>

View File

@@ -36,7 +36,6 @@ function renderTemplate(nodeType, template) {
export class Loading extends React.Component {
constructor(props, context) {
super(props, context);
@@ -54,5 +53,4 @@ export class Loading extends React.Component {
</NodesError>
);
}
}

View File

@@ -7,50 +7,63 @@ export default function Logo({ transform = '' }) {
<g className="logo" transform={transform}>
<path fill="#32324B" d="M114.937,118.165l75.419-67.366c-5.989-4.707-12.71-8.52-19.981-11.211l-55.438,49.52V118.165z" />
<path fill="#32324B" d="M93.265,108.465l-20.431,18.25c1.86,7.57,4.88,14.683,8.87,21.135l11.561-10.326V108.465z" />
<path fill="#00D2FF" d="M155.276,53.074V35.768C151.815,35.27,148.282,35,144.685,35c-3.766,0-7.465,0.286-11.079,0.828v36.604
<path fill="#00D2FF"
d="M155.276,53.074V35.768C151.815,35.27,148.282,35,144.685,35c-3.766,0-7.465,0.286-11.079,0.828v36.604
L155.276,53.074z" />
<path fill="#00D2FF" d="M155.276,154.874V82.133l-21.671,19.357v80.682c3.614,0.543,7.313,0.828,11.079,0.828
<path fill="#00D2FF"
d="M155.276,154.874V82.133l-21.671,19.357v80.682c3.614,0.543,7.313,0.828,11.079,0.828
c4.41,0,8.723-0.407,12.921-1.147l58.033-51.838c1.971-6.664,3.046-13.712,3.046-21.015c0-3.439-0.254-6.817-0.708-10.132
L155.276,154.874z" />
<path fill="#FF4B19" d="M155.276,133.518l58.14-51.933c-2.77-6.938-6.551-13.358-11.175-19.076l-46.965,41.951V133.518z" />
<path fill="#FF4B19" d="M133.605,123.817l-18.668,16.676V41.242c-8.086,3.555-15.409,8.513-21.672,14.567V162.19
<path fill="#FF4B19"
d="M133.605,123.817l-18.668,16.676V41.242c-8.086,3.555-15.409,8.513-21.672,14.567V162.19
c4.885,4.724,10.409,8.787,16.444,12.03l23.896-21.345V123.817z" />
<polygon fill="#32324B" points="325.563,124.099 339.389,72.22 357.955,72.22 337.414,144.377 315.556,144.377 303.311,95.79
<polygon fill="#32324B"
points="325.563,124.099 339.389,72.22 357.955,72.22 337.414,144.377 315.556,144.377 303.311,95.79
291.065,144.377 269.207,144.377 248.666,72.22 267.232,72.22 281.058,124.099 294.752,72.22 311.869,72.22 " />
<path fill="#32324B" d="M426.429,120.676c-2.106,14.352-13.167,24.623-32.128,24.623c-20.146,0-35.025-12.114-35.025-36.605
<path fill="#32324B"
d="M426.429,120.676c-2.106,14.352-13.167,24.623-32.128,24.623c-20.146,0-35.025-12.114-35.025-36.605
c0-24.622,15.406-37.395,35.025-37.395c21.726,0,33.182,15.933,33.182,37.263v3.819h-49.772c0,8.031,3.291,18.17,16.327,18.17
c7.242,0,12.904-3.555,14.353-10.27L426.429,120.676z M408.654,99.608c-0.659-10.008-7.11-13.694-14.484-13.694
c-8.427,0-14.879,5.135-15.801,13.694H408.654z" />
<path fill="#32324B" d="M480.628,97.634v-2.502c0-5.662-2.37-9.351-13.036-9.351c-13.298,0-13.694,7.375-13.694,9.877h-17.117
<path fill="#32324B"
d="M480.628,97.634v-2.502c0-5.662-2.37-9.351-13.036-9.351c-13.298,0-13.694,7.375-13.694,9.877h-17.117
c0-10.666,4.477-24.359,31.338-24.359c25.676,0,30.285,12.771,30.285,23.174v39.766c0,2.897,0.131,5.267,0.395,7.11l0.527,3.028
h-18.172v-7.241c-5.134,5.134-12.245,8.163-22.384,8.163c-14.221,0-25.018-8.296-25.018-22.648c0-16.59,15.67-20.146,21.99-21.199
L480.628,97.634z M480.628,111.195l-6.979,1.054c-3.819,0.658-8.427,1.315-11.192,1.843c-3.029,0.527-5.662,1.186-7.637,2.765
c-1.844,1.449-2.765,3.425-2.765,5.926c0,2.107,0.79,8.69,10.666,8.69c5.793,0,10.928-2.105,13.693-4.872
c3.556-3.555,4.214-8.032,4.214-11.587V111.195z" />
<polygon fill="#32324B" points="549.495,144.377 525.399,144.377 501.698,72.221 521.186,72.221 537.775,127.392 554.499,72.221
<polygon fill="#32324B"
points="549.495,144.377 525.399,144.377 501.698,72.221 521.186,72.221 537.775,127.392 554.499,72.221
573.459,72.221 " />
<path fill="#32324B" d="M641.273,120.676c-2.106,14.352-13.167,24.623-32.128,24.623c-20.146,0-35.025-12.114-35.025-36.605
<path fill="#32324B"
d="M641.273,120.676c-2.106,14.352-13.167,24.623-32.128,24.623c-20.146,0-35.025-12.114-35.025-36.605
c0-24.622,15.406-37.395,35.025-37.395c21.726,0,33.182,15.933,33.182,37.263v3.819h-49.772c0,8.031,3.291,18.17,16.327,18.17
c7.242,0,12.904-3.555,14.354-10.27L641.273,120.676z M623.498,99.608c-0.659-10.008-7.109-13.694-14.483-13.694
c-8.428,0-14.88,5.135-15.802,13.694H623.498z" />
<path fill="#32324B" d="M682.976,80.873c-7.524,0-16.896,2.376-16.896,10.692c0,17.952,46.201,1.452,46.201,30.229
<path fill="#32324B"
d="M682.976,80.873c-7.524,0-16.896,2.376-16.896,10.692c0,17.952,46.201,1.452,46.201,30.229
c0,9.637-5.676,22.309-30.229,22.309c-19.009,0-27.721-9.636-28.249-22.44h11.881c0.264,7.788,5.147,13.332,17.688,13.332
c14.52,0,17.952-6.204,17.952-12.54c0-13.332-24.421-7.788-37.753-15.181c-4.885-2.771-8.316-7.128-8.316-15.048
c0-11.616,10.824-20.461,27.853-20.461c20.989,0,27.193,12.145,27.589,20.196h-11.484
C698.685,83.381,691.556,80.873,682.976,80.873z" />
<path fill="#32324B" d="M756.233,134.994c10.429,0,17.953-5.939,19.009-16.632h10.957c-1.98,17.028-13.597,25.74-29.966,25.74
<path fill="#32324B"
d="M756.233,134.994c10.429,0,17.953-5.939,19.009-16.632h10.957c-1.98,17.028-13.597,25.74-29.966,25.74
c-18.744,0-32.076-12.012-32.076-35.905c0-23.76,13.464-36.433,32.209-36.433c16.104,0,27.721,8.712,29.568,25.213h-10.956
c-1.452-11.353-9.24-16.104-18.877-16.104c-12.012,0-20.856,8.448-20.856,27.324C735.245,127.471,744.485,134.994,756.233,134.994z
" />
<path fill="#32324B" d="M830.418,144.103c-19.141,0-32.341-12.145-32.341-36.169c0-23.893,13.2-36.169,32.341-36.169
<path fill="#32324B"
d="M830.418,144.103c-19.141,0-32.341-12.145-32.341-36.169c0-23.893,13.2-36.169,32.341-36.169
c19.009,0,32.209,12.145,32.209,36.169C862.627,132.091,849.427,144.103,830.418,144.103z M830.418,134.994
c12.145,0,21.12-7.392,21.12-27.061c0-19.536-8.976-27.061-21.12-27.061c-12.276,0-21.253,7.393-21.253,27.061
C809.165,127.603,818.142,134.994,830.418,134.994z" />
<path fill="#32324B" d="M888.629,72.688v10.692c3.96-6.732,12.54-11.616,22.969-11.616c19.009,0,30.757,12.673,30.757,36.169
<path fill="#32324B"
d="M888.629,72.688v10.692c3.96-6.732,12.54-11.616,22.969-11.616c19.009,0,30.757,12.673,30.757,36.169
c0,23.629-12.145,36.169-31.152,36.169c-10.429,0-18.745-4.224-22.573-11.22v35.641h-10.824V72.688H888.629z M910.409,134.994
c12.145,0,20.857-7.392,20.857-27.061c0-19.536-8.713-27.061-20.857-27.061c-12.275,0-21.912,7.393-21.912,27.061
C888.497,127.603,898.134,134.994,910.409,134.994z" />
<path fill="#32324B" d="M1016.801,119.022c-1.452,12.408-10.032,25.08-30.229,25.08c-18.745,0-32.341-12.804-32.341-36.037
<path fill="#32324B"
d="M1016.801,119.022c-1.452,12.408-10.032,25.08-30.229,25.08c-18.745,0-32.341-12.804-32.341-36.037
c0-21.912,13.464-36.301,32.209-36.301c19.8,0,30.757,14.784,30.757,38.018h-51.878c0.265,13.332,5.809,25.212,21.385,25.212
c11.484,0,18.217-7.128,19.141-16.104L1016.801,119.022z M1005.448,101.201c-1.056-14.916-9.636-20.328-19.272-20.328
c-10.824,0-19.141,7.26-20.46,20.328H1005.448z" />

View File

@@ -13,7 +13,8 @@ const Match = match => (
{match.label}:
</span>
<MatchedText
text={match.text} match={match}
text={match.text}
match={match}
maxLength={MAX_MATCH_LENGTH}
truncate={match.truncate} />
</div>
@@ -41,9 +42,11 @@ export default class MatchedResults extends React.PureComponent {
return (
<div className="matched-results" style={style}>
{matches.keySeq().take(SHOW_ROW_COUNT).map(fieldId => Match(matches.get(fieldId)))}
{moreFieldMatches && <div className="matched-results-more" title={moreFieldMatchesTitle}>
{`${moreFieldMatches.size} more matches`}
</div>}
{moreFieldMatches &&
<div className="matched-results-more" title={moreFieldMatchesTitle}>
{`${moreFieldMatches.size} more matches`}
</div>
}
</div>
);
}

View File

@@ -10,7 +10,7 @@ const TRUNCATE_ELLIPSIS = '…';
* `('text', {start: 2, length: 1}) => [{text: 'te'}, {text: 'x', match: true}, {text: 't'}]`
*/
function chunkText(text, { start, length }) {
if (text && !isNaN(start) && !isNaN(length)) {
if (text && !window.isNaN(start) && !window.isNaN(length)) {
const chunks = [];
// text chunk before match
if (start > 0) {
@@ -80,7 +80,9 @@ function truncateChunks(chunks, text, maxLength) {
*/
export default class MatchedText extends React.PureComponent {
render() {
const { match, text, truncate, maxLength } = this.props;
const {
match, text, truncate, maxLength
} = this.props;
const showFullValue = !truncate || (match && (match.start + match.length) > truncate);
const displayText = showFullValue ? text : text.slice(0, truncate);

View File

@@ -31,7 +31,7 @@ class MetricSelectorItem extends React.Component {
onMouseClick() {
const metricType = this.props.metric.get('label');
const pinnedMetricType = this.props.pinnedMetricType;
const { pinnedMetricType } = this.props;
if (metricType !== pinnedMetricType) {
this.trackEvent('scope.metric.selector.pin.click');

View File

@@ -22,14 +22,16 @@ class MetricSelector extends React.Component {
return (
<div className="metric-selector">
{hasMetrics && <div className="metric-selector-wrapper" onMouseLeave={this.onMouseOut}>
{availableMetrics.map(metric => (
<MetricSelectorItem
key={metric.get('id')}
metric={metric}
/>
))}
</div>}
{hasMetrics &&
<div className="metric-selector-wrapper" onMouseLeave={this.onMouseOut}>
{availableMetrics.map(metric => (
<MetricSelectorItem
key={metric.get('id')}
metric={metric}
/>
))}
</div>
}
</div>
);
}

View File

@@ -6,7 +6,6 @@ import { selectNetwork, pinNetwork, unpinNetwork } from '../actions/app-actions'
import { getNetworkColor } from '../utils/color-utils';
class NetworkSelectorItem extends React.Component {
constructor(props, context) {
super(props, context);
@@ -21,7 +20,7 @@ class NetworkSelectorItem extends React.Component {
onMouseClick() {
const k = this.props.network.get('id');
const pinnedNetwork = this.props.pinnedNetwork;
const { pinnedNetwork } = this.props;
if (k === pinnedNetwork) {
this.props.unpinNetwork(k);

View File

@@ -7,7 +7,6 @@ import { availableNetworksSelector } from '../selectors/node-networks';
import NetworkSelectorItem from './network-selector-item';
class NetworkSelector extends React.Component {
constructor(props, context) {
super(props, context);
this.onClick = this.onClick.bind(this);

View File

@@ -63,12 +63,14 @@ class NodeDetails extends React.Component {
return (
<div className="node-details-tools-wrapper">
<div className="node-details-tools">
{showSwitchTopology && <span
title={topologyTitle}
className="fa fa-long-arrow-left"
onClick={this.handleShowTopologyForNode}>
<span>Show in <span>{this.props.topologyId.replace(/-/g, ' ')}</span></span>
</span>}
{showSwitchTopology &&
<span
title={topologyTitle}
className="fa fa-long-arrow-left"
onClick={this.handleShowTopologyForNode}>
<span>Show in <span>{this.props.topologyId.replace(/-/g, ' ')}</span></span>
</span>
}
<span
title="Close details"
className="fa fa-close close-details"
@@ -87,8 +89,8 @@ class NodeDetails extends React.Component {
// caused by a bug having to do with animating the details panel).
const spinnerClassName = classNames('fa fa-circle-o-notch', { 'fa-spin': this.props.mounted });
const nodeColor = (node ?
getNodeColorDark(node.get('rank'), label, node.get('pseudo')) :
getNeutralColor());
getNodeColorDark(node.get('rank'), label, node.get('pseudo')) :
getNeutralColor());
const tools = this.renderTools();
const styles = {
header: {
@@ -158,7 +160,9 @@ class NodeDetails extends React.Component {
}
renderDetails() {
const { details, nodeControlStatus, nodeMatches = makeMap(), topologyId } = this.props;
const {
details, nodeControlStatus, nodeMatches = makeMap(), topologyId
} = this.props;
const showControls = details.controls && details.controls.length > 0;
const nodeColor = getNodeColorDark(details.rank, details.label, details.pseudo);
const {error, pending} = nodeControlStatus ? nodeControlStatus.toJS() : {};
@@ -188,35 +192,42 @@ class NodeDetails extends React.Component {
</div>
</div>
{showControls && <div className="node-details-controls-wrapper" style={styles.controls}>
<NodeDetailsControls
nodeId={this.props.nodeId}
controls={details.controls}
pending={pending}
error={error} />
</div>}
{showControls &&
<div className="node-details-controls-wrapper" style={styles.controls}>
<NodeDetailsControls
nodeId={this.props.nodeId}
controls={details.controls}
pending={pending}
error={error} />
</div>
}
<div className="node-details-content">
{details.metrics && <div className="node-details-content-section">
<div className="node-details-content-section-header">Status</div>
<NodeDetailsHealth
metrics={details.metrics}
topologyId={topologyId}
/>
</div>}
{details.metadata && <div className="node-details-content-section">
<div className="node-details-content-section-header">Info</div>
<NodeDetailsInfo rows={details.metadata} matches={nodeMatches.get('metadata')} />
</div>}
{details.metrics &&
<div className="node-details-content-section">
<div className="node-details-content-section-header">Status</div>
<NodeDetailsHealth
metrics={details.metrics}
topologyId={topologyId}
/>
</div>
}
{details.metadata &&
<div className="node-details-content-section">
<div className="node-details-content-section-header">Info</div>
<NodeDetailsInfo rows={details.metadata} matches={nodeMatches.get('metadata')} />
</div>
}
{details.connections && details.connections.filter(cs => cs.connections.length > 0)
.map(connections => (<div className="node-details-content-section" key={connections.id}>
<NodeDetailsTable
{...connections}
nodes={connections.connections}
nodeIdKey="nodeId"
/>
</div>
.map(connections => (
<div className="node-details-content-section" key={connections.id}>
<NodeDetailsTable
{...connections}
nodes={connections.connections}
nodeIdKey="nodeId"
/>
</div>
))}
{details.children && details.children.map(children => (
@@ -231,10 +242,12 @@ class NodeDetails extends React.Component {
<div className="node-details-content-section" key={table.id}>
<div className="node-details-content-section-header">
{table.label && table.label.length > 0 && table.label}
{table.truncationCount > 0 && <span
className="node-details-content-section-header-warning">
<Warning text={getTruncationText(table.truncationCount)} />
</span>}
{table.truncationCount > 0 &&
<span
className="node-details-content-section-header-warning">
<Warning text={getTruncationText(table.truncationCount)} />
</span>
}
</div>
{this.renderTable(table)}
</div>
@@ -263,14 +276,16 @@ class NodeDetails extends React.Component {
if (isGenericTable(table)) {
return (
<NodeDetailsGenericTable
rows={table.rows} columns={table.columns}
rows={table.rows}
columns={table.columns}
matches={nodeMatches.get('tables')}
/>
);
} else if (isPropertyList(table)) {
return (
<NodeDetailsPropertyList
rows={table.rows} controls={table.controls}
rows={table.rows}
controls={table.controls}
matches={nodeMatches.get('property-lists')}
/>
);

View File

@@ -58,8 +58,7 @@ describe('NodeDetailsTable', () => {
// form columnIndex + n * columns.length, where n >= 0. Therefore we take only the values
// at the index which divided by columns.length gives a reminder columnIndex.
const filteredValues = values.filter((element, index) =>
index % columns.length === columnIndex
);
index % columns.length === columnIndex);
// Array comparison
expect(filteredValues).toEqual(expectedValues);
}
@@ -67,21 +66,21 @@ describe('NodeDetailsTable', () => {
function clickColumn(title) {
const node = TestUtils.scryRenderedDOMComponentsWithTag(component, 'td')
.find(d => d.title === title);
TestUtils.Simulate.click(node);
TestUtils.Simulate.click(node.children[0]);
}
describe('kubernetes_ip', () => {
it('sorts by column', () => {
component = TestUtils.renderIntoDocument(
component = TestUtils.renderIntoDocument((
<Provider store={configureStore()}>
<NodeDetailsTable
columns={columns}
sortedBy="kubernetes_ip"
nodeIdKey="id"
nodes={nodes}
/>
/>
</Provider>
);
));
matchColumnValues('kubernetes_ip', [
'10.44.253.255',
@@ -108,16 +107,16 @@ describe('NodeDetailsTable', () => {
describe('kubernetes_namespace', () => {
it('sorts by column', () => {
component = TestUtils.renderIntoDocument(
component = TestUtils.renderIntoDocument((
<Provider store={configureStore()}>
<NodeDetailsTable
columns={columns}
sortedBy="kubernetes_namespace"
nodeIdKey="id"
nodes={nodes}
/>
/>
</Provider>
);
));
matchColumnValues('kubernetes_namespace', ['00000', '1111', '12', '5']);
clickColumn('Namespace');

View File

@@ -3,7 +3,9 @@ import { sortBy } from 'lodash';
import NodeDetailsControlButton from './node-details-control-button';
export default function NodeDetailsControls({controls, error, nodeId, pending}) {
export default function NodeDetailsControls({
controls, error, nodeId, pending
}) {
let spinnerClassName = 'fa fa-circle-o-notch fa-spin';
if (pending) {
spinnerClassName += ' node-details-controls-spinner';
@@ -13,13 +15,18 @@ export default function NodeDetailsControls({controls, error, nodeId, pending})
return (
<div className="node-details-controls">
{error && <div className="node-details-controls-error" title={error}>
<span className="node-details-controls-error-icon fa fa-warning" />
<span className="node-details-controls-error-messages">{error}</span>
</div>}
{error &&
<div className="node-details-controls-error" title={error}>
<span className="node-details-controls-error-icon fa fa-warning" />
<span className="node-details-controls-error-messages">{error}</span>
</div>
}
<span className="node-details-controls-buttons">
{sortBy(controls, 'rank').map(control => <NodeDetailsControlButton
nodeId={nodeId} control={control} pending={pending} key={control.id} />)}
{sortBy(controls, 'rank').map(control => (<NodeDetailsControlButton
nodeId={nodeId}
control={control}
pending={pending}
key={control.id} />))}
</span>
{controls && <span title="Applying..." className={spinnerClassName} />}
</div>

View File

@@ -63,8 +63,7 @@ export default class NodeDetailsGenericTable extends React.Component {
// expanded if any of them match the search query; otherwise hide them.
if (this.state.limit > 0 && rows.length > this.state.limit) {
const hasHiddenMatch = rows.slice(this.state.limit).some(row =>
columns.some(column => matches.has(genericTableEntryKey(row, column)))
);
columns.some(column => matches.has(genericTableEntryKey(row, column))));
if (!hasHiddenMatch) {
notShown = rows.length - NODE_DETAILS_DATA_ROWS_DEFAULT_LIMIT;
rows = rows.slice(0, this.state.limit);
@@ -92,7 +91,9 @@ export default class NodeDetailsGenericTable extends React.Component {
return (
<td
className="node-details-generic-table-value truncate"
title={value} key={column.id} style={styles[index]}>
title={value}
key={column.id}
style={styles[index]}>
<MatchedText text={value} match={match} />
</td>
);
@@ -102,8 +103,10 @@ export default class NodeDetailsGenericTable extends React.Component {
</tbody>
</table>
<ShowMore
handleClick={this.handleLimitClick} collection={this.props.rows}
expanded={expanded} notShown={notShown}
handleClick={this.handleLimitClick}
collection={this.props.rows}
expanded={expanded}
notShown={notShown}
/>
</div>
);

View File

@@ -10,8 +10,12 @@ function NodeDetailsHealthItem(props) {
{!props.valueEmpty && <div className="node-details-health-item-value" style={labelStyle}>{formatMetric(props.value, props)}</div>}
<div className="node-details-health-item-sparkline">
<Sparkline
data={props.samples} max={props.max} format={props.format}
first={props.first} last={props.last} hoverColor={props.metricColor}
data={props.samples}
max={props.max}
format={props.format}
first={props.first}
last={props.last}
hoverColor={props.metricColor}
hovered={props.hovered}
/>
</div>

View File

@@ -38,7 +38,6 @@ export function appendTime(url, time) {
}
class NodeDetailsHealthLinkItem extends React.Component {
constructor(props) {
super(props);
this.state = {
@@ -63,7 +62,9 @@ class NodeDetailsHealthLinkItem extends React.Component {
}
render() {
const { id, url, pausedAt, ...props } = this.props;
const {
id, url, pausedAt, ...props
} = this.props;
const metricColor = getMetricColor(id);
const labelColor = this.state.hovered && !props.valueEmpty && darkenColor(metricColor);

View File

@@ -4,7 +4,6 @@ import ShowMore from '../show-more';
import NodeDetailsHealthLinkItem from './node-details-health-link-item';
export default class NodeDetailsHealth extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
@@ -39,22 +38,25 @@ export default class NodeDetailsHealth extends React.Component {
return (
<div className="node-details-health" style={{ justifyContent: 'space-around' }}>
<div className="node-details-health-wrapper">
{shownWithData.map(item => <NodeDetailsHealthLinkItem
{shownWithData.map(item => (<NodeDetailsHealthLinkItem
{...item}
key={item.id}
topologyId={topologyId}
/>)}
/>))}
</div>
<div className="node-details-health-wrapper">
{shownEmpty.map(item => <NodeDetailsHealthLinkItem
{shownEmpty.map(item => (<NodeDetailsHealthLinkItem
{...item}
key={item.id}
topologyId={topologyId}
/>)}
/>))}
</div>
<ShowMore
handleClick={this.handleClickMore} collection={metrics}
expanded={this.state.expanded} notShown={notShown} hideNumber={this.state.expanded}
handleClick={this.handleClickMore}
collection={metrics}
expanded={this.state.expanded}
notShown={notShown}
hideNumber={this.state.expanded}
/>
</div>
);

View File

@@ -90,11 +90,11 @@ class NodeDetailsImageStatus extends React.PureComponent {
Container Image Status
{containers &&
<div>
<a
<button
onClick={this.handleServiceClick}
className="node-details-table-node-link">
View in Deploy
</a>
</button>
</div>
}

View File

@@ -57,8 +57,10 @@ class NodeDetailsInfo extends React.Component {
);
})}
<ShowMore
handleClick={this.handleClickMore} collection={this.props.rows}
expanded={this.state.expanded} notShown={notShown} />
handleClick={this.handleClickMore}
collection={this.props.rows}
expanded={this.state.expanded}
notShown={notShown} />
</div>
);
}

View File

@@ -9,8 +9,10 @@ import ShowMore from '../show-more';
const Controls = controls => (
<div className="node-details-property-list-controls">
{sortBy(controls, 'rank').map(control => <NodeDetailsControlButton
nodeId={control.nodeId} control={control} key={control.id} />)}
{sortBy(controls, 'rank').map(control => (<NodeDetailsControlButton
nodeId={control.nodeId}
control={control}
key={control.id} />))}
</div>
);
@@ -30,7 +32,7 @@ export default class NodeDetailsPropertyList extends React.Component {
render() {
const { controls, matches = makeMap() } = this.props;
let rows = this.props.rows;
let { rows } = this.props;
let notShown = 0;
const limited = rows && this.state.limit > 0 && rows.length > this.state.limit;
const expanded = this.state.limit === 0;
@@ -50,7 +52,8 @@ export default class NodeDetailsPropertyList extends React.Component {
<div className="node-details-property-list-field" key={field.id}>
<div
className="node-details-property-list-field-label truncate"
title={field.entries.label} key={field.id}>
title={field.entries.label}
key={field.id}>
{field.entries.label}
</div>
<div
@@ -61,8 +64,10 @@ export default class NodeDetailsPropertyList extends React.Component {
</div>
))}
<ShowMore
handleClick={this.handleLimitClick} collection={this.props.rows}
expanded={expanded} notShown={notShown} />
handleClick={this.handleLimitClick}
collection={this.props.rows}
expanded={expanded}
notShown={notShown} />
</div>
);
}

View File

@@ -35,8 +35,10 @@ class NodeDetailsRelativesLink extends React.Component {
const title = `View in ${this.props.topologyId}: ${this.props.label}`;
return (
<span
className="node-details-relatives-link" title={title}
onClick={this.handleClick} ref={this.saveNodeRef}>
className="node-details-relatives-link"
title={title}
onClick={this.handleClick}
ref={this.saveNodeRef}>
<MatchedText text={this.props.label} match={this.props.match} />
</span>
);

View File

@@ -5,7 +5,6 @@ import { NODE_DETAILS_DATA_ROWS_DEFAULT_LIMIT } from '../../constants/limits';
import NodeDetailsRelativesLink from './node-details-relatives-link';
export default class NodeDetailsRelatives extends React.Component {
constructor(props, context) {
super(props, context);
this.state = {
@@ -39,11 +38,13 @@ export default class NodeDetailsRelatives extends React.Component {
key={relative.id}
match={matches.get(relative.id)}
{...relative} />))}
{showLimitAction && <span
className="node-details-relatives-more"
onClick={this.handleLimitClick}>
{limitActionText}
</span>}
{showLimitAction &&
<span
className="node-details-relatives-more"
onClick={this.handleLimitClick}>
{limitActionText}
</span>
}
</div>
);
}

View File

@@ -38,14 +38,14 @@ export default class NodeDetailsTableHeaders extends React.Component {
NODE_DETAILS_TABLE_XS_LABEL[header.id] : header.label;
return (
<td
className={headerClasses.join(' ')} style={style} onClick={onClick}
title={header.label} key={header.id}>
{isSortedAsc
&& <span className="node-details-table-header-sorter fa fa-caret-up" />}
{isSortedDesc
&& <span className="node-details-table-header-sorter fa fa-caret-down" />}
{label}
<td className={headerClasses.join(' ')} style={style} title={header.label} key={header.id}>
<div className="node-details-table-header-sortable" onClick={onClick}>
{isSortedAsc
&& <span className="node-details-table-header-sorter fa fa-caret-up" />}
{isSortedDesc
&& <span className="node-details-table-header-sorter fa fa-caret-down" />}
{label}
</div>
</td>
);
})}

View File

@@ -38,8 +38,10 @@ class NodeDetailsTableNodeLink extends React.Component {
if (linkable) {
return (
<span
className="node-details-table-node-link" title={title}
ref={this.saveNodeRef} onClick={this.handleClick}
className="node-details-table-node-link"
title={title}
ref={this.saveNodeRef}
onClick={this.handleClick}
{...dismissRowClickProps}
>
{label}

View File

@@ -17,7 +17,9 @@ class NodeDetailsTableNodeMetricLink extends React.Component {
}
render() {
const { url, style, value, valueEmpty } = this.props;
const {
url, style, value, valueEmpty
} = this.props;
return (
<td

View File

@@ -66,20 +66,22 @@ function renderValues(node, columns = [], columnStyles = [], timestamp = null, t
style={style}
key={field.id}>
{intersperse(field.relatives.map(relative =>
<NodeDetailsTableNodeLink
(<NodeDetailsTableNodeLink
key={relative.id}
linkable
nodeId={relative.id}
{...relative}
/>
), ' ')}
/>)), ' ')}
</td>
);
}
// valueType === 'metrics'
return (
<NodeDetailsTableNodeMetricLink
style={style} key={field.id} topologyId={topologyId} {...field} />
style={style}
key={field.id}
topologyId={topologyId}
{...field} />
);
}
// empty cell to complete the row for proper hover
@@ -155,7 +157,9 @@ export default class NodeDetailsTableRow extends React.Component {
}
render() {
const { node, nodeIdKey, topologyId, columns, onClick, colStyles, timestamp } = this.props;
const {
node, nodeIdKey, topologyId, columns, onClick, colStyles, timestamp
} = this.props;
const [firstColumnStyle, ...columnStyles] = colStyles;
const values = renderValues(node, columns, columnStyles, timestamp, topologyId);
const nodeId = node[nodeIdKey];

View File

@@ -202,8 +202,10 @@ class NodeDetailsTable extends React.Component {
}
render() {
const { nodeIdKey, columns, topologyId, onClickRow,
onMouseEnter, onMouseLeave, timestamp } = this.props;
const {
nodeIdKey, columns, topologyId, onClickRow,
onMouseEnter, onMouseLeave, timestamp
} = this.props;
const sortedBy = this.state.sortedBy || getDefaultSortedBy(columns, this.props.nodes);
const sortedByHeader = this.getColumnHeaders().find(h => h.id === sortedBy);
@@ -294,7 +296,7 @@ class NodeDetailsTable extends React.Component {
NodeDetailsTable.defaultProps = {
nodeIdKey: 'id', // key to identify a node in a row (used for topology links)
nodeIdKey: 'id', // key to identify a node in a row (used for topology links)
limit: NODE_DETAILS_DATA_ROWS_DEFAULT_LIMIT,
onSortChange: () => {},
sortedDesc: null,

View File

@@ -42,7 +42,8 @@ class NodesResources extends React.Component {
<div className="nodes-resources">
<ZoomableCanvas
onClick={this.handleMouseClick}
fixVertical boundContent={CONTENT_COVERING}
fixVertical
boundContent={CONTENT_COVERING}
limitsSelector={resourcesLimitsSelector}
zoomStateSelector={resourcesZoomStateSelector}>
{transform => this.renderLayers(transform)}

View File

@@ -12,7 +12,9 @@ import {
class NodesResourcesLayer extends React.Component {
render() {
const { layerVerticalPosition, topologyId, transform, layoutNodes } = this.props;
const {
layerVerticalPosition, topologyId, transform, layoutNodes
} = this.props;
return (
<g className="node-resources-layer">
@@ -50,6 +52,4 @@ function mapStateToProps(state, props) {
};
}
export default connect(
mapStateToProps
)(NodesResourcesLayer);
export default connect(mapStateToProps)(NodesResourcesLayer);

View File

@@ -3,8 +3,10 @@ import React from 'react';
export default class NodeResourcesMetricBoxInfo extends React.Component {
humanizedMetricInfo() {
const { humanizedTotalCapacity, humanizedAbsoluteConsumption,
humanizedRelativeConsumption, showCapacity, format } = this.props.metricSummary.toJS();
const {
humanizedTotalCapacity, humanizedAbsoluteConsumption,
humanizedRelativeConsumption, showCapacity, format
} = this.props.metricSummary.toJS();
const showExtendedInfo = showCapacity && format !== 'percent';
return (
@@ -12,9 +14,11 @@ export default class NodeResourcesMetricBoxInfo extends React.Component {
<strong>
{showExtendedInfo ? humanizedRelativeConsumption : humanizedAbsoluteConsumption}
</strong> used
{showExtendedInfo && <i>{' - '}
({humanizedAbsoluteConsumption} / <strong>{humanizedTotalCapacity}</strong>)
</i>}
{showExtendedInfo &&
<i>
{' - '}({humanizedAbsoluteConsumption} / <strong>{humanizedTotalCapacity}</strong>)
</i>
}
</span>
);
}

View File

@@ -28,7 +28,9 @@ import {
// down to this component, so a lot of stuff gets rerendered/recalculated on every zoom action.
// On the other hand, this enables us to easily leave out the nodes that are not in the viewport.
const transformedDimensions = (props) => {
const { width, height, x, y } = applyTransform(props.transform, props);
const {
width, height, x, y
} = applyTransform(props.transform, props);
// Trim the beginning of the resource box just after the layer topology
// name to the left and the viewport width to the right. That enables us
@@ -79,7 +81,9 @@ class NodeResourcesMetricBox extends React.Component {
}
defaultRectProps(relativeHeight = 1) {
const { x, y, width, height } = this.state;
const {
x, y, width, height
} = this.state;
const translateY = height * (1 - relativeHeight);
return {
transform: `translate(0, ${translateY})`,
@@ -94,7 +98,9 @@ class NodeResourcesMetricBox extends React.Component {
render() {
const { x, y, width } = this.state;
const { id, selectedNodeId, label, color, metricSummary } = this.props;
const {
id, selectedNodeId, label, color, metricSummary
} = this.props;
const { showCapacity, relativeConsumption, type } = metricSummary.toJS();
const opacity = (selectedNodeId && selectedNodeId !== id) ? 0.35 : 1;
@@ -111,8 +117,10 @@ class NodeResourcesMetricBox extends React.Component {
return (
<g
className="node-resources-metric-box" style={{ opacity }}
onClick={this.handleClick} ref={this.saveNodeRef}>
className="node-resources-metric-box"
style={{ opacity }}
onClick={this.handleClick}
ref={this.saveNodeRef}>
<title>{label} - {type} usage at {resourceUsageTooltipInfo}</title>
{showCapacity && <rect className="frame" {...this.defaultRectProps()} />}
<rect className="bar" fill={color} {...this.defaultRectProps(relativeConsumption)} />

View File

@@ -52,8 +52,10 @@ class Nodes extends React.Component {
}
render() {
const { topologiesLoaded, nodesLoaded, topologies, currentTopology, isGraphViewMode,
isTableViewMode, isResourceViewMode } = this.props;
const {
topologiesLoaded, nodesLoaded, topologies, currentTopology, isGraphViewMode,
isTableViewMode, isResourceViewMode
} = this.props;
// TODO: Rename view mode components.
return (
@@ -92,6 +94,4 @@ function mapStateToProps(state) {
}
export default connect(
mapStateToProps
)(Nodes);
export default connect(mapStateToProps)(Nodes);

View File

@@ -5,7 +5,9 @@ import classNames from 'classnames';
import Tooltip from './tooltip';
const Plugin = ({id, label, description, status}) => {
const Plugin = ({
id, label, description, status
}) => {
const error = status !== 'ok';
const className = classNames({ error });
const tip = (<span>Description: {description}<br />Status: {status}</span>);
@@ -44,6 +46,4 @@ function mapStateToProps(state) {
};
}
export default connect(
mapStateToProps
)(Plugins);
export default connect(mapStateToProps)(Plugins);

View File

@@ -4,7 +4,6 @@ import { connect } from 'react-redux';
import { unpinSearch } from '../actions/app-actions';
class SearchItem extends React.Component {
constructor(props, context) {
super(props, context);
this.handleClick = this.handleClick.bind(this);

View File

@@ -30,8 +30,7 @@ function getHint(nodes) {
const node = nodes.filter(n => !n.get('pseudo') && n.has('metadata')).last();
if (node) {
label = shortenHintLabel(node.get('label'))
.split('.')[0];
[label] = shortenHintLabel(node.get('label')).split('.');
if (node.get('metadata')) {
const metadataField = node.get('metadata').first();
metadataLabel = shortenHintLabel(slugify(metadataField.get('label')))
@@ -46,7 +45,6 @@ function getHint(nodes) {
class Search extends React.Component {
constructor(props, context) {
super(props, context);
this.handleBlur = this.handleBlur.bind(this);
@@ -126,8 +124,10 @@ class Search extends React.Component {
}
render() {
const { nodes, pinnedSearches, searchFocused, searchMatchCountByTopology,
isResourceViewMode, searchQuery, topologiesLoaded, inputId = 'search' } = this.props;
const {
nodes, pinnedSearches, searchFocused, searchMatchCountByTopology,
isResourceViewMode, searchQuery, topologiesLoaded, inputId = 'search'
} = this.props;
const hidden = !topologiesLoaded || isResourceViewMode;
const disabled = this.props.isTopologyNodeCountZero && !hidden;
const matchCount = searchMatchCountByTopology
@@ -152,22 +152,30 @@ class Search extends React.Component {
{showPinnedSearches && pinnedSearches.toIndexedSeq()
.map(query => <SearchItem query={query} key={query} />)}
<input
className="search-input-field" type="text" id={inputId}
value={value} onChange={this.handleChange} onKeyUp={this.handleKeyUp}
onFocus={this.handleFocus} onBlur={this.handleBlur}
disabled={disabled} ref={this.saveQueryInputRef} />
className="search-input-field"
type="text"
id={inputId}
value={value}
onChange={this.handleChange}
onKeyUp={this.handleKeyUp}
onFocus={this.handleFocus}
onBlur={this.handleBlur}
disabled={disabled}
ref={this.saveQueryInputRef} />
</div>
<div className="search-label">
<i className="fa fa-search search-label-icon" />
<label className="search-label-hint" htmlFor={inputId}>
<span className="search-label-hint" htmlFor={inputId}>
Search
</label>
</span>
</div>
{!showPinnedSearches && <div className="search-hint">
{getHint(nodes)} <span
className="search-help-link fa fa-question-circle"
onMouseDown={this.props.toggleHelp} />
</div>}
{!showPinnedSearches &&
<div className="search-hint">
{getHint(nodes)} <span
className="search-help-link fa fa-question-circle"
onMouseDown={this.props.toggleHelp} />
</div>
}
</div>
</div>
);
@@ -188,5 +196,7 @@ export default connect(
searchQuery: state.get('searchQuery'),
searchMatchCountByTopology: searchMatchCountByTopologySelector(state),
}),
{ blurSearch, doSearch, focusSearch, pinSearch, toggleHelp }
{
blurSearch, doSearch, focusSearch, pinSearch, toggleHelp
}
)(Search);

View File

@@ -12,7 +12,9 @@ export default class ShowMore extends React.PureComponent {
}
render() {
const { collection, notShown, expanded, hideNumber } = this.props;
const {
collection, notShown, expanded, hideNumber
} = this.props;
const showLimitAction = collection && (expanded || notShown > 0);
const limitActionText = !hideNumber && !expanded && notShown > 0 ? `+${notShown}` : '';
const limitActionIcon = !expanded && notShown > 0 ? 'fa fa-caret-down' : 'fa fa-caret-up';

View File

@@ -33,12 +33,12 @@ export default class Sparkline extends React.Component {
this.x.range([MARGIN, this.props.width - circleSpace]);
this.y.range([this.props.height - circleSpace, circleSpace]);
this.line.curve(this.props.curve);
this.line.curve(curveLinear);
}
getGraphData() {
// data is of shape [{date, value}, ...] and is sorted by date (ASC)
let data = this.props.data;
let { data } = this.props;
this.initRanges(true);
@@ -75,7 +75,9 @@ export default class Sparkline extends React.Component {
const title = `Last ${Math.round((lastDate - firstDate) / 1000)} seconds, ` +
`${data.length} samples, min: ${min}, max: ${max}, mean: ${mean}`;
return {title, lastX, lastY, data};
return {
title, lastX, lastY, data
};
}
getEmptyGraphData() {
@@ -113,13 +115,21 @@ export default class Sparkline extends React.Component {
<div title={graph.title}>
<svg width={this.props.width} height={this.props.height}>
<path
className="sparkline" fill="none" stroke={strokeColor}
strokeWidth={strokeWidth} strokeDasharray={strokeDasharray}
className="sparkline"
fill="none"
stroke={strokeColor}
strokeWidth={strokeWidth}
strokeDasharray={strokeDasharray}
d={this.line(graph.data)}
/>
{hasData && <circle
className="sparkcircle" cx={graph.lastX} cy={graph.lastY} fill={circleColor}
fillOpacity={fillOpacity} stroke="none" r={radius}
className="sparkcircle"
cx={graph.lastX}
cy={graph.lastY}
fill={circleColor}
fillOpacity={fillOpacity}
stroke="none"
r={radius}
/>}
</svg>
</div>
@@ -128,7 +138,14 @@ export default class Sparkline extends React.Component {
}
Sparkline.propTypes = {
data: PropTypes.arrayOf(PropTypes.object)
width: PropTypes.number,
height: PropTypes.number,
strokeColor: PropTypes.string,
strokeWidth: PropTypes.number,
hoverColor: PropTypes.string,
circleRadius: PropTypes.number,
hovered: PropTypes.bool,
data: PropTypes.arrayOf(PropTypes.object),
};
Sparkline.defaultProps = {
@@ -137,7 +154,6 @@ Sparkline.defaultProps = {
strokeColor: '#7d7da8',
strokeWidth: 0.5,
hoverColor: '#7d7da8',
curve: curveLinear,
circleRadius: 1.75,
hovered: false,
data: [],

View File

@@ -6,7 +6,9 @@ import { isPausedSelector } from '../selectors/time-travel';
class Status extends React.Component {
render() {
const { errorUrl, topologiesLoaded, filteredNodeCount, topology, websocketClosed } = this.props;
const {
errorUrl, topologiesLoaded, filteredNodeCount, topology, websocketClosed
} = this.props;
let title = '';
let text = 'Trying to reconnect...';
@@ -54,6 +56,4 @@ function mapStateToProps(state) {
};
}
export default connect(
mapStateToProps
)(Status);
export default connect(mapStateToProps)(Status);

View File

@@ -7,14 +7,15 @@ import { receiveControlPipeFromParams, hitEsc } from '../actions/app-actions';
const ESC_KEY_CODE = 27;
class TerminalApp extends React.Component {
constructor(props, context) {
super(props, context);
const paramString = window.location.hash.split('/').pop();
const params = JSON.parse(decodeURIComponent(paramString));
this.props.receiveControlPipeFromParams(params.pipe.id, params.pipe.raw,
params.pipe.resizeTtyControl);
this.props.receiveControlPipeFromParams(
params.pipe.id, params.pipe.raw,
params.pipe.resizeTtyControl
);
this.state = {
title: params.title,

View File

@@ -132,7 +132,9 @@ class Terminal extends React.Component {
this.createWebsocket(term);
} else {
this.reconnectTimeout = setTimeout(
this.createWebsocket.bind(this, term), reconnectTimerInterval);
this.createWebsocket.bind(this, term),
reconnectTimerInterval
);
}
}
};
@@ -291,7 +293,8 @@ class Terminal extends React.Component {
Pop out
</span>
<span
title="Close" className="terminal-header-tools-item-icon fa fa-close"
title="Close"
className="terminal-header-tools-item-icon fa fa-close"
onClick={this.handleCloseClick} />
</div>
{this.getControlStatusIcon()}
@@ -379,8 +382,7 @@ class Terminal extends React.Component {
function mapStateToProps(state, ownProps) {
const controlStatus = state.get('controlPipes').find(pipe =>
pipe.get('nodeId') === ownProps.pipe.get('nodeId')
);
pipe.get('nodeId') === ownProps.pipe.get('nodeId'));
return { controlStatus };
}

View File

@@ -61,8 +61,10 @@ class TimeControl extends React.Component {
}
render() {
const { showingTimeTravel, pausedAt, timeTravelTransitioning, topologiesLoaded,
hasHistoricReports } = this.props;
const {
showingTimeTravel, pausedAt, timeTravelTransitioning, topologiesLoaded,
hasHistoricReports
} = this.props;
const isPausedNow = pausedAt && !showingTimeTravel;
const isTimeTravelling = showingTimeTravel;
@@ -92,24 +94,27 @@ class TimeControl extends React.Component {
{isPausedNow && <span className="fa fa-pause" />}
<span className="label">{isPausedNow ? 'Paused' : 'Pause'}</span>
</span>
{hasHistoricReports && <span
className={className(isTimeTravelling)}
onClick={this.handleTravelClick}
title="Travel back in time">
{isTimeTravelling && <span className="fa fa-clock-o" />}
<span className="label">Time Travel</span>
</span>}
{hasHistoricReports &&
<span
className={className(isTimeTravelling)}
onClick={this.handleTravelClick}
title="Travel back in time">
{isTimeTravelling && <span className="fa fa-clock-o" />}
<span className="label">Time Travel</span>
</span>
}
</div>
</div>
{(isPausedNow || isTimeTravelling) && <span
className="time-control-info"
title={moment(pausedAt).toISOString()}>
Showing state from {moment(pausedAt).fromNow()}
</span>}
{isRunningNow && timeTravelTransitioning && <span
className="time-control-info">
Resuming the live state
</span>}
{(isPausedNow || isTimeTravelling) &&
<span
className="time-control-info"
title={moment(pausedAt).toISOString()}>
Showing state from {moment(pausedAt).fromNow()}
</span>
}
{isRunningNow && timeTravelTransitioning &&
<span className="time-control-info">Resuming the live state</span>
}
</div>
);
}

View File

@@ -145,7 +145,20 @@ const DisabledRange = styled.rect`
fill-opacity: 0.15;
`;
const TimestampLabel = styled.a`
const ShallowButton = styled.button`
background-color: transparent;
border: 0;
color: ${props => props.theme.colors.primary.lavender};
cursor: pointer;
padding: 0;
outline: 0;
&:hover {
color: ${props => props.theme.colors.primary.charcoal};
}
`;
const TimestampLabel = ShallowButton.extend`
margin-left: 2px;
padding: 3px;
@@ -155,7 +168,7 @@ const TimestampLabel = styled.a`
}
`;
const TimelinePanButton = styled.a`
const TimelinePanButton = ShallowButton.extend`
pointer-events: all;
padding: 2px;
`;
@@ -236,7 +249,9 @@ export default class TimeTravelComponent extends React.Component {
this.instantUpdateTimestamp = this.instantUpdateTimestamp.bind(this);
this.debouncedUpdateTimestamp = debounce(
this.instantUpdateTimestamp.bind(this), TIMELINE_DEBOUNCE_INTERVAL);
this.instantUpdateTimestamp.bind(this),
TIMELINE_DEBOUNCE_INTERVAL
);
}
componentDidMount() {
@@ -498,7 +513,9 @@ export default class TimeTravelComponent extends React.Component {
<rect
className="tooltip-container"
transform={`translate(${-width / 2}, 0)`}
width={width} height={height} fillOpacity={0}
width={width}
height={height}
fillOpacity={0}
/>
{this.renderDisabledShadow(timelineTransform)}
<g className="ticks" transform="translate(0, 1)">

View File

@@ -20,7 +20,6 @@ function basicTopologyInfo(topology, searchMatchCount) {
}
class Topologies extends React.Component {
constructor(props, context) {
super(props, context);
this.onTopologyClick = this.onTopologyClick.bind(this);
@@ -48,7 +47,10 @@ class Topologies extends React.Component {
return (
<div
className={className} title={title} key={topologyId} rel={topologyId}
className={className}
title={title}
key={topologyId}
rel={topologyId}
onClick={ev => this.onTopologyClick(ev, subTopology)}>
<div className="topologies-sub-item-label">
{subTopology.get('name')}
@@ -71,7 +73,9 @@ class Topologies extends React.Component {
return (
<div className="topologies-item" key={topologyId}>
<div
className={className} title={title} rel={topologyId}
className={className}
title={title}
rel={topologyId}
onClick={ev => this.onTopologyClick(ev, topology)}>
<div className="topologies-item-label">
{topology.get('name')}
@@ -88,9 +92,7 @@ class Topologies extends React.Component {
render() {
return (
<div className="topologies">
{this.props.currentTopology && this.props.topologies.map(
topology => this.renderTopology(topology)
)}
{this.props.currentTopology && this.props.topologies.map(t => this.renderTopology(t))}
</div>
);
}

View File

@@ -1,7 +1,6 @@
import React from 'react';
export default class TopologyOptionAction extends React.Component {
constructor(props, context) {
super(props, context);
this.onClick = this.onClick.bind(this);

View File

@@ -129,8 +129,7 @@ class TopologyOptions extends React.Component {
const { options } = this.props;
return (
<div className="topology-options">
{options && options.toIndexedSeq().map(
option => this.renderOption(option))}
{options && options.toIndexedSeq().map(option => this.renderOption(option))}
</div>
);
}

View File

@@ -42,8 +42,7 @@ class DebugMenu extends React.Component {
</a>
</div>
<div className="troubleshooting-menu-item">
<a
href=""
<button
className="footer-icon"
onClick={this.props.clickDownloadGraph}
title="Save canvas as SVG (does not include search highlighting)"
@@ -52,24 +51,25 @@ class DebugMenu extends React.Component {
<span className="description">
Save canvas as SVG (does not include search highlighting)
</span>
</a>
</button>
</div>
<div className="troubleshooting-menu-item">
<a
href=""
<button
className="footer-icon"
title="Reset view state"
onClick={this.handleClickReset}
>
<span className="fa fa-undo" />
<span className="description">Reset your local view state and reload the page</span>
</a>
</button>
</div>
<div className="troubleshooting-menu-item">
<a
className="footer-icon" title="Report an issue"
className="footer-icon"
title="Report an issue"
href="https://gitreports.com/issue/weaveworks/scope"
target="_blank" rel="noopener noreferrer"
target="_blank"
rel="noopener noreferrer"
>
<span className="fa fa-bug" />
<span className="description">Report a bug</span>

View File

@@ -58,8 +58,10 @@ class ViewModeSelector extends React.Component {
<div className="view-mode-selector-wrapper">
{this.renderItem('fa fa-share-alt', 'Graph', GRAPH_VIEW_MODE, this.props.setGraphView)}
{this.renderItem('fa fa-table', 'Table', TABLE_VIEW_MODE, this.props.setTableView)}
{this.renderItem('fa fa-bar-chart', 'Resources', RESOURCE_VIEW_MODE,
this.props.setResourceView, hasResourceView)}
{this.renderItem(
'fa fa-bar-chart', 'Resources', RESOURCE_VIEW_MODE,
this.props.setResourceView, hasResourceView
)}
</div>
<MetricSelector />
</div>

View File

@@ -3,7 +3,6 @@ import classnames from 'classnames';
class Warning extends React.Component {
constructor(props, context) {
super(props, context);
this.handleClick = this.handleClick.bind(this);

View File

@@ -56,9 +56,13 @@ export default class ZoomControl extends React.Component {
return (
<div className="zoom-control">
<a className="zoom-in" onClick={this.handleZoomIn}><span className="fa fa-plus" /></a>
<button className="zoom-in" onClick={this.handleZoomIn}>
<span className="fa fa-plus" />
</button>
<Slider value={value} max={1} step={SLIDER_STEP} vertical onChange={this.handleChange} />
<a className="zoom-out" onClick={this.handleZoomOut}><span className="fa fa-minus" /></a>
<button className="zoom-out" onClick={this.handleZoomOut}>
<span className="fa fa-minus" />
</button>
</div>
);
}

View File

@@ -88,7 +88,9 @@ class ZoomableCanvas extends React.Component {
handleZoomControlAction(scale) {
// Get the center of the SVG and zoom around it.
const { top, bottom, left, right } = this.svg.node().getBoundingClientRect();
const {
top, bottom, left, right
} = this.svg.node().getBoundingClientRect();
const centerOfCanvas = {
x: (left + right) / 2,
y: (top + bottom) / 2,
@@ -162,9 +164,10 @@ class ZoomableCanvas extends React.Component {
}
handlePan() {
let state = this.state;
let { state } = this;
// Apply the translation respecting the boundaries.
state = this.clampedTranslation({ ...state,
state = this.clampedTranslation({
...state,
translateX: this.state.translateX + d3Event.dx,
translateY: this.state.translateY + d3Event.dy,
});
@@ -185,8 +188,12 @@ class ZoomableCanvas extends React.Component {
}
clampedTranslation(state) {
const { width, height, canvasMargins, boundContent, layoutLimits } = this.props;
const { contentMinX, contentMaxX, contentMinY, contentMaxY } = layoutLimits.toJS();
const {
width, height, canvasMargins, boundContent, layoutLimits
} = this.props;
const {
contentMinX, contentMaxX, contentMinY, contentMaxY
} = layoutLimits.toJS();
if (boundContent) {
// If the content is required to be bounded in any way, the translation will
@@ -239,7 +246,8 @@ class ZoomableCanvas extends React.Component {
// translation limits). Adapted from:
// https://github.com/d3/d3-zoom/blob/807f02c7a5fe496fbd08cc3417b62905a8ce95fa/src/zoom.js#L251
const inversePosition = inverseTransform(this.state, position);
state = this.clampedTranslation({ ...state,
state = this.clampedTranslation({
...state,
translateX: position.x - (inversePosition.x * scaleX),
translateY: position.y - (inversePosition.y * scaleY),
});

View File

@@ -38,9 +38,15 @@ export const NODE_BASE_SIZE = 100;
export const EDGE_WAYPOINTS_CAP = 10;
export const CANVAS_MARGINS = {
[GRAPH_VIEW_MODE]: { top: 160, left: 80, right: 80, bottom: 150 },
[TABLE_VIEW_MODE]: { top: 220, left: 40, right: 40, bottom: 30 },
[RESOURCE_VIEW_MODE]: { top: 140, left: 210, right: 40, bottom: 150 },
[GRAPH_VIEW_MODE]: {
top: 160, left: 80, right: 80, bottom: 150
},
[TABLE_VIEW_MODE]: {
top: 220, left: 40, right: 40, bottom: 30
},
[RESOURCE_VIEW_MODE]: {
top: 140, left: 210, right: 40, bottom: 150
},
};
// Node details table constants

View File

@@ -23,7 +23,6 @@ const WINDOW_LENGTH = 60;
* This component also keeps a historic max of all samples it sees over time.
*/
export default ComposedComponent => class extends React.Component {
constructor(props, context) {
super(props, context);
@@ -58,7 +57,7 @@ export default ComposedComponent => class extends React.Component {
updateBuffer(props) {
// merge new samples into buffer
let buffer = this.state.buffer;
let { buffer } = this.state;
const nextSamples = makeOrderedMap(props.samples.map(d => [d.date, d.value]));
// need to sort again after merge, some new data may have different times for old values
buffer = buffer.merge(nextSamples).sortBy(sortDate);

View File

@@ -15,12 +15,14 @@ const store = configureStore();
function renderApp() {
const App = require('./components/app').default;
ReactDOM.render((
<Provider store={store}>
<App />
<DevTools />
</Provider>
), document.getElementById('app'));
ReactDOM.render(
(
<Provider store={store}>
<App />
<DevTools />
</Provider>
), document.getElementById('app')
);
}
renderApp();

View File

@@ -11,11 +11,13 @@ const store = configureStore();
function renderApp() {
const App = require('./components/app').default;
ReactDOM.render((
<Provider store={store}>
<App />
</Provider>
), document.getElementById('app'));
ReactDOM.render(
(
<Provider store={store}>
<App />
</Provider>
), document.getElementById('app')
);
}
renderApp();

View File

@@ -10,15 +10,13 @@ import { highlightedEdgeIdsSelector } from '../../selectors/graph-view/decorator
describe('RootReducer', () => {
const ActionTypes = require('../../constants/action-types').default;
const reducer = require('../root').default;
const initialState = require('../root').initialState;
const { initialState } = require('../root');
const topologyUtils = require('../../utils/topology-utils');
const topologySelectors = require('../../selectors/topology');
// TODO maybe extract those to topology-utils tests?
const activeTopologyOptionsSelector = topologySelectors.activeTopologyOptionsSelector;
const getAdjacentNodes = topologyUtils.getAdjacentNodes;
const isNodesDisplayEmpty = topologyUtils.isNodesDisplayEmpty;
const isTopologyNodeCountZero = topologyUtils.isTopologyNodeCountZero;
const getUrlState = require('../../utils/router-utils').getUrlState;
const { activeTopologyOptionsSelector } = topologySelectors;
const { getAdjacentNodes, isNodesDisplayEmpty, isTopologyNodeCountZero } = topologyUtils;
const { getUrlState } = require('../../utils/router-utils');
// fixtures
@@ -427,7 +425,8 @@ describe('RootReducer', () => {
RouteAction.state = {
topologyId: 'topo1',
selectedNodeId: null,
topologyOptions: {topo1: {option1: 'on'}}};
topologyOptions: {topo1: {option1: 'on'}}
};
let nextState = initialState;
nextState = reducer(nextState, RouteAction);
@@ -445,7 +444,8 @@ describe('RootReducer', () => {
RouteAction.state = {
topologyId: 'topo1',
selectedNodeId: null,
topologyOptions: null};
topologyOptions: null
};
let nextState = initialState;
nextState = reducer(nextState, RouteAction);
nextState = reducer(nextState, ReceiveTopologiesAction);

View File

@@ -119,8 +119,10 @@ function processTopologies(state, nextTopologies) {
// set `selectType` field for topology and sub_topologies options (recursive).
const topologiesWithSelectType = visibleTopologies.map(calcSelectType);
// cache URLs by ID
state = state.set('topologyUrlsById',
setTopologyUrlsById(state.get('topologyUrlsById'), topologiesWithSelectType));
state = state.set(
'topologyUrlsById',
setTopologyUrlsById(state.get('topologyUrlsById'), topologiesWithSelectType)
);
const topologiesWithFullnames = addTopologyFullname(topologiesWithSelectType);
const immNextTopologies = fromJS(topologiesWithFullnames).sortBy(topologySorter);
@@ -144,7 +146,8 @@ function setDefaultTopologyOptions(state, topologyList) {
}
if (defaultOptions.size) {
state = state.setIn(['topologyOptions', topology.get('id')],
state = state.setIn(
['topologyOptions', topology.get('id')],
defaultOptions
);
}
@@ -157,8 +160,10 @@ function closeNodeDetails(state, nodeId) {
if (nodeDetails.size > 0) {
const popNodeId = nodeId || nodeDetails.keySeq().last();
// remove pipe if it belongs to the node being closed
state = state.update('controlPipes',
controlPipes => controlPipes.filter(pipe => pipe.get('nodeId') !== popNodeId));
state = state.update(
'controlPipes',
controlPipes => controlPipes.filter(pipe => pipe.get('nodeId') !== popNodeId)
);
state = state.deleteIn(['nodeDetails', popNodeId]);
}
if (state.get('nodeDetails').size === 0 || state.get('selectedNodeId') === nodeId) {
@@ -298,7 +303,8 @@ export function rootReducer(state = initialState, action) {
if (prevDetailsStackSize > 1 || prevSelectedNodeId !== action.nodeId) {
// dont set origin if a node was already selected, suppresses animation
const origin = prevSelectedNodeId === null ? action.origin : null;
state = state.setIn(['nodeDetails', action.nodeId],
state = state.setIn(
['nodeDetails', action.nodeId],
{
id: action.nodeId,
label: action.label,
@@ -318,7 +324,8 @@ export function rootReducer(state = initialState, action) {
state = state.deleteIn(['nodeDetails', action.nodeId]);
state = state.setIn(['nodeDetails', action.nodeId], details);
} else {
state = state.setIn(['nodeDetails', action.nodeId],
state = state.setIn(
['nodeDetails', action.nodeId],
{
id: action.nodeId,
label: action.label,
@@ -331,8 +338,10 @@ export function rootReducer(state = initialState, action) {
}
case ActionTypes.CLICK_SHOW_TOPOLOGY_FOR_NODE: {
state = state.update('nodeDetails',
nodeDetails => nodeDetails.filter((v, k) => k === action.nodeId));
state = state.update(
'nodeDetails',
nodeDetails => nodeDetails.filter((v, k) => k === action.nodeId)
);
state = state.update('controlPipes', controlPipes => controlPipes.clear());
state = state.set('selectedNodeId', action.nodeId);
@@ -551,7 +560,8 @@ export function rootReducer(state = initialState, action) {
// disregard if node is not selected anymore
if (state.hasIn(['nodeDetails', action.details.id])) {
state = state.updateIn(['nodeDetails', action.details.id], obj => ({ ...obj,
state = state.updateIn(['nodeDetails', action.details.id], obj => ({
...obj,
notFound: false,
timestamp: action.requestTimestamp,
details: action.details,
@@ -579,11 +589,13 @@ export function rootReducer(state = initialState, action) {
return state;
}
log('RECEIVE_NODES_DELTA',
log(
'RECEIVE_NODES_DELTA',
'remove', size(action.delta.remove),
'update', size(action.delta.update),
'add', size(action.delta.add),
'reset', action.delta.reset);
'reset', action.delta.reset
);
if (action.delta.reset) {
state = state.set('nodes', makeMap());
@@ -625,7 +637,8 @@ export function rootReducer(state = initialState, action) {
case ActionTypes.RECEIVE_NOT_FOUND: {
if (state.hasIn(['nodeDetails', action.nodeId])) {
state = state.updateIn(['nodeDetails', action.nodeId], obj => ({ ...obj,
state = state.updateIn(['nodeDetails', action.nodeId], obj => ({
...obj,
timestamp: action.requestTimestamp,
notFound: true,
}));
@@ -700,8 +713,7 @@ export function rootReducer(state = initialState, action) {
state = state.update('controlPipes', controlPipes => controlPipes.clear());
}
if (action.state.nodeDetails) {
const actionNodeDetails = makeOrderedMap(
action.state.nodeDetails.map(obj => [obj.id, obj]));
const actionNodeDetails = makeOrderedMap(action.state.nodeDetails.map(h => [h.id, h]));
// check if detail IDs have changed
if (!isDeepEqual(state.get('nodeDetails').keySeq(), actionNodeDetails.keySeq())) {
state = state.set('nodeDetails', actionNodeDetails);
@@ -709,8 +721,10 @@ export function rootReducer(state = initialState, action) {
} else {
state = state.update('nodeDetails', nodeDetails => nodeDetails.clear());
}
state = state.set('topologyOptions',
fromJS(action.state.topologyOptions) || state.get('topologyOptions'));
state = state.set(
'topologyOptions',
fromJS(action.state.topologyOptions) || state.get('topologyOptions')
);
return state;
}

View File

@@ -11,7 +11,9 @@ export const canvasMarginsSelector = createSelector(
[
state => state.get('topologyViewMode'),
],
viewMode => CANVAS_MARGINS[viewMode] || { top: 0, left: 0, right: 0, bottom: 0 }
viewMode => CANVAS_MARGINS[viewMode] || {
top: 0, left: 0, right: 0, bottom: 0
}
);
export const canvasWidthSelector = createSelector(

View File

@@ -28,7 +28,9 @@ const translationToViewportCenterSelector = createSelector(
graphZoomStateSelector,
],
(centerX, centerY, zoomState) => {
const { scaleX, scaleY, translateX, translateY } = zoomState.toJS();
const {
scaleX, scaleY, translateX, translateY
} = zoomState.toJS();
return {
x: (-translateX + centerX) / scaleX,
y: (-translateY + centerY) / scaleY,

View File

@@ -22,7 +22,9 @@ const graphBoundingRectangleSelector = createSelector(
const xMax = graphNodes.map(n => n.get('x') + NODE_BASE_SIZE).max();
const yMax = graphNodes.map(n => n.get('y') + NODE_BASE_SIZE).max();
return makeMap({ xMin, yMin, xMax, yMax });
return makeMap({
xMin, yMin, xMax, yMax
});
}
);
@@ -37,7 +39,9 @@ export const graphDefaultZoomSelector = createSelector(
(boundingRectangle, canvasMargins, width, height) => {
if (!boundingRectangle) return makeMap();
const { xMin, xMax, yMin, yMax } = boundingRectangle.toJS();
const {
xMin, xMax, yMin, yMax
} = boundingRectangle.toJS();
const xFactor = width / (xMax - xMin);
const yFactor = height / (yMax - yMin);
@@ -65,7 +69,9 @@ export const graphLimitsSelector = createSelector(
(boundingRectangle) => {
if (!boundingRectangle) return makeMap();
const { xMin, xMax, yMin, yMax } = boundingRectangle.toJS();
const {
xMin, xMax, yMin, yMax
} = boundingRectangle.toJS();
return makeMap({
minScale: MIN_SCALE,

View File

@@ -85,8 +85,8 @@ const decoratedNodesByTopologySelector = createSelector(
const isBaseLayer = (index === 0);
const nodeParentDecorator = nodeParentDecoratorByTopologyId(parentLayerTopologyId);
const nodeMetricSummaryDecorator = nodeMetricSummaryDecoratorByType(
pinnedMetricType, showCapacity);
const nodeMetricSummaryDecorator =
nodeMetricSummaryDecoratorByType(pinnedMetricType, showCapacity);
// Color the node, deduce its anchor point, dimensions and info about its pinned metric.
const decoratedTopologyNodes = (topologyNodes || makeMap())

View File

@@ -32,7 +32,9 @@ const resourceNodesBoundingRectangleSelector = createSelector(
const xMax = flattenedNodes.map(n => n.get('offset') + n.get('width')).max();
const yMax = verticalPositions.toList().max() + RESOURCES_LAYER_HEIGHT;
return makeMap({ xMin, xMax, yMin, yMax });
return makeMap({
xMin, xMax, yMin, yMax
});
}
);
@@ -47,7 +49,9 @@ export const resourcesDefaultZoomSelector = createSelector(
(boundingRectangle, canvasMargins, width, height) => {
if (!boundingRectangle) return makeMap();
const { xMin, xMax, yMin, yMax } = boundingRectangle.toJS();
const {
xMin, xMax, yMin, yMax
} = boundingRectangle.toJS();
// The default scale takes all the available horizontal space and 70% of the vertical space.
const scaleX = (width / (xMax - xMin)) * 1.0;
@@ -76,7 +80,9 @@ export const resourcesLimitsSelector = createSelector(
(defaultZoom, boundingRectangle, minNodeWidth, width) => {
if (defaultZoom.isEmpty()) return makeMap();
const { xMin, xMax, yMin, yMax } = boundingRectangle.toJS();
const {
xMin, xMax, yMin, yMax
} = boundingRectangle.toJS();
return makeMap({
// Maximal zoom is such that the smallest box takes the whole canvas.

View File

@@ -13,14 +13,14 @@ export const activeTopologyZoomCacheKeyPathSelector = createSelector(
state => JSON.stringify(activeTopologyOptionsSelector(state)),
],
(isGraphViewMode, viewMode, topologyId, pinnedMetricType, topologyOptions) => (
isGraphViewMode ?
isGraphViewMode
// In graph view, selecting different options/filters produces a different layout.
['zoomCache', viewMode, topologyId, topologyOptions] :
? ['zoomCache', viewMode, topologyId, topologyOptions]
// Otherwise we're in the resource view where the options are hidden (for now),
// but pinning different metrics can result in very different layouts.
// TODO: Take `topologyId` into account once the resource
// view layouts start differing between the topologies.
['zoomCache', viewMode, pinnedMetricType]
: ['zoomCache', viewMode, pinnedMetricType]
)
);

View File

@@ -11,11 +11,13 @@ const store = configureStore();
function renderApp() {
const TerminalApp = require('./components/terminal-app').default;
ReactDOM.render((
<Provider store={store}>
<TerminalApp />
</Provider>
), document.getElementById('app'));
ReactDOM.render(
(
<Provider store={store}>
<TerminalApp />
</Provider>
), document.getElementById('app')
);
}
renderApp();

View File

@@ -15,10 +15,18 @@ describe('LayouterUtils', () => {
c: {}
});
expect(initEdgesFromNodes(input).toJS()).toEqual({
[edge('a', 'b')]: { id: edge('a', 'b'), source: 'a', target: 'b', value: 1 },
[edge('a', 'c')]: { id: edge('a', 'c'), source: 'a', target: 'c', value: 1 },
[edge('b', 'a')]: { id: edge('b', 'a'), source: 'b', target: 'a', value: 1 },
[edge('b', 'b')]: { id: edge('b', 'b'), source: 'b', target: 'b', value: 1 },
[edge('a', 'b')]: {
id: edge('a', 'b'), source: 'a', target: 'b', value: 1
},
[edge('a', 'c')]: {
id: edge('a', 'c'), source: 'a', target: 'c', value: 1
},
[edge('b', 'a')]: {
id: edge('b', 'a'), source: 'b', target: 'a', value: 1
},
[edge('b', 'b')]: {
id: edge('b', 'b'), source: 'b', target: 'b', value: 1
},
});
});
});

View File

@@ -37,9 +37,15 @@ describe('MathUtils', () => {
expect(f(fromJS({...entryA, ...entryB}))).toBe(30);
expect(f(fromJS({...entryA, ...entryC}))).toBe(40);
expect(f(fromJS({...entryB, ...entryC}))).toBe(50);
expect(f(fromJS({...entryA, ...entryB, ...entryC, ...entryD}))).toBe(30);
expect(f(fromJS({...entryA, ...entryB, ...entryC, ...entryD, ...entryE}))).toBe(1);
expect(f(fromJS({...entryA, ...entryB, ...entryC, ...entryD, ...entryF}))).toBe(0);
expect(f(fromJS({
...entryA, ...entryB, ...entryC, ...entryD
}))).toBe(30);
expect(f(fromJS({
...entryA, ...entryB, ...entryC, ...entryD, ...entryE
}))).toBe(1);
expect(f(fromJS({
...entryA, ...entryB, ...entryC, ...entryD, ...entryF
}))).toBe(0);
});
});
});

View File

@@ -125,18 +125,24 @@ describe('SearchUtils', () => {
it('does not add a non-matching field', () => {
let matches = fromJS({});
matches = fun(matches, ['node1', 'field1'],
'some value', 'some query', null, 'some label');
matches = fun(
matches, ['node1', 'field1'],
'some value', 'some query', null, 'some label'
);
expect(matches.size).toBe(0);
});
it('adds a matching field', () => {
let matches = fromJS({});
matches = fun(matches, ['node1', 'field1'],
'samevalue', 'samevalue', null, 'some label');
matches = fun(
matches, ['node1', 'field1'],
'samevalue', 'samevalue', null, 'some label'
);
expect(matches.size).toBe(1);
expect(matches.getIn(['node1', 'field1'])).toBeDefined();
const {text, label, start, length} = matches.getIn(['node1', 'field1']);
const {
text, label, start, length
} = matches.getIn(['node1', 'field1']);
expect(text).toBe('samevalue');
expect(label).toBe('some label');
expect(start).toBe(0);
@@ -145,15 +151,19 @@ describe('SearchUtils', () => {
it('does not add a field when the prefix does not match the label', () => {
let matches = fromJS({});
matches = fun(matches, ['node1', 'field1'],
'samevalue', 'samevalue', 'some prefix', 'some label');
matches = fun(
matches, ['node1', 'field1'],
'samevalue', 'samevalue', 'some prefix', 'some label'
);
expect(matches.size).toBe(0);
});
it('adds a field when the prefix matches the label', () => {
let matches = fromJS({});
matches = fun(matches, ['node1', 'field1'],
'samevalue', 'samevalue', 'prefix', 'prefixed label');
matches = fun(
matches, ['node1', 'field1'],
'samevalue', 'samevalue', 'prefix', 'prefixed label'
);
expect(matches.size).toBe(1);
});
});
@@ -163,30 +173,40 @@ describe('SearchUtils', () => {
it('does not add a non-matching field', () => {
let matches = fromJS({});
matches = fun(matches, ['node1', 'field1'],
1, 'metric1', 'metric2', 'lt', 2);
matches = fun(
matches, ['node1', 'field1'],
1, 'metric1', 'metric2', 'lt', 2
);
expect(matches.size).toBe(0);
});
it('adds a matching field', () => {
let matches = fromJS({});
matches = fun(matches, ['node1', 'field1'],
1, 'metric1', 'metric1', 'lt', 2);
matches = fun(
matches, ['node1', 'field1'],
1, 'metric1', 'metric1', 'lt', 2
);
expect(matches.size).toBe(1);
expect(matches.getIn(['node1', 'field1'])).toBeDefined();
const { metric } = matches.getIn(['node1', 'field1']);
expect(metric).toBeTruthy();
matches = fun(matches, ['node2', 'field1'],
1, 'metric1', 'metric1', 'gt', 0);
matches = fun(
matches, ['node2', 'field1'],
1, 'metric1', 'metric1', 'gt', 0
);
expect(matches.size).toBe(2);
matches = fun(matches, ['node3', 'field1'],
1, 'metric1', 'metric1', 'eq', 1);
matches = fun(
matches, ['node3', 'field1'],
1, 'metric1', 'metric1', 'eq', 1
);
expect(matches.size).toBe(3);
matches = fun(matches, ['node3', 'field1'],
1, 'metric1', 'metric1', 'other', 1);
matches = fun(
matches, ['node3', 'field1'],
1, 'metric1', 'metric1', 'other', 1
);
expect(matches.size).toBe(3);
});
});

View File

@@ -79,7 +79,8 @@ describe('TopologyUtils', () => {
it('sets node degrees', () => {
nodes = TopologyUtils.updateNodeDegrees(
nodeSets.initial4.nodes,
nodeSets.initial4.edges).toJS();
nodeSets.initial4.edges
).toJS();
expect(nodes.n1.degree).toEqual(2);
expect(nodes.n2.degree).toEqual(1);
@@ -88,7 +89,8 @@ describe('TopologyUtils', () => {
nodes = TopologyUtils.updateNodeDegrees(
nodeSets.removeEdge24.nodes,
nodeSets.removeEdge24.edges).toJS();
nodeSets.removeEdge24.edges
).toJS();
expect(nodes.n1.degree).toEqual(2);
expect(nodes.n2.degree).toEqual(0);
@@ -97,7 +99,8 @@ describe('TopologyUtils', () => {
nodes = TopologyUtils.updateNodeDegrees(
nodeSets.single3.nodes,
nodeSets.single3.edges).toJS();
nodeSets.single3.edges
).toJS();
expect(nodes.n1.degree).toEqual(0);
expect(nodes.n2.degree).toEqual(0);

View File

@@ -8,8 +8,7 @@ export function uniformSelect(array, size) {
}
return range(size).map(index =>
array[parseInt(index * (array.length / (size - (1 - 1e-9))), 10)]
);
array[parseInt(index * (array.length / (size - (1 - 1e-9))), 10)]);
}
export function insertElement(array, index, element) {

View File

@@ -3,7 +3,7 @@ import { scaleLinear } from 'd3-scale';
import { extent } from 'd3-array';
// Inspired by Lee Byron's test data generator.
/*eslint-disable */
/* eslint-disable */
function bumpLayer(n, maxValue) {
function bump(a) {
const x = 1 / (0.1 + Math.random());
@@ -23,7 +23,7 @@ function bumpLayer(n, maxValue) {
const s = scaleLinear().domain(extent(values)).range([0, maxValue]);
return values.map(s);
}
/*eslint-enable */
/* eslint-enable */
const nodeData = {};

View File

@@ -76,7 +76,8 @@ function download(source, name) {
filename = `${window.document.title.replace(/[^a-z0-9]/gi, '-').toLowerCase()}-${(+new Date())}`;
}
const url = window.URL.createObjectURL(new Blob(source,
const url = window.URL.createObjectURL(new Blob(
source,
{ type: 'text/xml' }
));

View File

@@ -26,7 +26,9 @@ export function initEdgesFromNodes(nodes) {
// The direction source->target is important since dagre takes
// directionality into account when calculating the layout.
const edgeId = constructEdgeId(source, target);
const edge = makeMap({ id: edgeId, value: 1, source, target });
const edge = makeMap({
id: edgeId, value: 1, source, target
});
edges = edges.set(edgeId, edge);
}
});

View File

@@ -26,10 +26,10 @@ export function getMetricValue(metric) {
return {height: 0, value: null, formattedValue: 'n/a'};
}
const m = metric.toJS();
const value = m.value;
const { value } = m;
let valuePercentage = value === 0 ? 0 : value / m.max;
let max = m.max;
let { max } = m;
if (includes(['load1', 'load5', 'load15'], m.id)) {
valuePercentage = loadScale(value);
max = null;

View File

@@ -22,7 +22,9 @@ function curvedUnitPolygonPath(n) {
export const circleShapeProps = { r: 1 };
export const triangleShapeProps = { d: curvedUnitPolygonPath(3) };
export const squareShapeProps = { width: 1.8, height: 1.8, rx: 0.4, ry: 0.4, x: -0.9, y: -0.9 };
export const squareShapeProps = {
width: 1.8, height: 1.8, rx: 0.4, ry: 0.4, x: -0.9, y: -0.9
};
export const pentagonShapeProps = { d: curvedUnitPolygonPath(5) };
export const hexagonShapeProps = { d: curvedUnitPolygonPath(6) };
export const heptagonShapeProps = { d: curvedUnitPolygonPath(7) };

View File

@@ -72,8 +72,12 @@ function findNodeMatch(nodeMatches, keyPath, text, query, prefix, label, truncat
if (matches) {
const firstMatch = matches[0];
const index = text.search(queryRe);
nodeMatches = nodeMatches.setIn(keyPath,
{text, label, start: index, length: firstMatch.length, truncate});
nodeMatches = nodeMatches.setIn(
keyPath,
{
text, label, start: index, length: firstMatch.length, truncate
}
);
}
}
return nodeMatches;
@@ -110,14 +114,18 @@ function findNodeMatchMetric(nodeMatches, keyPath, fieldValue, fieldLabel, metri
}
}
if (matched) {
nodeMatches = nodeMatches.setIn(keyPath,
{fieldLabel, metric: true});
nodeMatches = nodeMatches.setIn(
keyPath,
{fieldLabel, metric: true}
);
}
}
return nodeMatches;
}
export function searchNode(node, { prefix, query, metric, comp, value }) {
export function searchNode(node, {
prefix, query, metric, comp, value
}) {
let nodeMatches = makeMap();
if (query) {
@@ -125,8 +133,10 @@ export function searchNode(node, { prefix, query, metric, comp, value }) {
SEARCH_FIELDS.forEach((field, label) => {
const keyPath = [label];
if (node.has(field)) {
nodeMatches = findNodeMatch(nodeMatches, keyPath, node.get(field),
query, prefix, label);
nodeMatches = findNodeMatch(
nodeMatches, keyPath, node.get(field),
query, prefix, label
);
}
});
@@ -134,8 +144,10 @@ export function searchNode(node, { prefix, query, metric, comp, value }) {
if (node.get('metadata')) {
node.get('metadata').forEach((field) => {
const keyPath = ['metadata', field.get('id')];
nodeMatches = findNodeMatch(nodeMatches, keyPath, field.get('value'),
query, prefix, field.get('label'), field.get('truncate'));
nodeMatches = findNodeMatch(
nodeMatches, keyPath, field.get('value'),
query, prefix, field.get('label'), field.get('truncate')
);
});
}
@@ -143,8 +155,10 @@ export function searchNode(node, { prefix, query, metric, comp, value }) {
if (node.get('parents')) {
node.get('parents').forEach((parent) => {
const keyPath = ['parents', parent.get('id')];
nodeMatches = findNodeMatch(nodeMatches, keyPath, parent.get('label'),
query, prefix, parent.get('topologyId'));
nodeMatches = findNodeMatch(
nodeMatches, keyPath, parent.get('label'),
query, prefix, parent.get('topologyId')
);
});
}
@@ -153,8 +167,10 @@ export function searchNode(node, { prefix, query, metric, comp, value }) {
(propertyList.get('rows') || []).forEach((row) => {
const entries = row.get('entries');
const keyPath = ['property-lists', row.get('id')];
nodeMatches = findNodeMatch(nodeMatches, keyPath, entries.get('value'),
query, prefix, entries.get('label'));
nodeMatches = findNodeMatch(
nodeMatches, keyPath, entries.get('value'),
query, prefix, entries.get('label')
);
});
});
@@ -173,8 +189,10 @@ export function searchNode(node, { prefix, query, metric, comp, value }) {
if (metrics) {
metrics.forEach((field) => {
const keyPath = ['metrics', field.get('id')];
nodeMatches = findNodeMatchMetric(nodeMatches, keyPath, field.get('value'),
field.get('label'), metric, comp, value);
nodeMatches = findNodeMatchMetric(
nodeMatches, keyPath, field.get('value'),
field.get('label'), metric, comp, value
);
});
}
}
@@ -221,7 +239,7 @@ export function parseQuery(query) {
if (comparisonQuery && comparisonQuery.length === 2) {
const value = parseValue(comparisonQuery[1]);
const metric = comparisonQuery[0].trim();
if (!isNaN(value) && metric) {
if (!window.isNaN(value) && metric) {
comparison = {
metric,
value,
@@ -259,8 +277,7 @@ export function getSearchableFields(nodes) {
// Consider only property lists (and not generic tables).
const tableRowLabels = nodes.reduce((labels, node) => (
labels.union(get(node, 'tables').filter(isPropertyList).flatMap(t => (t.get('rows') || makeList)
.map(f => f.getIn(['entries', 'label']))
))
.map(f => f.getIn(['entries', 'label']))))
), makeSet());
const metricLabels = nodes.reduce((labels, node) => (
@@ -282,8 +299,10 @@ export function getSearchableFields(nodes) {
*/
export function applyPinnedSearches(state) {
// clear old filter state
state = state.update('nodes',
nodes => nodes.map(node => node.set('filtered', false)));
state = state.update(
'nodes',
nodes => nodes.map(node => node.set('filtered', false))
);
const pinnedSearches = state.get('pinnedSearches');
if (pinnedSearches.size > 0) {
@@ -292,10 +311,12 @@ export function applyPinnedSearches(state) {
if (parsed) {
const nodeMatches = searchTopology(state.get('nodes'), parsed);
const filteredNodes = state.get('nodes')
.map(node => node.set('filtered',
.map(node => node.set(
'filtered',
node.get('filtered') // matched by previous pinned search
|| nodeMatches.size === 0 // no match, filter all nodes
|| !nodeMatches.has(node.get('id')))); // filter matches
|| !nodeMatches.has(node.get('id'))
)); // filter matches
state = state.set('nodes', filteredNodes);
}
});

View File

@@ -4,7 +4,9 @@ const applyTranslateY = ({ scaleY = 1, translateY = 0 }, y) => (y * scaleY) + tr
const applyScaleX = ({ scaleX = 1 }, width) => width * scaleX;
const applyScaleY = ({ scaleY = 1 }, height) => height * scaleY;
export const applyTransform = (transform, { width = 0, height = 0, x, y }) => ({
export const applyTransform = (transform, {
width = 0, height = 0, x, y
}) => ({
x: applyTranslateX(transform, x),
y: applyTranslateY(transform, y),
width: applyScaleX(transform, width),
@@ -17,7 +19,9 @@ const inverseTranslateY = ({ scaleY = 1, translateY = 0 }, y) => (y - translateY
const inverseScaleX = ({ scaleX = 1 }, width) => width / scaleX;
const inverseScaleY = ({ scaleY = 1 }, height) => height / scaleY;
export const inverseTransform = (transform, { width = 0, height = 0, x, y }) => ({
export const inverseTransform = (transform, {
width = 0, height = 0, x, y
}) => ({
x: inverseTranslateX(transform, x),
y: inverseTranslateY(transform, y),
width: inverseScaleX(transform, width),
@@ -25,6 +29,8 @@ export const inverseTransform = (transform, { width = 0, height = 0, x, y }) =>
});
export const transformToString = ({ translateX = 0, translateY = 0, scaleX = 1, scaleY = 1 }) => (
export const transformToString = ({
translateX = 0, translateY = 0, scaleX = 1, scaleY = 1
}) => (
`translate(${translateX},${translateY}) scale(${scaleX},${scaleY})`
);

View File

@@ -27,7 +27,7 @@ const csrfToken = (() => {
// Check for token at window level or parent level (for iframe);
/* eslint-disable no-underscore-dangle */
const token = typeof window !== 'undefined'
? window.__WEAVEWORKS_CSRF_TOKEN || parent.__WEAVEWORKS_CSRF_TOKEN
? window.__WEAVEWORKS_CSRF_TOKEN || window.parent.__WEAVEWORKS_CSRF_TOKEN
: null;
/* eslint-enable no-underscore-dangle */
if (!token || token === '$__CSRF_TOKEN_PLACEHOLDER__') {
@@ -110,7 +110,7 @@ function topologiesUrl(state) {
}
export function getWebsocketUrl(host = window.location.host, pathname = window.location.pathname) {
const wsProto = location.protocol === 'https:' ? 'wss' : 'ws';
const wsProto = window.location.protocol === 'https:' ? 'wss' : 'ws';
return `${wsProto}://${host}${process.env.SCOPE_API_PREFIX || ''}${basePath(pathname)}`;
}
@@ -167,8 +167,10 @@ function createWebsocket(websocketUrl, getState, dispatch) {
firstMessageOnWebsocketAt = new Date();
const timeToFirstMessage = firstMessageOnWebsocketAt - createWebsocketAt;
if (timeToFirstMessage > FIRST_RENDER_TOO_LONG_THRESHOLD) {
log('Time (ms) to first nodes render after websocket was created',
firstMessageOnWebsocketAt - createWebsocketAt);
log(
'Time (ms) to first nodes render after websocket was created',
firstMessageOnWebsocketAt - createWebsocketAt
);
}
}
};
@@ -197,12 +199,15 @@ function getNodesForTopologies(state, dispatch, topologyIds, topologyOptions = m
// fetch sequentially
state.get('topologyUrlsById')
.filter((_, topologyId) => topologyIds.contains(topologyId))
.reduce((sequence, topologyUrl, topologyId) => sequence.then(() => {
const optionsQuery = buildUrlQuery(topologyOptions.get(topologyId), state);
return doRequest({ url: `${getApiPath()}${topologyUrl}?${optionsQuery}` });
})
.then(json => dispatch(receiveNodesForTopology(json.nodes, topologyId))),
Promise.resolve());
.reduce(
(sequence, topologyUrl, topologyId) => sequence
.then(() => {
const optionsQuery = buildUrlQuery(topologyOptions.get(topologyId), state);
return doRequest({ url: `${getApiPath()}${topologyUrl}?${optionsQuery}` });
})
.then(json => dispatch(receiveNodesForTopology(json.nodes, topologyId))),
Promise.resolve()
);
}
function getNodesOnce(getState, dispatch) {

View File

@@ -9,9 +9,6 @@
url("../../node_modules/materialize-css/fonts/roboto/Roboto-Regular.ttf");
}
// TODO: Remove this line once Service UI CONFIGURE button stops being added to Scope.
.scope-wrapper .setup-nav-button { display: none; }
a {
text-decoration: none;
}
@@ -109,16 +106,19 @@ a {
position: absolute;
bottom: 11px;
a {
button {
@extend .btn-opacity;
background-color: transparent;
border: 1px solid transparent;
border-radius: 4px;
color: $text-secondary-color;
cursor: pointer;
line-height: 20px;
padding: 1px 3px;
outline: 0;
.fa {
font-size: 150%;
font-size: 125%;
position: relative;
top: 2px;
}
@@ -1016,8 +1016,12 @@ a {
color: $text-tertiary-color;
font-size: 90%;
text-align: right;
cursor: pointer;
padding: 0 4px;
padding: 0;
.node-details-table-header-sortable {
padding: 3px 2px;
cursor: pointer;
}
&-sorted {
color: $text-secondary-color;
@@ -1135,6 +1139,14 @@ a {
}
}
.node-details-table-node-link {
background-color: transparent;
border: 0;
cursor: pointer;
padding: 0;
outline: 0;
}
.node-details-table-node-link,
.node-details-table-node-label,
.node-details-table-node-value {
@@ -2118,6 +2130,22 @@ a {
height: 40px;
}
button {
border: 0;
background-color: transparent;
cursor: pointer;
padding: 0;
outline: 0;
}
button, a {
color: $weave-charcoal-blue;
&:hover {
color: $text-color;
}
}
.fa {
width: 20px;
text-align: center;

View File

@@ -52,7 +52,7 @@
"autoprefixer": "6.7.7",
"babel-cli": "6.24.1",
"babel-core": "6.24.1",
"babel-eslint": "7.2.3",
"babel-eslint": "8.0.1",
"babel-jest": "19.0.0",
"babel-loader": "7.0.0",
"babel-plugin-transform-object-rest-spread": "6.23.0",
@@ -60,12 +60,12 @@
"babel-preset-react": "6.24.1",
"clean-webpack-plugin": "0.1.16",
"css-loader": "0.28.1",
"eslint": "3.19.0",
"eslint-config-airbnb": "14.1.0",
"eslint-loader": "1.7.1",
"eslint-plugin-import": "2.2.0",
"eslint-plugin-jsx-a11y": "4.0.0",
"eslint-plugin-react": "6.10.3",
"eslint": "4.9.0",
"eslint-config-airbnb": "16.1.0",
"eslint-loader": "1.9.0",
"eslint-plugin-import": "2.7.0",
"eslint-plugin-jsx-a11y": "6.0.2",
"eslint-plugin-react": "7.4.0",
"expect": "1.20.2",
"extract-text-webpack-plugin": "2.1.0",
"file-loader": "0.11.1",

View File

@@ -8,12 +8,12 @@ const app = express();
const BACKEND_HOST = process.env.BACKEND_HOST || 'localhost';
/**
*
* Proxy requests to:
* - /api -> :4040/api
*
************************************************************/
/*
*
* Proxy requests to:
* - /api -> :4040/api
*
*/
const backendProxy = httpProxy.createProxy({
ws: true,
@@ -22,24 +22,24 @@ const backendProxy = httpProxy.createProxy({
backendProxy.on('error', err => console.error('Proxy error', err));
app.all('/api*', backendProxy.web.bind(backendProxy));
/**
*
* Production env serves precompiled content from build/
*
************************************************************/
/*
*
* Production env serves precompiled content from build/
*
*/
if (process.env.NODE_ENV === 'production') {
app.use(express.static('build'));
}
/**
*
* Webpack Dev Middleware with Hot Reload
*
* See: https://github.com/webpack/webpack-dev-middleware;
* https://github.com/glenjamin/webpack-hot-middleware
*
*************************************************************/
/*
*
* Webpack Dev Middleware with Hot Reload
*
* See: https://github.com/webpack/webpack-dev-middleware;
* https://github.com/glenjamin/webpack-hot-middleware
*
*/
if (process.env.NODE_ENV !== 'production') {
const webpack = require('webpack');
@@ -60,11 +60,11 @@ if (process.env.NODE_ENV !== 'production') {
}
/**
*
* Express server
*
*****************/
/*
*
* Express server
*
*/
const port = process.env.PORT || 4042;
const server = app.listen(port, 'localhost', () => {
@@ -76,11 +76,11 @@ const server = app.listen(port, 'localhost', () => {
server.on('upgrade', backendProxy.ws.bind(backendProxy));
/**
*
* Path proxy server
*
*************************************************************/
/*
*
* Path proxy server
*
*/
const proxyRules = new HttpProxyRules({
rules: {
@@ -100,8 +100,10 @@ const proxyPathServer = http.createServer((req, res) => {
return pathProxy.web(req, res, {target});
}).listen(pathProxyPort, 'localhost', () => {
const pathProxyHost = proxyPathServer.address().address;
console.log('Scope Proxy Path UI listening at http://%s:%s/scoped/',
pathProxyHost, pathProxyPort);
console.log(
'Scope Proxy Path UI listening at http://%s:%s/scoped/',
pathProxyHost, pathProxyPort
);
});
proxyPathServer.on('upgrade', (req, socket, head) => {

File diff suppressed because it is too large Load Diff