Skip to content

Commit

Permalink
BugReportingRepository will be automatically created for Students (#189)
Browse files Browse the repository at this point in the history
* BugReport Repo will be automatically created.

The BugReporting Phase Repository will be automatically created and
setup if it is missing during the initial log-in. Since the users are
required to have this repo for the PE.

* Streamline Repo Creation Process

Repository Initialization works only for the Bug Reporting Phase and
only for Students in that phase. (So Tutors/Admins will not have a
random repository created in their account). Error messages have been
put in place to identify any potential faliures.

* Usage of ErrorCode Constant

* Lint Fix

* Artificial wait time for repo creation

* User Permission Requested prior to Repo Creation

The app now seeks the user's permission prior to initializing the
BugReportingPhase's repository in their account (should it be missing).

* Error Message Fix

* Method Description Fix
  • Loading branch information
ptvrajsk authored Sep 9, 2019
1 parent e000317 commit d7ad194
Show file tree
Hide file tree
Showing 9 changed files with 187 additions and 11 deletions.
7 changes: 5 additions & 2 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import { PhaseBugReportingModule } from './phase-bug-reporting/phase-bug-reporti
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { UserConfirmationComponent } from './core/guards/user-confirmation/user-confirmation.component';
import { PhaseTesterResponseModule } from './phase-tester-response/phase-tester-response.module';
import { SessionFixConfirmationComponent } from './core/services/session-fix-confirmation/session-fix-confirmation.component';

@NgModule({
declarations: [
AppComponent,
HeaderComponent,
UserConfirmationComponent
UserConfirmationComponent,
SessionFixConfirmationComponent
],
imports: [
BrowserModule,
Expand Down Expand Up @@ -51,7 +53,8 @@ import { PhaseTesterResponseModule } from './phase-tester-response/phase-tester-
providers: [],
bootstrap: [AppComponent],
entryComponents: [
UserConfirmationComponent
UserConfirmationComponent,
SessionFixConfirmationComponent
]
})
export class AppModule { }
2 changes: 2 additions & 0 deletions src/app/core/services/error-handling.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import {NoInternetConnectionComponent} from '../../shared/error-toasters/no-inte
import {GeneralMessageErrorComponent} from '../../shared/error-toasters/general-message-error/general-message-error.component';
import {FormErrorComponent} from '../../shared/error-toasters/form-error/form-error.component';

export const ERRORCODE_NOT_FOUND = 404;

@Injectable({
providedIn: 'root',
})
Expand Down
32 changes: 29 additions & 3 deletions src/app/core/services/github.service.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Injectable } from '@angular/core';
import { map, mergeMap } from 'rxjs/operators';
import { forkJoin, from, Observable } from 'rxjs';
import { catchError, map, mergeMap } from 'rxjs/operators';
import { forkJoin, from, Observable, of } from 'rxjs';
import { githubPaginatorParser } from '../../shared/lib/github-paginator-parser';
import { IssueComment } from '../models/comment.model';
import { shell } from 'electron';
import { ErrorHandlingService } from './error-handling.service';
import { ERRORCODE_NOT_FOUND, ErrorHandlingService } from './error-handling.service';
const Octokit = require('@octokit/rest');


Expand Down Expand Up @@ -67,6 +67,32 @@ export class GithubService {
);
}

/**
* Checks if the specified repository exists.
* @param owner - Owner of Specified Repository.
* @param repo - Name of Repository.
*/
isRepositoryPresent(owner: string, repo: string): Observable<boolean> {
return from(octokit.repos.get({owner: owner, repo: repo})).pipe(
map((rawData: {status: number}) => {
return rawData.status !== ERRORCODE_NOT_FOUND;
}),
catchError(err => {
return of(false);
})
);
}

/**
* Creates a repository in for the authenticated user location.
* @param name - Name of Repo to create.
* @return Observable<boolean> - That returns true if the repository has been successfully
* created.
*/
createRepository(name: string): void {
octokit.repos.createForAuthenticatedUser({name: name});
}

