Skip to content

Commit

Permalink
[angular-ngrx-scss] issues tab (#1941)
Browse files Browse the repository at this point in the history
* wip: issues tab

* chore: cleanup and comments

* feat: use search api, connect filters

* fix: use interface in test

* fix: make pagination fetch correct page

---------

Co-authored-by: Jan Kaiser <[email protected]>
  • Loading branch information
honzikec and Jan Kaiser authored Aug 29, 2023
1 parent d576e9b commit ae1a799
Show file tree
Hide file tree
Showing 26 changed files with 637 additions and 90 deletions.
8 changes: 4 additions & 4 deletions angular-ngrx-scss/src/app/fixtures/repository.fixtures.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import {
PR_STATE,
ISSUE_STATE,
PullRequestAPIResponse,
PullRequestItemAPIResponse,
PullRequestLabel,
IssueLabel,
RepoPullRequests,
} from '../state/repository';

export const generatePullRequestAPIResponseFixture = (
state: PR_STATE = 'open',
state: ISSUE_STATE = 'open',
): PullRequestAPIResponse => {
const closedDate = new Date(2022, 2, 1).toISOString();
return {
Expand All @@ -34,7 +34,7 @@ export const generatePullRequestAPIResponseFixture = (
labels: [
{
name: 'bugs',
} as PullRequestLabel,
} as IssueLabel,
],
comments: 305,
} as PullRequestItemAPIResponse,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,42 @@
<div class="filters-container">
<div class="issue-status">
<a href="" class="active">
<span class="icon" appOcticon="no-entry" size="16"></span>
<span>412 Open</span>
</a>
<a href="">
<button [ngClass]="{ active: viewState === 'open' }" (click)="selectOpen()">
<span class="icon" appOcticon="check" size="16"></span
><span>4877 Open</span></a
><span>{{ openIssues?.total ?? 0 }} Open</span>
</button>
<button
[ngClass]="{ active: viewState === 'closed' }"
(click)="selectClosed()"
>
<span class="icon" appOcticon="no-entry" size="16"></span>
<span>{{ closedIssues?.total ?? 0 }} Closed</span>
</button>
</div>
<div class="issue-filters">
<app-filter-dropdown
name="Label"
description="Select label"
[isRepo]="true"
[items]="(labels$ | async) || []"
[toggle]="true"
(setFilter)="setLabel($event)"
[current]="filterParams.labels"
></app-filter-dropdown>
<app-filter-dropdown
name="Milestones"
description="Select milestone"
[isRepo]="true"
[items]="(milestones$ | async) || []"
(setFilter)="setMilestone($event)"
[current]="filterParams.milestone"
></app-filter-dropdown>
<app-filter-dropdown
name="Sort"
description="Select sort"
[isRepo]="true"
[items]="sortOptions"
(setFilter)="setSort($event)"
[current]="filterParams.sort"
></app-filter-dropdown>
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,10 @@
gap: variables.$padding--l;
color: variables.$gray600;

a {
button {
color: inherit;
border: none;
cursor: pointer;

.icon {
margin-right: functions.rem(5);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,101 @@
import { Component } from '@angular/core';
import { Component, EventEmitter, Input, Output } from '@angular/core';
import { Store } from '@ngrx/store';
import { Observable, map } from 'rxjs';
import { Sort } from 'src/app/repository/services/repository.interfaces';
import { FilterOption } from 'src/app/shared/components/filter-dropdown/filter-dropdown.component';
import { SORTING_OPTIONS } from 'src/app/shared/constants';
import {
ISSUE_STATE,
RepoIssues,
fetchIssues,
selectLabels,
selectMilestones,
} from 'src/app/state/repository';

@Component({
selector: 'app-issues-header',
templateUrl: './issues-header.component.html',
styleUrls: ['./issues-header.component.scss'],
})
export class IssuesHeaderComponent {}
export class IssuesHeaderComponent {
@Input() owner!: string;

@Input() repoName!: string;

filterParams: { labels?: string; milestone?: string; sort: Sort } = {
sort: 'created',
};

sortOptions: FilterOption[] = SORTING_OPTIONS;

milestones$: Observable<FilterOption[]> = this.store
.select(selectMilestones)
.pipe(
map((milestones) =>
milestones.map((milestone) => ({
label: milestone.title,
value: milestone.title,
})),
),
);

labels$: Observable<FilterOption[]> = this.store
.select(selectLabels)
.pipe(
map((labels) =>
labels.map((label) => ({ label: label.name, value: label.name })),
),
);

@Input() viewState: ISSUE_STATE = 'open';

@Input()
openIssues: RepoIssues | null = null;

@Input()
closedIssues: RepoIssues | null = null;

@Output() viewStateChange = new EventEmitter<ISSUE_STATE>();

constructor(private store: Store) {}

selectOpen() {
this.viewStateChange.emit('open');
}

selectClosed() {
this.viewStateChange.emit('closed');
}

setLabel(label: string) {
this.filterParams.labels = label;
this.refetchIssues();
}

setMilestone(milestone: string) {
this.filterParams.milestone = milestone;
this.refetchIssues();
}

setSort(sort: string) {
this.filterParams.sort = sort as Sort;
this.refetchIssues();
}

private refetchIssues() {
this.store.dispatch(
fetchIssues({
owner: this.owner,
repoName: this.repoName,
params: { state: 'open', ...this.filterParams },
}),
);
this.store.dispatch(
fetchIssues({
owner: this.owner,
repoName: this.repoName,
params: { state: 'closed', ...this.filterParams },
}),
);
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div class="issue">
<div class="issues-list-container">
<ng-container *ngFor="let issue of issues">
<div class="issues-list-container" *ngIf="issues">
<ng-container *ngFor="let issue of issues.issues">
<app-repo-issue-pull-card [item]="issue"></app-repo-issue-pull-card>
</ng-container>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Component, Input } from '@angular/core';
import { Issue } from 'src/app/repository/services/repository.interfaces';
import { RepoIssues } from 'src/app/state/repository';

@Component({
selector: 'app-issues-list',
templateUrl: './issues-list.component.html',
})
export class IssuesListComponent {
@Input() issues: Issue[] = [];
@Input() issues: RepoIssues | null = null;
}
24 changes: 21 additions & 3 deletions angular-ngrx-scss/src/app/issues/components/issues.component.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,23 @@
<div class="container issues-container">
<app-issues-header></app-issues-header>
<app-issues-list></app-issues-list>
<app-issues-header
[viewState]="viewState"
[openIssues]="openIssues$ | async"
[closedIssues]="closedIssues$ | async"
[owner]="owner"
[repoName]="repoName"
(viewStateChange)="viewStateChange($event)"
></app-issues-header>
<app-issues-list
[issues]="
viewState === 'open' ? (openIssues$ | async) : (closedIssues$ | async)
"
></app-issues-list>
</div>
<app-pagination></app-pagination>
<app-pagination
[params]="
viewState === 'open'
? (openIssuesPaginationParams$ | async)
: (closedIssuesPaginationParams$ | async)
"
(pageChange)="pageChange($event)"
></app-pagination>
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
.issues-container {
border-radius: variables.$padding;
border: 1px solid variables.$gray200;
margin: 1rem auto;
}
64 changes: 62 additions & 2 deletions angular-ngrx-scss/src/app/issues/components/issues.component.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,68 @@
import { Component } from '@angular/core';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Store } from '@ngrx/store';
import {
ISSUE_STATE,
fetchIssues,
selectClosedIssuePaginationParams,
selectClosedIssues,
selectOpenIssuePaginationParams,
selectOpenIssues,
} from 'src/app/state/repository';

@Component({
selector: 'app-issues',
templateUrl: './issues.component.html',
styleUrls: ['./issues.component.scss'],
})
export class IssuesComponent {}
export class IssuesComponent implements OnInit {
owner!: string;
repoName!: string;
openIssues$ = this.store.select(selectOpenIssues);
closedIssues$ = this.store.select(selectClosedIssues);
viewState: ISSUE_STATE = 'open';

openIssuesPaginationParams$ = this.store.select(
selectOpenIssuePaginationParams,
);
closedIssuesPaginationParams$ = this.store.select(
selectClosedIssuePaginationParams,
);

constructor(private route: ActivatedRoute, private store: Store) {}

ngOnInit(): void {
this.owner = this.route.snapshot.paramMap.get('owner') as string;
this.repoName = this.route.snapshot.paramMap.get('repo') as string;

this.store.dispatch(
fetchIssues({
owner: this.owner,
repoName: this.repoName,
params: { state: 'open' },
}),
);

this.store.dispatch(
fetchIssues({
owner: this.owner,
repoName: this.repoName,
params: { state: 'closed' },
}),
);
}

pageChange(page: number) {
this.store.dispatch(
fetchIssues({
owner: this.owner,
repoName: this.repoName,
params: { state: this.viewState, page },
}),
);
}

viewStateChange(viewState: ISSUE_STATE) {
this.viewState = viewState;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
Input,
Output,
} from '@angular/core';
import { PR_STATE, RepoPullRequests } from '../../../state/repository';
import { ISSUE_STATE, RepoPullRequests } from '../../../state/repository';

@Component({
selector: 'app-pull-requests-header',
Expand All @@ -16,10 +16,10 @@ import { PR_STATE, RepoPullRequests } from '../../../state/repository';
export class PullRequestsHeaderComponent {
@Input() openPullRequests!: RepoPullRequests | null;
@Input() closedPullRequests!: RepoPullRequests | null;
@Input() viewState: PR_STATE = 'open';
@Output() viewStateChange = new EventEmitter<PR_STATE>();
@Input() viewState: ISSUE_STATE = 'open';
@Output() viewStateChange = new EventEmitter<ISSUE_STATE>();

changeViewState(state: PR_STATE) {
changeViewState(state: ISSUE_STATE) {
this.viewState = state;
this.viewStateChange.emit(this.viewState);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { ActivatedRoute } from '@angular/router';
import { Store } from '@ngrx/store';
import {
fetchPullRequests,
PR_STATE,
ISSUE_STATE,
selectClosedPullRequests,
selectOpenPullRequests,
} from '../state/repository';
Expand All @@ -18,7 +18,7 @@ export class PullRequestsComponent implements OnInit {
repoName!: string;
openPullRequests$ = this.store.select(selectOpenPullRequests);
closedPullRequests$ = this.store.select(selectClosedPullRequests);
viewState: PR_STATE = 'open';
viewState: ISSUE_STATE = 'open';

constructor(private route: ActivatedRoute, private store: Store) {}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, Input } from '@angular/core';
import { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { selectedRepository } from 'src/app/state/repository';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,22 @@ export interface Issue {

export type Issues = Array<Issue>;

export type Sort =
| 'created'
| 'created-asc'
| 'updated'
| 'updated-asc'
| 'comments'
| 'comments-asc';

export interface RepositoryIssuesApiParams {
milestone?: string;
state?: 'open' | 'closed' | 'all';
state: 'open' | 'closed' | 'all';
assignee?: string;
creator?: string;
mentioned?: string;
labels?: string;
sort?: 'created' | 'updated' | 'comments';
direction?: 'asc' | 'desc';
sort?: Sort;
since?: string;
per_page?: number;
page?: number;
Expand Down
Loading

0 comments on commit ae1a799

Please sign in to comment.