Skip to content

Commit

Permalink
Merge pull request #277 from moul/dev/appigram/ghost-nodes
Browse files Browse the repository at this point in the history
feat: Ghost nodes
  • Loading branch information
moul committed Mar 27, 2020
2 parents a4e564f + 649eeca commit 269a1aa
Show file tree
Hide file tree
Showing 19 changed files with 836 additions and 644 deletions.
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
- ./web/node_modules
- run: "cd web && npm run build"
- run: "cd web && npm run test:CI"
#- run: "cd web && npm run lint" # temporarily disable JS lint
- run: "cd web && npm run lint"

workflows:
main:
Expand Down
5 changes: 4 additions & 1 deletion web/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@
],
"class-methods-use-this": 0,
"react/jsx-filename-extension": 0,
"react/forbid-prop-types": 0
"react/forbid-prop-types": 0,
"react/no-array-index-key": 0,
"array-callback-return": 1,
"no-use-before-define": ["error", { "functions": false, "variables": false }]
}
}
1,272 changes: 703 additions & 569 deletions web/package-lock.json

Large diffs are not rendered by default.

24 changes: 12 additions & 12 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
"react": "16.13.1",
"react-dom": "16.13.1",
"react-feather": "2.0.3",
"react-hook-form": "5.1.1",
"react-hook-form": "5.1.3",
"react-router-dom": "5.1.2",
"tabler": "appigram/tabler#dev"
},
Expand All @@ -53,13 +53,13 @@
"@babel/plugin-transform-runtime": "7.9.0",
"@babel/polyfill": "7.8.7",
"@babel/preset-env": "7.9.0",
"@babel/preset-react": "7.9.1",
"@babel/preset-react": "7.9.4",
"acorn": "7.1.1",
"ajv": "6.12.0",
"autoprefixer": "9.7.4",
"autoprefixer": "9.7.5",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "10.1.0",
"babel-jest": "25.1.0",
"babel-jest": "25.2.3",
"babel-loader": "8.1.0",
"babel-plugin-add-module-exports": "1.0.2",
"babel-plugin-inline-import": "3.0.0",
Expand All @@ -78,28 +78,28 @@
"enzyme-adapter-react-16": "1.15.2",
"eslint": "6.8.0",
"eslint-config-airbnb": "18.1.0",
"eslint-config-prettier": "6.10.0",
"eslint-config-prettier": "6.10.1",
"eslint-plugin-import": "2.20.1",
"eslint-plugin-jsx-a11y": "6.2.3",
"eslint-plugin-node": "11.0.0",
"eslint-plugin-promise": "4.2.1",
"eslint-plugin-react": "7.19.0",
"eslint-plugin-react-hooks": "2.5.1",
"eslint-plugin-react-hooks": "3.0.0",
"eslint-watch": "6.0.1",
"file-loader": "6.0.0",
"hard-source-webpack-plugin": "0.13.1",
"history": "4.10.1",
"html-webpack-plugin": "3.2.0",
"html-webpack-plugin": "4.0.2",
"identity-obj-proxy": "3.0.0",
"jest": "25.1.0",
"jest-cli": "25.1.0",
"jest": "25.2.3",
"jest-cli": "25.2.3",
"json-loader": "0.5.7",
"mini-css-extract-plugin": "0.9.0",
"mockdate": "2.0.5",
"node-sass": "4.13.1",
"open-cli": "6.0.0",
"open-cli": "6.0.1",
"postcss-loader": "3.0.0",
"prettier": "1.19.1",
"prettier": "2.0.2",
"prompt": "1.0.0",
"prop-types": "15.7.2",
"raf": "3.4.1",
Expand All @@ -113,7 +113,7 @@
"stats.js": "0.17.0",
"style-loader": "1.1.3",
"url-loader": "4.0.0",
"webpack": "4.42.0",
"webpack": "4.42.1",
"webpack-bundle-analyzer": "3.6.1",
"webpack-dev-middleware": "3.7.2",
"webpack-hot-middleware": "2.25.0"
Expand Down
2 changes: 1 addition & 1 deletion web/src/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import 'tabler/scss/tabler.scss'
import './App.scss'

