diff --git a/package.json b/package.json index cab22df9..2276f490 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "WATcher", - "version": "1.2.1", + "version": "1.2.2", "main": "main.js", "scripts": { "ng": "ng", diff --git a/src/app/core/services/filters.service.ts b/src/app/core/services/filters.service.ts index 6179fcaf..e3b7c9c0 100644 --- a/src/app/core/services/filters.service.ts +++ b/src/app/core/services/filters.service.ts @@ -16,6 +16,7 @@ export type Filter = { milestones: string[]; hiddenLabels: Set; deselectedLabels: Set; + itemsPerPage: number; }; @Injectable({ @@ -27,6 +28,8 @@ export type Filter = { */ export class FiltersService { public static readonly PRESET_VIEW_QUERY_PARAM_KEY = 'presetview'; + private itemsPerPage = 20; + readonly presetViews: { [key: string]: () => Filter; } = { @@ -38,7 +41,8 @@ export class FiltersService { labels: [], milestones: this.getMilestonesForCurrentlyActive().map((milestone) => milestone.title), hiddenLabels: new Set(), - deselectedLabels: new Set() + deselectedLabels: new Set(), + itemsPerPage: this.itemsPerPage }), contributions: () => ({ title: '', @@ -48,7 +52,8 @@ export class FiltersService { labels: [], milestones: this.milestoneService.milestones.map((milestone) => milestone.title), hiddenLabels: new Set(), - deselectedLabels: new Set() + deselectedLabels: new Set(), + itemsPerPage: this.itemsPerPage }), custom: () => this.filter$.value }; @@ -69,7 +74,11 @@ export class FiltersService { private router: Router, private route: ActivatedRoute, private milestoneService: MilestoneService - ) {} + ) { + this.filter$.subscribe((filter: Filter) => { + this.itemsPerPage = filter.itemsPerPage; + }); + } private pushFiltersToUrl(): void { const queryParams = { ...this.route.snapshot.queryParams }; @@ -111,6 +120,9 @@ export class FiltersService { case 'sort': queryParams[filterName] = JSON.stringify(filterValue); break; + case 'itemsPerPage': + queryParams[filterName] = filterValue.toString(); + break; default: } } @@ -174,6 +186,9 @@ export class FiltersService { case 'sort': nextFilter[filterName] = JSON.parse(filterData[0]); break; + case 'itemsPerPage': + nextFilter[filterName] = Number(filterData[0]); + break; default: } } diff --git a/src/app/core/services/github.service.ts b/src/app/core/services/github.service.ts index e306d00f..3d2d605e 100644 --- a/src/app/core/services/github.service.ts +++ b/src/app/core/services/github.service.ts @@ -115,31 +115,25 @@ export class GithubService { /* * Github Issues consists of issues and pull requests in WATcher. */ - const issueObs = this.toFetchIssues(issuesFilter).pipe( + return this.toFetchIssues(issuesFilter).pipe( filter((toFetch) => toFetch), flatMap(() => { - return this.fetchGraphqlList( - FetchIssues, - { owner: ORG_NAME, name: REPO, filter: graphqlFilter }, - (result) => result.data.repository.issues.edges, - GithubGraphqlIssueOrPr + return merge( + this.fetchGraphqlList( + FetchIssues, + { owner: ORG_NAME, name: REPO, filter: graphqlFilter }, + (result) => result.data.repository.issues.edges, + GithubGraphqlIssueOrPr + ), + this.fetchGraphqlList( + FetchPullRequests, + { owner: ORG_NAME, name: REPO }, + (result) => result.data.repository.pullRequests.edges, + GithubGraphqlIssueOrPr + ) ); }) ); - const prObs = this.toFetchIssues(issuesFilter).pipe( - filter((toFetch) => toFetch), - flatMap(() => { - return this.fetchGraphqlList( - FetchPullRequests, - { owner: ORG_NAME, name: REPO }, - (result) => result.data.repository.pullRequests.edges, - GithubGraphqlIssueOrPr - ); - }) - ); - - // Concatenate both streams together. - return merge(issueObs, prObs); } /** diff --git a/src/app/core/services/grouping/grouping-context.service.ts b/src/app/core/services/grouping/grouping-context.service.ts index 3957514c..0a3319e5 100644 --- a/src/app/core/services/grouping/grouping-context.service.ts +++ b/src/app/core/services/grouping/grouping-context.service.ts @@ -58,8 +58,10 @@ export class GroupingContextService { * @param groupBy The grouping type to set. */ setCurrentGroupingType(groupBy: GroupBy): void { - this.currGroupBy = groupBy; - this.currGroupBySubject.next(this.currGroupBy); + if (groupBy !== this.currGroupBy) { + this.currGroupBy = groupBy; + this.currGroupBySubject.next(this.currGroupBy); + } this.router.navigate([], { relativeTo: this.route, diff --git a/src/app/core/services/issue.service.ts b/src/app/core/services/issue.service.ts index 4bc7bb83..2c66051b 100644 --- a/src/app/core/services/issue.service.ts +++ b/src/app/core/services/issue.service.ts @@ -18,7 +18,7 @@ import { ViewService } from './view.service'; * using GitHub. */ export class IssueService { - static readonly POLL_INTERVAL = 5000; // 5 seconds + static readonly POLL_INTERVAL = 20000; // 20 seconds issues: Issues; issues$: BehaviorSubject; diff --git a/src/app/core/services/label.service.ts b/src/app/core/services/label.service.ts index cdbb6de8..2f42164f 100644 --- a/src/app/core/services/label.service.ts +++ b/src/app/core/services/label.service.ts @@ -22,7 +22,7 @@ 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 + static readonly POLL_INTERVAL = 20000; // 20 seconds labels: Label[]; simpleLabels: SimpleLabel[]; diff --git a/src/app/core/services/milestone.service.ts b/src/app/core/services/milestone.service.ts index 13e7ac8a..64d938ee 100644 --- a/src/app/core/services/milestone.service.ts +++ b/src/app/core/services/milestone.service.ts @@ -47,22 +47,37 @@ export class MilestoneService { } /** - * Gets the open milestone with the earliest deadline. - * Returns null if there is no open milestone with deadline. + * Returns the open milestone with earliest deadline. + * If no deadline exists, returns milestone with alphabetically smallest title. + * Returns null if there are no open milestones. */ getEarliestOpenMilestone(): Milestone { - let earliestOpenMilestone: Milestone = null; - for (const milestone of this.milestones) { - if (!milestone.deadline || milestone.state !== 'open') { - continue; + const openMilestones: Milestone[] = this.milestones.filter((milestone: Milestone) => milestone.state === 'open'); + + if (openMilestones.length === 0) { + return null; + } + + const target = openMilestones.reduce((prev, curr) => { + if (prev === null) { + return curr; } - if (earliestOpenMilestone === null) { - earliestOpenMilestone = milestone; - } else if (milestone.deadline < earliestOpenMilestone.deadline) { - earliestOpenMilestone = milestone; + + if (prev.deadline !== curr.deadline) { + if (!prev.deadline) { + return curr; + } + if (!curr.deadline) { + return prev; + } + return prev.deadline < curr.deadline ? prev : curr; } - } - return earliestOpenMilestone; + + // Both without due date or with the same due date + return prev.title.localeCompare(curr.title) < 0 ? prev : curr; + }, null); + + return target; } /** diff --git a/src/app/core/services/view.service.ts b/src/app/core/services/view.service.ts index 198a8cd4..3f8e959c 100644 --- a/src/app/core/services/view.service.ts +++ b/src/app/core/services/view.service.ts @@ -105,11 +105,12 @@ export class ViewService { /** Adds past repositories to view */ (this.otherRepos || []).push(this.currentRepo); } - this.setRepository(repo, this.otherRepos); + if (!repo.equals(this.currentRepo)) { + this.setRepository(repo, this.otherRepos); + this.repoChanged$.next(repo); + } this.repoUrlCacheService.cache(repo.toString()); - - this.repoChanged$.next(repo); } /** diff --git a/src/app/issues-viewer/card-view/card-view.component.css b/src/app/issues-viewer/card-view/card-view.component.css index b1c656be..10fdf38e 100644 --- a/src/app/issues-viewer/card-view/card-view.component.css +++ b/src/app/issues-viewer/card-view/card-view.component.css @@ -52,7 +52,26 @@ div.column-header .mat-card-header { position: relative; } -/* Ref: https://css-scroll-shadows.vercel.app */ +/* Ref: https://lea.verou.me/blog/2012/04/background-attachment-local/ */ +.scroll-shadow { + background: + /* Shadow covers */ linear-gradient(white 30%, rgba(255, 255, 255, 0)), + linear-gradient(rgba(255, 255, 255, 0), white 70%) 0 100%, + /* Shadows */ radial-gradient(50% 0, farthest-side, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0)), + radial-gradient(50% 100%, farthest-side, rgba(0, 0, 0, 0.2), rgba(0, 0, 0, 0)) 0 100%; + background: + /* Shadow covers */ linear-gradient(white 30%, rgba(255, 255, 255, 0)), + linear-gradient(rgba(255, 255, 255, 0), white 70%) 0 100%, + /* Shadows */ radial-gradient(farthest-side at 50% 0, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0)), + radial-gradient(farthest-side at 50% 100%, rgba(0, 0, 0, 0.3), rgba(0, 0, 0, 0)) 0 100%; + background-repeat: no-repeat; + background-color: white; + background-size: 100% 40px, 100% 40px, 100% 14px, 100% 14px; + + /* Opera doesn't support this in the shorthand */ + background-attachment: local, local, scroll, scroll; +} + .scrollable-container::before { pointer-events: none; content: ''; @@ -87,30 +106,6 @@ div.column-header .mat-card-header { display: none; } -.scrollable-container-wrapper::before { - pointer-events: none; - content: ''; - position: absolute; - z-index: 1; - top: 0; - left: 0; - right: 0; - height: 5px; - background-image: radial-gradient(farthest-side at 50% 0, rgba(0, 0, 0, 0.5), transparent); -} - -.scrollable-container-wrapper::after { - pointer-events: none; - content: ''; - position: absolute; - z-index: 1; - bottom: 0; - left: 0; - right: 0; - height: 5px; - background-image: radial-gradient(farthest-side at 50% 100%, rgba(0, 0, 0, 0.5), transparent); -} - .loading-spinner { display: flex; justify-content: center; diff --git a/src/app/issues-viewer/card-view/card-view.component.html b/src/app/issues-viewer/card-view/card-view.component.html index 37b3e98e..15273a4f 100644 --- a/src/app/issues-viewer/card-view/card-view.component.html +++ b/src/app/issues-viewer/card-view/card-view.component.html @@ -3,7 +3,7 @@ [ngTemplateOutlet]="getHeaderTemplate() || defaultHeader" [ngTemplateOutletContext]="{ $implicit: this.group }" > -
+
@@ -15,9 +15,9 @@
diff --git a/src/app/issues-viewer/card-view/card-view.component.ts b/src/app/issues-viewer/card-view/card-view.component.ts index b4fb9151..1eaf2de8 100644 --- a/src/app/issues-viewer/card-view/card-view.component.ts +++ b/src/app/issues-viewer/card-view/card-view.component.ts @@ -46,6 +46,7 @@ export class CardViewComponent implements OnInit, AfterViewInit, OnDestroy, Filt private timeoutId: NodeJS.Timeout | null = null; private issuesLengthSubscription: Subscription; private issuesLoadingStateSubscription: Subscription; + private filterSubscription: Subscription; isLoading = true; issueLength = 0; @@ -73,6 +74,10 @@ export class CardViewComponent implements OnInit, AfterViewInit, OnDestroy, Filt this.group, this.filters ); + + this.filterSubscription = this.filtersService.filter$.subscribe((filter: any) => { + this.pageSize = filter.itemsPerPage; + }); } ngAfterViewInit(): void { @@ -125,8 +130,4 @@ export class CardViewComponent implements OnInit, AfterViewInit, OnDestroy, Filt retrieveFilterable(): FilterableSource { return this.issues; } - - updatePageSize(newPageSize: number) { - this.pageSize = newPageSize; - } } diff --git a/src/app/issues-viewer/issues-viewer.component.ts b/src/app/issues-viewer/issues-viewer.component.ts index 58665dda..4afe6d85 100644 --- a/src/app/issues-viewer/issues-viewer.component.ts +++ b/src/app/issues-viewer/issues-viewer.component.ts @@ -106,20 +106,11 @@ export class IssuesViewerComponent implements OnInit, AfterViewInit, OnDestroy { this.availableGroupsSubscription.unsubscribe(); } - this.checkIfValidRepository().subscribe((isValidRepository) => { - if (!isValidRepository) { - throw new Error(ErrorMessageService.repositoryNotPresentMessage()); - } - }); - // Fetch assignees this.groups = []; this.hiddenGroups = []; this.availableGroupsSubscription = this.groupingContextService.getGroups().subscribe((x) => (this.groups = x)); - - // Fetch issues - this.issueService.reloadAllIssues(); } /** diff --git a/src/app/shared/filter-bar/filter-bar.component.css b/src/app/shared/filter-bar/filter-bar.component.css index be59aa93..c5728882 100644 --- a/src/app/shared/filter-bar/filter-bar.component.css +++ b/src/app/shared/filter-bar/filter-bar.component.css @@ -2,11 +2,11 @@ margin: 8px; font-size: 14px; max-width: 20%; - width: 17%; /* depends on number of filters*/ + width: 14%; /* depends on number of filters*/ } .search-bar { - width: 90%; + width: 80%; } .dropdown-filters { diff --git a/src/app/shared/filter-bar/filter-bar.component.html b/src/app/shared/filter-bar/filter-bar.component.html index 3689f0b0..9c57f634 100644 --- a/src/app/shared/filter-bar/filter-bar.component.html +++ b/src/app/shared/filter-bar/filter-bar.component.html @@ -1,5 +1,5 @@ - + - + diff --git a/src/app/shared/filter-bar/label-filter-bar/label-filter-bar.component.ts b/src/app/shared/filter-bar/label-filter-bar/label-filter-bar.component.ts index bf86435d..216db5fc 100644 --- a/src/app/shared/filter-bar/label-filter-bar/label-filter-bar.component.ts +++ b/src/app/shared/filter-bar/label-filter-bar/label-filter-bar.component.ts @@ -40,6 +40,7 @@ export class LabelFilterBarComponent implements OnInit, AfterViewInit, OnDestroy this.selectedLabelNames = new Set(this.filtersService.filter$.value.labels); this.deselectedLabelNames = this.filtersService.filter$.value.deselectedLabels; this.hiddenLabelNames = this.filtersService.filter$.value.hiddenLabels; + this.loaded = true; }); }); } @@ -100,15 +101,6 @@ export class LabelFilterBarComponent implements OnInit, AfterViewInit, 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.loaded = true; - } - ); } filter(filter: string, target: string): boolean { diff --git a/src/app/shared/issue-pr-card/issue-pr-card.component.css b/src/app/shared/issue-pr-card/issue-pr-card.component.css index 373546ae..76f2ced3 100644 --- a/src/app/shared/issue-pr-card/issue-pr-card.component.css +++ b/src/app/shared/issue-pr-card/issue-pr-card.component.css @@ -1,5 +1,6 @@ .card { margin: 8px 0px 8px 0px; + background-color: transparent; } .mat-card { diff --git a/src/app/shared/layout/header.component.html b/src/app/shared/layout/header.component.html index e0b8c0cf..65a3d207 100644 --- a/src/app/shared/layout/header.component.html +++ b/src/app/shared/layout/header.component.html @@ -11,7 +11,7 @@ WATcher v{{ this.getVersion() }} - + {{ this.presetViews[this.filtersService.presetView$.value] }} @@ -34,7 +34,7 @@ -
+