Skip to content

Commit 36e8fd4

Browse files
committed
Merge remote-tracking branch 'upstream/dev' into fix/undo-history-btoa-quota
# Conflicts: # src/graph-builder/local-storage-manager.js
2 parents e836e48 + 7181e6e commit 36e8fd4

21 files changed

Lines changed: 312 additions & 108 deletions

package-lock.json

Lines changed: 3 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

server/controller/workflow.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def getAllActionHash(root):
3030
def postWorkflow():
3131
try:
3232
lastestHash = getLasteshActionHash(ET.fromstring(request.data))
33-
except:
33+
except Exception:
3434
return "Invalid GraphML", 400
3535
graphML = request.data.decode('utf')
3636
return workFlowModel.insert(graphML, lastestHash)
@@ -60,7 +60,7 @@ def updateWorkflow(serverID):
6060
latestHash = getLasteshActionHash(root)
6161
if(not forceUpdate):
6262
allHash = getAllActionHash(root)
63-
except:
63+
except Exception:
6464
return "Invalid GraphML", 400
6565
graphML = request.data.decode('utf')
6666
if(forceUpdate):

server/model/workflows.py

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
from pymongo import MongoClient
2-
from pymongo import MongoClient
2+
from pymongo.errors import DuplicateKeyError
33
import time
44
from bson.objectid import ObjectId
55
from bson.errors import InvalidId
66
import os
77
import xml.etree.ElementTree as ET
8-
import random
8+
import secrets
99
import string
1010
from dotenv import load_dotenv
1111
load_dotenv()
@@ -14,20 +14,21 @@ class WorkFlowModel:
1414
def __init__(self) -> None:
1515
self.collection = MongoClient(os.getenv('MongoURL'))[
1616
os.getenv('dbName')][os.getenv('tableName')]
17+
self.collection.create_index('serverID', unique=True)
1718

1819
def get_random_string(self, length):
1920
letters = string.ascii_letters+string.digits
20-
return ''.join(random.choice(letters) for i in range(length))
21+
return ''.join(secrets.choice(letters) for i in range(length))
2122

2223
def insert(self, graphml, latestHash):
23-
serverID = ""
2424
while(True):
2525
serverID = self.get_random_string(6)
26-
if(not self.collection.find_one({'serverID': serverID})):
27-
break
28-
self.collection.insert_one(
29-
{'graphml': graphml, 'latestHash': latestHash, 'serverID': serverID})
30-
return serverID
26+
try:
27+
self.collection.insert_one(
28+
{'graphml': graphml, 'latestHash': latestHash, 'serverID': serverID})
29+
return serverID
30+
except DuplicateKeyError:
31+
continue
3132

3233
def get(self, serverID):
3334
cl = self.collection.find_one({'serverID': serverID})
@@ -36,20 +37,19 @@ def get(self, serverID):
3637
return cl['graphml']
3738

3839
def update(self, serverID, graphml, latestHash, allHash):
39-
existingRecord = self.collection.find_one({'serverID': serverID})
40+
existingRecord = self.collection.find_one_and_update(
41+
{'serverID': serverID, 'latestHash': {'$in': allHash}},
42+
{"$set": {'graphml': graphml, 'latestHash': latestHash}})
4043
if existingRecord is None:
41-
return False, 'serverID do not exists.'
42-
latestExistingHash = existingRecord['latestHash']
43-
if latestExistingHash not in allHash:
44+
if not self.collection.find_one({'serverID': serverID}):
45+
return False, 'serverID do not exists.'
4446
return False, 'Can not update as provided graph do not has latest changes.'
45-
self.collection.update_one({'serverID': serverID}, {
46-
"$set": {'graphml': graphml, 'latestHash': latestHash}})
4747
return True, latestHash
4848

4949
def forceUpdate(self, serverID, graphml, latestHash):
50-
existingRecord = self.collection.find_one({'serverID': serverID})
50+
existingRecord = self.collection.find_one_and_update(
51+
{'serverID': serverID},
52+
{"$set": {'graphml': graphml, 'latestHash': latestHash}})
5153
if existingRecord is None:
5254
return False, 'serverID do not exists.'
53-
self.collection.update_one({'serverID': serverID},
54-
{"$set": {'graphml': graphml, 'latestHash': latestHash}})
5555
return True, latestHash

