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

Time Value Component #36

Merged
merged 32 commits into from Mar 16, 2020
Merged

Time Value Component #36

merged 32 commits into from Mar 16, 2020

Conversation

mdelez
Copy link
Contributor

@mdelez mdelez commented Feb 26, 2020

closes #16

@mdelez
Copy link
Contributor Author

mdelez commented Mar 2, 2020

The tests aren't working very well since my local tests rely on my local time zone and the CI operates on a different time zone so the values I check for are always wrong on one of the tests (either local tests or the CI test)

@mdelez
Copy link
Contributor Author

mdelez commented Mar 3, 2020

I think using this library would be ideal since it combines a date picker and a time picker. Unfortunately it requires Angular 9. There is an old version for Angular 8 but it is no longer supported.

@mdelez
Copy link
Contributor Author

mdelez commented Mar 4, 2020

Screen Shot 2020-03-04 at 12 50 17

Running the code on localhost works with no issue and returns the correct timestamp. The offset for us from UTC is +2 hours so an offset value of -120 is correct (value is in minutes and a positive offset returns a negative number).

Screen Shot 2020-03-04 at 12 51 00

Running the unit test fails because for some reason the offset is -34. I have no idea where this seemingly random offset is coming from. From my understanding of time (which is a bit unclear now), except for a couple of timezones, namely some Australian timezones, every offset only deals with hours, not minutes or seconds. This adds to my confusion because in the tests I am setting the time value to "22:00" and it is returning a timestamp of "1776-07-04T21:25:52.000Z" when I should be expecting "1776-07-04T20:00:00.000Z" to account for the offset. Somehow the offset is also affecting the minutes and seconds.

Update
I've figured out the issue...kinda. I was using a rather old date for testing and this is what was causing me to receive an offset of -34. I changed the value to a more recent value (2019) and the tests run successfully now. Knora handles the offset perfectly and always returns to correct time so no issues there.

I've opened up a new issue for this #38

@mdelez mdelez requested review from tobiasschweizer and removed request for tobiasschweizer March 5, 2020 13:04
@@ -0,0 +1,3 @@
::ng-deep .parent-value-component .mat-form-field {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😣technology evolves too fast. I'll update my code. Thanks!

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

::ng-deep, /deep/ and >>> deprecation
The ::ng-deep pseudo-class selector also has a couple of aliases: >>> and /deep/, and all three are soon to be removed.

The main reason for that is that this mechanism for piercing the style isolation sandbox around a component can potentially encourage bad styling practices.

The situation is still evolving, but right now, ::ng-deep can be used if needed for certain use cases.

https://blog.angular-university.io/angular-host-context/

From what I can find online, Angular has marked the ::ng-deep selector as deprecated but has not yet provided a new solution. They recommend to use ::ng-deep only if absolutely necessary so I shall use it sparingly.

@mdelez mdelez self-assigned this Mar 9, 2020
@mdelez mdelez mentioned this pull request Mar 9, 2020
@tobiasschweizer
Copy link
Contributor

@mdelez Thanks, I will look at this PR today.

…mponent to the child component and cleaned up a lot of repeated code
@mdelez
Copy link
Contributor Author

mdelez commented Mar 12, 2020

I've refactored a lot my code and I think it looks quite clean now. Namely I have moved all of the date conversion and logic to the child component and now the parent component is only concerned about a datetime string. I've also moved the date conversion logic into separate methods so that it is easier to test.

Some minor things still left to do:

  • move html and css changes to separate PR
  • work on the time validator so that it operates correctly within Safari since Safari doesn't support the "time" type within an input

Copy link
Contributor

@tobiasschweizer tobiasschweizer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I hit the cancel button for the time part

Screenshot 2020-03-12 at 12 08 06

I get:

TimeInputComponent.html:8 ERROR RangeError: Invalid time value
at Date.toISOString ()
at TimeInputComponent.userInputToTimestamp (knora-ui.js:1822)
at TimeInputComponent.get value [as value] (knora-ui.js:1736)
at TimeInputComponent._handleInput (knora-ui.js:1810)
at Object.eval [as handleEvent] (TimeInputComponent.html:8)
at handleEvent (core.js:43993)
at callWithDebugContext (core.js:45632)
at Object.debugHandleEvent [as handleEvent] (core.js:45247)
at dispatchEvent (core.js:29804)
at core.js:42925

Probably this is because a cancelled time is null.

return (control: AbstractControl): { [key: string]: any } | null => {

const invalid = initValue === control.value &&
control.value.split(':').pop().split(';')[0].match(CustomRegex.TIME_REGEX) == null &&
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think yo don't need to validate the time in the parent since you are doing that already in the child component.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the best case overriding the standardValidatorFunc becomes unnecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed error and removed overridden validator in bfec41b

this.dateFormControl = new FormControl(null);
this.dateFormControl.setValidators([Validators.required]);

this.timeFormControl = new FormControl({value: null, Validators: [Validators.required, this.timeValidator]});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can directly put Validators.pattern(CustomRegex.TIME_REGEX) here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in bfec41b

<mat-error *ngIf="form.controls.timeValue.hasError('valueNotChanged')">
Please change the value
</mat-error>
</mat-form-field>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add some space or a line break between the value and comment?

in Chrome:
Screenshot 2020-03-12 at 12 03 36

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Screen Shot 2020-03-12 at 13 46 49

It looks okay on my screen but I know the css needs some improvement. I'll move the css to a new PR and add a line break as the last thing before merging.

super(_defaultErrorStateMatcher, _parentForm, _parentFormGroup, ngControl);

this.dateFormControl = new FormControl(null);
this.dateFormControl.setValidators([Validators.required]);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can use the more compact form like below to set the validator.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in bfec41b

}

// converts and returns a unix timestamp string as an array consisting of a GregorianCalendarDate and a string
convertTimestampToDateTime(timestamp: string): { gcd: GregorianCalendarDate, time: string }[] {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you make the return type an object with one member being time and the other the date?

What is { gcd: GregorianCalendarDate, time: string }[] supposed to mean? An array of objects each having a member gcd and time?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, returning an object would probably be better. Currently it's returning an array of objects because gcd and time are different types

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in bfec41b

}

// return converted Date obj as a string without the milliseconds
userInputToTimestamp(userInput: any): string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you give the argument userInputa type annotation other than any?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably you can make it consistent with what you return from convertTimestampToDateTime.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in 6392c37


// return converted Date obj as a string without the milliseconds
userInputToTimestamp(userInput: any): string {
let splitTime = userInput.time.split(":");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the user cancels the date, time seems to be an empty string:

{date: GregorianCalendarDate, time: ""}date: GregorianCalendarDate {calendarStart: CalendarDate, calendarEnd: CalendarDate, exactDate: true, jdnStart: 2458922, jdnEnd: 2458922, …}time: ""__proto__: Object

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in bfec41b

}
}

