Skip to content

Commit

Permalink
New release for 3.5.3 (#1254)
Browse files Browse the repository at this point in the history
* Fix broken duplicate link (#1233)

Fix the broken link of a duplicate issue

Currently, the user cannot open the link to a duplicate issue
when opening an issue, as described in #1228.

The links now work as expected.

* Add whitespace validation (#1237)

* Add whitespace validation

* Update whitespace validation for new issue

* Update whitespace validation for title of new issues

* Update whitespace validation for title of new issues

* Move validators into core

* Update import order

---------

Co-authored-by: Misra Aditya <e1096355@u.nus.edu>

* Fix uncaught errors when attempting to access an invalid route

There is an uncaught error when the users click on an invalid internal link in Markdown or enter an invalid link in browser.

Internal links are unlikely to be used for bug reporting and are more likely to be invalid.

Let's show an error toaster and stop the navigation when clicking on an internal link in Markdown. Also, redirect the users to the login page if the users enter invalid link in browser.

* Set default branch to `main`

Previously, image uploads depend on the user's default branch.
Now, we set the branch for image upload to be `main`. Images will 
be uploaded to `main` as a result.

---------

Co-authored-by: Chee Hong <c.h.wong2606@gmail.com>

* Preserve linebreaks (#1241)

With preserving linebreaks, subset list items are rendered as a list,
and paragraph rendering is the same as Github.

* Faulty list view when back navigating (#1243)

Issue table settings such as page index are not 
saved when table is re-mounted.

This behavior inconveniences users as their settings 
are reset everytime they navigate to a specific issue and back.

Let's lift up the table settings of each mounted table to 
a service which the tables pull from when mounted.

* Upgrade to Angular 12 (#1242)

Some of our packages are old and outdated. We should actively maintain 
and keep these packages up-to-date so it is easier to maintain in the 
future.

Let's upgrade to Angular 12 to keep our packages up-to-date.

* Fix markdown blockquote preview difference (#1245)

Due to DOMPurify, the content used for preview is different.
However, given that ngx-markdown already has sufficient sanitation
by default, we remove sanitation by DOMPurify.

* Create release for v3.5.3

---------

Co-authored-by: Nguyen <87511888+nknguyenhc@users.noreply.github.com>
Co-authored-by: AdityaMisra <114080910+MadLamprey@users.noreply.github.com>
Co-authored-by: Misra Aditya <e1096355@u.nus.edu>
Co-authored-by: NereusWB922 <107099783+NereusWB922@users.noreply.github.com>
Co-authored-by: Chee Hong <c.h.wong2606@gmail.com>
Co-authored-by: Arif Khalid <88131400+Arif-Khalid@users.noreply.github.com>
  • Loading branch information
7 people committed Mar 4, 2024
1 parent 013fbfa commit 404f470
Show file tree
Hide file tree
Showing 29 changed files with 270 additions and 65 deletions.
17 changes: 9 additions & 8 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"aot": true,
"outputPath": "dist",
"index": "src/index.html",
"main": "src/main.ts",
Expand All @@ -36,7 +35,13 @@
"node_modules/prismjs/prism.js",
"node_modules/prismjs/components/prism-csharp.min.js",
"node_modules/prismjs/components/prism-css.min.js"
]
],
"vendorChunk": true,
"extractLicenses": false,
"buildOptimizer": false,
"sourceMap": true,
"optimization": false,
"namedChunks": true
},
"configurations": {
"staging": {
Expand All @@ -50,7 +55,6 @@
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
Expand All @@ -72,7 +76,6 @@
"outputHashing": "all",
"sourceMap": false,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
Expand All @@ -90,22 +93,20 @@
"maximumWarning": "6kb"
}
],
"optimization": false,
"outputHashing": "all",
"sourceMap": true,
"namedChunks": false,
"aot": false,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": false,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.test.ts"
}
]
}
}
},
"defaultConfiguration": ""
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
Expand Down
45 changes: 22 additions & 23 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "CATcher",
"version": "3.5.2",
"version": "3.5.3",
"main": "main.js",
"engines": {
"node": ">=14.0.0"
},
"scripts": {
"ng": "ng",
"build:staging": "npm run codegen:gql && ng build --c staging --base-href=https://catcher-org.github.io/CATcher-staging/",
"build:prod:web": "npm run codegen:gql && ng build --prod --base-href=https://CATcher-org.github.io/CATcher/",
"build:prod:web": "npm run codegen:gql && ng build --configuration production --base-href=https://CATcher-org.github.io/CATcher/",
"ng:serve": "npm run codegen:gql && ng serve",
"ng:serve:web": "npm run ng:serve -- -o",
"deploy:staging": "npm run build:staging && ngh --dir=dist --name=CATcher --email=se-edu@comp.nus.edu.sg --no-silent",
Expand All @@ -29,43 +29,42 @@
}
},
"dependencies": {
"@angular/animations": "^11.2.14",
"@angular/cdk": "^11.2.13",
"@angular/common": "^11.2.14",
"@angular/compiler": "^11.2.14",
"@angular/core": "^11.2.14",
"@angular/forms": "^11.2.14",
"@angular/localize": "^11.2.14",
"@angular/material": "^11.2.13",
"@angular/platform-browser": "^11.2.14",
"@angular/platform-browser-dynamic": "^11.2.14",
"@angular/router": "^11.2.14",
"@angular/animations": "^12.2.17",
"@angular/cdk": "^12.2.13",
"@angular/common": "^12.2.17",
"@angular/compiler": "^12.2.17",
"@angular/core": "^12.2.17",
"@angular/forms": "^12.2.17",
"@angular/localize": "^12.2.17",
"@angular/material": "^12.2.13",
"@angular/platform-browser": "^12.2.17",
"@angular/platform-browser-dynamic": "^12.2.17",
"@angular/router": "^12.2.17",
"@github/markdown-toolbar-element": "^2.1.1",
"@octokit/rest": "^16.37.0",
"ajv": "^6.11.0",
"apollo-angular": "^2.6.0",
"arcsecond": "^4.1.0",
"core-js": "^3.16.4",
"diff-match-patch": "^1.0.4",
"dompurify": "^2.3.1",
"graphql": "^15.0.0",
"karma-spec-reporter": "0.0.32",
"moment": "^2.24.0",
"ngx-markdown": "^11.2.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",
"zone.js": "~0.10.2",
"zone.js": "~0.11.4",
"@apollo/client": "3.3.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.1102.19",
"@angular/cli": "^11.2.19",
"@angular/compiler-cli": "^11.2.14",
"@angular/language-service": "^11.2.14",
"@graphql-codegen/cli": "^2.6.4",
"@angular-devkit/build-angular": "~12.2.18",
"@angular/cli": "^12.2.18",
"@angular/compiler-cli": "^12.2.17",
"@angular/language-service": "^12.2.17",
"@graphql-codegen/cli": "2.16.4",
"@graphql-codegen/fragment-matcher": "^1.17.7",
"@graphql-codegen/typescript": "1.17.7",
"@graphql-codegen/typescript-document-nodes": "1.17.7",
Expand Down Expand Up @@ -93,10 +92,10 @@
"prettier": "2.2.1",
"pretty-quick": "^3.1.1",
"scuri": "^0.9.4",
"ts-node": "^7.0.1",
"ts-node": "^10.9.2",
"tslint": "~6.1.0",
"tslint-config-prettier": "^1.18.0",
"typescript": "4.0.8",
"typescript": "4.3.5",
"wait-on": "3.3.0"
}
}
3 changes: 2 additions & 1 deletion src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ const routes: Routes = [
{ path: 'phaseBugReporting', loadChildren: () => PhaseBugReportingModule, canLoad: [AuthGuard] },
{ path: 'phaseTeamResponse', loadChildren: () => PhaseTeamResponseModule, canLoad: [AuthGuard] },
{ path: 'phaseTesterResponse', loadChildren: () => PhaseTesterResponseModule, canLoad: [AuthGuard] },
{ path: 'phaseModeration', loadChildren: () => PhaseModerationModule, canLoad: [AuthGuard] }
{ path: 'phaseModeration', loadChildren: () => PhaseModerationModule, canLoad: [AuthGuard] },
{ path: '**', redirectTo: '' }
];

