Skip to content

Commit

Permalink
374 export algorithm system card as yaml (#417)
Browse files Browse the repository at this point in the history
  • Loading branch information
laurensWe authored Dec 10, 2024
2 parents 79e63cb + 32b177e commit 02fc060
Showing 11 changed files with 187 additions and 48 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -51,3 +51,6 @@ amt/site/static/js/*

# ignore webpack build files
amt/site/templates/layouts/base.html.j2

# downloaded system_cards
*00:00.yaml
18 changes: 17 additions & 1 deletion amt/api/routes/algorithm.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import asyncio
import datetime
import logging
from collections.abc import Sequence
from typing import Annotated, Any, cast

import yaml
from fastapi import APIRouter, Depends, Request
from fastapi.responses import HTMLResponse
from fastapi.responses import FileResponse, HTMLResponse
from pydantic import BaseModel, Field

from amt.api.deps import templates
@@ -736,3 +738,17 @@ async def get_model_card(
}

return templates.TemplateResponse(request, "pages/model_card.html.j2", context)


@router.get("/{algorithm_id}/details/system_card/download")
async def download_algorithm_system_card_as_yaml(
algorithm_id: int, algorithms_service: Annotated[AlgorithmsService, Depends(AlgorithmsService)], request: Request
) -> FileResponse:
algorithm = await get_algorithm_or_error(algorithm_id, algorithms_service, request)
filename = algorithm.name + "_" + datetime.datetime.now(datetime.UTC).isoformat() + ".yaml"
with open(filename, "w") as outfile:
yaml.dump(algorithm.system_card.model_dump(), outfile)
try:
return FileResponse(filename, filename=filename)
except AMTRepositoryError as e:
raise AMTNotFound from e
26 changes: 13 additions & 13 deletions amt/locale/base.pot
Original file line number Diff line number Diff line change
@@ -323,40 +323,40 @@ msgstr ""
msgid "No"
msgstr ""

#: amt/site/templates/algorithms/details_base.html.j2:57
msgid "Delete algorithm"
#: amt/site/templates/algorithms/details_base.html.j2:63
msgid "Download as YAML"
msgstr ""

#: amt/site/templates/algorithms/details_base.html.j2:74
#: amt/site/templates/algorithms/details_base.html.j2:87
msgid "Does the algorithm meet the requirements?"
msgstr ""

#: amt/site/templates/algorithms/details_base.html.j2:77
#: amt/site/templates/algorithms/details_base.html.j2:103
#: amt/site/templates/algorithms/details_base.html.j2:134
#: amt/site/templates/algorithms/details_base.html.j2:157
#: amt/site/templates/algorithms/details_base.html.j2:90
#: amt/site/templates/algorithms/details_base.html.j2:116
#: amt/site/templates/algorithms/details_base.html.j2:147
#: amt/site/templates/algorithms/details_base.html.j2:170
#: amt/site/templates/macros/tasks.html.j2:32
msgid "Done"
msgstr ""

#: amt/site/templates/algorithms/details_base.html.j2:99
#: amt/site/templates/algorithms/details_base.html.j2:155
#: amt/site/templates/algorithms/details_base.html.j2:112
#: amt/site/templates/algorithms/details_base.html.j2:168
msgid "To do"
msgstr ""

#: amt/site/templates/algorithms/details_base.html.j2:101
#: amt/site/templates/algorithms/details_base.html.j2:114
msgid "In progress"
msgstr ""

#: amt/site/templates/algorithms/details_base.html.j2:117
#: amt/site/templates/algorithms/details_base.html.j2:130
msgid "Go to all requirements"
msgstr ""

#: amt/site/templates/algorithms/details_base.html.j2:131
#: amt/site/templates/algorithms/details_base.html.j2:144
msgid "Which instruments are executed?"
msgstr ""

#: amt/site/templates/algorithms/details_base.html.j2:171
#: amt/site/templates/algorithms/details_base.html.j2:184
msgid "Go to all instruments"
msgstr ""

Binary file modified amt/locale/en_US/LC_MESSAGES/messages.mo
Binary file not shown.
26 changes: 13 additions & 13 deletions amt/locale/en_US/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
@@ -324,40 +324,40 @@ msgstr ""
msgid "No"
msgstr ""

#: amt/site/templates/algorithms/details_base.html.j2:57
msgid "Delete algorithm"
#: amt/site/templates/algorithms/details_base.html.j2:63
msgid "Download as YAML"
msgstr ""

#: amt/site/templates/algorithms/details_base.html.j2:74
#: amt/site/templates/algorithms/details_base.html.j2:87
msgid "Does the algorithm meet the requirements?"
msgstr ""

#: amt/site/templates/algorithms/details_base.html.j2:77
#: amt/site/templates/algorithms/details_base.html.j2:103
#: amt/site/templates/algorithms/details_base.html.j2:134
#: amt/site/templates/algorithms/details_base.html.j2:157
#: amt/site/templates/algorithms/details_base.html.j2:90
#: amt/site/templates/algorithms/details_base.html.j2:116
#: amt/site/templates/algorithms/details_base.html.j2:147
#: amt/site/templates/algorithms/details_base.html.j2:170
#: amt/site/templates/macros/tasks.html.j2:32
msgid "Done"
msgstr ""

#: amt/site/templates/algorithms/details_base.html.j2:99
#: amt/site/templates/algorithms/details_base.html.j2:155
#: amt/site/templates/algorithms/details_base.html.j2:112
#: amt/site/templates/algorithms/details_base.html.j2:168
msgid "To do"
msgstr ""

#: amt/site/templates/algorithms/details_base.html.j2:101
#: amt/site/templates/algorithms/details_base.html.j2:114
msgid "In progress"
msgstr ""

#: amt/site/templates/algorithms/details_base.html.j2:117
#: amt/site/templates/algorithms/details_base.html.j2:130
msgid "Go to all requirements"
msgstr ""

#: amt/site/templates/algorithms/details_base.html.j2:131
#: amt/site/templates/algorithms/details_base.html.j2:144
msgid "Which instruments are executed?"
msgstr ""

#: amt/site/templates/algorithms/details_base.html.j2:171
#: amt/site/templates/algorithms/details_base.html.j2:184
msgid "Go to all instruments"
msgstr ""

Binary file modified amt/locale/nl_NL/LC_MESSAGES/messages.mo
Binary file not shown.
28 changes: 14 additions & 14 deletions amt/locale/nl_NL/LC_MESSAGES/messages.po
Original file line number Diff line number Diff line change
@@ -338,40 +338,40 @@ msgstr "Ja"
msgid "No"
msgstr "Nee"

#: amt/site/templates/algorithms/details_base.html.j2:57
msgid "Delete algorithm"
msgstr "Verwijder algoritme"
#: amt/site/templates/algorithms/details_base.html.j2:63
msgid "Download as YAML"
msgstr "Download naar YAML"

#: amt/site/templates/algorithms/details_base.html.j2:74
#: amt/site/templates/algorithms/details_base.html.j2:87
msgid "Does the algorithm meet the requirements?"
msgstr "Voldoet het algoritme aan de vereisten?"

#: amt/site/templates/algorithms/details_base.html.j2:77
#: amt/site/templates/algorithms/details_base.html.j2:103
#: amt/site/templates/algorithms/details_base.html.j2:134
#: amt/site/templates/algorithms/details_base.html.j2:157
#: amt/site/templates/algorithms/details_base.html.j2:90
#: amt/site/templates/algorithms/details_base.html.j2:116
#: amt/site/templates/algorithms/details_base.html.j2:147
#: amt/site/templates/algorithms/details_base.html.j2:170
#: amt/site/templates/macros/tasks.html.j2:32
msgid "Done"
msgstr "Afgerond"

#: amt/site/templates/algorithms/details_base.html.j2:99
#: amt/site/templates/algorithms/details_base.html.j2:155
#: amt/site/templates/algorithms/details_base.html.j2:112
#: amt/site/templates/algorithms/details_base.html.j2:168
msgid "To do"
msgstr "Te doen"

#: amt/site/templates/algorithms/details_base.html.j2:101
#: amt/site/templates/algorithms/details_base.html.j2:114
msgid "In progress"
msgstr "Onderhanden"

#: amt/site/templates/algorithms/details_base.html.j2:117
#: amt/site/templates/algorithms/details_base.html.j2:130
msgid "Go to all requirements"
msgstr "Ga naar alle Vereisten"

#: amt/site/templates/algorithms/details_base.html.j2:131
#: amt/site/templates/algorithms/details_base.html.j2:144
msgid "Which instruments are executed?"
msgstr "Welke instrumenten zijn uitgevoerd?"

#: amt/site/templates/algorithms/details_base.html.j2:171
#: amt/site/templates/algorithms/details_base.html.j2:184
msgid "Go to all instruments"
msgstr "Ga naar all instrumenten"

35 changes: 35 additions & 0 deletions amt/site/static/scss/layout.scss
Original file line number Diff line number Diff line change
@@ -162,6 +162,7 @@ main {
&.amt-layout-grid-columns--two {
grid-template-columns: repeat(2, 1fr);
}

/* stylelint-enable */
}

