-
Notifications
You must be signed in to change notification settings - Fork 75
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Create templating classes for issue model * Add string representation of sections * Add return types to base issue * Migrate bug reporting phase to use the new model * Remove unused import * Fix typo * Update import style * Rank Labels * Rename methods * Lint Fix: const instead of let * Migrated TeamResponsePhase to use BaseIssue createIssueModel has been modified to work with baseIssue for the TeamResponsePhase. * Empty commit * Fix Team Response Parsing * TeamResp. Mostly working, left comment error * Refactor of Issue Model Creation for TeamResponse IssueModel Creation for TeamResponse has been refactored to follow the new mode. The description reading bug has also been fixed as of this commit. * Fixed Duplicate Info Display Duplicate information was not being correctly displayed on toggle. Issue has now been fixed. * Fixed Severity Toggling Severity Toggle Used to reset the issue in some cases. That issue has been resolved. * Fix Issue Duplicate Removing Description * Fix same issue with Assignees * Lint Fix
- Loading branch information
1 parent
f1a1bb6
commit 36f68d4
Showing
24 changed files
with
653 additions
and
39 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
import { TeamResponseTemplate } from './templates/team-response-template.model'; | ||
import { Issue } from './issue.model'; | ||
import { IssueDispute } from './issue-dispute.model'; | ||
import { IssueComment } from './comment.model'; | ||
import { TutorModerationIssueTemplate } from './templates/tutor-moderation-issue-template.model'; | ||
import { TutorModerationTodoTemplate } from './templates/tutor-moderation-todo-template.model'; | ||
import { Team } from './team.model'; | ||
import { TesterResponse } from './tester-response.model'; | ||
import { TesterResponseTemplate } from './templates/tester-response-template.model'; | ||
import { GithubIssue, GithubLabel } from './github-issue.model'; | ||
import * as moment from 'moment'; | ||
import { GithubComment } from './github-comment.model'; | ||
import { DataService } from '../services/data.service'; | ||
|
||
export class BaseIssue implements Issue { | ||
|
||
/** Basic Fields */ | ||
readonly id: number; | ||
readonly created_at: string; | ||
title: string; | ||
description: string; | ||
|
||
/** Fields derived from Labels */ | ||
severity: string; | ||
type: string; | ||
responseTag?: string; | ||
duplicated?: boolean; | ||
status?: string; | ||
pending?: string; | ||
unsure?: boolean; | ||
teamAssigned?: Team; | ||
|
||
/** Depending on the phase, assignees attribute can be derived from Github's assignee feature OR from the Github's issue description */ | ||
assignees?: string[]; | ||
|
||
/** Fields derived from parsing of Github's issue description */ | ||
duplicateOf?: number; | ||
todoList?: string[]; | ||
teamResponse?: string; | ||
tutorResponse?: string; | ||
testerResponses?: TesterResponse[]; | ||
issueComment?: IssueComment; // Issue comment is used for Tutor Response and Tester Response | ||
issueDisputes?: IssueDispute[]; | ||
|
||
protected constructor(githubIssue: GithubIssue) { | ||
/** Basic Fields */ | ||
this.id = +githubIssue.number; | ||
this.created_at = moment(githubIssue.created_at).format('lll'); | ||
this.title = githubIssue.title; | ||
this.description = githubIssue.body; | ||
|
||
/** Fields derived from Labels */ | ||
this.severity = githubIssue.findLabel(GithubLabel.LABELS.severity); | ||
this.type = githubIssue.findLabel(GithubLabel.LABELS.type); | ||
this.responseTag = githubIssue.findLabel(GithubLabel.LABELS.response); | ||
this.duplicated = !!githubIssue.findLabel(GithubLabel.LABELS.duplicated, false); | ||
this.status = githubIssue.findLabel(GithubLabel.LABELS.status); | ||
this.pending = githubIssue.findLabel(GithubLabel.LABELS.pending); | ||
} | ||
|
||
private static constructTeamData(githubIssue: GithubIssue, dataService: DataService): Team { | ||
const teamId = githubIssue.findLabel(GithubLabel.LABELS.tutorial).concat('-').concat(githubIssue.findLabel(GithubLabel.LABELS.team)); | ||
return dataService.getTeam(teamId); | ||
} | ||
|
||
public static createPhaseBugReportingIssue(githubIssue: GithubIssue): BaseIssue { | ||
return new BaseIssue(githubIssue); | ||
} | ||
|
||
public static createPhaseTeamResponseIssue(githubIssue: GithubIssue, githubComments: GithubComment[] | ||
, teamData: Team): Issue { | ||
const issue = new BaseIssue(githubIssue); | ||
const template = new TeamResponseTemplate(githubComments); | ||
|
||
issue.teamAssigned = teamData; | ||
issue.issueComment = template.comment; | ||
issue.teamResponse = template.teamResponse !== undefined ? template.teamResponse.content : undefined; | ||
issue.duplicateOf = template.duplicateOf !== undefined ? template.duplicateOf.issueNumber : undefined; | ||
issue.duplicated = issue.duplicateOf !== undefined && issue.duplicateOf !== null; | ||
issue.assignees = githubIssue.assignees.map(assignee => assignee.login); | ||
|
||
return issue; | ||
} | ||
|
||
public static createPhaseTesterResponseIssue(githubIssue: GithubIssue, githubComments: GithubComment[]): Issue { | ||
const issue = new BaseIssue(githubIssue); | ||
const template = new TesterResponseTemplate(githubComments); | ||
issue.teamResponse = template.teamResponse.content; | ||
issue.testerResponses = template.testerResponse.testerResponses; | ||
return issue; | ||
} | ||
|
||
public static createPhaseModerationIssue(githubIssue: GithubIssue, githubComments: GithubComment[]): Issue { | ||
const issue = new BaseIssue(githubIssue); | ||
const issueTemplate = new TutorModerationIssueTemplate(githubIssue); | ||
const todoTemplate = new TutorModerationTodoTemplate(githubComments); | ||
|
||
issue.description = issueTemplate.description.content; | ||
issue.teamResponse = issueTemplate.teamResponse.content; | ||
issue.issueDisputes = issueTemplate.dispute.disputes; | ||
issue.todoList = todoTemplate.moderation.todoList; | ||
return issue; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
export interface GithubComment { | ||
author_association: string; | ||
body: string; | ||
created_at: string; | ||
html_url: string; | ||
id: number; | ||
issue_url: string; | ||
updated_at: string; | ||
url: string; // api url | ||
user: { | ||
login: string, | ||
id: number, | ||
avatar_url: string, | ||
url: string, | ||
}; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
export class GithubLabel { | ||
static readonly LABEL_ORDER = { | ||
severity: { Low: 0, Medium: 1, High: 2 }, | ||
type: { DocumentationBug: 0, FunctionalityBug: 1 }, | ||
}; | ||
static readonly LABELS = { | ||
severity: 'severity', | ||
type: 'type', | ||
response: 'response', | ||
duplicated: 'duplicated', | ||
status: 'status', | ||
unsure: 'unsure', | ||
pending: 'pending', | ||
team: 'team', | ||
tutorial: 'tutorial' | ||
}; | ||
|
||
color: string; | ||
id: number; | ||
name: string; | ||
url: string; | ||
|
||
constructor(githubLabels: {}) { | ||
Object.assign(this, githubLabels); | ||
} | ||
|
||
getCategory(): string { | ||
if (this.isCategorical()) { | ||
return this.name.split('.')[0]; | ||
} else { | ||
return this.name; | ||
} | ||
} | ||
|
||
getValue(): string { | ||
if (this.isCategorical()) { | ||
return this.name.split('.')[1]; | ||
} else { | ||
return this.name; | ||
} | ||
} | ||
|
||
isCategorical(): boolean { | ||
const regex = /^\S+.\S+$/; | ||
return regex.test(this.name); | ||
} | ||
} | ||
|
||
export class GithubIssue { | ||
id: number; // Github's backend's id | ||
number: number; // Issue's display id | ||
assignees: Array<{ | ||
id: number, | ||
login: string, | ||
url: string, | ||
}>; | ||
body: string; | ||
created_at: string; | ||
labels: Array<GithubLabel>; | ||
state: string; | ||
title: string; | ||
updated_at: string; | ||
url: string; | ||
user: { // Author of the issue | ||
login: string, | ||
id: number, | ||
avatar_url: string, | ||
url: string, | ||
}; | ||
|
||
constructor(githubIssue: {}) { | ||
Object.assign(this, githubIssue); | ||
this.labels = []; | ||
for (const label of githubIssue['labels']) { | ||
this.labels.push(new GithubLabel(label)); | ||
} | ||
} | ||
|
||
/** | ||
* | ||
* @param name Depending on the isCategorical flag, this name either refers to the category name of label or the exact name of label. | ||
* @param isCategorical Whether the label is categorical. | ||
*/ | ||
findLabel(name: string, isCategorical: boolean = true): string { | ||
if (!isCategorical) { | ||
const label = this.labels.find(l => (!l.isCategorical() && l.name === name)); | ||
return label ? label.getValue() : undefined; | ||
} | ||
|
||
// Find labels with the same category name as what is specified in the parameter. | ||
const labels = this.labels.filter(l => (l.isCategorical() && l.getCategory() === name)); | ||
if (labels.length === 0) { | ||
return undefined; | ||
} else if (labels.length === 1) { | ||
return labels[0].getValue(); | ||
} else { | ||
// If Label order is not specified, return the first label value else | ||
// If Label order is specified, return the highest ranking label value | ||
if (!GithubLabel.LABEL_ORDER[name]) { | ||
return labels[0].getValue(); | ||
} else { | ||
const order = GithubLabel.LABEL_ORDER[name]; | ||
return labels.reduce((result, currLabel) => { | ||
return order[currLabel.getValue()] > order[result.getValue()] ? currLabel : result; | ||
}).getValue(); | ||
} | ||
} | ||
} | ||
|
||
findTeamId(): string { | ||
return `${this.findLabel('team')}.${this.findLabel('tutorial')}`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,4 @@ | ||
import {User} from './user.model'; | ||
import { User } from './user.model'; | ||
|
||
export interface Team { | ||
id: string; | ||
|
25 changes: 25 additions & 0 deletions
25
src/app/core/models/templates/sections/duplicate-of-section.model.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
import { Section, SectionalDependency } from './section.model'; | ||
|
||
export class DuplicateOfSection extends Section { | ||
private readonly duplicateOfRegex = /Duplicate of\s*#(\d+)/i; | ||
issueNumber: number; | ||
|
||
constructor(sectionalDependency: SectionalDependency, unprocessedContent: string) { | ||
super(sectionalDependency, unprocessedContent); | ||
if (!this.parseError) { | ||
this.issueNumber = this.parseDuplicateOfValue(this.content); | ||
} | ||
} | ||
|
||
private parseDuplicateOfValue(toParse): number { | ||
const result = this.duplicateOfRegex.exec(toParse); | ||
return result ? +result[1] : null; | ||
} | ||
|
||
toString(): string { | ||
let toString = ''; | ||
toString += `${this.header}\n`; | ||
toString += this.parseError ? '--' : `Duplicate of ${this.issueNumber}\n`; | ||
return toString; | ||
} | ||
} |
29 changes: 29 additions & 0 deletions
29
src/app/core/models/templates/sections/issue-dispute-section.model.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import { IssueDispute } from '../../issue-dispute.model'; | ||
import { Section, SectionalDependency } from './section.model'; | ||
|
||
export class IssueDisputeSection extends Section { | ||
disputes: IssueDispute[]; | ||
|
||
constructor(sectionalDependency: SectionalDependency, unprocessedContent: string) { | ||
super(sectionalDependency, unprocessedContent); | ||
if (!this.parseError) { | ||
let matches; | ||
const regex = /#{2} *:question: *(.*)[\r\n]*([\s\S]*?(?=-{19}))/gi; | ||
while (matches = regex.exec(this.content)) { | ||
if (matches) { | ||
const [regexString, title, description] = matches; | ||
this.disputes.push(new IssueDispute(title, description.trim())); | ||
} | ||
} | ||
} | ||
} | ||
|
||
toString(): string { | ||
let toString = ''; | ||
toString += `${this.header.toString()}\n`; | ||
for (const dispute of this.disputes) { | ||
toString += `${dispute.toString()}\n`; | ||
} | ||
return toString; | ||
} | ||
} |
38 changes: 38 additions & 0 deletions
38
src/app/core/models/templates/sections/moderation-section.model.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
import { IssueDispute } from '../../issue-dispute.model'; | ||
import { Section, SectionalDependency } from './section.model'; | ||
|
||
export class ModerationSection extends Section { | ||
disputesToResolve: IssueDispute[]; | ||
|
||
constructor(sectionalDependency: SectionalDependency, unprocessedContent: string) { | ||
super(sectionalDependency, unprocessedContent); | ||
if (!this.parseError) { | ||
let matches; | ||
const regex = /#{2} *:question: *(.*)[\n\r]*(.*)[\n\r]*([\s\S]*?(?=-{19}))/gi; | ||
while (matches = regex.exec(this.content)) { | ||
if (matches) { | ||
const [regexString, title, todo, tutorResponse] = matches; | ||
const description = `${todo}\n${tutorResponse}`; | ||
|
||
const newDispute = new IssueDispute(title, description); | ||
newDispute.todo = todo; | ||
newDispute.tutorResponse = tutorResponse.trim(); | ||
this.disputesToResolve.push(newDispute); | ||
} | ||
} | ||
} | ||
} | ||
|
||
get todoList(): string[] { | ||
return this.disputesToResolve.map(e => e.todo); | ||
} | ||
|
||
toString(): string { | ||
let toString = ''; | ||
toString += `${this.header.toString()}\n`; | ||
for (const dispute of this.disputesToResolve) { | ||
toString += `${dispute.toTutorResponseString()}\n`; | ||
} | ||
return toString; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
/** | ||
* A SectionalDependency defines a format that is needed to create a successful Section in a template. | ||
* It will require the Section's header to be defined and the other headers that are present in the template. | ||
* | ||
* Reason for the dependencies on other headers: We need them to create a regex expression that is capable of parsing the current | ||
* section out of the string. | ||
*/ | ||
import { Header } from '../template.model'; | ||
|
||
export interface SectionalDependency { | ||
sectionHeader: Header; | ||
remainingTemplateHeaders: Header[]; | ||
} | ||
|
||
export class Section { | ||
header: Header; | ||
sectionRegex: RegExp; | ||
content: string; | ||
parseError: string; | ||
|
||
/** | ||
* | ||
* @param sectionalDependency The dependency that is need to create a section's regex | ||
* @param unprocessedContent The string that stores the section's amongst other things | ||
*/ | ||
constructor(sectionalDependency: SectionalDependency, unprocessedContent: string) { | ||
this.header = sectionalDependency.sectionHeader; | ||
this.sectionRegex = new RegExp(`(${this.header})\\s+([\\s\\S]*?)(?=${sectionalDependency.remainingTemplateHeaders.join('|')}|$)`, 'i'); | ||
const matches = this.sectionRegex.exec(unprocessedContent); | ||
if (matches) { | ||
const [originalString, header, description] = matches; | ||
this.content = description.trim(); | ||
this.parseError = null; | ||
} else { | ||
this.content = null; | ||
this.parseError = `Unable to extract ${this.header.name} Section`; | ||
} | ||
} | ||
} |
Oops, something went wrong.