Skip to content

Commit

Permalink
Add algorithm overview for organization (#437)
Browse files Browse the repository at this point in the history
  • Loading branch information
uittenbroekrobbert authored Dec 18, 2024
2 parents 12b2868 + f3923f3 commit 4347a83
Show file tree
Hide file tree
Showing 20 changed files with 375 additions and 158 deletions.
8 changes: 6 additions & 2 deletions amt/api/forms/algorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,11 @@


async def get_algorithm_form(
id: str, translations: NullTranslations, organizations_service: OrganizationsService, user_id: str | UUID | None
id: str,
translations: NullTranslations,
organizations_service: OrganizationsService,
user_id: str | UUID | None,
organization_id: int | None,
) -> WebForm:
_ = translations.gettext

Expand All @@ -28,7 +32,7 @@ async def get_algorithm_form(
WebFormOption(value=str(organization.id), display_value=organization.name)
for organization in my_organizations
],
default_value="",
default_value=str(organization_id),
group="1",
),
]
Expand Down
73 changes: 46 additions & 27 deletions amt/api/routes/algorithms.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,33 +39,9 @@ async def get_root(
) -> HTMLResponse:
filters, drop_filters, localized_filters, sort_by = get_filters_and_sort_by(request)

amount_algorithm_systems: int = 0
if display_type == "LIFECYCLE":
algorithms: dict[str, list[Algorithm]] = {}

# When the lifecycle filter is active, only show these algorithms
if "lifecycle" in filters:
for lifecycle in Lifecycles:
algorithms[lifecycle.name] = []
algorithms[filters["lifecycle"]] = await algorithms_service.paginate(
skip=skip, limit=limit, search=search, filters=filters, sort=sort_by
)
amount_algorithm_systems += len(algorithms[filters["lifecycle"]])
else:
for lifecycle in Lifecycles:
filters["lifecycle"] = lifecycle.name
algorithms[lifecycle.name] = await algorithms_service.paginate(
skip=skip, limit=limit, search=search, filters=filters, sort=sort_by
)
amount_algorithm_systems += len(algorithms[lifecycle.name])
else:
algorithms = await algorithms_service.paginate(
skip=skip, limit=limit, search=search, filters=filters, sort=sort_by
) # pyright: ignore [reportAssignmentType]
# todo: the lifecycle has to be 'localized', maybe for display 'Algorithm' should become a different object
for algorithm in algorithms:
algorithm.lifecycle = get_localized_lifecycle(algorithm.lifecycle, request) # pyright: ignore [reportAttributeAccessIssue, reportUnknownMemberType, reportUnknownArgumentType]
amount_algorithm_systems += len(algorithms)
algorithms, amount_algorithm_systems = await get_algorithms(
algorithms_service, display_type, filters, limit, request, search, skip, sort_by
)
next = skip + limit

sub_menu_items = resolve_navigation_items([Navigation.ALGORITHMS_OVERVIEW], request) # pyright: ignore [reportUnusedVariable] # noqa
Expand All @@ -86,6 +62,7 @@ async def get_root(
"filters": localized_filters,
"sort_by": sort_by,
"display_type": display_type,
"base_href": "/algorithms/",
}

