diff --git a/.version b/.version
index afad818663..92536a9e48 100644
--- a/.version
+++ b/.version
@@ -1 +1 @@
-3.11.0
+3.12.0
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.skip.js
similarity index 100%
rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.js
rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/airTableHappyPath.cy.skip.js
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.skip.js
similarity index 100%
rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.js
rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/bigqueryHappyPath.cy.skip.js
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.skip.js
similarity index 100%
rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.js
rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mongoDbHappyPath.cy.skip.js
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mysqlHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mysqlHappyPath.cy.skip.js
similarity index 100%
rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mysqlHappyPath.cy.js
rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/mysqlHappyPath.cy.skip.js
diff --git a/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.js b/cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.skip.js
similarity index 100%
rename from cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.js
rename to cypress-tests/cypress/e2e/happyPath/marketplace/commonTestcases/data-source/sqlServerHappyPath.cy.skip.js
diff --git a/frontend/.version b/frontend/.version
index afad818663..92536a9e48 100644
--- a/frontend/.version
+++ b/frontend/.version
@@ -1 +1 @@
-3.11.0
+3.12.0
diff --git a/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/BulkUploadPrimaryKey.jsx b/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/BulkUploadPrimaryKey.jsx
index d1a9dfa3aa..3a74038367 100644
--- a/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/BulkUploadPrimaryKey.jsx
+++ b/frontend/src/AppBuilder/QueryManager/QueryEditors/TooljetDatabase/BulkUploadPrimaryKey.jsx
@@ -53,7 +53,11 @@ export const BulkUploadPrimaryKey = () => {
-
- Default value
+
+
+ Default value
+
+ {foreignKeyDetails?.length > 0 && isForeignKey && (
+
0 && isForeignKey}
+ >
+
+ Set default value to Null
+
+
+
+ )}
-
+
{isTimestamp ? (
) : (
-
-
- No data found
-
- }
- loader={
- <>
-
-
-
- >
- }
- isLoading={true}
- value={foreignKeyDefaultValue}
- foreignKeyAccessInRowForm={true}
- disabled={dataType === 'serial'}
- topPlaceHolder={dataType === 'serial' ? 'Auto-generated' : 'Enter a value'}
- onChange={(value) => {
- setForeignKeyDefaultValue(value);
- setDefaultValue(value?.value);
- }}
- onAdd={true}
- addBtnLabel={'Open referenced table'}
- foreignKeys={foreignKeyDetails}
- setReferencedColumnDetails={setReferencedColumnDetails}
- scrollEventForColumnValues={true}
- cellColumnName={columnName}
- columnDataType={dataType?.value}
- isCreateColumn={true}
- />
+ <>
+
+
+ No data found
+
+ }
+ loader={
+ <>
+
+
+
+ >
+ }
+ isLoading={true}
+ value={foreignKeyDefaultValue}
+ foreignKeyAccessInRowForm={true}
+ disabled={dataType === 'serial'}
+ topPlaceHolder={
+ dataType === 'serial'
+ ? 'Auto-generated'
+ : foreignKeyDefaultValue?.value === null
+ ? 'Null'
+ : 'Enter a value'
+ }
+ onChange={(value) => {
+ setForeignKeyDefaultValue(value);
+ setDefaultValue(value?.value);
+ }}
+ onAdd={true}
+ addBtnLabel={'Open referenced table'}
+ foreignKeys={foreignKeyDetails}
+ setReferencedColumnDetails={setReferencedColumnDetails}
+ scrollEventForColumnValues={true}
+ cellColumnName={columnName}
+ columnDataType={dataType?.value}
+ isCreateColumn={true}
+ />
+ {defaultValue === null && Null
}
+ >
)}
- {isNotNull === true && dataType?.value !== 'serial' && rows.length > 0 && defaultValue.length <= 0 ? (
+ {isNotNull === true && dataType?.value !== 'serial' && defaultValue?.length <= 0 ? (
Default value is required to populate this field in existing rows as NOT NULL constraint is added
@@ -546,6 +596,10 @@ const ColumnForm = ({
checked={isNotNull}
onChange={(e) => {
setIsNotNull(e.target.checked);
+ if (e.target.checked && defaultValue === null) {
+ setForeignKeyDefaultValue({ label: '', value: '' });
+ setDefaultValue('');
+ }
}}
disabled={dataType?.value === 'serial'}
/>
@@ -602,7 +656,7 @@ const ColumnForm = ({
shouldDisableCreateBtn={
isEmpty(columnName) ||
isEmpty(dataType) ||
- (isNotNull === true && rows.length > 0 && isEmpty(defaultValue) && dataType?.value !== 'serial') ||
+ (dataType?.value !== 'serial' && isNotNull === true && isEmpty(defaultValue)) ||
disabledSaveButton
}
showToolTipForFkOnReadDocsSection={true}
diff --git a/frontend/src/TooljetDatabase/Forms/EditColumnForm.jsx b/frontend/src/TooljetDatabase/Forms/EditColumnForm.jsx
index 3943964a16..071faf5e3e 100644
--- a/frontend/src/TooljetDatabase/Forms/EditColumnForm.jsx
+++ b/frontend/src/TooljetDatabase/Forms/EditColumnForm.jsx
@@ -31,6 +31,8 @@ import DateTimePicker from '@/Editor/QueryManager/QueryEditors/TooljetDatabase/D
import { getLocalTimeZone, timeZonesWithOffsets } from '@/Editor/QueryManager/QueryEditors/TooljetDatabase/util';
import CodeHinter from '@/AppBuilder/CodeEditor';
import { resolveReferences } from '@/AppBuilder/CodeEditor/utils';
+import Switch from '@/AppBuilder/CodeBuilder/Elements/Switch';
+import PostgrestQueryBuilder from '@/_helpers/postgrestQueryBuilder';
const ColumnForm = ({
onClose,
@@ -102,6 +104,68 @@ const ColumnForm = ({
const [foreignKeyDetails, setForeignKeyDetails] = useState([]);
+ // Add function to validate default value
+ const validateDefaultValue = async () => {
+ if (!isMatchingForeignKeyColumn(selectedColumn?.Header)) return;
+
+ try {
+ const referencedColumns = foreignKeys.find((item) => item.column_names[0] === selectedColumn?.Header);
+
+ if (!referencedColumns?.referenced_column_names?.length) {
+ setForeignKeyDefaultValue({
+ value: '',
+ label: '',
+ });
+ setDefaultValue('');
+ return;
+ }
+
+ const selectQuery = new PostgrestQueryBuilder();
+ selectQuery.select(referencedColumns.referenced_column_names[0]);
+ selectQuery.eq(referencedColumns.referenced_column_names[0], defaultValue);
+
+ const query = selectQuery.url.toString();
+
+ const { data = [], error } = await tooljetDatabaseService.findOne(
+ organizationId,
+ referencedColumns.referenced_table_id,
+ query
+ );
+
+ if (error) {
+ toast.error(error?.message ?? `Failed to validate default value`);
+ setForeignKeyDefaultValue({
+ value: '',
+ label: '',
+ });
+ setDefaultValue('');
+ return;
+ }
+
+ if (data.length === 0) {
+ setForeignKeyDefaultValue({
+ value: '',
+ label: '',
+ });
+ setDefaultValue('');
+ }
+ } catch (error) {
+ console.error('Error validating default value:', error);
+ setForeignKeyDefaultValue({
+ value: '',
+ label: '',
+ });
+ setDefaultValue('');
+ }
+ };
+
+ // Add useEffect to validate on mount
+ useEffect(() => {
+ if (isMatchingForeignKeyColumn(selectedColumn?.Header) && defaultValue) {
+ validateDefaultValue();
+ }
+ }, []);
+
useEffect(() => {
toast.dismiss();
setForeignKeyDetails(
@@ -471,6 +535,11 @@ const ColumnForm = ({
setDisabledSaveButton(columnName === '');
}, [columnName]);
+ useEffect(() => {
+ const shouldDisableForNullValue = dataType?.value !== 'serial' && isNotNull === true && isEmpty(defaultValue);
+ setDisabledSaveButton(shouldDisableForNullValue);
+ }, [isNotNull, defaultValue, dataType]);
+
const handleInputError = (bool = false) => {
setDisabledSaveButton(bool);
};
@@ -621,16 +690,52 @@ const ColumnForm = ({
)}
-
- Default value
+
+
+ Default value
+
+ {isMatchingForeignKeyColumn(selectedColumn?.Header) && (
+
+
+ Set default value to Null
+
+
+
+ )}
+
-
+
{isTimestamp ? (
) : (
-
-
- No data found
-
- }
- loader={
- <>
-
-
-
- >
- }
- isLoading={true}
- value={foreignKeyDefaultValue}
- foreignKeyAccessInRowForm={true}
- disabled={
- selectedColumn?.dataType === 'serial' || selectedColumn.constraints_type.is_primary_key === true
- }
- topPlaceHolder={selectedColumn?.dataType === 'serial' ? 'Auto-generated' : 'Enter a value'}
- onChange={(value) => {
- setForeignKeyDefaultValue(value);
- setDefaultValue(value?.value);
- }}
- onAdd={true}
- addBtnLabel={'Open referenced table'}
- foreignKeys={foreignKeys}
- setReferencedColumnDetails={setReferencedColumnDetails}
- scrollEventForColumnValues={true}
- cellColumnName={selectedColumn?.Header}
- columnDataType={dataType}
- isEditColumn={true}
- />
+ <>
+
+
+ No data found
+
+ }
+ loader={
+ <>
+
+
+
+ >
+ }
+ isLoading={true}
+ value={foreignKeyDefaultValue}
+ foreignKeyAccessInRowForm={true}
+ disabled={
+ selectedColumn?.dataType === 'serial' || selectedColumn.constraints_type.is_primary_key === true
+ }
+ topPlaceHolder={
+ selectedColumn?.dataType === 'serial'
+ ? 'Auto-generated'
+ : foreignKeyDefaultValue?.value === null || defaultValue === null
+ ? 'Null'
+ : 'Enter a value'
+ }
+ onChange={(value) => {
+ setForeignKeyDefaultValue(value);
+ setDefaultValue(value?.value);
+ }}
+ onAdd={true}
+ addBtnLabel={'Open referenced table'}
+ foreignKeys={foreignKeys}
+ setReferencedColumnDetails={setReferencedColumnDetails}
+ scrollEventForColumnValues={true}
+ cellColumnName={selectedColumn?.Header}
+ columnDataType={dataType}
+ isEditColumn={true}
+ />
+ {defaultValue === null && Null
}
+ >
)}
+ {isNotNull === true && dataType?.value !== 'serial' && defaultValue?.length <= 0 ? (
+
+ Default value is required to populate this field in existing rows as NOT NULL constraint is added
+
+ ) : null}
{isNotNull === true &&
selectedColumn?.dataType !== 'serial' &&
rows.length > 0 &&
@@ -866,6 +990,10 @@ const ColumnForm = ({
checked={isNotNull}
onChange={(e) => {
setIsNotNull(e.target.checked);
+ if (e.target.checked && defaultValue === null) {
+ setForeignKeyDefaultValue({ label: '', value: '' });
+ setDefaultValue('');
+ }
}}
disabled={selectedColumn?.dataType === 'serial' || selectedColumn?.constraints_type?.is_primary_key}
/>
diff --git a/frontend/src/TooljetDatabase/Forms/EditRowForm.jsx b/frontend/src/TooljetDatabase/Forms/EditRowForm.jsx
index 5451cc353f..98ff603242 100644
--- a/frontend/src/TooljetDatabase/Forms/EditRowForm.jsx
+++ b/frontend/src/TooljetDatabase/Forms/EditRowForm.jsx
@@ -177,7 +177,9 @@ const EditRowForm = ({
}
function isMatchingForeignKeyColumnDetails(columnHeader) {
- const matchingColumn = foreignKeys.find((foreignKey) => foreignKey.column_names[0] === columnHeader);
+ const matchingColumn = Array.isArray(foreignKeys)
+ ? foreignKeys.find((foreignKey) => foreignKey.column_names[0] === columnHeader)
+ : undefined;
return matchingColumn;
}
diff --git a/frontend/src/TooljetDatabase/Forms/RowForm.jsx b/frontend/src/TooljetDatabase/Forms/RowForm.jsx
index 8fcc9ef152..58dd3d1000 100644
--- a/frontend/src/TooljetDatabase/Forms/RowForm.jsx
+++ b/frontend/src/TooljetDatabase/Forms/RowForm.jsx
@@ -151,7 +151,9 @@ const RowForm = ({
}
function isMatchingForeignKeyColumnDetails(columnHeader) {
- const matchingColumn = foreignKeys.find((foreignKey) => foreignKey.column_names[0] === columnHeader);
+ const matchingColumn = Array.isArray(foreignKeys)
+ ? foreignKeys.find((foreignKey) => foreignKey.column_names[0] === columnHeader)
+ : undefined;
return matchingColumn;
}
diff --git a/frontend/src/TooljetDatabase/Table/index.jsx b/frontend/src/TooljetDatabase/Table/index.jsx
index 5d98ce70fe..3a1cafa460 100644
--- a/frontend/src/TooljetDatabase/Table/index.jsx
+++ b/frontend/src/TooljetDatabase/Table/index.jsx
@@ -958,7 +958,9 @@ const Table = ({ collapseSidebar }) => {
}
function isMatchingForeignKeyColumnDetails(columnHeader) {
- const matchingColumn = foreignKeys.find((foreignKey) => foreignKey.column_names[0] === columnHeader);
+ const matchingColumn = Array.isArray(foreignKeys)
+ ? foreignKeys.find((foreignKey) => foreignKey.column_names[0] === columnHeader)
+ : undefined;
return matchingColumn;
}
diff --git a/frontend/src/_components/DynamicFormV2.jsx b/frontend/src/_components/DynamicFormV2.jsx
index 1a6269976f..bada082e16 100644
--- a/frontend/src/_components/DynamicFormV2.jsx
+++ b/frontend/src/_components/DynamicFormV2.jsx
@@ -30,6 +30,8 @@ const DynamicFormV2 = ({
validationMessages,
setValidationMessages,
clearValidationMessages,
+ showValidationErrors,
+ clearValidationErrorBanner,
}) => {
const uiProperties = schema['tj:ui:properties'] || {};
const dsm = React.useMemo(() => new DataSourceSchemaManager(schema), [schema]);
@@ -89,18 +91,97 @@ const DynamicFormV2 = ({
React.useEffect(() => {
if (!hasUserInteracted) return;
- const { valid, errors } = dsm.validateData(options);
- if (valid) {
- clearValidationMessages();
- } else {
- setValidationMessages(errors, schema);
- const requiredFields = errors
- .filter((error) => error.keyword === 'required')
- .map((error) => error.params.missingProperty);
- setConditionallyRequiredProperties(requiredFields);
+ const timeout = setTimeout(() => {
+ validateOptions();
+ }, 300);
+
+ return () => clearTimeout(timeout);
+ }, [options, hasUserInteracted, validateOptions]);
+
+ const validateOptions = React.useCallback(async () => {
+ try {
+ const { valid, errors } = await dsm.validateData(options);
+
+ const conditionallyRequiredFields = processAllOfConditions(schema, options);
+ setConditionallyRequiredProperties(conditionallyRequiredFields);
+
+ if (valid) {
+ clearValidationMessages();
+ clearValidationErrorBanner();
+ } else {
+ setValidationMessages(errors, schema, interactedFields);
+ }
+ } catch (error) {
+ console.error('Validation error:', error);
}
- }, [options]);
+ }, [
+ dsm,
+ options,
+ processAllOfConditions,
+ schema,
+ clearValidationMessages,
+ clearValidationErrorBanner,
+ setValidationMessages,
+ interactedFields,
+ ]);
+
+ const processAllOfConditions = React.useCallback((schema, options, path = []) => {
+ let requiredFields = [];
+
+ if (schema.allOf) {
+ schema.allOf.forEach((condition) => {
+ if (condition.if && condition.then) {
+ const conditionMatches = Object.entries(condition.if.properties || {}).every(([propName, propCondition]) => {
+ const propertyPath = [...path, propName];
+
+ let currentValue = options;
+ for (const segment of propertyPath) {
+ if (!currentValue || typeof currentValue !== 'object') {
+ return false;
+ }
+ currentValue = currentValue[segment]?.value;
+ }
+
+ return propCondition.const === currentValue;
+ });
+
+ if (conditionMatches) {
+ if (condition.then.required) {
+ requiredFields = [...requiredFields, ...condition.then.required];
+ }
+
+ if (condition.then.allOf) {
+ const nestedRequired = processAllOfConditions({ allOf: condition.then.allOf }, options, path);
+ requiredFields = [...requiredFields, ...nestedRequired];
+ }
+
+ if (condition.then.properties) {
+ Object.entries(condition.then.properties).forEach(([propName, propSchema]) => {
+ if (propSchema.allOf) {
+ const nestedRequired = processAllOfConditions({ allOf: propSchema.allOf }, options, [
+ ...path,
+ propName,
+ ]);
+ requiredFields = [...requiredFields, ...nestedRequired];
+ }
+ });
+ }
+ }
+ }
+ });
+ }
+
+ return requiredFields;
+ }, []);
+
+ React.useEffect(() => {
+ if (showValidationErrors) {
+ setHasUserInteracted(true);
+ const allFieldKeys = Object.keys(options);
+ setInteractedFields(new Set(allFieldKeys));
+ }
+ }, [showValidationErrors, options]);
React.useEffect(() => {
const prevDataSourceId = prevDataSourceIdRef.current;
@@ -189,6 +270,7 @@ const DynamicFormV2 = ({
return Input;
case 'password-v3':
case 'text-v3':
+ case 'password-v3-textarea':
return InputV3;
case 'textarea':
return Textarea;
@@ -210,8 +292,10 @@ const DynamicFormV2 = ({
const isRequired = required || conditionallyRequiredProperties.includes(key);
const isEncrypted = widget === 'password-v3' || encryptedProperties.includes(key);
const currentValue = options?.[key]?.value;
+ const skipValidation =
+ (!hasUserInteracted && !showValidationErrors) || (!interactedFields.has(key) && !showValidationErrors);
- const handleOptionChange = (key, value, flag) => {
+ const handleOptionChange = (key, value, flag = true) => {
if (!hasUserInteracted) {
setHasUserInteracted(true);
}
@@ -243,6 +327,7 @@ const DynamicFormV2 = ({
};
}
case 'password-v3':
+ case 'password-v3-textarea':
case 'text-v3': {
return {
key,
@@ -262,14 +347,13 @@ const DynamicFormV2 = ({
encrypted: isEncrypted,
onBlur,
isRequired: isRequired,
- isValidatedMessages:
- !hasUserInteracted || !interactedFields.has(key)
- ? { valid: null, message: '' } // skip validation for initial render and untouched elements
- : validationMessages[key]
- ? { valid: false, message: validationMessages[key] }
- : isRequired && !isEncrypted
- ? { valid: true, message: '' }
- : { valid: null, message: '' }, // handle optional && encrypted fields
+ isValidatedMessages: skipValidation
+ ? { valid: null, message: '' } // skip validation for initial render and untouched elements
+ : validationMessages[key]
+ ? { valid: false, message: validationMessages[key] }
+ : isRequired
+ ? { valid: true, message: '' }
+ : { valid: null, message: '' }, // handle optional && encrypted fields
isDisabled: !canUpdateDataSource(selectedDataSource?.id) && !canDeleteDataSource(),
};
}
@@ -285,7 +369,7 @@ const DynamicFormV2 = ({
options: isRenderedAsQueryEditor
? options?.[key] ?? schema?.defaults?.[key]
: options?.[key]?.value ?? schema?.defaults?.[key]?.value,
- optionchanged,
+ handleOptionChange,
isRenderedAsQueryEditor,
workspaceConstants: currentOrgEnvironmentConstants,
isDisabled: !canUpdateDataSource(selectedDataSource?.id) && !canDeleteDataSource(),
@@ -298,14 +382,14 @@ const DynamicFormV2 = ({
return {
defaultChecked: currentValue,
checked: currentValue,
- onChange: (e) => optionchanged(key, e.target.checked),
+ onChange: (e) => handleOptionChange(key, e.target.checked, true),
};
case 'dropdown':
case 'dropdown-component-flip':
return {
options: list,
value: options?.[key]?.value || options?.[key],
- onChange: (value) => optionchanged(key, value),
+ onChange: (value) => handleOptionChange(key, value, true),
width: width || '100%',
encrypted: options?.[key]?.encrypted,
};
@@ -404,6 +488,7 @@ const DynamicFormV2 = ({
{label &&
widget !== 'text-v3' &&
widget !== 'password-v3' &&
+ widget !== 'password-v3-textarea' &&
renderLabel(label, uiProperties[key].tooltip)}
)}
diff --git a/frontend/src/_helpers/dataSourceSchemaManager.js b/frontend/src/_helpers/dataSourceSchemaManager.js
index abf2f3c9bb..6ce4f6f9a7 100644
--- a/frontend/src/_helpers/dataSourceSchemaManager.js
+++ b/frontend/src/_helpers/dataSourceSchemaManager.js
@@ -1,3 +1,4 @@
+import { datasourceService } from '@/_services';
import Ajv2020 from 'ajv';
const ajvOptions = {
@@ -18,13 +19,18 @@ export default class DataSourceSchemaManager {
this.validate = this.ajv.compile(this.schema);
}
- validateData(options) {
- const data = this._convertDataSourceOptionsToData(options);
- const valid = this.validate(data);
- if (!valid) {
- return { valid: false, errors: this.validate.errors };
+ async validateData(options) {
+ const decryptedOptions = await datasourceService.getDecryptedOptions(options);
+ const data = this._convertDataSourceOptionsToData(decryptedOptions);
+ try {
+ const valid = this.validate(data);
+ if (!valid) {
+ return { valid: false, errors: this.validate.errors };
+ }
+ return { valid: true, errors: [] };
+ } catch (error) {
+ console.log('Validtion error: ', error);
}
- return { valid: true, errors: [] };
}
getDefaults(options = {}) {
@@ -95,10 +101,6 @@ export default class DataSourceSchemaManager {
result[key] = value;
}
- // Add a dummy value to pass validation for encrypted keys
- if (this.getEncryptedProperties().includes(key)) {
- result[key] = 'REDACTED';
- }
return result;
}, {});
}
diff --git a/frontend/src/_services/datasource.service.js b/frontend/src/_services/datasource.service.js
index ef1a878db7..9ea24e99d0 100644
--- a/frontend/src/_services/datasource.service.js
+++ b/frontend/src/_services/datasource.service.js
@@ -11,8 +11,19 @@ export const datasourceService = {
save,
fetchOauth2BaseUrl,
testSampleDb,
+ getDecryptedOptions,
};
+function getDecryptedOptions(options) {
+ const requestOptions = {
+ method: 'POST',
+ headers: authHeader(),
+ credentials: 'include',
+ body: JSON.stringify(options),
+ };
+ return fetch(`${config.apiUrl}/data-sources/decrypt`, requestOptions).then(handleResponse);
+}
+
function getAll(appVersionId, environment_id, includeStaticSources = false) {
const requestOptions = { method: 'GET', headers: authHeader(), credentials: 'include' };
let searchParams = new URLSearchParams(
diff --git a/frontend/src/_styles/theme.scss b/frontend/src/_styles/theme.scss
index e464a7b86f..1162f50687 100644
--- a/frontend/src/_styles/theme.scss
+++ b/frontend/src/_styles/theme.scss
@@ -12345,6 +12345,17 @@ tbody {
}
}
+ .design-component-inputs textarea {
+
+ &.valid-textarea {
+ border: 1.5px solid #519b62 !important;
+ }
+
+ &.invalid-textarea {
+ border: 1.5px solid #e26367 !important;
+ }
+ }
+
}
.tj-app-input-wrapper {
@@ -13141,6 +13152,10 @@ tbody {
background-color: var(--slate3) !important;
}
+ textarea:disabled {
+ background-color: var(--slate3) !important;
+ }
+
.react-select__control--is-disabled {
background-color: var(--slate3) !important;
}
diff --git a/frontend/src/_ui/HttpHeaders/index.js b/frontend/src/_ui/HttpHeaders/index.js
index 04bbd8e3b5..3f254d1497 100644
--- a/frontend/src/_ui/HttpHeaders/index.js
+++ b/frontend/src/_ui/HttpHeaders/index.js
@@ -1,13 +1,14 @@
-import React from "react";
-import _ from "lodash";
-import QueryEditor from "./QueryEditor";
-import SourceEditor from "./SourceEditor";
-import { deepClone } from "@/_helpers/utilities/utils.helpers";
+import React from 'react';
+import _ from 'lodash';
+import QueryEditor from './QueryEditor';
+import SourceEditor from './SourceEditor';
+import { deepClone } from '@/_helpers/utilities/utils.helpers';
export default ({
getter,
- options = [["", ""]],
+ options = [['', '']],
optionchanged,
+ handleOptionChange,
isRenderedAsQueryEditor,
workspaceConstants,
isDisabled,
@@ -16,27 +17,46 @@ export default ({
dataCy,
}) => {
function addNewKeyValuePair(options) {
- const newPairs = [...options, ["", ""]];
- optionchanged(getter, newPairs);
+ const newPairs = [...options, ['', '']];
+
+ if (handleOptionChange) {
+ handleOptionChange(getter, newPairs, true);
+ } else {
+ optionchanged(getter, newPairs);
+ }
}
function removeKeyValuePair(index) {
const newOptions = [...options];
newOptions.splice(index, 1);
- optionchanged(getter, newOptions);
+ if (handleOptionChange) {
+ handleOptionChange(getter, newOptions, true);
+ } else {
+ optionchanged(getter, newOptions);
+ }
}
function keyValuePairValueChanged(value, keyIndex, index) {
if (!isRenderedAsQueryEditor) {
const newOptions = deepClone(options);
newOptions[index][keyIndex] = value;
- options.length - 1 === index
- ? addNewKeyValuePair(newOptions)
- : optionchanged(getter, newOptions);
+ if (options.length - 1 === index) {
+ addNewKeyValuePair(newOptions);
+ } else {
+ if (handleOptionChange) {
+ handleOptionChange(getter, newOptions, true);
+ } else {
+ optionchanged(getter, newOptions);
+ }
+ }
} else {
let newOptions = deepClone(options);
newOptions[index][keyIndex] = value;
- optionchanged(getter, newOptions);
+ if (handleOptionChange) {
+ handleOptionChange(getter, newOptions, true);
+ } else {
+ optionchanged(getter, newOptions);
+ }
}
}
@@ -53,10 +73,6 @@ export default ({
return isRenderedAsQueryEditor ? (