Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion src/components/cards/OrganisatieCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,18 @@
*/

<template>
<div class="organisatieCard" @click="handleCardClick">
<!-- role/tabindex/keydown rather than a click-only div: the whole card
navigates to the organisation detail page, so it is the primary control
in this component and a keyboard user could not activate it at all
(WCAG 2.1.1). The nested .cardHeaderActions already stops propagation,
so the NcActions menu is unaffected. -->
<div class="organisatieCard"
role="button"
tabindex="0"
:aria-label="`Open ${getOrganisatieTitle(item)}`"
@click="handleCardClick"
@keydown.enter.prevent="handleCardClick"
@keydown.space.prevent="handleCardClick">
<div class="cardHeader">
<h2 v-tooltip.bottom="getOrganisatieSummary(item)">
<component :is="cardIcon" :size="20" />
Expand Down
4 changes: 4 additions & 0 deletions src/icons.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import AccountMultiple from 'vue-material-design-icons/AccountMultiple.vue'
import AlertCircle from 'vue-material-design-icons/AlertCircle.vue'
import ApplicationOutline from 'vue-material-design-icons/ApplicationOutline.vue'
import ArrowRight from 'vue-material-design-icons/ArrowRight.vue'
import BookOpenVariant from 'vue-material-design-icons/BookOpenVariant.vue'
import BookOpenVariantOutline from 'vue-material-design-icons/BookOpenVariantOutline.vue'
import BriefcaseOutline from 'vue-material-design-icons/BriefcaseOutline.vue'
import ChartBar from 'vue-material-design-icons/ChartBar.vue'
Expand Down Expand Up @@ -56,6 +57,7 @@ import ShieldAlertOutline from 'vue-material-design-icons/ShieldAlertOutline.vue
import ShieldCheckOutline from 'vue-material-design-icons/ShieldCheckOutline.vue'
import ShieldLockOutline from 'vue-material-design-icons/ShieldLockOutline.vue'
import ShieldOutline from 'vue-material-design-icons/ShieldOutline.vue'
import SourceBranch from 'vue-material-design-icons/SourceBranch.vue'
import Star from 'vue-material-design-icons/Star.vue'
import ViewDashboardOutline from 'vue-material-design-icons/ViewDashboardOutline.vue'
import ViewGridOutline from 'vue-material-design-icons/ViewGridOutline.vue'
Expand All @@ -68,6 +70,7 @@ export default {
AlertCircle,
ApplicationOutline,
ArrowRight,
BookOpenVariant,
BookOpenVariantOutline,
BriefcaseOutline,
ChartBar,
Expand Down Expand Up @@ -106,6 +109,7 @@ export default {
ShieldCheckOutline,
ShieldLockOutline,
ShieldOutline,
SourceBranch,
Star,
ViewDashboardOutline,
ViewGridOutline,
Expand Down
8 changes: 4 additions & 4 deletions src/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -292,10 +292,10 @@
"_note": "bio-compliance-assessment: catalog archetype (an offered application). Body: application data 8-wide top-left (naam, descriptions, website, contactpersoon, hosting/licence fields, then the new BBN level and DPIA status/dates/document-reference/verwerkingsregister fields) with a Documentation files panel 4-wide top-right. Below, a Related panel surfaces the vendor (aanbieder) and linked services/couplings, then two object-lists: compliance claims (compliancy records — both GEMMA standards and, since this change, BIO measures — filtered by module=@objectId) and application versions (moduleVersie filtered by module=@objectId). An application does not communicate, so per the comms hard-rule NO Emails/Meetings widgets appear. Audit trail stays a sidebar tab.",
"widgets": [
{ "id": "md-data", "type": "data", "title": "Application", "icon": "Package", "content": { "columns": 2, "include": [ "naam", "beschrijvingKort", "beschrijvingLang", "website", "aanbieder", "contactpersoon", "cloudDienstverleningsmodel", "hostingJurisdictie", "hostingLocatie", "licentietype", "licentie", "bbnLevel", "dpiaStatus", "dpiaDate", "dpiaVolgendeBeoordeling", "dpiaDocumentRef", "verwerkingsregisterRef" ] } },
{ "id": "md-files", "type": "integration", "integrationId": "files", "title": "Documentation", "icon": "BookOpenVariantOutline" },
{ "id": "md-files", "type": "integration", "integrationId": "files", "title": "Documentation", "icon": "BookOpenVariant" },
{ "id": "md-related", "type": "related", "title": "Vendor & services", "icon": "LinkVariant" },
{ "id": "md-compliance", "type": "object-list", "title": "Compliance claims", "icon": "ClipboardCheckOutline", "content": { "register": "@resolve:voorzieningen_register", "schema": "compliancy", "filter": { "module": "@objectId" }, "columns": [ { "key": "standaardversie", "label": "Standard" }, { "key": "bioMaatregel", "label": "BIO measure" }, { "key": "url", "label": "Evidence" } ], "limit": 50, "rowRoute": "KompliantieDetail", "allowCreate": false, "emptyText": "No compliance claims yet" } },
{ "id": "md-versions", "type": "object-list", "title": "Application versions", "icon": "ViewModule", "content": { "register": "@resolve:voorzieningen_register", "schema": "moduleVersie", "filter": { "module": "@objectId" }, "columns": [ { "key": "versie", "label": "Version" }, { "key": "status", "label": "Status" } ], "limit": 25, "rowRoute": "ModuleversieDetail", "allowCreate": false, "emptyText": "No versions registered yet" } }
{ "id": "md-versions", "type": "object-list", "title": "Application versions", "icon": "SourceBranch", "content": { "register": "@resolve:voorzieningen_register", "schema": "moduleVersie", "filter": { "module": "@objectId" }, "columns": [ { "key": "versie", "label": "Version" }, { "key": "status", "label": "Status" } ], "limit": 25, "rowRoute": "ModuleversieDetail", "allowCreate": false, "emptyText": "No versions registered yet" } }
],
"layout": [
{ "id": "1", "widgetId": "md-data", "gridX": 0, "gridY": 0, "gridWidth": 8, "gridHeight": 8 },
Expand Down Expand Up @@ -474,7 +474,7 @@
"schema": "suite",
"_note": "suite-wizard: catalog archetype (a bundled product made up of one or more existing applications). Body: suite data 8-wide (naam, beschrijvingKort, beschrijvingLang, website, logo, contactpersoon) with a Related panel 4-wide surfacing both the contact person and the attached applications (`applicaties`) — the latter via the platform's generic bidirectional relation index (the same `/uses`+`/used` mechanism `md-related` on ModuleDetail already relies on, which is also how a module's own detail page surfaces which suite(s) include it, with no ModuleDetail change needed). A suite does not communicate, so per the comms hard-rule NO Emails/Meetings widgets appear. Audit trail stays a sidebar tab.",
"widgets": [
{ "id": "suite-data", "type": "data", "title": "Suite", "icon": "PackageVariant", "content": { "columns": 2, "include": [ "naam", "beschrijvingKort", "beschrijvingLang", "website", "logo", "contactpersoon" ] } },
{ "id": "suite-data", "type": "data", "title": "Suite", "icon": "Package", "content": { "columns": 2, "include": [ "naam", "beschrijvingKort", "beschrijvingLang", "website", "logo", "contactpersoon" ] } },
{ "id": "suite-related", "type": "related", "title": "Applications & contact", "icon": "LinkVariant" }
],
"layout": [
Expand Down Expand Up @@ -578,7 +578,7 @@
"schema": "bioMaatregel",
"_note": "Catalog archetype (a BIO 2.0 measure), mirroring StandaardDetail's shape for the parallel bioMaatregel relation (design.md Decision 1: compliancy links to a standaardversie OR a bioMaatregel, never a third parallel model). Body: measure data 8-wide (code, naam, omschrijving, thema, bioVersie, bbnNiveau, bron). Below, a compliancy object-list shows every compliance claim referencing this measure (compliancy.bioMaatregel = @objectId), mirroring how StandaardDetail's st-compliance widget shows claims for a standard.",
"widgets": [
{ "id": "bm-data", "type": "data", "title": "BIO measure", "icon": "ShieldLockOutline", "content": { "columns": 2 } },
{ "id": "bm-data", "type": "data", "title": "BIO measure", "icon": "ShieldCheckOutline", "content": { "columns": 2 } },
{ "id": "bm-compliance", "type": "object-list", "title": "Compliance claims for this measure", "icon": "ClipboardCheckOutline", "content": { "register": "@resolve:voorzieningen_register", "schema": "compliancy", "filter": { "bioMaatregel": "@objectId" }, "columns": [ { "key": "module", "label": "Module" }, { "key": "url", "label": "Evidence URL" } ], "limit": 50, "rowRoute": "KompliantieDetail", "allowCreate": false, "emptyText": "No compliance claims yet" } }
],
"layout": [
Expand Down
17 changes: 15 additions & 2 deletions src/modals/object/MergeObject.vue
Original file line number Diff line number Diff line change
Expand Up @@ -44,12 +44,25 @@ import { objectStore, navigationStore, catalogStore } from '../../store/store.js
<p>Loading objects...</p>
</div>

<div v-else-if="availableObjects.length" class="object-list">
<div v-else-if="availableObjects.length"
class="object-list"
role="listbox"
aria-label="Merge target">
<!-- role/tabindex/keydown rather than a click-only div: picking the
merge target is the consequential choice in this dialog, and a
keyboard user could not make it at all. role="option" inside the
listbox above carries the selected state to a screen reader
(WCAG 2.1.1, 4.1.2). -->
<div v-for="obj in availableObjects"
:key="obj['@self'].id"
class="object-item table-row-selectable"
role="option"
tabindex="0"
:aria-selected="selectedTargetObject?.['@self']?.id === obj['@self'].id"
:class="{ 'table-row-selected': selectedTargetObject?.['@self']?.id === obj['@self'].id }"
@click="selectTargetObject(obj)">
@click="selectTargetObject(obj)"
@keydown.enter.prevent="selectTargetObject(obj)"
@keydown.space.prevent="selectTargetObject(obj)">
<div class="object-info">
<strong>{{ obj['@self']?.name || obj.name || obj.title || obj['@self']?.title || obj['@self']?.id }}</strong>
<p class="object-id">
Expand Down
1 change: 1 addition & 0 deletions src/modals/object/ViewObject.vue
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,7 @@ import { objectStore, navigationStore, catalogStore } from '../../store/store.js
</NcButton>
<NcButton
v-tooltip="'Cancel'"
:aria-label="`cancel editing labels for ${attachment.name ?? attachment?.title ?? 'file'}`"
variant="secondary"
size="small"
@click="cancelFileLabelEditing">
Expand Down
1 change: 1 addition & 0 deletions src/views/settings/sections/ArchiMateImportExport.vue
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,7 @@
<h5>Import Errors Details</h5>
<NcButton
variant="tertiary-no-background"
aria-label="Close import error details"
@click="hideErrorDetails">
<template #icon>
<Close :size="20" />
Expand Down
15 changes: 13 additions & 2 deletions src/views/settings/sections/EmailConfiguration.vue
Original file line number Diff line number Diff line change
Expand Up @@ -301,13 +301,20 @@
<div class="available-variables">
<h5>Available Variables:</h5>
<div class="variables-list">
<span
<!-- A real button rather than a clickable span: this inserts a
template variable into the editor, so it is a control, and
a keyboard or screen-reader user could not reach it at all
while it was a span (WCAG 2.1.1). type="button" keeps it
from submitting the settings form. -->
<button
v-for="(description, variable) in getActiveTemplateVariables()"
:key="variable"
type="button"
class="variable-tag"
:title="description"
@click="insertVariable(variable)">
{{ formatTemplateVariable(variable) }}
</span>
</button>
</div>
</div>
</div>
Expand Down Expand Up @@ -848,6 +855,10 @@ export default {

.variable-tag {
display: inline-block;
/* The element is now a <button>; reset the UA chrome so the visual
result is byte-for-byte what the <span> rendered. */
border: none;
appearance: none;
padding: 0.25rem 0.5rem;
background: var(--color-primary-light);
color: var(--color-primary-text);
Expand Down
7 changes: 6 additions & 1 deletion tests/e2e/org-archimate-export.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,12 @@ async function seedOrganization(): Promise<void> {
* Auth is injected from storageState (see playwright.config.ts).
*/
async function goToArchiMateSettings(page: Page): Promise<void> {
await page.goto('/settings/admin/softwarecatalog', { waitUntil: 'networkidle' })
// `domcontentloaded`, not `networkidle`: Nextcloud keeps long-lived
// connections open (notifications polling, dashboard widgets), so the
// network never goes idle and this wait can only ever time out or be
// satisfied by luck (ADR-074 rule 4). The real readiness signal is the
// heading assertion below, which waits for the SPA to actually mount.
await page.goto('/settings/admin/softwarecatalog', { waitUntil: 'domcontentloaded' })
await expect(
page.getByRole('heading', { name: 'ArchiMate Import/Export' }),
).toBeVisible({ timeout: 30000 })
Expand Down
7 changes: 6 additions & 1 deletion tests/e2e/workflows/org-export-workflow.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,12 @@ test.afterAll(async () => {

/** Open the ArchiMate Import/Export admin section and wait for it to mount. */
async function goToArchiMateSettings(page: Page): Promise<void> {
await page.goto('/settings/admin/softwarecatalog', { waitUntil: 'networkidle' })
// `domcontentloaded`, not `networkidle`: Nextcloud keeps long-lived
// connections open (notifications polling, dashboard widgets), so the
// network never goes idle and this wait can only ever time out or be
// satisfied by luck (ADR-074 rule 4). The real readiness signal is the
// heading assertion below, which waits for the SPA to actually mount.
await page.goto('/settings/admin/softwarecatalog', { waitUntil: 'domcontentloaded' })
await expect(
page.getByRole('heading', { name: 'ArchiMate Import/Export' }),
).toBeVisible({ timeout: 30000 })
Expand Down
Loading
Loading