if request.state.htmx and drop_filters:
Expand All @@ -96,11 +73,52 @@ async def get_root(
return templates.TemplateResponse(request, "algorithms/index.html.j2", context)


async def get_algorithms(
algorithms_service: AlgorithmsService,
display_type: str,
filters: dict[str, str],
limit: int,
request: Request,
search: str,
skip: int,
sort_by: dict[str, str],
) -> tuple[dict[str, list[Algorithm]], int | Any]:
amount_algorithm_systems: int = 0
if display_type == "LIFECYCLE":
algorithms: dict[str, list[Algorithm]] = {}

# When the lifecycle filter is active, only show these algorithms
if "lifecycle" in filters:
for lifecycle in Lifecycles:
algorithms[lifecycle.name] = []
algorithms[filters["lifecycle"]] = await algorithms_service.paginate(
skip=skip, limit=limit, search=search, filters=filters, sort=sort_by
)
amount_algorithm_systems += len(algorithms[filters["lifecycle"]])
else:
for lifecycle in Lifecycles:
filters["lifecycle"] = lifecycle.name
algorithms[lifecycle.name] = await algorithms_service.paginate(
skip=skip, limit=limit, search=search, filters=filters, sort=sort_by
)
amount_algorithm_systems += len(algorithms[lifecycle.name])
else:
algorithms = await algorithms_service.paginate(
skip=skip, limit=limit, search=search, filters=filters, sort=sort_by
) # pyright: ignore [reportAssignmentType]
# todo: the lifecycle has to be 'localized', maybe for display 'Algorithm' should become a different object
for algorithm in algorithms:
algorithm.lifecycle = get_localized_lifecycle(algorithm.lifecycle, request) # pyright: ignore [reportAttributeAccessIssue, reportUnknownMemberType, reportUnknownArgumentType]
amount_algorithm_systems += len(algorithms)
return algorithms, amount_algorithm_systems


@router.get("/new")
async def get_new(
request: Request,
instrument_service: Annotated[InstrumentsService, Depends(create_instrument_service)],
organizations_service: Annotated[OrganizationsService, Depends(OrganizationsService)],
organization_id: int = Query(None),
) -> HTMLResponse:
sub_menu_items = resolve_navigation_items([Navigation.ALGORITHMS_OVERVIEW], request) # pyright: ignore [reportUnusedVariable] # noqa
breadcrumbs = resolve_base_navigation_items([Navigation.ALGORITHMS_ROOT, Navigation.ALGORITHM_NEW], request)
Expand All @@ -116,6 +134,7 @@ async def get_new(
translations=get_current_translation(request),
organizations_service=organizations_service,
user_id=user["sub"] if user else None,
organization_id=organization_id,
)

template_files = get_template_files()
Expand Down
114 changes: 89 additions & 25 deletions amt/api/routes/organizations.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@

from amt.api.deps import templates
from amt.api.forms.organization import get_organization_form
from amt.api.group_by_category import get_localized_group_by_categories
from amt.api.lifecycles import get_localized_lifecycles
from amt.api.navigation import (
BaseNavigationItem,
Navigation,
Expand All @@ -17,7 +19,9 @@
resolve_navigation_items,
)
from amt.api.organization_filter_options import get_localized_organization_filters
from amt.api.risk_group import get_localized_risk_groups
from amt.api.routes.algorithm import UpdateFieldModel, set_path
from amt.api.routes.algorithms import get_algorithms
from amt.api.routes.shared import get_filters_and_sort_by
from amt.core.authorization import get_user
from amt.core.exceptions import AMTAuthorizationError, AMTNotFound, AMTRepositoryError
Expand All @@ -26,6 +30,7 @@
from amt.repositories.organizations import OrganizationsRepository
from amt.repositories.users import UsersRepository
from amt.schema.organization import OrganizationBase, OrganizationNew, OrganizationSlug, OrganizationUsers
from amt.services.algorithms import AlgorithmsService
from amt.services.organizations import OrganizationsService

router = APIRouter()
Expand Down Expand Up @@ -145,27 +150,34 @@ async def get_by_slug(
slug: str,
organizations_repository: Annotated[OrganizationsRepository, Depends(OrganizationsRepository)],
) -> HTMLResponse:
organization = await get_organization_or_error(organizations_repository, request, slug)
breadcrumbs = resolve_base_navigation_items(
[
Navigation.ORGANIZATIONS_ROOT,
BaseNavigationItem(custom_display_text=organization.name, url="/organizations/{organization_slug}"),
],
request,
)

tab_items = get_organization_tabs(request, organization_slug=slug)
context = {
"base_href": f"/organizations/{ slug }",
"organization": organization,
"tab_items": tab_items,
"breadcrumbs": breadcrumbs,
}
return templates.TemplateResponse(request, "organizations/home.html.j2", context)


async def get_organization_or_error(
organizations_repository: OrganizationsRepository, request: Request, slug: str
) -> Organization:
try:
organization = await organizations_repository.find_by_slug(slug)
request.state.path_variables = {"organization_slug": organization.slug}
breadcrumbs = resolve_base_navigation_items(
[
Navigation.ORGANIZATIONS_ROOT,
BaseNavigationItem(custom_display_text=organization.name, url="/organizations/{organization_slug}"),
],
request,
)

tab_items = get_organization_tabs(request, organization_slug=slug)
context = {
"base_href": f"/organizations/{ slug }",
"organization": organization,
"tab_items": tab_items,
"breadcrumbs": breadcrumbs,
}
return templates.TemplateResponse(request, "organizations/home.html.j2", context)
except AMTRepositoryError as e:
raise AMTNotFound from e
return organization


@router.get("/{slug}/edit/{path:path}")
Expand All @@ -177,7 +189,7 @@ async def get_organization_edit(
edit_type: str,
) -> HTMLResponse:
context: dict[str, Any] = {"base_href": f"/organizations/{ slug }"}
organization = await organizations_repository.find_by_slug(slug)
organization = await get_organization_or_error(organizations_repository, request, slug)
context.update({"path": path.replace("/", "."), "edit_type": edit_type, "object": organization})
return templates.TemplateResponse(request, "parts/edit_cell.html.j2", context)

Expand All @@ -195,7 +207,7 @@ async def get_organization_cancel(
"path": path.replace("/", "."),
"edit_type": edit_type,
}
organization = await organizations_repository.find_by_slug(slug)
organization = await get_organization_or_error(organizations_repository, request, slug)
context.update({"object": organization})
return templates.TemplateResponse(request, "parts/view_cell.html.j2", context)

Expand All @@ -214,7 +226,7 @@ async def get_organization_update(
"path": path.replace("/", "."),
"edit_type": edit_type,
}
organization = await organizations_repository.find_by_slug(slug)
organization = await get_organization_or_error(organizations_repository, request, slug)
context.update({"object": organization})

redirect_to: str | None = None
Expand Down Expand Up @@ -244,10 +256,62 @@ async def get_organization_update(


@router.get("/{slug}/algorithms")
async def get_algorithms(
async def show_algorithms(
request: Request,
algorithms_service: Annotated[AlgorithmsService, Depends(AlgorithmsService)],
organizations_repository: Annotated[OrganizationsRepository, Depends(OrganizationsRepository)],
slug: str,
skip: int = Query(0, ge=0),
limit: int = Query(5000, ge=1), # todo: fix infinite scroll
search: str = Query(""),
display_type: str = Query(""),
) -> HTMLResponse:
return templates.TemplateResponse(request, "pages/under_construction.html.j2", {})
organization = await get_organization_or_error(organizations_repository, request, slug)
filters, drop_filters, localized_filters, sort_by = get_filters_and_sort_by(request)

filters["organization-id"] = str(organization.id)
algorithms, amount_algorithm_systems = await get_algorithms(
algorithms_service, display_type, filters, limit, request, search, skip, sort_by
)
next = skip + limit

tab_items = get_organization_tabs(request, organization_slug=slug)

breadcrumbs = resolve_base_navigation_items(
[
Navigation.ORGANIZATIONS_ROOT,
BaseNavigationItem(custom_display_text=organization.name, url="/organizations/{organization_slug}"),
Navigation.ORGANIZATIONS_ALGORITHMS,
],
request,
)

context: dict[str, Any] = {
"breadcrumbs": breadcrumbs,
"tab_items": tab_items,
"sub_menu_items": {},
"algorithms": algorithms,
"amount_algorithm_systems": amount_algorithm_systems,
"next": next,
"limit": limit,
"start": skip,
"search": search,
"lifecycles": get_localized_lifecycles(request),
"risk_groups": get_localized_risk_groups(request),
"group_by_categories": get_localized_group_by_categories(request),
"filters": localized_filters,
"sort_by": sort_by,
"display_type": display_type,
"base_href": f"/organizations/{slug}/algorithms",
"organization_id": organization.id,
}

if request.state.htmx and drop_filters:
return templates.TemplateResponse(request, "parts/algorithm_search.html.j2", context)
elif request.state.htmx:
return templates.TemplateResponse(request, "parts/filter_list.html.j2", context)
else:
return templates.TemplateResponse(request, "organizations/algorithms.html.j2", context)


@router.delete("/{slug}/members/{user_id}")
Expand All @@ -259,7 +323,7 @@ async def remove_member(
users_repository: Annotated[UsersRepository, Depends(UsersRepository)],
) -> HTMLResponse:
# TODO (Robbert): add authorization and check if user and organization exist?
organization = await organizations_repository.find_by_slug(slug)
organization = await get_organization_or_error(organizations_repository, request, slug)
user: User | None = await users_repository.find_by_id(user_id)
if user:
await organizations_repository.remove_user(organization, user)
Expand All @@ -281,10 +345,11 @@ async def get_members_form(
async def add_new_members(
request: Request,
slug: str,
organizations_repository: Annotated[OrganizationsRepository, Depends(OrganizationsRepository)],
organizations_service: Annotated[OrganizationsService, Depends(OrganizationsService)],
organization_users: OrganizationUsers,
) -> HTMLResponse:
organization = await organizations_service.find_by_slug(slug)
organization = await get_organization_or_error(organizations_repository, request, slug)
await organizations_service.add_users(organization, organization_users.user_ids)
return templates.Redirect(request, f"/organizations/{slug}/members")

Expand All @@ -299,10 +364,9 @@ async def get_members(
limit: int = Query(5000, ge=1), # todo: fix infinite scroll
search: str = Query(""),
) -> HTMLResponse:
organization = await get_organization_or_error(organizations_repository, request, slug)
filters, drop_filters, localized_filters, sort_by = get_filters_and_sort_by(request)
organization = await organizations_repository.find_by_slug(slug)
tab_items = get_organization_tabs(request, organization_slug=slug)
request.state.path_variables = {"organization_slug": organization.slug}
breadcrumbs = resolve_base_navigation_items(
[
Navigation.ORGANIZATIONS_ROOT,
Expand Down
2 changes: 2 additions & 0 deletions amt/core/exception_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"missing": _("Field required"),
"value_error": _("Field required"),
"string_pattern_mismatch": _("String should match pattern '{pattern}'"),
"int_parsing": _("Input should be a valid integer."),
}


Expand Down Expand Up @@ -78,6 +79,7 @@ async def general_exception_handler(request: Request, exc: Exception) -> HTMLRes
request, template_name, {"message": message}, status_code=status_code, headers=response_headers
)
except Exception:
logger.exception("Can not display error template")
response = templates.TemplateResponse(
request,
fallback_template_name,
Expand Down
Loading

0 comments on commit 4347a83

Please sign in to comment.