Skip to content

Component creation#1251

Open
jr-rk wants to merge 3 commits intocustom_dtfrom
component_creation
Open

Component creation#1251
jr-rk wants to merge 3 commits intocustom_dtfrom
component_creation

Conversation

@jr-rk
Copy link

@jr-rk jr-rk commented Mar 11, 2026

CLARIN WAYF — Identity Provider Picker

Adds a CLARIN WAYF (Where Are You From) IdP discovery component to DSpace Angular, replacing the legacy external DiscoJuice/jQuery solution with a fully integrated Angular component.

What was built

A standalone Angular component at src/app/clarin-wayf/ that allows users to search for and select their home institution (Identity Provider) before being redirected through Shibboleth SSO.

New files

Path Purpose
src/app/clarin-wayf/models/idp-entry.model.ts TypeScript interfaces for the IdP JSON feed schema
src/app/clarin-wayf/models/wayf-config.model.ts SAMLDS protocol params (entityID, return, returnIDParam, isPassive)
src/app/clarin-wayf/services/search.service.ts Fuzzy search engine — Sørensen–Dice bigram similarity, diacritics normalization, language-aware ranking
src/app/clarin-wayf/services/feed.service.ts HTTP fetch and cache of the IdP JSON feed
src/app/clarin-wayf/services/persistence.service.ts localStorage persistence — last IdP + up to 5 recent IdPs
src/app/clarin-wayf/services/i18n.service.ts Signal-based self-contained translation service (en/cs/de)
src/app/clarin-wayf/services/search.service.spec.ts 33 unit tests (all passing)
src/app/clarin-wayf/components/idp-card/ Single IdP card (logo, display name, tag badge)
src/app/clarin-wayf/components/search-bar/ Search input with ARIA combobox and keyboard delegation
src/app/clarin-wayf/components/idp-list/ Filtered and sorted list of IdP cards
src/app/clarin-wayf/components/recent-idps/ Strip of recently used IdPs
src/app/clarin-wayf/clarin-wayf.component.ts Main orchestrator — feed loading, SAMLDS redirect, hub pinning
src/app/clarin-wayf/clarin-wayf-routes.ts Standalone route at /wayf
src/app/shared/log-in/methods/shibboleth-wayf/log-in-shibboleth-wayf.component.ts Replaces the Shibboleth hard-redirect button with inline WAYF
src/assets/mock/wayf-feed.json 10 sample IdPs for local development
src/app/clarin-wayf/AGENTS.md Agent/developer context document

Modified files

  • src/app/app-routes.ts — added lazy route /wayf
  • src/app/login-page/login-page.component.{ts,html} — added WAYF toggle button + collapsible panel below the password form
  • src/themes/custom/app/login-page/login-page.component.ts — added ClarinWayfComponent to themed wrapper imports
  • src/app/shared/auth-nav-menu/auth-nav-menu.component.{ts,html,scss} — replaced single login form with two-tab layout: Local Login + Institution (WAYF); dropdown widened to 400px
  • src/themes/custom/app/shared/auth-nav-menu/auth-nav-menu.component.ts — added ClarinWayfComponent to themed wrapper imports
  • src/app/shared/log-in/methods/log-in.methods-decorator.tsAuthMethodType.Shibboleth now maps to LogInShibbolethWayfComponent
  • src/app/shared/log-in/methods/auth-methods.type.ts — added LogInShibbolethWayfComponent to union type
  • src/assets/i18n/en.json5 — added wayf.*, login.wayf.*, nav.login.tab.* translation keys

Key technical notes

  • All components are standalone (no NgModules), using inject(), signals, input()/output(), and @if/@for control flow
  • Feed URL resolution priority: [feedUrl] input → ?feedUrl= query param → assets/mock/wayf-feed.json
  • On IdP selection, builds /Shibboleth.sso/Login?entityID=...&target=... and calls HardRedirectService.redirect()
  • No external search/UI library dependencies

Testing

npm test -- --include='src/app/clarin-wayf/**/*.spec.ts'

@jr-rk jr-rk requested a review from Copilot March 11, 2026 15:21
@jr-rk jr-rk self-assigned this Mar 11, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new CLARIN WAYF (IdP discovery) feature area and integrates it into DSpace Angular’s login flows, including a standalone /wayf route and Shibboleth login method rendering.

