diff --git a/apps/backend/src/api/routes/ingestion/integrity_links.py b/apps/backend/src/api/routes/ingestion/integrity_links.py index e0f27c8d..d2baa38e 100644 --- a/apps/backend/src/api/routes/ingestion/integrity_links.py +++ b/apps/backend/src/api/routes/ingestion/integrity_links.py @@ -268,11 +268,13 @@ def _final_exists(lnk: IntegrityLink) -> bool: items.append(item) usernames = list({item.integrity_owner for item in items}) - display_names = ConsoleService(get_settings().CONSOLE_INTERNAL_URL).fetch_users_by_usernames( - usernames - ) + organizations = list({item.integrity_organization for item in items}) + console = ConsoleService(get_settings().CONSOLE_INTERNAL_URL) + display_names = console.fetch_users_by_usernames(usernames) + organization_names = console.fetch_organizations_by_short_names(organizations) for item in items: item.owner_display_name = display_names.get(item.integrity_owner) + item.organization_display_name = organization_names.get(item.integrity_organization) return IntegrityLinkListResponse( items=items, diff --git a/apps/backend/src/models/data_import.py b/apps/backend/src/models/data_import.py index c7bbc082..9784ab18 100644 --- a/apps/backend/src/models/data_import.py +++ b/apps/backend/src/models/data_import.py @@ -184,6 +184,7 @@ class IntegrityLinkListItem(BaseModel): public_access: PublicAccess = PublicAccess.UNCONFIGURED has_final_table: bool = False owner_display_name: str | None = None + organization_display_name: str | None = None @field_validator("id", mode="before") @classmethod diff --git a/apps/backend/tests/api/routes/test_integrity_links.py b/apps/backend/tests/api/routes/test_integrity_links.py index 489aac84..662da169 100644 --- a/apps/backend/tests/api/routes/test_integrity_links.py +++ b/apps/backend/tests/api/routes/test_integrity_links.py @@ -31,6 +31,7 @@ def mock_console_service(): """Patch ConsoleService so tests don't hit the network; returns empty display names by default.""" with patch(_CONSOLE_PATCH) as mock_cls: mock_cls.return_value.fetch_users_by_usernames.return_value = {} + mock_cls.return_value.fetch_organizations_by_short_names.return_value = {} yield mock_cls @@ -772,6 +773,42 @@ def test_owner_display_name_none_when_console_fails( assert response.items[0].owner_display_name is None + @patch("src.api.routes.ingestion.integrity_links.logger") + def test_organization_display_name_populated_from_console( + self, + mock_logger: MagicMock, + mock_session: MagicMock, + mock_data_session: MagicMock, + mock_console_service: MagicMock, + ) -> None: + """organization_display_name is the console long name of the owning organization.""" + link = IntegrityLink( + id=uuid4(), + integrity_title="Org Link", + integrity_owner="jdoe", + integrity_organization="testorg", + source_import_type=ImportType.URL, + staging_table_name="staging_org", + created_at=datetime.now(timezone.utc), + schedule_enabled=False, + ) + self._setup_session(mock_session, [link]) + self._setup_data_session(mock_data_session, ["staging_org"], []) + mock_console_service.return_value.fetch_organizations_by_short_names.return_value = { + "testorg": "Test Organization" + } + + geo_ctx = self._create_geo_ctx("jdoe", {"IMPORT"}) + response = list_integrity_links( + session=mock_session, + data_session=mock_data_session, + geo_ctx=geo_ctx, + group_ids=[], + offset=0, + ) + + assert response.items[0].organization_display_name == "Test Organization" + @patch("src.api.routes.ingestion.integrity_links.logger") def test_next_offset_present_no_more( self, mock_logger: MagicMock, mock_session: MagicMock, mock_data_session: MagicMock diff --git a/apps/frontend/src/app/core/api/models/integrity-link-list-item.ts b/apps/frontend/src/app/core/api/models/integrity-link-list-item.ts index 264b7a4e..72fadc8d 100644 --- a/apps/frontend/src/app/core/api/models/integrity-link-list-item.ts +++ b/apps/frontend/src/app/core/api/models/integrity-link-list-item.ts @@ -26,6 +26,7 @@ export interface IntegrityLinkListItem { integrity_title: string | null last_retrieval_timestamp: string | null metadata_id: string | null + organization_display_name?: string | null owner_display_name?: string | null preset_id?: RecurrencePreset | null public_access?: PublicAccess diff --git a/apps/frontend/src/app/features/integrity-link-list/integrity-link-list.component.html b/apps/frontend/src/app/features/integrity-link-list/integrity-link-list.component.html index ad9ddc13..182c7871 100644 --- a/apps/frontend/src/app/features/integrity-link-list/integrity-link-list.component.html +++ b/apps/frontend/src/app/features/integrity-link-list/integrity-link-list.component.html @@ -81,6 +81,9 @@

{{ 'integrityLinks.title' | translate }}
+
+ {{ 'integrityLinks.organization' | translate }} +
{{ 'integrityLinks.owner' | translate }}
@@ -126,7 +129,18 @@

} -
+
+ {{ link.organization_display_name ?? link.integrity_organization }} +
+
{{ link.owner_display_name ?? link.integrity_owner }}
diff --git a/apps/frontend/src/app/features/integrity-link-list/integrity-link-list.component.spec.ts b/apps/frontend/src/app/features/integrity-link-list/integrity-link-list.component.spec.ts index 52eddb68..5eef468e 100644 --- a/apps/frontend/src/app/features/integrity-link-list/integrity-link-list.component.spec.ts +++ b/apps/frontend/src/app/features/integrity-link-list/integrity-link-list.component.spec.ts @@ -203,6 +203,43 @@ describe('IntegrityLinkListComponent', () => { }) }) + describe('Organization column', () => { + it('should display the organization long name with a title tooltip, falling back to the short name', async () => { + const fixture = TestBed.createComponent(IntegrityLinkListComponent) + + const req = httpMock.expectOne( + 'http://localhost:8000/ingestion/integrity-links/?offset=0' + ) + req.flush({ + items: [ + { + ...createMockItem('1'), + organization_display_name: 'Organisation Longue' + }, + createMockItem('2') + ], + has_more: false, + offset: 0 + }) + + await new Promise((resolve) => setTimeout(resolve, 10)) + fixture.detectChanges() + + const cells = Array.from( + fixture.nativeElement.querySelectorAll('.group > div:nth-child(2)') + ) + + expect(cells.map((el) => el.textContent?.trim())).toEqual([ + 'Organisation Longue', + 'org' + ]) + expect(cells.map((el) => el.title)).toEqual([ + 'Organisation Longue', + 'org' + ]) + }) + }) + describe('Load More Pagination', () => { it('should append items when loadMore() is called', async () => { const fixture = TestBed.createComponent(IntegrityLinkListComponent) diff --git a/apps/frontend/translations/de.json b/apps/frontend/translations/de.json index 5e21a479..b5978a72 100644 --- a/apps/frontend/translations/de.json +++ b/apps/frontend/translations/de.json @@ -294,6 +294,7 @@ "integrityLinks.loadMore": "", "integrityLinks.noFinalTable": "Aufnahme zu konfigurieren", "integrityLinks.noResults": "", + "integrityLinks.organization": "", "integrityLinks.owner": "", "integrityLinks.readOnly": "", "integrityLinks.recurrence": "", diff --git a/apps/frontend/translations/en.json b/apps/frontend/translations/en.json index 2f80ede5..60c2ce45 100644 --- a/apps/frontend/translations/en.json +++ b/apps/frontend/translations/en.json @@ -306,6 +306,7 @@ "integrityLinks.loadMore": "Load more", "integrityLinks.noFinalTable": "Ingestion to configure", "integrityLinks.noResults": "No results found", + "integrityLinks.organization": "Organization", "integrityLinks.owner": "Author", "integrityLinks.readOnly": "Read only", "integrityLinks.recurrence": "Recurrence", diff --git a/apps/frontend/translations/es.json b/apps/frontend/translations/es.json index d5df8cda..252f030b 100644 --- a/apps/frontend/translations/es.json +++ b/apps/frontend/translations/es.json @@ -294,6 +294,7 @@ "integrityLinks.loadMore": "", "integrityLinks.noFinalTable": "Ingesta por configurar", "integrityLinks.noResults": "", + "integrityLinks.organization": "", "integrityLinks.owner": "", "integrityLinks.readOnly": "", "integrityLinks.recurrence": "", diff --git a/apps/frontend/translations/fr.json b/apps/frontend/translations/fr.json index 173f9cb3..f8a7daf4 100644 --- a/apps/frontend/translations/fr.json +++ b/apps/frontend/translations/fr.json @@ -307,6 +307,7 @@ "integrityLinks.loadMore": "Charger plus", "integrityLinks.noFinalTable": "Brouillon", "integrityLinks.noResults": "Aucun résultat trouvé", + "integrityLinks.organization": "Organisation", "integrityLinks.owner": "Propriétaire", "integrityLinks.readOnly": "Lecture seule", "integrityLinks.recurrence": "Récurrence", diff --git a/apps/frontend/translations/it.json b/apps/frontend/translations/it.json index dddd779e..8a388e78 100644 --- a/apps/frontend/translations/it.json +++ b/apps/frontend/translations/it.json @@ -294,6 +294,7 @@ "integrityLinks.loadMore": "", "integrityLinks.noFinalTable": "Ingestion da configurare", "integrityLinks.noResults": "", + "integrityLinks.organization": "", "integrityLinks.owner": "", "integrityLinks.readOnly": "", "integrityLinks.recurrence": "", diff --git a/apps/frontend/translations/nl.json b/apps/frontend/translations/nl.json index 69ef3083..cee6982a 100644 --- a/apps/frontend/translations/nl.json +++ b/apps/frontend/translations/nl.json @@ -294,6 +294,7 @@ "integrityLinks.loadMore": "", "integrityLinks.noFinalTable": "Ingestie te configureren", "integrityLinks.noResults": "", + "integrityLinks.organization": "", "integrityLinks.owner": "", "integrityLinks.readOnly": "", "integrityLinks.recurrence": "", diff --git a/apps/frontend/translations/pt.json b/apps/frontend/translations/pt.json index d1e4af70..aef2bf23 100644 --- a/apps/frontend/translations/pt.json +++ b/apps/frontend/translations/pt.json @@ -294,6 +294,7 @@ "integrityLinks.loadMore": "", "integrityLinks.noFinalTable": "Ingestão a configurar", "integrityLinks.noResults": "", + "integrityLinks.organization": "", "integrityLinks.owner": "", "integrityLinks.readOnly": "", "integrityLinks.recurrence": "", diff --git a/apps/frontend/translations/sk.json b/apps/frontend/translations/sk.json index 032a1fa2..88b49ce2 100644 --- a/apps/frontend/translations/sk.json +++ b/apps/frontend/translations/sk.json @@ -294,6 +294,7 @@ "integrityLinks.loadMore": "", "integrityLinks.noFinalTable": "Príjem na konfiguráciu", "integrityLinks.noResults": "", + "integrityLinks.organization": "", "integrityLinks.owner": "", "integrityLinks.readOnly": "", "integrityLinks.recurrence": "",