Skip to content

Commit

Permalink
Merge pull request #192 from notoraptor/cw-445-display-user-props-on-…
Browse files Browse the repository at this point in the history
…job-page

[CW-445] Display user props on the page of a single job on the web interface
  • Loading branch information
soline-b authored Jun 12, 2024
2 parents 8d9266e + ef1d031 commit d757803
Show file tree
Hide file tree
Showing 5 changed files with 256 additions and 2 deletions.
2 changes: 2 additions & 0 deletions clockwork_frontend_test/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
"""Import fixture fake data so that we can use it in tests from this module."""
from test_common.fake_data import fake_data
190 changes: 190 additions & 0 deletions clockwork_frontend_test/test_jobs_one.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import random
from playwright.sync_api import Page, expect

from clockwork_frontend_test.utils import BASE_URL


def _get_job_with_user_props(fake_data, mila_email_username):
"""Get a job from fake data that does have user props for given mila email username."""
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"] == mila_email_username
and len(D_job_user_props_entry["props"]) > 0
)
]
assert (
len(LD_candidates) > 0
), f"There should be at least one job_user_props entry for user {mila_email_username}."
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 _get_job_without_user_props(fake_data, mila_email_username):
"""Get a job from fake data that does not have any user prop for given mila email username."""
# Collect keys (job_id, cluster_name) for all jobs in fake data which do have user props.
jobs_with_user_props = {
(D_job_user_props_entry["job_id"], D_job_user_props_entry["cluster_name"])
for D_job_user_props_entry in fake_data["job_user_props"]
if (
D_job_user_props_entry["mila_email_username"] == mila_email_username
and len(D_job_user_props_entry["props"]) > 0
)
}
assert jobs_with_user_props
# Take the first job in fake data which is not present in jobs collected above.
for job in fake_data["jobs"]:
if (
job["slurm"]["job_id"],
job["slurm"]["cluster_name"],
) not in jobs_with_user_props:
return job
else:
raise AssertionError(
f"We should have at least one job without user props for user {mila_email_username}"
)


def test_job_no_user_props(page: Page, fake_data):
mila_email_username = "[email protected]"
# Login
page.goto(f"{BASE_URL}/login/testing?user_id={mila_email_username}")
# Go to settings to set language to english.
page.goto(f"{BASE_URL}/settings/")
# Get language select.
select = page.locator("select#language_selection")
# Switch to english.
select.select_option("en")
# Check english is selected.
expect(select).to_have_value("en")

job = _get_job_without_user_props(fake_data, mila_email_username)
job_id = job["slurm"]["job_id"]
# Go to job page
page.goto(f"{BASE_URL}/jobs/one?job_id={job_id}")
# Check we are on job page
expect(page.get_by_text(f"Single job {job_id}")).to_be_visible()
# Check user props section is well displayed
expect(page.get_by_text(f"Your props for job {job_id}")).to_be_visible()
# Check user props section does not contain any prop
expect(
page.get_by_text("You have not defined any user prop for this job.")
).to_be_visible()
props_table = page.locator("table#user_props_table")
expect(props_table).to_have_count(0)

# Back to default settings
page.goto(f"{BASE_URL}/settings/")
select = page.locator("select#language_selection")
select.select_option("fr")
expect(select).to_have_value("fr")


def test_job_with_user_props(page: Page, fake_data):
mila_email_username = "[email protected]"
# Login
page.goto(f"{BASE_URL}/login/testing?user_id={mila_email_username}")
# Go to settings to set language to english.
page.goto(f"{BASE_URL}/settings/")
# Get language select.
select = page.locator("select#language_selection")
# Switch to english.
select.select_option("en")
# Check english is selected.
expect(select).to_have_value("en")

job_id, _, user_props = _get_job_with_user_props(fake_data, mila_email_username)
assert user_props

# Go to job page
page.goto(f"{BASE_URL}/jobs/one?job_id={job_id}")
# Check we are on job page
expect(page.get_by_text(f"Single job {job_id}")).to_be_visible()
expect(page.get_by_text(f"Your props for job {job_id}")).to_be_visible()
# Check we do have user props displayed
expect(
page.get_by_text(
"The array below displays the user props you defined for this job."
)
).to_be_visible()

# Check displayed user props

props_table = page.locator("table#user_props_table")
expect(props_table).to_have_count(1)
rows = props_table.locator("tbody tr")
expect(rows).to_have_count(len(user_props))

for i, (k, v) in enumerate(sorted(user_props.items(), key=lambda e: e[0])):
row = rows.nth(i)
cols = row.locator("td")
expect(cols).to_have_count(2)
expect(cols.nth(0)).to_contain_text(k)
expect(cols.nth(1)).to_contain_text(str(v))

# Back to default settings
page.goto(f"{BASE_URL}/settings/")
select = page.locator("select#language_selection")
select.select_option("fr")
expect(select).to_have_value("fr")


def test_job_no_user_props_fr(page: Page, fake_data):
mila_email_username = "[email protected]"
# Login
page.goto(f"{BASE_URL}/login/testing?user_id={mila_email_username}")

