Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[NAE-1544] Timezone for Date a DateTime field #119

Open
wants to merge 11 commits into
base: release/6.4.0
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ import {authenticationServiceFactory} from './authentication.factory';
import {ConfigurationService} from '../configuration/configuration.service';
import {AuthenticationMethodService} from './services/authentication-method.service';
import {OverlayModule} from '@angular/cdk/overlay';
import {MatProgressSpinnerModule, MatSpinner} from '@angular/material/progress-spinner';
import {MatProgressSpinnerModule} from '@angular/material/progress-spinner';
import {AnonymousAuthenticationInterceptor} from './services/anonymous-authentication-interceptor';
import { TimezoneInterceptor } from './services/timezone-interceptor';


@NgModule({
Expand All @@ -24,6 +25,7 @@ import {AnonymousAuthenticationInterceptor} from './services/anonymous-authentic
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthenticationInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: AnonymousAuthenticationInterceptor, multi: true },
{ provide: HTTP_INTERCEPTORS, useClass: TimezoneInterceptor, multi: true },
{ provide: AuthenticationMethodService, useFactory: authenticationServiceFactory, deps: [ConfigurationService, HttpClient] },
// AuthenticationEffects
]
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import {inject, TestBed} from '@angular/core/testing';
import {ConfigurationService} from '../../configuration/configuration.service';
import {TestConfigurationService} from '../../utility/tests/test-config';
import {HTTP_INTERCEPTORS, HttpClient, HttpHeaders} from '@angular/common/http';
import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing';
import {RouterTestingModule} from '@angular/router/testing';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {LoggerService} from '../../logger/services/logger.service';
import { TimezoneInterceptor } from './timezone-interceptor';

describe('TimezoneInterceptor', () => {
let warnSpy: jasmine.Spy;

beforeEach(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, NoopAnimationsModule, RouterTestingModule.withRoutes([])],
providers: [
{provide: ConfigurationService, useClass: TestConfigurationService},
{
provide: HTTP_INTERCEPTORS,
useClass: TimezoneInterceptor,
multi: true
}
]
});
warnSpy = spyOn(TestBed.inject(LoggerService), 'warn');
});

describe('intercept HTTP request', () => {
it('should add timezone to Headers', (done) => {
inject([HttpClient, HttpTestingController],
(http: HttpClient, mock: HttpTestingController) => {

http.get('/api').subscribe(response => {
expect(response).toBeTruthy();
done();
});
const request = mock.expectOne(req => (req.headers.has('X-Timezone-Offset')));
request.flush({data: 'test'}, {headers: new HttpHeaders({'X-Timezone-Offset': '-120'})});
mock.verify();
})();
});
afterEach(inject([HttpTestingController], (mock: HttpTestingController) => {
mock.verify();
TestBed.resetTestingModule();
}));
});

afterEach(() => {
TestBed.resetTestingModule();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Injectable } from '@angular/core';
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Observable } from 'rxjs';

@Injectable()
export class TimezoneInterceptor implements HttpInterceptor {

private readonly TIMEZONE_OFFSET_HEADER_NAME = 'X-Timezone-Offset'

intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
req = req.clone({
headers: req.headers.set(this.TIMEZONE_OFFSET_HEADER_NAME, this.getTimeZoneOffset())
});
return next.handle(req)
}