@@ -389,6 +390,40 @@ main {
visibility: visible;
}

.dropdown-content {
display: none;
position: absolute;
background-color: #f9f9f9;
min-width: 160px;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
z-index: 1;
}

.dropdown-content a {
color: black;
padding: 12px 16px;
text-decoration: none;
display: block;
}

.dropdown-content a:hover {
background-color: #f1f1f1;
}

.dropdown:hover .dropdown-content {
display: block;
}

.dropdown-underlay {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 1;
}

/* stylelint-enable */

.amt-cursor-pointer {
55 changes: 55 additions & 0 deletions amt/site/static/ts/amt.ts
Original file line number Diff line number Diff line change
@@ -308,6 +308,61 @@ export function hide_form_search_options(id: string) {
}, 250);
}

export function hide_download_dropdown() {
const dropdownContent = document.querySelector(
".dropdown-content",
) as HTMLElement;
const dropdownUnderlay = document.querySelector(
".dropdown-underlay",
) as HTMLElement;

if (dropdownContent && dropdownUnderlay) {
dropdownContent.style.display = "none";
dropdownUnderlay.style.display = "none";
} else {
console.error("Could not find dropdown elements.");
}
}

export function show_download_dropdown() {
const dropdownContent = document.querySelector(
".dropdown-content",
) as HTMLElement;
const dropdownUnderlay = document.querySelector(
".dropdown-underlay",
) as HTMLElement;

if (dropdownContent && dropdownUnderlay) {
dropdownContent.style.display = "block";
dropdownUnderlay.style.display = "block";
} else {
console.error("Could not find dropdown elements.");
}
}

