From 8b72c82efc63dd781635a939a93982ec693b6930 Mon Sep 17 00:00:00 2001 From: crupest Date: Sat, 23 Feb 2019 00:51:45 +0800 Subject: Add icon and redesign card in todo page. --- .../todo-list-page/todo-list-page.component.css | 31 ++++++----- .../todo-list-page/todo-list-page.component.html | 12 ++--- .../todo-list-page.component.spec.ts | 4 +- .../app/todo-list-page/todo-list.service.spec.ts | 63 ++++++++++++---------- .../src/app/todo-list-page/todo-list.service.ts | 23 ++++++-- 5 files changed, 79 insertions(+), 54 deletions(-) (limited to 'Timeline/ClientApp/src') diff --git a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.css b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.css index 89a0f0ce..ed260d5c 100644 --- a/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.css +++ b/Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.css @@ -13,24 +13,19 @@ justify-content: space-between; } -.item-id { - display: inline-block; - text-align: center; - border-radius: 0.2rem; - width: 1.2rem; - height: 1.2rem; -} - -.item-id-open { - background: red; +.item-card { + display: flex; + align-items: center; } -.item-id-closed { - background: green; +.item-img { + padding: 1px; + background: white; + border-radius: 3px; } .item-title { - margin-left: 5px; + padding-left: 5px; color: black; text-decoration: none; } @@ -45,9 +40,17 @@ align-items: center; } -.item-id-sample { +.sample-color-block { border-radius: 0.2em; width: 1em; height: 1em; margin-right: 2px; } + +.sample-color-block-open { + background: tomato; +} + +.sample-color-block-closed { + background: mediumspringgreen; +} 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 4e1aa2f1..0dcc8a94 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 @@ -3,17 +3,17 @@
- - {{item.id}} - {{item.title}} + + + {{item.id}} {{item.title}}
- means working now. + means working now.
- means completed. + means completed.
click on item to see details.
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 2da74002..9543f7ad 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 @@ -30,9 +30,9 @@ describe('TodoListPageComponent', () => { const todoListService: jasmine.SpyObj = jasmine.createSpyObj('TodoListService', ['getWorkItemList']); mockWorkItems = [{ - id: 0, title: 'Test title 1', closed: true, detailUrl: 'https://test.org/workitems/0' + id: 0, title: 'Test title 1', closed: true, detailUrl: 'https://test.org/workitems/0', iconUrl: 'https://test.org/icon/0' }, { - id: 1, title: 'Test title 2', closed: false, detailUrl: 'https://test.org/workitems/1' + id: 1, title: 'Test title 2', closed: false, detailUrl: 'https://test.org/workitems/1', iconUrl: 'https://test.org/icon/1' }]; todoListService.getWorkItemList.and.returnValue(asyncData(mockWorkItems)); 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 3ed4e004..13bae5be 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 @@ -1,7 +1,10 @@ import { TestBed } from '@angular/core/testing'; import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; -import { TodoListService, WorkItem, AzureDevOpsAccessInfo, WiqlResult, WiqlWorkItemResult, WorkItemResult } from './todo-list.service'; +import { + TodoListService, WorkItem, AzureDevOpsAccessInfo, + WiqlResult, WiqlWorkItemResult, WorkItemResult, WorkItemTypeResult +} from './todo-list.service'; describe('TodoListServiceService', () => { @@ -26,20 +29,17 @@ describe('TodoListServiceService', () => { project: 'testproject' }; - const generateDetailUrl = (id: number) => - `https://dev.azure.com/${mockAccessInfo.organization}/${mockAccessInfo.project}/_workitems/edit/${id}/`; - - const mockWorkItems: WorkItem[] = [{ - id: 0, - title: 'Test work item 1', - closed: true, - detailUrl: generateDetailUrl(0) - }, { - id: 1, - title: 'Test work item 2', - closed: false, - detailUrl: generateDetailUrl(1) - }]; + const baseUrl = `https://dev.azure.com/${mockAccessInfo.organization}/${mockAccessInfo.project}/`; + + const mockWorkItems: WorkItem[] = Array.from({ length: 2 }, (_, i) => { + id: i, + title: 'Test work item ' + i, + closed: i === 0, + detailUrl: `${baseUrl}_workitems/edit/${i}/`, + iconUrl: `${baseUrl}_api/wit/icon/${i}`, + }); + + const workItemTypeMap = new Map(Array.from(mockWorkItems, v => <[WorkItem, string]>[v, 'type' + v.id])); service.getWorkItemList().subscribe(data => { expect(data).toEqual(mockWorkItems); @@ -47,31 +47,27 @@ describe('TodoListServiceService', () => { const httpController: HttpTestingController = TestBed.get(HttpTestingController); - - httpController.expectOne('/api/TodoPage/AzureDevOpsAccessInfo').flush(mockAccessInfo); - const mockWiqlWorkItems: WiqlWorkItemResult[] = [{ - id: 0, - url: `https://dev.azure.com/${mockAccessInfo.organization}/${mockAccessInfo.project}/_apis/wit/workItems/0` - }, { - id: 1, - url: `https://dev.azure.com/${mockAccessInfo.organization}/${mockAccessInfo.project}/_apis/wit/workItems/1` - }]; + const mockWiqlWorkItems: WiqlWorkItemResult[] = Array.from(mockWorkItems, v => { + id: v.id, + url: `${baseUrl}_apis/wit/workItems/${v.id}` + }); const authorizationHeader = 'Basic ' + btoa(mockAccessInfo.username + ':' + mockAccessInfo.personalAccessToken); httpController.expectOne(req => - req.url === `https://dev.azure.com/${mockAccessInfo.organization}/${mockAccessInfo.project}/_apis/wit/wiql?api-version=5.0` && + req.url === `${baseUrl}_apis/wit/wiql?api-version=5.0` && req.headers.get('Authorization') === authorizationHeader ).flush({ workItems: mockWiqlWorkItems }); - function mapWorkItemToResult(workItem: WorkItem): WorkItemResult { + function mapWorkItemToResult(mockWorkItem: WorkItem): WorkItemResult { return { - id: workItem.id, + id: mockWorkItem.id, fields: { - [TodoListService.titleFieldName]: workItem.title, - [TodoListService.stateFieldName]: (workItem.closed ? 'Closed' : 'Active') + [TodoListService.titleFieldName]: mockWorkItem.title, + [TodoListService.stateFieldName]: (mockWorkItem.closed ? 'Closed' : 'Active'), + [TodoListService.typeFieldName]: workItemTypeMap.get(mockWorkItem) } }; } @@ -81,6 +77,15 @@ describe('TodoListServiceService', () => { 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({ + icon: { + url: mockWorkItems[i].iconUrl + } + }); } }); }); 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 ed28bc59..17ded67b 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 @@ -24,12 +24,18 @@ export interface WorkItemResult { fields: { [name: string]: any }; } +export interface WorkItemTypeResult { + icon: { + url: string + }; +} export interface WorkItem { id: number; title: string; closed: boolean; detailUrl: string; + iconUrl: string; } @Injectable({ @@ -39,6 +45,7 @@ export class TodoListService { public static titleFieldName = 'System.Title'; public static stateFieldName = 'System.State'; + public static typeFieldName = 'System.WorkItemType'; constructor(private client: HttpClient) { } @@ -46,6 +53,14 @@ export class TodoListService { return this.client.get('/api/TodoPage/AzureDevOpsAccessInfo'); } + private getItemIconUrl(baseUrl: string, headers: HttpHeaders, type: string): Observable { + return this.client.get(`${baseUrl}_apis/wit/workitemtypes/${encodeURIComponent(type)}?api-version=5.0`, { + headers: headers + }).pipe( + map(result => result.icon.url) + ); + } + getWorkItemList(): Observable { return this.getAzureDevOpsAccessInfo().pipe( switchMap( @@ -61,12 +76,14 @@ export class TodoListService { }, { headers: headers }).pipe( switchMap(result => result.workItems), concatMap(result => this.client.get(result.url, { headers: headers })), - map(result => { + concatMap(result => this.getItemIconUrl(baseUrl, headers, result.fields[TodoListService.typeFieldName]).pipe( + map(iconResult => { id: result.id, title: result.fields[TodoListService.titleFieldName], closed: ((result.fields[TodoListService.stateFieldName]).toLowerCase() === 'closed'), - detailUrl: `${baseUrl}_workitems/edit/${result.id}/` - }), + detailUrl: `${baseUrl}_workitems/edit/${result.id}/`, + iconUrl: iconResult + }))), toArray() ); } -- cgit v1.2.3