Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Include the Monarch link app as a service on IMAGE-server #894

Merged
merged 16 commits into from
Oct 23, 2024

Conversation

VenissaCarolQuadros
Copy link
Member

@VenissaCarolQuadros VenissaCarolQuadros commented Oct 8, 2024

This PR merges a containerized version of the Monarch web application that links the IMAGE-TactileAuthoring tool to the Monarch client.

As per discussions, this application has been made into a service on the IMAGE-server. New channels can be created and existing channels can be updated by the authoring tool by posting to https://monarch.unicorn.cim.mcgill.ca/create/<subscribed_code>. The JSON can be accessed by the Monarch client by getting from https://monarch.unicorn.cim.mcgill.ca/display/<subscribed_code>. Resolves #888.

Testing:

  • Checked that restarting container clears previously created channel
  • Checked that a new channel can be created using publish on the authoring tool and getting the same on the Monarch
<svg width="254.00000000000003" height="105.83333" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" version="1.1" xml:space="preserve">
<g class="layer" data-image-layer="Layer 1">
<ellipse cx="59.5" cy="27.82" fill="#FF0000" id="svg_1" rx="27" ry="25" stroke="#000000" stroke-width="5"/>
</g>
</svg>

was published on code 123456.

  • Checked that a channel can be updated when the code and secret id is correct
    Graphic on 123456 was updated to
<svg width="254.00000000000003" height="105.83333" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg" version="1.1" xml:space="preserve">
 <g class="layer" data-image-layer="Layer 1">
  <rect fill="#FF0000" height="34.62" id="svg_2" stroke="#000000" stroke-width="5" width="84.83" x="26.47" y="33.31"/>
 </g>
</svg>

and the change was observed on the Monarch

  • Checked that update fails when the secret id is incorrect
    The update failed when the secret key was changed

Required Information

  • I referenced the issue addressed in this PR.
  • I described the changes made and how these address the issue.
  • I described how I tested these changes.

Coding/Commit Requirements

  • I followed applicable coding standards where appropriate (e.g., PEP8)
  • I have not committed any models or other large files.

New Component Checklist (mandatory for new microservices)

  • I added an entry to docker-compose.yml and build.yml.
  • I created A CI workflow under .github/workflows.
  • I have created a README.md file that describes what the component does and what it depends on (other microservices, ML models, etc.).

OR

  • I have not added a new component in this PR.

@VenissaCarolQuadros VenissaCarolQuadros self-assigned this Oct 8, 2024
@VenissaCarolQuadros VenissaCarolQuadros marked this pull request as ready for review October 9, 2024 15:40
Copy link
Collaborator

@JRegimbal JRegimbal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comments within the file, plus the inputs are not checked. I was able to cause an internal server error by omitting or incorrectly entering fields in the POST requests.

.github/workflows/monarch-link-app-service.yml Outdated Show resolved Hide resolved
@@ -0,0 +1,19 @@
FROM python:3.10
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current versions is 3.13 - why are you using 3.10?

Comment on lines 37 to 47
def write_data(svgData):
with open("data.json", "w") as outfile:
json.dump(svgData, outfile)


def read_data():
with open('data.json', 'r') as openfile:
try:
return json.load(openfile)
except Exception:
return dict()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK for quick prototyping, but this should not be used in production. Would suggest something like sqlite3 that may scale better.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Created #897 to keep track of this

return dict()


@app.route("/create/<id>", methods=["POST"])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there restrictions on what "id" can be here?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also - any reason why the create and update methods were combined?

Copy link
Member Author

@VenissaCarolQuadros VenissaCarolQuadros Oct 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't limited this currently but I typically use a 6-digit numeric code. Flask has options to limit this something generic like 'int' or we can also build custom 'converters' if we want to enforce this.

EDIT: I have added in a custom converter.

req_data = request.get_json()
svgData = read_data()
if id in svgData:
if (svgData[id])["secret"] == req_data["secret"]:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally considered bad practice to save this in plain text...compare using something like bcrypt?

