Skip to content

Commit 7ced8ae

Browse files
authored
Refactor issue actions and conditions for clarity and maintainability
The deleteIssue and undeleteIssue methods share highly similar logic that performs the same actions in opposite directions. This causes code duplication and makes future maintenance more error-prone. Let's, * extract the common logic into a reusable function to eliminate duplication and improve clarity. * merge the two separate state-tracking lists — issuesPendingDeletion and issuesPendingRestore into a single list issuesPendingAction. * move complex conditional logic in issue-tables.component.html into issue-tables.component.ts. This makes the template easier to read, improves maintainability, and reduce duplicated code for the same actions in reverse.
1 parent 34665e9 commit 7ced8ae

File tree

2 files changed

+68
-59
lines changed

2 files changed

+68
-59
lines changed

src/app/shared/issue-tables/issue-tables.component.html

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,7 @@
207207
<mat-icon>open_in_new</mat-icon>
208208
</button>
209209
<button
210-
*ngIf="this.isResponseEditable() && !issue.status && this.isActionVisible(action_buttons.RESPOND_TO_ISSUE); else tryEditIssue"
210+
*ngIf="shouldEnableRespondToIssue(issue); else tryEditIssue"
211211
[routerLink]="'issues/' + issue.id"
212212
mat-button
213213
color="accent"
@@ -219,7 +219,7 @@
219219
</button>
220220
<ng-template #tryEditIssue>
221221
<button
222-
*ngIf="permissions.isIssueEditable() && this.isActionVisible(action_buttons.FIX_ISSUE)"
222+
*ngIf="shouldEnableEditIssue()"
223223
mat-button
224224
color="accent"
225225
style="transform: scale(0.8)"
@@ -230,7 +230,7 @@
230230
</button>
231231
</ng-template>
232232
<button
233-
*ngIf="this.isResponseEditable() && issue.status && this.isActionVisible(action_buttons.MARK_AS_RESPONDED)"
233+
*ngIf="shouldEnableMarkAsResponded(issue)"
234234
mat-button
235235
color="primary"
236236
(click)="markAsResponded(issue, $event)"
@@ -246,19 +246,16 @@
246246
mat-button
247247
(click)="markAsPending(issue, $event)"
248248
style="transform: scale(0.8)"
249-
*ngIf="
250-
(userService.currentUser.role === 'Student' || userService.currentUser.role === 'Admin') &&
251-
this.isActionVisible(action_buttons.MARK_AS_PENDING)
252-
"
249+
*ngIf="shouldEnablePendingButton()"
253250
data-testid="mark_pending_button"
254251
>
255252
<mat-icon>cancel</mat-icon>
256253
</button>
257254
<button
258255
mat-button
259256
color="warn"
260-
*ngIf="permissions.isIssueDeletable() && !issuesPendingDeletion[issue.id] && this.isActionVisible(action_buttons.DELETE_ISSUE)"
261-
(click)="deleteIssue(issue.id, $event); $event.stopPropagation()"
257+
*ngIf="isActionPerformAllowed(true, issue.id)"
258+
(click)="deleteOrRestoreIssue(true, issue.id, $event)"
262259
matTooltip="Delete this issue"
263260
style="transform: scale(0.8)"
264261
data-testid="delete_issue_button"
@@ -268,8 +265,8 @@
268265
<button
269266
mat-button
270267
color="primary"
271-
*ngIf="permissions.isIssueRestorable() && !issuesPendingRestore[issue.id] && this.isActionVisible(action_buttons.RESTORE_ISSUE)"
272-
(click)="undeleteIssue(issue.id, $event); $event.stopPropagation()"
268+
*ngIf="isActionPerformAllowed(false, issue.id)"
269+
(click)="deleteOrRestoreIssue(false, issue.id, $event)"
273270
matTooltip="Restore this issue"
274271
style="transform: scale(0.8)"
275272
>
@@ -279,10 +276,7 @@
279276
color="warn"
280277
[diameter]="25"
281278
style="display: inline; padding-right: 30px; margin-left: 5px"
282-
*ngIf="
283-
(issuesPendingDeletion[issue.id] && this.isActionVisible(action_buttons.DELETE_ISSUE)) ||
284-
(issuesPendingRestore[issue.id] && this.isActionVisible(action_buttons.RESTORE_ISSUE))
285-
"
279+
*ngIf="shouldEnablePendingActionSpinner(issue.id)"
286280
></mat-spinner>
287281
</mat-cell>
288282
</ng-container>