@NgModule({
Expand Down
32 changes: 32 additions & 0 deletions src/app/core/directives/internal-link-disable.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Directive, HostListener } from '@angular/core';
import { ErrorHandlingService } from '../services/error-handling.service';

class InvalidLinkError extends Error {
constructor() {
super('Invalid link!');
Object.setPrototypeOf(this, InvalidLinkError.prototype);
}
}

@Directive({
selector: '[disableInternalLink]'
})
export class InternalLinkDisableDirective {
constructor(private errorHandlingService: ErrorHandlingService) {}

@HostListener('click', ['$event'])
public onClick(e: MouseEvent): void {
const srcElement = e.target;

if (srcElement instanceof HTMLAnchorElement) {
const baseURI = srcElement.baseURI;
const href = srcElement.href;

if (href.startsWith(baseURI)) {
this.errorHandlingService.handleError(new InvalidLinkError());
e.preventDefault();
e.stopPropagation();
}
}
}
}
6 changes: 6 additions & 0 deletions src/app/core/models/table-settings.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class TableSettings {
public sortActiveId = ''; // The ID of the column the table is sorted by
public sortDirection = '';
public pageSize = 20;
public pageIndex = 0;
}
82 changes: 82 additions & 0 deletions src/app/core/services/github.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
FetchLabelsQuery
} from '../../../../graphql/graphql-types';
import { AppConfig } from '../../../environments/environment';
import { throwIfFalse } from '../../shared/lib/custom-ops';
import { getNumberOfPages } from '../../shared/lib/github-paginator-parser';
import { IssueComment } from '../models/comment.model';
import { GithubUser } from '../models/github-user.model';
Expand All @@ -35,7 +36,9 @@ import { LoggingService } from './logging.service';
const { Octokit } = require('@octokit/rest');
const CATCHER_ORG = 'CATcher-org';
const CATCHER_REPO = 'CATcher';
const BRANCH = 'main';
const UNABLE_TO_OPEN_IN_BROWSER = 'Unable to open this issue in Browser';
const BRANCH_CREATION_FAILED = `Unable to create ${BRANCH} branch.`;
function getSettingsUrl(org: string, repoName: string): string {
return `https://raw.githubusercontent.com/${org}/${repoName}/master/settings.json`;
}
Expand Down Expand Up @@ -201,6 +204,84 @@ export class GithubService {
octokit.repos.createForAuthenticatedUser({ name: name });
}

