Feature: DatePicker V2 (#2889)

Co-authored-by: vicky-primathon.in <vicky.bansal@primathon.in>
Co-authored-by: nandan.anantharamu <nandan.anantharamu@thoughtspot.com>
Co-authored-by: Abhinav Jha <abhinav@appsmith.com>
This commit is contained in:
NandanAnantharamu 2021-02-23 18:05:09 +05:30 committed by GitHub
parent 72df6fb299
commit 6c80f23201
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 1091 additions and 223 deletions

View File

@ -0,0 +1,80 @@
{
"dsl": {
"widgetName": "MainContainer",
"backgroundColor": "none",
"rightColumn": 1224,
"snapColumns": 16,
"detachFromLayout": true,
"widgetId": "0",
"topRow": 0,
"bottomRow": 1280,
"containerStyle": "none",
"snapRows": 33,
"parentRowSpace": 1,
"type": "CANVAS_WIDGET",
"canExtend": true,
"version": 9,
"minHeight": 1292,
"parentColumnSpace": 1,
"dynamicBindingPathList": [],
"leftColumn": 0,
"children": [
{
"isVisible": true,
"isDisabled": false,
"datePickerType": "DATE_PICKER",
"label": "",
"dateFormat": "DD/MM/YYYY HH:mm",
"widgetName": "DatePicker1",
"defaultDate": "2021-02-05T10:53:12.791Z",
"version": 2,
"type": "DATE_PICKER_WIDGET2",
"isLoading": false,
"parentColumnSpace": 74,
"parentRowSpace": 40,
"leftColumn": 5,
"rightColumn": 10,
"topRow": 0,
"bottomRow": 1,
"parentId": "0",
"widgetId": "w4htilgv5t"
},
{
"isVisible": true,
"text": "Label",
"textStyle": "LABEL",
"textAlign": "LEFT",
"widgetName": "Text1",
"version": 1,
"type": "TEXT_WIDGET",
"isLoading": false,
"parentColumnSpace": 74,
"parentRowSpace": 40,
"leftColumn": 1,
"rightColumn": 5,
"topRow": 3,
"bottomRow": 4,
"parentId": "0",
"widgetId": "voohxsv4t2"
},
{
"isVisible": true,
"text": "Label",
"textStyle": "LABEL",
"textAlign": "LEFT",
"widgetName": "Text2",
"version": 1,
"type": "TEXT_WIDGET",
"isLoading": false,
"parentColumnSpace": 74,
"parentRowSpace": 40,
"leftColumn": 8,
"rightColumn": 12,
"topRow": 3,
"bottomRow": 4,
"parentId": "0",
"widgetId": "xif8wugzjv"
}
]
}
}

View File

@ -13,6 +13,7 @@ describe("Duplicate application", function() {
cy.get(commonlocators.homeIcon).click({ force: true });
const appname = localStorage.getItem("AppName");
cy.get(homePage.searchInput).type(appname);
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(2000);
cy.get(homePage.applicationCard)

View File

@ -14,6 +14,7 @@ describe("Update Application", function() {
cy.get(commonlocators.homeIcon).click({ force: true });
appname = localStorage.getItem("AppName");
cy.get(homePage.searchInput).type(appname);
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(2000);
cy.get(homePage.applicationCard)
@ -53,6 +54,7 @@ describe("Update Application", function() {
it("Check for errors in updating application name", function() {
cy.get(commonlocators.homeIcon).click({ force: true });
cy.get(homePage.searchInput).type(appname);
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(2000);
cy.get(homePage.applicationCard)
.first()
@ -61,6 +63,7 @@ describe("Update Application", function() {
.first()
.click({ force: true });
cy.get("#loading").should("not.exist");
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(2000);
cy.get(homePage.applicationName).type(" ");
cy.get(homePage.toastMessage).should(
@ -79,6 +82,7 @@ describe("Update Application", function() {
it("Updates the name of first application to very long name and checks whether update is reflected in the application card with a popover", function() {
cy.get(commonlocators.homeIcon).click({ force: true });
cy.get(homePage.searchInput).clear();
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(2000);
cy.get(homePage.applicationCard)
@ -94,6 +98,7 @@ describe("Update Application", function() {
"response.body.responseMeta.status",
200,
);
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(2000);
cy.get(homePage.applicationCard)

View File

@ -31,6 +31,7 @@ describe("Test Create Api and Bind to Table widget", function() {
/**Validate Table data on current page(page1) */
cy.ValidateTableData("1");
cy.get(commonlocators.tableNextPage).click({ force: true });
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(5000);
/*
cy.wait("@postExecute").should(

View File

@ -22,12 +22,14 @@ describe("Binding the button Widgets and validating NavigateTo Page functionalit
.click();
cy.enterNavigatePageName(testdata.externalPage);
cy.get(commonlocators.editPropCrossButton).click({ force: true });
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(300);
});
it("Button click should take the control to page link validation", function() {
cy.PublishtheApp();
cy.get(publish.buttonWidget).click();
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.get(publish.buttonWidget).should("not.exist");
cy.go("back");

View File

@ -16,7 +16,7 @@ describe("JS Toggle tests", () => {
.should("have.class", "is-active");
cy.testJsontext("visible", "false");
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(1000);
cy.get(".t--property-control-visible")

View File

@ -29,6 +29,7 @@ describe("Table Widget and Navigate to functionality validation", function() {
it("Create MyPage and valdiate if its successfully created", function() {
cy.Createpage(pageid);
cy.addDsl(dsl2);
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.get(`.t--entity-name:contains("${pageid}")`).should("be.visible");
});

View File

@ -21,6 +21,7 @@ describe("Test Suite to validate copy/delete/undo functionalites", function() {
formWidgetsPage.formInner,
);
cy.get(commonlocators.copyWidget).click();
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.get(commonlocators.toastBody)
.first()
@ -36,6 +37,7 @@ describe("Test Suite to validate copy/delete/undo functionalites", function() {
expect($lis.eq(1)).to.contain("{{FormTest.data}}");
});
cy.DeleteWidgetFromSideBar();
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.get(apiwidget.propertyList).should("not.exist");
/*
@ -53,6 +55,7 @@ describe("Test Suite to validate copy/delete/undo functionalites", function() {
"response.body.responseMeta.status",
200,
);
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.get(apiwidget.propertyList).then(function($lis) {
expect($lis).to.have.length(2);

View File

@ -24,6 +24,7 @@ describe("Test Suite to validate copy/delete/undo functionalites", function() {
);
cy.get("body").click();
cy.get("body").type(`{${modifierKey}}c`);
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.get(commonlocators.toastBody)
.first()

View File

@ -63,43 +63,45 @@ describe("DatePicker Widget Functionality", function() {
);
});
it("Datepicker min/max date validation", function() {
cy.get(formWidgetsPage.defaultDate).click({ force: true });
cy.SetDateToToday();
// it("Datepicker min/max date validation", function() {
// cy.get(formWidgetsPage.defaultDate).click({ force: true });
// cy.SetDateToToday();
cy.get(formWidgetsPage.minDate)
.first()
.click();
cy.wait(1000);
cy.setDate(-1, "ddd MMM DD YYYY");
// cy.get(formWidgetsPage.minDate)
// .first()
// .click();
// // eslint-disable-next-line cypress/no-unnecessary-waiting
// cy.wait(1000);
// cy.setDate(-1, "ddd MMM DD YYYY");
cy.get(formWidgetsPage.maxDate)
.first()
.click();
cy.wait(1000);
cy.setDate(1, "ddd MMM DD YYYY");
// cy.get(formWidgetsPage.maxDate)
// .first()
// .click();
// // eslint-disable-next-line cypress/no-unnecessary-waiting
// cy.wait(1000);
// cy.setDate(1, "ddd MMM DD YYYY");
cy.PublishtheApp();
cy.get(publishPage.datepickerWidget + " .bp3-input").click();
// cy.PublishtheApp();
// cy.get(publishPage.datepickerWidget + " .bp3-input").click();
const minDate = Cypress.moment()
.add(2, "days")
.format("ddd MMM DD YYYY");
const maxDate = Cypress.moment()
.add(2, "days")
.format("ddd MMM DD YYYY");
// const minDate = Cypress.moment()
// .add(2, "days")
// .format("ddd MMM DD YYYY");
// const maxDate = Cypress.moment()
// .add(2, "days")
// .format("ddd MMM DD YYYY");
cy.get(`.DayPicker-Day[aria-label=\"${minDate}\"]`).should(
"have.attr",
"aria-disabled",
"true",
);
cy.get(`.DayPicker-Day[aria-label=\"${maxDate}\"]`).should(
"have.attr",
"aria-disabled",
"true",
);
});
// cy.get(`.DayPicker-Day[aria-label=\"${minDate}\"]`).should(
// "have.attr",
// "aria-disabled",
// "true",
// );
// cy.get(`.DayPicker-Day[aria-label=\"${maxDate}\"]`).should(
// "have.attr",
// "aria-disabled",
// "true",
// );
// });
// it("Datepicker default date validation", function() {
// cy.get(formWidgetsPage.defaultDate).click();

View File

@ -19,7 +19,7 @@ describe("Form reset functionality", function() {
cy.get(widgetsPage.formButtonWidget)
.contains("Reset")
.click();
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.get(".tr")

View File

@ -32,6 +32,7 @@ describe("Tab widget test", function() {
.click({ force: true })
.should("be.visible");
cy.get(Layoutpage.tabButton).click({ force: true });
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(200);
cy.tabVerify(2, "Day");
cy.get(Layoutpage.tabDelete)

View File

@ -0,0 +1,39 @@
const commonlocators = require("../../../locators/commonlocators.json");
const formWidgetsPage = require("../../../locators/FormWidgets.json");
const dsl = require("../../../fixtures/datePicker2dsl.json");
const publishPage = require("../../../locators/publishWidgetspage.json");
const pages = require("../../../locators/Pages.json");
describe("DatePicker Widget Property pane tests with js bindings", function() {
before(() => {
cy.addDsl(dsl);
});
it("Datepicker default date validation with js binding and default date", function() {
cy.openPropertyPane("datepickerwidget2");
cy.get(".t--property-control-defaultdate .bp3-input").clear();
cy.get(formWidgetsPage.toggleJsDefaultDate).click();
cy.testJsontext("defaultdate", "{{ moment().add(-1,'days') }}");
});
it("Text widgets binding with datepicker", function() {
cy.SearchEntityandOpen("Text1");
cy.testJsontext("text", "{{DatePicker1.formattedDate}}");
cy.closePropertyPane();
cy.SearchEntityandOpen("Text2");
cy.testJsontext("text", "{{DatePicker1.selectedDate}}");
cy.closePropertyPane();
});
it("Text widgets binding with datepicker", function() {
cy.openPropertyPane("datepickerwidget2");
cy.selectDateFormat("DD/MM/YYYY");
cy.assertDateFormat();
cy.closePropertyPane();
cy.assertDateFormat();
});
it("Datepicker default date validation with js binding", function() {
cy.PublishtheApp();
cy.wait(10000);
});
});

View File

@ -0,0 +1,79 @@
const commonlocators = require("../../../locators/commonlocators.json");
const formWidgetsPage = require("../../../locators/FormWidgets.json");
const dsl = require("../../../fixtures/datePicker2dsl.json");
const publishPage = require("../../../locators/publishWidgetspage.json");
const pages = require("../../../locators/Pages.json");
describe("DatePicker Widget Property pane tests with js bindings", function() {
before(() => {
cy.addDsl(dsl);
});
it("Datepicker default date validation with js binding", function() {
cy.openPropertyPane("datepickerwidget2");
cy.get(".t--property-control-defaultdate .bp3-input").clear();
cy.get(formWidgetsPage.toggleJsDefaultDate).click();
cy.testJsontext("defaultdate", "{{moment().toISOString()}}");
cy.get(formWidgetsPage.toggleJsMinDate).click();
cy.testJsontext(
"mindate",
"{{moment().subtract(10, 'days').toISOString()}}",
);
cy.get(formWidgetsPage.toggleJsMaxDate).click();
cy.testJsontext("maxdate", "{{moment().add(10, 'days').toISOString()}}");
/*
cy.get(formWidgetsPage.datepickerWidget + " .bp3-input").should(
"contain.value",
"14/02/2021",
);
cy.PublishtheApp();
cy.get(publishPage.datepickerWidget + " .bp3-input").should(
"contain.value",
"14/02/2021",
);
*/
});
it("Text widgets binding with datepicker", function() {
cy.SearchEntityandOpen("Text1");
cy.testJsontext("text", "{{DatePicker1.formattedDate}}");
cy.closePropertyPane();
cy.SearchEntityandOpen("Text2");
cy.testJsontext("text", "{{DatePicker1.selectedDate}}");
cy.closePropertyPane();
});
it("Text widgets binding with datepicker", function() {
cy.openPropertyPane("datepickerwidget2");
cy.selectDateFormat("YYYY-MM-DD");
cy.assertDateFormat();
cy.selectDateFormat("YYYY-MM-DD HH:mm");
cy.assertDateFormat();
cy.selectDateFormat("YYYY-MM-DDTHH:mm:ss.sssZ");
cy.assertDateFormat();
cy.selectDateFormat("DD/MM/YYYY");
cy.assertDateFormat();
cy.selectDateFormat("DD/MM/YYYY HH:mm");
cy.closePropertyPane();
cy.assertDateFormat();
});
it("Datepicker default date validation with strings", function() {
cy.openPropertyPane("datepickerwidget2");
cy.get(formWidgetsPage.toggleJsDefaultDate).click();
cy.get(".t--property-control-defaultdate .bp3-input").clear();
cy.get(".t--property-control-defaultdate .bp3-input").type("2020-02-01");
cy.closePropertyPane();
cy.openPropertyPane("datepickerwidget2");
cy.get(formWidgetsPage.toggleJsMinDate).click();
cy.get(".t--property-control-mindate .bp3-input").type("2020-01-01");
cy.get(formWidgetsPage.toggleJsMaxDate).click();
cy.get(".t--property-control-maxdate .bp3-input").type("2020-02-10");
cy.closePropertyPane();
});
it("Datepicker default date validation with js binding", function() {
cy.PublishtheApp();
cy.wait(10000);
});
});

View File

@ -0,0 +1,47 @@
const commonlocators = require("../../../locators/commonlocators.json");
const formWidgetsPage = require("../../../locators/FormWidgets.json");
const dsl = require("../../../fixtures/newFormDsl.json");
const publishPage = require("../../../locators/publishWidgetspage.json");
const pages = require("../../../locators/Pages.json");
describe("DatePicker Widget Property pane tests with js bindings", function() {
before(() => {
cy.addDsl(dsl);
});
beforeEach(() => {
cy.openPropertyPane("datepickerwidget");
});
it("Datepicker default date validation with js binding", function() {
cy.get(".t--property-control-defaultdate .bp3-input").clear();
cy.get(formWidgetsPage.toggleJsDefaultDate).click();
cy.testJsontext(
"defaultdate",
"{{moment('14/02/2021', 'DD/MM/YYYY').format('DD/MM/YYYY')}}",
);
cy.get(formWidgetsPage.toggleJsMinDate).click();
cy.testJsontext(
"mindate",
"{{moment('12/02/2021', 'DD/MM/YYYY').format('DD/MM/YYYY')}}",
);
cy.get(formWidgetsPage.toggleJsMaxDate).click();
cy.testJsontext(
"maxdate",
"{{moment('17/02/2021', 'DD/MM/YYYY').format('DD/MM/YYYY')}}",
);
cy.get(formWidgetsPage.datepickerWidget + " .bp3-input").should(
"contain.value",
"14/02/2021",
);
cy.PublishtheApp();
cy.get(publishPage.datepickerWidget + " .bp3-input").should(
"contain.value",
"14/02/2021",
);
});
afterEach(() => {
cy.get(publishPage.backToEditor).click({ force: true });
});
});

View File

@ -24,6 +24,7 @@ describe("Update Organization", function() {
localStorage.setItem("OrgName", orgid);
cy.get(homePage.orgNameInput).clear();
cy.get(homePage.orgNameInput).type(orgid);
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(2000);
cy.get(homePage.orgHeaderName).should("have.text", orgid);
});

View File

@ -29,6 +29,7 @@ describe("Addwidget from Query and bind with other widgets", function() {
.first()
.focus()
.type("SELECT * FROM configs LIMIT 10;");
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.get(queryEditor.runQuery).click();
cy.wait("@postExecute").should(

View File

@ -22,6 +22,7 @@ describe("Add widget", function() {
.first()
.focus()
.type("select * from configs");
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.get(queryEditor.runQuery).click();
cy.wait("@postExecute").should(

View File

@ -26,8 +26,10 @@ describe("Login from UI and check the functionality", function() {
cy.LogintoApp(Cypress.env("USERNAME"), Cypress.env("PASSWORD"));
cy.get(homePage.profileMenu).click();
cy.get(homePage.signOutIcon).click();
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.get(homePage.headerAppSmithLogo).click();
// eslint-disable-next-line cypress/no-unnecessary-waiting
cy.wait(500);
cy.url().should("include", "user/login");
});

View File

@ -1,37 +1,36 @@
{
"checkboxWidget": ".t--draggable-checkboxwidget",
"dropdownWidget": ".t--draggable-dropdownwidget",
"dropdownSelectionType": ".t--property-control-selectiontype",
"radioWidget": ".t--draggable-radiogroupwidget",
"radioOnSelectionChangeDropdown": ".t--property-control-onselectionchange",
"nextDayBtn": ".DayPicker-Day[aria-selected='true'] + div.DayPicker-Day",
"datepickerWidget": ".t--draggable-datepickerwidget",
"defaultDate": ".t--property-control-defaultdate input",
"minDate": ".t--property-control-mindate input",
"maxDate": ".t--property-control-maxdate input",
"filepickerWidget": ".t--draggable-filepickerwidget",
"formWidget": ".t--draggable-formwidget",
"richTextEditorWidget": ".t--draggable-richtexteditorwidget",
"richEditorOnTextChange": ".t--property-control-ontextchange",
"optionvalue": ".t--property-control-defaultselectedvalue .kDwnRc",
"defselected": ".CodeMirror textarea",
"dropdowninner": ".bp3-button > .bp3-button-text",
"Textinput": ".t--property-control-options .CodeMirror-code",
"labelvalue": ".t--draggable-dropdownwidget label",
"dropdownInput": ".bp3-tag-input-values",
"labelradio": ".t--draggable-radiogroupwidget label",
"deleteradiovalue": ".t--property-control-options mask",
"inputRadio": ".t--draggable-radiogroupwidget input",
"defaultSelect": ".t--property-control-defaultselectedvalue .CodeMirror-code",
"formInner": ".t--draggable-formwidget span.t--widget-name",
"radioInput": ".t--draggable-radiogroupwidget span.t--widget-name",
"radioAddButton": ".t--property-control-options button",
"formD": "div[type='FORM_WIDGET']",
"datepickerFooter": ".bp3-datepicker-footer span",
"disableJs":".t--property-control-disable input[type='checkbox']",
"switchWidget": ".t--draggable-switchwidget",
"toggleJsDefaultDate": ".t--property-control-defaultdate .t--js-toggle",
"toggleJsMinDate": ".t--property-control-mindate .t--js-toggle",
"toggleJsMaxDate": ".t--property-control-maxdate .t--js-toggle"
}
"checkboxWidget": ".t--draggable-checkboxwidget",
"dropdownWidget": ".t--draggable-dropdownwidget",
"dropdownSelectionType": ".t--property-control-selectiontype",
"radioWidget": ".t--draggable-radiogroupwidget",
"radioOnSelectionChangeDropdown": ".t--property-control-onselectionchange",
"nextDayBtn": ".DayPicker-Day[aria-selected='true'] + div.DayPicker-Day",
"datepickerWidget": ".t--draggable-datepickerwidget",
"defaultDate": ".t--property-control-defaultdate input",
"minDate": ".t--property-control-mindate input",
"maxDate": ".t--property-control-maxdate input",
"filepickerWidget": ".t--draggable-filepickerwidget",
"formWidget": ".t--draggable-formwidget",
"richTextEditorWidget": ".t--draggable-richtexteditorwidget",
"richEditorOnTextChange": ".t--property-control-ontextchange",
"optionvalue": ".t--property-control-defaultselectedvalue .kDwnRc",
"defselected": ".CodeMirror textarea",
"dropdowninner": ".bp3-button > .bp3-button-text",
"Textinput": ".t--property-control-options .CodeMirror-code",
"labelvalue": ".t--draggable-dropdownwidget label",
"dropdownInput": ".bp3-tag-input-values",
"labelradio": ".t--draggable-radiogroupwidget label",
"deleteradiovalue": ".t--property-control-options mask",
"inputRadio": ".t--draggable-radiogroupwidget input",
"defaultSelect": ".t--property-control-defaultselectedvalue .CodeMirror-code",
"formInner": ".t--draggable-formwidget span.t--widget-name",
"radioInput": ".t--draggable-radiogroupwidget span.t--widget-name",
"radioAddButton": ".t--property-control-options button",
"formD": "div[type='FORM_WIDGET']",
"datepickerFooter": ".bp3-datepicker-footer span",
"disableJs": ".t--property-control-disable input[type='checkbox']",
"switchWidget": ".t--draggable-switchwidget",
"toggleJsDefaultDate": ".t--property-control-defaultdate .t--js-toggle",
"toggleJsMinDate": ".t--property-control-mindate .t--js-toggle",
"toggleJsMaxDate": ".t--property-control-maxdate .t--js-toggle"
}

View File

@ -690,6 +690,37 @@ Cypress.Commands.add("switchToAPIInputTab", () => {
.click({ force: true });
});
Cypress.Commands.add("selectDateFormat", (value) => {
cy.get(".t--property-control-dateformat button")
.first()
.click({ force: true });
cy.get("ul.bp3-menu")
.children()
.contains(value)
.click();
});
Cypress.Commands.add("assertDateFormat", () => {
cy.get(".t--draggable-datepickerwidget2 input")
.first()
.invoke("attr", "value")
.then((text) => {
const firstTxt = text;
cy.log("date time : ", firstTxt);
cy.get(commonlocators.labelTextStyle)
.first()
.should("contain", firstTxt);
cy.get(commonlocators.labelTextStyle)
.last()
.invoke("text")
.then((text) => {
const secondText = text;
cy.log("date time : ", secondText);
expect(firstTxt).not.to.equal(secondText);
});
});
});
Cypress.Commands.add("selectPaginationType", (option) => {
cy.xpath(option).click({ force: true });
});

View File

@ -8,7 +8,7 @@ import { ControlIcons } from "icons/ControlIcons";
import { AnyStyledComponent } from "styled-components";
import { Skin } from "constants/DefaultTheme";
import AutoToolTipComponent from "components/designSystems/appsmith/TableComponent/AutoToolTipComponent";
import DatePickerComponent from "components/designSystems/blueprint/DatePickerComponent";
import DatePickerComponent from "components/designSystems/blueprint/DatePickerComponent2";
import {
OperatorTypes,
Condition,

View File

@ -1,6 +1,10 @@
import React from "react";
import styled from "styled-components";
import { getBorderCSSShorthand, labelStyle } from "constants/DefaultTheme";
import {
getBorderCSSShorthand,
labelStyle,
IntentColors,
} from "constants/DefaultTheme";
import { ControlGroup, Classes, Label } from "@blueprintjs/core";
import { ComponentProps } from "components/designSystems/appsmith/BaseComponent";
import { DateInput } from "@blueprintjs/datetime";
@ -11,24 +15,30 @@ import { WIDGET_PADDING } from "constants/WidgetConstants";
import { TimePrecision } from "@blueprintjs/datetime";
import { Colors } from "constants/Colors";
import { ISO_DATE_FORMAT } from "constants/WidgetValidation";
import ErrorTooltip from "components/editorComponents/ErrorTooltip";
import { DATE_WIDGET_DEFAULT_VALIDATION_ERROR } from "constants/messages";
const StyledControlGroup = styled(ControlGroup)`
const StyledControlGroup = styled(ControlGroup)<{ isValid: boolean }>`
&&& {
.${Classes.INPUT} {
box-shadow: none;
color: ${Colors.OXFORD_BLUE};
font-size: ${(props) => props.theme.fontSizes[3]}px;
border: ${(props) => getBorderCSSShorthand(props.theme.borders[2])};
border-color: ${(props) =>
!props.isValid ? IntentColors.danger : Colors.GEYSER_LIGHT};
border-radius: 0;
width: 100%;
height: inherit;
align-items: center;
&:active {
border-color: ${Colors.HIT_GRAY};
border-color: ${(props) =>
!props.isValid ? IntentColors.danger : Colors.HIT_GRAY};
}
&:focus {
border: ${(props) => getBorderCSSShorthand(props.theme.borders[2])};
border-color: #80bdff;
border-color: ${(props) =>
!props.isValid ? IntentColors.danger : "#80bdff"};
outline: 0;
box-shadow: 0 0 0 0.1rem rgba(0, 123, 255, 0.25);
}
@ -95,10 +105,17 @@ class DatePickerComponent extends React.Component<
.clone()
.set({ month: 11, date: 31, year: year + 20 })
.toDate();
const isValid = this.state.selectedDate
? this.isValidDate(this.parseDate(this.state.selectedDate))
: true;
const value =
isValid && this.state.selectedDate
? this.parseDate(this.state.selectedDate)
: null;
return (
<StyledControlGroup
fill
isValid={isValid}
onClick={(e: any) => {
e.stopPropagation();
}}
@ -115,29 +132,58 @@ class DatePickerComponent extends React.Component<
</Label>
)}
{
<DateInput
className={this.props.isLoading ? "bp3-skeleton" : ""}
formatDate={this.formatDate}
parseDate={this.parseDate}
placeholder={"Select Date"}
disabled={this.props.isDisabled}
showActionsBar={true}
timePrecision={TimePrecision.MINUTE}
closeOnSelection
onChange={this.onDateSelected}
value={
this.state.selectedDate
? this.parseDate(this.state.selectedDate)
: null
}
minDate={minDate}
maxDate={maxDate}
/>
<ErrorTooltip
isOpen={!isValid}
message={DATE_WIDGET_DEFAULT_VALIDATION_ERROR}
>
<DateInput
className={this.props.isLoading ? "bp3-skeleton" : ""}
formatDate={this.formatDate}
parseDate={this.parseDate}
placeholder={"Select Date"}
disabled={this.props.isDisabled}
showActionsBar={true}
timePrecision={TimePrecision.MINUTE}
closeOnSelection
onChange={this.onDateSelected}
value={value}
minDate={minDate}
maxDate={maxDate}
/>
</ErrorTooltip>
}
</StyledControlGroup>
);
}
isValidDate = (date: Date): boolean => {
let isValid = true;
const dateFormat = this.props.dateFormat || ISO_DATE_FORMAT;
const parsedCurrentDate = moment(date);
if (this.props.minDate) {
const parsedMinDate = moment(this.props.minDate, dateFormat);
if (
this.props.minDate &&
parsedMinDate.isValid() &&
parsedCurrentDate.isBefore(parsedMinDate)
) {
isValid = false;
}
}
if (this.props.maxDate) {
const parsedMaxDate = moment(this.props.maxDate, dateFormat);
if (
isValid &&
this.props.maxDate &&
parsedMaxDate.isValid() &&
parsedCurrentDate.isAfter(parsedMaxDate)
) {
isValid = false;
}
}
return isValid;
};
formatDate = (date: Date): string => {
const dateFormat = this.props.dateFormat || ISO_DATE_FORMAT;
return moment(date).format(dateFormat);
@ -165,9 +211,6 @@ class DatePickerComponent extends React.Component<
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;
onDateSelected(date);
}
};

View File

@ -0,0 +1,236 @@
import React from "react";
import styled from "styled-components";
import { labelStyle, IntentColors } from "constants/DefaultTheme";
import { ControlGroup, Classes, Label } from "@blueprintjs/core";
import { ComponentProps } from "components/designSystems/appsmith/BaseComponent";
import { DateInput } from "@blueprintjs/datetime";
import moment from "moment-timezone";
import "../../../../node_modules/@blueprintjs/datetime/lib/css/blueprint-datetime.css";
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";
import ErrorTooltip from "components/editorComponents/ErrorTooltip";
import { DATE_WIDGET_DEFAULT_VALIDATION_ERROR } from "constants/messages";
const StyledControlGroup = styled(ControlGroup)<{ isValid: boolean }>`
&&& {
.${Classes.INPUT} {
box-shadow: none;
border: 1px solid;
border-color: ${(props) =>
!props.isValid ? IntentColors.danger : Colors.GEYSER_LIGHT};
border-radius: ${(props) => props.theme.radii[1]}px;
width: 100%;
height: inherit;
align-items: center;
&:active {
border-color: ${(props) =>
!props.isValid ? IntentColors.danger : Colors.HIT_GRAY};
}
&:focus {
border-color: ${(props) =>
!props.isValid ? IntentColors.danger : Colors.MYSTIC};
}
}
.${Classes.INPUT_GROUP} {
display: block;
margin: 0;
}
.${Classes.CONTROL_GROUP} {
justify-content: flex-start;
}
label {
${labelStyle}
flex: 0 1 30%;
margin: 7px ${WIDGET_PADDING * 2}px 0 0;
text-align: right;
align-self: flex-start;
max-width: calc(30% - ${WIDGET_PADDING}px);
}
}
&&& {
input {
border: 1px solid;
border-color: ${(props) =>
!props.isValid ? IntentColors.danger : Colors.HIT_GRAY};
border-radius: ${(props) => props.theme.radii[1]}px;
box-shadow: none;
color: ${Colors.OXFORD_BLUE};
font-size: ${(props) => props.theme.fontSizes[3]}px;
}
}
`;
class DatePickerComponent extends React.Component<
DatePickerComponentProps,
DatePickerComponentState
> {
constructor(props: DatePickerComponentProps) {
super(props);
this.state = {
selectedDate: props.selectedDate,
};
}
componentDidUpdate(prevProps: DatePickerComponentProps) {
if (
this.props.selectedDate !== this.state.selectedDate &&
!moment(this.props.selectedDate).isSame(
moment(prevProps.selectedDate),
"seconds",
)
) {
this.setState({ selectedDate: this.props.selectedDate });
}
}
getValidDate = (date: string, format: string) => {
const _date = moment(date, format);
return _date.isValid() ? _date.toDate() : undefined;
};
render() {
const now = moment();
const year = now.get("year");
const minDate = this.props.minDate
? new Date(this.props.minDate)
: now
.clone()
.set({ month: 0, date: 1, year: year - 100 })
.toDate();
const maxDate = this.props.maxDate
? new Date(this.props.maxDate)
: now
.clone()
.set({ month: 11, date: 31, year: year + 20 })
.toDate();
const isValid = this.state.selectedDate
? this.isValidDate(new Date(this.state.selectedDate))
: true;
const value =
isValid && this.state.selectedDate
? new Date(this.state.selectedDate)
: null;
return (
<StyledControlGroup
fill
isValid={isValid}
onClick={(e: any) => {
e.stopPropagation();
}}
>
{this.props.label && (
<Label
className={
this.props.isLoading
? Classes.SKELETON
: Classes.TEXT_OVERFLOW_ELLIPSIS
}
>
{this.props.label}
</Label>
)}
{
<ErrorTooltip
isOpen={!isValid}
message={DATE_WIDGET_DEFAULT_VALIDATION_ERROR}
>
<DateInput
className={this.props.isLoading ? "bp3-skeleton" : ""}
formatDate={this.formatDate}
parseDate={this.parseDate}
placeholder={"Select Date"}
disabled={this.props.isDisabled}
showActionsBar={true}
timePrecision={TimePrecision.MINUTE}
closeOnSelection
onChange={this.onDateSelected}
value={value}
minDate={minDate}
maxDate={maxDate}
/>
</ErrorTooltip>
}
</StyledControlGroup>
);
}
isValidDate = (date: Date): boolean => {
let isValid = true;
const parsedCurrentDate = moment(date);
if (this.props.minDate) {
const parsedMinDate = moment(this.props.minDate);
if (
this.props.minDate &&
parsedMinDate.isValid() &&
parsedCurrentDate.isBefore(parsedMinDate)
) {
isValid = false;
}
}
if (this.props.maxDate) {
const parsedMaxDate = moment(this.props.maxDate);
if (
isValid &&
this.props.maxDate &&
parsedMaxDate.isValid() &&
parsedCurrentDate.isAfter(parsedMaxDate)
) {
isValid = false;
}
}
return isValid;
};
formatDate = (date: Date): string => {
const dateFormat = this.props.dateFormat || ISO_DATE_FORMAT;
return moment(date).format(dateFormat);
};
parseDate = (dateStr: string): Date => {
const date = moment(dateStr);
if (date.isValid()) return moment(dateStr).toDate();
else return moment().toDate();
};
/**
* checks if selelectedDate is null or not,
* sets state and calls props onDateSelected
* if its null, don't call onDateSelected
*
* @param selectedDate
*/
onDateSelected = (selectedDate: Date, isUserChange: boolean) => {
if (isUserChange) {
const { onDateSelected } = this.props;
const date = selectedDate ? selectedDate.toISOString() : "";
this.setState({ selectedDate: date });
onDateSelected(date);
}
};
}
interface DatePickerComponentProps extends ComponentProps {
label: string;
dateFormat: string;
enableTimePicker?: boolean;
selectedDate?: string;
minDate?: string;
maxDate?: string;
timezone?: string;
datePickerType: DatePickerType;
isDisabled: boolean;
onDateSelected: (selectedDate: string) => void;
isLoading: boolean;
}
interface DatePickerComponentState {
selectedDate?: string;
}
export default DatePickerComponent;

View File

@ -5,8 +5,6 @@ import moment from "moment-timezone";
import styled from "styled-components";
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 }>`
@ -63,18 +61,22 @@ class DatePickerControl extends BaseControl<
}
render() {
const version = this.props.widgetProperties.version;
const dateFormat =
this.props.widgetProperties.dateFormat || ISO_DATE_FORMAT;
version === 2
? ISO_DATE_FORMAT
: 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;
const value =
this.props.propertyValue && isValid
? version === 2
? new Date(this.props.propertyValue)
: this.parseDate(this.props.propertyValue)
: null;
return (
<DatePickerControlWrapper isValid={isValid}>
<DatePickerControlWrapper isValid={true}>
<StyledDatePicker
formatDate={this.formatDate}
parseDate={this.parseDate}
@ -83,21 +85,7 @@ class DatePickerControl extends BaseControl<
timePrecision={TimePrecision.MINUTE}
closeOnSelection
onChange={this.onDateSelected}
maxDate={
this.props.propertyName === "defaultDate"
? this.getValidDate(maxDate, dateFormat)
: undefined
}
minDate={
this.props.propertyName === "defaultDate"
? this.getValidDate(minDate, dateFormat)
: undefined
}
value={
this.props.propertyValue && isValid
? this.parseDate(this.props.propertyValue)
: null
}
value={value}
/>
</DatePickerControlWrapper>
);
@ -117,10 +105,13 @@ class DatePickerControl extends BaseControl<
*/
onDateSelected = (date: Date, isUserChange: boolean): void => {
if (isUserChange) {
const selectedDate = date ? this.formatDate(date) : undefined;
const selectedDate = date
? this.props.widgetProperties.version === 2
? date.toISOString()
: this.formatDate(date)
: undefined;
const isValid = this.validateDate(date);
if (!isValid) return;
// if everything is ok, put date in state
this.setState({ selectedDate: selectedDate });
this.updateProperty(this.props.propertyName, selectedDate);
@ -128,73 +119,14 @@ class DatePickerControl extends BaseControl<
};
/**
* checks:
* 1. if max date is greater than the default date
* 2. if default date is in range of min and max date
* checks if date is of valid date format
*/
validateDate = (date: Date): boolean => {
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,
dateFormat,
);
// checking if widget date is after min date
if (this.props.propertyName === "minDate") {
if (
parsedSelectedDate.isValid() &&
parsedWidgetDate.isBefore(parsedSelectedDate)
) {
Toaster.show({
text: "Min date cannot be greater than current widget value.",
variant: Variant.danger,
});
return false;
}
}
// checking if widget date is before max date
if (this.props.propertyName === "maxDate") {
if (
parsedSelectedDate.isValid() &&
parsedWidgetDate.isAfter(parsedSelectedDate)
) {
Toaster.show({
text: "Max date cannot be less than current widget value.",
variant: Variant.danger,
});
return false;
}
}
}
return true;
this.props.widgetProperties.version === 2
? ISO_DATE_FORMAT
: this.props.widgetProperties.dateFormat || ISO_DATE_FORMAT;
return date ? moment(date, dateFormat).isValid() : true;
};
formatDate = (date: Date): string => {
@ -205,7 +137,9 @@ class DatePickerControl extends BaseControl<
parseDate = (dateStr: string): Date => {
const dateFormat =
this.props.widgetProperties.dateFormat || ISO_DATE_FORMAT;
this.props.widgetProperties.version === 2
? ISO_DATE_FORMAT
: this.props.widgetProperties.dateFormat || ISO_DATE_FORMAT;
const date = moment(dateStr, dateFormat);
if (date.isValid()) return moment(dateStr, dateFormat).toDate();

View File

@ -24,6 +24,13 @@ const FIELD_VALUES: Record<
isDisabled: "boolean",
// onDateSelected: "Function Call",
},
DATE_PICKER_WIDGET2: {
defaultDate: "string", //TODO:Vicky validate this property
isRequired: "boolean",
isVisible: "boolean",
isDisabled: "boolean",
// onDateSelected: "Function Call",
},
TABLE_WIDGET: {
tableData: "Array<Object>",
serverSidePaginationEnabled: "boolean",

View File

@ -27,6 +27,10 @@ export const HelpMap = {
path: "/widget-reference/datepicker",
searchKey: "DatePicker",
},
DATE_PICKER_WIDGET2: {
path: "/widget-reference/datepicker",
searchKey: "DatePicker",
},
TABLE_WIDGET: {
path: "/widget-reference/table",
searchKey: "Table",

View File

@ -5,6 +5,7 @@ export enum WidgetTypes {
INPUT_WIDGET = "INPUT_WIDGET",
CONTAINER_WIDGET = "CONTAINER_WIDGET",
DATE_PICKER_WIDGET = "DATE_PICKER_WIDGET",
DATE_PICKER_WIDGET2 = "DATE_PICKER_WIDGET2",
TABLE_WIDGET = "TABLE_WIDGET",
DROP_DOWN_WIDGET = "DROP_DOWN_WIDGET",
CHECKBOX_WIDGET = "CHECKBOX_WIDGET",

View File

@ -42,7 +42,7 @@ export type Validator = (
dataTree?: DataTree,
) => ValidationResponse;
export const ISO_DATE_FORMAT = "YYYY-MM-DDTHH:mm:ss.Z";
export const ISO_DATE_FORMAT = "YYYY-MM-DDTHH:mm:ss.sssZ";
export const JAVASCRIPT_KEYWORDS = {
true: "true",

View File

@ -138,6 +138,8 @@ export const FORGOT_PASSWORD_PAGE_LOGIN_LINK = "Back to Sign In";
export const ADD_API_TO_PAGE_SUCCESS_MESSAGE = "Api added to page.";
export const INPUT_WIDGET_DEFAULT_VALIDATION_ERROR = "Invalid input";
export const DATE_WIDGET_DEFAULT_VALIDATION_ERROR = "Date out of range";
export const AUTOFIT_ALL_COLUMNS = "Autofit all columns";
export const AUTOFIT_THIS_COLUMN = "Autofit this column";
export const AUTOFIT_COLUMN = "Autofit column";

View File

@ -29,6 +29,7 @@ describe("getAllPathsFromPropertyConfig", () => {
isLoading: false,
horizontalAlignment: "LEFT",
parentColumnSpace: 74,
version: 1,
dynamicTriggerPathList: [
{
key: "primaryColumns.status.onClick",

View File

@ -51,7 +51,7 @@ export const WidgetIcons: {
<ContainerIcon />
</IconWrapper>
),
DATE_PICKER_WIDGET: (props: IconProps) => (
DATE_PICKER_WIDGET2: (props: IconProps) => (
<IconWrapper {...props}>
<DatePickerIcon />
</IconWrapper>

View File

@ -14,6 +14,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
isDisabled: false,
isVisible: true,
isDefaultClickDisabled: true,
version: 1,
},
TEXT_WIDGET: {
text: "Label",
@ -22,6 +23,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
rows: 1,
columns: 4,
widgetName: "Text",
version: 1,
},
RICH_TEXT_EDITOR_WIDGET: {
defaultText: "This is the initial <b>content</b> of the editor",
@ -31,6 +33,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
isVisible: true,
widgetName: "RichTextEditor",
isDefaultClickDisabled: true,
version: 1,
},
IMAGE_WIDGET: {
defaultImage:
@ -41,6 +44,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
rows: 3,
columns: 4,
widgetName: "Image",
version: 1,
},
INPUT_WIDGET: {
inputType: "TEXT",
@ -48,6 +52,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
label: "",
columns: 5,
widgetName: "Input",
version: 1,
resetOnSubmit: true,
},
SWITCH_WIDGET: {
@ -57,11 +62,13 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
defaultSwitchState: true,
widgetName: "Switch",
alignWidget: "LEFT",
version: 1,
},
ICON_WIDGET: {
widgetName: "Icon",
rows: 1,
columns: 1,
version: 1,
},
CONTAINER_WIDGET: {
backgroundColor: "#FFFFFF",
@ -84,6 +91,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
},
],
},
version: 1,
},
DATE_PICKER_WIDGET: {
isDisabled: false,
@ -94,14 +102,26 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
columns: 5,
widgetName: "DatePicker",
defaultDate: moment().format("DD/MM/YYYY HH:mm"),
version: 1,
},
DATE_PICKER_WIDGET2: {
isDisabled: false,
datePickerType: "DATE_PICKER",
rows: 1,
label: "",
dateFormat: "DD/MM/YYYY HH:mm",
columns: 5,
widgetName: "DatePicker",
defaultDate: moment().toISOString(),
version: 2,
},
VIDEO_WIDGET: {
rows: 7,
columns: 7,
widgetName: "Video",
url: "https://www.youtube.com/watch?v=mzqK0QIZRLs",
autoPlay: false,
version: 1,
},
TABLE_WIDGET: {
rows: 7,
@ -137,6 +157,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
orderAmount: 19.99,
},
],
version: 1,
},
DROP_DOWN_WIDGET: {
rows: 1,
@ -150,6 +171,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
],
widgetName: "Dropdown",
defaultOptionValue: "VEG",
version: 1,
},
CHECKBOX_WIDGET: {
rows: 1,
@ -157,6 +179,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
label: "Label",
defaultCheckedState: true,
widgetName: "Checkbox",
version: 1,
alignWidget: "LEFT",
},
RADIO_GROUP_WIDGET: {
@ -169,6 +192,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
],
defaultOptionValue: "M",
widgetName: "RadioGroup",
version: 1,
},
ALERT_WIDGET: {
alertType: "NOTIFICATION",
@ -178,6 +202,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
header: "",
message: "",
widgetName: "Alert",
version: 1,
},
FILE_PICKER_WIDGET: {
rows: 1,
@ -188,6 +213,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
maxFileSize: 5,
widgetName: "FilePicker",
isDefaultClickDisabled: true,
version: 1,
},
TABS_WIDGET: {
rows: 7,
@ -224,6 +250,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
},
],
},
version: 1,
},
MODAL_WIDGET: {
rows: 6,
@ -235,6 +262,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
shouldScrollContents: true,
widgetName: "Modal",
children: [],
version: 1,
blueprint: {
view: [
{
@ -247,6 +275,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
isDisabled: false,
shouldScrollContents: false,
children: [],
version: 1,
blueprint: {
view: [
{
@ -257,6 +286,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
iconName: "cross",
iconSize: 24,
color: "#040627",
version: 1,
},
},
{
@ -266,6 +296,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
props: {
text: "Modal Title",
textStyle: "HEADING",
version: 1,
},
},
{
@ -275,6 +306,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
props: {
text: "Cancel",
buttonStyle: "SECONDARY_BUTTON",
version: 1,
},
},
{
@ -284,6 +316,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
props: {
text: "Confirm",
buttonStyle: "PRIMARY_BUTTON",
version: 1,
},
},
],
@ -322,6 +355,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
rows: 0,
columns: 0,
widgetName: "Canvas",
version: 1,
},
CHART_WIDGET: {
rows: 8,
@ -330,6 +364,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
chartType: "LINE_CHART",
chartName: "Sales on working days",
allowHorizontalScroll: false,
version: 1,
chartData: [
{
seriesName: "Sales",
@ -374,6 +409,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
widgetName: "FormButton",
text: "Submit",
isDefaultClickDisabled: true,
version: 1,
},
FORM_WIDGET: {
rows: 13,
@ -391,6 +427,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
canExtend: false,
detachFromLayout: true,
children: [],
version: 1,
blueprint: {
view: [
{
@ -400,6 +437,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
props: {
text: "Form",
textStyle: "HEADING",
version: 1,
},
},
{
@ -411,6 +449,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
buttonStyle: "PRIMARY_BUTTON",
disabledWhenInvalid: true,
resetFormOnClick: true,
version: 1,
},
},
{
@ -422,6 +461,7 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
buttonStyle: "SECONDARY_BUTTON",
disabledWhenInvalid: false,
resetFormOnClick: true,
version: 1,
},
},
],
@ -443,12 +483,14 @@ const WidgetConfigResponse: WidgetConfigReducerState = {
allowZoom: true,
mapCenter: { lat: -34.397, long: 150.644 },
defaultMarkers: [{ lat: -34.397, long: 150.644, title: "Test A" }],
version: 1,
},
SKELETON_WIDGET: {
isLoading: true,
rows: 1,
columns: 1,
widgetName: "Skeleton",
version: 1,
},
},
configVersion: 1,

View File

@ -29,7 +29,7 @@ const WidgetSidebarResponse: WidgetCardProps[] = [
key: generateReactKey(),
},
{
type: "DATE_PICKER_WIDGET",
type: "DATE_PICKER_WIDGET2",
widgetCardName: "DatePicker",
key: generateReactKey(),
},

View File

@ -9,6 +9,7 @@ import { ImageWidgetProps } from "widgets/ImageWidget";
import { InputWidgetProps } from "widgets/InputWidget";
import { RichTextEditorWidgetProps } from "widgets/RichTextEditorWidget";
import { DatePickerWidgetProps } from "../../widgets/DatePickerWidget";
import { DatePickerWidget2Props } from "../../widgets/DatePickerWidget2";
import { TableWidgetProps } from "../../widgets/TableWidget/TableWidgetConstants";
import { DropdownWidgetProps } from "../../widgets/DropdownWidget";
import { CheckboxWidgetProps } from "../../widgets/CheckboxWidget";
@ -59,6 +60,7 @@ export interface WidgetConfigReducerState {
CONTAINER_WIDGET: Partial<ContainerWidgetProps<WidgetProps>> &
WidgetConfigProps;
DATE_PICKER_WIDGET: Partial<DatePickerWidgetProps> & WidgetConfigProps;
DATE_PICKER_WIDGET2: Partial<DatePickerWidget2Props> & WidgetConfigProps;
TABLE_WIDGET: Partial<TableWidgetProps> & WidgetConfigProps;
VIDEO_WIDGET: Partial<VideoWidgetProps> & WidgetConfigProps;
DROP_DOWN_WIDGET: Partial<DropdownWidgetProps> & WidgetConfigProps;

View File

@ -143,6 +143,7 @@ function* getChildWidgetProps(
parentColumnSpace,
widgetName,
widgetProps,
restDefaultConfig.version,
);
widget.widgetId = newWidgetId;
@ -1411,6 +1412,7 @@ function* addTableWidgetFromQuerySaga(action: ReduxAction<string>) {
parentRowSpace: GridDefaults.DEFAULT_GRID_ROW_HEIGHT,
parentColumnSpace: 1,
isLoading: false,
version: 1,
props: {
tableData: `{{${queryName}.data}}`,
dynamicBindingPathList: [{ key: "tableData" }],

View File

@ -259,6 +259,18 @@ const dynamicPathListMigration = (
return currentDSL;
};
const addVersionNumberMigration = (
currentDSL: ContainerWidgetProps<WidgetProps>,
) => {
if (currentDSL.children && currentDSL.children.length) {
currentDSL.children = currentDSL.children.map(addVersionNumberMigration);
}
if (currentDSL.version === undefined) {
currentDSL.version = 1;
}
return currentDSL;
};
const canvasNameConflictMigration = (
currentDSL: ContainerWidgetProps<WidgetProps>,
props = { counter: 1 },
@ -367,6 +379,11 @@ const transformDSL = (currentDSL: ContainerWidgetProps<WidgetProps>) => {
currentDSL.version = 10;
}
if (currentDSL.version === 10) {
currentDSL = addVersionNumberMigration(currentDSL);
currentDSL.version = 11;
}
return currentDSL;
};
@ -590,6 +607,7 @@ export const generateWidgetProps = (
widgetId: string;
renderMode: RenderMode;
} & Partial<WidgetProps>,
version: number,
): ContainerWidgetProps<WidgetProps> => {
if (parent) {
const sizes = {
@ -611,6 +629,7 @@ export const generateWidgetProps = (
...sizes,
...others,
parentId: parent.widgetId,
version,
};
delete props.rows;
delete props.columns;

View File

@ -71,6 +71,10 @@ import DatePickerWidget, {
DatePickerWidgetProps,
ProfiledDatePickerWidget,
} from "widgets/DatePickerWidget";
import DatePickerWidget2, {
DatePickerWidget2Props,
ProfiledDatePickerWidget2,
} from "widgets/DatePickerWidget2";
import FormWidget, { ProfiledFormWidget } from "widgets/FormWidget";
import FormButtonWidget, {
FormButtonWidgetProps,
@ -286,6 +290,20 @@ export default class WidgetBuilderRegistry {
DatePickerWidget.getMetaPropertiesMap(),
DatePickerWidget.getPropertyPaneConfig(),
);
WidgetFactory.registerWidgetBuilder(
"DATE_PICKER_WIDGET2",
{
buildWidget(widgetData: DatePickerWidget2Props): JSX.Element {
return <ProfiledDatePickerWidget2 {...widgetData} />;
},
},
DatePickerWidget2.getPropertyValidationMap(),
DatePickerWidget2.getDerivedPropertiesMap(),
DatePickerWidget2.getTriggerPropertyMap(),
DatePickerWidget2.getDefaultPropertiesMap(),
DatePickerWidget2.getMetaPropertiesMap(),
DatePickerWidget.getPropertyPaneConfig(),
);
WidgetFactory.registerWidgetBuilder(
"TABS_WIDGET",
{

View File

@ -124,6 +124,15 @@ export const entityDefinitions = {
selectedDate: "string",
isDisabled: "bool",
},
DATE_PICKER_WIDGET2: {
"!doc":
"Datepicker is used to capture the date and time from a user. It can be used to filter data base on the input date range as well as to capture personal information such as date of birth",
"!url": "https://docs.appsmith.com/widget-reference/datepicker",
isVisible: isVisible,
selectedDate: "string",
formattedDate: "string",
isDisabled: "bool",
},
CHECKBOX_WIDGET: {
"!doc":
"Checkbox is a simple UI widget you can use when you want users to make a binary choice",

View File

@ -24,6 +24,7 @@ describe("dataTreeTypeDefCreator", () => {
topRow: 1,
bottomRow: 2,
isLoading: false,
version: 1,
bindingPaths: {
defaultText: true,
},

View File

@ -44,6 +44,7 @@ const input1: ContainerWidgetProps<WidgetProps> = {
widgetId: "fs785w9gcy",
dynamicBindingPathList: [],
renderMode: "CANVAS",
version: 1,
},
],
};
@ -105,6 +106,7 @@ const input2: ContainerWidgetProps<WidgetProps> = {
},
],
renderMode: "CANVAS",
version: 1,
},
],
};
@ -157,6 +159,7 @@ const input3: ContainerWidgetProps<WidgetProps> = {
onRowSelected: "{{showAlert('test','success')}}",
onSearchTextChanged: "{{showAlert('fail','error')}}",
renderMode: "CANVAS",
version: 1,
},
],
};
@ -293,6 +296,7 @@ const output1 = {
horizontalAlignment: "LEFT",
verticalAlignment: "CENTER",
renderMode: "CANVAS",
version: 1,
},
],
};
@ -474,6 +478,7 @@ const output2 = {
horizontalAlignment: "LEFT",
verticalAlignment: "CENTER",
renderMode: "CANVAS",
version: 1,
},
],
};
@ -616,6 +621,7 @@ const output3 = {
horizontalAlignment: "LEFT",
verticalAlignment: "CENTER",
renderMode: "CANVAS",
version: 1,
},
],
};

View File

@ -319,6 +319,7 @@ export interface WidgetBaseProps {
widgetName: string;
parentId: string;
renderMode: RenderMode;
version: number;
}
export type WidgetRowCols = {

View File

@ -0,0 +1,223 @@
import React from "react";
import BaseWidget, { WidgetProps, WidgetState } from "./BaseWidget";
import { WidgetType } from "constants/WidgetConstants";
import { EventType } from "constants/ActionConstants";
import DatePickerComponent from "components/designSystems/blueprint/DatePickerComponent2";
import {
WidgetPropertyValidationType,
BASE_WIDGET_VALIDATION,
} from "utils/WidgetValidation";
import { VALIDATION_TYPES } from "constants/WidgetValidation";
import {
DerivedPropertiesMap,
TriggerPropertiesMap,
} from "utils/WidgetFactory";
import * as Sentry from "@sentry/react";
import withMeta, { WithMeta } from "./MetaHOC";
class DatePickerWidget extends BaseWidget<DatePickerWidget2Props, WidgetState> {
static getPropertyPaneConfig() {
return [
{
sectionName: "General",
children: [
{
propertyName: "defaultDate",
label: "Default Date",
helpText:
"Sets the default date of the widget. The date is updated if the default date changes",
controlType: "DATE_PICKER",
placeholderText: "Enter Default Date",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
},
{
helpText: "Sets the format of the selected date",
propertyName: "dateFormat",
label: "Date Format",
controlType: "DROP_DOWN",
isJSConvertible: true,
options: [
{
label: "YYYY-MM-DD",
value: "YYYY-MM-DD",
},
{
label: "YYYY-MM-DD HH:mm",
value: "YYYY-MM-DD HH:mm",
},
{
label: "YYYY-MM-DDTHH:mm:ss.sssZ",
value: "YYYY-MM-DDTHH:mm:ss.sssZ",
},
{
label: "DD/MM/YYYY",
value: "DD/MM/YYYY",
},
{
label: "DD/MM/YYYY HH:mm",
value: "DD/MM/YYYY HH:mm",
},
],
isBindProperty: true,
isTriggerProperty: false,
},
{
propertyName: "isRequired",
label: "Required",
helpText: "Makes input to the widget mandatory",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
},
{
propertyName: "isVisible",
label: "Visible",
helpText: "Controls the visibility of the widget",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
},
{
propertyName: "isDisabled",
label: "Disabled",
helpText: "Disables input to this widget",
controlType: "SWITCH",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
},
{
propertyName: "minDate",
label: "Min Date",
helpText: "Defines the min date for this widget",
controlType: "DATE_PICKER",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
},
{
propertyName: "maxDate",
label: "Max Date",
helpText: "Defines the max date for this widget",
controlType: "DATE_PICKER",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: false,
},
],
},
{
sectionName: "Actions",
children: [
{
propertyName: "onDateSelected",
label: "onDateSelected",
controlType: "ACTION_SELECTOR",
isJSConvertible: true,
isBindProperty: true,
isTriggerProperty: true,
},
],
},
];
}
static getPropertyValidationMap(): WidgetPropertyValidationType {
return {
...BASE_WIDGET_VALIDATION,
defaultDate: VALIDATION_TYPES.DEFAULT_DATE,
timezone: VALIDATION_TYPES.TEXT,
enableTimePicker: VALIDATION_TYPES.BOOLEAN,
dateFormat: VALIDATION_TYPES.TEXT,
label: VALIDATION_TYPES.TEXT,
datePickerType: VALIDATION_TYPES.TEXT,
maxDate: VALIDATION_TYPES.DATE,
minDate: VALIDATION_TYPES.DATE,
isRequired: VALIDATION_TYPES.BOOLEAN,
// onDateSelected: VALIDATION_TYPES.ACTION_SELECTOR,
// onDateRangeSelected: VALIDATION_TYPES.ACTION_SELECTOR,
};
}
static getDerivedPropertiesMap(): DerivedPropertiesMap {
return {
isValid: `{{ this.isRequired ? !!this.selectedDate : true }}`,
selectedDate: `{{ this.value ? moment(this.value).toISOString() : (this.defaultDate ? moment(this.defaultDate).toISOString() : "")}}`,
formattedDate: `{{ this.value ? moment(this.value).format(this.dateFormat) : (this.defaultDate ? moment(this.defaultDate).format(this.dateFormat) : "")}}`,
};
}
static getTriggerPropertyMap(): TriggerPropertiesMap {
return {
onDateSelected: true,
};
}
static getDefaultPropertiesMap(): Record<string, string> {
return {
value: "defaultDate",
};
}
static getMetaPropertiesMap(): Record<string, any> {
return {
value: undefined,
};
}
getPageView() {
return (
<DatePickerComponent
label={`${this.props.label}`}
dateFormat={this.props.dateFormat}
widgetId={this.props.widgetId}
isDisabled={this.props.isDisabled}
datePickerType={"DATE_PICKER"}
onDateSelected={this.onDateSelected}
selectedDate={this.props.value}
isLoading={this.props.isLoading}
minDate={this.props.minDate}
maxDate={this.props.maxDate}
/>
);
}
onDateSelected = (selectedDate: string) => {
this.props.updateWidgetMetaProperty("value", selectedDate, {
dynamicString: this.props.onDateSelected,
event: {
type: EventType.ON_DATE_SELECTED,
},
});
};
getWidgetType(): WidgetType {
return "DATE_PICKER_WIDGET2";
}
}
export type DatePickerType = "DATE_PICKER" | "DATE_RANGE_PICKER";
export interface DatePickerWidget2Props extends WidgetProps, WithMeta {
defaultDate: string;
selectedDate: string;
formattedDate: string;
isDisabled: boolean;
dateFormat: string;
label: string;
datePickerType: DatePickerType;
onDateSelected?: string;
onDateRangeSelected?: string;
maxDate: string;
minDate: string;
isRequired?: boolean;
}
export default DatePickerWidget;
export const ProfiledDatePickerWidget2 = Sentry.withProfiler(
withMeta(DatePickerWidget),
);

View File

@ -289,6 +289,33 @@ const WIDGET_CONFIG_MAP: WidgetTypeConfigMap = {
},
metaProperties: {},
},
DATE_PICKER_WIDGET2: {
validations: {
isLoading: "BOOLEAN",
isVisible: "BOOLEAN",
isDisabled: "BOOLEAN",
defaultDate: "DATE",
timezone: "TEXT",
enableTimePicker: "BOOLEAN",
dateFormat: "TEXT",
label: "TEXT",
datePickerType: "TEXT",
maxDate: "DATE",
minDate: "DATE",
isRequired: "BOOLEAN",
},
defaultProperties: {
selectedDate: "defaultDate",
},
derivedProperties: {
isValid: "{{ this.isRequired ? !!this.selectedDate : true }}",
value: "{{ this.selectedDate }}",
},
triggerProperties: {
onDateSelected: true,
},
metaProperties: {},
},
TABS_WIDGET: {
validations: {
tabs: "TABS_DATA",
@ -447,6 +474,7 @@ const BASE_WIDGET: DataTreeWidget = {
topRow: 0,
type: WidgetTypes.SKELETON_WIDGET,
parentId: "0",
version: 1,
bindingPaths: {},
triggerPaths: {},
ENTITY_TYPE: ENTITY_TYPE.WIDGET,

View File

@ -391,7 +391,10 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
dateString: string,
props: WidgetProps,
): ValidationResponse => {
const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT;
const dateFormat =
props.version === 2
? ISO_DATE_FORMAT
: props.dateFormat || ISO_DATE_FORMAT;
if (dateString === undefined) {
return {
@ -421,39 +424,22 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
dateString: string,
props: WidgetProps,
): ValidationResponse => {
const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT;
const dateFormat =
props.version === 2
? ISO_DATE_FORMAT
: props.dateFormat || ISO_DATE_FORMAT;
if (dateString === undefined) {
return {
isValid: false,
parsed: "",
message:
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + props.dateFormat
? props.dateFormat
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + dateFormat
? dateFormat
: "",
};
}
const parsedCurrentDate = moment(dateString, dateFormat);
let isValid = parsedCurrentDate.isValid();
const parsedMinDate = moment(props.minDate, dateFormat);
const parsedMaxDate = moment(props.maxDate, dateFormat);
// checking for max/min date range
if (isValid) {
if (
parsedMinDate.isValid() &&
parsedCurrentDate.isBefore(parsedMinDate)
) {
isValid = false;
}
if (
isValid &&
parsedMaxDate.isValid() &&
parsedCurrentDate.isAfter(parsedMaxDate)
) {
isValid = false;
}
}
const isValid = parsedCurrentDate.isValid();
if (!isValid) {
return {
isValid: isValid,
@ -471,14 +457,17 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
dateString: string,
props: WidgetProps,
): ValidationResponse => {
const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT;
const dateFormat =
props.version === 2
? ISO_DATE_FORMAT
: props.dateFormat || ISO_DATE_FORMAT;
if (dateString === undefined) {
return {
isValid: false,
parsed: "",
message:
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + props.dateFormat
? props.dateFormat
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + dateFormat
? dateFormat
: "",
};
}
@ -517,14 +506,17 @@ export const VALIDATORS: Record<ValidationType, Validator> = {
dateString: string,
props: WidgetProps,
): ValidationResponse => {
const dateFormat = props.dateFormat ? props.dateFormat : ISO_DATE_FORMAT;
const dateFormat =
props.version === 2
? ISO_DATE_FORMAT
: props.dateFormat || ISO_DATE_FORMAT;
if (dateString === undefined) {
return {
isValid: false,
parsed: "",
message:
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + props.dateFormat
? props.dateFormat
`${WIDGET_TYPE_VALIDATION_ERROR}: Date ` + dateFormat
? dateFormat
: "",
};
}