diff options
Diffstat (limited to 'Timeline/ClientApp/src/app/todo-list-page')
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 }) ); } |