Skip to content

Commit

Permalink
Update labels without refresh (#165)
Browse files Browse the repository at this point in the history
Update labels without needing to refresh

Labels are only fetched on initialization, requiring users to refresh
in order for labels to update.

Let's change it so labels are updated periodically, and when the Sync
button is clicked.
  • Loading branch information
chia-yh authored Aug 15, 2023
1 parent 6978f23 commit d2d5370
Show file tree
Hide file tree
Showing 10 changed files with 122 additions and 42 deletions.
13 changes: 12 additions & 1 deletion src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { IssueServiceFactory } from './core/services/factories/factory.issue.ser
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 { PhaseService } from './core/services/phase.service';
import { SessionFixConfirmationComponent } from './core/services/session-fix-confirmation/session-fix-confirmation.component';
Expand Down Expand Up @@ -64,7 +65,17 @@ import { SharedModule } from './shared/shared.module';
{
provide: AuthService,
useFactory: AuthServiceFactory,
deps: [Router, NgZone, GithubService, UserService, IssueService, PhaseService, GithubEventService, Title, LoggingService]
deps: [
Router,
NgZone,
GithubService,
UserService,
IssueService,
LabelService,
PhaseService,
GithubEventService,
Title,
LoggingService]
},
{
provide: IssueService,
Expand Down
21 changes: 11 additions & 10 deletions src/app/core/models/label.model.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,30 @@
/**
* Represents a label and its attributes.
*/
export class Label {
export class Label implements SimpleLabel {
readonly category: string;
readonly name: string;
readonly formattedName: string; // 'category'.'name' (e.g. severity.Low) if a category exists or 'name' if the category does not exist.
color: string;
definition?: string;

constructor(label: { name: string; color: string; definition?: string }) {
const containsDotRegex = /\.\b/g; // contains dot in middle of name
[this.category, this.name] = containsDotRegex.test(label.name) ? label.name.split('.') : [undefined, label.name];
this.formattedName = this.category === undefined || this.category === '' ? this.name : this.category.concat('.', this.name);
this.color = label.color;
this.definition = label.definition;
}

/**
* Returns the name of the label with the format of
* 'category'.'name' (e.g. severity.Low) if a category exists or
* 'name' if the category does not exist.
*/
public getFormattedName(): string {
return this.category === undefined || this.category === '' ? this.name : this.category.concat('.', this.name);
}

public equals(label: Label) {
return this.name === label.name && this.category === label.category;
}
}

/**
* Represents a simplified label with name and color
*/
export type SimpleLabel = {
formattedName: string;
color: string;
};
3 changes: 3 additions & 0 deletions src/app/core/services/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { uuid } from '../../shared/lib/uuid';
import { GithubService } from './github.service';
import { GithubEventService } from './githubevent.service';
import { IssueService } from './issue.service';
import { LabelService } from './label.service';
import { LoggingService } from './logging.service';
import { PhaseService } from './phase.service';
import { UserService } from './user.service';
Expand Down Expand Up @@ -42,6 +43,7 @@ export class AuthService {
private githubService: GithubService,
private userService: UserService,
private issueService: IssueService,
private labelService: LabelService,
private phaseService: PhaseService,
private githubEventService: GithubEventService,
private titleService: Title,
Expand All @@ -67,6 +69,7 @@ export class AuthService {
this.githubService.reset();
this.userService.reset();
this.issueService.reset(true);
this.labelService.reset();
this.phaseService.reset();
this.githubEventService.reset();
this.logger.reset();
Expand Down
15 changes: 14 additions & 1 deletion src/app/core/services/factories/factory.auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { AuthService } from '../auth.service';
import { GithubService } from '../github.service';
import { GithubEventService } from '../githubevent.service';
import { IssueService } from '../issue.service';
import { LabelService } from '../label.service';
import { LoggingService } from '../logging.service';
// import { MockAuthService } from '../mocks/mock.auth.service';
import { PhaseService } from '../phase.service';
Expand All @@ -17,6 +18,7 @@ export function AuthServiceFactory(
githubService: GithubService,
userService: UserService,
issueService: IssueService,
labelService: LabelService,
phaseService: PhaseService,
githubEventService: GithubEventService,
titleService: Title,
Expand All @@ -30,12 +32,23 @@ export function AuthServiceFactory(
// githubService,
// userService,
// issueService,
// labelService,
// phaseService,
// githubEventService,
// titleService,
// logger
// );
// }
console.log(logger);
return new AuthService(router, ngZone, githubService, userService, issueService, phaseService, githubEventService, titleService, logger);
return new AuthService(
router,
ngZone,
githubService,
userService,
issueService,
labelService,
phaseService,
githubEventService,
titleService,
logger);
}
50 changes: 47 additions & 3 deletions src/app/core/services/label.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Injectable } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { Label } from '../models/label.model';
import { BehaviorSubject, EMPTY, Observable, of, Subscription, timer } from 'rxjs';
import { catchError, exhaustMap, finalize, map } from 'rxjs/operators';
import { Label, SimpleLabel } from '../models/label.model';
import { GithubService } from './github.service';

/* The threshold to decide if color is dark or light.
Expand All @@ -22,17 +22,55 @@ const COLOR_WHITE = 'ffffff'; // Light color for text with dark background
* from the GitHub repository for the WATcher application.
*/
export class LabelService {
static readonly POLL_INTERVAL = 5000; // 5 seconds

labels: Label[];
simpleLabels: SimpleLabel[];

private labelsPollSubscription: Subscription;
private labelsSubject = new BehaviorSubject<SimpleLabel[]>([]);

constructor(private githubService: GithubService) {}

startPollLabels() {
if (this.labelsPollSubscription) {
return;
}
this.labelsPollSubscription = timer(0, LabelService.POLL_INTERVAL)
.pipe(
exhaustMap(() => {
return this.fetchLabels().pipe(
catchError(() => {
return EMPTY;
})
);
})
)
.subscribe(() => {
this.labelsSubject.next(this.simpleLabels);
});
}

stopPollLabels() {
if (this.labelsPollSubscription) {
this.labelsPollSubscription.unsubscribe();
this.labelsPollSubscription = undefined;
}
}

connect(): Observable<SimpleLabel[]> {
return this.labelsSubject.asObservable();
}

/**
* Fetch labels from Github.
*/
public fetchLabels(): Observable<any> {
return this.githubService.fetchAllLabels().pipe(
map((response) => {
this.labels = this.parseLabelData(response);
this.simpleLabels = this.labels;
this.labelsSubject.next(this.simpleLabels);
return response;
})
);
Expand Down Expand Up @@ -92,4 +130,10 @@ export class LabelService {

return styles;
}

reset() {
this.labels = undefined;
this.simpleLabels = undefined;
this.stopPollLabels();
}
}
3 changes: 3 additions & 0 deletions src/app/issues-viewer/issues-viewer.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { GithubUser } from '../core/models/github-user.model';
import { Repo } from '../core/models/repo.model';
import { GithubService } from '../core/services/github.service';
import { IssueService } from '../core/services/issue.service';
import { LabelService } from '../core/services/label.service';
import { MilestoneService } from '../core/services/milestone.service';
import { PhaseService } from '../core/services/phase.service';
import { TABLE_COLUMNS } from '../shared/issue-tables/issue-tables-columns';
Expand Down Expand Up @@ -34,10 +35,12 @@ export class IssuesViewerComponent implements OnInit, AfterViewInit, OnDestroy {
public phaseService: PhaseService,
public githubService: GithubService,
public issueService: IssueService,
public labelService: LabelService,
public milestoneService: MilestoneService
) {
this.repoChangeSubscription = this.phaseService.repoChanged$.subscribe((newRepo) => {
this.issueService.reset(false);
this.labelService.reset();
this.initialize();
});
}
Expand Down
3 changes: 0 additions & 3 deletions src/app/shared/filter-bar/filter-bar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,6 @@ export class FilterBarComponent implements OnInit, AfterViewInit, OnDestroy {
* Fetch and initialize all information from repository to populate Issue Dashboard.
*/
private initialize() {
// Fetch labels
this.labelFilterBar.load();

// Fetch milestones and update dropdown filter
this.milestoneSubscription = this.milestoneService.fetchMilestones().subscribe(
(response) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,13 @@
<button mat-button (click)="removeAllSelection()">Remove all</button>

<div *ngIf="!hasLabels(input.value)" class="no-labels">No Labels Found!</div>

<div class="scroll-container-wrapper">
<div class="scroll-container">
<mat-selection-list [(ngModel)]="selectedLabelNames" (selectionChange)="updateSelection()">
<mat-list-option
#option
*ngFor="let label of allLabels"
*ngFor="let label of this.labels$ | async;"
[value]="label.name"
[selected]="selectedLabelNames.includes(label.name)"
class="list-option"
Expand All @@ -39,7 +40,7 @@
[disabled]="hiddenLabelNames.has(label.name)"
(click)="simulateClick(option); $event.stopPropagation()"
>
{{ label.name }}
{{ label.formattedName }}
</mat-chip>
</div>
</mat-list-option>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
import { Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { AfterViewInit, Component, Input, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { MatListOption, MatSelectionList } from '@angular/material/list';
import { BehaviorSubject, Subscription } from 'rxjs';
import { BehaviorSubject, Observable, Subscription } from 'rxjs';
import { SimpleLabel } from '../../../core/models/label.model';
import { LabelService } from '../../../core/services/label.service';
import { LoggingService } from '../../../core/services/logging.service';

export type simplifiedLabel = {
name: string;
color: string;
};

@Component({
selector: 'app-label-filter-bar',
templateUrl: './label-filter-bar.component.html',
styleUrls: ['./label-filter-bar.component.css']
})
export class LabelFilterBarComponent implements OnInit, OnDestroy {
export class LabelFilterBarComponent implements OnInit, AfterViewInit, OnDestroy {
@Input() selectedLabels: BehaviorSubject<string[]>;
@Input() hiddenLabels: BehaviorSubject<Set<string>>;
@ViewChild(MatSelectionList) matSelectionList;

allLabels: simplifiedLabel[];
labels$: Observable<SimpleLabel[]>;
allLabels: SimpleLabel[];
selectedLabelNames: string[] = [];
hiddenLabelNames: Set<string> = new Set();
loaded = false;
Expand All @@ -30,7 +27,16 @@ export class LabelFilterBarComponent implements OnInit, OnDestroy {

ngOnInit() {
this.loaded = false;
this.load();
}

ngAfterViewInit(): void {
setTimeout(() => {
this.load();
this.labels$ = this.labelService.connect();
this.labels$.subscribe((labels) => {
this.allLabels = labels;
});
});
}

ngOnDestroy(): void {
Expand Down Expand Up @@ -70,27 +76,18 @@ export class LabelFilterBarComponent implements OnInit, OnDestroy {

/** loads in the labels in the repository */
public load() {
this.labelService.startPollLabels();
this.labelSubscription = this.labelService.fetchLabels().subscribe(
(response) => {
this.logger.debug('LabelFilterBarComponent: Fetched labels from Github');
},
(err) => {},
() => {
this.initialize();
this.loaded = true;
}
);
}

private initialize() {
this.allLabels = this.labelService.labels.map((label) => {
return {
name: label.getFormattedName(),
color: label.color
};
});
this.loaded = true;
}

filter(filter: string, target: string): boolean {
return !target.toLowerCase().includes(filter.toLowerCase());
}
Expand All @@ -99,7 +96,7 @@ export class LabelFilterBarComponent implements OnInit, OnDestroy {
if (this.allLabels === undefined || this.allLabels.length === 0) {
return false;
}
return this.allLabels.some((label) => !this.filter(filter, label.name));
return this.allLabels.some((label) => !this.filter(filter, label.formattedName));
}

updateSelection(): void {
Expand Down
10 changes: 10 additions & 0 deletions src/app/shared/layout/header.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ErrorHandlingService } from '../../core/services/error-handling.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 { PhaseDescription, PhaseService } from '../../core/services/phase.service';
import { UserService } from '../../core/services/user.service';
Expand Down Expand Up @@ -49,6 +50,7 @@ export class HeaderComponent implements OnInit {
private location: Location,
private githubEventService: GithubEventService,
private issueService: IssueService,
private labelService: LabelService,
private errorHandlingService: ErrorHandlingService,
private githubService: GithubService,
private dialogService: DialogService
Expand Down Expand Up @@ -90,6 +92,7 @@ export class HeaderComponent implements OnInit {
// Remove current phase issues and load selected phase issues.
this.githubService.reset();
this.issueService.reset(false);
this.labelService.reset();
this.reload();

// Route app to new phase.
Expand Down Expand Up @@ -160,6 +163,13 @@ export class HeaderComponent implements OnInit {
}
);

this.labelService.fetchLabels().subscribe(
(success) => success,
(error) => {
this.errorHandlingService.handleError(error, () => this.labelService.fetchLabels());
}
);

// Prevent user from spamming the reload button
setTimeout(() => {
this.isReloadButtonDisabled = false;
Expand Down

0 comments on commit d2d5370

Please sign in to comment.