feat: adding ads component for UQI (#8777)
This commit is contained in:
parent
7a0715af7c
commit
f6b9abdacf
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -4,6 +4,8 @@
|
|||
.env
|
||||
.vscode/*
|
||||
|
||||
app/client/cypress.env.json
|
||||
|
||||
# to ignore the node_modeules folder
|
||||
node_modules
|
||||
# to ignore the package-lock.json file
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ describe("API Panel Test Functionality", function() {
|
|||
cy.log("Creation of FirstAPI Action successful");
|
||||
cy.enterDatasourceAndPath(testdata.baseUrl, testdata.methods);
|
||||
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(".bp3-dialog")
|
||||
.find("button")
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
const testdata = require("../../../../fixtures/testdata.json");
|
||||
const apiwidget = require("../../../../locators/apiWidgetslocator.json");
|
||||
const commonlocators = require("../../../../locators/commonlocators.json");
|
||||
const dsl = require("../../../../fixtures/MultipleInput.json");
|
||||
const widgetsPage = require("../../../../locators/Widgets.json");
|
||||
const publish = require("../../../../locators/publishWidgetspage.json");
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ const datasource = require("../../../../locators/DatasourcesEditor.json");
|
|||
const datasourceEditor = require("../../../../locators/DatasourcesEditor.json");
|
||||
const datasourceFormData = require("../../../../fixtures/datasources.json");
|
||||
const queryLocators = require("../../../../locators/QueryEditor.json");
|
||||
const jsEditorLocators = require("../../../../locators/JSEditor.json");
|
||||
|
||||
describe("Undo/Redo functionality", function() {
|
||||
const modifierKey = Cypress.platform === "darwin" ? "meta" : "ctrl";
|
||||
|
|
|
|||
|
|
@ -29,12 +29,13 @@ describe("API Panel Test Functionality", function() {
|
|||
});
|
||||
|
||||
it("Shows which action failed on action fail.", function() {
|
||||
cy.wait(2000);
|
||||
cy.NavigateToAPI_Panel();
|
||||
cy.CreateAPI("PageLoadApi2");
|
||||
cy.enterDatasourceAndPath("https://abc.com", "users");
|
||||
cy.WaitAutoSave();
|
||||
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");
|
||||
|
||||
|
|
@ -44,6 +45,7 @@ describe("API Panel Test Functionality", function() {
|
|||
cy.wait("@updateLayout");
|
||||
|
||||
cy.reload();
|
||||
cy.wait(3000);
|
||||
cy.get(commonlocators.toastMsg).contains(
|
||||
`The action "PageLoadApi2" has failed.`,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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.get(queryLocators.templateMenu).click();
|
||||
cy.typeValueNValidate('{"find": "listingsAndReviews","limit": 10}');
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
});
|
||||
|
||||
// it("5. Validate Read file command, Verify possible error msgs, run & delete the query", () => {
|
||||
// cy.NavigateToActiveDSQueryPane(datasourceName);
|
||||
// cy.setQueryTimeout(30000);
|
||||
// cy.validateNSelectDropdown("Commands", "List files in bucket", "Read file");
|
||||
it("5. Validate Read file command, Verify possible error msgs, run & delete the query", () => {
|
||||
cy.NavigateToActiveDSQueryPane(datasourceName);
|
||||
cy.setQueryTimeout(30000);
|
||||
cy.validateNSelectDropdown("Commands", "List files in bucket", "Read file");
|
||||
|
||||
// cy.onlyQueryRun();
|
||||
// cy.wait("@postExecute").should(({ response }) => {
|
||||
// expect(response.body.data.isExecutionSuccess).to.eq(false);
|
||||
// expect(response.body.data.body).to.contains(
|
||||
// "Mandatory parameter 'Bucket Name' is missing.",
|
||||
// );
|
||||
// });
|
||||
// cy.typeValueNValidate("AutoTest", "Bucket Name");
|
||||
cy.onlyQueryRun();
|
||||
cy.wait("@postExecute").should(({ response }) => {
|
||||
expect(response.body.data.isExecutionSuccess).to.eq(false);
|
||||
expect(response.body.data.body).to.contains(
|
||||
"Mandatory parameter 'Bucket Name' is missing.",
|
||||
);
|
||||
});
|
||||
cy.typeValueNValidate("AutoTest", "Bucket Name");
|
||||
|
||||
// cy.onlyQueryRun();
|
||||
// cy.wait("@postExecute").then(({ response }) => {
|
||||
// expect(response.body.data.isExecutionSuccess).to.eq(false);
|
||||
// expect(response.body.data.body).to.contains(
|
||||
// "Required parameter 'File Path' is missing.",
|
||||
// );
|
||||
// });
|
||||
// cy.typeValueNValidate("Auto", "File Path");
|
||||
cy.onlyQueryRun();
|
||||
cy.wait("@postExecute").then(({ response }) => {
|
||||
expect(response.body.data.isExecutionSuccess).to.eq(false);
|
||||
expect(response.body.data.body).to.contains(
|
||||
"Required parameter 'File Path' is missing.",
|
||||
);
|
||||
});
|
||||
cy.typeValueNValidate("Auto", "File Path");
|
||||
|
||||
// cy.onlyQueryRun();
|
||||
// cy.wait("@postExecute").then(({ response }) => {
|
||||
// expect(response.body.data.isExecutionSuccess).to.eq(false);
|
||||
// expect(response.body.data.body.split("(")[0].trim()).to.be.oneOf([
|
||||
// "The specified bucket does not exist",
|
||||
// "The specified bucket is not valid.",
|
||||
// ]);
|
||||
// });
|
||||
cy.onlyQueryRun();
|
||||
cy.wait("@postExecute").then(({ response }) => {
|
||||
expect(response.body.data.isExecutionSuccess).to.eq(false);
|
||||
expect(response.body.data.body.split("(")[0].trim()).to.be.oneOf([
|
||||
"The specified bucket does not exist",
|
||||
"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.wait("@postExecute").then(({ response }) => {
|
||||
// expect(response.body.data.isExecutionSuccess).to.eq(false);
|
||||
// expect(response.body.data.body).to.contain(
|
||||
// "The specified key does not exist.",
|
||||
// );
|
||||
// });
|
||||
cy.onlyQueryRun();
|
||||
cy.wait("@postExecute").then(({ response }) => {
|
||||
expect(response.body.data.isExecutionSuccess).to.eq(false);
|
||||
expect(response.body.data.body).to.contain(
|
||||
"The specified key does not exist.",
|
||||
);
|
||||
});
|
||||
|
||||
// cy.typeValueNValidate("Autofile", "File Path");
|
||||
cy.typeValueNValidate("Autofile", "File Path");
|
||||
|
||||
// cy.onlyQueryRun();
|
||||
// cy.wait("@postExecute").then(({ response }) => {
|
||||
// expect(response.body.data.isExecutionSuccess).to.eq(false);
|
||||
// expect(response.body.data.body).to.contain(
|
||||
// "The specified key does not exist.",
|
||||
// );
|
||||
// });
|
||||
cy.onlyQueryRun();
|
||||
cy.wait("@postExecute").then(({ response }) => {
|
||||
expect(response.body.data.isExecutionSuccess).to.eq(false);
|
||||
expect(response.body.data.body).to.contain(
|
||||
"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
|
||||
// //cy.validateNSelectDropdown("File Data Type", "Base64", "Text / Binary");
|
||||
//Commenting below since below dropdown is removed from Read
|
||||
//cy.validateNSelectDropdown("File Data Type", "Base64", "Text / Binary");
|
||||
|
||||
// cy.onlyQueryRun();
|
||||
// cy.wait("@postExecute").then(({ response }) => {
|
||||
// expect(response.body.data.isExecutionSuccess).to.eq(true);
|
||||
// expect(response.body.data.body.fileData).to.not.eq(
|
||||
// "Hi, this is Automation script adding File!",
|
||||
// );
|
||||
// });
|
||||
cy.onlyQueryRun();
|
||||
cy.wait("@postExecute").then(({ response }) => {
|
||||
expect(response.body.data.isExecutionSuccess).to.eq(true);
|
||||
expect(response.body.data.body.fileData).to.not.eq(
|
||||
"Hi, this is Automation script adding File!",
|
||||
);
|
||||
});
|
||||
|
||||
// cy.validateNSelectDropdown("Base64 Encode File - Yes/No", "Yes", "No");
|
||||
// cy.onlyQueryRun();
|
||||
// cy.wait("@postExecute").then(({ response }) => {
|
||||
// expect(response.body.data.isExecutionSuccess).to.eq(true);
|
||||
// expect(response.body.data.body.fileData).to.eq(
|
||||
// "Hi, this is Automation script adding File!",
|
||||
// );
|
||||
// });
|
||||
// cy.deleteQueryUsingContext(); //exeute actions & 200 response is verified in this method
|
||||
// });
|
||||
cy.validateNSelectDropdown("Base64 Encode File - Yes/No", "Yes", "No");
|
||||
cy.onlyQueryRun();
|
||||
cy.wait("@postExecute").then(({ response }) => {
|
||||
expect(response.body.data.isExecutionSuccess).to.eq(true);
|
||||
expect(response.body.data.body.fileData).to.eq(
|
||||
"Hi, this is Automation script adding File!",
|
||||
);
|
||||
});
|
||||
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", () => {
|
||||
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
|
||||
});
|
||||
|
||||
it("11. Deletes the datasource", () => {
|
||||
it("13. Deletes the datasource", () => {
|
||||
cy.NavigateToQueryEditor();
|
||||
cy.NavigateToActiveTab();
|
||||
cy.contains(".t--datasource-name", datasourceName).click({ force: true });
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@
|
|||
"Redshift": ".t--plugin-name:contains('Redshift')",
|
||||
"AmazonS3": ".t--plugin-name:contains('S3')",
|
||||
"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",
|
||||
"clienID": "[data-cy='authentication.clientId'] input",
|
||||
"clientSecret": "[data-cy='authentication.clientSecret'] input",
|
||||
|
|
@ -47,7 +47,7 @@
|
|||
"serviceAccCredential": "[data-cy='datasourceConfiguration.authentication.password'] input",
|
||||
"grantType": "[data-cy='authentication.grantType']",
|
||||
"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')",
|
||||
"basic": "//div[contains(@class,'option') and text()='Basic']",
|
||||
"basicUsername": "input[name='authentication.username']",
|
||||
|
|
|
|||
|
|
@ -52,12 +52,12 @@
|
|||
"actionlist": ".action div div",
|
||||
"settings": "li:contains('Settings')",
|
||||
"headers": "li:contains('Headers')",
|
||||
"onPageLoad": "[data-cy=executeOnLoad]",
|
||||
"onPageLoad": "[name=executeOnLoad]",
|
||||
"renameEntity": ".single-select >div:contains('Edit Name')",
|
||||
"paramsTab": "//li//span[text()='Params']",
|
||||
"paramKey": ".t--actionConfiguration\\.queryParameters\\[0\\]\\.key\\.0",
|
||||
"paramValue": ".t--actionConfiguration\\.queryParameters\\[0\\]\\.value\\.0",
|
||||
"multipartTypeDropdown":"button:contains('Type')",
|
||||
"confirmBeforeExecute": "[data-cy=confirmBeforeExecute]",
|
||||
"confirmBeforeExecute": "[name=confirmBeforeExecute]",
|
||||
"runQueryButton": ".t--apiFormRunBtn"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -390,9 +390,9 @@ Cypress.Commands.add(
|
|||
"addOauthAuthDetails",
|
||||
(accessTokenUrl, clientId, clientSecret, authURL) => {
|
||||
cy.get(datasource.authType).click();
|
||||
cy.xpath(datasource.OAuth2).click();
|
||||
cy.get(datasource.OAuth2).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.clienID).type(clientId);
|
||||
cy.get(datasource.clientSecret).type(clientSecret);
|
||||
|
|
@ -3369,16 +3369,18 @@ Cypress.Commands.add(
|
|||
"validateNSelectDropdown",
|
||||
(ddTitle, currentValue, newValue) => {
|
||||
let toChange = false;
|
||||
cy.xpath('//div[contains(text(),"' + currentValue + '")]').should(
|
||||
cy.xpath('//span[contains(text(),"' + currentValue + '")]').should(
|
||||
"exist",
|
||||
currentValue + " dropdown value not present",
|
||||
);
|
||||
if (newValue) toChange = true;
|
||||
if (toChange) {
|
||||
cy.xpath(
|
||||
"//p[text()='" + ddTitle + "']/following-sibling::div/div",
|
||||
"//p[text()='" +
|
||||
ddTitle +
|
||||
"']/parent::label/following-sibling::div/div/div",
|
||||
).click(); //to expand the dropdown
|
||||
cy.xpath('//div[contains(text(),"' + newValue + '")]')
|
||||
cy.xpath('//span[contains(text(),"' + newValue + '")]')
|
||||
.last()
|
||||
.click({ force: true }); //to select the new value
|
||||
}
|
||||
|
|
@ -3387,19 +3389,17 @@ Cypress.Commands.add(
|
|||
|
||||
Cypress.Commands.add("typeValueNValidate", (valueToType, fieldName = "") => {
|
||||
if (fieldName) {
|
||||
cy.xpath("//p[text()='" + fieldName + "']/following-sibling::div").then(
|
||||
($field) => {
|
||||
cy.updateCodeInput($field, valueToType);
|
||||
},
|
||||
);
|
||||
cy.xpath(
|
||||
"//p[text()='" + fieldName + "']/parent::label/following-sibling::div",
|
||||
).then(($field) => {
|
||||
cy.updateCodeInput($field, valueToType);
|
||||
});
|
||||
} else {
|
||||
cy.xpath("//div[@class='CodeEditorTarget']").then(($field) => {
|
||||
cy.updateCodeInput($field, valueToType);
|
||||
});
|
||||
}
|
||||
|
||||
cy.EvaluateCurrentValue(valueToType);
|
||||
|
||||
// cy.xpath("//p[text()='" + fieldName + "']/following-sibling::div//div[@class='CodeMirror-code']//span/span").should((fieldValue) => {
|
||||
// textF = fieldValue.innerText
|
||||
// fieldValue.innerText = ""
|
||||
|
|
@ -3446,26 +3446,22 @@ Cypress.Commands.add(
|
|||
(fieldName = "", currentValue = "") => {
|
||||
let toValidate = false;
|
||||
if (currentValue) toValidate = true;
|
||||
|
||||
if (fieldName) {
|
||||
cy.xpath(
|
||||
"//p[text()='" +
|
||||
fieldName +
|
||||
"']/following-sibling::div//div[@class='CodeMirror-code']",
|
||||
"']/parent::label/following-sibling::div//div[@class='CodeMirror-code']",
|
||||
).click();
|
||||
} else {
|
||||
cy.xpath("//div[@class='CodeMirror-code']").click();
|
||||
}
|
||||
|
||||
cy.wait(2000);
|
||||
const val = cy
|
||||
.get(commonlocators.evaluatedCurrentValue)
|
||||
.first()
|
||||
.should("be.visible")
|
||||
.invoke("text");
|
||||
|
||||
if (toValidate) expect(val).to.eq(currentValue);
|
||||
|
||||
return val;
|
||||
},
|
||||
);
|
||||
|
|
|
|||
3
app/client/src/assets/icons/form/help-outline.svg
Normal file
3
app/client/src/assets/icons/form/help-outline.svg
Normal 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 |
|
|
@ -12,9 +12,10 @@ export type CheckboxProps = CommonComponentProps & {
|
|||
info?: string;
|
||||
backgroundColor?: string;
|
||||
fill?: boolean;
|
||||
name?: string;
|
||||
};
|
||||
|
||||
const Checkmark = styled.span<{
|
||||
export const Checkmark = styled.span<{
|
||||
disabled?: boolean;
|
||||
isChecked?: boolean;
|
||||
info?: string;
|
||||
|
|
@ -137,6 +138,7 @@ function Checkbox(props: CheckboxProps) {
|
|||
<input
|
||||
checked={checked}
|
||||
disabled={props.disabled}
|
||||
name={props?.name}
|
||||
onChange={(e: React.ChangeEvent<HTMLInputElement>) =>
|
||||
onChangeHandler(e.target.checked)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import styled from "constants/DefaultTheme";
|
|||
import SearchComponent from "components/designSystems/appsmith/SearchComponent";
|
||||
import { Colors } from "constants/Colors";
|
||||
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 Tooltip from "components/ads/Tooltip";
|
||||
import { isEllipsisActive } from "utils/helpers";
|
||||
|
|
@ -44,7 +46,7 @@ export interface DropdownSearchProps {
|
|||
|
||||
export interface RenderDropdownOptionType {
|
||||
index?: number;
|
||||
option: DropdownOption;
|
||||
option: DropdownOption | DropdownOption[];
|
||||
optionClickHandler?: (dropdownOption: DropdownOption) => void;
|
||||
isSelectedNode?: boolean;
|
||||
extraProps?: any;
|
||||
|
|
@ -63,8 +65,9 @@ type RenderOption = ({
|
|||
export type DropdownProps = CommonComponentProps &
|
||||
DropdownSearchProps & {
|
||||
options: DropdownOption[];
|
||||
selected: DropdownOption;
|
||||
selected: DropdownOption | DropdownOption[];
|
||||
onSelect?: DropdownOnSelect;
|
||||
isMultiSelect?: boolean;
|
||||
width?: string;
|
||||
height?: string;
|
||||
showLabelOnly?: boolean;
|
||||
|
|
@ -91,16 +94,19 @@ export type DropdownProps = CommonComponentProps &
|
|||
fillOptions?: boolean;
|
||||
dontUsePortal?: boolean;
|
||||
hideSubText?: boolean;
|
||||
removeSelectedOption?: DropdownOnSelect;
|
||||
boundary?: PopperBoundary;
|
||||
defaultIcon?: IconName;
|
||||
truncateOption?: boolean; // enabled wrapping and adding tooltip on option item of dropdown menu
|
||||
};
|
||||
export interface DefaultDropDownValueNodeProps {
|
||||
selected: DropdownOption;
|
||||
selected: DropdownOption | DropdownOption[];
|
||||
showLabelOnly?: boolean;
|
||||
isMultiSelect?: boolean;
|
||||
isOpen?: boolean;
|
||||
hasError?: boolean;
|
||||
renderNode?: RenderOption;
|
||||
selectedOptionClickHandler?: (option: DropdownOption) => void;
|
||||
placeholder?: string;
|
||||
showDropIcon?: boolean;
|
||||
optionWidth: string;
|
||||
|
|
@ -108,13 +114,13 @@ export interface DefaultDropDownValueNodeProps {
|
|||
}
|
||||
|
||||
export interface RenderDropdownOptionType {
|
||||
option: DropdownOption;
|
||||
option: DropdownOption | DropdownOption[];
|
||||
optionClickHandler?: (dropdownOption: DropdownOption) => void;
|
||||
}
|
||||
|
||||
export const DropdownContainer = styled.div<{ width: string; height?: string }>`
|
||||
width: ${(props) => props.width};
|
||||
height: ${(props) => props.height || `38px`};
|
||||
min-height: ${(props) => props.height};
|
||||
position: relative;
|
||||
span.bp3-popover-target {
|
||||
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<{
|
||||
isOpen: boolean;
|
||||
disabled?: boolean;
|
||||
|
|
@ -154,6 +195,7 @@ const Selected = styled.div<{
|
|||
hasError?: boolean;
|
||||
selected?: boolean;
|
||||
isLoading?: boolean;
|
||||
isMultiSelect?: boolean;
|
||||
}>`
|
||||
padding: ${(props) => props.theme.spaces[2]}px
|
||||
${(props) => props.theme.spaces[3]}px;
|
||||
|
|
@ -170,7 +212,7 @@ const Selected = styled.div<{
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: 100%;
|
||||
height: ${(props) => props.height};
|
||||
min-height: ${(props) => props.height};
|
||||
cursor: ${(props) =>
|
||||
props.disabled || props.isLoading ? "not-allowed" : "pointer"};
|
||||
${(props) =>
|
||||
|
|
@ -209,11 +251,11 @@ const Selected = styled.div<{
|
|||
}
|
||||
&:hover {
|
||||
background: ${(props) =>
|
||||
props.hasError
|
||||
? Colors.FAIR_PINK
|
||||
: props.theme.colors.dropdown.hovered.bg};
|
||||
border: 1px solid var(--appsmith-input-focus-border-color);
|
||||
}
|
||||
!props.isMultiSelect
|
||||
? props.hasError
|
||||
? Colors.FAIR_PINK
|
||||
: props.theme.colors.dropdown.hovered.bg
|
||||
: Colors.WHITE}
|
||||
`;
|
||||
|
||||
const DropdownSelect = styled.div``;
|
||||
|
|
@ -284,7 +326,6 @@ const OptionWrapper = styled.div<{
|
|||
align-items: center;
|
||||
min-height: 36px;
|
||||
background-color: ${(props) => (props.selected ? Colors.GREEN_3 : null)};
|
||||
|
||||
&&& svg {
|
||||
rect {
|
||||
fill: ${(props) => props.theme.colors.dropdownIconBg};
|
||||
|
|
@ -384,7 +425,7 @@ const SelectedDropDownHolder = styled.div`
|
|||
display: flex;
|
||||
align-items: center;
|
||||
min-width: 0;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
|
||||
& ${Text} {
|
||||
|
|
@ -403,7 +444,6 @@ const SelectedIcon = styled(Icon)`
|
|||
svg {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
|
||||
rect {
|
||||
fill: ${(props) => props.theme.colors.dropdownIconBg};
|
||||
rx: 0;
|
||||
|
|
@ -479,26 +519,64 @@ function TooltipWrappedText(
|
|||
function DefaultDropDownValueNode({
|
||||
hasError,
|
||||
hideSubText,
|
||||
isMultiSelect,
|
||||
optionWidth,
|
||||
placeholder,
|
||||
renderNode,
|
||||
selected,
|
||||
selectedOptionClickHandler,
|
||||
showDropIcon,
|
||||
showLabelOnly,
|
||||
}: DefaultDropDownValueNodeProps) {
|
||||
const LabelText = selected
|
||||
? showLabelOnly
|
||||
? selected.label
|
||||
: selected.value
|
||||
: placeholder
|
||||
? placeholder
|
||||
: "Please select a option.";
|
||||
const LabelText =
|
||||
!Array.isArray(selected) && selected
|
||||
? showLabelOnly
|
||||
? selected.label
|
||||
: selected.value
|
||||
: placeholder
|
||||
? placeholder
|
||||
: "Please select a option.";
|
||||
|
||||
function Label() {
|
||||
return hasError ? (
|
||||
<ErrorLabel>{LabelText}</ErrorLabel>
|
||||
) : (
|
||||
<Text type={TextType.P1}>{LabelText}</Text>
|
||||
);
|
||||
if (isMultiSelect && Array.isArray(selected) && selected.length) {
|
||||
return (
|
||||
<div style={{ display: "flex", width: "100%", flexWrap: "wrap" }}>
|
||||
{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 (
|
||||
|
|
@ -510,29 +588,33 @@ function DefaultDropDownValueNode({
|
|||
hasError,
|
||||
optionWidth,
|
||||
})
|
||||
) : isMultiSelect && Array.isArray(selected) && selected.length ? (
|
||||
<Label />
|
||||
) : (
|
||||
<>
|
||||
{selected?.icon ? (
|
||||
<SelectedIcon
|
||||
fillColor={hasError ? Colors.POMEGRANATE2 : selected?.iconColor}
|
||||
hoverFillColor={
|
||||
hasError ? Colors.POMEGRANATE2 : selected?.iconColor
|
||||
}
|
||||
name={selected.icon}
|
||||
size={selected.iconSize || IconSize.XL}
|
||||
/>
|
||||
) : null}
|
||||
<Label />
|
||||
{selected?.subText && !hideSubText ? (
|
||||
<StyledSubText
|
||||
className="sub-text"
|
||||
showDropIcon={showDropIcon}
|
||||
type={TextType.P1}
|
||||
>
|
||||
{selected.subText}
|
||||
</StyledSubText>
|
||||
) : null}
|
||||
</>
|
||||
!Array.isArray(selected) && (
|
||||
<>
|
||||
{selected?.icon ? (
|
||||
<SelectedIcon
|
||||
fillColor={hasError ? Colors.POMEGRANATE2 : selected?.iconColor}
|
||||
hoverFillColor={
|
||||
hasError ? Colors.POMEGRANATE2 : selected?.iconColor
|
||||
}
|
||||
name={selected.icon}
|
||||
size={selected.iconSize || IconSize.XL}
|
||||
/>
|
||||
) : null}
|
||||
<Label />
|
||||
{selected?.subText && !hideSubText ? (
|
||||
<StyledSubText
|
||||
className="sub-text"
|
||||
showDropIcon={showDropIcon}
|
||||
type={TextType.P1}
|
||||
>
|
||||
{selected.subText}
|
||||
</StyledSubText>
|
||||
) : null}
|
||||
</>
|
||||
)
|
||||
)}
|
||||
</SelectedDropDownHolder>
|
||||
);
|
||||
|
|
@ -542,8 +624,9 @@ interface DropdownOptionsProps extends DropdownProps, DropdownSearchProps {
|
|||
optionClickHandler: (option: DropdownOption) => void;
|
||||
renderOption?: RenderOption;
|
||||
headerLabel?: string;
|
||||
selected: DropdownOption;
|
||||
selected: DropdownOption | DropdownOption[];
|
||||
optionWidth: string;
|
||||
isMultiSelect?: boolean;
|
||||
}
|
||||
|
||||
export function RenderDropdownOptions(props: DropdownOptionsProps) {
|
||||
|
|
@ -594,12 +677,25 @@ export function RenderDropdownOptions(props: DropdownOptionsProps) {
|
|||
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 ? (
|
||||
<OptionWrapper
|
||||
className="t--dropdown-option"
|
||||
key={index}
|
||||
onClick={() => props.optionClickHandler(option)}
|
||||
selected={props.selected.value === option.value}
|
||||
selected={isSelected}
|
||||
>
|
||||
{option.leftElement && (
|
||||
<LeftIconWrapper>{option.leftElement}</LeftIconWrapper>
|
||||
|
|
@ -612,7 +708,15 @@ export function RenderDropdownOptions(props: DropdownOptionsProps) {
|
|||
size={option.iconSize || IconSize.XL}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{props.isMultiSelect ? (
|
||||
isSelected ? (
|
||||
<SquareBox backgroundColor="#f86a2b" borderColor="#f86a2b">
|
||||
<StyledCheckmark />
|
||||
</SquareBox>
|
||||
) : (
|
||||
<SquareBox borderColor="#a9a7a7" />
|
||||
)
|
||||
) : null}
|
||||
{props.showLabelOnly ? (
|
||||
props.truncateOption ? (
|
||||
<TooltipWrappedText
|
||||
|
|
@ -635,7 +739,6 @@ export function RenderDropdownOptions(props: DropdownOptionsProps) {
|
|||
) : (
|
||||
<Text type={TextType.P1}>{option.value}</Text>
|
||||
)}
|
||||
|
||||
{option.subText ? (
|
||||
<StyledSubText type={TextType.P3}>
|
||||
{option.subText}
|
||||
|
|
@ -664,10 +767,13 @@ export default function Dropdown(props: DropdownProps) {
|
|||
errorMsg = "",
|
||||
placeholder,
|
||||
helperText,
|
||||
removeSelectedOption,
|
||||
hasError,
|
||||
} = { ...props };
|
||||
const [isOpen, setIsOpen] = useState<boolean>(false);
|
||||
const [selected, setSelected] = useState<DropdownOption>(props.selected);
|
||||
const [selected, setSelected] = useState<DropdownOption | DropdownOption[]>(
|
||||
props.selected,
|
||||
);
|
||||
|
||||
const closeIfOpen = () => {
|
||||
if (isOpen) {
|
||||
|
|
@ -682,7 +788,21 @@ export default function Dropdown(props: DropdownProps) {
|
|||
|
||||
const optionClickHandler = useCallback(
|
||||
(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);
|
||||
onSelect && onSelect(option.value, option);
|
||||
option.onSelect && option.onSelect(option.value, option);
|
||||
|
|
@ -690,6 +810,20 @@ export default function Dropdown(props: DropdownProps) {
|
|||
[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 disabled = props.disabled || isLoading;
|
||||
const downIconColor = errorFlag ? Colors.POMEGRANATE2 : Colors.DARK_GRAY;
|
||||
|
|
@ -734,7 +868,8 @@ export default function Dropdown(props: DropdownProps) {
|
|||
className={props.className}
|
||||
disabled={props.disabled}
|
||||
hasError={errorFlag}
|
||||
height={props.height || "38px"}
|
||||
height={props.height || getMinHeight(props.isMultiSelect)}
|
||||
isMultiSelect={props.isMultiSelect}
|
||||
isOpen={isOpen}
|
||||
onClick={() => setIsOpen(!isOpen)}
|
||||
selected={!!selected}
|
||||
|
|
@ -742,14 +877,15 @@ export default function Dropdown(props: DropdownProps) {
|
|||
<SelectedValueNode
|
||||
hasError={errorFlag}
|
||||
hideSubText={props.hideSubText}
|
||||
isMultiSelect={props.isMultiSelect}
|
||||
optionWidth={dropdownOptionWidth}
|
||||
placeholder={placeholder}
|
||||
renderNode={renderOption}
|
||||
selected={selected}
|
||||
selectedOptionClickHandler={selectedOptionClickHandler}
|
||||
showDropIcon={showDropIcon}
|
||||
showLabelOnly={props.showLabelOnly}
|
||||
/>
|
||||
{}
|
||||
{isLoading ? (
|
||||
<Spinner size={IconSize.LARGE} />
|
||||
) : (
|
||||
|
|
@ -776,7 +912,7 @@ export default function Dropdown(props: DropdownProps) {
|
|||
<DropdownContainer
|
||||
className={props.containerClassName + " " + replayHighlightClass}
|
||||
data-cy={props.cypressSelector}
|
||||
height={props.height || "36px"}
|
||||
height={getMinHeight(props.isMultiSelect)}
|
||||
tabIndex={0}
|
||||
width={dropdownWidth}
|
||||
>
|
||||
|
|
@ -793,6 +929,7 @@ export default function Dropdown(props: DropdownProps) {
|
|||
{dropdownTrigger}
|
||||
<RenderDropdownOptions
|
||||
{...props}
|
||||
isMultiSelect={props.isMultiSelect}
|
||||
optionClickHandler={optionClickHandler}
|
||||
optionWidth={dropdownOptionWidth}
|
||||
selected={
|
||||
|
|
@ -805,3 +942,8 @@ export default function Dropdown(props: DropdownProps) {
|
|||
</DropdownContainer>
|
||||
);
|
||||
}
|
||||
|
||||
function getMinHeight(isMultiSelect?: boolean): string {
|
||||
if (isMultiSelect) return "44px";
|
||||
return "38px";
|
||||
}
|
||||
|
|
|
|||
|
|
@ -316,7 +316,7 @@ const TextInput = forwardRef(
|
|||
setInputValue(inputValue);
|
||||
const inputValueValidation =
|
||||
props.validator && props.validator(inputValue);
|
||||
if (inputValueValidation) {
|
||||
if (inputValueValidation && inputValueValidation.isValid) {
|
||||
props.validator && setValidation(inputValueValidation);
|
||||
return (
|
||||
inputValueValidation.isValid &&
|
||||
|
|
@ -410,6 +410,7 @@ const TextInput = forwardRef(
|
|||
data-cy={props.cypressSelector}
|
||||
hasLeftIcon={hasLeftIcon}
|
||||
inputRef={ref}
|
||||
name={props?.name}
|
||||
onBlur={onBlurHandler}
|
||||
onChange={memoizedChangeHandler}
|
||||
onFocus={onFocusHandler}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ export interface CommonComponentProps {
|
|||
isLoading?: boolean; //default false
|
||||
cypressSelector?: string;
|
||||
className?: string;
|
||||
name?: string;
|
||||
disabled?: boolean; //default false
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ export const EditorWrapper = styled.div<{
|
|||
top: 0;
|
||||
`
|
||||
: `position: relative;`}
|
||||
min-height: 35px;
|
||||
min-height: 38px;
|
||||
height: ${(props) => props.height || "auto"};
|
||||
background-color: ${(props) => editorBackground(props.editorTheme)};
|
||||
background-color: ${(props) => props.disabled && "#eef2f5"};
|
||||
|
|
@ -260,7 +260,7 @@ export const EditorWrapper = styled.div<{
|
|||
${(props) => {
|
||||
let height = props.height || "auto";
|
||||
if (props.size === EditorSize.COMPACT && !props.isFocused) {
|
||||
height = props.height || "35px";
|
||||
height = props.height || "38px";
|
||||
}
|
||||
return `height: ${height}`;
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
@ -41,6 +41,8 @@ export interface ControlProps extends ControlData, ControlFunctions {
|
|||
export interface ControlData {
|
||||
id: string;
|
||||
label: string;
|
||||
displayType?: "UI" | "JSON"; //used for switch to JSON view
|
||||
tooltipText?: string;
|
||||
configProperty: string;
|
||||
controlType: ControlType;
|
||||
propertyValue?: any;
|
||||
|
|
@ -48,11 +50,19 @@ export interface ControlData {
|
|||
validationMessage?: string;
|
||||
validationRegex?: string;
|
||||
dataType?: InputType;
|
||||
initialValue?: string | boolean | number;
|
||||
info?: string; //helper text
|
||||
isRequired?: boolean;
|
||||
conditionals: string;
|
||||
hidden?: HiddenType;
|
||||
placeholderText?: string;
|
||||
schema?: any;
|
||||
errorText?: string;
|
||||
showError?: boolean;
|
||||
encrypted?: boolean;
|
||||
subtitle?: string;
|
||||
url?: string;
|
||||
urlText?: string;
|
||||
logicalTypes?: string[];
|
||||
comparisonTypes?: string[];
|
||||
nestedLevels?: number;
|
||||
|
|
|
|||
|
|
@ -1,28 +1,53 @@
|
|||
import React from "react";
|
||||
import CheckboxField from "components/editorComponents/form/fields/CheckboxField";
|
||||
import Checkbox from "components/ads/Checkbox";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
import {
|
||||
Field,
|
||||
WrappedFieldInputProps,
|
||||
WrappedFieldMetaProps,
|
||||
} from "redux-form";
|
||||
import styled from "styled-components";
|
||||
|
||||
const StyledCheckbox = styled(CheckboxField)`
|
||||
&&& {
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
`;
|
||||
const StyledCheckbox = styled(Checkbox)``;
|
||||
|
||||
class CheckboxControl extends BaseControl<CheckboxControlProps> {
|
||||
getControlType(): ControlType {
|
||||
return "CHECKBOX";
|
||||
}
|
||||
|
||||
render() {
|
||||
const { configProperty, info, label } = this.props;
|
||||
|
||||
return <StyledCheckbox info={info} label={label} name={configProperty} />;
|
||||
return (
|
||||
<Field
|
||||
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 {
|
||||
info?: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,126 +1,84 @@
|
|||
import React from "react";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import styled from "styled-components";
|
||||
import { MenuItem } from "@blueprintjs/core";
|
||||
import { IItemRendererProps } from "@blueprintjs/select";
|
||||
import DropdownField from "components/editorComponents/form/fields/DropdownField";
|
||||
import { DropdownOption } from "components/constants";
|
||||
import Dropdown, { DropdownOption } from "components/ads/Dropdown";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
import { theme } from "constants/DefaultTheme";
|
||||
import FormLabel from "components/editorComponents/FormLabel";
|
||||
import { Colors } from "constants/Colors";
|
||||
import _ from "lodash";
|
||||
import {
|
||||
Field,
|
||||
WrappedFieldInputProps,
|
||||
WrappedFieldMetaProps,
|
||||
} from "redux-form";
|
||||
|
||||
const DropdownSelect = styled.div`
|
||||
font-size: 14px;
|
||||
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> {
|
||||
render() {
|
||||
const {
|
||||
configProperty,
|
||||
customStyles,
|
||||
isDisabled,
|
||||
isRequired,
|
||||
isSearchable,
|
||||
label,
|
||||
options,
|
||||
subtitle,
|
||||
} = this.props;
|
||||
|
||||
let width = "50vh";
|
||||
if (customStyles && customStyles.width) {
|
||||
width = customStyles.width;
|
||||
if (this.props.customStyles && this.props?.customStyles?.width) {
|
||||
width = this.props?.customStyles?.width;
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<FormLabel>
|
||||
{label} {isRequired && "*"}
|
||||
{subtitle && (
|
||||
<>
|
||||
<br />
|
||||
<StyledInfo>{subtitle}</StyledInfo>
|
||||
</>
|
||||
)}
|
||||
</FormLabel>
|
||||
<DropdownSelect data-cy={configProperty} style={{ width }}>
|
||||
<DropdownField
|
||||
customSelectStyles={customSelectStyles}
|
||||
isDisabled={isDisabled}
|
||||
isSearchable={isSearchable}
|
||||
name={configProperty}
|
||||
options={options}
|
||||
placeholder=""
|
||||
width={width}
|
||||
/>
|
||||
</DropdownSelect>
|
||||
</div>
|
||||
<DropdownSelect data-cy={this.props.configProperty} style={{ width }}>
|
||||
<Field
|
||||
component={renderDropdown}
|
||||
name={this.props.configProperty}
|
||||
options={this.props.options}
|
||||
props={{ ...this.props, width }}
|
||||
type={this.props?.isMultiSelect ? "select-multiple" : undefined}
|
||||
/>
|
||||
</DropdownSelect>
|
||||
);
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
options: DropdownOption[];
|
||||
placeholderText: string;
|
||||
propertyValue: string;
|
||||
subtitle?: string;
|
||||
isMultiSelect?: boolean;
|
||||
isDisabled?: boolean;
|
||||
isSearchable?: boolean;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import React from "react";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
import FormLabel from "components/editorComponents/FormLabel";
|
||||
import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField";
|
||||
import { AppState } from "reducers";
|
||||
import { formValueSelector } from "redux-form";
|
||||
|
|
@ -45,7 +44,7 @@ export function InputText(props: {
|
|||
inputType?: INPUT_TEXT_INPUT_TYPES;
|
||||
customStyles?: any;
|
||||
}) {
|
||||
const { actionName, inputType, isRequired, label, name, placeholder } = props;
|
||||
const { actionName, inputType, name, placeholder } = props;
|
||||
const dataTreePath = actionPathFromName(actionName, name);
|
||||
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) {
|
||||
customStyle = props.customStyles;
|
||||
customStyle = { ...props.customStyles };
|
||||
if (props.customStyles?.width) {
|
||||
customStyle.width = "50vh";
|
||||
}
|
||||
if (props.customStyles?.minHeight) {
|
||||
customStyle.minHeight = "34px";
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div style={customStyle}>
|
||||
<FormLabel>
|
||||
{label} {isRequired && "*"}
|
||||
</FormLabel>
|
||||
<StyledDynamicTextField
|
||||
dataTreePath={dataTreePath}
|
||||
name={name}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import { formValueSelector, change } from "redux-form";
|
|||
import { connect } from "react-redux";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
import FormLabel from "components/editorComponents/FormLabel";
|
||||
import DynamicTextField from "components/editorComponents/form/fields/DynamicTextField";
|
||||
import {
|
||||
EditorSize,
|
||||
|
|
@ -64,7 +63,6 @@ class DynamicTextControl extends BaseControl<
|
|||
actionName,
|
||||
configProperty,
|
||||
evaluationSubstitutionType,
|
||||
label,
|
||||
placeholderText,
|
||||
responseType,
|
||||
} = this.props;
|
||||
|
|
@ -81,7 +79,6 @@ class DynamicTextControl extends BaseControl<
|
|||
|
||||
return (
|
||||
<Wrapper>
|
||||
<FormLabel>{label}</FormLabel>
|
||||
{showTemplate ? (
|
||||
<TemplateMenu
|
||||
createTemplate={(templateString) => {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import Icon, { IconSize } from "components/ads/Icon";
|
|||
import { Classes } from "components/ads/common";
|
||||
import styled from "styled-components";
|
||||
import { FieldArray } from "redux-form";
|
||||
import FormLabel from "components/editorComponents/FormLabel";
|
||||
import { ControlProps } from "./BaseControl";
|
||||
|
||||
const CenteredIcon = styled(Icon)`
|
||||
|
|
@ -105,17 +104,14 @@ function NestedComponents(props: any) {
|
|||
}
|
||||
|
||||
export default function FieldArrayControl(props: FieldArrayControlProps) {
|
||||
const { configProperty, formName, label, schema } = props;
|
||||
const { configProperty, formName, schema } = props;
|
||||
return (
|
||||
<>
|
||||
<FormLabel>{label}</FormLabel>
|
||||
<FieldArray
|
||||
component={NestedComponents}
|
||||
name={configProperty}
|
||||
props={{ formName, schema }}
|
||||
rerenderOnEveryChange={false}
|
||||
/>
|
||||
</>
|
||||
<FieldArray
|
||||
component={NestedComponents}
|
||||
name={configProperty}
|
||||
props={{ formName, schema }}
|
||||
rerenderOnEveryChange={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,18 +1,20 @@
|
|||
import * as React from "react";
|
||||
import { WrappedFieldProps } from "redux-form";
|
||||
import "@uppy/core/dist/style.css";
|
||||
import "@uppy/dashboard/dist/style.css";
|
||||
import "@uppy/webcam/dist/style.css";
|
||||
import { Field } from "redux-form";
|
||||
import { useState } from "react";
|
||||
import styled from "styled-components";
|
||||
import Uppy from "@uppy/core";
|
||||
import Dashboard from "@uppy/dashboard";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
import FormLabel from "components/editorComponents/FormLabel";
|
||||
import { BaseButton } from "components/designSystems/appsmith/BaseButton";
|
||||
import { ButtonVariantTypes } from "components/constants";
|
||||
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";
|
||||
|
||||
const StyledDiv = styled.div`
|
||||
|
|
@ -29,7 +31,7 @@ const SelectButton = styled(BaseButton)`
|
|||
max-width: 59px;
|
||||
margin: 0 0px;
|
||||
min-height: 32px;
|
||||
border-radius: 0px 4px 4px 0px;
|
||||
border-radius: 0px;
|
||||
font-weight: bold;
|
||||
background-color: #fff;
|
||||
border-color: ${Colors.PRIMARY_ORANGE} !important;
|
||||
|
|
@ -48,85 +50,92 @@ const SelectButton = styled(BaseButton)`
|
|||
}
|
||||
`;
|
||||
|
||||
interface FieldFileInputState {
|
||||
text: string;
|
||||
}
|
||||
const FilePickerWrapper = styled.div`
|
||||
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> {
|
||||
uppy: any;
|
||||
function RenderFilePicker(props: RenderFilePickerProps) {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [appFileToBeUploaded, setAppFileToBeUploaded] = useState<{
|
||||
file: File;
|
||||
setProgress: SetProgress;
|
||||
} | null>(null);
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.refreshUppy();
|
||||
const FileUploader = useCallback(
|
||||
async (file: File, setProgress: SetProgress) => {
|
||||
if (!!file) {
|
||||
setAppFileToBeUploaded({
|
||||
file,
|
||||
setProgress,
|
||||
});
|
||||
} else {
|
||||
setAppFileToBeUploaded(null);
|
||||
}
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
this.state = {
|
||||
text: "Select file to upload",
|
||||
};
|
||||
}
|
||||
const onRemoveFile = useCallback(() => setAppFileToBeUploaded(null), []);
|
||||
|
||||
refreshUppy = () => {
|
||||
this.uppy = Uppy({
|
||||
id: "test",
|
||||
autoProceed: false,
|
||||
debug: false,
|
||||
restrictions: {
|
||||
maxNumberOfFiles: 1,
|
||||
},
|
||||
}).use(Dashboard, {
|
||||
hideUploadButton: true,
|
||||
});
|
||||
this.uppy.on("file-added", (file: any) => {
|
||||
const dslFiles = [];
|
||||
useEffect(() => {
|
||||
if (appFileToBeUploaded?.file) {
|
||||
const reader = new FileReader();
|
||||
reader.readAsDataURL(file.data);
|
||||
reader.readAsDataURL(appFileToBeUploaded?.file);
|
||||
reader.onloadend = () => {
|
||||
const base64data = reader.result;
|
||||
const newFile = {
|
||||
id: file.id,
|
||||
base64: base64data,
|
||||
blob: file.data,
|
||||
};
|
||||
dslFiles.push(newFile);
|
||||
this.uppy.getPlugin("Dashboard").closeModal();
|
||||
this.props.input.onChange({
|
||||
name: file.name,
|
||||
props.input?.onChange({
|
||||
name: appFileToBeUploaded?.file.name,
|
||||
base64Content: base64data,
|
||||
});
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
}, [appFileToBeUploaded]);
|
||||
|
||||
openModal = () => {
|
||||
// this.setState({ isOpen: true });
|
||||
this.uppy.getPlugin("Dashboard").openModal();
|
||||
};
|
||||
|
||||
render() {
|
||||
const {
|
||||
input: { value },
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
return (
|
||||
<>
|
||||
<div
|
||||
className={replayHighlightClass}
|
||||
style={{ flexDirection: "row", display: "flex", width: "50vh" }}
|
||||
>
|
||||
<StyledDiv>{value.name}</StyledDiv>
|
||||
<StyledDiv>{props?.input?.value?.name}</StyledDiv>
|
||||
<SelectButton
|
||||
buttonStyle="PRIMARY"
|
||||
buttonVariant={ButtonVariantTypes.SECONDARY}
|
||||
onClick={() => {
|
||||
this.openModal();
|
||||
setIsOpen(true);
|
||||
}}
|
||||
text={"Select"}
|
||||
/>
|
||||
</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> {
|
||||
constructor(props: FilePickerControlProps) {
|
||||
super(props);
|
||||
|
|
@ -136,16 +145,8 @@ class FilePickerControl extends BaseControl<FilePickerControlProps> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { configProperty, isRequired, label } = this.props;
|
||||
|
||||
return (
|
||||
<>
|
||||
<FormLabel>
|
||||
{label} {isRequired && "*"}
|
||||
</FormLabel>
|
||||
<Field component={FieldFileInput} name={configProperty} />
|
||||
</>
|
||||
);
|
||||
const { configProperty } = this.props;
|
||||
return <Field component={RenderFilePicker} name={configProperty} />;
|
||||
}
|
||||
|
||||
getControlType(): ControlType {
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ import BaseControl, { ControlProps } from "./BaseControl";
|
|||
import { InputType } from "components/constants";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
import TextField from "components/editorComponents/form/fields/TextField";
|
||||
import FormLabel from "components/editorComponents/FormLabel";
|
||||
import styled from "styled-components";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
|
|
@ -12,20 +11,10 @@ const Wrapper = styled.div`
|
|||
|
||||
class FixKeyInputControl extends BaseControl<FixedKeyInputControlProps> {
|
||||
render() {
|
||||
const {
|
||||
configProperty,
|
||||
dataType,
|
||||
fixedKey,
|
||||
isRequired,
|
||||
label,
|
||||
placeholderText,
|
||||
} = this.props;
|
||||
const { configProperty, dataType, fixedKey, placeholderText } = this.props;
|
||||
|
||||
return (
|
||||
<Wrapper>
|
||||
<FormLabel>
|
||||
{label} {isRequired && "*"}
|
||||
</FormLabel>
|
||||
<TextField
|
||||
format={(value) => {
|
||||
// Get the value property
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
import React from "react";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
import TextField from "components/editorComponents/form/fields/TextField";
|
||||
import FormLabel from "components/editorComponents/FormLabel";
|
||||
import { FormIcons } from "icons/FormIcons";
|
||||
import TextInput from "components/ads/TextInput";
|
||||
import { Colors } from "constants/Colors";
|
||||
import styled from "styled-components";
|
||||
import { InputType } from "components/constants";
|
||||
import {
|
||||
Field,
|
||||
WrappedFieldMetaProps,
|
||||
WrappedFieldInputProps,
|
||||
} from "redux-form";
|
||||
|
||||
export const StyledInfo = styled.span`
|
||||
font-weight: normal;
|
||||
|
|
@ -29,51 +32,52 @@ export function InputText(props: {
|
|||
encrypted?: boolean;
|
||||
disabled?: boolean;
|
||||
}) {
|
||||
const {
|
||||
dataType,
|
||||
disabled,
|
||||
encrypted,
|
||||
isRequired,
|
||||
label,
|
||||
name,
|
||||
placeholder,
|
||||
subtitle,
|
||||
} = props;
|
||||
const { dataType, disabled, name, placeholder } = props;
|
||||
|
||||
return (
|
||||
<div data-cy={name} style={{ width: "50vh" }}>
|
||||
<FormLabel>
|
||||
{label} {isRequired && "*"}{" "}
|
||||
{encrypted && (
|
||||
<>
|
||||
<FormIcons.LOCK_ICON height={12} keepColors width={12} />
|
||||
<StyledInfo>Encrypted</StyledInfo>
|
||||
</>
|
||||
)}
|
||||
{subtitle && (
|
||||
<>
|
||||
<br />
|
||||
<StyledInfo>{subtitle}</StyledInfo>
|
||||
</>
|
||||
)}
|
||||
</FormLabel>
|
||||
<TextField
|
||||
<Field
|
||||
component={renderComponent}
|
||||
datatype={dataType}
|
||||
disabled={disabled || false}
|
||||
name={name}
|
||||
placeholder={placeholder}
|
||||
showError
|
||||
type={dataType}
|
||||
{...props}
|
||||
asyncControl
|
||||
/>
|
||||
</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> {
|
||||
render() {
|
||||
const {
|
||||
configProperty,
|
||||
dataType,
|
||||
disabled,
|
||||
encrypted,
|
||||
isValid,
|
||||
label,
|
||||
placeholderText,
|
||||
|
|
@ -86,7 +90,7 @@ class InputTextControl extends BaseControl<InputControlProps> {
|
|||
<InputText
|
||||
dataType={this.getType(dataType)}
|
||||
disabled={disabled}
|
||||
encrypted={this.props.encrypted}
|
||||
encrypted={encrypted}
|
||||
isValid={isValid}
|
||||
label={label}
|
||||
name={configProperty}
|
||||
|
|
|
|||
|
|
@ -1,15 +1,28 @@
|
|||
import React, { useEffect, useCallback, JSXElementConstructor } from "react";
|
||||
import { FieldArray, WrappedFieldArrayProps } from "redux-form";
|
||||
import React, { useEffect, useCallback } from "react";
|
||||
import {
|
||||
Field,
|
||||
FieldArray,
|
||||
WrappedFieldArrayProps,
|
||||
WrappedFieldMetaProps,
|
||||
WrappedFieldInputProps,
|
||||
} from "redux-form";
|
||||
import styled from "styled-components";
|
||||
import { Icon } from "@blueprintjs/core";
|
||||
import { FormIcons } from "icons/FormIcons";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import TextField from "components/editorComponents/form/fields/TextField";
|
||||
import BaseControl, { ControlProps, ControlData } from "./BaseControl";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
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 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`
|
||||
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-outer-spin-button {
|
||||
-webkit-appearance: none;
|
||||
margin: 0;
|
||||
margin: 0px;
|
||||
}
|
||||
`;
|
||||
|
||||
function KeyValueRow(props: KeyValueArrayProps & WrappedFieldArrayProps) {
|
||||
function KeyValueRow(
|
||||
props: KeyValueArrayControlProps & WrappedFieldArrayProps,
|
||||
) {
|
||||
const { extraData = [] } = props;
|
||||
const keyName = getFieldName(extraData[0].configProperty);
|
||||
const valueName = getFieldName(extraData[1].configProperty);
|
||||
const valueDataType = getType(extraData[1].dataType);
|
||||
const keyName = getFieldName(extraData[0]?.configProperty);
|
||||
const valueName = getFieldName(extraData[1]?.configProperty);
|
||||
const keyFieldProps = extraData[0];
|
||||
|
||||
let isRequired: boolean | undefined;
|
||||
|
||||
useEffect(() => {
|
||||
// Always maintain 1 row
|
||||
if (props.fields.length < 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]);
|
||||
|
|
@ -58,87 +75,96 @@ function KeyValueRow(props: KeyValueArrayProps & WrappedFieldArrayProps) {
|
|||
|
||||
const keyFieldValidate = useCallback(
|
||||
(value: string) => {
|
||||
if (value && keyFieldProps.validationRegex) {
|
||||
const regex = new RegExp(keyFieldProps.validationRegex);
|
||||
if (value && keyFieldProps?.validationRegex) {
|
||||
const regex = new RegExp(keyFieldProps?.validationRegex);
|
||||
|
||||
return regex.test(value) ? undefined : keyFieldProps.validationMessage;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
},
|
||||
[keyFieldProps.validationRegex, keyFieldProps.validationMessage],
|
||||
[keyFieldProps?.validationRegex, keyFieldProps?.validationMessage],
|
||||
);
|
||||
|
||||
if (extraData) {
|
||||
isRequired = extraData[0].isRequired || extraData[1].isRequired;
|
||||
}
|
||||
const maxLen = props.maxLen;
|
||||
//if maxLen exists apply a check on the length
|
||||
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" ? (
|
||||
<>
|
||||
{props.fields.map((field: any, index: number) => {
|
||||
const otherProps: Record<string, any> = {};
|
||||
if (
|
||||
props.actionConfig &&
|
||||
props.actionConfig[index].description &&
|
||||
props.rightIcon
|
||||
) {
|
||||
otherProps.rightIcon = (
|
||||
<HelperTooltip
|
||||
description={props.actionConfig[index].description}
|
||||
rightIcon={
|
||||
props.actionConfig[index].description && props.rightIcon
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
let keyTextFieldName = `${field}.key`;
|
||||
let valueTextFieldName = `${field}.value`;
|
||||
|
||||
if (keyName && Array.isArray(keyName) && keyName?.length)
|
||||
keyTextFieldName = `${field}.${keyName[1]}`;
|
||||
|
||||
if (valueName && Array.isArray(valueName) && valueName?.length)
|
||||
valueTextFieldName = `${field}.${valueName[1]}`;
|
||||
|
||||
return (
|
||||
<FormRowWithLabel key={index} style={{ marginTop: 16 }}>
|
||||
<FormRowWithLabel
|
||||
key={index}
|
||||
style={{ marginTop: index > 0 ? "16px" : "0px" }}
|
||||
>
|
||||
<div
|
||||
data-replay-id={btoa(`${field}.${keyName[1]}`)}
|
||||
data-replay-id={btoa(keyTextFieldName)}
|
||||
style={{ width: "50vh" }}
|
||||
>
|
||||
<FormLabel>
|
||||
{extraData && extraData[0].label} {isRequired && "*"}
|
||||
</FormLabel>
|
||||
<TextField
|
||||
name={`${field}.${keyName[1]}`}
|
||||
placeholder={(extraData && extraData[0].placeholderText) || ""}
|
||||
showError
|
||||
validate={keyFieldValidate}
|
||||
<Field
|
||||
component={renderTextInput}
|
||||
name={keyTextFieldName}
|
||||
props={{
|
||||
dataType: getType(extraData[0]?.dataType),
|
||||
defaultValue: props.initialValue,
|
||||
keyFieldValidate,
|
||||
placeholder: props.extraData
|
||||
? props.extraData[1]?.placeholderText
|
||||
: "",
|
||||
isRequired: extraData[0]?.isRequired,
|
||||
name: keyTextFieldName,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{!props.actionConfig && (
|
||||
<div style={{ marginLeft: 16 }}>
|
||||
<FormLabel>
|
||||
{extraData && extraData[1].label} {isRequired && "*"}
|
||||
</FormLabel>
|
||||
<div style={{ display: "flex", flexDirection: "row" }}>
|
||||
<div
|
||||
data-replay-id={btoa(`${field}.${valueName[1]}`)}
|
||||
style={{ marginRight: 14, width: 72 }}
|
||||
>
|
||||
<StyledTextField
|
||||
name={`${field}.${valueName[1]}`}
|
||||
placeholder={
|
||||
(extraData && extraData[1].placeholderText) || ""
|
||||
}
|
||||
type={valueDataType}
|
||||
/>
|
||||
</div>
|
||||
{index === props.fields.length - 1 ? (
|
||||
<div style={{ marginLeft: "16px", width: "50vh" }}>
|
||||
<div
|
||||
data-replay-id={valueTextFieldName}
|
||||
style={{ display: "flex", flexDirection: "row" }}
|
||||
>
|
||||
<Field
|
||||
component={renderTextInput}
|
||||
name={valueTextFieldName}
|
||||
props={{
|
||||
dataType: getType(extraData[1]?.dataType),
|
||||
defaultValue: props.initialValue,
|
||||
placeholder: props.extraData
|
||||
? props.extraData[1]?.placeholderText
|
||||
: "",
|
||||
name: valueTextFieldName,
|
||||
isRequired: extraData[1]?.isRequired,
|
||||
}}
|
||||
/>
|
||||
{showAddIcon(index) ? (
|
||||
<Icon
|
||||
className="t--add-field"
|
||||
color={Colors["CADET_BLUE"]}
|
||||
icon="plus"
|
||||
iconSize={20}
|
||||
onClick={() => props.fields.push({ key: "", value: "" })}
|
||||
style={{ alignSelf: "center" }}
|
||||
onClick={() => {
|
||||
props.fields.push({ key: "", value: "" });
|
||||
}}
|
||||
style={{ marginLeft: "16px", alignSelf: "center" }}
|
||||
/>
|
||||
) : (
|
||||
<FormIcons.DELETE_ICON
|
||||
className="t--delete-field"
|
||||
color={Colors["CADET_BLUE"]}
|
||||
height={20}
|
||||
onClick={() => props.fields.remove(index)}
|
||||
style={{ alignSelf: "center" }}
|
||||
style={{ marginLeft: "16px", alignSelf: "center" }}
|
||||
width={20}
|
||||
/>
|
||||
)}
|
||||
|
|
@ -155,7 +181,6 @@ function KeyValueRow(props: KeyValueArrayProps & WrappedFieldArrayProps) {
|
|||
? `Value (Type: ${props.actionConfig[index].type})`
|
||||
: `Value (optional)`
|
||||
}
|
||||
{...otherProps}
|
||||
/>
|
||||
)}
|
||||
</FormRowWithLabel>
|
||||
|
|
@ -165,7 +190,7 @@ function KeyValueRow(props: KeyValueArrayProps & WrappedFieldArrayProps) {
|
|||
) : null;
|
||||
}
|
||||
|
||||
class KeyValueFieldArray extends BaseControl<KeyValueArrayProps> {
|
||||
class KeyValueArrayControl extends BaseControl<KeyValueArrayControlProps> {
|
||||
render() {
|
||||
const name = getFieldName(this.props.configProperty);
|
||||
|
||||
|
|
@ -174,7 +199,7 @@ class KeyValueFieldArray extends BaseControl<KeyValueArrayProps> {
|
|||
component={KeyValueRow}
|
||||
rerenderOnEveryChange={false}
|
||||
{...this.props}
|
||||
name={name[0]}
|
||||
name={name ? name[0] : ""}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
|
@ -184,8 +209,8 @@ class KeyValueFieldArray extends BaseControl<KeyValueArrayProps> {
|
|||
}
|
||||
}
|
||||
|
||||
const getFieldName = (configProperty: string) => {
|
||||
return configProperty.split("[*].");
|
||||
const getFieldName = (configProperty: string): string[] | undefined => {
|
||||
if (configProperty) return configProperty.split("[*].");
|
||||
};
|
||||
|
||||
const getType = (dataType: string | undefined) => {
|
||||
|
|
@ -199,12 +224,34 @@ const getType = (dataType: string | undefined) => {
|
|||
}
|
||||
};
|
||||
|
||||
export interface KeyValueArrayProps extends ControlProps {
|
||||
name: string;
|
||||
label: string;
|
||||
rightIcon?: JSXElementConstructor<{ height: number; width: number }>;
|
||||
description?: string;
|
||||
actionConfig?: any;
|
||||
function renderTextInput(
|
||||
props: TextInputProps & {
|
||||
dataType?: "text" | "number" | "password";
|
||||
placeholder?: string;
|
||||
defaultValue: string | number;
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -6,9 +6,28 @@ import { FormIcons } from "icons/FormIcons";
|
|||
import BaseControl, { ControlProps, ControlData } from "./BaseControl";
|
||||
import TextField from "components/editorComponents/form/fields/TextField";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
import FormLabel from "components/editorComponents/FormLabel";
|
||||
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`
|
||||
display: flex;
|
||||
flex: 1;
|
||||
|
|
@ -18,7 +37,7 @@ const FormRowWithLabel = styled.div`
|
|||
}
|
||||
`;
|
||||
|
||||
function KeyValueRow(props: Props & WrappedFieldArrayProps) {
|
||||
function KeyValueRow(props: KeyValueRowProps & WrappedFieldArrayProps) {
|
||||
useEffect(() => {
|
||||
// Always maintain 1 row
|
||||
if (props.fields.length < 1) {
|
||||
|
|
@ -32,21 +51,16 @@ function KeyValueRow(props: Props & WrappedFieldArrayProps) {
|
|||
<div>
|
||||
{typeof props.fields.getAll() === "object" && (
|
||||
<div>
|
||||
<FormLabel>
|
||||
{props.label} {props.isRequired && "*"}
|
||||
</FormLabel>
|
||||
{props.fields.map((field: any, index: number) => (
|
||||
<FormRowWithLabel key={index} style={{ marginTop: index ? 13 : 0 }}>
|
||||
<div
|
||||
data-replay-id={btoa(`${field}.key`)}
|
||||
style={{ width: "50vh" }}
|
||||
>
|
||||
{/* <FormLabel></FormLabel> */}
|
||||
<TextField name={`${field}.key`} placeholder="Key" />
|
||||
</div>
|
||||
|
||||
<div style={{ marginLeft: 16 }}>
|
||||
{/* <FormLabel></FormLabel> */}
|
||||
<div style={{ display: "flex", flexDirection: "row" }}>
|
||||
<div
|
||||
data-replay-id={btoa(`${field}.value`)}
|
||||
|
|
@ -83,17 +97,7 @@ function KeyValueRow(props: Props & WrappedFieldArrayProps) {
|
|||
);
|
||||
}
|
||||
|
||||
type Props = {
|
||||
name: string;
|
||||
label: string;
|
||||
rightIcon?: JSXElementConstructor<{ height: number; width: number }>;
|
||||
description?: string;
|
||||
actionConfig?: any;
|
||||
extraData?: ControlData[];
|
||||
isRequired?: boolean;
|
||||
};
|
||||
|
||||
class KeyValueFieldInput extends BaseControl<KeyValueInputProps> {
|
||||
class KeyValueFieldInputControl extends BaseControl<KeyValueInputControlProps> {
|
||||
render() {
|
||||
return (
|
||||
<FieldArray
|
||||
|
|
@ -110,12 +114,4 @@ class KeyValueFieldInput extends BaseControl<KeyValueInputProps> {
|
|||
}
|
||||
}
|
||||
|
||||
export interface KeyValueInputProps extends ControlProps {
|
||||
name: string;
|
||||
label: string;
|
||||
rightIcon?: JSXElementConstructor<{ height: number; width: number }>;
|
||||
description?: string;
|
||||
actionConfig?: any;
|
||||
}
|
||||
|
||||
export default KeyValueFieldInput;
|
||||
export default KeyValueFieldInputControl;
|
||||
|
|
|
|||
|
|
@ -1,19 +1,29 @@
|
|||
import React from "react";
|
||||
import BaseControl, { ControlProps } from "./BaseControl";
|
||||
import { StyledSwitch } from "./StyledControls";
|
||||
import Toggle from "components/ads/Toggle";
|
||||
import { ControlType } from "constants/PropertyControlConstants";
|
||||
import FormLabel from "components/editorComponents/FormLabel";
|
||||
import { Field, WrappedFieldProps } from "redux-form";
|
||||
import styled from "styled-components";
|
||||
|
||||
type Props = WrappedFieldProps & {
|
||||
type SwitchFieldProps = WrappedFieldProps & {
|
||||
label: string;
|
||||
isRequired: boolean;
|
||||
info: string;
|
||||
};
|
||||
|
||||
const StyledFormLabel = styled(FormLabel)`
|
||||
margin-bottom: 0px;
|
||||
const StyledToggle = styled(Toggle)`
|
||||
.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`
|
||||
|
|
@ -26,14 +36,7 @@ const SwitchWrapped = styled.div`
|
|||
max-width: 60vw;
|
||||
`;
|
||||
|
||||
const Info = styled.div`
|
||||
font-size: 12px;
|
||||
opacity: 0.7;
|
||||
margin-top: 8px;
|
||||
max-width: 60vw;
|
||||
`;
|
||||
|
||||
export class SwitchField extends React.Component<Props, any> {
|
||||
export class SwitchField extends React.Component<SwitchFieldProps, any> {
|
||||
get value() {
|
||||
const { input } = this.props;
|
||||
if (typeof input.value !== "string") return !!input.value;
|
||||
|
|
@ -44,21 +47,18 @@ export class SwitchField extends React.Component<Props, any> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const { info, input, isRequired, label } = this.props;
|
||||
|
||||
return (
|
||||
<div>
|
||||
<SwitchWrapped data-cy={this.props.input.name}>
|
||||
<StyledFormLabel>
|
||||
{label} {isRequired && "*"}
|
||||
</StyledFormLabel>
|
||||
<StyledSwitch
|
||||
checked={this.value}
|
||||
large
|
||||
onChange={(value) => input.onChange(value)}
|
||||
<StyledToggle
|
||||
className="switch-control"
|
||||
name={this.props.input.name}
|
||||
onToggle={(value: any) => {
|
||||
this.props.input.onChange(value);
|
||||
}}
|
||||
value={this.value}
|
||||
/>
|
||||
</SwitchWrapped>
|
||||
{info && <Info>{info}</Info>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import Icon, { IconSize } from "components/ads/Icon";
|
|||
import { Classes } from "components/ads/common";
|
||||
import styled from "styled-components";
|
||||
import { FieldArray, getFormValues } from "redux-form";
|
||||
import FormLabel from "components/editorComponents/FormLabel";
|
||||
import { ControlProps } from "./BaseControl";
|
||||
import _ from "lodash";
|
||||
import { useSelector } from "react-redux";
|
||||
|
|
@ -64,7 +63,7 @@ const CenteredIcon = styled(Icon)`
|
|||
// Outer box that houses the whole component
|
||||
const PrimaryBox = styled.div`
|
||||
display: flex;
|
||||
width: 105vh;
|
||||
width: min-content;
|
||||
flex-direction: column;
|
||||
border: 2px solid ${(props) => props.theme.colors.apiPane.dividerBg};
|
||||
padding: 10px;
|
||||
|
|
@ -216,7 +215,7 @@ function ConditionBlock(props: any) {
|
|||
isDisabled = true;
|
||||
}
|
||||
return (
|
||||
<PrimaryBox style={{ width: `${props.maxWidth}vh`, marginTop }}>
|
||||
<PrimaryBox style={{ marginTop }}>
|
||||
<SecondaryBox>
|
||||
{/* Component to render the joining operator between multiple conditions */}
|
||||
<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 */}
|
||||
|
|
@ -320,7 +317,6 @@ export default function WhereClauseControl(props: WhereClauseControlProps) {
|
|||
comparisonTypes, // All possible keys for the comparison
|
||||
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
|
||||
label, // Label for the where clause
|
||||
logicalTypes, // All possible keys for the logical operators joining multiple conditions
|
||||
nestedLevels, // Number of nested levels allowed
|
||||
} = 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
|
||||
const maxWidth = 105;
|
||||
return (
|
||||
<>
|
||||
<FormLabel>{label}</FormLabel>
|
||||
<FieldArray
|
||||
component={ConditionBlock}
|
||||
key={`${configProperty}.children`}
|
||||
name={`${configProperty}.children`}
|
||||
props={{
|
||||
configProperty,
|
||||
maxWidth,
|
||||
formName,
|
||||
logicalTypes,
|
||||
comparisonTypes,
|
||||
nestedLevels,
|
||||
currentNestingLevel: 0,
|
||||
}}
|
||||
rerenderOnEveryChange={false}
|
||||
/>
|
||||
</>
|
||||
<FieldArray
|
||||
component={ConditionBlock}
|
||||
key={`${configProperty}.children`}
|
||||
name={`${configProperty}.children`}
|
||||
props={{
|
||||
configProperty,
|
||||
maxWidth,
|
||||
formName,
|
||||
logicalTypes,
|
||||
comparisonTypes,
|
||||
nestedLevels,
|
||||
currentNestingLevel: 0,
|
||||
}}
|
||||
rerenderOnEveryChange={false}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { Icon } from "@blueprintjs/core";
|
|||
import { IconNames } from "@blueprintjs/icons";
|
||||
import { IconProps, IconWrapper } from "constants/IconConstants";
|
||||
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 LockIcon } from "assets/icons/form/lock.svg";
|
||||
|
||||
|
|
@ -18,6 +19,11 @@ export const FormIcons: {
|
|||
<InfoIcon />
|
||||
</IconWrapper>
|
||||
),
|
||||
HELP_ICON: (props: IconProps) => (
|
||||
<IconWrapper {...props}>
|
||||
<HelpIcon />
|
||||
</IconWrapper>
|
||||
),
|
||||
HOME_ICON: (props: IconProps) => (
|
||||
<IconWrapper {...props}>
|
||||
<Icon color={props.color} icon={IconNames.HOME} iconSize={props.height} />
|
||||
|
|
|
|||
|
|
@ -12,11 +12,8 @@ import {
|
|||
reduxForm,
|
||||
} from "redux-form";
|
||||
import AnalyticsUtil from "utils/AnalyticsUtil";
|
||||
import InputTextControl, {
|
||||
StyledInfo,
|
||||
} from "components/formControls/InputTextControl";
|
||||
import KeyValueInputControl from "components/formControls/KeyValueInputControl";
|
||||
import DropDownControl from "components/formControls/DropDownControl";
|
||||
import FormControl from "pages/Editor/FormControl";
|
||||
import { StyledInfo } from "components/formControls/InputTextControl";
|
||||
import CenteredWrapper from "components/designSystems/appsmith/CenteredWrapper";
|
||||
import { connect } from "react-redux";
|
||||
import { AppState } from "reducers";
|
||||
|
|
@ -127,7 +124,6 @@ export const Header = styled.div`
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
//margin-top: 16px;
|
||||
`;
|
||||
|
||||
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> {
|
||||
componentDidMount() {
|
||||
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} />
|
||||
))}
|
||||
<FormInputContainer data-replay-id={btoa("url")}>
|
||||
<InputTextControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="url"
|
||||
isRequired
|
||||
label="URL"
|
||||
placeholderText="https://example.com"
|
||||
/>
|
||||
{this.renderInputTextControlViaFormControl(
|
||||
"url",
|
||||
"URL",
|
||||
"https://example.com",
|
||||
"TEXT",
|
||||
false,
|
||||
true,
|
||||
)}
|
||||
</FormInputContainer>
|
||||
<FormInputContainer data-replay-id={btoa("headers")}>
|
||||
<KeyValueInputControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="headers"
|
||||
label="Headers"
|
||||
/>
|
||||
{this.renderKeyValueControlViaFormControl(
|
||||
"headers",
|
||||
"Headers",
|
||||
"",
|
||||
false,
|
||||
)}
|
||||
</FormInputContainer>
|
||||
|
||||
<FormInputContainer>
|
||||
<KeyValueInputControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="queryParameters"
|
||||
label="Query Parameters"
|
||||
/>
|
||||
<FormInputContainer data-replay-id={btoa("queryParameters")}>
|
||||
{this.renderKeyValueControlViaFormControl(
|
||||
"queryParameters",
|
||||
"Query Parameters",
|
||||
"",
|
||||
false,
|
||||
)}
|
||||
</FormInputContainer>
|
||||
<FormInputContainer data-replay-id={btoa("isSendSessionEnabled")}>
|
||||
<DropDownControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="isSendSessionEnabled"
|
||||
isRequired
|
||||
label="Send Appsmith signature header"
|
||||
options={[
|
||||
{this.renderDropdownControlViaFormControl(
|
||||
"isSendSessionEnabled",
|
||||
[
|
||||
{
|
||||
label: "Yes",
|
||||
value: true,
|
||||
|
|
@ -418,28 +405,29 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
|
|||
label: "No",
|
||||
value: false,
|
||||
},
|
||||
]}
|
||||
placeholderText=""
|
||||
propertyValue=""
|
||||
subtitle="Header key: X-APPSMITH-SIGNATURE"
|
||||
/>
|
||||
],
|
||||
"Send Appsmith signature header",
|
||||
"",
|
||||
true,
|
||||
"Header key: X-APPSMITH-SIGNATURE",
|
||||
)}
|
||||
</FormInputContainer>
|
||||
{formData.isSendSessionEnabled && (
|
||||
<FormInputContainer data-replay-id={btoa("sessionSignatureKey")}>
|
||||
<InputTextControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="sessionSignatureKey"
|
||||
label="Session Details Signature Key"
|
||||
placeholderText=""
|
||||
/>
|
||||
{this.renderInputTextControlViaFormControl(
|
||||
"sessionSignatureKey",
|
||||
"Session Details Signature Key",
|
||||
"",
|
||||
"TEXT",
|
||||
false,
|
||||
false,
|
||||
)}
|
||||
</FormInputContainer>
|
||||
)}
|
||||
<FormInputContainer data-replay-id={btoa("authType")}>
|
||||
<DropDownControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authType"
|
||||
label="Authentication Type"
|
||||
options={[
|
||||
{this.renderDropdownControlViaFormControl(
|
||||
"authType",
|
||||
[
|
||||
{
|
||||
label: "None",
|
||||
value: AuthType.NONE,
|
||||
|
|
@ -460,10 +448,12 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
|
|||
label: "Bearer Token",
|
||||
value: AuthType.bearerToken,
|
||||
},
|
||||
]}
|
||||
placeholderText=""
|
||||
propertyValue=""
|
||||
/>
|
||||
],
|
||||
"Authentication Type",
|
||||
"",
|
||||
false,
|
||||
"",
|
||||
)}
|
||||
</FormInputContainer>
|
||||
{this.renderAuthFields()}
|
||||
</>
|
||||
|
|
@ -497,28 +487,29 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
|
|||
return (
|
||||
<>
|
||||
<FormInputContainer data-replay-id={btoa("authentication.label")}>
|
||||
<InputTextControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authentication.label"
|
||||
label="Key"
|
||||
placeholderText="api_key"
|
||||
/>
|
||||
{this.renderInputTextControlViaFormControl(
|
||||
"authentication.label",
|
||||
"Key",
|
||||
"api_key",
|
||||
"TEXT",
|
||||
false,
|
||||
false,
|
||||
)}
|
||||
</FormInputContainer>
|
||||
<FormInputContainer data-replay-id={btoa("authentication.value")}>
|
||||
<InputTextControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authentication.value"
|
||||
encrypted
|
||||
label="Value"
|
||||
placeholderText="value"
|
||||
/>
|
||||
<FormInputContainer>
|
||||
{this.renderInputTextControlViaFormControl(
|
||||
"authentication.value",
|
||||
"Value",
|
||||
"value",
|
||||
"TEXT",
|
||||
true,
|
||||
false,
|
||||
)}
|
||||
</FormInputContainer>
|
||||
<FormInputContainer data-replay-id={btoa("authentication.addTo")}>
|
||||
<DropDownControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authentication.addTo"
|
||||
label="Add To"
|
||||
options={[
|
||||
<FormInputContainer>
|
||||
{this.renderDropdownControlViaFormControl(
|
||||
"authentication.addTo",
|
||||
[
|
||||
{
|
||||
label: "Query Params",
|
||||
value: ApiKeyAuthType.QueryParams,
|
||||
|
|
@ -527,21 +518,25 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
|
|||
label: "Header",
|
||||
value: ApiKeyAuthType.Header,
|
||||
},
|
||||
]}
|
||||
placeholderText=""
|
||||
propertyValue=""
|
||||
/>
|
||||
],
|
||||
"Add To",
|
||||
"",
|
||||
false,
|
||||
"",
|
||||
)}
|
||||
</FormInputContainer>
|
||||
{_.get(authentication, "addTo") == "header" && (
|
||||
<FormInputContainer
|
||||
data-replay-id={btoa("authentication.headerPrefix")}
|
||||
>
|
||||
<InputTextControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authentication.headerPrefix"
|
||||
label="Header Prefix"
|
||||
placeholderText="eg: Bearer "
|
||||
/>
|
||||
{this.renderInputTextControlViaFormControl(
|
||||
"authentication.headerPrefix",
|
||||
"Header Prefix",
|
||||
"eg: Bearer ",
|
||||
"TEXT",
|
||||
false,
|
||||
false,
|
||||
)}
|
||||
</FormInputContainer>
|
||||
)}
|
||||
</>
|
||||
|
|
@ -551,13 +546,14 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
|
|||
renderBearerToken = () => {
|
||||
return (
|
||||
<FormInputContainer data-replay-id={btoa("authentication.bearerToken")}>
|
||||
<InputTextControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authentication.bearerToken"
|
||||
encrypted
|
||||
label="Bearer Token"
|
||||
placeholderText="Bearer Token"
|
||||
/>
|
||||
{this.renderInputTextControlViaFormControl(
|
||||
"authentication.bearerToken",
|
||||
"Bearer Token",
|
||||
"Bearer Token",
|
||||
"TEXT",
|
||||
true,
|
||||
false,
|
||||
)}
|
||||
</FormInputContainer>
|
||||
);
|
||||
};
|
||||
|
|
@ -566,22 +562,24 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
|
|||
return (
|
||||
<>
|
||||
<FormInputContainer data-replay-id={btoa("authentication.username")}>
|
||||
<InputTextControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authentication.username"
|
||||
label="Username"
|
||||
placeholderText="Username"
|
||||
/>
|
||||
{this.renderInputTextControlViaFormControl(
|
||||
"authentication.username",
|
||||
"Username",
|
||||
"Username",
|
||||
"TEXT",
|
||||
false,
|
||||
false,
|
||||
)}
|
||||
</FormInputContainer>
|
||||
<FormInputContainer data-replay-id={btoa("authentication.password")}>
|
||||
<InputTextControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authentication.password"
|
||||
dataType="PASSWORD"
|
||||
encrypted
|
||||
label="Password"
|
||||
placeholderText="Password"
|
||||
/>
|
||||
{this.renderInputTextControlViaFormControl(
|
||||
"authentication.password",
|
||||
"Password",
|
||||
"Password",
|
||||
"PASSWORD",
|
||||
true,
|
||||
false,
|
||||
)}
|
||||
</FormInputContainer>
|
||||
</>
|
||||
);
|
||||
|
|
@ -603,11 +601,9 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
|
|||
return (
|
||||
<>
|
||||
<FormInputContainer data-replay-id={btoa("authentication.grantType")}>
|
||||
<DropDownControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authentication.grantType"
|
||||
label="Grant Type"
|
||||
options={[
|
||||
{this.renderDropdownControlViaFormControl(
|
||||
"authentication.grantType",
|
||||
[
|
||||
{
|
||||
label: "Client Credentials",
|
||||
value: GrantType.ClientCredentials,
|
||||
|
|
@ -616,10 +612,12 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
|
|||
label: "Authorization Code",
|
||||
value: GrantType.AuthorizationCode,
|
||||
},
|
||||
]}
|
||||
placeholderText=""
|
||||
propertyValue=""
|
||||
/>
|
||||
],
|
||||
"Grant Type",
|
||||
"",
|
||||
false,
|
||||
"",
|
||||
)}
|
||||
</FormInputContainer>
|
||||
{content}
|
||||
</>
|
||||
|
|
@ -633,11 +631,9 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
|
|||
<FormInputContainer
|
||||
data-replay-id={btoa("authentication.isTokenHeader")}
|
||||
>
|
||||
<DropDownControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authentication.isTokenHeader"
|
||||
label="Add Access Token To"
|
||||
options={[
|
||||
{this.renderDropdownControlViaFormControl(
|
||||
"authentication.isTokenHeader",
|
||||
[
|
||||
{
|
||||
label: "Request Header",
|
||||
value: true,
|
||||
|
|
@ -646,58 +642,70 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
|
|||
label: "Request URL",
|
||||
value: false,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
],
|
||||
"Add Access Token To",
|
||||
"",
|
||||
false,
|
||||
"",
|
||||
)}
|
||||
</FormInputContainer>
|
||||
{_.get(formData.authentication, "isTokenHeader") && (
|
||||
<FormInputContainer
|
||||
data-replay-id={btoa("authentication.headerPrefix")}
|
||||
>
|
||||
<InputTextControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authentication.headerPrefix"
|
||||
label="Header Prefix"
|
||||
placeholderText="eg: Bearer "
|
||||
/>
|
||||
{this.renderInputTextControlViaFormControl(
|
||||
"authentication.headerPrefix",
|
||||
"Header Prefix",
|
||||
"eg: Bearer ",
|
||||
"TEXT",
|
||||
false,
|
||||
false,
|
||||
)}
|
||||
</FormInputContainer>
|
||||
)}
|
||||
<FormInputContainer
|
||||
data-replay-id={btoa("authentication.accessTokenUrl")}
|
||||
>
|
||||
<InputTextControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authentication.accessTokenUrl"
|
||||
label="Access Token URL"
|
||||
placeholderText="https://example.com/login/oauth/access_token"
|
||||
/>
|
||||
{this.renderInputTextControlViaFormControl(
|
||||
"authentication.accessTokenUrl",
|
||||
"Access Token URL",
|
||||
"https://example.com/login/oauth/access_token",
|
||||
"TEXT",
|
||||
false,
|
||||
false,
|
||||
)}
|
||||
</FormInputContainer>
|
||||
<FormInputContainer data-replay-id={btoa("authentication.clientId")}>
|
||||
<InputTextControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authentication.clientId"
|
||||
label="Client ID"
|
||||
placeholderText="Client ID"
|
||||
/>
|
||||
{this.renderInputTextControlViaFormControl(
|
||||
"authentication.clientId",
|
||||
"Client ID",
|
||||
"Client ID",
|
||||
"TEXT",
|
||||
false,
|
||||
false,
|
||||
)}
|
||||
</FormInputContainer>
|
||||
<FormInputContainer
|
||||
data-replay-id={btoa("authentication.clientSecret")}
|
||||
>
|
||||
<InputTextControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authentication.clientSecret"
|
||||
dataType="PASSWORD"
|
||||
encrypted
|
||||
label="Client Secret"
|
||||
placeholderText="Client Secret"
|
||||
/>
|
||||
{this.renderInputTextControlViaFormControl(
|
||||
"authentication.clientSecret",
|
||||
"Client Secret",
|
||||
"Client Secret",
|
||||
"PASSWORD",
|
||||
true,
|
||||
false,
|
||||
)}
|
||||
</FormInputContainer>
|
||||
<FormInputContainer data-replay-id={btoa("authentication.scopeString")}>
|
||||
<InputTextControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authentication.scopeString"
|
||||
label="Scope(s)"
|
||||
placeholderText="e.g. read, write"
|
||||
/>
|
||||
{this.renderInputTextControlViaFormControl(
|
||||
"authentication.scopeString",
|
||||
"Scope(s)",
|
||||
"e.g. read, write",
|
||||
"TEXT",
|
||||
false,
|
||||
false,
|
||||
)}
|
||||
</FormInputContainer>
|
||||
</>
|
||||
);
|
||||
|
|
@ -706,28 +714,25 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
|
|||
renderOauth2CommonAdvanced = () => {
|
||||
return (
|
||||
<>
|
||||
<FormInputContainer>
|
||||
<KeyValueInputControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authentication.customTokenParameters"
|
||||
label="Custom Token Parameters"
|
||||
/>
|
||||
</FormInputContainer>
|
||||
<FormInputContainer>
|
||||
<InputTextControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authentication.audience"
|
||||
label="Audience"
|
||||
placeholderText="https://example.com/oauth/audience"
|
||||
/>
|
||||
<FormInputContainer data-replay-id={btoa("authentication.audience")}>
|
||||
{this.renderInputTextControlViaFormControl(
|
||||
"authentication.audience",
|
||||
"Audience",
|
||||
"https://example.com/oauth/audience",
|
||||
"TEXT",
|
||||
false,
|
||||
false,
|
||||
)}
|
||||
</FormInputContainer>
|
||||
<FormInputContainer data-replay-id={btoa("authentication.resource")}>
|
||||
<InputTextControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authentication.resource"
|
||||
label="Resource"
|
||||
placeholderText="https://example.com/oauth/resource"
|
||||
/>
|
||||
{this.renderInputTextControlViaFormControl(
|
||||
"authentication.resource",
|
||||
"Resource",
|
||||
"https://example.com/oauth/resource",
|
||||
"TEXT",
|
||||
false,
|
||||
false,
|
||||
)}
|
||||
</FormInputContainer>
|
||||
</>
|
||||
);
|
||||
|
|
@ -757,12 +762,14 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
|
|||
<FormInputContainer
|
||||
data-replay-id={btoa("authentication.authorizationUrl")}
|
||||
>
|
||||
<InputTextControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authentication.authorizationUrl"
|
||||
label="Authorization URL"
|
||||
placeholderText="https://example.com/login/oauth/authorize"
|
||||
/>
|
||||
{this.renderInputTextControlViaFormControl(
|
||||
"authentication.authorizationUrl",
|
||||
"Authorization URL",
|
||||
"https://example.com/login/oauth/authorize",
|
||||
"TEXT",
|
||||
false,
|
||||
false,
|
||||
)}
|
||||
</FormInputContainer>
|
||||
<FormInputContainer>
|
||||
<div style={{ width: "50vh" }}>
|
||||
|
|
@ -779,20 +786,19 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
|
|||
<FormInputContainer
|
||||
data-replay-id={btoa("authentication.customAuthenticationParameters")}
|
||||
>
|
||||
<KeyValueInputControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authentication.customAuthenticationParameters"
|
||||
label="Custom Authentication Parameters"
|
||||
/>
|
||||
{this.renderKeyValueControlViaFormControl(
|
||||
"authentication.customAuthenticationParameters",
|
||||
"Custom Authentication Parameters",
|
||||
"",
|
||||
false,
|
||||
)}
|
||||
</FormInputContainer>
|
||||
<FormInputContainer
|
||||
data-replay-id={btoa("authentication.isAuthorizationHeader")}
|
||||
>
|
||||
<DropDownControl
|
||||
{...COMMON_INPUT_PROPS}
|
||||
configProperty="authentication.isAuthorizationHeader"
|
||||
label="Client Authentication"
|
||||
options={[
|
||||
{this.renderDropdownControlViaFormControl(
|
||||
"authentication.isAuthorizationHeader",
|
||||
[
|
||||
{
|
||||
label: "Send as Basic Auth header",
|
||||
value: true,
|
||||
|
|
@ -801,8 +807,12 @@ class DatasourceRestAPIEditor extends React.Component<Props> {
|
|||
label: "Send client credentials in body",
|
||||
value: false,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
],
|
||||
"Client Authentication",
|
||||
"",
|
||||
false,
|
||||
"",
|
||||
)}
|
||||
</FormInputContainer>
|
||||
{!_.get(formData.authentication, "isAuthorizationHeader", true) &&
|
||||
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) => {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,17 @@ import { isHidden } from "components/formControls/utils";
|
|||
import { useSelector } from "react-redux";
|
||||
import { getFormValues } from "redux-form";
|
||||
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 {
|
||||
config: ControlProps;
|
||||
|
|
@ -20,17 +31,137 @@ function FormControl(props: FormControlProps) {
|
|||
if (hidden) return null;
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`t--form-control-${props.config.controlType}`}
|
||||
data-replay-id={btoa(props.config.configProperty)}
|
||||
<FormConfig
|
||||
config={props.config}
|
||||
formName={props.formName}
|
||||
multipleConfig={props?.multipleConfig}
|
||||
>
|
||||
{FormControlFactory.createControl(
|
||||
props.config,
|
||||
props.formName,
|
||||
props?.multipleConfig,
|
||||
)}
|
||||
<div className={`t--form-control-${props.config.controlType}`}>
|
||||
{FormControlFactory.createControl(
|
||||
props.config,
|
||||
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>
|
||||
);
|
||||
}
|
||||
|
||||
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>}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import { Colors } from "constants/Colors";
|
||||
import { RenderDropdownOptionType } from "components/ads/Dropdown";
|
||||
import {
|
||||
DropdownOption,
|
||||
RenderDropdownOptionType,
|
||||
} from "components/ads/Dropdown";
|
||||
import { useSelector } from "react-redux";
|
||||
import { getPluginImages } from "../../../../selectors/entitiesSelector";
|
||||
import { Classes } from "../../../../components/ads/common";
|
||||
|
|
@ -101,13 +104,14 @@ function DataSourceOption({
|
|||
optionClickHandler,
|
||||
optionWidth,
|
||||
}: DataSourceOptionType) {
|
||||
const { label } = dropdownOption;
|
||||
const { label } = dropdownOption as DropdownOption;
|
||||
const { routeToCreateNewDatasource = () => null } = extraProps;
|
||||
const pluginImages = useSelector(getPluginImages);
|
||||
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 =
|
||||
!isSupportedForTemplate && !isSelectedNode && !isConnectNewDataSourceBtn;
|
||||
|
||||
|
|
@ -127,7 +131,7 @@ function DataSourceOption({
|
|||
className="t--dropdown-option"
|
||||
data-cy={optionCypressSelector}
|
||||
disabled={isNotSupportedDatasource}
|
||||
key={dropdownOption.id}
|
||||
key={(dropdownOption as DropdownOption).id}
|
||||
onClick={() => {
|
||||
if (isNotSupportedDatasource) {
|
||||
return;
|
||||
|
|
@ -135,7 +139,7 @@ function DataSourceOption({
|
|||
if (isConnectNewDataSourceBtn) {
|
||||
routeToCreateNewDatasource(dropdownOption);
|
||||
} else if (optionClickHandler) {
|
||||
optionClickHandler(dropdownOption);
|
||||
optionClickHandler(dropdownOption as DropdownOption);
|
||||
}
|
||||
}}
|
||||
selected={isSelectedNode}
|
||||
|
|
@ -149,12 +153,14 @@ function DataSourceOption({
|
|||
width={20}
|
||||
/>
|
||||
</CreateIconWrapper>
|
||||
) : pluginImages[dropdownOption.data.pluginId] ? (
|
||||
) : pluginImages[(dropdownOption as DropdownOption).data.pluginId] ? (
|
||||
<ImageWrapper>
|
||||
<DatasourceImage
|
||||
alt=""
|
||||
className="dataSourceImage"
|
||||
src={pluginImages[dropdownOption.data.pluginId]}
|
||||
src={
|
||||
pluginImages[(dropdownOption as DropdownOption).data.pluginId]
|
||||
}
|
||||
/>
|
||||
</ImageWrapper>
|
||||
) : null}
|
||||
|
|
|
|||
|
|
@ -629,7 +629,7 @@ function GeneratePageForm() {
|
|||
cypressSelector="t--datasource-dropdown-option"
|
||||
extraProps={{ routeToCreateNewDatasource }}
|
||||
isSelectedNode={isSelectedNode}
|
||||
key={option.id}
|
||||
key={(option as DropdownOption).id}
|
||||
option={option}
|
||||
optionClickHandler={optionClickHandler}
|
||||
optionWidth={DROPDOWN_DIMENSION.WIDTH}
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ function SelectedValueNode(props: DefaultDropDownValueNodeProps) {
|
|||
const { selected } = props;
|
||||
return (
|
||||
<SelectedValueNodeContainer>
|
||||
<span className="label">{selected.label}</span>
|
||||
<span className="label">{(selected as DropdownOption).label}</span>
|
||||
<ChevronDown />
|
||||
</SelectedValueNodeContainer>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -13,10 +13,10 @@ import SwitchControl, {
|
|||
SwitchControlProps,
|
||||
} from "components/formControls/SwitchControl";
|
||||
import KeyValueArrayControl, {
|
||||
KeyValueArrayProps,
|
||||
KeyValueArrayControlProps,
|
||||
} from "components/formControls/KeyValueArrayControl";
|
||||
import KeyValueInputControl, {
|
||||
KeyValueInputProps,
|
||||
KeyValueInputControlProps,
|
||||
} from "components/formControls/KeyValueInputControl";
|
||||
import FilePickerControl, {
|
||||
FilePickerControlProps,
|
||||
|
|
@ -30,7 +30,6 @@ import CheckboxControl, {
|
|||
import DynamicInputTextControl, {
|
||||
DynamicInputControlProps,
|
||||
} from "components/formControls/DynamicInputTextControl";
|
||||
import InputNumberControl from "components/formControls/InputNumberControl";
|
||||
import FieldArrayControl, {
|
||||
FieldArrayControlProps,
|
||||
} from "components/formControls/FieldArrayControl";
|
||||
|
|
@ -52,6 +51,7 @@ class FormControlRegistry {
|
|||
buildPropertyControl(
|
||||
controlProps: FixedKeyInputControlProps,
|
||||
): JSX.Element {
|
||||
//TODO: may not be in use
|
||||
return <FixedKeyInputControl {...controlProps} />;
|
||||
},
|
||||
});
|
||||
|
|
@ -66,17 +66,23 @@ class FormControlRegistry {
|
|||
},
|
||||
});
|
||||
FormControlFactory.registerControlBuilder("KEYVALUE_ARRAY", {
|
||||
buildPropertyControl(controlProps: KeyValueArrayProps): JSX.Element {
|
||||
buildPropertyControl(
|
||||
controlProps: KeyValueArrayControlProps,
|
||||
): JSX.Element {
|
||||
return <KeyValueArrayControl {...controlProps} />;
|
||||
},
|
||||
});
|
||||
FormControlFactory.registerControlBuilder("FILE_PICKER", {
|
||||
buildPropertyControl(controlProps: FilePickerControlProps): JSX.Element {
|
||||
//used by redshift datasource
|
||||
return <FilePickerControl {...controlProps} />;
|
||||
},
|
||||
});
|
||||
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} />;
|
||||
},
|
||||
});
|
||||
|
|
@ -94,12 +100,13 @@ class FormControlRegistry {
|
|||
});
|
||||
FormControlFactory.registerControlBuilder("CHECKBOX", {
|
||||
buildPropertyControl(controlProps: CheckboxControlProps): JSX.Element {
|
||||
//used in API datasource form only
|
||||
return <CheckboxControl {...controlProps} />;
|
||||
},
|
||||
});
|
||||
FormControlFactory.registerControlBuilder("NUMBER_INPUT", {
|
||||
buildPropertyControl(controlProps: InputControlProps): JSX.Element {
|
||||
return <InputNumberControl {...controlProps} />;
|
||||
return <InputTextControl {...controlProps} />;
|
||||
},
|
||||
});
|
||||
FormControlFactory.registerControlBuilder("ARRAY_FIELD", {
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user