From 5873bbbde2db0a90e7813ba740daf9713008e776 Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Wed, 26 Aug 2026 14:19:23 -0700 Subject: [PATCH 01/13] :art: Breadcrumb changes --- web/src/pages/DashboardPage.vue | 4 ++-- web/src/pages/administration/GroupsPage.vue | 10 +++++++++- .../InformationSharingAgreementPage.vue | 9 +++++++++ web/src/pages/archive-items/ArchiveItemListPage.vue | 10 +++++++++- .../InformationSharingAgreementPage.vue | 9 +++++++++ web/src/pages/users/UsersPage.vue | 10 +++++++++- web/src/use/use-breadcrumbs.ts | 2 +- 7 files changed, 48 insertions(+), 6 deletions(-) diff --git a/web/src/pages/DashboardPage.vue b/web/src/pages/DashboardPage.vue index 20dbdb7c..73ae9a7f 100644 --- a/web/src/pages/DashboardPage.vue +++ b/web/src/pages/DashboardPage.vue @@ -10,8 +10,8 @@

The Vault provides layers of secure storage for Yukon First Nations and Indigenous Governments to share their Traditional Knowledge with the Yukon Government. An - Information Sharing Agreement sets the terms of the sharing, and Traditional Knowledge is - then added under that agreement. + Information Sharing Agreement sets the terms of the sharing, and Traditional Knowledge + is then added under that agreement.