Changes:

  • Introduces src/app/clarin-wayf/ with standalone WAYF UI components plus feed/search/persistence/i18n services.
  • Integrates WAYF into /login and header login dropdown, and maps AuthMethodType.Shibboleth to a new WAYF-backed login method component.
  • Adds /wayf lazy route, English i18n keys, and a local mock IdP feed.

Reviewed changes

Copilot reviewed 27 out of 27 changed files in this pull request and generated 18 comments.

Show a summary per file
File Description
src/themes/custom/app/shared/auth-nav-menu/auth-nav-menu.component.ts Updates themed wrapper imports to include ClarinWayfComponent.
src/themes/custom/app/login-page/login-page.component.ts Updates themed wrapper imports to include ClarinWayfComponent.
src/assets/mock/wayf-feed.json Adds a mock DiscoFeed-style IdP JSON feed for local dev.
src/assets/i18n/en.json5 Adds new WAYF/login tab translation keys.
src/app/shared/log-in/methods/shibboleth-wayf/log-in-shibboleth-wayf.component.ts New Shibboleth login method that embeds the WAYF picker.
src/app/shared/log-in/methods/log-in.methods-decorator.ts Maps AuthMethodType.Shibboleth to the new WAYF-backed component.
src/app/shared/log-in/methods/auth-methods.type.ts Extends union type to include LogInShibbolethWayfComponent.
src/app/shared/auth-nav-menu/auth-nav-menu.component.ts Adds tab state + IdP selection redirect logic for header dropdown.
src/app/shared/auth-nav-menu/auth-nav-menu.component.scss Styles header dropdown tabs and increases dropdown width/scrolling.
src/app/shared/auth-nav-menu/auth-nav-menu.component.html Replaces single login content with Local/Institution tab layout + WAYF embed.
src/app/login-page/login-page.component.ts Adds WAYF toggle state + IdP selection redirect logic on /login.
src/app/login-page/login-page.component.html Adds collapsible WAYF panel below existing login methods.
src/app/clarin-wayf/services/search.service.ts Implements fuzzy search and language-aware ranking utilities.
src/app/clarin-wayf/services/search.service.spec.ts Adds unit tests for fuzzy search behavior.
src/app/clarin-wayf/services/persistence.service.ts Adds localStorage persistence for last/recent IdPs.
src/app/clarin-wayf/services/i18n.service.ts Adds self-contained signal-based i18n for WAYF UI strings.
src/app/clarin-wayf/services/feed.service.ts Adds HTTP feed loading + optional tag filtering + error state.
src/app/clarin-wayf/models/wayf-config.model.ts Adds typed config + SAMLDS params interfaces.
src/app/clarin-wayf/models/idp-entry.model.ts Adds TypeScript interfaces for the feed schema.
src/app/clarin-wayf/components/search-bar/wayf-search-bar.component.ts Adds search bar component (ARIA combobox scaffolding + events).
src/app/clarin-wayf/components/recent-idps/wayf-recent-idps.component.ts Adds recent IdP shortcut/selection component.
src/app/clarin-wayf/components/idp-list/wayf-idp-list.component.ts Adds listbox component intended for keyboard navigation and selection.
src/app/clarin-wayf/components/idp-card/wayf-idp-card.component.ts Adds a selectable IdP “card” renderer (logo/name/badge).
src/app/clarin-wayf/clarin-wayf.component.ts Adds main orchestrator (feed load, filtering, SAMLDS redirect, persistence).
src/app/clarin-wayf/clarin-wayf-routes.ts Adds standalone route definition for /wayf.
src/app/clarin-wayf/AGENTS.md Documents architecture, integration points, and TODOs.
src/app/app-routes.ts Adds lazy-loaded /wayf route.

Comment on lines +223 to +224
// Move focus from search bar into the list
this.idpList()?.activeIndex.set(0);
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onArrowDown() only updates activeIndex but doesn’t actually move focus into the list, so subsequent ArrowUp/Down/Enter keystrokes remain on the input and can’t select results. After setting the active index, programmatically focus the listbox/first option (or implement full combobox aria-activedescendant behavior).

Suggested change
// Move focus from search bar into the list
this.idpList()?.activeIndex.set(0);
const FIRST_OPTION_INDEX = 0;
this.idpList()?.activeIndex.set(FIRST_OPTION_INDEX);
const listComponent = this.idpList() as unknown as { focusActiveOption?: () => void };
listComponent.focusActiveOption?.();

