Skip to content

Commit

Permalink
Dev Response Phase Fixed to Follow Template (#171)
Browse files Browse the repository at this point in the history
* Fixed Parsing From Comment for Dev Response

Team Response phase now takes the data from the comment (following the
template) instead of the description itself.

TODO: Ensure that updates to the Dev Response (Either new or editing)
both correctly gets updated to the comment instead of the description.

* Temp Commit

Attempting to replicate a weird bug where the output emitter from
new-team-response-component.ts is not triggering the function to update
the issueComment that is necesary to allow editing of a new comment.

* Fixed Comment Updates

TeamResponse now relies on the IssueComment to store the Team's
Response.

* Lint Fixes

* Nit Fixes

* Fixed issue where Duplicates were not displayed.

Duplicates now display correctly when assigned to an edited / new issue.
Also noticed an issue whereby the latest comment of an issue is not
fetched (needed to work in the case where a new issue is assigned a
duplicate and the duplicate is then removed.) So added the functionality
to retrieve the most recent comment that matches the required template.

* Fixed issue where duplicate disappears upon edit.
  • Loading branch information
ptvrajsk authored Sep 3, 2019
1 parent 6f18f20 commit c217711
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 33 deletions.
6 changes: 6 additions & 0 deletions src/app/core/services/issue-comment.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ export class IssueCommentService {
`# Items for the Tester to Verify\n${this.getTesterResponsesString(testerResponses)}`;
}

// Template url: https://github.com/CATcher-org/templates#dev-response-phase
createGithubTeamResponse(teamResponse: string, duplicateOf: number): string {
return `# Team\'s Response\n${teamResponse}\n ` +
`## Duplicate status (if any):\n${duplicateOf ? `Duplicate of #${duplicateOf}` : `--`}`;
}

// Template url: https://github.com/CATcher-org/templates#tutor-moderation
createGithubTutorResponse(issueDisputes: IssueDispute[]): string {
let tutorResponseString = '# Tutor Moderation\n\n';
Expand Down
50 changes: 43 additions & 7 deletions src/app/core/services/issue.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,7 @@ export class IssueService {
private createGithubIssueDescription(issue: Issue): string {
switch (this.phaseService.currentPhase) {
case Phase.phaseTeamResponse:
return `# Description\n${issue.description}\n# Team\'s Response\n${issue.teamResponse}\n ` +
`## State the duplicated issue here, if any\n${issue.duplicateOf ? `Duplicate of #${issue.duplicateOf}` : `--`}`;
return `# Description\n${issue.description}\n`;
case Phase.phaseModeration:
return `# Description\n${issue.description}\n# Team\'s Response\n${issue.teamResponse}\n ` +
// `## State the duplicated issue here, if any\n${issue.duplicateOf ? `Duplicate of #${issue.duplicateOf}` : `--`}\n` +
Expand Down Expand Up @@ -430,6 +429,14 @@ export class IssueService {
return this.issueCommentService.getIssueComments(issueId, this.isIssueReloaded).pipe(
map((issueComments: IssueComments) => {
const issueComment = this.getIssueComment(issueComments);
let teamResponse = issueInJson['teamResponse'];
let duplicateOf = issueInJson['duplicateOf'];
if ( !!issueComment && this.phaseService.currentPhase === Phase.phaseTesterResponse) {
teamResponse = this.parseTeamResponseForTesterResponsePhase(issueComment.description);
} else if ( !!issueComment && this.phaseService.currentPhase === Phase.phaseTeamResponse) {
teamResponse = this.parseTeamResponseForTeamResponsePhase(issueComment.description);
duplicateOf = this.parseDuplicateOfForTeamResponsePhase(issueComment.description);
}
const incompleteIssueDisputes: IssueDispute[] = issueInJson['issueDisputes'];
this.isIssueReloaded = false;
return <Issue>{
Expand All @@ -441,10 +448,9 @@ export class IssueService {
description: issueInJson['body'],
teamAssigned: this.getTeamAssignedToIssue(issueInJson),
todoList: this.getToDoList(issueComment, issueInJson['issueDisputes']),
teamResponse: this.phaseService.currentPhase === Phase.phaseTesterResponse && !!issueComment ?
this.parseTeamResponse(issueComment.description) : issueInJson['teamResponse'],
teamResponse: teamResponse,
tutorResponse: issueInJson['tutorResponse'],
duplicateOf: issueInJson['duplicateOf'],
duplicateOf: +duplicateOf,
testerResponses: this.phaseService.currentPhase === Phase.phaseTesterResponse && !!issueComment ?
this.parseTesterResponse(issueComment.description) : issueInJson['testerResponses'],
issueComment: issueComment,
Expand Down Expand Up @@ -481,13 +487,21 @@ export class IssueService {
);
}

/**
* Searches for a comment in the issue that matches the required template.
* @return IssueComment - Latest Comment that matches the required template.
*/
getIssueComment(issueComments: IssueComments): IssueComment {
let regex = /# *Team\'?s Response[\n\r]*[\s\S]*# Items for the Tester to Verify/gi;
if (this.phaseService.currentPhase === Phase.phaseModeration) {
regex = /# Tutor Moderation[\n\r]*#{2} *:question: *.*[\n\r]*.*[\n\r]*[\s\S]*?(?=-{19})/gi;
} else if (this.phaseService.currentPhase === Phase.phaseTeamResponse) {
regex = /# Team's Response[\r\n]*[\S\s]*?[\r\n]*## Duplicate status \(if any\):[\r\n]*[\S\s]*/gi;
}

for (const comment of issueComments.comments) {
// Re-Order the comments (Most Recent First)
const comments = issueComments.comments.reverse();
for (const comment of comments) {
const matched = regex.exec(comment.description);
if (matched) {
return comment;
Expand Down Expand Up @@ -522,7 +536,7 @@ export class IssueService {
}

// Template url: https://github.com/CATcher-org/templates#teams-response-1
parseTeamResponse(toParse: string): string {
parseTeamResponseForTesterResponsePhase(toParse: string): string {
let teamResponse = '';
const regex = /# *Team\'?s Response[\n\r]*([\s\S]*)# Items for the Tester to Verify/gi;
const matches = regex.exec(toParse);
Expand All @@ -533,6 +547,28 @@ export class IssueService {
return teamResponse;
}

parseTeamResponseForTeamResponsePhase(toParse: string): string {
let teamResponse = '';
const regex = /# Team's Response[\r\n]*([\S\s]*?)[\r\n]*## Duplicate status \(if any\):/gi;
const matches = regex.exec(toParse);

if (matches && matches.length > this.MINIMUM_MATCHES) {
teamResponse = matches[1].trim();
}
return teamResponse;
}

parseDuplicateOfForTeamResponsePhase(toParse: string): string {
let duplicateOf = '';
const regex = /## Duplicate status \(if any\):[\r\n]*Duplicate of #(.*)/gi;
const matches = regex.exec(toParse);

if (matches && matches.length > this.MINIMUM_MATCHES) {
duplicateOf = matches[1].trim();
}
return duplicateOf;
}

// Template url: https://github.com/CATcher-org/templates#disputes
parseIssueDisputes(toParse: string): IssueDispute[] {
let matches;
Expand Down
20 changes: 17 additions & 3 deletions src/app/shared/issue/duplicateOf/duplicate-of.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { MatCheckbox, MatSelect } from '@angular/material';
import { PermissionService } from '../../../core/services/permission.service';
import { IssueCommentService } from '../../../core/services/issue-comment.service';
import { IssueComment } from '../../../core/models/comment.model';

@Component({
selector: 'app-duplicate-of-component',
Expand All @@ -30,6 +32,7 @@ export class DuplicateOfComponent implements OnInit {
@Input() issue: Issue;

@Output() issueUpdated = new EventEmitter<Issue>();
@Output() commentUpdated = new EventEmitter<IssueComment>();

@ViewChild(MatSelect) duplicateOfSelection: MatSelect;
@ViewChild(MatCheckbox) duplicatedCheckbox: MatCheckbox;
Expand All @@ -40,6 +43,7 @@ export class DuplicateOfComponent implements OnInit {
readonly MAX_TITLE_LENGTH_FOR_NON_DUPLICATE_ISSUE = 37;

constructor(public issueService: IssueService,
public issueCommentService: IssueCommentService,
private errorHandlingService: ErrorHandlingService,
public permissions: PermissionService) {
}
Expand All @@ -66,12 +70,22 @@ export class DuplicateOfComponent implements OnInit {
}

updateDuplicateStatus(event) {
this.issueService.updateIssue({
const latestIssue = {
...this.issue,
duplicated: !!event,
duplicateOf: event ? event.value : null,
}).subscribe((updatedIssue) => {
this.issueUpdated.emit(updatedIssue);
};

latestIssue.issueComment.description = this.issueCommentService.
createGithubTeamResponse(latestIssue.teamResponse, latestIssue.duplicateOf);

this.issueService.updateIssue(latestIssue).subscribe((updatedIssue) => {
this.issueCommentService.updateIssueComment(latestIssue.issueComment).subscribe((updatedComment) => {
updatedIssue.duplicateOf = latestIssue.duplicateOf;
updatedIssue.issueComment = updatedComment;
this.commentUpdated.emit(updatedComment);
this.issueUpdated.emit(updatedIssue);
});
}, (error) => {
this.errorHandlingService.handleHttpError(error);
});
Expand Down
36 changes: 29 additions & 7 deletions src/app/shared/issue/response/response.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import {finalize} from 'rxjs/operators';
import {PermissionService} from '../../../core/services/permission.service';
import {Phase, PhaseService} from '../../../core/services/phase.service';
import {Issue} from '../../../core/models/issue.model';
import {IssueCommentService} from '../../../core/services/issue-comment.service';
import {IssueComment} from '../../../core/models/comment.model';

@Component({
selector: 'app-issue-response',
Expand Down Expand Up @@ -36,9 +38,11 @@ export class ResponseComponent implements OnInit {
@Input() isEditing: boolean;
@Output() issueUpdated = new EventEmitter<Issue>();
@Output() updateEditState = new EventEmitter<boolean>();
@Output() commentUpdated = new EventEmitter<IssueComment>();

constructor(private issueService: IssueService,
private formBuilder: FormBuilder,
private issueCommentService: IssueCommentService,
private errorHandlingService: ErrorHandlingService,
private permissions: PermissionService,
private phaseService: PhaseService) {
Expand All @@ -65,14 +69,32 @@ export class ResponseComponent implements OnInit {
if (this.responseForm.invalid) {
return;
}

const latestIssue = this.getUpdatedIssue();
this.isSavePending = true;
this.issueService.updateIssue(this.getUpdatedIssue()).pipe(finalize(() => {
this.updateEditState.emit(false);
this.isSavePending = false;
})).subscribe((updatedIssue: Issue) => {
this.issueUpdated.emit(updatedIssue);
form.resetForm();
this.issueService.updateIssue(latestIssue).subscribe((updatedIssue: Issue) => {

if (this.phaseService.currentPhase === Phase.phaseTeamResponse) {
// For Team Response phase, where the items are in the issue's comment
latestIssue.issueComment.description = this.issueCommentService.
createGithubTeamResponse(latestIssue.teamResponse, latestIssue.duplicateOf);

this.issueCommentService.updateIssueComment(latestIssue.issueComment).subscribe(
(updatedComment) => {
this.commentUpdated.emit(updatedComment);
this.updateEditState.emit(false);
this.isSavePending = false;
updatedIssue.issueComment = updatedComment;
updatedIssue.teamResponse = this.issueService.parseTeamResponseForTeamResponsePhase(updatedComment.description);
updatedIssue.duplicateOf = +this.issueService.parseDuplicateOfForTeamResponsePhase(updatedComment.description);
this.issueUpdated.emit(updatedIssue);
form.resetForm();
}, (error) => {
this.errorHandlingService.handleHttpError(error);
});
} else {
this.issueUpdated.emit(updatedIssue);
form.resetForm();
}
}, (error) => {
this.errorHandlingService.handleHttpError(error);
});
Expand Down
38 changes: 28 additions & 10 deletions src/app/shared/new-team-respond/new-team-response.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { IssueService } from '../../core/services/issue.service';
import { FormBuilder, FormGroup, NgForm, Validators } from '@angular/forms';
import { Issue, SEVERITY_ORDER, STATUS } from '../../core/models/issue.model';
import { ErrorHandlingService } from '../../core/services/error-handling.service';
import { finalize, map } from 'rxjs/operators';
import { map } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { LabelService } from '../../core/services/label.service';
import { IssueCommentService } from '../../core/services/issue-comment.service';
import { IssueComment } from '../../core/models/comment.model';

@Component({
selector: 'app-new-team-response',
Expand All @@ -20,9 +22,11 @@ export class NewTeamResponseComponent implements OnInit {
isFormPending = false;
@Input() issue: Issue;
@Output() issueUpdated = new EventEmitter<Issue>();
@Output() updatedCommentEmitter = new EventEmitter<IssueComment>();

constructor(private issueService: IssueService,
private formBuilder: FormBuilder,
private issueCommentService: IssueCommentService,
public labelService: LabelService,
private errorHandlingService: ErrorHandlingService) { }

Expand Down Expand Up @@ -51,15 +55,34 @@ export class NewTeamResponseComponent implements OnInit {
this.duplicateOf.updateValueAndValidity();
this.responseTag.updateValueAndValidity();
});

}

submitNewTeamResponse(form: NgForm) {
if (this.newTeamResponseForm.invalid) {
return;
}
this.isFormPending = true;
this.issueService.updateIssue({
const latestIssue = this.getUpdatedIssue();

this.issueService.updateIssue(latestIssue)
.subscribe(() => {

// New Team Response has no pre-existing comments hence new comment will be added.
const newCommentDescription = this.issueCommentService.createGithubTeamResponse(this.description.value, this.duplicateOf.value);
this.issueCommentService.createIssueComment(this.issue.id, newCommentDescription)
.subscribe((newComment: IssueComment) => {
this.updatedCommentEmitter.emit(newComment);
latestIssue.issueComment = newComment;
this.issueUpdated.emit(latestIssue);
form.resetForm();
});
}, (error) => {
this.errorHandlingService.handleHttpError(error);
});
}

getUpdatedIssue() {
return <Issue>{
...this.issue,
severity: this.severity.value,
type: this.type.value,
Expand All @@ -68,13 +91,8 @@ export class NewTeamResponseComponent implements OnInit {
duplicated: this.duplicated.value,
status: STATUS.Done,
teamResponse: this.description.value,
duplicateOf: this.duplicateOf.value,
}).pipe(finalize(() => this.isFormPending = false)).subscribe((updatedIssue: Issue) => {
this.issueUpdated.emit(updatedIssue);
form.resetForm();
}, (error) => {
this.errorHandlingService.handleHttpError(error);
});
duplicateOf: this.duplicateOf.value
};
}

dupIssueOptionIsDisabled(issue: Issue): boolean {
Expand Down
8 changes: 4 additions & 4 deletions src/app/shared/view-issue/view-issue.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,14 @@

<!-- Team's Response -->
<app-issue-response *ngIf="this.isComponentVisible(issueComponentsEnum.TEAM_RESPONSE) && !isIssueLoading && issue.teamResponse"
[issue]="issue" [isEditing]="isTeamResponseEditing" (updateEditState)="updateTeamResponseEditState($event)"
attributeName="teamResponse" (issueUpdated)="updateIssue($event)">
[issue]="issue" [isEditing]="isTeamResponseEditing" (updateEditState)="updateTeamResponseEditState($event)" attributeName="teamResponse"
(issueUpdated)="updateIssue($event)" (commentUpdated)="updateComment($event)">
</app-issue-response>

<!-- New Team's Response -->
<app-new-team-response *ngIf="this.isComponentVisible(issueComponentsEnum.NEW_TEAM_RESPONSE) &&
permissions.isTeamResponseEditable() && !issue.teamResponse" [issue]="issue"
(issueUpdated)="updateIssue($event)">
(issueUpdated)="updateIssue($event)" (updatedCommentEmitter)= "updateComment($event)">
</app-new-team-response>

<!-- Tester's Response -->
Expand Down Expand Up @@ -85,7 +85,7 @@
<div *ngIf="this.isComponentVisible(issueComponentsEnum.DUPLICATE) &&
(issue.duplicateOf || (issueService.getDuplicateIssuesFor(issue) | async).length === 0)">
<mat-divider></mat-divider>
<app-duplicate-of-component [issue]="issue" (issueUpdated)="updateIssue($event)"></app-duplicate-of-component>
<app-duplicate-of-component [issue]="issue" (commentUpdated)="updateComment($event)" (issueUpdated)="updateIssue($event)"></app-duplicate-of-component>
</div>

<!-- Faulty issue warning -->
Expand Down
4 changes: 2 additions & 2 deletions src/app/shared/view-issue/view-issue.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,13 @@ export class ViewIssueComponent implements OnInit, OnDestroy {

updateIssue(newIssue: Issue) {
this.issue = newIssue;
this.issueService.updateLocalStore(this.issue);
this.issueService.updateLocalStore(newIssue);
}

updateComment(newComment: IssueComment) {
this.issue.issueComment = newComment;
this.issueService.updateLocalStore(this.issue);
this.issueCommentService.updateLocalStore(newComment, this.issueId);
this.issueService.updateLocalStore(this.issue);
}

updateDescriptionEditState(updatedState: boolean) {
Expand Down

0 comments on commit c217711

Please sign in to comment.