Skip to content

Commit

Permalink
Add filters to url (#314)
Browse files Browse the repository at this point in the history
Filters cannot be shared to among users.

Users might want to share their current view
to others, consisting of their current filters.

Let's pull and store filters in the URL to allow
sharing of filter combinations.
  • Loading branch information
Arif-Khalid authored Mar 30, 2024
1 parent 3f0aa1f commit fa69625
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 11 deletions.
67 changes: 63 additions & 4 deletions src/app/core/services/filters.service.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { Injectable } from '@angular/core';
import { Sort } from '@angular/material/sort';
import { ActivatedRoute, Router } from '@angular/router';
import { BehaviorSubject, pipe } from 'rxjs';
import { SimpleLabel } from '../models/label.model';
import { Milestone } from '../models/milestone.model';
import { LoggingService } from './logging.service';
import { MilestoneService } from './milestone.service';

export type Filter = {
Expand All @@ -24,6 +26,7 @@ export type Filter = {
* Filters are subscribed to and emitted from this service
*/
export class FiltersService {
public static readonly PRESET_VIEW_QUERY_PARAM_KEY = 'presetview';
readonly presetViews: {
[key: string]: () => Filter;
} = {
Expand Down Expand Up @@ -61,21 +64,76 @@ export class FiltersService {
// Helps in determining whether all milestones were selected from previous repo during sanitization of milestones
private previousMilestonesLength = 0;

constructor(private milestoneService: MilestoneService) {}
constructor(
private logger: LoggingService,
private router: Router,
private route: ActivatedRoute,
private milestoneService: MilestoneService
) {}

private pushFiltersToUrl(): void {
const queryParams = {};
for (const filterName of Object.keys(this.filter$.value)) {
if (this.filter$.value[filterName] instanceof Set) {
queryParams[filterName] = JSON.stringify([...this.filter$.value[filterName]]);
} else {
queryParams[filterName] = JSON.stringify(this.filter$.value[filterName]);
}
}
queryParams[FiltersService.PRESET_VIEW_QUERY_PARAM_KEY] = this.presetView$.value;

this.router.navigate([], {
relativeTo: this.route,
queryParams,
queryParamsHandling: 'merge',
replaceUrl: true
});
}

clearFilters(): void {
this.filter$.next(this.defaultFilter());
this.presetView$.next('currentlyActive');
this.updatePresetView('currentlyActive');
this.previousMilestonesLength = 0;
}

initializeFromURLParams() {
const nextFilter: Filter = this.defaultFilter();
const queryParams = this.route.snapshot.queryParamMap;
try {
const presetView = queryParams.get(FiltersService.PRESET_VIEW_QUERY_PARAM_KEY);

// Use preset view if set in url
if (presetView && this.presetViews.hasOwnProperty(presetView) && presetView !== 'custom') {
this.updatePresetView(presetView);
return;
}

for (const filterName of Object.keys(nextFilter)) {
const stringifiedFilterData = queryParams.get(filterName);
if (!stringifiedFilterData) {
continue;
}
const filterData = JSON.parse(stringifiedFilterData);

if (nextFilter[filterName] instanceof Set) {
nextFilter[filterName] = new Set(filterData);
} else {
nextFilter[filterName] = filterData;
}
}
this.updateFilters(nextFilter);
} catch (err) {
this.logger.info(`FiltersService: Update filters from URL failed with an error: ${err}`);
}
}

updateFilters(newFilters: Partial<Filter>): void {
const nextDropdownFilter: Filter = {
...this.filter$.value,
...newFilters
};
this.filter$.next(nextDropdownFilter);
this.updatePresetViewFromFilters(newFilters);
this.pushFiltersToUrl();
}

/**
Expand Down Expand Up @@ -109,9 +167,10 @@ export class FiltersService {
updatePresetView(presetViewName: string) {
this.filter$.next(this.presetViews[presetViewName]());
this.presetView$.next(presetViewName);
this.pushFiltersToUrl();
}

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

const newHiddenLabels: Set<string> = new Set();
Expand Down
4 changes: 2 additions & 2 deletions src/app/core/services/label.service.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Injectable } from '@angular/core';
import { BehaviorSubject, EMPTY, Observable, of, Subscription, timer } from 'rxjs';
import { BehaviorSubject, EMPTY, Observable, of, Subject, Subscription, timer } from 'rxjs';
import { catchError, exhaustMap, finalize, map } from 'rxjs/operators';
import { Label, SimpleLabel } from '../models/label.model';
import { GithubService } from './github.service';
Expand Down Expand Up @@ -28,7 +28,7 @@ export class LabelService {
simpleLabels: SimpleLabel[];

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

constructor(private githubService: GithubService) {}

Expand Down
6 changes: 5 additions & 1 deletion src/app/issues-viewer/issues-viewer.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { filter } from 'rxjs/operators';
import { Group } from '../core/models/github/group.interface';
import { Repo } from '../core/models/repo.model';
import { ErrorMessageService } from '../core/services/error-message.service';
import { FiltersService } from '../core/services/filters.service';
import { GithubService } from '../core/services/github.service';
import { GroupingContextService } from '../core/services/grouping/grouping-context.service';
import { IssueService } from '../core/services/issue.service';
Expand Down Expand Up @@ -53,7 +54,8 @@ export class IssuesViewerComponent implements OnInit, AfterViewInit, OnDestroy {
public labelService: LabelService,
public milestoneService: MilestoneService,
public groupingContextService: GroupingContextService,
private router: Router
private router: Router,
private filtersService: FiltersService
) {
this.repoChangeSubscription = this.viewService.repoChanged$.subscribe((newRepo) => {
this.issueService.reset(false);
Expand All @@ -75,13 +77,15 @@ export class IssuesViewerComponent implements OnInit, AfterViewInit, OnDestroy {
if (event instanceof NavigationEnd && event.id === this.popStateNavigationId) {
this.viewService.initializeRepoFromUrlParams();
this.groupingContextService.initializeFromUrlParams();
this.filtersService.initializeFromURLParams();
}
});
}

ngOnInit() {
this.initialize();
this.groupingContextService.initializeFromUrlParams();
this.filtersService.initializeFromURLParams();
}

ngAfterViewInit(): void {
Expand Down
5 changes: 1 addition & 4 deletions src/app/shared/layout/header.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,17 +249,14 @@ export class HeaderComponent implements OnInit {
return;
}

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

this.viewService
.changeRepositoryIfValid(repo)
.then(() => {
this.auth.setTitleWithViewDetail();
this.currentRepo = newRepoString;
if (!keepFilters) {
this.groupingContextService.reset();
this.filtersService.clearFilters();
}
})
.catch((error) => {
Expand Down

0 comments on commit fa69625

Please sign in to comment.