Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
152a1c0
Create pull request model
TobyCyan Jul 16, 2025
0852a24
Fix incompatible dependencies
TobyCyan Jul 16, 2025
de8b73c
Fix dependencies
TobyCyan Jul 16, 2025
918c0b5
Fix dependencies
TobyCyan Jul 16, 2025
1853c88
Reset dependencies
TobyCyan Jul 16, 2025
c5203cb
Add RepoItem class as base class to PullRequest and Issue
TobyCyan Jul 17, 2025
176c395
Fix initialization of repo items bug
TobyCyan Jul 19, 2025
da75a61
Change reference of review decision to be at RepoItem
TobyCyan Jul 19, 2025
62ab478
Rename issueLength to repoItemLength in html
TobyCyan Jul 19, 2025
912a3ed
Fix unordered imports
TobyCyan Jul 19, 2025
206e6f5
Fix unordered imports
TobyCyan Jul 19, 2025
5bd78c0
Fix tests
TobyCyan Jul 19, 2025
24fa04c
Rename files
TobyCyan Jul 19, 2025
684e643
Fix imports
TobyCyan Jul 19, 2025
732eb6f
Fix unordered imports
TobyCyan Jul 19, 2025
07413cb
Update comments
TobyCyan Jul 19, 2025
019ea01
Merge branch 'main' of https://github.com/TobyCyan/WATcher
TobyCyan Jul 30, 2025
e0e2167
Merge branch 'main' into 464-refactor-split-issue-and-pr
TobyCyan Jul 30, 2025
b345854
Implement a readonly type field and reference this in type checks
TobyCyan Jul 30, 2025
ea65fd6
Remove PhaseBugReporting from issue and pr factory method
TobyCyan Jul 30, 2025
665eb94
Update type check method to reference the readonly type field
TobyCyan Jul 30, 2025
ce95f7e
Merge branch 'main' into 464-refactor-split-issue-and-pr
damithc Aug 6, 2025
bb7b711
Merge branch '464-refactor-split-issue-and-pr' of https://github.com/…
TobyCyan Aug 8, 2025
6f1dce7
Merge branch 'CATcher-org:main' into main
TobyCyan Aug 12, 2025
5a564ea
Merge branch 'main' of https://github.com/TobyCyan/WATcher
TobyCyan Aug 12, 2025
f44f7db
Merge branch 'main' into 464-refactor-split-issue-and-pr
TobyCyan Aug 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { EventWeek } from '../event-week.model';
import { paginateData } from './event-paginator';

