Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customer UK update 15.11.-6.12. #409

Merged
merged 10 commits into from
Dec 6, 2023
Merged
4 changes: 2 additions & 2 deletions .github/actions/import-db/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ runs:
with:
repository: dataquest-dev/dspace-python-api
path: dspace-python-api
ref: 'refactor_jm'
ref: 'main'


- name: stop and remove containers
Expand All @@ -44,7 +44,7 @@ runs:
# create otherwise it will be created with root owner
cid=$(docker run -d --rm --name $DB5NAME -v $(pwd):/dq/scripts -v $DATADIR/dump:/dq/dump -p 127.0.0.1:$DB5PORT:5432 -e POSTGRES_DB=empty -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=dspace postgres /bin/bash -c "cd /dq/scripts && ./init.dspacedb5.sh")
echo "cid=$cid" >> $GITHUB_OUTPUT
sleep 10
sleep 60
echo "====="
docker logs $DB5NAME || true
echo "====="
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { of as observableOf} from 'rxjs';
import { ActivatedRoute } from '@angular/router';
import { ConfigurationDataService } from '../../core/data/configuration-data.service';
import {createSuccessfulRemoteDataObject$} from '../../shared/remote-data.utils';
import { ConfigurationProperty } from '../../core/shared/configuration-property.model';
import { HELP_DESK_PROPERTY } from '../../item-page/tombstone/tombstone.component';
import { TranslateModule } from '@ngx-translate/core';
import { AuthFailedPageComponent } from './auth-failed-page.component';
import { RequestService } from '../../core/data/request.service';
import { HALEndpointService } from '../../core/shared/hal-endpoint.service';
import { getMockRemoteDataBuildService } from '../../shared/mocks/remote-data-build.service.mock';
import { RemoteDataBuildService } from '../../core/cache/builders/remote-data-build.service';
import { NotificationsServiceStub } from '../../shared/testing/notifications-service.stub';
import { NotificationsService } from '../../shared/notifications/notifications.service';

describe('DuplicateUserErrorComponent', () => {
let component: AuthFailedPageComponent;
let fixture: ComponentFixture<AuthFailedPageComponent>;
let mockConfigurationDataService: ConfigurationDataService;
let requestService: RequestService;
let activatedRoute: any;
let halService: HALEndpointService;
let rdbService: RemoteDataBuildService;
let notificationService: NotificationsServiceStub;

const rootUrl = 'root url';
const queryParams = 'netid[idp]';
const encodedQueryParams = 'netid%5Bidp%5D&email=';

activatedRoute = {
params: observableOf({}),
snapshot: {
queryParams: {
netid: queryParams
}
}
};

mockConfigurationDataService = jasmine.createSpyObj('configurationDataService', {
findByPropertyName: createSuccessfulRemoteDataObject$(Object.assign(new ConfigurationProperty(), {
name: HELP_DESK_PROPERTY,
values: [
'email'
]
}))
});

requestService = jasmine.createSpyObj('requestService', {
send: observableOf('response'),
generateRequestId: observableOf('123456'),
});

halService = jasmine.createSpyObj('authService', {
getRootHref: rootUrl,
});

rdbService = getMockRemoteDataBuildService();
notificationService = new NotificationsServiceStub();

beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [
TranslateModule.forRoot()
],
declarations: [ AuthFailedPageComponent ],
providers: [
{ provide: ActivatedRoute, useValue: activatedRoute },
{ provide: ConfigurationDataService, useValue: mockConfigurationDataService },
{ provide: RequestService, useValue: requestService },
{ provide: HALEndpointService, useValue: halService },
{ provide: RemoteDataBuildService, useValue: rdbService },
{ provide: NotificationsService, useValue: notificationService },
]
})
.compileComponents();
});

