Skip to content

Commit 18b69ff

Browse files
authored
Merge branch 'dev' into dependabot/npm_and_yarn/npm_and_yarn-04d31be16b
2 parents 5a9df01 + e84aade commit 18b69ff

12 files changed

Lines changed: 274 additions & 103 deletions

File tree

server/controller/workflow.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
from model.workflows import *
1+
from model.workflows import WorkFlowModel
22
from flask import request, make_response, Blueprint
33
import defusedxml.ElementTree as ET
44

55
workFlow = Blueprint('workflow', __name__)
66
workFlowModel = WorkFlowModel()
77

88

9+
def isMissingWorkflow(graphml):
10+
if graphml is None:
11+
return True
12+
# Backward-compatible guard for legacy model return type.
13+
return isinstance(graphml, tuple) and len(graphml) > 0 and graphml[0] is False
14+
15+
916
def getLasteshActionHash(root):
1017
xmlns = root.tag[root.tag.index('{')+1:root.tag.rindex('}')]
1118
return root.find(f'{{{xmlns}}}graph')\
@@ -32,7 +39,7 @@ def postWorkflow():
3239
@workFlow.route("/<serverID>")
3340
def getWorkflow(serverID):
3441
graphml = workFlowModel.get(serverID)
35-
if graphml is None:
42+
if isMissingWorkflow(graphml):
3643
return "Not Found", 404
3744
if('X-Latest-Hash' in request.headers):
3845
latestHash = request.headers['X-Latest-Hash']

server/model/workflows.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def insert(self, graphml, latestHash):
3232
def get(self, serverID):
3333
cl = self.collection.find_one({'serverID': serverID})
3434
if not cl:
35-
return False, 'Record Not Found'
35+
return None
3636
return cl['graphml']
3737

3838
def update(self, serverID, graphml, latestHash, allHash):

server/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
dnspython==2.6.1
2-
Flask==2.2.5
2+
Flask==3.1.3
33
python-dotenv==0.19.0
44
pymongo==4.6.3
55
gunicorn==23.0.0
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import importlib
2+
import pathlib
3+
import sys
4+
import types
5+
import unittest
6+
7+
from flask import Flask
8+
9+
VALID_GRAPHML = (
10+
'<graphml xmlns="http://graphml.graphdrawing.org/xmlns">'
11+
'<graph edgedefault="directed">'
12+
'<actionHistory><hash>hash-1</hash></actionHistory>'
13+
'</graph>'
14+
'</graphml>'
15+
)
16+
17+
18+
class FakeWorkFlowModel:
19+
def __init__(self, graph_response):
20+
self.graph_response = graph_response
21+
22+
def get(self, _server_id):
23+
return self.graph_response
24+
25+
26+
class WorkflowControllerTests(unittest.TestCase):
27+
@classmethod
28+
def setUpClass(cls):
29+
server_root = pathlib.Path(__file__).resolve().parents[1]
30+
if str(server_root) not in sys.path:
31+
sys.path.insert(0, str(server_root))
32+
33+
fake_model_pkg = types.ModuleType('model')
34+
fake_model_workflows = types.ModuleType('model.workflows')
35+
36+
class StubWorkFlowModel:
37+
def get(self, _server_id):
38+
return None
39+
40+
fake_model_workflows.WorkFlowModel = StubWorkFlowModel
41+
fake_model_pkg.workflows = fake_model_workflows
42+
43+
sys.modules['model'] = fake_model_pkg
44+
sys.modules['model.workflows'] = fake_model_workflows
45+
46+
if 'controller.workflow' in sys.modules:
47+
del sys.modules['controller.workflow']
48+
cls.workflow_module = importlib.import_module('controller.workflow')
49+
50+
def make_client(self, graph_response):
51+
self.workflow_module.workFlowModel = FakeWorkFlowModel(graph_response)
52+
app = Flask(__name__)
53+
app.register_blueprint(self.workflow_module.workFlow, url_prefix='/workflow')
54+
return app.test_client()
55+
56+
def test_missing_workflow_returns_404_for_none(self):
57+
client = self.make_client(None)
58+
response = client.get('/workflow/missing-id')
59+
self.assertEqual(response.status_code, 404)
60+
self.assertEqual(response.get_data(as_text=True), 'Not Found')
61+
62+
def test_missing_workflow_returns_404_for_legacy_tuple(self):
63+
client = self.make_client((False, 'Record Not Found'))
64+
response = client.get('/workflow/missing-id')
65+
self.assertEqual(response.status_code, 404)
66+
self.assertEqual(response.get_data(as_text=True), 'Not Found')
67+
68+
def test_hash_header_returns_400_for_different_history(self):
69+
client = self.make_client(VALID_GRAPHML)
70+
response = client.get('/workflow/existing-id', headers={'X-Latest-Hash': 'unknown-hash'})
71+
self.assertEqual(response.status_code, 400)
72+
self.assertEqual(response.get_data(as_text=True), 'Different History')
73+
74+
def test_hash_header_returns_200_for_matching_history(self):
75+
client = self.make_client(VALID_GRAPHML)
76+
response = client.get('/workflow/existing-id', headers={'X-Latest-Hash': 'hash-1'})
77+
self.assertEqual(response.status_code, 200)
78+
self.assertEqual(response.get_data(as_text=True), VALID_GRAPHML)
79+
80+
81+
if __name__ == '__main__':
82+
unittest.main()

src/GraphArea.jsx

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,12 @@ function Graph({
5656
}, [active, instance, graphID, dispatcher]);
5757

