diff --git a/clockwork_frontend_test/conftest.py b/clockwork_frontend_test/conftest.py new file mode 100644 index 00000000..1ef8bbcb --- /dev/null +++ b/clockwork_frontend_test/conftest.py @@ -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 diff --git a/clockwork_frontend_test/test_jobs_one.py b/clockwork_frontend_test/test_jobs_one.py new file mode 100644 index 00000000..2c5bfdcf --- /dev/null +++ b/clockwork_frontend_test/test_jobs_one.py @@ -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 = "student01@mila.quebec" + # 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 = "student01@mila.quebec" + # 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 = "student01@mila.quebec" + # 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 = "student01@mila.quebec" + # 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)) diff --git a/clockwork_web/browser_routes/jobs.py b/clockwork_web/browser_routes/jobs.py index 72bbfc18..9a49d5d9 100644 --- a/clockwork_web/browser_routes/jobs.py +++ b/clockwork_web/browser_routes/jobs.py @@ -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, diff --git a/clockwork_web/static/locales/fr/LC_MESSAGES/messages.po b/clockwork_web/static/locales/fr/LC_MESSAGES/messages.po index 8f133011..798e8be1 100644 --- a/clockwork_web/static/locales/fr/LC_MESSAGES/messages.po +++ b/clockwork_web/static/locales/fr/LC_MESSAGES/messages.po @@ -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." diff --git a/clockwork_web/templates/single_job.html b/clockwork_web/templates/single_job.html index e8eca42a..560d7b6a 100644 --- a/clockwork_web/templates/single_job.html +++ b/clockwork_web/templates/single_job.html @@ -15,7 +15,7 @@ {% endblock %} {% block content %} - +
@@ -44,7 +44,7 @@

{{ gettext("Single job %(job_id)s", job_id=job_id) }}

{% if k in ['batch_host'] and v != None %} {{v}} - + {% elif k == "cluster_name" %} {{v}} @@ -78,6 +78,34 @@

{{ gettext("Single job %(job_id)s", job_id=job_id) }}

{% endfor %} + +
+
+

{{ gettext("Your props for job %(job_id)s", job_id=job_id) }}

+
+
+ + {% if LP_job_user_props %} +

{{ gettext("The array below displays the user props you defined for this job.") }}

+ + + + + + + + + {% for (k, v) in LP_job_user_props %} + + + + + {% endfor %} + +
{{gettext("Prop key")}}{{ gettext("Prop value") }}
{{k}}{{v}}
+ {% else %} +

{{ gettext("You have not defined any user prop for this job.") }}

+ {% endif %}