server/server.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
load_dotenv()
77

88
app = Flask(__name__)
9+
app.config['MAX_CONTENT_LENGTH'] = 2 * 1024 * 1024
910
CORS(app)
1011

1112
app.register_blueprint(workFlow, url_prefix='/workflow')

server/tests/test_workflow_controller.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,15 @@ def __init__(self, graph_response):
2222
def get(self, _server_id):
2323
return self.graph_response
2424

25+
def insert(self, graphml, latestHash):
26+
return 'test01'
27+
28+
def update(self, serverID, graphml, latestHash, allHash):
29+
return (True, latestHash)
30+
31+
def forceUpdate(self, serverID, graphml, latestHash):
32+
return (True, latestHash)
33+
2534

2635
class WorkflowControllerTests(unittest.TestCase):
2736
@classmethod
@@ -77,6 +86,37 @@ def test_hash_header_returns_200_for_matching_history(self):
7786
self.assertEqual(response.status_code, 200)
7887
self.assertEqual(response.get_data(as_text=True), VALID_GRAPHML)
7988

89+
def test_post_workflow_returns_server_id(self):
90+
client = self.make_client(None)
91+
response = client.post('/workflow/', data=VALID_GRAPHML,
92+
content_type='application/xml')
93+
self.assertEqual(response.status_code, 200)
94+
self.assertEqual(response.get_data(as_text=True), 'test01')
95+
96+
def test_post_workflow_invalid_xml_returns_400(self):
97+
client = self.make_client(None)
98+
response = client.post('/workflow/', data=b'not xml',
99+
content_type='application/xml')
100+
self.assertEqual(response.status_code, 400)
101+
102+
def test_update_workflow_returns_200(self):
103+
client = self.make_client(None)
104+
response = client.post('/workflow/test01', data=VALID_GRAPHML,
105+
content_type='application/xml')
106+
self.assertEqual(response.status_code, 200)
107+
108+
def test_update_workflow_invalid_xml_returns_400(self):
109+
client = self.make_client(None)
110+
response = client.post('/workflow/test01', data=b'not xml',
111+
content_type='application/xml')
112+
self.assertEqual(response.status_code, 400)
113+
114+
def test_force_update_workflow_returns_200(self):
115+
client = self.make_client(None)
116+
response = client.post('/workflow/test01?force=true', data=VALID_GRAPHML,
117+
content_type='application/xml')
118+
self.assertEqual(response.status_code, 200)
119+
80120

81121
if __name__ == '__main__':
82122
unittest.main()

src/GraphWorkspace.jsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import React from 'react';
22
import ZoomComp from './component/ZoomSetter';
3-
4-
// import { actionType as T } from './reducer';
3+
import ConfirmModal from './component/modals/ConfirmModal';
4+
import { actionType as T } from './reducer';
55
import './graphWorkspace.css';
66
// import localStorageManager from './graph-builder/local-storage-manager';
77
import TabBar from './component/TabBar';
@@ -80,6 +80,16 @@ const GraphComp = (props) => {
8080
))}
8181
<ZoomComp dispatcher={dispatcher} superState={superState} />
8282
</div>
83+
<ConfirmModal
84+
isOpen={superState.confirmModal.open}
85+
title="Confirm"
86+
message={superState.confirmModal.message}
87+
onConfirm={() => {
88+
if (superState.confirmModal.onConfirm) superState.confirmModal.onConfirm();
89+
dispatcher({ type: T.SET_CONFIRM_MODAL, payload: { open: false, message: '', onConfirm: null } });
90+
}}
91+
onCancel={() => dispatcher({ type: T.SET_CONFIRM_MODAL, payload: { open: false, message: '', onConfirm: null } })}
92+
/>
8393
</div>
8494
);
8595
};

