From 75992e3cf08405680aa6106e6291e6a22728abff Mon Sep 17 00:00:00 2001 From: Paurikova2 <107862249+Paurikova2@users.noreply.github.com> Date: Tue, 10 Feb 2026 10:49:00 +0100 Subject: [PATCH 01/63] Fix incorrect translation of "withdraw" (#1191) * used stahnout instead of odstranit * Update confirmation text for withdrawal action * removed two empty lines * added empty lines as in origin * removed spaces --------- Co-authored-by: Kasinhou <129340513+Kasinhou@users.noreply.github.com> --- src/assets/i18n/cs.json5 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5 index 23dee6592f7..7f513b4f918 100644 --- a/src/assets/i18n/cs.json5 +++ b/src/assets/i18n/cs.json5 @@ -3663,10 +3663,10 @@ "item.edit.tabs.status.buttons.unauthorized": "Nejste oprávněni provést tuto akci", // "item.edit.tabs.status.buttons.withdraw.button": "Withdraw this item", - "item.edit.tabs.status.buttons.withdraw.button": "Odstranit...", + "item.edit.tabs.status.buttons.withdraw.button": "Stáhnout...", // "item.edit.tabs.status.buttons.withdraw.label": "Withdraw item from the repository", - "item.edit.tabs.status.buttons.withdraw.label": "Odstranit záznam z repozitáře", + "item.edit.tabs.status.buttons.withdraw.label": "Stáhnout záznam z repozitáře", // "item.edit.tabs.status.description": "Welcome to the item management page. From here you can withdraw, reinstate, move or delete the item. You may also update or add new metadata / bitstreams on the other tabs.", "item.edit.tabs.status.description": "Vítejte na stránce pro správu záznamů. Odtud můžete záznam stáhnout, obnovit, přesunout nebo odstranit. Na ostatních záložkách můžete také aktualizovat nebo přidávat nová metadata / bitové toky.", @@ -3711,16 +3711,16 @@ "item.edit.withdraw.confirm": "Potvrdit", // "item.edit.withdraw.description": "Are you sure this item should be withdrawn from the archive?", - "item.edit.withdraw.description": "Jste si jisti, že by tento záznam měl být odebrán z archivu?", + "item.edit.withdraw.description": "Jste si jisti, že by tento záznam měl být stažen z archivu?", // "item.edit.withdraw.error": "An error occurred while withdrawing the item", - "item.edit.withdraw.error": "Při odebírání záznamu došlo k chybě", + "item.edit.withdraw.error": "Při stahování záznamu došlo k chybě", // "item.edit.withdraw.header": "Withdraw item: {{ id }}", - "item.edit.withdraw.header": "Odebrat záznam: {{ id }}", + "item.edit.withdraw.header": "Stáhnout záznam: {{ id }}", // "item.edit.withdraw.success": "The item was withdrawn successfully", - "item.edit.withdraw.success": "Záznam byl úspěšně odebrán", + "item.edit.withdraw.success": "Záznam byl úspěšně stažen", // "item.orcid.return": "Back", "item.orcid.return": "Zpět", From f9ddc449b92f0b317267385fd2d5de03d8fc8010 Mon Sep 17 00:00:00 2001 From: milanmajchrak <90026355+milanmajchrak@users.noreply.github.com> Date: Tue, 10 Feb 2026 15:47:19 +0100 Subject: [PATCH 02/63] UFAL/Removed green line from the home page (#1204) --- .../app/header-nav-wrapper/header-navbar-wrapper.component.scss | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/themes/dspace/app/header-nav-wrapper/header-navbar-wrapper.component.scss b/src/themes/dspace/app/header-nav-wrapper/header-navbar-wrapper.component.scss index eb8b65adfe9..328fbcbcffd 100644 --- a/src/themes/dspace/app/header-nav-wrapper/header-navbar-wrapper.component.scss +++ b/src/themes/dspace/app/header-nav-wrapper/header-navbar-wrapper.component.scss @@ -6,7 +6,6 @@ div#header-navbar-wrapper { // The header-navbar-wrapper should not have a z-index, otherwise it would cover the media viewer despite its higher z-index position: relative; // required by the mobile collapsible navbar - border-bottom: var(--ds-header-navbar-border-bottom-style); // gets covered by mobile navbar wrapper, when open div#mobile-navbar-wrapper { width: 100%; @@ -19,7 +18,6 @@ // Following parameters are changed by slideMobileNav animation min-height: var(--ds-expandable-navbar-height); height: auto; - border-bottom: var(--ds-header-navbar-border-bottom-style); } } } From c794f52b72c8cb06343667729e3a92966f18ac8f Mon Sep 17 00:00:00 2001 From: Paurikova2 <107862249+Paurikova2@users.noreply.github.com> Date: Thu, 12 Feb 2026 12:16:36 +0100 Subject: [PATCH 03/63] UFAL/Added import-5 to deploy (#1206) --- .github/workflows/deploy.yml | 42 ++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e6104dc8775..951a56d570c 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -109,6 +109,48 @@ jobs: /bin/bash -c 'while [[ "$(curl -s -o /dev/null -w ''%{http_code}'' http://dev-5.pc:8$INSTANCE/repository/server/api)" != "200" ]]; do sleep 5; done' + import-5: + runs-on: dspace-dep-1 + if: inputs.IMPORT + needs: deploy-5 + env: + INSTANCE: '5' + ENVFILE: /opt/dspace-envs/.env.dspace.dev-5 + steps: + - uses: ./.github/actions/import-db + with: + INSTANCE: ${{ env.INSTANCE }} + DATADIR: /opt/dspace-data/clarin-dspace-oxford/ + ASSETSTORE: /opt/dspace-data/clarin-dspace-oxford/assetstore/ + LOGDIR: /log/ + ADMIN_PASSWORD: ${{ secrets.DSPACE_ADMIN_PASSWORD }} + + - name: dspace basic command + run: | + export DNAME=dspace$INSTANCE + docker logs -n 50 $DNAME + + echo "dspace version:" + docker exec $DNAME /bin/bash -c "cd /dspace/bin && ./dspace version" + + echo "dspace cleanup:" + docker exec $DNAME /bin/bash -c "cd /dspace/bin && ./dspace cleanup -v" + + echo "dspace reindex solr:" + docker exec $DNAME /bin/bash -c "cd /dspace/bin && ./dspace index-discovery -b" + + echo "dspace reindex OAI-PMH:" + docker exec $DNAME /bin/bash -c "cd /dspace/bin && ./dspace oai import -c" + + echo "dspace checker:" + docker exec $DNAME /bin/bash -c "cd /dspace/bin && ./dspace checker -v -l" + + - name: dspace healthcheck + run: | + export DNAME=dspace$INSTANCE + echo "dspace healthcheck:" + docker exec $DNAME /bin/bash -c "cd /dspace/bin && ./dspace healthcheck -v" + import-8: runs-on: dspace-dep-1 if: inputs.IMPORT From 1b769b9bfd1727cc00e1e90fbf505352aa99ca3f Mon Sep 17 00:00:00 2001 From: milanmajchrak <90026355+milanmajchrak@users.noreply.github.com> Date: Wed, 18 Feb 2026 13:44:02 +0100 Subject: [PATCH 04/63] UFAL/Removed unused github docker registry (#1212) --- .github/workflows/build.yml | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d08eaf48708..249b4d84d78 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -49,9 +49,6 @@ jobs: NODE_OPTIONS: '--max-old-space-size=4096' # Project name to use when running "docker compose" prior to e2e tests COMPOSE_PROJECT_NAME: 'ci' - # Docker Registry to use for Docker compose scripts below. - # We use GitHub's Container Registry to avoid aggressive rate limits at DockerHub. - DOCKER_REGISTRY: ghcr.io strategy: # Create a matrix of Node versions to test against (in parallel) matrix: @@ -125,14 +122,6 @@ jobs: path: 'coverage/dspace-angular/lcov.info' retention-days: 14 - # Login to our Docker registry, so that we can access private Docker images using "docker compose" below. - - name: Login to ${{ env.DOCKER_REGISTRY }} - uses: docker/login-action@v3 - with: - registry: ${{ env.DOCKER_REGISTRY }} - username: ${{ github.repository_owner }} - password: ${{ secrets.GITHUB_TOKEN }} - # Using "docker compose" start backend using CI configuration # and load assetstore from a cached copy - name: Start DSpace REST Backend via Docker (for e2e tests) From d4d84cb063ac9415b82f0f1561538c689ed96186 Mon Sep 17 00:00:00 2001 From: Paurikova2 <107862249+Paurikova2@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:42:35 +0100 Subject: [PATCH 05/63] internal/Renamed dspace-import to dspace-import-clarin in import action (#1209) * renamed dspace-import to dspace-import-clarin * removed unwanted changes --- .github/actions/import-db/action.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/actions/import-db/action.yml b/.github/actions/import-db/action.yml index 15c3b355089..0b8984fb5ac 100644 --- a/.github/actions/import-db/action.yml +++ b/.github/actions/import-db/action.yml @@ -30,7 +30,7 @@ runs: - uses: actions/checkout@v4 with: - repository: dataquest-dev/dspace-import + repository: dataquest-dev/dspace-import-clarin ref: 'main' submodules: 'recursive' path: 'dspace-import' @@ -39,7 +39,7 @@ runs: - name: stop and remove containers id: import shell: bash - working-directory: dspace-import/scripts + working-directory: dspace-import-clarin/scripts env: DATADIR: ${{ inputs.DATADIR }} DB5PORT: 15432 From 0ee58b20a6b17105df4e33ec4ca914afbc668e6f Mon Sep 17 00:00:00 2001 From: Paurikova2 <107862249+Paurikova2@users.noreply.github.com> Date: Wed, 18 Feb 2026 15:55:17 +0100 Subject: [PATCH 06/63] UFAL/Editing similar process parameters (#1195) * Preserve parameters when creating similar process * Extract duplicate deep copy logic into helper * Fix redundant parameter logic prevent empty accumulation --- .../form/process-form.component.html | 2 +- .../form/process-form.component.ts | 13 +++++++++ .../process-parameters.component.ts | 29 +++++++++++++++++-- 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/app/process-page/form/process-form.component.html b/src/app/process-page/form/process-form.component.html index 211129489e5..6d1cfa4478d 100644 --- a/src/app/process-page/form/process-form.component.html +++ b/src/app/process-page/form/process-form.component.html @@ -5,7 +5,7 @@

- + {{ 'process.new.cancel' | translate }} diff --git a/src/app/process-page/form/process-form.component.ts b/src/app/process-page/form/process-form.component.ts index 70eb3160a8e..7b25acf6d4a 100644 --- a/src/app/process-page/form/process-form.component.ts +++ b/src/app/process-page/form/process-form.component.ts @@ -65,6 +65,19 @@ export class ProcessFormComponent implements OnInit { this.process = new Process(); } + /** + * Handle script selection, preserving existing parameters if creating from existing process + * @param script The selected script + */ + onScriptSelect(script: Script): void { + this.selectedScript = script; + // Clear parameters if we're not creating from an existing process + // (i.e., when no existing parameters or when switching to a different script) + if (!this.parameters) { + this.parameters = []; + } + } + /** * Validates the form, sets the parameters to correct values and invokes the script with the correct parameters * @param form diff --git a/src/app/process-page/form/process-parameters/process-parameters.component.ts b/src/app/process-page/form/process-parameters/process-parameters.component.ts index 85b59f76447..4eab7879027 100644 --- a/src/app/process-page/form/process-parameters/process-parameters.component.ts +++ b/src/app/process-page/form/process-parameters/process-parameters.component.ts @@ -41,7 +41,8 @@ export class ProcessParametersComponent implements OnChanges { ngOnInit() { if (hasValue(this.initialParams)) { - this.parameterValues = this.initialParams; + // Create deep copy to avoid reference issues + this.parameterValues = this.deepCopyParameters(this.initialParams); } } @@ -51,7 +52,17 @@ export class ProcessParametersComponent implements OnChanges { */ ngOnChanges(changes: SimpleChanges): void { if (changes.script) { - this.initParameters(); + // Only reset parameters if we don't have initial parameters to preserve + if (!hasValue(this.initialParams)) { + this.initParameters(); + } else { + // If we have initial parameters, preserve them with deep copy + this.parameterValues = this.deepCopyParameters(this.initialParams); + // Only add an empty parameter if the list is empty or doesn't have a trailing empty parameter + if (this.parameterValues.length === 0 || hasValue(this.parameterValues[this.parameterValues.length - 1].name)) { + this.addParameter(); + } + } } } @@ -61,7 +72,8 @@ export class ProcessParametersComponent implements OnChanges { */ initParameters() { if (hasValue(this.initialParams)) { - this.parameterValues = this.initialParams; + // Create deep copy to avoid reference issues + this.parameterValues = this.deepCopyParameters(this.initialParams); } else { this.parameterValues = []; this.initializeParameter(); @@ -111,4 +123,15 @@ export class ProcessParametersComponent implements OnChanges { addParameter() { this.parameterValues = [...this.parameterValues, new ProcessParameter()]; } + + /** + * Creates a deep copy of ProcessParameter array to avoid reference issues + * @param params The parameters to copy + * @returns A new array with copied ProcessParameter instances + */ + private deepCopyParameters(params: ProcessParameter[]): ProcessParameter[] { + return params.map(param => + Object.assign(new ProcessParameter(), { name: param.name, value: param.value }) + ); + } } From 2f146949a57e5f36e5791e353c67a9b3435d99bd Mon Sep 17 00:00:00 2001 From: Kasinhou <129340513+Kasinhou@users.noreply.github.com> Date: Thu, 19 Feb 2026 08:56:29 +0100 Subject: [PATCH 07/63] UFAL/Display all versions in version history (#1213) * Override pagesize and display all versions * Changed hardcoded variable --------- Co-authored-by: Matus Kasak --- .../clarin-item-versions-field.component.ts | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts index 5ed269c5988..d0507103c12 100644 --- a/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts +++ b/src/app/item-page/simple/field-components/clarin-item-versions-field/clarin-item-versions-field.component.ts @@ -1,6 +1,6 @@ import { Component, Input, OnInit } from '@angular/core'; import { Observable, of, combineLatest } from 'rxjs'; -import { map, switchMap } from 'rxjs/operators'; +import { map, switchMap, shareReplay } from 'rxjs/operators'; import { ItemVersionsComponent } from '../../../versions/item-versions.component'; import { Item } from '../../../../core/shared/item.model'; import { Version } from '../../../../core/shared/version.model'; @@ -40,6 +40,11 @@ interface EnhancedVersionDTO extends VersionDTO { }) export class ClarinItemVersionsFieldComponent extends ItemVersionsComponent implements OnInit { + /** + * Maximum number of versions to fetch at once for the dropdown display. + */ + private readonly MAX_VERSIONS_TO_DISPLAY = 9999; + /** * Icon name for the clarin field */ @@ -62,7 +67,12 @@ export class ClarinItemVersionsFieldComponent extends ItemVersionsComponent impl enhancedVersions$: Observable; ngOnInit(): void { - // Call parent's ngOnInit first to set up all the observables + // Override the parent's pageSize to fetch all versions at once for the dropdown display + this.pageSize = this.MAX_VERSIONS_TO_DISPLAY; + this.options = Object.assign(this.options, { + pageSize: this.pageSize + }); + super.ngOnInit(); // Set up clarin-specific showMetadataValue logic @@ -98,7 +108,8 @@ export class ClarinItemVersionsFieldComponent extends ItemVersionsComponent impl isCurrentVersion: versionDTO.version.id === currentVersionId } as EnhancedVersionDTO; }); - }) + }), + shareReplay(1) // Cache the result to prevent duplicate requests ); } else { // Fallback: check if isAdmin$ is available, otherwise hide the component From aac563ea9706d39bedf080204b43b7347256b6e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Ko=C5=A1arko?= Date: Thu, 19 Feb 2026 09:00:06 +0100 Subject: [PATCH 08/63] UFAL/Missing translation for new curation task (#1208) (cherry picked from commit 5465283b9bac58d8af9513df00af90e7a0ba9baa) --- src/assets/i18n/cs.json5 | 5 ++++- src/assets/i18n/en.json5 | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/assets/i18n/cs.json5 b/src/assets/i18n/cs.json5 index 7f513b4f918..635847a464a 100644 --- a/src/assets/i18n/cs.json5 +++ b/src/assets/i18n/cs.json5 @@ -2183,6 +2183,9 @@ // "curation-task.task.registerdoi.label": "Register DOI", "curation-task.task.registerdoi.label": "Zaregistrujte DOI", + // "curation-task.task.metadataqa.label": "Metadata QA", + "curation-task.task.metadataqa.label": "Metadata QA", + // "curation.form.task-select.label": "Task:", "curation.form.task-select.label": "Úloha:", @@ -10068,4 +10071,4 @@ "statistics.views-downloads.view-button": "Statistiky", -} \ No newline at end of file +} diff --git a/src/assets/i18n/en.json5 b/src/assets/i18n/en.json5 index 0b384eafc3a..77e9cb4497d 100644 --- a/src/assets/i18n/en.json5 +++ b/src/assets/i18n/en.json5 @@ -1455,6 +1455,8 @@ "curation-task.task.registerdoi.label": "Register DOI", + "curation-task.task.metadataqa.label": "Metadata QA", + "curation.form.task-select.label": "Task:", "curation.form.submit": "Start", From 0434ff9be5d854aa46c6d279a3869713211c433a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Ko=C5=A1arko?= Date: Thu, 19 Feb 2026 11:57:04 +0100 Subject: [PATCH 09/63] UFAL/We expect (in metadata) the path to be "licence" (#1193) (cherry picked from commit 21521d1e16dbb7650b9dddeff5a916fdf142e200) (cherry picked from commit c296d8771ca0ad2e82d358ac12c031a1e3488f7d) --- .../clarin-license-agreement-page.component.ts | 2 +- .../cs/{szn-dataset-license.html => szn-dataset-licence.html} | 0 .../{szn-dataset-license.html => szn-dataset-licence.html} | 0 3 files changed, 1 insertion(+), 1 deletion(-) rename src/static-files/cs/{szn-dataset-license.html => szn-dataset-licence.html} (100%) rename src/static-files/{szn-dataset-license.html => szn-dataset-licence.html} (100%) diff --git a/src/app/bitstream-page/clarin-license-agreement-page/clarin-license-agreement-page.component.ts b/src/app/bitstream-page/clarin-license-agreement-page/clarin-license-agreement-page.component.ts index fb66d19ea98..38636514dcf 100644 --- a/src/app/bitstream-page/clarin-license-agreement-page/clarin-license-agreement-page.component.ts +++ b/src/app/bitstream-page/clarin-license-agreement-page/clarin-license-agreement-page.component.ts @@ -115,7 +115,7 @@ export class ClarinLicenseAgreementPageComponent implements OnInit { /** * The path to the Seznam dataset license content. */ - LICENSE_PATH_SEZNAM_CZ = 'szn-dataset-license.html'; + LICENSE_PATH_SEZNAM_CZ = 'szn-dataset-licence.html'; /** * The content of the Seznam dataset license. Fetch from the static file. diff --git a/src/static-files/cs/szn-dataset-license.html b/src/static-files/cs/szn-dataset-licence.html similarity index 100% rename from src/static-files/cs/szn-dataset-license.html rename to src/static-files/cs/szn-dataset-licence.html diff --git a/src/static-files/szn-dataset-license.html b/src/static-files/szn-dataset-licence.html similarity index 100% rename from src/static-files/szn-dataset-license.html rename to src/static-files/szn-dataset-licence.html From 99637f023dd63a66057512c32c2577fc992160e6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Ko=C5=A1arko?= Date: Thu, 19 Feb 2026 12:00:51 +0100 Subject: [PATCH 10/63] UFAL/Rewrite OAI links in static page HTML with rest.baseUrl (ufal/dspace-angular#90) (#1189) * Rewrite OAI links in static page HTML with rest.baseUrl Updated StaticPageComponent to rewrite OAI links in loaded HTML content to use the configured rest.baseUrl, ensuring correct API endpoint references. Added comprehensive tests to verify link rewriting, handling of missing baseUrl, avoidance of double slashes, and cases with no OAI links. * Remove unused ComponentFixture import in test Cleaned up the static-page.component.spec.ts file by removing the unused ComponentFixture import to improve code clarity. * Fix OAI URL construction and improve test coverage Corrects the construction of the OAI URL in StaticPageComponent to avoid double slashes by removing the extra slash in the base URL. Also updates the unit test to properly instantiate the component and check its creation. (cherry picked from commit cb86d0778d8b5808d75b95358380a0012e79af4f) Co-authored-by: Amad Ul Hassan --- .../static-page/static-page.component.spec.ts | 90 ++++++++++++++----- src/app/static-page/static-page.component.ts | 6 +- 2 files changed, 71 insertions(+), 25 deletions(-) diff --git a/src/app/static-page/static-page.component.spec.ts b/src/app/static-page/static-page.component.spec.ts index 1ad4e607c3c..0c461042381 100644 --- a/src/app/static-page/static-page.component.spec.ts +++ b/src/app/static-page/static-page.component.spec.ts @@ -1,4 +1,4 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { TestBed } from '@angular/core/testing'; import { StaticPageComponent } from './static-page.component'; import { HtmlContentService } from '../shared/html-content.service'; @@ -11,27 +11,25 @@ import { environment } from '../../environments/environment'; import { ClarinSafeHtmlPipe } from '../shared/utils/clarin-safehtml.pipe'; describe('StaticPageComponent', () => { - let component: StaticPageComponent; - let fixture: ComponentFixture; - - let htmlContentService: HtmlContentService; - let appConfig: any; - - const htmlContent = '
TEST MESSAGE
'; - - beforeEach(async () => { - htmlContentService = jasmine.createSpyObj('htmlContentService', { - fetchHtmlContent: of(htmlContent), - getHmtlContentByPathAndLocale: Promise.resolve(htmlContent) + async function setupTest(html: string, restBase?: string) { + const htmlContentService = jasmine.createSpyObj('htmlContentService', { + fetchHtmlContent: of(html), + getHmtlContentByPathAndLocale: Promise.resolve(html) }); - appConfig = Object.assign(environment, { + const appConfig = { + ...environment, ui: { + ...(environment as any).ui, namespace: 'testNamespace' + }, + rest: { + ...(environment as any).rest, + baseUrl: restBase } - }); + }; - TestBed.configureTestingModule({ + await TestBed.configureTestingModule({ declarations: [ StaticPageComponent, ClarinSafeHtmlPipe ], imports: [ TranslateModule.forRoot() @@ -41,22 +39,66 @@ describe('StaticPageComponent', () => { { provide: Router, useValue: new RouterMock() }, { provide: APP_CONFIG, useValue: appConfig } ] - }); - - }); + }).compileComponents(); - beforeEach(() => { - fixture = TestBed.createComponent(StaticPageComponent); - component = fixture.componentInstance; - }); + const fixture = TestBed.createComponent(StaticPageComponent); + const component = fixture.componentInstance; + return { fixture, component, htmlContentService }; + } - it('should create', () => { + it('should create', async () => { + const { component } = await setupTest('
test
'); expect(component).toBeTruthy(); }); // Load `TEST MESSAGE` it('should load html file content', async () => { + const { component } = await setupTest('
TEST MESSAGE
'); await component.ngOnInit(); expect(component.htmlContent.value).toBe('
TEST MESSAGE
'); }); + + it('should rewrite OAI link with rest.baseUrl', async () => { + const oaiHtml = 'OAI'; + const { fixture, component } = await setupTest(oaiHtml, 'https://api.example.org/rest'); + + await component.ngOnInit(); + fixture.detectChanges(); + + const rewritten = 'https://api.example.org/server/oai/request?verb=ListSets'; + expect(component.htmlContent.value).toContain(rewritten); + const anchor = fixture.nativeElement.querySelector('a'); + expect(anchor.getAttribute('href')).toBe(rewritten); + }); + + it('should leave OAI link unchanged when rest.baseUrl is missing', async () => { + const oaiHtml = 'OAI'; + const { fixture, component } = await setupTest(oaiHtml, undefined); + + await component.ngOnInit(); + fixture.detectChanges(); + + expect(component.htmlContent.value).toContain('/server/oai/request?verb=Identify'); + }); + + it('should avoid double slashes when rest.baseUrl ends with slash', async () => { + const oaiHtml = 'OAI'; + const { fixture, component } = await setupTest(oaiHtml, 'https://api.example.org/rest/'); + + await component.ngOnInit(); + fixture.detectChanges(); + + expect(component.htmlContent.value).toContain('https://api.example.org/server/oai/request?verb=ListRecords'); + expect(component.htmlContent.value).not.toContain('//server'); + }); + + it('should leave content unchanged when no OAI link is present', async () => { + const otherHtml = 'Other'; + const { fixture, component } = await setupTest(otherHtml, 'https://api.example.org/rest'); + + await component.ngOnInit(); + fixture.detectChanges(); + + expect(component.htmlContent.value).toBe(otherHtml); + }); }); diff --git a/src/app/static-page/static-page.component.ts b/src/app/static-page/static-page.component.ts index bb19403a704..aff3eed0aed 100644 --- a/src/app/static-page/static-page.component.ts +++ b/src/app/static-page/static-page.component.ts @@ -28,8 +28,12 @@ export class StaticPageComponent implements OnInit { // Fetch html file name from the url path. `static/some_file.html` this.htmlFileName = this.getHtmlFileName(); - const htmlContent = await this.htmlContentService.getHmtlContentByPathAndLocale(this.htmlFileName); + let htmlContent = await this.htmlContentService.getHmtlContentByPathAndLocale(this.htmlFileName); if (isNotEmpty(htmlContent)) { + const restBase = this.appConfig?.rest?.baseUrl; + const oaiUrl = restBase ? new URL('/server/oai', restBase).href : '/server/oai'; + htmlContent = htmlContent.replace(/href="\/server\/oai/gi, 'href="' + oaiUrl); + this.htmlContent.next(htmlContent); return; } From d6ed9726d94beeba6fd8799f9eb8eb49c5365ac5 Mon Sep 17 00:00:00 2001 From: Kasinhou <129340513+Kasinhou@users.noreply.github.com> Date: Mon, 23 Feb 2026 13:55:19 +0100 Subject: [PATCH 11/63] Update path for dspace-import in action.yml (#1216) Use dspace-import-clarin path instead of dspace-import --- .github/actions/import-db/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/import-db/action.yml b/.github/actions/import-db/action.yml index 0b8984fb5ac..21fab40bdcd 100644 --- a/.github/actions/import-db/action.yml +++ b/.github/actions/import-db/action.yml @@ -33,7 +33,7 @@ runs: repository: dataquest-dev/dspace-import-clarin ref: 'main' submodules: 'recursive' - path: 'dspace-import' + path: 'dspace-import-clarin' - name: stop and remove containers From 6fc73d8ca0475fb6689d3824138683f2366bb76c Mon Sep 17 00:00:00 2001 From: Jozef Misutka <332350+vidiecan@users.noreply.github.com> Date: Tue, 24 Feb 2026 11:41:53 +0100 Subject: [PATCH 12/63] Refactor erase-db action to clean up comments Removed unnecessary comments and improved logging for volume removal. --- .github/actions/erase-db/action.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/actions/erase-db/action.yml b/.github/actions/erase-db/action.yml index 9ec7e828cd1..ec0ad2ce435 100644 --- a/.github/actions/erase-db/action.yml +++ b/.github/actions/erase-db/action.yml @@ -28,9 +28,6 @@ runs: env: NAME: ${{ inputs.NAME }} run: | - # # condition below was found by accident and appears to be useless. Investigate later. - # be sure to have INSTANCE set - # if [[ "x${NAME}" != "dspace-" ]]; then - docker volume rm $(docker volume ls --filter name="${NAME}_" -q) || true - # fi; + echo "Removing volumes for $(docker volume ls --filter name="${NAME}_" -q)" + docker volume rm $(docker volume ls --filter name="${NAME}_" -q) || true From 911bb36817f38e7d7467dc789b64a713e6f37298 Mon Sep 17 00:00:00 2001 From: milanmajchrak <90026355+milanmajchrak@users.noreply.github.com> Date: Tue, 24 Feb 2026 17:00:51 +0100 Subject: [PATCH 13/63] Use `dspace:dspace` permissions for the assetstore - not ubuntu:ubuntu (#1222) * Use `dspace:dspace` permissions for the assetstore - not ubuntu:ubuntu * Change permissions as root --- .github/actions/import-db/action.yml | 1 + build-scripts/run/start.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/actions/import-db/action.yml b/.github/actions/import-db/action.yml index 21fab40bdcd..4e17c221365 100644 --- a/.github/actions/import-db/action.yml +++ b/.github/actions/import-db/action.yml @@ -62,6 +62,7 @@ runs: echo Location of assetstore folder is empty. Not copping assetstore else docker cp ${{ inputs.ASSETSTORE }} dspace${{ inputs.INSTANCE }}:/dspace/ + docker exec -u root dspace${{ inputs.INSTANCE }} chown -R dspace:dspace /dspace/assetstore fi echo "=====" cd ../ diff --git a/build-scripts/run/start.sh b/build-scripts/run/start.sh index 5dfdfc8cd4d..07a57418779 100755 --- a/build-scripts/run/start.sh +++ b/build-scripts/run/start.sh @@ -48,6 +48,7 @@ popd echo "=====" echo "Copy assetstore" docker cp assetstore dspace${INSTANCE}:/dspace/ +docker exec -u root dspace${INSTANCE} chown -R dspace:dspace /dspace/assetstore echo "=====" echo "Finished start.sh" From f7e63c4d5a91f3baed5c35016d87ed11f5a94bbd Mon Sep 17 00:00:00 2001 From: Matus Kasak Date: Wed, 25 Feb 2026 16:24:54 +0100 Subject: [PATCH 14/63] First version of agents.md --- docs/agents.md | 434 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 434 insertions(+) create mode 100644 docs/agents.md diff --git a/docs/agents.md b/docs/agents.md new file mode 100644 index 00000000000..7cd073a7437 --- /dev/null +++ b/docs/agents.md @@ -0,0 +1,434 @@ +# DSpace Angular – Agent & User Guide + +> **Audience**: AI coding agents (GitHub Copilot, Cursor, etc.) **and** humans who delegate issues to them. +> Covers the exact steps a user must do manually, the prompt to give the agent, what the agent must do, and hard-won lessons from real CI failures. + +--- + +## 1. Project Facts + +| Property | Value | +|----------|-------| +| Framework | Angular 15 + Angular Universal (SSR) | +| Language | TypeScript 4.8 | +| Package manager | **Yarn 1.x** — never use `npm` | +| Node.js | **18.x** (`nvm use 18`) — Node 20+ breaks `eslint-plugin-jsdoc` | +| Unit tests | Jasmine / Karma (~5 300 specs, ~3.5 min) | +| E2E tests | Cypress 13, Chrome headless | +| Main branch | `dtq-dev` | +| CI file | `.github/workflows/build.yml` | +| Repo | `dataquest-dev/dspace-angular` | + +--- + +## 2. User Checklist — What YOU Must Do Before the Agent Starts + +The agent cannot start Docker, install nvm, or open VS Code for you. Do these steps **once** per machine / session. + +### 2.1 One-time Setup + +1. Install **Docker Desktop** and start it. +2. Install **nvm** (or nvm-windows) and run: + ```bash + nvm install 18 + nvm use 18 + ``` +3. Install Yarn globally: `npm install -g yarn` +4. Clone the repo (if not done): + ```bash + git clone https://github.com/dataquest-dev/dspace-angular.git + cd dspace-angular + ``` + +### 2.2 Before Every Agent Session + +1. **Start the DSpace backend in Docker** (needed for e2e tests): + ```bash + docker compose -p ci -f docker/docker-compose-ci.yml up -d + docker compose -p ci -f docker/cli.yml -f docker/cli.assetstore.yml run --rm dspace-cli + ``` + Wait until `docker container ls` shows all containers healthy. + Verify: `curl http://localhost:8080/server/api/core/sites` returns JSON. + +2. **Ensure correct Node version** — `node --version` must say `v18.x`. + +3. **Set the heap size** (prevents OOM on build): + - PowerShell: `$env:NODE_OPTIONS='--max-old-space-size=4096'` + - Bash: `export NODE_OPTIONS='--max-old-space-size=4096'` + +4. **Install dependencies**: + ```bash + yarn install --frozen-lockfile + ``` + If Cypress download fails: `CYPRESS_INSTALL_BINARY=0 yarn install --frozen-lockfile` + +5. **Open the project in VS Code** (or your editor). + +6. **Create a feature branch**: + ```bash + git checkout dtq-dev && git pull + git checkout -b ufal/ + ``` + +### 2.3 Giving the Issue to the Agent + +Paste the agent a prompt like this (adapt the issue URL and description): + +> Here is the issue: `` +> Here is the agent guide: `docs/agents.md` +> +> Please read the guide first, then implement the fix, and iterate through **every CI step** +> (lint → circ-deps → build → unit tests → e2e tests) until all pass. +> Only then commit and push to the branch `ufal/`. +> If e2e tests fail on known pre-existing issues, that is acceptable — see the guide. + +That's it. The rest is the agent's job. + +--- + +## 3. The CI Pipeline (`build.yml`) + +Every PR triggers CI on Node 18.x and 20.x. **All steps must pass.** + +| # | Step | Command | Time | +|---|------|---------|------| +| 1 | Install | `yarn install --frozen-lockfile` | ~2 min | +| 2 | Lint | `yarn run lint --quiet` | ~76 sec | +| 3 | Circular deps | `yarn run check-circ-deps` | ~34 sec | +| 4 | Build (browser + server) | `yarn run build:prod` | ~6.5 min | +| 5 | Unit tests | `yarn run test:headless` | ~3.5 min | +| 6 | Docker backend up | `docker compose -f docker/docker-compose-ci.yml up -d` + assetstore | ~3 min | +| 7 | E2E tests | `yarn run serve:ssr` + Cypress via `cypress-io/github-action` | ~5 min | +| 8 | SSR verification | `wget` + `grep` on 10 entity pages (Home, Community, Collection, Publication, Person, Project, OrgUnit, Journal, Journal Volume, Journal Issue) | ~1 min | +| 9 | HTTP status codes | 301 `/handle/*`, 403, 404, 500 | ~30 sec | + +--- + +## 4. Agent Workflow — Step-by-Step Iteration + +### ⚠️ RULE: Do NOT commit or push until steps 1–5 all pass. Steps 6–7 if Docker backend is available. + +``` +FOR EACH step below: + 1. Run the command + 2. If it FAILS → fix the code → rerun THAT SAME step + 3. Only proceed to the next step when current one is green +``` + +### Step 1 — Install Dependencies +```bash +yarn install --frozen-lockfile +``` + +### Step 2 — Lint +```bash +yarn run lint --quiet +``` +Must exit with code 0 and print "All files pass linting." + +### Step 3 — Circular Dependencies +```bash +yarn run check-circ-deps +``` +Must print "✔ No circular dependency found!" + +**⚠️ PowerShell trap**: The underlying `madge --exclude` regex contains `|` pipe characters. PowerShell interprets `|` as a pipeline operator even inside quotes. Use the stop-parsing token: +```powershell +npx --% madge --exclude "(bitstream|bundle|collection|config-submission-form|eperson|item|version)\.model\.ts$" --circular --extensions ts ./ +``` + +### Step 4 — Production Build +```bash +yarn run build:prod +``` +Takes ~6.5 min. Run as a background process and poll for completion. +Must produce **two** successful bundles: browser + server. Ignore warnings about unused theme components and CommonJS dependencies — these are pre-existing. + +### Step 5 — Unit Tests +```bash +yarn run test:headless +``` +Expect ~5 300 specs, 0 failures. If a test fails, read the error, fix the code or test, rerun. + +Run a single test file: +```bash +yarn run test:headless --include='**/path/to/component.spec.ts' +``` + +### Step 6 — E2E Tests (requires Docker backend) + +Start the SSR server first (if not already running): +```bash +yarn run serve:ssr & # background +# Wait for http://localhost:4000 to respond +``` + +Run safe public-page specs (no login, no specific test data required): +```bash +npx cypress run --spec "cypress/e2e/footer.cy.ts,cypress/e2e/header.cy.ts,cypress/e2e/pagenotfound.cy.ts,cypress/e2e/browse-by-title.cy.ts,cypress/e2e/browse-by-author.cy.ts,cypress/e2e/browse-by-subject.cy.ts,cypress/e2e/community-list.cy.ts,cypress/e2e/search-page.cy.ts,cypress/e2e/feedback.cy.ts,cypress/e2e/browse-by-dateissued.cy.ts" --browser chrome +``` + +On Windows PowerShell, set the base URL first: +```powershell +$env:CYPRESS_BASE_URL="http://localhost:4000" +npx cypress run --spec "cypress/e2e/footer.cy.ts,..." --browser chrome +``` + +### Step 7 — Commit & Push (only after all green) + +```bash +git add # NEVER add config/config.yml or .env.* files +git commit -m "fix: " +git push origin ufal/ +``` + +--- + +## 5. E2E Test Categories & What to Run + +| Category | Spec files | Requires | +|----------|-----------|----------| +| **Public pages** (always safe) | `footer`, `header`, `pagenotfound`, `browse-by-*`, `community-list`, `search-page`, `feedback` | Frontend only | +| **Data-dependent** | `collection-page`, `community-page`, `item-page` | Backend + Demo Entities assetstore | +| **Login-required** | `submission*`, `admin-*`, `my-dspace`, `profile-page`, `handle-page`, `health-page`, `tombstone`, `system-wide-alert` | Backend + Demo Entities + valid credentials | +| **No active tests** | `homepage`, `homepage-statistics`, `search-navbar`, `login-modal` | N/A | + +### Cypress Config + +- Config: `cypress.config.ts` +- Specs: `cypress/e2e/*.cy.ts` +- Default base URL: `http://localhost:4000` (override with `CYPRESS_BASE_URL`) +- Retries: 2 in run mode, 0 in open mode +- Test data: [Demo Entities Data set](https://github.com/DSpace-Labs/AIP-Files/releases/tag/demo-entities-data) + +### Key Test Variables (from `cypress.config.ts`) + +| Variable | Value | +|----------|-------| +| `DSPACE_TEST_ADMIN_USER` | `dspacedemo+admin@gmail.com` | +| `DSPACE_TEST_ADMIN_PASSWORD` | `dspace` | +| `DSPACE_TEST_COMMUNITY` | `0958c910-2037-42a9-81c7-dca80e3892b4` | +| `DSPACE_TEST_COLLECTION` | `282164f5-d325-4740-8dd1-fa4d6d3e7200` | +| `DSPACE_TEST_ENTITY_PUBLICATION` | `6160810f-1e53-40db-81ef-f6621a727398` | + +--- + +## 6. Known Pre-existing E2E Failures (Not Your Bug) + +Before panicking about a red test, check this list: + +| Symptom | Affected Specs | Root Cause | Action | +|---------|---------------|------------|--------| +| `cy.get('.discojuice_close').click()` fails — element is `display: none` | All login-dependent tests | DiscoJuice popup is hidden in Docker env | **Skip** — pre-existing | +| `cy.wait('@viewevent')` timeout | `collection-page.cy.ts` | Matomo/statistics not configured in Docker | **Skip** — pre-existing | +| Entity redirect fails | `item-page.cy.ts` | Backend entity routing not configured | **Skip** — pre-existing | +| `link-in-text-block` a11y violation | `privacy.cy.ts`, `end-user-agreement.cy.ts` | CSS link styling issue | **Skip** — pre-existing | + +**Rule**: Only fix failures that YOUR changes caused. If a test was already failing on `dtq-dev`, leave it alone. + +--- + +## 7. Hard-Won Lessons (Pitfalls & Traps) + +These come from real agent sessions. Read them before writing code. + +### 7.1 Angular Template Binding + +❌ **WRONG** — interpolation inside `aria-*` / `attr.*`: +```html +
+``` +Angular will throw `Can't bind to 'aria-labelledby' since it isn't a known property` at build time. + +✅ **CORRECT** — property binding: +```html +
+``` + +This applies to **all** `aria-*` and custom HTML attributes. Always use `[attr.X]="expression"` instead of `X="{{ interpolation }}"` for non-standard attributes. + +### 7.2 Dynamic HTML IDs Must Be Valid + +HTML `id` attributes must not contain whitespace or special characters. If an ID is built from dynamic data (e.g., bundle names, file names, user input), **sanitize it**: + +```typescript +sanitizedName = rawName.replace(/\s+/g, ''); +``` + +Then use `sanitizedName` in the template `id` attribute. Never trust that a service will return a "safe" string for an HTML ID. + +### 7.3 Conditional `aria-describedby` + +If a `