feat: adding ads component for UQI (#8777)

This commit is contained in:
rashmi rai 2021-12-27 17:34:45 +05:30 committed by GitHub
parent 7a0715af7c
commit f6b9abdacf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
37 changed files with 1329 additions and 771 deletions

2
.gitignore vendored
View File

@ -4,6 +4,8 @@
.env .env
.vscode/* .vscode/*
app/client/cypress.env.json
# to ignore the node_modeules folder # to ignore the node_modeules folder
node_modules node_modules
# to ignore the package-lock.json file # to ignore the package-lock.json file

View File

@ -38,7 +38,7 @@ describe("API Panel Test Functionality", function() {
cy.log("Creation of FirstAPI Action successful"); cy.log("Creation of FirstAPI Action successful");
cy.enterDatasourceAndPath(testdata.baseUrl, testdata.methods); cy.enterDatasourceAndPath(testdata.baseUrl, testdata.methods);
cy.get(apiwidget.settings).click({ force: true }); cy.get(apiwidget.settings).click({ force: true });
cy.get(apiwidget.confirmBeforeExecute).click(); cy.get(apiwidget.confirmBeforeExecute).click({ force: true });
cy.get(apiwidget.runQueryButton).click(); cy.get(apiwidget.runQueryButton).click();
cy.get(".bp3-dialog") cy.get(".bp3-dialog")
.find("button") .find("button")

View File

@ -1,6 +1,5 @@
const testdata = require("../../../../fixtures/testdata.json"); const testdata = require("../../../../fixtures/testdata.json");
const apiwidget = require("../../../../locators/apiWidgetslocator.json"); const apiwidget = require("../../../../locators/apiWidgetslocator.json");
const commonlocators = require("../../../../locators/commonlocators.json");
const dsl = require("../../../../fixtures/MultipleInput.json"); const dsl = require("../../../../fixtures/MultipleInput.json");
const widgetsPage = require("../../../../locators/Widgets.json"); const widgetsPage = require("../../../../locators/Widgets.json");
const publish = require("../../../../locators/publishWidgetspage.json"); const publish = require("../../../../locators/publishWidgetspage.json");

View File

@ -4,7 +4,6 @@ const datasource = require("../../../../locators/DatasourcesEditor.json");
const datasourceEditor = require("../../../../locators/DatasourcesEditor.json"); const datasourceEditor = require("../../../../locators/DatasourcesEditor.json");
const datasourceFormData = require("../../../../fixtures/datasources.json"); const datasourceFormData = require("../../../../fixtures/datasources.json");
const queryLocators = require("../../../../locators/QueryEditor.json"); const queryLocators = require("../../../../locators/QueryEditor.json");
const jsEditorLocators = require("../../../../locators/JSEditor.json");
describe("Undo/Redo functionality", function() { describe("Undo/Redo functionality", function() {
const modifierKey = Cypress.platform === "darwin" ? "meta" : "ctrl"; const modifierKey = Cypress.platform === "darwin" ? "meta" : "ctrl";

View File

@ -29,12 +29,13 @@ describe("API Panel Test Functionality", function() {
}); });
it("Shows which action failed on action fail.", function() { it("Shows which action failed on action fail.", function() {
cy.wait(2000);
cy.NavigateToAPI_Panel(); cy.NavigateToAPI_Panel();
cy.CreateAPI("PageLoadApi2"); cy.CreateAPI("PageLoadApi2");
cy.enterDatasourceAndPath("https://abc.com", "users"); cy.enterDatasourceAndPath("https://abc.com", "users");
cy.WaitAutoSave(); cy.WaitAutoSave();
cy.get("li:contains('Settings')").click({ force: true }); cy.get("li:contains('Settings')").click({ force: true });
cy.get("[data-cy=executeOnLoad]").click({ force: true }); cy.get("[name=executeOnLoad]").click({ force: true });
cy.wait("@setExecuteOnLoad"); cy.wait("@setExecuteOnLoad");
@ -44,6 +45,7 @@ describe("API Panel Test Functionality", function() {
cy.wait("@updateLayout"); cy.wait("@updateLayout");
cy.reload(); cy.reload();
cy.wait(3000);
cy.get(commonlocators.toastMsg).contains( cy.get(commonlocators.toastMsg).contains(
`The action "PageLoadApi2" has failed.`, `The action "PageLoadApi2" has failed.`,
); );

View File

@ -35,7 +35,6 @@ describe("Create a query with a mongo datasource, run, save and then delete the
// ); // );
cy.validateNSelectDropdown("Commands", "Find Document(s)", "Raw"); cy.validateNSelectDropdown("Commands", "Find Document(s)", "Raw");
cy.get(queryLocators.templateMenu).click(); cy.get(queryLocators.templateMenu).click();
cy.typeValueNValidate('{"find": "listingsAndReviews","limit": 10}'); cy.typeValueNValidate('{"find": "listingsAndReviews","limit": 10}');

View File

@ -258,81 +258,81 @@ describe("Validate CRUD queries for Amazon S3 along with UI flow verifications",
cy.deleteQueryUsingContext(); //exeute actions & 200 response is verified in this method cy.deleteQueryUsingContext(); //exeute actions & 200 response is verified in this method
}); });
// it("5. Validate Read file command, Verify possible error msgs, run & delete the query", () => { it("5. Validate Read file command, Verify possible error msgs, run & delete the query", () => {
// cy.NavigateToActiveDSQueryPane(datasourceName); cy.NavigateToActiveDSQueryPane(datasourceName);
// cy.setQueryTimeout(30000); cy.setQueryTimeout(30000);
// cy.validateNSelectDropdown("Commands", "List files in bucket", "Read file"); cy.validateNSelectDropdown("Commands", "List files in bucket", "Read file");
// cy.onlyQueryRun(); cy.onlyQueryRun();
// cy.wait("@postExecute").should(({ response }) => { cy.wait("@postExecute").should(({ response }) => {
// expect(response.body.data.isExecutionSuccess).to.eq(false); expect(response.body.data.isExecutionSuccess).to.eq(false);
// expect(response.body.data.body).to.contains( expect(response.body.data.body).to.contains(
// "Mandatory parameter 'Bucket Name' is missing.", "Mandatory parameter 'Bucket Name' is missing.",
// ); );
// }); });
// cy.typeValueNValidate("AutoTest", "Bucket Name"); cy.typeValueNValidate("AutoTest", "Bucket Name");
// cy.onlyQueryRun(); cy.onlyQueryRun();
// cy.wait("@postExecute").then(({ response }) => { cy.wait("@postExecute").then(({ response }) => {
// expect(response.body.data.isExecutionSuccess).to.eq(false); expect(response.body.data.isExecutionSuccess).to.eq(false);
// expect(response.body.data.body).to.contains( expect(response.body.data.body).to.contains(
// "Required parameter 'File Path' is missing.", "Required parameter 'File Path' is missing.",
// ); );
// }); });
// cy.typeValueNValidate("Auto", "File Path"); cy.typeValueNValidate("Auto", "File Path");
// cy.onlyQueryRun(); cy.onlyQueryRun();
// cy.wait("@postExecute").then(({ response }) => { cy.wait("@postExecute").then(({ response }) => {
// expect(response.body.data.isExecutionSuccess).to.eq(false); expect(response.body.data.isExecutionSuccess).to.eq(false);
// expect(response.body.data.body.split("(")[0].trim()).to.be.oneOf([ expect(response.body.data.body.split("(")[0].trim()).to.be.oneOf([
// "The specified bucket does not exist", "The specified bucket does not exist",
// "The specified bucket is not valid.", "The specified bucket is not valid.",
// ]); ]);
// }); });
// cy.typeValueNValidate("assets-test.appsmith.com", "Bucket Name"); cy.typeValueNValidate("assets-test.appsmith.com", "Bucket Name");
// cy.onlyQueryRun(); cy.onlyQueryRun();
// cy.wait("@postExecute").then(({ response }) => { cy.wait("@postExecute").then(({ response }) => {
// expect(response.body.data.isExecutionSuccess).to.eq(false); expect(response.body.data.isExecutionSuccess).to.eq(false);
// expect(response.body.data.body).to.contain( expect(response.body.data.body).to.contain(
// "The specified key does not exist.", "The specified key does not exist.",
// ); );
// }); });
// cy.typeValueNValidate("Autofile", "File Path"); cy.typeValueNValidate("Autofile", "File Path");
// cy.onlyQueryRun(); cy.onlyQueryRun();
// cy.wait("@postExecute").then(({ response }) => { cy.wait("@postExecute").then(({ response }) => {
// expect(response.body.data.isExecutionSuccess).to.eq(false); expect(response.body.data.isExecutionSuccess).to.eq(false);
// expect(response.body.data.body).to.contain( expect(response.body.data.body).to.contain(
// "The specified key does not exist.", "The specified key does not exist.",
// ); );
// }); });
// cy.typeValueNValidate("AutoFile", "File Path"); cy.typeValueNValidate("AutoFile", "File Path");
// //Commenting below since below dropdown is removed from Read //Commenting below since below dropdown is removed from Read
// //cy.validateNSelectDropdown("File Data Type", "Base64", "Text / Binary"); //cy.validateNSelectDropdown("File Data Type", "Base64", "Text / Binary");
// cy.onlyQueryRun(); cy.onlyQueryRun();
// cy.wait("@postExecute").then(({ response }) => { cy.wait("@postExecute").then(({ response }) => {
// expect(response.body.data.isExecutionSuccess).to.eq(true); expect(response.body.data.isExecutionSuccess).to.eq(true);
// expect(response.body.data.body.fileData).to.not.eq( expect(response.body.data.body.fileData).to.not.eq(
// "Hi, this is Automation script adding File!", "Hi, this is Automation script adding File!",
// ); );
// }); });
// cy.validateNSelectDropdown("Base64 Encode File - Yes/No", "Yes", "No"); cy.validateNSelectDropdown("Base64 Encode File - Yes/No", "Yes", "No");
// cy.onlyQueryRun(); cy.onlyQueryRun();
// cy.wait("@postExecute").then(({ response }) => { cy.wait("@postExecute").then(({ response }) => {
// expect(response.body.data.isExecutionSuccess).to.eq(true); expect(response.body.data.isExecutionSuccess).to.eq(true);
// expect(response.body.data.body.fileData).to.eq( expect(response.body.data.body.fileData).to.eq(
// "Hi, this is Automation script adding File!", "Hi, this is Automation script adding File!",
// ); );
// }); });
// cy.deleteQueryUsingContext(); //exeute actions & 200 response is verified in this method cy.deleteQueryUsingContext(); //exeute actions & 200 response is verified in this method
// }); });
it("6. Validate Delete file command for new file, Verify possible error msgs, run & delete the query", () => { it("6. Validate Delete file command for new file, Verify possible error msgs, run & delete the query", () => {
cy.NavigateToActiveDSQueryPane(datasourceName); cy.NavigateToActiveDSQueryPane(datasourceName);
@ -564,7 +564,7 @@ describe("Validate CRUD queries for Amazon S3 along with UI flow verifications",
cy.wait(3000); //waiting for deletion to complete! - else next case fails cy.wait(3000); //waiting for deletion to complete! - else next case fails
}); });
it("11. Deletes the datasource", () => { it("13. Deletes the datasource", () => {
cy.NavigateToQueryEditor(); cy.NavigateToQueryEditor();
cy.NavigateToActiveTab(); cy.NavigateToActiveTab();
cy.contains(".t--datasource-name", datasourceName).click({ force: true }); cy.contains(".t--datasource-name", datasourceName).click({ force: true });

View File

@ -38,7 +38,7 @@
"Redshift": ".t--plugin-name:contains('Redshift')", "Redshift": ".t--plugin-name:contains('Redshift')",
"AmazonS3": ".t--plugin-name:contains('S3')", "AmazonS3": ".t--plugin-name:contains('S3')",
"authType": "[data-cy=authType]", "authType": "[data-cy=authType]",
"OAuth2": "//div[contains(@class,'option') and text()='OAuth 2.0']", "OAuth2": ".t--dropdown-option:contains('OAuth 2.0')",
"accessTokenUrl": "[data-cy='authentication.accessTokenUrl'] input", "accessTokenUrl": "[data-cy='authentication.accessTokenUrl'] input",
"clienID": "[data-cy='authentication.clientId'] input", "clienID": "[data-cy='authentication.clientId'] input",
"clientSecret": "[data-cy='authentication.clientSecret'] input", "clientSecret": "[data-cy='authentication.clientSecret'] input",
@ -47,7 +47,7 @@
"serviceAccCredential": "[data-cy='datasourceConfiguration.authentication.password'] input", "serviceAccCredential": "[data-cy='datasourceConfiguration.authentication.password'] input",
"grantType": "[data-cy='authentication.grantType']", "grantType": "[data-cy='authentication.grantType']",
"authorizationURL":"[data-cy='authentication.authorizationUrl'] input", "authorizationURL":"[data-cy='authentication.authorizationUrl'] input",
"authorisecode": "//div[contains(@class,'option') and text()='Authorization Code']", "authorisecode": ".t--dropdown-option:contains('Authorization Code')",
"saveAndAuthorize": "button:contains('Save and Authorize')", "saveAndAuthorize": "button:contains('Save and Authorize')",
"basic": "//div[contains(@class,'option') and text()='Basic']", "basic": "//div[contains(@class,'option') and text()='Basic']",
"basicUsername": "input[name='authentication.username']", "basicUsername": "input[name='authentication.username']",

View File

@ -52,12 +52,12 @@
"actionlist": ".action div div", "actionlist": ".action div div",
"settings": "li:contains('Settings')", "settings": "li:contains('Settings')",
"headers": "li:contains('Headers')", "headers": "li:contains('Headers')",
"onPageLoad": "[data-cy=executeOnLoad]", "onPageLoad": "[name=executeOnLoad]",
"renameEntity": ".single-select >div:contains('Edit Name')", "renameEntity": ".single-select >div:contains('Edit Name')",
"paramsTab": "//li//span[text()='Params']", "paramsTab": "//li//span[text()='Params']",
"paramKey": ".t--actionConfiguration\\.queryParameters\\[0\\]\\.key\\.0", "paramKey": ".t--actionConfiguration\\.queryParameters\\[0\\]\\.key\\.0",
"paramValue": ".t--actionConfiguration\\.queryParameters\\[0\\]\\.value\\.0", "paramValue": ".t--actionConfiguration\\.queryParameters\\[0\\]\\.value\\.0",
"multipartTypeDropdown":"button:contains('Type')", "multipartTypeDropdown":"button:contains('Type')",
"confirmBeforeExecute": "[data-cy=confirmBeforeExecute]", "confirmBeforeExecute": "[name=confirmBeforeExecute]",
"runQueryButton": ".t--apiFormRunBtn" "runQueryButton": ".t--apiFormRunBtn"
} }

View File

@ -390,9 +390,9 @@ Cypress.Commands.add(
"addOauthAuthDetails", "addOauthAuthDetails",
(accessTokenUrl, clientId, clientSecret, authURL) => { (accessTokenUrl, clientId, clientSecret, authURL) => {
cy.get(datasource.authType).click(); cy.get(datasource.authType).click();
cy.xpath(datasource.OAuth2).click(); cy.get(datasource.OAuth2).click();
cy.get(datasource.grantType).click(); cy.get(datasource.grantType).click();
cy.xpath(datasource.authorisecode).click(); cy.get(datasource.authorisecode).click();
cy.get(datasource.accessTokenUrl).type(accessTokenUrl); cy.get(datasource.accessTokenUrl).type(accessTokenUrl);
cy.get(datasource.clienID).type(clientId); cy.get(datasource.clienID).type(clientId);
cy.get(datasource.clientSecret).type(clientSecret); cy.get(datasource.clientSecret).type(clientSecret);
@ -3369,16 +3369,18 @@ Cypress.Commands.add(
"validateNSelectDropdown", "validateNSelectDropdown",
(ddTitle, currentValue, newValue) => { (ddTitle, currentValue, newValue) => {
let toChange = false; let toChange = false;
cy.xpath('//div[contains(text(),"' + currentValue + '")]').should( cy.xpath('//span[contains(text(),"' + currentValue + '")]').should(
"exist", "exist",
currentValue + " dropdown value not present", currentValue + " dropdown value not present",
); );
if (newValue) toChange = true; if (newValue) toChange = true;
if (toChange) { if (toChange) {
cy.xpath( cy.xpath(
"//p[text()='" + ddTitle + "']/following-sibling::div/div", "//p[text()='" +
ddTitle +
"']/parent::label/following-sibling::div/div/div",
).click(); //to expand the dropdown ).click(); //to expand the dropdown
cy.xpath('//div[contains(text(),"' + newValue + '")]') cy.xpath('//span[contains(text(),"' + newValue + '")]')
.last() .last()
.click({ force: true }); //to select the new value .click({ force: true }); //to select the new value
} }
@ -3387,19 +3389,17 @@ Cypress.Commands.add(
Cypress.Commands.add("typeValueNValidate", (valueToType, fieldName = "") => { Cypress.Commands.add("typeValueNValidate", (valueToType, fieldName = "") => {
if (fieldName) { if (fieldName) {
cy.xpath("//p[text()='" + fieldName + "']/following-sibling::div").then( cy.xpath(
($field) => { "//p[text()='" + fieldName + "']/parent::label/following-sibling::div",
cy.updateCodeInput($field, valueToType); ).then(($field) => {
}, cy.updateCodeInput($field, valueToType);
); });
} else { } else {
cy.xpath("//div[@class='CodeEditorTarget']").then(($field) => { cy.xpath("//div[@class='CodeEditorTarget']").then(($field) => {
cy.updateCodeInput($field, valueToType); cy.updateCodeInput($field, valueToType);
}); });
} }
cy.EvaluateCurrentValue(valueToType); cy.EvaluateCurrentValue(valueToType);
// cy.xpath("//p[text()='" + fieldName + "']/following-sibling::div//div[@class='CodeMirror-code']//span/span").should((fieldValue) => { // cy.xpath("//p[text()='" + fieldName + "']/following-sibling::div//div[@class='CodeMirror-code']//span/span").should((fieldValue) => {
// textF = fieldValue.innerText // textF = fieldValue.innerText
// fieldValue.innerText = "" // fieldValue.innerText = ""
@ -3446,26 +3446,22 @@ Cypress.Commands.add(
(fieldName = "", currentValue = "") => { (fieldName = "", currentValue = "") => {
let toValidate = false; let toValidate = false;
if (currentValue) toValidate = true; if (currentValue) toValidate = true;
if (fieldName) { if (fieldName) {
cy.xpath( cy.xpath(
"//p[text()='" + "//p[text()='" +
fieldName + fieldName +
"']/following-sibling::div//div[@class='CodeMirror-code']", "']/parent::label/following-sibling::div//div[@class='CodeMirror-code']",
).click(); ).click();
} else { } else {
cy.xpath("//div[@class='CodeMirror-code']").click(); cy.xpath("//div[@class='CodeMirror-code']").click();
} }
cy.wait(2000); cy.wait(2000);
const val = cy const val = cy
.get(commonlocators.evaluatedCurrentValue) .get(commonlocators.evaluatedCurrentValue)
.first() .first()
.should("be.visible") .should("be.visible")
.invoke("text"); .invoke("text");
if (toValidate) expect(val).to.eq(currentValue); if (toValidate) expect(val).to.eq(currentValue);
return val; return val;
}, },
); );

View File

@ -0,0 +1,3 @@
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M10 18.5C14.4183 18.5 18 14.9183 18 10.5C18 6.08172 14.4183 2.5 10 2.5C5.58172 2.5 2 6.08172 2 10.5C2 14.9183 5.58172 18.5 10 18.5ZM10.4459 11.9234V11.6155C10.4459 11.0807 10.6459 10.8052 11.3541 10.3893C12.0892 9.95172 12.5 9.37373 12.5 8.54727C12.5 7.35348 11.5108 6.5 10.0297 6.5C8.42432 6.5 7.54324 7.4291 7.5 8.70932H8.77027C8.81351 8.0503 9.26216 7.62897 9.95946 7.62897C10.6405 7.62897 11.0946 8.0341 11.0946 8.59588C11.0946 9.13066 10.8676 9.41695 10.1973 9.82208C9.45135 10.2596 9.13784 10.7458 9.18649 11.556L9.19189 11.9234H10.4459ZM9.87838 14.5C10.4297 14.5 10.7757 14.1597 10.7757 13.6411C10.7757 13.1172 10.4297 12.7768 9.87838 12.7768C9.33784 12.7768 8.98108 13.1172 8.98108 13.6411C8.98108 14.1597 9.33784 14.5 9.87838 14.5Z" fill="#939090"/>
</svg>

After

Width:  |  Height:  |  Size: 911 B

View File

@ -12,9 +12,10 @@ export type CheckboxProps = CommonComponentProps & {
info?: string; info?: string;
backgroundColor?: string; backgroundColor?: string;
fill?: boolean; fill?: boolean;
name?: string;
}; };
const Checkmark = styled.span<{ export const Checkmark = styled.span<{
disabled?: boolean; disabled?: boolean;
isChecked?: boolean; isChecked?: boolean;
info?: string; info?: string;
@ -137,6 +138,7 @@ function Checkbox(props: CheckboxProps) {
<input <input
checked={checked} checked={checked}
disabled={props.disabled} disabled={props.disabled}
name={props?.name}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
onChangeHandler(e.target.checked) onChangeHandler(e.target.checked)
} }

View File

@ -14,6 +14,8 @@ import styled from "constants/DefaultTheme";
import SearchComponent from "components/designSystems/appsmith/SearchComponent"; import SearchComponent from "components/designSystems/appsmith/SearchComponent";
import { Colors } from "constants/Colors"; import { Colors } from "constants/Colors";
import Spinner from "./Spinner"; import Spinner from "./Spinner";
import { ReactComponent as Check } from "assets/icons/control/checkmark.svg";
import { ReactComponent as Close } from "assets/icons/control/remove.svg";
import { replayHighlightClass } from "globalStyles/portals"; import { replayHighlightClass } from "globalStyles/portals";
import Tooltip from "components/ads/Tooltip"; import Tooltip from "components/ads/Tooltip";
import { isEllipsisActive } from "utils/helpers"; import { isEllipsisActive } from "utils/helpers";
@ -44,7 +46,7 @@ export interface DropdownSearchProps {
export interface RenderDropdownOptionType { export interface RenderDropdownOptionType {
index?: number; index?: number;
option: DropdownOption; option: DropdownOption | DropdownOption[];
optionClickHandler?: (dropdownOption: DropdownOption) => void; optionClickHandler?: (dropdownOption: DropdownOption) => void;
isSelectedNode?: boolean; isSelectedNode?: boolean;
extraProps?: any; extraProps?: any;
@ -63,8 +65,9 @@ type RenderOption = ({
export type DropdownProps = CommonComponentProps & export type DropdownProps = CommonComponentProps &
DropdownSearchProps & { DropdownSearchProps & {
options: DropdownOption[]; options: DropdownOption[];
selected: DropdownOption; selected: DropdownOption | DropdownOption[];
onSelect?: DropdownOnSelect; onSelect?: DropdownOnSelect;
isMultiSelect?: boolean;
width?: string; width?: string;
height?: string; height?: string;
showLabelOnly?: boolean; showLabelOnly?: boolean;
@ -91,16 +94,19 @@ export type DropdownProps = CommonComponentProps &
fillOptions?: boolean; fillOptions?: boolean;
dontUsePortal?: boolean; dontUsePortal?: boolean;
hideSubText?: boolean; hideSubText?: boolean;
removeSelectedOption?: DropdownOnSelect;
boundary?: PopperBoundary; boundary?: PopperBoundary;
defaultIcon?: IconName; defaultIcon?: IconName;
truncateOption?: boolean; // enabled wrapping and adding tooltip on option item of dropdown menu truncateOption?: boolean; // enabled wrapping and adding tooltip on option item of dropdown menu
}; };
export interface DefaultDropDownValueNodeProps { export interface DefaultDropDownValueNodeProps {
selected: DropdownOption; selected: DropdownOption | DropdownOption[];
showLabelOnly?: boolean; showLabelOnly?: boolean;
isMultiSelect?: boolean;
isOpen?: boolean; isOpen?: boolean;
hasError?: boolean; hasError?: boolean;
renderNode?: RenderOption; renderNode?: RenderOption;
selectedOptionClickHandler?: (option: DropdownOption) => void;
placeholder?: string; placeholder?: string;
showDropIcon?: boolean; showDropIcon?: boolean;
optionWidth: string; optionWidth: string;
@ -108,13 +114,13 @@ export interface DefaultDropDownValueNodeProps {
} }
export interface RenderDropdownOptionType { export interface RenderDropdownOptionType {
option: DropdownOption; option: DropdownOption | DropdownOption[];
optionClickHandler?: (dropdownOption: DropdownOption) => void; optionClickHandler?: (dropdownOption: DropdownOption) => void;
} }
export const DropdownContainer = styled.div<{ width: string; height?: string }>` export const DropdownContainer = styled.div<{ width: string; height?: string }>`
width: ${(props) => props.width}; width: ${(props) => props.width};
height: ${(props) => props.height || `38px`}; min-height: ${(props) => props.height};
position: relative; position: relative;
span.bp3-popover-target { span.bp3-popover-target {
display: inline-block; display: inline-block;
@ -146,6 +152,41 @@ const DropdownTriggerWrapper = styled.div<{
} }
`; `;
const StyledCheckmark = styled(Check)`
width: 14px;
height: 14px;
position: absolute;
top: -1px;
left: -1px;
`;
const StyledClose = styled(Close)`
width: 24px;
height: 24px;
padding: 3px;
padding-right: 10px;
&:hover {
background-color: #ebebeb;
}
`;
const SquareBox = styled.div<{
backgroundColor?: string;
borderColor?: string;
}>`
width: 14px;
height: 14px;
box-sizing: border-box;
position: relative;
margin-right: 10px;
background-color: ${(props) =>
props.backgroundColor ? props.backgroundColor : "transparent"};
border: ${(props) =>
props.borderColor
? `1.8px solid ${props.borderColor}`
: "1.8px solid #A9A7A7"};
border-width: 1.8px;
`;
const Selected = styled.div<{ const Selected = styled.div<{
isOpen: boolean; isOpen: boolean;
disabled?: boolean; disabled?: boolean;
@ -154,6 +195,7 @@ const Selected = styled.div<{
hasError?: boolean; hasError?: boolean;
selected?: boolean; selected?: boolean;
isLoading?: boolean; isLoading?: boolean;
isMultiSelect?: boolean;
}>` }>`
padding: ${(props) => props.theme.spaces[2]}px padding: ${(props) => props.theme.spaces[2]}px
${(props) => props.theme.spaces[3]}px; ${(props) => props.theme.spaces[3]}px;
@ -170,7 +212,7 @@ const Selected = styled.div<{
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
width: 100%; width: 100%;
height: ${(props) => props.height}; min-height: ${(props) => props.height};
cursor: ${(props) => cursor: ${(props) =>
props.disabled || props.isLoading ? "not-allowed" : "pointer"}; props.disabled || props.isLoading ? "not-allowed" : "pointer"};
${(props) => ${(props) =>
@ -209,11 +251,11 @@ const Selected = styled.div<{
} }
&:hover { &:hover {
background: ${(props) => background: ${(props) =>
props.hasError !props.isMultiSelect
? Colors.FAIR_PINK ? props.hasError
: props.theme.colors.dropdown.hovered.bg}; ? Colors.FAIR_PINK
border: 1px solid var(--appsmith-input-focus-border-color); : props.theme.colors.dropdown.hovered.bg
} : Colors.WHITE}
`; `;
const DropdownSelect = styled.div``; const DropdownSelect = styled.div``;
@ -284,7 +326,6 @@ const OptionWrapper = styled.div<{
align-items: center; align-items: center;
min-height: 36px; min-height: 36px;
background-color: ${(props) => (props.selected ? Colors.GREEN_3 : null)}; background-color: ${(props) => (props.selected ? Colors.GREEN_3 : null)};
&&& svg { &&& svg {
rect { rect {
fill: ${(props) => props.theme.colors.dropdownIconBg}; fill: ${(props) => props.theme.colors.dropdownIconBg};
@ -384,7 +425,7 @@ const SelectedDropDownHolder = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
min-width: 0; min-width: 0;
width: 100%; max-width: 100%;
overflow: hidden; overflow: hidden;
& ${Text} { & ${Text} {
@ -403,7 +444,6 @@ const SelectedIcon = styled(Icon)`
svg { svg {
height: 18px; height: 18px;
width: 18px; width: 18px;
rect { rect {
fill: ${(props) => props.theme.colors.dropdownIconBg}; fill: ${(props) => props.theme.colors.dropdownIconBg};
rx: 0; rx: 0;
@ -479,26 +519,64 @@ function TooltipWrappedText(
function DefaultDropDownValueNode({ function DefaultDropDownValueNode({
hasError, hasError,
hideSubText, hideSubText,
isMultiSelect,
optionWidth, optionWidth,
placeholder, placeholder,
renderNode, renderNode,
selected, selected,
selectedOptionClickHandler,
showDropIcon, showDropIcon,
showLabelOnly, showLabelOnly,
}: DefaultDropDownValueNodeProps) { }: DefaultDropDownValueNodeProps) {
const LabelText = selected const LabelText =
? showLabelOnly !Array.isArray(selected) && selected
? selected.label ? showLabelOnly
: selected.value ? selected.label
: placeholder : selected.value
? placeholder : placeholder
: "Please select a option."; ? placeholder
: "Please select a option.";
function Label() { function Label() {
return hasError ? ( if (isMultiSelect && Array.isArray(selected) && selected.length) {
<ErrorLabel>{LabelText}</ErrorLabel> return (
) : ( <div style={{ display: "flex", width: "100%", flexWrap: "wrap" }}>
<Text type={TextType.P1}>{LabelText}</Text> {selected?.map((s: DropdownOption) => {
); return (
<div
key={s.value}
style={{
border: "1.2px solid #E0DEDE",
display: "flex",
alignItems: "center",
lineHeight: "19px",
margin: "2px 2px",
}}
>
<span style={{ padding: "3px" }}>
<Text type={TextType.P1}>{s.label}</Text>
</span>
<StyledClose
onClick={(event: any) => {
event.stopPropagation();
if (selectedOptionClickHandler) {
selectedOptionClickHandler(s as DropdownOption);
}
}}
/>
</div>
);
})}
</div>
);
} else
return hasError ? (
<ErrorLabel>{LabelText}</ErrorLabel>
) : (
<span style={{ width: "100%" }}>
<Text type={TextType.P1}>{LabelText}</Text>
</span>
);
} }
return ( return (
@ -510,29 +588,33 @@ function DefaultDropDownValueNode({
hasError, hasError,
optionWidth, optionWidth,
}) })
) : isMultiSelect && Array.isArray(selected) && selected.length ? (
<Label />
) : ( ) : (
<> !Array.isArray(selected) && (
{selected?.icon ? ( <>
<SelectedIcon {selected?.icon ? (
fillColor={hasError ? Colors.POMEGRANATE2 : selected?.iconColor} <SelectedIcon
hoverFillColor={ fillColor={hasError ? Colors.POMEGRANATE2 : selected?.iconColor}
hasError ? Colors.POMEGRANATE2 : selected?.iconColor hoverFillColor={
} hasError ? Colors.POMEGRANATE2 : selected?.iconColor
name={selected.icon} }
size={selected.iconSize || IconSize.XL} name={selected.icon}
/> size={selected.iconSize || IconSize.XL}
) : null} />
<Label /> ) : null}
{selected?.subText && !hideSubText ? ( <Label />
<StyledSubText {selected?.subText && !hideSubText ? (
className="sub-text" <StyledSubText
showDropIcon={showDropIcon} className="sub-text"
type={TextType.P1} showDropIcon={showDropIcon}
> type={TextType.P1}
{selected.subText} >
</StyledSubText> {selected.subText}
) : null} </StyledSubText>
</> ) : null}
</>
)
)} )}
</SelectedDropDownHolder> </SelectedDropDownHolder>
); );
@ -542,8 +624,9 @@ interface DropdownOptionsProps extends DropdownProps, DropdownSearchProps {
optionClickHandler: (option: DropdownOption) => void; optionClickHandler: (option: DropdownOption) => void;
renderOption?: RenderOption; renderOption?: RenderOption;
headerLabel?: string; headerLabel?: string;
selected: DropdownOption; selected: DropdownOption | DropdownOption[];
optionWidth: string; optionWidth: string;
isMultiSelect?: boolean;
} }
export function RenderDropdownOptions(props: DropdownOptionsProps) { export function RenderDropdownOptions(props: DropdownOptionsProps) {
@ -594,12 +677,25 @@ export function RenderDropdownOptions(props: DropdownOptionsProps) {
optionWidth, optionWidth,
}); });
} }
let isSelected = false;
if (
props.isMultiSelect &&
Array.isArray(props.selected) &&
props.selected.length
) {
isSelected = !!props.selected.find(
(selectedOption) => selectedOption.value === option.value,
);
} else {
isSelected =
(props.selected as DropdownOption).value === option.value;
}
return !option.isSectionHeader ? ( return !option.isSectionHeader ? (
<OptionWrapper <OptionWrapper
className="t--dropdown-option" className="t--dropdown-option"
key={index} key={index}
onClick={() => props.optionClickHandler(option)} onClick={() => props.optionClickHandler(option)}
selected={props.selected.value === option.value} selected={isSelected}
> >
{option.leftElement && ( {option.leftElement && (
<LeftIconWrapper>{option.leftElement}</LeftIconWrapper> <LeftIconWrapper>{option.leftElement}</LeftIconWrapper>
@ -612,7 +708,15 @@ export function RenderDropdownOptions(props: DropdownOptionsProps) {
size={option.iconSize || IconSize.XL} size={option.iconSize || IconSize.XL}
/> />
) : null} ) : null}
{props.isMultiSelect ? (
isSelected ? (
<SquareBox backgroundColor="#f86a2b" borderColor="#f86a2b">
<StyledCheckmark />
</SquareBox>
) : (
<SquareBox borderColor="#a9a7a7" />
)
) : null}
{props.showLabelOnly ? ( {props.showLabelOnly ? (
props.truncateOption ? ( props.truncateOption ? (
<TooltipWrappedText <TooltipWrappedText
@ -635,7 +739,6 @@ export function RenderDropdownOptions(props: DropdownOptionsProps) {
) : ( ) : (
<Text type={TextType.P1}>{option.value}</Text> <Text type={TextType.P1}>{option.value}</Text>
)} )}
{option.subText ? ( {option.subText ? (
<StyledSubText type={TextType.P3}> <StyledSubText type={TextType.P3}>
{option.subText} {option.subText}
@ -664,10 +767,13 @@ export default function Dropdown(props: DropdownProps) {
errorMsg = "", errorMsg = "",
placeholder, placeholder,
helperText, helperText,
removeSelectedOption,
hasError, hasError,
} = { ...props }; } = { ...props };
const [isOpen, setIsOpen] = useState<boolean>(false); const [isOpen, setIsOpen] = useState<boolean>(false);
const [selected, setSelected] = useState<DropdownOption>(props.selected); const [selected, setSelected] = useState<DropdownOption | DropdownOption[]>(
props.selected,
);
const closeIfOpen = () => { const closeIfOpen = () => {
if (isOpen) { if (isOpen) {
@ -682,7 +788,21 @@ export default function Dropdown(props: DropdownProps) {
const optionClickHandler = useCallback( const optionClickHandler = useCallback(
(option: DropdownOption) => { (option: DropdownOption) => {
setSelected(option); if (props.isMultiSelect) {
// Multi select -> typeof selected is array of objects
if (!selected) {
setSelected([option]);
} else {
const newOptions: DropdownOption[] = [
...(selected as DropdownOption[]),
option,
];
setSelected(newOptions);
}
} else {
// Single select -> typeof selected is object
setSelected(option);
}
setIsOpen(false); setIsOpen(false);
onSelect && onSelect(option.value, option); onSelect && onSelect(option.value, option);
option.onSelect && option.onSelect(option.value, option); option.onSelect && option.onSelect(option.value, option);
@ -690,6 +810,20 @@ export default function Dropdown(props: DropdownProps) {
[onSelect], [onSelect],
); );
//Removes selected option
const selectedOptionClickHandler = useCallback(
(optionToBeRemoved: DropdownOption) => {
setIsOpen(false);
const selectedOptions = (selected as DropdownOption[]).filter(
(option: DropdownOption) => option.value !== optionToBeRemoved.value,
);
setSelected(selectedOptions);
removeSelectedOption &&
removeSelectedOption(optionToBeRemoved.value, optionToBeRemoved);
},
[removeSelectedOption],
);
const errorFlag = hasError || errorMsg.length > 0; const errorFlag = hasError || errorMsg.length > 0;
const disabled = props.disabled || isLoading; const disabled = props.disabled || isLoading;
const downIconColor = errorFlag ? Colors.POMEGRANATE2 : Colors.DARK_GRAY; const downIconColor = errorFlag ? Colors.POMEGRANATE2 : Colors.DARK_GRAY;
@ -734,7 +868,8 @@ export default function Dropdown(props: DropdownProps) {
className={props.className} className={props.className}
disabled={props.disabled} disabled={props.disabled}
hasError={errorFlag} hasError={errorFlag}
height={props.height || "38px"} height={props.height || getMinHeight(props.isMultiSelect)}
isMultiSelect={props.isMultiSelect}
isOpen={isOpen} isOpen={isOpen}
onClick={() => setIsOpen(!isOpen)} onClick={() => setIsOpen(!isOpen)}
selected={!!selected} selected={!!selected}
@ -742,14 +877,15 @@ export default function Dropdown(props: DropdownProps) {
<SelectedValueNode <SelectedValueNode
hasError={errorFlag} hasError={errorFlag}
hideSubText={props.hideSubText} hideSubText={props.hideSubText}
isMultiSelect={props.isMultiSelect}
optionWidth={dropdownOptionWidth} optionWidth={dropdownOptionWidth}
placeholder={placeholder} placeholder={placeholder}
renderNode={renderOption} renderNode={renderOption}
selected={selected} selected={selected}
selectedOptionClickHandler={selectedOptionClickHandler}
showDropIcon={showDropIcon} showDropIcon={showDropIcon}
showLabelOnly={props.showLabelOnly} showLabelOnly={props.showLabelOnly}
/> />
{}
{isLoading ? ( {isLoading ? (
<Spinner size={IconSize.LARGE} /> <Spinner size={IconSize.LARGE} />
) : ( ) : (
@ -776,7 +912,7 @@ export default function Dropdown(props: DropdownProps) {
<DropdownContainer <DropdownContainer
className={props.containerClassName + " " + replayHighlightClass} className={props.containerClassName + " " + replayHighlightClass}
data-cy={props.cypressSelector} data-cy={props.cypressSelector}
height={props.height || "36px"} height={getMinHeight(props.isMultiSelect)}
tabIndex={0} tabIndex={0}
width={dropdownWidth} width={dropdownWidth}
> >
@ -793,6 +929,7 @@ export default function Dropdown(props: DropdownProps) {
{dropdownTrigger} {dropdownTrigger}
<RenderDropdownOptions <RenderDropdownOptions
{...props} {...props}
isMultiSelect={props.isMultiSelect}
optionClickHandler={optionClickHandler} optionClickHandler={optionClickHandler}
optionWidth={dropdownOptionWidth} optionWidth={dropdownOptionWidth}
selected={ selected={
@ -805,3 +942,8 @@ export default function Dropdown(props: DropdownProps) {
</DropdownContainer> </DropdownContainer>
); );
} }
function getMinHeight(isMultiSelect?: boolean): string {
if (isMultiSelect) return "44px";
return "38px";
}

View File

@ -316,7 +316,7 @@ const TextInput = forwardRef(
setInputValue(inputValue); setInputValue(inputValue);
const inputValueValidation = const inputValueValidation =
props.validator && props.validator(inputValue); props.validator && props.validator(inputValue);
if (inputValueValidation) { if (inputValueValidation && inputValueValidation.isValid) {
props.validator && setValidation(inputValueValidation); props.validator && setValidation(inputValueValidation);
return ( return (
inputValueValidation.isValid && inputValueValidation.isValid &&
@ -410,6 +410,7 @@ const TextInput = forwardRef(
data-cy={props.cypressSelector} data-cy={props.cypressSelector}
hasLeftIcon={hasLeftIcon} hasLeftIcon={hasLeftIcon}
inputRef={ref} inputRef={ref}
name={props?.name}
onBlur={onBlurHandler} onBlur={onBlurHandler}
onChange={memoizedChangeHandler} onChange={memoizedChangeHandler}
onFocus={onFocusHandler} onFocus={onFocusHandler}

View File

@ -7,6 +7,7 @@ export interface CommonComponentProps {
isLoading?: boolean; //default false isLoading?: boolean; //default false
cypressSelector?: string; cypressSelector?: string;
className?: string; className?: string;
name?: string;
disabled?: boolean; //default false disabled?: boolean; //default false
} }

View File

@ -61,7 +61,7 @@ export const EditorWrapper = styled.div<{
top: 0; top: 0;
` `
: `position: relative;`} : `position: relative;`}
min-height: 35px; min-height: 38px;
height: ${(props) => props.height || "auto"}; height: ${(props) => props.height || "auto"};
background-color: ${(props) => editorBackground(props.editorTheme)}; background-color: ${(props) => editorBackground(props.editorTheme)};
background-color: ${(props) => props.disabled && "#eef2f5"}; background-color: ${(props) => props.disabled && "#eef2f5"};
@ -260,7 +260,7 @@ export const EditorWrapper = styled.div<{
${(props) => { ${(props) => {
let height = props.height || "auto"; let height = props.height || "auto";
if (props.size === EditorSize.COMPACT && !props.isFocused) { if (props.size === EditorSize.COMPACT && !props.isFocused) {
height = props.height || "35px"; height = props.height || "38px";
} }
return `height: ${height}`; return `height: ${height}`;
}} }}

View File

@ -0,0 +1,147 @@
import React from "react";
import styled from "styled-components";
import { Colors } from "constants/Colors";
import { ControlProps } from "components/formControls/BaseControl";
//Styled help text, intended to be used with Form Fields
export const StyledFormInfo = styled.span<{ config?: ControlProps }>`
display: ${(props) =>
//SWITCH and CHECKBOX display label text and form input aligned side by side
props?.config?.controlType !== "SWITCH" &&
props?.config?.controlType !== "CHECKBOX"
? "block;"
: "inline;"}
font-weight: normal;
color: ${Colors.DOVE_GRAY};
font-size: 12px;
margin-left: 1px;
margin-top: 5px;
margin-bottom: 8px;
`;
const FormSubtitleText = styled.span<{ config?: ControlProps }>`
display: ${(props) =>
//SWITCH and CHECKBOX display label text and form input aligned side by side
props?.config?.controlType !== "SWITCH" &&
props?.config?.controlType !== "CHECKBOX"
? "block;"
: "inline;"}
font-weight: normal;
color: ${Colors.DOVE_GRAY};
font-size: 12px;
`;
//Styled help text, intended to be used with Form Fields
const FormInputHelperText = styled.p`
color: #858282;
font-style: normal;
font-weight: normal;
font-size: 12px;
line-height: 16px;
letter-spacing: -0.221538px;
margin: 0px;
`;
//Styled error text, intended to be used with Form Fields
const FormInputErrorText = styled.p`
font-style: normal;
font-weight: normal;
font-size: 12px;
line-height: 16px;
letter-spacing: -0.221538px;
color: #f22b2b;
margin: 8px 0 0 0;
`;
//Styled anchor tag, intended to be used with Form Fields
const FormInputAnchor = styled.a`
display: block;
font-weight: 500;
font-size: 12px;
line-height: 14px;
letter-spacing: 0.8px;
color: #6a86ce;
margin: 0 0 8px 0;
text-transform: uppercase;
`;
const FormInputSwitchToJsonButton = styled.button`
font-weight: 500;
font-size: 12px;
line-height: 14px;
letter-spacing: 0.8px;
text-transform: uppercase;
color: #6a86ce;
margin: 0 0 8px 0;
border: none;
padding-left: 0px;
display: block;
cursor: pointer;
background-color: #fff;
`;
//Styled form label tag, intended to be used with Form Fields
const StyledFormLabel = styled.label<{ config?: ControlProps }>`
display: inline-block;
// TODO: replace condition with props.config?.dataType === "TOGGLE"
// required for large texts in CHECKBOX and SWITCH
width: ${(props) => props.config?.customStyles?.width || "auto;"}
min-width: ${(props) =>
props.config?.controlType === "SWITCH" ||
props.config?.controlType === "CHECKBOX"
? "auto;"
: "50vh;"}
margin-left: ${(props) =>
// margin required for CHECKBOX
props.config?.controlType === "CHECKBOX" ? "0px;" : "16px;"}
font-weight: 400;
font-size: 14px;
line-height: 16px;
letter-spacing: 0.02em;
color: ${Colors.CHARCOAL};
margin-bottom: ${(props) =>
props.config?.controlType === "CHECKBOX" ? "0px;" : "8px;"}
&:first-child {
margin-left: 0px;
}
p {
display: flex;
}
.label-icon-wrapper {
margin-bottom: 0px;
display: flex;
align-items: center;
}
.label-icon-wrapper svg path {
fill: #939090;
}
`;
interface FormLabelProps {
config?: ControlProps;
children: JSX.Element | React.ReactNode;
}
//Wrapper on styled <label/>
function FormLabel(props: FormLabelProps) {
return (
<StyledFormLabel config={props.config}>{props.children}</StyledFormLabel>
);
}
//Wrapper on styled <span/>
function FormInfoText(props: FormLabelProps) {
return (
<StyledFormInfo config={props.config}>{props.children}</StyledFormInfo>
);
}
export {
FormInputSwitchToJsonButton,
FormLabel,
FormInputAnchor,
FormInputErrorText,
FormInputHelperText,
FormInfoText,
FormSubtitleText,
};

View File

@ -41,6 +41,8 @@ export interface ControlProps extends ControlData, ControlFunctions {
export interface ControlData { export interface ControlData {
id: string; id: string;
label: string; label: string;
displayType?: "UI" | "JSON"; //used for switch to JSON view
tooltipText?: string;
configProperty: string; configProperty: string;
controlType: ControlType; controlType: ControlType;
propertyValue?: any; propertyValue?: any;
@ -48,11 +50,19 @@ export interface ControlData {
validationMessage?: string; validationMessage?: string;
validationRegex?: string; validationRegex?: string;
dataType?: InputType; dataType?: InputType;
initialValue?: string | boolean | number;
info?: string; //helper text
isRequired?: boolean; isRequired?: boolean;
conditionals: string; conditionals: string;
hidden?: HiddenType; hidden?: HiddenType;
placeholderText?: string; placeholderText?: string;
schema?: any; schema?: any;
errorText?: string;
showError?: boolean;
encrypted?: boolean;
subtitle?: string;
url?: string;
urlText?: string;
logicalTypes?: string[]; logicalTypes?: string[];
comparisonTypes?: string[]; comparisonTypes?: string[];
nestedLevels?: number; nestedLevels?: number;

View File

@ -1,28 +1,53 @@
import React from "react"; import React from "react";
import CheckboxField from "components/editorComponents/form/fields/CheckboxField"; import Checkbox from "components/ads/Checkbox";
import BaseControl, { ControlProps } from "./BaseControl"; import BaseControl, { ControlProps } from "./BaseControl";
import { ControlType } from "constants/PropertyControlConstants"; import { ControlType } from "constants/PropertyControlConstants";
import {
Field,
WrappedFieldInputProps,
WrappedFieldMetaProps,
} from "redux-form";
import styled from "styled-components"; import styled from "styled-components";
const StyledCheckbox = styled(CheckboxField)` const StyledCheckbox = styled(Checkbox)``;
&&& {
font-size: 14px;
margin-top: 10px;
}
`;
class CheckboxControl extends BaseControl<CheckboxControlProps> { class CheckboxControl extends BaseControl<CheckboxControlProps> {
getControlType(): ControlType { getControlType(): ControlType {
return "CHECKBOX"; return "CHECKBOX";
} }
render() { render() {
const { configProperty, info, label } = this.props; return (
<Field
return <StyledCheckbox info={info} label={label} name={configProperty} />; component={renderComponent}
name={this.props.configProperty}
props={{ ...this.props }}
type="checkbox"
/>
);
} }
} }
type renderComponentProps = CheckboxControlProps & {
input?: WrappedFieldInputProps;
meta?: WrappedFieldMetaProps;
};
function renderComponent(props: renderComponentProps) {
const onChangeHandler = (value: boolean) => {
props.input && props.input.onChange && props.input.onChange(value);
};
return (
<StyledCheckbox
isDefaultChecked={props?.input?.checked as boolean}
{...props}
info={undefined}
label={""}
name={props?.input?.name}
onCheckChange={onChangeHandler}
/>
);
}
export interface CheckboxControlProps extends ControlProps { export interface CheckboxControlProps extends ControlProps {
info?: string; info?: string;
} }

View File

@ -1,126 +1,84 @@
import React from "react"; import React from "react";
import BaseControl, { ControlProps } from "./BaseControl"; import BaseControl, { ControlProps } from "./BaseControl";
import styled from "styled-components"; import styled from "styled-components";
import { MenuItem } from "@blueprintjs/core"; import Dropdown, { DropdownOption } from "components/ads/Dropdown";
import { IItemRendererProps } from "@blueprintjs/select";
import DropdownField from "components/editorComponents/form/fields/DropdownField";
import { DropdownOption } from "components/constants";
import { ControlType } from "constants/PropertyControlConstants"; import { ControlType } from "constants/PropertyControlConstants";
import { theme } from "constants/DefaultTheme"; import _ from "lodash";
import FormLabel from "components/editorComponents/FormLabel"; import {
import { Colors } from "constants/Colors"; Field,
WrappedFieldInputProps,
WrappedFieldMetaProps,
} from "redux-form";
const DropdownSelect = styled.div` const DropdownSelect = styled.div`
font-size: 14px; font-size: 14px;
width: 50vh; width: 50vh;
`; `;
const StyledInfo = styled.span`
font-weight: normal;
line-height: normal;
color: ${Colors.DOVE_GRAY};
font-size: 12px;
margin-left: 1px;
`;
const customSelectStyles = {
option: (
styles: { [x: string]: any },
{ isDisabled, isFocused, isSelected }: any,
) => {
return {
...styles,
color: Colors.CODE_GRAY,
backgroundColor: isDisabled
? undefined
: isSelected
? Colors.GREY_3
: isFocused
? Colors.GREY_2
: undefined,
":active": {
...styles[":active"],
backgroundColor:
!isDisabled &&
(isSelected ? theme.colors.primaryOld : theme.colors.hover),
},
};
},
};
class DropDownControl extends BaseControl<DropDownControlProps> { class DropDownControl extends BaseControl<DropDownControlProps> {
render() { render() {
const {
configProperty,
customStyles,
isDisabled,
isRequired,
isSearchable,
label,
options,
subtitle,
} = this.props;
let width = "50vh"; let width = "50vh";
if (customStyles && customStyles.width) { if (this.props.customStyles && this.props?.customStyles?.width) {
width = customStyles.width; width = this.props?.customStyles?.width;
} }
return ( return (
<div> <DropdownSelect data-cy={this.props.configProperty} style={{ width }}>
<FormLabel> <Field
{label} {isRequired && "*"} component={renderDropdown}
{subtitle && ( name={this.props.configProperty}
<> options={this.props.options}
<br /> props={{ ...this.props, width }}
<StyledInfo>{subtitle}</StyledInfo> type={this.props?.isMultiSelect ? "select-multiple" : undefined}
</> />
)} </DropdownSelect>
</FormLabel>
<DropdownSelect data-cy={configProperty} style={{ width }}>
<DropdownField
customSelectStyles={customSelectStyles}
isDisabled={isDisabled}
isSearchable={isSearchable}
name={configProperty}
options={options}
placeholder=""
width={width}
/>
</DropdownSelect>
</div>
); );
} }
renderItem = (option: DropdownOption, itemProps: IItemRendererProps) => {
if (!itemProps.modifiers.matchesPredicate) {
return null;
}
const isSelected: boolean = this.isOptionSelected(option);
return (
<MenuItem
active={isSelected}
className="single-select"
key={option.value}
onClick={itemProps.handleClick}
text={option.label}
/>
);
};
isOptionSelected = (selectedOption: DropdownOption) => {
return selectedOption.value === this.props.propertyValue;
};
getControlType(): ControlType { getControlType(): ControlType {
return "DROP_DOWN"; return "DROP_DOWN";
} }
} }
function renderDropdown(props: {
input?: WrappedFieldInputProps;
meta?: WrappedFieldMetaProps;
props: DropDownControlProps & { width?: string };
options: { label: string; value: string }[];
}): JSX.Element {
let selectedValue = props.input?.value;
if (_.isUndefined(props.input?.value)) {
selectedValue = props?.props?.initialValue;
}
const selectedOption =
props?.options.find(
(option: DropdownOption) => option.value === selectedValue,
) || {};
return (
<Dropdown
boundary="window"
dontUsePortal={false}
dropdownMaxHeight="250px"
errorMsg={props.props?.errorText}
helperText={props.props?.info}
isMultiSelect={props?.props?.isMultiSelect}
onSelect={props.input?.onChange}
optionWidth="50vh"
options={props.options}
placeholder={props.props?.placeholderText}
selected={selectedOption}
showLabelOnly
width={props?.props?.width ? props?.props?.width : "50vh"}
/>
);
}
export interface DropDownControlProps extends ControlProps { export interface DropDownControlProps extends ControlProps {
options: DropdownOption[]; options: DropdownOption[];
placeholderText: string; placeholderText: string;
propertyValue: string; propertyValue: string;
subtitle?: string; subtitle?: string;
isMultiSelect?: boolean;
isDisabled?: boolean; isDisabled?: boolean;
isSearchable?: boolean; isSearchable?: boolean;
} }

View File

@ -1,7 +1,6 @@
import React from "react"; import React from "react";
import BaseControl, { ControlProps } from "./BaseControl"; import BaseControl, { ControlProps } from "./BaseControl";
import { ControlType } from "constants/PropertyControlConstants"; import { ControlType } from "constants/PropertyControlConstants";
import FormLabel from "components/editorComponents/FormLabel";
import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField"; import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField";
import { AppState } from "reducers"; import { AppState } from "reducers";
import { formValueSelector } from "redux-form"; import { formValueSelector } from "redux-form";
@ -45,7 +44,7 @@ export function InputText(props: {
inputType?: INPUT_TEXT_INPUT_TYPES; inputType?: INPUT_TEXT_INPUT_TYPES;
customStyles?: any; customStyles?: any;
}) { }) {
const { actionName, inputType, isRequired, label, name, placeholder } = props; const { actionName, inputType, name, placeholder } = props;
const dataTreePath = actionPathFromName(actionName, name); const dataTreePath = actionPathFromName(actionName, name);
let editorProps = {}; let editorProps = {};
@ -57,15 +56,18 @@ export function InputText(props: {
}; };
} }
let customStyle = { width: "50vh", minHeight: "55px" }; let customStyle = { width: "50vh", minHeight: "38px" };
if (!!props.customStyles && _.isEmpty(props.customStyles) === false) { if (!!props.customStyles && _.isEmpty(props.customStyles) === false) {
customStyle = props.customStyles; customStyle = { ...props.customStyles };
if (props.customStyles?.width) {
customStyle.width = "50vh";
}
if (props.customStyles?.minHeight) {
customStyle.minHeight = "34px";
}
} }
return ( return (
<div style={customStyle}> <div style={customStyle}>
<FormLabel>
{label} {isRequired && "*"}
</FormLabel>
<StyledDynamicTextField <StyledDynamicTextField
dataTreePath={dataTreePath} dataTreePath={dataTreePath}
name={name} name={name}

View File

@ -3,7 +3,6 @@ import { formValueSelector, change } from "redux-form";
import { connect } from "react-redux"; import { connect } from "react-redux";
import BaseControl, { ControlProps } from "./BaseControl"; import BaseControl, { ControlProps } from "./BaseControl";
import { ControlType } from "constants/PropertyControlConstants"; import { ControlType } from "constants/PropertyControlConstants";
import FormLabel from "components/editorComponents/FormLabel";
import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField"; import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField";
import { import {
EditorSize, EditorSize,
@ -64,7 +63,6 @@ class DynamicTextControl extends BaseControl<
actionName, actionName,
configProperty, configProperty,
evaluationSubstitutionType, evaluationSubstitutionType,
label,
placeholderText, placeholderText,
responseType, responseType,
} = this.props; } = this.props;
@ -81,7 +79,6 @@ class DynamicTextControl extends BaseControl<
return ( return (
<Wrapper> <Wrapper>
<FormLabel>{label}</FormLabel>
{showTemplate ? ( {showTemplate ? (
<TemplateMenu <TemplateMenu
createTemplate={(templateString) => { createTemplate={(templateString) => {

View File

@ -5,7 +5,6 @@ import Icon, { IconSize } from "components/ads/Icon";
import { Classes } from "components/ads/common"; import { Classes } from "components/ads/common";
import styled from "styled-components"; import styled from "styled-components";
import { FieldArray } from "redux-form"; import { FieldArray } from "redux-form";
import FormLabel from "components/editorComponents/FormLabel";
import { ControlProps } from "./BaseControl"; import { ControlProps } from "./BaseControl";
const CenteredIcon = styled(Icon)` const CenteredIcon = styled(Icon)`
@ -105,17 +104,14 @@ function NestedComponents(props: any) {
} }
export default function FieldArrayControl(props: FieldArrayControlProps) { export default function FieldArrayControl(props: FieldArrayControlProps) {
const { configProperty, formName, label, schema } = props; const { configProperty, formName, schema } = props;
return ( return (
<> <FieldArray
<FormLabel>{label}</FormLabel> component={NestedComponents}
<FieldArray name={configProperty}
component={NestedComponents} props={{ formName, schema }}
name={configProperty} rerenderOnEveryChange={false}
props={{ formName, schema }} />
rerenderOnEveryChange={false}
/>
</>
); );
} }

View File

@ -1,18 +1,20 @@
import * as React from "react"; import * as React from "react";
import { WrappedFieldProps } from "redux-form"; import { useState } from "react";
import "@uppy/core/dist/style.css";
import "@uppy/dashboard/dist/style.css";
import "@uppy/webcam/dist/style.css";
import { Field } from "redux-form";
import styled from "styled-components"; import styled from "styled-components";
import Uppy from "@uppy/core";
import Dashboard from "@uppy/dashboard";
import BaseControl, { ControlProps } from "./BaseControl"; import BaseControl, { ControlProps } from "./BaseControl";
import { ControlType } from "constants/PropertyControlConstants"; import { ControlType } from "constants/PropertyControlConstants";
import FormLabel from "components/editorComponents/FormLabel";
import { BaseButton } from "components/designSystems/appsmith/BaseButton"; import { BaseButton } from "components/designSystems/appsmith/BaseButton";
import { ButtonVariantTypes } from "components/constants"; import { ButtonVariantTypes } from "components/constants";
import { Colors } from "constants/Colors"; import { Colors } from "constants/Colors";
import FilePickerV2 from "components/ads/FilePickerV2";
import { FileType, SetProgress } from "components/ads/FilePicker";
import {
Field,
WrappedFieldInputProps,
WrappedFieldMetaProps,
} from "redux-form";
import DialogComponent from "components/ads/DialogComponent";
import { useEffect, useCallback } from "react";
import { replayHighlightClass } from "globalStyles/portals"; import { replayHighlightClass } from "globalStyles/portals";
const StyledDiv = styled.div` const StyledDiv = styled.div`
@ -29,7 +31,7 @@ const SelectButton = styled(BaseButton)`
max-width: 59px; max-width: 59px;
margin: 0 0px; margin: 0 0px;
min-height: 32px; min-height: 32px;
border-radius: 0px 4px 4px 0px; border-radius: 0px;
font-weight: bold; font-weight: bold;
background-color: #fff; background-color: #fff;
border-color: ${Colors.PRIMARY_ORANGE} !important; border-color: ${Colors.PRIMARY_ORANGE} !important;
@ -48,85 +50,92 @@ const SelectButton = styled(BaseButton)`
} }
`; `;
interface FieldFileInputState { const FilePickerWrapper = styled.div`
text: string; width: 100%;
} display: flex;
align-items: center;
justify-content: center;
`;
type Props = WrappedFieldProps; type RenderFilePickerProps = FilePickerControlProps & {
input?: WrappedFieldInputProps;
meta?: WrappedFieldMetaProps;
onChange: (event: any) => void;
};
class FieldFileInput extends React.Component<Props, FieldFileInputState> { function RenderFilePicker(props: RenderFilePickerProps) {
uppy: any; const [isOpen, setIsOpen] = useState(false);
const [appFileToBeUploaded, setAppFileToBeUploaded] = useState<{
file: File;
setProgress: SetProgress;
} | null>(null);
constructor(props: Props) { const FileUploader = useCallback(
super(props); async (file: File, setProgress: SetProgress) => {
this.refreshUppy(); if (!!file) {
setAppFileToBeUploaded({
file,
setProgress,
});
} else {
setAppFileToBeUploaded(null);
}
},
[],
);
this.state = { const onRemoveFile = useCallback(() => setAppFileToBeUploaded(null), []);
text: "Select file to upload",
};
}
refreshUppy = () => { useEffect(() => {
this.uppy = Uppy({ if (appFileToBeUploaded?.file) {
id: "test",
autoProceed: false,
debug: false,
restrictions: {
maxNumberOfFiles: 1,
},
}).use(Dashboard, {
hideUploadButton: true,
});
this.uppy.on("file-added", (file: any) => {
const dslFiles = [];
const reader = new FileReader(); const reader = new FileReader();
reader.readAsDataURL(file.data); reader.readAsDataURL(appFileToBeUploaded?.file);
reader.onloadend = () => { reader.onloadend = () => {
const base64data = reader.result; const base64data = reader.result;
const newFile = { props.input?.onChange({
id: file.id, name: appFileToBeUploaded?.file.name,
base64: base64data,
blob: file.data,
};
dslFiles.push(newFile);
this.uppy.getPlugin("Dashboard").closeModal();
this.props.input.onChange({
name: file.name,
base64Content: base64data, base64Content: base64data,
}); });
}; };
}); }
}; }, [appFileToBeUploaded]);
openModal = () => { return (
// this.setState({ isOpen: true }); <>
this.uppy.getPlugin("Dashboard").openModal();
};
render() {
const {
input: { value },
} = this.props;
return (
<div <div
className={replayHighlightClass} className={replayHighlightClass}
style={{ flexDirection: "row", display: "flex", width: "50vh" }} style={{ flexDirection: "row", display: "flex", width: "50vh" }}
> >
<StyledDiv>{value.name}</StyledDiv> <StyledDiv>{props?.input?.value?.name}</StyledDiv>
<SelectButton <SelectButton
buttonStyle="PRIMARY" buttonStyle="PRIMARY"
buttonVariant={ButtonVariantTypes.SECONDARY} buttonVariant={ButtonVariantTypes.SECONDARY}
onClick={() => { onClick={() => {
this.openModal(); setIsOpen(true);
}} }}
text={"Select"} text={"Select"}
/> />
</div> </div>
); {isOpen ? (
} <DialogComponent
canOutsideClickClose
isOpen={isOpen}
maxHeight={"540px"}
setModalClose={() => setIsOpen(false)}
>
<FilePickerWrapper>
<FilePickerV2
delayedUpload
fileType={FileType.ANY}
fileUploader={FileUploader}
onFileRemoved={onRemoveFile}
/>
</FilePickerWrapper>
</DialogComponent>
) : null}
</>
);
} }
class FilePickerControl extends BaseControl<FilePickerControlProps> { class FilePickerControl extends BaseControl<FilePickerControlProps> {
constructor(props: FilePickerControlProps) { constructor(props: FilePickerControlProps) {
super(props); super(props);
@ -136,16 +145,8 @@ class FilePickerControl extends BaseControl<FilePickerControlProps> {
} }
render() { render() {
const { configProperty, isRequired, label } = this.props; const { configProperty } = this.props;
return <Field component={RenderFilePicker} name={configProperty} />;
return (
<>
<FormLabel>
{label} {isRequired && "*"}
</FormLabel>
<Field component={FieldFileInput} name={configProperty} />
</>
);
} }
getControlType(): ControlType { getControlType(): ControlType {

View File

@ -3,7 +3,6 @@ import BaseControl, { ControlProps } from "./BaseControl";
import { InputType } from "components/constants"; import { InputType } from "components/constants";
import { ControlType } from "constants/PropertyControlConstants"; import { ControlType } from "constants/PropertyControlConstants";
import TextField from "components/editorComponents/form/fields/TextField"; import TextField from "components/editorComponents/form/fields/TextField";
import FormLabel from "components/editorComponents/FormLabel";
import styled from "styled-components"; import styled from "styled-components";
const Wrapper = styled.div` const Wrapper = styled.div`
@ -12,20 +11,10 @@ const Wrapper = styled.div`
class FixKeyInputControl extends BaseControl<FixedKeyInputControlProps> { class FixKeyInputControl extends BaseControl<FixedKeyInputControlProps> {
render() { render() {
const { const { configProperty, dataType, fixedKey, placeholderText } = this.props;
configProperty,
dataType,
fixedKey,
isRequired,
label,
placeholderText,
} = this.props;
return ( return (
<Wrapper> <Wrapper>
<FormLabel>
{label} {isRequired && "*"}
</FormLabel>
<TextField <TextField
format={(value) => { format={(value) => {
// Get the value property // Get the value property

View File

@ -1,12 +1,15 @@
import React from "react"; import React from "react";
import BaseControl, { ControlProps } from "./BaseControl"; import BaseControl, { ControlProps } from "./BaseControl";
import { ControlType } from "constants/PropertyControlConstants"; import { ControlType } from "constants/PropertyControlConstants";
import TextField from "components/editorComponents/form/fields/TextField"; import TextInput from "components/ads/TextInput";
import FormLabel from "components/editorComponents/FormLabel";
import { FormIcons } from "icons/FormIcons";
import { Colors } from "constants/Colors"; import { Colors } from "constants/Colors";
import styled from "styled-components"; import styled from "styled-components";
import { InputType } from "components/constants"; import { InputType } from "components/constants";
import {
Field,
WrappedFieldMetaProps,
WrappedFieldInputProps,
} from "redux-form";
export const StyledInfo = styled.span` export const StyledInfo = styled.span`
font-weight: normal; font-weight: normal;
@ -29,51 +32,52 @@ export function InputText(props: {
encrypted?: boolean; encrypted?: boolean;
disabled?: boolean; disabled?: boolean;
}) { }) {
const { const { dataType, disabled, name, placeholder } = props;
dataType,
disabled,
encrypted,
isRequired,
label,
name,
placeholder,
subtitle,
} = props;
return ( return (
<div data-cy={name} style={{ width: "50vh" }}> <div data-cy={name} style={{ width: "50vh" }}>
<FormLabel> <Field
{label} {isRequired && "*"}{" "} component={renderComponent}
{encrypted && ( datatype={dataType}
<>
<FormIcons.LOCK_ICON height={12} keepColors width={12} />
<StyledInfo>Encrypted</StyledInfo>
</>
)}
{subtitle && (
<>
<br />
<StyledInfo>{subtitle}</StyledInfo>
</>
)}
</FormLabel>
<TextField
disabled={disabled || false} disabled={disabled || false}
name={name}
placeholder={placeholder} placeholder={placeholder}
showError {...props}
type={dataType} asyncControl
/> />
</div> </div>
); );
} }
function renderComponent(
props: {
placeholder: string;
dataType?: InputType;
disabled?: boolean;
} & {
meta: Partial<WrappedFieldMetaProps>;
input: Partial<WrappedFieldInputProps>;
},
) {
return (
<TextInput
dataType={props.dataType}
disabled={props.disabled || false}
name={props.input?.name}
onChange={props.input.onChange}
placeholder={props.placeholder}
value={props.input.value}
{...props.input}
width="100%"
/>
);
}
class InputTextControl extends BaseControl<InputControlProps> { class InputTextControl extends BaseControl<InputControlProps> {
render() { render() {
const { const {
configProperty, configProperty,
dataType, dataType,
disabled, disabled,
encrypted,
isValid, isValid,
label, label,
placeholderText, placeholderText,
@ -86,7 +90,7 @@ class InputTextControl extends BaseControl<InputControlProps> {
<InputText <InputText
dataType={this.getType(dataType)} dataType={this.getType(dataType)}
disabled={disabled} disabled={disabled}
encrypted={this.props.encrypted} encrypted={encrypted}
isValid={isValid} isValid={isValid}
label={label} label={label}
name={configProperty} name={configProperty}

View File

@ -1,15 +1,28 @@
import React, { useEffect, useCallback, JSXElementConstructor } from "react"; import React, { useEffect, useCallback } from "react";
import { FieldArray, WrappedFieldArrayProps } from "redux-form"; import {
Field,
FieldArray,
WrappedFieldArrayProps,
WrappedFieldMetaProps,
WrappedFieldInputProps,
} from "redux-form";
import styled from "styled-components"; import styled from "styled-components";
import { Icon } from "@blueprintjs/core"; import { Icon } from "@blueprintjs/core";
import { FormIcons } from "icons/FormIcons"; import { FormIcons } from "icons/FormIcons";
import BaseControl, { ControlProps } from "./BaseControl"; import BaseControl, { ControlProps, ControlData } from "./BaseControl";
import TextField from "components/editorComponents/form/fields/TextField";
import { ControlType } from "constants/PropertyControlConstants"; import { ControlType } from "constants/PropertyControlConstants";
import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField"; import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField";
import FormLabel from "components/editorComponents/FormLabel";
import HelperTooltip from "components/editorComponents/HelperTooltip";
import { Colors } from "constants/Colors"; import { Colors } from "constants/Colors";
import TextInput, { TextInputProps } from "components/ads/TextInput";
export interface KeyValueArrayControlProps extends ControlProps {
name: string;
label: string;
maxLen?: number;
description?: string;
actionConfig?: any;
extraData?: ControlData[];
isRequired?: boolean;
}
const FormRowWithLabel = styled.div` const FormRowWithLabel = styled.div`
display: flex; display: flex;
@ -20,28 +33,32 @@ const FormRowWithLabel = styled.div`
} }
`; `;
const StyledTextField = styled(TextField)` const StyledTextInput = styled(TextInput)`
min-width: 66px;
input[type="number"]::-webkit-inner-spin-button, input[type="number"]::-webkit-inner-spin-button,
input[type="number"]::-webkit-outer-spin-button { input[type="number"]::-webkit-outer-spin-button {
-webkit-appearance: none; -webkit-appearance: none;
margin: 0; margin: 0px;
} }
`; `;
function KeyValueRow(props: KeyValueArrayProps & WrappedFieldArrayProps) { function KeyValueRow(
props: KeyValueArrayControlProps & WrappedFieldArrayProps,
) {
const { extraData = [] } = props; const { extraData = [] } = props;
const keyName = getFieldName(extraData[0].configProperty); const keyName = getFieldName(extraData[0]?.configProperty);
const valueName = getFieldName(extraData[1].configProperty); const valueName = getFieldName(extraData[1]?.configProperty);
const valueDataType = getType(extraData[1].dataType);
const keyFieldProps = extraData[0]; const keyFieldProps = extraData[0];
let isRequired: boolean | undefined;
useEffect(() => { useEffect(() => {
// Always maintain 1 row // Always maintain 1 row
if (props.fields.length < 1) { if (props.fields.length < 1) {
for (let i = props.fields.length; i < 1; i += 1) { for (let i = props.fields.length; i < 1; i += 1) {
props.fields.push({ [keyName[1]]: "", [valueName[1]]: "" }); if (keyName && valueName) {
props.fields.push({ [keyName[1]]: "", [valueName[1]]: "" });
} else {
props.fields.push({ key: "", value: "" });
}
} }
} }
}, [props.fields, keyName, valueName]); }, [props.fields, keyName, valueName]);
@ -58,87 +75,96 @@ function KeyValueRow(props: KeyValueArrayProps & WrappedFieldArrayProps) {
const keyFieldValidate = useCallback( const keyFieldValidate = useCallback(
(value: string) => { (value: string) => {
if (value && keyFieldProps.validationRegex) { if (value && keyFieldProps?.validationRegex) {
const regex = new RegExp(keyFieldProps.validationRegex); const regex = new RegExp(keyFieldProps?.validationRegex);
return regex.test(value) ? undefined : keyFieldProps.validationMessage; return regex.test(value) ? undefined : keyFieldProps.validationMessage;
} }
return undefined; return undefined;
}, },
[keyFieldProps.validationRegex, keyFieldProps.validationMessage], [keyFieldProps?.validationRegex, keyFieldProps?.validationMessage],
); );
const maxLen = props.maxLen;
if (extraData) { //if maxLen exists apply a check on the length
isRequired = extraData[0].isRequired || extraData[1].isRequired; const showAddIcon = (index: number): boolean =>
} maxLen
? index === props.fields.length - 1 && props.fields.length < maxLen
: index === props.fields.length - 1;
return typeof props.fields.getAll() === "object" ? ( return typeof props.fields.getAll() === "object" ? (
<> <>
{props.fields.map((field: any, index: number) => { {props.fields.map((field: any, index: number) => {
const otherProps: Record<string, any> = {}; let keyTextFieldName = `${field}.key`;
if ( let valueTextFieldName = `${field}.value`;
props.actionConfig &&
props.actionConfig[index].description && if (keyName && Array.isArray(keyName) && keyName?.length)
props.rightIcon keyTextFieldName = `${field}.${keyName[1]}`;
) {
otherProps.rightIcon = ( if (valueName && Array.isArray(valueName) && valueName?.length)
<HelperTooltip valueTextFieldName = `${field}.${valueName[1]}`;
description={props.actionConfig[index].description}
rightIcon={
props.actionConfig[index].description && props.rightIcon
}
/>
);
}
return ( return (
<FormRowWithLabel key={index} style={{ marginTop: 16 }}> <FormRowWithLabel
key={index}
style={{ marginTop: index > 0 ? "16px" : "0px" }}
>
<div <div
data-replay-id={btoa(`${field}.${keyName[1]}`)} data-replay-id={btoa(keyTextFieldName)}
style={{ width: "50vh" }} style={{ width: "50vh" }}
> >
<FormLabel> <Field
{extraData && extraData[0].label} {isRequired && "*"} component={renderTextInput}
</FormLabel> name={keyTextFieldName}
<TextField props={{
name={`${field}.${keyName[1]}`} dataType: getType(extraData[0]?.dataType),
placeholder={(extraData && extraData[0].placeholderText) || ""} defaultValue: props.initialValue,
showError keyFieldValidate,
validate={keyFieldValidate} placeholder: props.extraData
? props.extraData[1]?.placeholderText
: "",
isRequired: extraData[0]?.isRequired,
name: keyTextFieldName,
}}
/> />
</div> </div>
{!props.actionConfig && ( {!props.actionConfig && (
<div style={{ marginLeft: 16 }}> <div style={{ marginLeft: "16px", width: "50vh" }}>
<FormLabel> <div
{extraData && extraData[1].label} {isRequired && "*"} data-replay-id={valueTextFieldName}
</FormLabel> style={{ display: "flex", flexDirection: "row" }}
<div style={{ display: "flex", flexDirection: "row" }}> >
<div <Field
data-replay-id={btoa(`${field}.${valueName[1]}`)} component={renderTextInput}
style={{ marginRight: 14, width: 72 }} name={valueTextFieldName}
> props={{
<StyledTextField dataType: getType(extraData[1]?.dataType),
name={`${field}.${valueName[1]}`} defaultValue: props.initialValue,
placeholder={ placeholder: props.extraData
(extraData && extraData[1].placeholderText) || "" ? props.extraData[1]?.placeholderText
} : "",
type={valueDataType} name: valueTextFieldName,
/> isRequired: extraData[1]?.isRequired,
</div> }}
{index === props.fields.length - 1 ? ( />
{showAddIcon(index) ? (
<Icon <Icon
className="t--add-field"
color={Colors["CADET_BLUE"]} color={Colors["CADET_BLUE"]}
icon="plus" icon="plus"
iconSize={20} iconSize={20}
onClick={() => props.fields.push({ key: "", value: "" })} onClick={() => {
style={{ alignSelf: "center" }} props.fields.push({ key: "", value: "" });
}}
style={{ marginLeft: "16px", alignSelf: "center" }}
/> />
) : ( ) : (
<FormIcons.DELETE_ICON <FormIcons.DELETE_ICON
className="t--delete-field"
color={Colors["CADET_BLUE"]} color={Colors["CADET_BLUE"]}
height={20} height={20}
onClick={() => props.fields.remove(index)} onClick={() => props.fields.remove(index)}
style={{ alignSelf: "center" }} style={{ marginLeft: "16px", alignSelf: "center" }}
width={20} width={20}
/> />
)} )}
@ -155,7 +181,6 @@ function KeyValueRow(props: KeyValueArrayProps & WrappedFieldArrayProps) {
? `Value (Type: ${props.actionConfig[index].type})` ? `Value (Type: ${props.actionConfig[index].type})`
: `Value (optional)` : `Value (optional)`
} }
{...otherProps}
/> />
)} )}
</FormRowWithLabel> </FormRowWithLabel>
@ -165,7 +190,7 @@ function KeyValueRow(props: KeyValueArrayProps & WrappedFieldArrayProps) {
) : null; ) : null;
} }
class KeyValueFieldArray extends BaseControl<KeyValueArrayProps> { class KeyValueArrayControl extends BaseControl<KeyValueArrayControlProps> {
render() { render() {
const name = getFieldName(this.props.configProperty); const name = getFieldName(this.props.configProperty);
@ -174,7 +199,7 @@ class KeyValueFieldArray extends BaseControl<KeyValueArrayProps> {
component={KeyValueRow} component={KeyValueRow}
rerenderOnEveryChange={false} rerenderOnEveryChange={false}
{...this.props} {...this.props}
name={name[0]} name={name ? name[0] : ""}
/> />
); );
} }
@ -184,8 +209,8 @@ class KeyValueFieldArray extends BaseControl<KeyValueArrayProps> {
} }
} }
const getFieldName = (configProperty: string) => { const getFieldName = (configProperty: string): string[] | undefined => {
return configProperty.split("[*]."); if (configProperty) return configProperty.split("[*].");
}; };
const getType = (dataType: string | undefined) => { const getType = (dataType: string | undefined) => {
@ -199,12 +224,34 @@ const getType = (dataType: string | undefined) => {
} }
}; };
export interface KeyValueArrayProps extends ControlProps { function renderTextInput(
name: string; props: TextInputProps & {
label: string; dataType?: "text" | "number" | "password";
rightIcon?: JSXElementConstructor<{ height: number; width: number }>; placeholder?: string;
description?: string; defaultValue: string | number;
actionConfig?: any; isRequired: boolean;
keyFieldValidate?: (value: string) => { isValid: boolean; message: string };
errorMsg?: string;
helperText?: string;
} & {
meta: Partial<WrappedFieldMetaProps>;
input: Partial<WrappedFieldInputProps>;
},
): JSX.Element {
return (
<StyledTextInput
dataType={props.dataType}
defaultValue={props.defaultValue}
errorMsg={props.errorMsg}
helperText={props.helperText}
name={props.input?.name}
onChange={props.input.onChange}
placeholder={props.placeholder}
validator={props.keyFieldValidate}
value={props.input.value}
width="100%"
/>
);
} }
export default KeyValueFieldArray; export default KeyValueArrayControl;

View File

@ -6,9 +6,28 @@ import { FormIcons } from "icons/FormIcons";
import BaseControl, { ControlProps, ControlData } from "./BaseControl"; import BaseControl, { ControlProps, ControlData } from "./BaseControl";
import TextField from "components/editorComponents/form/fields/TextField"; import TextField from "components/editorComponents/form/fields/TextField";
import { ControlType } from "constants/PropertyControlConstants"; import { ControlType } from "constants/PropertyControlConstants";
import FormLabel from "components/editorComponents/FormLabel";
import { Colors } from "constants/Colors"; import { Colors } from "constants/Colors";
//TODO: combine it with KeyValueArrayControl and deprecate KeyValueInputControl
type KeyValueRowProps = {
name: string;
label: string;
rightIcon?: JSXElementConstructor<{ height: number; width: number }>;
description?: string;
actionConfig?: any;
extraData?: ControlData[];
isRequired?: boolean;
};
export interface KeyValueInputControlProps extends ControlProps {
name: string;
label: string;
rightIcon?: JSXElementConstructor<{ height: number; width: number }>;
description?: string;
actionConfig?: any;
}
const FormRowWithLabel = styled.div` const FormRowWithLabel = styled.div`
display: flex; display: flex;
flex: 1; flex: 1;
@ -18,7 +37,7 @@ const FormRowWithLabel = styled.div`
} }
`; `;
function KeyValueRow(props: Props & WrappedFieldArrayProps) { function KeyValueRow(props: KeyValueRowProps & WrappedFieldArrayProps) {
useEffect(() => { useEffect(() => {
// Always maintain 1 row // Always maintain 1 row
if (props.fields.length < 1) { if (props.fields.length < 1) {
@ -32,21 +51,16 @@ function KeyValueRow(props: Props & WrappedFieldArrayProps) {
<div> <div>
{typeof props.fields.getAll() === "object" && ( {typeof props.fields.getAll() === "object" && (
<div> <div>
<FormLabel>
{props.label} {props.isRequired && "*"}
</FormLabel>
{props.fields.map((field: any, index: number) => ( {props.fields.map((field: any, index: number) => (
<FormRowWithLabel key={index} style={{ marginTop: index ? 13 : 0 }}> <FormRowWithLabel key={index} style={{ marginTop: index ? 13 : 0 }}>
<div <div
data-replay-id={btoa(`${field}.key`)} data-replay-id={btoa(`${field}.key`)}
style={{ width: "50vh" }} style={{ width: "50vh" }}
> >
{/* <FormLabel></FormLabel> */}
<TextField name={`${field}.key`} placeholder="Key" /> <TextField name={`${field}.key`} placeholder="Key" />
</div> </div>
<div style={{ marginLeft: 16 }}> <div style={{ marginLeft: 16 }}>
{/* <FormLabel></FormLabel> */}
<div style={{ display: "flex", flexDirection: "row" }}> <div style={{ display: "flex", flexDirection: "row" }}>
<div <div
data-replay-id={btoa(`${field}.value`)} data-replay-id={btoa(`${field}.value`)}
@ -83,17 +97,7 @@ function KeyValueRow(props: Props & WrappedFieldArrayProps) {
); );
} }
type Props = { class KeyValueFieldInputControl extends BaseControl<KeyValueInputControlProps> {
name: string;
label: string;
rightIcon?: JSXElementConstructor<{ height: number; width: number }>;
description?: string;
actionConfig?: any;
extraData?: ControlData[];
isRequired?: boolean;
};
class KeyValueFieldInput extends BaseControl<KeyValueInputProps> {
render() { render() {
return ( return (
<FieldArray <FieldArray
@ -110,12 +114,4 @@ class KeyValueFieldInput extends BaseControl<KeyValueInputProps> {
} }
} }
export interface KeyValueInputProps extends ControlProps { export default KeyValueFieldInputControl;
name: string;
label: string;
rightIcon?: JSXElementConstructor<{ height: number; width: number }>;
description?: string;
actionConfig?: any;
}
export default KeyValueFieldInput;

View File

@ -1,19 +1,29 @@
import React from "react"; import React from "react";
import BaseControl, { ControlProps } from "./BaseControl"; import BaseControl, { ControlProps } from "./BaseControl";
import { StyledSwitch } from "./StyledControls"; import Toggle from "components/ads/Toggle";
import { ControlType } from "constants/PropertyControlConstants"; import { ControlType } from "constants/PropertyControlConstants";
import FormLabel from "components/editorComponents/FormLabel";
import { Field, WrappedFieldProps } from "redux-form"; import { Field, WrappedFieldProps } from "redux-form";
import styled from "styled-components"; import styled from "styled-components";
type Props = WrappedFieldProps & { type SwitchFieldProps = WrappedFieldProps & {
label: string; label: string;
isRequired: boolean; isRequired: boolean;
info: string; info: string;
}; };
const StyledFormLabel = styled(FormLabel)` const StyledToggle = styled(Toggle)`
margin-bottom: 0px; .slider {
margin-left: 10px;
width: 40px;
height: 20px;
}
.slider::before {
height: 16px;
width: 16px;
}
input:checked + .slider::before {
transform: translateX(19px);
}
`; `;
const SwitchWrapped = styled.div` const SwitchWrapped = styled.div`
@ -26,14 +36,7 @@ const SwitchWrapped = styled.div`
max-width: 60vw; max-width: 60vw;
`; `;
const Info = styled.div` export class SwitchField extends React.Component<SwitchFieldProps, any> {
font-size: 12px;
opacity: 0.7;
margin-top: 8px;
max-width: 60vw;
`;
export class SwitchField extends React.Component<Props, any> {
get value() { get value() {
const { input } = this.props; const { input } = this.props;
if (typeof input.value !== "string") return !!input.value; if (typeof input.value !== "string") return !!input.value;
@ -44,21 +47,18 @@ export class SwitchField extends React.Component<Props, any> {
} }
render() { render() {
const { info, input, isRequired, label } = this.props;
return ( return (
<div> <div>
<SwitchWrapped data-cy={this.props.input.name}> <SwitchWrapped data-cy={this.props.input.name}>
<StyledFormLabel> <StyledToggle
{label} {isRequired && "*"} className="switch-control"
</StyledFormLabel> name={this.props.input.name}
<StyledSwitch onToggle={(value: any) => {
checked={this.value} this.props.input.onChange(value);
large }}
onChange={(value) => input.onChange(value)} value={this.value}
/> />
</SwitchWrapped> </SwitchWrapped>
{info && <Info>{info}</Info>}
</div> </div>
); );
} }

View File

@ -5,7 +5,6 @@ import Icon, { IconSize } from "components/ads/Icon";
import { Classes } from "components/ads/common"; import { Classes } from "components/ads/common";
import styled from "styled-components"; import styled from "styled-components";
import { FieldArray, getFormValues } from "redux-form"; import { FieldArray, getFormValues } from "redux-form";
import FormLabel from "components/editorComponents/FormLabel";
import { ControlProps } from "./BaseControl"; import { ControlProps } from "./BaseControl";
import _ from "lodash"; import _ from "lodash";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
@ -64,7 +63,7 @@ const CenteredIcon = styled(Icon)`
// Outer box that houses the whole component // Outer box that houses the whole component
const PrimaryBox = styled.div` const PrimaryBox = styled.div`
display: flex; display: flex;
width: 105vh; width: min-content;
flex-direction: column; flex-direction: column;
border: 2px solid ${(props) => props.theme.colors.apiPane.dividerBg}; border: 2px solid ${(props) => props.theme.colors.apiPane.dividerBg};
padding: 10px; padding: 10px;
@ -216,7 +215,7 @@ function ConditionBlock(props: any) {
isDisabled = true; isDisabled = true;
} }
return ( return (
<PrimaryBox style={{ width: `${props.maxWidth}vh`, marginTop }}> <PrimaryBox style={{ marginTop }}>
<SecondaryBox> <SecondaryBox>
{/* Component to render the joining operator between multiple conditions */} {/* Component to render the joining operator between multiple conditions */}
<FormControl <FormControl
@ -302,8 +301,6 @@ function ConditionBlock(props: any) {
}, },
], ],
}); });
// eslint-disable-next-line no-console
console.log("Ayush new field", props.fields.getAll());
}} }}
> >
{/*Hardcoded label to be removed */} {/*Hardcoded label to be removed */}
@ -320,7 +317,6 @@ export default function WhereClauseControl(props: WhereClauseControlProps) {
comparisonTypes, // All possible keys for the comparison comparisonTypes, // All possible keys for the comparison
configProperty, // JSON path for the where clause data configProperty, // JSON path for the where clause data
formName, // Name of the form, used by redux-form lib to store the data in redux store formName, // Name of the form, used by redux-form lib to store the data in redux store
label, // Label for the where clause
logicalTypes, // All possible keys for the logical operators joining multiple conditions logicalTypes, // All possible keys for the logical operators joining multiple conditions
nestedLevels, // Number of nested levels allowed nestedLevels, // Number of nested levels allowed
} = props; } = props;
@ -328,24 +324,21 @@ export default function WhereClauseControl(props: WhereClauseControlProps) {
// Max width is designed in a way that the proportion stays same even after nesting // Max width is designed in a way that the proportion stays same even after nesting
const maxWidth = 105; const maxWidth = 105;
return ( return (
<> <FieldArray
<FormLabel>{label}</FormLabel> component={ConditionBlock}
<FieldArray key={`${configProperty}.children`}
component={ConditionBlock} name={`${configProperty}.children`}
key={`${configProperty}.children`} props={{
name={`${configProperty}.children`} configProperty,
props={{ maxWidth,
configProperty, formName,
maxWidth, logicalTypes,
formName, comparisonTypes,
logicalTypes, nestedLevels,
comparisonTypes, currentNestingLevel: 0,
nestedLevels, }}
currentNestingLevel: 0, rerenderOnEveryChange={false}
}} />
rerenderOnEveryChange={false}
/>
</>
); );
} }