5858
useEffect(() => {
59-
if (ref.current) {
60-
setContainerDim(ref.current);
61-
window.addEventListener('resize', () => setContainerDim(ref.current));
62-
setInstance(initialiseNewGraph());
63-
}
59+
if (!ref.current) return;
60+
setContainerDim(ref.current);
61+
const handleResize = () => setContainerDim(ref.current);
62+
window.addEventListener('resize', handleResize);
63+
setInstance(initialiseNewGraph());
64+
return () => window.removeEventListener('resize', handleResize);
6465
}, [ref]);
6566

6667
// Update theme when darkMode changes

src/component/File-drag-drop.jsx

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -6,42 +6,58 @@ import { readFile } from '../toolbarActions/toolbarFunctions';
66

77
const app = ({ superState, dispatcher }) => {
88
const fileRef = React.useRef();
9+
const superStateRef = React.useRef(superState);
10+
const dispatcherRef = React.useRef(dispatcher);
11+
12+
useEffect(() => {
13+
superStateRef.current = superState;
14+
dispatcherRef.current = dispatcher;
15+
});
916

1017
useEffect(() => {
1118
dispatcher({ type: T.SET_FILE_REF, payload: fileRef });
1219
const p = document.getElementsByTagName('body')[0];
1320
const c = document.getElementsByClassName('drag-drop-area')[0];
1421
let cc = 0;
15-
p.addEventListener('dragenter', (e) => {
22+
23+
const onDragEnter = (e) => {
1624
e.preventDefault();
1725
cc += 1;
1826
if (cc === 1) c.classList.remove('hidden');
19-
});
20-
p.addEventListener('dragleave', (e) => {
27+
};
28+
const onDragLeave = (e) => {
2129
e.preventDefault();
2230
cc -= 1;
2331
if (cc === 0) c.classList.add('hidden');
24-
});
25-
26-
p.addEventListener('dragover', (e) => {
32+
};
33+
const onDragOver = (e) => { e.preventDefault(); };
34+
const onDragReset = (e) => {
2735
e.preventDefault();
28-
});
29-
['dragend', 'dragexit', 'drop'].forEach((dragEvent) => {
30-
p.addEventListener(dragEvent, (e) => {
31-
e.preventDefault();
32-
cc = 0;
33-
c.classList.add('hidden');
34-
});
35-
});
36-
37-
p.addEventListener('drop', (e) => {
36+
cc = 0;
37+
c.classList.add('hidden');
38+
};
39+
const onDrop = (e) => {
3840
e.preventDefault();
3941
fileRef.current.value = null;
4042
if (e.dataTransfer.files.length === 1
4143
&& e.dataTransfer.files[0].name.split('.').slice(-1)[0] === 'graphml') {
42-
readFile(superState, dispatcher, e.dataTransfer.files[0]);
44+
readFile(superStateRef.current, dispatcherRef.current, e.dataTransfer.files[0]);
4345
}
44-
});
46+
};
47+
48+
p.addEventListener('dragenter', onDragEnter);
49+
p.addEventListener('dragleave', onDragLeave);
50+
p.addEventListener('dragover', onDragOver);
51+
['dragend', 'dragexit', 'drop'].forEach((dragEvent) => p.addEventListener(dragEvent, onDragReset));
52+
p.addEventListener('drop', onDrop);
53+
54+
return () => {
55+
p.removeEventListener('dragenter', onDragEnter);
56+
p.removeEventListener('dragleave', onDragLeave);
57+
p.removeEventListener('dragover', onDragOver);
58+
['dragend', 'dragexit', 'drop'].forEach((dragEvent) => p.removeEventListener(dragEvent, onDragReset));
59+
p.removeEventListener('drop', onDrop);
60+
};
4561
}, []);
4662
return (
4763
<div className="drag-drop-area hidden">

src/component/Header.jsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,14 @@ const setHotKeys = (actions) => {
3131
event.preventDefault();
3232
map[handler.shortcut].click();
3333
});
34+
return keys;
3435
};
3536

3637
const Header = ({ superState, dispatcher }) => {
3738
const actions = toolbarList(superState, dispatcher);
3839
React.useEffect(() => {
39-
setHotKeys(actions, superState, dispatcher);
40+
const keys = setHotKeys(actions);
41+
return () => { if (keys) hotkeys.unbind(keys); };
4042
}, []);
4143

4244
return (

src/component/TabBar.jsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ const TabBar = ({ superState, dispatcher }) => {
6363
const el = document.querySelector('.tab.tab-graph.selected > .tab-act.close');
6464
if (el) el.click();
6565
});
66+
return () => {
67+
hotkeys.unbind('ctrl+shift+m,command+shift+m');
68+
hotkeys.unbind('ctrl+shift+e,command+shift+e');
69+
hotkeys.unbind('ctrl+shift+l,command+shift+l');
70+
};
6671
}, []);
6772

6873
return (

src/graph-builder/graph-core/3-component.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -234,10 +234,10 @@ class GraphComponent extends GraphCanvas {
234234
}
235235

236236
setEdgeNodeValidator({ nodeValidator, edgeValidator }) {
237-
// eslint-disable-next-line no-eval
238-
this.nodeValidator = eval(nodeValidator);
239-
// eslint-disable-next-line no-eval
240-
this.edgeValidator = eval(edgeValidator);
237+
// eslint-disable-next-line no-new-func
238+
this.nodeValidator = new Function(`return ${nodeValidator}`)();
239+
// eslint-disable-next-line no-new-func
240+
this.edgeValidator = new Function(`return ${edgeValidator}`)();
241241
}
242242

243243
getNodesEdges() {

0 commit comments

Comments
 (0)