aboutsummaryrefslogtreecommitdiff
path: root/Timeline/ClientApp/src/app/todo-list-page
diff options
context:
space:
mode:
author杨宇千 <crupest@outlook.com>2019-03-04 19:57:56 +0800
committerGitHub <noreply@github.com>2019-03-04 19:57:56 +0800
commit8033d6523885486c24af2bdd57a24b0fd62d0b00 (patch)
tree942326b2d9a0c71a5c141b633e90b5f77d2887cd /Timeline/ClientApp/src/app/todo-list-page
parente64ace0173d5bb737bf7320c8779fbcce0251aae (diff)
parent0ae0459be4f9eade994acbca65a60070672854fb (diff)
downloadtimeline-8033d6523885486c24af2bdd57a24b0fd62d0b00.tar.gz
timeline-8033d6523885486c24af2bdd57a24b0fd62d0b00.tar.bz2
timeline-8033d6523885486c24af2bdd57a24b0fd62d0b00.zip
Merge pull request #2 from crupest/migrate-todo
Migrate todo page from Azure DevOps WorkItems to Github Issues.
Diffstat (limited to 'Timeline/ClientApp/src/app/todo-list-page')
-rw-r--r--Timeline/ClientApp/src/app/todo-list-page/todo-list-color-block.css4
-rw-r--r--Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.html4
-rw-r--r--Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.spec.ts36
-rw-r--r--Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.ts4
-rw-r--r--Timeline/ClientApp/src/app/todo-list-page/todo-list.service.spec.ts87
-rw-r--r--Timeline/ClientApp/src/app/todo-list-page/todo-list.service.ts112
6 files changed, 75 insertions, 172 deletions
diff --git a/Timeline/ClientApp/src/app/todo-list-page/todo-list-color-block.css b/Timeline/ClientApp/src/app/todo-list-page/todo-list-color-block.css
index 456e477f..5e0d4ba9 100644
--- a/Timeline/ClientApp/src/app/todo-list-page/todo-list-color-block.css
+++ b/Timeline/ClientApp/src/app/todo-list-page/todo-list-color-block.css
@@ -1,7 +1,7 @@
-.color-block-resolving {
+.color-block-open {
background: red;
}
-.color-block-completed {
+.color-block-closed {
background: green;
}
diff --git a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.html b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.html
index e8f5f30b..50180fe8 100644
--- a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.html
+++ b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.html
@@ -7,11 +7,11 @@
<div class="space"></div>
<div class="sample-box" *ngIf="i === 0">
<div class="mat-caption sample-item">
- <span class="sample-color-block color-block-resolving"></span>
+ <span class="sample-color-block color-block-open"></span>
<span> means working now.</span>
</div>
<div class="mat-caption sample-item">
- <span class="sample-color-block color-block-completed"></span>
+ <span class="sample-color-block color-block-closed"></span>
<span> means completed.</span>
</div>
<div class="mat-caption">click on item to see details.</div>
diff --git a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.spec.ts b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.spec.ts
index a757b2a5..5706bf51 100644
--- a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.spec.ts
+++ b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.spec.ts
@@ -4,7 +4,7 @@ import { async, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core
import { Observable, from } from 'rxjs';
import { TodoListPageComponent } from './todo-list-page.component';
-import { TodoListService, WorkItem } from './todo-list.service';
+import { TodoListService, TodoItem } from './todo-list.service';
import { By } from '@angular/platform-browser';
import { delay } from 'rxjs/operators';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
@@ -24,29 +24,25 @@ describe('TodoListPageComponent', () => {
let component: TodoListPageComponent;
let fixture: ComponentFixture<TodoListPageComponent>;
- let mockWorkItems: WorkItem[];
+ const mockTodoItems: TodoItem[] = [
+ {
+ number: 0,
+ title: 'Test title 1',
+ isClosed: true,
+ detailUrl: 'test_url1'
+ },
+ {
+ number: 1,
+ title: 'Test title 2',
+ isClosed: false,
+ detailUrl: 'test_url2'
+ }
+ ];
beforeEach(async(() => {
const todoListService: jasmine.SpyObj<TodoListService> = jasmine.createSpyObj('TodoListService', ['getWorkItemList']);
- mockWorkItems = [
- {
- id: 0,
- title: 'Test title 1',
- isCompleted: true,
- detailUrl: 'https://test.org/workitems/0',
- iconUrl: 'https://test.org/icon/0'
- },
- {
- id: 1,
- title: 'Test title 2',
- isCompleted: false,
- detailUrl: 'https://test.org/workitems/1',
- iconUrl: 'https://test.org/icon/1'
- }
- ];
-
- todoListService.getWorkItemList.and.returnValue(asyncArray(mockWorkItems));
+ todoListService.getWorkItemList.and.returnValue(asyncArray(mockTodoItems));
TestBed.configureTestingModule({
declarations: [TodoListPageComponent, MatProgressBarStubComponent],
diff --git a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.ts b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.ts
index b04d1300..c62dd808 100644
--- a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.ts
+++ b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.ts
@@ -1,5 +1,5 @@
import { Component, OnInit } from '@angular/core';
-import { TodoListService, WorkItem } from './todo-list.service';
+import { TodoListService, TodoItem } from './todo-list.service';
import { trigger, transition, style, animate } from '@angular/animations';
@Component({
@@ -21,7 +21,7 @@ import { trigger, transition, style, animate } from '@angular/animations';
})
export class TodoListPageComponent implements OnInit {
- items: WorkItem[] = [];
+ items: TodoItem[] = [];
isLoadCompleted = false;
constructor(private todoService: TodoListService) {
diff --git a/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.spec.ts b/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.spec.ts
index 49b7bbc4..a2ad0cbd 100644
--- a/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.spec.ts
+++ b/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.spec.ts
@@ -2,8 +2,7 @@ import { TestBed } from '@angular/core/testing';
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import {
- TodoListService, WorkItem, AzureDevOpsAccessInfo,
- WiqlResult, WiqlWorkItemResult, WorkItemResult, WorkItemTypeResult
+ TodoListService, IssueResponse, IssueResponseItem, TodoItem
} from './todo-list.service';
import { toArray } from 'rxjs/operators';
@@ -14,80 +13,42 @@ describe('TodoListServiceService', () => {
}));
it('should be created', () => {
-
const service: TodoListService = TestBed.get(TodoListService);
expect(service).toBeTruthy();
});
it('should work well', () => {
const service: TodoListService = TestBed.get(TodoListService);
- expect(service).toBeTruthy();
- const mockAccessInfo: AzureDevOpsAccessInfo = {
- username: 'testusername',
- personalAccessToken: 'testtoken',
- organization: 'testorganization',
- project: 'testproject'
- };
-
- const baseUrl = `https://dev.azure.com/${mockAccessInfo.organization}/${mockAccessInfo.project}/`;
-
- const mockWorkItems: WorkItem[] = Array.from({ length: 2 }, (_, i) => <WorkItem>{
- id: i,
- title: 'Test work item ' + i,
- isCompleted: i === 0,
- detailUrl: `${baseUrl}_workitems/edit/${i}/`,
- iconUrl: `${baseUrl}_api/wit/icon/${i}`,
- });
-
- const workItemTypeMap = new Map<WorkItem, string>(Array.from(mockWorkItems, v => <[WorkItem, string]>[v, 'type' + v.id]));
+ const baseUrl = service.baseUrl;
+
+ const mockIssueList: IssueResponse = [{
+ number: 1,
+ title: 'Issue title 1',
+ state: 'open',
+ html_url: 'test_url1'
+ }, {
+ number: 2,
+ title: 'Issue title 2',
+ state: 'closed',
+ html_url: 'test_url2',
+ pull_request: {}
+ }];
+
+ const mockTodoItemList: TodoItem[] = [{
+ number: 1,
+ title: 'Issue title 1',
+ isClosed: false,
+ detailUrl: 'test_url1'
+ }];
service.getWorkItemList().pipe(toArray()).subscribe(data => {
- expect(data).toEqual(mockWorkItems);
+ expect(data).toEqual(mockTodoItemList);
});
const httpController: HttpTestingController = TestBed.get(HttpTestingController);
- httpController.expectOne('/api/TodoPage/AzureDevOpsAccessInfo').flush(mockAccessInfo);
-
- const mockWiqlWorkItems: WiqlWorkItemResult[] = Array.from(mockWorkItems, v => <WiqlWorkItemResult>{
- id: v.id,
- url: `${baseUrl}_apis/wit/workItems/${v.id}`
- });
-
- const authorizationHeader = 'Basic ' + btoa(mockAccessInfo.username + ':' + mockAccessInfo.personalAccessToken);
-
- httpController.expectOne(req =>
- req.url === `${baseUrl}_apis/wit/wiql?api-version=5.0` &&
- req.headers.get('Authorization') === authorizationHeader
- ).flush(<WiqlResult>{ workItems: mockWiqlWorkItems });
-
- function mapWorkItemToResult(mockWorkItem: WorkItem): WorkItemResult {
- return {
- id: mockWorkItem.id,
- fields: {
- [TodoListService.titleFieldName]: mockWorkItem.title,
- [TodoListService.stateFieldName]: (mockWorkItem.isCompleted ? 'Closed' : 'Active'),
- [TodoListService.typeFieldName]: workItemTypeMap.get(mockWorkItem)
- }
- };
- }
-
- for (let i = 0; i < mockWorkItems.length; i++) {
- httpController.expectOne(req =>
- req.url === mockWiqlWorkItems[i].url &&
- req.headers.get('Authorization') === authorizationHeader
- ).flush(mapWorkItemToResult(mockWorkItems[i]));
-
- httpController.expectOne(req =>
- req.url === `${baseUrl}_apis/wit/workitemtypes/${encodeURIComponent(workItemTypeMap.get(mockWorkItems[i]))}?api-version=5.0` &&
- req.headers.get('Authorization') === authorizationHeader
- ).flush(<WorkItemTypeResult>{
- icon: {
- url: mockWorkItems[i].iconUrl
- }
- });
- }
+ httpController.expectOne(request => request.url === baseUrl + '/issues' && request.params.get('state') === 'all').flush(mockIssueList);
httpController.verify();
});
diff --git a/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.ts b/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.ts
index bfeb3285..ffcbbc6f 100644
--- a/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.ts
+++ b/Timeline/ClientApp/src/app/todo-list-page/todo-list.service.ts
@@ -1,101 +1,47 @@
import { Injectable } from '@angular/core';
-import { HttpClient, HttpHeaders } from '@angular/common/http';
+import { HttpClient } from '@angular/common/http';
import { Observable, from } from 'rxjs';
-import { switchMap, concatMap, map } from 'rxjs/operators';
+import { switchMap, map, filter } from 'rxjs/operators';
-export interface AzureDevOpsAccessInfo {
- username: string;
- personalAccessToken: string;
- organization: string;
- project: string;
-}
-
-export interface WiqlWorkItemResult {
- id: number;
- url: string;
-}
-
-export interface WiqlResult {
- workItems: WiqlWorkItemResult[];
-}
-
-export interface WorkItemResult {
- id: number;
- fields: { [name: string]: any };
+export interface IssueResponseItem {
+ number: number;
+ title: string;
+ state: string;
+ html_url: string;
+ pull_request?: any;
}
-export interface WorkItemTypeResult {
- icon: {
- url: string;
- };
-}
+export type IssueResponse = IssueResponseItem[];
-export interface WorkItem {
- id: number;
+export interface TodoItem {
+ number: number;
title: string;
- isCompleted: boolean;
+ isClosed: boolean;
detailUrl: string;
- iconUrl: string;
}
@Injectable({
providedIn: 'root'
})
export class TodoListService {
- public static titleFieldName = 'System.Title';
- public static stateFieldName = 'System.State';
- public static typeFieldName = 'System.WorkItemType';
-
- constructor(private client: HttpClient) {}
-
- private getAzureDevOpsAccessInfo(): Observable<AzureDevOpsAccessInfo> {
- return this.client.get<AzureDevOpsAccessInfo>('/api/TodoPage/AzureDevOpsAccessInfo');
- }
-
- private getItemIconUrl(baseUrl: string, headers: HttpHeaders, type: string): Observable<string> {
- return this.client
- .get<WorkItemTypeResult>(`${baseUrl}_apis/wit/workitemtypes/${encodeURIComponent(type)}?api-version=5.0`, {
- headers: headers
- })
- .pipe(map(result => result.icon.url));
- }
- getWorkItemList(): Observable<WorkItem> {
- return this.getAzureDevOpsAccessInfo().pipe(
- switchMap(accessInfo => {
- const baseUrl = `https://dev.azure.com/${accessInfo.organization}/${accessInfo.project}/`;
- const headers = new HttpHeaders({
- Accept: 'application/json',
- Authorization: `Basic ${btoa(accessInfo.username + ':' + accessInfo.personalAccessToken)}`
- });
- return this.client
- .post<WiqlResult>(
- `${baseUrl}_apis/wit/wiql?api-version=5.0`,
- {
- query: 'SELECT [System.Id] FROM workitems WHERE [System.TeamProject] = @project'
- },
- { headers: headers }
- )
- .pipe(
- concatMap(result => from(result.workItems)),
- concatMap(result => this.client.get<WorkItemResult>(result.url, { headers: headers })),
- concatMap(result =>
- this.getItemIconUrl(baseUrl, headers, result.fields[TodoListService.typeFieldName]).pipe(
- map(
- iconResult =>
- <WorkItem>{
- id: result.id,
- title: <string>result.fields[TodoListService.titleFieldName],
- isCompleted: (function(stateErasedCase: string): Boolean {
- return stateErasedCase === 'closed' || stateErasedCase === 'resolved';
- })((result.fields[TodoListService.stateFieldName] as string).toLowerCase()),
- detailUrl: `${baseUrl}_workitems/edit/${result.id}/`,
- iconUrl: iconResult
- }
- )
- )
- )
- );
+ readonly baseUrl = 'https://api.github.com/repos/crupest/Timeline';
+
+ constructor(private client: HttpClient) { }
+
+ getWorkItemList(): Observable<TodoItem> {
+ return this.client.get<IssueResponse>(`${this.baseUrl}/issues`, {
+ params: {
+ state: 'all'
+ }
+ }).pipe(
+ switchMap(result => from(result)),
+ filter(result => result.pull_request === undefined), // filter out pull requests.
+ map(result => <TodoItem>{
+ number: result.number,
+ title: result.title,
+ isClosed: result.state === 'closed',
+ detailUrl: result.html_url
})
);
}