aboutsummaryrefslogtreecommitdiffstats
path: root/graphgraph-fe/src
diff options
context:
space:
mode:
authorVlastimil Starec <vlastimil.starec@orange.com>2020-04-14 21:49:47 +0200
committerVlastimil Starec <vlastimil.starec@orange.com>2020-04-16 22:52:21 +0200
commit6c4672dbb3cccd6c7d8cdfde2f59a97af7cfac42 (patch)
treef2415c6e0de9c63a7b7ebe41adc62120dc45a9ea /graphgraph-fe/src
parent208ab81cd84c06387d6ce7ad335b3fbe4d51ac04 (diff)
Perform repository cleanup
1. Perform whitespace cleanup in source files Changes are almost exclusively in indentation and line breaks. Some safe spelling fixes may had gotten staged too. 2. Update license file and license headers on java files Change "2019" to "2019-2020" (fix 2017-2018 in some cases). Convert to block comment format. Make "Orange Intellectual Property" consistent across headers. 3. Prepend javascript files with license headers 4. Rename frontend filenames to lowercase 5. Add missing semicolons after statements in react files Issue-ID: AAI-2861 Signed-off-by: Vlastimil Starec <vlastimil.starec@orange.com> Change-Id: Ieee5562ce9e360da3db1f990a6cf4c1b1e56f657
Diffstat (limited to 'graphgraph-fe/src')
-rw-r--r--graphgraph-fe/src/App.css53
-rw-r--r--graphgraph-fe/src/App.js124
-rw-r--r--graphgraph-fe/src/App.test.js9
-rw-r--r--graphgraph-fe/src/DownloadExport.js29
-rw-r--r--graphgraph-fe/src/Graph.js342
-rw-r--r--graphgraph-fe/src/GraphHops.css9
-rw-r--r--graphgraph-fe/src/GraphHops.js70
-rw-r--r--graphgraph-fe/src/GraphInfoMenu.js67
-rw-r--r--graphgraph-fe/src/GraphSettings.js239
-rw-r--r--graphgraph-fe/src/GraphSettingsMenu.js24
-rw-r--r--graphgraph-fe/src/PathBreadCrumb.js26
-rw-r--r--graphgraph-fe/src/PopupSettings.js27
-rw-r--r--graphgraph-fe/src/ValidationModal.js49
-rw-r--r--graphgraph-fe/src/app.css53
-rw-r--r--graphgraph-fe/src/app.js152
-rw-r--r--graphgraph-fe/src/app.test.js29
-rw-r--r--graphgraph-fe/src/constants.js24
-rw-r--r--graphgraph-fe/src/download_export.js43
-rw-r--r--graphgraph-fe/src/graph.css (renamed from graphgraph-fe/src/Graph.css)1
-rw-r--r--graphgraph-fe/src/graph.js372
-rw-r--r--graphgraph-fe/src/graph_hops.css8
-rw-r--r--graphgraph-fe/src/graph_hops.js79
-rw-r--r--graphgraph-fe/src/graph_info_menu.css (renamed from graphgraph-fe/src/GraphInfoMenu.css)7
-rw-r--r--graphgraph-fe/src/graph_info_menu.js81
-rw-r--r--graphgraph-fe/src/graph_settings.css (renamed from graphgraph-fe/src/GraphSettings.css)2
-rw-r--r--graphgraph-fe/src/graph_settings.js256
-rw-r--r--graphgraph-fe/src/graph_settings_menu.css (renamed from graphgraph-fe/src/GraphSettingsMenu.css)7
-rw-r--r--graphgraph-fe/src/graph_settings_menu.js45
-rw-r--r--graphgraph-fe/src/index.css18
-rw-r--r--graphgraph-fe/src/index.js36
-rw-r--r--graphgraph-fe/src/path_breadcrumb.js46
-rw-r--r--graphgraph-fe/src/popup_settings.css (renamed from graphgraph-fe/src/PopupSettings.css)1
-rw-r--r--graphgraph-fe/src/popup_settings.js40
-rw-r--r--graphgraph-fe/src/requests.js52
-rw-r--r--graphgraph-fe/src/serviceWorker.js135
-rw-r--r--graphgraph-fe/src/service_worker.js146
-rw-r--r--graphgraph-fe/src/validation_modal.css (renamed from graphgraph-fe/src/ValidationModal.css)8
-rw-r--r--graphgraph-fe/src/validation_modal.js58
38 files changed, 1514 insertions, 1253 deletions
diff --git a/graphgraph-fe/src/App.css b/graphgraph-fe/src/App.css
deleted file mode 100644
index 74c1327..0000000
--- a/graphgraph-fe/src/App.css
+++ /dev/null
@@ -1,53 +0,0 @@
-.App {
-width: 100%;
-height: 100%;
-position: absolute;
-top: 0;
-left: 0;
-}
-
-.App-logo {
- animation: App-logo-spin infinite 20s linear;
- height: 40vmin;
-}
-
-.App-header {
- background-color: #282c34;
- min-height: 100vh;
- display: flex;
- flex-direction: column;
- align-items: center;
- justify-content: center;
- font-size: calc(10px + 2vmin);
- color: white;
-}
-
-.App-link {
- color: #61dafb;
-}
-
-@keyframes App-logo-spin {
- from {
- transform: rotate(0deg);
- }
- to {
- transform: rotate(360deg);
- }
-}
-
-.root {
-width: 100%;
-height: 100%;
-position: absolute;
-top: 0;
-left: 0;
-}
-
-.graph-area{
- border-style: solid;
- border-color: darkgray;
- border-width: 1px 0 1px 0;
- position: relative;
- width: 100%;
- height: 70%;
-}
diff --git a/graphgraph-fe/src/App.js b/graphgraph-fe/src/App.js
deleted file mode 100644
index 788aca1..0000000
--- a/graphgraph-fe/src/App.js
+++ /dev/null
@@ -1,124 +0,0 @@
-import React from 'react'
-import './App.css'
-import Graph from './Graph'
-import GraphSettingsMenu from './GraphSettingsMenu'
-import GraphInfoMenu from './GraphInfoMenu'
-import _ from 'underscore'
-import { nodeProperty, edgeProperty } from './requests'
-import * as constants from './constants'
-
-var emptyState = {
- selectedSchema: '', // currently selected schema
- graph: {
- nodeNames: [], // names of nodes
- edges: [], // edges (each edge has source, target, type and a list of key value properties for tooltip)
- paths: [] // all paths between start node and end node
- },
- displayedProperties: [], // properties of currently thing (edge or node)
- nodeStates: {}, // possible states CLICKED - the currently selected node PATH - currently displayed path
- pathIndex: 0, // array index to paths i.e. which path from paths is currently displayed
- selectedEdge: { source: 'none', target: 'none' } // defines currently selected edge like like a js object - {source: "pserver", target: "vserver"}
-}
-
-class App extends React.Component {
- constructor (props, context) {
- super(props, context)
- this.graphdata = this.graphdata.bind(this)
- this.changePaths = this.changePaths.bind(this)
- this.loadNodeProperties = this.loadNodeProperties.bind(this)
- this.loadEdgeProperties = this.loadEdgeProperties.bind(this)
- this.computeNodeStatesFromPath = this.computeNodeStatesFromPath.bind(this)
- this.computeNodeStates = this.computeNodeStates.bind(this)
-
- this.state = emptyState
- }
-
- loadNodeProperties (nodeName) {
- var s = this.state
- fetch(nodeProperty(s.selectedSchema, nodeName))
- .then(response => response.json())
- .then(p => {
- s['displayedProperties'] = p
- s['nodeStates'] = this.computeNodeStates(s['pathIndex'])
- // select node
- s['nodeStates'][nodeName] = constants.CLICKED
- // unselect edge
- s['selectedEdge']['source'] = ''
- s['selectedEdge']['target'] = ''
- this.setState(s)
- })
- }
-
- loadEdgeProperties (source, target) {
- var s = this.state
- fetch(edgeProperty(s.selectedSchema, source, target))
- .then(response => response.json())
- .then(p => {
- s['displayedProperties'] = p
- // select edge
- s['selectedEdge']['source'] = source
- s['selectedEdge']['target'] = target
- // unselect node
- s['nodeStates'] = this.computeNodeStates(s['pathIndex'])
- this.setState(s)
- })
- }
-
- graphdata (data, selectedGraphSchema, graphFingerprint) {
- var s = this.state
- s['selectedSchema'] = selectedGraphSchema
- s['graphFingerprint'] = graphFingerprint
- s['graph'] = data
- // TODO this should be handled more gracefully ...
- if (_.isEmpty(data.edges)) {
- alert('The graph has no edges, nothing to display')
- }
- s['displayedProperties'] = data.startNodeProperties
- if (_.isArray(data.paths) && !_.isEmpty(data.paths)) {
- s['paths'] = data.paths
- s['nodeStates'] = this.computeNodeStatesFromPath(data.paths[0])
- s['pathIndex'] = 0
- } else {
- s['paths'] = []
- s['nodeStates'] = {}
- }
- this.setState(s)
- return data
- }
-
- computeNodeStatesFromPath (path) {
- return _.reduce(path, (acc, node) => {
- acc[node.id] = constants.PATH
- return acc
- }, {})
- }
-
- computeNodeStates (pathIndex) {
- return this.computeNodeStatesFromPath(this.state.paths[pathIndex])
- }
-
- changePaths (pathIndex, selectedNode) {
- var s = this.state
- s['pathIndex'] = pathIndex
- this.setState(s)
- this.loadNodeProperties(selectedNode)
- }
-
- render () {
- let n = _.invert(this.state.nodeStates)[constants.CLICKED]
-
- let selectedNode = _.isUndefined(n) ? '' : n
- return (
- <div className="App">
- <GraphSettingsMenu graphData={this.graphdata} nodePropsLoader={this.loadNodeProperties} selectedNode={selectedNode}/>
- <div className='graph-area'>
- <Graph graphFingerprint={this.state.graphFingerprint} edgePropsLoader={this.loadEdgeProperties} selectedEdge={this.state.selectedEdge} nodes={this.state.graph.nodeNames} edges={this.state.graph.edges} nodeStates={this.state.nodeStates} nodePropsLoader={this.loadNodeProperties}/>
- </div>
-
- <GraphInfoMenu pathCallback={this.changePaths} paths={ this.state.graph.paths } nodeProperties={ this.state.displayedProperties} />
- </div>
- )
- }
-}
-
-export default App
diff --git a/graphgraph-fe/src/App.test.js b/graphgraph-fe/src/App.test.js
deleted file mode 100644
index 4bf1935..0000000
--- a/graphgraph-fe/src/App.test.js
+++ /dev/null
@@ -1,9 +0,0 @@
-import React from 'react'
-import ReactDOM from 'react-dom'
-import App from './App'
-
-it('renders without crashing', () => {
- const div = document.createElement('div')
- ReactDOM.render(<App />, div)
- ReactDOM.unmountComponentAtNode(div)
-})
diff --git a/graphgraph-fe/src/DownloadExport.js b/graphgraph-fe/src/DownloadExport.js
deleted file mode 100644
index b031773..0000000
--- a/graphgraph-fe/src/DownloadExport.js
+++ /dev/null
@@ -1,29 +0,0 @@
-import React from 'react'
-import { Button } from 'react-bootstrap'
-import { exportSchema } from './requests'
-
-
-class DownloadExport extends React.Component {
- constructor (props, context) {
- super(props, context)
- this.download = this.download.bind(this)
- }
-
- download() {
-
- setTimeout(() => {
- const response = {
- file: exportSchema(this.props.schemaVersion),
- };
- window.open(response.file);
- }, 100);
- }
-
- render() {
- return (
- <Button onClick={this.download}>Download as XMI</Button>
- );
- }
-}
-
-export default DownloadExport
diff --git a/graphgraph-fe/src/Graph.js b/graphgraph-fe/src/Graph.js
deleted file mode 100644
index c4b1aa0..0000000
--- a/graphgraph-fe/src/Graph.js
+++ /dev/null
@@ -1,342 +0,0 @@
-import React from 'react'
-import * as d3 from 'd3'
-import './Graph.css'
-import _ from 'underscore'
-import * as constants from './constants'
-
-const CLICKED_COLOR = 'blue'
-const PATH_COLOR = 'red'
-const EDGE_MOUSE_OVER_COLOR = 'yellow'
-const EDGE_NORMAL_COLOR = '#ccc'
-const EDGE_LABEL_COLOR = 'black'
-const NODE_NORMAL_COLOR = '#E3E3E3'
-const NODE_LABEL_COLOR = 'black'
-const NODE_MOUSE_OVER_COLOR = '#F6F6F6'
-const NODE_BORDER_COLOR = 'lightgray'
-
-// variable holds state in order to determine if graph should be redrawn completely
-// it breaks the react concept, so a better approach is needed
-var graphFingerprint = ''
-
-var htmlfyProperties = function (properties) {
- return "<div class='d3-tip'>" + (_.reduce(properties, (html, e) => { return html + "<span style='color: lightgray'>" + e.propertyName + ':</span> <span>' + e.propertyValue + '</span><br/>' }, '')) + '</div>'
-}
-
-var mouseOverEdge = function (edge, div) {
- div.transition()
- .duration(20)
- .style('opacity', 0.9)
- div.html(htmlfyProperties(edge.tooltipProperties))
- .style('left', `${d3.event.pageX}px`)
- .style('top', `${d3.event.pageY - 28}px`)
-}
-
-var mouseOutEdge = function (edge, div) {
- div.transition()
- .duration(6000)
- .style('opacity', 0)
-}
-
-var addEdgePaths = function (links, g) {
- d3.select('body').append('div')
- .attr('class', 'tooltip')
- .style('opacity', 0)
- g.selectAll('.edgepath')
- .data(links)
- .enter()
- .append('path')
- .attr('d', d => `M ${d.source.x} ${d.source.y} L ${d.target.x} ${d.target.y}`)
- .attr('class', 'edgepath')
- .attr('fill-opacity', 0)
- .attr('stroke-opacity', 0)
- .attr('fill', EDGE_LABEL_COLOR)
- .attr('id', (d, i) => `edgepath${i}`)
-}
-
-var chooseColor = function (state) {
- if (state === constants.CLICKED) {
- return CLICKED_COLOR
- }
-
- if (state === constants.PATH) {
- return PATH_COLOR
- }
-
- return NODE_NORMAL_COLOR
-}
-
-var redrawNodeColors = function (nodeStates, selectedEdge) {
- d3.selectAll('svg').selectAll('g').selectAll('circle').style('fill', n => chooseColor(nodeStates[n.id]))
- d3.selectAll('svg').selectAll('g').selectAll('line').style('stroke', EDGE_NORMAL_COLOR).attr('oldStroke', EDGE_NORMAL_COLOR)
- d3.selectAll('svg').selectAll('g').selectAll('line').filter(edge => edge.source.id === selectedEdge.source && edge.target.id === selectedEdge.target).attr('oldStroke', CLICKED_COLOR).style('stroke', CLICKED_COLOR)
-}
-
-var addEdgeLabels = function (links, g, div) {
- var edgelabels = g.selectAll('.edgelabel')
- .data(links)
- .enter()
- .append('text')
- .attr('class', 'edgelabel')
- .attr('text-anchor', 'middle')
- .attr('dx', 200)
- .attr('dy', 0)
- .attr('font-size', '22px')
- .attr('id', (d, i) => `edgelabel${i}`)
-
- edgelabels.append('textPath')
- .attr('xlink:href', (d, i) => `#edgepath${i}`)
- .text((d, i) => d.type)
-}
-
-var addNodeLabels = function (nodes, g) {
- g.selectAll('.nodelabel')
- .data(nodes)
- .enter()
- .append('text')
- .attr('x', d => d.x - 14)
- .attr('y', d => d.y - 17)
- .attr('class', 'nodelabel')
- .attr('fill', NODE_LABEL_COLOR)
- .attr('font-size', '32px')
- .text(d => d.id)
- .on('mouseenter', onNodeLabelMouseOver)
- .on('mouseout', onNodeLabelMouseOut)
-}
-
-var addLinks = function (links, g, div, edgePropsLoader) {
- let ss = _.filter(links, l => l.source.id === l.target.id)
-
- let selfLinks = _.isUndefined(ss) ? [] : ss
-
- g.selectAll('ellipse')
- .data(selfLinks)
- .enter().append('ellipse')
- .attr('fill-opacity', 0)
- .attr('rx', d => 100)
- .attr('ry', d => 16)
- .attr('cx', d => d.target.x + 80)
- .attr('cy', d => d.target.y)
- .style('stroke', NODE_BORDER_COLOR)
- .attr('stroke-width', 5)
- .on('click', edge => edgePropsLoader(edge.source.id, edge.target.id))
- .on('mouseenter', function (edge) {
- mouseOverEdge(edge, div)
- d3.select(this)
- .transition()
- .attr('oldStroke', EDGE_NORMAL_COLOR)
- .duration(10)
- .style('stroke', EDGE_MOUSE_OVER_COLOR)
- })
- .on('mouseleave', function (edge) {
- mouseOutEdge(edge, div)
- var strokeColor = d3.select(this).attr('oldStroke')
- d3.select(this)
- .transition()
- .duration(300)
- .style('stroke', strokeColor)
- })
-
- g.selectAll('.edgelooplabel')
- .data(selfLinks)
- .enter()
- .append('text')
- .attr('x', d => d.source.x + 35)
- .attr('y', d => d.source.y + 50)
- .attr('class', 'edgelooplabel')
- .attr('fill', NODE_LABEL_COLOR)
- .attr('font-size', '22px')
- .text(d => d.type)
-
- g.selectAll('line')
- .data(links)
- .enter().append('line')
- .attr('stroke-width', 5)
- .attr('x1', d => d.source.x)
- .attr('y1', d => d.source.y)
- .attr('x2', d => d.target.x)
- .attr('y2', d => d.target.y)
- .attr('id', (d, i) => `edge${i}`)
- .attr('marker-end', 'url(#arrowhead)')
- .style('stroke', EDGE_NORMAL_COLOR)
- .on('click', edge => edgePropsLoader(edge.source.id, edge.target.id))
- .on('mouseenter', function (edge) {
- mouseOverEdge(edge, div)
- d3.select(this)
- .transition()
- .attr('oldStroke', EDGE_NORMAL_COLOR)
- .duration(10)
- .style('stroke', EDGE_MOUSE_OVER_COLOR)
- })
- .on('mouseleave', function (edge) {
- mouseOutEdge(edge, div)
- var strokeColor = d3.select(this).attr('oldStroke')
- d3.select(this)
- .transition()
- .duration(300)
- .style('stroke', strokeColor)
- })
-}
-
-var addMarkers = function (g, svg) {
- g.append('defs').append('marker')
- .attr('id', 'arrowhead')
- .attr('viewBox', '-0 -5 10 10')
- .attr('refX', '20')
- .attr('refY', '0')
- .attr('orient', 'auto')
- .attr('markerWidth', '4')
- .attr('markerHeight', '4')
- .attr('xoverflow', 'visible')
- .append('svg:path')
- .attr('d', 'M 0,-5 L 10 ,0 L 0,5')
- .attr('fill', EDGE_NORMAL_COLOR)
- .attr('stroke', EDGE_NORMAL_COLOR)
-
- var zoomHandler = d3.zoom()
- .on('zoom', _ => g.attr('transform', d3.event.transform))
-
- zoomHandler(svg)
- zoomHandler.translateTo(svg, -7000, -4000)
- zoomHandler.scaleTo(svg, 0.08)
-}
-
-var drawGraph = function (nodes, links, g, simulation, svg, addNodes, edgePropsLoader) {
- for (var i = 0, n = Math.ceil(Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay())); i < n; ++i) {
- simulation.tick()
- }
-
- var div = d3.select('body').append('div')
- .attr('class', 'tooltip')
- .style('opacity', 0)
-
- addLinks(links, g, div, edgePropsLoader)
- addNodes(nodes, g)
- addNodeLabels(nodes, g)
- addEdgePaths(links, g)
- addEdgeLabels(links, g, div)
- addMarkers(g, svg)
-}
-
-var prepareLinks = function (nodes, links) {
- var result = []
- links.forEach(e => {
- var sourceNode = nodes.filter(n => n.id === e.source)[0]
- var targetNode = nodes.filter(n => n.id === e.target)[0]
-
- result.push({
- source: sourceNode,
- target: targetNode,
- type: e.type,
- tooltipProperties: e.tooltipProperties
- })
- })
-
- return result
-}
-
-var createSimulation = function (nodes, links) {
- return d3.forceSimulation(nodes)
- .force('charge', d3.forceManyBody().strength(-201))
- .force('link', d3.forceLink(links).distance(1200).strength(1).iterations(400))
- .force('collision', d3.forceCollide().radius(d => 310))
- .force('x', d3.forceX())
- .force('y', d3.forceY())
- .stop()
-}
-
-var createSvg = function () {
- var svg = d3.select('#graph').append('svg').attr('height', '100%').attr('width', '100%')
- var g = svg.append('g')
-
- return { 'g': g, 'svg': svg }
-}
-
-var onNodeLabelMouseOut = function () {
- d3.select(this)
- .transition()
- .duration(600)
- .attr('font-size', '32px')
-}
-
-var onNodeLabelMouseOver = function () {
- d3.select(this)
- .transition()
- .duration(200)
- .attr('font-size', '232px')
-}
-
-var onNodeMouseOver = function () {
- var oldFill = d3.select(this).style('fill')
- d3.select(this)
- .transition()
- .duration(200)
- .attr('r', 31)
- .attr('oldFill', oldFill)
- .style('fill', NODE_MOUSE_OVER_COLOR)
-}
-
-var onNodeMouseOut = function (nodeStates) {
- var oldFill = d3.select(this).attr('oldFill')
- d3.select(this)
- .transition()
- .duration(300)
- .attr('r', 23)
- .style('fill', oldFill)
-}
-
-class Graph extends React.Component {
- onNodeClick (x) {
- // on mouse out the node will change color read from 'oldFill' attribute
- d3.selectAll('svg').selectAll('g').selectAll('circle').filter(c => c.id === x.id).attr('oldFill', CLICKED_COLOR)
- this.props.nodePropsLoader(x.id)
- }
-
- addNodes (nodes, g) {
- g.selectAll('circle')
- .data(nodes)
- .enter().append('circle')
- .attr('cx', d => d.x)
- .attr('cy', d => d.y)
- .style('fill', n => {
- return chooseColor(this.props.nodeStates[n.id])
- })
- .style('stroke', NODE_BORDER_COLOR)
- .attr('stroke-width', 3)
- .attr('r', 23)
- .on('click', this.onNodeClick)
- .on('mouseover', onNodeMouseOver)
- .on('mouseout', onNodeMouseOut)
- }
-
- reCreateGraph () {
- d3.select('#graph').selectAll('*').remove()
-
- var nodes = this.props.nodes
- var links = prepareLinks(this.props.nodes, this.props.edges)
- var o = createSvg()
- var simulation = createSimulation(nodes, links)
-
- drawGraph(nodes, links, o.g, simulation, o.svg, this.addNodes, this.props.edgePropsLoader)
- }
-
- constructor (props, context) {
- super(props, context)
-
- this.reCreateGraph = this.reCreateGraph.bind(this)
- this.addNodes = this.addNodes.bind(this)
- this.onNodeClick = this.onNodeClick.bind(this)
- }
-
- render () {
- if (this.props.graphFingerprint !== graphFingerprint) {
- this.reCreateGraph()
- graphFingerprint = this.props.graphFingerprint
- } else {
- redrawNodeColors(this.props.nodeStates, this.props.selectedEdge)
- }
-
- return (<div id="graph"/>)
- }
-}
-
-export default Graph
diff --git a/graphgraph-fe/src/GraphHops.css b/graphgraph-fe/src/GraphHops.css
deleted file mode 100644
index b6207eb..0000000
--- a/graphgraph-fe/src/GraphHops.css
+++ /dev/null
@@ -1,9 +0,0 @@
-
-.hops-input{
-display: flex;
-flex-direction: column;
-}
-
-.hops-input-field{
-width: 190px;
-}
diff --git a/graphgraph-fe/src/GraphHops.js b/graphgraph-fe/src/GraphHops.js
deleted file mode 100644
index de7a4cc..0000000
--- a/graphgraph-fe/src/GraphHops.js
+++ /dev/null
@@ -1,70 +0,0 @@
-import React from 'react'
-import { Label } from 'react-bootstrap'
-import NumericInput from 'react-numeric-input'
-import './GraphHops.css'
-
-var createNumInput = function (label, callback, current) {
- return (
- <div>
- <Label>{label}</Label>
- <NumericInput onChange={callback} min={1} max={500} value={current} className="hops-input-field" />
- </div>
- )
-}
-
-class GraphHops extends React.Component {
- constructor (props) {
- super(props)
-
- this.state = {
- value: this.props.defaultValue
- }
- let p = props.parentHops
- let c = props.cousinHops
- let ch = props.childHops
-
- this.onChangeParent = (e) => this._onChangeParent(e)
- this.onChangeCousin = (e) => this._onChangeCousin(e)
- this.onChangeChild = (e) => this._onChangeChild(e)
- this.onChange = (hopsName, num) => this._onChange(hopsName, num)
- this.state = { parentHops: p, childHops: ch, cousinHops: c }
- }
-
- _onChange (hopsName, num) {
- var s = this.state
- s[hopsName] = num
- this.setState(s)
- this.props.updateHops(this.state.parentHops, this.state.cousinHops, this.state.childHops)
- }
-
- _onChangeParent (e) {
- this.onChange('parentHops', e)
- }
-
- _onChangeCousin (e) {
- this.onChange('cousinHops', e)
- }
-
- _onChangeChild (e) {
- this.onChange('childHops', e)
- }
-
- render () {
- if (this.props.edgeFilter === 'Edgerules'){
- return (
- <div className="hops-input">
- {createNumInput('edgerule hops', this.onChangeCousin, this.state.cousinHops)}
- </div>
- )
- }
-
- return (
- <div className="hops-input">
- {createNumInput('parent hops', this.onChangeParent, this.state.parentHops)}
- {createNumInput('child hops', this.onChangeChild, this.state.childHops)}
- </div>
- )
- }
-}
-
-export default GraphHops
diff --git a/graphgraph-fe/src/GraphInfoMenu.js b/graphgraph-fe/src/GraphInfoMenu.js
deleted file mode 100644
index d001d50..0000000
--- a/graphgraph-fe/src/GraphInfoMenu.js
+++ /dev/null
@@ -1,67 +0,0 @@
-import React from 'react'
-import './GraphInfoMenu.css'
-import PathBreadCrumb from './PathBreadCrumb'
-import _ from 'underscore'
-import ReactTable from "react-table";
-import "react-table/react-table.css";
-
-class GraphInfoMenu extends React.Component {
-
- render () {
- var paths = this.props.paths
- var callback = this.props.pathCallback
- var showPaths = _.isArray(paths) && !_.isEmpty(paths)
- var breadcrumbs = _.map(paths, (path, i) => <PathBreadCrumb key={i} index={i} pathCallback={callback} path={path}/>)
- return (
- <div className="node-property-list">
- <div className="fixed-height-container" style={{ display: showPaths ? 'block' : 'none' }}>
- <p className='path-heading'>Paths</p>
- {breadcrumbs}
- </div>
- <div className="kv-table datatable">
- <ReactTable pageSizeOptions={[4, 25]} data={this.props.nodeProperties}
- columns={[
- {
- Header: "Attribute",
- accessor: "propertyName",
- minWidth: 40
- },
- {
- Header: "Type",
- accessor: "type",
- minWidth: 70
- },
- {
- Header: "Description",
- accessor: "description",
- minWidth: 260
- },
- {
- id: "Key",
- Header: "Key",
- accessor: p => p.key ? "yes" : "",
- minWidth: 20
- },
- {
- id: "Index",
- Header: "Index",
- accessor: p => p.index ? "yes" : "",
- minWidth: 20
- },
- {
- id: "Required",
- Header: "Required",
- accessor: p => p.required ? "yes" : "",
- minWidth: 20
- }
- ]}
- defaultPageSize={4}
- className="-striped -highlight"
- />
- </div>
- </div>
- )
- }
-}
-
-export default GraphInfoMenu
diff --git a/graphgraph-fe/src/GraphSettings.js b/graphgraph-fe/src/GraphSettings.js
deleted file mode 100644
index d511068..0000000
--- a/graphgraph-fe/src/GraphSettings.js
+++ /dev/null
@@ -1,239 +0,0 @@
-import React from 'react'
-import _ from 'underscore'
-import { DropdownButton, MenuItem, Label } from 'react-bootstrap'
-import './GraphSettings.css'
-import Popup from './PopupSettings'
-import ValidationModal from './ValidationModal'
-import DownloadExport from './DownloadExport'
-import { validateSchema, pathGraph, basicGraph, schemas, nodeNames } from './requests'
-
-var emptyState = {
- schemaProblems: [],
- nodeNames: [],
- fromNode: '',
- graph: {
- nodeNames: [],
- edges: []
- },
- showHops: false,
- enableDestinationNode: false,
- toNode: '',
- edgeFilter: 'Edgerules',
- hops: {
- parents: 1,
- cousin: 1,
- child: 1
- },
- selectedSchema: ''
-}
-
-class GraphSettings extends React.Component {
- constructor (props, context) {
- super(props, context)
- this.onChangeStartNode = this.onChangeStartNode.bind(this)
- this.onSelectNode = this.onSelectNode.bind(this)
- this.selectSchema = this.selectSchema.bind(this)
- this.onChangeToNode = this.onChangeToNode.bind(this)
- this.loadInitialGraph = this.loadInitialGraph.bind(this)
- this.updateHops = this.updateHops.bind(this)
- this.changeEdgeFilter = this.changeEdgeFilter.bind(this)
- this.graphFingerprint = this.graphFingerprint.bind(this)
- this.state = emptyState
- }
- // this serves as a config 'fingerprint' to know if the d3 visualisation should be redrawn from scratch or just updated
- graphFingerprint (schema, from, to, parents, cousin, child, edgeFilter) {
- return `${schema}:${from}:${to}:${parents}:${cousin}:${child}:${edgeFilter}`
- }
-
- loadInitialGraph (startNode, endNode, parentHops, cousinHops, childHops, edgeFilter) {
- if (this.state.selectedSchema === '' || startNode === 'none') {
- var s = this.state
- s['edgeFilter'] = edgeFilter
- this.setState(s)
- return
- }
- if (startNode === 'all') {
- endNode = 'none'
- }
-
- let requestUri = endNode === 'none'
- ? basicGraph(this.state.selectedSchema, startNode, parentHops, cousinHops, childHops, edgeFilter) : pathGraph(this.state.selectedSchema, startNode, endNode, edgeFilter)
-
- fetch(requestUri)
- .then(response => response.json())
- .then(g => {
- let schema = this.state.selectedSchema
-
- let f = this.graphFingerprint(schema, startNode, endNode, parentHops, cousinHops, childHops, edgeFilter)
- this.props.graphData(g, this.state.selectedSchema, f)
- return g
- })
- .then(g => {
- var s = this.state
- s['hops']['parents'] = parentHops
- s['hops']['cousin'] = cousinHops
- s['hops']['child'] = childHops
- s['fromNode'] = startNode
- s['toNode'] = endNode
- s['graph'] = g
- s['edgeFilter'] = edgeFilter
- s['showHops'] = endNode === 'none' && startNode !== 'none' && startNode !== 'all'
- s['enableDestinationNode'] = startNode !== 'none' && startNode !== 'all'
- this.setState(s)
-
- if (startNode !== 'all') {
- this.onSelectNode(startNode)
- }
- })
- }
-
- selectSchema (schema) {
- var s = this.state
- s['selectedSchema'] = schema
- fetch(nodeNames(schema, s['edgeFilter']))
- .then(response => response.json())
- .then(nodeNames => {
- s['fromNode'] = s['toNode'] = 'none'
- s['nodeNames'] = nodeNames
- this.setState(s)
- })
- fetch(validateSchema(schema))
- .then(response => response.json())
- .then(p => {
- s['schemaProblems'] = p.problems
- this.setState(s)
- })
- }
-
- changeEdgeFilter (edgeFilter) {
- fetch(nodeNames(this.state.selectedSchema, edgeFilter))
- .then(response => response.json())
- .then(nodeNames => {
- let s = this.state
- s['edgeFilter'] = edgeFilter
- s['fromNode'] = s['toNode'] = 'none'
- s['nodeNames'] = nodeNames
- this.setState(s)
- })
- this.loadInitialGraph(
- this.state.fromNode,
- this.state.toNode,
- this.state.hops.parents,
- this.state.hops.cousin,
- this.state.hops.child,
- edgeFilter
- )
- }
-
- updateHops (parentHops, cousinHops, childHops) {
- this.loadInitialGraph(
- this.state.fromNode,
- this.state.toNode,
- parentHops,
- cousinHops,
- childHops,
- this.state.edgeFilter)
- }
-
- onChangeToNode (eventKey) {
- this.loadInitialGraph(this.state.fromNode,
- eventKey,
- this.state.hops.parents,
- this.state.hops.cousin,
- this.state.hops.child,
- this.state.edgeFilter)
- }
-
- onSelectNode (eventKey) {
- this.props.nodePropsLoader(eventKey)
- }
-
- onChangeStartNode (eventKey) {
- this.loadInitialGraph(eventKey, this.state.toNode,
- this.state.hops.parents,
- this.state.hops.cousin,
- this.state.hops.child,
- this.state.edgeFilter)
- }
-
- componentDidMount () {
- fetch(schemas())
- .then(response => response.json())
- .then(schemas => {
- let s = this.state
- s['schemas'] = schemas
- this.setState(s)
- })
- }
-
- render () {
- var schemas = _.map(this.state.schemas, (x, k) => <MenuItem key={k} eventKey={x}>{x}</MenuItem>)
-
- var items = _.map(this.state.nodeNames, (x, k) => <MenuItem key={k} eventKey={x.id}>{x.id}</MenuItem>)
- let sortedNames = _.sortBy(this.state.graph.nodeNames, 'id')
- var currentNodeNames = _.map(sortedNames, (x, k) => <MenuItem key={k} eventKey={x.id}>{x.id}</MenuItem>)
-
- var fromItems = items.slice()
- fromItems.unshift(<MenuItem key='divider' divider />)
- fromItems.unshift(<MenuItem key='all' eventKey='all'>all</MenuItem>)
-
- items.unshift(<MenuItem key='anotherdivider' divider />)
- items.unshift(<MenuItem key='none' eventKey='none'>none</MenuItem>)
-
- let edgeFilterItems = [
- <MenuItem key='Edgerules' eventKey='Edgerules'>Edgerules</MenuItem>,
- <MenuItem key='Parents' eventKey='Parents'>Parent-child (OXM structure)</MenuItem>,
- ]
- return (
- <div>
- <div className="graph-menu">
- <div className="startendnode-dropdown">
- <div>
- <Label>Schemas</Label>
- <DropdownButton className="schemas-dropdown" onSelect={this.selectSchema} id="schemas" title={this.state.selectedSchema}>
- {schemas}
- </DropdownButton>
- </div>
- <div className="source-dropdown-div">
- <Label>Source Node</Label>
- <DropdownButton className="node-dropdown" onSelect={this.onChangeStartNode} id="namesFrom" title={this.state.fromNode}>
- {fromItems}
- </DropdownButton>
- </div>
- <div>
- <Label>Destination Node</Label>
- <DropdownButton disabled={!this.state.enableDestinationNode} className="node-dropdown" onSelect={this.onChangeToNode} id="namesTo" title={this.state.toNode}>
- {items}
- </DropdownButton>
- </div>
- <div className="source-dropdown-div">
- <Label>Edge filter</Label>
- <DropdownButton className="node-dropdown" onSelect={this.changeEdgeFilter} id="filterEdge" title={this.state.edgeFilter}>
- {edgeFilterItems}
- </DropdownButton>
- </div>
- <div className="source-dropdown-div">
- <Label>Selected Node</Label>
- <DropdownButton className="node-dropdown" onSelect={this.onSelectNode} id="selectedNode" title={this.props.selectedNode}>
- {currentNodeNames}
- </DropdownButton>
- </div>
-
- <Popup isDisabled={!this.state.showHops} edgeFilter={this.state.edgeFilter} parentHops={this.state.hops.parents} childHops={this.state.hops.child} cousinHops={this.state.hops.cousin} updateHops={this.updateHops}/>
- <div className="modal-button">
- <ValidationModal schemaProblems={this.state.schemaProblems}/>
- </div>
-
- <div className="modal-button">
- <DownloadExport schemaVersion={this.state.selectedSchema}/>
- </div>
-
- </div>
-
- </div>
- </div>
- )
- }
-}
-
-export default GraphSettings
diff --git a/graphgraph-fe/src/GraphSettingsMenu.js b/graphgraph-fe/src/GraphSettingsMenu.js
deleted file mode 100644
index 5ce1e12..0000000
--- a/graphgraph-fe/src/GraphSettingsMenu.js
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from 'react'
-import GraphSettings from './GraphSettings'
-import { Navbar, Nav } from 'react-bootstrap'
-import './GraphSettingsMenu.css'
-
-class GraphSettingsMenu extends React.Component {
- render () {
- return (
- <Navbar className='navbar-adjust'>
- <Navbar.Header>
- <Navbar.Brand>
- <a href="https://gerrit.onap.org/r/gitweb?p=aai/graphgraph.git">GraphGraph</a>
- </Navbar.Brand>
- </Navbar.Header>
- <Nav className="mr-auto">
- <Navbar.Collapse className='mr-sm-2'>
- <GraphSettings selectedNode={this.props.selectedNode} graphData={this.props.graphData} nodePropsLoader={this.props.nodePropsLoader} />
- </Navbar.Collapse>
-</Nav>
- </Navbar>)
- }
-}
-
-export default GraphSettingsMenu
diff --git a/graphgraph-fe/src/PathBreadCrumb.js b/graphgraph-fe/src/PathBreadCrumb.js
deleted file mode 100644
index a2c508a..0000000
--- a/graphgraph-fe/src/PathBreadCrumb.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import _ from 'underscore'
-import React from 'react'
-import { Breadcrumb } from 'react-bootstrap'
-
-class PathBreadCrumb extends React.Component {
- constructor (props, context) {
- super(props, context)
- this.pathSelected = this.pathSelected.bind(this)
- }
-
- pathSelected (evt) {
- evt.preventDefault()
- // the data is only piggyback riding on the "target" property .. not nice but works
- this.props.pathCallback(this.props.index, evt.target.getAttribute('target'))
- }
-
- render () {
- var path = this.props.path
- var callback = this.pathSelected
- var items = _.map(path, (item, i) => <Breadcrumb.Item key={i} target={item.id} onClick={callback}> {item.id} </Breadcrumb.Item>)
-
- return (<Breadcrumb>{items}</Breadcrumb>)
- }
-}
-
-export default PathBreadCrumb
diff --git a/graphgraph-fe/src/PopupSettings.js b/graphgraph-fe/src/PopupSettings.js
deleted file mode 100644
index cb8a533..0000000
--- a/graphgraph-fe/src/PopupSettings.js
+++ /dev/null
@@ -1,27 +0,0 @@
-import React from 'react'
-import Popup from 'reactjs-popup'
-import './PopupSettings.css'
-import GraphHops from './GraphHops'
-
-class PopupMenu extends React.Component {
- render () {
- return (
- <Popup trigger={<button className='settings-button' disabled={this.props.isDisabled}>Hops</button>} position="bottom right">
- {close => (
- <div>
- <GraphHops edgeFilter={this.props.edgeFilter} parentHops={this.props.parentHops} childHops={this.props.childHops} cousinHops={this.props.cousinHops} updateHops={this.props.updateHops} />
- <button
- type="button"
- className="link-button, close"
- onClick={close}>
- &times;
- </button>
- </div>
- )}
- </Popup>
-
- )
- }
-}
-
-export default PopupMenu
diff --git a/graphgraph-fe/src/ValidationModal.js b/graphgraph-fe/src/ValidationModal.js
deleted file mode 100644
index 8bf1989..0000000
--- a/graphgraph-fe/src/ValidationModal.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import _ from 'underscore'
-import React from 'react'
-import './ValidationModal.css'
-import { Button, Modal, ListGroup, ListGroupItem } from 'react-bootstrap'
-
-
-class ValidationModal extends React.Component {
- constructor(...args) {
- super(...args);
- this.state = { showModal: false };
-
- this.close = () => {
- this.setState({ showModal: false });
- };
-
- this.open = () => {
- this.setState({ showModal: true });
- };
- }
-
- renderBackdrop(props) {
- return <div {...props} className="modal-backdrop" />;
- }
-
- render() {
- var problems = this.props.schemaProblems
- var items = _.map(problems, (problem, i) => <ListGroupItem key={i}> {problem} </ListGroupItem>)
- return (
- <div>
- <Button onClick={this.open}>Validate schema</Button>
- <Modal
- onHide={this.close}
- className="modal-validator"
- aria-labelledby="modal-label"
- show={this.state.showModal}
- renderBackdrop={this.renderBackdrop}
- >
- <div className="modal-list">
- <ListGroup>
- {items}
- </ListGroup>
- </div>
- </Modal>
- </div>
- );
- }
-}
-
-export default ValidationModal
diff --git a/graphgraph-fe/src/app.css b/graphgraph-fe/src/app.css
new file mode 100644
index 0000000..8c39ae0
--- /dev/null
+++ b/graphgraph-fe/src/app.css
@@ -0,0 +1,53 @@
+.App {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.App-logo {
+ animation: App-logo-spin infinite 20s linear;
+ height: 40vmin;
+}
+
+.App-header {
+ background-color: #282c34;
+ min-height: 100vh;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ font-size: calc(10px + 2vmin);
+ color: white;
+}
+
+.App-link {
+ color: #61dafb;
+}
+
+@keyframes App-logo-spin {
+ from {
+ transform: rotate(0deg);
+ }
+ to {
+ transform: rotate(360deg);
+ }
+}
+
+.root {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.graph-area{
+ border-style: solid;
+ border-color: darkgray;
+ border-width: 1px 0 1px 0;
+ position: relative;
+ width: 100%;
+ height: 70%;
+}
diff --git a/graphgraph-fe/src/app.js b/graphgraph-fe/src/app.js
new file mode 100644
index 0000000..30eb3d8
--- /dev/null
+++ b/graphgraph-fe/src/app.js
@@ -0,0 +1,152 @@
+/*
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2019-2020 Orange Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import React from 'react';
+import './app.css';
+import Graph from './graph.js';
+import GraphSettingsMenu from './graph_settings_menu.js';
+import GraphInfoMenu from './graph_info_menu.js';
+import _ from 'underscore';
+import { nodeProperty, edgeProperty } from './requests.js';
+import * as constants from './constants.js';
+
+var emptyState = {
+ // currently selected schema
+ selectedSchema: '',
+ graph: {
+ // names of nodes
+ nodeNames: [],
+ // edges (each edge has source, target, type and a list of key value properties for tooltip)
+ edges: [],
+ // all paths between start node and end node
+ paths: []
+ },
+ // properties of currently thing (edge or node)
+ displayedProperties: [],
+ // possible states:
+ // CLICKED - the currently selected node
+ // PATH - currently displayed path
+ nodeStates: {},
+ // array index to paths i.e. which path from paths is currently displayed
+ pathIndex: 0,
+ // defines currently selected edge like a js object - {source: "pserver", target: "vserver"}
+ selectedEdge: { source: 'none', target: 'none' }
+};
+
+class App extends React.Component {
+ constructor (props, context) {
+ super(props, context);
+ this.graphdata = this.graphdata.bind(this);
+ this.changePaths = this.changePaths.bind(this);
+ this.loadNodeProperties = this.loadNodeProperties.bind(this);
+ this.loadEdgeProperties = this.loadEdgeProperties.bind(this);
+ this.computeNodeStatesFromPath = this.computeNodeStatesFromPath.bind(this);
+ this.computeNodeStates = this.computeNodeStates.bind(this);
+ this.state = emptyState;
+ }
+
+ loadNodeProperties (nodeName) {
+ var s = this.state;
+ fetch(nodeProperty(s.selectedSchema, nodeName))
+ .then(response => response.json())
+ .then(p => {
+ s['displayedProperties'] = p;
+ s['nodeStates'] = this.computeNodeStates(s['pathIndex']);
+ // select node
+ s['nodeStates'][nodeName] = constants.CLICKED;
+ // unselect edge
+ s['selectedEdge']['source'] = '';
+ s['selectedEdge']['target'] = '';
+ this.setState(s);
+ });
+ }
+
+ loadEdgeProperties (source, target) {
+ var s = this.state;
+ fetch(edgeProperty(s.selectedSchema, source, target))
+ .then(response => response.json())
+ .then(p => {
+ s['displayedProperties'] = p;
+ // select edge
+ s['selectedEdge']['source'] = source;
+ s['selectedEdge']['target'] = target;
+ // unselect node
+ s['nodeStates'] = this.computeNodeStates(s['pathIndex']);
+ this.setState(s);
+ });
+ }
+
+ graphdata (data, selectedGraphSchema, graphFingerprint) {
+ var s = this.state;
+ s['selectedSchema'] = selectedGraphSchema;
+ s['graphFingerprint'] = graphFingerprint;
+ s['graph'] = data;
+ // TODO this should be handled more gracefully ...
+ if (_.isEmpty(data.edges)) {
+ alert('The graph has no edges, nothing to display');
+ }
+ s['displayedProperties'] = data.startNodeProperties;
+ if (_.isArray(data.paths) && !_.isEmpty(data.paths)) {
+ s['paths'] = data.paths;
+ s['nodeStates'] = this.computeNodeStatesFromPath(data.paths[0]);
+ s['pathIndex'] = 0;
+ } else {
+ s['paths'] = [];
+ s['nodeStates'] = {};
+ }
+ this.setState(s);
+ return data;
+ }
+
+ computeNodeStatesFromPath (path) {
+ return _.reduce(path, (acc, node) => {
+ acc[node.id] = constants.PATH;
+ return acc;
+ }, {});
+ }
+
+ computeNodeStates (pathIndex) {
+ return this.computeNodeStatesFromPath(this.state.paths[pathIndex]);
+ }
+
+ changePaths (pathIndex, selectedNode) {
+ var s = this.state;
+ s['pathIndex'] = pathIndex;
+ this.setState(s);
+ this.loadNodeProperties(selectedNode);
+ }
+
+ render () {
+ let n = _.invert(this.state.nodeStates)[constants.CLICKED];
+
+ let selectedNode = _.isUndefined(n) ? '' : n;
+ return (
+ <div className="App">
+ <GraphSettingsMenu graphData={this.graphdata} nodePropsLoader={this.loadNodeProperties} selectedNode={selectedNode}/>
+ <div className='graph-area'>
+ <Graph graphFingerprint={this.state.graphFingerprint} edgePropsLoader={this.loadEdgeProperties} selectedEdge={this.state.selectedEdge} nodes={this.state.graph.nodeNames} edges={this.state.graph.edges} nodeStates={this.state.nodeStates} nodePropsLoader={this.loadNodeProperties}/>
+ </div>
+ <GraphInfoMenu pathCallback={this.changePaths} paths={ this.state.graph.paths } nodeProperties={ this.state.displayedProperties}/>
+ </div>
+ );
+ }
+}
+
+export default App;
diff --git a/graphgraph-fe/src/app.test.js b/graphgraph-fe/src/app.test.js
new file mode 100644
index 0000000..893bcbb
--- /dev/null
+++ b/graphgraph-fe/src/app.test.js
@@ -0,0 +1,29 @@
+/*
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2019-2020 Orange Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import React from 'react';
+import ReactDOM from 'react-dom';
+import App from './app.js';
+
+it('renders without crashing', () => {
+ const div = document.createElement('div');
+ ReactDOM.render(<App/>, div);
+ ReactDOM.unmountComponentAtNode(div);
+});
diff --git a/graphgraph-fe/src/constants.js b/graphgraph-fe/src/constants.js
index 6076e6f..9181850 100644
--- a/graphgraph-fe/src/constants.js
+++ b/graphgraph-fe/src/constants.js
@@ -1,2 +1,22 @@
-export const CLICKED = 'clicked'
-export const PATH = 'on-path'
+/*
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2019-2020 Orange Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+export const CLICKED = 'clicked';
+export const PATH = 'on-path';
diff --git a/graphgraph-fe/src/download_export.js b/graphgraph-fe/src/download_export.js
new file mode 100644
index 0000000..21b2829
--- /dev/null
+++ b/graphgraph-fe/src/download_export.js
@@ -0,0 +1,43 @@
+/*
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2019-2020 Orange Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import React from 'react';
+import { Button } from 'react-bootstrap';
+import { exportSchema } from './requests.js';
+
+class DownloadExport extends React.Component {
+ constructor (props, context) {
+ super(props, context);
+ this.download = this.download.bind(this);
+ }
+
+ download() {
+ setTimeout(() => {
+ const response = { file: exportSchema(this.props.schemaVersion) };
+ window.open(response.file);
+ }, 100);
+ }
+
+ render() {
+ return <Button onClick={this.download}>Download as XMI</Button>;
+ }
+}
+
+export default DownloadExport;
diff --git a/graphgraph-fe/src/Graph.css b/graphgraph-fe/src/graph.css
index 1e5c7d8..0c9460f 100644
--- a/graphgraph-fe/src/Graph.css
+++ b/graphgraph-fe/src/graph.css
@@ -17,4 +17,3 @@
color: #fff;
border-radius: 2px;
}
-
diff --git a/graphgraph-fe/src/graph.js b/graphgraph-fe/src/graph.js
new file mode 100644
index 0000000..492959a
--- /dev/null
+++ b/graphgraph-fe/src/graph.js
@@ -0,0 +1,372 @@
+/*
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2019-2020 Orange Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import React from 'react';
+import * as d3 from 'd3';
+import './graph.css';
+import _ from 'underscore';
+import * as constants from './constants.js';
+
+const CLICKED_COLOR = 'blue';
+const PATH_COLOR = 'red';
+const EDGE_MOUSE_OVER_COLOR = 'yellow';
+const EDGE_NORMAL_COLOR = '#ccc';
+const EDGE_LABEL_COLOR = 'black';
+const NODE_NORMAL_COLOR = '#E3E3E3';
+const NODE_LABEL_COLOR = 'black';
+const NODE_MOUSE_OVER_COLOR = '#F6F6F6';
+const NODE_BORDER_COLOR = 'lightgray';
+
+// variable holds state in order to determine if graph should be redrawn completely
+// it breaks the react concept, so a better approach is needed
+var graphFingerprint = '';
+
+var htmlfyProperties = function (properties) {
+ return "<div class='d3-tip'>" + (_.reduce(properties, (html, e) => {
+ return html + "<span style='color: lightgray'>" +
+ e.propertyName + ':</span> <span>' +
+ e.propertyValue + '</span><br/>';
+ }, '')) + '</div>';
+};
+
+var mouseOverEdge = function (edge, div) {
+ div.transition()
+ .duration(20)
+ .style('opacity', 0.9);
+ div.html(htmlfyProperties(edge.tooltipProperties))
+ .style('left', `${d3.event.pageX}px`)
+ .style('top', `${d3.event.pageY - 28}px`);
+};
+
+var mouseOutEdge = function (edge, div) {
+ div.transition()
+ .duration(6000)
+ .style('opacity', 0);
+};
+
+var addEdgePaths = function (links, g) {
+ d3.select('body').append('div')
+ .attr('class', 'tooltip')
+ .style('opacity', 0);
+ g.selectAll('.edgepath')
+ .data(links)
+ .enter()
+ .append('path')
+ .attr('d', d => `M ${d.source.x} ${d.source.y} L ${d.target.x} ${d.target.y}`)
+ .attr('class', 'edgepath')
+ .attr('fill-opacity', 0)
+ .attr('stroke-opacity', 0)
+ .attr('fill', EDGE_LABEL_COLOR)
+ .attr('id', (d, i) => `edgepath${i}`);
+};
+
+var chooseColor = function (state) {
+ if (state === constants.CLICKED) {
+ return CLICKED_COLOR;
+ }
+ if (state === constants.PATH) {
+ return PATH_COLOR;
+ }
+ return NODE_NORMAL_COLOR;
+};
+
+var redrawNodeColors = function (nodeStates, selectedEdge) {
+ d3.selectAll('svg').selectAll('g').selectAll('circle')
+ .style('fill', n => chooseColor(nodeStates[n.id]));
+ d3.selectAll('svg').selectAll('g').selectAll('line')
+ .style('stroke', EDGE_NORMAL_COLOR).attr('oldStroke', EDGE_NORMAL_COLOR);
+ d3.selectAll('svg').selectAll('g').selectAll('line')
+ .filter(edge => edge.source.id === selectedEdge.source && edge.target.id === selectedEdge.target)
+ .attr('oldStroke', CLICKED_COLOR)
+ .style('stroke', CLICKED_COLOR);
+};
+
+var addEdgeLabels = function (links, g, div) {
+ var edgelabels = g.selectAll('.edgelabel')
+ .data(links)
+ .enter()
+ .append('text')
+ .attr('class', 'edgelabel')
+ .attr('text-anchor', 'middle')
+ .attr('dx', 200)
+ .attr('dy', 0)
+ .attr('font-size', '22px')
+ .attr('id', (d, i) => `edgelabel${i}`);
+
+ edgelabels.append('textPath')
+ .attr('xlink:href', (d, i) => `#edgepath${i}`)
+ .text((d, i) => d.type);
+};
+
+var addNodeLabels = function (nodes, g) {
+ g.selectAll('.nodelabel')
+ .data(nodes)
+ .enter()
+ .append('text')
+ .attr('x', d => d.x - 14)
+ .attr('y', d => d.y - 17)
+ .attr('class', 'nodelabel')
+ .attr('fill', NODE_LABEL_COLOR)
+ .attr('font-size', '32px')
+ .text(d => d.id)
+ .on('mouseenter', onNodeLabelMouseOver)
+ .on('mouseout', onNodeLabelMouseOut);
+};
+
+var addLinks = function (links, g, div, edgePropsLoader) {
+ let ss = _.filter(links, l => l.source.id === l.target.id);
+
+ let selfLinks = _.isUndefined(ss) ? [] : ss;
+
+ g.selectAll('ellipse')
+ .data(selfLinks)
+ .enter().append('ellipse')
+ .attr('fill-opacity', 0)
+ .attr('rx', d => 100)
+ .attr('ry', d => 16)
+ .attr('cx', d => d.target.x + 80)
+ .attr('cy', d => d.target.y)
+ .style('stroke', NODE_BORDER_COLOR)
+ .attr('stroke-width', 5)
+ .on('click', edge => edgePropsLoader(edge.source.id, edge.target.id))
+ .on('mouseenter', function (edge) {
+ mouseOverEdge(edge, div);
+ d3.select(this)
+ .transition()
+ .attr('oldStroke', EDGE_NORMAL_COLOR)
+ .duration(10)
+ .style('stroke', EDGE_MOUSE_OVER_COLOR);
+ })
+ .on('mouseleave', function (edge) {
+ mouseOutEdge(edge, div);
+ var strokeColor = d3.select(this).attr('oldStroke');
+ d3.select(this)
+ .transition()
+ .duration(300)
+ .style('stroke', strokeColor);
+ });
+
+ g.selectAll('.edgelooplabel')
+ .data(selfLinks)
+ .enter()
+ .append('text')
+ .attr('x', d => d.source.x + 35)
+ .attr('y', d => d.source.y + 50)
+ .attr('class', 'edgelooplabel')
+ .attr('fill', NODE_LABEL_COLOR)
+ .attr('font-size', '22px')
+ .text(d => d.type);
+
+ g.selectAll('line')
+ .data(links)
+ .enter().append('line')
+ .attr('stroke-width', 5)
+ .attr('x1', d => d.source.x)
+ .attr('y1', d => d.source.y)
+ .attr('x2', d => d.target.x)
+ .attr('y2', d => d.target.y)
+ .attr('id', (d, i) => `edge${i}`)
+ .attr('marker-end', 'url(#arrowhead)')
+ .style('stroke', EDGE_NORMAL_COLOR)
+ .on('click', edge => edgePropsLoader(edge.source.id, edge.target.id))
+ .on('mouseenter', function (edge) {
+ mouseOverEdge(edge, div);
+ d3.select(this)
+ .transition()
+ .attr('oldStroke', EDGE_NORMAL_COLOR)
+ .duration(10)
+ .style('stroke', EDGE_MOUSE_OVER_COLOR);
+ })
+ .on('mouseleave', function (edge) {
+ mouseOutEdge(edge, div);
+ var strokeColor = d3.select(this).attr('oldStroke');
+ d3.select(this)
+ .transition()
+ .duration(300)
+ .style('stroke', strokeColor);
+ });
+};
+
+var addMarkers = function (g, svg) {
+ g.append('defs').append('marker')
+ .attr('id', 'arrowhead')
+ .attr('viewBox', '-0 -5 10 10')
+ .attr('refX', '20')
+ .attr('refY', '0')
+ .attr('orient', 'auto')
+ .attr('markerWidth', '4')
+ .attr('markerHeight', '4')
+ .attr('xoverflow', 'visible')
+ .append('svg:path')
+ .attr('d', 'M 0,-5 L 10 ,0 L 0,5')
+ .attr('fill', EDGE_NORMAL_COLOR)
+ .attr('stroke', EDGE_NORMAL_COLOR);
+
+ var zoomHandler = d3.zoom()
+ .on('zoom', _ => g.attr('transform', d3.event.transform));
+
+ zoomHandler(svg);
+ zoomHandler.translateTo(svg, -7000, -4000);
+ zoomHandler.scaleTo(svg, 0.08);
+};
+
+var drawGraph = function (nodes, links, g, simulation, svg, addNodes, edgePropsLoader) {
+ for (var i = 0, n = Math.ceil(Math.log(simulation.alphaMin()) / Math.log(1 - simulation.alphaDecay()));
+ i < n; ++i) {
+ simulation.tick();
+ }
+
+ var div = d3.select('body').append('div')
+ .attr('class', 'tooltip')
+ .style('opacity', 0);
+
+ addLinks(links, g, div, edgePropsLoader);
+ addNodes(nodes, g);
+ addNodeLabels(nodes, g);
+ addEdgePaths(links, g);
+ addEdgeLabels(links, g, div);
+ addMarkers(g, svg);
+};
+
+var prepareLinks = function (nodes, links) {
+ var result = [];
+ links.forEach(e => {
+ var sourceNode = nodes.filter(n => n.id === e.source)[0];
+ var targetNode = nodes.filter(n => n.id === e.target)[0];
+
+ result.push({
+ source: sourceNode,
+ target: targetNode,
+ type: e.type,
+ tooltipProperties: e.tooltipProperties
+ });
+ });
+
+ return result;
+};
+
+var createSimulation = function (nodes, links) {
+ return d3.forceSimulation(nodes)
+ .force('charge', d3.forceManyBody().strength(-201))
+ .force('link', d3.forceLink(links).distance(1200).strength(1).iterations(400))
+ .force('collision', d3.forceCollide().radius(d => 310))
+ .force('x', d3.forceX())
+ .force('y', d3.forceY())
+ .stop();
+};
+
+var createSvg = function () {
+ var svg = d3.select('#graph').append('svg').attr('height', '100%').attr('width', '100%');
+ var g = svg.append('g');
+
+ return { 'g': g, 'svg': svg };
+};
+
+var onNodeLabelMouseOut = function () {
+ d3.select(this)
+ .transition()
+ .duration(600)
+ .attr('font-size', '32px');
+};
+
+var onNodeLabelMouseOver = function () {
+ d3.select(this)
+ .transition()
+ .duration(200)
+ .attr('font-size', '232px');
+};
+
+var onNodeMouseOver = function () {
+ var oldFill = d3.select(this).style('fill');
+ d3.select(this)
+ .transition()
+ .duration(200)
+ .attr('r', 31)
+ .attr('oldFill', oldFill)
+ .style('fill', NODE_MOUSE_OVER_COLOR);
+};
+
+var onNodeMouseOut = function (nodeStates) {
+ var oldFill = d3.select(this).attr('oldFill');
+ d3.select(this)
+ .transition()
+ .duration(300)
+ .attr('r', 23)
+ .style('fill', oldFill);
+};
+
+class Graph extends React.Component {
+ onNodeClick (x) {
+ // on mouse out the node will change color read from 'oldFill' attribute
+ d3.selectAll('svg').selectAll('g').selectAll('circle')
+ .filter(c => c.id === x.id)
+ .attr('oldFill', CLICKED_COLOR);
+ this.props.nodePropsLoader(x.id);
+ }
+
+ addNodes (nodes, g) {
+ g.selectAll('circle')
+ .data(nodes)
+ .enter().append('circle')
+ .attr('cx', d => d.x)
+ .attr('cy', d => d.y)
+ .style('fill', n => {
+ return chooseColor(this.props.nodeStates[n.id]);
+ })
+ .style('stroke', NODE_BORDER_COLOR)
+ .attr('stroke-width', 3)
+ .attr('r', 23)
+ .on('click', this.onNodeClick)
+ .on('mouseover', onNodeMouseOver)
+ .on('mouseout', onNodeMouseOut);
+ }
+
+ reCreateGraph () {
+ d3.select('#graph').selectAll('*').remove();
+
+ var nodes = this.props.nodes;
+ var links = prepareLinks(this.props.nodes, this.props.edges);
+ var o = createSvg();
+ var simulation = createSimulation(nodes, links);
+
+ drawGraph(nodes, links, o.g, simulation, o.svg, this.addNodes, this.props.edgePropsLoader);
+ }
+
+ constructor (props, context) {
+ super(props, context);
+
+ this.reCreateGraph = this.reCreateGraph.bind(this);
+ this.addNodes = this.addNodes.bind(this);
+ this.onNodeClick = this.onNodeClick.bind(this);
+ }
+
+ render () {
+ if (this.props.graphFingerprint !== graphFingerprint) {
+ this.reCreateGraph();
+ graphFingerprint = this.props.graphFingerprint;
+ } else {
+ redrawNodeColors(this.props.nodeStates, this.props.selectedEdge);
+ }
+
+ return <div id="graph"/>;
+ }
+}
+
+export default Graph;
diff --git a/graphgraph-fe/src/graph_hops.css b/graphgraph-fe/src/graph_hops.css
new file mode 100644
index 0000000..474b661
--- /dev/null
+++ b/graphgraph-fe/src/graph_hops.css
@@ -0,0 +1,8 @@
+.hops-input{
+ display: flex;
+ flex-direction: column;
+}
+
+.hops-input-field{
+ width: 190px;
+}
diff --git a/graphgraph-fe/src/graph_hops.js b/graphgraph-fe/src/graph_hops.js
new file mode 100644
index 0000000..da98db2
--- /dev/null
+++ b/graphgraph-fe/src/graph_hops.js
@@ -0,0 +1,79 @@
+/*
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2019-2020 Orange Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import React from 'react';
+import { Label } from 'react-bootstrap';
+import NumericInput from 'react-numeric-input';
+import './graph_hops.css';
+
+var createNumInput = function (label, callback, current) {
+ return (
+ <div>
+ <Label>{label}</Label>
+ <NumericInput onChange={callback} min={1} max={500} value={current} className="hops-input-field"/>
+ </div>
+ );
+};
+
+class GraphHops extends React.Component {
+ constructor (props) {
+ super(props);
+
+ this.state = { value: this.props.defaultValue };
+ let p = props.parentHops;
+ let c = props.cousinHops;
+ let ch = props.childHops;
+
+ this.onChangeParent = (e) => this._onChangeParent(e);
+ this.onChangeCousin = (e) => this._onChangeCousin(e);
+ this.onChangeChild = (e) => this._onChangeChild(e);
+ this.onChange = (hopsName, num) => this._onChange(hopsName, num);
+ this.state = { parentHops: p, childHops: ch, cousinHops: c };
+ }
+
+ _onChange (hopsName, num) {
+ var s = this.state;
+ s[hopsName] = num;
+ this.setState(s);
+ this.props.updateHops(this.state.parentHops, this.state.cousinHops, this.state.childHops);
+ }
+
+ _onChangeParent (e) {
+ this.onChange('parentHops', e);
+ }
+
+ _onChangeCousin (e) {
+ this.onChange('cousinHops', e);
+ }
+
+ _onChangeChild (e) {
+ this.onChange('childHops', e);
+ }
+
+ render () {
+ if (this.props.edgeFilter === 'Edgerules') {
+ return <div className="hops-input">{createNumInput('edgerule hops', this.onChangeCousin, this.state.cousinHops)}</div>;
+ }
+
+ return <div className="hops-input">{createNumInput('parent hops', this.onChangeParent, this.state.parentHops)} {createNumInput('child hops', this.onChangeChild, this.state.childHops)}</div>;
+ }
+}
+
+export default GraphHops;
diff --git a/graphgraph-fe/src/GraphInfoMenu.css b/graphgraph-fe/src/graph_info_menu.css
index 24a2719..6182210 100644
--- a/graphgraph-fe/src/GraphInfoMenu.css
+++ b/graphgraph-fe/src/graph_info_menu.css
@@ -6,15 +6,15 @@
}
.pagination {
- margin: 0 12px 0 20px !important;
+ margin: 0 12px 0 20px !important;
}
.fixed-height-container {
overflow: scroll;
float:top;
height: 200px;
- width:40%;
- padding:3px;
+ width:40%;
+ padding:3px;
background:white;
}
@@ -70,4 +70,3 @@
margin-right: 0;
margin-left: 0;
}
-
diff --git a/graphgraph-fe/src/graph_info_menu.js b/graphgraph-fe/src/graph_info_menu.js
new file mode 100644
index 0000000..66ffbba
--- /dev/null
+++ b/graphgraph-fe/src/graph_info_menu.js
@@ -0,0 +1,81 @@
+/*
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2019-2020 Orange Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import React from 'react';
+import './graph_info_menu.css';
+import PathBreadcrumb from './path_breadcrumb.js';
+import _ from 'underscore';
+import ReactTable from "react-table";
+import "react-table/react-table.css";
+
+class GraphInfoMenu extends React.Component {
+ render () {
+ var paths = this.props.paths;
+ var callback = this.props.pathCallback;
+ var showPaths = _.isArray(paths) && !_.isEmpty(paths);
+ var breadcrumbs = _.map(
+ paths, (path, i) => <PathBreadcrumb key={i} index={i} pathCallback={callback} path={path}/>);
+ return (
+ <div className="node-property-list">
+ <div className="fixed-height-container" style={{ display: showPaths ? 'block' : 'none' }}><p className='path-heading'>Paths</p>{breadcrumbs}</div>
+ <div className="kv-table datatable">
+ <ReactTable pageSizeOptions={[4, 25]} data={this.props.nodeProperties} columns={[
+ {
+ Header: "Attribute",
+ accessor: "propertyName",
+ minWidth: 40
+ },
+ {
+ Header: "Type",
+ accessor: "type",
+ minWidth: 70
+ },
+ {
+ Header: "Description",
+ accessor: "description",
+ minWidth: 260
+ },
+ {
+ id: "Key",
+ Header: "Key",
+ accessor: p => p.key ? "yes" : "",
+ minWidth: 20
+ },
+ {
+ id: "Index",
+ Header: "Index",
+ accessor: p => p.index ? "yes" : "",
+ minWidth: 20
+ },
+ {
+ id: "Required",
+ Header: "Required",
+ accessor: p => p.required ? "yes" : "",
+ minWidth: 20
+ }
+ ]} defaultPageSize={4} className="-striped -highlight"
+ />
+ </div>
+ </div>
+ );
+ }
+}
+
+export default GraphInfoMenu;
diff --git a/graphgraph-fe/src/GraphSettings.css b/graphgraph-fe/src/graph_settings.css
index 6fb4550..c30cda9 100644
--- a/graphgraph-fe/src/GraphSettings.css
+++ b/graphgraph-fe/src/graph_settings.css
@@ -29,7 +29,7 @@
}
-.modal-button
+.modal-button
{
padding-top: 20px;
margin: 0;
diff --git a/graphgraph-fe/src/graph_settings.js b/graphgraph-fe/src/graph_settings.js
new file mode 100644
index 0000000..c8f03f1
--- /dev/null
+++ b/graphgraph-fe/src/graph_settings.js
@@ -0,0 +1,256 @@
+/*
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2019-2020 Orange Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import React from 'react';
+import _ from 'underscore';
+import { DropdownButton, MenuItem, Label } from 'react-bootstrap';
+import './graph_settings.css';
+import Popup from './popup_settings.js';
+import ValidationModal from './validation_modal.js';
+import DownloadExport from './download_export.js';
+import { validateSchema, pathGraph, basicGraph, schemas, nodeNames } from './requests.js';
+
+var emptyState = {
+ schemaProblems: [],
+ nodeNames: [],
+ fromNode: '',
+ graph: {
+ nodeNames: [],
+ edges: []
+ },
+ showHops: false,
+ enableDestinationNode: false,
+ toNode: '',
+ edgeFilter: 'Edgerules',
+ hops: {
+ parents: 1,
+ cousin: 1,
+ child: 1
+ },
+ selectedSchema: ''
+};
+
+class GraphSettings extends React.Component {
+ constructor (props, context) {
+ super(props, context);
+ this.onChangeStartNode = this.onChangeStartNode.bind(this);
+ this.onSelectNode = this.onSelectNode.bind(this);
+ this.selectSchema = this.selectSchema.bind(this);
+ this.onChangeToNode = this.onChangeToNode.bind(this);
+ this.loadInitialGraph = this.loadInitialGraph.bind(this);
+ this.updateHops = this.updateHops.bind(this);
+ this.changeEdgeFilter = this.changeEdgeFilter.bind(this);
+ this.graphFingerprint = this.graphFingerprint.bind(this);
+ this.state = emptyState;
+ }
+
+ /* this serves as a config 'fingerprint' to know if the d3 visualisation
+ should be redrawn from scratch or just updated */
+ graphFingerprint (schema, from, to, parents, cousin, child, edgeFilter) {
+ return `${schema}:${from}:${to}:${parents}:${cousin}:${child}:${edgeFilter}`;
+ }
+
+ loadInitialGraph (startNode, endNode, parentHops, cousinHops, childHops, edgeFilter) {
+ if (this.state.selectedSchema === '' || startNode === 'none') {
+ var s = this.state;
+ s['edgeFilter'] = edgeFilter;
+ this.setState(s);
+ return;
+ }
+ if (startNode === 'all') {
+ endNode = 'none';
+ }
+
+ let requestUri = endNode === 'none'
+ ? basicGraph(
+ this.state.selectedSchema, startNode, parentHops, cousinHops, childHops, edgeFilter)
+ : pathGraph(
+ this.state.selectedSchema, startNode, endNode, edgeFilter);
+
+ fetch(requestUri)
+ .then(response => response.json())
+ .then(g => {
+ let schema = this.state.selectedSchema;
+ let f = this.graphFingerprint(
+ schema, startNode, endNode, parentHops, cousinHops, childHops, edgeFilter);
+ this.props.graphData(g, this.state.selectedSchema, f);
+ return g;
+ })
+ .then(g => {
+ var s = this.state;
+ s['hops']['parents'] = parentHops;
+ s['hops']['cousin'] = cousinHops;
+ s['hops']['child'] = childHops;
+ s['fromNode'] = startNode;
+ s['toNode'] = endNode;
+ s['graph'] = g;
+ s['edgeFilter'] = edgeFilter;
+ s['showHops'] = endNode === 'none' && startNode !== 'none' && startNode !== 'all';
+ s['enableDestinationNode'] = startNode !== 'none' && startNode !== 'all';
+ this.setState(s);
+
+ if (startNode !== 'all') {
+ this.onSelectNode(startNode);
+ }
+ });
+ }
+
+ selectSchema (schema) {
+ var s = this.state;
+ s['selectedSchema'] = schema;
+ fetch(nodeNames(schema, s['edgeFilter']))
+ .then(response => response.json())
+ .then(nodeNames => {
+ s['fromNode'] = s['toNode'] = 'none';
+ s['nodeNames'] = nodeNames;
+ this.setState(s);
+ });
+ fetch(validateSchema(schema))
+ .then(response => response.json())
+ .then(p => {
+ s['schemaProblems'] = p.problems;
+ this.setState(s);
+ });
+ }
+
+ changeEdgeFilter (edgeFilter) {
+ fetch(nodeNames(this.state.selectedSchema, edgeFilter))
+ .then(response => response.json())
+ .then(nodeNames => {
+ let s = this.state;
+ s['edgeFilter'] = edgeFilter;
+ s['fromNode'] = s['toNode'] = 'none';
+ s['nodeNames'] = nodeNames;
+ this.setState(s);
+ });
+ this.loadInitialGraph(
+ this.state.fromNode,
+ this.state.toNode,
+ this.state.hops.parents,
+ this.state.hops.cousin,
+ this.state.hops.child,
+ edgeFilter
+ );
+ }
+
+ updateHops (parentHops, cousinHops, childHops) {
+ this.loadInitialGraph(
+ this.state.fromNode,
+ this.state.toNode,
+ parentHops,
+ cousinHops,
+ childHops,
+ this.state.edgeFilter
+ );
+ }
+
+ onChangeToNode (eventKey) {
+ this.loadInitialGraph(
+ this.state.fromNode,
+ eventKey,
+ this.state.hops.parents,
+ this.state.hops.cousin,
+ this.state.hops.child,
+ this.state.edgeFilter
+ );
+ }
+
+ onSelectNode (eventKey) {
+ this.props.nodePropsLoader(eventKey);
+ }
+
+ onChangeStartNode (eventKey) {
+ this.loadInitialGraph(
+ eventKey,
+ this.state.toNode,
+ this.state.hops.parents,
+ this.state.hops.cousin,
+ this.state.hops.child,
+ this.state.edgeFilter
+ );
+ }
+
+ componentDidMount () {
+ fetch(schemas())
+ .then(response => response.json())
+ .then(schemas => {
+ let s = this.state;
+ s['schemas'] = schemas;
+ this.setState(s);
+ });
+ }
+
+ render () {
+ var schemas = _.map(this.state.schemas, (x, k) => <MenuItem key={k} eventKey={x}>{x}</MenuItem>);
+
+ var items = _.map(this.state.nodeNames, (x, k) => <MenuItem key={k} eventKey={x.id}>{x.id}</MenuItem>);
+ let sortedNames = _.sortBy(this.state.graph.nodeNames, 'id');
+ var currentNodeNames = _.map(sortedNames, (x, k) => <MenuItem key={k} eventKey={x.id}>{x.id}</MenuItem>);
+
+ var fromItems = items.slice();
+ fromItems.unshift(<MenuItem key='divider' divider/>);
+ fromItems.unshift(<MenuItem key='all' eventKey='all'>all</MenuItem>);
+
+ items.unshift(<MenuItem key='anotherdivider' divider/>);
+ items.unshift(<MenuItem key='none' eventKey='none'>none</MenuItem>);
+
+ let edgeFilterItems = [
+ <MenuItem key='Edgerules' eventKey='Edgerules'>Edgerules</MenuItem>,
+ <MenuItem key='Parents' eventKey='Parents'>Parent-child (OXM structure)</MenuItem>,
+ ];
+ return (
+ <div>
+ <div className="graph-menu">
+ <div className="startendnode-dropdown">
+ <div>
+ <Label>Schemas</Label>
+ <DropdownButton className="schemas-dropdown" onSelect={this.selectSchema} id="schemas" title={this.state.selectedSchema}>{schemas}</DropdownButton>
+ </div>
+ <div className="source-dropdown-div">
+ <Label>Source Node</Label>
+ <DropdownButton className="node-dropdown" onSelect={this.onChangeStartNode} id="namesFrom" title={this.state.fromNode}>{fromItems}</DropdownButton>
+ </div>
+ <div>
+ <Label>Destination Node</Label>
+ <DropdownButton disabled={!this.state.enableDestinationNode} className="node-dropdown" onSelect={this.onChangeToNode} id="namesTo" title={this.state.toNode}>{items}</DropdownButton>
+ </div>
+ <div className="source-dropdown-div">
+ <Label>Edge filter</Label>
+ <DropdownButton className="node-dropdown" onSelect={this.changeEdgeFilter} id="filterEdge" title={this.state.edgeFilter}>{edgeFilterItems}</DropdownButton>
+ </div>
+ <div className="source-dropdown-div">
+ <Label>Selected Node</Label>
+ <DropdownButton className="node-dropdown" onSelect={this.onSelectNode} id="selectedNode" title={this.props.selectedNode}>{currentNodeNames}</DropdownButton>
+ </div>
+ <Popup isDisabled={!this.state.showHops} edgeFilter={this.state.edgeFilter} parentHops={this.state.hops.parents} childHops={this.state.hops.child} cousinHops={this.state.hops.cousin} updateHops={this.updateHops}/>
+ <div className="modal-button">
+ <ValidationModal schemaProblems={this.state.schemaProblems}/>
+ </div>
+ <div className="modal-button">
+ <DownloadExport schemaVersion={this.state.selectedSchema}/>
+ </div>
+ </div>
+ </div>
+ </div>
+ );
+ }
+}
+
+export default GraphSettings;
diff --git a/graphgraph-fe/src/GraphSettingsMenu.css b/graphgraph-fe/src/graph_settings_menu.css
index 466d07d..718dfa0 100644
--- a/graphgraph-fe/src/GraphSettingsMenu.css
+++ b/graphgraph-fe/src/graph_settings_menu.css
@@ -1,12 +1,11 @@
-
.navbar.navbar-adjust{
-margin-bottom: 0px;
+ margin-bottom: 0px;
}
.navbar-adjust .container {
-margin-left: 0;
+ margin-left: 0;
}
.navbar-adjust .container .navbar-header {
-margin-right: 250px;
+ margin-right: 250px;
}
diff --git a/graphgraph-fe/src/graph_settings_menu.js b/graphgraph-fe/src/graph_settings_menu.js
new file mode 100644
index 0000000..05def48
--- /dev/null
+++ b/graphgraph-fe/src/graph_settings_menu.js
@@ -0,0 +1,45 @@
+/*
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2019-2020 Orange Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import React from 'react';
+import GraphSettings from './graph_settings.js';
+import { Navbar, Nav } from 'react-bootstrap';
+import './graph_settings_menu.css';
+
+class GraphSettingsMenu extends React.Component {
+ render () {
+ return (
+ <Navbar className='navbar-adjust'>
+ <Navbar.Header>
+ <Navbar.Brand>
+ <a href="https://gerrit.onap.org/r/gitweb?p=aai/graphgraph.git">GraphGraph</a>
+ </Navbar.Brand>
+ </Navbar.Header>
+ <Nav className="mr-auto">
+ <Navbar.Collapse className='mr-sm-2'>
+ <GraphSettings selectedNode={this.props.selectedNode} graphData={this.props.graphData} nodePropsLoader={this.props.nodePropsLoader}/>
+ </Navbar.Collapse>
+ </Nav>
+ </Navbar>
+ );
+ }
+}
+
+export default GraphSettingsMenu;
diff --git a/graphgraph-fe/src/index.css b/graphgraph-fe/src/index.css
index cee5f34..cf5eae8 100644
--- a/graphgraph-fe/src/index.css
+++ b/graphgraph-fe/src/index.css
@@ -1,14 +1,14 @@
body {
- margin: 0;
- padding: 0;
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
- "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
- sans-serif;
- -webkit-font-smoothing: antialiased;
- -moz-osx-font-smoothing: grayscale;
+ margin: 0;
+ padding: 0;
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
+ "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
+ sans-serif;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
}
code {
- font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
- monospace;
+ font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
+ monospace;
}
diff --git a/graphgraph-fe/src/index.js b/graphgraph-fe/src/index.js
index bdd3da2..4269967 100644
--- a/graphgraph-fe/src/index.js
+++ b/graphgraph-fe/src/index.js
@@ -1,13 +1,33 @@
-import React from 'react'
-import ReactDOM from 'react-dom'
-import './index.css'
-import App from './App'
-import * as serviceWorker from './serviceWorker'
-import 'bootstrap-css-only/css/bootstrap.css'
+/*
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2019-2020 Orange Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
-ReactDOM.render(<App />, document.getElementById('root'))
+import React from 'react';
+import ReactDOM from 'react-dom';
+import './index.css';
+import App from './app.js';
+import * as serviceWorker from './service_worker.js';
+import 'bootstrap-css-only/css/bootstrap.css';
+
+ReactDOM.render(<App/>, document.getElementById('root'));
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: http://bit.ly/CRA-PWA
-serviceWorker.unregister()
+serviceWorker.unregister();
diff --git a/graphgraph-fe/src/path_breadcrumb.js b/graphgraph-fe/src/path_breadcrumb.js
new file mode 100644
index 0000000..6ed9d16
--- /dev/null
+++ b/graphgraph-fe/src/path_breadcrumb.js
@@ -0,0 +1,46 @@
+/*
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2019-2020 Orange Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import _ from 'underscore';
+import React from 'react';
+import { Breadcrumb } from 'react-bootstrap';
+
+class PathBreadcrumb extends React.Component {
+ constructor (props, context) {
+ super(props, context);
+ this.pathSelected = this.pathSelected.bind(this);
+ }
+
+ pathSelected (evt) {
+ evt.preventDefault();
+ // the data is only piggyback riding on the "target" property .. not nice but works
+ this.props.pathCallback(this.props.index, evt.target.getAttribute('target'));
+ }
+
+ render () {
+ var path = this.props.path;
+ var callback = this.pathSelected;
+ var items = _.map(path, (item, i) => <Breadcrumb.Item key={i} target={item.id} onClick={callback}> {item.id} </Breadcrumb.Item>);
+
+ return <Breadcrumb>{items}</Breadcrumb>;
+ }
+}
+
+export default PathBreadcrumb;
diff --git a/graphgraph-fe/src/PopupSettings.css b/graphgraph-fe/src/popup_settings.css
index f80e264..2a548af 100644
--- a/graphgraph-fe/src/PopupSettings.css
+++ b/graphgraph-fe/src/popup_settings.css
@@ -13,4 +13,3 @@
text-decoration: none;
font-size: 25px;
}
-
diff --git a/graphgraph-fe/src/popup_settings.js b/graphgraph-fe/src/popup_settings.js
new file mode 100644
index 0000000..2430ab2
--- /dev/null
+++ b/graphgraph-fe/src/popup_settings.js
@@ -0,0 +1,40 @@
+/*
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2019-2020 Orange Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import React from 'react';
+import Popup from 'reactjs-popup';
+import './popup_settings.css';
+import GraphHops from './graph_hops.js';
+
+class PopupMenu extends React.Component {
+ render () {
+ return (
+ <Popup trigger={<button className='settings-button' disabled={this.props.isDisabled}>Hops</button>} position="bottom right">
+ {close => (
+ <div>
+ <GraphHops edgeFilter={this.props.edgeFilter} parentHops={this.props.parentHops} childHops={this.props.childHops} cousinHops={this.props.cousinHops} updateHops={this.props.updateHops}/>
+ <button type="button" className="link-button, close" onClick={close}>&times;</button>
+ </div>
+ )}</Popup>
+ );
+ }
+}
+
+export default PopupMenu;
diff --git a/graphgraph-fe/src/requests.js b/graphgraph-fe/src/requests.js
index 8a86e0c..ae41f78 100644
--- a/graphgraph-fe/src/requests.js
+++ b/graphgraph-fe/src/requests.js
@@ -1,35 +1,55 @@
+/*
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2019-2020 Orange Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
const host = window.location.hostname;
const port = window.location.port;
const protocol = window.location.protocol;
export function schemas () {
- return `${protocol}//${host}:${port}/schemas`
-}
+ return `${protocol}//${host}:${port}/schemas`;
+};
export function validateSchema (schema) {
- return `${protocol}//${host}:${port}/schemas/${schema}/validation`
-}
+ return `${protocol}//${host}:${port}/schemas/${schema}/validation`;
+};
export function exportSchema (schema) {
- return `${protocol}//${host}:${port}/schemas/${schema}/xmiexport`
-}
+ return `${protocol}//${host}:${port}/schemas/${schema}/xmiexport`;
+};
export function nodeNames (schema, edgeFilter) {
- return `${protocol}//${host}:${port}/schemas/${schema}/nodes?edgeFilter=${edgeFilter}`
-}
+ return `${protocol}//${host}:${port}/schemas/${schema}/nodes?edgeFilter=${edgeFilter}`;
+};
export function basicGraph (schema, node, parentHops, cousinHops, childHops, edgeFilter) {
- return `${protocol}//${host}:${port}/schemas/${schema}/graph/basic?node=${node}&parentHops=${parentHops}&cousinHops=${cousinHops}&childHops=${childHops}&edgeFilter=${edgeFilter}`
-}
+ return `${protocol}//${host}:${port}/schemas/${schema}/graph/basic?node=${node}&parentHops=${parentHops}&cousinHops=${cousinHops}&childHops=${childHops}&edgeFilter=${edgeFilter}`;
+};
export function pathGraph (schema, fromNode, toNode, edgeFilter) {
- return `${protocol}//${host}:${port}/schemas/${schema}/graph/paths?fromNode=${fromNode}&toNode=${toNode}&edgeFilter=${edgeFilter}`
-}
+ return `${protocol}//${host}:${port}/schemas/${schema}/graph/paths?fromNode=${fromNode}&toNode=${toNode}&edgeFilter=${edgeFilter}`;
+};
export function nodeProperty (schema, node) {
- return `${protocol}//${host}:${port}/schemas/${schema}/nodes/${node}`
-}
+ return `${protocol}//${host}:${port}/schemas/${schema}/nodes/${node}`;
+};
export function edgeProperty (schema, fromNode, toNode) {
- return `${protocol}//${host}:${port}/schemas/${schema}/edges?fromNode=${fromNode}&toNode=${toNode}`
-}
+ return `${protocol}//${host}:${port}/schemas/${schema}/edges?fromNode=${fromNode}&toNode=${toNode}`;
+};
diff --git a/graphgraph-fe/src/serviceWorker.js b/graphgraph-fe/src/serviceWorker.js
deleted file mode 100644
index 5c6ead6..0000000
--- a/graphgraph-fe/src/serviceWorker.js
+++ /dev/null
@@ -1,135 +0,0 @@
-// This optional code is used to register a service worker.
-// register() is not called by default.
-
-// This lets the app load faster on subsequent visits in production, and gives
-// it offline capabilities. However, it also means that developers (and users)
-// will only see deployed updates on subsequent visits to a page, after all the
-// existing tabs open on the page have been closed, since previously cached
-// resources are updated in the background.
-
-// To learn more about the benefits of this model and instructions on how to
-// opt-in, read http://bit.ly/CRA-PWA
-
-const isLocalhost = Boolean(
- window.location.hostname === 'localhost' ||
- // [::1] is the IPv6 localhost address.
- window.location.hostname === '[::1]' ||
- // 127.0.0.1/8 is considered localhost for IPv4.
- window.location.hostname.match(
- /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
- )
-)
-
-export function register (config) {
- if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
- // The URL constructor is available in all browsers that support SW.
- const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href)
- if (publicUrl.origin !== window.location.origin) {
- // Our service worker won't work if PUBLIC_URL is on a different origin
- // from what our page is served on. This might happen if a CDN is used to
- // serve assets; see https://github.com/facebook/create-react-app/issues/2374
- return
- }
-
- window.addEventListener('load', () => {
- const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`
-
- if (isLocalhost) {
- // This is running on localhost. Let's check if a service worker still exists or not.
- checkValidServiceWorker(swUrl, config)
-
- // Add some additional logging to localhost, pointing developers to the
- // service worker/PWA documentation.
- navigator.serviceWorker.ready.then(() => {
- console.log(
- 'This web app is being served cache-first by a service ' +
- 'worker. To learn more, visit http://bit.ly/CRA-PWA'
- )
- })
- } else {
- // Is not localhost. Just register service worker
- registerValidSW(swUrl, config)
- }
- })
- }
-}
-
-function registerValidSW (swUrl, config) {
- navigator.serviceWorker
- .register(swUrl)
- .then(registration => {
- registration.onupdatefound = () => {
- const installingWorker = registration.installing
- if (installingWorker == null) {
- return
- }
- installingWorker.onstatechange = () => {
- if (installingWorker.state === 'installed') {
- if (navigator.serviceWorker.controller) {
- // At this point, the updated precached content has been fetched,
- // but the previous service worker will still serve the older
- // content until all client tabs are closed.
- console.log(
- 'New content is available and will be used when all ' +
- 'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
- )
-
- // Execute callback
- if (config && config.onUpdate) {
- config.onUpdate(registration)
- }
- } else {
- // At this point, everything has been precached.
- // It's the perfect time to display a
- // "Content is cached for offline use." message.
- console.log('Content is cached for offline use.')
-
- // Execute callback
- if (config && config.onSuccess) {
- config.onSuccess(registration)
- }
- }
- }
- }
- }
- })
- .catch(error => {
- console.error('Error during service worker registration:', error)
- })
-}
-
-function checkValidServiceWorker (swUrl, config) {
- // Check if the service worker can be found. If it can't reload the page.
- fetch(swUrl)
- .then(response => {
- // Ensure service worker exists, and that we really are getting a JS file.
- const contentType = response.headers.get('content-type')
- if (
- response.status === 404 ||
- (contentType != null && contentType.indexOf('javascript') === -1)
- ) {
- // No service worker found. Probably a different app. Reload the page.
- navigator.serviceWorker.ready.then(registration => {
- registration.unregister().then(() => {
- window.location.reload()
- })
- })
- } else {
- // Service worker found. Proceed as normal.
- registerValidSW(swUrl, config)
- }
- })
- .catch(() => {
- console.log(
- 'No internet connection found. App is running in offline mode.'
- )
- })
-}
-
-export function unregister () {
- if ('serviceWorker' in navigator) {
- navigator.serviceWorker.ready.then(registration => {
- registration.unregister()
- })
- }
-}
diff --git a/graphgraph-fe/src/service_worker.js b/graphgraph-fe/src/service_worker.js
new file mode 100644
index 0000000..0ac740b
--- /dev/null
+++ b/graphgraph-fe/src/service_worker.js
@@ -0,0 +1,146 @@
+/*
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2019-2020 Orange Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+// This optional code is used to register a service worker.
+// register() is not called by default.
+
+// This lets the app load faster on subsequent visits in production, and gives
+// it offline capabilities. However, it also means that developers (and users)
+// will only see deployed updates on subsequent visits to a page, after all the
+// existing tabs open on the page have been closed, since previously cached
+// resources are updated in the background.
+
+// To learn more about the benefits of this model and instructions on how to
+// opt-in, read http://bit.ly/CRA-PWA
+
+const isLocalhost = Boolean(
+ window.location.hostname === 'localhost' ||
+ // [::1] is the IPv6 localhost address.
+ window.location.hostname === '[::1]' ||
+ // 127.0.0.1/8 is considered localhost for IPv4.
+ window.location.hostname.match(/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/)
+);
+
+export function register (config) {
+ if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
+ // The URL constructor is available in all browsers that support SW.
+ const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
+ if (publicUrl.origin !== window.location.origin) {
+ // Our service worker won't work if PUBLIC_URL is on a different origin
+ // from what our page is served on. This might happen if a CDN is used to
+ // serve assets; see https://github.com/facebook/create-react-app/issues/2374
+ return;
+ }
+
+ window.addEventListener('load', () => {
+ const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
+
+ if (isLocalhost) {
+ // This is running on localhost. Let's check if a service worker still exists or not.
+ checkValidServiceWorker(swUrl, config);
+
+ // Add some additional logging to localhost, pointing developers to the
+ // service worker/PWA documentation.
+ navigator.serviceWorker.ready.then(() => {
+ console.log(
+ 'This web app is being served cache-first by a service ' +
+ 'worker. To learn more, visit http://bit.ly/CRA-PWA'
+ );
+ });
+ } else {
+ // Is not localhost. Just register service worker
+ registerValidSW(swUrl, config);
+ }
+ });
+ }
+}
+
+function registerValidSW (swUrl, config) {
+ navigator.serviceWorker
+ .register(swUrl)
+ .then(registration => {
+ registration.onupdatefound = () => {
+ const installingWorker = registration.installing;
+ if (installingWorker == null) {
+ return;
+ }
+ installingWorker.onstatechange = () => {
+ if (installingWorker.state === 'installed') {
+ if (navigator.serviceWorker.controller) {
+ // At this point, the updated precached content has been fetched,
+ // but the previous service worker will still serve the older
+ // content until all client tabs are closed.
+ console.log('New content is available and will be used when all ' +
+ 'tabs for this page are closed. See http://bit.ly/CRA-PWA.');
+
+ // Execute callback
+ if (config && config.onUpdate) {
+ config.onUpdate(registration);
+ }
+ } else {
+ // At this point, everything has been precached.
+ // It's the perfect time to display a
+ // "Content is cached for offline use." message.
+ console.log('Content is cached for offline use.');
+
+ // Execute callback
+ if (config && config.onSuccess) {
+ config.onSuccess(registration);
+ }
+ }
+ }
+ };
+ };
+ })
+ .catch(error => {
+ console.error('Error during service worker registration:', error);
+ });
+}
+
+function checkValidServiceWorker (swUrl, config) {
+ // Check if the service worker can be found. If it can't reload the page.
+ fetch(swUrl)
+ .then(response => {
+ // Ensure service worker exists, and that we really are getting a JS file.
+ const contentType = response.headers.get('content-type');
+ if (response.status === 404 || (contentType != null && contentType.indexOf('javascript') === -1)) {
+ // No service worker found. Probably a different app. Reload the page.
+ navigator.serviceWorker.ready.then(registration => {
+ registration.unregister().then(() => {
+ window.location.reload();
+ });
+ });
+ } else {
+ // Service worker found. Proceed as normal.
+ registerValidSW(swUrl, config);
+ }
+ })
+ .catch(() => {
+ console.log('No internet connection found. App is running in offline mode.');
+ });
+}
+
+export function unregister () {
+ if ('serviceWorker' in navigator) {
+ navigator.serviceWorker.ready.then(registration => {
+ registration.unregister();
+ });
+ }
+};
diff --git a/graphgraph-fe/src/ValidationModal.css b/graphgraph-fe/src/validation_modal.css
index 56b4567..4fe12a9 100644
--- a/graphgraph-fe/src/ValidationModal.css
+++ b/graphgraph-fe/src/validation_modal.css
@@ -1,7 +1,7 @@
.modal-content
{
-height: 100%;
-width: 100%;
+ height: 100%;
+ width: 100%;
}
.modal-validator
@@ -16,9 +16,9 @@ width: 100%;
backgroundColor: 'white';
boxShadow: '0 5px 15px rgba(0,0,0,.5)';
padding: 0;
-}
+}
-.modal-backdrop
+.modal-backdrop
{
position: 'fixed';
zIndex: 1040;
diff --git a/graphgraph-fe/src/validation_modal.js b/graphgraph-fe/src/validation_modal.js
new file mode 100644
index 0000000..1944c36
--- /dev/null
+++ b/graphgraph-fe/src/validation_modal.js
@@ -0,0 +1,58 @@
+/*
+ * ============LICENSE_START=======================================================
+ * org.onap.aai
+ * ================================================================================
+ * Copyright © 2019-2020 Orange Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+import _ from 'underscore';
+import React from 'react';
+import './validation_modal.css';
+import { Button, Modal, ListGroup, ListGroupItem } from 'react-bootstrap';
+
+class ValidationModal extends React.Component {
+ constructor(...args) {
+ super(...args);
+ this.state = { showModal: false };
+ this.close = () => {
+ this.setState({ showModal: false });
+ };
+ this.open = () => {
+ this.setState({ showModal: true });
+ };
+ }
+
+ renderBackdrop(props) {
+ return <div {...props} className="modal-backdrop"/>;
+ }
+
+ render() {
+ var problems = this.props.schemaProblems;
+ var items = _.map(problems, (problem, i) => <ListGroupItem key={i}>{problem}</ListGroupItem>);
+ return (
+ <div>
+ <Button onClick={this.open}>Validate schema</Button>
+ <Modal onHide={this.close} className="modal-validator" aria-labelledby="modal-label" show={this.state.showModal} renderBackdrop={this.renderBackdrop}>
+ <div className="modal-list">
+ <ListGroup>{items}</ListGroup>
+ </div>
+ </Modal>
+ </div>
+ );
+ }
+}
+
+export default ValidationModal;