diff --git a/ui/src/app/app.component.spec.ts b/ui/src/app/app.component.spec.ts index 4b18e9bf2a..e1f92c60d8 100644 --- a/ui/src/app/app.component.spec.ts +++ b/ui/src/app/app.component.spec.ts @@ -105,7 +105,6 @@ describe('App: CDS', () => { expect(compiled.querySelector('#navbar.connected')).toBeFalsy('Nav bar must have connected css class'); })); - // FIXME CACHE NOT INITIALIZE it('should update cache', fakeAsync(() => { // Create cache projectStore.getProjects('key1').subscribe(() => {}).unsubscribe(); diff --git a/ui/src/app/app.routing.ts b/ui/src/app/app.routing.ts index f17b27d6f2..e779d12c96 100644 --- a/ui/src/app/app.routing.ts +++ b/ui/src/app/app.routing.ts @@ -6,7 +6,8 @@ const routes: Routes = [ { path: 'home', loadChildren: 'app/views/home/home.module#HomeModule' }, { path: 'account', loadChildren: 'app/views/account/account.module#AccountModule' }, { path: 'project', loadChildren: 'app/views/project/project.module#ProjectModule' }, - { path: 'settings', loadChildren: 'app/views/settings/settings.module#SettingsModule' } + { path: 'settings', loadChildren: 'app/views/settings/settings.module#SettingsModule' }, + { path: 'warnings', loadChildren: 'app/views/warnings/warnings.module#WarningsModule' } ]; export const routing: ModuleWithProviders = RouterModule.forRoot(routes, { diff --git a/ui/src/app/app.service.ts b/ui/src/app/app.service.ts index 1f5cdaac99..21621c1260 100644 --- a/ui/src/app/app.service.ts +++ b/ui/src/app/app.service.ts @@ -7,13 +7,14 @@ import {NotificationService} from './service/notification/notification.service'; import {AuthentificationStore} from './service/auth/authentification.store'; import {TranslateService} from 'ng2-translate'; import {PipelineStore} from './service/pipeline/pipeline.store'; +import {RouterService} from './service/router/router.service'; @Injectable() export class AppService { - constructor(private _projStore: ProjectStore, private _routeActivated: ActivatedRoute, private _router: Router, + constructor(private _projStore: ProjectStore, private _routeActivated: ActivatedRoute, private _appStore: ApplicationStore, private _notif: NotificationService, private _authStore: AuthentificationStore, - private _translate: TranslateService, private _pipStore: PipelineStore) { + private _translate: TranslateService, private _pipStore: PipelineStore, private _routerService: RouterService) { } updateCache(lastUpdates: Array) { @@ -22,7 +23,7 @@ export class AppService { } // Get current route params - let params = this.getRouteParams({}, this._routeActivated); + let params = this._routerService.getRouteParams({}, this._routeActivated); // Get all projects this._projStore.getProjects().first().subscribe(projects => { @@ -104,18 +105,18 @@ export class AppService { return; } - plu.pipelines.forEach( p => { + plu.pipelines.forEach(p => { let pipKey = plu.name + '-' + p.name; if (!pips.get(pipKey)) { return; } - if ( pips.get(pipKey).last_modified < p.last_modified ) { - if (params['key'] && params['key'] === plu.name && params['pipName'] === p.name ) { + if (pips.get(pipKey).last_modified < p.last_modified) { + if (params['key'] && params['key'] === plu.name && params['pipName'] === p.name) { this._pipStore.externalModification(pipKey); if (p.username !== this._authStore.getUser().username) { - this._notif.create(this._translate.instant('pipeline_modification', { username: plu.username})); + this._notif.create(this._translate.instant('pipeline_modification', {username: plu.username})); } if (params['buildNumber'] || p.username === this._authStore.getUser().username) { @@ -129,30 +130,4 @@ export class AppService { }); } - - getRouteParams(params: {}, activatedRoute: ActivatedRoute): {} { - if (activatedRoute) { - if (activatedRoute.snapshot.params) { - if (activatedRoute.snapshot.params['key']) { - params['key'] = activatedRoute.snapshot.params['key']; - } - if (activatedRoute.snapshot.params['pipName']) { - params['pipName'] = activatedRoute.snapshot.params['pipName']; - } - if (activatedRoute.snapshot.params['appName']) { - params['appName'] = activatedRoute.snapshot.params['appName']; - } - if (activatedRoute.snapshot.params['buildNumber']) { - params['buildNumber'] = activatedRoute.snapshot.params['buildNumber']; - } - } - - if (activatedRoute.children) { - activatedRoute.children.forEach(c => { - params = this.getRouteParams(params, c); - }); - } - } - return params; - } } diff --git a/ui/src/app/model/warning.model.ts b/ui/src/app/model/warning.model.ts index c0f7550cf4..8df5f7f597 100644 --- a/ui/src/app/model/warning.model.ts +++ b/ui/src/app/model/warning.model.ts @@ -4,27 +4,20 @@ import {Application} from './application.model'; import {Environment} from './environment.model'; import {Pipeline} from './pipeline.model'; -export class WarningsUI { - [key: string]: WarningUI; -} - export class WarningUI { - pipelines: WarningsPipeline; - applications: WarningsApplication; - environments: WarningsEnvironment; // Not implemented + pipelines: Map; + applications: Map; + environments: Map; // Not implemented variables: WarningAPI[]; // Not implemented constructor() { - this.pipelines = new WarningsPipeline(); - this.applications = new WarningsApplication(); - this.environments = new WarningsEnvironment(); + this.pipelines = new Map(); + this.applications = new Map(); + this.environments = new Map(); + this.variables = new Array(); } } -export class WarningsPipeline { - [name: string]: WarningPipeline; -} - export class WarningPipeline { parameters: WarningAPI[]; // Not implemented jobs: WarningAPI[]; @@ -35,10 +28,6 @@ export class WarningPipeline { } } -export class WarningsApplication { - [name: string]: WarningApplication; -} - export class WarningApplication { variables: WarningAPI[]; // Not implemented actions: WarningAPI[]; @@ -49,10 +38,6 @@ export class WarningApplication { } } -export class WarningsEnvironment { - [name: string]: WarningEnvironment; -} - export class WarningEnvironment { variables: WarningAPI[]; // Not implemented diff --git a/ui/src/app/service/router/router.service.ts b/ui/src/app/service/router/router.service.ts new file mode 100644 index 0000000000..5b5b226c48 --- /dev/null +++ b/ui/src/app/service/router/router.service.ts @@ -0,0 +1,31 @@ +import {Injectable} from '@angular/core'; +import {ActivatedRoute} from '@angular/router'; + +@Injectable() +export class RouterService { + + getRouteParams(params: {}, activatedRoute: ActivatedRoute): {} { + if (activatedRoute) { + if (activatedRoute.snapshot.params) { + if (activatedRoute.snapshot.params['key']) { + params['key'] = activatedRoute.snapshot.params['key']; + } + if (activatedRoute.snapshot.params['pipName']) { + params['pipName'] = activatedRoute.snapshot.params['pipName']; + } + if (activatedRoute.snapshot.params['appName']) { + params['appName'] = activatedRoute.snapshot.params['appName']; + } + if (activatedRoute.snapshot.params['buildNumber']) { + params['buildNumber'] = activatedRoute.snapshot.params['buildNumber']; + } + } + if (activatedRoute.children) { + activatedRoute.children.forEach(c => { + params = this.getRouteParams(params, c); + }); + } + } + return params; + } +} diff --git a/ui/src/app/service/services.module.ts b/ui/src/app/service/services.module.ts index 3c1806df82..7d9bfd5331 100644 --- a/ui/src/app/service/services.module.ts +++ b/ui/src/app/service/services.module.ts @@ -37,6 +37,7 @@ import {NotificationService} from './notification/notification.service'; import {WorkflowService} from './workflow/workflow.service'; import {WorkflowStore} from './workflow/workflow.store'; import {WorkflowRunService} from './workflow/run/workflow.run.service'; +import {RouterService} from './router/router.service'; @NgModule({}) export class ServicesModule { @@ -73,6 +74,7 @@ export class ServicesModule { RepoManagerService, RequirementStore, RequirementService, + RouterService, UserService, VariableService, WarningStore, @@ -123,6 +125,7 @@ export { ProjectAuditService, RepoManagerService, RequirementStore, + RouterService, UserService, VariableService, WarningStore, diff --git a/ui/src/app/service/warning/warning.store.spec.ts b/ui/src/app/service/warning/warning.store.spec.ts index 5cb8ee1f9b..6be92f5ae4 100644 --- a/ui/src/app/service/warning/warning.store.spec.ts +++ b/ui/src/app/service/warning/warning.store.spec.ts @@ -22,23 +22,23 @@ describe('CDS: Warning Store', () => { let haveWarnings = false; warnStore.getWarnings().subscribe( res => { - expect(res['key1']).toBeTruthy('Must have warnings on project key1'); - expect(res['key1'].applications['app1']).toBeTruthy('Must have warnings on application app1'); - expect(res['key1'].applications['app1'].actions.length).toBe(3, 'App1 must have 3 warnings on jobs'); + expect(res.get('key1')).toBeTruthy('Must have warnings on project key1'); + expect(res.get('key1').applications.get('app1')).toBeTruthy('Must have warnings on application app1'); + expect(res.get('key1').applications.get('app1').actions.length).toBe(3, 'App1 must have 3 warnings on jobs'); - expect(res['key1'].pipelines['pip1']).toBeTruthy('Must have warnings on pipeline pip1'); - expect(res['key1'].pipelines['pip1'].jobs.length).toBe(1, 'Pip1 must have 1 warning on jobs'); + expect(res.get('key1').pipelines.get('pip1')).toBeTruthy('Must have warnings on pipeline pip1'); + expect(res.get('key1').pipelines.get('pip1').jobs.length).toBe(1, 'Pip1 must have 1 warning on jobs'); - expect(res['key1'].pipelines['pip2']).toBeTruthy('Must have warnings on pipeline pip2'); - expect(res['key1'].pipelines['pip2'].jobs.length).toBe(2, 'Pip2 must have 2 warning on jobs'); + expect(res.get('key1').pipelines.get('pip2')).toBeTruthy('Must have warnings on pipeline pip2'); + expect(res.get('key1').pipelines.get('pip2').jobs.length).toBe(2, 'Pip2 must have 2 warning on jobs'); - expect(res['key2']).toBeTruthy('Must have warnings on project key2'); - expect(res['key2'].applications['app2']).toBeTruthy('Must have warnings on application app2'); - expect(res['key2'].applications['app2'].actions.length).toBe(1, 'App2 must have 1 warning on actions'); + expect(res.get('key2')).toBeTruthy('Must have warnings on project key2'); + expect(res.get('key2').applications.get('app2')).toBeTruthy('Must have warnings on application app2'); + expect(res.get('key2').applications.get('app2').actions.length).toBe(1, 'App2 must have 1 warning on actions'); - expect(res['key2'].pipelines['pip3']).toBeTruthy('Must have warnings on pipeline pip2'); - expect(res['key2'].pipelines['pip3'].jobs.length).toBe(2, 'Pip3 must have 2 warnings on jobs'); + expect(res.get('key2').pipelines.get('pip3')).toBeTruthy('Must have warnings on pipeline pip2'); + expect(res.get('key2').pipelines.get('pip3').jobs.length).toBe(2, 'Pip3 must have 2 warnings on jobs'); haveWarnings = true; }); diff --git a/ui/src/app/service/warning/warning.store.ts b/ui/src/app/service/warning/warning.store.ts index 7dd002614e..0236eeb271 100644 --- a/ui/src/app/service/warning/warning.store.ts +++ b/ui/src/app/service/warning/warning.store.ts @@ -1,7 +1,7 @@ import {Injectable} from '@angular/core'; import {BehaviorSubject, Observable} from 'rxjs/Rx'; import { - WarningsUI, WarningAPI, WarningUI, WarningPipeline, WarningApplication, + WarningAPI, WarningUI, WarningPipeline, WarningApplication, WarningEnvironment } from '../../model/warning.model'; @@ -10,59 +10,57 @@ import { export class WarningStore { // List of all project. Use by Navbar - private _warningCache: BehaviorSubject = new BehaviorSubject(null); + private _warningCache: BehaviorSubject> = new BehaviorSubject(new Map()); /** * Get a WarningsUI Observable * @returns {Observable} */ - getWarnings(): Observable { - return new Observable(fn => this._warningCache.subscribe(fn)); + getWarnings(): Observable> { + return new Observable>(fn => this._warningCache.subscribe(fn)); } /** * Update warning Store. */ updateWarnings(warnings: WarningAPI[]): void { - let updatedWarnings: WarningsUI = new WarningsUI(); + let updatedWarnings: Map = new Map(); warnings.forEach(function (w) { if (w.project && w.project.key) { - // if not in list, create new warning on the current project - if (!updatedWarnings[w.project.key]) { - updatedWarnings[w.project.key] = new WarningUI(); + if (!updatedWarnings.get(w.project.key)) { + updatedWarnings.set(w.project.key, new WarningUI); } - - let warningUI = updatedWarnings[w.project.key]; + let warningUI = updatedWarnings.get(w.project.key); // If warning on pipeline if (w.pipeline && w.pipeline.name) { - if (!warningUI.pipelines[w.pipeline.name]) { - warningUI.pipelines[w.pipeline.name] = new WarningPipeline(); + if (!warningUI.pipelines.get(w.pipeline.name)) { + warningUI.pipelines.set(w.pipeline.name, new WarningPipeline()); } } // If warning on application if (w.application && w.application.name) { - if (!warningUI.applications[w.application.name]) { - warningUI.applications[w.application.name] = new WarningApplication(); + if (!warningUI.applications.get(w.application.name)) { + warningUI.applications.set(w.application.name, new WarningApplication()); } } // If warning on an action if (w.action && w.action.name) { - warningUI.pipelines[w.pipeline.name].jobs.push(w); + warningUI.pipelines.get(w.pipeline.name).jobs.push(w); // If action link to an application if (w.application && w.application.name) { - warningUI.applications[w.application.name].actions.push(w); + warningUI.applications.get(w.application.name).actions.push(w); } } // If Warning on environment if (w.environment && w.environment.name) { - if (!warningUI.environments[w.environment.name]) { - warningUI.environments[w.environment.name] = new WarningEnvironment(); + if (!warningUI.environments.get(w.environment.name)) { + warningUI.environments.set(w.environment.name, new WarningEnvironment()); } } diff --git a/ui/src/app/shared/pipes/map.pipe.ts b/ui/src/app/shared/pipes/map.pipe.ts new file mode 100644 index 0000000000..fc5841e21c --- /dev/null +++ b/ui/src/app/shared/pipes/map.pipe.ts @@ -0,0 +1,12 @@ +import {Pipe, PipeTransform} from '@angular/core'; + +@Pipe({name: 'forMap'}) +export class ForMapPipe implements PipeTransform { + transform(m: Map): Array<{key, value}> { + let listkeyValue = new Array<{key, value}>(); + m.forEach((v, k) => { + listkeyValue.push({key: k, value: v}); + }); + return listkeyValue; + } +} diff --git a/ui/src/app/shared/shared.module.ts b/ui/src/app/shared/shared.module.ts index 1e88894db4..c20edf3549 100644 --- a/ui/src/app/shared/shared.module.ts +++ b/ui/src/app/shared/shared.module.ts @@ -57,6 +57,7 @@ import {WorkflowDeleteJoinComponent} from './workflow/join/delete/workflow.join. import {WorkflowTriggerJoinComponent} from './workflow/join/trigger/trigger.join.component'; import {WorkflowJoinTriggerSrcComponent} from './workflow/join/trigger/src/trigger.src.component'; import {RouterModule} from '@angular/router'; +import {ForMapPipe} from './pipes/map.pipe'; @NgModule({ imports: [ CommonModule, NgSemanticModule, FormsModule, TranslateModule, DragulaModule, MomentModule, @@ -69,6 +70,7 @@ import {RouterModule} from '@angular/router'; CommitListComponent, CutPipe, DeleteButtonComponent, + ForMapPipe, GroupFormComponent, HistoryComponent, KeysPipe, @@ -124,6 +126,7 @@ import {RouterModule} from '@angular/router'; CutPipe, DeleteButtonComponent, DragulaModule, + ForMapPipe, FormsModule, GroupFormComponent, HistoryComponent, diff --git a/ui/src/app/views/navbar/navbar.component.spec.ts b/ui/src/app/views/navbar/navbar.component.spec.ts index 099bdf071e..cbf7f9c1c9 100644 --- a/ui/src/app/views/navbar/navbar.component.spec.ts +++ b/ui/src/app/views/navbar/navbar.component.spec.ts @@ -15,6 +15,8 @@ import {ApplicationStore} from '../../service/application/application.store'; import {ApplicationService} from '../../service/application/application.service'; import {Project} from '../../model/project.model'; import {LanguageStore} from '../../service/language/language.store'; +import {RouterService} from '../../service/router/router.service'; +import {WarningStore} from '../../service/warning/warning.store'; describe('CDS: Navbar Component', () => { @@ -32,6 +34,8 @@ describe('CDS: Navbar Component', () => { TranslateLoader, ProjectStore, ProjectService, + RouterService, + WarningStore, AuthentificationStore, ApplicationStore, ApplicationService, diff --git a/ui/src/app/views/navbar/navbar.component.ts b/ui/src/app/views/navbar/navbar.component.ts index 6f8955a455..09b57897c4 100644 --- a/ui/src/app/views/navbar/navbar.component.ts +++ b/ui/src/app/views/navbar/navbar.component.ts @@ -1,16 +1,19 @@ -import {Component, OnInit, AfterViewInit, OnDestroy} from '@angular/core'; +import {AfterViewInit, Component, OnDestroy, OnInit} from '@angular/core'; import {ProjectStore} from '../../service/project/project.store'; import {AuthentificationStore} from '../../service/auth/authentification.store'; import {Project} from '../../model/project.model'; import {ApplicationStore} from '../../service/application/application.store'; import {Application} from '../../model/application.model'; import {User} from '../../model/user.model'; -import {Router} from '@angular/router'; +import {NavigationEnd, Router} from '@angular/router'; import {TranslateService} from 'ng2-translate'; import {List} from 'immutable'; import {LanguageStore} from '../../service/language/language.store'; import {Subscription} from 'rxjs/Subscription'; import {AutoUnsubscribe} from '../../shared/decorator/autoUnsubscribe'; +import {RouterService} from '../../service/router/router.service'; +import {WarningStore} from '../../service/warning/warning.store'; +import {WarningUI} from '../../model/warning.model'; @Component({ selector: 'app-navbar', @@ -34,17 +37,22 @@ export class NavbarComponent implements OnInit, OnDestroy, AfterViewInit { currentCountry: string; langSubscrition: Subscription; - dropdownOptions = { fullTextSearch: true }; + dropdownOptions = {fullTextSearch: true}; + + warnings: Map; + warningsCount: number; + currentRoute: {}; userSubscription: Subscription; + warningSubscription: Subscription; public currentUser: User; constructor(private _projectStore: ProjectStore, private _authStore: AuthentificationStore, private _appStore: ApplicationStore, - private _router: Router, private _language: LanguageStore, - private _translate: TranslateService, + private _router: Router, private _language: LanguageStore, private _routerService: RouterService, + private _translate: TranslateService, private _warningStore: WarningStore, private _authentificationStore: AuthentificationStore) { this.selectedProjectKey = '#NOPROJECT#'; this.userSubscription = this._authentificationStore.getUserlst().subscribe(u => { @@ -54,6 +62,18 @@ export class NavbarComponent implements OnInit, OnDestroy, AfterViewInit { this.langSubscrition = this._language.get().subscribe(l => { this.currentCountry = l; }); + + this.warningSubscription = this._warningStore.getWarnings().subscribe(ws => { + this.warnings = ws; + this.calculateWarningCount(); + }); + + this._router.events + .filter(e => e instanceof NavigationEnd) + .forEach(() => { + this.currentRoute = this._routerService.getRouteParams({}, this._router.routerState.root); + this.calculateWarningCount(); + }); } changeCountry() { @@ -66,22 +86,22 @@ export class NavbarComponent implements OnInit, OnDestroy, AfterViewInit { } } - ngAfterViewInit () { - this._translate.get('navbar_projects_placeholder').subscribe( () => { + ngAfterViewInit() { + this._translate.get('navbar_projects_placeholder').subscribe(() => { this.ready = true; }); } ngOnInit() { // Listen list of nav project - this._authStore.getUserlst().subscribe( user => { + this._authStore.getUserlst().subscribe(user => { if (user) { this.getProjects(); } }); // Listen change on recent app viewed - this._appStore.getRecentApplications().subscribe( app => { + this._appStore.getRecentApplications().subscribe(app => { if (app) { this.navRecentApp = app; this.listApplications = this.navRecentApp.toArray(); @@ -89,11 +109,49 @@ export class NavbarComponent implements OnInit, OnDestroy, AfterViewInit { }); } + calculateWarningCount(): void { + if (!this.currentRoute || !this.warnings) { + return; + } + + this.warningsCount = 0; + let k = this.currentRoute['key']; + + if (k && this.warnings.get(k)) { + this.warningsCount += this.warnings.get(k).variables.length; + + // If on pipeline page + let pip = this.currentRoute['pipName']; + if (pip && this.warnings.get(k).pipelines.get(pip)) { + this.warningsCount += this.warnings.get(k).pipelines.get(pip).jobs.length + + this.warnings.get(k).pipelines.get(pip).parameters.length; + } + + // If on application page + let app = this.currentRoute['appName']; + if (app && this.warnings.get(k).applications.get(app)) { + this.warningsCount += this.warnings.get(k).applications.get(app).variables.length + + this.warnings.get(k).applications.get(app).actions.length; + } + + // On project page + if (!this.currentRoute['appName'] && !this.currentRoute['pipName']) { + this.warnings.get(k).pipelines.forEach((v) => { + this.warningsCount += v.jobs.length + v.parameters.length; + }); + this.warnings.get(k).applications.forEach((v) => { + this.warningsCount += v.variables.length + v.actions.length; + }); + + } + } + } + /** * Listen change on project list. */ getProjects(): void { - this._projectStore.getProjectsList().subscribe( projects => { + this._projectStore.getProjectsList().subscribe(projects => { if (projects.size > 0) { this.navProjects = projects; } @@ -121,6 +179,10 @@ export class NavbarComponent implements OnInit, OnDestroy, AfterViewInit { this._router.navigate(['/project/' + key]); } + getWarningParams(): {} { + return this.currentRoute; + } + /** * Navigate to the selected application. */ diff --git a/ui/src/app/views/navbar/navbar.html b/ui/src/app/views/navbar/navbar.html index ba70a41f2c..4281a9fbd0 100644 --- a/ui/src/app/views/navbar/navbar.html +++ b/ui/src/app/views/navbar/navbar.html @@ -1,5 +1,4 @@