src/app/shared/issue-tables/issue-tables.component.ts

Lines changed: 59 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { PhaseService } from '../../core/services/phase.service';
1616
import { UserService } from '../../core/services/user.service';
1717
import { UndoActionComponent } from '../../shared/action-toasters/undo-action/undo-action.component';
1818
import { IssuesDataTable } from './IssuesDataTable';
19+
import { Observable } from 'rxjs';
1920

2021
export enum ACTION_BUTTONS {
2122
VIEW_IN_WEB,
@@ -44,8 +45,7 @@ export class IssueTablesComponent implements OnInit, AfterViewInit {
4445
@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
4546

4647
issues: IssuesDataTable;
47-
issuesPendingDeletion: { [id: number]: boolean };
48-
issuesPendingRestore: { [id: number]: boolean };
48+
issuesPendingAction: { [id: number]: boolean };
4949

5050
public tableSettings: TableSettings;
5151

@@ -66,8 +66,7 @@ export class IssueTablesComponent implements OnInit, AfterViewInit {
6666

6767
ngOnInit() {
6868
this.issues = new IssuesDataTable(this.issueService, this.sort, this.paginator, this.headers, this.filters);
69-
this.issuesPendingDeletion = {};
70-
this.issuesPendingRestore = {};
69+
this.issuesPendingAction = {};
7170
this.tableSettings = this.issueTableSettingsService.getTableSettings(this.table_name);
7271
}
7372

@@ -158,69 +157,85 @@ export class IssueTablesComponent implements OnInit, AfterViewInit {
158157
this.githubService.viewIssueInBrowser(id, event);
159158
}
160159

161-
private handleIssueDeletionSuccess(id: number, event: Event, actionUndoable: boolean) {
162-
if (!actionUndoable) {
163-
return;
164-
}
165-
let snackBarRef = null;
166-
snackBarRef = this.snackBar.openFromComponent(UndoActionComponent, {
167-
data: { message: `Deleted issue ${id}` },
168-
duration: this.snackBarAutoCloseTime
169-
});
170-
snackBarRef.onAction().subscribe(() => {
171-
this.undeleteIssue(id, event, false);
172-
});
173-
}
160+
deleteOrRestoreIssue(isDeleteAction: boolean, id: number, event: Event, actionUndoable: boolean = true) {
161+
const deletingKeyword = 'Deleting';
162+
const undeletingKeyword = 'Undeleting';
163+
this.logger.info(`IssueTablesComponent: ${isDeleteAction ? deletingKeyword : undeletingKeyword} Issue ${id}`);
174164

175-
deleteIssue(id: number, event: Event, actionUndoable: boolean = true) {
176-
this.logger.info(`IssueTablesComponent: Deleting Issue ${id}`);
165+
this.issuesPendingAction = { ...this.issuesPendingAction, [id]: true };
177166

178-
this.issuesPendingDeletion = { ...this.issuesPendingDeletion, [id]: true };
179-
this.issueService
180-
.deleteIssue(id)
167+
let observableActionedIssue: Observable<Issue>;
168+
if (isDeleteAction) {
169+
observableActionedIssue = this.issueService.deleteIssue(id);
170+
} else {
171+
observableActionedIssue = this.issueService.undeleteIssue(id);
172+
}
173+
observableActionedIssue
181174
.pipe(
182175
finalize(() => {
183-
const { [id]: issueRemoved, ...theRest } = this.issuesPendingDeletion;
184-
this.issuesPendingDeletion = theRest;
176+
const { [id]: issueDeletedOrRestored, ...theRest } = this.issuesPendingAction;
177+
this.issuesPendingAction = theRest;
185178
})
186179
)
187180
.subscribe(
188-
(removedIssue) => this.handleIssueDeletionSuccess(id, event, actionUndoable),
181+
() => this.handleIssueActionPerformedSuccess(isDeleteAction, id, event, actionUndoable),
189182
(error) => this.errorHandlingService.handleError(error)
190183
);
191184
event.stopPropagation();
192185
}
193186

194-
private handleIssueRestorationSuccess(id: number, event: Event, actionUndoable: boolean) {
187+
private handleIssueActionPerformedSuccess(isDeleteAction: boolean, id: number, event: Event, actionUndoable: boolean) {
188+
const deletedKeyword = 'Deleted';
189+
const restoredKeyword = 'Restored';
195190
if (!actionUndoable) {
196191
return;
197192
}
198193
let snackBarRef = null;
199194
snackBarRef = this.snackBar.openFromComponent(UndoActionComponent, {
200-
data: { message: `Restored issue ${id}` },
195+
data: { message: `${isDeleteAction ? deletedKeyword : restoredKeyword} issue ${id}` },
201196
duration: this.snackBarAutoCloseTime
202197
});
203198
snackBarRef.onAction().subscribe(() => {
204-
this.deleteIssue(id, event, false);
199+
this.deleteOrRestoreIssue(!isDeleteAction, id, event, false);
205200
});
206201
}
207202

208-
undeleteIssue(id: number, event: Event, actionUndoable: boolean = true) {
209-
this.logger.info(`IssueTablesComponent: Undeleting Issue ${id}`);
203+
isActionPerformAllowed(isDeleteAction: boolean, id: number) {
204+
const actionButton = isDeleteAction ? this.action_buttons.DELETE_ISSUE : this.action_buttons.RESTORE_ISSUE;
205+
const isPermissionGranted = this.isIssueActionPermitted(isDeleteAction);
206+
return isPermissionGranted && !this.issuesPendingAction[id] && this.isActionVisible(actionButton);
207+
}
210208

211-
this.issuesPendingRestore = { ...this.issuesPendingRestore, [id]: true };
212-
this.issueService
213-
.undeleteIssue(id)
214-
.pipe(
215-
finalize(() => {
216-
const { [id]: issueRestored, ...theRest } = this.issuesPendingRestore;
217-
this.issuesPendingRestore = theRest;
218-
})
219-
)
220-
.subscribe(
221-
(restoredIssue) => this.handleIssueRestorationSuccess(id, event, actionUndoable),
222-
(error) => this.errorHandlingService.handleError(error)
223-
);
224-
event.stopPropagation();
209+
private isIssueActionPermitted(isDeleteAction: boolean) {
210+
if (isDeleteAction) {
211+
return this.permissions.isIssueDeletable();
212+
}
213+
return this.permissions.isIssueRestorable();
214+
}
215+
216+
shouldEnablePendingButton() {
217+
return (
218+
(this.userService.currentUser.role === 'Student' || this.userService.currentUser.role === 'Admin') &&
219+
this.isActionVisible(this.action_buttons.MARK_AS_PENDING)
220+
);
221+
}
222+
223+
shouldEnablePendingActionSpinner(id: number) {
224+
return (
225+
this.issuesPendingAction[id] &&
226+
(this.isActionVisible(this.action_buttons.DELETE_ISSUE) || this.isActionVisible(this.action_buttons.RESTORE_ISSUE))
227+
);
228+
}
229+
230+
shouldEnableRespondToIssue(issue: Issue) {
231+
return this.isResponseEditable() && !issue.status && this.isActionVisible(this.action_buttons.RESPOND_TO_ISSUE);
232+
}
233+
234+
shouldEnableMarkAsResponded(issue: Issue) {
235+
return this.isResponseEditable() && issue.status && this.isActionVisible(this.action_buttons.MARK_AS_RESPONDED);
236+
}
237+
238+
shouldEnableEditIssue() {
239+
return this.permissions.isIssueEditable() && this.isActionVisible(this.action_buttons.FIX_ISSUE);
225240
}
226241
}

0 commit comments

Comments
 (0)