private getTimeZoneOffset(): string {
return (new Date()).getTimezoneOffset().toString();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ import {LanguageService} from "../../../translate/language.service";
})
export abstract class AbstractDateDefaultFieldComponent extends AbstractTimeInstanceFieldComponent<DateField> {

constructor(protected _translate: TranslateService,
protected _adapter: DateAdapter<any>,
@Inject(MAT_DATE_LOCALE) protected _locale: string,
protected _languageService: LanguageService,
constructor(_translate: TranslateService,
_adapter: DateAdapter<any>,
@Inject(MAT_DATE_LOCALE) _locale: string,
_languageService: LanguageService,
@Optional() @Inject(DATA_FIELD_PORTAL_DATA) dataFieldPortalData: DataFieldPortalData<DateField>) {
super(_translate, _adapter, _locale, _languageService, dataFieldPortalData)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ import {LanguageService} from "../../../translate/language.service";
})
export abstract class AbstractDateTimeDefaultFieldComponent extends AbstractTimeInstanceFieldComponent<DateTimeField> {

constructor(protected _translate: TranslateService,
protected _adapter: NgxMatDateAdapter<any>,
constructor(_translate: TranslateService,
_adapter: NgxMatDateAdapter<any>,
@Inject(MAT_DATE_LOCALE) protected _locale: string,
protected _languageService: LanguageService,
@Optional() @Inject(DATA_FIELD_PORTAL_DATA) dataFieldPortalData: DataFieldPortalData<DateTimeField>) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import {ComponentFixture, TestBed, waitForAsync} from '@angular/core/testing';
import {AngularResizeEventModule} from 'angular-resize-event';
import {NgxMatDatetimePickerModule} from '@angular-material-components/datetime-picker';
import {HttpClientTestingModule} from '@angular/common/http/testing';
import {NoopAnimationsModule} from '@angular/platform-browser/animations';
import {Component, CUSTOM_ELEMENTS_SCHEMA, Inject, Optional} from '@angular/core';
import moment from 'moment';
import {BehaviorSubject} from 'rxjs';
import {DateTimeField} from '../models/date-time-field';
import {ChangedFields} from '../../models/changed-fields';
import {TranslateService} from '@ngx-translate/core';
import {MaterialModule} from '../../../material/material.module';
import {TranslateLibModule} from '../../../translate/translate-lib.module';
import {MockAuthenticationMethodService} from '../../../utility/tests/mocks/mock-authentication-method-service';
import {AuthenticationMethodService} from '../../../authentication/services/authentication-method.service';
import {AuthenticationService} from '../../../authentication/services/authentication/authentication.service';
import {MockAuthenticationService} from '../../../utility/tests/mocks/mock-authentication.service';
import {UserResourceService} from '../../../resources/engine-endpoint/user-resource.service';
import {MockUserResourceService} from '../../../utility/tests/mocks/mock-user-resource.service';
import {TestConfigurationService} from '../../../utility/tests/test-config';
import {ConfigurationService} from '../../../configuration/configuration.service';
import {NAE_INFORM_ABOUT_INVALID_DATA} from '../../models/invalid-data-policy-token';
import { AbstractZonedDateTimeFieldComponent } from './abstract-zoned-date-time-field.component';

describe('AbstractZonedDateTimeFieldComponent', () => {
let component: TestDateTimeFieldComponent;
let fixture: ComponentFixture<TestWrapperComponent>;

beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [
MaterialModule,
AngularResizeEventModule,
NgxMatDatetimePickerModule,
TranslateLibModule,
HttpClientTestingModule,
NoopAnimationsModule
],
providers: [
{provide: AuthenticationMethodService, useClass: MockAuthenticationMethodService},
{provide: AuthenticationService, useClass: MockAuthenticationService},
{provide: UserResourceService, useClass: MockUserResourceService},
{provide: ConfigurationService, useClass: TestConfigurationService}
],
declarations: [
TestDateTimeFieldComponent,
TestWrapperComponent
],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
}).compileComponents();
fixture = TestBed.createComponent(TestWrapperComponent);
component = fixture.debugElement.children[0].componentInstance;
fixture.detectChanges();
}));

it('should create', () => {
expect(component).toBeTruthy();
});

it('should get error message', () => {
expect(component.getErrorMessage()).toEqual('This is custom message!');
});

afterEach(() => {
TestBed.resetTestingModule();
});
});

@Component({
selector: 'ncc-zoned-test-date-time',
template: ''
})
class TestDateTimeFieldComponent extends AbstractZonedDateTimeFieldComponent {
constructor(translate: TranslateService,
@Optional() @Inject(NAE_INFORM_ABOUT_INVALID_DATA) informAboutInvalidData: boolean | null) {
super(translate, informAboutInvalidData);
}
}

@Component({
selector: 'ncc-test-wrapper',
template: '<ncc-zoned-test-date-time [dataField]="field" [changedFields]="changedFields"></ncc-zoned-test-date-time>'
})
class TestWrapperComponent {
field = new DateTimeField('', '', moment('2020-03-09'), {
required: true,
optional: true,
visible: true,
editable: true,
hidden: true
}, undefined, undefined, undefined, [
{validationRule: 'between today,future', validationMessage: 'This is custom message!'},
{validationRule: 'between past,today', validationMessage: 'This is custom message!'},
{validationRule: 'between 2020-03-03,today', validationMessage: 'This is custom message!'},
]);
changedFields = new BehaviorSubject<ChangedFields>({behavior: {editable: true}, taskId: 'testTaskId'});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import {Component, Inject, Optional} from '@angular/core';
import {TranslateService} from '@ngx-translate/core';
import {
AbstractDateDefaultFieldComponent
} from "../../date-field/date-default-field/abstract-date-default-field.component";
import {DateAdapter, MAT_DATE_LOCALE} from "@angular/material/core";
import {LanguageService} from "../../../translate/language.service";
import {DATA_FIELD_PORTAL_DATA, DataFieldPortalData} from "../../models/data-field-portal-data-injection-token";
import {DateField} from "../../date-field/models/date-field";
import {DateTimeField} from "../models/date-time-field";
import {
AbstractDateTimeDefaultFieldComponent
} from "../date-time-default-field/abstract-date-time-default-field.component";
import {NgxMatDateAdapter} from "@angular-material-components/datetime-picker";


@Component({
selector: 'ncc-abstract-date-time-field',
template: ''
})
export abstract class AbstractZonedDateTimeFieldComponent extends AbstractDateTimeDefaultFieldComponent {

public timeZone: string;

constructor(_translate: TranslateService,
_adapter: NgxMatDateAdapter<any>,
@Inject(MAT_DATE_LOCALE) _locale: string,
_languageService: LanguageService,
@Optional() @Inject(DATA_FIELD_PORTAL_DATA) dataFieldPortalData: DataFieldPortalData<DateTimeField>) {
super(_translate, _adapter, _locale, _languageService, dataFieldPortalData)
this.timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export * from './i18n-field/i18n-text-field/abstract-i18n-text-field.component';
export * from './i18n-field/i18n-divider-field/abstract-i18n-divider-field.component';
export * from './i18n-field/abstract-i18n-errors.component';
export * from './user-list-field/abstract-user-list-field.component';
export * from './date-time-field/zoned-date-time-field/abstract-zoned-date-time-field.component';
export * from './user-list-field/user-list-default-field/abstract-user-list-default-field.component';
export * from './task-ref-field/abstract-task-ref-field.component';
export * from './task-ref-field/task-ref-dashboard-field/task-ref-dashboard-tile/abstract-task-ref-dashboard-tile.component';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ import {
MultichoiceAutocompleteFieldComponent
} from './multichoice-field/multichoice-autocomplete-field/multichoice-autocomplete-field.component';
import { UserListFieldComponent } from './user-list-field/user-list-field.component';
import { ZonedDateTimeFieldComponent } from './date-time-field/zoned-date-time-field/zoned-date-time-field.component';
import {
SideMenuMultiUserAssignComponentModule
} from "../side-menu/content-components/multi-user-assign/side-menu-multi-user-assign-component.module";
Expand Down Expand Up @@ -188,7 +189,8 @@ import { StringCollectionDefaultFieldComponent } from './string-collection-field
CaseRefDefaultComponent,
MultichoiceCaserefFieldComponent,
EnumerationCaserefFieldComponent,
StringCollectionDefaultFieldComponent
StringCollectionDefaultFieldComponent,
ZonedDateTimeFieldComponent
],
exports: [
DataFieldTemplateComponent
Expand Down Expand Up @@ -228,6 +230,7 @@ export class DataFieldsComponentModule {
registry.register("button-default", (injector: Injector) => new ComponentPortal<any>(ButtonDefaultFieldComponent, null, injector));
registry.register("date-default", (injector: Injector) => new ComponentPortal<any>(DateDefaultFieldComponent, null, injector));
registry.register("date-time-default", (injector: Injector) => new ComponentPortal<any>(DateTimeDefaultFieldComponent, null, injector));
registry.register("date-time-timeZone", (injector: Injector) => new ComponentPortal<any>(DateTimeDefaultFieldComponent, null, injector));
registry.register("enumeration-default", (injector: Injector) => new ComponentPortal<any>(EnumerationSelectFieldComponent, null, injector));
registry.register("enumeration-autocomplete_dynamic", (injector: Injector) => new ComponentPortal<any>(EnumerationAutocompleteDynamicFieldComponent, null, injector));
registry.register("enumeration-autocomplete", (injector: Injector) => new ComponentPortal<any>(EnumerationAutocompleteSelectFieldComponent, null, injector));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<mat-form-field [appearance]="dataField.materialAppearance" class="full-width" color="primary">
<mat-label *ngIf="!showLargeLayout.value">{{dataField.title}}</mat-label>
<input class='left' matInput
[ngxMatDatetimePicker]="picker"
[required]="dataField.behavior.required"
[placeholder]="dataField.placeholder"
[formControl]="formControlRef"
[min]="dataField.min"
[max]="dataField.max"
(click)="picker.open()">
<mat-datepicker-toggle matPrefix [for]="picker"></mat-datepicker-toggle>
<ngx-mat-datetime-picker #picker
[showSpinners]="true"
[showSeconds]="false"
[stepHour]="1"
[stepMinute]="5"
[color]="'primary'"
[enableMeridian]="false">
</ngx-mat-datetime-picker>
<mat-hint>Time zone: {{this.timeZone}} {{!!dataField.description ? '-' + dataField.description : ''}}</mat-hint>
<mat-error *ngIf="dataField.isInvalid(formControlRef)">{{getErrorMessage()}}</mat-error>
</mat-form-field>
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.full-width {
display: block;
margin: 0 auto;
width: 100%;
}
Loading
Loading