]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
mgr/dashboard: displaying time in human-readable format 37757/head
authorVolker Theile <vtheile@suse.com>
Thu, 22 Oct 2020 09:17:31 +0000 (11:17 +0200)
committerVolker Theile <vtheile@suse.com>
Sun, 1 Nov 2020 08:53:20 +0000 (09:53 +0100)
Fixes: https://tracker.ceph.com/issues/47884
Signed-off-by: Volker Theile <vtheile@suse.com>
13 files changed:
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi/iscsi.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/block/iscsi/iscsi.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/monitor/monitor.component.html
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/monitor/monitor.component.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/service-daemon-list/service-daemon-list.component.ts
src/pybind/mgr/dashboard/frontend/src/app/ceph/cluster/services/services.component.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/components/notifications-sidebar/notifications-sidebar.component.html
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/duration.pipe.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/duration.pipe.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/not-available.pipe.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/not-available.pipe.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/relative-date.pipe.spec.ts
src/pybind/mgr/dashboard/frontend/src/app/shared/pipes/relative-date.pipe.ts

index 5ccdbf03807bcdffdd9a3ebb0c8dd6fcfb139c6f..eccb79514c1bf6ce6dc41a37a0a8d3aeb0eb73ff 100644 (file)
@@ -40,7 +40,7 @@
              let-row="row"
              let-value="value">
   <span *ngIf="row.backstore === 'user:rbd'">
-    {{ value | relativeDate }}
+    {{ value | relativeDate | notAvailable }}
   </span>
   <span *ngIf="row.backstore !== 'user:rbd'"
         class="text-muted">
index 4ac569c77a7baa394c632728128dd6a60b81d8fa..c081b7c31e8ae0d31ad4419e91c7e4025ce5bf62 100644 (file)
@@ -9,7 +9,6 @@ import { IscsiService } from '../../../shared/api/iscsi.service';
 import { CephShortVersionPipe } from '../../../shared/pipes/ceph-short-version.pipe';
 import { DimlessPipe } from '../../../shared/pipes/dimless.pipe';
 import { IscsiBackstorePipe } from '../../../shared/pipes/iscsi-backstore.pipe';
-import { RelativeDatePipe } from '../../../shared/pipes/relative-date.pipe';
 import { FormatterService } from '../../../shared/services/formatter.service';
 import { SharedModule } from '../../../shared/shared.module';
 import { IscsiComponent } from './iscsi.component';
