node-details-table-row.js 5.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. import React from 'react';
  2. import classNames from 'classnames';
  3. import { groupBy, mapValues } from 'lodash';
  4. import { intersperse } from '../../utils/array-utils';
  5. import NodeDetailsTableNodeLink from './node-details-table-node-link';
  6. import NodeDetailsTableNodeMetricLink from './node-details-table-node-metric-link';
  7. import { formatDataType } from '../../utils/string-utils';
  8. function getValuesForNode(node) {
  9. let values = {};
  10. ['metrics', 'metadata'].forEach((collection) => {
  11. if (node[collection]) {
  12. node[collection].forEach((field) => {
  13. const result = Object.assign({}, field);
  14. result.valueType = collection;
  15. values[field.id] = result;
  16. });
  17. }
  18. });
  19. if (node.parents) {
  20. const byTopologyId = groupBy(node.parents, parent => parent.topologyId);
  21. const relativesByTopologyId = mapValues(byTopologyId, (relatives, topologyId) => ({
  22. id: topologyId,
  23. label: topologyId,
  24. relatives,
  25. value: relatives.map(relative => relative.label).join(', '),
  26. valueType: 'relatives',
  27. }));
  28. values = {
  29. ...values,
  30. ...relativesByTopologyId,
  31. };
  32. }
  33. return values;
  34. }
  35. function renderValues(node, columns = [], columnStyles = [], timestamp = null, topologyId = null) {
  36. const fields = getValuesForNode(node);
  37. return columns.map(({ id }, i) => {
  38. const field = fields[id];
  39. const style = columnStyles[i];
  40. if (field) {
  41. if (field.valueType === 'metadata') {
  42. const { value, title } = formatDataType(field, timestamp);
  43. return (
  44. <td
  45. className="node-details-table-node-value truncate"
  46. title={title}
  47. style={style}
  48. key={field.id}>
  49. {field.dataType === 'link'
  50. ? (
  51. <a
  52. rel="noopener noreferrer"
  53. target="_blank"
  54. className="node-details-table-node-link"
  55. href={value}>
  56. {value}
  57. </a>
  58. )
  59. : value}
  60. </td>
  61. );
  62. }
  63. if (field.valueType === 'relatives') {
  64. return (
  65. <td
  66. className="node-details-table-node-value truncate"
  67. title={field.value}
  68. style={style}
  69. key={field.id}>
  70. {intersperse(field.relatives.map(relative => (
  71. <NodeDetailsTableNodeLink
  72. key={relative.id}
  73. linkable
  74. nodeId={relative.id}
  75. {...relative}
  76. />
  77. )), ' ')}
  78. </td>
  79. );
  80. }
  81. // valueType === 'metrics'
  82. return (
  83. <NodeDetailsTableNodeMetricLink
  84. style={style}
  85. key={field.id}
  86. topologyId={topologyId}
  87. {...field} />
  88. );
  89. }
  90. // empty cell to complete the row for proper hover
  91. return (
  92. <td className="node-details-table-node-value" style={style} key={id} />
  93. );
  94. });
  95. }
  96. export default class NodeDetailsTableRow extends React.Component {
  97. constructor(props, context) {
  98. super(props, context);
  99. //
  100. // We watch how far the mouse moves when click on a row, move to much and we assume that the
  101. // user is selecting some data in the row. In this case don't trigger the onClick event which
  102. // is most likely a details panel popping open.
  103. //
  104. this.state = { focused: false };
  105. this.mouseDrag = {};
  106. }
  107. onMouseEnter = () => {
  108. this.setState({ focused: true });
  109. if (this.props.onMouseEnter) {
  110. this.props.onMouseEnter(this.props.index, this.props.node);
  111. }
  112. }
  113. onMouseLeave = () => {
  114. this.setState({ focused: false });
  115. if (this.props.onMouseLeave) {
  116. this.props.onMouseLeave();
  117. }
  118. }
  119. onMouseDown = (ev) => {
  120. this.mouseDrag = {
  121. originX: ev.pageX,
  122. originY: ev.pageY,
  123. };
  124. }
  125. onClick = (ev) => {
  126. const thresholdPx = 2;
  127. const { pageX, pageY } = ev;
  128. const { originX, originY } = this.mouseDrag;
  129. const movedTheMouseTooMuch = (
  130. Math.abs(originX - pageX) > thresholdPx
  131. || Math.abs(originY - pageY) > thresholdPx
  132. );
  133. if (movedTheMouseTooMuch && originX && originY) {
  134. return;
  135. }
  136. this.props.onClick(ev, this.props.node);
  137. this.mouseDrag = {};
  138. }
  139. render() {
  140. const {
  141. node, nodeIdKey, topologyId, columns, onClick, colStyles, timestamp
  142. } = this.props;
  143. const [firstColumnStyle, ...columnStyles] = colStyles;
  144. const values = renderValues(node, columns, columnStyles, timestamp, topologyId);
  145. const nodeId = node[nodeIdKey];
  146. const className = classNames('tour-step-anchor node-details-table-node', {
  147. focused: this.state.focused,
  148. selected: this.props.selected,
  149. });
  150. return (
  151. <tr
  152. onClick={onClick && this.onClick}
  153. onMouseDown={onClick && this.onMouseDown}
  154. onMouseEnter={this.onMouseEnter}
  155. onMouseLeave={this.onMouseLeave}
  156. className={className}>
  157. <td className="node-details-table-node-label truncate" style={firstColumnStyle}>
  158. {this.props.renderIdCell(Object.assign(node, { nodeId, topologyId }))}
  159. </td>
  160. {values}
  161. </tr>
  162. );
  163. }
  164. }
  165. NodeDetailsTableRow.defaultProps = {
  166. renderIdCell: props => <NodeDetailsTableNodeLink {...props} />
  167. };