Skip to content

Commit

Permalink
[CW-498] Implémenter le dashboard du panneau d'administration
Browse files Browse the repository at this point in the history
  • Loading branch information
notoraptor committed Oct 21, 2024
1 parent eaa22c9 commit 6930770
Show file tree
Hide file tree
Showing 7 changed files with 521 additions and 6 deletions.
233 changes: 233 additions & 0 deletions clockwork_frontend_test/test_admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
from slurm_state.mongo_client import get_mongo_client
from slurm_state.config import get_config
from playwright.sync_api import Page, expect

from clockwork_frontend_test.utils import BASE_URL


class UsersFactory:
def __init__(self):
client = get_mongo_client()
db = client[get_config("mongo.database_name")]
self.users_collection = db["users"]

def _get_users(self) -> list[dict]:
return sorted(
self.users_collection.find({}), key=lambda user: user["mila_email_username"]
)

def get_an_admin_user(self) -> dict:
admin_users = [
user for user in self._get_users() if user.get("admin_access", False)
]
return admin_users[0]

def get_a_non_admin_user(self, exclude=()):
users = [
user for user in self._get_users() if not user.get("admin_access", False)
]
if exclude:
users = [
user for user in users if user["mila_email_username"] not in exclude
]
return users[0]


def test_admin_access_for_admin(page: Page):
users_factory = UsersFactory()
admin_user = users_factory.get_an_admin_user()
admin_email = admin_user["mila_email_username"]
random_user = users_factory.get_a_non_admin_user(exclude=admin_email)
# login
page.goto(f"{BASE_URL}/login/testing?user_id={admin_email}")
# Go to settings to set language to english if necessary.
page.goto(f"{BASE_URL}/settings/")
# Get language select.
select = page.locator("select#language_selection")
# Switch to english.
lang = select.input_value()
if lang != "en":
select.select_option("en")
# Check english is selected.
expect(select).to_have_value("en")

page.goto(f"{BASE_URL}/admin/panel")
expect(page.get_by_text("Administration panel")).to_have_count(1)
page.goto(f"{BASE_URL}/admin/users")
expect(page.get_by_text("Administration panel / Users")).to_have_count(1)
page.goto(f"{BASE_URL}/admin/user?username={random_user['mila_email_username']}")
expect(
page.get_by_text(
f"Administration panel / Users / {random_user['mila_email_username']}"
)
).to_have_count(1)

# Back to default language
if lang != "en":
page.goto(f"{BASE_URL}/settings/")
select = page.locator("select#language_selection")
select.select_option(lang)
expect(select).to_have_value(lang)


def test_admin_access_for_non_admin(page: Page):
users_factory = UsersFactory()
random_user = users_factory.get_a_non_admin_user()
random_email = random_user["mila_email_username"]
# Login
page.goto(f"{BASE_URL}/login/testing?user_id={random_email}")
# Go to settings to set language to english if necessary.
page.goto(f"{BASE_URL}/settings/")
# Get language select.
select = page.locator("select#language_selection")
# Switch to english.
lang = select.input_value()
if lang != "en":
select.select_option("en")
# Check english is selected.
expect(select).to_have_value("en")

page.goto(f"{BASE_URL}/admin/panel")
expect(page.get_by_text("Administration panel")).to_have_count(0)
expect(page.get_by_text("Authorization error.")).to_have_count(1)
page.goto(f"{BASE_URL}/admin/users")
expect(page.get_by_text("Administration panel / Users")).to_have_count(0)
expect(page.get_by_text("Authorization error.")).to_have_count(1)
page.goto(f"{BASE_URL}/admin/user?username={random_user['mila_email_username']}")
expect(
page.get_by_text(
f"Administration panel / Users / {random_user['mila_email_username']}"
)
).to_have_count(0)
expect(page.get_by_text("Authorization error.")).to_have_count(1)

# Back to default language
if lang != "en":
page.goto(f"{BASE_URL}/settings/")
select = page.locator("select#language_selection")
select.select_option(lang)
expect(select).to_have_value(lang)


def test_admin_pages(page: Page):
users_factory = UsersFactory()
admin_user = users_factory.get_an_admin_user()
admin_email = admin_user["mila_email_username"]

# Login
page.goto(f"{BASE_URL}/login/testing?user_id={admin_email}")
# Go to settings to set language to english if necessary.
page.goto(f"{BASE_URL}/settings/")
# Get language select.
select = page.locator("select#language_selection")
# Switch to english.
lang = select.input_value()
if lang != "en":
select.select_option("en")
# Check english is selected.
expect(select).to_have_value("en")