write_data(svgData)
return jsonify("Graphic in channel "+id+" has been updated!")
else:
return jsonify("Unauthorized access to existing channel!")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unauthorized access should return 401 code.

"data": req_data["data"],
"layer": req_data["layer"]}
write_data(svgData)
return jsonify("New channel created with code "+id)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the model we discussed earlier, the ID and secret was generated on the server and returned to the client. Create only allowed the client to specify a title. Any reason for the changes?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oops, it looks like I've somehow completely messed up the implementation here! :')
Will probably need to touch base in person to sort this out.

return jsonify("New channel created with code "+id)


@app.route("/display/<id>", methods=["GET"])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason for the change in name from receive? Also should check the ID.

Comment on lines 1 to 15
click==8.1.3
colorama==0.4.6
Flask==2.2.2
Flask-Cors==4.0.2
Flask-Login==0.6.2
Flask-SQLAlchemy==3.0.2
greenlet==2.0.1
itsdangerous==2.1.2
Jinja2==3.1.2
MarkupSafe==2.1.1
six==1.16.0
SQLAlchemy==1.4.43
SQLAlchemy-Utils==0.38.3
Werkzeug==2.2.2
gunicorn==22.0.0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest setting only what you use directly with semver and allowing some newer verisons. pip freeze output is almost always overly restrictive.

Comment on lines 84 to 86
response.add_etag(hashlib.md5(
(svgData[id]["data"]+svgData[id]["layer"]).encode()))
response.make_conditional(request)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

@VenissaCarolQuadros
Copy link
Member Author

I ran additional tests with POST requests with incorrect/ omitted fields. It now returns 400 in these cases.

Comment on lines +61 to +62
while code in svgData:
code = generate_code(svgData)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Putting a pin in this for later updates.

@app.route("/create/<id>", methods=["POST"])
# generate an id that does not already exist
def generate_code(svgData):
code = ''.join([str(random.randint(1, 9)) for i in range(6)])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like randint(a, b) returns a value n where a <= n <= b. This conflicts with the check for values between 1 and 8 on line 71.

https://docs.python.org/3/library/random.html

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oopsies! I referred numpy randint docs 😶

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah yes, the legally distinct randint. No worries!

# has six digits between 1 and 8
pattern = re.compile("[1-8]{6}")
# also has a length of six
if not pattern.match(value) or len(value) != 6:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggest updating the pattern to include line start/end (^[1-8]{6}$) to avoid the separate length check.

app.url_map.converters['code'] = CodeConverter


@app.route("/create/<string:title>", methods=["POST"])
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't have put the title as part of the path here. This makes more sense as part of the request body.

@VenissaCarolQuadros
Copy link
Member Author

Made the requested changes.
I also noticed that I was not saving 'title' to JSON- I've included it and also made it possible to change this when 'update/' is called.

Also, an exception was being raised when 'display/' was called on a valid ID that didn't exist in the JSON since abort() raises an exception.

Copy link
Collaborator

@JRegimbal JRegimbal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good! We should chat tomorrow during the meeting about merging this.

@JRegimbal JRegimbal merged commit d1bfea8 into main Oct 23, 2024
2 checks passed
@JRegimbal JRegimbal deleted the monarch-link-app branch October 23, 2024 15:53
@jeffbl
Copy link
Member

jeffbl commented Dec 9, 2024

Marking for deploy on pegasus since I haven't noticed any ill effects on unicorn. @JRegimbal @VenissaCarolQuadros please push back if you think this could cause issues.

@jeffbl jeffbl added the deploy! deploy into production from test! label Dec 9, 2024
@VenissaCarolQuadros
Copy link
Member Author

Unless there is already some cleanup happening on Pegasus, we need have a way to clear the .json file this app creates since this could bulk up over time. (It is cleared on Unicorn every time the container is rebuilt)

@jeffbl
Copy link
Member

jeffbl commented Dec 9, 2024

Is there a work item for doing this? (If so, please link here, otw it sounds like we should create one?)

And I assume deploying this is also blocked on Shared-Reality-Lab/IMAGE-website#73?

@jeffbl jeffbl removed the deploy! deploy into production from test! label Jan 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
3 participants