diff --git a/cypress-tests/cypress/constants/selectors/workspaceConstants.js b/cypress-tests/cypress/constants/selectors/workspaceConstants.js index 3f2a0e9275..de38a10273 100644 --- a/cypress-tests/cypress/constants/selectors/workspaceConstants.js +++ b/cypress-tests/cypress/constants/selectors/workspaceConstants.js @@ -27,4 +27,7 @@ export const workspaceConstantsSelectors = { constDeleteButton: (constName) => { return `[data-cy="${cyParamName(constName)}-delete-button"]`; }, + constHideButton: (constName) => { + return `[data-cy="${cyParamName(constName)}-constant-visibility"]`; + }, }; diff --git a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/workspace/workspaceConstants.cy.js b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/workspace/workspaceConstants.cy.js index 1fc8cab168..989748a159 100644 --- a/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/workspace/workspaceConstants.cy.js +++ b/cypress-tests/cypress/e2e/happyPath/platform/ceTestcases/workspace/workspaceConstants.cy.js @@ -9,11 +9,9 @@ import { AddNewconstants, } from "Support/utils/workspaceConstants"; import { buttonText } from "Texts/button"; -import { - editAndVerifyWidgetName, -} from "Support/utils/commonWidget"; -import { verifypreview } from "Support/utils/dataSource"; - +import { editAndVerifyWidgetName } from "Support/utils/commonWidget"; +import { verifypreview, createDataQuery } from "Support/utils/dataSource"; +import { dataSourceSelector } from "Selectors/dataSource"; import { selectQueryFromLandingPage, query, @@ -21,12 +19,6 @@ import { } from "Support/utils/queries"; const data = {}; -data.constName = fake.firstName.toLowerCase().replaceAll("[^A-Za-z]", ""); -data.newConstvalue = `New ${data.constName}`; -data.constantsName = fake.firstName.toLowerCase().replaceAll("[^A-Za-z]", ""); -data.constantsValue = "dJ_8Q~BcaMPd"; -data.appName = `${fake.companyName}-App`; -data.slug = data.appName.toLowerCase().replace(/\s+/g, "-"); describe("Workspace constants", () => { const envVar = Cypress.env("environment"); @@ -36,7 +28,16 @@ describe("Workspace constants", () => { cy.skipWalkthrough(); }); it("Verify workspace constants UI and CRUD operations", () => { - cy.get('[data-cy="icon-workspace-constants"]').click(); + data.constName = fake.firstName.toLowerCase().replaceAll("[^A-Za-z]", ""); + data.newConstvalue = `New ${data.constName}`; + data.constantsName = fake.firstName + .toLowerCase() + .replaceAll("[^A-Za-z]", ""); + data.constantsValue = "dJ_8Q~BcaMPd"; + data.appName = `${fake.companyName}-App`; + data.slug = data.appName.toLowerCase().replace(/\s+/g, "-"); + + cy.get(commonSelectors.workspaceConstantsIcon).click(); cy.get(commonSelectors.pageSectionHeader).verifyVisibleElement( "have.text", @@ -88,13 +89,14 @@ describe("Workspace constants", () => { .invoke("attr", "placeholder") .should("eq", "Enter Constant Name"); cy.get(commonSelectors.nameInputField).should("be.visible"); - cy.get(commonSelectors.valueLabel).verifyVisibleElement( - "have.text", - "Value" - ); + cy.get(commonSelectors.valueLabel).should(($el) => { + expect($el.contents().first().text().trim()).to.eq("Value"); + }); + cy.get('[data-cy="encrypted-label"]>').should("be.visible"); + cy.verifyLabel("Encrypted"); cy.get(commonSelectors.valueInputField) .invoke("attr", "placeholder") - .should("eq", "Enter Value"); + .should("eq", "Enter value"); cy.get(commonSelectors.valueInputField).should("be.visible"); cy.get(commonSelectors.cancelButton).verifyVisibleElement( "have.text", @@ -118,6 +120,7 @@ describe("Workspace constants", () => { "Constant name should be between 1 and 32 characters" ); + cy.get(commonSelectors.valueInputField).click(); cy.clearAndType(commonSelectors.valueInputField, " "); cy.get(commonSelectors.valueErrorText).verifyVisibleElement( "have.text", @@ -128,6 +131,7 @@ describe("Workspace constants", () => { cy.get(workspaceConstantsSelectors.addNewConstantButton).click(); cy.clearAndType(commonSelectors.nameInputField, data.constName); + cy.get(commonSelectors.valueInputField).click(); cy.clearAndType(commonSelectors.valueInputField, data.constName); cy.get(workspaceConstantsSelectors.addConstantButton).should("be.enabled"); cy.get(commonSelectors.cancelButton).click(); @@ -137,6 +141,7 @@ describe("Workspace constants", () => { cy.get(workspaceConstantsSelectors.addNewConstantButton).click(); cy.clearAndType(commonSelectors.nameInputField, data.constName); + cy.get(commonSelectors.valueInputField).click(); cy.clearAndType(commonSelectors.valueInputField, data.constName); cy.get(workspaceConstantsSelectors.addConstantButton).click(); cy.verifyToastMessage( @@ -167,6 +172,8 @@ describe("Workspace constants", () => { cy.get( workspaceConstantsSelectors.constantName(data.constName) ).verifyVisibleElement("have.text", data.constName); + + cy.get(workspaceConstantsSelectors.constHideButton(data.constName)).click(); cy.get( workspaceConstantsSelectors.constantValue(data.constName) ).verifyVisibleElement("have.text", data.constName); @@ -189,13 +196,15 @@ describe("Workspace constants", () => { cy.get(commonSelectors.nameInputField) .should("be.visible") .and("be.disabled"); - cy.get(commonSelectors.valueLabel).verifyVisibleElement( - "have.text", - "Value" - ); + cy.get(commonSelectors.valueLabel).should(($el) => { + expect($el.contents().first().text().trim()).to.eq("Value"); + }); cy.get(commonSelectors.valueInputField) + .click() .should("be.visible") .and("have.value", data.constName); + // cy.get(commonSelectors.valueInputField) + cy.get(commonSelectors.cancelButton).verifyVisibleElement( "have.text", "Cancel" @@ -206,6 +215,7 @@ describe("Workspace constants", () => { ); cy.get(workspaceConstantsSelectors.addConstantButton).should("be.disabled"); + cy.get(commonSelectors.valueInputField).click(); cy.clearAndType(commonSelectors.valueInputField, data.newConstvalue); cy.get(workspaceConstantsSelectors.addConstantButton).should("be.enabled"); cy.get(commonSelectors.cancelButton).click(); @@ -214,16 +224,17 @@ describe("Workspace constants", () => { ).verifyVisibleElement("have.text", data.constName); cy.get(workspaceConstantsSelectors.constEditButton(data.constName)).click(); + cy.get(commonSelectors.valueInputField).click(); cy.clearAndType(commonSelectors.valueInputField, data.newConstvalue); cy.get(workspaceConstantsSelectors.addConstantButton).click(); cy.verifyToastMessage( commonSelectors.toastMessage, "Constant updated successfully" ); - cy.get( - workspaceConstantsSelectors.constantValue(data.constName) - ).verifyVisibleElement("have.text", data.newConstvalue); + cy.get(workspaceConstantsSelectors.constantValue(data.constName)) + .should("be.visible") + .and("have.text", data.newConstvalue); cy.get( workspaceConstantsSelectors.constDeleteButton(data.constName) ).click(); @@ -256,77 +267,74 @@ describe("Workspace constants", () => { }); it("should verify the constants resolving value on components and query", () => { - cy.get('[data-cy="icon-workspace-constants"]').click(); - AddNewconstants(data.constantsName, data.constantsValue); - cy.get( - workspaceConstantsSelectors.constantName(data.constantsName) - ).verifyVisibleElement("have.text", data.constantsName); - cy.get( - workspaceConstantsSelectors.constantValue(data.constantsName) - ).verifyVisibleElement("have.text", data.constantsValue); + cy.viewport(1200, 1300); + + data.widgetName = fake.firstName.toLowerCase().replaceAll("[^A-Za-z]", ""); + data.appName = `${fake.companyName}-App`; + data.restapilink = fake.firstName.toLowerCase().replaceAll("[^A-Za-z]", ""); + data.restapiHeaderKey = fake.firstName + .toLowerCase() + .replaceAll("[^A-Za-z]", ""); + data.restapiHeaderValue = fake.firstName + .toLowerCase() + .replaceAll("[^A-Za-z]", ""); + + cy.get(commonSelectors.workspaceConstantsIcon).click(); + AddNewconstants(data.restapilink, "http://localhost:4000/production"); + AddNewconstants(data.restapiHeaderKey, "customHeader"); + AddNewconstants(data.restapiHeaderValue, "key=value"); cy.get(commonSelectors.homePageLogo).click(); cy.wait("@homePage"); - cy.createApp(data.appName); + cy.apiCreateApp(data.appName); + + cy.getCookie("tj_auth_token").then((cookie) => { + const headers = { + "Tj-Workspace-Id": Cypress.env("workspaceId"), + Cookie: `tj_auth_token=${cookie.value}`, + }; + cy.request({ + method: "GET", + url: `http://localhost:3000/api/app-environments/versions?app_id=${Cypress.env( + "appId" + )}`, + headers: headers, + }).then((response) => { + const appVersions = response.body.appVersions; + const appVersionId = appVersions[0].id; + createDataQuery( + appVersionId, + data.restapilink, + data.restapiHeaderKey, + data.restapiHeaderValue + ); + }); + }); - selectQueryFromLandingPage("runjs", "JavaScript"); - addInputOnQueryField("runjs", `return constants.${data.constantsName}`); - query("preview"); - verifypreview("raw", "dJ_8Q~BcaMPd"); + cy.openApp(); - cy.dragAndDropWidget("Text", 550, 350); - editAndVerifyWidgetName(data.constantsName, []); + cy.get(".custom-toggle-switch>.switch>").eq(3).click(); + + cy.waitForAutoSave(); + cy.dragAndDropWidget("Text", 550, 650); + editAndVerifyWidgetName(data.widgetName, []); cy.waitForAutoSave(); cy.get( '[data-cy="textcomponenttextinput-input-field"]' - ).clearAndTypeOnCodeMirror(`{{constants.${data.constantsName}`); + ).clearAndTypeOnCodeMirror(`{{queries.restapi1.data.message`); cy.forceClickOnCanvas(); cy.waitForAutoSave(); - - common.pinInspector(); - // cy.get(".tooltip-inner").invoke("hide"); - cy.get(commonWidgetSelector.sidebarinspector).click(); - cy.get(commonWidgetSelector.inspectorNodeComponents).click(); - cy.get(commonWidgetSelector.nodeComponent(data.constantsName)).click(); - cy.get('[data-cy="inspector-node-text"] > .mx-2').verifyVisibleElement( - "have.text", - `"dJ_8Q~BcaMPd"` - ); - - cy.get('[data-cy="inspector-node-constants"] > .node-key').click(); - cy.get(`[data-cy="inspector-node-${data.constantsName}"] > .node-key`) - .eq(1) - .verifyVisibleElement("have.text", data.constantsName); + cy.get(dataSourceSelector.queryCreateAndRunButton).click(); cy.get( - `[data-cy="inspector-node-${data.constantsName}"] > .mx-2` - ).verifyVisibleElement("have.text", `"dJ_8Q~BcaMPd"`); + commonWidgetSelector.draggableWidget(data.widgetName) + ).verifyVisibleElement("have.text", "Production environment testing"); - cy.get('[data-cy="button-release"]').click(); - cy.get('[data-cy="yes-button"]').click(); - cy.verifyToastMessage(commonSelectors.toastMessage, "Version v1 released"); - - cy.get(commonWidgetSelector.shareAppButton).click(); - cy.clearAndType(commonWidgetSelector.appNameSlugInput, `${data.slug}`); - cy.wait(1500); - cy.get(commonWidgetSelector.modalCloseButton).click(); - cy.forceClickOnCanvas(); - cy.waitForAutoSave(); - cy.wait(500); cy.openInCurrentTab(commonWidgetSelector.previewButton); cy.wait(4000); cy.get( - commonWidgetSelector.draggableWidget(data.constantsName) - ).verifyVisibleElement("have.text", "dJ_8Q~BcaMPd"); - - cy.get('[data-cy="viewer-page-logo"]').click(); - cy.wait("@homePage"); - cy.visit(`/applications/${data.slug}`); - cy.wait(4000); - - cy.get( - commonWidgetSelector.draggableWidget(data.constantsName) - ).verifyVisibleElement("have.text", "dJ_8Q~BcaMPd"); + commonWidgetSelector.draggableWidget(data.widgetName) + ).verifyVisibleElement("have.text", "Production environment testing"); }); }); diff --git a/cypress-tests/cypress/support/utils/dataSource.js b/cypress-tests/cypress/support/utils/dataSource.js index 29939455c2..8ba7a51019 100644 --- a/cypress-tests/cypress/support/utils/dataSource.js +++ b/cypress-tests/cypress/support/utils/dataSource.js @@ -7,7 +7,6 @@ import { dataSourceSelector } from "Selectors/dataSource"; import { dataSourceText } from "Texts/dataSource"; import { navigateToAppEditor } from "Support/utils/common"; - export const verifyCouldnotConnectWithAlert = (dangerText) => { cy.get(postgreSqlSelector.connectionFailedText, { timeout: 10000, @@ -133,7 +132,6 @@ export const addQueryAndOpenEditor = (queryName, query, dbName, appName) => { }); }; - export const verifyValueOnInspector = (queryName, value) => { cy.get('[data-cy="inspector-node-queries"]') .parent() @@ -157,6 +155,40 @@ export const verifyValueOnInspector = (queryName, value) => { export const selectDatasource = (datasourceName) => { cy.get(dataSourceSelector.addedDsSearchIcon).click(); cy.clearAndType(dataSourceSelector.AddedDsSearchBar, datasourceName); - cy.wait(500) - cy.get(`[data-cy="${cyParamName(datasourceName)}-button"]`).click() -} \ No newline at end of file + cy.wait(500); + cy.get(`[data-cy="${cyParamName(datasourceName)}-button"]`).click(); +}; + +export const createDataQuery = (versionId, url, key, value) => { + cy.getCookie("tj_auth_token").then((cookie) => { + const headers = { + "Tj-Workspace-Id": Cypress.env("workspaceId"), + Cookie: `tj_auth_token=${cookie.value}`, + }; + cy.request({ + method: "POST", + url: "http://localhost:3000/api/data_queries", + headers: headers, + body: { + app_id: Cypress.env("appId"), + app_version_id: versionId, + name: "restapi1", + kind: "restapi", + options: { + method: "get", + url: `{{constants.${url}}}`, + url_params: [["", ""]], + headers: [[`{{constants.${key}}}`, `{{constants.${value}}}`]], + body: [["", ""]], + json_body: null, + body_toggle: false, + transformationLanguage: "javascript", + enableTransformation: false, + }, + data_source_id: null, + }, + }).then((response) => { + expect(response.status).to.equal(201); + }); + }); +}; diff --git a/cypress-tests/cypress/support/utils/workspaceConstants.js b/cypress-tests/cypress/support/utils/workspaceConstants.js index 5e874b016a..3634c91908 100644 --- a/cypress-tests/cypress/support/utils/workspaceConstants.js +++ b/cypress-tests/cypress/support/utils/workspaceConstants.js @@ -7,13 +7,17 @@ import * as common from "Support/utils/common"; export const contantsNameValidation = (value, error) => { cy.clearAndType(commonSelectors.nameInputField, value); - cy.get(commonSelectors.nameErrorText).verifyVisibleElement("have.text", error) + cy.get(commonSelectors.nameErrorText).verifyVisibleElement( + "have.text", + error + ); cy.get(workspaceConstantsSelectors.addConstantButton).should("be.disabled"); -} +}; export const AddNewconstants = (name, value) => { cy.get(workspaceConstantsSelectors.addNewConstantButton).click(); cy.clearAndType(commonSelectors.nameInputField, name); + cy.get(commonSelectors.valueInputField).click(); cy.clearAndType(commonSelectors.valueInputField, value); cy.get(workspaceConstantsSelectors.addConstantButton).click(); -} \ No newline at end of file +}; diff --git a/frontend/src/Editor/CodeBuilder/CodeHinter.jsx b/frontend/src/Editor/CodeBuilder/CodeHinter.jsx index b3d0d4c7d2..c0fc187185 100644 --- a/frontend/src/Editor/CodeBuilder/CodeHinter.jsx +++ b/frontend/src/Editor/CodeBuilder/CodeHinter.jsx @@ -103,6 +103,8 @@ export function CodeHinter({ }) { const context = useContext(CodeHinterContext); + const hiddenWorkspaceConstantText = 'Workspace constant values are hidden'; + const darkMode = localStorage.getItem('darkMode') === 'true'; const options = { lineNumbers: lineNumbers ?? false, @@ -116,6 +118,7 @@ export function CodeHinter({ placeholder, }; const currentState = useCurrentState(); + const definedConstants = currentState?.constants; const [realState, setRealState] = useState({ ...currentState, ..._currentState, ...context }); const [currentValue, setCurrentValue] = useState(''); @@ -134,6 +137,10 @@ export function CodeHinter({ const isWorkspaceVariable = typeof currentValue === 'string' && (currentValue.includes('%%client') || currentValue.includes('%%server')); + const isWorkspaceConstant = typeof currentValue === 'string' && currentValue.includes('constants.'); + + const constantRegex = /{{constants\.([a-zA-Z0-9_]+)}}/g; + const slideInStyles = useSpring({ config: { ...config.stiff }, from: { opacity: 0, height: 0 }, @@ -180,10 +187,32 @@ export function CodeHinter({ const getPreviewAndErrorFromValue = (value) => { const customResolvables = getCustomResolvables(); + const invalidConstants = verifyConstant(value); + if (invalidConstants?.length) { + return [value, `undefined constants: ${invalidConstants}`]; + } const [preview, error] = resolveReferences(value, realState, null, customResolvables, true, true); return [preview, error]; }; + const verifyConstant = (value) => { + if (typeof value !== 'string') { + return []; + } + const matches = value.match(constantRegex); + if (!matches) { + return []; + } + const resolvedMatches = matches.map((match) => { + const cleanedMatch = match.replace(/{{constants\./, '').replace(/}}/, ''); + return Object.keys(definedConstants).includes(cleanedMatch) ? null : cleanedMatch; + }); + const invalidConstants = resolvedMatches?.filter((item) => item != null); + if (invalidConstants?.length) { + return invalidConstants; + } + }; + useEffect(() => { setCurrentValue(initialValue); const [preview, error] = getPreviewAndErrorFromValue(initialValue); @@ -240,7 +269,7 @@ export function CodeHinter({ globalPreviewCopy = preview; globalErrorCopy = null; setResolvingError(null); - setResolvedValue(preview); + setResolvedValue(isWorkspaceConstant ? hiddenWorkspaceConstantText : preview); } return [globalPreviewCopy, globalErrorCopy]; @@ -252,7 +281,7 @@ export function CodeHinter({ return () => { if (enablePreview) { setPrevCurrentValue(null); - setResolvedValue(globalPreviewCopy); + setResolvedValue(isWorkspaceConstant ? hiddenWorkspaceConstantText : globalPreviewCopy); setResolvingError(globalErrorCopy); } }; @@ -348,9 +377,9 @@ export function CodeHinter({
- {previewType} + {!isWorkspaceConstant && previewType}
- {isFocused && ( + {isFocused && !isWorkspaceConstant && (
copyToClipboard(content)} icon="copy" tip="Copy to clipboard" />
diff --git a/frontend/src/Editor/Editor.jsx b/frontend/src/Editor/Editor.jsx index 87ee134c34..54065e5bc2 100644 --- a/frontend/src/Editor/Editor.jsx +++ b/frontend/src/Editor/Editor.jsx @@ -77,6 +77,8 @@ enablePatches(); const decimalToHex = (alpha) => (alpha === 0 ? '00' : Math.round(255 * alpha).toString(16)); +const maskedWorkspaceConstantStr = '**************'; + const EditorComponent = (props) => { const { socket } = createWebsocketConnection(props?.params?.id); const mounted = useMounted(); @@ -372,8 +374,7 @@ const EditorComponent = (props) => { orgEnvironmentConstantService.getAll().then(({ constants }) => { const orgConstants = {}; constants.map((constant) => { - const constantValue = constant.values.find((value) => value.environmentName === 'production')['value']; - orgConstants[constant.name] = constantValue; + orgConstants[constant.name] = maskedWorkspaceConstantStr; }); useCurrentStateStore.getState().actions.setCurrentState({ diff --git a/frontend/src/Editor/Viewer.jsx b/frontend/src/Editor/Viewer.jsx index ccae0a6580..a179e1d221 100644 --- a/frontend/src/Editor/Viewer.jsx +++ b/frontend/src/Editor/Viewer.jsx @@ -48,6 +48,7 @@ import DesktopHeader from './Viewer/DesktopHeader'; import './Viewer/viewer.scss'; import useAppDarkMode from '@/_hooks/useAppDarkMode'; +const maskedWorkspaceConstantStr = '**************'; class ViewerComponent extends React.Component { constructor(props) { super(props); @@ -245,7 +246,7 @@ class ViewerComponent extends React.Component { if (variablesResult && Array.isArray(variablesResult)) { variablesResult.map((constant) => { const constantValue = constant.values.find((value) => value.environmentName === 'production')['value']; - orgConstants[constant.name] = constantValue; + orgConstants[constant.name] = maskedWorkspaceConstantStr; }); return { constants: orgConstants, diff --git a/frontend/src/ManageGroupPermissionResources/ManageGroupPermissionResources.jsx b/frontend/src/ManageGroupPermissionResources/ManageGroupPermissionResources.jsx index a8b5dcd023..906517fe1f 100644 --- a/frontend/src/ManageGroupPermissionResources/ManageGroupPermissionResources.jsx +++ b/frontend/src/ManageGroupPermissionResources/ManageGroupPermissionResources.jsx @@ -319,7 +319,9 @@ class ManageGroupPermissionResourcesComponent extends React.Component { const orgEnvironmentPermission = groupPermission ? groupPermission.org_environment_variable_create && groupPermission.org_environment_variable_update && - groupPermission.org_environment_variable_delete + groupPermission.org_environment_variable_delete && + groupPermission.org_environment_constant_create && + groupPermission.org_environment_constant_delete : false; return ( @@ -893,6 +895,8 @@ class ManageGroupPermissionResourcesComponent extends React.Component { org_environment_variable_create: !orgEnvironmentPermission, org_environment_variable_update: !orgEnvironmentPermission, org_environment_variable_delete: !orgEnvironmentPermission, + org_environment_constant_create: !orgEnvironmentPermission, + org_environment_constant_delete: !orgEnvironmentPermission, }); }} checked={orgEnvironmentPermission} diff --git a/frontend/src/ManageOrgConstants/ConstantForm.jsx b/frontend/src/ManageOrgConstants/ConstantForm.jsx index 30ac314fe2..5d6a6a6c13 100644 --- a/frontend/src/ManageOrgConstants/ConstantForm.jsx +++ b/frontend/src/ManageOrgConstants/ConstantForm.jsx @@ -4,6 +4,8 @@ import { ButtonSolid } from '@/_ui/AppButton/AppButton'; import _, { capitalize } from 'lodash'; import { Tooltip } from 'react-tooltip'; import { FormWrapper, textAreaEnterOnSave } from '@/_components/FormWrapper'; +import EyeHide from '../../assets/images/onboardingassets/Icons/EyeHide'; +import EyeShow from '../../assets/images/onboardingassets/Icons/EyeShow'; const ConstantForm = ({ selectedConstant, @@ -19,6 +21,21 @@ const ConstantForm = ({ environments: [{ label: currentEnvironment?.name, value: currentEnvironment?.id }], })); + const [showValue, setShowValue] = useState(false); + + const toggleShowValue = () => { + setShowValue(!showValue); + }; + + const getDisplayedValue = () => { + if (!fields['value']) { + return ''; + } + return showValue ? fields['value'] : '*'.repeat(fields['value'].length); + }; + + const darkMode = localStorage.getItem('darkMode') === 'true'; + const [error, setError] = useState({}); function isValidPropertyName(name) { @@ -133,6 +150,10 @@ const ConstantForm = ({ } }; + const handleBlur = () => { + setShowValue(false); + }; + return (
@@ -177,28 +198,78 @@ const ConstantForm = ({
-