View File

@ -3,6 +3,7 @@ import { Icon } from "@blueprintjs/core";
import { IconNames } from "@blueprintjs/icons"; import { IconNames } from "@blueprintjs/icons";
import { IconProps, IconWrapper } from "constants/IconConstants"; import { IconProps, IconWrapper } from "constants/IconConstants";
import { ReactComponent as InfoIcon } from "assets/icons/form/info-outline.svg"; import { ReactComponent as InfoIcon } from "assets/icons/form/info-outline.svg";
import { ReactComponent as HelpIcon } from "assets/icons/form/help-outline.svg";
import { ReactComponent as AddNewIcon } from "assets/icons/form/add-new.svg"; import { ReactComponent as AddNewIcon } from "assets/icons/form/add-new.svg";
import { ReactComponent as LockIcon } from "assets/icons/form/lock.svg"; import { ReactComponent as LockIcon } from "assets/icons/form/lock.svg";
@ -18,6 +19,11 @@ export const FormIcons: {
<InfoIcon /> <InfoIcon />
</IconWrapper> </IconWrapper>
), ),
HELP_ICON: (props: IconProps) => (
<IconWrapper {...props}>
<HelpIcon />
</IconWrapper>
),
HOME_ICON: (props: IconProps) => ( HOME_ICON: (props: IconProps) => (
<IconWrapper {...props}> <IconWrapper {...props}>
<Icon color={props.color} icon={IconNames.HOME} iconSize={props.height} /> <Icon color={props.color} icon={IconNames.HOME} iconSize={props.height} />

View File

@ -12,11 +12,8 @@ import {
reduxForm, reduxForm,
} from "redux-form"; } from "redux-form";
import AnalyticsUtil from "utils/AnalyticsUtil"; import AnalyticsUtil from "utils/AnalyticsUtil";
import InputTextControl, { import FormControl from "pages/Editor/FormControl";
StyledInfo, import { StyledInfo } from "components/formControls/InputTextControl";
} from "components/formControls/InputTextControl";
import KeyValueInputControl from "components/formControls/KeyValueInputControl";
import DropDownControl from "components/formControls/DropDownControl";
import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper"; import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { AppState } from "reducers"; import { AppState } from "reducers";
@ -127,7 +124,6 @@ export const Header = styled.div`
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-between; justify-content: space-between;
//margin-top: 16px;
`; `;
const SaveButtonContainer = styled.div` const SaveButtonContainer = styled.div`
@ -157,14 +153,6 @@ const AuthorizeButton = styled(StyledButton)`
} }
`; `;
const COMMON_INPUT_PROPS: any = {
name: "",
formName: DATASOURCE_REST_API_FORM,
id: "",
isValid: false,
controlType: "",
};
class DatasourceRestAPIEditor extends React.Component<Props> { class DatasourceRestAPIEditor extends React.Component<Props> {
componentDidMount() { componentDidMount() {
const search = new URLSearchParams(this.props.location.search); const search = new URLSearchParams(this.props.location.search);
@ -380,36 +368,35 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
<Callout fill key={i} text={msg} variant={Variant.warning} /> <Callout fill key={i} text={msg} variant={Variant.warning} />
))} ))}
<FormInputContainer data-replay-id={btoa("url")}> <FormInputContainer data-replay-id={btoa("url")}>
<InputTextControl {this.renderInputTextControlViaFormControl(
{...COMMON_INPUT_PROPS} "url",
configProperty="url" "URL",
isRequired "https://example.com",
label="URL" "TEXT",
placeholderText="https://example.com" false,
/> true,
)}
</FormInputContainer> </FormInputContainer>
<FormInputContainer data-replay-id={btoa("headers")}> <FormInputContainer data-replay-id={btoa("headers")}>
<KeyValueInputControl {this.renderKeyValueControlViaFormControl(
{...COMMON_INPUT_PROPS} "headers",
configProperty="headers" "Headers",
label="Headers" "",
/> false,
)}
</FormInputContainer> </FormInputContainer>
<FormInputContainer data-replay-id={btoa("queryParameters")}>
<FormInputContainer> {this.renderKeyValueControlViaFormControl(
<KeyValueInputControl "queryParameters",
{...COMMON_INPUT_PROPS} "Query Parameters",
configProperty="queryParameters" "",
label="Query Parameters" false,
/> )}
</FormInputContainer> </FormInputContainer>
<FormInputContainer data-replay-id={btoa("isSendSessionEnabled")}> <FormInputContainer data-replay-id={btoa("isSendSessionEnabled")}>
<DropDownControl {this.renderDropdownControlViaFormControl(
{...COMMON_INPUT_PROPS} "isSendSessionEnabled",
configProperty="isSendSessionEnabled" [
isRequired
label="Send Appsmith signature header"
options={[
{ {
label: "Yes", label: "Yes",
value: true, value: true,
@ -418,28 +405,29 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
label: "No", label: "No",
value: false, value: false,
}, },
]} ],
placeholderText="" "Send Appsmith signature header",
propertyValue="" "",
subtitle="Header key: X-APPSMITH-SIGNATURE" true,
/> "Header key: X-APPSMITH-SIGNATURE",
)}
</FormInputContainer> </FormInputContainer>
{formData.isSendSessionEnabled && ( {formData.isSendSessionEnabled && (
<FormInputContainer data-replay-id={btoa("sessionSignatureKey")}> <FormInputContainer data-replay-id={btoa("sessionSignatureKey")}>
<InputTextControl {this.renderInputTextControlViaFormControl(
{...COMMON_INPUT_PROPS} "sessionSignatureKey",
configProperty="sessionSignatureKey" "Session Details Signature Key",
label="Session Details Signature Key" "",
placeholderText="" "TEXT",
/> false,
false,
)}
</FormInputContainer> </FormInputContainer>
)} )}
<FormInputContainer data-replay-id={btoa("authType")}> <FormInputContainer data-replay-id={btoa("authType")}>
<DropDownControl {this.renderDropdownControlViaFormControl(
{...COMMON_INPUT_PROPS} "authType",
configProperty="authType" [
label="Authentication Type"
options={[
{ {
label: "None", label: "None",
value: AuthType.NONE, value: AuthType.NONE,
@ -460,10 +448,12 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
label: "Bearer Token", label: "Bearer Token",
value: AuthType.bearerToken, value: AuthType.bearerToken,
}, },
]} ],
placeholderText="" "Authentication Type",
propertyValue="" "",
/> false,
"",
)}
</FormInputContainer> </FormInputContainer>
{this.renderAuthFields()} {this.renderAuthFields()}
</> </>
@ -497,28 +487,29 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
return ( return (
<> <>
<FormInputContainer data-replay-id={btoa("authentication.label")}> <FormInputContainer data-replay-id={btoa("authentication.label")}>
<InputTextControl {this.renderInputTextControlViaFormControl(
{...COMMON_INPUT_PROPS} "authentication.label",
configProperty="authentication.label" "Key",
label="Key" "api_key",
placeholderText="api_key" "TEXT",
/> false,
false,
)}
</FormInputContainer> </FormInputContainer>
<FormInputContainer data-replay-id={btoa("authentication.value")}> <FormInputContainer>
<InputTextControl {this.renderInputTextControlViaFormControl(
{...COMMON_INPUT_PROPS} "authentication.value",
configProperty="authentication.value" "Value",
encrypted "value",
label="Value" "TEXT",
placeholderText="value" true,
/> false,
)}
</FormInputContainer> </FormInputContainer>
<FormInputContainer data-replay-id={btoa("authentication.addTo")}> <FormInputContainer>
<DropDownControl {this.renderDropdownControlViaFormControl(
{...COMMON_INPUT_PROPS} "authentication.addTo",
configProperty="authentication.addTo" [
label="Add To"
options={[
{ {
label: "Query Params", label: "Query Params",
value: ApiKeyAuthType.QueryParams, value: ApiKeyAuthType.QueryParams,
@ -527,21 +518,25 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
label: "Header", label: "Header",
value: ApiKeyAuthType.Header, value: ApiKeyAuthType.Header,
}, },
]} ],
placeholderText="" "Add To",
propertyValue="" "",
/> false,
"",
)}
</FormInputContainer> </FormInputContainer>
{_.get(authentication, "addTo") == "header" && ( {_.get(authentication, "addTo") == "header" && (
<FormInputContainer <FormInputContainer
data-replay-id={btoa("authentication.headerPrefix")} data-replay-id={btoa("authentication.headerPrefix")}
> >
<InputTextControl {this.renderInputTextControlViaFormControl(
{...COMMON_INPUT_PROPS} "authentication.headerPrefix",
configProperty="authentication.headerPrefix" "Header Prefix",
label="Header Prefix" "eg: Bearer ",
placeholderText="eg: Bearer " "TEXT",
/> false,
false,
)}
</FormInputContainer> </FormInputContainer>
)} )}
</> </>
@ -551,13 +546,14 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
renderBearerToken = () => { renderBearerToken = () => {
return ( return (
<FormInputContainer data-replay-id={btoa("authentication.bearerToken")}> <FormInputContainer data-replay-id={btoa("authentication.bearerToken")}>
<InputTextControl {this.renderInputTextControlViaFormControl(
{...COMMON_INPUT_PROPS} "authentication.bearerToken",
configProperty="authentication.bearerToken" "Bearer Token",
encrypted "Bearer Token",
label="Bearer Token" "TEXT",
placeholderText="Bearer Token" true,
/> false,
)}
</FormInputContainer> </FormInputContainer>
); );
}; };
@ -566,22 +562,24 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
return ( return (
<> <>
<FormInputContainer data-replay-id={btoa("authentication.username")}> <FormInputContainer data-replay-id={btoa("authentication.username")}>
<InputTextControl {this.renderInputTextControlViaFormControl(
{...COMMON_INPUT_PROPS} "authentication.username",
configProperty="authentication.username" "Username",
label="Username" "Username",
placeholderText="Username" "TEXT",
/> false,
false,
)}
</FormInputContainer> </FormInputContainer>
<FormInputContainer data-replay-id={btoa("authentication.password")}> <FormInputContainer data-replay-id={btoa("authentication.password")}>
<InputTextControl {this.renderInputTextControlViaFormControl(
{...COMMON_INPUT_PROPS} "authentication.password",
configProperty="authentication.password" "Password",
dataType="PASSWORD" "Password",
encrypted "PASSWORD",
label="Password" true,
placeholderText="Password" false,
/> )}
</FormInputContainer> </FormInputContainer>
</> </>
); );
@ -603,11 +601,9 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
return ( return (
<> <>
<FormInputContainer data-replay-id={btoa("authentication.grantType")}> <FormInputContainer data-replay-id={btoa("authentication.grantType")}>
<DropDownControl {this.renderDropdownControlViaFormControl(
{...COMMON_INPUT_PROPS} "authentication.grantType",
configProperty="authentication.grantType" [
label="Grant Type"
options={[
{ {
label: "Client Credentials", label: "Client Credentials",
value: GrantType.ClientCredentials, value: GrantType.ClientCredentials,
@ -616,10 +612,12 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
label: "Authorization Code", label: "Authorization Code",
value: GrantType.AuthorizationCode, value: GrantType.AuthorizationCode,
}, },
]} ],
placeholderText="" "Grant Type",
propertyValue="" "",
/> false,
"",
)}
</FormInputContainer> </FormInputContainer>
{content} {content}
</> </>
@ -633,11 +631,9 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
<FormInputContainer <FormInputContainer
data-replay-id={btoa("authentication.isTokenHeader")} data-replay-id={btoa("authentication.isTokenHeader")}
> >
<DropDownControl {this.renderDropdownControlViaFormControl(
{...COMMON_INPUT_PROPS} "authentication.isTokenHeader",
configProperty="authentication.isTokenHeader" [
label="Add Access Token To"
options={[
{ {
label: "Request Header", label: "Request Header",
value: true, value: true,
@ -646,58 +642,70 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
label: "Request URL", label: "Request URL",
value: false, value: false,
}, },
]} ],
/> "Add Access Token To",
"",
false,
"",
)}
</FormInputContainer> </FormInputContainer>
{_.get(formData.authentication, "isTokenHeader") && ( {_.get(formData.authentication, "isTokenHeader") && (
<FormInputContainer <FormInputContainer
data-replay-id={btoa("authentication.headerPrefix")} data-replay-id={btoa("authentication.headerPrefix")}
> >
<InputTextControl {this.renderInputTextControlViaFormControl(
{...COMMON_INPUT_PROPS} "authentication.headerPrefix",
configProperty="authentication.headerPrefix" "Header Prefix",
label="Header Prefix" "eg: Bearer ",
placeholderText="eg: Bearer " "TEXT",
/> false,
false,
)}
</FormInputContainer> </FormInputContainer>
)} )}
<FormInputContainer <FormInputContainer
data-replay-id={btoa("authentication.accessTokenUrl")} data-replay-id={btoa("authentication.accessTokenUrl")}
> >
<InputTextControl {this.renderInputTextControlViaFormControl(
{...COMMON_INPUT_PROPS} "authentication.accessTokenUrl",
configProperty="authentication.accessTokenUrl" "Access Token URL",
label="Access Token URL" "https://example.com/login/oauth/access_token",
placeholderText="https://example.com/login/oauth/access_token" "TEXT",
/> false,
false,
)}
</FormInputContainer> </FormInputContainer>
<FormInputContainer data-replay-id={btoa("authentication.clientId")}> <FormInputContainer data-replay-id={btoa("authentication.clientId")}>
<InputTextControl {this.renderInputTextControlViaFormControl(
{...COMMON_INPUT_PROPS} "authentication.clientId",
configProperty="authentication.clientId" "Client ID",
label="Client ID" "Client ID",
placeholderText="Client ID" "TEXT",
/> false,
false,
)}
</FormInputContainer> </FormInputContainer>
<FormInputContainer <FormInputContainer
data-replay-id={btoa("authentication.clientSecret")} data-replay-id={btoa("authentication.clientSecret")}
> >
<InputTextControl {this.renderInputTextControlViaFormControl(
{...COMMON_INPUT_PROPS} "authentication.clientSecret",
configProperty="authentication.clientSecret" "Client Secret",
dataType="PASSWORD" "Client Secret",
encrypted "PASSWORD",
label="Client Secret" true,
placeholderText="Client Secret" false,
/> )}
</FormInputContainer> </FormInputContainer>
<FormInputContainer data-replay-id={btoa("authentication.scopeString")}> <FormInputContainer data-replay-id={btoa("authentication.scopeString")}>
<InputTextControl {this.renderInputTextControlViaFormControl(
{...COMMON_INPUT_PROPS} "authentication.scopeString",
configProperty="authentication.scopeString" "Scope(s)",
label="Scope(s)" "e.g. read, write",
placeholderText="e.g. read, write" "TEXT",
/> false,
false,
)}
</FormInputContainer> </FormInputContainer>
</> </>
); );
@ -706,28 +714,25 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
renderOauth2CommonAdvanced = () => { renderOauth2CommonAdvanced = () => {
return ( return (
<> <>
<FormInputContainer> <FormInputContainer data-replay-id={btoa("authentication.audience")}>
<KeyValueInputControl {this.renderInputTextControlViaFormControl(
{...COMMON_INPUT_PROPS} "authentication.audience",
configProperty="authentication.customTokenParameters" "Audience",
label="Custom Token Parameters" "https://example.com/oauth/audience",
/> "TEXT",
</FormInputContainer> false,
<FormInputContainer> false,
<InputTextControl )}
{...COMMON_INPUT_PROPS}
configProperty="authentication.audience"
label="Audience"
placeholderText="https://example.com/oauth/audience"
/>
</FormInputContainer> </FormInputContainer>
<FormInputContainer data-replay-id={btoa("authentication.resource")}> <FormInputContainer data-replay-id={btoa("authentication.resource")}>
<InputTextControl {this.renderInputTextControlViaFormControl(
{...COMMON_INPUT_PROPS} "authentication.resource",
configProperty="authentication.resource" "Resource",
label="Resource" "https://example.com/oauth/resource",
placeholderText="https://example.com/oauth/resource" "TEXT",
/> false,
false,
)}
</FormInputContainer> </FormInputContainer>
</> </>
); );
@ -757,12 +762,14 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
<FormInputContainer <FormInputContainer
data-replay-id={btoa("authentication.authorizationUrl")} data-replay-id={btoa("authentication.authorizationUrl")}
> >
<InputTextControl {this.renderInputTextControlViaFormControl(
{...COMMON_INPUT_PROPS} "authentication.authorizationUrl",
configProperty="authentication.authorizationUrl" "Authorization URL",
label="Authorization URL" "https://example.com/login/oauth/authorize",
placeholderText="https://example.com/login/oauth/authorize" "TEXT",
/> false,
false,
)}
</FormInputContainer> </FormInputContainer>
<FormInputContainer> <FormInputContainer>
<div style={{ width: "50vh" }}> <div style={{ width: "50vh" }}>
@ -779,20 +786,19 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
<FormInputContainer <FormInputContainer
data-replay-id={btoa("authentication.customAuthenticationParameters")} data-replay-id={btoa("authentication.customAuthenticationParameters")}
> >
<KeyValueInputControl {this.renderKeyValueControlViaFormControl(
{...COMMON_INPUT_PROPS} "authentication.customAuthenticationParameters",
configProperty="authentication.customAuthenticationParameters" "Custom Authentication Parameters",
label="Custom Authentication Parameters" "",
/> false,
)}
</FormInputContainer> </FormInputContainer>
<FormInputContainer <FormInputContainer
data-replay-id={btoa("authentication.isAuthorizationHeader")} data-replay-id={btoa("authentication.isAuthorizationHeader")}
> >
<DropDownControl {this.renderDropdownControlViaFormControl(
{...COMMON_INPUT_PROPS} "authentication.isAuthorizationHeader",
configProperty="authentication.isAuthorizationHeader" [
label="Client Authentication"
options={[
{ {
label: "Send as Basic Auth header", label: "Send as Basic Auth header",
value: true, value: true,
@ -801,8 +807,12 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
label: "Send client credentials in body", label: "Send client credentials in body",
value: false, value: false,
}, },
]} ],
/> "Client Authentication",
"",
false,
"",
)}
</FormInputContainer> </FormInputContainer>
{!_.get(formData.authentication, "isAuthorizationHeader", true) && {!_.get(formData.authentication, "isAuthorizationHeader", true) &&
this.renderOauth2CommonAdvanced()} this.renderOauth2CommonAdvanced()}
@ -824,6 +834,93 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
</> </>
); );
}; };
// All components in formControls must be rendered via FormControl.
// FormControl is the common wrapper for all formcontrol components and contains common elements i.e. label, subtitle, helpertext
renderInputTextControlViaFormControl(
configProperty: string,
label: string,
placeholderText: string,
dataType: "TEXT" | "PASSWORD" | "NUMBER",
encrypted: boolean,
isRequired: boolean,
) {
return (
<FormControl
config={{
id: "",
isValid: false,
isRequired: isRequired,
controlType: "INPUT_TEXT",
dataType: dataType,
configProperty: configProperty,
encrypted: encrypted,
label: label,
conditionals: "",
placeholderText: placeholderText,
formName: DATASOURCE_REST_API_FORM,
}}
formName={DATASOURCE_REST_API_FORM}
multipleConfig={[]}
/>
);
}
renderDropdownControlViaFormControl(
configProperty: string,
options: { label: string; value: string | boolean }[],
label: string,
placeholderText: string,
isRequired: boolean,
subtitle?: string,
) {
const config = {
id: "",
isValid: false,
isRequired: isRequired,
controlType: "DROP_DOWN",
configProperty: configProperty,
options: options,
subtitle: subtitle,
label: label,
conditionals: "",
placeholderText: placeholderText,
formName: DATASOURCE_REST_API_FORM,
};
return (
<FormControl
config={config}
formName={DATASOURCE_REST_API_FORM}
multipleConfig={[]}
/>
);
}
renderKeyValueControlViaFormControl(
configProperty: string,
label: string,
placeholderText: string,
isRequired: boolean,
) {
const config = {
id: "",
configProperty: configProperty,
isValid: false,
controlType: "KEYVALUE_ARRAY",
placeholderText: placeholderText,
label: label,
conditionals: "",
formName: DATASOURCE_REST_API_FORM,
isRequired: isRequired,
};
return (
<FormControl
config={config}
formName={DATASOURCE_REST_API_FORM}
multipleConfig={[]}
/>
);
}
} }
const mapStateToProps = (state: AppState, props: any) => { const mapStateToProps = (state: AppState, props: any) => {

View File

@ -4,6 +4,17 @@ import { isHidden } from "components/formControls/utils";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { getFormValues } from "redux-form"; import { getFormValues } from "redux-form";
import FormControlFactory from "utils/FormControlFactory"; import FormControlFactory from "utils/FormControlFactory";
import Tooltip from "components/ads/Tooltip";
import {
FormLabel,
FormInputHelperText,
FormInputAnchor,
FormInputErrorText,
FormInfoText,
FormSubtitleText,
FormInputSwitchToJsonButton,
} from "components/editorComponents/form/fields/StyledFormComponents";
import { FormIcons } from "icons/FormIcons";
interface FormControlProps { interface FormControlProps {
config: ControlProps; config: ControlProps;
@ -20,17 +31,137 @@ function FormControl(props: FormControlProps) {
if (hidden) return null; if (hidden) return null;
return ( return (
<div <FormConfig
className={`t--form-control-${props.config.controlType}`} config={props.config}
data-replay-id={btoa(props.config.configProperty)} formName={props.formName}
multipleConfig={props?.multipleConfig}
> >
{FormControlFactory.createControl( <div className={`t--form-control-${props.config.controlType}`}>
props.config, {FormControlFactory.createControl(
props.formName, props.config,
props?.multipleConfig, props.formName,
)} props?.multipleConfig,
)}
</div>
</FormConfig>
);
}
interface FormConfigProps extends FormControlProps {
children: JSX.Element;
}
// top contains label, subtitle, urltext, tooltip, dispaly type
// bottom contains the info and error text
// props.children will render the form element
function FormConfig(props: FormConfigProps) {
let top, bottom;
if (props.multipleConfig?.length) {
top = (
<div style={{ display: "flex" }}>
{props.multipleConfig?.map((config) => {
return renderFormConfigTop({ config });
})}
</div>
);
bottom = props.multipleConfig?.map((config) => {
return renderFormConfigBottom({ config });
});
return (
<>
{top}
{props.children}
{bottom}
</>
);
}
return (
<div>
<div
style={{
// TODO: replace condition with props.config.dataType === "TOGGLE"
// label and form element is rendered side by side for CHECKBOX and SWITCH
display:
props.config.controlType === "SWITCH" ||
props.config.controlType === "CHECKBOX"
? "flex"
: "block",
}}
>
{props.config.controlType === "CHECKBOX" ? (
<>
{props.children}
{renderFormConfigTop({ config: props.config })}
</>
) : (
<>
{renderFormConfigTop({ config: props.config })}
{props.children}
</>
)}
</div>
{renderFormConfigBottom({ config: props.config })}
</div> </div>
); );
} }
export default FormControl; export default FormControl;
function renderFormConfigTop(props: { config: ControlProps }) {
const {
displayType,
encrypted,
isRequired,
label,
subtitle,
tooltipText = "",
url,
urlText,
} = { ...props.config };
return (
<React.Fragment key={props.config.label}>
<FormLabel config={props.config}>
<p className="label-icon-wrapper">
{label} {isRequired && "*"}{" "}
{encrypted && (
<>
<FormIcons.LOCK_ICON height={12} keepColors width={12} />
<FormSubtitleText config={props.config}>
Encrypted
</FormSubtitleText>
</>
)}
{tooltipText && (
<Tooltip content={tooltipText} hoverOpenDelay={1000}>
<FormIcons.HELP_ICON height={16} width={16} />
</Tooltip>
)}
</p>
{subtitle && (
<FormInfoText config={props.config}>{subtitle}</FormInfoText>
)}
</FormLabel>
{urlText && (
<FormInputAnchor href={url} target="_blank">
{urlText}
</FormInputAnchor>
)}
{displayType && (
<FormInputSwitchToJsonButton type="button">
{displayType === "JSON" ? "SWITCH TO GUI" : "SWITCH TO JSON EDITOR"}
</FormInputSwitchToJsonButton>
)}
</React.Fragment>
);
}
function renderFormConfigBottom(props: { config: ControlProps }) {
const { errorText, info, showError } = { ...props.config };
return (
<>
{info && <FormInputHelperText>{info}</FormInputHelperText>}
{showError && <FormInputErrorText>{errorText}</FormInputErrorText>}
</>
);
}

View File

@ -1,7 +1,10 @@
import React from "react"; import React from "react";
import styled from "styled-components"; import styled from "styled-components";
import { Colors } from "constants/Colors"; import { Colors } from "constants/Colors";
import { RenderDropdownOptionType } from "components/ads/Dropdown"; import {
DropdownOption,
RenderDropdownOptionType,
} from "components/ads/Dropdown";
import { useSelector } from "react-redux"; import { useSelector } from "react-redux";
import { getPluginImages } from "../../../../selectors/entitiesSelector"; import { getPluginImages } from "../../../../selectors/entitiesSelector";
import { Classes } from "../../../../components/ads/common"; import { Classes } from "../../../../components/ads/common";
@ -101,13 +104,14 @@ function DataSourceOption({
optionClickHandler, optionClickHandler,
optionWidth, optionWidth,
}: DataSourceOptionType) { }: DataSourceOptionType) {
const { label } = dropdownOption; const { label } = dropdownOption as DropdownOption;
const { routeToCreateNewDatasource = () => null } = extraProps; const { routeToCreateNewDatasource = () => null } = extraProps;
const pluginImages = useSelector(getPluginImages); const pluginImages = useSelector(getPluginImages);
const isConnectNewDataSourceBtn = const isConnectNewDataSourceBtn =
CONNECT_NEW_DATASOURCE_OPTION_ID === dropdownOption.id; CONNECT_NEW_DATASOURCE_OPTION_ID === (dropdownOption as DropdownOption).id;
const isSupportedForTemplate = dropdownOption.data.isSupportedForTemplate; const isSupportedForTemplate = (dropdownOption as DropdownOption).data
.isSupportedForTemplate;
const isNotSupportedDatasource = const isNotSupportedDatasource =
!isSupportedForTemplate && !isSelectedNode && !isConnectNewDataSourceBtn; !isSupportedForTemplate && !isSelectedNode && !isConnectNewDataSourceBtn;
@ -127,7 +131,7 @@ function DataSourceOption({
className="t--dropdown-option" className="t--dropdown-option"
data-cy={optionCypressSelector} data-cy={optionCypressSelector}
disabled={isNotSupportedDatasource} disabled={isNotSupportedDatasource}
key={dropdownOption.id} key={(dropdownOption as DropdownOption).id}
onClick={() => { onClick={() => {
if (isNotSupportedDatasource) { if (isNotSupportedDatasource) {
return; return;
@ -135,7 +139,7 @@ function DataSourceOption({
if (isConnectNewDataSourceBtn) { if (isConnectNewDataSourceBtn) {
routeToCreateNewDatasource(dropdownOption); routeToCreateNewDatasource(dropdownOption);
} else if (optionClickHandler) { } else if (optionClickHandler) {
optionClickHandler(dropdownOption); optionClickHandler(dropdownOption as DropdownOption);
} }
}} }}
selected={isSelectedNode} selected={isSelectedNode}
@ -149,12 +153,14 @@ function DataSourceOption({
width={20} width={20}
/> />
</CreateIconWrapper> </CreateIconWrapper>
) : pluginImages[dropdownOption.data.pluginId] ? ( ) : pluginImages[(dropdownOption as DropdownOption).data.pluginId] ? (
<ImageWrapper> <ImageWrapper>
<DatasourceImage <DatasourceImage
alt="" alt=""
className="dataSourceImage" className="dataSourceImage"
src={pluginImages[dropdownOption.data.pluginId]} src={
pluginImages[(dropdownOption as DropdownOption).data.pluginId]
}
/> />
</ImageWrapper> </ImageWrapper>
) : null} ) : null}

View File

@ -629,7 +629,7 @@ function GeneratePageForm() {
cypressSelector="t--datasource-dropdown-option" cypressSelector="t--datasource-dropdown-option"
extraProps={{ routeToCreateNewDatasource }} extraProps={{ routeToCreateNewDatasource }}
isSelectedNode={isSelectedNode} isSelectedNode={isSelectedNode}
key={option.id} key={(option as DropdownOption).id}
option={option} option={option}
optionClickHandler={optionClickHandler} optionClickHandler={optionClickHandler}
optionWidth={DROPDOWN_DIMENSION.WIDTH} optionWidth={DROPDOWN_DIMENSION.WIDTH}

View File

@ -23,7 +23,7 @@ function SelectedValueNode(props: DefaultDropDownValueNodeProps) {
const { selected } = props; const { selected } = props;
return ( return (
<SelectedValueNodeContainer> <SelectedValueNodeContainer>
<span className="label">{selected.label}</span> <span className="label">{(selected as DropdownOption).label}</span>
<ChevronDown /> <ChevronDown />
</SelectedValueNodeContainer> </SelectedValueNodeContainer>
); );

View File

@ -13,10 +13,10 @@ import SwitchControl, {
SwitchControlProps, SwitchControlProps,
} from "components/formControls/SwitchControl"; } from "components/formControls/SwitchControl";
import KeyValueArrayControl, { import KeyValueArrayControl, {
KeyValueArrayProps, KeyValueArrayControlProps,
} from "components/formControls/KeyValueArrayControl"; } from "components/formControls/KeyValueArrayControl";
import KeyValueInputControl, { import KeyValueInputControl, {
KeyValueInputProps, KeyValueInputControlProps,
} from "components/formControls/KeyValueInputControl"; } from "components/formControls/KeyValueInputControl";
import FilePickerControl, { import FilePickerControl, {
FilePickerControlProps, FilePickerControlProps,
@ -30,7 +30,6 @@ import CheckboxControl, {
import DynamicInputTextControl, { import DynamicInputTextControl, {
DynamicInputControlProps, DynamicInputControlProps,
} from "components/formControls/DynamicInputTextControl"; } from "components/formControls/DynamicInputTextControl";
import InputNumberControl from "components/formControls/InputNumberControl";
import FieldArrayControl, { import FieldArrayControl, {
FieldArrayControlProps, FieldArrayControlProps,
} from "components/formControls/FieldArrayControl"; } from "components/formControls/FieldArrayControl";
@ -52,6 +51,7 @@ class FormControlRegistry {
buildPropertyControl( buildPropertyControl(
controlProps: FixedKeyInputControlProps, controlProps: FixedKeyInputControlProps,
): JSX.Element { ): JSX.Element {
//TODO: may not be in use
return <FixedKeyInputControl {...controlProps} />; return <FixedKeyInputControl {...controlProps} />;
}, },
}); });
@ -66,17 +66,23 @@ class FormControlRegistry {
}, },
}); });
FormControlFactory.registerControlBuilder("KEYVALUE_ARRAY", { FormControlFactory.registerControlBuilder("KEYVALUE_ARRAY", {
buildPropertyControl(controlProps: KeyValueArrayProps): JSX.Element { buildPropertyControl(
controlProps: KeyValueArrayControlProps,
): JSX.Element {
return <KeyValueArrayControl {...controlProps} />; return <KeyValueArrayControl {...controlProps} />;
}, },
}); });
FormControlFactory.registerControlBuilder("FILE_PICKER", { FormControlFactory.registerControlBuilder("FILE_PICKER", {
buildPropertyControl(controlProps: FilePickerControlProps): JSX.Element { buildPropertyControl(controlProps: FilePickerControlProps): JSX.Element {
//used by redshift datasource
return <FilePickerControl {...controlProps} />; return <FilePickerControl {...controlProps} />;
}, },
}); });
FormControlFactory.registerControlBuilder("KEY_VAL_INPUT", { FormControlFactory.registerControlBuilder("KEY_VAL_INPUT", {
buildPropertyControl(controlProps: KeyValueInputProps): JSX.Element { //TODO: may not be in use, replace it with KeyValueArrayControl
buildPropertyControl(
controlProps: KeyValueInputControlProps,
): JSX.Element {
return <KeyValueInputControl {...controlProps} />; return <KeyValueInputControl {...controlProps} />;
}, },
}); });
@ -94,12 +100,13 @@ class FormControlRegistry {
}); });
FormControlFactory.registerControlBuilder("CHECKBOX", { FormControlFactory.registerControlBuilder("CHECKBOX", {
buildPropertyControl(controlProps: CheckboxControlProps): JSX.Element { buildPropertyControl(controlProps: CheckboxControlProps): JSX.Element {
//used in API datasource form only
return <CheckboxControl {...controlProps} />; return <CheckboxControl {...controlProps} />;
}, },
}); });
FormControlFactory.registerControlBuilder("NUMBER_INPUT", { FormControlFactory.registerControlBuilder("NUMBER_INPUT", {
buildPropertyControl(controlProps: InputControlProps): JSX.Element { buildPropertyControl(controlProps: InputControlProps): JSX.Element {
return <InputNumberControl {...controlProps} />; return <InputTextControl {...controlProps} />;
}, },
}); });
FormControlFactory.registerControlBuilder("ARRAY_FIELD", { FormControlFactory.registerControlBuilder("ARRAY_FIELD", {