From 11f5687b38ff3a330464d5c9aa9d8eb2d15cbf44 Mon Sep 17 00:00:00 2001 From: Vicky Bansal <67091118+vicky-primathon@users.noreply.github.com> Date: Tue, 2 Feb 2021 20:12:49 +0530 Subject: [PATCH] Date picker validation fixed (#2789) * Date picker validation fixed * Added date value check to remove unncessary validations Commented defaultDate validation test as invalid date cannot be saved in date control * Fixed date selection on user change Fixed min and max date parsing for date picker control default date * Added validation for min and max date in validations to handle validations when field is converted to JS --- .../FormWidgets/DatePicker_spec.js | 22 +-- .../blueprint/DatePickerComponent.tsx | 34 +++-- .../propertyControls/DatePickerControl.tsx | 88 ++++++++---- .../src/constants/FieldExpectedValue.ts | 2 +- app/client/src/constants/WidgetValidation.ts | 4 +- app/client/src/widgets/DatePickerWidget.tsx | 4 +- app/client/src/workers/validations.ts | 131 +++++++++++++++--- 7 files changed, 210 insertions(+), 75 deletions(-) diff --git a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/DatePicker_spec.js b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/DatePicker_spec.js index 0fc6821471..8653b18df7 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/DatePicker_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/DatePicker_spec.js @@ -101,18 +101,18 @@ describe("DatePicker Widget Functionality", function() { ); }); - it("Datepicker default date validation", function() { - cy.get(formWidgetsPage.defaultDate).click(); - cy.wait(1000); - cy.setDate(-2, "ddd MMM DD YYYY"); - cy.get(formWidgetsPage.defaultDate).should( - "have.css", - "border", - "1px solid rgb(206, 66, 87)", - ); + // it("Datepicker default date validation", function() { + // cy.get(formWidgetsPage.defaultDate).click(); + // cy.wait(1000); + // cy.setDate(-2, "ddd MMM DD YYYY"); + // cy.get(formWidgetsPage.defaultDate).should( + // "have.css", + // "border", + // "1px solid rgb(206, 66, 87)", + // ); - cy.PublishtheApp(); - }); + // cy.PublishtheApp(); + // }); // it("DatePicker-check Required field validation", function() { // // Check the required checkbox diff --git a/app/client/src/components/designSystems/blueprint/DatePickerComponent.tsx b/app/client/src/components/designSystems/blueprint/DatePickerComponent.tsx index 00f556bcc6..0bfd3c00a0 100644 --- a/app/client/src/components/designSystems/blueprint/DatePickerComponent.tsx +++ b/app/client/src/components/designSystems/blueprint/DatePickerComponent.tsx @@ -10,6 +10,7 @@ import { DatePickerType } from "widgets/DatePickerWidget"; import { WIDGET_PADDING } from "constants/WidgetConstants"; import { TimePrecision } from "@blueprintjs/datetime"; import { Colors } from "constants/Colors"; +import { ISO_DATE_FORMAT } from "constants/WidgetValidation"; const StyledControlGroup = styled(ControlGroup)` &&& { @@ -67,10 +68,11 @@ class DatePickerComponent extends React.Component< } componentDidUpdate(prevProps: DatePickerComponentProps) { + const dateFormat = this.props.dateFormat || ISO_DATE_FORMAT; if ( this.props.selectedDate !== this.state.selectedDate && - !moment(this.props.selectedDate, this.props.dateFormat).isSame( - moment(prevProps.selectedDate, this.props.dateFormat), + !moment(this.props.selectedDate, dateFormat).isSame( + moment(prevProps.selectedDate, dateFormat), "seconds", ) ) { @@ -81,13 +83,13 @@ class DatePickerComponent extends React.Component< render() { const now = moment(); const year = now.get("year"); + const dateFormat = this.props.dateFormat || ISO_DATE_FORMAT; const minDate = this.props.minDate - ? moment(this.props.minDate) + ? moment(this.props.minDate, dateFormat) : now.clone().set({ month: 0, date: 1, year: year - 100 }); const maxDate = this.props.maxDate - ? moment(this.props.maxDate) + ? moment(this.props.maxDate, dateFormat) : now.clone().set({ month: 11, date: 31, year: year + 20 }); - return ( { - return moment(date).format(this.props.dateFormat); + const dateFormat = this.props.dateFormat || ISO_DATE_FORMAT; + return moment(date).format(dateFormat); }; parseDate = (dateStr: string): Date => { - return moment(dateStr, this.props.dateFormat).toDate(); + const dateFormat = this.props.dateFormat || ISO_DATE_FORMAT; + return moment(dateStr, dateFormat).toDate(); }; /** @@ -156,16 +160,18 @@ class DatePickerComponent extends React.Component< * * @param selectedDate */ - onDateSelected = (selectedDate: Date) => { - const { onDateSelected } = this.props; + onDateSelected = (selectedDate: Date, isUserChange: boolean) => { + if (isUserChange) { + const { onDateSelected } = this.props; - const date = selectedDate ? this.formatDate(selectedDate) : ""; - this.setState({ selectedDate: date }); + const date = selectedDate ? this.formatDate(selectedDate) : ""; + this.setState({ selectedDate: date }); - // if date is null ( if date is cleared ), don't call onDateSelected - if (!selectedDate) return false; + // if date is null ( if date is cleared ), don't call onDateSelected + if (!selectedDate) return false; - onDateSelected(date); + onDateSelected(date); + } }; } diff --git a/app/client/src/components/propertyControls/DatePickerControl.tsx b/app/client/src/components/propertyControls/DatePickerControl.tsx index 735b322848..b6c5a22e1d 100644 --- a/app/client/src/components/propertyControls/DatePickerControl.tsx +++ b/app/client/src/components/propertyControls/DatePickerControl.tsx @@ -7,6 +7,7 @@ import { TimePrecision } from "@blueprintjs/datetime"; import { WidgetProps } from "widgets/BaseWidget"; import { Toaster } from "components/ads/Toast"; import { Variant } from "components/ads/common"; +import { ISO_DATE_FORMAT } from "constants/WidgetValidation"; const DatePickerControlWrapper = styled.div<{ isValid: boolean }>` display: flex; @@ -62,8 +63,17 @@ class DatePickerControl extends BaseControl< } render() { + const dateFormat = + this.props.widgetProperties.dateFormat || ISO_DATE_FORMAT; + const isValid = this.state.selectedDate + ? this.validateDate(moment(this.state.selectedDate, dateFormat).toDate()) + : true; + const maxDate = + this.props.widgetProperties?.evaluatedValues?.maxDate ?? this.maxDate; + const minDate = + this.props.widgetProperties?.evaluatedValues?.minDate ?? this.minDate; return ( - + { - const selectedDate = date ? this.formatDate(date) : undefined; - const isValid = this.validateDate(date); + onDateSelected = (date: Date, isUserChange: boolean): void => { + if (isUserChange) { + const selectedDate = date ? this.formatDate(date) : undefined; + const isValid = this.validateDate(date); - if (!isValid) return; + if (!isValid) return; - // if everything is ok, put date in state - this.setState({ selectedDate: selectedDate }); - this.updateProperty(this.props.propertyName, selectedDate); + // if everything is ok, put date in state + this.setState({ selectedDate: selectedDate }); + this.updateProperty(this.props.propertyName, selectedDate); + } }; /** @@ -108,17 +128,37 @@ class DatePickerControl extends BaseControl< * 2. if default date is in range of min and max date */ validateDate = (date: Date): boolean => { - const parsedSelectedDate = moment( - date, - this.props.widgetProperties.dateFormat, - ); - + const dateFormat = + this.props.widgetProperties.dateFormat || ISO_DATE_FORMAT; + const parsedSelectedDate = moment(date, dateFormat); + //validate defaultDate if both minDate and maxDate is already selected + if (this.props.propertyName === "defaultDate") { + if ( + parsedSelectedDate.isValid() && + this.props.widgetProperties?.evaluatedValues?.minDate && + this.props.widgetProperties?.evaluatedValues?.maxDate + ) { + const parsedMinDate = moment( + this.props.widgetProperties.evaluatedValues.minDate, + dateFormat, + ); + const parsedMaxDate = moment( + this.props.widgetProperties.evaluatedValues.maxDate, + dateFormat, + ); + if ( + parsedSelectedDate.isBefore(parsedMinDate) || + parsedSelectedDate.isAfter(parsedMaxDate) + ) { + return false; + } + } + } if (this.props.widgetProperties?.evaluatedValues?.value) { const parsedWidgetDate = moment( this.props.widgetProperties.evaluatedValues.value, - this.props.widgetProperties.dateFormat, + dateFormat, ); - // checking if widget date is after min date if (this.props.propertyName === "minDate") { if ( @@ -149,21 +189,19 @@ class DatePickerControl extends BaseControl< } } } - return true; }; formatDate = (date: Date): string => { - return moment(date).format( - this.props.widgetProperties.dateFormat || "DD/MM/YYYY HH:mm", - ); + const dateFormat = + this.props.widgetProperties.dateFormat || ISO_DATE_FORMAT; + return moment(date).format(dateFormat); }; parseDate = (dateStr: string): Date => { - return moment( - dateStr, - this.props.widgetProperties.dateFormat || "DD/MM/YYYY HH:mm", - ).toDate(); + const dateFormat = + this.props.widgetProperties.dateFormat || ISO_DATE_FORMAT; + return moment(dateStr, dateFormat).toDate(); }; static getControlType() { diff --git a/app/client/src/constants/FieldExpectedValue.ts b/app/client/src/constants/FieldExpectedValue.ts index a41adfe2d1..2568af4926 100644 --- a/app/client/src/constants/FieldExpectedValue.ts +++ b/app/client/src/constants/FieldExpectedValue.ts @@ -18,7 +18,7 @@ const FIELD_VALUES: Record< isVisible: "boolean", }, DATE_PICKER_WIDGET: { - defaultDate: "Date", + defaultDate: "string", //TODO:Vicky validate this property isRequired: "boolean", isVisible: "boolean", isDisabled: "boolean", diff --git a/app/client/src/constants/WidgetValidation.ts b/app/client/src/constants/WidgetValidation.ts index 3193be57fd..5e069f413b 100644 --- a/app/client/src/constants/WidgetValidation.ts +++ b/app/client/src/constants/WidgetValidation.ts @@ -14,6 +14,8 @@ export const VALIDATION_TYPES = { OPTIONS_DATA: "OPTIONS_DATA", DATE: "DATE", DEFAULT_DATE: "DEFAULT_DATE", + MIN_DATE: "MIN_DATE", + MAX_DATE: "MAX_DATE", TABS_DATA: "TABS_DATA", CHART_DATA: "CHART_DATA", MARKERS: "MARKERS", @@ -39,7 +41,7 @@ export type Validator = ( dataTree?: DataTree, ) => ValidationResponse; -export const ISO_DATE_FORMAT = "YYYY-MM-DDTHH:mm:ss.SSSZ"; +export const ISO_DATE_FORMAT = "YYYY-MM-DDTHH:mm:ss.Z"; export const JAVASCRIPT_KEYWORDS = { true: "true", diff --git a/app/client/src/widgets/DatePickerWidget.tsx b/app/client/src/widgets/DatePickerWidget.tsx index 8abaaf1306..5489d9bbe7 100644 --- a/app/client/src/widgets/DatePickerWidget.tsx +++ b/app/client/src/widgets/DatePickerWidget.tsx @@ -25,8 +25,8 @@ class DatePickerWidget extends BaseWidget { dateFormat: VALIDATION_TYPES.TEXT, label: VALIDATION_TYPES.TEXT, datePickerType: VALIDATION_TYPES.TEXT, - maxDate: VALIDATION_TYPES.DATE, - minDate: VALIDATION_TYPES.DATE, + maxDate: VALIDATION_TYPES.MAX_DATE, + minDate: VALIDATION_TYPES.MIN_DATE, isRequired: VALIDATION_TYPES.BOOLEAN, // onDateSelected: VALIDATION_TYPES.ACTION_SELECTOR, // onDateRangeSelected: VALIDATION_TYPES.ACTION_SELECTOR, diff --git a/app/client/src/workers/validations.ts b/app/client/src/workers/validations.ts index 9b8060627e..7532ee9bd2 100644 --- a/app/client/src/workers/validations.ts +++ b/app/client/src/workers/validations.ts @@ -390,14 +390,8 @@ export const VALIDATORS: Record = { dateString: string, props: WidgetProps, ): ValidationResponse => { - const today = moment() - .hour(0) - .minute(0) - .second(0) - .millisecond(0); const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT; - const todayDateString = today.format(dateFormat); if (dateString === undefined) { return { isValid: false, @@ -409,10 +403,16 @@ export const VALIDATORS: Record = { }; } const isValid = moment(dateString, dateFormat).isValid(); - const parsed = isValid ? dateString : todayDateString; + if (!isValid) { + return { + isValid: isValid, + parsed: "", + message: `${WIDGET_TYPE_VALIDATION_ERROR}: Date`, + }; + } return { isValid, - parsed, + parsed: dateString, message: isValid ? "" : `${WIDGET_TYPE_VALIDATION_ERROR}: Date`, }; }, @@ -420,14 +420,7 @@ export const VALIDATORS: Record = { dateString: string, props: WidgetProps, ): ValidationResponse => { - const today = moment() - .hour(0) - .minute(0) - .second(0) - .millisecond(0); const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT; - - const todayDateString = today.format(dateFormat); if (dateString === undefined) { return { isValid: false, @@ -460,13 +453,109 @@ export const VALIDATORS: Record = { isValid = false; } } - - const parsed = isValid ? dateString : todayDateString; - + if (!isValid) { + return { + isValid: isValid, + parsed: "", + message: `${WIDGET_TYPE_VALIDATION_ERROR}: Date R`, + }; + } return { - isValid, - parsed, - message: isValid ? "" : `${WIDGET_TYPE_VALIDATION_ERROR}: Date R`, + isValid: isValid, + parsed: dateString, + message: "", + }; + }, + [VALIDATION_TYPES.MIN_DATE]: ( + dateString: string, + props: WidgetProps, + ): ValidationResponse => { + const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT; + if (dateString === undefined) { + return { + isValid: false, + parsed: "", + message: + `${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + props.dateFormat + ? props.dateFormat + : "", + }; + } + const parsedMinDate = moment(dateString, dateFormat); + let isValid = parsedMinDate.isValid(); + if (!props.defaultDate) { + return { + isValid: isValid, + parsed: dateString, + message: "", + }; + } + const parsedDefaultDate = moment(props.defaultDate, dateFormat); + + if ( + isValid && + parsedDefaultDate.isValid() && + parsedDefaultDate.isBefore(parsedMinDate) + ) { + isValid = false; + } + if (!isValid) { + return { + isValid: isValid, + parsed: "", + message: `${WIDGET_TYPE_VALIDATION_ERROR}: Date R`, + }; + } + return { + isValid: isValid, + parsed: dateString, + message: "", + }; + }, + [VALIDATION_TYPES.MAX_DATE]: ( + dateString: string, + props: WidgetProps, + ): ValidationResponse => { + const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT; + if (dateString === undefined) { + return { + isValid: false, + parsed: "", + message: + `${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + props.dateFormat + ? props.dateFormat + : "", + }; + } + const parsedMaxDate = moment(dateString, dateFormat); + let isValid = parsedMaxDate.isValid(); + if (!props.defaultDate) { + return { + isValid: isValid, + parsed: dateString, + message: "", + }; + } + const parsedDefaultDate = moment(props.defaultDate, dateFormat); + + if ( + isValid && + parsedDefaultDate.isValid() && + parsedDefaultDate.isAfter(parsedMaxDate) + ) { + isValid = false; + } + if (!isValid) { + return { + isValid: isValid, + parsed: "", + message: `${WIDGET_TYPE_VALIDATION_ERROR}: Date R`, + }; + } + return { + isValid: isValid, + parsed: dateString, + message: "", }; }, [VALIDATION_TYPES.ACTION_SELECTOR]: (value: any): ValidationResponse => {