From 858a0a2c249b88b4eb491b5a4f9312dc254f6f5c Mon Sep 17 00:00:00 2001 From: JUDIT GRESKOVITS Date: Thu, 27 Apr 2017 12:49:52 +0100 Subject: [PATCH] SHow dragged node on top and refactor --- frontend/src/state/selectors.js | 13 +- ui/src/components/topology/big-data.js | 2 + .../components/topology/graph-link/arrow.js | 53 ++++ .../topology/graph-link/functions.js | 100 +++++++ .../components/topology/graph-link/index.js | 131 +-------- .../components/topology/graph-link/shapes.js | 2 +- .../components/topology/graph-node/index.js | 2 +- .../components/topology/graph-simulation.js | 33 ++- ui/src/components/topology/story-helper.js | 2 +- ui/src/components/topology/story.js | 7 +- ui/src/components/topology/topology-graph.js | 270 ++++++++++-------- .../topology/{data.js => wp-data.js} | 0 12 files changed, 343 insertions(+), 272 deletions(-) create mode 100644 ui/src/components/topology/big-data.js create mode 100644 ui/src/components/topology/graph-link/arrow.js create mode 100644 ui/src/components/topology/graph-link/functions.js rename ui/src/components/topology/{data.js => wp-data.js} (100%) diff --git a/frontend/src/state/selectors.js b/frontend/src/state/selectors.js index 3b2d0c87..32622655 100644 --- a/frontend/src/state/selectors.js +++ b/frontend/src/state/selectors.js @@ -126,17 +126,8 @@ const metricByInterval = (data = [], { const q1 = statistics.quantile(data, 0.25); const median = statistics.median(data); const q3 = statistics.quantile(data, 0.75); - - const iqr = q3-q1; - const outlierMultiplier = 1.5; - let max = statistics.max(data); - if(max < q3 + iqr*outlierMultiplier) { - max = q3; - } - let min = statistics.min(data); - if(min > q1 - iqr*outlierMultiplier){ - min = q3; - } + const max = statistics.max(data); + const min = statistics.min(data); return { start: sample.start.valueOf(), diff --git a/ui/src/components/topology/big-data.js b/ui/src/components/topology/big-data.js new file mode 100644 index 00000000..a68f696b --- /dev/null +++ b/ui/src/components/topology/big-data.js @@ -0,0 +1,2 @@ +/* eslint-disable */ +module.exports = [{"uuid":"primary-consul","id":"consul","name":"Consul","instances":3,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":"primary-nginx","id":"nginx","name":"Nginx","instances":1,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"connections":[21393,688632,3802,211054,514555,823837,728066,626040,39531,338897,870461,306578,557611,24764,866947,521183,920500,673424,752876,125936,195071,101278,766252,272044,579947,211151,712793,831906,897694,261116,246274,380300,299558,303216,974334,127763,437955,143274,634968,431609,282549,262582,418482,284724,798952,308763,111726],"datacentres":1,"healthy":true},{"uuid":"primary-mongodb","id":"mongodb","name":"MongoDB","instances":3,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":21393,"id":"accountservice","name":"AccountService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"connections":["primary-mongodb"],"datacentres":1,"healthy":true},{"uuid":688632,"id":"addressservice","name":"AddressService","instances":5,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":3802,"id":"bloomreachservice","name":"BloomreachService","instances":5,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":211054,"id":"cartservice","name":"CartService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":514555,"id":"extra service reported by containerpilot: cartservice-https","name":"Extra service reported by ContainerPilot: CartService-HTTPS","instances":5,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":823837,"id":"cctokenizationclientservice","name":"CCTokenizationClientService","instances":3,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"connections":["primary-mongodb"],"datacentres":1,"healthy":true},{"uuid":728066,"id":"checkoutservice","name":"CheckoutService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":626040,"id":"colorswatchservice","name":"ColorSwatchService","instances":5,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":39531,"id":"emailmarketingservice","name":"EmailMarketingService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":338897,"id":"favoriteservice","name":"FavoriteService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":870461,"id":"findinstoreservice","name":"FindInStoreService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"connections":["primary-mongodb"],"datacentres":1,"healthy":true},{"uuid":306578,"id":"fitpredictorservice","name":"FitpredictorService","instances":5,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":557611,"id":"hidefromcatalogservice","name":"HidefromcatalogService","instances":5,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":24764,"id":"internationalcheckoutservice","name":"InternationalCheckoutService","instances":5,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":866947,"id":"internationalposervice","name":"InternationalPOService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":521183,"id":"internationalshippingservice","name":"InternationalShippingService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"connections":["primary-mongodb"],"datacentres":1,"healthy":true},{"uuid":920500,"id":"inventoryservice","name":"InventoryService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":673424,"id":"localizationservice","name":"LocalizationService","instances":3,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":752876,"id":"moreaccountservice","name":"MoreAccountService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":125936,"id":"navigationservice","name":"NavigationService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":195071,"id":"ordercreationjob","name":"OrderCreationJob","instances":3,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"connections":["primary-mongodb"],"datacentres":1,"healthy":true},{"uuid":101278,"id":"orderservice","name":"OrderService","instances":3,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":766252,"id":"orderservice","name":"OrderService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":272044,"id":"paymentmethodservice","name":"PaymentMethodService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":579947,"id":"paymentservice","name":"PaymentService","instances":3,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":211151,"id":"paymentservice","name":"PaymentService","instances":3,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"connections":["primary-mongodb"],"datacentres":1,"healthy":true},{"uuid":712793,"id":"priceservice","name":"PriceService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":831906,"id":"privatesaleservice","name":"PrivatesaleService","instances":3,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":897694,"id":"productarrayservice","name":"ProductArrayService","instances":5,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":261116,"id":"productdetailservice","name":"ProductDetailService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":246274,"id":"productservice","name":"ProductService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"connections":["primary-mongodb"],"datacentres":1,"healthy":true},{"uuid":380300,"id":"profileservice","name":"ProfileService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":299558,"id":"promoservice","name":"PromoService","instances":3,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":303216,"id":"promotionservice","name":"PromotionService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":974334,"id":"questionanswerservice","name":"QuestionanswerService","instances":3,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":127763,"id":"redbaloonservice","name":"RedBaloonService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"connections":["primary-mongodb"],"datacentres":1,"healthy":true},{"uuid":437955,"id":"extra service reported by containerpilot: redbaloonservice-https","name":"Extra service reported by ContainerPilot: RedBaloonService-HTTPS","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":143274,"id":"reviewsservice","name":"ReviewsService","instances":5,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":634968,"id":"searchfacetsservice","name":"SearchFacetsService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":431609,"id":"searchindexservice","name":"SearchIndexService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":282549,"id":"shoprunnerservice","name":"ShopRunnerService","instances":5,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"connections":["primary-mongodb"],"datacentres":1,"healthy":true},{"uuid":262582,"id":"taxservice","name":"TaxService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":418482,"id":"toggleservice","name":"ToggleService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":284724,"id":"useraccountservice","name":"UserAccountService","instances":3,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":798952,"id":"userauthenticationservice","name":"UserAuthenticationService","instances":4,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true},{"uuid":308763,"id":"waitlistoverlayservice","name":"WaitlistOverlayService","instances":5,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"connections":["primary-mongodb"],"datacentres":1,"healthy":true},{"uuid":111726,"id":"waitlistservice","name":"WaitlistService","instances":3,"project":"9fcb374d-a267-4c2a-9d9c-ba469b804639","metrics":[{"name":"CPU","value":"50%"},{"name":"Memory","value":"20%"},{"name":"Network","value":"2.9Kb/sec"}],"datacentres":1,"healthy":true}] diff --git a/ui/src/components/topology/graph-link/arrow.js b/ui/src/components/topology/graph-link/arrow.js new file mode 100644 index 00000000..24e3a951 --- /dev/null +++ b/ui/src/components/topology/graph-link/arrow.js @@ -0,0 +1,53 @@ +import { Baseline } from '../../../shared/composers'; +import React from 'react'; +import { + GraphLinkCircle, + GraphLinkArrowLine +} from './shapes'; + +const GraphLinkArrow = ({ + data, + index +}) => { + + const { + targetPosition, + arrowAngle + } = data; + + return ( + + + + + + ); +}; + +GraphLinkArrow.propTypes = { + data: React.PropTypes.object.isRequired, + index: React.PropTypes.number +}; + +export default Baseline( + GraphLinkArrow +); diff --git a/ui/src/components/topology/graph-link/functions.js b/ui/src/components/topology/graph-link/functions.js new file mode 100644 index 00000000..73c2e3cf --- /dev/null +++ b/ui/src/components/topology/graph-link/functions.js @@ -0,0 +1,100 @@ +import Constants from '../constants'; + +const getAngleFromPoints = (source, target) => { + + const lineAngle = Math.atan2(target.y-source.y, target.x - source.x); + const lineAngleDeg = lineAngle*180/Math.PI; + const zeroToThreeSixty = lineAngleDeg < 0 ? 360 + lineAngleDeg : lineAngleDeg; + + return zeroToThreeSixty; +}; + +const getPosition = (angle, positions, position, noCorners=false) => { + const positionIndex = noCorners ? + Math.round(angle/90)*2 : Math.round(angle/45); + const offsetPosition = positions[positionIndex]; + return { + id: offsetPosition.id, + x: position.x + offsetPosition.x, + y: position.y + offsetPosition.y + }; +}; + +const getPositions = (rect, halfCorner=0) => ([{ + id: 'r', + x: rect.right, + y: 0 +}, { + id: 'br', + x: rect.right - halfCorner, + y: rect.bottom - halfCorner +}, { + id: 'b', + x: 0, + y: rect.bottom +}, { + id: 'bl', + x: rect.left + halfCorner, + y: rect.bottom - halfCorner +}, { + id: 'l', + x: rect.left, + y: 0 +}, { + id: 'tl', + x: rect.left + halfCorner, + y: rect.top + halfCorner +}, { + id: 't', + x: 0, + y: rect.top +}, { + id: 'tr', + x: rect.right- halfCorner, + y: rect.top + halfCorner +},{ + id: 'r', + x: rect.right, + y: 0 +}]); + +const getRect = (data) => { + return data.children ? + Constants.nodeRectWithChildren : + Constants.nodeRect; +}; + +const calculateLineLayout = ({ + source, + target +}) => { + // actually, this will need to be got dynamically, in case them things are different sizes + // yeah right, now you'll get to do exactly that + + const sourceRect = getRect(source); + const targetRect= getRect(target); + + const halfCorner = 2; + + const sourcePositions = getPositions(sourceRect, halfCorner); + const sourceAngle = getAngleFromPoints(source, target); + const sourcePosition = getPosition(sourceAngle, sourcePositions, source); + + const targetPositions = getPositions(targetRect, halfCorner); + const targetAngle = getAngleFromPoints(target, sourcePosition); + const targetPosition = getPosition(targetAngle, targetPositions, target); //, true); + + const arrowAngle = getAngleFromPoints(sourcePosition, targetPosition); + + return { + source, + target, + sourcePosition, + targetPosition, + arrowAngle + }; +}; + +export { + calculateLineLayout +}; diff --git a/ui/src/components/topology/graph-link/index.js b/ui/src/components/topology/graph-link/index.js index 86e4e86a..e8f5702a 100644 --- a/ui/src/components/topology/graph-link/index.js +++ b/ui/src/components/topology/graph-link/index.js @@ -1,143 +1,32 @@ import { Baseline } from '../../../shared/composers'; -import Constants from '../constants'; import React from 'react'; import { - GraphLinkLine, - GraphLinkCircle, - GraphLinkArrow + GraphLinkLine } from './shapes'; -const getAngleFromPoints = (source, target) => { - - const lineAngle = Math.atan2(target.y-source.y, target.x - source.x); - const lineAngleDeg = lineAngle*180/Math.PI; - const zeroToThreeSixty = lineAngleDeg < 0 ? 360 + lineAngleDeg : lineAngleDeg; - - return zeroToThreeSixty; -}; - -const getPosition = (angle, positions, position, noCorners=false) => { - const positionIndex = noCorners ? - Math.round(angle/90)*2 : Math.round(angle/45); - const offsetPosition = positions[positionIndex]; - return { - id: offsetPosition.id, - x: position.x + offsetPosition.x, - y: position.y + offsetPosition.y - }; -}; - -const getPositions = (rect, halfCorner=0) => ([{ - id: 'r', - x: rect.right, - y: 0 -}, { - id: 'br', - x: rect.right - halfCorner, - y: rect.bottom - halfCorner -}, { - id: 'b', - x: 0, - y: rect.bottom -}, { - id: 'bl', - x: rect.left + halfCorner, - y: rect.bottom - halfCorner -}, { - id: 'l', - x: rect.left, - y: 0 -}, { - id: 'tl', - x: rect.left + halfCorner, - y: rect.top + halfCorner -}, { - id: 't', - x: 0, - y: rect.top -}, { - id: 'tr', - x: rect.right- halfCorner, - y: rect.top + halfCorner -},{ - id: 'r', - x: rect.right, - y: 0 -}]); - -const getRect = (data) => { - return data.children ? - Constants.nodeRectWithChildren : - Constants.nodeRect; -}; - const GraphLink = ({ data, index }) => { const { - source, - target + sourcePosition, + targetPosition } = data; - // actually, this will need to be got dynamically, in case them things are different sizes - // yeah right, now you'll get to do exactly that - - const sourceRect = getRect(source); - const targetRect= getRect(target); - - const halfCorner = 2; - - const sourcePositions = getPositions(sourceRect, halfCorner); - const sourceAngle = getAngleFromPoints(source, target); - const sourcePosition = getPosition(sourceAngle, sourcePositions, source); - - const targetPositions = getPositions(targetRect, halfCorner); - const targetAngle = getAngleFromPoints(target, sourcePosition); - const targetPosition = getPosition(targetAngle, targetPositions, target); //, true); - - const arrowAngle = getAngleFromPoints(sourcePosition, targetPosition); - return ( - - - - - - - - + ); }; GraphLink.propTypes = { data: React.PropTypes.object.isRequired, - index: React.PropTypes.number.isRequired + index: React.PropTypes.number }; export default Baseline( diff --git a/ui/src/components/topology/graph-link/shapes.js b/ui/src/components/topology/graph-link/shapes.js index c92f3abb..95960f1f 100644 --- a/ui/src/components/topology/graph-link/shapes.js +++ b/ui/src/components/topology/graph-link/shapes.js @@ -12,7 +12,7 @@ export const GraphLinkCircle = styled.circle` stroke-width: 1.5; `; -export const GraphLinkArrow = styled.line` +export const GraphLinkArrowLine = styled.line` stroke: ${colors.base.white}; stroke-width: 2; stroke-linecap: round; diff --git a/ui/src/components/topology/graph-node/index.js b/ui/src/components/topology/graph-node/index.js index ed2061cd..2c406867 100644 --- a/ui/src/components/topology/graph-node/index.js +++ b/ui/src/components/topology/graph-node/index.js @@ -67,7 +67,7 @@ const GraphNode = ({ const onStart = (evt) => { evt.preventDefault(); - onDragStart(evt, data.id); + onDragStart(evt, data.uuid); }; const nodeRectEvents = connected ? { diff --git a/ui/src/components/topology/graph-simulation.js b/ui/src/components/topology/graph-simulation.js index 2b3957df..bdc8e601 100644 --- a/ui/src/components/topology/graph-simulation.js +++ b/ui/src/components/topology/graph-simulation.js @@ -14,6 +14,16 @@ const rectRadius = (size) => { return Math.round(hypotenuse(width, height)/2); }; +const forcePlayAnimation = (simulation, animationTicks) => { + const n = Math.ceil( + Math.log( + simulation.alphaMin()) / Math.log( + 1 - simulation.alphaDecay())) - animationTicks; + for (var i = 0; i < n; ++i) { + simulation.tick(); + } +}; + const createLinks = (services) => services.reduce((acc, service, index) => service.connections ? @@ -28,13 +38,12 @@ const createLinks = (services) => const createSimulation = ( services, svgSize, - onTick, - onEnd + animationTicks = 0 ) => { // This is not going to work given that as well as the d3 layout stuff, other things might be at play too // We should pass two objects to the components - one for positioning and one for data const nodes = services.map((service, index) => ({ - id: service.uuid, + uuid: service.uuid, index: index })); @@ -47,15 +56,17 @@ const createSimulation = ( const nodeRadius = rectRadius(Constants.nodeSizeWithChildren); + const simulation = forceSimulation(nodes) + .force('link', forceLink(links).id(d => d.uuid)) + .force('collide', forceCollide(nodeRadius)) + .force('center', forceCenter(width/2, height/2)); + + forcePlayAnimation(simulation, animationTicks); + return ({ - simulation: forceSimulation(nodes) - .force('link', forceLink(links).id(d => d.id)) - .force('collide', forceCollide(nodeRadius)) - .force('center', forceCenter(width/2, height/2)) - .on('tick', onTick) - .on('end', onEnd), - nodes: nodes, - links: links + nodes, + links, + simulation }); }; diff --git a/ui/src/components/topology/story-helper.js b/ui/src/components/topology/story-helper.js index b6e4f0b9..04bed77d 100644 --- a/ui/src/components/topology/story-helper.js +++ b/ui/src/components/topology/story-helper.js @@ -4,7 +4,7 @@ import { Baseline } from '../../shared/composers'; import Input from '../form/input'; import Select from '../form/select'; import { TopologyGraph } from './'; -import data from './data'; +import data from './wp-data'; import React from 'react'; const StyledForm = styled.form` diff --git a/ui/src/components/topology/story.js b/ui/src/components/topology/story.js index cf58e4cf..a526e6f0 100644 --- a/ui/src/components/topology/story.js +++ b/ui/src/components/topology/story.js @@ -6,11 +6,16 @@ import README from './readme.md'; import StoryHelper from './story-helper'; import GraphNode from './graph-node'; +import TopologyGraph from './topology-graph'; +import data from './big-data'; storiesOf('Topology', module) -.add('5 services', withReadme(README, () => ( +.add('Wordpress example', withReadme(README, () => ( ))) +.add('Many services example', withReadme(README, () => ( + +))) .add('Consul', withReadme(README, () => ( { - if(service.id !== 'consul') acc.push(service); - return acc; - }, []); + const services = this.getServicesWithoutConsul(); const svgSize = this.getSvgSize(); - const simulationData = createSimulation( - services, - svgSize//, - //() => this.forceUpdate(), - //() => this.forceUpdate() - ); - - const simulation = simulationData.simulation; - - const n = Math.ceil( - Math.log( - simulation.alphaMin()) / Math.log( - 1 - simulation.alphaDecay())); - for (var i = 0; i < n; ++i) { - simulation.tick(); - } - - this.setState(simulationData); - } - - /*componentWillReceiveProps(nextProps) { - // either, we'll have more services - // or, we'll have less services - // or, data of services had changed => - // do shallow check on objects and links, if no change, don't do rerender - // otherwise, redo them bitches = by what I mean to update the simulation - // try freezing exisiting ones... then adding another const { nodes, - links - } = this.state; + links, + simulation + } = createSimulation( + services, + svgSize + ); - const services = nextProps.services.reduce((acc, service, index) => { + this.setState({ + nodes, + links, + simulation + }); + } + + getServicesWithoutConsul() { + + return this.props.services.reduce((acc, service, index) => { if(service.id !== 'consul') acc.push(service); return acc; }, []); - // TODO this here means we'll need to evaluate whether to we have more links! - - // this is tmp for the compare above - if(services !== nodes.length) { - const simulation = this.state.simulation; - const nextSimulationData = updateSimulation( - simulation, - services, - nodes, - links, - svgSize, - () => this.forceUpdate(), - () => this.forceUpdate() - ); - - const nextSimulation = nextSimulationData.simulation; - // console.log('nextSimulationData.nodes = ', nextSimulationData.nodes); - - const n = Math.ceil( - Math.log( - nextSimulation.alphaMin()) / Math.log( - 1 - nextSimulation.alphaDecay())); - for (var i = 0; i < n; ++i) { - nextSimulation.tick(); - } - - //this.state.simulation.nodes().forEach((node, index) => { - // delete node.fx; - // delete node.fy; - //}); - - this.setState(nextSimulationData); - } - }*/ + } getSvgSize() { if(document.getElementById('topology-svg')) { @@ -116,7 +67,7 @@ class TopologyGraph extends React.Component { }; } - constrain(x, y, children=false) { + constrainNodePosition(x, y, children=false) { const svgSize = this.getSvgSize(); const nodeRect = children ? @@ -142,6 +93,40 @@ class TopologyGraph extends React.Component { }; } + findNode(nodeUuid) { + + return this.state.nodes.reduce((acc, simNode, index) => + simNode.uuid === nodeUuid ? simNode : acc, {}); + } + + getConsulNodePosition() { + + const svgSize = this.getSvgSize(); + const x = svgSize.width - Constants.nodeSize.width; + return { + x, + y: 0 + }; + } + + getConstrainedNodePosition(nodeUuid, children=false) { + const node = this.findNode(nodeUuid); + return this.constrainNodePosition(node.x, node.y, children); + } + + findNodeData(nodesData, nodeUuid) { + return nodesData.reduce((acc, nodeData, index) => + nodeData.uuid === nodeUuid ? nodeData : acc, {}); + } + + setDragInfo(dragging, nodeUuid=null, position={}) { + this.dragInfo = { + dragging, + nodeUuid, + position + }; + } + render() { const { @@ -154,55 +139,42 @@ class TopologyGraph extends React.Component { links } = this.state; - const simNode = (nodeId) => - nodes.reduce((acc, simNode, index) => - simNode.id === nodeId ? simNode : acc, {}); - - const svgSize = this.getSvgSize(); const nodesData = services.map((service, index) => { - const sNode = service.id === 'consul' ? { - x: svgSize.width - Constants.nodeSize.width, - y: 0 - } : simNode(service.uuid); - - const constrained = { - ...sNode, - ...this.constrain(sNode.x, sNode.y, service.children) - }; + const nodePosition = service.id === 'consul' ? + this.getConsulNodePosition() : + this.getConstrainedNodePosition(service.uuid, service.children); return ({ ...service, - ...constrained + ...nodePosition }); }); - const nodeData = (nodeId) => - nodesData.reduce((acc, nodeData, index) => - nodeData.id === nodeId ? nodeData : acc, {}); // TODO links will need to know whether a service has children // if it does, the height of it will be different const linksData = links.map((link, index) => ({ - source: nodeData(link.source.id), - target: nodeData(link.target.id) - })); + source: this.findNodeData(nodesData, link.source.uuid), + target: this.findNodeData(nodesData, link.target.uuid) + })).map((linkData, index) => calculateLineLayout(linkData, index )); const onDragStart = (evt, nodeId) => { // it's this node's position that we'll need to update - dragInfo.dragging = true; - dragInfo.nodeId = nodeId; const x = evt.changedTouches ? evt.changedTouches[0].pageX : evt.clientX; const y = evt.changedTouches ? evt.changedTouches[0].pageY : evt.clientY; - dragInfo.position = { - x, - y - }; + this.setDragInfo( + true, + nodeId, + { + x, + y + } + ); }; const onDragMove = (evt) => { - - if ( dragInfo.dragging ) { + if ( this.dragInfo && this.dragInfo.dragging ) { const x = evt.changedTouches ? evt.changedTouches[0].pageX @@ -212,12 +184,12 @@ class TopologyGraph extends React.Component { : evt.clientY; const offset = { - x: x - dragInfo.position.x, - y: y - dragInfo.position.y + x: x - this.dragInfo.position.x, + y: y - this.dragInfo.position.y }; const dragNodes = nodes.map((simNode, index) => { - if ( simNode.id === dragInfo.nodeId ) { + if ( simNode.uuid === this.dragInfo.nodeUuid ) { return ({ ...simNode, x: simNode.x + offset.x, @@ -233,25 +205,25 @@ class TopologyGraph extends React.Component { nodes: dragNodes }); - dragInfo.position = { - x, - y - }; + this.setDragInfo( + true, + this.dragInfo.nodeUuid, + { + x, + y + } + ); } }; const onDragEnd = (evt) => { - dragInfo = { - dragging: false, - nodeId: null, - position: {} - }; + this.setDragInfo(false); }; const onTitleClick = (serviceUUID) => this.props.onNodeTitleClick(serviceUUID); - const renderedNodes = nodesData.map((n, index) => ( + const renderedNode = (n, index) => ( - )); + ); - const renderedLinks = linksData.map((l, index) => ( + const renderedLink = (l, index) => ( - )); + ); + + const renderedLinkArrow = (l, index) => ( + + ); + + const renderedNodes = this.dragInfo && this.dragInfo.dragging ? + nodesData.filter((n, index) => n.uuid !== this.dragInfo.nodeUuid) + .map((n, index) => renderedNode(n, index)) : + nodesData.map((n, index) => renderedNode(n, index)); + + const renderedLinks = linksData.map((l, index) => renderedLink(l, index)); + + const renderedLinkArrows = this.dragInfo && this.dragInfo.dragging ? + linksData.filter((l, index) => l.target.uuid !== this.dragInfo.nodeUuid) + .map((l, index) => renderedLinkArrow(l, index)) : + linksData.map((l, index) => renderedLinkArrow(l, index)); + + const dragNode = !this.dragInfo || !this.dragInfo.dragging ? null : + renderedNode( + nodesData.reduce((dragNode, n, index) => { + if(n.uuid === this.dragInfo.nodeUuid) { + return n; + } + return dragNode; + }, {})); + + const dragLinkArrow = !this.dragInfo || !this.dragInfo.dragging || + renderedLinkArrows.length === renderedLinks.length ? null : + renderedLinkArrow( + linksData.reduce((dragLinkArrow, l, index) => { + if(l.target.uuid === this.dragInfo.nodeUuid) { + return l; + } + return dragLinkArrow; + }, {})); return ( {renderedLinks} + + {renderedLinkArrows} + + + {dragNode} + + + {dragLinkArrow} + ); } diff --git a/ui/src/components/topology/data.js b/ui/src/components/topology/wp-data.js similarity index 100% rename from ui/src/components/topology/data.js rename to ui/src/components/topology/wp-data.js