describe('TimeInputComponent', () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you made separate functions for time conversion userInputToTimestamp and convertTimestampToDateTime, could add some tests for these? Just test the functions themselves, without any HTML/Dome interaction.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

tests added in bfec41b

@tobiasschweizer
Copy link
Contributor

@mdelez If you need my help, just give me a call.


const date = new GregorianCalendarDate(new CalendarPeriod(calendarDate, calendarDate));

let time = this.datePipe.transform(timestamp, "HH:mm");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use const instead of let. Please use single quotes for strings.

Or just use WebStorm .. ;-)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in e78d4cc I really tried to only use single quotes, but some slipped through 😜

@Input()
get value(): string | null {
const userInput = new DateTime(this.form.value.date, this.form.value.time);
if (userInput.date !== null && userInput.time !== null && userInput.time !== '') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you should also check the time string against the regex here (to prevent an error in this.userInputToTimestamp ). If invalid, null is returned.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or probably you can just see if the FormControl for time is valid

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in 548005d


set value(timestamp: string | null) {
if (timestamp !== null) {
const dateTime = this.convertTimestampToDateTime(timestamp);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be wrapped in a try-catch block. If it fails to parse the timevalue string, you can still init {date: null, time: null}.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in 19c3324

get value(): string | null {
if (this.form.valid) {
const userInput = new DateTime(this.form.value.date, this.form.value.time);
return this.userInputToTimestamp(userInput);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you wrap this with a try-catch block too (just return null if there is an error)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in 19c3324

userInputToTimestamp(userInput: DateTime): string {
const splitTime = userInput.time.split(':');
const updateDate = new Date(userInput.date.toCalendarPeriod().periodStart.year,
(userInput.date.toCalendarPeriod().periodStart.month - 1),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a comment about 0-based indexes for months

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in 19c3324

Copy link
Contributor

@tobiasschweizer tobiasschweizer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great, thx!

I think we still need to find out about the timezones, but there is #38 for this.

@mdelez mdelez merged commit dc9c557 into master Mar 16, 2020
@mdelez mdelez deleted the wip/16-time-value-component-2 branch March 16, 2020 08:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Viewer Value Component: TimeValueComponent
3 participants