Skip to content

Commit

Permalink
Integrate Grouping Service (#313)
Browse files Browse the repository at this point in the history
Implement GroupBy feature to allow users to group the issues/prs
based on different criteria such as milestone, status and etc.

Let's integrate the grouping service in the components.
  • Loading branch information
NereusWB922 authored Mar 25, 2024
1 parent 1fa6138 commit 3420a73
Show file tree
Hide file tree
Showing 15 changed files with 184 additions and 113 deletions.
3 changes: 1 addition & 2 deletions src/app/core/services/grouping/grouping-context.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ import { AssigneeGroupingStrategy } from './assignee-grouping-strategy.service';
import { GroupingStrategy } from './grouping-strategy.interface';

export enum GroupBy {
Assignee = 'assignee',
Milestone = 'milestone'
Assignee = 'assignee'
}

export const DEFAULT_GROUPBY = GroupBy.Assignee;
Expand Down
51 changes: 32 additions & 19 deletions src/app/issues-viewer/card-view/card-view.component.html
Original file line number Diff line number Diff line change
@@ -1,23 +1,8 @@
<div class="card-column">
<div class="column-header">
<mat-card>
<mat-card-header [ngStyle]="{ height: '40px' }">
<div
mat-card-avatar
*ngIf="this.assignee"
[ngStyle]="{
background: 'url(' + this.assignee.avatar_url + ')',
'background-size': '40px'
}"
></div>
<mat-card-title>
{{ this.assignee !== undefined ? this.assignee.login : 'Unassigned Issues' }}
</mat-card-title>
<div class="row-count">{{ this.issues.count }}</div>
</mat-card-header>
</mat-card>
</div>

<ng-container
[ngTemplateOutlet]="getHeaderTemplate() || defaultHeader"
[ngTemplateOutletContext]="{ $implicit: this.group }"
></ng-container>
<div class="scrollable-container-wrapper">
<div class="scrollable-container">
<div class="issue-pr-cards" *ngFor="let issue of this.issues$ | async; index as i">
Expand All @@ -35,3 +20,31 @@
(page)="updatePageSize($event.pageSize)"
></mat-paginator>
</div>

<!-- Template -->
<ng-template #defaultHeader>
<mat-card class="loading-spinner">
<mat-progress-spinner color="primary" mode="indeterminate" diameter="50" strokeWidth="5"></mat-progress-spinner>
</mat-card>
</ng-template>

<ng-template #assigneeHeader let-assignee>
<div class="column-header">
<mat-card>
<mat-card-header [ngStyle]="{ height: '40px' }">
<div
mat-card-avatar
*ngIf="assignee"
[ngStyle]="{
background: 'url(' + assignee.avatar_url + ')',
'background-size': '40px'
}"
></div>
<mat-card-title>
{{ assignee.login }}
</mat-card-title>
<div class="row-count">{{ this.issues.count }}</div>
</mat-card-header>
</mat-card>
</div>
</ng-template>
41 changes: 35 additions & 6 deletions src/app/issues-viewer/card-view/card-view.component.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
import { AfterViewInit, Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, ViewChild } from '@angular/core';
import {
AfterViewInit,
Component,
ElementRef,
EventEmitter,
Input,
OnDestroy,
OnInit,
Output,
TemplateRef,
ViewChild
} from '@angular/core';
import { MatPaginator } from '@angular/material/paginator';
import { MatSort } from '@angular/material/sort';
import { Observable } from 'rxjs';
import { GithubUser } from '../../core/models/github-user.model';
import { Group } from '../../core/models/github/group.interface';
import { Issue } from '../../core/models/issue.model';
import { GroupBy, GroupingContextService } from '../../core/services/grouping/grouping-context.service';
import { IssueService } from '../../core/services/issue.service';
import { FilterableComponent, FilterableSource } from '../../shared/issue-tables/filterableTypes';
import { IssuesDataTable } from '../../shared/issue-tables/IssuesDataTable';
Expand All @@ -19,10 +30,12 @@ import { IssuesDataTable } from '../../shared/issue-tables/IssuesDataTable';
*/
export class CardViewComponent implements OnInit, AfterViewInit, OnDestroy, FilterableComponent {
@Input() headers: string[];
@Input() assignee?: GithubUser = undefined;
@Input() group?: Group = undefined;
@Input() filters?: any = undefined;

@ViewChild(MatPaginator, { static: true }) paginator: MatPaginator;
@ViewChild('defaultHeader') defaultHeaderTemplate: TemplateRef<any>;
@ViewChild('assigneeHeader') assigneeHeaderTemplate: TemplateRef<any>;

issues: IssuesDataTable;
issues$: Observable<Issue[]>;
Expand All @@ -34,10 +47,17 @@ export class CardViewComponent implements OnInit, AfterViewInit, OnDestroy, Filt

@Output() issueLengthChange: EventEmitter<Number> = new EventEmitter<Number>();

constructor(public element: ElementRef, public issueService: IssueService) {}
constructor(public element: ElementRef, public issueService: IssueService, public groupingContextService: GroupingContextService) {}

ngOnInit() {
this.issues = new IssuesDataTable(this.issueService, this.paginator, this.headers, this.assignee, this.filters);
this.issues = new IssuesDataTable(
this.issueService,
this.groupingContextService,
this.paginator,
this.headers,
this.group,
this.filters
);
}

ngAfterViewInit(): void {
Expand All @@ -58,6 +78,15 @@ export class CardViewComponent implements OnInit, AfterViewInit, OnDestroy, Filt
});
}

