Commit 33bd557b authored by Tifenn Guillas's avatar Tifenn Guillas
Browse files

Merge branch '151-display-auth-buttons-depending-on-instance-config' into 'develop'

Resolve "Display auth buttons depending on instance config"

Closes #151

See merge request !163
parents 86e33b0a d6511893
Pipeline #3458 passed with stages
in 8 minutes and 26 seconds
......@@ -6,13 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [3.5.0] - 2020-xx
### Added
- #140 => Add description tooltip on search multiple datasetsranch
- #140 => Add description tooltip on search multiple datasets page
- #136 => Add detail view for spectra type object and default object
### Fixed
- #142 => Fix bug datatable: attribute disappeared in specific circonstances
### Changed
- #151 => Display authentication actions buttons if instance configuration allows it
- #145 => Display public datasets only when no user logged in
- #134 => Result search summary into accordion
- #139 => Datasets selected in search multiple is a configurable option in anis-admin
......
......@@ -8,6 +8,7 @@ module.exports = function (config) {
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-spec-reporter'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
......@@ -20,7 +21,7 @@ module.exports = function (config) {
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
reporters: ['progress', 'kjhtml', 'spec'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
......
......@@ -12,28 +12,29 @@
<span class="fas fa-home"></span> Home
</a>
</li>
<li *ngIf="isSearchAllowed()" id="search_link" class="nav-item">
<li *ngIf="getConfig('search', 'allowed')" id="search_link" class="nav-item">
<a class="nav-link" routerLink="/search" routerLinkActive="active">
<span class="fas fa-search"></span> Search
</a>
</li>
<li *ngIf="isSearchMultipleAllowed()" id="search_multiple_link" class="nav-item">
<li *ngIf="getConfig('search_multiple', 'allowed')" id="search_multiple_link" class="nav-item">
<a class="nav-link" routerLink="/search-multiple" routerLinkActive="active">
<span class="fas fa-search-plus"></span> Search multiple
</a>
</li>
<li *ngIf="isDocumentationAllowed()" id="documentation_link" class="nav-item">
<li *ngIf="getConfig('documentation', 'allowed')" id="documentation_link" class="nav-item">
<a class="nav-link" routerLink="/documentation" routerLinkActive="active">
<span class="fas fa-question"></span> Documentation
</a>
</li>
</ul>
<button *ngIf="!isAuthenticated" class="btn btn-outline-success my-2 my-sm-0"
<button *ngIf="getConfig('authentication', 'allowed') && !isAuthenticated"
class="btn btn-outline-success my-2 my-sm-0"
id="button-sign-in"
(click)="emitLogin()">
(click)="login.emit()">
Sign In / Register
</button>
<span *ngIf="isAuthenticated" id="dropdown-menu" dropdown>
<span *ngIf="getConfig('authentication', 'allowed') && isAuthenticated" id="dropdown-menu" dropdown>
<button id="button-basic" dropdownToggle type="button" class="btn btn-light" aria-controls="dropdown-basic">
<span class="fa-stack theme-color">
<span class="fas fa-circle fa-2x"></span>
......@@ -49,15 +50,15 @@
</li>
<li class="divider dropdown-divider"></li>
<li role="menuitem">
<a class="dropdown-item pointer" (click)="emitOpenEditProfile()">
<button class="dropdown-item pointer" (click)="openEditProfile.emit()">
<span class="fas fa-id-card"></span> Edit profile
</a>
</button>
</li>
<li class="divider dropdown-divider"></li>
<li role="menuitem">
<a class="dropdown-item text-danger pointer" (click)="emitLogout()">
<button class="dropdown-item text-danger pointer" (click)="logout.emit()">
<span class="fas fa-sign-out-alt fa-fw"></span> Sign Out
</a>
</button>
</li>
</ul>
</span>
......@@ -70,46 +71,48 @@
</button>
<ul id="basic-link-dropdown" *dropdownMenu class="dropdown-menu dropdown-menu-right dropdown-up" role="menu"
aria-labelledby="basic-link">
<li *ngIf="isAuthenticated" role="menuitem">
<li *ngIf="getConfig('authentication', 'allowed') && isAuthenticated" role="menuitem">
<span class="dropdown-item font-italic">{{ userProfile.email }}</span>
</li>
<li *ngIf="isAuthenticated" class="divider dropdown-divider"></li>
<li *ngIf="getConfig('authentication', 'allowed') && isAuthenticated" class="divider dropdown-divider"></li>
<li role="menuitem">
<a class="dropdown-item" routerLink="/home">
<span class="fas fa-home fa-fw"></span> Home
</a>
</li>
<li *ngIf="isSearchAllowed()" role="menuitem">
<li *ngIf="getConfig('search', 'allowed')" role="menuitem">
<a class="dropdown-item" routerLink="/search">
<span class="fas fa-search fa-fw"></span> Search
</a>
</li>
<li *ngIf="isSearchMultipleAllowed()" role="menuitem">
<li *ngIf="getConfig('search_multiple', 'allowed')" role="menuitem">
<a class="dropdown-item" routerLink="/search-multiple">
<span class="fas fa-search-plus fa-fw"></span> Search multiple
</a>
</li>
<li *ngIf="isDocumentationAllowed()" role="menuitem">
<li *ngIf="getConfig('documentation', 'allowed')" role="menuitem">
<a class="dropdown-item" routerLink="/documentation">
<span class="fas fa-question fa-fw"></span> Documentation
</a>
</li>
<li *ngIf="isAuthenticated" class="divider dropdown-divider"></li>
<li *ngIf="isAuthenticated" role="menuitem">
<a class="dropdown-item pointer" (click)="emitOpenEditProfile()">
<li *ngIf="getConfig('authentication', 'allowed') && isAuthenticated" class="divider dropdown-divider"></li>
<li *ngIf="getConfig('authentication', 'allowed') && isAuthenticated" role="menuitem">
<button class="dropdown-item pointer" (click)="openEditProfile.emit()">
<span class="fas fa-id-card"></span> Edit profile
</a>
</button>
</li>
<li class="divider dropdown-divider"></li>
<li *ngIf="getConfig('authentication', 'allowed')" class="divider dropdown-divider"></li>
<li role="menuitem">
<a *ngIf="!isAuthenticated" class="dropdown-item text-success" routerLink="/login">
<button *ngIf="getConfig('authentication', 'allowed') && !isAuthenticated"
class="dropdown-item text-success"
(click)="login.emit()">
<span class="fas fa-sign-in-alt fa-fw"></span> Sign In / Register
</a>
</button>
</li>
<li role="menuitem">
<a *ngIf="isAuthenticated" class="dropdown-item pointer text-danger" (click)="logout.emit()">
<button *ngIf="getConfig('authentication', 'allowed') && isAuthenticated" class="dropdown-item pointer text-danger" (click)="logout.emit()">
<span class="fas fa-sign-out-alt fa-fw"></span> Sign Out
</a>
</button>
</li>
</ul>
</span>
......
......@@ -19,49 +19,23 @@ describe('[Core] Component: NavComponent', () => {
expect(component).toBeTruthy();
});
it('should display the Sign In button if no user logged in', () => {
it('should display the Sign In button if authentication allowed and no user logged in', () => {
component.instance = INSTANCE;
component.isAuthenticated = false;
fixture.detectChanges();
const template = fixture.nativeElement;
expect(template.querySelector('#button-sign-in')).toBeTruthy();
});
it('should not display the dropdown menu if no user logged in', () => {
component.isAuthenticated = false;
fixture.detectChanges();
const template = fixture.nativeElement;
expect(template.querySelector('#dropdown-menu')).toBeFalsy();
});
it('should display the dropdown menu if user logged in', () => {
component.isAuthenticated = true;
fixture.detectChanges();
const template = fixture.nativeElement;
expect(template.querySelector('#dropdown-menu')).toBeTruthy();
});
it('should not display the Sign In button if user logged in', () => {
component.instance = INSTANCE;
component.isAuthenticated = true;
fixture.detectChanges();
const template = fixture.nativeElement;
expect(template.querySelector('#button-sign-in')).toBeFalsy();
});
it('raises the logout event when clicked', () => {
component.logout.subscribe((event) => expect(event).toBe(undefined));
component.emitLogout();
});
it('should display search, search multiple and documentation links if instance config allows it', () => {
component.instance = INSTANCE;
fixture.detectChanges();
const template = fixture.nativeElement;
expect(template.querySelector('#search_link')).toBeTruthy();
expect(template.querySelector('#search_multiple_link')).toBeTruthy();
expect(template.querySelector('#documentation_link')).toBeNull();
});
it('should not display search, search multiple and documentation links if instance config don\'t allows it', () => {
it('should not display the Sign In button if authentication not allowed', () => {
component.instance = {
name: 'toto',
label: 'Toto',
......@@ -69,21 +43,28 @@ describe('[Core] Component: NavComponent', () => {
nb_dataset_families: 1,
nb_datasets: 1,
config: {
search: false,
authentication: {
allowed: false
},
search: {
allowed: false
},
search_multiple: {
allowed: false,
all_datasets_selected: false
},
documentation: false
} };
documentation: {
allowed: false
}
}
};
component.isAuthenticated = true;
fixture.detectChanges();
const template = fixture.nativeElement;
expect(template.querySelector('#search_link')).toBeFalsy();
expect(template.querySelector('#search_multiple_link')).toBeFalsy();
expect(template.querySelector('#documentation_link')).toBeFalsy();
expect(template.querySelector('#button-sign-in')).toBeFalsy();
});
it('#isSearchAllowed() return if search link has to be displayed', () => {
it('should not display the dropdown menu if authentication not allowed', () => {
component.instance = {
name: 'toto',
label: 'Toto',
......@@ -91,33 +72,53 @@ describe('[Core] Component: NavComponent', () => {
nb_dataset_families: 1,
nb_datasets: 1,
config: {
search: true,
authentication: {
allowed: false
},
search: {
allowed: false
},
search_multiple: {
allowed: false,
all_datasets_selected: false
},
documentation: false
documentation: {
allowed: false
}
}
};
expect(component.isSearchAllowed()).toBeTruthy();
component.instance = {
name: 'toto',
label: 'Toto',
client_url: '',
nb_dataset_families: 1,
nb_datasets: 1,
config: {
search: false,
search_multiple: {
allowed: false,
all_datasets_selected: false
},
documentation: false
} };
expect(component.isSearchAllowed()).toBeFalsy();
component.isAuthenticated = true;
fixture.detectChanges();
const template = fixture.nativeElement;
expect(template.querySelector('#dropdown-menu')).toBeFalsy();
});
it('#isSearchMultipleAllowed() return if search multiple link has to be displayed', () => {
it('should not display the dropdown menu if no user logged in', () => {
component.instance = INSTANCE;
component.isAuthenticated = false;
fixture.detectChanges();
const template = fixture.nativeElement;
expect(template.querySelector('#dropdown-menu')).toBeFalsy();
});
it('should display the dropdown menu if authentication allowed and user logged in', () => {
component.instance = INSTANCE;
component.isAuthenticated = true;
fixture.detectChanges();
const template = fixture.nativeElement;
expect(template.querySelector('#dropdown-menu')).toBeTruthy();
});
it('should display search, search multiple and documentation links if instance config allows it', () => {
component.instance = INSTANCE;
fixture.detectChanges();
const template = fixture.nativeElement;
expect(template.querySelector('#search_link')).toBeTruthy();
expect(template.querySelector('#search_multiple_link')).toBeTruthy();
expect(template.querySelector('#documentation_link')).toBeNull();
});
it('should not display search, search multiple and documentation links if instance config don\'t allows it', () => {
component.instance = {
name: 'toto',
label: 'Toto',
......@@ -125,34 +126,28 @@ describe('[Core] Component: NavComponent', () => {
nb_dataset_families: 1,
nb_datasets: 1,
config: {
search: true,
search_multiple: {
allowed: true,
all_datasets_selected: false
authentication: {
allowed: false
},
search: {
allowed: false
},
documentation: false
}
};
expect(component.isSearchMultipleAllowed()).toBeTruthy();
component.instance = {
name: 'toto',
label: 'Toto',
client_url: '',
nb_dataset_families: 1,
nb_datasets: 1,
config: {
search: true,
search_multiple: {
allowed: false,
all_datasets_selected: false
},
documentation: false
documentation: {
allowed: false
}
};
expect(component.isSearchMultipleAllowed()).toBeFalsy();
} };
fixture.detectChanges();
const template = fixture.nativeElement;
expect(template.querySelector('#search_link')).toBeFalsy();
expect(template.querySelector('#search_multiple_link')).toBeFalsy();
expect(template.querySelector('#documentation_link')).toBeFalsy();
});
it('#isDocumentationAllowed() return if documentation link has to be displayed', () => {
it('#getConfig() return if the given kay is allowed for the given instance configuration propriety', () => {
component.instance = {
name: 'toto',
label: 'Toto',
......@@ -160,30 +155,23 @@ describe('[Core] Component: NavComponent', () => {
nb_dataset_families: 1,
nb_datasets: 1,
config: {
search: true,
search_multiple: {
allowed: true,
all_datasets_selected: false
authentication: {
allowed: false
},
search: {
allowed: true
},
documentation: true
}
};
expect(component.isDocumentationAllowed()).toBeTruthy();
component.instance = {
name: 'toto',
label: 'Toto',
client_url: '',
nb_dataset_families: 1,
nb_datasets: 1,
config: {
search: true,
search_multiple: {
allowed: true,
allowed: false,
all_datasets_selected: false
},
documentation: false
documentation: {
allowed: false
}
}
};
expect(component.isDocumentationAllowed()).toBeFalsy();
expect(component.getConfig('search', 'allowed')).toBeTruthy();
expect(component.getConfig('search_multiple', 'allowed')).toBeFalsy();
expect(component.getConfig('documentation', 'allowed')).toBeFalsy();
});
});
......@@ -34,65 +34,17 @@ export class NavComponent {
baseHref: string = environment.baseHref;
/**
* Checks if search link is allowed.
* Checks if given key for the given configuration propriety is allowed.
*
* @return boolean
*/
isSearchAllowed(): boolean {
if (this.instance && this.instance.config.search) {
return this.instance.config.search;
}
return false;
}
/**
* Checks if search multiple link is allowed.
*
* @return boolean
*/
isSearchMultipleAllowed(): boolean {
if (this.instance && this.instance.config.search_multiple) {
return this.instance.config.search_multiple.allowed;
}
return false;
}
/**
* Checks if documentation link is allowed.
* @param {string} prop - The configuration propriety.
* @param {string} key - The key in configuration propriety.
*
* @return boolean
*/
isDocumentationAllowed(): boolean {
if (this.instance && this.instance.config.documentation) {
return this.instance.config.documentation;
getConfig(prop: string, key: string): boolean {
if (this.instance && this.instance.config[prop] && this.instance.config[prop][key]) {
return this.instance.config[prop][key];
}
return false;
}
/**
* Emits event to log in.
*
* @fires EventEmitter<any>
*/
emitLogin(): void {
this.login.emit();
}
/**
* Emits event to log out.
*
* @return EventEmitter<any>
*/
emitLogout(): void {
this.logout.emit();
}
/**
* Emits event to go to edit profile page.
*
* @return EventEmitter<any>
*/
emitOpenEditProfile(): void {
this.openEditProfile.emit();
}
}
......@@ -53,6 +53,14 @@ describe('[Core] Container: AppComponent', () => {
expect(spy).toHaveBeenCalledWith(loadInstanceMetaAction);
});
it('#login() should dispatch LoginAction', () => {
const loginAction = new authActions.LoginAction();
const spy = spyOn(store, 'dispatch');
component.login();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(loginAction);
});
it('#logout() should dispatch LogoutAction', () => {
const logoutAction = new authActions.LogoutAction();
const spy = spyOn(store, 'dispatch');
......@@ -60,4 +68,12 @@ describe('[Core] Container: AppComponent', () => {
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(logoutAction);
});
it('#openEditProfile() should dispatch OpenEditProfileAction', () => {
const openEditProfileAction = new authActions.OpenEditProfileAction();
const spy = spyOn(store, 'dispatch');
component.openEditProfile();
expect(spy).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledWith(openEditProfileAction);
});
});
......@@ -15,9 +15,7 @@ describe('[Guard] NavigationGuard', () => {
beforeEach(() => {
TestBed.configureTestingModule({
providers: [
provideMockStore({ initialState })
]
providers: [ provideMockStore({ initialState }) ]
});
guard = TestBed.inject(NavigationGuard);
});
......@@ -26,9 +24,14 @@ describe('[Guard] NavigationGuard', () => {
expect(guard).toBeTruthy();
});
it('#isModuleAllowed() should return if module is allowed or not', () => {
guard.isModuleAllowed('search').subscribe(allowed => expect(allowed).toBeTruthy());
guard.isModuleAllowed('search-multiple').subscribe(allowed => expect(allowed).toBeTruthy());
guard.isModuleAllowed('documentation').subscribe(allowed => expect(allowed).toBeFalsy());
// it('#isModuleAllowed() should return if module is allowed or not', () => {
// guard.isModuleAllowed('search').subscribe(allowed => expect(allowed).toBeTruthy());
// guard.isModuleAllowed('search-multiple').subscribe(allowed => expect(allowed).toBeTruthy());
// guard.isModuleAllowed('documentation').subscribe(allowed => expect(allowed).toBeFalsy());
// });
it('#url2module() should return correct module name', () => {
expect(guard.url2module('search')).toEqual('search');
expect(guard.url2module('search-multiple')).toEqual('search_multiple');
});
});
......@@ -38,7 +38,7 @@ export class NavigationGuard implements CanActivate {
canActivate(
next: ActivatedRouteSnapshot,
state: RouterStateSnapshot): Observable<boolean | UrlTree> | Promise<boolean | UrlTree> | boolean | UrlTree {
const module: string = next.routeConfig.path;
const module: string = this.url2module(next.routeConfig.path);
return this.isModuleAllowed(module);
}
......@@ -53,13 +53,18 @@ export class NavigationGuard implements CanActivate {
return this.store$.select(state => state.metamodel.instance.instance)
.pipe(
filter( instance => instance !== null),
map(instance => {
if (module === 'search-multiple') {
return instance.config.search_multiple.allowed;
} else {
return instance.config[module];
}
})
map(instance => instance.config[module].allowed)
);
}
/**
* Translates URL segment to a module name.
*
* @param {string} url - The URL segment.
*
* @return string
*/
url2module(url: string): string {
return url.replace(/-/, '_');
}
}
......@@ -19,11 +19,18 @@ export interface Instance {
nb_dataset_families: number;
nb_datasets: number;
config: {
search: boolean;
authentication: {
allowed: boolean;
};
search: {
allowed: boolean;
};
search_multiple: {