# Select "Admin" menu and click on it
menu_external = page.locator(
"#navbarSupportedContent li.nav-item.dropdown", has_text="EXTERNAL"
)
expect(menu_external).to_have_count(1)
menu_external.click()
link_admin = menu_external.locator(
"ul.dropdown-menu.show li a.dropdown-item", has_text="Admin"
)
expect(link_admin).to_have_count(1)
# CLicking on "Admin" link opens a new page we must capture.
# Playwright doc here: https://playwright.dev/python/docs/pages#handling-new-pages
with page.context.expect_page() as new_page_info:
link_admin.click()
new_page = new_page_info.value
expect(new_page).to_have_url(f"{BASE_URL}/admin/panel")

# Go to page "Manage users"
link_manage_users = new_page.locator("a.btn", has_text="Manage users")
expect(link_manage_users).to_have_count(1)
link_manage_users.click()
expect(new_page).to_have_url(f"{BASE_URL}/admin/users")

# Select user data from table first row
rows = new_page.locator("table tbody tr")
row = rows.nth(0)
expect(row).to_have_count(1)
columns = row.locator("td")
user_email = columns.nth(0).text_content().strip()
user_mila_cluster_id = columns.nth(1).text_content().strip()
user_drac_cluster_id = columns.nth(2).text_content().strip()
user_edit_button = columns.nth(3).locator("a")
assert user_email.endswith("@mila.quebec")
assert user_mila_cluster_id
assert user_drac_cluster_id
assert user_mila_cluster_id != user_drac_cluster_id
expect(user_edit_button).to_have_text("edit")

# Go to edition page for selected user
user_edit_button.click()
expect(new_page).to_have_url(f"{BASE_URL}/admin/user?username={user_email}")

# Check default input values on edition page
inputs = new_page.locator("table input")
input_mila_cluster_id = inputs.nth(0)
input_drac_cluster_id = inputs.nth(1)
expect(input_mila_cluster_id).to_have_attribute("name", "mila_cluster_username")
expect(input_mila_cluster_id).to_have_value(user_mila_cluster_id)
expect(input_drac_cluster_id).to_have_attribute("name", "cc_account_username")
expect(input_drac_cluster_id).to_have_value(user_drac_cluster_id)

# Edit user
new_user_mila_cluster_id = f"{user_mila_cluster_id}_new_mila"
new_user_drac_cluster_id = f"{user_drac_cluster_id}_new_drac"
input_mila_cluster_id.fill(new_user_mila_cluster_id)
input_drac_cluster_id.fill(new_user_drac_cluster_id)
# Submit form
button_submit = new_page.locator("button", has_text="Save")
expect(button_submit).to_have_count(1)
button_submit.click()
expect(new_page).to_have_url(f"{BASE_URL}/admin/user?username={user_email}")
expect(new_page.get_by_text("User successfully updated.")).to_have_count(1)

# Check new input values
inputs = new_page.locator("table input")
input_mila_cluster_id = inputs.nth(0)
input_drac_cluster_id = inputs.nth(1)
expect(input_mila_cluster_id).to_have_attribute("name", "mila_cluster_username")
expect(input_mila_cluster_id).to_have_value(new_user_mila_cluster_id)
expect(input_drac_cluster_id).to_have_attribute("name", "cc_account_username")
expect(input_drac_cluster_id).to_have_value(new_user_drac_cluster_id)

# Edit user back to default values
input_mila_cluster_id.fill(user_mila_cluster_id)
input_drac_cluster_id.fill(user_drac_cluster_id)
new_page.locator("button", has_text="Save").click()
expect(new_page).to_have_url(f"{BASE_URL}/admin/user?username={user_email}")
expect(new_page.get_by_text("User successfully updated.")).to_have_count(1)
inputs = new_page.locator("table input")
input_mila_cluster_id = inputs.nth(0)
input_drac_cluster_id = inputs.nth(1)
expect(input_mila_cluster_id).to_have_attribute("name", "mila_cluster_username")
expect(input_mila_cluster_id).to_have_value(user_mila_cluster_id)
expect(input_drac_cluster_id).to_have_attribute("name", "cc_account_username")
expect(input_drac_cluster_id).to_have_value(user_drac_cluster_id)

# Check what happens when submitting form with default values
new_page.locator("button", has_text="Save").click()
expect(new_page).to_have_url(f"{BASE_URL}/admin/user?username={user_email}")
expect(new_page.get_by_text("No changes for this user.")).to_have_count(1)
inputs = new_page.locator("table input")
input_mila_cluster_id = inputs.nth(0)
input_drac_cluster_id = inputs.nth(1)
expect(input_mila_cluster_id).to_have_attribute("name", "mila_cluster_username")
expect(input_mila_cluster_id).to_have_value(user_mila_cluster_id)
expect(input_drac_cluster_id).to_have_attribute("name", "cc_account_username")
expect(input_drac_cluster_id).to_have_value(user_drac_cluster_id)