getHeaderTemplate(): TemplateRef<any> {
switch (this.groupingContextService.currGroupBy) {
case GroupBy.Assignee:
return this.assigneeHeaderTemplate;
default:
return this.defaultHeaderTemplate;
}
}

ngOnDestroy(): void {
setTimeout(() => {
this.issues.disconnect();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.hidden-users {
.hidden-groups {
margin: 8px;
min-width: 150px;
max-width: 300px;
Expand Down
33 changes: 33 additions & 0 deletions src/app/issues-viewer/hidden-groups/hidden-groups.component.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<div *ngIf="groups.length > 0" class="hidden-groups">
<mat-card matTooltip="Users that have no Issues/PRs pertaining to the current filter" matTooltipShowDelay="0" matTooltipHideDelay="0">
<mat-card-title>Hidden {{ this.groupingContextService.currGroupBy }}s</mat-card-title>
<div class="row-count">{{ groups.length }}</div>
</mat-card>
<div class="scrollable-container">
<div *ngFor="let group of groups">
<ng-container [ngTemplateOutlet]="getCardTemplate()" [ngTemplateOutletContext]="{ $implicit: group }"></ng-container>
</div>
</div>
</div>

<!-- Templates -->
<ng-template #defaultCard let-group>
<mat-card class="loading-spinner">
<mat-progress-spinner color="primary" mode="indeterminate" diameter="50" strokeWidth="5"></mat-progress-spinner>
</mat-card>
</ng-template>

<ng-template #assigneeCard let-assignee>
<mat-card>
<mar-card-header class="mat-card-header">
<div
mat-card-avatar
[ngStyle]="{
background: 'url(' + assignee.avatar_url + ')',
'background-size': '30px'
}"
></div>
<mat-card-title>{{ assignee.login }}</mat-card-title>
</mar-card-header>
</mat-card>
</ng-template>
26 changes: 26 additions & 0 deletions src/app/issues-viewer/hidden-groups/hidden-groups.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Component, Input, TemplateRef, ViewChild } from '@angular/core';
import { Group } from '../../core/models/github/group.interface';
import { GroupBy, GroupingContextService } from '../../core/services/grouping/grouping-context.service';

@Component({
selector: 'app-hidden-groups',
templateUrl: './hidden-groups.component.html',
styleUrls: ['./hidden-groups.component.css']
})
export class HiddenGroupsComponent {
@Input() groups: Group[] = [];

@ViewChild('defaultCard') defaultCardTemplate: TemplateRef<any>;
@ViewChild('assigneeCard') assigneeCardTemplate: TemplateRef<any>;

constructor(public groupingContextService: GroupingContextService) {}

getCardTemplate(): TemplateRef<any> {
switch (this.groupingContextService.currGroupBy) {
case GroupBy.Assignee:
return this.assigneeCardTemplate;
default:
return this.defaultCardTemplate;
}
}
}
22 changes: 0 additions & 22 deletions src/app/issues-viewer/hidden-users/hidden-users.component.html

This file was deleted.

11 changes: 0 additions & 11 deletions src/app/issues-viewer/hidden-users/hidden-users.component.ts

This file was deleted.

17 changes: 5 additions & 12 deletions src/app/issues-viewer/issues-viewer.component.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<div>
<div class="loading-spinner" *ngIf="this.viewService.isChangingRepo | async; else elseBlock">
<div class="loading-spinner" *ngIf="(this.viewService.isChangingRepo | async) || this.groups.length === 0; else elseBlock">
<mat-progress-spinner color="primary" mode="indeterminate" diameter="50" strokeWidth="5"> </mat-progress-spinner>
</div>

Expand All @@ -8,22 +8,15 @@

<div class="wrapper">
<app-card-view
*ngFor="let assignee of assignees"
*ngFor="let group of groups"
class="issue-table"
#card
[ngStyle]="{ display: card.isLoading || card.issueLength > 0 ? 'initial' : 'none' }"
[assignee]="assignee"
[group]="group"
[headers]="this.displayedColumns"
(issueLengthChange)="updateHiddenUsers($event, assignee)"
(issueLengthChange)="updateHiddenGroups($event, group)"
></app-card-view>
<app-card-view
class="issue-table"
#card
[ngStyle]="{ display: card.isLoading || card.issueLength > 0 ? 'initial' : 'none' }"
[headers]="this.displayedColumns"
>
</app-card-view>
<app-hidden-users [users]="hiddenAssignees"></app-hidden-users>
<app-hidden-groups [groups]="this.hiddenGroups"></app-hidden-groups>
</div>
</ng-template>
</div>
50 changes: 30 additions & 20 deletions src/app/issues-viewer/issues-viewer.component.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { AfterViewInit, Component, OnDestroy, OnInit, QueryList, ViewChild, ViewChildren } from '@angular/core';
import { AfterViewInit, Component, OnDestroy, OnInit, QueryList, ViewChildren } from '@angular/core';
import { BehaviorSubject, of, Subscription } from 'rxjs';
import { GithubUser } from '../core/models/github-user.model';
import { Group } from '../core/models/github/group.interface';
import { Repo } from '../core/models/repo.model';
import { ErrorMessageService } from '../core/services/error-message.service';
import { GithubService } from '../core/services/github.service';
import { GroupingContextService } from '../core/services/grouping/grouping-context.service';
import { IssueService } from '../core/services/issue.service';
import { LabelService } from '../core/services/label.service';
import { MilestoneService } from '../core/services/milestone.service';
Expand All @@ -22,14 +23,16 @@ export class IssuesViewerComponent implements OnInit, AfterViewInit, OnDestroy {
/** Observes for any change in repo*/
repoChangeSubscription: Subscription;

groupByChangeSubscription: Subscription;

/** Observes for any change in the cardviews */
viewChange: Subscription;

/** Users to show as columns */
assignees: GithubUser[];
groups: Group[] = [];

/** The list of users with 0 issues (hidden) */
hiddenAssignees: GithubUser[] = [];
hiddenGroups: Group[] = [];

@ViewChildren(CardViewComponent) cardViews: QueryList<CardViewComponent>;

Expand All @@ -40,13 +43,18 @@ export class IssuesViewerComponent implements OnInit, AfterViewInit, OnDestroy {
public githubService: GithubService,
public issueService: IssueService,
public labelService: LabelService,
public milestoneService: MilestoneService
public milestoneService: MilestoneService,
public groupingContextService: GroupingContextService
) {
this.repoChangeSubscription = this.viewService.repoChanged$.subscribe((newRepo) => {
this.issueService.reset(false);
this.labelService.reset();
this.initialize();
});

this.groupByChangeSubscription = this.groupingContextService.currGroupBy$.subscribe((newGroupBy) => {
this.initialize();
});
}

ngOnInit() {
Expand All @@ -73,10 +81,10 @@ export class IssuesViewerComponent implements OnInit, AfterViewInit, OnDestroy {
});

// Fetch assignees
this.assignees = [];
this.hiddenAssignees = [];
this.groups = [];
this.hiddenGroups = [];

this.githubService.getUsersAssignable().subscribe((x) => (this.assignees = x));
this.groupingContextService.getGroups().subscribe((x) => (this.groups = x));

// Fetch issues
this.issueService.reloadAllIssues();
Expand All @@ -96,25 +104,27 @@ export class IssuesViewerComponent implements OnInit, AfterViewInit, OnDestroy {
}

/**
* Update the list of hidden user based on the new info.
* @param issueLength The number of issues assigned to this user.
* @param assignee The assignee.
* Update the list of hidden group based on the new info.
* @param issueLength The number of issues under this group.
* @param group The group.
*/
updateHiddenUsers(issueLength: number, assignee: GithubUser) {
if (issueLength === 0) {
this.updateHiddenUser(assignee);
updateHiddenGroups(issueLength: number, target: Group) {
if (issueLength === 0 && this.groupingContextService.isInHiddenList(target)) {
this.addToHiddenGroups(target);
} else {
this.removeHiddenUser(assignee);
this.removeFromHiddenGroups(target);
}
}

private updateHiddenUser(assignee: GithubUser) {
if (!this.hiddenAssignees.includes(assignee)) {
this.hiddenAssignees.push(assignee);
private addToHiddenGroups(target: Group) {
const isGroupPresent = this.hiddenGroups.some((group) => group.equals(target));

if (!isGroupPresent) {
this.hiddenGroups.push(target);
}
}

private removeHiddenUser(assignee: GithubUser) {
this.hiddenAssignees = this.hiddenAssignees.filter((user) => user !== assignee);
private removeFromHiddenGroups(target: Group) {
this.hiddenGroups = this.hiddenGroups.filter((group) => !group.equals(target));
}
}
Loading

0 comments on commit 3420a73

Please sign in to comment.