From 62ef100677a765c2d19ab65b255875ff3ca6b45b Mon Sep 17 00:00:00 2001 From: Jiahao Date: Thu, 28 Mar 2024 12:57:33 +0800 Subject: [PATCH 1/6] Resolve issue of allocating invalid assignees MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently, when a user attempts to submit a team response with an invalid assignee (defined as an assignee who has not joined the organization), the team response is processed (by adding the team response GitHub comment) before the assignee checks are made. The error message for an invalid assignee is also vague, only stating "Validation Failed", with no additional context. This causes the issue to be erroneously classified as “Responded” when it has not actually been properly responded to as the “assignee” field would be left empty. The vague invalid assignee message also means users will not know what to do to fix the issue. This issue can be resolved by inverting the logic of team responses, ensuring the assignee validity is checked before making the team response comment. If the assignee is invalid, then the team response comment is never created, thus avoiding the erroneous classification. Also provided custom error parsing for invalid assignees to display proper reasons and actions for the validation failure for users to rectify the issue. A proper diagram of the inversion of logic can be found in the pull request: https://github.com/CATcher-org/CATcher/pull/1264#issuecomment-2022201156 --- src/app/core/services/issue.service.ts | 65 ++++++++++++++++--- .../new-team-response.component.ts | 4 +- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/src/app/core/services/issue.service.ts b/src/app/core/services/issue.service.ts index 5b55cab15..a7afb2f58 100644 --- a/src/app/core/services/issue.service.ts +++ b/src/app/core/services/issue.service.ts @@ -135,17 +135,27 @@ export class IssueService { } updateIssue(issue: Issue): Observable { + return this.updateGithubIssue(issue).pipe( + map((githubIssue: GithubIssue) => { + githubIssue.comments = issue.githubComments; + return this.createIssueModel(githubIssue); + }) + ); + } + + /** + * Updates an issue without attempting to create an issue model. Used when we want to treat + * updateIssue as an atomic operation that only performs an API call. + * @param issue current issue model + * @returns GitHubIssue from the API request + */ + updateGithubIssue(issue: Issue): Observable { const assignees = this.phaseService.currentPhase === Phase.phaseModeration ? [] : issue.assignees; return this.githubService .updateIssue(issue.id, issue.title, this.createGithubIssueDescription(issue), this.createLabelsForIssue(issue), assignees) .pipe( - map((response: GithubIssue) => { - response.comments = issue.githubComments; - return this.createIssueModel(response); - }), catchError((err) => { - this.logger.error('IssueService: ', err); // Log full details of error first - return throwError(err.response.data.message); // More readable error message + return this.parseUpdateIssueResponseError(err); }) ); } @@ -188,11 +198,17 @@ export class IssueService { } createTeamResponse(issue: Issue): Observable { + // The issue must be updated first to ensure that fields like assignees are valid const teamResponse = issue.createGithubTeamResponse(); - return this.githubService.createIssueComment(issue.id, teamResponse).pipe( - mergeMap((githubComment: GithubComment) => { - issue.githubComments = [githubComment, ...issue.githubComments.filter((c) => c.id !== githubComment.id)]; - return this.updateIssue(issue); + return this.updateGithubIssue(issue).pipe( + mergeMap((response: GithubIssue) => { + return this.githubService.createIssueComment(issue.id, teamResponse).pipe( + map((githubComment: GithubComment) => { + issue.githubComments = [githubComment, ...issue.githubComments.filter((c) => c.id !== githubComment.id)]; + response.comments = issue.githubComments; + return this.createIssueModel(response); + }) + ); }) ); } @@ -479,6 +495,35 @@ export class IssueService { return issue; } + private parseUpdateIssueResponseError(err: any) { + this.logger.error('IssueService: ', err); // Log full details of error first + + if (err.code !== 422 || !err.hasOwnProperty('message')) { + return throwError(err.response.data.message); // More readable error message + } + + // Error code 422 implies that one of the fields are invalid + const validationFailedPrefix = 'Validation Failed:'; + const message: string = err.message; + const errorJsonRaw = message.substring(validationFailedPrefix.length); + const errorJson = JSON.parse(errorJsonRaw); + + const mandatoryFields = ['field', 'code', 'value']; + const hasMandatoryFields = mandatoryFields.every((field) => errorJson.hasOwnProperty(field)); + + if (hasMandatoryFields) { + if (errorJson['field'] === 'assignees' && errorJson['code'] === 'invalid') { + // If assignees are invalid, return a custom error + return throwError( + `Assignee ${errorJson['value']} has not joined your organization yet. Please remove them from the assignees list.` + ); + } + } + + // Generic 422 Validation Failed since it is not an assignees problem + return throwError(err.response.data.message); + } + setIssueTeamFilter(filterValue: string) { if (filterValue) { this.issueTeamFilter = filterValue; diff --git a/src/app/shared/view-issue/new-team-response/new-team-response.component.ts b/src/app/shared/view-issue/new-team-response/new-team-response.component.ts index 7c963e46c..fb3b7add6 100644 --- a/src/app/shared/view-issue/new-team-response/new-team-response.component.ts +++ b/src/app/shared/view-issue/new-team-response/new-team-response.component.ts @@ -108,9 +108,9 @@ export class NewTeamResponseComponent implements OnInit, OnDestroy { this.isSafeToSubmit() .pipe( - mergeMap((isSaveToSubmit: boolean) => { + mergeMap((isSafeToSubmit: boolean) => { const newCommentDescription = latestIssue.createGithubTeamResponse(); - if (isSaveToSubmit) { + if (isSafeToSubmit) { return this.issueService.createTeamResponse(latestIssue); } else if (this.submitButtonText === SUBMIT_BUTTON_TEXT.OVERWRITE) { const issueCommentId = this.issueService.issues[this.issue.id].issueComment.id; From 21ca2cdb95fb1b1cc61f52e1977105e96a8ed183 Mon Sep 17 00:00:00 2001 From: Wong Chee Hong Date: Mon, 19 Feb 2024 15:21:13 +0800 Subject: [PATCH 2/6] Remove `wait-on` as dev-dep wait-on doesn't seem to be used at all in our workflows. Let's remove it. --- package.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/package.json b/package.json index 6c7371f15..296262ac8 100644 --- a/package.json +++ b/package.json @@ -95,7 +95,6 @@ "ts-node": "^10.9.2", "tslint": "~6.1.0", "tslint-config-prettier": "^1.18.0", - "typescript": "4.3.5", - "wait-on": "3.3.0" + "typescript": "4.3.5" } } From 70fec008baf6b3664811e6ade67ff38dfad26091 Mon Sep 17 00:00:00 2001 From: Wong Chee Hong Date: Mon, 19 Feb 2024 17:17:52 +0800 Subject: [PATCH 3/6] Add importHelpers option to tsconfig In order to use tslib, we need to include this compiler option in tsconfig. Based off clean installs of angular 12 projects via ng new, this setting appears to be the default as well. See: - https://angular.io/guide/typescript-configuration#tsconfig - From https://www.npmjs.com/package/tslib --- tsconfig.json | 1 + 1 file changed, 1 insertion(+) diff --git a/tsconfig.json b/tsconfig.json index 8caa59868..26942ea79 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ "outDir": "./dist/out-tsc", "sourceMap": true, "declaration": false, + "importHelpers": true, "moduleResolution": "node", "experimentalDecorators": true, "target": "es2015", From c2713f6c21c15b8ed3fdf8f989d7868c315e8a63 Mon Sep 17 00:00:00 2001 From: Wong Chee Hong Date: Mon, 19 Feb 2024 21:11:26 +0800 Subject: [PATCH 4/6] Remove node-fetch as direct dependency CATcher does not directly depend on node-fetch. After the removal of electron, there are no more files depending on node-fetch. --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 296262ac8..92e9191e2 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,6 @@ "moment": "^2.24.0", "ngx-markdown": "^12.0.1", "ngx-mat-select-search": "^3.3.3", - "node-fetch": "^2.6.8", "rxjs": "6.6.7", "tslib": "^2.0.0", "uuid": "7.0.3", From 969067642bdcb787237680be02964e48cfe8ac34 Mon Sep 17 00:00:00 2001 From: Nguyen <87511888+nknguyenhc@users.noreply.github.com> Date: Mon, 15 Apr 2024 21:55:33 +0800 Subject: [PATCH 5/6] Add login redirect (#1256) Previously, upon login, users are brought to the default landing page of the phase. We redirect to the intended landing page, with appropriate next route checking. --- .../confirm-login/confirm-login.component.ts | 2 +- src/app/core/guards/auth.guard.ts | 2 + src/app/core/services/auth.service.ts | 37 ++++++++++++++++++- src/app/core/services/phase.service.ts | 7 ++++ .../shared/view-issue/view-issue.component.ts | 10 ++++- 5 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/app/auth/confirm-login/confirm-login.component.ts b/src/app/auth/confirm-login/confirm-login.component.ts index e292672a2..ad8f5d558 100644 --- a/src/app/auth/confirm-login/confirm-login.component.ts +++ b/src/app/auth/confirm-login/confirm-login.component.ts @@ -44,8 +44,8 @@ export class ConfirmLoginComponent implements OnInit { */ handleAuthSuccess() { this.authService.setTitleWithPhaseDetail(); - this.router.navigateByUrl(this.phaseService.currentPhase); this.authService.changeAuthState(AuthState.Authenticated); + this.authService.navigateToLandingPage(); } /** diff --git a/src/app/core/guards/auth.guard.ts b/src/app/core/guards/auth.guard.ts index def276425..2fadddd46 100644 --- a/src/app/core/guards/auth.guard.ts +++ b/src/app/core/guards/auth.guard.ts @@ -11,8 +11,10 @@ export class AuthGuard implements CanActivate, CanLoad { canActivate(next: ActivatedRouteSnapshot, state: RouterStateSnapshot): Observable | Promise | boolean { if (this.auth.isAuthenticated()) { + this.auth.clearNext(); return true; } else { + this.auth.storeNext(state); this.router.navigate(['']); return false; } diff --git a/src/app/core/services/auth.service.ts b/src/app/core/services/auth.service.ts index cf8a9cb68..7dba57036 100644 --- a/src/app/core/services/auth.service.ts +++ b/src/app/core/services/auth.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; import { NgZone } from '@angular/core'; import { Title } from '@angular/platform-browser'; -import { Router } from '@angular/router'; +import { Router, RouterStateSnapshot } from '@angular/router'; import { BehaviorSubject } from 'rxjs'; import { AppConfig } from '../../../environments/environment'; import { generateSessionId } from '../../shared/lib/session'; @@ -30,6 +30,8 @@ export enum AuthState { * updating the application state with regards to authentication. */ export class AuthService { + private static readonly SESSION_NEXT_KEY = 'next'; + authStateSource = new BehaviorSubject(AuthState.NotAuthenticated); currentAuthState = this.authStateSource.asObservable(); accessToken = new BehaviorSubject(undefined); @@ -50,6 +52,27 @@ export class AuthService { private logger: LoggingService ) {} + /** + * Stores the data about the next route in the session storage. + */ + storeNext(next: RouterStateSnapshot) { + sessionStorage.setItem(AuthService.SESSION_NEXT_KEY, next.url); + } + + /** + * Returns the next route + */ + private getNext(): string { + return sessionStorage.getItem(AuthService.SESSION_NEXT_KEY); + } + + /** + * Clears the next route from the session storage. + */ + clearNext() { + sessionStorage.removeItem(AuthService.SESSION_NEXT_KEY); + } + /** * Will store the OAuth token. */ @@ -144,4 +167,16 @@ export class AuthService { } window.location.href = url; } + + /** + * Navigates to next if there is, or default landing page. + */ + navigateToLandingPage() { + const nextRoute = this.getNext(); + if (!nextRoute || !this.phaseService.isValidRoute(nextRoute)) { + this.router.navigateByUrl(this.phaseService.currentPhase); + } else { + this.router.navigateByUrl(nextRoute); + } + } } diff --git a/src/app/core/services/phase.service.ts b/src/app/core/services/phase.service.ts index ada3c9bb4..3e5d8b221 100644 --- a/src/app/core/services/phase.service.ts +++ b/src/app/core/services/phase.service.ts @@ -161,6 +161,13 @@ export class PhaseService { return this.orgName.concat('/').concat(this.repoName); } + /** + * Checks whether the given route is allowed in this phase. + */ + isValidRoute(route: string): boolean { + return route.startsWith('/' + this.currentPhase); + } + reset() { this.currentPhase = null; } diff --git a/src/app/shared/view-issue/view-issue.component.ts b/src/app/shared/view-issue/view-issue.component.ts index 34b24e93d..f36b67260 100644 --- a/src/app/shared/view-issue/view-issue.component.ts +++ b/src/app/shared/view-issue/view-issue.component.ts @@ -1,4 +1,5 @@ import { Component, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core'; +import { Router } from '@angular/router'; import { Subscription } from 'rxjs'; import { Issue } from '../../core/models/issue.model'; import { UserRole } from '../../core/models/user.model'; @@ -53,7 +54,8 @@ export class ViewIssueComponent implements OnInit, OnDestroy, OnChanges { public permissions: PermissionService, public userService: UserService, public issueService: IssueService, - private phaseService: PhaseService + private phaseService: PhaseService, + private router: Router ) {} ngOnInit() { @@ -126,7 +128,11 @@ export class ViewIssueComponent implements OnInit, OnDestroy, OnChanges { this.issue = issue; this.pollIssue(id); }, - (err) => this.errorHandlingService.handleError(err) + (err) => { + this.router.navigateByUrl(this.phaseService.currentPhase).then(() => { + this.errorHandlingService.handleError(new Error('Invalid URL provided!')); + }); + } ); } From c7461b2feaec76686fb2ccd9b6c89ef3324af98d Mon Sep 17 00:00:00 2001 From: "Lee Xiong Jie, Isaac" <68138671+luminousleek@users.noreply.github.com> Date: Fri, 26 Apr 2024 15:07:51 +0800 Subject: [PATCH 6/6] Add documentation for CATcher's parser (#1240) When CATcher's parsers were updated to use arcsecond, there was minimal documentation within the codebase to explain how the parsers work. As such, it was difficult to understand and maintain these parsers. Let's add examples for each comment template and explain the parsers that are more difficult to understand. --- .../section-parsers/common-parsers.model.ts | 32 +++++++++++ .../issue-dispute-section-parser.model.ts | 34 ++++++++++++ .../moderation-section-parser.model.ts | 18 +++++++ .../tester-response-section-parser.model.ts | 54 ++++++++++++++++++- .../templates/team-accepted-template.model.ts | 6 +++ .../templates/team-response-template.model.ts | 19 +++++++ .../core/models/templates/template.model.ts | 4 ++ .../tester-response-template.model.ts | 50 +++++++++++++++++ .../tutor-moderation-issue-template.model.ts | 49 +++++++++++++++++ .../tutor-moderation-todo-template.model.ts | 24 +++++++++ 10 files changed, 288 insertions(+), 2 deletions(-) diff --git a/src/app/core/models/templates/section-parsers/common-parsers.model.ts b/src/app/core/models/templates/section-parsers/common-parsers.model.ts index 7edc0b34b..d15e2f30c 100644 --- a/src/app/core/models/templates/section-parsers/common-parsers.model.ts +++ b/src/app/core/models/templates/section-parsers/common-parsers.model.ts @@ -3,6 +3,31 @@ const { char, choice, coroutine, everyCharUntil, str, whitespace } = require('ar const TEAM_RESPONSE_HEADER = "# Team's Response"; const DEFAULT_TEAM_RESPONSE = 'No details provided by team.'; +/** + * Builds a parser for the team response section. + * Team response sections appear in both the Team Response and Tester Response comment templates. + * The format of the Team Response section is as follows + * # Team's response + * + * { team's response } + * + * { next header } + * + * A concrete example would be: + * + * # Team's response + * + * This not a bug, it's a feature + * + * ## Duplicate status (if any): + * + * This parser works by reading everything in between the Team's reponse header and the next header. + * The reason why this parser builder exists is because the next header is different in both comment templates. + * The next header in the Team Response comment is ## Duplicate status (if any): + * While the next header in the Tester Response comment is # Disputes + * @param nextHeader + * @returns a string containing the team response + */ export function buildTeamResponseSectionParser(nextHeader: string) { return coroutine(function* () { yield str(TEAM_RESPONSE_HEADER); @@ -12,6 +37,13 @@ export function buildTeamResponseSectionParser(nextHeader: string) { }); } +/** + * Parses the checkbox and returns whether it is filled or not. + * Filled checkboxes look like this: - [x] { description }, e.g. - [x] I disagree + * Unfilled checkboxes look like thsi: - [ ] { description } + * @param description + * @returns true if the checkbox is filled, false otherwise + */ export function buildCheckboxParser(description: string) { return coroutine(function* () { yield str('- ['); diff --git a/src/app/core/models/templates/section-parsers/issue-dispute-section-parser.model.ts b/src/app/core/models/templates/section-parsers/issue-dispute-section-parser.model.ts index fec1f6900..e38a91d2e 100644 --- a/src/app/core/models/templates/section-parsers/issue-dispute-section-parser.model.ts +++ b/src/app/core/models/templates/section-parsers/issue-dispute-section-parser.model.ts @@ -1,3 +1,37 @@ +/** + * An issue dispute section has the following format: + * + * ## :question: Issue { type of verification } + * + * ### Team says: + * + * { team's action that is being disputed } + * + * ### Tester says: + * + * { tester's objection } + * + *
+ * + * A concrete example would be: + * + * ## :question: Issue type + * + * ### Team says: + * + * Team chose [`type.DocumentationBug`]. + * Originally [`type.FunctionalityBug`]. + * + * This use case is just not in the docs. + * + * ### Tester says: + * + * It's not a use case, it's a bug! This has nothing to do with the docs. + * + *
+ * + */ + import { IssueDispute } from '../../issue-dispute.model'; const { coroutine, everyCharUntil, optionalWhitespace, str } = require('arcsecond'); diff --git a/src/app/core/models/templates/section-parsers/moderation-section-parser.model.ts b/src/app/core/models/templates/section-parsers/moderation-section-parser.model.ts index 20e7a7363..f837978a7 100644 --- a/src/app/core/models/templates/section-parsers/moderation-section-parser.model.ts +++ b/src/app/core/models/templates/section-parsers/moderation-section-parser.model.ts @@ -1,3 +1,21 @@ +/** + * A Tutor Moderation section has this format: + * + * ## :question: Issue { type of verification } + * - [ ] Done + * + * { tutor response } + * + *
+ * + * A concrete example would be: + * + * ## :question: Issue type + * - [x] Done + * + * I think it's all cool + */ + import { Checkbox } from '../../checkbox.model'; import { IssueDispute } from '../../issue-dispute.model'; import { buildCheckboxParser } from './common-parsers.model'; diff --git a/src/app/core/models/templates/section-parsers/tester-response-section-parser.model.ts b/src/app/core/models/templates/section-parsers/tester-response-section-parser.model.ts index c42ef42b1..501724c74 100644 --- a/src/app/core/models/templates/section-parsers/tester-response-section-parser.model.ts +++ b/src/app/core/models/templates/section-parsers/tester-response-section-parser.model.ts @@ -1,3 +1,33 @@ +/** + * A tester response section generally has this format (apart from the duplicate issue section) + * + * ## :question: Issue {type of verification} + * + * Team chose [{ team response }]. + * Originally [{ tester response }]. + * + * - [ ] I disagree + * + * **Reason for disagreement:** + * { disagreement reason } + * + *
+ * + * A concrete example would be: + * + * ## :question: Issue severity + * + * Team chose [`severity.Low`]. + * Originally [`severity.Medium`]. + * + * - [x] I disagree + * + * **Reason for disagreement:** + * The team is silly and doesn't understand how bad this bug is!!! + * + *
+ */ + import { buildCheckboxParser } from './common-parsers.model'; const { @@ -24,6 +54,12 @@ const DUPLICATE_STATUS_MESSAGE = export const DisagreeCheckboxParser = buildCheckboxParser(DISAGREE_CHECKBOX_DESCRIPTION); +/** + * This parser extracts the response for the item disagreed on. + * E.g. for [`severity.Low`], the category is 'severity' and the parser would return 'Low' + * @param category + * @returns a string indicating the response in that category + */ function buildExtractResponseParser(category: string) { return between(str('[`' + category + '.'))(str('`]'))(letters); } @@ -48,7 +84,21 @@ export const DisagreeReasonParser = coroutine(function* () { return reasonForDisagreement.trim(); }); -// Issue duplicate section has a different format than the other three +/** + * The duplicate issue section has a different format than the other three, which is below: + * + * ## :question: Issue duplicate status + * + * Team chose to mark this issue as a duplicate of another issue (as explained in the _**Team's response**_ above) + * + * + * + * - [ ] I disagree + * + * **Reason for disagreement:** [replace this with your explanation] + * + *
+ */ const DuplicateSectionParser = coroutine(function* () { yield str('status'); yield whitespace; @@ -93,7 +143,7 @@ export const TesterResponseSectionParser = coroutine(function* () { const teamChose = yield teamResponseParser; yield whitespace; - // response section does not have tester response + // response section does not have tester response, i.e. no "Originally [`response.Something`]" const testerChose = yield possibly(testerResponseParser); yield optionalWhitespace; diff --git a/src/app/core/models/templates/team-accepted-template.model.ts b/src/app/core/models/templates/team-accepted-template.model.ts index 9b3d2d404..f7bc0c7c4 100644 --- a/src/app/core/models/templates/team-accepted-template.model.ts +++ b/src/app/core/models/templates/team-accepted-template.model.ts @@ -1,3 +1,9 @@ +/** + * A Team Accepted comment has this format: + * + * Your response not required for this bug as the team has accepted the bug as it is. + */ + import { GithubComment } from '../github/github-comment.model'; import { Template } from './template.model'; diff --git a/src/app/core/models/templates/team-response-template.model.ts b/src/app/core/models/templates/team-response-template.model.ts index 56d4ba0dd..66ff75ef6 100644 --- a/src/app/core/models/templates/team-response-template.model.ts +++ b/src/app/core/models/templates/team-response-template.model.ts @@ -1,3 +1,22 @@ +/** + * A Team Response comment has the following format: + * + * # Team's response + * + * { team's response } + * + * ## Duplicate status (if any): + * { duplicate issue number, or '--' if not a duplicate } + * + * A concrete example would be: + * + * # Team's response + * + * This is not a bug, it's a feature + * + * ## Duplicate status (if any): + * Duplicate of #42 + */ import { IssueComment } from '../comment.model'; import { GithubComment } from '../github/github-comment.model'; import { buildTeamResponseSectionParser } from './section-parsers/common-parsers.model'; diff --git a/src/app/core/models/templates/template.model.ts b/src/app/core/models/templates/template.model.ts index 8df67538b..3083986f0 100644 --- a/src/app/core/models/templates/template.model.ts +++ b/src/app/core/models/templates/template.model.ts @@ -1,3 +1,7 @@ +/** + * Comment template formats can be found at https://catcher-org.github.io/dg/user-workflow.html + */ + import { GithubComment } from '../github/github-comment.model'; export abstract class Template { diff --git a/src/app/core/models/templates/tester-response-template.model.ts b/src/app/core/models/templates/tester-response-template.model.ts index 4d5db7dce..296a2e713 100644 --- a/src/app/core/models/templates/tester-response-template.model.ts +++ b/src/app/core/models/templates/tester-response-template.model.ts @@ -1,3 +1,53 @@ +/** + * A Tester Response comment has the following format: + * + * [IMPORTANT!: Please do not edit or reply to this comment using the GitHub UI. \ + You can respond to it using CATcher during the next phase of the PE] + * + * # Team's response + * + * { team's response } + * + * # Items for the Tester to Verify + * + * { 1 or more Tester Response sections, see tester-response-section-parser.model.ts } + * + * A concrete example would be: + * + * [IMPORTANT!: Please do not edit or reply to this comment using the GitHub UI. \ + You can respond to it using CATcher during the next phase of the PE] + * + * # Team's response + * + * This is not a bug, it's a feature + * + * # Items for the Tester to Verify + * + * ## :question: Issue type + * + * Team chose [`type.DocumentationBug`]. + * Originally [`type.FunctionalityBug`]. + * + * [x] - I disagree + * + * **Reason for disagreement:** + * It's not a use case, it's a bug! This has nothing to do with the docs. + * + *
+ * + * ## :question: Issue severity + * + * Team chose [`severity.VeryLow`]. + * Originally [`severity.High`]. + * + * [x] - I disagree + * + * **Reason for disagreement:** + * You don't understand how frustrating this bug is!! + * + *
+ */ + import { IssueComment } from '../comment.model'; import { GithubComment } from '../github/github-comment.model'; import { TesterResponse } from '../tester-response.model'; diff --git a/src/app/core/models/templates/tutor-moderation-issue-template.model.ts b/src/app/core/models/templates/tutor-moderation-issue-template.model.ts index bb413ea1a..3e7dab8ab 100644 --- a/src/app/core/models/templates/tutor-moderation-issue-template.model.ts +++ b/src/app/core/models/templates/tutor-moderation-issue-template.model.ts @@ -1,3 +1,52 @@ +/** + * A Tutor Moderation Issue comment has this format: + * + * # Issue Description + * { original issue description } + * + * # Team's Response + * { team's response} + * + * # Disputes + * + * { 1 or more Issue Dispute sections, see issue-dispute-section-parser.model.ts } + * + * A concrete example would be: + * + * # Issue Description + * This feature doesn't work when I enter this input! + * + * # Team's Response + * This is not a bug, it is a feature + * + * # Disputes + * + * ## :question: Issue type + * + * Team chose [`type.DocumentationBug`]. + * Originally [`type.FunctionalityBug`]. + * + * This use case is just not in the docs. + * + * ### Tester says: + * + * It's not a use case, it's a bug! This has nothing to do with the docs. + * + *
+ * + * ## :question: Issue severity + * + * Team chose [`severity.VeryLow`]. + * Originally [`severity.High`]. + * + * This only affects users a tiny little bit. + * + * ### Tester says: + * You don't understand how frustrating this bug is!! + * + *
+ */ + import { GithubIssue } from '../github/github-issue.model'; import { IssueDispute } from '../issue-dispute.model'; import { buildTeamResponseSectionParser } from './section-parsers/common-parsers.model'; diff --git a/src/app/core/models/templates/tutor-moderation-todo-template.model.ts b/src/app/core/models/templates/tutor-moderation-todo-template.model.ts index b198a7eda..62565904f 100644 --- a/src/app/core/models/templates/tutor-moderation-todo-template.model.ts +++ b/src/app/core/models/templates/tutor-moderation-todo-template.model.ts @@ -1,3 +1,27 @@ +/** + * A Tutor Moderation Todo comment has this format: + * + * # Tutor Moderation + * + * { 1 or more Tutor Moderation sections, see moderation-section-parser.model.ts } + * + * A concrete example would be: + * + * # Tutor Moderation + * + * ## :question: Issue severity + * - [x] Done + * + * I think it is justified. + * + *
+ * + * ## :question: Issue type + * - [ ] Done + * + *
+ */ + import { IssueComment } from '../comment.model'; import { GithubComment } from '../github/github-comment.model'; import { IssueDispute } from '../issue-dispute.model';