Copilot uses AI. Check for mistakes.
Comment on lines +60 to +64
<div id="login-tab-institution" role="tabpanel" class="p-2">
<ds-clarin-wayf
(idpSelected)="onIdpSelected($event)"
/>
</div>
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<ds-clarin-wayf> is embedded without a feedUrl input. With the current ClarinWayfComponent defaults, this will always load assets/mock/wayf-feed.json (sample IdPs) in production unless something passes ?feedUrl=. Provide a real, configurable feed URL (e.g., from app config) or avoid defaulting to mock data in embedded usage.

Copilot uses AI. Check for mistakes.
Comment on lines +120 to +123
const origin = window.location.origin;
const returnUrl = encodeURIComponent(`${origin}/login`);
const ssoUrl = `${origin}/Shibboleth.sso/Login?entityID=${encodeURIComponent(entry.entityID)}&target=${returnUrl}`;
this.hardRedirectService.redirect(ssoUrl);
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The redirect URL is built using window.location.origin, which breaks SSR/testing and duplicates logic already available on HardRedirectService. Use hardRedirectService.getCurrentOrigin() (and ideally avoid hardcoding the Shibboleth handler path/params).

Copilot uses AI. Check for mistakes.
Comment on lines +212 to +217
if (params.return) {
// SAMLDS redirect
const separator = params.return.includes('?') ? '&' : '?';
const redirectUrl = `${params.return}${separator}${encodeURIComponent(params.returnIDParam)}=${encodeURIComponent(entry.entityID)}`;
window.location.href = redirectUrl;
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

window.location.href is used for redirects. This breaks SSR/testing (no window) and bypasses the app’s redirect abstraction. Prefer HardRedirectService.redirect(...) or NativeWindowService so redirects are platform-safe and consistent.

Copilot uses AI. Check for mistakes.
Comment on lines +130 to +134
onIdpSelected(entry: IdpEntry): void {
const origin = window.location.origin;
const returnUrl = encodeURIComponent(this.hardRedirectService.getCurrentRoute());
const ssoUrl = `${origin}/Shibboleth.sso/Login?entityID=${encodeURIComponent(entry.entityID)}&target=${returnUrl}`;
this.hardRedirectService.redirect(ssoUrl);
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The /Shibboleth.sso/Login URL is constructed manually using window.location.origin. This is brittle (SP handler path/params vary by deployment), and direct window access breaks SSR/testing. Use HardRedirectService.getCurrentOrigin() (instead of window) and make the SP endpoint/param names configurable (or derive them from the backend-provided auth method location).

Copilot uses AI. Check for mistakes.
Comment on lines +59 to +64
@if (activeLoginTab() === 'institution') {
<div id="login-tab-institution" role="tabpanel" class="p-2">
<ds-clarin-wayf
(idpSelected)="onIdpSelected($event)"
/>
</div>
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<ds-clarin-wayf> is rendered unconditionally in the header login dropdown and will attempt to redirect to /Shibboleth.sso/Login on selection even when Shibboleth isn’t configured as an auth method. Consider only showing the “Institution” tab (and wiring onIdpSelected) when a Shibboleth auth method is available, otherwise users may hit a broken redirect.

Copilot uses AI. Check for mistakes.
<i class="fas fa-times" aria-hidden="true"></i>
</button>
</div>
<ds-clarin-wayf
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

<ds-clarin-wayf> is embedded without a feedUrl input, so it will fall back to assets/mock/wayf-feed.json by default. This will surface sample IdPs on the real login page unless a production feed URL is provided. Pass a configurable feed URL (e.g., from app config) or change the component default to not use mock data in production.

Suggested change
<ds-clarin-wayf
<ds-clarin-wayf
[feedUrl]="wayfFeedUrl"

Copilot uses AI. Check for mistakes.
Comment on lines +245 to +252
if (this.samldsParams().isPassive) {
const lastIdp = this.persistence.lastIdp();
if (lastIdp && this.samldsParams().return) {
const params = this.samldsParams();
const separator = params.return!.includes('?') ? '&' : '?';
const redirectUrl = `${params.return}${separator}${encodeURIComponent(params.returnIDParam)}=${encodeURIComponent(lastIdp)}`;
window.location.href = redirectUrl;
}
Copy link

Copilot AI Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseSamldsParams() performs an automatic redirect via window.location.href when isPassive=true. This will throw in SSR/testing and bypasses the redirect abstraction. Use HardRedirectService.redirect(...)/NativeWindowService here as well (and consider making this behavior testable).

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants