[ADF-4342] Create localized pipe and centralize date format (#4813)

* [ADF-4342] Date Format defined in app config

* [ADF-4342] Create localized pipe and centralize date format

* Add unit test for new date pipe

* Add info internationalization docs

* Fix lining

* Fix linting

* Fix date pipe unit test

* [ADF-4342] Add supported language files

* Fix e2e tests
This commit is contained in:
davidcanonieto
2019-06-11 09:35:35 +01:00
committed by Denys Vuika
parent 990fa4625b
commit 7497822a46
38 changed files with 473 additions and 67 deletions

View File

@@ -518,6 +518,29 @@
}
}
},
"dateValues": {
"description": "Configuration of date formats in the app",
"type": "object",
"required": [
"defaultDateFormat",
"defaultDateTimeFormat",
"defaultLocale"
],
"properties": {
"defaultDateFormat": {
"description": "Default date format",
"type": "string"
},
"defaultDateTimeFormat": {
"description": "Default date time format",
"type": "string"
},
"defaultLocale": {
"description": "Default date locale",
"type": "string"
}
}
},
"files": {
"description": "Configuration of rules applied to file upload",
"type": "object",

View File

@@ -26,6 +26,7 @@ import { CardViewUpdateService } from '../../services/card-view-update.service';
import { UserPreferencesService, UserPreferenceValues } from '../../../services/user-preferences.service';
import { MomentDateAdapter } from '../../../utils/momentDateAdapter';
import { MOMENT_DATE_FORMATS } from '../../../utils/moment-date-formats.model';
import { AppConfigService } from '../../../app-config/app-config.service';
@Component({
providers: [
@@ -40,8 +41,6 @@ import { MOMENT_DATE_FORMATS } from '../../../utils/moment-date-formats.model';
})
export class CardViewDateItemComponent implements OnInit {
public SHOW_FORMAT: string = 'MMM DD YY';
@Input()
property: CardViewDateItemModel;
@@ -55,10 +54,13 @@ export class CardViewDateItemComponent implements OnInit {
public datepicker: MatDatetimepicker<any>;
valueDate: Moment;
dateFormat: string;
constructor(private cardViewUpdateService: CardViewUpdateService,
private dateAdapter: DateAdapter<Moment>,
private userPreferencesService: UserPreferencesService) {
private userPreferencesService: UserPreferencesService,
private appConfig: AppConfigService) {
this.dateFormat = this.appConfig.get('dateValues.defaultDateFormat');
}
ngOnInit() {
@@ -66,10 +68,10 @@ export class CardViewDateItemComponent implements OnInit {
this.dateAdapter.setLocale(locale);
});
(<MomentDateAdapter> this.dateAdapter).overrideDisplayFormat = this.SHOW_FORMAT;
(<MomentDateAdapter> this.dateAdapter).overrideDisplayFormat = this.dateFormat;
if (this.property.value) {
this.valueDate = moment(this.property.value, this.SHOW_FORMAT);
this.valueDate = moment(this.property.value, this.dateFormat);
}
}
@@ -87,7 +89,7 @@ export class CardViewDateItemComponent implements OnInit {
onDateChanged(newDateValue) {
if (newDateValue) {
const momentDate = moment(newDateValue.value, this.SHOW_FORMAT, true);
const momentDate = moment(newDateValue.value, this.dateFormat, true);
if (momentDate.isValid()) {
this.valueDate = momentDate;
this.cardViewUpdateService.update(this.property, momentDate.toDate());

View File

@@ -57,6 +57,41 @@ import { ExtensionsModule } from '@alfresco/adf-extensions';
import { directionalityConfigFactory } from './services/directionality-config-factory';
import { DirectionalityConfigService } from './services/directionality-config.service';
import { registerLocaleData } from '@angular/common';
import localeFr from '@angular/common/locales/fr';
import localeDe from '@angular/common/locales/de';
import localeIt from '@angular/common/locales/it';
import localeEs from '@angular/common/locales/es';
import localeJa from '@angular/common/locales/ja';
import localeNl from '@angular/common/locales/nl';
import localePt from '@angular/common/locales/pt';
import localeNb from '@angular/common/locales/nb';
import localeRu from '@angular/common/locales/ru';
import localeCh from '@angular/common/locales/zh';
import localeAr from '@angular/common/locales/ar';
import localeCs from '@angular/common/locales/cs';
import localePl from '@angular/common/locales/pl';
import localeFi from '@angular/common/locales/fi';
import localeDa from '@angular/common/locales/da';
import localeSv from '@angular/common/locales/sv';
registerLocaleData(localeFr);
registerLocaleData(localeDe);
registerLocaleData(localeIt);
registerLocaleData(localeEs);
registerLocaleData(localeJa);
registerLocaleData(localeNl);
registerLocaleData(localePt);
registerLocaleData(localeNb);
registerLocaleData(localeRu);
registerLocaleData(localeCh);
registerLocaleData(localeAr);
registerLocaleData(localeCs);
registerLocaleData(localePl);
registerLocaleData(localeFi);
registerLocaleData(localeDa);
registerLocaleData(localeSv);
@NgModule({
imports: [
TranslateModule,

View File

@@ -28,14 +28,14 @@ describe('DataTableCellComponent', () => {
});
it('should use medium format by default', () => {
const component = new DateCellComponent(null, null);
const component = new DateCellComponent(null, null, new AppConfigService(null));
expect(component.format).toBe('medium');
});
it('should use column format', () => {
const component = new DateCellComponent(null, <any> {
nodeUpdated: new Subject<any>()
});
}, new AppConfigService(null));
component.column = {
key: 'created',
type: 'date',
@@ -49,7 +49,8 @@ describe('DataTableCellComponent', () => {
it('should update cell data on alfrescoApiService.nodeUpdated event', () => {
const component = new DateCellComponent(
null,
alfrescoApiService
alfrescoApiService,
new AppConfigService(null)
);
component.column = {
@@ -83,7 +84,8 @@ describe('DataTableCellComponent', () => {
it('not should update cell data if ids don`t match', () => {
const component = new DateCellComponent(
null,
alfrescoApiService
alfrescoApiService,
new AppConfigService(null)
);
component.column = {

View File

@@ -121,8 +121,8 @@
<mat-icon>{{ data.getValue(row, col) }}</mat-icon>
</div>
<div *ngSwitchCase="'date'" class="adf-cell-value"
[attr.data-automation-id]="'date_' + (data.getValue(row, col) | date: 'medium') ">
<adf-date-cell class = "adf-datatable-center-date-column-ie"
[attr.data-automation-id]="'date_' + (data.getValue(row, col) | adfLocalizedDate: 'medium') ">
<adf-date-cell class="adf-datatable-center-date-column-ie"
[data]="data"
[column]="col"
[row]="row"

View File

@@ -22,6 +22,7 @@ import {
UserPreferenceValues
} from '../../../services/user-preferences.service';
import { AlfrescoApiService } from '../../../services/alfresco-api.service';
import { AppConfigService } from '../../../app-config/app-config.service';
@Component({
selector: 'adf-date-cell',
@@ -30,20 +31,19 @@ import { AlfrescoApiService } from '../../../services/alfresco-api.service';
<ng-container>
<span
[attr.aria-label]="value$ | async | adfTimeAgo: currentLocale"
title="{{ tooltip | date: 'medium' }}"
title="{{ tooltip | adfLocalizedDate: 'medium' }}"
class="adf-datatable-cell-value"
*ngIf="format === 'timeAgo'; else standard_date"
>
*ngIf="format === 'timeAgo'; else standard_date">
{{ value$ | async | adfTimeAgo: currentLocale }}
</span>
</ng-container>
<ng-template #standard_date>
<span
title="{{ tooltip | date: format }}"
class="adf-datatable-cell-value"
[attr.aria-label]="value$ | async | date: format"
>
{{ value$ | async | date: format }}
title="{{ tooltip | adfLocalizedDate: format }}"
class="adf-datatable-cell-value"
[attr.aria-label]="value$ | async | adfLocalizedDate: format">
{{ value$ | async | adfLocalizedDate: format }}
</span>
</ng-template>
`,
@@ -51,21 +51,27 @@ import { AlfrescoApiService } from '../../../services/alfresco-api.service';
host: { class: 'adf-date-cell adf-datatable-content-cell' }
})
export class DateCellComponent extends DataTableCellComponent {
static DATE_FORMAT = 'medium';
currentLocale: string;
dateFormat: string;
get format(): string {
if (this.column) {
return this.column.format || 'medium';
return this.column.format || this.dateFormat;
}
return 'medium';
return this.dateFormat;
}
constructor(
userPreferenceService: UserPreferencesService,
alfrescoApiService: AlfrescoApiService
alfrescoApiService: AlfrescoApiService,
appConfig: AppConfigService
) {
super(alfrescoApiService);
this.dateFormat = appConfig.get('dateValues.defaultDateFormat', DateCellComponent.DATE_FORMAT);
if (userPreferenceService) {
userPreferenceService
.select(UserPreferenceValues.Locale)

View File

@@ -39,9 +39,10 @@ import { baseHost, WidgetComponent } from './../widget.component';
})
export class DateWidgetComponent extends WidgetComponent implements OnInit {
DATE_FORMAT = 'DD/MM/YYYY';
minDate: Moment;
maxDate: Moment;
displayDate: Moment;
constructor(public formService: FormService,
@@ -60,11 +61,11 @@ export class DateWidgetComponent extends WidgetComponent implements OnInit {
if (this.field) {
if (this.field.minValue) {
this.minDate = moment(this.field.minValue, 'DD/MM/YYYY');
this.minDate = moment(this.field.minValue, this.DATE_FORMAT);
}
if (this.field.maxValue) {
this.maxDate = moment(this.field.maxValue, 'DD/MM/YYYY');
this.maxDate = moment(this.field.maxValue, this.DATE_FORMAT);
}
}
this.displayDate = moment(this.field.value, this.field.dateDisplayFormat);

View File

@@ -1,6 +1,6 @@
<div>
<mat-form-field class="adf-date-editor">
<label [attr.for]="column.id">{{column.name}} (d-M-yyyy)</label>
<label [attr.for]="column.id">{{column.name}} (DATE_FORMAT)</label>
<input matInput
id="dateInput"
type="text"

View File

@@ -18,6 +18,7 @@
/* tslint:disable:component-selector */
import { UserPreferencesService, UserPreferenceValues } from '../../../../../../services/user-preferences.service';
import { MomentDateAdapter } from '../../../../../../utils/momentDateAdapter';
import { MOMENT_DATE_FORMATS } from '../../../../../../utils/moment-date-formats.model';
import { Component, Input, OnInit } from '@angular/core';

View File

@@ -1,6 +1,6 @@
<div>
<mat-form-field class="adf-date-editor">
<label [attr.for]="column.id">{{column.name}} {{DATE_FORMAT}}</label>
<label [attr.for]="column.id">{{column.name}} {{DATE_TIME_FORMAT}}</label>
<input matInput
[matDatetimepicker]="datetimePicker"
[(ngModel)]="value"
@@ -9,12 +9,16 @@
[disabled]="!column.editable"
(focusout)="onDateChanged($event.srcElement.value)"
(dateChange)="onDateChanged($event)">
<mat-datetimepicker-toggle matSuffix [for]="datetimePicker"
*ngIf="column.editable"
class="adf-date-editor-button"></mat-datetimepicker-toggle>
<mat-datetimepicker-toggle
matSuffix
[for]="datetimePicker"
class="adf-date-editor-button">
</mat-datetimepicker-toggle>
</mat-form-field>
<mat-datetimepicker #datetimePicker
type="datetime"
openOnFocus="true"
timeInterval="5"></mat-datetimepicker>
<mat-datetimepicker
#datetimePicker
type="datetime"
openOnFocus="true"
timeInterval="5">
</mat-datetimepicker>
</div>

View File

@@ -43,7 +43,7 @@ import { MomentDatetimeAdapter, MAT_MOMENT_DATETIME_FORMATS } from '@mat-datetim
})
export class DateTimeEditorComponent implements OnInit {
DATE_FORMAT: string = 'D-M-YYYY hh:mm A';
DATE_TIME_FORMAT: string = 'DD/MM/YYYY HH:mm';
value: any;
@@ -69,19 +69,19 @@ export class DateTimeEditorComponent implements OnInit {
});
const momentDateAdapter = <MomentDateAdapter> this.dateAdapter;
momentDateAdapter.overrideDisplayFormat = this.DATE_FORMAT;
momentDateAdapter.overrideDisplayFormat = this.DATE_TIME_FORMAT;
this.value = moment(this.table.getCellValue(this.row, this.column), this.DATE_FORMAT);
this.value = moment(this.table.getCellValue(this.row, this.column), this.DATE_TIME_FORMAT);
}
onDateChanged(newDateValue) {
if (newDateValue && newDateValue.value) {
const newValue = moment(newDateValue.value, this.DATE_FORMAT);
this.row.value[this.column.id] = newDateValue.value.format(this.DATE_FORMAT);
const newValue = moment(newDateValue.value, this.DATE_TIME_FORMAT);
this.row.value[this.column.id] = newDateValue.value.format(this.DATE_TIME_FORMAT);
this.value = newValue;
this.table.flushValue();
} else if (newDateValue) {
const newValue = moment(newDateValue, this.DATE_FORMAT);
const newValue = moment(newDateValue, this.DATE_TIME_FORMAT);
this.value = newValue;
this.row.value[this.column.id] = newDateValue;
this.table.flushValue();

View File

@@ -0,0 +1,56 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { LocalizedDatePipe } from './localized-date.pipe';
import { async, TestBed } from '@angular/core/testing';
import { AppConfigService } from '../app-config/app-config.service';
import { UserPreferencesService } from '../services/user-preferences.service';
import { of } from 'rxjs';
import { setupTestBed } from '../testing/setupTestBed';
import { CoreTestingModule } from '../testing/core.testing.module';
describe('LocalizedDatePipe', () => {
let pipe: LocalizedDatePipe;
let userPreferences: UserPreferencesService;
setupTestBed({
imports: [CoreTestingModule]
});
beforeEach(async(() => {
userPreferences = TestBed.get(UserPreferencesService);
spyOn(userPreferences, 'select').and.returnValue(of(''));
pipe = new LocalizedDatePipe(userPreferences, new AppConfigService(null));
}));
it('should return time with locale en-US', () => {
const date = new Date('1990-11-03');
expect(pipe.transform(date)).toBe('Nov 3, 1990, 12:00:00 AM');
});
it('should return correct date when formating and locating it', () => {
const date = new Date();
expect(new Date(pipe.transform(date)).toDateString()).toBe(date.toDateString());
});
it('should return formated time when a formar is given', () => {
const date = new Date('1990-11-03');
expect(pipe.transform(date, 'MMM dd')).toBe('Nov 03');
});
});

View File

@@ -0,0 +1,50 @@
/*!
* @license
* Copyright 2019 Alfresco Software, Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { DatePipe } from '@angular/common';
import { Pipe, PipeTransform } from '@angular/core';
import { AppConfigService } from '../app-config/app-config.service';
import { UserPreferencesService, UserPreferenceValues } from '../services/user-preferences.service';
@Pipe({
name: 'adfLocalizedDate',
pure: false
})
export class LocalizedDatePipe implements PipeTransform {
static DEFAULT_LOCALE = 'en-US';
static DEFAULT_DATE_TIME_FORMAT = 'medium';
defaultLocale: string;
defaultFormat: string;
constructor(public userPreferenceService: UserPreferencesService,
public appConfig: AppConfigService) {
this.userPreferenceService.select(UserPreferenceValues.Locale).subscribe((locale) => {
this.defaultLocale = locale || LocalizedDatePipe.DEFAULT_LOCALE;
});
this.defaultFormat = this.appConfig.get<string>('dateValues.defaultFormat', LocalizedDatePipe.DEFAULT_DATE_TIME_FORMAT);
}
transform(value: any, format?: string, locale?: string): any {
const actualFormat = format || this.defaultFormat;
const actualLocale = locale || this.defaultLocale;
const datePipe: DatePipe = new DatePipe(actualLocale);
return datePipe.transform(value, actualFormat);
}
}

View File

@@ -28,6 +28,7 @@ import { FullNamePipe } from './full-name.pipe';
import { FormatSpacePipe } from './format-space.pipe';
import { FileTypePipe } from './file-type.pipe';
import { MultiValuePipe } from './multi-value.pipe';
import { LocalizedDatePipe } from './localized-date.pipe';
@NgModule({
imports: [
@@ -43,7 +44,8 @@ import { MultiValuePipe } from './multi-value.pipe';
NodeNameTooltipPipe,
FormatSpacePipe,
FileTypePipe,
MultiValuePipe
MultiValuePipe,
LocalizedDatePipe
],
providers: [
FileSizePipe,
@@ -54,7 +56,8 @@ import { MultiValuePipe } from './multi-value.pipe';
NodeNameTooltipPipe,
FormatSpacePipe,
FileTypePipe,
MultiValuePipe
MultiValuePipe,
LocalizedDatePipe
],
exports: [
FileSizePipe,
@@ -66,7 +69,8 @@ import { MultiValuePipe } from './multi-value.pipe';
NodeNameTooltipPipe,
FormatSpacePipe,
FileTypePipe,
MultiValuePipe
MultiValuePipe,
LocalizedDatePipe
]
})
export class PipeModule {

View File

@@ -23,5 +23,6 @@ export * from './time-ago.pipe';
export * from './user-initial.pipe';
export * from './full-name.pipe';
export * from './multi-value.pipe';
export * from './localized-date.pipe';
export * from './pipe.module';

View File

@@ -16,14 +16,26 @@
*/
import { TimeAgoPipe } from './time-ago.pipe';
import { async } from '@angular/core/testing';
import { async, TestBed } from '@angular/core/testing';
import { AppConfigService } from '../app-config/app-config.service';
import { UserPreferencesService } from '../services/user-preferences.service';
import { setupTestBed } from '../testing/setupTestBed';
import { CoreTestingModule } from '../testing/core.testing.module';
import { of } from 'rxjs';
describe('TimeAgoPipe', () => {
let pipe: TimeAgoPipe;
let userPreferences: UserPreferencesService;
setupTestBed({
imports: [CoreTestingModule]
});
beforeEach(async(() => {
pipe = new TimeAgoPipe();
userPreferences = TestBed.get(UserPreferencesService);
spyOn(userPreferences, 'select').and.returnValue(of(''));
pipe = new TimeAgoPipe(userPreferences, new AppConfigService(null));
}));
it('should return time difference for a given date', () => {

View File

@@ -17,20 +17,34 @@
import moment from 'moment-es6';
import { Pipe, PipeTransform } from '@angular/core';
import { AppConfigService } from '../app-config/app-config.service';
import { UserPreferenceValues, UserPreferencesService } from '../services/user-preferences.service';
@Pipe({
name: 'adfTimeAgo'
})
export class TimeAgoPipe implements PipeTransform {
defaultLocale = 'en-US';
static DEFAULT_LOCALE = 'en-US';
static DEFAULT_DATE_TIME_FORMAT = 'DD/MM/YYYY HH:mm';
defaultLocale: string;
defaultDateTimeFormat: string;
constructor(public userPreferenceService: UserPreferencesService,
public appConfig: AppConfigService) {
this.userPreferenceService.select(UserPreferenceValues.Locale).subscribe((locale) => {
this.defaultLocale = locale || TimeAgoPipe.DEFAULT_LOCALE;
});
this.defaultDateTimeFormat = this.appConfig.get<string>('dateValues.defaultDateTimeFormat', TimeAgoPipe.DEFAULT_DATE_TIME_FORMAT);
}
transform(value: Date, locale?: string) {
if (value !== null && value !== undefined ) {
const actualLocale = locale ? locale : this.defaultLocale;
const actualLocale = locale || this.defaultLocale;
const then = moment(value);
const diff = moment().locale(actualLocale).diff(then, 'days');
return diff > 7 ? then.locale(actualLocale).format('DD/MM/YYYY HH:mm') : then.locale(actualLocale).fromNow();
return diff > 7 ? then.locale(actualLocale).format(this.defaultDateTimeFormat) : then.locale(actualLocale).fromNow();
}
return '';
}

View File

@@ -128,7 +128,7 @@ describe('ProcessHeaderCloudComponent', () => {
fixture.whenStable().then(() => {
const valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-startDate"] .adf-property-value'));
expect(valueEl.nativeElement.innerText.trim()).toBe('09-03-2019');
expect(valueEl.nativeElement.innerText.trim()).toBe('Mar 09 2019');
});
}));
@@ -138,7 +138,7 @@ describe('ProcessHeaderCloudComponent', () => {
fixture.whenStable().then(() => {
const valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-lastModified"] .adf-property-value'));
expect(valueEl.nativeElement.innerText.trim()).toBe('09-03-2019');
expect(valueEl.nativeElement.innerText.trim()).toBe('Mar 09 2019');
});
}));

View File

@@ -102,14 +102,12 @@ export class ProcessHeaderCloudComponent implements OnChanges {
{
label: 'ADF_CLOUD_PROCESS_HEADER.PROPERTIES.START_DATE',
value: this.processInstanceDetails.startDate,
format: 'DD-MM-YYYY',
key: 'startDate'
}),
new CardViewDateItemModel(
{
label: 'ADF_CLOUD_PROCESS_HEADER.PROPERTIES.LAST_MODIFIED',
value: this.processInstanceDetails.lastModified,
format: 'DD-MM-YYYY',
key: 'lastModified'
}),
new CardViewTextItemModel(

View File

@@ -118,7 +118,7 @@ describe('TaskHeaderCloudComponent', () => {
fixture.whenStable().then(() => {
const valueEl = fixture.debugElement.query(By.css('[data-automation-id="header-dueDate"] .adf-property-value'));
expect(valueEl.nativeElement.innerText.trim()).toBe('18-12-2018');
expect(valueEl.nativeElement.innerText.trim()).toBe('Dec 18 2018');
});
}));

View File

@@ -59,6 +59,7 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy {
properties: CardViewItem[];
inEdit: boolean = false;
parentTaskName: string;
dateFormat: string;
private subscriptions: Subscription[] = [];
@@ -68,7 +69,9 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy {
private appConfig: AppConfigService,
private router: Router,
private cardViewUpdateService: CardViewUpdateService
) { }
) {
this.dateFormat = this.appConfig.get('dateValues.defaultDateFormat');
}
ngOnInit() {
if ((this.appName || this.appName === '') && this.taskId) {
@@ -122,7 +125,6 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy {
label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.DUE_DATE',
value: this.taskDetails.dueDate,
key: 'dueDate',
format: 'DD-MM-YYYY',
default: this.translationService.instant('ADF_CLOUD_TASK_HEADER.PROPERTIES.DUE_DATE_DEFAULT'),
editable: true
}
@@ -139,7 +141,6 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy {
{
label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.CREATED',
value: this.taskDetails.createdDate,
format: 'DD-MM-YYYY',
key: 'created'
}
),
@@ -162,7 +163,6 @@ export class TaskHeaderCloudComponent implements OnInit, OnDestroy {
{
label: 'ADF_CLOUD_TASK_HEADER.PROPERTIES.END_DATE',
value: this.taskDetails.completedDate,
format: 'DD-MM-YYYY',
key: 'endDate'
}
),