/**
* Adapted from IssuesDataTable for Events.
* Adapted from RepoItemsDataTable for Events.
*/
export class GithubEventDataTable extends DataSource<EventWeek> {
private startDate = new BehaviorSubject('');
Expand Down
6 changes: 3 additions & 3 deletions src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,12 @@ import { ActivityDashboardModule } from './activity-dashboard/activity-dashboard
import { AuthModule } from './auth/auth.module';
import { AuthGuard } from './core/guards/auth.guard';
import { ParseUrlParamsGuard } from './core/guards/parse-url-params.guard';
import { IssuesViewerModule } from './issues-viewer/issues-viewer.module';
import { RepoItemsViewerModule } from './repo-items-viewer/repo-items-viewer.module';

const routes: Routes = [
{ path: '', loadChildren: () => AuthModule },
{ path: 'issuesViewer/:org/:repo', canActivate: [ParseUrlParamsGuard], children: [] },
{ path: 'issuesViewer', loadChildren: () => IssuesViewerModule, canLoad: [AuthGuard] },
{ path: 'repoItemsViewer/:org/:repo', canActivate: [ParseUrlParamsGuard], children: [] },
{ path: 'repoItemsViewer', loadChildren: () => RepoItemsViewerModule, canLoad: [AuthGuard] },
{ path: 'activityDashboard', loadChildren: () => ActivityDashboardModule, canLoad: [AuthGuard] }
];

Expand Down
14 changes: 7 additions & 7 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,16 @@ import { ErrorHandlingService } from './core/services/error-handling.service';
import { ErrorMessageService } from './core/services/error-message.service';
import { AuthServiceFactory } from './core/services/factories/factory.auth.service';
import { GithubServiceFactory } from './core/services/factories/factory.github.service';
import { IssueServiceFactory } from './core/services/factories/factory.issue.service';
import { RepoItemServiceFactory } from './core/services/factories/factory.issue.service';
import { GithubService } from './core/services/github.service';
import { GithubEventService } from './core/services/githubevent.service';
import { IssueService } from './core/services/issue.service';
import { LabelService } from './core/services/label.service';
import { LoggingService } from './core/services/logging.service';
import { RepoItemService } from './core/services/repo-item.service';
import { RepoSessionStorageService } from './core/services/repo-session-storage.service';
import { UserService } from './core/services/user.service';
import { ViewService } from './core/services/view.service';
import { IssuesViewerModule } from './issues-viewer/issues-viewer.module';
import { RepoItemsViewerModule } from './repo-items-viewer/repo-items-viewer.module';
import { LabelDefinitionPopupComponent } from './shared/label-definition-popup/label-definition-popup.component';
import { HeaderComponent } from './shared/layout';
import { RepoChangeFormComponent } from './shared/repo-change-form/repo-change-form.component';
Expand All @@ -42,7 +42,7 @@ import { SharedModule } from './shared/shared.module';
BrowserModule,
BrowserAnimationsModule,
AuthModule,
IssuesViewerModule,
RepoItemsViewerModule,
ActivityDashboardModule,
SharedModule,
HttpClientModule,
Expand All @@ -62,7 +62,7 @@ import { SharedModule } from './shared/shared.module';
NgZone,
GithubService,
UserService,
IssueService,
RepoItemService,
LabelService,
ViewService,
GithubEventService,
Expand All @@ -72,8 +72,8 @@ import { SharedModule } from './shared/shared.module';
]
},
{
provide: IssueService,
useFactory: IssueServiceFactory,
provide: RepoItemService,
useFactory: RepoItemServiceFactory,
deps: [GithubService, UserService, ViewService]
},
{
Expand Down
2 changes: 1 addition & 1 deletion src/app/core/guards/parse-url-params.guard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class ParseUrlParamsGuard implements CanActivate {
/**
* Saves org/repo url parameters to session storage
* Redirects to '/' as login session not persistant
* This keeps /issuesViewer route protected
* This keeps /repoItemsViewer route protected
* @return false
*/
canActivate(route: ActivatedRouteSnapshot, state: RouterStateSnapshot) {
Expand Down
2 changes: 1 addition & 1 deletion src/app/core/models/github-user.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export class GithubUser implements RawGithubUser, Group {
/*
This method is used to enable comparisons between
a Group and the filtering criteria, which is stored
as a string, in IssuesDataTable.ts
as a string, in RepoItemsDataTable.ts
*/
static fromUsername(username: string) {
return new GithubUser({
Expand Down
2 changes: 1 addition & 1 deletion src/app/core/models/github/github-issue.model.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { IssueState, IssueStateReason } from '../../../../../graphql/graphql-types';
import { ReviewDecision } from '../issue.model';
import { PullrequestReviewState } from '../pullrequest-review.model';
import { ReviewDecision } from '../repo-item.model';
import { GithubComment } from './github-comment.model';
import { GithubLabel } from './github-label.model';

Expand Down
122 changes: 5 additions & 117 deletions src/app/core/models/issue.model.ts
Original file line number Diff line number Diff line change
@@ -1,118 +1,14 @@
import * as moment from 'moment';
import { GithubComment } from './github/github-comment.model';
import { GithubIssue } from './github/github-issue.model';
import { GithubLabel } from './github/github-label.model';
import { HiddenData } from './hidden-data.model';
import { Milestone } from './milestone.model';
import { PullrequestReview } from './pullrequest-review.model';

export enum ReviewDecision {
CHANGES_REQUESTED = 'CHANGES_REQUESTED',
APPROVED = 'APPROVED',
REVIEW_REQUIRED = 'REVIEW_REQUIRED'
}

export class Issue {
/** Basic Fields */
readonly globalId: string;
readonly id: number;
readonly created_at: string;
readonly githubIssue: GithubIssue;
githubComments: GithubComment[];
title: string;
description: string;
hiddenDataInDescription: HiddenData;
updated_at: string;
closed_at: string;
milestone: Milestone;
state: string;
stateReason: string;
issueOrPr: string;
author: string;
headRepository: string;
isDraft: boolean;

/** Depending on the view, assignees attribute can be derived from Github's assignee feature OR from the Github's issue description */
assignees?: string[];
labels?: string[];
githubLabels?: GithubLabel[];
reviews?: PullrequestReview[];
reviewDecision?: ReviewDecision | null;

/**
* Formats the text to create space at the end of the user input to prevent any issues with
* the markdown interpretation.
*
* Brought over from comment-editor.component.ts
*/
static formatText(text: string): string {
if (text === null) {
return null;
}

if (text === undefined) {
return undefined;
}

const newLinesRegex = /[\n\r]/gi;
const textSplitArray = text.split(newLinesRegex);
if (textSplitArray.filter((split) => split.trim() !== '').length > 0) {
return `${text}\n\n`;
} else {
return text;
}
}

/**
* Processes and cleans a raw issue description obtained from user input.
*/
static updateDescription(description: string): string {
const defaultString = 'No details provided by bug reporter.';
return Issue.orDefaultString(Issue.formatText(description), defaultString);
}

/**
* Given two strings, returns the first if it is not an empty string or a false value such as null/undefined.
* Returns the second string if the first is an empty string.
*/
private static orDefaultString(stringA: string, def: string): string {
if (!stringA) {
return def;
}
return stringA.length !== 0 ? stringA : def;
}
import { RepoItem } from './repo-item.model';

export class Issue extends RepoItem {
protected constructor(githubIssue: GithubIssue) {
/** Basic Fields */
this.globalId = githubIssue.id;
this.id = +githubIssue.number;
this.created_at = moment(githubIssue.created_at).format('lll');
this.updated_at = moment(githubIssue.updated_at).format('lll');
this.closed_at = moment(githubIssue.closed_at).format('lll');
this.title = githubIssue.title;
this.hiddenDataInDescription = new HiddenData(githubIssue.body);
this.description = Issue.updateDescription(this.hiddenDataInDescription.originalStringWithoutHiddenData);
this.state = githubIssue.state;
this.stateReason = githubIssue.stateReason;
this.issueOrPr = githubIssue.issueOrPr;
this.author = githubIssue.user.login;
// this.githubIssue = githubIssue;
this.isDraft = githubIssue.isDraft;
this.reviewDecision = githubIssue.reviewDecision;

this.assignees = githubIssue.assignees.map((assignee) => assignee.login);
this.githubLabels = githubIssue.labels;
this.labels = githubIssue.labels.map((label) => label.name);
this.milestone = githubIssue.milestone
? new Milestone(githubIssue.milestone)
: this.issueOrPr === 'Issue'
? Milestone.IssueWithoutMilestone
: Milestone.PRWithoutMilestone;
this.headRepository = githubIssue.headRepository?.nameWithOwner;
this.reviews = githubIssue.reviews?.map((review) => new PullrequestReview(review));
super(githubIssue, 'Issue');
this.milestone = githubIssue.milestone ? new Milestone(githubIssue.milestone) : Milestone.IssueWithoutMilestone;
}

public static createPhaseBugReportingIssue(githubIssue: GithubIssue): Issue {
public static createIssue(githubIssue: GithubIssue): Issue {
return new Issue(githubIssue);
}

Expand All @@ -124,11 +20,3 @@ export class Issue {
export interface Issues {
[id: number]: Issue;
}

export const IssuesFilter = {
issuesViewer: {
Student: 'NO_FILTER',
Tutor: 'NO_FILTER',
Admin: 'NO_FILTER'
}
};
2 changes: 1 addition & 1 deletion src/app/core/models/milestone.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class Milestone implements Group {
/*
This method is used to enable comparisons between
a Group and the filtering criteria, which is stored
as a string, in IssuesDataTable.ts
as a string, in RepoItemsDataTable.ts
*/
static fromTitle(title: string): Milestone {
return new Milestone({ title, state: '' });
Expand Down
18 changes: 18 additions & 0 deletions src/app/core/models/pull-request.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { GithubIssue } from './github/github-issue.model';
import { Milestone } from './milestone.model';
import { RepoItem } from './repo-item.model';

export class PullRequest extends RepoItem {
protected constructor(githubIssue: GithubIssue) {
super(githubIssue, 'PullRequest');
this.milestone = githubIssue.milestone ? new Milestone(githubIssue.milestone) : Milestone.PRWithoutMilestone;
}

public static createPullRequest(githubIssue: GithubIssue): RepoItem {
return new PullRequest(githubIssue);
}
}

export interface PullRequests {
[id: number]: PullRequests;
}
125 changes: 125 additions & 0 deletions src/app/core/models/repo-item.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import * as moment from 'moment';
import { GithubComment } from './github/github-comment.model';
import { GithubIssue } from './github/github-issue.model';
import { GithubLabel } from './github/github-label.model';
import { HiddenData } from './hidden-data.model';
import { Milestone } from './milestone.model';
import { PullrequestReview } from './pullrequest-review.model';

export enum ReviewDecision {
CHANGES_REQUESTED = 'CHANGES_REQUESTED',
APPROVED = 'APPROVED',
REVIEW_REQUIRED = 'REVIEW_REQUIRED'
}

export abstract class RepoItem {
/** Basic Fields */
readonly type: string;
readonly globalId: string;
readonly id: number;
readonly created_at: string;
readonly githubIssue: GithubIssue;
githubComments: GithubComment[];
title: string;
description: string;
hiddenDataInDescription: HiddenData;
updated_at: string;
closed_at: string;
milestone: Milestone;
state: string;
stateReason: string;
author: string;
headRepository: string;
isDraft: boolean;

/** Depending on the view, assignees attribute can be derived from Github's assignee feature OR from the Github's issue description */
assignees?: string[];
labels?: string[];
githubLabels?: GithubLabel[];
reviews?: PullrequestReview[];
reviewDecision?: ReviewDecision | null;

/**
* Formats the text to create space at the end of the user input to prevent any issues with
* the markdown interpretation.
*
* Brought over from comment-editor.component.ts
*/
static formatText(text: string): string {
if (text === null) {
return null;
}

if (text === undefined) {
return undefined;
}

const newLinesRegex = /[\n\r]/gi;
const textSplitArray = text.split(newLinesRegex);
if (textSplitArray.filter((split) => split.trim() !== '').length > 0) {
return `${text}\n\n`;
} else {
return text;
}
}

/**
* Processes and cleans a raw issue description obtained from user input.
*/
static updateDescription(description: string): string {
const defaultString = 'No details provided by bug reporter.';
return RepoItem.orDefaultString(RepoItem.formatText(description), defaultString);
}

/**
* Given two strings, returns the first if it is not an empty string or a false value such as null/undefined.
* Returns the second string if the first is an empty string.
*/
private static orDefaultString(stringA: string, def: string): string {
if (!stringA) {
return def;
}
return stringA.length !== 0 ? stringA : def;
}

protected constructor(githubIssue: GithubIssue, type: string = 'RepoItem') {
/** Basic Fields */
this.type = type;
this.globalId = githubIssue.id;
this.id = +githubIssue.number;
this.created_at = moment(githubIssue.created_at).format('lll');
this.updated_at = moment(githubIssue.updated_at).format('lll');
this.closed_at = moment(githubIssue.closed_at).format('lll');
this.title = githubIssue.title;
this.hiddenDataInDescription = new HiddenData(githubIssue.body);
this.description = RepoItem.updateDescription(this.hiddenDataInDescription.originalStringWithoutHiddenData);
this.state = githubIssue.state;
this.stateReason = githubIssue.stateReason;
this.author = githubIssue.user.login;
// this.githubIssue = githubIssue;
this.isDraft = githubIssue.isDraft;
this.reviewDecision = githubIssue.reviewDecision;

this.assignees = githubIssue.assignees.map((assignee) => assignee.login);
this.githubLabels = githubIssue.labels;
this.labels = githubIssue.labels.map((label) => label.name);
this.headRepository = githubIssue.headRepository?.nameWithOwner;
this.reviews = githubIssue.reviews?.map((review) => new PullrequestReview(review));
}

createGithubIssueDescription(): string {
return `${this.description}\n${this.hiddenDataInDescription.toString()}`;
}
}

export interface RepoItems {
[id: number]: RepoItem;
}

export const RepoItemFilter = {
repoItemsViewer: {
Student: 'NO_FILTER',
Tutor: 'NO_FILTER',
Admin: 'NO_FILTER'
}
};
2 changes: 1 addition & 1 deletion src/app/core/models/view.model.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
export enum View {
issuesViewer = 'issuesViewer',
repoItemsViewer = 'repoItemsViewer',
activityDashboard = 'activityDashboard'
}
Loading