/**
* Creates the `main` branch for the current repository.
*/
createBranch() {
return this.getDefaultBranch().pipe(
mergeMap((res) => this.getBranchHeadInfo(res)),
map((res) => res.data.object.sha),
mergeMap((sha: string) => this.createBranchFromCommit(sha)),
mergeMap(() => this.isMainBranchPresent()),
throwIfFalse(
(isBranchPresent: boolean) => isBranchPresent,
() => new Error(BRANCH_CREATION_FAILED)
)
);
}

/**
* Creates the `main` branch for the current repository,
* from the commit with the given SHA.
*/
createBranchFromCommit(commitSha: string) {
return from(
octokit.git.createRef({
owner: ORG_NAME,
repo: REPO,
ref: `refs/heads/${BRANCH}`,
sha: commitSha
})
);
}

/**
* Get the default branch of the specified repository.
* @param owner The owner of the repository.
* @param repo The name of the repository.
*/
getDefaultBranch(): Observable<string> {
return from(
octokit.repos.get({
owner: ORG_NAME,
repo: REPO
})
).pipe(map((res: any) => res.data.default_branch));
}

/**
* Get information of the head of the given branch name,
* in the current repository.
* @param branch The name of the branch.
*/
getBranchHeadInfo(branch: string): Observable<any> {
return from(
octokit.git.getRef({
owner: ORG_NAME,
repo: REPO,
ref: `heads/${branch}`
})
);
}