@@ -36,7 +35,6 @@ describe('IscsiComponent', () => {
       CephShortVersionPipe,
       DimlessPipe,
       FormatterService,
-      RelativeDatePipe,
       IscsiBackstorePipe,
       { provide: IscsiService, useValue: fakeService }
     ]
index 70d8dc5befe97419abc364eb723212694e9901ac..ca9ac82212c78cc0b8a6c6955211da245c269d63 100644 (file)
@@ -13,7 +13,7 @@
         <tr>
           <td i18n
               class="bold">monmap modified</td>
-          <td>{{ mon_status.monmap.modified }}</td>
+          <td>{{ mon_status.monmap.modified | relativeDate }}</td>
         </tr>
         <tr>
           <td i18n
index 1df4af8810f988c4eaa282ace0eef1f165a8c097..d3e203acea2205f00d02dc5d1bbcac29019bbfeb 100644 (file)
@@ -6,6 +6,7 @@ import { of } from 'rxjs';
 
 import { configureTestBed } from '../../../../testing/unit-test-helper';
 import { MonitorService } from '../../../shared/api/monitor.service';
+import { SharedModule } from '../../../shared/shared.module';
 import { MonitorComponent } from './monitor.component';
 
 describe('MonitorComponent', () => {
@@ -14,7 +15,7 @@ describe('MonitorComponent', () => {
   let getMonitorSpy: jasmine.Spy;
 
   configureTestBed({
-    imports: [HttpClientTestingModule],
+    imports: [HttpClientTestingModule, SharedModule],
     declarations: [MonitorComponent],
     schemas: [NO_ERRORS_SCHEMA],
     providers: [MonitorService]
index e0df084e2990eb4a2d9680a0e3ddb8be859a3b28..f4589d2da12023c3f0a7f8abb05eae0a5e8520c5 100644 (file)
@@ -22,6 +22,7 @@ import { CellTemplate } from '../../../../shared/enum/cell-template.enum';
 import { CdTableColumn } from '../../../../shared/models/cd-table-column';
 import { CdTableFetchDataContext } from '../../../../shared/models/cd-table-fetch-data-context';
 import { Daemon } from '../../../../shared/models/daemon.interface';
+import { RelativeDatePipe } from '../../../../shared/pipes/relative-date.pipe';
 
 @Component({
   selector: 'cd-service-daemon-list',
@@ -52,7 +53,8 @@ export class ServiceDaemonListComponent implements OnInit, OnChanges, AfterViewI
   constructor(
     private hostService: HostService,
     private cephServiceService: CephServiceService,
-    private orchService: OrchestratorService
+    private orchService: OrchestratorService,
+    private relativeDatePipe: RelativeDatePipe
   ) {}
 
   ngOnInit() {
@@ -117,6 +119,7 @@ export class ServiceDaemonListComponent implements OnInit, OnChanges, AfterViewI
       {
         name: $localize`Last Refreshed`,
         prop: 'last_refresh',
+        pipe: this.relativeDatePipe,
         flexGrow: 2
       }
     ];
index b92e39bb31bc99e226378a3b33e2009c1a23d02b..5f065704c9952fd1b14ac9575f99f9557757137a 100644 (file)
@@ -19,6 +19,7 @@ import { OrchestratorFeature } from '../../../shared/models/orchestrator.enum';
 import { OrchestratorStatus } from '../../../shared/models/orchestrator.interface';
 import { Permissions } from '../../../shared/models/permissions';
 import { CephServiceSpec } from '../../../shared/models/service.interface';
+import { RelativeDatePipe } from '../../../shared/pipes/relative-date.pipe';
 import { AuthStorageService } from '../../../shared/services/auth-storage.service';
 import { ModalService } from '../../../shared/services/modal.service';
 import { TaskWrapperService } from '../../../shared/services/task-wrapper.service';
@@ -61,6 +62,7 @@ export class ServicesComponent extends ListWithDetails implements OnChanges, OnI
     private modalService: ModalService,
     private orchService: OrchestratorService,
     private cephServiceService: CephServiceService,
+    private relativeDatePipe: RelativeDatePipe,
     private taskWrapperService: TaskWrapperService,
     private urlBuilder: URLBuilderService
   ) {
@@ -119,6 +121,7 @@ export class ServicesComponent extends ListWithDetails implements OnChanges, OnI
       {
         name: $localize`Last Refreshed`,
         prop: 'status.last_refresh',
+        pipe: this.relativeDatePipe,
         flexGrow: 1
       }
     ];
index 6f55a882a3d1a32d6e3933e7e57b853b3bfcc5d0..bba23747b01d4be0aeceebd3f300887f4e0bd8a9 100644 (file)
@@ -83,7 +83,7 @@
                   <br>
                 </ng-container>
                 <small class="date"
-                       [title]="notification.timestamp | cdDate">{{ notification.timestamp | duration: true }}</small>
+                       [title]="notification.timestamp | cdDate">{{ notification.timestamp | relativeDate }}</small>
                 <i class="float-right custom-icon"
                    [ngClass]="[notification.applicationClass]"
                    [title]="notification.application"></i>
index a97f8a751666b921fa68896c1785eca101b8ef03..1b0e22578aafee7f4c0812e49f763897456f7e60 100644 (file)
@@ -1,5 +1,3 @@
-import moment from 'moment';
-
 import { DurationPipe } from './duration.pipe';
 
 describe('DurationPipe', () => {
@@ -16,9 +14,4 @@ describe('DurationPipe', () => {
     expect(pipe.transform(600)).toBe('10 minutes');
     expect(pipe.transform(6000)).toBe('1 hour 40 minutes');
   });
-
-  it('transforms date into a human readable relative duration', () => {
-    const date = moment().subtract(130, 'seconds');
-    expect(pipe.transform(date, true)).toBe('2 minutes ago');
-  });
 });
index 407de929154e89c39cf282d130a368fade962ae7..4675fc0f6c6d6fbe5c85af7b513c160d8353139f 100644 (file)
@@ -1,20 +1,10 @@
 import { Pipe, PipeTransform } from '@angular/core';
 
-import moment from 'moment';
-
 @Pipe({
   name: 'duration',
   pure: false
 })
 export class DurationPipe implements PipeTransform {
-  transform(date: any, isRelative = false): string {
-    if (isRelative) {
-      return moment(date).fromNow();
-    } else {
-      return this._forHumans(date);
-    }
-  }
-
   /**
    * Translates seconds into human readable format of seconds, minutes, hours, days, and years
    * source: https://stackoverflow.com/a/34270811
@@ -22,7 +12,7 @@ export class DurationPipe implements PipeTransform {
    * @param  {number} seconds The number of seconds to be processed
    * @return {string}         The phrase describing the the amount of time
    */
-  _forHumans(seconds: number): string {
+  transform(seconds: number): string {
     const levels = [
       [`${Math.floor(seconds / 31536000)}`, 'years'],
       [`${Math.floor((seconds % 31536000) / 86400)}`, 'days'],
index 9d36defb937f95743784431a4aa5bdb20ce0729b..06279a5ea96e6cb21005e67365814941d3750f40 100644 (file)
@@ -11,12 +11,20 @@ describe('NotAvailablePipe', () => {
     expect(pipe).toBeTruthy();
   });
 
-  it('transforms not available', () => {
+  it('transforms not available (1)', () => {
     expect(pipe.transform('')).toBe('n/a');
   });
 
-  it('transforms number', () => {
+  it('transforms not available (2)', () => {
+    expect(pipe.transform('', 'Unknown')).toBe('Unknown');
+  });
+
+  it('transform not necessary (1)', () => {
     expect(pipe.transform(0)).toBe(0);
     expect(pipe.transform(1)).toBe(1);
   });
+
+  it('transform not necessary (2)', () => {
+    expect(pipe.transform('foo')).toBe('foo');
+  });
 });
index 17184c38ae10e0762517acbba991bd35624f0ecd..9d0222724782cbcd4aac7a2fb8df0bf68ac202a1 100644 (file)
@@ -1,12 +1,14 @@
 import { Pipe, PipeTransform } from '@angular/core';
 
+import _ from 'lodash';
+
 @Pipe({
   name: 'notAvailable'
 })
 export class NotAvailablePipe implements PipeTransform {
-  transform(value: any): any {
+  transform(value: any, text?: string): any {
     if (value === '') {
-      return $localize`n/a`;
+      return _.defaultTo(text, $localize`n/a`);
     }
     return value;
   }
index 97859d10646218b79d793710a0930b0b66484555..a12d3c2a11d16bb36df62221279aebc27de11189 100644 (file)
@@ -9,17 +9,36 @@ describe('RelativeDatePipe', () => {
     expect(pipe).toBeTruthy();
   });
 
-  it('transforms without value', () => {
-    expect(pipe.transform(undefined)).toBe('unknown');
+  it('transforms date into a human readable relative time (1)', () => {
+    const date: Date = moment().subtract(130, 'seconds').toDate();
+    expect(pipe.transform(date)).toBe('2 minutes ago');
   });
 
-  it('transforms "in 7 days"', () => {
-    const value = moment().add(7, 'days').unix();
-    expect(pipe.transform(value)).toBe('in 7 days');
+  it('transforms date into a human readable relative time (2)', () => {
+    const date: Date = moment().subtract(65, 'minutes').toDate();
+    expect(pipe.transform(date)).toBe('An hour ago');
   });
 
-  it('transforms "7 days ago"', () => {
-    const value = moment().subtract(7, 'days').unix();
-    expect(pipe.transform(value)).toBe('7 days ago');
+  it('transforms date into a human readable relative time (3)', () => {
+    const date: string = moment().subtract(130, 'minutes').toISOString();
+    expect(pipe.transform(date)).toBe('2 hours ago');
+  });
+
+  it('transforms date into a human readable relative time (4)', () => {
+    const date: string = moment().subtract(30, 'seconds').toISOString();
+    expect(pipe.transform(date, false)).toBe('a few seconds ago');
+  });
+
+  it('transforms date into a human readable relative time (5)', () => {
+    const date: number = moment().subtract(3, 'days').unix();
+    expect(pipe.transform(date)).toBe('3 days ago');
+  });
+
+  it('invalid input (1)', () => {
+    expect(pipe.transform('')).toBe('');
+  });
+
+  it('invalid input (2)', () => {
+    expect(pipe.transform('2011-10-10T10:20:90')).toBe('');
   });
 });
index 1718ee258b30d6b6c70345bbe2be7b9a2728b9be..f802b6b2add174a0124dad8eb1a20c3bc9b7b24c 100644 (file)
@@ -1,15 +1,57 @@
 import { Pipe, PipeTransform } from '@angular/core';
 
+import _ from 'lodash';
 import moment from 'moment';
 
+moment.updateLocale('en', {
+  relativeTime: {
+    future: $localize`in %s`,
+    past: $localize`%s ago`,
+    s: $localize`a few seconds`,
+    ss: $localize`%d seconds`,
+    m: $localize`a minute`,
+    mm: $localize`%d minutes`,
+    h: $localize`an hour`,
+    hh: $localize`%d hours`,
+    d: $localize`a day`,
+    dd: $localize`%d days`,
+    w: $localize`a week`,
+    ww: $localize`%d weeks`,
+    M: $localize`a month`,
+    MM: $localize`%d months`,
+    y: $localize`a year`,
+    yy: $localize`%d years`
+  }
+});
+
 @Pipe({
-  name: 'relativeDate'
+  name: 'relativeDate',
+  pure: false
 })
 export class RelativeDatePipe implements PipeTransform {
-  transform(value: any): any {
-    if (!value) {
-      return 'unknown';
+  /**
+   * Convert a time into a human readable form, e.g. '2 minutes ago'.
+   * @param {Date | string | number} value The date to convert, should be
+   *   an ISO8601 string, an Unix timestamp (seconds) or Date object.
+   * @param {boolean} upperFirst Set to `true` to start the sentence
+   *   upper case. Defaults to `true`.
+   * @return {string} The time in human readable form or an empty string
+   *   on failure (e.g. invalid input).
+   */
+  transform(value: Date | string | number, upperFirst = true): string {
+    let date: moment.Moment;
+    if (_.isNumber(value)) {
+      date = moment.unix(value);
+    } else {
+      date = moment(value);
+    }
+    if (!date.isValid()) {
+      return '';
+    }
+    let relativeDate: string = date.fromNow();
+    if (upperFirst) {
+      relativeDate = _.upperFirst(relativeDate);
     }
-    return moment(value * 1000).fromNow();
+    return relativeDate;
   }
 }