diff --git a/web/src/pages/administration/GroupsPage.vue b/web/src/pages/administration/GroupsPage.vue index d69026cd..dbeee67e 100644 --- a/web/src/pages/administration/GroupsPage.vue +++ b/web/src/pages/administration/GroupsPage.vue @@ -46,5 +46,13 @@ const filters = computed(() => { const { isSystemAdmin } = useCurrentUser() -useBreadcrumbs("Groups", [ADMIN_CRUMB]) +useBreadcrumbs("Groups", [ + ADMIN_CRUMB, + { + title: "Groups", + to: { + name: "administration/GroupsPage", + }, + }, +]) diff --git a/web/src/pages/administration/information-sharing-agreements/InformationSharingAgreementPage.vue b/web/src/pages/administration/information-sharing-agreements/InformationSharingAgreementPage.vue index d9562210..1683139c 100644 --- a/web/src/pages/administration/information-sharing-agreements/InformationSharingAgreementPage.vue +++ b/web/src/pages/administration/information-sharing-agreements/InformationSharingAgreementPage.vue @@ -115,5 +115,14 @@ useBreadcrumbs(pageTitle, [ name: "administration/InformationSharingAgreementsPage", }, }, + { + title: formatInformationSharingAgreementNumber(informationSharingAgreementIdAsNumber.value), + to: { + name: "administration/information-sharing-agreements/InformationSharingAgreementPage", + params: { + informationSharingAgreementId: props.informationSharingAgreementId, + }, + }, + }, ]) diff --git a/web/src/pages/archive-items/ArchiveItemListPage.vue b/web/src/pages/archive-items/ArchiveItemListPage.vue index b38b7514..f6b72073 100644 --- a/web/src/pages/archive-items/ArchiveItemListPage.vue +++ b/web/src/pages/archive-items/ArchiveItemListPage.vue @@ -142,7 +142,15 @@ const headers = [ { title: "Attachments", value: "archiveItemFileCount" }, ] -useBreadcrumbs("Knowledge Items", [BASE_CRUMB]) +useBreadcrumbs("Knowledge Items", [ + BASE_CRUMB, + { + title: "Knowledge Items", + to: { + name: "archive-items/ArchiveItemListPage", + }, + }, +]) function updatePage(newPage: number) { if (isLoading.value) return diff --git a/web/src/pages/information-sharing-agreements/InformationSharingAgreementPage.vue b/web/src/pages/information-sharing-agreements/InformationSharingAgreementPage.vue index 9b54ac4e..df45a927 100644 --- a/web/src/pages/information-sharing-agreements/InformationSharingAgreementPage.vue +++ b/web/src/pages/information-sharing-agreements/InformationSharingAgreementPage.vue @@ -115,5 +115,14 @@ useBreadcrumbs(pageTitle, [ name: "InformationSharingAgreementsPage", }, }, + { + title: formatInformationSharingAgreementNumber(informationSharingAgreementIdAsNumber.value), + to: { + name: "information-sharing-agreements/InformationSharingAgreementPage", + params: { + informationSharingAgreementId: props.informationSharingAgreementId, + }, + }, + }, ]) diff --git a/web/src/pages/users/UsersPage.vue b/web/src/pages/users/UsersPage.vue index 1171efec..3f4d9fd6 100644 --- a/web/src/pages/users/UsersPage.vue +++ b/web/src/pages/users/UsersPage.vue @@ -59,5 +59,13 @@ const filters = computed(() => { } }) -useBreadcrumbs("Users", [ADMIN_CRUMB]) +useBreadcrumbs("Users", [ + ADMIN_CRUMB, + { + title: "Users", + to: { + name: "users/UsersPage", + }, + }, +]) diff --git a/web/src/use/use-breadcrumbs.ts b/web/src/use/use-breadcrumbs.ts index 2c54b4bf..1fcf7f4b 100644 --- a/web/src/use/use-breadcrumbs.ts +++ b/web/src/use/use-breadcrumbs.ts @@ -10,7 +10,7 @@ export type Breadcrumb = { } export const BASE_CRUMB: Breadcrumb = { - title: "Traditional Knowledge", + title: "Home", disabled: false, to: { name: "DashboardPage", From 82ea1bf69ae6a228bc7c4c5594e201d330a1d0db Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Wed, 26 Aug 2026 17:37:33 -0700 Subject: [PATCH 02/13] :arrow_up: Upgrade vuetify to 3.13.2 to fix notifications page pagination. VDataIterator only gained support for the items-length prop (manual/server-side pagination) in 3.12.0. On the previously installed 3.11.8, it silently ignored items-length and computed page count from the current page's item count instead of the total, so the notifications list never showed more than one page. package.json's existing ^3.11.8 range already permitted this version. --- web/package-lock.json | 52 +++++-------------------------------------- 1 file changed, 5 insertions(+), 47 deletions(-) diff --git a/web/package-lock.json b/web/package-lock.json index 143e8448..8997d75c 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -1235,7 +1235,6 @@ "version": "2.5.6", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.6.tgz", "integrity": "sha512-tmmZ3lQxAe/k/+rNnXQRawJ4NjxO2hqiOLTHvWchtGZULp4RyFeh6aU4XdOYBFe2KE1oShQTv4AblOs2iOrNnQ==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -1275,7 +1274,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1296,7 +1294,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1317,7 +1314,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1338,7 +1334,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1359,7 +1354,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1380,7 +1374,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1401,7 +1394,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1422,7 +1414,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1443,7 +1434,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1464,7 +1454,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1485,7 +1474,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1506,7 +1494,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1527,7 +1514,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1545,7 +1531,6 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", - "dev": true, "license": "MIT", "optional": true, "engines": { @@ -1586,7 +1571,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1600,7 +1584,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1614,7 +1597,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1628,7 +1610,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1642,7 +1623,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1656,7 +1636,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1670,7 +1649,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1684,7 +1662,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1698,7 +1675,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1712,7 +1688,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1726,7 +1701,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1740,7 +1714,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1754,7 +1727,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1768,7 +1740,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1782,7 +1753,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1796,7 +1766,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1810,7 +1779,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1824,7 +1792,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1838,7 +1805,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1852,7 +1818,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1866,7 +1831,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1880,7 +1844,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1894,7 +1857,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1908,7 +1870,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1922,7 +1883,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -3012,7 +2972,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", - "dev": true, "license": "Apache-2.0", "optional": true, "engines": { @@ -4085,7 +4044,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.10.0" @@ -4105,7 +4064,7 @@ "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "is-extglob": "^2.1.1" @@ -4559,7 +4518,6 @@ "version": "7.1.1", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", - "dev": true, "license": "MIT", "optional": true }, @@ -6780,9 +6738,9 @@ } }, "node_modules/vuetify": { - "version": "3.11.8", - "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.11.8.tgz", - "integrity": "sha512-4iKnntOnLFFklygZjzlVfcHrtLO8+iK4HOhiia6HP2U8v82x+ngaSCgm+epvPrGyCMfCpfuEttqD2qElrr1axw==", + "version": "3.13.2", + "resolved": "https://registry.npmjs.org/vuetify/-/vuetify-3.13.2.tgz", + "integrity": "sha512-CuBtJ2/lKo4s6vDmDr/oJ+77oRUb7X8goUhTKZG/qSqBkL5J2EzeBEhaZ57ZSLcYnnViWHObuVjHjRFmVzx55Q==", "license": "MIT", "funding": { "type": "github", From aeab3bde12bb37a4e0b4a018573efdc23bc8543d Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Wed, 26 Aug 2026 17:37:36 -0700 Subject: [PATCH 03/13] :sparkles: Add EnhancedPagination component for route-query-driven pagination. Pairs with the existing useRouteQueryPagination composable; wires a v-pagination control to a page/perPage model and computes total pages from a totalCount prop. --- .../components/common/EnhancedPagination.vue | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 web/src/components/common/EnhancedPagination.vue diff --git a/web/src/components/common/EnhancedPagination.vue b/web/src/components/common/EnhancedPagination.vue new file mode 100644 index 00000000..a53dbd0e --- /dev/null +++ b/web/src/components/common/EnhancedPagination.vue @@ -0,0 +1,31 @@ + + + + + From 4c4c1019f54c630a3b629d179efc9f59352e15d1 Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Wed, 26 Aug 2026 17:42:17 -0700 Subject: [PATCH 04/13] :art: Breadcrumbs fix for notifications page --- web/src/pages/NotificationsPage.vue | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/web/src/pages/NotificationsPage.vue b/web/src/pages/NotificationsPage.vue index 5706358c..f874e267 100644 --- a/web/src/pages/NotificationsPage.vue +++ b/web/src/pages/NotificationsPage.vue @@ -35,6 +35,7 @@ import { computed } from "vue" import { stringTransformer } from "@/utils/use-route-query-transformers" +import useBreadcrumbs, { BASE_CRUMB } from "@/use/use-breadcrumbs" import { NotificationWhereOptions } from "@/use/use-notifications" import AppCard from "@/components/common/AppCard.vue" @@ -53,4 +54,14 @@ const where = computed(() => { return {} }) + +useBreadcrumbs("Notifications", [ + BASE_CRUMB, + { + title: "Notifications", + to: { + name: "NotificationsPage", + }, + }, +]) From 8154598ddc985878ec5811c3ab454467e312546d Mon Sep 17 00:00:00 2001 From: Marlen Brunner Date: Fri, 31 Jul 2026 12:40:47 -0700 Subject: [PATCH 05/13] :bug: Link signed agreements to ISA-scoped knowledge items. Use the ISA number for the canonical knowledge item list and association detail URLs, preserving many-to-many links without selecting an arbitrary first item. --- CHANGELOG.md | 9 ++ web/README.md | 11 ++ ...nSharingAgreementKnowledgeItemListItem.vue | 26 ++++ ...ationSharingAgreementSignedActionsMenu.vue | 30 +++-- ...ationSharingAgreementKnowledgeItemPage.vue | 111 ++++++++++++++++++ ...tionSharingAgreementKnowledgeItemsPage.vue | 85 ++++++++++++++ web/src/routes.ts | 14 +++ 7 files changed, 270 insertions(+), 16 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 web/src/components/information-sharing-agreements/InformationSharingAgreementKnowledgeItemListItem.vue create mode 100644 web/src/pages/information-sharing-agreements/InformationSharingAgreementKnowledgeItemPage.vue create mode 100644 web/src/pages/information-sharing-agreements/InformationSharingAgreementKnowledgeItemsPage.vue diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..99a9871e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +All notable changes to this project are documented in this file. + +## [Unreleased] + +### Fixed + +- Link signed agreement actions to an ISA-scoped knowledge item list so URLs use the ISA number without the `archive-items` segment. diff --git a/web/README.md b/web/README.md index 121dea9c..b0404272 100644 --- a/web/README.md +++ b/web/README.md @@ -51,6 +51,17 @@ Vue 3 + Vuetify 3 + TypeScript + Vite + Pinia. --- +## Knowledge Item and ISA Navigation + +An information sharing agreement (ISA) and a knowledge item are separate domain records linked through the `InformationSharingAgreementArchiveItem` association. The relationship is many-to-many; neither `informationSharingAgreementId` nor `archiveItemId` identifies the association by itself. + +Use the ISA-scoped routes for navigation from an ISA: + +- `/information-sharing-agreements/:informationSharingAgreementId/knowledge-items` lists every linked knowledge item. +- `/information-sharing-agreements/:informationSharingAgreementId/knowledge-items/:informationSharingAgreementArchiveItemId` opens one linked item. + +The detail route validates that the association belongs to the ISA in the URL before rendering the underlying knowledge item. Do not select the first association or expose an archive-item ID as the canonical ISA link. + ## Open In Editor When the frontend runs in Docker, Vue Devtools cannot launch your host editor directly from inside the container. This project handles that by: diff --git a/web/src/components/information-sharing-agreements/InformationSharingAgreementKnowledgeItemListItem.vue b/web/src/components/information-sharing-agreements/InformationSharingAgreementKnowledgeItemListItem.vue new file mode 100644 index 00000000..681c522e --- /dev/null +++ b/web/src/components/information-sharing-agreements/InformationSharingAgreementKnowledgeItemListItem.vue @@ -0,0 +1,26 @@ + + + diff --git a/web/src/components/information-sharing-agreements/signed/InformationSharingAgreementSignedActionsMenu.vue b/web/src/components/information-sharing-agreements/signed/InformationSharingAgreementSignedActionsMenu.vue index 099bf350..6390a349 100644 --- a/web/src/components/information-sharing-agreements/signed/InformationSharingAgreementSignedActionsMenu.vue +++ b/web/src/components/information-sharing-agreements/signed/InformationSharingAgreementSignedActionsMenu.vue @@ -4,7 +4,7 @@ :loading="isLoading" > diff --git a/web/src/routes.ts b/web/src/routes.ts index 05fba4a7..04f55761 100644 --- a/web/src/routes.ts +++ b/web/src/routes.ts @@ -61,10 +61,32 @@ const routes: RouteRecordRaw[] = [ }, { path: "sharing-agreements/:informationSharingAgreementId/knowledge-items/:informationSharingAgreementArchiveItemId", - name: "information-sharing-agreements/InformationSharingAgreementKnowledgeItemPage", component: () => import("@/pages/information-sharing-agreements/InformationSharingAgreementKnowledgeItemPage.vue"), props: true, + children: [ + { + path: "", + name: "information-sharing-agreements/InformationSharingAgreementKnowledgeItemPage", + redirect: { + name: "information-sharing-agreements/InformationSharingAgreementKnowledgeItemInformationSharingAgreementsPage", + }, + }, + { + path: "sharing-agreements", + name: "information-sharing-agreements/InformationSharingAgreementKnowledgeItemInformationSharingAgreementsPage", + component: () => + import("@/pages/information-sharing-agreements/InformationSharingAgreementKnowledgeItemInformationSharingAgreementsPage.vue"), + props: true, + }, + { + path: "users-with-access", + name: "information-sharing-agreements/InformationSharingAgreementKnowledgeItemUsersWithAccessPage", + component: () => + import("@/pages/information-sharing-agreements/InformationSharingAgreementKnowledgeItemUsersWithAccessPage.vue"), + props: true, + }, + ], }, { path: "sharing-agreements/:informationSharingAgreementId/sign", diff --git a/web/src/use/use-information-sharing-agreement-archive-item.ts b/web/src/use/use-information-sharing-agreement-archive-item.ts new file mode 100644 index 00000000..16d5e254 --- /dev/null +++ b/web/src/use/use-information-sharing-agreement-archive-item.ts @@ -0,0 +1,62 @@ +import { type Ref, reactive, toRefs, unref, watch } from "vue" +import { isNil } from "lodash" + +import informationSharingAgreementArchiveItemsApi, { + type InformationSharingAgreementArchiveItem, +} from "@/api/information-sharing-agreement-archive-items-api" + +export { type InformationSharingAgreementArchiveItem } + +export function useInformationSharingAgreementArchiveItem( + id: Ref +) { + const state = reactive<{ + informationSharingAgreementArchiveItem: InformationSharingAgreementArchiveItem | null + isLoading: boolean + isErrored: boolean + }>({ + informationSharingAgreementArchiveItem: null, + isLoading: false, + isErrored: false, + }) + + async function fetch(): Promise { + const staticId = unref(id) + if (isNil(staticId)) { + throw new Error("id is required") + } + + state.isLoading = true + try { + const { informationSharingAgreementArchiveItem } = + await informationSharingAgreementArchiveItemsApi.get(staticId) + state.isErrored = false + state.informationSharingAgreementArchiveItem = informationSharingAgreementArchiveItem + return informationSharingAgreementArchiveItem + } catch (error) { + console.error("Failed to fetch information sharing agreement knowledge item:", error) + state.isErrored = true + throw error + } finally { + state.isLoading = false + } + } + + watch( + () => unref(id), + async (newId) => { + if (isNil(newId)) return + + await fetch() + }, + { immediate: true } + ) + + return { + ...toRefs(state), + fetch, + refresh: fetch, + } +} + +export default useInformationSharingAgreementArchiveItem From 6117c67b6cfaf866956e8138e73fa73d59bf13bc Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Thu, 27 Aug 2026 13:50:20 -0700 Subject: [PATCH 12/13] :burn: Removing WRAP artifacts --- CHANGELOG.md | 10 -------- web/tests/routes.test.ts | 53 ---------------------------------------- 2 files changed, 63 deletions(-) delete mode 100644 CHANGELOG.md delete mode 100644 web/tests/routes.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index 81f5c595..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,10 +0,0 @@ -# Changelog - -All notable changes to this project are documented in this file. - -## [Unreleased] - -### Fixed - -- Link signed agreement actions to an ISA-scoped knowledge item list so URLs use the ISA number without the `archive-items` segment. -- Shorten public, administration, and archive-item sharing agreement URLs to use the `sharing-agreements` route prefix. diff --git a/web/tests/routes.test.ts b/web/tests/routes.test.ts deleted file mode 100644 index fb411376..00000000 --- a/web/tests/routes.test.ts +++ /dev/null @@ -1,53 +0,0 @@ -import { createMemoryHistory, createRouter } from "vue-router" -import routes from "@/routes" - -describe("sharing agreement and knowledge item routes", () => { - const router = createRouter({ history: createMemoryHistory(), routes }) - - test.each([ - ["/sharing-agreements", "InformationSharingAgreementsPage"], - [ - "/sharing-agreements/new", - "information-sharing-agreements/InformationSharingAgreementNewPage", - ], - ["/sharing-agreements/12", "information-sharing-agreements/InformationSharingAgreementPage"], - [ - "/sharing-agreements/12/knowledge-items/34", - "information-sharing-agreements/InformationSharingAgreementKnowledgeItemPage", - ], - [ - "/sharing-agreements/12/sign", - "information-sharing-agreements/InformationSharingAgreementSignPage", - ], - [ - "/sharing-agreements/12/edit", - "information-sharing-agreements/InformationSharingAgreementEditPage", - ], - [ - "/sharing-agreements/12/edit/edit-basic-information", - "information-sharing-agreements/InformationSharingAgreementEditBasicInformationPage", - ], - ["/administration/sharing-agreements", "administration/InformationSharingAgreementsPage"], - [ - "/administration/sharing-agreements/12/access-grants", - "administration/information-sharing-agreements/InformationSharingAgreementAccessGrantsPage", - ], - ["/knowledge-items", "archive-items/ArchiveItemListPage"], - ["/knowledge-items/new", "archive-items/ArchiveItemNewPage"], - [ - "/knowledge-items/34/sharing-agreements", - "archive-items/ArchiveItemInformationSharingAgreementsPage", - ], - ["/knowledge-items/34/users-with-access", "archive-items/ArchiveItemUsersWithAccessPage"], - ])("resolves %s", (path, name) => { - expect(router.resolve(path).matched.some((record) => record.name === name)).toBe(true) - }) - - test("does not resolve the former public prefix", () => { - expect(router.resolve("/information-sharing-agreements").name).toBe("NotFoundPage") - }) - - test("does not resolve the former archive-items prefix", () => { - expect(router.resolve("/archive-items/34/sharing-agreements").name).toBe("NotFoundPage") - }) -}) From 07e1e60a6dda7b42985f96d00c894b5819d8837e Mon Sep 17 00:00:00 2001 From: Caleb Burke Date: Thu, 27 Aug 2026 15:33:21 -0700 Subject: [PATCH 13/13] :bug: Allow creating Knowledge Items from ISAs whose contact has no linked external organization. A missing external organization on the ISA's external group contact is a state the UI already tolerates elsewhere (shown as "Not specified"), but archive-item creation treated it as fatal, throwing an error that the frontend surfaced only as a generic "Save failed" message. Resolve yukonFirstNations to an empty list instead of failing the whole request. Undoes the throw added in 3309466. See TK-82. --- .../archive-items/create-service.ts | 4 +- .../archive-items/create-service.test.ts | 80 +++++++++++++++++++ 2 files changed, 81 insertions(+), 3 deletions(-) create mode 100644 api/tests/services/information-sharing-agreements/archive-items/create-service.test.ts diff --git a/api/src/services/information-sharing-agreements/archive-items/create-service.ts b/api/src/services/information-sharing-agreements/archive-items/create-service.ts index 0581b610..4fbff84b 100644 --- a/api/src/services/information-sharing-agreements/archive-items/create-service.ts +++ b/api/src/services/information-sharing-agreements/archive-items/create-service.ts @@ -115,9 +115,7 @@ export class CreateService extends BaseService { ], }) - if (isNil(organization)) { - throw new Error("External group contact is missing its associated external organization") - } + if (isNil(organization)) return null const { name } = organization return [name] diff --git a/api/tests/services/information-sharing-agreements/archive-items/create-service.test.ts b/api/tests/services/information-sharing-agreements/archive-items/create-service.test.ts new file mode 100644 index 00000000..a3278864 --- /dev/null +++ b/api/tests/services/information-sharing-agreements/archive-items/create-service.test.ts @@ -0,0 +1,80 @@ +import { + externalOrganizationFactory, + informationSharingAgreementFactory, + userFactory, +} from "@/tests/factories" + +import CreateService from "@/services/information-sharing-agreements/archive-items/create-service" + +describe("api/src/services/information-sharing-agreements/archive-items/create-service.ts", () => { + describe("CreateService", () => { + describe("#perform", () => { + test("when external group contact has an associated external organization, sets yukonFirstNations from it", async () => { + // Arrange + const currentUser = await userFactory.create() + const externalOrganization = await externalOrganizationFactory.create({ + name: "Test External Organization", + }) + const externalGroupContact = await userFactory.create({ + isExternal: true, + externalOrganizationId: externalOrganization.id, + }) + const informationSharingAgreement = await informationSharingAgreementFactory.create({ + title: "Test Agreement", + purpose: "Test purpose", + externalGroupContactId: externalGroupContact.id, + }) + + // Act + const archiveItem = await CreateService.perform( + informationSharingAgreement, + { confidentialityReceipt: true }, + currentUser + ) + + // Assert + expect(archiveItem.yukonFirstNations).toEqual(["Test External Organization"]) + }) + + test("when external group contact is missing its associated external organization, creates the archive item without yukonFirstNations", async () => { + // Arrange + const currentUser = await userFactory.create() + const externalOrganization = await externalOrganizationFactory.create() + const externalGroupContact = await userFactory.create({ + isExternal: true, + externalOrganizationId: externalOrganization.id, + }) + const informationSharingAgreement = await informationSharingAgreementFactory.create({ + title: "Test Agreement", + purpose: "Test purpose", + externalGroupContactId: externalGroupContact.id, + }) + await externalOrganization.destroy() + + // Act + const archiveItem = await CreateService.perform( + informationSharingAgreement, + { confidentialityReceipt: true }, + currentUser + ) + + // Assert + expect(archiveItem.yukonFirstNations).toEqual([]) + }) + + test("when confidentiality receipt is not true, throws an informative error", async () => { + // Arrange + const currentUser = await userFactory.create() + const informationSharingAgreement = await informationSharingAgreementFactory.create({ + title: "Test Agreement", + purpose: "Test purpose", + }) + + // Act & Assert + await expect( + CreateService.perform(informationSharingAgreement, {}, currentUser) + ).rejects.toThrow("Confidentiality receipt is required, and must be true") + }) + }) + }) +})