diff --git a/app/client/cypress/fixtures/formResetDsl.json b/app/client/cypress/fixtures/formResetDsl.json
new file mode 100644
index 00000000000..c40bb6d5c20
--- /dev/null
+++ b/app/client/cypress/fixtures/formResetDsl.json
@@ -0,0 +1,297 @@
+{
+ "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,
+ "dynamicBindings": {
+ },
+ "version": 5,
+ "minHeight": 1292,
+ "parentColumnSpace": 1,
+ "leftColumn": 0,
+ "children": [
+ {
+ "isVisible": true,
+ "widgetName": "Form1",
+ "backgroundColor": "white",
+ "children": [
+ {
+ "isVisible": true,
+ "widgetName": "Canvas1",
+ "containerStyle": "none",
+ "canExtend": false,
+ "detachFromLayout": true,
+ "children": [
+ {
+ "isVisible": true,
+ "text": "Form",
+ "textStyle": "HEADING",
+ "textAlign": "LEFT",
+ "widgetName": "Text1",
+ "type": "TEXT_WIDGET",
+ "isLoading": false,
+ "leftColumn": 0,
+ "rightColumn": 12,
+ "topRow": 0,
+ "bottomRow": 1,
+ "parentId": "qrqizehc5b",
+ "widgetId": "c481ah2q0i"
+ },
+ {
+ "isVisible": true,
+ "widgetName": "FormButton1",
+ "text": "Submit",
+ "isDefaultClickDisabled": true,
+ "buttonStyle": "PRIMARY_BUTTON",
+ "disabledWhenInvalid": true,
+ "resetFormOnClick": true,
+ "type": "FORM_BUTTON_WIDGET",
+ "isLoading": false,
+ "leftColumn": 12,
+ "rightColumn": 16,
+ "topRow": 12,
+ "bottomRow": 13,
+ "parentId": "qrqizehc5b",
+ "widgetId": "zsu1y41p1e"
+ },
+ {
+ "isVisible": true,
+ "widgetName": "FormButton2",
+ "text": "Reset",
+ "isDefaultClickDisabled": true,
+ "buttonStyle": "SECONDARY_BUTTON",
+ "disabledWhenInvalid": false,
+ "resetFormOnClick": true,
+ "type": "FORM_BUTTON_WIDGET",
+ "isLoading": false,
+ "leftColumn": 8,
+ "rightColumn": 12,
+ "topRow": 12,
+ "bottomRow": 13,
+ "parentId": "qrqizehc5b",
+ "widgetId": "7o0r2rp3s1"
+ },
+ {
+ "isVisible": true,
+ "label": "Data",
+ "widgetName": "Table1",
+ "searchKey": "",
+ "tableData": "[\n {\n \"id\": 2381224,\n \"email\": \"michael.lawson@reqres.in\",\n \"userName\": \"Michael Lawson\",\n \"productName\": \"Chicken Sandwich\",\n \"orderAmount\": 4.99\n },\n {\n \"id\": 2736212,\n \"email\": \"lindsay.ferguson@reqres.in\",\n \"userName\": \"Lindsay Ferguson\",\n \"productName\": \"Tuna Salad\",\n \"orderAmount\": 9.99\n },\n {\n \"id\": 6788734,\n \"email\": \"tobias.funke@reqres.in\",\n \"userName\": \"Tobias Funke\",\n \"productName\": \"Beef steak\",\n \"orderAmount\": 19.99\n }\n]",
+ "type": "TABLE_WIDGET",
+ "isLoading": false,
+ "parentColumnSpace": 71.5,
+ "parentRowSpace": 40,
+ "leftColumn": 0,
+ "rightColumn": 16,
+ "topRow": 1,
+ "bottomRow": 8,
+ "parentId": "qrqizehc5b",
+ "widgetId": "xptqefixji",
+ "dynamicBindings": {
+ }
+ },
+ {
+ "isVisible": true,
+ "inputType": "TEXT",
+ "label": "",
+ "widgetName": "Input1",
+ "type": "INPUT_WIDGET",
+ "isLoading": false,
+ "parentColumnSpace": 71.5,
+ "parentRowSpace": 40,
+ "leftColumn": 0,
+ "rightColumn": 5,
+ "topRow": 10,
+ "bottomRow": 11,
+ "parentId": "qrqizehc5b",
+ "widgetId": "r3xvjtuhad",
+ "dynamicBindings": {
+ "defaultText": true,
+ "isValid": true,
+ "value": true
+ },
+ "defaultText": "{{Table1.selectedRow.email}}"
+ },
+ {
+ "isVisible": true,
+ "text": "Email",
+ "textStyle": "LABEL",
+ "textAlign": "LEFT",
+ "widgetName": "Text2",
+ "type": "TEXT_WIDGET",
+ "isLoading": false,
+ "parentColumnSpace": 71.5,
+ "parentRowSpace": 40,
+ "leftColumn": 0,
+ "rightColumn": 4,
+ "topRow": 9,
+ "bottomRow": 10,
+ "parentId": "qrqizehc5b",
+ "widgetId": "672gf8vm2q",
+ "dynamicBindings": {
+ "value": true
+ }
+ }
+ ],
+ "blueprint": {
+ "view": [
+ {
+ "type": "TEXT_WIDGET",
+ "size": {
+ "rows": 1,
+ "cols": 12
+ },
+ "position": {
+ "top": 0,
+ "left": 0
+ },
+ "props": {
+ "text": "Form",
+ "textStyle": "HEADING"
+ }
+ },
+ {
+ "type": "FORM_BUTTON_WIDGET",
+ "size": {
+ "rows": 1,
+ "cols": 4
+ },
+ "position": {
+ "top": 11,
+ "left": 12
+ },
+ "props": {
+ "text": "Submit",
+ "buttonStyle": "PRIMARY_BUTTON",
+ "disabledWhenInvalid": true,
+ "resetFormOnClick": true
+ }
+ },
+ {
+ "type": "FORM_BUTTON_WIDGET",
+ "size": {
+ "rows": 1,
+ "cols": 4
+ },
+ "position": {
+ "top": 11,
+ "left": 8
+ },
+ "props": {
+ "text": "Reset",
+ "buttonStyle": "SECONDARY_BUTTON",
+ "disabledWhenInvalid": false,
+ "resetFormOnClick": true
+ }
+ }
+ ]
+ },
+ "minHeight": 520,
+ "type": "CANVAS_WIDGET",
+ "isLoading": false,
+ "parentColumnSpace": 1,
+ "parentRowSpace": 1,
+ "leftColumn": 0,
+ "rightColumn": 518,
+ "topRow": 0,
+ "bottomRow": 520,
+ "parentId": "ozm6zwjk4b",
+ "widgetId": "qrqizehc5b"
+ }
+ ],
+ "blueprint": {
+ "view": [
+ {
+ "type": "CANVAS_WIDGET",
+ "position": {
+ "top": 0,
+ "left": 0
+ },
+ "props": {
+ "containerStyle": "none",
+ "canExtend": false,
+ "detachFromLayout": true,
+ "children": [
+ ],
+ "blueprint": {
+ "view": [
+ {
+ "type": "TEXT_WIDGET",
+ "size": {
+ "rows": 1,
+ "cols": 12
+ },
+ "position": {
+ "top": 0,
+ "left": 0
+ },
+ "props": {
+ "text": "Form",
+ "textStyle": "HEADING"
+ }
+ },
+ {
+ "type": "FORM_BUTTON_WIDGET",
+ "size": {
+ "rows": 1,
+ "cols": 4
+ },
+ "position": {
+ "top": 11,
+ "left": 12
+ },
+ "props": {
+ "text": "Submit",
+ "buttonStyle": "PRIMARY_BUTTON",
+ "disabledWhenInvalid": true,
+ "resetFormOnClick": true
+ }
+ },
+ {
+ "type": "FORM_BUTTON_WIDGET",
+ "size": {
+ "rows": 1,
+ "cols": 4
+ },
+ "position": {
+ "top": 11,
+ "left": 8
+ },
+ "props": {
+ "text": "Reset",
+ "buttonStyle": "SECONDARY_BUTTON",
+ "disabledWhenInvalid": false,
+ "resetFormOnClick": true
+ }
+ }
+ ]
+ }
+ }
+ }
+ ]
+ },
+ "type": "FORM_WIDGET",
+ "isLoading": false,
+ "parentColumnSpace": 74,
+ "parentRowSpace": 40,
+ "leftColumn": 0,
+ "rightColumn": 16,
+ "topRow": 0,
+ "bottomRow": 14,
+ "parentId": "0",
+ "widgetId": "ozm6zwjk4b"
+ }
+ ]
+ }
+}
+
diff --git a/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/FormReset_spec.js b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/FormReset_spec.js
new file mode 100644
index 00000000000..eea5f26e118
--- /dev/null
+++ b/app/client/cypress/integration/Smoke_TestSuite/FormWidgets/FormReset_spec.js
@@ -0,0 +1,32 @@
+const dsl = require("../../../fixtures/formResetDsl.json");
+const widgetsPage = require("../../../locators/Widgets.json");
+
+describe("Form reset functionality", function() {
+ before(() => {
+ cy.addDsl(dsl);
+ });
+
+ it("Resets the form ", () => {
+ cy.get(".tr")
+ .eq(2)
+ .click()
+ .should("have.class", "selected-row");
+
+ cy.get(widgetsPage.inputWidget + " " + "input")
+ .invoke("attr", "value")
+ .should("contain", "lindsay.ferguson@reqres.in");
+
+ cy.get(widgetsPage.formButtonWidget)
+ .contains("Reset")
+ .click();
+
+ cy.get(".tr")
+ .eq(2)
+ .click()
+ .should("not.have.class", "selected-row");
+
+ cy.get(widgetsPage.inputWidget + " " + "input")
+ .invoke("attr", "value")
+ .should("not.contain", "lindsay.ferguson@reqres.in");
+ });
+});
diff --git a/app/client/cypress/integration/Smoke_TestSuite/QueryPane/PostgreDatasource_spec.js b/app/client/cypress/integration/Smoke_TestSuite/QueryPane/PostgreDatasource_spec.js
index 80ea044b58a..83f696674d0 100644
--- a/app/client/cypress/integration/Smoke_TestSuite/QueryPane/PostgreDatasource_spec.js
+++ b/app/client/cypress/integration/Smoke_TestSuite/QueryPane/PostgreDatasource_spec.js
@@ -1,8 +1,10 @@
const queryLocators = require("../../../locators/QueryEditor.json");
const datasource = require("../../../locators/DatasourcesEditor.json");
+let datasourceName;
+
describe("Create a query with a postgres datasource, run, save and then delete the query", function() {
- it("Create a query with a postgres datasource, run, save and then delete the query", function() {
+ it("Create a postgres datasource", function() {
cy.NavigateToDatasourceEditor();
cy.get(datasource.PostgreSQL).click();
@@ -12,38 +14,43 @@ describe("Create a query with a postgres datasource, run, save and then delete t
cy.testSaveDatasource();
- cy.NavigateToQueryEditor();
-
cy.get("@createDatasource").then(httpResponse => {
- const datasourceName = httpResponse.response.body.data.name;
-
- cy.get(".t--datasource-name")
- .contains(datasourceName)
- .click();
+ datasourceName = httpResponse.response.body.data.name;
});
-
- cy.get("@getPluginForm").should(
- "have.nested.property",
- "response.body.responseMeta.status",
- 200,
- );
+ });
+ it("Create, runs and delete a query", () => {
+ cy.NavigateToQueryEditor();
+ cy.get(".t--datasource-name")
+ .contains(datasourceName)
+ .click();
cy.get(queryLocators.templateMenu).click();
cy.get(".CodeMirror textarea")
.first()
.focus()
- .type("select * from users");
+ .type("select * from users limit 10");
- cy.EvaluateCurrentValue("select * from users");
+ cy.EvaluateCurrentValue("select * from users limit 10");
cy.runAndDeleteQuery();
+ });
+ it("Create, runs and delete another query", () => {
+ cy.NavigateToQueryEditor();
+ cy.get(".t--datasource-name")
+ .contains(datasourceName)
+ .click();
+ cy.get(queryLocators.templateMenu).click();
+ cy.get(".CodeMirror textarea")
+ .first()
+ .focus()
+ .type("select * from configs");
+ cy.EvaluateCurrentValue("select * from configs");
+ cy.runAndDeleteQuery();
+ });
+ it("Deletes a datasource", () => {
cy.NavigateToDatasourceEditor();
cy.get(".t--entity-name:contains(PostgreSQL)").click();
- cy.get("@createDatasource").then(httpResponse => {
- const datasourceName = httpResponse.response.body.data.name;
-
- cy.get(`.t--entity-name:contains(${datasourceName})`).click();
- });
+ cy.get(`.t--entity-name:contains(${datasourceName})`).click();
cy.get(".t--delete-datasource").click();
cy.wait("@deleteDatasource").should(
diff --git a/app/client/cypress/locators/Widgets.json b/app/client/cypress/locators/Widgets.json
index 30d39512e6e..0888b882903 100644
--- a/app/client/cypress/locators/Widgets.json
+++ b/app/client/cypress/locators/Widgets.json
@@ -5,6 +5,7 @@
"inputPropsDataType": ".t--property-control-datatype",
"inputdatatypeplaceholder": ".t--property-control-placeholder",
"buttonWidget": ".t--draggable-buttonwidget",
+ "formButtonWidget": ".t--widget-formbuttonwidget",
"textWidget": ".t--draggable-textwidget",
"tableWidget": ".t--draggable-tablewidget",
"tableOnRowSelected": ".t--property-control-onrowselected",
@@ -37,4 +38,4 @@
"textInputval": ".t--draggable-textwidget span.t--widget-name",
"textAlign": ".t--property-control-textalign",
"ColumnAction": ".t--property-control-rowbutton button"
-}
\ No newline at end of file
+}
diff --git a/app/client/public/index.html b/app/client/public/index.html
index 5702864a809..7a51cdaa5ec 100755
--- a/app/client/public/index.html
+++ b/app/client/public/index.html
@@ -33,6 +33,18 @@
gtag('config', "%REACT_APP_GOOGLE_ANALYTICS_ID%");
+
diff --git a/app/client/src/actions/datasourceActions.ts b/app/client/src/actions/datasourceActions.ts
index 292ed39011c..0ea4e647932 100644
--- a/app/client/src/actions/datasourceActions.ts
+++ b/app/client/src/actions/datasourceActions.ts
@@ -25,6 +25,11 @@ export const updateDatasource = (
};
};
+export const saveDatasourceName = (payload: { id: string; name: string }) => ({
+ type: ReduxActionTypes.SAVE_DATASOURCE_NAME,
+ payload: payload,
+});
+
export const changeDatasource = (payload: Datasource) => {
return {
type: ReduxActionTypes.CHANGE_DATASOURCE,
diff --git a/app/client/src/assets/icons/ads/bag.svg b/app/client/src/assets/icons/ads/bag.svg
new file mode 100644
index 00000000000..04096a467db
--- /dev/null
+++ b/app/client/src/assets/icons/ads/bag.svg
@@ -0,0 +1,4 @@
+
diff --git a/app/client/src/assets/icons/ads/book.svg b/app/client/src/assets/icons/ads/book.svg
new file mode 100644
index 00000000000..f016135fcd5
--- /dev/null
+++ b/app/client/src/assets/icons/ads/book.svg
@@ -0,0 +1,5 @@
+
diff --git a/app/client/src/assets/icons/ads/calender.svg b/app/client/src/assets/icons/ads/calender.svg
new file mode 100644
index 00000000000..8be12ddf068
--- /dev/null
+++ b/app/client/src/assets/icons/ads/calender.svg
@@ -0,0 +1,4 @@
+
diff --git a/app/client/src/assets/icons/ads/camera.svg b/app/client/src/assets/icons/ads/camera.svg
new file mode 100644
index 00000000000..d1b23590c07
--- /dev/null
+++ b/app/client/src/assets/icons/ads/camera.svg
@@ -0,0 +1,5 @@
+
diff --git a/app/client/src/assets/icons/ads/chat.svg b/app/client/src/assets/icons/ads/chat.svg
new file mode 100644
index 00000000000..6fbbcfc7546
--- /dev/null
+++ b/app/client/src/assets/icons/ads/chat.svg
@@ -0,0 +1,4 @@
+
diff --git a/app/client/src/assets/icons/ads/close.svg b/app/client/src/assets/icons/ads/close.svg
new file mode 100644
index 00000000000..afaca4c528d
--- /dev/null
+++ b/app/client/src/assets/icons/ads/close.svg
@@ -0,0 +1,3 @@
+
diff --git a/app/client/src/assets/icons/ads/edit.svg b/app/client/src/assets/icons/ads/edit.svg
new file mode 100644
index 00000000000..93eaaa69469
--- /dev/null
+++ b/app/client/src/assets/icons/ads/edit.svg
@@ -0,0 +1,3 @@
+
diff --git a/app/client/src/assets/icons/ads/error.svg b/app/client/src/assets/icons/ads/error.svg
new file mode 100644
index 00000000000..43f866b350e
--- /dev/null
+++ b/app/client/src/assets/icons/ads/error.svg
@@ -0,0 +1,4 @@
+
diff --git a/app/client/src/assets/icons/ads/file.svg b/app/client/src/assets/icons/ads/file.svg
new file mode 100644
index 00000000000..4e83e5a8aef
--- /dev/null
+++ b/app/client/src/assets/icons/ads/file.svg
@@ -0,0 +1,4 @@
+
diff --git a/app/client/src/assets/icons/ads/flight.svg b/app/client/src/assets/icons/ads/flight.svg
new file mode 100644
index 00000000000..0a6daa90ab3
--- /dev/null
+++ b/app/client/src/assets/icons/ads/flight.svg
@@ -0,0 +1,4 @@
+
diff --git a/app/client/src/assets/icons/ads/frame.svg b/app/client/src/assets/icons/ads/frame.svg
new file mode 100644
index 00000000000..3bd9d4a5029
--- /dev/null
+++ b/app/client/src/assets/icons/ads/frame.svg
@@ -0,0 +1,5 @@
+
diff --git a/app/client/src/assets/icons/ads/globe.svg b/app/client/src/assets/icons/ads/globe.svg
new file mode 100644
index 00000000000..228290a5d65
--- /dev/null
+++ b/app/client/src/assets/icons/ads/globe.svg
@@ -0,0 +1,4 @@
+
diff --git a/app/client/src/assets/icons/ads/heart.svg b/app/client/src/assets/icons/ads/heart.svg
new file mode 100644
index 00000000000..372367768d6
--- /dev/null
+++ b/app/client/src/assets/icons/ads/heart.svg
@@ -0,0 +1,4 @@
+
diff --git a/app/client/src/assets/icons/ads/product.svg b/app/client/src/assets/icons/ads/product.svg
new file mode 100644
index 00000000000..9237160df7c
--- /dev/null
+++ b/app/client/src/assets/icons/ads/product.svg
@@ -0,0 +1,4 @@
+
diff --git a/app/client/src/assets/icons/ads/search.svg b/app/client/src/assets/icons/ads/search.svg
new file mode 100644
index 00000000000..e3e23f0e537
--- /dev/null
+++ b/app/client/src/assets/icons/ads/search.svg
@@ -0,0 +1,4 @@
+
diff --git a/app/client/src/assets/icons/ads/shopper.svg b/app/client/src/assets/icons/ads/shopper.svg
new file mode 100644
index 00000000000..e8deb2e9305
--- /dev/null
+++ b/app/client/src/assets/icons/ads/shopper.svg
@@ -0,0 +1,4 @@
+
diff --git a/app/client/src/assets/icons/ads/success.svg b/app/client/src/assets/icons/ads/success.svg
new file mode 100644
index 00000000000..c5c3d40e418
--- /dev/null
+++ b/app/client/src/assets/icons/ads/success.svg
@@ -0,0 +1,4 @@
+
diff --git a/app/client/src/components/ads/AppIcon.tsx b/app/client/src/components/ads/AppIcon.tsx
new file mode 100644
index 00000000000..fe8fd23ce56
--- /dev/null
+++ b/app/client/src/components/ads/AppIcon.tsx
@@ -0,0 +1,115 @@
+import React from "react";
+import { ReactComponent as BagIcon } from "assets/icons/ads/bag.svg";
+import { ReactComponent as ProductIcon } from "assets/icons/ads/product.svg";
+import { ReactComponent as BookIcon } from "assets/icons/ads/book.svg";
+import { ReactComponent as CameraIcon } from "assets/icons/ads/camera.svg";
+import { ReactComponent as FileIcon } from "assets/icons/ads/file.svg";
+import { ReactComponent as ChatIcon } from "assets/icons/ads/chat.svg";
+import { ReactComponent as CalenderIcon } from "assets/icons/ads/calender.svg";
+import { ReactComponent as FrameIcon } from "assets/icons/ads/frame.svg";
+import { ReactComponent as GlobeIcon } from "assets/icons/ads/globe.svg";
+import { ReactComponent as ShopperIcon } from "assets/icons/ads/shopper.svg";
+import { ReactComponent as HeartIcon } from "assets/icons/ads/heart.svg";
+import { ReactComponent as FlightIcon } from "assets/icons/ads/flight.svg";
+
+import styled from "styled-components";
+import { Size } from "./Button";
+
+export enum AppIconName {
+ BAG = "bag",
+ PRODUCT = "product",
+ BOOK = "book",
+ CAMERA = "camera",
+ FILE = "file",
+ CHAT = "chat",
+ CALENDER = "calender",
+ FLIGHT = "flight",
+ FRAME = "frame",
+ GLOBE = "globe",
+ SHOPPER = "shopper",
+ HEART = "heart",
+}
+
+export const sizeHandler = (size: Size) => {
+ let iconSize = 0;
+ switch (size) {
+ case Size.small:
+ iconSize = 20;
+ break;
+ case Size.medium:
+ iconSize = 30;
+ break;
+ case Size.large:
+ iconSize = 54;
+ break;
+ }
+ return iconSize;
+};
+
+const IconWrapper = styled.div`
+ cursor: pointer;
+ &:focus {
+ outline: none;
+ }
+ display: flex;
+ svg {
+ width: ${props => sizeHandler(props.size)}px;
+ height: ${props => sizeHandler(props.size)}px;
+ path {
+ fill: ${props => props.theme.colors.blackShades[9]};
+ }
+ }
+`;
+
+export type AppIconProps = {
+ size: Size;
+ name: AppIconName;
+};
+
+const AppIcon = (props: AppIconProps) => {
+ let returnIcon;
+ switch (props.name) {
+ case AppIconName.BAG:
+ returnIcon = ;
+ break;
+ case AppIconName.PRODUCT:
+ returnIcon = ;
+ break;
+ case AppIconName.BOOK:
+ returnIcon = ;
+ break;
+ case AppIconName.CAMERA:
+ returnIcon = ;
+ break;
+ case AppIconName.FILE:
+ returnIcon = ;
+ break;
+ case AppIconName.CHAT:
+ returnIcon = ;
+ break;
+ case AppIconName.CALENDER:
+ returnIcon = ;
+ break;
+ case AppIconName.FRAME:
+ returnIcon = ;
+ break;
+ case AppIconName.GLOBE:
+ returnIcon = ;
+ break;
+ case AppIconName.SHOPPER:
+ returnIcon = ;
+ break;
+ case AppIconName.HEART:
+ returnIcon = ;
+ break;
+ case AppIconName.FLIGHT:
+ returnIcon = ;
+ break;
+ default:
+ returnIcon = null;
+ break;
+ }
+ return returnIcon ? {returnIcon} : null;
+};
+
+export default AppIcon;
diff --git a/app/client/src/components/ads/Button.tsx b/app/client/src/components/ads/Button.tsx
index 21a969a2ccb..38329daf44a 100644
--- a/app/client/src/components/ads/Button.tsx
+++ b/app/client/src/components/ads/Button.tsx
@@ -1,7 +1,7 @@
import React from "react";
import { CommonComponentProps, hexToRgba, ThemeProp } from "./common";
import styled from "styled-components";
-import { IconName, Icon } from "./Icon";
+import Icon, { IconName } from "./Icon";
import Spinner from "./Spinner";
import {
mediumButton,
@@ -233,7 +233,7 @@ const btnFontStyles = (props: ThemeProp & ButtonProps): BtnFontType => {
buttonFont = largeButton;
padding =
!props.text && props.icon
- ? `${props.theme.spaces[5] - 1}px ${props.theme.spaces[5] - 1}px`
+ ? `${props.theme.spaces[3]}px`
: `${props.theme.spaces[5] - 1}px ${props.theme.spaces[12] - 4}px`;
break;
}
diff --git a/app/client/src/components/ads/ColorSelector.tsx b/app/client/src/components/ads/ColorSelector.tsx
index 57d0063757c..4784e7d1dfc 100644
--- a/app/client/src/components/ads/ColorSelector.tsx
+++ b/app/client/src/components/ads/ColorSelector.tsx
@@ -1,9 +1,95 @@
+import React, { useState } from "react";
+import styled from "styled-components";
import { CommonComponentProps } from "./common";
+export const appColorPalette = [
+ "#4F70FD",
+ "#54A9FB",
+ "#5ED3DA",
+ "#F56AF4",
+ "#F36380",
+ "#FE9F44",
+ "#E9C951",
+ "#A8D76C",
+ "#6C4CF1",
+];
+
type ColorSelectorProps = CommonComponentProps & {
- onSelect: (hex: string) => void;
+ onSelect?: (hex: string) => void;
+ colorPalette?: string[];
+ fill?: boolean;
+};
+
+const Palette = styled.div<{ fill?: boolean }>`
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ width: ${props => (props.fill ? "100%" : "234px")};
+`;
+
+const ColorBox = styled.div<{ selected: string; color: string }>`
+ width: ${props => props.theme.spaces[8]}px;
+ height: ${props => props.theme.spaces[8]}px;
+ margin: 0 ${props => props.theme.spaces[2]}px
+ ${props => props.theme.spaces[2]}px 0;
+ background-color: ${props => props.color};
+ cursor: pointer;
+ position: relative;
+
+ &:hover {
+ box-shadow: 0px 0px 0px ${props => props.theme.spaces[1] - 1}px #353535;
+ }
+
+ &:last-child {
+ margin-right: ${props => props.theme.spaces[1] - 1}px;
+ }
+
+ ${props =>
+ props.selected === props.color
+ ? `&::before {
+ content: "";
+ position: absolute;
+ left: ${props.theme.spaces[3] - 1}px;
+ top: ${props.theme.spaces[1] - 1}px
+ width: ${props.theme.spaces[2] - 1}px
+ height: ${props.theme.spaces[4] - 1}px
+ border: 1.5px solid ${props.theme.colors.blackShades[9]};
+ border-width: 0 1.5px 1.5px 0;
+ transform: rotate(45deg);
+ }`
+ : `
+ &::before {
+ display: none;
+ }
+ `}
+`;
+
+const ColorSelector = (props: ColorSelectorProps) => {
+ const [selected, setSelected] = useState("");
+ console.log("colors", props.colorPalette);
+
+ return (
+
+ {props.colorPalette &&
+ props.colorPalette.map((hex: string, index: number) => {
+ return (
+ {
+ setSelected(hex);
+ props.onSelect && props.onSelect(hex);
+ }}
+ />
+ );
+ })}
+
+ );
+};
+
+ColorSelector.defaultProps = {
+ fill: false,
};
-export default function ColorSelector(props: ColorSelectorProps) {
- return null;
-}
+export default ColorSelector;
diff --git a/app/client/src/components/ads/EditableInput.tsx b/app/client/src/components/ads/EditableInput.tsx
deleted file mode 100644
index 816ad2d1b37..00000000000
--- a/app/client/src/components/ads/EditableInput.tsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { CommonComponentProps } from "./common";
-
-export enum EditInteractionKind {
- SINGLE,
- DOUBLE,
-}
-
-type EditableTextProps = CommonComponentProps & {
- type: "text" | "password" | "email" | "phone" | "date";
- defaultValue: string;
- onTextChanged: (value: string) => void;
- placeholder: string;
- cypressSelector?: string;
- valueTransform?: (value: string) => string;
- isEditingDefault?: boolean;
- forceDefault?: boolean;
- updating?: boolean;
- isInvalid?: (value: string) => string | boolean;
- editInteractionKind: EditInteractionKind;
- hideEditIcon?: boolean;
-};
-
-// Check EditableText Component
-export default function(props: EditableTextProps) {
- return null;
-}
diff --git a/app/client/src/components/ads/EditableText.tsx b/app/client/src/components/ads/EditableText.tsx
new file mode 100644
index 00000000000..1a4aa7f9a61
--- /dev/null
+++ b/app/client/src/components/ads/EditableText.tsx
@@ -0,0 +1,292 @@
+import React, { useState, useEffect, useMemo, useCallback } from "react";
+import { EditableText as BlueprintEditableText } from "@blueprintjs/core";
+import styled from "styled-components";
+import { Size } from "./Button";
+import Text, { TextType } from "./Text";
+import Spinner from "./Spinner";
+import { hexToRgba } from "./common";
+import { theme } from "constants/DefaultTheme";
+import { noop } from "lodash";
+import Icon from "./Icon";
+
+export enum EditInteractionKind {
+ SINGLE = "SINGLE",
+ DOUBLE = "DOUBLE",
+}
+
+export type SavingStateHandler = (
+ isSaving: boolean,
+ state?: SavingState,
+) => void;
+
+export enum SavingState {
+ NOT_STARTED = "NOT_STARTED",
+ SUCCESS = "SUCCESS",
+ ERROR = "ERROR",
+}
+
+type EditableTextProps = {
+ defaultValue: string;
+ onTextChanged: (value: string) => void;
+ placeholder: string;
+ className?: string;
+ valueTransform?: (value: string) => string;
+ isEditingDefault?: boolean;
+ forceDefault?: boolean;
+ updating?: boolean;
+ isInvalid?: (value: string) => string | boolean;
+ editInteractionKind: EditInteractionKind;
+ hideEditIcon?: boolean;
+ fill?: boolean;
+ onSubmit: (
+ value: string,
+ callback: SavingStateHandler,
+ ) => { saving: SavingState };
+};
+
+const EditableTextWrapper = styled.div<{
+ fill?: boolean;
+}>`
+ width: ${props => (!props.fill ? "234px" : "100%")};
+ .error-message {
+ color: ${props => props.theme.colors.danger.main};
+ }
+`;
+
+const editModeBgcolor = (
+ isInvalid: boolean,
+ isEditing: boolean,
+ savingState: { isSaving: boolean; name?: SavingState },
+): string => {
+ if (
+ (isInvalid && isEditing) ||
+ (!savingState.isSaving && savingState.name === SavingState.ERROR)
+ ) {
+ return hexToRgba(theme.colors.danger.main, 0.08);
+ } else if (!isInvalid && isEditing) {
+ return theme.colors.blackShades[2];
+ } else {
+ return "transparent";
+ }
+};
+
+const TextContainer = styled.div<{
+ isInvalid: boolean;
+ isEditing: boolean;
+ bgColor: string;
+}>`
+ display: flex;
+ align-items: center;
+ ${props =>
+ props.isEditing && props.isInvalid
+ ? `margin-bottom: ${props.theme.spaces[2]}px`
+ : null};
+ .bp3-editable-text.bp3-editable-text-editing::before,
+ .bp3-editable-text.bp3-disabled::before {
+ display: none;
+ }
+
+ &&& .bp3-editable-text-content,
+ &&& .bp3-editable-text-input {
+ font-size: ${props => props.theme.typography.p1.fontSize}px;
+ line-height: ${props => props.theme.typography.p1.lineHeight}px;
+ letter-spacing: ${props => props.theme.typography.p1.letterSpacing}px;
+ font-weight: ${props => props.theme.typography.p1.fontWeight}px;
+ }
+
+ & .bp3-editable-text-content {
+ cursor: pointer;
+ color: ${props => props.theme.colors.blackShades[9]};
+ overflow: hidden;
+ text-overflow: ellipsis;
+ ${props => (props.isEditing ? "display: none" : "display: block")};
+ }
+
+ & .bp3-editable-text-input {
+ border: none;
+ outline: none;
+ height: ${props => props.theme.spaces[13] + 3}px;
+ padding: ${props => props.theme.spaces[0]}px;
+ color: ${props => props.theme.colors.blackShades[9]};
+ min-width: 100%;
+ border-radius: ${props => props.theme.spaces[0]}px;
+ }
+
+ & .bp3-editable-text {
+ overflow: hidden;
+ padding: ${props => props.theme.spaces[4]}px
+ ${props => props.theme.spaces[5]}px;
+ width: calc(100% - 40px);
+ background-color: ${props => props.bgColor};
+ }
+
+ .icon-wrapper {
+ background-color: ${props => props.bgColor};
+ }
+`;
+
+const IconWrapper = styled.div`
+ width: ${props => props.theme.spaces[13] + 4}px;
+ padding-right: ${props => props.theme.spaces[5]}px;
+ height: ${props => props.theme.spaces[13] + 3}px;
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+`;
+
+export const AdsEditableText = (props: EditableTextProps) => {
+ const [isEditing, setIsEditing] = useState(!!props.isEditingDefault);
+ const [value, setValue] = useState(props.defaultValue);
+ const [lastValidValue, setLastValidValue] = useState(props.defaultValue);
+ const [isInvalid, setIsInvalid] = useState(false);
+ const [changeStarted, setChangeStarted] = useState(false);
+ const [savingState, setSavingState] = useState<{
+ isSaving: boolean;
+ name?: SavingState;
+ }>({ isSaving: false, name: SavingState.NOT_STARTED });
+
+ useEffect(() => {
+ setValue(props.defaultValue);
+ setIsEditing(!!props.isEditingDefault);
+ }, [props.defaultValue, props.isEditingDefault]);
+
+ useEffect(() => {
+ if (props.forceDefault === true) setValue(props.defaultValue);
+ }, [props.forceDefault, props.defaultValue]);
+
+ const bgColor = useMemo(
+ () => editModeBgcolor(!!isInvalid, isEditing, savingState),
+ [isInvalid, isEditing, savingState],
+ );
+
+ /* should I write ? */
+ const editMode = useCallback((e: React.MouseEvent) => {
+ setIsEditing(true);
+ const errorMessage = props.isInvalid && props.isInvalid(props.defaultValue);
+ setIsInvalid(errorMessage ? errorMessage : false);
+ e.preventDefault();
+ e.stopPropagation();
+ }, []);
+
+ const onConfirm = (_value: string) => {
+ if (
+ (!savingState.isSaving && savingState.name === SavingState.ERROR) ||
+ isInvalid
+ ) {
+ setValue(lastValidValue);
+ setSavingState({ isSaving: false, name: SavingState.NOT_STARTED });
+ } else if (changeStarted) {
+ props.onTextChanged(_value);
+ props.onSubmit(_value, SavingStateHandler);
+ }
+ setIsEditing(false);
+ setChangeStarted(false);
+ };
+
+ const onInputchange = useCallback((_value: string) => {
+ let finalVal: string = _value;
+ if (props.valueTransform) {
+ finalVal = props.valueTransform(_value);
+ }
+ setValue(finalVal);
+
+ const errorMessage = props.isInvalid && props.isInvalid(finalVal);
+ const error = errorMessage ? errorMessage : false;
+ if (!error) {
+ setLastValidValue(finalVal);
+ }
+ setIsInvalid(error);
+ setChangeStarted(true);
+ }, []);
+
+ const SavingStateHandler = (isSaving: boolean, state?: SavingState) => {
+ setIsEditing(false);
+ if (isSaving) {
+ setSavingState({ isSaving: true });
+ } else {
+ switch (state) {
+ case SavingState.SUCCESS:
+ setSavingState({ isSaving: false, name: SavingState.SUCCESS });
+ break;
+ default:
+ setValue(props.defaultValue);
+ setSavingState({ isSaving: false, name: SavingState.NOT_STARTED });
+ break;
+ }
+ }
+ };
+
+ const iconName =
+ !isEditing && savingState.name === SavingState.NOT_STARTED
+ ? "edit"
+ : !isEditing && savingState.name === SavingState.SUCCESS
+ ? "success"
+ : (isEditing && savingState.name === SavingState.ERROR) ||
+ (isEditing && !!isInvalid)
+ ? "error"
+ : undefined;
+
+ const nonEditMode = () => {
+ if (
+ !isEditing &&
+ !savingState.isSaving &&
+ savingState.name === SavingState.SUCCESS
+ ) {
+ setSavingState({ isSaving: false, name: SavingState.NOT_STARTED });
+ }
+ };
+
+ return (
+
+
+
+
+
+ {savingState.isSaving ? (
+
+ ) : (
+
+ )}
+
+
+ {isEditing && !!isInvalid ? (
+
+ {isInvalid}
+
+ ) : null}
+
+ );
+};
+
+AdsEditableText.defaultProps = {
+ fill: false,
+};
+
+export default AdsEditableText;
diff --git a/app/client/src/components/ads/Icon.tsx b/app/client/src/components/ads/Icon.tsx
index 8cb876cb1a4..3f547a05d9c 100644
--- a/app/client/src/components/ads/Icon.tsx
+++ b/app/client/src/components/ads/Icon.tsx
@@ -3,6 +3,11 @@ import { ReactComponent as DeleteIcon } from "assets/icons/ads/delete.svg";
import { ReactComponent as UserIcon } from "assets/icons/ads/user.svg";
import { ReactComponent as GeneralIcon } from "assets/icons/ads/general.svg";
import { ReactComponent as BillingIcon } from "assets/icons/ads/billing.svg";
+import { ReactComponent as EditIcon } from "assets/icons/ads/edit.svg";
+import { ReactComponent as ErrorIcon } from "assets/icons/ads/error.svg";
+import { ReactComponent as SuccessIcon } from "assets/icons/ads/success.svg";
+import { ReactComponent as SearchIcon } from "assets/icons/ads/search.svg";
+import { ReactComponent as CloseIcon } from "assets/icons/ads/close.svg";
import styled from "styled-components";
import { Size } from "./Button";
import { sizeHandler } from "./Spinner";
@@ -13,6 +18,11 @@ export type IconName =
| "user"
| "general"
| "billing"
+ | "edit"
+ | "error"
+ | "success"
+ | "search"
+ | "close"
| undefined;
const IconWrapper = styled.div`
@@ -21,8 +31,10 @@ const IconWrapper = styled.div`
}
display: flex;
svg {
- width: ${props => sizeHandler(props)}px;
- height: ${props => sizeHandler(props)}px;
+ width: ${props =>
+ props.size ? sizeHandler(props) : props.theme.spaces[9]}px;
+ height: ${props =>
+ props.size ? sizeHandler(props) : props.theme.spaces[9]}px;
path {
fill: ${props => props.theme.colors.blackShades[4]};
}
@@ -48,42 +60,53 @@ export type IconProps = {
size?: Size;
name?: IconName;
invisible?: boolean;
+ className?: string;
+ click?: () => void;
};
-export const Icon = (props: IconProps) => {
+const Icon = (props: IconProps) => {
let returnIcon;
switch (props.name) {
case "delete":
- returnIcon = (
-
-
-
- );
+ returnIcon = ;
break;
case "user":
- returnIcon = (
-
-
-
- );
+ returnIcon = ;
break;
case "general":
- returnIcon = (
-
-
-
- );
+ returnIcon = ;
break;
case "billing":
- returnIcon = (
-
-
-
- );
+ returnIcon = ;
+ break;
+ case "edit":
+ returnIcon = ;
+ break;
+ case "error":
+ returnIcon = ;
+ break;
+ case "success":
+ returnIcon = ;
+ break;
+ case "search":
+ returnIcon = ;
+ break;
+ case "close":
+ returnIcon = ;
break;
default:
returnIcon = null;
break;
}
- return returnIcon;
+ return returnIcon ? (
+ props.click && props.click()}
+ >
+ {returnIcon}
+
+ ) : null;
};
+
+export default Icon;
diff --git a/app/client/src/components/ads/IconSelector.tsx b/app/client/src/components/ads/IconSelector.tsx
index 49d7b8304c6..c0f354960f8 100644
--- a/app/client/src/components/ads/IconSelector.tsx
+++ b/app/client/src/components/ads/IconSelector.tsx
@@ -1,10 +1,95 @@
+import React, { useState, useEffect } from "react";
+import styled from "styled-components";
+import AppIcon, { AppIconName } from "./AppIcon";
+import { Size } from "./Button";
import { CommonComponentProps } from "./common";
-import { IconName } from "./Icon";
+
+export const appIconPalette = [
+ AppIconName.BAG,
+ AppIconName.PRODUCT,
+ AppIconName.BOOK,
+ AppIconName.CAMERA,
+ AppIconName.FILE,
+ AppIconName.CHAT,
+ AppIconName.CALENDER,
+ AppIconName.FLIGHT,
+ AppIconName.FRAME,
+ AppIconName.GLOBE,
+ AppIconName.SHOPPER,
+ AppIconName.HEART,
+];
type IconSelectorProps = CommonComponentProps & {
- onSelect: (icon: IconName) => void;
+ onSelect?: (icon: AppIconName) => void;
+ selectedColor: string;
+ selectedIcon?: AppIconName;
+ iconPalette?: AppIconName[];
+ fill?: boolean;
+};
+
+const IconPalette = styled.div<{ fill?: boolean }>`
+ display: flex;
+ align-items: center;
+ flex-wrap: wrap;
+ width: ${props => (props.fill ? "100%" : "234px")};
+`;
+
+const IconBox = styled.div<{
+ iconName: AppIconName;
+ selected: AppIconName;
+ bgColor: string;
+}>`
+ padding: ${props => props.theme.spaces[2]}px
+ ${props => props.theme.spaces[2] - 1}px;
+ margin: 0 ${props => props.theme.spaces[2]}px
+ ${props => props.theme.spaces[2]}px 0;
+ background-color: ${props =>
+ props.selected === props.iconName
+ ? props.bgColor
+ : props.theme.colors.blackShades[2]};
+ cursor: pointer;
+ position: relative;
+
+ &:last-child {
+ margin-right: ${props => props.theme.spaces[0]}px;
+ }
+`;
+
+const IconSelector = (props: IconSelectorProps) => {
+ const [selected, setSelected] = useState(appIconPalette[0]);
+
+ useEffect(() => {
+ if (props.selectedIcon) {
+ setSelected(props.selectedIcon);
+ }
+ }, [props.selectedIcon]);
+
+ return (
+
+ {props.iconPalette &&
+ props.iconPalette.map((iconName: AppIconName, index: number) => {
+ return (
+ {
+ setSelected(iconName);
+ props.onSelect && props.onSelect(iconName);
+ }}
+ >
+
+
+ );
+ })}
+
+ );
+};
+
+IconSelector.defaultProps = {
+ fill: false,
+ iconPalette: appIconPalette,
};
-export default function IconSelector(props: IconSelectorProps) {
- return null;
-}
+export default IconSelector;
diff --git a/app/client/src/components/ads/SearchInput.tsx b/app/client/src/components/ads/SearchInput.tsx
new file mode 100644
index 00000000000..dbe8fb4d365
--- /dev/null
+++ b/app/client/src/components/ads/SearchInput.tsx
@@ -0,0 +1,138 @@
+import React, { forwardRef, Ref, useCallback, useMemo, useState } from "react";
+import { CommonComponentProps } from "./common";
+import styled from "styled-components";
+import { Size } from "./Button";
+import Icon from "./Icon";
+
+export enum SearchVariant {
+ BACKGROUND = "BACKGROUND",
+ SEAMLESS = "SEAMLESS",
+}
+
+export type TextInputProps = CommonComponentProps & {
+ placeholder?: string;
+ fill?: boolean;
+ defaultValue?: string;
+ variant?: SearchVariant;
+ onChange?: (value: string) => void;
+};
+
+const StyledInput = styled.input<
+ TextInputProps & { value?: string; isFocused: boolean }
+>`
+ width: ${props =>
+ props.value && props.variant === SearchVariant.BACKGROUND && props.isFocused
+ ? "calc(100% - 50px)"
+ : "100%"};
+ border-radius: 0;
+ outline: 0;
+ box-shadow: none;
+ border: none;
+ padding: 0;
+ background-color: transparent;
+ font-size: ${props => props.theme.typography.p1.fontSize}px;
+ font-weight: ${props => props.theme.typography.p1.fontWeight};
+ line-height: ${props => props.theme.typography.p1.lineHeight}px;
+ letter-spacing: ${props => props.theme.typography.p1.letterSpacing}px;
+ text-overflow: ellipsis;
+
+ color: ${props => props.theme.colors.blackShades[9]};
+
+ &::placeholder {
+ color: ${props => props.theme.colors.blackShades[5]};
+ }
+`;
+
+const InputWrapper = styled.div<{
+ value?: string;
+ isFocused: boolean;
+ variant?: SearchVariant;
+ fill?: boolean;
+}>`
+ display: flex;
+ align-items: center;
+ padding: ${props => props.theme.spaces[3]}px
+ ${props => props.theme.spaces[4]}px ${props => props.theme.spaces[3]}px
+ ${props => props.theme.spaces[6]}px;
+ width: ${props => (props.fill ? "100%" : "210px")};
+ background-color: ${props =>
+ props.variant === SearchVariant.SEAMLESS ? "transparent" : "#262626"};
+ ${props =>
+ props.variant === SearchVariant.BACKGROUND
+ ? props.isFocused || props.value
+ ? `box-shadow: 0px 1px 0px ${props.theme.colors.info.main}`
+ : `box-shadow: 0px 1px 0px ${props.theme.colors.blackShades[4]}`
+ : null}
+
+ .search-icon {
+ margin-right: ${props => props.theme.spaces[5]}px;
+
+ svg {
+ path,
+ circle {
+ stroke: ${props =>
+ props.isFocused || props.value
+ ? props.theme.colors.blackShades[7]
+ : props.theme.colors.blackShades[5]};
+ }
+ }
+ }
+
+ .close-icon {
+ margin-right: ${props => props.theme.spaces[4]}px;
+ margin-left: ${props => props.theme.spaces[4]}px;
+ }
+`;
+
+const SearchInput = forwardRef(
+ (props: TextInputProps, ref: Ref) => {
+ const [searchValue, setSearchValue] = useState(props.defaultValue);
+ const [isFocused, setIsFocused] = useState(false);
+
+ const memoizedChangeHandler = useCallback(
+ el => {
+ setSearchValue(el.target.value);
+ return props.onChange && props.onChange(el.target.value);
+ },
+ [props],
+ );
+
+ return (
+
+
+ setIsFocused(true)}
+ onBlur={() => setIsFocused(false)}
+ onChange={memoizedChangeHandler}
+ />
+ {searchValue && props.variant === SearchVariant.BACKGROUND ? (
+ setSearchValue("")}
+ />
+ ) : null}
+
+ );
+ },
+);
+
+SearchInput.defaultProps = {
+ fill: false,
+};
+
+SearchInput.displayName = "SearchInput";
+
+export default SearchInput;
diff --git a/app/client/src/components/ads/Tabs.tsx b/app/client/src/components/ads/Tabs.tsx
index c5ade3092e6..68f5303705c 100644
--- a/app/client/src/components/ads/Tabs.tsx
+++ b/app/client/src/components/ads/Tabs.tsx
@@ -2,7 +2,7 @@ import React from "react";
import { Tab, Tabs, TabList, TabPanel } from "react-tabs";
import "react-tabs/style/react-tabs.css";
import styled from "styled-components";
-import { Icon, IconName } from "./Icon";
+import Icon, { IconName } from "./Icon";
import { Size } from "./Button";
const TabsWrapper = styled.div<{ shouldOverflow?: boolean }>`
diff --git a/app/client/src/components/stories/ColorSelector.stories.tsx b/app/client/src/components/stories/ColorSelector.stories.tsx
new file mode 100644
index 00000000000..9ca90b491cf
--- /dev/null
+++ b/app/client/src/components/stories/ColorSelector.stories.tsx
@@ -0,0 +1,23 @@
+import React from "react";
+import { action } from "@storybook/addon-actions";
+import ColorSelector, { appColorPalette } from "components/ads/ColorSelector";
+import { withKnobs, array, boolean } from "@storybook/addon-knobs";
+import { withDesign } from "storybook-addon-designs";
+
+export default {
+ title: "ColorSelector",
+ component: ColorSelector,
+ decorators: [withKnobs, withDesign],
+};
+
+const defaultValue = appColorPalette;
+
+export const ColorPickerStory = () => (
+
+
+
+);
diff --git a/app/client/src/components/stories/EditableText.stories.tsx b/app/client/src/components/stories/EditableText.stories.tsx
new file mode 100644
index 00000000000..de5a12f3b3a
--- /dev/null
+++ b/app/client/src/components/stories/EditableText.stories.tsx
@@ -0,0 +1,60 @@
+import React from "react";
+import { boolean, select, text, withKnobs } from "@storybook/addon-knobs";
+import { withDesign } from "storybook-addon-designs";
+import AdsEditableText, {
+ EditInteractionKind,
+ SavingStateHandler,
+ SavingState,
+} from "../ads/EditableText";
+import { action } from "@storybook/addon-actions";
+
+export default {
+ title: "EditableText",
+ component: AdsEditableText,
+ decorators: [withKnobs, withDesign],
+};
+
+const calls = (value: string, callback: any) => {
+ console.log("value", value);
+
+ // setTimeout(() => {
+ // return callback(SavingState.ERROR);
+ // }, 2000);
+
+ setTimeout(() => {
+ return callback(false, SavingState.SUCCESS);
+ }, 2000);
+
+ return callback(true);
+};
+
+const errorFunction = (name: string) => {
+ if (name === "") {
+ return "Name cannot be empty";
+ } else {
+ return false;
+ }
+};
+
+export const EditableTextStory = () => (
+
+
value.toUpperCase()}
+ placeholder={text("placeholder", "Edit input")}
+ hideEditIcon={boolean("hideEditIcon", false)}
+ isInvalid={name => errorFunction(name)}
+ isEditingDefault={boolean("isEditingDefault", false)}
+ fill={boolean("fill", false)}
+ onSubmit={(value: string, callback: SavingStateHandler) =>
+ calls(value, callback)
+ }
+ >
+
+);
diff --git a/app/client/src/components/stories/Icon.stories.tsx b/app/client/src/components/stories/Icon.stories.tsx
index abe85098777..7e267cad2d2 100644
--- a/app/client/src/components/stories/Icon.stories.tsx
+++ b/app/client/src/components/stories/Icon.stories.tsx
@@ -1,8 +1,9 @@
import React from "react";
-import { Icon } from "../ads/Icon";
import Button, { Size, Category, Variant } from "components/ads/Button";
import { withKnobs, select, boolean } from "@storybook/addon-knobs";
import { withDesign } from "storybook-addon-designs";
+import Icon from "../ads/Icon";
+import AppIcon, { AppIconName } from "../ads/AppIcon";
export default {
title: "Icon",
@@ -11,29 +12,57 @@ export default {
};
export const ButtonIcon = () => (
-
+
+
+
);
export const BordelessIcon = () => (
-
+
);
+
+export const BorderlessAppIcon = () => (
+
+);
diff --git a/app/client/src/components/stories/IconSelector.stories.tsx b/app/client/src/components/stories/IconSelector.stories.tsx
new file mode 100644
index 00000000000..9058aad8489
--- /dev/null
+++ b/app/client/src/components/stories/IconSelector.stories.tsx
@@ -0,0 +1,54 @@
+import React from "react";
+import { withKnobs, select, boolean, text } from "@storybook/addon-knobs";
+import { withDesign } from "storybook-addon-designs";
+import IconSelector from "../ads/IconSelector";
+import { action } from "@storybook/addon-actions";
+import { AppIconName } from "../ads/AppIcon";
+
+export default {
+ title: "IconSelector",
+ component: IconSelector,
+ decorators: [withKnobs, withDesign],
+};
+
+export const IconPicker = () => (
+
+
+
+);
diff --git a/app/client/src/components/stories/SearchInput.stories.tsx b/app/client/src/components/stories/SearchInput.stories.tsx
new file mode 100644
index 00000000000..c245cc932ee
--- /dev/null
+++ b/app/client/src/components/stories/SearchInput.stories.tsx
@@ -0,0 +1,26 @@
+import React from "react";
+import { withKnobs, boolean, text, select } from "@storybook/addon-knobs";
+import { action } from "@storybook/addon-actions";
+import SearchInput, { SearchVariant } from "../ads/SearchInput";
+
+export default {
+ title: "Search Input",
+ component: SearchInput,
+ decorators: [withKnobs],
+};
+
+export const SearchInputStory = () => (
+
+
+
+);
diff --git a/app/client/src/components/stories/Table.stories.tsx b/app/client/src/components/stories/Table.stories.tsx
index 5144f8ae7fd..1e401568358 100644
--- a/app/client/src/components/stories/Table.stories.tsx
+++ b/app/client/src/components/stories/Table.stories.tsx
@@ -1,7 +1,7 @@
import React from "react";
import Table from "../ads/Table";
import Button, { Category, Variant, Size } from "../ads/Button";
-import { Icon } from "../ads/Icon";
+import Icon from "../ads/Icon";
export default {
title: "Table",
diff --git a/app/client/src/configs/index.ts b/app/client/src/configs/index.ts
index eab451cd81f..56e6d1b02bb 100644
--- a/app/client/src/configs/index.ts
+++ b/app/client/src/configs/index.ts
@@ -30,6 +30,7 @@ type INJECTED_CONFIGS = {
};
intercomAppID: string;
mailEnabled: boolean;
+ smartLookKey: string;
};
declare global {
interface Window {
@@ -94,6 +95,7 @@ const getConfigsFromEnvVars = (): INJECTED_CONFIGS => {
mailEnabled: process.env.REACT_APP_MAIL_ENABLED
? process.env.REACT_APP_MAIL_ENABLED.length > 0
: false,
+ smartLookKey: process.env.REACT_APP_SMART_LOOK_KEY || "",
};
};
diff --git a/app/client/src/constants/ReduxActionConstants.tsx b/app/client/src/constants/ReduxActionConstants.tsx
index 1996a0ab1a7..3dea6e53540 100644
--- a/app/client/src/constants/ReduxActionConstants.tsx
+++ b/app/client/src/constants/ReduxActionConstants.tsx
@@ -62,6 +62,8 @@ export const ReduxActionTypes: { [key: string]: string } = {
CREATE_QUERY_INIT: "CREATE_QUERY_INIT",
FETCH_DATASOURCES_INIT: "FETCH_DATASOURCES_INIT",
FETCH_DATASOURCES_SUCCESS: "FETCH_DATASOURCES_SUCCESS",
+ SAVE_DATASOURCE_NAME: "SAVE_DATASOURCE_NAME",
+ SAVE_DATASOURCE_NAME_SUCCESS: "SAVE_DATASOURCE_NAME_SUCCESS",
CREATE_DATASOURCE_INIT: "CREATE_DATASOURCE_INIT",
CREATE_DATASOURCE_SUCCESS: "CREATE_DATASOURCE_SUCCESS",
CREATE_DATASOURCE_FROM_FORM_INIT: "CREATE_DATASOURCE_FROM_FORM_INIT",
@@ -281,6 +283,7 @@ export const ReduxActionErrorTypes: { [key: string]: string } = {
FETCH_DATASOURCES_ERROR: "FETCH_DATASOURCES_ERROR",
SEARCH_APIORPROVIDERS_ERROR: "SEARCH_APIORPROVIDERS_ERROR",
UPDATE_DATASOURCE_ERROR: "UPDATE_DATASOURCE_ERROR",
+ SAVE_DATASOURCE_NAME_ERROR: "SAVE_DATASOURCE_NAME_ERROR",
CREATE_DATASOURCE_ERROR: "CREATE_DATASOURCE_ERROR",
DELETE_DATASOURCE_ERROR: "DELETE_DATASOURCE_ERROR",
FETCH_PUBLISHED_PAGE_ERROR: "FETCH_PUBLISHED_PAGE_ERROR",
diff --git a/app/client/src/pages/Editor/DataSourceEditor/DBForm.tsx b/app/client/src/pages/Editor/DataSourceEditor/DBForm.tsx
index 8d991f3a9ac..0d867162b10 100644
--- a/app/client/src/pages/Editor/DataSourceEditor/DBForm.tsx
+++ b/app/client/src/pages/Editor/DataSourceEditor/DBForm.tsx
@@ -16,7 +16,7 @@ import FormControlFactory from "utils/FormControlFactory";
import { HelpBaseURL, HelpMap } from "constants/HelpConstants";
import Button from "components/editorComponents/Button";
import { Datasource } from "api/DatasourcesApi";
-import { reduxForm, InjectedFormProps, Field } from "redux-form";
+import { reduxForm, InjectedFormProps } from "redux-form";
import { BaseButton } from "components/designSystems/blueprint/ButtonComponent";
import { APPSMITH_IP_ADDRESS } from "constants/DatasourceEditorConstants";
import { getAppsmithConfigs } from "configs";
@@ -314,11 +314,7 @@ class DatasourceDBEditor extends React.Component<
-
+
{cloudHosting && (
diff --git a/app/client/src/pages/Editor/DataSourceEditor/FormTitle.tsx b/app/client/src/pages/Editor/DataSourceEditor/FormTitle.tsx
index 55a0370e066..1d0adafdf0a 100644
--- a/app/client/src/pages/Editor/DataSourceEditor/FormTitle.tsx
+++ b/app/client/src/pages/Editor/DataSourceEditor/FormTitle.tsx
@@ -1,6 +1,5 @@
import styled from "styled-components";
-import React from "react";
-import { WrappedFieldInputProps } from "redux-form";
+import React, { useCallback, useEffect, useState } from "react";
import { useParams } from "react-router-dom";
import EditableText, {
EditInteractionKind,
@@ -8,33 +7,47 @@ import EditableText, {
import { AppState } from "reducers";
import { getDatasource } from "selectors/entitiesSelector";
-import { useSelector } from "react-redux";
+import { useSelector, useDispatch } from "react-redux";
import { Datasource } from "api/DatasourcesApi";
import { getDataSources } from "selectors/editorSelectors";
+import { saveDatasourceName } from "actions/datasourceActions";
+import { Spinner } from "@blueprintjs/core";
const Wrapper = styled.div`
margin-left: 10px;
font-size: 18px;
font-weight: 500;
line-height: 24px;
+ display: flex;
`;
interface ComponentProps {
- input: WrappedFieldInputProps;
focusOnMount: boolean;
}
type FormTitleProps = ComponentProps;
const FormTitle = (props: FormTitleProps) => {
- const { input } = props;
const params = useParams<{ datasourceId: string }>();
const currentDatasource:
- | Partial
+ | Datasource
| undefined = useSelector((state: AppState) =>
getDatasource(state, params.datasourceId),
);
const datasources: Datasource[] = useSelector(getDataSources);
+ const [forceUpdate, setForceUpdate] = useState(false);
+ const dispatch = useDispatch();
+ const saveStatus: {
+ isSaving: boolean;
+ error: boolean;
+ } = useSelector((state: AppState) => {
+ const id = currentDatasource ? currentDatasource.id : "";
+
+ return {
+ isSaving: state.ui.datasourceName.isSaving[id],
+ error: state.ui.datasourceName.errors[id],
+ };
+ });
const hasNameConflict = React.useCallback(
(name: string) =>
@@ -57,17 +70,41 @@ const FormTitle = (props: FormTitleProps) => {
[hasNameConflict],
);
+ const handleDatasourceNameChange = useCallback(
+ (name: string) => {
+ if (
+ !isInvalidDatasourceName(name) &&
+ currentDatasource &&
+ currentDatasource.name !== name
+ ) {
+ dispatch(saveDatasourceName({ id: currentDatasource?.id ?? "", name }));
+ }
+ },
+ [dispatch, isInvalidDatasourceName, currentDatasource],
+ );
+
+ useEffect(() => {
+ if (saveStatus.isSaving === false && saveStatus.error === true) {
+ setForceUpdate(true);
+ } else if (saveStatus.isSaving === true) {
+ setForceUpdate(false);
+ }
+ }, [saveStatus.isSaving, saveStatus.error]);
+
return (
input.onChange(value)}
+ onTextChanged={handleDatasourceNameChange}
placeholder="Datasource Name"
editInteractionKind={EditInteractionKind.SINGLE}
isEditingDefault={props.focusOnMount}
+ updating={saveStatus.isSaving}
/>
+ {saveStatus.isSaving && }
);
};
diff --git a/app/client/src/pages/Editor/QueryEditor/Table.tsx b/app/client/src/pages/Editor/QueryEditor/Table.tsx
index 36d8e4e6278..9dc40037f59 100644
--- a/app/client/src/pages/Editor/QueryEditor/Table.tsx
+++ b/app/client/src/pages/Editor/QueryEditor/Table.tsx
@@ -6,6 +6,8 @@ import {
import { useTable, useFlexLayout } from "react-table";
import styled from "styled-components";
import { CompactModeTypes, TABLE_SIZES } from "widgets/TableWidget";
+import AutoToolTipComponent from "components/designSystems/appsmith/AutoToolTipComponent";
+import { getType, Types } from "utils/TypeHelpers";
interface TableProps {
data: Record[];
@@ -30,6 +32,38 @@ const StyledTableWrapped = styled(TableWrapper)`
}
`;
+const renderCell = (props: any) => {
+ const value = props.cell.value;
+ let displayValue;
+ switch (getType(value)) {
+ case Types.NUMBER:
+ case Types.BOOLEAN:
+ displayValue = value.toString();
+ break;
+ case Types.ARRAY:
+ case Types.FUNCTION:
+ case Types.OBJECT:
+ displayValue = JSON.stringify(value);
+ break;
+ case Types.STRING:
+ displayValue = value;
+ break;
+ case Types.NULL:
+ case Types.UNDEFINED:
+ case Types.UNKNOWN:
+ displayValue = "";
+ break;
+ default:
+ displayValue = "";
+ }
+
+ return (
+
+ {displayValue}
+
+ );
+};
+
const Table = (props: TableProps) => {
const data = React.useMemo(() => props.data, [props.data]);
const columns = React.useMemo(() => {
@@ -38,6 +72,7 @@ const Table = (props: TableProps) => {
return {
Header: key,
accessor: key,
+ Cell: renderCell,
};
});
}
diff --git a/app/client/src/reducers/entityReducers/datasourceReducer.ts b/app/client/src/reducers/entityReducers/datasourceReducer.ts
index c53a48329dd..d9ce250ef5a 100644
--- a/app/client/src/reducers/entityReducers/datasourceReducer.ts
+++ b/app/client/src/reducers/entityReducers/datasourceReducer.ts
@@ -93,6 +93,20 @@ const datasourceReducer = createReducer(initialState, {
}),
};
},
+ [ReduxActionTypes.SAVE_DATASOURCE_NAME_SUCCESS]: (
+ state: DatasourceDataState,
+ action: ReduxAction,
+ ): DatasourceDataState => {
+ return {
+ ...state,
+ loading: false,
+ list: state.list.map(datasource => {
+ if (datasource.id === action.payload.id) return action.payload;
+
+ return datasource;
+ }),
+ };
+ },
[ReduxActionErrorTypes.CREATE_DATASOURCE_ERROR]: (
state: DatasourceDataState,
) => {
diff --git a/app/client/src/reducers/index.tsx b/app/client/src/reducers/index.tsx
index 1ca2fb02120..e6e9a946345 100644
--- a/app/client/src/reducers/index.tsx
+++ b/app/client/src/reducers/index.tsx
@@ -31,6 +31,7 @@ import { ApiNameReduxState } from "./uiReducers/apiNameReducer";
import { ExplorerReduxState } from "./uiReducers/explorerReducer";
import { PageDSLsReduxState } from "./uiReducers/pageDSLReducer";
import { AppDataState } from "@appsmith/reducers/entityReducers/appReducer";
+import { DatasourceNameReduxState } from "./uiReducers/datasourceNameReducer";
const appReducer = combineReducers({
entities: entityReducer,
@@ -62,6 +63,7 @@ export interface AppState {
apiName: ApiNameReduxState;
explorer: ExplorerReduxState;
pageDSLs: PageDSLsReduxState;
+ datasourceName: DatasourceNameReduxState;
};
entities: {
canvasWidgets: CanvasWidgetsReduxState;
diff --git a/app/client/src/reducers/uiReducers/datasourceNameReducer.ts b/app/client/src/reducers/uiReducers/datasourceNameReducer.ts
new file mode 100644
index 00000000000..4581dc6056a
--- /dev/null
+++ b/app/client/src/reducers/uiReducers/datasourceNameReducer.ts
@@ -0,0 +1,70 @@
+import { createReducer } from "utils/AppsmithUtils";
+import {
+ ReduxAction,
+ ReduxActionTypes,
+ ReduxActionErrorTypes,
+} from "constants/ReduxActionConstants";
+
+const initialState: DatasourceNameReduxState = {
+ isSaving: {},
+ errors: {},
+};
+
+const datasourceNameReducer = createReducer(initialState, {
+ [ReduxActionErrorTypes.SAVE_DATASOURCE_NAME_ERROR]: (
+ state: DatasourceNameReduxState,
+ action: ReduxAction<{ id: string }>,
+ ) => {
+ return {
+ ...state,
+ isSaving: {
+ ...state.isSaving,
+ [action.payload.id]: false,
+ },
+ errors: {
+ ...state.errors,
+ [action.payload.id]: true,
+ },
+ };
+ },
+
+ [ReduxActionTypes.SAVE_DATASOURCE_NAME]: (
+ state: DatasourceNameReduxState,
+ action: ReduxAction<{ id: string }>,
+ ) => {
+ return {
+ ...state,
+ isSaving: {
+ ...state.isSaving,
+ [action.payload.id]: true,
+ },
+ errors: {
+ ...state.errors,
+ [action.payload.id]: false,
+ },
+ };
+ },
+ [ReduxActionTypes.SAVE_DATASOURCE_NAME_SUCCESS]: (
+ state: DatasourceNameReduxState,
+ action: ReduxAction<{ id: string }>,
+ ) => {
+ return {
+ ...state,
+ isSaving: {
+ ...state.isSaving,
+ [action.payload.id]: false,
+ },
+ errors: {
+ ...state.errors,
+ [action.payload.id]: false,
+ },
+ };
+ },
+});
+
+export interface DatasourceNameReduxState {
+ isSaving: Record;
+ errors: Record;
+}
+
+export default datasourceNameReducer;
diff --git a/app/client/src/reducers/uiReducers/index.tsx b/app/client/src/reducers/uiReducers/index.tsx
index c2ed4b9742d..f3f4926a847 100644
--- a/app/client/src/reducers/uiReducers/index.tsx
+++ b/app/client/src/reducers/uiReducers/index.tsx
@@ -19,6 +19,7 @@ import helpReducer from "./helpReducer";
import apiNameReducer from "./apiNameReducer";
import explorerReducer from "./explorerReducer";
import pageDSLsReducer from "./pageDSLReducer";
+import datasourceNameReducer from "./datasourceNameReducer";
const uiReducer = combineReducers({
widgetSidebar: widgetSidebarReducer,
@@ -37,6 +38,7 @@ const uiReducer = combineReducers({
imports: importReducer,
queryPane: queryPaneReducer,
datasourcePane: datasourcePaneReducer,
+ datasourceName: datasourceNameReducer,
help: helpReducer,
apiName: apiNameReducer,
explorer: explorerReducer,
diff --git a/app/client/src/sagas/DatasourcesSagas.ts b/app/client/src/sagas/DatasourcesSagas.ts
index 4c02af58034..e6ea0cf5f57 100644
--- a/app/client/src/sagas/DatasourcesSagas.ts
+++ b/app/client/src/sagas/DatasourcesSagas.ts
@@ -162,14 +162,16 @@ function* updateDatasourceSaga(
}>,
) {
try {
+ const datasourcePayload = _.omit(actionPayload.payload.datasource, "name");
+
const response: GenericApiResponse = yield DatasourcesApi.updateDatasource(
- actionPayload.payload.datasource,
- actionPayload.payload.datasource.id,
+ datasourcePayload,
+ datasourcePayload.id,
);
const isValidResponse = yield validateResponse(response);
if (isValidResponse) {
AppToaster.show({
- message: `${actionPayload.payload.datasource.name} Datasource updated`,
+ message: `${response.data.name} Datasource updated`,
type: ToastType.SUCCESS,
});
yield put({
@@ -183,9 +185,7 @@ function* updateDatasourceSaga(
},
});
if (actionPayload.payload.reinitializeForm) {
- yield put(
- initialize(DATASOURCE_DB_FORM, actionPayload.payload.datasource),
- );
+ yield put(initialize(DATASOURCE_DB_FORM, datasourcePayload));
}
}
} catch (error) {
@@ -196,6 +196,38 @@ function* updateDatasourceSaga(
}
}
+function* saveDatasourceNameSaga(
+ actionPayload: ReduxAction<{ id: string; name: string }>,
+) {
+ try {
+ const response: GenericApiResponse = yield DatasourcesApi.updateDatasource(
+ {
+ name: actionPayload.payload.name,
+ },
+ actionPayload.payload.id,
+ );
+
+ const isValidResponse = yield validateResponse(response);
+ if (isValidResponse) {
+ yield put({
+ type: ReduxActionTypes.SAVE_DATASOURCE_NAME_SUCCESS,
+ payload: { ...response.data },
+ });
+ }
+ } catch (error) {
+ yield put({
+ type: ReduxActionErrorTypes.SAVE_DATASOURCE_NAME_ERROR,
+ payload: { id: actionPayload.payload.id },
+ });
+ }
+}
+
+function* handleDatasourceNameChangeFailureSaga(
+ action: ReduxAction<{ oldName: string }>,
+) {
+ yield put(change(DATASOURCE_DB_FORM, "name", action.payload.oldName));
+}
+
function* testDatasourceSaga(actionPayload: ReduxAction) {
const organizationId = yield select(getCurrentOrgId);
const { initialValues, values } = yield select(
@@ -405,8 +437,9 @@ function* switchDatasourceSaga(action: ReduxAction<{ datasourceId: string }>) {
function* formValueChangeSaga(
actionPayload: ReduxActionWithMeta,
) {
- const { form } = actionPayload.meta;
+ const { form, field } = actionPayload.meta;
if (form !== DATASOURCE_DB_FORM) return;
+ if (field === "name") return;
yield all([call(updateDraftsSaga)]);
}
@@ -466,6 +499,11 @@ export function* watchDatasourcesSagas() {
createDatasourceFromFormSaga,
),
takeEvery(ReduxActionTypes.UPDATE_DATASOURCE_INIT, updateDatasourceSaga),
+ takeEvery(ReduxActionTypes.SAVE_DATASOURCE_NAME, saveDatasourceNameSaga),
+ takeEvery(
+ ReduxActionErrorTypes.SAVE_DATASOURCE_NAME_ERROR,
+ handleDatasourceNameChangeFailureSaga,
+ ),
takeEvery(ReduxActionTypes.TEST_DATASOURCE_INIT, testDatasourceSaga),
takeEvery(ReduxActionTypes.DELETE_DATASOURCE_INIT, deleteDatasourceSaga),
takeEvery(ReduxActionTypes.CHANGE_DATASOURCE, changeDatasourceSaga),
diff --git a/app/client/src/sagas/PageSagas.tsx b/app/client/src/sagas/PageSagas.tsx
index 4c194915958..6e09af77de5 100644
--- a/app/client/src/sagas/PageSagas.tsx
+++ b/app/client/src/sagas/PageSagas.tsx
@@ -91,20 +91,18 @@ export function* fetchPageListSaga(
isDefault: page.isDefault,
}));
yield put({
- type: ReduxActionTypes.FETCH_PAGE_LIST_SUCCESS,
+ type: ReduxActionTypes.SET_CURRENT_ORG_ID,
payload: {
- pages,
- applicationId,
+ orgId,
},
});
yield put({
- type: ReduxActionTypes.SET_CURRENT_ORG_ID,
+ type: ReduxActionTypes.FETCH_PAGE_LIST_SUCCESS,
payload: {
- orgId,
+ pages,
+ applicationId,
},
});
-
- return;
}
} catch (error) {
yield put({
diff --git a/app/client/src/sagas/PluginSagas.ts b/app/client/src/sagas/PluginSagas.ts
index 34c78a4c9fa..45bbf5d80cb 100644
--- a/app/client/src/sagas/PluginSagas.ts
+++ b/app/client/src/sagas/PluginSagas.ts
@@ -11,6 +11,9 @@ import { getCurrentOrgId } from "selectors/organizationSelectors";
function* fetchPluginsSaga() {
try {
const orgId = yield select(getCurrentOrgId);
+ if (!orgId) {
+ throw Error("Org id does not exist");
+ }
const pluginsResponse = yield call(PluginsApi.fetchPlugins, orgId);
const isValid = yield validateResponse(pluginsResponse);
if (isValid) {
diff --git a/app/client/src/selectors/entitiesSelector.ts b/app/client/src/selectors/entitiesSelector.ts
index f10c8d7937d..705ba20153a 100644
--- a/app/client/src/selectors/entitiesSelector.ts
+++ b/app/client/src/selectors/entitiesSelector.ts
@@ -101,7 +101,7 @@ export const getDatasourceRefs = (state: AppState): any =>
export const getDatasource = (
state: AppState,
datasourceId: string,
-): Partial | undefined =>
+): Datasource | undefined =>
state.entities.datasources.list.find(
datasource => datasource.id === datasourceId,
);
diff --git a/app/client/src/utils/AnalyticsUtil.tsx b/app/client/src/utils/AnalyticsUtil.tsx
index ec55c2e7ed5..83b9bb58f69 100644
--- a/app/client/src/utils/AnalyticsUtil.tsx
+++ b/app/client/src/utils/AnalyticsUtil.tsx
@@ -200,9 +200,14 @@ class AnalyticsUtil {
});
}
if (windowDoc.hj) {
- // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
- // @ts-ignore
- window.hj("identify", userData.email, { email: userData.email });
+ windowDoc.hj("identify", userData.email, { email: userData.email });
+ }
+ if (windowDoc.smartLook) {
+ windowDoc.smartlook("identify", userId, {
+ email: userData.email,
+ name: userData.name,
+ userId: userId,
+ });
}
}
diff --git a/app/client/src/widgets/TableWidget.tsx b/app/client/src/widgets/TableWidget.tsx
index 78ccdfc9062..0795eeb5478 100644
--- a/app/client/src/widgets/TableWidget.tsx
+++ b/app/client/src/widgets/TableWidget.tsx
@@ -81,7 +81,6 @@ class TableWidget extends BaseWidget {
nextPageKey: VALIDATION_TYPES.TEXT,
prevPageKey: VALIDATION_TYPES.TEXT,
label: VALIDATION_TYPES.TEXT,
- selectedRowIndex: VALIDATION_TYPES.NUMBER,
searchText: VALIDATION_TYPES.TEXT,
defaultSearchText: VALIDATION_TYPES.TEXT,
};
@@ -337,7 +336,8 @@ class TableWidget extends BaseWidget {
JSON.stringify(prevProps.filters) ||
this.props.searchText !== prevProps.searchText ||
JSON.stringify(this.props.sortedColumn) !==
- JSON.stringify(prevProps.sortedColumn)
+ JSON.stringify(prevProps.sortedColumn) ||
+ !this.props.filteredTableData
) {
const filteredTableData = this.filterTableData();
super.updateWidgetMetaProperty("filteredTableData", filteredTableData);