job = _get_job_without_user_props(fake_data, mila_email_username)
job_id = job["slurm"]["job_id"]
# Go to job page
page.goto(f"{BASE_URL}/jobs/one?job_id={job_id}")
# Check we are on job page
expect(page.locator("h1").get_by_text(f"Job {job_id}")).to_be_visible()
# Check user props section is well displayed
expect(page.get_by_text(f"Vos propriétés pour le job {job_id}")).to_be_visible()
# Check user props section does not contain any prop
expect(
page.get_by_text("Vous n'avez défini aucune propriété pour ce job.")
).to_be_visible()
props_table = page.locator("table#user_props_table")
expect(props_table).to_have_count(0)


def test_job_with_user_props_fr(page: Page, fake_data):
mila_email_username = "[email protected]"
# Login
page.goto(f"{BASE_URL}/login/testing?user_id={mila_email_username}")

job_id, _, user_props = _get_job_with_user_props(fake_data, mila_email_username)
assert user_props

# Go to job page
page.goto(f"{BASE_URL}/jobs/one?job_id={job_id}")
# Check we are on job page
expect(page.locator("h1").get_by_text(f"Job {job_id}")).to_be_visible()
expect(page.get_by_text(f"Vos propriétés pour le job {job_id}")).to_be_visible()
# Check we do have user props displayed
expect(
page.get_by_text(
"Le tableau ci-dessous affiche les propriétés que vous avez définies pour ce job."
)
).to_be_visible()

# Check displayed user props

props_table = page.locator("table#user_props_table")
expect(props_table).to_have_count(1)
rows = props_table.locator("tbody tr")
expect(rows).to_have_count(len(user_props))

for i, (k, v) in enumerate(sorted(user_props.items(), key=lambda e: e[0])):
row = rows.nth(i)
cols = row.locator("td")
expect(cols).to_have_count(2)
expect(cols.nth(0)).to_contain_text(k)
expect(cols.nth(1)).to_contain_text(str(v))
10 changes: 10 additions & 0 deletions clockwork_web/browser_routes/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,9 +276,19 @@ def route_one():
"@"
)[0]

# Get job user props and sort them alphabetically.
# NB: Currently, there is no constraint on prop value type,
# so it may be a basic type (e.g. str or int), or even an array.
# For simplification, we convert any value to its string representation.
LP_job_user_props = sorted(
((k, str(v)) for k, v in D_job.get("job_user_props", {}).items()),
key=lambda e: e[0],
)

return render_template_with_user_settings(
"single_job.html",
LP_single_job_slurm=LP_single_job_slurm,
LP_job_user_props=LP_job_user_props,
D_single_job_cw=D_single_job_cw,
job_id=job_ids[0],
mila_email_username=current_user.mila_email_username,
Expand Down
24 changes: 24 additions & 0 deletions clockwork_web/static/locales/fr/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -661,3 +661,27 @@ msgstr "Nombre de jobs"
#~ msgid "Director"
#~ msgstr "Directeur"

#: clockwork_web/templates/single_job.html:84
#, python-format
msgid "Your props for job %(job_id)s"
msgstr "Vos propriétés pour le job %(job_id)s"

#: clockwork_web/templates/single_job.html:89
#, python-format
msgid "The array below displays the user props you defined for this job."
msgstr "Le tableau ci-dessous affiche les propriétés que vous avez définies pour ce job."

#: clockwork_web/templates/single_job.html:93
#, python-format
msgid "Prop key"
msgstr "Nom de la propriété"

#: clockwork_web/templates/single_job.html:94
#, python-format
msgid "Prop value"
msgstr "Valeur de la propriété"

#: clockwork_web/templates/single_job.html:107
#, python-format
msgid "You have not defined any user prop for this job."
msgstr "Vous n'avez défini aucune propriété pour ce job."
32 changes: 30 additions & 2 deletions clockwork_web/templates/single_job.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

{% endblock %}
{% block content %}

<div class="container">
<div class="row">
<div class="col-sm-12" id="changeme">
Expand Down Expand Up @@ -44,7 +44,7 @@ <h1>{{ gettext("Single job %(job_id)s", job_id=job_id) }}</h1>
{% if k in ['batch_host'] and v != None %}
<!-- TODO : Implement this link because it doesn't work now. -->
<td><a href="/nodes/single_node/{{v}}"> {{v}} </a></td>

{% elif k == "cluster_name" %}
<td><a href="/clusters/one?cluster_name={{v}}"> {{v}} </a></td>

Expand Down Expand Up @@ -78,6 +78,34 @@ <h1>{{ gettext("Single job %(job_id)s", job_id=job_id) }}</h1>
{% endfor %}
</tbody>
</table>

<div class="row justify-content-between">
<div class="col-8">
<h2><strong>{{ gettext("Your props for job %(job_id)s", job_id=job_id) }}</strong></h2>
</div>
</div>

{% if LP_job_user_props %}
<p>{{ gettext("The array below displays the user props you defined for this job.") }}</p>
<table class="table table-striped table-hover table-responsive" data-sortable id="user_props_table">
<thead>
<tr>
<th>{{gettext("Prop key")}}</th>
<th>{{ gettext("Prop value") }}</th>
</tr>
</thead>
<tbody>
{% for (k, v) in LP_job_user_props %}
<tr>
<td>{{k}}</td>
<td>{{v}}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>{{ gettext("You have not defined any user prop for this job.") }}</p>
{% endif %}
</div>
</div>
</div>
Expand Down

0 comments on commit d757803

Please sign in to comment.