-
Notifications
You must be signed in to change notification settings - Fork 2
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
Ajout des fonctions et REST endpoints pour gérer les dictionnaires job-utilisateur #186
Changes from 16 commits
aa20f4e
5a8c319
ed6ea1b
db78f6d
64fc63b
e89a5df
1955a3a
cb0d2fd
7bfc643
a626be7
5d29008
ec1c4b4
11e3d78
db99cd5
6914b2f
4426490
cf4e345
71fca56
041834e
73477ee
a81a073
87a285a
b28ebb0
63a8503
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -227,7 +227,19 @@ def test_jobs_search_columns(page: Page): | |
|
||
def test_jobs_search_column_job_user_props(page: Page): | ||
# Login | ||
page.goto(f"{BASE_URL}/login/[email protected]") | ||
page.goto(f"{BASE_URL}/login/[email protected]") | ||
|
||
# Change language in settings | ||
page.goto(f"{BASE_URL}/settings/") | ||
# Get language select. | ||
select = page.locator("select#language_selection") | ||
# Check default language is english. | ||
expect(select).to_have_value("fr") | ||
# Switch to French. | ||
select.select_option("en") | ||
# Check french is selected. | ||
expect(select).to_have_value("en") | ||
|
||
# Check default jobs search columns. | ||
page.goto(f"{BASE_URL}/jobs/search") | ||
headers = page.locator("table#search_table thead tr th") | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
import random | ||
|
||
|
||
def _get_test_user_props(mtclient, fake_data): | ||
# Find an entry that's associated with the user that's currently logged in. | ||
# This becomes the ground truth against which we compare the retrieved user props. | ||
LD_candidates = [ | ||
D_job_user_props_entry | ||
for D_job_user_props_entry in fake_data["job_user_props"] | ||
if ( | ||
D_job_user_props_entry["mila_email_username"] == mtclient.email | ||
and len(D_job_user_props_entry["props"]) > 0 | ||
) | ||
] | ||
assert ( | ||
len(LD_candidates) > 0 | ||
), "There should be at least one job_user_props entry for the user that's currently logged in." | ||
D_job_user_props_entry = random.choice(LD_candidates) | ||
|
||
job_id = D_job_user_props_entry["job_id"] | ||
cluster_name = D_job_user_props_entry["cluster_name"] | ||
original_props = D_job_user_props_entry["props"] | ||
return job_id, cluster_name, original_props | ||
|
||
|
||
def test_cw_tools_get_user_props(mtclient, fake_data): | ||
job_id, cluster_name, original_props = _get_test_user_props(mtclient, fake_data) | ||
props = mtclient.get_user_props(job_id, cluster_name) | ||
assert original_props == props | ||
|
||
|
||
def test_cw_tools_set_user_props(mtclient, fake_data): | ||
job_id, cluster_name, original_props = _get_test_user_props(mtclient, fake_data) | ||
|
||
new_prop_name = "a new name" | ||
assert new_prop_name not in original_props | ||
props = mtclient.set_user_props(job_id, cluster_name, {new_prop_name: "a new prop"}) | ||
assert props == {**original_props, new_prop_name: "a new prop"} | ||
|
||
|
||
def test_cw_tools_delete_user_props(mtclient, fake_data): | ||
job_id, cluster_name, original_props = _get_test_user_props(mtclient, fake_data) | ||
|
||
new_props = {"a new name": "a new prop", "aa": "aa", "bb": "bb", "cc": "cc"} | ||
gyom marked this conversation as resolved.
Show resolved
Hide resolved
|
||
for new_prop_name in new_props.keys(): | ||
assert new_prop_name not in original_props | ||
|
||
props = mtclient.set_user_props(job_id, cluster_name, new_props) | ||
assert len(props) == len(original_props) + len(new_props) | ||
assert props == {**original_props, **new_props} | ||
|
||
assert mtclient.delete_user_props(job_id, cluster_name, "aa") == "" | ||
props = mtclient.get_user_props(job_id, cluster_name) | ||
assert len(props) == len(original_props) + len(new_props) - 1 | ||
assert props == { | ||
**original_props, | ||
"a new name": "a new prop", | ||
"bb": "bb", | ||
"cc": "cc", | ||
} | ||
|
||
assert mtclient.delete_user_props(job_id, cluster_name, ["a new name"]) == "" | ||
props = mtclient.get_user_props(job_id, cluster_name) | ||
assert len(props) == len(original_props) + len(new_props) - 2 | ||
assert props == {**original_props, "bb": "bb", "cc": "cc"} | ||
|
||
assert mtclient.delete_user_props(job_id, cluster_name, ["bb", "cc"]) == "" | ||
props = mtclient.get_user_props(job_id, cluster_name) | ||
assert props == original_props |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
from flask_login import current_user | ||
from ..db import get_db | ||
import json | ||
|
||
|
||
# Max length allowed for a JSON-string representing a job-user props dict. | ||
# Currently, 2 Mb. | ||
MAX_PROPS_LENGTH = 2 * 1024 * 1024 | ||
|
||
|
||
def get_user_props( | ||
job_id: str, cluster_name: str, mila_email_username: str = None | ||
) -> dict: | ||
""" | ||
Get job-user props. | ||
|
||
Parameters: | ||
job_id ID of job for which we want to get user props. | ||
cluster_name Name of cluster to which the job belongs. | ||
mila_email_username Optional email of user who sets the props we want to get. | ||
Default is current logged user. | ||
|
||
Returns: | ||
Dictionary of user-props, empty if no props were found. | ||
Each prop is a key-value pair in returned dictionary. | ||
""" | ||
# Get matching MongoDB document. | ||
props_dict = _get_user_props_document(job_id, cluster_name, mila_email_username) | ||
# Return props if available. | ||
return props_dict.get("props", {}) | ||
|
||
|
||
def set_user_props( | ||
job_id: str, cluster_name: str, updates: dict, mila_email_username: str = None | ||
) -> dict: | ||
""" | ||
Update job-user-props. | ||
|
||
Parameters: | ||
job_id ID of job for which we want to update user props. | ||
cluster_name Name of cluster to which the job belongs. | ||
updates Dictionary of props to add. | ||
Each key-value represents a prop. | ||
mila_email_username Optional email of user who wants to update his props. | ||
Default is current logged user. | ||
|
||
Returns: | ||
Dictionary of Updated job-user props. | ||
""" | ||
# Get previous props and check that | ||
# previous + updates props do not exceed a size limit. | ||
previous_doc = _get_user_props_document(job_id, cluster_name, mila_email_username) | ||
previous_props = previous_doc.get("props", {}) | ||
new_props = previous_props.copy() | ||
new_props.update(updates) | ||
if _get_dict_size(new_props) > MAX_PROPS_LENGTH: | ||
raise ValueError( | ||
f"Too huge job-user props: {_get_megabytes(MAX_PROPS_LENGTH)} Mbytes " | ||
f"(previous: {_get_megabytes(_get_dict_size(previous_props))} Mbytes, " | ||
f"updates: {_get_megabytes(_get_dict_size(updates))} Mbytes)" | ||
) | ||
|
||
# Update or insert job-user props. | ||
mc = get_db() | ||
db = mc["job_user_props"] | ||
if "_id" in previous_doc: | ||
db.update_one({"_id": previous_doc["_id"]}, {"$set": {"props": new_props}}) | ||
else: | ||
db.insert_one( | ||
{ | ||
"job_id": job_id, | ||
"cluster_name": str(cluster_name), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. C'est bizarre qu'on veuille faire Si on ne fait pas confiance à cette valeur pour être une string, alors il fallait produire un message d'erreur avant ça. Rendus ici, on devrait quasiment plus rejeter l'appel ou quelque chose si on a quoique ce soit d'autre qu'une string pour le La réponse c'est que l'endroit pour faire la conversion de There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. En effet, c'est une précaution excessive et inutile. Retirée: 73477ee |
||
"mila_email_username": current_user.mila_email_username, | ||
"props": new_props, | ||
} | ||
) | ||
|
||
return new_props | ||
|
||
|
||
def delete_user_props(job_id, cluster_name, key_or_keys, mila_email_username=None): | ||
""" | ||
Delete some job-user props. | ||
|
||
Parameters: | ||
job_id ID of job for which we want to delete some user props. | ||
cluster_name Name of cluster to which the job belongs. | ||
key_or_keys Either a key or a sequence of keys, | ||
representing prop names to delete. | ||
mila_email_username Optional email of user who wants to delete props. | ||
Default is current logged user. | ||
""" | ||
# Parsekey_or_keys to get keys to delete. | ||
if isinstance(key_or_keys, str): | ||
keys = [key_or_keys] | ||
else: | ||
assert isinstance(key_or_keys, (list, tuple, set)) | ||
keys = list(key_or_keys) | ||
|
||
# Get previous props | ||
previous_doc = _get_user_props_document(job_id, cluster_name, mila_email_username) | ||
previous_props = previous_doc.get("props", {}) | ||
new_props = previous_props.copy() | ||
# Remove keys | ||
for key in keys: | ||
new_props.pop(key, None) | ||
# Register in MongoDB | ||
if len(new_props) < len(previous_props): | ||
mc = get_db() | ||
db = mc["job_user_props"] | ||
db.update_one({"_id": previous_doc["_id"]}, {"$set": {"props": new_props}}) | ||
|
||
|
||
def _get_user_props_document( | ||
job_id: str, cluster_name: str, mila_email_username: str = None | ||
) -> dict: | ||
""" | ||
Get MongoDB document representing job-user props corresponding to given parameters. | ||
|
||
Parameters: | ||
job_id ID of job for which we want to get user props. | ||
cluster_name Name of cluster to which the job belongs. | ||
mila_email_username Optional email of user who sets the props we want to get. | ||
Default is current logged user. | ||
|
||
Returns: | ||
MongoDB document representing job-user props. | ||
Document is a dictionary with following format: | ||
{ | ||
job_id: str # ID of job associated to props. | ||
cluster_name: str # cluster name of job associated to props. | ||
mila_email_username: str # user who created these props. | ||
props: dict # actual user-props, each prop is a key-value. | ||
} | ||
""" | ||
mc = get_db() | ||
result = list( | ||
mc["job_user_props"].find( | ||
{ | ||
"job_id": job_id, | ||
"cluster_name": str(cluster_name), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Même commentaire qu'avant. Comment est-ce qu'une valeur de There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Retirée, ce n'est en effet pas nécessaire: 73477ee |
||
"mila_email_username": ( | ||
mila_email_username or current_user.mila_email_username | ||
), | ||
} | ||
) | ||
) | ||
if not result: | ||
return {} | ||
else: | ||
assert len(result) == 1 | ||
(props,) = result | ||
return props | ||
|
||
|
||
def _get_dict_size(dct: dict) -> int: | ||
"""Get length of JSON-string representation for given dictionary.""" | ||
return len(json.dumps(dct)) | ||
|
||
|
||
def _get_megabytes(size: int) -> float: | ||
"""Convert bytes to megabytes.""" | ||
return size / (1024 * 1024) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Minor typo in the comments :
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed! 041834e