/**
* Checks if the repo already has the branch `main`.
*/
isMainBranchPresent(): Observable<boolean> {
return from(
octokit.git.getRef({
owner: ORG_NAME,
repo: REPO,
ref: `heads/${BRANCH}`
})
).pipe(
map((res: any) => res.status !== ERRORCODE_NOT_FOUND),
catchError(() => {
return of(false);
})
);
}

/**
* Fetches information about an issue using GraphQL.
*
Expand Down Expand Up @@ -360,6 +441,7 @@ export class GithubService {
octokit.repos.createOrUpdateFile({
owner: ORG_NAME,
repo: REPO,
branch: BRANCH,
path: `files/${filename}`,
message: 'upload file',
content: base64String
Expand Down
25 changes: 25 additions & 0 deletions src/app/core/services/issue-table-settings.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { Injectable } from '@angular/core';
import { TableSettings } from '../models/table-settings.model';
@Injectable({
providedIn: 'root'
})

/**
* Responsible for storing and retrieving the table settings for issue tables created
* Map is required since there can be multiple tables within the same page
*/
export class IssueTableSettingsService {
private _tableSettingsMap: { [index: string]: TableSettings } = {};

public getTableSettings(tableName: string): TableSettings {
return this._tableSettingsMap[tableName] || new TableSettings();
}

public setTableSettings(tableName: string, tableSettings: TableSettings): void {
this._tableSettingsMap[tableName] = tableSettings;
}

public clearTableSettings(): void {
this._tableSettingsMap = {};
}
}
20 changes: 18 additions & 2 deletions src/app/core/services/upload.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Injectable } from '@angular/core';
import { throwError } from 'rxjs';
import { of, throwError } from 'rxjs';
import { catchError, mergeMap } from 'rxjs/operators';
import { uuid } from '../../shared/lib/uuid';
import { ERRORCODE_NOT_FOUND } from './error-handling.service';
import { GithubService } from './github.service';

const SUPPORTED_VIDEO_FILE_TYPES = ['mp4', 'mov'];
Expand Down Expand Up @@ -49,7 +51,21 @@ export class UploadService {
if (SUPPORTED_FILE_TYPES.includes(fileType.toLowerCase())) {
base64String = base64String.split(',')[1];
const onlineFilename = uuid();
return this.githubService.uploadFile(`${onlineFilename}.${fileType}`, base64String);
const attemptUploadFile = () => this.githubService.uploadFile(`${onlineFilename}.${fileType}`, base64String);
return attemptUploadFile().pipe(
catchError((err: any) => {
if (!(err.status === ERRORCODE_NOT_FOUND)) {
return throwError(err);
}
return of(false);
}),
mergeMap((isBranchPresent) => {
if (isBranchPresent) {
return of(isBranchPresent);
}
return this.githubService.createBranch().pipe(mergeMap(attemptUploadFile));
})
);
} else {
return throwError(FILE_TYPE_SUPPORT_ERROR);
}
Expand Down
10 changes: 10 additions & 0 deletions src/app/core/validators/noWhitespace.validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { AbstractControl, ValidationErrors, ValidatorFn } from '@angular/forms';

export function noWhitespace(): ValidatorFn {
return (title: AbstractControl): ValidationErrors | null => {
if (title.value && title.value.trim() === '') {
return { whitespace: true };
}
return null;
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ <h1 class="mat-display-1 title">New Issue</h1>
<mat-form-field>
<input id="title" formControlName="title" matInput placeholder="Title" required maxlength="256" />
<mat-error *ngIf="title.errors && title.errors['required'] && (title.touched || title.dirty)"> Title required. </mat-error>
<mat-error *ngIf="title.errors && title.errors['whitespace']"> Title cannot contain only whitespaces. </mat-error>
<mat-error *ngIf="title.errors && title.errors['maxlength']"> Title cannot exceed 256 characters. </mat-error>
<mat-hint *ngIf="title.value?.length >= 206"> {{ 256 - title.value?.length }} characters remaining. </mat-hint>
</mat-form-field>
Expand Down
Loading

0 comments on commit 404f470

Please sign in to comment.