aboutsummaryrefslogtreecommitdiff
path: root/Timeline/ClientApp/src/app/todo-list-page
diff options
context:
space:
mode:
author杨宇千 <crupest@outlook.com>2019-02-22 15:24:26 +0000
committer杨宇千 <crupest@outlook.com>2019-02-22 15:24:26 +0000
commitfe5838a966a314b72ecb4d992f86f5fce635a96f (patch)
tree6d00a55b9e5f98315384f33aaf5bfc5a60645a0d /Timeline/ClientApp/src/app/todo-list-page
parent25e606b67506b3bc0242414aebe472f13a02bd9c (diff)
parente236b5064cd62f40bc910fafe48ac4b9701a4bcd (diff)
downloadtimeline-fe5838a966a314b72ecb4d992f86f5fce635a96f.tar.gz
timeline-fe5838a966a314b72ecb4d992f86f5fce635a96f.tar.bz2
timeline-fe5838a966a314b72ecb4d992f86f5fce635a96f.zip
Merged PR 5: Develop link feature on todo page.
Develop link feature on todo page. Related work items: #1, #3
Diffstat (limited to 'Timeline/ClientApp/src/app/todo-list-page')
-rw-r--r--Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.css41
-rw-r--r--Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.html17
-rw-r--r--Timeline/ClientApp/src/app/todo-list-page/todo-list-page.component.spec.ts26
-rw-r--r--Timeline/ClientApp/src/app/todo-list-page/todo-list.service.spec.ts23
-rw-r--r--Timeline/ClientApp/src/app/todo-list-page/todo-list.service.ts11
5 files changed, 77 insertions, 41 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 6a603863..89a0f0ce 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
@@ -2,20 +2,15 @@
align-self: flex-end;
}
-.first-item-box {
- justify-content: space-between;
-}
-
.item-box {
display: flex;
width: 100%;
- padding: 10px;
+ padding: 5px;
box-sizing: border-box;
}
-.sample-box {
- box-sizing: border-box;
- align-self: flex-start;
+.first-item-box {
+ justify-content: space-between;
}
.item-id {
@@ -26,13 +21,6 @@
height: 1.2rem;
}
-.item-id-sample {
- display: inline-block;
- border-radius: 0.2em;
- width: 1em;
- height: 1em;
-}
-
.item-id-open {
background: red;
}
@@ -40,3 +28,26 @@
.item-id-closed {
background: green;
}
+
+.item-title {
+ margin-left: 5px;
+ color: black;
+ text-decoration: none;
+}
+
+.sample-box {
+ box-sizing: border-box;
+ align-self: flex-start;
+}
+
+.sample-container {
+ display: flex;
+ align-items: center;
+}
+
+.item-id-sample {
+ border-radius: 0.2em;
+ width: 1em;
+ height: 1em;
+ margin-right: 2px;
+}
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 ce6eb2ed..4e1aa2f1 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,19 +3,20 @@
<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.align-self-bottom]="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>
- {{item.title}}
+ <a class="mat-h3 item-title" [href]="item.detailUrl">{{item.title}}</a>
</mat-card>
<div class="sample-box" *ngIf="i === 0">
- <div class="mat-caption">
- <span class="item-id-sample item-id-open"></span> means working now.
- </div>
- <div class="mat-caption">
- <span class="item-id-sample item-id-closed"></span> means completed.
- </div>
+ <div class="mat-caption sample-container">
+ <span class="item-id-sample item-id-open"></span> means working now.
</div>
+ <div class="mat-caption sample-container">
+ <span class="item-id-sample item-id-closed"></span> means completed.
+ </div>
+ <div class="mat-caption">click on item to see details.</div>
+ </div>
</div>
</mat-list-item>
</mat-list>
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 e01e91fb..2da74002 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
@@ -8,8 +8,8 @@ import { TodoListService, WorkItem } from './todo-list.service';
import { By } from '@angular/platform-browser';
@Component({
-/* tslint:disable-next-line:component-selector*/
-selector: 'mat-progress-bar',
+ /* tslint:disable-next-line:component-selector*/
+ selector: 'mat-progress-bar',
template: ''
})
class MatProgressBarStubComponent {
@@ -24,14 +24,18 @@ describe('TodoListPageComponent', () => {
let component: TodoListPageComponent;
let fixture: ComponentFixture<TodoListPageComponent>;
+ let mockWorkItems: WorkItem[];
+
beforeEach(async(() => {
const todoListService: jasmine.SpyObj<TodoListService> = jasmine.createSpyObj('TodoListService', ['getWorkItemList']);
- todoListService.getWorkItemList.and.returnValue(asyncData(<WorkItem[]>[{
- id: 0, title: 'Test title 1', closed: true
+ mockWorkItems = [{
+ id: 0, title: 'Test title 1', closed: true, detailUrl: 'https://test.org/workitems/0'
}, {
- id: 1, title: 'Test title 2', closed: false
- }]));
+ id: 1, title: 'Test title 2', closed: false, detailUrl: 'https://test.org/workitems/1'
+ }];
+
+ todoListService.getWorkItemList.and.returnValue(asyncData(mockWorkItems));
TestBed.configureTestingModule({
declarations: [TodoListPageComponent, MatProgressBarStubComponent],
@@ -64,4 +68,14 @@ describe('TodoListPageComponent', () => {
fixture.detectChanges();
expect(fixture.debugElement.query(By.css('mat-progress-bar'))).toBeFalsy();
}));
+
+ it('should set href on item title', fakeAsync(() => {
+ fixture.detectChanges();
+ tick();
+ fixture.detectChanges();
+
+ fixture.debugElement.queryAll(By.css('a.item-title')).forEach((element, index) => {
+ expect(element.properties['href']).toBe(mockWorkItems[index].detailUrl);
+ });
+ }));
});
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 7e88ca52..3ed4e004 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
@@ -19,14 +19,26 @@ describe('TodoListServiceService', () => {
const service: TodoListService = TestBed.get(TodoListService);
expect(service).toBeTruthy();
+ const mockAccessInfo: AzureDevOpsAccessInfo = {
+ username: 'testusername',
+ personalAccessToken: 'testtoken',
+ organization: 'testorganization',
+ 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
+ closed: true,
+ detailUrl: generateDetailUrl(0)
}, {
id: 1,
title: 'Test work item 2',
- closed: false
+ closed: false,
+ detailUrl: generateDetailUrl(1)
}];
service.getWorkItemList().subscribe(data => {
@@ -35,12 +47,7 @@ describe('TodoListServiceService', () => {
const httpController: HttpTestingController = TestBed.get(HttpTestingController);
- const mockAccessInfo: AzureDevOpsAccessInfo = {
- username: 'testusername',
- personalAccessToken: 'testtoken',
- organization: 'testorganization',
- project: 'testproject'
- };
+
httpController.expectOne('/api/TodoPage/AzureDevOpsAccessInfo').flush(mockAccessInfo);
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 619e9a6b..ed28bc59 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
@@ -29,6 +29,7 @@ export interface WorkItem {
id: number;
title: string;
closed: boolean;
+ detailUrl: string;
}
@Injectable({
@@ -41,20 +42,21 @@ export class TodoListService {
constructor(private client: HttpClient) { }
- private getAzureDevOpsPat(): Observable<AzureDevOpsAccessInfo> {
+ private getAzureDevOpsAccessInfo(): Observable<AzureDevOpsAccessInfo> {
return this.client.get<AzureDevOpsAccessInfo>('/api/TodoPage/AzureDevOpsAccessInfo');
}
getWorkItemList(): Observable<WorkItem[]> {
- return this.getAzureDevOpsPat().pipe(
+ 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>(
- `https://dev.azure.com/${accessInfo.organization}/${accessInfo.project}/_apis/wit/wiql?api-version=5.0`, {
+ `${baseUrl}_apis/wit/wiql?api-version=5.0`, {
query: 'SELECT [System.Id] FROM workitems WHERE [System.TeamProject] = @project'
}, { headers: headers }).pipe(
switchMap(result => result.workItems),
@@ -62,7 +64,8 @@ export class TodoListService {
map(result => <WorkItem>{
id: result.id,
title: <string>result.fields[TodoListService.titleFieldName],
- closed: ((<string>result.fields[TodoListService.stateFieldName]).toLowerCase() === 'closed')
+ closed: ((<string>result.fields[TodoListService.stateFieldName]).toLowerCase() === 'closed'),
+ detailUrl: `${baseUrl}_workitems/edit/${result.id}/`
}),
toArray()
);