const App = () => {
const [showAuthModal, setShowAuthModal] = useState(!store.getItem('auth_token'))
const [showAuthModal, setShowAuthModal] = useState(false) // !store.getItem('auth_token'))
const [authToken, setAuthToken] = useState(store.getItem('auth_token') || '')
const searchParams = new URLSearchParams(window.location.search)
const urlData = {
Expand Down
8 changes: 4 additions & 4 deletions web/src/api/depviz.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { baseApi } from './index'
import baseApi from './index'

export function fetchDepviz(url, params) {
return baseApi.get(`${url}`, params)
}
const fetchDepviz = (url, params) => baseApi.get(`${url}`, params)

export default fetchDepviz
34 changes: 21 additions & 13 deletions web/src/api/index.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,40 @@
import axios from 'axios'
import store from '../utils/store'

export const baseApi = axios.create({
let retryCounter = 0

const baseApi = axios.create({
baseURL: process.env.API_URL,
})

// Add a response interceptor
baseApi.interceptors.request.use(
(config) => {
const auth = store.getItem('auth_token')
config.headers.Authorization = `Basic ${btoa(`depviz:${auth}`)}`
return config
const newConfig = config
newConfig.headers.Authorization = `Basic ${btoa(`depviz:${store.getItem('auth_token')}`)}`
return newConfig
},
)

// Add a response interceptor
baseApi.interceptors.response.use((response) => response,
(error) => {
const status = error.response ? error.response.status : null
const newError = error
const status = newError.response ? newError.response.status : null

if (status === 401) {
const auth = process.env.AUTH_TOKEN
store.setItem('auth_token', auth)
error.config.headers.Authorization = `Basic ${btoa(`depviz:${auth}`)}`
return baseApi.request(error.config)
// });
if (process.env.AUTH_TOKEN) {
store.setItem('auth_token', process.env.AUTH_TOKEN)
newError.config.headers.Authorization = `Basic ${btoa(`depviz:${process.env.AUTH_TOKEN}`)}`
}
retryCounter += 1
if (retryCounter < 4) { // Allow 3 attempts to request
return baseApi.request(newError.config)
}
}
console.error('failed', error, status, error)
alert(`failed: ${error}`)
return Promise.reject(error)
console.error('failed', newError, status, newError)
alert(`failed: ${newError}`)
return Promise.reject(newError)
})

export default baseApi
1 change: 1 addition & 0 deletions web/src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* eslint-disable import/default */
/* eslint-disable global-require */

import React from 'react'
import { render } from 'react-dom'
Expand Down
28 changes: 15 additions & 13 deletions web/src/ui/Visualizer/renderers/Cytoscape.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,9 @@ const CytoscapeRenderer = ({ nodes, edges, layout }) => {
}

nodes.forEach((node) => {
node.group = 'nodes'
config.elements.push(node)
const newNode = node
newNode.group = 'nodes'
config.elements.push(newNode)
})

const cy = cytoscape(config)
Expand Down Expand Up @@ -105,26 +106,27 @@ const CytoscapeRenderer = ({ nodes, edges, layout }) => {
const edgeMap = {}
cy.batch(() => {
edges.forEach((edge) => {
const newEdge = edge
let isOk = true
if (cy.getElementById(edge.data.source).empty()) {
console.warn('missing node', edge.data.source)
if (cy.getElementById(newEdge.data.source).empty()) {
console.warn('missing node', newEdge.data.source)
isOk = false
}
if (cy.getElementById(edge.data.target).empty()) {
console.warn('missing node', edge.data.target)
if (cy.getElementById(newEdge.data.target).empty()) {
console.warn('missing node', newEdge.data.target)
isOk = false
}
if (!isOk) {
return
}
edge.group = 'edges'
edge.data.id = edge.data.relation + edge.data.source + edge.data.target
edge.data.arrow = 'triangle'
if (edge.data.id in edgeMap) {
console.warn('duplicate edge', edge)
newEdge.group = 'edges'
newEdge.data.id = newEdge.data.relation + newEdge.data.source + newEdge.data.target
newEdge.data.arrow = 'triangle'
if (newEdge.data.id in edgeMap) {
console.warn('duplicate edge', newEdge)
} else {
edgeMap[edge.data.id] = edge
cy.add(edge)
edgeMap[newEdge.data.id] = newEdge
cy.add(newEdge)
}
})
})
Expand Down
53 changes: 38 additions & 15 deletions web/src/ui/Visualizer/renderers/Mermaid.js
Original file line number Diff line number Diff line change
@@ -1,21 +1,28 @@
import React, { useState, useEffect } from 'react'
import { mermaidAPI } from 'mermaid'
import mermaid, { mermaidAPI } from 'mermaid'
import { useStore } from '../../../hooks/useStore'

import './styles.scss'

const MermaidRenderer = ({ nodes, layout }) => {
const { repName } = useStore()
const [mermaidGraph, setMermaidGraph] = useState('Loading diagram...')
const [mermaidOrientation, setMermaidOrientation] = useState('TB')
const [graphInfo, setGraphInfo] = useState('')

useEffect(() => {
/* mermaid.initialize({
mermaid.initialize({
securityLevel: 'loose',
startOnLoad: true,
flowchart: {
useMaxWidth: false,
htmlLabels: true
}
}) */
startOnLoad: true,
flowchart: {
useMaxWidth: true,
htmlLabels: true,
curve: 'cardinal',
},
})
})

useEffect(() => {
if (layout.name === 'gantt') {
mermaidAPI.render('gantt', renderGanttTemplate(), (html) => setMermaidGraph(html))
} else if (layout.name === 'flow') {
Expand Down Expand Up @@ -59,13 +66,13 @@ const MermaidRenderer = ({ nodes, layout }) => {

if (item.is_depending_on) {
ganttStr += ', after'
for (let i = 0; i < item.is_depending_on.length; i++) {
for (let i = 0; i < item.is_depending_on.length; i += 1) {
const urlArr = item.is_depending_on[i].split('/')
const issId = urlArr[urlArr.length - 1]
const issIdStr = `issue${issId.replace('/', '_')}`
// Check missing nodes
let nodeInStack = false
for (let j = 0; j < ganttTasks.length; j++) {
for (let j = 0; j < ganttTasks.length; j += 1) {
const ganttItem = ganttTasks[j]
if (ganttItem.includes(issIdStr)) {
nodeInStack = true
Expand Down Expand Up @@ -115,22 +122,30 @@ const MermaidRenderer = ({ nodes, layout }) => {
let flowTemplate = `graph ${mermaidOrientation}\n\r`

const flowTasks = []
/* const flowClickNode = (e) => {
const node = e.target
try { // your browser may block popups
window.open(node.id())
} catch (e) { // fall back on url change
window.location.href = node.id()
}
} */
nodes.forEach((node) => {
const item = node.data
if (!item.local_id) {
return
}
const issId = `issue${item.local_id.replace(`${repName}#`, '').replace('/', '_')}`
let flowStr = `${issId}("${issId}")`
let flowStr = `${issId}("${issId}"):::cy-card`
if (item.is_depending_on) {
flowStr += ' --> '
for (let i = 0; i < item.is_depending_on.length - 1; i++) {
for (let i = 0; i < item.is_depending_on.length - 1; i += 1) {
const urlArr = item.is_depending_on[i].split('/')
const issId = urlArr[urlArr.length - 1]
const issIdStr = `issue${issId.replace('/', '_')}&`
// Check missing nodes
let nodeInStack = false
for (let j = 0; j < flowTasks.length; j++) {
for (let j = 0; j < flowTasks.length; j += 1) {
const flowItem = flowTasks[j]
if (flowItem.includes(issIdStr)) {
nodeInStack = true
Expand All @@ -140,7 +155,7 @@ const MermaidRenderer = ({ nodes, layout }) => {

if (!nodeInStack || flowTasks.length === 0) {
// Add missing node first
flowTasks.push(`issue${issId.replace('/', '_')}(missing issue${issId})\n\rstyle issue${issId.replace('/', '_')} fill:#ddd`)
flowTasks.push(`issue${issId.replace('/', '_')}(missing issue${issId}):::closed\n\rstyle issue${issId.replace('/', '_')} fill:#ddd`)
flowStr += `issue${issId.replace('/', '_')}&`
} else {
flowStr += `issue${issId.replace('/', '_')}&`
Expand All @@ -149,6 +164,7 @@ const MermaidRenderer = ({ nodes, layout }) => {
const urlArr = item.is_depending_on[item.is_depending_on.length - 1].split('/')
const issId = urlArr[urlArr.length - 1]
flowStr += `issue${issId.replace('/', '_')}(issue${issId})`
flowStr += `\n\r\tclick issue${issId.replace('/', '_')} flowClickNode "Open link"`
}
flowTasks.push(flowStr)
})
Expand All @@ -164,6 +180,7 @@ const MermaidRenderer = ({ nodes, layout }) => {
` */

const flowStr = flowTemplate.toString()
setGraphInfo(flowStr)
return flowStr
}

Expand All @@ -187,7 +204,13 @@ const MermaidRenderer = ({ nodes, layout }) => {
</div>
)}
<br />
<div className="mermaid-graph" dangerouslySetInnerHTML={{ __html: mermaidGraph }} />
<div className="mermaid-graph-wrapper">
<div className="mermaid-graph" dangerouslySetInnerHTML={{ __html: mermaidGraph }} />
</div>
<div className="mermaid-graph-info">
<h3>Graph layout (for debug)</h3>
{graphInfo.split('\n').map((node, index) => <p key={index} dangerouslySetInnerHTML={{ __html: node.replace(/\\t/gi, '&nbsp;').replace(/\s/gi, '&nbsp;') }} />)}
</div>
</div>
)
}
Expand Down
18 changes: 16 additions & 2 deletions web/src/ui/Visualizer/renderers/styles.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,20 @@
width: 100%;
height: 80vh;
margin-top: 1rem;
overflow: hidden;
overflow-x: auto;
}

/* mermaid styles */
.mermaid-wrapper {
.mermaid-graph-wrapper {
overflow: hidden;
overflow-x: auto;
}
.mermaid-graph-info {
background-color: #d0d3f7;
padding: 1rem;
margin-top: 4rem;
p {
margin: 0;
}
}
}
5 changes: 3 additions & 2 deletions web/src/ui/components/ErrorBoundary/ErrorBoundary.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class ErrorBoundary extends React.Component {
this.setState({
showError: true,
errMessage: error.toString(),
errStack: info.componentStack.split('\n').map((i) => <p>{i}</p>),
errStack: info.componentStack.split('\n').map((i) => <p key={i}>{i}</p>),
})
}
console.log('error: ', error)
Expand All @@ -25,6 +25,7 @@ class ErrorBoundary extends React.Component {

render() {
const { showError, errMessage, errStack } = this.state
const { children } = this.props
if (showError) {
return (
<div className="error-stack">
Expand All @@ -37,7 +38,7 @@ class ErrorBoundary extends React.Component {
</div>
)
}
return this.props.children
return children
}
}

Expand Down

0 comments on commit 269a1aa

Please sign in to comment.