diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 96710356..7dd8801e 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -75,7 +75,9 @@ import { SharedModule } from './shared/shared.module'; PhaseService, GithubEventService, Title, - LoggingService] + ErrorHandlingService, + LoggingService + ] }, { provide: IssueService, diff --git a/src/app/auth/auth.component.html b/src/app/auth/auth.component.html index ee113cc7..605b674f 100644 --- a/src/app/auth/auth.component.html +++ b/src/app/auth/auth.component.html @@ -1,13 +1,16 @@
- - + + + + + +
- Session on {{ currentSessionOrg }}
@@ -24,3 +27,8 @@
+ +
+ + +
diff --git a/src/app/auth/auth.component.ts b/src/app/auth/auth.component.ts index a30274e5..9bb41d18 100644 --- a/src/app/auth/auth.component.ts +++ b/src/app/auth/auth.component.ts @@ -134,6 +134,14 @@ export class AuthComponent implements OnInit, OnDestroy { return this.authState === AuthState.ConfirmOAuthUser; } + isUserAuthenticated(): boolean { + return this.authState === AuthState.Authenticated; + } + + isRepoSet(): boolean { + return this.phaseService.isRepoSet(); + } + get currentSessionOrg(): string { if (!this.sessionInformation) { // Retrieve org details of session information from local storage diff --git a/src/app/auth/auth.module.ts b/src/app/auth/auth.module.ts index 901bc391..46d81bbf 100644 --- a/src/app/auth/auth.module.ts +++ b/src/app/auth/auth.module.ts @@ -4,12 +4,20 @@ import { SharedModule } from '../shared/shared.module'; import { AuthRoutingModule } from './auth-routing.module'; import { AuthComponent } from './auth.component'; import { ConfirmLoginComponent } from './confirm-login/confirm-login.component'; +import { LoginComponent } from './login/login.component'; import { JsonParseErrorDialogComponent } from './profiles/json-parse-error-dialog/json-parse-error-dialog.component'; import { ProfilesComponent } from './profiles/profiles.component'; import { SessionSelectionComponent } from './session-selection/session-selection.component'; @NgModule({ imports: [AuthRoutingModule, SharedModule, CommonModule], - declarations: [AuthComponent, ProfilesComponent, JsonParseErrorDialogComponent, ConfirmLoginComponent, SessionSelectionComponent] + declarations: [ + AuthComponent, + ProfilesComponent, + JsonParseErrorDialogComponent, + LoginComponent, + ConfirmLoginComponent, + SessionSelectionComponent + ] }) export class AuthModule {} diff --git a/src/app/auth/confirm-login/confirm-login.component.ts b/src/app/auth/confirm-login/confirm-login.component.ts index 95df775e..94c414d3 100644 --- a/src/app/auth/confirm-login/confirm-login.component.ts +++ b/src/app/auth/confirm-login/confirm-login.component.ts @@ -1,15 +1,7 @@ import { Component, Input, OnInit } from '@angular/core'; -import { Router } from '@angular/router'; -import { Observable, of } from 'rxjs'; -import { mergeMap } from 'rxjs/operators'; -import { Phase } from '../../core/models/phase.model'; -import { Repo } from '../../core/models/repo.model'; import { AuthService, AuthState } from '../../core/services/auth.service'; import { ErrorHandlingService } from '../../core/services/error-handling.service'; -import { GithubService } from '../../core/services/github.service'; -import { GithubEventService } from '../../core/services/githubevent.service'; import { LoggingService } from '../../core/services/logging.service'; -import { PhaseService } from '../../core/services/phase.service'; import { UserService } from '../../core/services/user.service'; @Component({ @@ -23,13 +15,9 @@ export class ConfirmLoginComponent implements OnInit { constructor( private authService: AuthService, - private phaseService: PhaseService, private userService: UserService, private errorHandlingService: ErrorHandlingService, - private githubEventService: GithubEventService, private logger: LoggingService, - private router: Router, - public githubService: GithubService ) {} ngOnInit() {} @@ -44,42 +32,17 @@ export class ConfirmLoginComponent implements OnInit { this.authService.startOAuthProcess(); } - /** - * Handles the clean up required after authentication and setting up of user data is completed. - */ - handleAuthSuccess() { - this.authService.setTitleWithPhaseDetail(); - this.router.navigateByUrl(Phase.issuesViewer); - this.authService.changeAuthState(AuthState.Authenticated); - } - /** * Will complete the process of logging in the given user. */ completeLoginProcess(): void { this.authService.changeAuthState(AuthState.AwaitingAuthentication); - this.phaseService.initializeCurrentRepository(); - this.logger.info(`ConfirmLoginComponent: Current repo is ${this.phaseService.currentRepo}`); + this.logger.info(`ConfirmLoginComponent: Completing login process`); this.userService .createUserModel(this.username) - .pipe( - mergeMap(() => { - const currentRepo = this.phaseService.currentRepo; - if (Repo.isInvalidRepoName(currentRepo)) { - return of(false); - } - return this.githubService.isRepositoryPresent(currentRepo.owner, currentRepo.name); - }), - mergeMap((isValidRepository) => { - if (!isValidRepository) { - return new Observable(); - } - return this.githubEventService.setLatestChangeEvent(); - }) - ) .subscribe( () => { - this.handleAuthSuccess(); + this.authService.changeAuthState(AuthState.Authenticated); }, (error) => { this.authService.changeAuthState(AuthState.NotAuthenticated); @@ -87,6 +50,5 @@ export class ConfirmLoginComponent implements OnInit { this.logger.info(`ConfirmLoginComponent: Completion of login process failed with an error: ${error}`); } ); - this.handleAuthSuccess(); } } diff --git a/src/app/auth/login/login.component.css b/src/app/auth/login/login.component.css new file mode 100644 index 00000000..dec75881 --- /dev/null +++ b/src/app/auth/login/login.component.css @@ -0,0 +1,18 @@ +.login-button { + background: #f7fcfe; + line-height: 45px; + border: 1px solid currentColor; + width: 100%; +} + +.logo { + align-items: center; + display: inline-flex; + margin: 0 3px 3px 3px; +} + +.github-logo { + font-size: 20px; + width: 20px; + height: 20px; +} diff --git a/src/app/auth/login/login.component.html b/src/app/auth/login/login.component.html new file mode 100644 index 00000000..afcde449 --- /dev/null +++ b/src/app/auth/login/login.component.html @@ -0,0 +1,4 @@ + diff --git a/src/app/auth/login/login.component.ts b/src/app/auth/login/login.component.ts new file mode 100644 index 00000000..b08195d5 --- /dev/null +++ b/src/app/auth/login/login.component.ts @@ -0,0 +1,29 @@ +import { Component } from '@angular/core'; +import { AuthService, AuthState } from '../../core/services/auth.service'; +import { ErrorHandlingService } from '../../core/services/error-handling.service'; +import { LoggingService } from '../../core/services/logging.service'; + +@Component({ + selector: 'app-auth-login', + templateUrl: './login.component.html', + styleUrls: ['./login.component.css'] +}) + +export class LoginComponent { + constructor( + private authService: AuthService, + private errorHandlingService: ErrorHandlingService, + private logger: LoggingService + ) {} + + startLoginProcess() { + this.logger.info('LoginComponent: Beginning login process'); + try { + this.authService.startOAuthProcess(); + } catch (error) { + this.authService.changeAuthState(AuthState.NotAuthenticated); + this.errorHandlingService.handleError(error); + this.logger.info(`LoginComponent: Login process failed with an error: ${error}`); + } + } +} diff --git a/src/app/auth/session-selection/session-selection.component.ts b/src/app/auth/session-selection/session-selection.component.ts index 3a71b533..8ba5421a 100644 --- a/src/app/auth/session-selection/session-selection.component.ts +++ b/src/app/auth/session-selection/session-selection.component.ts @@ -2,7 +2,7 @@ import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { Observable } from 'rxjs'; import { Profile } from '../../core/models/profile.model'; -import { AuthService, AuthState } from '../../core/services/auth.service'; +import { AuthService } from '../../core/services/auth.service'; import { ErrorHandlingService } from '../../core/services/error-handling.service'; import { LoggingService } from '../../core/services/logging.service'; import { RepoUrlCacheService } from '../../core/services/repo-url-cache.service'; @@ -78,13 +78,12 @@ export class SessionSelectionComponent implements OnInit { this.logger.info(`SessionSelectionComponent: Selected Repository: ${repoInformation}`); - try { - this.authService.startOAuthProcess(); - } catch (error) { - this.errorHandlingService.handleError(error); - this.authService.changeAuthState(AuthState.NotAuthenticated); - this.isSettingUpSession = false; - } + this.authService.setRepo() + .subscribe( + (res) => { + this.isSettingUpSession = false; + } + ); } /** diff --git a/src/app/core/services/auth.service.ts b/src/app/core/services/auth.service.ts index b300d9b3..3ab0ec02 100644 --- a/src/app/core/services/auth.service.ts +++ b/src/app/core/services/auth.service.ts @@ -2,10 +2,13 @@ import { Injectable } from '@angular/core'; import { NgZone } from '@angular/core'; import { Title } from '@angular/platform-browser'; import { Router } from '@angular/router'; -import { BehaviorSubject } from 'rxjs'; +import { BehaviorSubject, from, Observable, of } from 'rxjs'; +import { catchError, map } from 'rxjs/operators'; import { AppConfig } from '../../../environments/environment'; import { generateSessionId } from '../../shared/lib/session'; import { uuid } from '../../shared/lib/uuid'; +import { Phase } from '../models/phase.model'; +import { ErrorHandlingService } from './error-handling.service'; import { GithubService } from './github.service'; import { GithubEventService } from './githubevent.service'; import { IssueService } from './issue.service'; @@ -47,8 +50,9 @@ export class AuthService { private phaseService: PhaseService, private githubEventService: GithubEventService, private titleService: Title, + private errorHandlingService: ErrorHandlingService, private logger: LoggingService - ) {} + ) {} /** * Will store the OAuth token. @@ -136,6 +140,35 @@ export class AuthService { this.logger.info(`AuthService: Redirecting for Github authentication`); } + /** + * Handles the clean up required after authentication and setting up of repository is completed. + */ + handleSetRepoSuccess() { + this.setTitleWithPhaseDetail(); + this.router.navigateByUrl(Phase.issuesViewer); + } + + /** + * Setup repository after authentication. + */ + setRepo(): Observable { + return from(this.phaseService.initializeCurrentRepository()) + .pipe( + map(() => { + if (!this.phaseService.currentRepo) { + return false; + } + this.githubEventService.setLatestChangeEvent(); + this.handleSetRepoSuccess(); + return true; + }), + catchError((error) => { + this.errorHandlingService.handleError(error); + return of(false); + }) + ); + } + /** * Will redirect to GitHub OAuth page */ diff --git a/src/app/core/services/factories/factory.auth.service.ts b/src/app/core/services/factories/factory.auth.service.ts index e3cf520d..21476b48 100644 --- a/src/app/core/services/factories/factory.auth.service.ts +++ b/src/app/core/services/factories/factory.auth.service.ts @@ -3,6 +3,7 @@ import { Title } from '@angular/platform-browser'; import { Router } from '@angular/router'; // import { AppConfig } from '../../../../environments/environment'; import { AuthService } from '../auth.service'; +import { ErrorHandlingService } from '../error-handling.service'; import { GithubService } from '../github.service'; import { GithubEventService } from '../githubevent.service'; import { IssueService } from '../issue.service'; @@ -22,6 +23,7 @@ export function AuthServiceFactory( phaseService: PhaseService, githubEventService: GithubEventService, titleService: Title, + errorHandlingService: ErrorHandlingService, logger: LoggingService ) { // TODO: Write Mocks @@ -50,5 +52,7 @@ export function AuthServiceFactory( phaseService, githubEventService, titleService, - logger); + errorHandlingService, + logger + ); } diff --git a/src/app/core/services/github.service.ts b/src/app/core/services/github.service.ts index 30cf078b..1480ac21 100644 --- a/src/app/core/services/github.service.ts +++ b/src/app/core/services/github.service.ts @@ -148,6 +148,8 @@ export class GithubService { * @returns Observable that returns true if there are pages that do not exist in the cache model. */ private toFetchIssues(filter: RestGithubIssueFilter): Observable { + const pageFetchLimit = 100; + let responseInFirstPage: GithubResponse; return this.getIssuesAPICall(filter, 1).pipe( map((response: GithubResponse) => { @@ -156,6 +158,9 @@ export class GithubService { }), flatMap((numOfPages: number) => { const apiCalls: Observable>[] = []; + if (numOfPages > pageFetchLimit) { + throw new Error(`Repository has too many pages (${numOfPages}), not supported.`); + } for (let i = 2; i <= numOfPages; i++) { apiCalls.push(this.getIssuesAPICall(filter, i)); } diff --git a/src/app/core/services/issue.service.ts b/src/app/core/services/issue.service.ts index ec9721cf..e1a52022 100644 --- a/src/app/core/services/issue.service.ts +++ b/src/app/core/services/issue.service.ts @@ -1,5 +1,5 @@ import { Injectable } from '@angular/core'; -import { BehaviorSubject, EMPTY, Observable, of, Subscription, timer } from 'rxjs'; +import { BehaviorSubject, Observable, of, Subscription, throwError, timer } from 'rxjs'; import { catchError, exhaustMap, finalize, map } from 'rxjs/operators'; import RestGithubIssueFilter from '../models/github/github-issue-filter.model'; import { GithubIssue } from '../models/github/github-issue.model'; @@ -43,9 +43,7 @@ export class IssueService { .pipe( exhaustMap(() => { return this.reloadAllIssues().pipe( - catchError(() => { - return EMPTY; - }), + catchError((err) => throwError(err)), finalize(() => this.isLoading.next(false)) ); }) diff --git a/src/app/core/services/phase.service.ts b/src/app/core/services/phase.service.ts index 9147593f..4d168f0a 100644 --- a/src/app/core/services/phase.service.ts +++ b/src/app/core/services/phase.service.ts @@ -45,6 +45,9 @@ export class PhaseService { public currentRepo: Repo; // current or main repository of current phase public otherRepos: Repo[]; // more repositories relevant to this phase + repoSetSource = new BehaviorSubject(false); + repoSetState = this.repoSetSource.asObservable(); + /** * Expose an observable to track changes to currentRepo * @@ -58,7 +61,10 @@ export class PhaseService { public sessionData = STARTING_SESSION_DATA; // stores session data for the session - constructor(private githubService: GithubService, private repoUrlCacheService: RepoUrlCacheService, public logger: LoggingService) {} + constructor( + private githubService: GithubService, + private repoUrlCacheService: RepoUrlCacheService, + public logger: LoggingService) {} /** * Sets the current main repository and additional repos if any. @@ -120,7 +126,7 @@ export class PhaseService { /** * Retrieves the repository url from local storage and sets to current repository. */ - initializeCurrentRepository() { + async initializeCurrentRepository() { const org = window.localStorage.getItem('org'); const repoName = window.localStorage.getItem('dataRepo'); this.logger.info(`Phase Service: received initial org (${org}) and initial name (${repoName})`); @@ -130,15 +136,17 @@ export class PhaseService { } else { repo = new Repo(org, repoName); } + const isValidRepository = await this.githubService.isRepositoryPresent(repo.owner, repo.name).toPromise(); + if (!isValidRepository) { + throw new Error('Invalid repository name. Please check your organisation and repository name.'); + } this.logger.info(`PhaseService: Repo is ${repo}`); this.setRepository(repo); + this.repoSetSource.next(true); } - /** - * Checks if the necessary repository is available. TODO: Future to use to verify setRepository. - */ - verifySessionAvailability(): Observable { - return this.githubService.isRepositoryPresent(this.currentRepo.owner, this.currentRepo.name); + isRepoSet(): boolean { + return this.repoSetSource.getValue(); } /** diff --git a/src/app/shared/layout/header.component.html b/src/app/shared/layout/header.component.html index dc16a9d9..0632a9aa 100644 --- a/src/app/shared/layout/header.component.html +++ b/src/app/shared/layout/header.component.html @@ -37,7 +37,7 @@ -
+
{{ this.currentRepo }} @@ -56,7 +56,7 @@