Skip to content

Commit b8df7c1

Browse files
Internal: Improve timespan component (#1025)
1 parent ac2ff40 commit b8df7c1

File tree

9 files changed

+216
-69
lines changed

9 files changed

+216
-69
lines changed

app/components/base/base.module.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ import { DialogsModule } from "./dialogs";
1818
import { DropdownModule } from "./dropdown";
1919
import { DurationPickerComponent } from "./duration-picker";
2020
import { EditorModule } from "./editor";
21-
import { ElapsedTimeComponent } from "./elapsed-time";
2221
import { FocusSectionModule } from "./focus-section";
2322
import { FormModule } from "./form";
2423
import { GraphsModule } from "./graphs";
@@ -41,6 +40,7 @@ import { SummaryCardModule } from "./summary-card";
4140
import { TableModule } from "./table";
4241
import { TabsModule } from "./tabs";
4342
import { TagsModule } from "./tags";
43+
import { TimespanComponent } from "./timespan";
4444
import { VirtualScrollModule } from "./virtual-scroll";
4545
import { VTabsModule } from "./vtabs";
4646

@@ -79,7 +79,7 @@ const components = [
7979
BannerComponent,
8080
BannerOtherFixDirective,
8181
CardComponent,
82-
ElapsedTimeComponent,
82+
TimespanComponent,
8383
EntityDetailsListComponent,
8484
DurationPickerComponent,
8585
IconComponent,

app/components/base/elapsed-time/elapsed-time.component.ts

Lines changed: 0 additions & 62 deletions
This file was deleted.

app/components/base/elapsed-time/index.ts

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from "./timespan.component";
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import { Component, DebugElement } from "@angular/core";
2+
import { ComponentFixture, TestBed, discardPeriodicTasks, fakeAsync, tick } from "@angular/core/testing";
3+
import { By } from "@angular/platform-browser";
4+
import * as moment from "moment";
5+
6+
import { TimespanComponent, TimespanDisplayType } from "./timespan.component";
7+
8+
@Component({
9+
template: `
10+
<bl-timespan [startTime]="startTime" [endTime]="endTime" type="type">
11+
</bl-timespan>
12+
`,
13+
})
14+
class TestComponent {
15+
public startTime: Date = null;
16+
public endTime: Date = null;
17+
public type = TimespanDisplayType.compact;
18+
}
19+
20+
describe("TimespanComponent", () => {
21+
let fixture: ComponentFixture<TestComponent>;
22+
let testComponent: TestComponent;
23+
let component: TimespanComponent;
24+
let de: DebugElement;
25+
let nowDate;
26+
27+
function passTime(milli: number) {
28+
nowDate = nowDate.add(milli, "milliseconds");
29+
tick(milli);
30+
}
31+
32+
beforeEach(() => {
33+
nowDate = moment.utc();
34+
TestBed.configureTestingModule({
35+
imports: [],
36+
declarations: [TimespanComponent, TestComponent],
37+
});
38+
fixture = TestBed.createComponent(TestComponent);
39+
testComponent = fixture.componentInstance;
40+
de = fixture.debugElement.query(By.css("bl-timespan"));
41+
component = de.componentInstance;
42+
fixture.detectChanges();
43+
spyOn(component, "now").and.callFake(() => nowDate);
44+
});
45+
46+
describe("when providing start and endtime", () => {
47+
beforeEach(() => {
48+
const start = moment();
49+
testComponent.startTime = start.toDate();
50+
testComponent.endTime = start.add(83, "seconds").toDate();
51+
fixture.detectChanges();
52+
});
53+
54+
it("should show the duration between start and end", () => {
55+
expect(de.nativeElement.textContent).toContain("1:23");
56+
});
57+
58+
it("should not update with time", fakeAsync(() => {
59+
tick(4000);
60+
fixture.detectChanges();
61+
expect(de.nativeElement.textContent).toContain("1:23");
62+
}));
63+
});
64+
65+
describe("when providing only start time", () => {
66+
function reset() {
67+
const start = moment().subtract(123, "seconds");
68+
testComponent.startTime = start.toDate();
69+
fixture.detectChanges();
70+
}
71+
72+
it("should show the duration between start and end", () => {
73+
reset();
74+
expect(de.nativeElement.textContent).toContain("2:03");
75+
});
76+
77+
it("should update with time", fakeAsync(() => {
78+
reset();
79+
passTime(4000);
80+
fixture.detectChanges();
81+
expect(de.nativeElement.textContent).toContain("2:07");
82+
passTime(1000);
83+
fixture.detectChanges();
84+
expect(de.nativeElement.textContent).toContain("2:08");
85+
discardPeriodicTasks();
86+
}));
87+
});
88+
89+
describe("when providing only end time", () => {
90+
function reset() {
91+
const end = moment().add(234, "seconds");
92+
testComponent.endTime = end.toDate();
93+
fixture.detectChanges();
94+
}
95+
96+
it("should show the duration between start and end", () => {
97+
reset();
98+
expect(de.nativeElement.textContent).toContain("3:54");
99+
});
100+
101+
it("should update with time", fakeAsync(() => {
102+
reset();
103+
passTime(4000);
104+
fixture.detectChanges();
105+
expect(de.nativeElement.textContent).toContain("3:50");
106+
passTime(1000);
107+
fixture.detectChanges();
108+
expect(de.nativeElement.textContent).toContain("3:49");
109+
discardPeriodicTasks();
110+
}));
111+
});
112+
});
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
import {
2+
ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, OnChanges, OnDestroy,
3+
} from "@angular/core";
4+
import * as moment from "moment";
5+
import { Subscription } from "rxjs";
6+
7+
import { DateUtils } from "app/utils";
8+
import { Observable } from "rxjs/Observable";
9+
10+
export enum TimespanDisplayType {
11+
humanized = "humanized",
12+
pretty = "pretty",
13+
compact = "compact",
14+
}
15+
@Component({
16+
selector: "bl-timespan",
17+
template: `{{formattedValue}}`,
18+
changeDetection: ChangeDetectionStrategy.OnPush,
19+
})
20+
export class TimespanComponent implements OnChanges, OnDestroy {
21+
public formattedValue = "";
22+
23+
@Input() public type: TimespanDisplayType = TimespanDisplayType.pretty;
24+
25+
@Input() public startTime: Date = null;
26+
27+
@Input() public endTime: Date = null;
28+
29+
private _intervalSub: Subscription;
30+
31+
constructor(private changeDetector: ChangeDetectorRef) {
32+
}
33+
34+
public ngOnChanges(changes) {
35+
if (changes.startTime || changes.endTime) {
36+
this._updateInterval();
37+
this._updateElapsedTime();
38+
}
39+
}
40+
41+
public _updateElapsedTime() {
42+
const duration = this._computeTimespan();
43+
const value = this._formatDuration(duration);
44+
if (value !== this.formattedValue) {
45+
this.formattedValue = value;
46+
this.changeDetector.markForCheck();
47+
}
48+
}
49+
50+
public now() {
51+
return moment.utc();
52+
}
53+
54+
public ngOnDestroy() {
55+
this._clearInterval();
56+
}
57+
58+
private _clearInterval() {
59+
if (this._intervalSub) {
60+
this._intervalSub.unsubscribe();
61+
this._intervalSub = null;
62+
}
63+
}
64+
65+
private _updateInterval() {
66+
this._clearInterval();
67+
if (this.startTime && this.endTime) { return; }
68+
if (!this.startTime && !this.endTime) { return; }
69+
this._intervalSub = Observable.interval(1000).subscribe(() => {
70+
this._updateElapsedTime();
71+
});
72+
}
73+
74+
private _computeTimespan() {
75+
if (!this.startTime && !this.endTime) { return null; }
76+
const startTime = this.startTime ? moment.utc(this.startTime) : this.now();
77+
const endTime = this.endTime ? moment.utc(this.endTime) : this.now();
78+
return moment.duration(endTime.diff(moment(startTime)));
79+
}
80+
81+
private _formatDuration(duration: any) {
82+
if (!duration) { return null; }
83+
switch (this.type) {
84+
case TimespanDisplayType.humanized:
85+
if (duration.asSeconds() >= 60) {
86+
return duration.humanize();
87+
} else {
88+
return DateUtils.prettyDuration(duration);
89+
}
90+
case TimespanDisplayType.pretty:
91+
return DateUtils.prettyDuration(duration);
92+
case TimespanDisplayType.compact:
93+
default:
94+
return DateUtils.compactDuration(duration);
95+
}
96+
}
97+
}

app/components/task/browse/preview/task-preview.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<div [ngSwitch]="task.state">
22
<div *ngSwitchCase="taskStates.running">
33
<i class="fa fa-clock-o" aria-hidden="true"></i>
4-
<bl-elapsed-time [startTime]="startTime"></bl-elapsed-time>
4+
<bl-timespan [startTime]="startTime"></bl-timespan>
55

66
</div>
77

app/components/task/details/task-timeline/task-timeline.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
<!-- Running-->
2222
<bl-task-timeline-state state="running" [currentState]="task.state">
2323
<div *ngIf="started" [class.warn]="isTaskTimeoutClose" [class.error]="task.didTimeout" [title]="timeoutMessage">
24-
<bl-elapsed-time [startTime]="task.executionInfo?.startTime" [endTime]="task.executionInfo?.endTime"></bl-elapsed-time>
24+
<bl-timespan [startTime]="task.executionInfo?.startTime" [endTime]="task.executionInfo?.endTime"></bl-timespan>
2525
</div>
2626
<div *ngIf="started && retryCount" class="warn">{{retryCount}} retries</div>
2727
</bl-task-timeline-state>

test/app/components/task/details/task-lifetime/task-timeline.component.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { By } from "@angular/platform-browser";
44
import { RouterTestingModule } from "@angular/router/testing";
55
import * as moment from "moment";
66

7-
import { ElapsedTimeComponent } from "app/components/base/elapsed-time";
7+
import { TimespanComponent } from "app/components/base/timespan";
88
import { TaskTimelineComponent, TaskTimelineStateComponent } from "app/components/task/details/task-timeline";
99
import { Job, Task, TaskState } from "app/models";
1010

@@ -50,7 +50,7 @@ describe("TaskTimelineComponent", () => {
5050
TestBed.configureTestingModule({
5151
imports: [RouterTestingModule],
5252
declarations: [
53-
ElapsedTimeComponent, TaskTimelineComponent, TaskTimelineMockComponent, TaskTimelineStateComponent,
53+
TimespanComponent, TaskTimelineComponent, TaskTimelineMockComponent, TaskTimelineStateComponent,
5454
],
5555
});
5656

0 commit comments

Comments
 (0)