Skip to content

Commit

Permalink
Merge branch 'main' into three-state-checkbox
Browse files Browse the repository at this point in the history
  • Loading branch information
nknguyenhc committed Mar 20, 2024
2 parents 4aba7cf + 8817ca3 commit ce6878e
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 39 deletions.
7 changes: 7 additions & 0 deletions src/app/core/models/repo-change-response.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
/**
* Represents the response of the repo-change-form component
*/
export type RepoChangeResponse = {
repo: string;
keepFilters: boolean;
};
40 changes: 28 additions & 12 deletions src/app/core/services/filters.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Injectable } from '@angular/core';
import { Sort } from '@angular/material/sort';
import { BehaviorSubject, pipe } from 'rxjs';
import { SimpleLabel } from '../models/label.model';

export type Filter = {
title: string;
Expand All @@ -9,7 +10,7 @@ export type Filter = {
sort: Sort;
labels: string[];
milestones: string[];
hiddenLabels?: Set<string>;
hiddenLabels: Set<string>;
deselectedLabels: Set<string>;
};

Expand All @@ -20,6 +21,7 @@ export const DEFAULT_FILTER: Filter = {
sort: { active: 'id', direction: 'asc' },
labels: [],
milestones: [],
hiddenLabels: new Set<string>(),
deselectedLabels: new Set<string>()
};

Expand All @@ -40,33 +42,47 @@ export class FiltersService {
}

updateFilters(newFilters: Partial<Filter>): void {
let nextDropdownFilter: Filter = {
let nextFilter: Filter = {
...this.filter$.value,
...newFilters
};

nextDropdownFilter = this._validateFilter(nextDropdownFilter);
nextFilter = this._validateFilter(nextFilter);

this.filter$.next(nextDropdownFilter);
this.filter$.next(nextFilter);
}

sanitizeLabels(allLabels: SimpleLabel[]) {
const allLabelsSet = new Set(allLabels.map((label) => label.name));

const newHiddenLabels: Set<string> = new Set();
for (const hiddenLabel of this.filter$.value.hiddenLabels) {
if (allLabelsSet.has(hiddenLabel)) {
newHiddenLabels.add(hiddenLabel);
}
}

const newLabels = this.filter$.value.labels.filter((label) => allLabelsSet.has(label));

this.updateFilters({ labels: newLabels, hiddenLabels: newHiddenLabels });
}
/**
* Changes type to a valid, default value when an incompatible combination of type and status is encountered.
*/
updateTypePairing(dropdownFilter: Filter): Filter {
if (dropdownFilter.status === 'merged') {
dropdownFilter.type = 'pullrequest';
updateTypePairing(filter: Filter): Filter {
if (filter.status === 'merged') {
filter.type = 'pullrequest';
}
return dropdownFilter;
return filter;
}

/**
* Changes status to a valid, default value when an incompatible combination of type and status is encountered.
*/
updateStatusPairing(dropdownFilter: Filter): Filter {
if (dropdownFilter.status === 'merged' && dropdownFilter.type === 'issue') {
dropdownFilter.status = 'all';
updateStatusPairing(filter: Filter): Filter {
if (filter.status === 'merged' && filter.type === 'issue') {
filter.status = 'all';
}
return dropdownFilter;
return filter;
}
}
7 changes: 6 additions & 1 deletion src/app/shared/filter-bar/filter-bar.component.html
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
<mat-grid-list cols="7" rowHeight="80px">
<mat-grid-tile colspan="3">
<mat-form-field class="search-bar">
<input matInput (keyup)="this.filtersService.updateFilters({ title: $event.target.value })" placeholder="Search" />
<input
matInput
value="{{ this.filtersService.filter$.value.title }}"
(keyup)="this.filtersService.updateFilters({ title: $event.target.value })"
placeholder="Search"
/>
</mat-form-field>
</mat-grid-tile>

Expand Down
20 changes: 11 additions & 9 deletions src/app/shared/filter-bar/filter-bar.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import { LabelFilterBarComponent } from './label-filter-bar/label-filter-bar.com
templateUrl: './filter-bar.component.html',
styleUrls: ['./filter-bar.component.css']
})
export class FilterBarComponent implements OnInit, AfterViewInit, OnDestroy {
export class FilterBarComponent implements OnInit, OnDestroy {
@Input() views$: BehaviorSubject<QueryList<FilterableComponent>>;

repoChangeSubscription: Subscription;
Expand All @@ -38,18 +38,19 @@ export class FilterBarComponent implements OnInit, AfterViewInit, OnDestroy {
private phaseService: PhaseService,
private logger: LoggingService
) {
this.repoChangeSubscription = this.phaseService.repoChanged$.subscribe((newRepo) => this.initialize());
this.repoChangeSubscription = this.phaseService.repoChanged$.subscribe((newRepo) => this.newRepoInitialize());
}

ngOnInit() {
this.initialize();
}
this.newRepoInitialize();

ngAfterViewInit(): void {
this.filtersService.filter$.subscribe((dropdownFilter) => {
this.filter = dropdownFilter;
// One-time initializations
this.filtersService.filter$.subscribe((filter) => {
this.filter = filter;
this.applyFilter();
});

this.views$.subscribe(() => this.applyFilter());
}

ngOnDestroy(): void {
Expand All @@ -73,13 +74,14 @@ export class FilterBarComponent implements OnInit, AfterViewInit, OnDestroy {

/**
* Fetch and initialize all information from repository to populate Issue Dashboard.
* Re-called when repo has changed
*/
private initialize() {
private newRepoInitialize() {
// Fetch milestones and update dropdown filter
this.milestoneSubscription = this.milestoneService.fetchMilestones().subscribe(
(response) => {
this.logger.debug('IssuesViewerComponent: Fetched milestones from Github');
this.milestoneService.milestones.forEach((milestone) => this.filter.milestones.push(milestone.title));
this.filtersService.updateFilters({ milestones: this.milestoneService.milestones.map((milestone) => milestone.title) });
},
(err) => {},
() => {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export class LabelFilterBarComponent implements OnInit, AfterViewInit, OnDestroy
this.labels$ = this.labelService.connect();
this.labels$.subscribe((labels) => {
this.allLabels = labels;
this.filtersService.sanitizeLabels(this.allLabels);
this.selectedLabelNames = new Set<string>(this.filtersService.filter$.value.labels);
this.hiddenLabelNames = this.filtersService.filter$.value.hiddenLabels;
});
});
}
Expand Down
13 changes: 8 additions & 5 deletions src/app/shared/layout/header.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { filter, pairwise, switchMap } from 'rxjs/operators';
import { AppConfig } from '../../../environments/environment';
import { STORAGE_KEYS } from '../../core/constants/storage-keys.constants';
import { Phase } from '../../core/models/phase.model';
import { RepoChangeResponse } from '../../core/models/repo-change-response.model';
import { Repo } from '../../core/models/repo.model';
import { AuthService } from '../../core/services/auth.service';
import { DialogService } from '../../core/services/dialog.service';
Expand Down Expand Up @@ -229,12 +230,14 @@ export class HeaderComponent implements OnInit {
* Change repository viewed on Issue Dashboard, if a valid repository is provided.
* Re-open dialog to prompt for another repository if an invalid one is provided.
*/
changeRepositoryIfValid(repo: Repo, newRepoString: string) {
changeRepositoryIfValid(repo: Repo, newRepoString: string, keepFilters: boolean) {
if (newRepoString === this.currentRepo) {
return;
}

this.filtersService.clearFilters();
if (!keepFilters) {
this.filtersService.clearFilters();
}

this.phaseService
.changeRepositoryIfValid(repo)
Expand All @@ -251,14 +254,14 @@ export class HeaderComponent implements OnInit {
openChangeRepoDialog() {
const dialogRef = this.dialogService.openChangeRepoDialog(this.currentRepo);

dialogRef.afterClosed().subscribe((res) => {
dialogRef.afterClosed().subscribe((res: RepoChangeResponse | null) => {
if (!res) {
return;
}
const newRepo = Repo.of(res);
const newRepo = Repo.of(res.repo);

if (this.phaseService.isRepoSet()) {
this.changeRepositoryIfValid(newRepo, newRepo.toString());
this.changeRepositoryIfValid(newRepo, newRepo.toString(), res.keepFilters);
} else {
/**
* From session-selection.component.ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,8 @@
.mat-dialog-actions {
justify-content: flex-end;
}

.change-repo-form-header {
display: flex;
justify-content: space-between;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
<h1 mat-dialog-title class="change-repo-form-title">{{ data.repoName ? 'Change repository' : 'Select repository' }}</h1>
<div class="change-repo-form-header">
<h1 mat-dialog-title class="change-repo-form-title">{{ data.repoName ? 'Change repository' : 'Select repository' }}</h1>
<mat-checkbox *ngIf="data.repoName" [(ngModel)]="this.keepFilters">Keep Filters</mat-checkbox>
</div>
<div mat-dialog-content>
<form (ngSubmit)="onYesClick()">
<mat-form-field appearance="fill">
Expand Down
12 changes: 9 additions & 3 deletions src/app/shared/repo-change-form/repo-change-form.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Component, Inject, OnInit } from '@angular/core';
import { FormControl } from '@angular/forms';
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
import { Observable } from 'rxjs';
import { RepoChangeResponse } from '../../core/models/repo-change-response.model';
import { RepoUrlCacheService } from '../../core/services/repo-url-cache.service';

@Component({
Expand All @@ -10,7 +11,8 @@ import { RepoUrlCacheService } from '../../core/services/repo-url-cache.service'
styleUrls: ['./repo-change-form.component.css']
})
export class RepoChangeFormComponent implements OnInit {
public repoName: String;
public repoName: string;
public keepFilters: boolean;
filteredSuggestions: Observable<string[]>;
repoChangeForm = new FormControl();

Expand All @@ -31,10 +33,14 @@ export class RepoChangeFormComponent implements OnInit {
}

onYesClick(): void {
this.dialogRef.close(this.repoName);
const response: RepoChangeResponse = {
repo: this.repoName,
keepFilters: this.keepFilters
};
this.dialogRef.close(response);
}

onNoClick(): void {
this.dialogRef.close(false);
this.dialogRef.close(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ describe('LabelFilterBarComponent', () => {
beforeEach(async () => {
labelServiceSpy = jasmine.createSpyObj('LabelService', ['connect', 'startPollLabels', 'fetchLabels']);
loggingServiceSpy = jasmine.createSpyObj('LoggingService', ['info', 'debug']);
filtersServiceSpy = jasmine.createSpyObj('FiltersService', ['updateFilters']);
filtersServiceSpy = jasmine.createSpyObj('FiltersService', ['updateFilters', 'sanitizeLabels']);

TestBed.configureTestingModule({
providers: [
Expand Down Expand Up @@ -49,14 +49,15 @@ describe('LabelFilterBarComponent', () => {
labelsSubject = new BehaviorSubject<SimpleLabel[]>([]);
labelServiceSpy.fetchLabels.and.returnValue(of([]));
labelServiceSpy.connect.and.returnValue(labelsSubject.asObservable());
filtersServiceSpy.sanitizeLabels.and.callThrough();
});

it('should update allLabels with latest emmitted value after ngAfterViewInit', fakeAsync(() => {
component.ngAfterViewInit();
labelsSubject.next(SEVERITY_SIMPLE_LABELS);
tick();
expect(component.allLabels).toEqual(SEVERITY_SIMPLE_LABELS);
}));
// it('should update allLabels with latest emmitted value after ngAfterViewInit', fakeAsync(() => {
// component.ngAfterViewInit();
// tick();
// labelsSubject.next(SEVERITY_SIMPLE_LABELS);
// expect(component.allLabels).toEqual(SEVERITY_SIMPLE_LABELS);
// }));
});

describe('hide(label)', () => {
Expand Down Expand Up @@ -120,7 +121,7 @@ describe('LabelFilterBarComponent', () => {
const selectedLabels = [LABEL_NAME_SEVERITY_HIGH, LABEL_NAME_SEVERITY_LOW];
component.selectedLabelNames = new Set<string>(selectedLabels);

component.updateSelection();
component.updateSelection([]);

expect(filtersServiceSpy.updateFilters).toHaveBeenCalledWith({ labels: selectedLabels, deselectedLabels: new Set<string>() });
});
Expand Down

0 comments on commit ce6878e

Please sign in to comment.