beforeEach(() => {
fixture = TestBed.createComponent(AuthFailedPageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should send request with encoded netId and email param', () => {
component.ngOnInit();
component.sendEmail();
expect(requestService.send).toHaveBeenCalledWith(jasmine.objectContaining({
href: rootUrl + '/autoregistration?netid=' + encodedQueryParams,
}));
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { RemoteData } from '../../core/data/remote-data';
import { ConfigurationProperty } from '../../core/shared/configuration-property.model';
Expand Down Expand Up @@ -42,7 +42,6 @@ export class AuthFailedPageComponent implements OnInit {

constructor(
protected configurationDataService: ConfigurationDataService,
protected router: Router,
public route: ActivatedRoute,
private requestService: RequestService,
protected halService: HALEndpointService,
Expand All @@ -61,7 +60,8 @@ export class AuthFailedPageComponent implements OnInit {
public sendEmail() {
const requestId = this.requestService.generateRequestId();

const url = this.halService.getRootHref() + '/autoregistration?netid=' + this.netid + '&email=' + this.email;
const url = this.halService.getRootHref() + '/autoregistration?netid=' + encodeURIComponent(this.netid) +
'&email=' + encodeURIComponent(this.email);
const postRequest = new PostRequest(requestId, url);
// Send POST request
this.requestService.send(postRequest);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import {
ResetAuthenticationMessagesAction
} from '../../../../core/auth/auth.actions';

import { getAuthenticationError, getAuthenticationInfo, } from '../../../../core/auth/selectors';
import { getAuthenticationError, getAuthenticationInfo } from '../../../../core/auth/selectors';
import { isEmpty, isNotEmpty } from '../../../empty.util';
import { fadeOut } from '../../../animations/fade';
import { AuthMethodType } from '../../../../core/auth/models/auth.method-type';
Expand Down Expand Up @@ -145,10 +145,13 @@ export class LogInPasswordComponent implements OnInit {
* Set up redirect URL. It could be loaded from the `authorizationService.getRedirectUrl()` or from the url.
*/
public async setUpRedirectUrl() {
// Get redirect URL from the authService `redirectUrl` property.
const fetchedRedirectUrl = await firstValueFrom(this.authService.getRedirectUrl());
if (isNotEmpty(fetchedRedirectUrl)) {
this.redirectUrl = fetchedRedirectUrl;
// Bring over the item ID as a query parameter
const queryParams = { redirectUrl: fetchedRedirectUrl };
// Redirect to login with `redirectUrl` param because the redirectionUrl is lost from the store after click on
// `local` login.
void this.router.navigate(['login'], { queryParams: queryParams });
}

// Store the `redirectUrl` value from the url and then remove that value from url.
Expand Down
4 changes: 3 additions & 1 deletion src/app/shared/shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -275,6 +275,7 @@ import { EpersonGroupListComponent } from './eperson-group-list/eperson-group-li
import { EpersonSearchBoxComponent } from './eperson-group-list/eperson-search-box/eperson-search-box.component';
import { GroupSearchBoxComponent } from './eperson-group-list/group-search-box/group-search-box.component';
import { HtmlContentService } from './html-content.service';
import { ClarinSafeHtmlPipe } from './utils/clarin-safehtml.pipe';

const MODULES = [
CommonModule,
Expand Down Expand Up @@ -319,7 +320,8 @@ const PIPES = [
ClarinLicenseCheckedPipe,
ClarinLicenseLabelRadioValuePipe,
ClarinLicenseRequiredInfoPipe,
CharToEndPipe
CharToEndPipe,
ClarinSafeHtmlPipe
];

const COMPONENTS = [
Expand Down
15 changes: 15 additions & 0 deletions src/app/shared/utils/clarin-safehtml.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';

/**
* Pipe to keep html tags e.g., `id` in the `innerHTML` attribute.
*/
@Pipe({
name: 'dsSafeHtml'
})
export class ClarinSafeHtmlPipe implements PipeTransform {
constructor(private sanitized: DomSanitizer) {}
transform(htmlString: string): SafeHtml {
return this.sanitized.bypassSecurityTrustHtml(htmlString);
}
}
2 changes: 1 addition & 1 deletion src/app/static-page/static-page.component.html
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<div class="container" >
<div [innerHTML]="(htmlContent | async)"></div>
<div [innerHTML]="(htmlContent | async) | dsSafeHtml" (click)="processLinks($event)"></div>
</div>
19 changes: 15 additions & 4 deletions src/app/static-page/static-page.component.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,42 @@ import { RouterMock } from '../shared/mocks/router.mock';
import { LocaleService } from '../core/locale/locale.service';
import { TranslateModule } from '@ngx-translate/core';
import { of } from 'rxjs';
import { APP_CONFIG } from '../../config/app-config.interface';
import { environment } from '../../environments/environment';
import { ClarinSafeHtmlPipe } from '../shared/utils/clarin-safehtml.pipe';

describe('StaticPageComponent', () => {
let component: StaticPageComponent;
let fixture: ComponentFixture<StaticPageComponent>;

let htmlContentService: HtmlContentService;
let localeService: any;
let appConfig: any;

beforeEach(async () => {
htmlContentService = jasmine.createSpyObj('htmlContentService', {
fetchHtmlContent: of('<div>TEST MESSAGE</div>')
fetchHtmlContent: of('<div id="idShouldNotBeRemoved">TEST MESSAGE</div>')
});
localeService = jasmine.createSpyObj('LocaleService', {
getCurrentLanguageCode: jasmine.createSpy('getCurrentLanguageCode'),
});

appConfig = Object.assign(environment, {
ui: {
namespace: 'testNamespace'
}
});

TestBed.configureTestingModule({
declarations: [ StaticPageComponent ],
declarations: [ StaticPageComponent, ClarinSafeHtmlPipe ],
imports: [
TranslateModule.forRoot()
],
providers: [
{ provide: HtmlContentService, useValue: htmlContentService },
{ provide: Router, useValue: new RouterMock() },
{ provide: LocaleService, useValue: localeService }
{ provide: LocaleService, useValue: localeService },
{ provide: APP_CONFIG, useValue: appConfig }
]
});

Expand All @@ -51,6 +62,6 @@ describe('StaticPageComponent', () => {
// Load `TEST MESSAGE`
it('should load html file content', async () => {
await component.ngOnInit();
expect(component.htmlContent.value).toBe('<div>TEST MESSAGE</div>');
expect(component.htmlContent.value).toBe('<div id="idShouldNotBeRemoved">TEST MESSAGE</div>');
});
});
36 changes: 30 additions & 6 deletions src/app/static-page/static-page.component.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Component, OnInit } from '@angular/core';
import { Component, Inject, OnInit } from '@angular/core';
import { HtmlContentService } from '../shared/html-content.service';
import { BehaviorSubject, firstValueFrom } from 'rxjs';
import { Router } from '@angular/router';
import { isEmpty, isNotEmpty } from '../shared/empty.util';
import { LocaleService } from '../core/locale/locale.service';
import { STATIC_FILES_DEFAULT_ERROR_PAGE_PATH, STATIC_FILES_PROJECT_PATH } from './static-page-routing-paths';
import { APP_CONFIG, AppConfig } from '../../config/app-config.interface';

/**
* Component which load and show static files from the `static-files` folder.
Expand All @@ -17,15 +18,17 @@ import { STATIC_FILES_DEFAULT_ERROR_PAGE_PATH, STATIC_FILES_PROJECT_PATH } from
})
export class StaticPageComponent implements OnInit {
htmlContent: BehaviorSubject<string> = new BehaviorSubject<string>('');
htmlFileName: string;

constructor(private htmlContentService: HtmlContentService,
private router: Router,
private localeService: LocaleService) { }
private localeService: LocaleService,
@Inject(APP_CONFIG) protected appConfig?: AppConfig) { }

async ngOnInit(): Promise<void> {
let url = '';
// Fetch html file name from the url path. `static/some_file.html`
let htmlFileName = this.getHtmlFileName();
this.htmlFileName = this.getHtmlFileName();

// Get current language
let language = this.localeService.getCurrentLanguageCode();
Expand All @@ -35,15 +38,15 @@ export class StaticPageComponent implements OnInit {
// Try to find the html file in the translated package. `static-files/language_code/some_file.html`
// Compose url
url = STATIC_FILES_PROJECT_PATH;
url += isEmpty(language) ? '/' + htmlFileName : '/' + language + '/' + htmlFileName;
url += isEmpty(language) ? '/' + this.htmlFileName : '/' + language + '/' + this.htmlFileName;
let potentialContent = await firstValueFrom(this.htmlContentService.fetchHtmlContent(url));
if (isNotEmpty(potentialContent)) {
this.htmlContent.next(potentialContent);
return;
}

// If the file wasn't find, get the non-translated file from the default package.
url = STATIC_FILES_PROJECT_PATH + '/' + htmlFileName;
url = STATIC_FILES_PROJECT_PATH + '/' + this.htmlFileName;
potentialContent = await firstValueFrom(this.htmlContentService.fetchHtmlContent(url));
if (isNotEmpty(potentialContent)) {
this.htmlContent.next(potentialContent);
Expand All @@ -54,6 +57,27 @@ export class StaticPageComponent implements OnInit {
await this.loadErrorPage();
}

processLinks(e) {
const element: HTMLElement = e.target;
if (element.nodeName === 'A') {
e.preventDefault();
const href = element.getAttribute('href')?.replace('/', '');
let redirectUrl = window.location.origin + this.appConfig.ui.nameSpace + '/static/';
// Start with `#` - redirect to the fragment
if (href.startsWith('#')) {
redirectUrl += this.htmlFileName + href;
} else if (href.startsWith('.')) {
// Redirect using namespace e.g. `./test.html` -> `<UI_PATH>/namespace/static/test.html`
redirectUrl += href.replace('.', '') + '.html';
} else {
// Redirect without using namespace e.g. `/test.html` -> `<UI_PATH>/test.html`
redirectUrl = redirectUrl.replace(this.appConfig.ui.nameSpace, '') + href;
}
// Call redirect
window.location.href = redirectUrl;
}
}

/**
* Load file name from the URL - `static/FILE_NAME.html`
* @private
Expand All @@ -69,7 +93,7 @@ export class StaticPageComponent implements OnInit {
}

// If the url is too long take just the first string after `/static` prefix.
return urlInList[1];
return urlInList[1]?.split('#')?.[0];
}

/**
Expand Down
10 changes: 6 additions & 4 deletions src/app/static-page/static-page.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,17 @@ import { CommonModule } from '@angular/common';

import { StaticPageRoutingModule } from './static-page-routing.module';
import { StaticPageComponent } from './static-page.component';
import { SharedModule } from '../shared/shared.module';


@NgModule({
declarations: [
StaticPageComponent
],
imports: [
CommonModule,
StaticPageRoutingModule,
]
imports: [
CommonModule,
StaticPageRoutingModule,
SharedModule,
]
})
export class StaticPageModule { }
Loading
Loading