fetchIssue(id: number): Observable<{}> {
return from(octokit.issues.get({owner: ORG_NAME, repo: REPO, number: id})).pipe(
map((response) => {
Expand Down
94 changes: 89 additions & 5 deletions src/app/core/services/phase.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import {Injectable} from '@angular/core';
import { HttpClient} from '@angular/common/http';
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { flatMap, map } from 'rxjs/operators';
import { Observable, throwError } from 'rxjs';
import {GithubService} from './github.service';
import { Observable, of, throwError } from 'rxjs';
import { GithubService } from './github.service';
import { LabelService } from './label.service';
import { UserService } from './user.service';
import { UserRole } from '../models/user.model';
import { ErrorHandlingService } from './error-handling.service';
import { MatDialog } from '@angular/material';
import { SessionFixConfirmationComponent } from './session-fix-confirmation/session-fix-confirmation.component';

export enum Phase {
phaseBugReporting = 'phaseBugReporting',
Expand Down Expand Up @@ -47,7 +52,9 @@ export class PhaseService {

constructor(private http: HttpClient,
private githubService: GithubService,
private labelService: LabelService) {}
private labelService: LabelService,
private userService: UserService,
public phaseFixConfirmationDialog: MatDialog) {}

/**
* Stores the location of the repositories belonging to
Expand Down Expand Up @@ -85,6 +92,34 @@ export class PhaseService {
}
}

/**
* Checks if the necessary repository is available and creates it if the permissions are available.
* @param sessionData
*/
verifySessionAvailability(sessionData: SessionData): Observable<boolean> {
return this.githubService.isRepositoryPresent(this.phaseRepoOwners[this.currentPhase], sessionData[this.currentPhase]);
}

/**
* If a Session is unavailable (because the repository is missing) attempt to create IF it is
* the BugReporting Phase
* @param sessionData - SessionData containing repository information.
* @return - Dummy Observable to give the API sometime to propagate the creation of the new repository since
* the API Call used here does not return any response.
*/
attemptSessionAvailabilityFix(sessionData: SessionData): Observable<any> {
if (this.currentPhase !== Phase.phaseBugReporting) {
throw new Error('Current Phase\'s Repository has not been opened.');
} else if (this.currentPhase === Phase.phaseBugReporting && this.userService.currentUser.role !== UserRole.Student) {
throw new Error('Bug-Reporting Phase\'s repository initialisation is only available to Students.');
}
this.githubService.createRepository(sessionData[this.currentPhase]);

return new Observable(subscriber => {
setTimeout(() => subscriber.next(true), 1000);
});
}

/**
* Ensures that the input session Data has been correctly defined.
* Returns true if satisfies these properties, false otherwise.
Expand Down Expand Up @@ -115,11 +150,60 @@ export class PhaseService {
this.githubService.storePhaseDetails(this.phaseRepoOwners[this.currentPhase], this.repoName);
}

/**
* Launches the SessionFixConfirmation Dialog.
* @return Observable<boolean> - Representing user's permission grant.
*/
openSessionFixConfirmation(): Observable<boolean> {
const dialogRef = this.phaseFixConfirmationDialog.open(SessionFixConfirmationComponent, {
data: {user: this.userService.currentUser.loginId, repoName: this.sessionData[this.currentPhase]}
});

return dialogRef.afterClosed();
}


/**
* Ensures that the necessary data for the current session is available
* and synchronized with the remote server.
*/
sessionSetup(): Observable<any> {
return this.fetchSessionData().pipe(
flatMap((sessionData: SessionData) => {
this.assertSessionDataIntegrity(sessionData);
this.updateSessionParameters(sessionData);
return this.verifySessionAvailability(sessionData);
}),
flatMap((isSessionAvailable: boolean) => {
if (!isSessionAvailable) {
return this.openSessionFixConfirmation();
} else {
return of(null);
}
}),
flatMap((sessionFixPermission: boolean | null) => {
if (sessionFixPermission === null) {
// No Session Fix Necessary
return of(null);
} if (sessionFixPermission === true) {
return this.attemptSessionAvailabilityFix(this.sessionData);
} else {
throw new Error('You cannot proceed without the required repository.');
}
}),
flatMap((isFixAttempted: boolean | null) => {
if (isFixAttempted === null) {
// If no fix has been attempted, there is no need to verify fix outcome.
return of(true);
} else if (isFixAttempted === true) {
// Verify that Repository has been created if a fix attempt has occurred.
return this.verifySessionAvailability(this.sessionData);
}
}),
flatMap((isSessionCreated: boolean) => {
if (!isSessionCreated) {
throw new Error('Session Availability Fix failed.');
}
return this.labelService.synchronizeRemoteLabels();
})
);
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<h1 mat-dialog-title>Hi {{ data.user }}</h1>
<div mat-dialog-content>
<p>Do you want CATcher to create a repository named "{{ data.repoName }}"</p>
<p>in your Github Account?</p>
</div>
<div mat-dialog-actions>
<button mat-button mat-raised-button color="warn" [mat-dialog-close]="false" (click)="onNoClick()">No Thanks</button>
<button mat-button mat-raised-button color="primary" [mat-dialog-close]="true" cdkFocusInitial>Ok</button>
</div>
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import { SessionFixConfirmationComponent } from './session-fix-confirmation.component';

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

beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ SessionFixConfirmationComponent ]
})
.compileComponents();
}));

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

it('should create', () => {
expect(component).toBeTruthy();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';

export interface RepositoryData {
user: string;
repoName: string;
}

@Component({
selector: 'app-session-fix-confirmation',
templateUrl: './session-fix-confirmation.component.html',
styleUrls: ['./session-fix-confirmation.component.css']
})
export class SessionFixConfirmationComponent implements OnInit {

constructor(
public dialogRef: MatDialogRef<SessionFixConfirmationComponent>,
@Inject(MAT_DIALOG_DATA) public data: RepositoryData) {}

onNoClick(): void {
this.dialogRef.close();
}

ngOnInit() {
}

}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<p style="display: inline-block; max-width:300px"> {{"Error: " + data.message}} </p>
<p style="display: inline-block; max-width:300px"> {{data.message}} </p>
<button style="float: right; margin-top: 8px;" mat-button
color="accent"
(click)="snackBarRef.dismiss()"> Close </button>

0 comments on commit d7ad194

Please sign in to comment.