# Back to default language
if lang != "en":
page.goto(f"{BASE_URL}/settings/")
select = page.locator("select#language_selection")
select.select_option(lang)
expect(select).to_have_value(lang)
122 changes: 120 additions & 2 deletions clockwork_web/browser_routes/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@
from flask import Blueprint

from clockwork_web.core.utils import to_boolean, get_custom_array_from_request_args
from clockwork_web.core.users_helper import render_template_with_user_settings
from clockwork_web.core.users_helper import (
render_template_with_user_settings,
get_users,
)
from ..db import get_db

flask_api = Blueprint("admin", __name__)

Expand Down Expand Up @@ -62,7 +66,7 @@ def decorated(*args, **kwargs):
@login_required
@admin_access_required
def panel():
""" """
"""Admin home page"""
logging.info(
f"clockwork browser route: /admin/panel - current_user={current_user.mila_email_username}"
)
Expand All @@ -75,3 +79,117 @@ def panel():
mila_email_username=current_user.mila_email_username,
previous_request_args=previous_request_args,
)


@flask_api.route("/users")
@login_required
@admin_access_required
def users():
"""Admin users page"""
logging.info(
f"clockwork browser route: /admin/users - current_user={current_user.mila_email_username}"
)

# Initialize the request arguments (it is further transferred to the HTML)
previous_request_args = {}

# Get users
LD_users = sorted(get_users(), key=lambda user: user["mila_email_username"])

return render_template_with_user_settings(
"admin_users.html",
mila_email_username=current_user.mila_email_username,
previous_request_args=previous_request_args,
LD_users=LD_users,
)


@flask_api.route("/user", methods=["POST", "GET"])
@login_required
@admin_access_required
def user():
"""
Admin page to edit a specific user
User to edit is passed as GEt parameter `username`
Edited values (mila cluster ID and DRAC cluster ID)
are passed as POST form.
"""
logging.info(
f"clockwork browser route: /admin/user - current_user={current_user.mila_email_username}"
)

# Initialize the request arguments (it is further transferred to the HTML)
previous_request_args = {}

# Get user
mila_email_username = request.args.get("username", None)
previous_request_args["username"] = mila_email_username

if mila_email_username is None:
return (
render_template_with_user_settings(
"error.html",
error_msg=gettext("Missing argument username."),
previous_request_args=previous_request_args,
),
400, # Bad Request
)

mc = get_db()
users_collection = mc["users"]
D_users = list(users_collection.find({"mila_email_username": mila_email_username}))

if len(D_users) != 1:
return (
render_template_with_user_settings(
"error.html",
error_msg=gettext(f"Cannot find username: {mila_email_username}"),
previous_request_args=previous_request_args,
),
400, # Bad Request
)

(D_user,) = D_users

user_edit_status = ""
if request.method == "POST":
# Handle edition form
old_mila_cluster_username = D_user["mila_cluster_username"]
old_cc_account_username = D_user["cc_account_username"]
new_mila_cluster_username = request.form["mila_cluster_username"].strip()
new_cc_account_username = request.form["cc_account_username"].strip()

if not new_mila_cluster_username:
new_mila_cluster_username = old_mila_cluster_username

if not new_cc_account_username:
new_cc_account_username = old_cc_account_username

if (
new_mila_cluster_username != old_mila_cluster_username
or new_cc_account_username != old_cc_account_username
):
users_collection.update_one(
{"mila_email_username": D_user["mila_email_username"]},
{
"$set": {
"mila_cluster_username": new_mila_cluster_username,
"cc_account_username": new_cc_account_username,
}
},
)
D_user["mila_cluster_username"] = new_mila_cluster_username
D_user["cc_account_username"] = new_cc_account_username
user_edit_status = "User successfully updated."
else:
user_edit_status = "No changes for this user."

return render_template_with_user_settings(
"admin_user.html",
mila_email_username=current_user.mila_email_username,
previous_request_args=previous_request_args,
D_user=D_user,
user_edit_status=user_edit_status,
)
19 changes: 15 additions & 4 deletions clockwork_web/templates/admin_panel.html
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,19 @@

{% endblock %}
{% block content %}
<div class="cc_subheader_banner">
<h1>Hello, admin world !</h1>
This page is a placeholder.
</div>
<div class="container">
<div class="row">
<div class="col-sm-12">
<div class="title float-start">
<i class="fa-solid fa-user"></i>
<h1>{{ gettext("Administration panel") }}</h1>
</div>
</div>
</div>
<div class="row">
<a class="btn btn-turquoise" href="{{url_for('admin.users')}}">
{{ gettext("Manage users") }}
</a>
</div>
</div>
{% endblock %}
Loading

0 comments on commit 6930770

Please sign in to comment.