export async function download_as_yaml(
algorithm_id: string,
algorithm_name: string,
): Promise<void> {
try {
const response = await fetch(
`/algorithm/${algorithm_id}/details/system_card/download`,
);
const blob = await response.blob(); // Get the response as a Blob

const url = window.URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
const filename = algorithm_name + "_" + new Date().toISOString() + ".yaml";
link.setAttribute("download", filename);
document.body.appendChild(link);
link.click();
} catch (error) {
console.error("Error downloading system card:", error);
}
hide_download_dropdown();
}

export function add_field_on_enter(id: string) {
if (!event) {
return;
27 changes: 20 additions & 7 deletions amt/site/templates/algorithms/details_base.html.j2
Original file line number Diff line number Diff line change
@@ -49,13 +49,26 @@
<div class="rvo-content">
<div class="amt-flex-container">
<h1 class="utrecht-heading-1 algorithm-title">{{ algorithm.name }}</h1>
<a class="rvo-link rvo-link--normal rvo-link--with-icon"
onclick="amt.openModal('delete-modal')">
<span class="utrecht-icon rvo-icon rvo-icon-verwijderen rvo-icon--md rvo-icon--hemelblauw rvo-link__icon--before"
role="img"
aria-label="Home"></span>
{% trans %}Delete algorithm{% endtrans %}
</a>
<div class="rvo-layout-row" id="algorithm-operations-div">
<div class="download-dropdown">
<a class="rvo-alert--padding-xs" onclick="amt.show_download_dropdown()">
<span class="utrecht-icon rvo-icon rvo-icon-downloaden rvo-icon--xl rvo-icon--hemelblauw rvo-link__icon--before"
role="img"
aria-label="Home"></span>
</a>
<div class="dropdown-underlay" onclick="amt.hide_download_dropdown()"></div>
<div class="dropdown-content"
id="dropdown-content"
onclick="amt.download_as_yaml('{{ algorithm_id }}', '{{ algorithm.name }}')">
<a>{% trans %}Download as YAML{% endtrans %}</a>
</div>
</div>
<a onclick="amt.openModal('delete-modal')">
<span class="utrecht-icon rvo-icon rvo-icon-verwijderen rvo-icon--xl rvo-icon--hemelblauw rvo-link__icon--before"
role="img"
aria-label="Home"></span>
</a>
</div>
</div>
</div>
<!-- Requirements Widget -->
17 changes: 17 additions & 0 deletions tests/api/routes/test_algorithm.py
Original file line number Diff line number Diff line change
@@ -575,3 +575,20 @@ async def test_update_measure_value(client: AsyncClient, mocker: MockFixture, db
)
assert response.status_code == 200
assert response.headers["content-type"] == "text/html; charset=utf-8"


@pytest.mark.asyncio
async def test_download_algorithm_system_card_as_yaml(
client: AsyncClient, mocker: MockFixture, db: DatabaseTestUtils
) -> None:
# given
await db.given([default_user(), default_algorithm_with_system_card("testalgorithm1")])
client.cookies["fastapi-csrf-token"] = "1"
mocker.patch("fastapi_csrf_protect.CsrfProtect.validate_csrf", new_callable=mocker.AsyncMock)

# happy flow
response = await client.get("/algorithm/1/details/system_card/download")

assert response.status_code == 200
assert response.headers["content-type"] == "text/plain; charset=utf-8"
assert b"ai_act_profile:" in response.content

0 comments on commit 02fc060

Please sign in to comment.