src/component/ErrorBoundary.jsx

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import React from 'react';
2+
3+
const crashStyle = {
4+
minHeight: '100vh',
5+
display: 'flex',
6+
alignItems: 'center',
7+
justifyContent: 'center',
8+
padding: '24px',
9+
backgroundColor: 'var(--bg-primary)',
10+
color: 'var(--text-primary)',
11+
textAlign: 'center',
12+
};
13+
14+
class ErrorBoundary extends React.Component {
15+
constructor(props) {
16+
super(props);
17+
this.state = { hasError: false };
18+
}
19+
20+
static getDerivedStateFromError() {
21+
return { hasError: true };
22+
}
23+
24+
render() {
25+
const { hasError } = this.state;
26+
const { children } = this.props;
27+
28+
if (hasError) {
29+
return (
30+
<div style={crashStyle}>
31+
<div>
32+
<h2 style={{ marginBottom: '8px' }}>Something went wrong</h2>
33+
<p style={{ marginBottom: '16px', opacity: 0.9 }}>
34+
The app hit an unexpected error. Reload to continue.
35+
</p>
36+
<button
37+
type="button"
38+
className="btn btn-primary"
39+
onClick={() => window.location.reload()}
40+
>
41+
Reload
42+
</button>
43+
</div>
44+
</div>
45+
);
46+
}
47+
48+
return children;
49+
}
50+
}
51+
52+
export default ErrorBoundary;

src/component/FullScreenButton.jsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,11 @@ export default function FullScreenButton() {
1515
};
1616

1717
return (
18-
<button type="button" onClick={toggleFullscreen}>
18+
<button
19+
type="button"
20+
aria-label={isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}
21+
onClick={toggleFullscreen}
22+
>
1923
{isFullscreen ? <Minimize size={20} /> : <Maximize size={20} />}
2024
</button>
2125
);

src/component/HeaderComps.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ const Switcher = ({
3737
tabIndex={tabIndex}
3838
className={`tool ${active ? 'active' : ''}`}
3939
onClick={action}
40-
onKeyDown={(ev) => ev.key === ' ' && action()}
40+
onKeyDown={(ev) => (ev.key === ' ' || ev.key === 'Enter') && action()}
4141
>
4242
{Icon && <div className="icon"><Icon size="20" /></div>}
4343
<Switch
@@ -60,7 +60,7 @@ const ActionButton = ({
6060
tabIndex={tabIndex}
6161
className={`tool ${active ? 'active' : ''}`}
6262
onClick={() => (active && action())}
63-
onKeyDown={(ev) => active && ev.key === ' ' && action()}
63+
onKeyDown={(ev) => active && (ev.key === ' ' || ev.key === 'Enter') && action()}
6464
data-tip={hotkey ? hotkey.split(',')[0] : ''}
6565
style={{ display: `${visibility ? '' : 'none'}` }}
6666
>

src/component/TabBar.jsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ const TabBar = ({ superState, dispatcher }) => {
7979
onClick={newProject.bind(this, superState, dispatcher)}
8080
type="button"
8181
id="new_graph"
82+
aria-label="New workflow tab"
8283
data-tip="New Workflow Tab (Ctrl + Shift + M)"
8384
>
8485
<MdAdd size={25} />
@@ -88,7 +89,7 @@ const TabBar = ({ superState, dispatcher }) => {
8889
key={el.graphID}
8990
className={`tab tab-graph ${superState.curGraphIndex === i ? 'selected' : 'none'}`}
9091
onClick={() => dispatcher({ type: T.CHANGE_TAB, payload: i })}
91-
onKeyDown={(ev) => ev.key === ' ' && dispatcher({ type: T.CHANGE_TAB, payload: i })}
92+
onKeyDown={(ev) => (ev.key === ' ' || ev.key === 'Enter') && dispatcher({ type: T.CHANGE_TAB, payload: i })}
9293
role="button"
9394
tabIndex={0}
9495
id={`tab_${i}`}
@@ -102,6 +103,7 @@ const TabBar = ({ superState, dispatcher }) => {
102103
className="tab-act edit"
103104
onClick={editCur}
104105
type="button"
106+
aria-label="Edit workflow details"
105107
data-tip="Edit Workflow Details (Ctrl + Shift + E)"
106108
data-for="header-tab"
107109
>
@@ -112,6 +114,7 @@ const TabBar = ({ superState, dispatcher }) => {
112114
className="tab-act close"
113115
onClick={handleRequestCloseTab.bind(this, i)}
114116
type="button"
117+
aria-label="Close workflow tab"
115118
data-tip="Close current Workflow (Ctrl + Shift + L)"
116119
data-for="header-tab"
117120
>

0 commit comments

Comments
 (0)