diff options
author | crupest <crupest@outlook.com> | 2019-02-23 00:51:45 +0800 |
---|---|---|
committer | crupest <crupest@outlook.com> | 2019-02-23 00:51:45 +0800 |
commit | 8b72c82efc63dd781635a939a93982ec693b6930 (patch) | |
tree | 7ae9593aa65a091f83492c67244f9743b5cfc461 /Timeline/ClientApp/src | |
parent | e236b5064cd62f40bc910fafe48ac4b9701a4bcd (diff) | |
download | timeline-8b72c82efc63dd781635a939a93982ec693b6930.tar.gz timeline-8b72c82efc63dd781635a939a93982ec693b6930.tar.bz2 timeline-8b72c82efc63dd781635a939a93982ec693b6930.zip |
Add icon and redesign card in todo page.
Diffstat (limited to 'Timeline/ClientApp/src')
5 files changed, 79 insertions, 54 deletions
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 @@ <mat-list> <mat-list-item *ngFor="let item of items; let i = index" style="height:unset;"> <div class="item-box" [class.first-item-box]="i === 0"> - <mat-card class="mat-elevation-z2" [class.align-self-bottom]="i === 0"> - <span class="item-id" [class.item-id-closed]="item.closed" - [class.item-id-open]="!item.closed">{{item.id}}</span> - <a class="mat-h3 item-title" [href]="item.detailUrl">{{item.title}}</a> + <mat-card class="mat-elevation-z2 item-card" [style.background]="item.closed ? 'mediumspringgreen' : 'tomato'" + [class.align-self-bottom]="i === 0"> + <img class="item-img" width="18" height="18" [src]="item.iconUrl"> + <a class="mat-h3 item-title" [href]="item.detailUrl">{{item.id}} {{item.title}}</a> </mat-card> <div class="sample-box" *ngIf="i === 0"> <div class="mat-caption sample-container"> - <span class="item-id-sample item-id-open"></span> means working now. + <span class="sample-color-block sample-color-block-open"></span> means working now. </div> <div class="mat-caption sample-container"> - <span class="item-id-sample item-id-closed"></span> means completed. + <span class="sample-color-block sample-color-block-closed"></span> means completed. </div> <div class="mat-caption">click on item to see details.</div> </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 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<TodoListService> = 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) => <WorkItem>{ + 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<WorkItem, string>(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 => <WiqlWorkItemResult>{ + 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(<WiqlResult>{ 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(<WorkItemTypeResult>{ + 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<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( @@ -61,12 +76,14 @@ export class TodoListService { }, { headers: headers }).pipe( switchMap(result => result.workItems), concatMap(result => this.client.get<WorkItemResult>(result.url, { headers: headers })), - map(result => <WorkItem>{ + concatMap(result => this.getItemIconUrl(baseUrl, headers, result.fields[TodoListService.typeFieldName]).pipe( + map(iconResult => <WorkItem>{ id: result.id, title: <string>result.fields[TodoListService.titleFieldName], closed: ((<string>result.fields[TodoListService.stateFieldName]).toLowerCase() === 'closed'), - detailUrl: `${baseUrl}_workitems/edit/${result.id}/` - }), + detailUrl: `${baseUrl}_workitems/edit/${result.id}/`, + iconUrl: iconResult + }))), toArray() ); } |