diff --git a/.github/config.json b/.github/config.json index c51b7d1402..91c61bec64 100644 --- a/.github/config.json +++ b/.github/config.json @@ -1 +1 @@ -{"runners":[{"versioning":{"source":"milestones","type":"SemVer"},"prereleaseName":"alpha","issue":{"labels":{"Team Managers Pod":{"conditions":[{"label":"Settings","type":"hasLabel","value":true},{"label":"Git Version Control","type":"hasLabel","value":true},{"label":"Home Page","type":"hasLabel","value":true},{"label":"Import-Export-App","type":"hasLabel","value":true},{"label":"Invite users","type":"hasLabel","value":true},{"label":"Realtime Commenting","type":"hasLabel","value":true},{"label":"SSO","type":"hasLabel","value":true},{"label":"Multi User Realtime","type":"hasLabel","value":true},{"label":"Business Edition","type":"hasLabel","value":true},{"label":"RBAC","type":"hasLabel","value":true},{"label":"ABAC","type":"hasLabel","value":true},{"label":"Billing","type":"hasLabel","value":true},{"label":"Audit Logs","type":"hasLabel","value":true}],"requires":1},"New Developers Pod":{"conditions":[{"label":"Fork App","type":"hasLabel","value":true},{"label":"Omnibar","type":"hasLabel","value":true},{"label":"Onboarding","type":"hasLabel","value":true},{"label":"Telemetry","type":"hasLabel","value":true},{"label":"Entity Explorer","type":"hasLabel","value":true},{"label":"Generate Page","type":"hasLabel","value":true},{"label":"IDE","type":"hasLabel","value":true},{"label":"In App Comms","type":"hasLabel","value":true},{"label":"Sniping Mode","type":"hasLabel","value":true},{"label":"Design System","type":"hasLabel","value":true},{"label":"Example Apps","type":"hasLabel","value":true},{"label":"i18n","type":"hasLabel","value":true},{"label":"Welcome Screen","type":"hasLabel","value":true},{"label":"Templates","type":"hasLabel","value":true},{"label":"IDE Navigation","type":"hasLabel","value":true},{"label":"Login / Signup","type":"hasLabel","value":true},{"label":"Clean URLs","type":"hasLabel","value":true},{"label":"Embedding Apps","type":"hasLabel","value":true}],"requires":1},"BE Coders Pod":{"conditions":[{"label":"SAAS Plugins","type":"hasLabel","value":true},{"label":"SAAS Manager App","type":"hasLabel","value":true},{"label":"Data Platform Pod","type":"hasLabel","value":true},{"label":"Integrations Pod","type":"hasLabel","value":true}],"requires":1},"FE Coders Pod":{"conditions":[{"label":"JS Linting & Errors","type":"hasLabel","value":true},{"label":"Debugger","type":"hasLabel","value":true},{"label":"JS Snippets","type":"hasLabel","value":true},{"label":"Autocomplete","type":"hasLabel","value":true},{"label":"Evaluated Value","type":"hasLabel","value":true},{"label":"Slash Command","type":"hasLabel","value":true},{"label":"New JS Function","type":"hasLabel","value":true},{"label":"JS Promises","type":"hasLabel","value":true},{"label":"Function execution","type":"hasLabel","value":true},{"label":"JS Usability","type":"hasLabel","value":true},{"label":"Code Refactoring","type":"hasLabel","value":true},{"label":"storeValue","type":"hasLabel","value":true},{"label":"OnPageLoad","type":"hasLabel","value":true},{"label":"Framework Functions","type":"hasLabel","value":true},{"label":"AST","type":"hasLabel","value":true},{"label":"Code Editor","type":"hasLabel","value":true},{"label":"JS Objects","type":"hasLabel","value":true},{"label":"JS Evaluation","type":"hasLabel","value":true}],"requires":1},"App Viewers Pod":{"conditions":[{"label":"Button Widget","type":"hasLabel","value":true},{"label":"Chart Widget","type":"hasLabel","value":true},{"label":"Checkbox Widget","type":"hasLabel","value":true},{"label":"Container Widget","type":"hasLabel","value":true},{"label":"Date Picker Widget","type":"hasLabel","value":true},{"label":"Select Widget","type":"hasLabel","value":true},{"label":"File Picker Widget","type":"hasLabel","value":true},{"label":"Form Widget","type":"hasLabel","value":true},{"label":"Image Widget","type":"hasLabel","value":true},{"label":"Input Widget","type":"hasLabel","value":true},{"label":"List Widget","type":"hasLabel","value":true},{"label":"MultiSelect Widget","type":"hasLabel","value":true},{"label":"Map Widget","type":"hasLabel","value":true},{"label":"Modal Widget","type":"hasLabel","value":true},{"label":"Radio Widget","type":"hasLabel","value":true},{"label":"Rich Text Editor Widget","type":"hasLabel","value":true},{"label":"Tab Widget","type":"hasLabel","value":true},{"label":"Table Widget","type":"hasLabel","value":true},{"label":"Text Widget","type":"hasLabel","value":true},{"label":"Video Widget","type":"hasLabel","value":true},{"label":"iFrame","type":"hasLabel","value":true},{"label":"Menu Button","type":"hasLabel","value":true},{"label":"Rating","type":"hasLabel","value":true},{"label":"Widget Validation","type":"hasLabel","value":true},{"label":"reallabel","type":"hasLabel","value":true},{"label":"New Widget","type":"hasLabel","value":true},{"label":"Switch widget","type":"hasLabel","value":true},{"label":"Widget Styling","type":"hasLabel","value":true},{"label":"Audio Widget","type":"hasLabel","value":true},{"label":"Icon Button Widget","type":"hasLabel","value":true},{"label":"Checkbox Group widget","type":"hasLabel","value":true},{"label":"Stat Box Widget","type":"hasLabel","value":true},{"label":"Voice Recorder Widget","type":"hasLabel","value":true},{"label":"Calendar Widget","type":"hasLabel","value":true},{"label":"Menu Button Widget","type":"hasLabel","value":true},{"label":"Divider Widget","type":"hasLabel","value":true},{"label":"Rating Widget","type":"hasLabel","value":true},{"label":"App Navigation","type":"hasLabel","value":true},{"label":"View Mode","type":"hasLabel","value":true},{"label":"Widget Property","type":"hasLabel","value":true},{"label":"Document Viewer Widget","type":"hasLabel","value":true},{"label":"Radio Group Widget","type":"hasLabel","value":true},{"label":"Currency Input Widget","type":"hasLabel","value":true},{"label":"TreeSelect","type":"hasLabel","value":true},{"label":"MultiTree Select Widget","type":"hasLabel","value":true},{"label":"Phone Input Widget","type":"hasLabel","value":true},{"label":"JSON Form","type":"hasLabel","value":true},{"label":"All Widgets","type":"hasLabel","value":true},{"label":"App Theming","type":"hasLabel","value":true},{"label":"Button Group widget","type":"hasLabel","value":true},{"label":"Progress bar widget","type":"hasLabel","value":true},{"label":"Audio Recorder Widget","type":"hasLabel","value":true},{"label":"Camera Widget","type":"hasLabel","value":true},{"label":"Table Widget V2","type":"hasLabel","value":true},{"label":"Branding","type":"hasLabel","value":true},{"label":"Map Chart Widget","type":"hasLabel","value":true},{"label":"Code Scanner Widget","type":"hasLabel","value":true},{"label":"Widget keyboard accessibility","type":"hasLabel","value":true},{"label":"List Widget V2","type":"hasLabel","value":true}],"requires":1},"UI Builders Pod":{"conditions":[{"label":"Property Pane","type":"hasLabel","value":true},{"label":"Pages","type":"hasLabel","value":true},{"label":"Copy Paste","type":"hasLabel","value":true},{"label":"Drag & Drop","type":"hasLabel","value":true},{"label":"Undo/Redo","type":"hasLabel","value":true},{"label":"Responsive Viewport","type":"hasLabel","value":true},{"label":"Widgets Pane","type":"hasLabel","value":true},{"label":"UI Performance","type":"hasLabel","value":true},{"label":"Widget Grouping","type":"hasLabel","value":true},{"label":"Reflow & Resize","type":"hasLabel","value":true},{"label":"Canvas / Grid","type":"hasLabel","value":true},{"label":"Canvas Zooms","type":"hasLabel","value":true},{"label":"Frontend Libraries Upgrade","type":"hasLabel","value":true},{"label":"Auto Height","type":"hasLabel","value":true}],"requires":1},"User Education Pod":{"conditions":[{"label":"Content","type":"hasLabel","value":true},{"label":"Documentation","type":"hasLabel","value":true}],"requires":1},"DevOps Pod":{"conditions":[{"label":"Docker","type":"hasLabel","value":true},{"label":"Super Admin","type":"hasLabel","value":true},{"label":"Deployment","type":"hasLabel","value":true},{"label":"K8s","type":"hasLabel","value":true},{"label":"Email Config","type":"hasLabel","value":true},{"label":"Backup & Restore","type":"hasLabel","value":true}],"requires":1},"Design System Pod":{"conditions":[{"label":"Design System Pod","type":"hasLabel","value":true},{"label":"ads migration","type":"hasLabel","value":true},{"label":"ads deduplication ","type":"hasLabel","value":true}],"requires":1},"Data Platform Pod":{"conditions":[{"label":"Datasource Environments","type":"hasLabel","value":true},{"label":"Datatype issue","type":"hasLabel","value":true},{"label":"Entity Refactor","type":"hasLabel","value":true},{"label":"Core Query Execution","type":"hasLabel","value":true},{"label":"Query Management","type":"hasLabel","value":true},{"label":"Query Settings","type":"hasLabel","value":true},{"label":"SmartSubstitution","type":"hasLabel","value":true},{"label":"Query Generation","type":"hasLabel","value":true},{"label":"Query performance","type":"hasLabel","value":true},{"label":"Suggested Widgets","type":"hasLabel","value":true},{"label":"Page load executions","type":"hasLabel","value":true}],"requires":1},"Integrations Pod":{"conditions":[{"label":"New Datasource","type":"hasLabel","value":true},{"label":"Firestore","type":"hasLabel","value":true},{"label":"Google Sheets","type":"hasLabel","value":true},{"label":"Mongo","type":"hasLabel","value":true},{"label":"Redshift","type":"hasLabel","value":true},{"label":"snowflake","type":"hasLabel","value":true},{"label":"S3","type":"hasLabel","value":true},{"label":"Redis","type":"hasLabel","value":true},{"label":"Postgres","type":"hasLabel","value":true},{"label":"GraphQL Plugin","type":"hasLabel","value":true},{"label":"ArangoDB","type":"hasLabel","value":true},{"label":"MsSQL","type":"hasLabel","value":true},{"label":"REST API plugin","type":"hasLabel","value":true},{"label":"Elastic Search","type":"hasLabel","value":true},{"label":"OAuth","type":"hasLabel","value":true},{"label":"Airtable","type":"hasLabel","value":true},{"label":"CURL","type":"hasLabel","value":true},{"label":"DynamoDB","type":"hasLabel","value":true},{"label":"Zendesk","type":"hasLabel","value":true},{"label":"Hubspot","type":"hasLabel","value":true},{"label":"Query Forms","type":"hasLabel","value":true},{"label":"Twilio","type":"hasLabel","value":true},{"label":"MySQL","type":"hasLabel","value":true},{"label":"Connection pool","type":"hasLabel","value":true},{"label":"Datasources","type":"hasLabel","value":true}],"requires":1}}},"root":"."}],"labels":{"Tab Widget":{"color":"e2c76c","name":"Tab Widget","description":""},"Dont merge":{"color":"ADB39C","name":"Dont merge","description":""},"Epic":{"color":"3E4B9E","name":"Epic","description":"A zenhub epic that describes a project"},"Menu Button Widget":{"color":"235708","name":"Menu Button Widget","description":"Issues related to Menu Button widget"},"Checkbox Group widget":{"color":"D2ACD2","name":"Checkbox Group widget","description":"Issues related to Checkbox Group Widget"},"Input Widget":{"color":"ae65d8","name":"Input Widget","description":""},"Security":{"color":"99139C","name":"Security","description":""},"QA":{"color":"e2ca68","name":"QA","description":""},"Verified":{"color":"9bf416","name":"Verified","description":""},"Wont Fix":{"color":"ffffff","name":"Wont Fix","description":"This will not be worked on"},"MySQL":{"color":"c9ddc6","name":"MySQL","description":"Issues related to MySQL plugin"},"Development":{"color":"9F8A02","name":"Development","description":""},"Help Wanted":{"color":"008672","name":"Help Wanted","description":"Extra attention is needed"},"Home Page":{"color":"9c0c8e","name":"Home Page","description":"Issues related to the application home page"},"Rating Widget":{"color":"235708","name":"Rating Widget","description":"Issues related to the rating widget"},"Stat Box Widget":{"color":"f1c9ce","name":"Stat Box Widget","description":"Issues related to stat box"},"Enhancement":{"color":"a2eeef","name":"Enhancement","description":"New feature or request"},"Settings":{"color":"f7ff60","name":"Settings","description":"organization, team & user settings"},"Fork App":{"color":"5369db","name":"Fork App","description":"Issues related to forking apps"},"Container Widget":{"color":"19AD0D","name":"Container Widget","description":"Container widget"},"Papercut":{"color":"B562F6","name":"Papercut","description":""},"community":{"color":"dded34","name":"community","description":"issues reported by community members"},"Needs Design":{"color":"bfd4f2","name":"Needs Design","description":"needs design or changes to design"},"i18n":{"color":"1799b0","name":"i18n","description":"Represents issues that need to be tackled to handle internationalization"},"Rich Text Editor Widget":{"color":"f72cac","name":"Rich Text Editor Widget","description":""},"Onboarding":{"color":"d5794b","name":"Onboarding","description":"Issues related to onboarding new developers"},"Pages":{"color":"d7fd80","name":"Pages","description":"Issues related to configuring pages"},"skip-changelog":{"color":"06086F","name":"skip-changelog","description":"Adding this label to a PR prevents it from being listed in the changelog"},"Low":{"color":"79e53b","name":"Low","description":"An issue that is neither critical nor breaks a user flow"},"potential-duplicate":{"color":"d3cb2e","name":"potential-duplicate","description":"This label marks issues that are potential duplicates of already open issues"},"Audio Widget":{"color":"447B9A","name":"Audio Widget","description":"Issues related to Audio Widget"},"Firestore":{"color":"8078b0","name":"Firestore","description":"Issues related to the firestore Integration"},"New Widget":{"color":"be4cf2","name":"New Widget","description":"A request for a new widget"},"Performance":{"color":"d30e53","name":"Performance","description":"Page Load and evaluations"},"Modal Widget":{"color":"03846f","name":"Modal Widget","description":""},"UX Improvement":{"color":"f4a089","name":"UX Improvement","description":""},"S3":{"color":"8078b0","name":"S3","description":"Issues related to the S3 plugin"},"Release Blocker":{"color":"5756bf","name":"Release Blocker","description":"This issue must be resolved before the release"},"safari":{"color":"51C6AA","name":"safari","description":"Bugs seen on safari browser"},"Example Apps":{"color":"1799b0","name":"Example Apps","description":"Example apps created for new signups"},"MultiSelect Widget":{"color":"AB62D4","name":"MultiSelect Widget","description":"Issues related to MultiSelect Widget"},"Widget Styling":{"color":"37EA75","name":"Widget Styling","description":"all about widget styling"},"Calendar Widget":{"color":"8c6644","name":"Calendar Widget","description":""},"Website":{"color":"151720","name":"Website","description":"Related to www.appsmith.com website"},"Low effort":{"color":"8B59F0","name":"Low effort","description":"Something that'll take a few days to build"},"App Viewers Pod":{"color":"cd8ef9","name":"App Viewers Pod","description":"This label assigns issues to the app viewers pod"},"Checkbox Widget":{"color":"074ac6","name":"Checkbox Widget","description":""},"Spam":{"color":"620faf","name":"Spam","description":""},"Voice Recorder Widget":{"color":"85bc87","name":"Voice Recorder Widget","description":""},"Select Widget":{"color":"0c669e","name":"Select Widget","description":"Select or dropdown widget"},"Bug":{"color":"d73a4a","name":"Bug","description":"Something isn't working"},"Widget Validation":{"color":"6990BC","name":"Widget Validation","description":"Issues related to widget property validation"},"Generate Page":{"color":"f14274","name":"Generate Page","description":"Issures related to page generation"},"File Picker Widget":{"color":"6ae4f2","name":"File Picker Widget","description":""},"snowflake":{"color":"8078b0","name":"snowflake","description":"Issues related to the snowflake Integration"},"Automation":{"color":"CCAF60","name":"Automation","description":""},"hotfix":{"color":"BA3F1D","name":"hotfix","description":""},"Team Managers Pod":{"color":"bddb81","name":"Team Managers Pod","description":"Issues that team managers care about for the security and efficiency of their teams"},"Import-Export-App":{"color":"a7768a","name":"Import-Export-App","description":"Issues related to importing and exporting apps"},"High effort":{"color":"A7E87B","name":"High effort","description":"Something that'll take more than a month to build"},"Telemetry":{"color":"bc70f9","name":"Telemetry","description":"Issues related to instrumenting appsmith"},"Radio Widget":{"color":"91ef15","name":"Radio Widget","description":""},"Omnibar":{"color":"10b5ce","name":"Omnibar","description":"Issues related to the omnibar for navigation"},"Button Widget":{"color":"34efae","name":"Button Widget","description":""},"Switch widget":{"color":"33A8CE","name":"Switch widget","description":"The switch widget"},"Map Widget":{"color":"7eef7a","name":"Map Widget","description":""},"Task":{"color":"085630","name":"Task","description":"A simple Todo"},"Design System":{"color":"12b715","name":"Design System","description":"Design system"},"opera":{"color":"C63F5B","name":"opera","description":"Any issues identified on the opera browser"},"Login / Signup":{"color":"771e69","name":"Login / Signup","description":"Authentication flows"},"Image Widget":{"color":"8de8ad","name":"Image Widget","description":""},"firefox":{"color":"6d56e2","name":"firefox","description":""},"Property Pane":{"color":"b356ff","name":"Property Pane","description":"Issues related to the behaviour of the property pane"},"Deployment":{"color":"93491f","name":"Deployment","description":"Installation process of appsmith"},"Critical":{"color":"9b1b28","name":"Critical","description":"This issue needs immediate attention. Drop everything else"},"IDE":{"color":"61b2ee","name":"IDE","description":"Issues related to the IDE"},"Production":{"color":"b60205","name":"Production","description":""},"Dependencies":{"color":"0366d6","name":"Dependencies","description":"Pull requests that update a dependency file"},"Google Sheets":{"color":"8078b0","name":"Google Sheets","description":"Issues related to Google Sheets"},"Icon Button Widget":{"color":"D319CE","name":"Icon Button Widget","description":"Issues related to the icon button widget"},"Mongo":{"color":"8078b0","name":"Mongo","description":"Issues related to Mongo DB plugin"},"Documentation":{"color":"a8dff7","name":"Documentation","description":"Improvements or additions to documentation"},"TestGap":{"color":"f28253","name":"TestGap","description":"Issues identified for test plan improvement"},"keyboard shortcut":{"color":"0688B6","name":"keyboard shortcut","description":""},"Git Version Control":{"color":"C4568E","name":"Git Version Control","description":"Issues related to version control"},"Reopen":{"color":"897548","name":"Reopen","description":""},"Redshift":{"color":"8078b0","name":"Redshift","description":"Issues related to the redshift integration"},"Date Picker Widget":{"color":"ef1ce1","name":"Date Picker Widget","description":""},"Entity Explorer":{"color":"a2e2f9","name":"Entity Explorer","description":"Issues related to navigation using the entity explorer"},"JS Linting & Errors":{"color":"E56AA5","name":"JS Linting & Errors","description":"Issues related to JS Linting and errors"},"iFrame":{"color":"3CD1DB","name":"iFrame","description":"Issues related to iFrame"},"Stale":{"color":"ededed","name":"Stale","description":null},"Debugger":{"color":"e79062","name":"Debugger","description":"Issues related to the debugger"},"Quick effort":{"color":"95ED65","name":"Quick effort","description":"Something that'll take a few hours to build"},"Text Widget":{"color":"d130d1","name":"Text Widget","description":""},"Video Widget":{"color":"23dd4b","name":"Video Widget","description":""},"Datasources":{"color":"2cc0d4","name":"Datasources","description":"Issues related to configuring datasource on appsmith"},"error":{"color":"B66773","name":"error","description":"All issues connected to error messages"},"Form Widget":{"color":"09ed77","name":"Form Widget","description":""},"Needs Triaging":{"color":"e8b851","name":"Needs Triaging","description":"Needs attention from maintainers to triage"},"Autocomplete":{"color":"235708","name":"Autocomplete","description":"Issues related to the autocomplete"},"hacktoberfest":{"color":"0052cc","name":"hacktoberfest","description":"All issues that can be solved by the community during Hacktoberfest"},"Medium effort":{"color":"D31156","name":"Medium effort","description":"Something that'll take more than a week but less than a month to build"},"Release":{"color":"57e5e0","name":"Release","description":""},"High":{"color":"c94d14","name":"High","description":"This issue blocks a user from building or impacts a lot of users"},"UI Performance":{"color":"1799b0","name":"UI Performance","description":"Issues related to UI performance"},"UI Builders Pod":{"color":"517fba","name":"UI Builders Pod","description":"Issues that UI Builders face using appsmith"},"Deploy Preview":{"color":"bfdadc","name":"Deploy Preview","description":"Issues found in Deploy Preview"},"Needs Tests":{"color":"8ee263","name":"Needs Tests","description":"Needs automated tests to assert a feature/bug fix"},"Refactor":{"color":"B96662","name":"Refactor","description":"needs refactoring of code"},"Divider Widget":{"color":"235708","name":"Divider Widget","description":"Issues related to the divider widget"},"Table Widget":{"color":"2eead1","name":"Table Widget","description":""},"Needs More Info":{"color":"e54c10","name":"Needs More Info","description":"Needs additional information"},"Good First Issue":{"color":"7057ff","name":"Good First Issue","description":"Good for newcomers"},"UI Improvement":{"color":"9aeef4","name":"UI Improvement","description":""},"Backend":{"color":"d4c5f9","name":"Backend","description":"This marks the issue or pull request to reference server code"},"Frontend":{"color":"87c7f2","name":"Frontend","description":"This label marks the issue or pull request to reference client code"},"In App Comms":{"color":"9168f4","name":"In App Comms","description":"Issues around communication with appsmith instances"},"Chart Widget":{"color":"616ecc","name":"Chart Widget","description":""},"regression":{"color":"ffe5bc","name":"regression","description":""},"List Widget":{"color":"8508A0","name":"List Widget","description":"Issues related to the list widget"},"Duplicate":{"color":"cfd3d7","name":"Duplicate","description":"This issue or pull request already exists"},"JS Snippets":{"color":"8d62d2","name":"JS Snippets","description":"issues related to JS Snippets"},"Copy Paste":{"name":"Copy Paste","description":"Issues related to copy paste","color":"b4f0a9"},"Drag & Drop":{"name":"Drag & Drop","description":"Issues related to the drag & drop experience","color":"92115a"},"BE Coders Pod":{"color":"5d9848","name":"BE Coders Pod","description":"Issues related to users writing code to fetch and update data"},"FE Coders Pod":{"color":"a7effc","name":"FE Coders Pod","description":"Issues related to users writing javascript in appsmith"},"New Developers Pod":{"color":"6310da","name":"New Developers Pod","description":"Issues that new developers face while exploring the IDE"},"Sniping Mode":{"name":"Sniping Mode","description":"Issues related to sniping mode","color":"6310da"},"Redis":{"name":"Redis","description":"Issues related to Redis","color":"8078b0"},"New Datasource":{"color":"60b14c","name":"New Datasource","description":"Requests for new datasources"},"Evaluated Value":{"name":"Evaluated Value","description":"Issues related to evaluated values","color":"39f6e7"},"Undo/Redo":{"name":"Undo/Redo","description":"Issues related to undo/redo","color":"f25880"},"App Navigation":{"name":"App Navigation","description":"Issues related to the topbar navigation and configuring it","color":"12b715"},"Responsive Viewport":{"color":"12b715","name":"Responsive Viewport","description":"Issues seen on different viewports like mobile"},"Widgets Pane":{"name":"Widgets Pane","description":"Issues related to the discovery and organisation of widgets","color":"ad5d78"},"Invite users":{"color":"1799b0","name":"Invite users","description":"Invite users flow and any associated actions"},"View Mode":{"color":"1799b0","name":"View Mode","description":"Issues related to the view mode"},"User Education Pod":{"name":"User Education Pod","description":"Issues related to user education","color":"1799b0"},"Content":{"name":"Content","description":"For content related topics i.e blogs, templates, videos","color":"a8dff7"},"Embedding Apps":{"name":"Embedding Apps","description":"Issues related to embedding","color":"26ef4f"},"Slash Command":{"name":"Slash Command","description":"Issues related to the slash command","color":"a0608e"},"Widget Property":{"name":"Widget Property","description":"Issues related to adding / modifying widget properties across widgets","color":"5e92cb"},"Windows":{"name":"Windows","description":"Issues related exclusively to Windows systems","color":"b4cb8a"},"Old App Issues":{"name":"Old App Issues","description":"Issues related to apps old apps a few weeks old and app issues in stale browser session","color":"87ab18"},"Document Viewer Widget":{"name":"Document Viewer Widget","description":"Issues related to Document Viewer Widget","color":"899d4b"},"Radio Group Widget":{"name":"Radio Group Widget","description":"Issues related to radio group widget","color":"b68495"},"Super Admin":{"name":"Super Admin","description":"Issues related to the super admin page","color":"aa95cf"},"Postgres":{"name":"Postgres","description":"Postgres related issues","color":"8078b0"},"REST API plugin":{"name":"REST API plugin","description":"REST API plugin related issues","color":"8078b0"},"New JS Function":{"name":"New JS Function","description":"Issues related to adding a JS Function","color":"8e8aa4"},"Cannot Reproduce Issue":{"color":"93c9cc","name":"Cannot Reproduce Issue","description":"Issues that cannot be reproduced"},"Widget Grouping":{"name":"Widget Grouping","description":"Issues related to Widget Grouping","color":"a49951"},"K8s":{"name":"K8s","description":"Kubernetes related issues","color":"5f318a"},"Docker":{"name":"Docker","description":"Issues related to docker","color":"89b808"},"Camera Widget":{"name":"Camera Widget","description":"Issues and enhancements related to camera widget","color":"e6038e"},"SAAS Plugins":{"name":"SAAS Plugins","description":"Issues related to SAAS Plugins","color":"ef9c9d"},"JS Promises":{"name":"JS Promises","description":"Issues related to promises","color":"d7771f"},"OnPageLoad":{"name":"OnPageLoad","description":"OnPageLoad issues on functions and queries","color":"50559d"},"Function execution":{"name":"Function execution","description":"JS function execution","color":"a302b0"},"JS Usability":{"name":"JS Usability","description":"usability issues with JS editor and JS elsewhere","color":"a302b0"},"Currency Input Widget":{"name":"Currency Input Widget","description":"Issues related to currency input widget","color":"b2164f"},"TreeSelect":{"name":"TreeSelect","description":"Issues related to TreeSelect Widget","color":"a1633e"},"MultiTree Select Widget":{"name":"MultiTree Select Widget","description":"Issues related to MultiTree Select Widget","color":"a1633e"},"Welcome Screen":{"name":"Welcome Screen","description":"Issues related to the welcome screen","color":"3897be"},"Realtime Commenting":{"color":"a70b86","name":"Realtime Commenting","description":"In-app communication between teams"},"Phone Input Widget":{"name":"Phone Input Widget","description":"Issues related to the Phone Input widget","color":"a70b86"},"JSON Form":{"name":"JSON Form","description":"Issue / features related to the JSON form wiget","color":"46b209"},"All Widgets":{"name":"All Widgets","description":"Issues related to all widgets","color":"972b36"},"V1":{"name":"V1","description":"V1","color":"67ab2e"},"Reflow & Resize":{"name":"Reflow & Resize","description":"All issues related to reflow and resize experience","color":"748a13"},"App Theming":{"name":"App Theming","description":"Items that are related to the App level theming controls epic","color":"8bf430"},"SSO":{"name":"SSO","description":"Issues, requests and enhancements around Single sign-on.","color":"bf019b"},"Multi User Realtime":{"name":"Multi User Realtime","description":"Issues related to multiple users using or editing an application","color":"e7b6ce"},"Templates":{"name":"Templates","description":"Issues related to Templates","color":"c3b541"},"Ready for design":{"name":"Ready for design","description":"this issue is ready for design: it contains clear problem statements and other required information","color":"ebf442"},"Support":{"name":"Support","description":"Issues created by the A-force team to address user queries","color":"1740f3"},"Button Group widget":{"name":"Button Group widget","description":"Issue and enhancements related to the button group widget","color":"f17025"},"GraphQL Plugin":{"name":"GraphQL Plugin","description":"Issues related to GraphQL plugin","color":"8078b0"},"DevOps Pod":{"name":"DevOps Pod","description":"Issues related to devops","color":"d956c7"},"medium":{"name":"medium","description":"Issues that frustrate users due to poor UX","color":"23dfd9"},"ArangoDB":{"name":"ArangoDB","description":"Issues related to arangoDB","color":"8078b0"},"Code Refactoring":{"name":"Code Refactoring","description":"Issues related to code refactoring","color":"76310e"},"Progress bar widget":{"name":"Progress bar widget","description":"To track issues related to progress bar","color":"2d7abf"},"Audio Recorder Widget":{"name":"Audio Recorder Widget","description":"Issues related to Audio Recorder Widget","color":"9accef"},"Airtable":{"name":"Airtable","description":"Issues for Airtable","color":"60885f"},"RBAC":{"name":"RBAC","description":"Issues, requests and enhancements around RBAC.","color":"9211c3"},"Canvas / Grid":{"name":"Canvas / Grid","description":"Issues related to the canvas","color":"16b092"},"Email Config":{"name":"Email Config","description":"Issues related to configuring the email service","color":"2a21d1"},"CURL":{"name":"CURL","description":"Issues related to CURL impor","color":"60885f"},"Canvas Zooms":{"name":"Canvas Zooms","description":"Issues related to zooming the canvas","color":"e6038e"},"business":{"name":"business","description":"Features that will be a part of our business edition","color":"cd59eb"},"Action Pod":{"name":"Action Pod","description":"","color":"ee2e36"},"AutomationGap1":{"color":"a5e07c","name":"AutomationGap1","description":"Issues that needs automated tests"},"A-Force11":{"name":"A-Force11","description":"Issues raised by A-Force team","color":"d667b6"},"A-Force":{"name":"A-Force","description":"Issues raised by A-Force team","color":"274ecc"},"Business Edition":{"name":"Business Edition","description":"Features that will be a part of our business edition","color":"55184d"},"storeValue":{"name":"storeValue","description":"Issues related to the store value function","color":"5d3e66"},"Tests":{"name":"Tests","description":"test item","color":"1c6990"},"DynamoDB":{"name":"DynamoDB","description":"Issues that are related to DynamoDB should have this label","color":"60885f"},"Design System Pod":{"name":"Design System Pod","description":"Design system related issues","color":"6d1c11"},"ABAC":{"color":"e009a5","name":"ABAC","description":"User permissions and access controls"},"Backup & Restore":{"name":"Backup & Restore","description":"Issues related to backup and restore","color":"86874d"},"ads migration":{"name":"ads migration","description":"All issues related to Appsmith design system migration","color":"6d1c11"},"Billing":{"name":"Billing","description":"Billing infrastructure and flows for Business Edition and Trial users","color":"41dd97"},"Datatype issue":{"name":"Datatype issue","description":"Issues that have risen because data types weren't handled","color":"60885f"},"OAuth":{"name":"OAuth","description":"OAuth related bugs or features","color":"60885f"},"Table Widget V2":{"name":"Table Widget V2","description":"Issues related to Table Widget V2","color":"3a7192"},"AST":{"name":"AST","description":"Issues related to maintaining AST logic","color":"418fa4"},"IDE Navigation":{"name":"IDE Navigation","description":"Issues/feature requests related to IDE navigation, and context switching","color":"bc0cba"},"Query performance":{"name":"Query performance","description":"Issues that have to do with lack in performance of query execution","color":"e4d966"},"SAAS Manager App":{"name":"SAAS Manager App","description":"Issues with the SAAS manager app","color":"d427db"},"Twilio":{"name":"Twilio","description":"Issues related to Twilio integration","color":"23ba8d"},"Hubspot":{"name":"Hubspot","description":"Issues related to Hubspot integration","color":"60885f"},"Zendesk":{"name":"Zendesk","description":"Issues related to Zendesk integration","color":"60885f"},"Entity Refactor":{"name":"Entity Refactor","description":"Issues related to refactor logic","color":"418fa4"},"Branding":{"name":"Branding","description":"All issues under branding and whitelabelling appsmith ecosystem","color":"7aaaf1"},"Map Chart Widget":{"name":"Map Chart Widget","description":"Issues related to Map Chart Widgets","color":"c8397f"},"Product Catchup":{"name":"Product Catchup","description":"Issues created in the product catchup","color":"29cd2c"},"Framework Functions":{"name":"Framework Functions","description":"Issues related to internal functions like showAlert(), navigateTo() etc...","color":"c25a09"},"Frontend Libraries Upgrade":{"name":"Frontend Libraries Upgrade","description":"Issues related to frontend libraries upgrade","color":"ede1fc"},"Audit Logs":{"name":"Audit Logs","description":"Audit trails to ensure data security","color":"f3fd62"},"MsSQL":{"name":"MsSQL","description":"Issues related to MsSQL plugin","color":"8078b0"},"Data Platform Pod":{"name":"Data Platform Pod","description":"Issues related to the underlying data platform","color":"3f8c3a"},"Integrations Pod":{"name":"Integrations Pod","description":"Issues related to a specific integration","color":"5dbbb1"},"Datasource Environments":{"name":"Datasource Environments","description":"Issues related to datasource environments","color":"bb7a14"},"Elastic Search":{"name":"Elastic Search","description":"Issues related to the elastic search datasource","color":"8078b0"},"Core Query Execution":{"color":"418fa4","name":"Core Query Execution","description":"Issues related to the execution of all queries"},"Query Management":{"name":"Query Management","description":"Issues related to the CRUD of actions or queries","color":"6a5b42"},"Query Settings":{"name":"Query Settings","description":"Issues related to the settings of all queries","color":"c7da7a"},"Code Editor":{"name":"Code Editor","description":"Issues related to the code editor","color":"4ca16e"},"Query Forms":{"color":"12b253","name":"Query Forms","description":"Isuses related to the query forms"},"JS Objects":{"color":"22962c","name":"JS Objects","description":"Issues related to JS Objects"},"JS Evaluation":{"color":"22962c","name":"JS Evaluation","description":"Issues related to JS evaluation on the platform"},"SmartSubstitution":{"name":"SmartSubstitution","description":"Issues related to Smart substitution of mustache bindings in queries","color":"e4d966"},"Query Generation":{"name":"Query Generation","description":"Issues related to query generation","color":"e4d966"},"Suggested Widgets":{"name":"Suggested Widgets","description":"Issues related to suggesting widgets based on query response","color":"e4d966"},"Page load executions":{"name":"Page load executions","description":"Issues related to page load execution","color":"5696b2"},"Code Scanner Widget":{"name":"Code Scanner Widget","description":"Issues related to code scanner widget","color":"9bc1a0"},"Clean URLs":{"name":"Clean URLs","description":"Issues related to clean URLs epic","color":"112623"},"Widget keyboard accessibility":{"name":"Widget keyboard accessibility","description":"All issues related to keyboard accessibility in widgets","color":"b626fd"},"Connection pool":{"name":"Connection pool","description":"issues to do with connection pooling of various plugins","color":"94fe36"},"List Widget V2":{"name":"List Widget V2","description":"Issues related to the list widget v2","color":"adaaf7"},"Auto Height":{"name":"Auto Height","description":"Issues related to dynamic height of widgets","color":"5149cf"},"cypress_failed_test":{"name":"cypress_failed_test","description":"Cypress failed tests","color":"4745d5"},"ads deduplication ":{"name":"ads deduplication ","description":"Replacing component with ADS components","color":"741fa1"},"Needs validation":{"name":"Needs validation","description":"Needs problem validation before being picked up","color":"66673d"}},"success":true} \ No newline at end of file +{"runners":[{"versioning":{"source":"milestones","type":"SemVer"},"prereleaseName":"alpha","issue":{"labels":{"Team Managers Pod":{"conditions":[{"label":"Settings","type":"hasLabel","value":true},{"label":"Git Version Control","type":"hasLabel","value":true},{"label":"Home Page","type":"hasLabel","value":true},{"label":"Import-Export-App","type":"hasLabel","value":true},{"label":"Invite users","type":"hasLabel","value":true},{"label":"Realtime Commenting","type":"hasLabel","value":true},{"label":"SSO","type":"hasLabel","value":true},{"label":"Multi User Realtime","type":"hasLabel","value":true},{"label":"Business Edition","type":"hasLabel","value":true},{"label":"RBAC","type":"hasLabel","value":true},{"label":"ABAC","type":"hasLabel","value":true},{"label":"Billing","type":"hasLabel","value":true},{"label":"Audit Logs","type":"hasLabel","value":true},{"label":"Multitenancy","type":"hasLabel","value":true}],"requires":1},"New Developers Pod":{"conditions":[{"label":"Fork App","type":"hasLabel","value":true},{"label":"Omnibar","type":"hasLabel","value":true},{"label":"Onboarding","type":"hasLabel","value":true},{"label":"Telemetry","type":"hasLabel","value":true},{"label":"Entity Explorer","type":"hasLabel","value":true},{"label":"Generate Page","type":"hasLabel","value":true},{"label":"IDE","type":"hasLabel","value":true},{"label":"In App Comms","type":"hasLabel","value":true},{"label":"Sniping Mode","type":"hasLabel","value":true},{"label":"Design System","type":"hasLabel","value":true},{"label":"Example Apps","type":"hasLabel","value":true},{"label":"i18n","type":"hasLabel","value":true},{"label":"Welcome Screen","type":"hasLabel","value":true},{"label":"Templates","type":"hasLabel","value":true},{"label":"IDE Navigation","type":"hasLabel","value":true},{"label":"Login / Signup","type":"hasLabel","value":true},{"label":"Clean URLs","type":"hasLabel","value":true},{"label":"Embedding Apps","type":"hasLabel","value":true}],"requires":1},"BE Coders Pod":{"conditions":[{"label":"SAAS Plugins","type":"hasLabel","value":true},{"label":"SAAS Manager App","type":"hasLabel","value":true},{"label":"Data Platform Pod","type":"hasLabel","value":true},{"label":"Integrations Pod","type":"hasLabel","value":true}],"requires":1},"FE Coders Pod":{"conditions":[{"label":"JS Linting & Errors","type":"hasLabel","value":true},{"label":"Debugger","type":"hasLabel","value":true},{"label":"JS Snippets","type":"hasLabel","value":true},{"label":"Autocomplete","type":"hasLabel","value":true},{"label":"Evaluated Value","type":"hasLabel","value":true},{"label":"Slash Command","type":"hasLabel","value":true},{"label":"New JS Function","type":"hasLabel","value":true},{"label":"JS Promises","type":"hasLabel","value":true},{"label":"Function execution","type":"hasLabel","value":true},{"label":"JS Usability","type":"hasLabel","value":true},{"label":"Code Refactoring","type":"hasLabel","value":true},{"label":"storeValue","type":"hasLabel","value":true},{"label":"OnPageLoad","type":"hasLabel","value":true},{"label":"Framework Functions","type":"hasLabel","value":true},{"label":"AST","type":"hasLabel","value":true},{"label":"Code Editor","type":"hasLabel","value":true},{"label":"JS Objects","type":"hasLabel","value":true},{"label":"JS Evaluation","type":"hasLabel","value":true}],"requires":1},"App Viewers Pod":{"conditions":[{"label":"Button Widget","type":"hasLabel","value":true},{"label":"Chart Widget","type":"hasLabel","value":true},{"label":"Checkbox Widget","type":"hasLabel","value":true},{"label":"Container Widget","type":"hasLabel","value":true},{"label":"Date Picker Widget","type":"hasLabel","value":true},{"label":"Select Widget","type":"hasLabel","value":true},{"label":"File Picker Widget","type":"hasLabel","value":true},{"label":"Form Widget","type":"hasLabel","value":true},{"label":"Image Widget","type":"hasLabel","value":true},{"label":"Input Widget","type":"hasLabel","value":true},{"label":"List Widget","type":"hasLabel","value":true},{"label":"MultiSelect Widget","type":"hasLabel","value":true},{"label":"Map Widget","type":"hasLabel","value":true},{"label":"Modal Widget","type":"hasLabel","value":true},{"label":"Radio Widget","type":"hasLabel","value":true},{"label":"Rich Text Editor Widget","type":"hasLabel","value":true},{"label":"Tab Widget","type":"hasLabel","value":true},{"label":"Table Widget","type":"hasLabel","value":true},{"label":"Text Widget","type":"hasLabel","value":true},{"label":"Video Widget","type":"hasLabel","value":true},{"label":"iFrame","type":"hasLabel","value":true},{"label":"Menu Button","type":"hasLabel","value":true},{"label":"Rating","type":"hasLabel","value":true},{"label":"Widget Validation","type":"hasLabel","value":true},{"label":"reallabel","type":"hasLabel","value":true},{"label":"New Widget","type":"hasLabel","value":true},{"label":"Switch widget","type":"hasLabel","value":true},{"label":"Widget Styling","type":"hasLabel","value":true},{"label":"Audio Widget","type":"hasLabel","value":true},{"label":"Icon Button Widget","type":"hasLabel","value":true},{"label":"Checkbox Group widget","type":"hasLabel","value":true},{"label":"Stat Box Widget","type":"hasLabel","value":true},{"label":"Voice Recorder Widget","type":"hasLabel","value":true},{"label":"Calendar Widget","type":"hasLabel","value":true},{"label":"Menu Button Widget","type":"hasLabel","value":true},{"label":"Divider Widget","type":"hasLabel","value":true},{"label":"Rating Widget","type":"hasLabel","value":true},{"label":"App Navigation","type":"hasLabel","value":true},{"label":"View Mode","type":"hasLabel","value":true},{"label":"Widget Property","type":"hasLabel","value":true},{"label":"Document Viewer Widget","type":"hasLabel","value":true},{"label":"Radio Group Widget","type":"hasLabel","value":true},{"label":"Currency Input Widget","type":"hasLabel","value":true},{"label":"TreeSelect","type":"hasLabel","value":true},{"label":"MultiTree Select Widget","type":"hasLabel","value":true},{"label":"Phone Input Widget","type":"hasLabel","value":true},{"label":"JSON Form","type":"hasLabel","value":true},{"label":"All Widgets","type":"hasLabel","value":true},{"label":"App Theming","type":"hasLabel","value":true},{"label":"Button Group widget","type":"hasLabel","value":true},{"label":"Progress bar widget","type":"hasLabel","value":true},{"label":"Audio Recorder Widget","type":"hasLabel","value":true},{"label":"Camera Widget","type":"hasLabel","value":true},{"label":"Table Widget V2","type":"hasLabel","value":true},{"label":"Branding","type":"hasLabel","value":true},{"label":"Map Chart Widget","type":"hasLabel","value":true},{"label":"Code Scanner Widget","type":"hasLabel","value":true},{"label":"Widget keyboard accessibility","type":"hasLabel","value":true},{"label":"List Widget V2","type":"hasLabel","value":true},{"label":"Slider Widget","type":"hasLabel","value":true}],"requires":1},"UI Builders Pod":{"conditions":[{"label":"Property Pane","type":"hasLabel","value":true},{"label":"Pages","type":"hasLabel","value":true},{"label":"Copy Paste","type":"hasLabel","value":true},{"label":"Drag & Drop","type":"hasLabel","value":true},{"label":"Undo/Redo","type":"hasLabel","value":true},{"label":"Responsive Viewport","type":"hasLabel","value":true},{"label":"Widgets Pane","type":"hasLabel","value":true},{"label":"UI Performance","type":"hasLabel","value":true},{"label":"Widget Grouping","type":"hasLabel","value":true},{"label":"Reflow & Resize","type":"hasLabel","value":true},{"label":"Canvas / Grid","type":"hasLabel","value":true},{"label":"Canvas Zooms","type":"hasLabel","value":true},{"label":"Frontend Libraries Upgrade","type":"hasLabel","value":true},{"label":"Auto Height","type":"hasLabel","value":true}],"requires":1},"User Education Pod":{"conditions":[{"label":"Content","type":"hasLabel","value":true},{"label":"Documentation","type":"hasLabel","value":true}],"requires":1},"DevOps Pod":{"conditions":[{"label":"Docker","type":"hasLabel","value":true},{"label":"Super Admin","type":"hasLabel","value":true},{"label":"Deployment","type":"hasLabel","value":true},{"label":"K8s","type":"hasLabel","value":true},{"label":"Email Config","type":"hasLabel","value":true},{"label":"Backup & Restore","type":"hasLabel","value":true}],"requires":1},"Design System Pod":{"conditions":[{"label":"Design System Pod","type":"hasLabel","value":true},{"label":"ads migration","type":"hasLabel","value":true},{"label":"ads deduplication ","type":"hasLabel","value":true},{"label":"ads revamp","type":"hasLabel","value":true},{"label":"ads deduplication","type":"hasLabel","value":true},{"label":"ads grayscale","type":"hasLabel","value":true}],"requires":1},"Data Platform Pod":{"conditions":[{"label":"Datasource Environments","type":"hasLabel","value":true},{"label":"Datatype issue","type":"hasLabel","value":true},{"label":"Entity Refactor","type":"hasLabel","value":true},{"label":"Core Query Execution","type":"hasLabel","value":true},{"label":"Query Management","type":"hasLabel","value":true},{"label":"Query Settings","type":"hasLabel","value":true},{"label":"SmartSubstitution","type":"hasLabel","value":true},{"label":"Query Generation","type":"hasLabel","value":true},{"label":"Query performance","type":"hasLabel","value":true},{"label":"Suggested Widgets","type":"hasLabel","value":true},{"label":"Page load executions","type":"hasLabel","value":true}],"requires":1},"Integrations Pod":{"conditions":[{"label":"New Datasource","type":"hasLabel","value":true},{"label":"Firestore","type":"hasLabel","value":true},{"label":"Google Sheets","type":"hasLabel","value":true},{"label":"Mongo","type":"hasLabel","value":true},{"label":"Redshift","type":"hasLabel","value":true},{"label":"snowflake","type":"hasLabel","value":true},{"label":"S3","type":"hasLabel","value":true},{"label":"Redis","type":"hasLabel","value":true},{"label":"Postgres","type":"hasLabel","value":true},{"label":"GraphQL Plugin","type":"hasLabel","value":true},{"label":"ArangoDB","type":"hasLabel","value":true},{"label":"MsSQL","type":"hasLabel","value":true},{"label":"REST API plugin","type":"hasLabel","value":true},{"label":"Elastic Search","type":"hasLabel","value":true},{"label":"OAuth","type":"hasLabel","value":true},{"label":"Airtable","type":"hasLabel","value":true},{"label":"CURL","type":"hasLabel","value":true},{"label":"DynamoDB","type":"hasLabel","value":true},{"label":"Zendesk","type":"hasLabel","value":true},{"label":"Hubspot","type":"hasLabel","value":true},{"label":"Query Forms","type":"hasLabel","value":true},{"label":"Twilio","type":"hasLabel","value":true},{"label":"MySQL","type":"hasLabel","value":true},{"label":"Connection pool","type":"hasLabel","value":true},{"label":"Datasources","type":"hasLabel","value":true}],"requires":1}}},"root":"."}],"labels":{"Tab Widget":{"color":"e2c76c","name":"Tab Widget","description":""},"Dont merge":{"color":"ADB39C","name":"Dont merge","description":""},"Epic":{"color":"3E4B9E","name":"Epic","description":"A zenhub epic that describes a project"},"Menu Button Widget":{"color":"235708","name":"Menu Button Widget","description":"Issues related to Menu Button widget"},"Checkbox Group widget":{"color":"D2ACD2","name":"Checkbox Group widget","description":"Issues related to Checkbox Group Widget"},"Input Widget":{"color":"ae65d8","name":"Input Widget","description":""},"Security":{"color":"99139C","name":"Security","description":""},"QA":{"color":"e2ca68","name":"QA","description":""},"Verified":{"color":"9bf416","name":"Verified","description":""},"Wont Fix":{"color":"ffffff","name":"Wont Fix","description":"This will not be worked on"},"MySQL":{"color":"c9ddc6","name":"MySQL","description":"Issues related to MySQL plugin"},"Development":{"color":"9F8A02","name":"Development","description":""},"Help Wanted":{"color":"008672","name":"Help Wanted","description":"Extra attention is needed"},"Home Page":{"color":"9c0c8e","name":"Home Page","description":"Issues related to the application home page"},"Rating Widget":{"color":"235708","name":"Rating Widget","description":"Issues related to the rating widget"},"Stat Box Widget":{"color":"f1c9ce","name":"Stat Box Widget","description":"Issues related to stat box"},"Enhancement":{"color":"a2eeef","name":"Enhancement","description":"New feature or request"},"Settings":{"color":"f7ff60","name":"Settings","description":"organization, team & user settings"},"Fork App":{"color":"5369db","name":"Fork App","description":"Issues related to forking apps"},"Container Widget":{"color":"19AD0D","name":"Container Widget","description":"Container widget"},"Papercut":{"color":"B562F6","name":"Papercut","description":""},"community":{"color":"dded34","name":"community","description":"issues reported by community members"},"Needs Design":{"color":"bfd4f2","name":"Needs Design","description":"needs design or changes to design"},"i18n":{"color":"1799b0","name":"i18n","description":"Represents issues that need to be tackled to handle internationalization"},"Rich Text Editor Widget":{"color":"f72cac","name":"Rich Text Editor Widget","description":""},"Onboarding":{"color":"d5794b","name":"Onboarding","description":"Issues related to onboarding new developers"},"Pages":{"color":"d7fd80","name":"Pages","description":"Issues related to configuring pages"},"skip-changelog":{"color":"06086F","name":"skip-changelog","description":"Adding this label to a PR prevents it from being listed in the changelog"},"Low":{"color":"79e53b","name":"Low","description":"An issue that is neither critical nor breaks a user flow"},"potential-duplicate":{"color":"d3cb2e","name":"potential-duplicate","description":"This label marks issues that are potential duplicates of already open issues"},"Audio Widget":{"color":"447B9A","name":"Audio Widget","description":"Issues related to Audio Widget"},"Firestore":{"color":"8078b0","name":"Firestore","description":"Issues related to the firestore Integration"},"New Widget":{"color":"be4cf2","name":"New Widget","description":"A request for a new widget"},"Performance":{"color":"d30e53","name":"Performance","description":"Page Load and evaluations"},"Modal Widget":{"color":"03846f","name":"Modal Widget","description":""},"UX Improvement":{"color":"f4a089","name":"UX Improvement","description":""},"S3":{"color":"8078b0","name":"S3","description":"Issues related to the S3 plugin"},"Release Blocker":{"color":"5756bf","name":"Release Blocker","description":"This issue must be resolved before the release"},"safari":{"color":"51C6AA","name":"safari","description":"Bugs seen on safari browser"},"Example Apps":{"color":"1799b0","name":"Example Apps","description":"Example apps created for new signups"},"MultiSelect Widget":{"color":"AB62D4","name":"MultiSelect Widget","description":"Issues related to MultiSelect Widget"},"Widget Styling":{"color":"37EA75","name":"Widget Styling","description":"all about widget styling"},"Calendar Widget":{"color":"8c6644","name":"Calendar Widget","description":""},"Website":{"color":"151720","name":"Website","description":"Related to www.appsmith.com website"},"Low effort":{"color":"8B59F0","name":"Low effort","description":"Something that'll take a few days to build"},"App Viewers Pod":{"color":"cd8ef9","name":"App Viewers Pod","description":"This label assigns issues to the app viewers pod"},"Checkbox Widget":{"color":"074ac6","name":"Checkbox Widget","description":""},"Spam":{"color":"620faf","name":"Spam","description":""},"Voice Recorder Widget":{"color":"85bc87","name":"Voice Recorder Widget","description":""},"Select Widget":{"color":"0c669e","name":"Select Widget","description":"Select or dropdown widget"},"Bug":{"color":"d73a4a","name":"Bug","description":"Something isn't working"},"Widget Validation":{"color":"6990BC","name":"Widget Validation","description":"Issues related to widget property validation"},"Generate Page":{"color":"f14274","name":"Generate Page","description":"Issures related to page generation"},"File Picker Widget":{"color":"6ae4f2","name":"File Picker Widget","description":""},"snowflake":{"color":"8078b0","name":"snowflake","description":"Issues related to the snowflake Integration"},"Automation":{"color":"CCAF60","name":"Automation","description":""},"hotfix":{"color":"BA3F1D","name":"hotfix","description":""},"Team Managers Pod":{"color":"bddb81","name":"Team Managers Pod","description":"Issues that team managers care about for the security and efficiency of their teams"},"Import-Export-App":{"color":"a7768a","name":"Import-Export-App","description":"Issues related to importing and exporting apps"},"High effort":{"color":"A7E87B","name":"High effort","description":"Something that'll take more than a month to build"},"Telemetry":{"color":"bc70f9","name":"Telemetry","description":"Issues related to instrumenting appsmith"},"Radio Widget":{"color":"91ef15","name":"Radio Widget","description":""},"Omnibar":{"color":"10b5ce","name":"Omnibar","description":"Issues related to the omnibar for navigation"},"Button Widget":{"color":"34efae","name":"Button Widget","description":""},"Switch widget":{"color":"33A8CE","name":"Switch widget","description":"The switch widget"},"Map Widget":{"color":"7eef7a","name":"Map Widget","description":""},"Task":{"color":"085630","name":"Task","description":"A simple Todo"},"Design System":{"color":"12b715","name":"Design System","description":"Design system"},"opera":{"color":"C63F5B","name":"opera","description":"Any issues identified on the opera browser"},"Login / Signup":{"color":"771e69","name":"Login / Signup","description":"Authentication flows"},"Image Widget":{"color":"8de8ad","name":"Image Widget","description":""},"firefox":{"color":"6d56e2","name":"firefox","description":""},"Property Pane":{"color":"b356ff","name":"Property Pane","description":"Issues related to the behaviour of the property pane"},"Deployment":{"color":"93491f","name":"Deployment","description":"Installation process of appsmith"},"Critical":{"color":"9b1b28","name":"Critical","description":"This issue needs immediate attention. Drop everything else"},"IDE":{"color":"61b2ee","name":"IDE","description":"Issues related to the IDE"},"Production":{"color":"b60205","name":"Production","description":""},"Dependencies":{"color":"0366d6","name":"Dependencies","description":"Pull requests that update a dependency file"},"Google Sheets":{"color":"8078b0","name":"Google Sheets","description":"Issues related to Google Sheets"},"Icon Button Widget":{"color":"D319CE","name":"Icon Button Widget","description":"Issues related to the icon button widget"},"Mongo":{"color":"8078b0","name":"Mongo","description":"Issues related to Mongo DB plugin"},"Documentation":{"color":"a8dff7","name":"Documentation","description":"Improvements or additions to documentation"},"TestGap":{"color":"f28253","name":"TestGap","description":"Issues identified for test plan improvement"},"keyboard shortcut":{"color":"0688B6","name":"keyboard shortcut","description":""},"Git Version Control":{"color":"C4568E","name":"Git Version Control","description":"Issues related to version control"},"Reopen":{"color":"897548","name":"Reopen","description":""},"Redshift":{"color":"8078b0","name":"Redshift","description":"Issues related to the redshift integration"},"Date Picker Widget":{"color":"ef1ce1","name":"Date Picker Widget","description":""},"Entity Explorer":{"color":"a2e2f9","name":"Entity Explorer","description":"Issues related to navigation using the entity explorer"},"JS Linting & Errors":{"color":"E56AA5","name":"JS Linting & Errors","description":"Issues related to JS Linting and errors"},"iFrame":{"color":"3CD1DB","name":"iFrame","description":"Issues related to iFrame"},"Stale":{"color":"ededed","name":"Stale","description":null},"Debugger":{"color":"e79062","name":"Debugger","description":"Issues related to the debugger"},"Quick effort":{"color":"95ED65","name":"Quick effort","description":"Something that'll take a few hours to build"},"Text Widget":{"color":"d130d1","name":"Text Widget","description":""},"Video Widget":{"color":"23dd4b","name":"Video Widget","description":""},"Datasources":{"color":"2cc0d4","name":"Datasources","description":"Issues related to configuring datasource on appsmith"},"error":{"color":"B66773","name":"error","description":"All issues connected to error messages"},"Form Widget":{"color":"09ed77","name":"Form Widget","description":""},"Needs Triaging":{"color":"e8b851","name":"Needs Triaging","description":"Needs attention from maintainers to triage"},"Autocomplete":{"color":"235708","name":"Autocomplete","description":"Issues related to the autocomplete"},"hacktoberfest":{"color":"0052cc","name":"hacktoberfest","description":"All issues that can be solved by the community during Hacktoberfest"},"Medium effort":{"color":"D31156","name":"Medium effort","description":"Something that'll take more than a week but less than a month to build"},"Release":{"color":"57e5e0","name":"Release","description":""},"High":{"color":"c94d14","name":"High","description":"This issue blocks a user from building or impacts a lot of users"},"UI Performance":{"color":"1799b0","name":"UI Performance","description":"Issues related to UI performance"},"UI Builders Pod":{"color":"517fba","name":"UI Builders Pod","description":"Issues that UI Builders face using appsmith"},"Deploy Preview":{"color":"bfdadc","name":"Deploy Preview","description":"Issues found in Deploy Preview"},"Needs Tests":{"color":"8ee263","name":"Needs Tests","description":"Needs automated tests to assert a feature/bug fix"},"Refactor":{"color":"B96662","name":"Refactor","description":"needs refactoring of code"},"Divider Widget":{"color":"235708","name":"Divider Widget","description":"Issues related to the divider widget"},"Table Widget":{"color":"2eead1","name":"Table Widget","description":""},"Needs More Info":{"color":"e54c10","name":"Needs More Info","description":"Needs additional information"},"Good First Issue":{"color":"7057ff","name":"Good First Issue","description":"Good for newcomers"},"UI Improvement":{"color":"9aeef4","name":"UI Improvement","description":""},"Backend":{"color":"d4c5f9","name":"Backend","description":"This marks the issue or pull request to reference server code"},"Frontend":{"color":"87c7f2","name":"Frontend","description":"This label marks the issue or pull request to reference client code"},"In App Comms":{"color":"9168f4","name":"In App Comms","description":"Issues around communication with appsmith instances"},"Chart Widget":{"color":"616ecc","name":"Chart Widget","description":""},"regression":{"color":"ffe5bc","name":"regression","description":""},"List Widget":{"color":"8508A0","name":"List Widget","description":"Issues related to the list widget"},"Duplicate":{"color":"cfd3d7","name":"Duplicate","description":"This issue or pull request already exists"},"JS Snippets":{"color":"8d62d2","name":"JS Snippets","description":"issues related to JS Snippets"},"Copy Paste":{"name":"Copy Paste","description":"Issues related to copy paste","color":"b4f0a9"},"Drag & Drop":{"name":"Drag & Drop","description":"Issues related to the drag & drop experience","color":"92115a"},"BE Coders Pod":{"color":"5d9848","name":"BE Coders Pod","description":"Issues related to users writing code to fetch and update data"},"FE Coders Pod":{"color":"a7effc","name":"FE Coders Pod","description":"Issues related to users writing javascript in appsmith"},"New Developers Pod":{"color":"6310da","name":"New Developers Pod","description":"Issues that new developers face while exploring the IDE"},"Sniping Mode":{"name":"Sniping Mode","description":"Issues related to sniping mode","color":"6310da"},"Redis":{"name":"Redis","description":"Issues related to Redis","color":"8078b0"},"New Datasource":{"color":"60b14c","name":"New Datasource","description":"Requests for new datasources"},"Evaluated Value":{"name":"Evaluated Value","description":"Issues related to evaluated values","color":"39f6e7"},"Undo/Redo":{"name":"Undo/Redo","description":"Issues related to undo/redo","color":"f25880"},"App Navigation":{"name":"App Navigation","description":"Issues related to the topbar navigation and configuring it","color":"12b715"},"Responsive Viewport":{"color":"12b715","name":"Responsive Viewport","description":"Issues seen on different viewports like mobile"},"Widgets Pane":{"name":"Widgets Pane","description":"Issues related to the discovery and organisation of widgets","color":"ad5d78"},"Invite users":{"color":"1799b0","name":"Invite users","description":"Invite users flow and any associated actions"},"View Mode":{"color":"1799b0","name":"View Mode","description":"Issues related to the view mode"},"User Education Pod":{"name":"User Education Pod","description":"Issues related to user education","color":"1799b0"},"Content":{"name":"Content","description":"For content related topics i.e blogs, templates, videos","color":"a8dff7"},"Embedding Apps":{"name":"Embedding Apps","description":"Issues related to embedding","color":"26ef4f"},"Slash Command":{"name":"Slash Command","description":"Issues related to the slash command","color":"a0608e"},"Widget Property":{"name":"Widget Property","description":"Issues related to adding / modifying widget properties across widgets","color":"5e92cb"},"Windows":{"name":"Windows","description":"Issues related exclusively to Windows systems","color":"b4cb8a"},"Old App Issues":{"name":"Old App Issues","description":"Issues related to apps old apps a few weeks old and app issues in stale browser session","color":"87ab18"},"Document Viewer Widget":{"name":"Document Viewer Widget","description":"Issues related to Document Viewer Widget","color":"899d4b"},"Radio Group Widget":{"name":"Radio Group Widget","description":"Issues related to radio group widget","color":"b68495"},"Super Admin":{"name":"Super Admin","description":"Issues related to the super admin page","color":"aa95cf"},"Postgres":{"name":"Postgres","description":"Postgres related issues","color":"8078b0"},"REST API plugin":{"name":"REST API plugin","description":"REST API plugin related issues","color":"8078b0"},"New JS Function":{"name":"New JS Function","description":"Issues related to adding a JS Function","color":"8e8aa4"},"Cannot Reproduce Issue":{"color":"93c9cc","name":"Cannot Reproduce Issue","description":"Issues that cannot be reproduced"},"Widget Grouping":{"name":"Widget Grouping","description":"Issues related to Widget Grouping","color":"a49951"},"K8s":{"name":"K8s","description":"Kubernetes related issues","color":"5f318a"},"Docker":{"name":"Docker","description":"Issues related to docker","color":"89b808"},"Camera Widget":{"name":"Camera Widget","description":"Issues and enhancements related to camera widget","color":"e6038e"},"SAAS Plugins":{"name":"SAAS Plugins","description":"Issues related to SAAS Plugins","color":"ef9c9d"},"JS Promises":{"name":"JS Promises","description":"Issues related to promises","color":"d7771f"},"OnPageLoad":{"name":"OnPageLoad","description":"OnPageLoad issues on functions and queries","color":"50559d"},"Function execution":{"name":"Function execution","description":"JS function execution","color":"a302b0"},"JS Usability":{"name":"JS Usability","description":"usability issues with JS editor and JS elsewhere","color":"a302b0"},"Currency Input Widget":{"name":"Currency Input Widget","description":"Issues related to currency input widget","color":"b2164f"},"TreeSelect":{"name":"TreeSelect","description":"Issues related to TreeSelect Widget","color":"a1633e"},"MultiTree Select Widget":{"name":"MultiTree Select Widget","description":"Issues related to MultiTree Select Widget","color":"a1633e"},"Welcome Screen":{"name":"Welcome Screen","description":"Issues related to the welcome screen","color":"3897be"},"Realtime Commenting":{"color":"a70b86","name":"Realtime Commenting","description":"In-app communication between teams"},"Phone Input Widget":{"name":"Phone Input Widget","description":"Issues related to the Phone Input widget","color":"a70b86"},"JSON Form":{"name":"JSON Form","description":"Issue / features related to the JSON form wiget","color":"46b209"},"All Widgets":{"name":"All Widgets","description":"Issues related to all widgets","color":"972b36"},"V1":{"name":"V1","description":"V1","color":"67ab2e"},"Reflow & Resize":{"name":"Reflow & Resize","description":"All issues related to reflow and resize experience","color":"748a13"},"App Theming":{"name":"App Theming","description":"Items that are related to the App level theming controls epic","color":"8bf430"},"SSO":{"name":"SSO","description":"Issues, requests and enhancements around Single sign-on.","color":"bf019b"},"Multi User Realtime":{"name":"Multi User Realtime","description":"Issues related to multiple users using or editing an application","color":"e7b6ce"},"Templates":{"name":"Templates","description":"Issues related to Templates","color":"c3b541"},"Ready for design":{"name":"Ready for design","description":"this issue is ready for design: it contains clear problem statements and other required information","color":"ebf442"},"Support":{"name":"Support","description":"Issues created by the A-force team to address user queries","color":"1740f3"},"Button Group widget":{"name":"Button Group widget","description":"Issue and enhancements related to the button group widget","color":"f17025"},"GraphQL Plugin":{"name":"GraphQL Plugin","description":"Issues related to GraphQL plugin","color":"8078b0"},"DevOps Pod":{"name":"DevOps Pod","description":"Issues related to devops","color":"d956c7"},"medium":{"name":"medium","description":"Issues that frustrate users due to poor UX","color":"23dfd9"},"ArangoDB":{"name":"ArangoDB","description":"Issues related to arangoDB","color":"8078b0"},"Code Refactoring":{"name":"Code Refactoring","description":"Issues related to code refactoring","color":"76310e"},"Progress bar widget":{"name":"Progress bar widget","description":"To track issues related to progress bar","color":"2d7abf"},"Audio Recorder Widget":{"name":"Audio Recorder Widget","description":"Issues related to Audio Recorder Widget","color":"9accef"},"Airtable":{"name":"Airtable","description":"Issues for Airtable","color":"60885f"},"RBAC":{"name":"RBAC","description":"Issues, requests and enhancements around RBAC.","color":"9211c3"},"Canvas / Grid":{"name":"Canvas / Grid","description":"Issues related to the canvas","color":"16b092"},"Email Config":{"name":"Email Config","description":"Issues related to configuring the email service","color":"2a21d1"},"CURL":{"name":"CURL","description":"Issues related to CURL impor","color":"60885f"},"Canvas Zooms":{"name":"Canvas Zooms","description":"Issues related to zooming the canvas","color":"e6038e"},"business":{"name":"business","description":"Features that will be a part of our business edition","color":"cd59eb"},"Action Pod":{"name":"Action Pod","description":"","color":"ee2e36"},"AutomationGap1":{"color":"a5e07c","name":"AutomationGap1","description":"Issues that needs automated tests"},"A-Force11":{"name":"A-Force11","description":"Issues raised by A-Force team","color":"d667b6"},"A-Force":{"name":"A-Force","description":"Issues raised by A-Force team","color":"274ecc"},"Business Edition":{"name":"Business Edition","description":"Features that will be a part of our business edition","color":"55184d"},"storeValue":{"name":"storeValue","description":"Issues related to the store value function","color":"5d3e66"},"Tests":{"name":"Tests","description":"test item","color":"1c6990"},"DynamoDB":{"name":"DynamoDB","description":"Issues that are related to DynamoDB should have this label","color":"60885f"},"Design System Pod":{"name":"Design System Pod","description":"Design system related issues","color":"6d1c11"},"ABAC":{"color":"e009a5","name":"ABAC","description":"User permissions and access controls"},"Backup & Restore":{"name":"Backup & Restore","description":"Issues related to backup and restore","color":"86874d"},"ads migration":{"name":"ads migration","description":"All issues related to Appsmith design system migration","color":"6d1c11"},"Billing":{"name":"Billing","description":"Billing infrastructure and flows for Business Edition and Trial users","color":"41dd97"},"Datatype issue":{"name":"Datatype issue","description":"Issues that have risen because data types weren't handled","color":"60885f"},"OAuth":{"name":"OAuth","description":"OAuth related bugs or features","color":"60885f"},"Table Widget V2":{"name":"Table Widget V2","description":"Issues related to Table Widget V2","color":"3a7192"},"AST":{"name":"AST","description":"Issues related to maintaining AST logic","color":"418fa4"},"IDE Navigation":{"name":"IDE Navigation","description":"Issues/feature requests related to IDE navigation, and context switching","color":"bc0cba"},"Query performance":{"name":"Query performance","description":"Issues that have to do with lack in performance of query execution","color":"e4d966"},"SAAS Manager App":{"name":"SAAS Manager App","description":"Issues with the SAAS manager app","color":"d427db"},"Twilio":{"name":"Twilio","description":"Issues related to Twilio integration","color":"23ba8d"},"Hubspot":{"name":"Hubspot","description":"Issues related to Hubspot integration","color":"60885f"},"Zendesk":{"name":"Zendesk","description":"Issues related to Zendesk integration","color":"60885f"},"Entity Refactor":{"name":"Entity Refactor","description":"Issues related to refactor logic","color":"418fa4"},"Branding":{"name":"Branding","description":"All issues under branding and whitelabelling appsmith ecosystem","color":"7aaaf1"},"Map Chart Widget":{"name":"Map Chart Widget","description":"Issues related to Map Chart Widgets","color":"c8397f"},"Product Catchup":{"name":"Product Catchup","description":"Issues created in the product catchup","color":"29cd2c"},"Framework Functions":{"name":"Framework Functions","description":"Issues related to internal functions like showAlert(), navigateTo() etc...","color":"c25a09"},"Frontend Libraries Upgrade":{"name":"Frontend Libraries Upgrade","description":"Issues related to frontend libraries upgrade","color":"ede1fc"},"Audit Logs":{"name":"Audit Logs","description":"Audit trails to ensure data security","color":"f3fd62"},"MsSQL":{"name":"MsSQL","description":"Issues related to MsSQL plugin","color":"8078b0"},"Data Platform Pod":{"name":"Data Platform Pod","description":"Issues related to the underlying data platform","color":"3f8c3a"},"Integrations Pod":{"name":"Integrations Pod","description":"Issues related to a specific integration","color":"5dbbb1"},"Datasource Environments":{"name":"Datasource Environments","description":"Issues related to datasource environments","color":"bb7a14"},"Elastic Search":{"name":"Elastic Search","description":"Issues related to the elastic search datasource","color":"8078b0"},"Core Query Execution":{"color":"418fa4","name":"Core Query Execution","description":"Issues related to the execution of all queries"},"Query Management":{"name":"Query Management","description":"Issues related to the CRUD of actions or queries","color":"6a5b42"},"Query Settings":{"name":"Query Settings","description":"Issues related to the settings of all queries","color":"c7da7a"},"Code Editor":{"name":"Code Editor","description":"Issues related to the code editor","color":"4ca16e"},"Query Forms":{"color":"12b253","name":"Query Forms","description":"Isuses related to the query forms"},"JS Objects":{"color":"22962c","name":"JS Objects","description":"Issues related to JS Objects"},"JS Evaluation":{"color":"22962c","name":"JS Evaluation","description":"Issues related to JS evaluation on the platform"},"SmartSubstitution":{"name":"SmartSubstitution","description":"Issues related to Smart substitution of mustache bindings in queries","color":"e4d966"},"Query Generation":{"name":"Query Generation","description":"Issues related to query generation","color":"e4d966"},"Suggested Widgets":{"name":"Suggested Widgets","description":"Issues related to suggesting widgets based on query response","color":"e4d966"},"Page load executions":{"name":"Page load executions","description":"Issues related to page load execution","color":"5696b2"},"Code Scanner Widget":{"name":"Code Scanner Widget","description":"Issues related to code scanner widget","color":"9bc1a0"},"Clean URLs":{"name":"Clean URLs","description":"Issues related to clean URLs epic","color":"112623"},"Widget keyboard accessibility":{"name":"Widget keyboard accessibility","description":"All issues related to keyboard accessibility in widgets","color":"b626fd"},"Connection pool":{"name":"Connection pool","description":"issues to do with connection pooling of various plugins","color":"94fe36"},"List Widget V2":{"name":"List Widget V2","description":"Issues related to the list widget v2","color":"adaaf7"},"Auto Height":{"name":"Auto Height","description":"Issues related to dynamic height of widgets","color":"5149cf"},"cypress_failed_test":{"name":"cypress_failed_test","description":"Cypress failed tests","color":"4745d5"},"ads deduplication ":{"name":"ads deduplication ","description":"Replacing component with ADS components","color":"741fa1"},"Needs validation":{"name":"Needs validation","description":"Needs problem validation before being picked up","color":"66673d"},"ads grayscale":{"name":"ads grayscale","description":"Support grayscale color changes","color":"66670c"},"ads revamp":{"name":"ads revamp","description":"All issues related to ads revamp. ","color":"6e41a2"},"ads deduplication":{"name":"ads deduplication","description":"Replacing component with ADS components","color":"7171b8"},"Slider Widget":{"name":"Slider Widget","description":"Issues raised for slider widgets.","color":"2eef5f"},"Multitenancy":{"name":"Multitenancy","description":"Support multitenancy within single appsmith instance","color":"8c49a9"}},"success":true} \ No newline at end of file diff --git a/.github/workflows/fat-migration.yml b/.github/workflows/fat-migration.yml index 2f612d31be..ee7ceedc8c 100644 --- a/.github/workflows/fat-migration.yml +++ b/.github/workflows/fat-migration.yml @@ -218,11 +218,12 @@ jobs: run: echo "I'm alive!" && exit 0 # Setup Java - - name: Set up JDK 1.11 + - name: Set up JDK 17 if: steps.run_result.outputs.run_result != 'success' - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: - java-version: "11.0.10" + distribution: 'temurin' + java-version: '17' # Retrieve maven dependencies from cache. After a successful run, these dependencies are cached again - name: Cache maven dependencies @@ -485,11 +486,12 @@ jobs: run: echo ${{ steps.run_result.outputs.run_result }} # Setup Java - - name: Set up JDK 1.11 + - name: Set up JDK 17 if: steps.run_result.outputs.run_result != 'success' - uses: actions/setup-java@v1 + uses: actions/setup-java@v3 with: - java-version: "11.0.10" + distribution: 'temurin' + java-version: '17' - name: Download the react build artifact uses: actions/download-artifact@v2 diff --git a/.github/workflows/github-release.yml b/.github/workflows/github-release.yml index bf00b365e5..440cfb7232 100644 --- a/.github/workflows/github-release.yml +++ b/.github/workflows/github-release.yml @@ -114,11 +114,11 @@ jobs: with: fetch-depth: 0 - - name: Set up JDK 1.11 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: "11.0.10" - distribution: adopt + distribution: 'temurin' + java-version: '17' # Retrieve maven dependencies from cache. After a successful run, these dependencies are cached again - name: Cache maven dependencies diff --git a/.github/workflows/integration-tests-command.yml b/.github/workflows/integration-tests-command.yml index 1e25b2514c..ece7112df8 100644 --- a/.github/workflows/integration-tests-command.yml +++ b/.github/workflows/integration-tests-command.yml @@ -151,12 +151,12 @@ jobs: run: echo ${{ steps.run_result.outputs.run_result }} # Setup Java - - name: Set up JDK 1.11 + - name: Set up JDK 17 if: steps.run_result.outputs.run_result != 'success' uses: actions/setup-java@v3 with: - java-version: "11.0.10" - distribution: adopt + distribution: 'temurin' + java-version: '17' - name: Download the client build artifact uses: actions/download-artifact@v3 @@ -226,6 +226,7 @@ jobs: -v "$PWD/stacks:/appsmith-stacks" -e APPSMITH_LICENSE_KEY=$APPSMITH_LICENSE_KEY \ -e APPSMITH_AUDITLOG_ENABLED=true \ -e APPSMITH_CLOUD_SERVICES_BASE_URL=http://host.docker.internal:5001 \ + --add-host=host.docker.internal:host-gateway \ fatcontainer - name: Use Node.js 16.14.0 @@ -585,6 +586,13 @@ jobs: if: steps.run_result.outputs.run_result != 'success' run: yarn install --frozen-lockfile + # Setup Java + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + - name: Download the react build artifact if: steps.run_result.outputs.run_result != 'success' uses: actions/download-artifact@v3 diff --git a/.github/workflows/label-checker.yml b/.github/workflows/label-checker.yml index f2fab9a88d..6b03792d46 100644 --- a/.github/workflows/label-checker.yml +++ b/.github/workflows/label-checker.yml @@ -19,5 +19,5 @@ jobs: - uses: docker://agilepathway/pull-request-label-checker:latest with: # Needs to have a Test Plan Approved label if not part of any of the pods below - any_of: Test Plan Approved,App Viewers Pod,User Education Pod,New Developers Pod,Team Managers Pod,UI Builders Pod,Performance,Release Promotion,CI,Design System Pod,DevOps Pod,skip-testPlan + any_of: Test Plan Approved,User Education Pod,DevOps Pod,Performance,CI,Release Promotion,skip-testPlan repo_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/perf-test.yml b/.github/workflows/perf-test.yml index 750985e8fc..d46d43a265 100644 --- a/.github/workflows/perf-test.yml +++ b/.github/workflows/perf-test.yml @@ -121,6 +121,14 @@ jobs: if: steps.run_result.outputs.run_result != 'success' run: yarn install --frozen-lockfile + # Setup Java + - name: Set up JDK 17 + if: steps.run_result.outputs.run_result != 'success' + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' + - name: Download the react build artifact if: steps.run_result.outputs.run_result != 'success' uses: actions/download-artifact@v3 diff --git a/.github/workflows/server-build.yml b/.github/workflows/server-build.yml index e43e1477b8..aab50f0a98 100644 --- a/.github/workflows/server-build.yml +++ b/.github/workflows/server-build.yml @@ -85,12 +85,12 @@ jobs: run: echo "I'm alive!" && exit 0 # Setup Java - - name: Set up JDK 1.11 + - name: Set up JDK 17 if: steps.run_result.outputs.run_result != 'success' uses: actions/setup-java@v3 with: - java-version: "11.0.10" - distribution: adopt + distribution: 'temurin' + java-version: '17' # Retrieve maven dependencies from cache. After a successful run, these dependencies are cached again - name: Cache maven dependencies @@ -125,6 +125,7 @@ jobs: - name: Build and test if: steps.run_result.outputs.run_result != 'success' env: + ACTIVE_PROFILE: test APPSMITH_MONGODB_URI: "mongodb://localhost:27017/mobtools" APPSMITH_CLOUD_SERVICES_BASE_URL: "https://release-cs.appsmith.com" APPSMITH_REDIS_URL: "redis://127.0.0.1:6379" diff --git a/.github/workflows/test-build-docker-image.yml b/.github/workflows/test-build-docker-image.yml index 643f7e0191..38301b0b20 100644 --- a/.github/workflows/test-build-docker-image.yml +++ b/.github/workflows/test-build-docker-image.yml @@ -227,12 +227,12 @@ jobs: run: echo "I'm alive!" && exit 0 # Setup Java - - name: Set up JDK 1.11 + - name: Set up JDK 17 if: steps.run_result.outputs.run_result != 'success' uses: actions/setup-java@v3 with: - java-version: "11.0.10" - distribution: adopt + distribution: 'temurin' + java-version: '17' # Retrieve maven dependencies from cache. After a successful run, these dependencies are cached again - name: Cache maven dependencies @@ -399,15 +399,235 @@ jobs: - name: Save the status of the run run: echo "run_result=success" >> $GITHUB_OUTPUT > ~/run_result + perf-test: needs: [buildClient, buildServer, buildRts] + # Only run if the build step is successful if: success() - name: perf-test - uses: ./.github/workflows/perf-test.yml - secrets: inherit - with: - pr: ${{ github.event.client_payload.pull_request.number }} + runs-on: ubuntu-latest-4-cores + + defaults: + run: + working-directory: app/client + shell: bash + + # Service containers to run with this job. Required for running tests + services: + # Label used to access the service container + redis: + # Docker Hub image for Redis + image: redis + ports: + # Opens tcp port 6379 on the host and service container + - 6379:6379 + mongo: + image: mongo + ports: + - 27017:27017 + steps: + - name: Checkout the merged commit from PR and base branch + if: github.event_name == 'pull_request_review' + uses: actions/checkout@v3 + with: + ref: refs/pull/${{ github.event.pull_request.number }}/merge + + - name: Checkout the head commit of the branch + if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' + uses: actions/checkout@v3 + + # Timestamp will be used to create cache key + - id: timestamp + run: echo "timestamp=$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_OUTPUT + + # In case this is second attempt try restoring status of the prior attempt from cache + - name: Restore the previous run result + uses: actions/cache@v3 + with: + path: | + ~/run_result + key: ${{ github.run_id }}-${{ github.job }}-${{ steps.timestamp.outputs.timestamp }} + restore-keys: | + ${{ github.run_id }}-${{ github.job }}- + # Fetch prior run result + - name: Get the previous run result + id: run_result + run: cat ~/run_result 2>/dev/null || echo 'default' + + # In case of prior failure run the job + - if: steps.run_result.outputs.run_result != 'success' + run: echo "I'm alive!" && exit 0 + + # Set status = success + - name: Save the status of the run + run: echo "run_result=success" >> $GITHUB_OUTPUT > ~/run_result + + - name: Use Node.js 16.14.0 + if: steps.run_result.outputs.run_result != 'success' + uses: actions/setup-node@v3 + with: + node-version: "16.14.0" + + - name: Get yarn cache directory path + if: steps.run_result.outputs.run_result != 'success' + id: yarn-dep-cache-dir-path + run: echo "dir=$(yarn cache dir)" >> $GITHUB_OUTPUT + + # Retrieve npm dependencies from cache. After a successful run, these dependencies are cached again + - name: Cache npm dependencies + if: steps.run_result.outputs.run_result != 'success' + id: yarn-dep-cache + uses: actions/cache@v3 + env: + cache-name: cache-yarn-dependencies + with: + path: | + ${{ steps.yarn-dep-cache-dir-path.outputs.dir }} + key: ${{ runner.os }}-yarn-dep-${{ hashFiles('**/yarn.lock') }} + restore-keys: | + ${{ runner.os }}-yarn-dep- + # Install all the dependencies + - name: Install dependencies + if: steps.run_result.outputs.run_result != 'success' + run: yarn install --frozen-lockfile + - name: Download the react build artifact + if: steps.run_result.outputs.run_result != 'success' + uses: actions/download-artifact@v3 + with: + name: client-build + path: app/client/build + + - name: Download the server build artifact + if: steps.run_result.outputs.run_result != 'success' + uses: actions/download-artifact@v3 + with: + name: server-build + path: app/server/dist + + # Start server + - name: start server + if: steps.run_result.outputs.run_result != 'success' + working-directory: app/server + env: + APPSMITH_MONGODB_URI: "mongodb://localhost:27017/mobtools" + APPSMITH_REDIS_URL: "redis://127.0.0.1:6379" + APPSMITH_ENCRYPTION_PASSWORD: "password" + APPSMITH_ENCRYPTION_SALT: "salt" + APPSMITH_IS_SELF_HOSTED: false + APPSMITH_CLOUD_SERVICES_BASE_URL: https://release-cs.appsmith.com + APPSMITH_CLOUD_SERVICES_USERNAME: "" + APPSMITH_CLOUD_SERVICES_PASSWORD: "" + APPSMITH_GIT_ROOT: "./container-volumes/git-storage" + APPSMITH_AUDITLOG_ENABLED: true + run: | + ls -l + ls -l scripts/ + ls -l dist/ + # Run the server in the background and redirect logs to a log file + ./scripts/start-dev-server.sh &> server-logs.log & + - name: Wait for 30 seconds for server to start + if: steps.run_result.outputs.run_result != 'success' + run: | + sleep 30s + - name: Installing Yarn serve + if: steps.run_result.outputs.run_result != 'success' + run: | + yarn global add serve + echo "$(yarn global bin)" >> $GITHUB_PATH + # Start rts + - name: Start RTS Server + if: steps.run_result.outputs.run_result != 'success' + working-directory: ./app/rts + run: | + cp .env.example .env + ./start-server.sh & + - name: Setting up the perf tests + if: steps.run_result.outputs.run_result != 'success' + shell: bash + env: + APPSMITH_SSL_CERTIFICATE: ${{ secrets.APPSMITH_SSL_CERTIFICATE }} + APPSMITH_SSL_KEY: ${{ secrets.APPSMITH_SSL_KEY }} + CYPRESS_URL: ${{ secrets.CYPRESS_URL }} + CYPRESS_USERNAME: ${{ secrets.CYPRESS_USERNAME }} + CYPRESS_PASSWORD: ${{ secrets.CYPRESS_PASSWORD }} + CYPRESS_TESTUSERNAME1: ${{ secrets.CYPRESS_TESTUSERNAME1 }} + CYPRESS_TESTPASSWORD1: ${{ secrets.CYPRESS_TESTPASSWORD1 }} + CYPRESS_TESTUSERNAME2: ${{ secrets.CYPRESS_TESTUSERNAME2 }} + CYPRESS_TESTPASSWORD2: ${{ secrets.CYPRESS_TESTPASSWORD1 }} + CYPRESS_TESTUSERNAME3: ${{ secrets.CYPRESS_TESTUSERNAME3 }} + CYPRESS_TESTPASSWORD3: ${{ secrets.CYPRESS_TESTPASSWORD3 }} + CYPRESS_TESTUSERNAME4: ${{ secrets.CYPRESS_TESTUSERNAME4 }} + CYPRESS_TESTPASSWORD4: ${{ secrets.CYPRESS_TESTPASSWORD4 }} + CYPRESS_S3_ACCESS_KEY: ${{ secrets.CYPRESS_S3_ACCESS_KEY }} + CYPRESS_S3_SECRET_KEY: ${{ secrets.CYPRESS_S3_SECRET_KEY }} + CYPRESS_APPSMITH_OAUTH2_GOOGLE_CLIENT_ID: ${{ secrets.CYPRESS_APPSMITH_OAUTH2_GOOGLE_CLIENT_ID }} + CYPRESS_APPSMITH_OAUTH2_GOOGLE_CLIENT_SECRET: ${{ secrets.CYPRESS_APPSMITH_OAUTH2_GOOGLE_CLIENT_SECRET }} + CYPRESS_APPSMITH_OAUTH2_GITHUB_CLIENT_ID: ${{ secrets.CYPRESS_APPSMITH_OAUTH2_GITHUB_CLIENT_ID }} + CYPRESS_APPSMITH_OAUTH2_GITHUB_CLIENT_SECRET: ${{ secrets.CYPRESS_APPSMITH_OAUTH2_GITHUB_CLIENT_SECRET }} + APPSMITH_DISABLE_TELEMETRY: true + APPSMITH_GOOGLE_MAPS_API_KEY: ${{ secrets.APPSMITH_GOOGLE_MAPS_API_KEY }} + POSTGRES_PASSWORD: postgres + run: | + ./cypress/setup-test.sh + - name: Checkout Performance Infra code + uses: actions/checkout@v3 + with: + repository: appsmithorg/performance-infra + token: ${{ secrets.APPSMITH_PERF_INFRA_REPO_PAT }} + ref: main + path: app/client/perf + + - name: Installing performance tests dependencies + if: steps.run_result.outputs.run_result != 'success' + working-directory: app/client/perf + shell: bash + run: yarn install --frozen-lockfile + + - name: Change test script permissions + if: steps.run_result.outputs.run_result != 'success' + working-directory: app/client/perf + run: chmod +x ./start-test.sh + + - name: Run performance tests + if: steps.run_result.outputs.run_result != 'success' + working-directory: app/client/perf + shell: bash + env: + APPSMITH_SSL_CERTIFICATE: ${{ secrets.APPSMITH_SSL_CERTIFICATE }} + APPSMITH_SSL_KEY: ${{ secrets.APPSMITH_SSL_KEY }} + CYPRESS_TESTUSERNAME1: ${{ secrets.CYPRESS_TESTUSERNAME9 }} + CYPRESS_TESTPASSWORD1: ${{ secrets.CYPRESS_TESTPASSWORD9 }} + APPSMITH_PERFORMANCE_DB_CONFIG: ${{ secrets.APPSMITH_PERFORMANCE_DB_CONFIG }} + APPSMITH_PERFORMANCE_S3_KEY: ${{secrets.APPSMITH_PERFORMANCE_S3_KEY}} + APPSMITH_PERFORMANCE_S3_SECRET: ${{secrets.APPSMITH_PERFORMANCE_S3_SECRET}} + APPSMITH_PERFORMANCE_DB_PASSWORD: ${{secrets.APPSMITH_PERFORMANCE_DB_PASSWORD}} + APPSMITH_PERFORMANCE_DB_HOST: ${{secrets.APPSMITH_PERFORMANCE_DB_HOST}} + PERF_GITHUB_PAT: ${{ secrets.APPSMITH_PERF_INFRA_REPO_PAT }} + APPSMITH_DISABLE_TELEMETRY: true + POSTGRES_PASSWORD: postgres + NODE_TLS_REJECT_UNAUTHORIZED: "0" + MACHINE: ubuntu-latest-4-cores + + run: ./start-test.sh + + # Restore the previous built bundle if present. If not push the newly built into the cache + - name: Restore the previous bundle + uses: actions/cache@v3 + with: + path: | + app/client/perf/traces + key: ${{ github.run_id }}-${{ github.job }}-${{ steps.timestamp.outputs.timestamp }} + restore-keys: | + ${{ github.run_id }}-${{ github.job }} + - uses: actions/upload-artifact@v3 + with: + name: performance-summaries + path: app/client/perf/traces + + # Set status = success + - name: Save the status of the run + run: echo "run_result=success" >> $GITHUB_OUTPUT > ~/run_result + fat-container-test: needs: [buildClient, buildServer, buildRts] @@ -514,12 +734,12 @@ jobs: run: echo ${{ steps.run_result.outputs.run_result }} # Setup Java - - name: Set up JDK 1.11 + - name: Set up JDK 17 if: steps.run_result.outputs.run_result != 'success' uses: actions/setup-java@v3 with: - java-version: "11.0.10" - distribution: adopt + distribution: 'temurin' + java-version: '17' - name: Download the react build artifact uses: actions/download-artifact@v3 @@ -587,8 +807,9 @@ jobs: cd fatcontainerlocal docker run -d --name appsmith -p 80:80 -p 9001:9001 \ -v "$PWD/stacks:/appsmith-stacks" -e APPSMITH_LICENSE_KEY=$APPSMITH_LICENSE_KEY \ - -e APPSMITH_CLOUD_SERVICES_BASE_URL=http://host.docker.internal:5001 \ -e APPSMITH_AUDITLOG_ENABLED=true \ + -e APPSMITH_CLOUD_SERVICES_BASE_URL=http://host.docker.internal:5001 \ + --add-host=host.docker.internal:host-gateway \ fatcontainer - name: Use Node.js 16.14.0 @@ -945,12 +1166,12 @@ jobs: run: echo ${{ steps.run_result.outputs.run_result }} # Setup Java - - name: Set up JDK 1.11 + - name: Set up JDK 17 if: steps.run_result.outputs.run_result != 'success' uses: actions/setup-java@v3 with: - java-version: "11.0.10" - distribution: adopt + distribution: 'temurin' + java-version: '17' - name: Download the server build artifact if: steps.run_result.outputs.run_result != 'success' diff --git a/Dockerfile b/Dockerfile index c6b096d552..a8381c98cb 100644 --- a/Dockerfile +++ b/Dockerfile @@ -13,9 +13,12 @@ ENV LC_ALL C.UTF-8 RUN apt-get update \ && apt-get upgrade --yes \ && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends --yes \ - supervisor curl cron certbot nginx gnupg netcat openssh-client \ - software-properties-common gettext openjdk-11-jdk \ + supervisor curl cron certbot nginx gnupg wget netcat openssh-client \ + software-properties-common gettext \ python3-pip python-setuptools git ca-certificates-java \ + && wget -O - https://packages.adoptium.net/artifactory/api/gpg/key/public | apt-key add - \ + && echo "deb https://packages.adoptium.net/artifactory/deb $(awk -F= '/^VERSION_CODENAME/{print$2}' /etc/os-release) main" | tee /etc/apt/sources.list.d/adoptium.list \ + && apt-get update && apt-get install --no-install-recommends --yes temurin-17-jdk \ && pip install --no-cache-dir git+https://github.com/coderanger/supervisor-stdout@973ba19967cdaf46d9c1634d1675fc65b9574f6e \ && apt-get remove --yes git python3-pip @@ -67,7 +70,7 @@ COPY ./deploy/docker/templates/nginx/* \ templates/ # Add bootstrapfile -COPY ./deploy/docker/entrypoint.sh ./deploy/docker/scripts/* ./scripts/start-backend.sh ./ +COPY ./deploy/docker/entrypoint.sh ./deploy/docker/scripts/* ./ # Add util tools COPY ./deploy/docker/utils ./utils @@ -81,7 +84,7 @@ COPY ./deploy/docker/templates/supervisord/ templates/supervisord/ COPY ./deploy/docker/templates/cron.d /etc/cron.d/ RUN chmod 0644 /etc/cron.d/* -RUN chmod +x entrypoint.sh renew-certificate.sh healthcheck.sh start-backend.sh +RUN chmod +x entrypoint.sh renew-certificate.sh healthcheck.sh # Disable setuid/setgid bits for the files inside container. RUN find / \( -path /proc -prune \) -o \( \( -perm -2000 -o -perm -4000 \) -print -exec chmod -s '{}' + \) || true diff --git a/README.md b/README.md index abbec405bb..e348709c37 100644 --- a/README.md +++ b/README.md @@ -180,54 +180,55 @@ Lets build great software together. [![nayan-rafiq](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/83352306?v=4&w=50&h=50&mask=circle)](https://github.com/nayan-rafiq) [![jsartisan](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/6636360?v=4&w=50&h=50&mask=circle)](https://github.com/jsartisan) [![yaldram](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/13429535?v=4&w=50&h=50&mask=circle)](https://github.com/yaldram) +[![Rishabh-Rathod](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/23132741?v=4&w=50&h=50&mask=circle)](https://github.com/Rishabh-Rathod) [![aswathkk](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/10436935?v=4&w=50&h=50&mask=circle)](https://github.com/aswathkk) [![ankitakinger](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/28362912?v=4&w=50&h=50&mask=circle)](https://github.com/ankitakinger) -[![Rishabh-Rathod](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/23132741?v=4&w=50&h=50&mask=circle)](https://github.com/Rishabh-Rathod) [![sbalaji1192](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/5328605?v=4&w=50&h=50&mask=circle)](https://github.com/sbalaji1192) [![ohansFavour](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/46670083?v=4&w=50&h=50&mask=circle)](https://github.com/ohansFavour) [![Aishwarya-U-R](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/91450662?v=4&w=50&h=50&mask=circle)](https://github.com/Aishwarya-U-R) [![Irongade](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/37867493?v=4&w=50&h=50&mask=circle)](https://github.com/Irongade) +[![SatishGandham](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/441914?v=4&w=50&h=50&mask=circle)](https://github.com/SatishGandham) [![prsidhu](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/5424788?v=4&w=50&h=50&mask=circle)](https://github.com/prsidhu) [![pranavkanade](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/13262095?v=4&w=50&h=50&mask=circle)](https://github.com/pranavkanade) -[![SatishGandham](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/441914?v=4&w=50&h=50&mask=circle)](https://github.com/SatishGandham) +[![yatinappsmith](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/84702014?v=4&w=50&h=50&mask=circle)](https://github.com/yatinappsmith) [![somangshu](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/11089579?v=4&w=50&h=50&mask=circle)](https://github.com/somangshu) [![ApekshaBhosale](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/7846888?v=4&w=50&h=50&mask=circle)](https://github.com/ApekshaBhosale) -[![yatinappsmith](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/84702014?v=4&w=50&h=50&mask=circle)](https://github.com/yatinappsmith) [![sidhantgoel](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/3933675?v=4&w=50&h=50&mask=circle)](https://github.com/sidhantgoel) [![rahulramesha](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/71900764?v=4&w=50&h=50&mask=circle)](https://github.com/rahulramesha) +[![IAmAnubhavSaini](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/1573771?v=4&w=50&h=50&mask=circle)](https://github.com/IAmAnubhavSaini) [![Parthvi12](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/80334441?v=4&w=50&h=50&mask=circle)](https://github.com/Parthvi12) [![marks0351](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/35134347?v=4&w=50&h=50&mask=circle)](https://github.com/marks0351) [![albinAppsmith](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/87797149?v=4&w=50&h=50&mask=circle)](https://github.com/albinAppsmith) -[![ayushpahwa](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/8526215?v=4&w=50&h=50&mask=circle)](https://github.com/ayushpahwa) [![AmanAgarwal041](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/7565635?v=4&w=50&h=50&mask=circle)](https://github.com/AmanAgarwal041) +[![ayushpahwa](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/8526215?v=4&w=50&h=50&mask=circle)](https://github.com/ayushpahwa) [![ashit-rath](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/88306433?v=4&w=50&h=50&mask=circle)](https://github.com/ashit-rath) [![areyabhishek](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/30255708?v=4&w=50&h=50&mask=circle)](https://github.com/areyabhishek) [![ankurrsinghal](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/17961105?v=4&w=50&h=50&mask=circle)](https://github.com/ankurrsinghal) [![rimildeyjsr](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/10229595?v=4&w=50&h=50&mask=circle)](https://github.com/rimildeyjsr) [![vishnu-gp](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/9128194?v=4&w=50&h=50&mask=circle)](https://github.com/vishnu-gp) -[![cokoghenun](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/17744578?v=4&w=50&h=50&mask=circle)](https://github.com/cokoghenun) [![keyurparalkar](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/14138515?v=4&w=50&h=50&mask=circle)](https://github.com/keyurparalkar) [![eco-monk](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/66776129?v=4&w=50&h=50&mask=circle)](https://github.com/eco-monk) +[![cokoghenun](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/17744578?v=4&w=50&h=50&mask=circle)](https://github.com/cokoghenun) [![vihar](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/16307796?v=4&w=50&h=50&mask=circle)](https://github.com/vihar) [![ChandanBalajiBP](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/104058110?v=4&w=50&h=50&mask=circle)](https://github.com/ChandanBalajiBP) +[![dhruvikn](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/22471214?v=4&w=50&h=50&mask=circle)](https://github.com/dhruvikn) [![berzerkeer](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/74818788?v=4&w=50&h=50&mask=circle)](https://github.com/berzerkeer) [![souma-ghosh](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/103924539?v=4&w=50&h=50&mask=circle)](https://github.com/souma-ghosh) -[![dhruvikn](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/22471214?v=4&w=50&h=50&mask=circle)](https://github.com/dhruvikn) +[![tanvibhakta](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/13763558?v=4&w=50&h=50&mask=circle)](https://github.com/tanvibhakta) [![subrata71](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/3524599?v=4&w=50&h=50&mask=circle)](https://github.com/subrata71) [![sum35h](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/20785806?v=4&w=50&h=50&mask=circle)](https://github.com/sum35h) -[![tanvibhakta](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/13763558?v=4&w=50&h=50&mask=circle)](https://github.com/tanvibhakta) [![sneha122](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/30018882?v=4&w=50&h=50&mask=circle)](https://github.com/sneha122) [![nsarupr](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/20905988?v=4&w=50&h=50&mask=circle)](https://github.com/nsarupr) [![pratapaprasanna](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/15846947?v=4&w=50&h=50&mask=circle)](https://github.com/pratapaprasanna) [![sondermanish](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/107841575?v=4&w=50&h=50&mask=circle)](https://github.com/sondermanish) -[![Pranay105](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/48308728?v=4&w=50&h=50&mask=circle)](https://github.com/Pranay105) -[![vaibh1297](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/40293928?v=4&w=50&h=50&mask=circle)](https://github.com/vaibh1297) -[![ankitsrivas14](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/67647761?v=4&w=50&h=50&mask=circle)](https://github.com/ankitsrivas14) -[![NilanshBansal](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/25542733?v=4&w=50&h=50&mask=circle)](https://github.com/NilanshBansal) -[![ravikp7](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/13567359?v=4&w=50&h=50&mask=circle)](https://github.com/ravikp7) -[![gitstart-appsmith](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/114667275?v=4&w=50&h=50&mask=circle)](https://github.com/gitstart-appsmith) -[![kocharrahul7](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/20532920?v=4&w=50&h=50&mask=circle)](https://github.com/kocharrahul7) [![rohitagarwal88](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/890915?v=4&w=50&h=50&mask=circle)](https://github.com/rohitagarwal88) +[![vaibh1297](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/40293928?v=4&w=50&h=50&mask=circle)](https://github.com/vaibh1297) +[![Pranay105](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/48308728?v=4&w=50&h=50&mask=circle)](https://github.com/Pranay105) +[![NilanshBansal](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/25542733?v=4&w=50&h=50&mask=circle)](https://github.com/NilanshBansal) +[![ankitsrivas14](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/67647761?v=4&w=50&h=50&mask=circle)](https://github.com/ankitsrivas14) +[![gitstart-appsmith](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/114667275?v=4&w=50&h=50&mask=circle)](https://github.com/gitstart-appsmith) +[![ravikp7](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/13567359?v=4&w=50&h=50&mask=circle)](https://github.com/ravikp7) +[![kocharrahul7](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/20532920?v=4&w=50&h=50&mask=circle)](https://github.com/kocharrahul7) [![ramsaptami](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/79509062?v=4&w=50&h=50&mask=circle)](https://github.com/ramsaptami) [![AS-Laguna](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/101155659?v=4&w=50&h=50&mask=circle)](https://github.com/AS-Laguna) [![vivonk](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/25587962?v=4&w=50&h=50&mask=circle)](https://github.com/vivonk) @@ -238,7 +239,6 @@ Lets build great software together. [![tejasahluwalia](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/39881648?v=4&w=50&h=50&mask=circle)](https://github.com/tejasahluwalia) [![Vijetha-Kaja](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/119562824?v=4&w=50&h=50&mask=circle)](https://github.com/Vijetha-Kaja) [![appsmithguru](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/116803038?v=4&w=50&h=50&mask=circle)](https://github.com/appsmithguru) -[![IAmAnubhavSaini](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/1573771?v=4&w=50&h=50&mask=circle)](https://github.com/IAmAnubhavSaini) [![hiteshjoshi](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/217911?v=4&w=50&h=50&mask=circle)](https://github.com/hiteshjoshi) [![rlnorthcutt](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/673633?v=4&w=50&h=50&mask=circle)](https://github.com/rlnorthcutt) [![tomjose92](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/5265702?v=4&w=50&h=50&mask=circle)](https://github.com/tomjose92) @@ -334,6 +334,7 @@ Lets build great software together. [![Bhavin789](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/15359575?v=4&w=50&h=50&mask=circle)](https://github.com/Bhavin789) [![bhuvanaindukuri](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/29200083?v=4&w=50&h=50&mask=circle)](https://github.com/bhuvanaindukuri) [![donno2048](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/61805754?v=4&w=50&h=50&mask=circle)](https://github.com/donno2048) +[![jacobwgillespie](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/130874?v=4&w=50&h=50&mask=circle)](https://github.com/jacobwgillespie) [![reachtokish](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/10685261?v=4&w=50&h=50&mask=circle)](https://github.com/reachtokish) [![nuwan94](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/25694570?v=4&w=50&h=50&mask=circle)](https://github.com/nuwan94) [![OmkarPh](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/48476025?v=4&w=50&h=50&mask=circle)](https://github.com/OmkarPh) @@ -341,6 +342,7 @@ Lets build great software together. [![rafaeelaudibert](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/32079912?v=4&w=50&h=50&mask=circle)](https://github.com/rafaeelaudibert) [![samyakjain10](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/56185041?v=4&w=50&h=50&mask=circle)](https://github.com/samyakjain10) [![Jain-Sanchit](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/31254015?v=4&w=50&h=50&mask=circle)](https://github.com/Jain-Sanchit) +[![irfan-ansari-au28](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/18440293?v=4&w=50&h=50&mask=circle)](https://github.com/irfan-ansari-au28) [![sheetal2001p](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/76812440?v=4&w=50&h=50&mask=circle)](https://github.com/sheetal2001p) [![Hard-Coder05](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/54059881?v=4&w=50&h=50&mask=circle)](https://github.com/Hard-Coder05) [![vj-codes](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/60894542?v=4&w=50&h=50&mask=circle)](https://github.com/vj-codes) @@ -370,7 +372,6 @@ Lets build great software together. [![felixsuarez0727](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/25110207?v=4&w=50&h=50&mask=circle)](https://github.com/felixsuarez0727) [![gitstart](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/1501599?v=4&w=50&h=50&mask=circle)](https://github.com/gitstart) [![ishaanmehta4](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/61118240?v=4&w=50&h=50&mask=circle)](https://github.com/ishaanmehta4) -[![jacobwgillespie](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/130874?v=4&w=50&h=50&mask=circle)](https://github.com/jacobwgillespie) [![JarLob](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/26652396?v=4&w=50&h=50&mask=circle)](https://github.com/JarLob) [![JeffResc](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/5752328?v=4&w=50&h=50&mask=circle)](https://github.com/JeffResc) [![jmakhack](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/1442227?v=4&w=50&h=50&mask=circle)](https://github.com/jmakhack) @@ -421,6 +422,7 @@ Lets build great software together. [![nupur-singhal1992](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/35266222?v=4&w=50&h=50&mask=circle)](https://github.com/nupur-singhal1992) [![nzidol](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/13618973?v=4&w=50&h=50&mask=circle)](https://github.com/nzidol) [![onifs10](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/48095055?v=4&w=50&h=50&mask=circle)](https://github.com/onifs10) +[![ps-xaf](https://images.weserv.nl/?url=https://avatars.githubusercontent.com/u/4478450?v=4&w=50&h=50&mask=circle)](https://github.com/ps-xaf) ## License diff --git a/app/client/cypress/fixtures/Templates/MockAppViewerUser.json b/app/client/cypress/fixtures/Templates/MockAppViewerUser.json new file mode 100644 index 0000000000..e0145ac9b0 --- /dev/null +++ b/app/client/cypress/fixtures/Templates/MockAppViewerUser.json @@ -0,0 +1,230 @@ +{ + "responseMeta": { + "status": 200, + "success": true + }, + "data": { + "user": { + "id": "63abf1f3cf1ed12127ce3b01", + "userPermissions": [], + "name": "test1@sharklasers.com", + "email": "test1@sharklasers.com", + "source": "FORM", + "isEnabled": true, + "groupIds": [], + "permissions": [], + "isAnonymous": false, + "tenantId": "63abe1a2cf1ed12127ce3ac4", + "enabled": true, + "username": "test1@sharklasers.com", + "accountNonExpired": true, + "accountNonLocked": true, + "credentialsNonExpired": true, + "claims": {}, + "address": {}, + "new": false + }, + "workspaceApplications": [ + { + "workspace": { + "id": "63ad2c156e7a9012ada704c7", + "userPermissions": [ + "inviteUsers:workspace", + "read:workspaces", + "execute:workspaceDatasources", + "read:workspaceApplications" + ], + "name": "Untitled workspace 1", + "email": "test@sharklasers.com", + "plugins": [ + { + "userPermissions": [], + "pluginId": "63abe19ecf1ed12127ce3a5a", + "status": "FREE", + "new": true + }, + { + "userPermissions": [], + "pluginId": "63abe1c5cf1ed12127ce3af5", + "status": "FREE", + "new": true + }, + { + "userPermissions": [], + "pluginId": "63abe19fcf1ed12127ce3a69", + "status": "FREE", + "new": true + }, + { + "userPermissions": [], + "pluginId": "63abe19ecf1ed12127ce3a55", + "status": "FREE", + "new": true + }, + { + "userPermissions": [], + "pluginId": "63abe1c5cf1ed12127ce3af4", + "status": "FREE", + "new": true + }, + { + "userPermissions": [], + "pluginId": "63abe19ccf1ed12127ce3a38", + "status": "FREE", + "new": true + }, + { + "userPermissions": [], + "pluginId": "63abe19fcf1ed12127ce3a64", + "status": "FREE", + "new": true + }, + { + "userPermissions": [], + "pluginId": "63abe1a0cf1ed12127ce3a92", + "status": "FREE", + "new": true + }, + { + "userPermissions": [], + "pluginId": "63abe1c5cf1ed12127ce3af6", + "status": "FREE", + "new": true + }, + { + "userPermissions": [], + "pluginId": "63abe19ecf1ed12127ce3a4c", + "status": "FREE", + "new": true + }, + { + "userPermissions": [], + "pluginId": "63abe1a1cf1ed12127ce3aa3", + "status": "FREE", + "new": true + }, + { + "userPermissions": [], + "pluginId": "63abe1a3cf1ed12127ce3ae9", + "status": "FREE", + "new": true + }, + { + "userPermissions": [], + "pluginId": "63abe19ccf1ed12127ce3a39", + "status": "FREE", + "new": true + }, + { + "userPermissions": [], + "pluginId": "63abe19fcf1ed12127ce3a6d", + "status": "FREE", + "new": true + }, + { + "userPermissions": [], + "pluginId": "63abe19ecf1ed12127ce3a5c", + "status": "FREE", + "new": true + }, + { + "userPermissions": [], + "pluginId": "63abe1a0cf1ed12127ce3a8a", + "status": "FREE", + "new": true + }, + { + "userPermissions": [], + "pluginId": "63abe19fcf1ed12127ce3a84", + "status": "FREE", + "new": true + }, + { + "userPermissions": [], + "pluginId": "63abe19ccf1ed12127ce3a3a", + "status": "FREE", + "new": true + }, + { + "userPermissions": [], + "pluginId": "63abe19fcf1ed12127ce3a79", + "status": "FREE", + "new": true + }, + { + "userPermissions": [], + "pluginId": "63abe19ecf1ed12127ce3a57", + "status": "FREE", + "new": true + } + ], + "slug": "untitled-workspace-1", + "tenantId": "63abe1a2cf1ed12127ce3ac4", + "logoUrl": "/api/v1/assets/null", + "new": false + }, + "applications": [ + { + "id": "63ad2c166e7a9012ada704cb", + "modifiedBy": "test@sharklasers.com", + "userPermissions": [ + "canComment:applications", + "read:applications" + ], + "name": "Untitled application 1", + "workspaceId": "63ad2c156e7a9012ada704c7", + "isPublic": false, + "pages": [ + { + "id": "63ad2c166e7a9012ada704ce", + "isDefault": true, + "slug": "page1", + "defaultPageId": "63ad2c166e7a9012ada704ce", + "default": true + } + ], + "appIsExample": false, + "unreadCommentThreads": 0, + "color": "#FBF4ED", + "icon": "dollar", + "slug": "untitled-application-1", + "unpublishedCustomJSLibs": [], + "publishedCustomJSLibs": [], + "evaluationVersion": 2, + "applicationVersion": 2, + "isManualUpdate": true, + "isAutoUpdate": false, + "new": false, + "modifiedAt": "2022-12-29T05:56:41.772Z" + } + ], + "users": [ + { + "userId": "63abf1edcf1ed12127ce3af7", + "username": "test@sharklasers.com", + "name": "test@sharklasers.com", + "permissionGroupName": "Administrator - Untitled workspace 1", + "permissionGroupId": "63ad2c156e7a9012ada704c8" + }, + { + "userId": "63abf1f3cf1ed12127ce3b01", + "username": "test1@sharklasers.com", + "name": "test1@sharklasers.com", + "permissionGroupName": "App Viewer - Untitled workspace 1", + "permissionGroupId": "63ad2c156e7a9012ada704ca" + } + ] + } + ], + "newReleasesCount": "1", + "releaseItems": [ + { + "tagName": "v1.8.15", + "name": "Release v1.8.15 🌈", + "url": "https://github.com/appsmithorg/appsmith/releases/tag/v1.8.15", + "descriptionHtml": "

💪🏾 Improvements

\n", + "publishedAt": "2022-12-28T12:36:48Z" + } + ] + } +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/jsFunctionTriggerDsl.json b/app/client/cypress/fixtures/jsFunctionTriggerDsl.json new file mode 100644 index 0000000000..2cd12f27f5 --- /dev/null +++ b/app/client/cypress/fixtures/jsFunctionTriggerDsl.json @@ -0,0 +1,146 @@ +{ + "dsl": { + "widgetName": "MainContainer", + "backgroundColor": "none", + "rightColumn": 4896.0, + "snapColumns": 64.0, + "detachFromLayout": true, + "widgetId": "0", + "topRow": 0.0, + "bottomRow": 1292.0, + "containerStyle": "none", + "snapRows": 125.0, + "parentRowSpace": 1.0, + "type": "CANVAS_WIDGET", + "canExtend": true, + "version": 74.0, + "minHeight": 1292.0, + "dynamicTriggerPathList": [], + "parentColumnSpace": 1.0, + "dynamicBindingPathList": [], + "leftColumn": 0.0, + "children": [ + { + "resetFormOnClick": false, + "boxShadow": "none", + "widgetName": "Button1", + "buttonColor": "{{appsmith.theme.colors.primaryColor}}", + "displayName": "Button", + "iconSVG": "/static/media/icon.cca026338f1c8eb6df8ba03d084c2fca.svg", + "searchTags": ["click", "submit"], + "topRow": 8.0, + "bottomRow": 12.0, + "parentRowSpace": 10.0, + "type": "BUTTON_WIDGET", + "hideCard": false, + "animateLoading": true, + "parentColumnSpace": 10.0625, + "dynamicTriggerPathList": [], + "leftColumn": 7.0, + "dynamicBindingPathList": [ + { "key": "buttonColor" }, + { "key": "borderRadius" }, + { "key": "text" } + ], + "text": "{{JSObject1.myFun1.data}}", + "isDisabled": false, + "key": "a1lbivvht8", + "isDeprecated": false, + "rightColumn": 23.0, + "isDefaultClickDisabled": true, + "widgetId": "wxkz5y9ypf", + "isVisible": true, + "recaptchaType": "V3", + "version": 1.0, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "disabledWhenInvalid": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "buttonVariant": "PRIMARY", + "placement": "CENTER" + }, + { + "resetFormOnClick": false, + "boxShadow": "none", + "widgetName": "Button2", + "buttonColor": "{{appsmith.theme.colors.primaryColor}}", + "displayName": "Button", + "iconSVG": "/static/media/icon.cca026338f1c8eb6df8ba03d084c2fca.svg", + "searchTags": ["click", "submit"], + "topRow": 8.0, + "bottomRow": 12.0, + "parentRowSpace": 10.0, + "type": "BUTTON_WIDGET", + "hideCard": false, + "animateLoading": true, + "parentColumnSpace": 10.0625, + "dynamicTriggerPathList": [], + "leftColumn": 39.0, + "dynamicBindingPathList": [ + { "key": "buttonColor" }, + { "key": "borderRadius" }, + { "key": "text" } + ], + "text": "{{JSObject1.myFun2.data}}", + "isDisabled": false, + "key": "a1lbivvht8", + "isDeprecated": false, + "rightColumn": 55.0, + "isDefaultClickDisabled": true, + "widgetId": "ih110zq4c4", + "isVisible": true, + "recaptchaType": "V3", + "version": 1.0, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "disabledWhenInvalid": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "buttonVariant": "PRIMARY", + "placement": "CENTER" + }, + { + "resetFormOnClick": false, + "boxShadow": "none", + "widgetName": "Button3", + "onClick": "{{JSObject1.myFun2()\n}}", + "buttonColor": "{{appsmith.theme.colors.primaryColor}}", + "dynamicPropertyPathList": [{ "key": "onClick" }], + "displayName": "Button", + "iconSVG": "/static/media/icon.cca026338f1c8eb6df8ba03d084c2fca.svg", + "searchTags": ["click", "submit"], + "topRow": 23.0, + "bottomRow": 27.0, + "parentRowSpace": 10.0, + "type": "BUTTON_WIDGET", + "hideCard": false, + "animateLoading": true, + "parentColumnSpace": 10.0625, + "dynamicTriggerPathList": [{ "key": "onClick" }], + "leftColumn": 24.0, + "dynamicBindingPathList": [ + { "key": "buttonColor" }, + { "key": "borderRadius" } + ], + "text": "Submit", + "isDisabled": false, + "key": "a1lbivvht8", + "isDeprecated": false, + "rightColumn": 40.0, + "isDefaultClickDisabled": true, + "widgetId": "irxds8yzxq", + "isVisible": true, + "recaptchaType": "V3", + "version": 1.0, + "parentId": "0", + "renderMode": "CANVAS", + "isLoading": false, + "disabledWhenInvalid": false, + "borderRadius": "{{appsmith.theme.borderRadius.appBorderRadius}}", + "buttonVariant": "PRIMARY", + "placement": "CENTER" + } + ] + } +} \ No newline at end of file diff --git a/app/client/cypress/fixtures/jsonFormDslWithSchema.json b/app/client/cypress/fixtures/jsonFormDslWithSchema.json index 1c874074a6..c3c5c2e589 100644 --- a/app/client/cypress/fixtures/jsonFormDslWithSchema.json +++ b/app/client/cypress/fixtures/jsonFormDslWithSchema.json @@ -30,6 +30,7 @@ "type": "TEXT_WIDGET", "hideCard": false, "animateLoading": true, + "useSourceData": false, "parentColumnSpace": 18.0625, "leftColumn": 22, "text": "Label", diff --git a/app/client/cypress/fixtures/jsonFormDslWithoutSchema.json b/app/client/cypress/fixtures/jsonFormDslWithoutSchema.json index ec1faf97d0..43885d8376 100644 --- a/app/client/cypress/fixtures/jsonFormDslWithoutSchema.json +++ b/app/client/cypress/fixtures/jsonFormDslWithoutSchema.json @@ -30,6 +30,7 @@ "type": "TEXT_WIDGET", "hideCard": false, "animateLoading": true, + "useSourceData": false, "parentColumnSpace": 18.0625, "leftColumn": 22, "text": "Label", diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug15056_Spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug15056_Spec.ts new file mode 100644 index 0000000000..a0098ebdcf --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug15056_Spec.ts @@ -0,0 +1,46 @@ +const dsl = require("../../../../fixtures/jsFunctionTriggerDsl.json"); +import { ObjectsRegistry } from "../../../../support/Objects/Registry"; + +const agHelper = ObjectsRegistry.AggregateHelper; +const jsEditor = ObjectsRegistry.JSEditor; +const apiPage = ObjectsRegistry.ApiPage; +const ee = ObjectsRegistry.EntityExplorer; + +describe("JS data update on button click", function() { + before(() => { + agHelper.AddDsl(dsl); + }); + + it("Populates js function data when triggered via button click", function() { + apiPage.CreateAndFillApi( + "https://jsonplaceholder.typicode.com/posts", + "Api1", + ); + + const jsObjectString = `export default { + myVar1: [], + myVar2: {}, + myFun1: async () => { + //write code here + const data = await Api1.run() + return "myFun1 Data" + }, + myFun2: async () => { + //use async-await or promises + await this.myFun1() + return "myFun2 Data" + } + }`; + + jsEditor.CreateJSObject(jsObjectString, { + paste: true, + completeReplace: true, + toRun: false, + shouldCreateNewJSObj: true, + }); + ee.SelectEntityByName("Button2", "Widgets"); + agHelper.ClickButton("Submit"); + agHelper.AssertContains("myFun1 Data", "exist"); + agHelper.AssertContains("myFun2 Data", "exist"); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug18664_spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug18664_spec.ts deleted file mode 100644 index d740083669..0000000000 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug18664_spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { ObjectsRegistry } from "../../../../support/Objects/Registry"; - -let dsName: any; - -const agHelper = ObjectsRegistry.AggregateHelper, - dataSources = ObjectsRegistry.DataSources; - -describe("Bug 18664: datasource unsaved changes popup shows even without changes", function() { - it("1. Create postgres datasource, save it and edit it and go back, now unsaved changes popup should not be shown", () => { - dataSources.NavigateToDSCreateNew(); - agHelper.GenerateUUID(); - cy.get("@guid").then((uid) => { - dataSources.CreatePlugIn("PostgreSQL"); - dsName = "Postgres" + uid; - agHelper.RenameWithInPane(dsName, false); - dataSources.SaveDatasource(); - cy.wait(1000); - dataSources.EditDatasource(); - agHelper.GoBack(); - agHelper.AssertElementVisible(dataSources._activeDS); - dataSources.DeleteDatasouceFromActiveTab(dsName); - }); - }); -}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug19426_spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug19426_spec.ts new file mode 100644 index 0000000000..8290a60bcd --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/Bug19426_spec.ts @@ -0,0 +1,12 @@ +import { ObjectsRegistry } from "../../../../support/Objects/Registry"; + +const dataSources = ObjectsRegistry.DataSources; + +describe("Testing empty datasource without saving should not throw 404", function() { + it("Bug 19426: Create empty S3 datasource, test it", function() { + dataSources.NavigateToDSCreateNew(); + dataSources.CreatePlugIn("S3"); + dataSources.TestDatasource(false); + dataSources.SaveDSFromDialog(false); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/DSDiscardBugs_spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/DSDiscardBugs_spec.ts new file mode 100644 index 0000000000..a6adc2e31a --- /dev/null +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/BugTests/DSDiscardBugs_spec.ts @@ -0,0 +1,96 @@ +import { ObjectsRegistry } from "../../../../support/Objects/Registry"; + +let dsName: any; + +const agHelper = ObjectsRegistry.AggregateHelper, + dataSources = ObjectsRegistry.DataSources; + +const testString = "test"; + +describe("datasource unsaved changes popup shows even without changes", function() { + // In case of postgres and other plugins, host address and port key values are initialized by default making form dirty + it("1. Bug 18664: Create postgres datasource, save it and edit it and go back, now unsaved changes popup should not be shown", () => { + dataSources.NavigateToDSCreateNew(); + agHelper.GenerateUUID(); + cy.get("@guid").then((uid) => { + // using CreatePlugIn function instead of CreateDatasource, + // because I do not need to fill the datasource form and use the same default data + dataSources.CreatePlugIn("PostgreSQL"); + dsName = "Postgres" + uid; + agHelper.RenameWithInPane(dsName, false); + dataSources.SaveDatasource(); + agHelper.Sleep(); + dataSources.EditDatasource(); + agHelper.GoBack(); + agHelper.AssertElementVisible(dataSources._activeDS); + dataSources.DeleteDatasouceFromActiveTab(dsName); + }); + }); + + // In case of Auth DS, headers, query parameters and custom query parameters are being initialized, which makes form dirty + it("2. Bug 18962: Create REST API datasource, save it and edit it and go back, now unsaved changes popup should not be shown", () => { + dataSources.NavigateToDSCreateNew(); + agHelper.GenerateUUID(); + cy.get("@guid").then((uid) => { + // using CreatePlugIn function instead of CreateDatasource, + // because I do not need to fill the datasource form and use the same default data + dataSources.CreatePlugIn("Authenticated API"); + dsName = "AuthDS" + uid; + agHelper.RenameWithInPane(dsName, false); + dataSources.FillAuthAPIUrl(); + dataSources.SaveDatasource(); + agHelper.Sleep(); + + // Edit DS for the first time, we shouldnt see discard popup on back button + // Even if headers, and query parameters are being initialized, we shouldnt see the popup + // as those are not initialized by user + dataSources.EditDatasource(); + agHelper.GoBack(); + agHelper.AssertElementVisible(dataSources._activeDS); + + // Edit DS from active tab and add oauth2 details + dataSources.EditDSFromActiveTab(dsName); + dataSources.AddOAuth2AuthorizationCodeDetails( + testString, + testString, + testString, + testString, + ); + dataSources.UpdateDatasource(); + agHelper.Sleep(); + + // Now edit DS, and ensure that discard popup is not shown on back button click + // Even if custom authentication params are being initialized, we shouldnt see the popup + // as those are not initialized by user + dataSources.EditDatasource(); + agHelper.GoBack(); + agHelper.AssertElementVisible(dataSources._activeDS); + + dataSources.DeleteDatasouceFromActiveTab(dsName); + }); + }); + + it("3. Bug 18998: Create mongoDB datasource, save it and edit it and change connection URI param, discard popup should be shown as user has made valid change", () => { + dataSources.NavigateToDSCreateNew(); + agHelper.GenerateUUID(); + cy.get("@guid").then((uid) => { + // using CreatePlugIn function instead of CreateDatasource, + // because I do not need to fill the datasource form and use the same default data + dataSources.CreatePlugIn("MongoDB"); + dsName = "Mongo" + uid; + agHelper.RenameWithInPane(dsName, false); + dataSources.FillMongoDSForm(); + dataSources.SaveDatasource(); + agHelper.Sleep(); + + // Edit datasource, change connection string uri param and click on back button + dataSources.EditDatasource(); + dataSources.FillMongoDatasourceFormWithURI(testString); + + // Assert that popup is visible + dataSources.SaveDSFromDialog(false); + + dataSources.DeleteDatasouceFromActiveTab(dsName); + }); + }); +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/IDE/MaintainContext&Focus_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/IDE/MaintainContext&Focus_spec.js index bbfb209653..e4f7a4bd50 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/IDE/MaintainContext&Focus_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/IDE/MaintainContext&Focus_spec.js @@ -166,7 +166,7 @@ describe("MaintainContext&Focus", function() { it("9. Datasource edit mode has to be maintained", () => { ee.SelectEntityByName("Appsmith", "Datasources"); dataSources.EditDatasource(); - dataSources.SaveDSFromDialog(false); + agHelper.GoBack(); ee.SelectEntityByName("Github", "Datasources"); dataSources.AssertViewMode(); ee.SelectEntityByName("Appsmith", "Datasources"); @@ -175,7 +175,7 @@ describe("MaintainContext&Focus", function() { it("10. Datasource collapse state has to be maintained", () => { // Create datasource 1 - dataSources.SaveDSFromDialog(false); + agHelper.GoBack(); dataSources.NavigateToDSCreateNew(); dataSources.CreatePlugIn("PostgreSQL"); agHelper.RenameWithInPane("Postgres1", false); @@ -197,7 +197,7 @@ describe("MaintainContext&Focus", function() { dataSources.AssertSectionCollapseState(1, false); }); - it("10. Maintain focus of form control inputs", () => { + it("11. Maintain focus of form control inputs", () => { ee.SelectEntityByName("SQL_Query"); dataSources.ToggleUsePreparedStatement(false); cy.SearchEntityandOpen("S3_Query"); @@ -207,7 +207,8 @@ describe("MaintainContext&Focus", function() { cy.SearchEntityandOpen("SQL_Query"); cy.get(".t--form-control-SWITCH input").should("be.focused"); cy.SearchEntityandOpen("S3_Query"); - cy.get(queryLocators.querySettingsTab).click(); + agHelper.Sleep(); + agHelper.GetNClick(dataSources._queryResponse("SETTINGS")); cy.xpath(queryLocators.queryTimeout).should("be.focused"); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Onboarding/CreateNewApp_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Onboarding/CreateNewApp_spec.js index 8d6999348b..72e9d9e096 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Onboarding/CreateNewApp_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Onboarding/CreateNewApp_spec.js @@ -12,7 +12,7 @@ describe("Creating new app after discontinuing guided tour should not start the cy.get(commonlocators.homeIcon) .click({ force: true }) .wait(8000); //for page to settle! - datasources.CloseReconnectDataSourceModal() // Check if reconnect data source modal is visible and close it + datasources.CloseReconnectDataSourceModal(); // Check if reconnect data source modal is visible and close it cy.get(guidedTourLocators.welcomeTour) .click() .wait(2000); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_spec.js index 94ace8b422..669af44642 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Templates/Fork_Template_spec.js @@ -1,5 +1,7 @@ const commonlocators = require("../../../../locators/commonlocators.json"); const templateLocators = require("../../../../locators/TemplatesLocators.json"); +import { ObjectsRegistry } from "../../../../support/Objects/Registry"; +const { AggregateHelper, HomePage } = ObjectsRegistry; describe("Fork a template to an workspace", () => { it("Fork a template to an workspace", () => { @@ -30,4 +32,21 @@ describe("Fork a template to an workspace", () => { expect(location.search).to.eq("?showForkTemplateModal=true"); }); }); + it("Hide template fork button if user does not have a valid workspace to fork", () => { + HomePage.NavigateToHome(); + // Mock user with App Viewer permission + cy.intercept("/api/v1/applications/new", { + fixture: "Templates/MockAppViewerUser.json", + }); + AggregateHelper.RefreshPage(); + HomePage.SwitchToTemplatesTab(); + AggregateHelper.AssertElementExist(templateLocators.templateCard); + AggregateHelper.AssertElementAbsence(templateLocators.templateForkButton); + + AggregateHelper.GetNClick(templateLocators.templateCard); + AggregateHelper.AssertElementExist(templateLocators.templateCard); + AggregateHelper.AssertElementAbsence( + templateLocators.templateViewForkButton, + ); + }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Basic_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Basic_spec.js index 58b18d6230..1cf60401e1 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Basic_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/ThemingTests/Basic_spec.js @@ -819,8 +819,8 @@ describe("App Theming funtionality", function() { //Change Border & verify - cy.get(".t--button-tab-0px").click(); - cy.get(".t--button-tab-0px") + cy.get(".t--button-group-0px").click(); + cy.get(".t--button-group-0px") .eq(0) .invoke("css", "border-top-left-radius") .then((borderRadius) => { @@ -842,8 +842,8 @@ describe("App Theming funtionality", function() { }); //Change Shadow & verify - cy.get(".t--button-tab-0.10px").click(); - cy.get(".t--button-tab-0.10px div") + cy.get(".t--button-group-0.10px").click(); + cy.get(".t--button-group-0.10px div") .eq(0) .invoke("css", "box-shadow") .then((boxshadow) => { @@ -1051,10 +1051,10 @@ describe("App Theming funtionality", function() { //Change Border & verify - cy.get(".t--button-tab-0\\.375rem") + cy.get(".t--button-group-0\\.375rem") .click() .wait(500); - cy.get(".t--button-tab-0\\.375rem div") + cy.get(".t--button-group-0\\.375rem div") .eq(0) .invoke("css", "border-top-left-radius") .then((borderRadius) => { @@ -1076,10 +1076,10 @@ describe("App Theming funtionality", function() { }); //Change Shadow & verify - cy.get(".t--button-tab-0.1px") + cy.get(".t--button-group-0.1px") .click() .wait(500); - cy.get(".t--button-tab-0.1px div") + cy.get(".t--button-group-0.1px div") .invoke("css", "box-shadow") .then((boxshadow) => { cy.get(widgetsPage.widgetBtn) diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Button/ButtonGroup_MenuButton_Width_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Button/ButtonGroup_MenuButton_Width_spec.js index f017266157..566a078274 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Button/ButtonGroup_MenuButton_Width_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Button/ButtonGroup_MenuButton_Width_spec.js @@ -60,7 +60,7 @@ describe("In a button group widget, menu button width", function() { const menuButtonId = "groupButton1"; // Change the first button type to menu cy.editColumn(menuButtonId); - cy.get(".t--button-tab-MENU").click({ force: true }); + cy.get(".t--button-group-MENU").click({ force: true }); cy.get(".t--add-menu-item-btn").click(); // Get the newly converted menu button cy.get(`.appsmith_widget_${widgetId} div.t--buttongroup-widget`) @@ -126,7 +126,7 @@ describe("In a button group widget, menu button width", function() { .click(); cy.moveToStyleTab(); // Change its orientation to vetical - cy.get(".t--button-tab-vertical").click({ force: true }); + cy.get(".t--button-group-vertical").click({ force: true }); // Get the default menu button cy.get(`.appsmith_widget_${widgetId} div.t--buttongroup-widget`) .children() diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Button/ButtonGroup_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Button/ButtonGroup_spec.js index 47215225f3..ce45898c1d 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Button/ButtonGroup_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Button/ButtonGroup_spec.js @@ -97,7 +97,7 @@ describe("Button Group Widget Functionality", function() { it("Update icon alignment and Verify buttons alignments", function() { // align right - cy.get(".t--property-control-position .t--button-tab-left") + cy.get(".t--property-control-position .t--button-group-left") .first() .click(); cy.wait(200); @@ -106,7 +106,7 @@ describe("Button Group Widget Functionality", function() { .eq(1) .should("have.css", "flex-direction", "row"); // align left - cy.get(".t--property-control-position .t--button-tab-right") + cy.get(".t--property-control-position .t--button-group-right") .last() .click(); cy.wait(200); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Button/Button_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Button/Button_spec.js index 20f2005983..5a135c3e0b 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Button/Button_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Button/Button_spec.js @@ -31,7 +31,7 @@ describe("Button Widget Functionality", function() { // Assert if the icon exists cy.get(`${widgetsPage.buttonWidget} .bp3-icon-add`).should("exist"); // Change icon alignment to right - cy.get(`${iconAlignmentProperty} .t--button-tab-right`) + cy.get(`${iconAlignmentProperty} .t--button-group-right`) .last() .click({ force: true, diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/CodeScanner/CodeScanner_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/CodeScanner/CodeScanner_spec.js index 787ca5083e..3e4ad93d8b 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/CodeScanner/CodeScanner_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/CodeScanner/CodeScanner_spec.js @@ -119,7 +119,7 @@ describe("Code Scanner widget's functionality", () => { // Select scanner layout as CLICK_TO_SCAN cy.get( - `${commonlocators.codeScannerScannerLayout} .t--button-tab-CLICK_TO_SCAN`, + `${commonlocators.codeScannerScannerLayout} .t--button-group-CLICK_TO_SCAN`, ) .last() .click({ diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Form/Form_With_CheckBox_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Form/Form_With_CheckBox_spec.js index 2f13d99e96..30a84ec360 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Form/Form_With_CheckBox_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Form/Form_With_CheckBox_spec.js @@ -34,7 +34,7 @@ describe("Checkbox Widget Functionality", function() { ); // align right - cy.get(".t--property-control-alignment .t--button-tab-RIGHT") + cy.get(".t--property-control-alignment .t--button-group-RIGHT") .first() .click(); cy.get(publish.checkboxWidget + " " + ".t--checkbox-widget-label").should( @@ -63,7 +63,7 @@ describe("Checkbox Widget Functionality", function() { .last() .click({ force: true }); cy.wait(200); - cy.get(".t--button-tab-Left").click({ force: true }); + cy.get(".t--button-group-Left").click({ force: true }); cy.wait(200); cy.PublishtheApp(); @@ -110,7 +110,7 @@ describe("Checkbox Widget Functionality", function() { it("6. Checkbox Functionality To change label style of checkbox", function() { cy.openPropertyPane("checkboxwidget"); cy.moveToStyleTab(); - cy.get(".t--property-control-emphasis .t--button-tab-BOLD").click({ + cy.get(".t--property-control-emphasis .t--button-group-BOLD").click({ force: true, }); cy.PublishtheApp(); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/JSONForm/JSONForm_FormProperties_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/JSONForm/JSONForm_FormProperties_spec.js index a03a691471..dc6c247c95 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/JSONForm/JSONForm_FormProperties_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/JSONForm/JSONForm_FormProperties_spec.js @@ -190,4 +190,54 @@ describe("JSON Form Widget Form Bindings", () => { cy.get(publishPage.backToEditor).click({ force: true }); }); + + it("8. Form value should contain hidden fields value if useSourceData is set to true", () => { + cy.addDsl(dslWithoutSchema); + + const schema = { + name: "", + age: 10, + }; + + cy.openPropertyPane("jsonformwidget"); + cy.testJsontext("sourcedata", JSON.stringify(schema)); + + cy.togglebar(`${propertyControlPrefix}-usesourcedata input`); + + cy.openFieldConfiguration("age"); + cy.togglebarDisable(`${propertyControlPrefix}-visible input`); + + cy.openPropertyPane("textwidget"); + cy.testJsontext("text", "{{JSON.stringify(JSONForm1.formData)}}"); + + cy.get(".t--widget-textwidget .bp3-ui-text").contains( + JSON.stringify(schema), + ); + }); + + it("9. Form value should not contain hidden fields value if useSourceData is set to false", () => { + cy.addDsl(dslWithoutSchema); + + const name = "JOHN"; + + const schema = { + name, + age: 10, + }; + + cy.openPropertyPane("jsonformwidget"); + cy.testJsontext("sourcedata", JSON.stringify(schema)); + + cy.togglebarDisable(`${propertyControlPrefix}-usesourcedata input`); + + cy.openFieldConfiguration("age"); + cy.togglebarDisable(`${propertyControlPrefix}-visible input`); + + cy.openPropertyPane("textwidget"); + cy.testJsontext("text", "{{JSON.stringify(JSONForm1.formData)}}"); + + cy.get(".t--widget-textwidget .bp3-ui-text").contains( + JSON.stringify({ name }), + ); + }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Others/MenuButton_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Others/MenuButton_spec.js index f42f7770fd..ae6d19f72e 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Others/MenuButton_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Others/MenuButton_spec.js @@ -23,7 +23,7 @@ describe("Menu Button Widget Functionality", () => { // Assert if the icon exists cy.get(`${formWidgetsPage.menuButtonWidget} .bp3-icon-add`).should("exist"); // Change its icon alignment to right - cy.get(".t--property-control-position .t--button-tab-right") + cy.get(".t--property-control-position .t--button-group-right") .last() .click({ force: true }); cy.wait(200); @@ -110,7 +110,9 @@ describe("Menu Button Widget Functionality", () => { cy.moveToContentTab(); // Select menu items source as Dynamic - cy.get(`${commonlocators.menuButtonMenuItemsSource} .t--button-tab-DYNAMIC`) + cy.get( + `${commonlocators.menuButtonMenuItemsSource} .t--button-group-DYNAMIC`, + ) .last() .click({ force: true, diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Others/Progress_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Others/Progress_spec.js index 2d4ec8fb62..fd0422d13d 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Others/Progress_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Others/Progress_spec.js @@ -66,7 +66,7 @@ describe("Progress Widget", function() { // Circular progress it("Property: type, Change type to Circular", function() { // Switch to circular mode - cy.get(".t--button-tab-circular").click({ force: true }); + cy.get(".t--button-group-circular").click({ force: true }); cy.get("[data-cy='circular']").should("exist"); }); it("Property: isIndeterminate, Toggle infinite loading", function() { diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/RTE/RichTextEditor_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/RTE/RichTextEditor_spec.js index 699c5627c6..5f27f01583 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/RTE/RichTextEditor_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/RTE/RichTextEditor_spec.js @@ -246,10 +246,10 @@ describe("RichTextEditor Widget Functionality", function() { // Changing the input type to markdown and again testing the cursor position cy.openPropertyPane("richtexteditorwidget"); - cy.get(".t--button-tab-markdown").click({ force: true }); + cy.get(".t--button-group-markdown").click({ force: true }); setRTEContent(testString); testCursorPoistion(testStringLen, tinyMceId); - cy.get(".t--button-tab-html").click({ force: true }); + cy.get(".t--button-group-html").click({ force: true }); }); it("13. Check if different font size texts are supported inside the RTE widget", function() { diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Switch/Switch_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Switch/Switch_spec.js index e166e1c363..726ca6a5ca 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Switch/Switch_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Switch/Switch_spec.js @@ -84,7 +84,7 @@ describe("Switch Widget Functionality", function() { "left", ); // align right - cy.get(".t--property-control-alignment .t--button-tab-RIGHT") + cy.get(".t--property-control-alignment .t--button-group-RIGHT") .first() .click(); cy.wait(200); @@ -106,7 +106,7 @@ describe("Switch Widget Functionality", function() { .last() .click({ force: true }); cy.wait(200); - cy.get(".t--button-tab-Left").click({ force: true }); + cy.get(".t--button-group-Left").click({ force: true }); cy.wait(200); cy.PublishtheApp(); cy.get(publish.switchwidget + " " + ".bp3-align-right").should("exist"); @@ -150,7 +150,7 @@ describe("Switch Widget Functionality", function() { it("11. Switch Functionality To change label style of switch", function() { cy.openPropertyPane("switchwidget"); cy.moveToStyleTab(); - cy.get(".t--property-control-emphasis .t--button-tab-BOLD").click({ + cy.get(".t--property-control-emphasis .t--button-group-BOLD").click({ force: true, }); cy.PublishtheApp(); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/Image_resize_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/Image_resize_spec.js index 527a108bb6..3862c9f182 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/Image_resize_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/Image_resize_spec.js @@ -14,17 +14,17 @@ describe("Table Widget Image Resize feature validation", function() { cy.editColumn("image"); cy.moveToStyleTab(); - cy.get(".t--button-tab-MEDIUM").click(); + cy.get(".t--button-group-MEDIUM").click(); cy.getTableV2DataSelector("1", "3").then((selector) => { cy.get(`${selector} img`).should("have.css", "height", "64px"); }); - cy.get(".t--button-tab-LARGE").click(); + cy.get(".t--button-group-LARGE").click(); cy.getTableV2DataSelector("1", "3").then((selector) => { cy.get(`${selector} img`).should("have.css", "height", "128px"); }); - cy.get(".t--button-tab-DEFAULT").click(); + cy.get(".t--button-group-DEFAULT").click(); cy.getTableV2DataSelector("1", "3").then((selector) => { cy.get(`${selector} img`).should("have.css", "height", "32px"); }); @@ -47,17 +47,17 @@ describe("Table Widget Image Resize feature validation", function() { cy.editColumn("image"); cy.moveToStyleTab(); - cy.get(".t--button-tab-MEDIUM").click(); + cy.get(".t--button-group-MEDIUM").click(); cy.getTableV2DataSelector("1", "3").then((selector) => { cy.get(`${selector} img`).should("have.css", "height", "64px"); }); - cy.get(".t--button-tab-LARGE").click(); + cy.get(".t--button-group-LARGE").click(); cy.getTableV2DataSelector("1", "3").then((selector) => { cy.get(`${selector} img`).should("have.css", "height", "128px"); }); - cy.get(".t--button-tab-DEFAULT").click(); + cy.get(".t--button-group-DEFAULT").click(); cy.getTableV2DataSelector("1", "3").then((selector) => { cy.get(`${selector} img`).should("have.css", "height", "32px"); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/Inline_editing_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/Inline_editing_spec.js index 4eca007482..e1493cc660 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/Inline_editing_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/Inline_editing_spec.js @@ -307,26 +307,26 @@ describe("Table widget inline editing functionality", () => { cy.get(".t--property-control-updatemode .bp3-popover-target") .last() .click(); - cy.get(".t--button-tab-CUSTOM").click({ force: true }); + cy.get(".t--button-group-CUSTOM").click({ force: true }); cy.get("[data-rbd-draggable-id='EditActions1']").should("not.exist"); cy.makeColumnEditable("task"); cy.get("[data-rbd-draggable-id='EditActions1']").should("not.exist"); cy.get(".t--property-control-updatemode .bp3-popover-target") .last() .click(); - cy.get(".t--button-tab-ROW_LEVEL").click({ force: true }); + cy.get(".t--button-group-ROW_LEVEL").click({ force: true }); cy.get("[data-rbd-draggable-id='EditActions1']").should("exist"); cy.get(".t--property-control-updatemode .bp3-popover-target") .last() .click(); - cy.get(".t--button-tab-CUSTOM").click({ force: true }); + cy.get(".t--button-group-CUSTOM").click({ force: true }); cy.get("[data-rbd-draggable-id='EditActions1']").should("not.exist"); cy.makeColumnEditable("step"); cy.makeColumnEditable("task"); cy.get(".t--property-control-updatemode .bp3-popover-target") .last() .click(); - cy.get(".t--button-tab-ROW_LEVEL").click({ force: true }); + cy.get(".t--button-group-ROW_LEVEL").click({ force: true }); cy.get("[data-rbd-draggable-id='EditActions1']").should("not.exist"); }); @@ -787,7 +787,7 @@ describe("Table widget inline editing functionality", () => { cy.openPropertyPane("tablewidgetv2"); cy.makeColumnEditable("step"); - cy.get(".t--button-tab-CUSTOM").click({ force: true }); + cy.get(".t--button-group-CUSTOM").click({ force: true }); cy.wait(1000); // case 1: check if updatedRowIndex is 0, when cell at row 0 is updated. diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/TableV2_GeneralProperty_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/TableV2_GeneralProperty_spec.js index 4ec964242f..b589e0213b 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/TableV2_GeneralProperty_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/TableV2_GeneralProperty_spec.js @@ -186,7 +186,7 @@ describe("Table Widget property pane feature validation", function() { cy.get(widgetsPage.rowHeight) .last() .click({ force: true }); - cy.get(".t--button-tab-SHORT").click({ force: true }); + cy.get(".t--button-group-SHORT").click({ force: true }); cy.wait(2000); cy.PublishtheApp(); cy.readTableV2dataValidateCSS("0", "1", "height", "29px", true); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/TableV2_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/TableV2_spec.js index bf0ed0cb1d..11cddb29fa 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/TableV2_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/TableV2_spec.js @@ -195,7 +195,7 @@ describe("Table Widget V2 Functionality", function() { cy.get(".t--property-pane-back-btn").click(); cy.makeColumnEditable("step"); - cy.get(".t--button-tab-ROW_LEVEL").click(); + cy.get(".t--button-group-ROW_LEVEL").click(); cy.get(".t--table-filter-columns-dropdown").click(); cy.get(".t--dropdown-option").should("not.contain", "Save / Discard"); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/Text_wrapping_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/Text_wrapping_spec.js index 14abeff504..57c7553585 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/Text_wrapping_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/Text_wrapping_spec.js @@ -30,7 +30,29 @@ describe("Table Widget text wrapping functionality", function() { }); }); - it("2. should check that other cells in the row is not wrapped when one of the cell is wrapped", () => { + it("2. should check that a tooltip is shown when hovered on a ellipsised content", () => { + cy.get( + `.td[data-colindex=2][data-rowindex=0] .t--table-cell-tooltip-target`, + ).trigger("mouseenter"); + + cy.get(".bp3-tooltip").should("exist"); + cy.get(".bp3-tooltip .bp3-popover-content").should( + "contain", + "michael.lawson@reqres.in", + ); + + cy.get( + `.td[data-colindex=2][data-rowindex=1] .t--table-cell-tooltip-target`, + ).trigger("mouseenter", { force: true }); + + cy.get(".bp3-tooltip").should("exist"); + cy.get(".bp3-tooltip .bp3-popover-content").should( + "contain", + "lindsay.ferguson@reqres.in", + ); + }); + + it("3. should check that other cells in the row is not wrapped when one of the cell is wrapped", () => { cy.getTableCellHeight(2, 0).then((height) => { expect(height).to.equal("28px"); }); @@ -52,7 +74,7 @@ describe("Table Widget text wrapping functionality", function() { }); }); - it("3. should check that cell wrapping option is only available for plain text, number, date and URL", () => { + it("4. should check that cell wrapping option is only available for plain text, number, date and URL", () => { cy.openPropertyPane("tablewidgetv2"); cy.editColumn("email"); [ @@ -105,7 +127,7 @@ describe("Table Widget text wrapping functionality", function() { }); }); - it("4. should check that plain text, number, date and URL column is getting wrapped when cell wrapping is enabled", () => { + it("5. should check that plain text, number, date and URL column is getting wrapped when cell wrapping is enabled", () => { cy.openPropertyPane("tablewidgetv2"); cy.editColumn("id"); @@ -134,7 +156,7 @@ describe("Table Widget text wrapping functionality", function() { }); }); - it("5. should check that pageSize does not change when cell wrapping is enabled", () => { + it("6. should check that pageSize does not change when cell wrapping is enabled", () => { cy.openPropertyPane("tablewidgetv2"); cy.editColumn("image"); let pageSizeBeforeWrapping; diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/columnTypes/checkboxCell_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/columnTypes/checkboxCell_spec.js index 5c5a4c8213..161f8b96e4 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/columnTypes/checkboxCell_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/columnTypes/checkboxCell_spec.js @@ -71,7 +71,7 @@ describe("Checkbox column type funtionality test", () => { .contains("STYLE") .click({ force: true }); // Check horizontal alignment - cy.get(".t--property-control-horizontalalignment .t--button-tab-CENTER") + cy.get(".t--property-control-horizontalalignment .t--button-group-CENTER") .first() .click(); @@ -80,7 +80,7 @@ describe("Checkbox column type funtionality test", () => { }); // Check vertical alignment - cy.get(".t--property-control-verticalalignment .t--button-tab-BOTTOM") + cy.get(".t--property-control-verticalalignment .t--button-group-BOTTOM") .first() .click(); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/columnTypes/switchCell_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/columnTypes/switchCell_spec.js index 9a3b247723..6215e506da 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/columnTypes/switchCell_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/columnTypes/switchCell_spec.js @@ -71,7 +71,7 @@ describe("Switch column type funtionality test", () => { .contains("STYLE") .click({ force: true }); // Check horizontal alignment - cy.get(".t--property-control-horizontalalignment .t--button-tab-CENTER") + cy.get(".t--property-control-horizontalalignment .t--button-group-CENTER") .first() .click(); @@ -80,7 +80,7 @@ describe("Switch column type funtionality test", () => { }); // Check vertical alignment - cy.get(".t--property-control-verticalalignment .t--button-tab-BOTTOM") + cy.get(".t--property-control-verticalalignment .t--button-group-BOTTOM") .first() .click(); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/pagesize_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/pagesize_spec.js index 551394d6fd..5e9b04bce9 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/pagesize_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/TableV2/pagesize_spec.js @@ -11,13 +11,13 @@ describe("Table widget v2", function() { cy.openPropertyPane("tablewidgetv2"); cy.moveToStyleTab(); - cy.get(".t--button-tab-SHORT").click({ force: true }); + cy.get(".t--button-group-SHORT").click({ force: true }); cy.get(".t--widget-textwidget .bp3-ui-text").should("contain", "7"); - cy.get(".t--button-tab-DEFAULT").click({ force: true }); + cy.get(".t--button-group-DEFAULT").click({ force: true }); cy.get(".t--widget-textwidget .bp3-ui-text").should("contain", "5"); - cy.get(".t--button-tab-TALL").click({ force: true }); + cy.get(".t--button-group-TALL").click({ force: true }); cy.get(".t--widget-textwidget .bp3-ui-text").should("contain", "4"); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Text/Text_new_feature_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Text/Text_new_feature_spec.js index d19a4eee98..07057540fb 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Text/Text_new_feature_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Text/Text_new_feature_spec.js @@ -137,7 +137,7 @@ describe("Text Widget color/font/alignment Functionality", function() { it("Test to validate enable scroll feature", function() { cy.moveToContentTab(); - cy.get(".t--button-tab-SCROLL").click({ force: true }); + cy.get(".t--button-group-SCROLL").click({ force: true }); cy.wait("@updateLayout"); cy.get(commonlocators.headingTextStyle).trigger("mouseover", { force: true, diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Text/Text_truncate_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Text/Text_truncate_spec.js index 24389115c7..fb5b9cdd92 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Text/Text_truncate_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/Text/Text_truncate_spec.js @@ -8,7 +8,7 @@ describe("Text Widget Truncate Functionality", function() { it("Check default overflow property is No overflow", function() { cy.openPropertyPane("textwidget"); - cy.get(".t--button-tab-NONE") + cy.get(".t--button-group-NONE") .last() .should("have.attr", "aria-selected", "true"); cy.closePropertyPane(); @@ -34,7 +34,7 @@ describe("Text Widget Truncate Functionality", function() { it("Enable Truncate Text option and Validate", function() { cy.wait(2000); cy.get("body").type("{esc}"); - cy.get(".t--button-tab-TRUNCATE").click({ force: true }); + cy.get(".t--button-group-TRUNCATE").click({ force: true }); cy.wait("@updateLayout"); cy.get( `.appsmith_widget_${dsl.dsl.children[0].widgetId} .t--widget-textwidget-truncate`, diff --git a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/WidgetCopyPaste_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/WidgetCopyPaste_spec.js index ded2d5f201..60f6586f20 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/WidgetCopyPaste_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ClientSideTests/Widgets/WidgetCopyPaste_spec.js @@ -166,4 +166,4 @@ describe("Widget Copy paste", function() { //verify a pasted list widget cy.get(widgetsPage.listWidget).should("have.length", 1); }); -}); \ No newline at end of file +}); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/MySQLNoiseTest_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/MySQLNoiseTest_spec.js index 7479016c4a..7bdf9db30e 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/MySQLNoiseTest_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/Datasources/MySQLNoiseTest_spec.js @@ -63,10 +63,8 @@ describe("MySQL noise test", function() { expect(response.body.data.statusCode).to.eq("200 OK"); }); cy.wait("@postExecute").then(({ response }) => { - expect(response.body.data.statusCode).to.eq("5004"); - expect(response.body.data.title).to.eq( - "Datasource configuration is invalid", - ); + expect(response.body.data.statusCode).to.eq("5000"); + expect(response.body.data.title).to.eq("Query execution error"); }); }); }); diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/JsFunctionExecution/JSFunctionExecution_spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/JsFunctionExecution/JSFunctionExecution_spec.ts index 3f51bc8a1b..62d8bb3d49 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/JsFunctionExecution/JSFunctionExecution_spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/JsFunctionExecution/JSFunctionExecution_spec.ts @@ -437,13 +437,15 @@ describe("JS Function Execution", function() { it("9. Bug 13197: Verify converting async functions to sync resets all settings", () => { const asyncJSCode = `export default { +name: "Appsmith", asyncToSync : async ()=>{ return "yes";`; const syncJSCode = `export default { + name: "Appsmith", asyncToSync : ()=>{ return "yes"; - } + }, }`; jsEditor.CreateJSObject(asyncJSCode, { diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OnLoadTests/JSOnLoad2_Spec.ts b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OnLoadTests/JSOnLoad2_Spec.ts index d5d1ce5623..d90e0727ff 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OnLoadTests/JSOnLoad2_Spec.ts +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OnLoadTests/JSOnLoad2_Spec.ts @@ -40,7 +40,7 @@ describe("JSObjects OnLoad Actions tests", function() { AssertJSOnPageLoad( "runWorldCountries", false, - "UncaughtPromiseRejection: getWorldCountries is not defined", + "ReferenceError: getWorldCountries is not defined", ); }); }); @@ -51,7 +51,7 @@ describe("JSObjects OnLoad Actions tests", function() { AssertJSOnPageLoad( "runWorldCountries", false, - "UncaughtPromiseRejection: getWorldCountries is not defined", + "ReferenceError: getWorldCountries is not defined", ); homePage.NavigateToHome(); @@ -59,7 +59,7 @@ describe("JSObjects OnLoad Actions tests", function() { AssertJSOnPageLoad( "runWorldCountries", false, - "UncaughtPromiseRejection: getWorldCountries is not defined", + "ReferenceError: getWorldCountries is not defined", ); }); @@ -244,7 +244,7 @@ describe("JSObjects OnLoad Actions tests", function() { function AssertJSOnPageLoad( jsMethod: string, shouldCheckImport = false, - faliureMsg: string = "", + faliureMsg = "", ) { agHelper.AssertElementVisible( jsEditor._dialogBody("JSObject1." + jsMethod), diff --git a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OnLoadTests/JSOnLoad_cyclic_dependency_errors_spec.js b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OnLoadTests/JSOnLoad_cyclic_dependency_errors_spec.js index 455ecc4821..4b9a290ea8 100644 --- a/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OnLoadTests/JSOnLoad_cyclic_dependency_errors_spec.js +++ b/app/client/cypress/integration/Smoke_TestSuite/ServerSideTests/OnLoadTests/JSOnLoad_cyclic_dependency_errors_spec.js @@ -40,7 +40,7 @@ describe("Cyclic Dependency Informational Error Messagaes", function() { .last() .find(`${datasource.createQuery}`) .click({ force: true }); - + //Step5.1: Click the editing field cy.get(".t--action-name-edit-field").click({ force: true }); @@ -170,4 +170,4 @@ describe("Cyclic Dependency Informational Error Messagaes", function() { 1, ); }); -}); \ No newline at end of file +}); diff --git a/app/client/cypress/locators/Widgets.json b/app/client/cypress/locators/Widgets.json index 0979b26894..d550d7fa5d 100644 --- a/app/client/cypress/locators/Widgets.json +++ b/app/client/cypress/locators/Widgets.json @@ -58,7 +58,7 @@ "deleteWidget": ".t--modal-widget>div .t--widget-delete-control", "textbuttonWidget": ".t--draggable-buttonwidget button.bp3-button[type='button']", "textInputval": ".t--draggable-textwidget span.t--widget-name", - "textCenterAlign": ".t--property-control-alignment .t--button-tab-CENTER", + "textCenterAlign": ".t--property-control-alignment .t--button-group-CENTER", "ColumnAction": ".t--property-control-rowbutton button", "SearchTextChangeAction": ".t--property-control-onsearchtextchanged button", "tableSearchTextChangeSelected": ".t--property-control-onsearchtextchanged", @@ -86,15 +86,15 @@ "editCreatedColumn": ".t--property-control-createdcolumns input", "alignOpt": ".t--dropdown-option", "tableCol": ".draggable-header ", - "centerAlign": ".t--button-tab-CENTER", - "rightAlign": ".t--button-tab-RIGHT", - "leftAlign": ".t--button-tab-LEFT", - "bold": ".t--button-tab-BOLD", - "italics": ".t--button-tab-ITALIC", - "underline": ".t--button-tab-UNDERLINE", - "verticalTop": ".t--button-tab-TOP", - "verticalCenter": ".t--button-tab-CENTER", - "verticalBottom": ".t--button-tab-BOTTOM", + "centerAlign": ".t--button-group-CENTER", + "rightAlign": ".t--button-group-RIGHT", + "leftAlign": ".t--button-group-LEFT", + "bold": ".t--button-group-BOLD", + "italics": ".t--button-group-ITALIC", + "underline": ".t--button-group-UNDERLINE", + "verticalTop": ".t--button-group-TOP", + "verticalCenter": ".t--button-group-CENTER", + "verticalBottom": ".t--button-group-BOTTOM", "textColor": ".t--property-control-textcolor input", "boadercolorPicker": ".t--property-control-bordercolour input", "boxShadowColorPicker": ".t--property-control-shadowcolor input", diff --git a/app/client/cypress/support/Objects/mySqlData.ts b/app/client/cypress/support/Objects/mySqlData.ts index 96e1e0d986..88f1106592 100644 --- a/app/client/cypress/support/Objects/mySqlData.ts +++ b/app/client/cypress/support/Objects/mySqlData.ts @@ -219,7 +219,7 @@ const mySqlData = { ["a", "abcdefghijklmnopqrst", "12345678912345", "true", "null"], ["a", "abcdefghij", "012345", "false", "NulL"], ["a", "b", "c"], - ["0", "1"], + ["false", "true"], [{"abc": "123"}, {}, [1, 2, 3, 4], [], ["a",true,0,12.34]], ], query: { diff --git a/app/client/cypress/support/Pages/AggregateHelper.ts b/app/client/cypress/support/Pages/AggregateHelper.ts index 891561992d..5f1cf8502d 100644 --- a/app/client/cypress/support/Pages/AggregateHelper.ts +++ b/app/client/cypress/support/Pages/AggregateHelper.ts @@ -579,8 +579,8 @@ export class AggregateHelper { cy.get(selector) .contains(containsText) .eq(index) - .click() - .wait(200); + .click({ force: true }) + .wait(500); } public CheckUncheck(selector: string, check = true) { diff --git a/app/client/cypress/support/Pages/DataSources.ts b/app/client/cypress/support/Pages/DataSources.ts index 6fefeb51bc..e6816bec65 100644 --- a/app/client/cypress/support/Pages/DataSources.ts +++ b/app/client/cypress/support/Pages/DataSources.ts @@ -115,6 +115,22 @@ export class DataSources { private _queryTimeout = "//input[@name='actionConfiguration.timeoutInMillisecond']"; _getStructureReq = "/api/v1/datasources/*/structure?ignoreCache=true"; + _editDatasourceFromActiveTab = (dsName: string) => + ".t--datasource-name:contains('" + dsName + "')"; + private _urlInputControl = "input[name='url']"; + + // Authenticated API locators + private _authType = "[data-cy=authType]"; + private _oauth2 = ".t--dropdown-option:contains('OAuth 2.0')"; + private _accessTokenUrl = "[data-cy='authentication.accessTokenUrl'] input"; + private _clientID = "[data-cy='authentication.clientId'] input"; + private _clientSecret = "[data-cy='authentication.clientSecret'] input"; + private _authorizationCode = + ".t--dropdown-option:contains('Authorization Code')"; + private _grantType = "[data-cy='authentication.grantType']"; + private _authorizationURL = + "[data-cy='authentication.authorizationUrl'] input"; + public _datasourceModalSave = ".t--datasource-modal-save"; public _datasourceModalDoNotSave = ".t--datasource-modal-do-not-save"; @@ -372,7 +388,9 @@ export class DataSources { public TestDatasource(expectedRes = true) { this.agHelper.GetNClick(this._testDs, 0, false, 0); this.agHelper.ValidateNetworkDataSuccess("@testDatasource", expectedRes); - this.agHelper.AssertContains("datasource is valid"); + if (expectedRes) { + this.agHelper.AssertContains("datasource is valid"); + } } public SaveDatasource() { @@ -391,7 +409,7 @@ export class DataSources { this.agHelper.ValidateNetworkStatus("@saveDatasource", 201); } - public updateDatasource() { + public UpdateDatasource() { this.agHelper.GetNClick(this._saveDs); // this.agHelper.ValidateNetworkStatus("@updateDatasource", 200); this.agHelper.AssertContains("datasource updated"); @@ -519,11 +537,11 @@ export class DataSources { } public CloseReconnectDataSourceModal() { - cy.get('body').then(($ele) =>{ - if($ele.find(this._reconnectDataSourceModal).length){ - this.agHelper.GetNClick(this._closeDataSourceModal) + cy.get("body").then(($ele) => { + if ($ele.find(this._reconnectDataSourceModal).length) { + this.agHelper.GetNClick(this._closeDataSourceModal); } - }) + }); } RunQuery( @@ -760,7 +778,7 @@ export class DataSources { ) { cy.intercept("GET", this._getStructureReq).as("getDSStructure"); if (isUpdate) { - this.updateDatasource(); + this.UpdateDatasource(); } else { this.SaveDatasource(); } @@ -772,6 +790,8 @@ export class DataSources { public SaveDSFromDialog(save = true) { this.agHelper.GoBack(); + this.agHelper.AssertElementVisible(this._datasourceModalDoNotSave); + this.agHelper.AssertElementVisible(this._datasourceModalSave); if (save) { this.agHelper.GetNClick( this.locator._visibleTextSpan("SAVE"), @@ -790,7 +810,40 @@ export class DataSources { ); } - public getDSEntity(dSName: string){ + public getDSEntity(dSName: string) { return `[data-guided-tour-id="explorer-entity-${dSName}"]`; } + + public FillAuthAPIUrl() { + const URL = datasourceFormData["authenticatedApiUrl"]; + this.agHelper.TypeText(this._urlInputControl, URL); + } + + public AddOAuth2AuthorizationCodeDetails( + accessTokenUrl: string, + clientId: string, + clientSecret: string, + authURL: string, + ) { + this.agHelper.GetNClick(this._authType); + this.agHelper.GetNClick(this._oauth2); + this.agHelper.GetNClick(this._grantType); + this.agHelper.GetNClick(this._authorizationCode); + this.agHelper.TypeText(this._accessTokenUrl, accessTokenUrl); + this.agHelper.TypeText(this._clientID, clientId); + this.agHelper.TypeText(this._clientSecret, clientSecret); + this.agHelper.TypeText(this._authorizationURL, authURL); + } + + public EditDSFromActiveTab(dsName: string) { + this.agHelper.GetNClick(this._editDatasourceFromActiveTab(dsName)); + } + + public FillMongoDatasourceFormWithURI(uri: string) { + this.ValidateNSelectDropdown("Use Mongo Connection String URI", "", "Yes"); + this.agHelper.TypeText( + this.locator._inputFieldByName("Connection String URI") + "//input", + uri, + ); + } } diff --git a/app/client/cypress/support/Pages/HomePage.ts b/app/client/cypress/support/Pages/HomePage.ts index 9561c16881..1c7f5ebac5 100644 --- a/app/client/cypress/support/Pages/HomePage.ts +++ b/app/client/cypress/support/Pages/HomePage.ts @@ -80,6 +80,16 @@ export class HomePage { private _deleteAppConfirm = '[data-cy="t--delete"]'; private _wsAction = (action: string) => "//span[text()='" + action + "']/ancestor::a"; + private _homeTab = ".t--apps-tab"; + private _templatesTab = ".t--templates-tab"; + + public SwitchToAppsTab() { + this.agHelper.GetNClick(this._homeTab); + } + + public SwitchToTemplatesTab() { + this.agHelper.GetNClick(this._templatesTab); + } public CreateNewWorkspace(workspaceNewName: string) { let oldName = ""; diff --git a/app/client/cypress/support/commands.js b/app/client/cypress/support/commands.js index 08d1e2c29d..6efd6291b0 100644 --- a/app/client/cypress/support/commands.js +++ b/app/client/cypress/support/commands.js @@ -1673,7 +1673,7 @@ Cypress.Commands.add("checkLabelForWidget", (options) => { const containerSelector = `${widgetSelector} ${options.containerSelector}`; const labelPositionSelector = ".t--property-control-position"; const labelAlignmentRightSelector = - ".t--property-control-alignment .t--button-tab-right"; + ".t--property-control-alignment .t--button-group-right"; const labelWidth = options.labelWidth; // Drag a widget @@ -1690,17 +1690,17 @@ Cypress.Commands.add("checkLabelForWidget", (options) => { .contains(labelText); // Set the label position: Auto - cy.get(".t--button-tab-Auto").click({ force: true }); + cy.get(".t--button-group-Auto").click({ force: true }); // Assert label position: Auto cy.get(containerSelector).should("have.css", "flex-direction", "column"); // Change the label position to Top - cy.get(".t--button-tab-Top").click({ force: true }); + cy.get(".t--button-group-Top").click({ force: true }); // Assert label position: Top cy.get(containerSelector).should("have.css", "flex-direction", "column"); // Change the label position to Left - cy.get(".t--button-tab-Left").click({ force: true }); + cy.get(".t--button-group-Left").click({ force: true }); // Assert label position: Left cy.get(containerSelector).should("have.css", "flex-direction", "row"); diff --git a/app/client/package.json b/app/client/package.json index 9558fd66c5..8f4cae2d27 100644 --- a/app/client/package.json +++ b/app/client/package.json @@ -46,7 +46,7 @@ "cypress-log-to-output": "^1.1.2", "dayjs": "^1.10.6", "deep-diff": "^1.0.2", - "design-system": "npm:@appsmithorg/design-system@1.0.41", + "design-system": "npm:@appsmithorg/design-system@1.0.44", "downloadjs": "^1.4.7", "draft-js": "^0.11.7", "exceljs-lightweight": "^1.14.0", diff --git a/app/client/src/actions/datasourceActions.ts b/app/client/src/actions/datasourceActions.ts index 5a703cf128..90540bc50a 100644 --- a/app/client/src/actions/datasourceActions.ts +++ b/app/client/src/actions/datasourceActions.ts @@ -338,6 +338,25 @@ export const toggleSaveActionFromPopupFlag = (isDSSavedFromPopup: boolean) => { }; }; +// This function stores the config property for key value pairs in +// datasource form which are initialized by default +export const setDefaultKeyValPairFlag = (defaultKeyValArrayConfig: string) => { + return { + type: ReduxActionTypes.SET_DATASOURCE_DEFAULT_KEY_VALUE_PAIR_SET, + payload: defaultKeyValArrayConfig, + }; +}; + +// This function resets the config property stored in redux for key value pairs in +// datasource form which are initialized by default, once these key value pairs are initialized +// in the datasource config form, store needs to reset those values +export const resetDefaultKeyValPairFlag = () => { + return { + type: ReduxActionTypes.RESET_DATASOURCE_DEFAULT_KEY_VALUE_PAIR_SET, + payload: [], + }; +}; + export default { fetchDatasources, initDatasourcePane, diff --git a/app/client/src/actions/evaluationActions.ts b/app/client/src/actions/evaluationActions.ts index dd402e2cd9..aca42ec15b 100644 --- a/app/client/src/actions/evaluationActions.ts +++ b/app/client/src/actions/evaluationActions.ts @@ -61,7 +61,7 @@ export const EVALUATE_REDUX_ACTIONS = [ ReduxActionTypes.FETCH_JS_ACTIONS_VIEW_MODE_SUCCESS, ReduxActionErrorTypes.FETCH_JS_ACTIONS_VIEW_MODE_ERROR, ReduxActionTypes.UPDATE_JS_ACTION_BODY_SUCCESS, - ReduxActionTypes.EXECUTE_JS_FUNCTION_SUCCESS, + ReduxActionTypes.SET_JS_FUNCTION_EXECUTION_DATA, // App Data ReduxActionTypes.SET_APP_MODE, ReduxActionTypes.FETCH_USER_DETAILS_SUCCESS, diff --git a/app/client/src/ce/constants/ReduxActionConstants.tsx b/app/client/src/ce/constants/ReduxActionConstants.tsx index d97a970b9d..e16881ac51 100644 --- a/app/client/src/ce/constants/ReduxActionConstants.tsx +++ b/app/client/src/ce/constants/ReduxActionConstants.tsx @@ -557,6 +557,7 @@ export const ReduxActionTypes = { FETCH_JS_ACTIONS_VIEW_MODE_SUCCESS: "FETCH_JS_ACTIONS_VIEW_MODE_SUCCESS", EXECUTE_JS_FUNCTION_INIT: "EXECUTE_JS_FUNCTION_INIT", EXECUTE_JS_FUNCTION_SUCCESS: "EXECUTE_JS_FUNCTION_SUCCESS", + SET_JS_FUNCTION_EXECUTION_DATA: "SET_JS_FUNCTION_EXECUTION_DATA", GET_PLUGIN_FORM_CONFIG_INIT: "GET_PLUGIN_FORM_CONFIG_INIT", EXECUTE_DATASOURCE_QUERY_INIT: "EXECUTE_DATASOURCE_QUERY_INIT", EXECUTE_DATASOURCE_QUERY_SUCCESS: "EXECUTE_DATASOURCE_QUERY_SUCCESS", @@ -752,6 +753,10 @@ export const ReduxActionTypes = { SET_DATASOURCE_SAVE_ACTION_FLAG: "SET_DATASOURCE_SAVE_ACTION_FLAG", SET_DATASOURCE_SAVE_ACTION_FROM_POPUP_FLAG: "SET_DATASOURCE_SAVE_ACTION_FROM_POPUP_FLAG", + SET_DATASOURCE_DEFAULT_KEY_VALUE_PAIR_SET: + "SET_DATASOURCE_DEFAULT_KEY_VALUE_PAIR_SET", + RESET_DATASOURCE_DEFAULT_KEY_VALUE_PAIR_SET: + "RESET_DATASOURCE_DEFAULT_KEY_VALUE_PAIR_SET", }; export type ReduxActionType = typeof ReduxActionTypes[keyof typeof ReduxActionTypes]; diff --git a/app/client/src/ce/constants/messages.ts b/app/client/src/ce/constants/messages.ts index f58c699b9d..967f7f9590 100644 --- a/app/client/src/ce/constants/messages.ts +++ b/app/client/src/ce/constants/messages.ts @@ -1190,6 +1190,8 @@ export const ADMIN_BRANDING_COLOR_TOOLTIP_FONT = () => `Used as text color for the buttons.`; export const ADMIN_BRANDING_COLOR_TOOLTIP_DISABLED = () => `Used as background color for disabled buttons.`; +export const ADMIN_BRANDING_UPGRADE_INTERCOM_MESSAGE = () => + `I would like to enable Custom Branding for my workspace and am interested in Appsmith Business.`; // Guided tour // -- STEPS --- diff --git a/app/client/src/ce/pages/AdminSettings/config/authentication/AuthPage.tsx b/app/client/src/ce/pages/AdminSettings/config/authentication/AuthPage.tsx index 516f5ff220..819ca8f274 100644 --- a/app/client/src/ce/pages/AdminSettings/config/authentication/AuthPage.tsx +++ b/app/client/src/ce/pages/AdminSettings/config/authentication/AuthPage.tsx @@ -212,7 +212,7 @@ export function AuthPage({ authMethods }: { authMethods: AuthMethodType[] }) { { const { onUpgrade } = useOnUpgrade({ logEventName: "BRANDING_UPGRADE_CLICK", - intercomMessage: - "Hello, I would like to upgrade my appsmith instance to use the custom branding feature", + intercomMessage: createMessage(ADMIN_BRANDING_UPGRADE_INTERCOM_MESSAGE), }); return ( diff --git a/app/client/src/ce/pages/workspace/WorkspaceInviteUsersForm.tsx b/app/client/src/ce/pages/workspace/WorkspaceInviteUsersForm.tsx index 3862b1b47e..22769824f8 100644 --- a/app/client/src/ce/pages/workspace/WorkspaceInviteUsersForm.tsx +++ b/app/client/src/ce/pages/workspace/WorkspaceInviteUsersForm.tsx @@ -636,6 +636,7 @@ export default connect( applicationId?: string; workspaceId?: string; isApplicationInvite?: boolean; + placeholder?: string; } >({ validate, diff --git a/app/client/src/components/designSystems/appsmith/BaseButton.tsx b/app/client/src/components/designSystems/appsmith/BaseButton.tsx index ff881bb5e6..d623ee1618 100644 --- a/app/client/src/components/designSystems/appsmith/BaseButton.tsx +++ b/app/client/src/components/designSystems/appsmith/BaseButton.tsx @@ -152,7 +152,7 @@ const StyledButton = styled((props) => ( } !important; } - &:hover:enabled, &:active:enabled { + &:hover:enabled, &:active:enabled. &:focus:enabled { background: ${ buttonStyle === ButtonStyleTypes.WARNING ? buttonVariant === ButtonVariantTypes.SECONDARY diff --git a/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx b/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx index da5c6a7c63..59c0252008 100644 --- a/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx +++ b/app/client/src/components/designSystems/appsmith/PositionedContainer.tsx @@ -22,7 +22,10 @@ import { import { POSITIONED_WIDGET } from "constants/componentClassNameConstants"; import equal from "fast-deep-equal"; -const PositionedWidget = styled.div<{ zIndexOnHover: number }>` +const PositionedWidget = styled.div<{ + zIndexOnHover: number; + disabled?: boolean; +}>` &:hover { z-index: ${(props) => props.zIndexOnHover} !important; } @@ -42,6 +45,8 @@ export type PositionedContainerProps = { noContainerOffset?: boolean; leftColumn: number; parentColumnSpace: number; + isDisabled?: boolean; + isVisible?: boolean; }; export const checkIsDropTarget = memoize(function isDropTarget( @@ -163,7 +168,9 @@ export function PositionedContainer(props: PositionedContainerProps) { return ( { - const childNodes = navigationData.children; + const childNodes = navigationData.children || {}; if (Object.keys(childNodes).length) { const token = editor.getTokenAt( { diff --git a/app/client/src/components/formControls/KeyValueArrayControl.tsx b/app/client/src/components/formControls/KeyValueArrayControl.tsx index 913e19a452..cc4e67268a 100644 --- a/app/client/src/components/formControls/KeyValueArrayControl.tsx +++ b/app/client/src/components/formControls/KeyValueArrayControl.tsx @@ -21,6 +21,8 @@ import { TextInputProps, TextType, } from "design-system"; +import { setDefaultKeyValPairFlag } from "actions/datasourceActions"; +import { useDispatch } from "react-redux"; export interface KeyValueArrayControlProps extends ControlProps { name: string; label: string; @@ -88,6 +90,7 @@ function KeyValueRow( const keyName = getFieldName(extraData[0]?.configProperty); const valueName = getFieldName(extraData[1]?.configProperty); const keyFieldProps = extraData[0]; + const dispatch = useDispatch(); const addRow = useCallback(() => { if (keyName && valueName) { @@ -102,6 +105,9 @@ function KeyValueRow( if (props.fields.length < 1) { for (let i = props.fields.length; i < 1; i += 1) { addRow(); + // Since we are initializing one default key value pair, it needs to stored in redux store + // so that it can be used to initilize datasource config form as well + dispatch(setDefaultKeyValPairFlag(props.configProperty)); } } }, [props.fields, keyName, valueName]); diff --git a/app/client/src/components/propertyControls/BorderRadiusOptionsControl.tsx b/app/client/src/components/propertyControls/BorderRadiusOptionsControl.tsx index a4216c1ef7..cc576b81fb 100644 --- a/app/client/src/components/propertyControls/BorderRadiusOptionsControl.tsx +++ b/app/client/src/components/propertyControls/BorderRadiusOptionsControl.tsx @@ -1,9 +1,8 @@ import * as React from "react"; -import { TooltipComponent } from "design-system"; +import { ButtonGroup, TooltipComponent } from "design-system"; import BaseControl, { ControlData, ControlProps } from "./BaseControl"; import { borderRadiusOptions } from "constants/ThemeConstants"; -import { ButtonTab } from "design-system"; import { DSEventDetail, DSEventTypes, @@ -64,7 +63,7 @@ class BorderRadiusOptionsControl extends BaseControl< handleAdsEvent = (e: CustomEvent) => { if ( - e.detail.component === "ButtonTab" && + e.detail.component === "ButtonGroup" && e.detail.event === DSEventTypes.KEYPRESS ) { emitInteractionAnalyticsEvent(this.componentRef.current, { @@ -80,7 +79,7 @@ class BorderRadiusOptionsControl extends BaseControl< public render() { return ( - { diff --git a/app/client/src/components/propertyControls/BoxShadowOptionsControl.tsx b/app/client/src/components/propertyControls/BoxShadowOptionsControl.tsx index da2e8bb236..ec4cae863e 100644 --- a/app/client/src/components/propertyControls/BoxShadowOptionsControl.tsx +++ b/app/client/src/components/propertyControls/BoxShadowOptionsControl.tsx @@ -1,10 +1,9 @@ import * as React from "react"; import BaseControl, { ControlData, ControlProps } from "./BaseControl"; -import { TooltipComponent } from "design-system"; +import { ButtonGroup, TooltipComponent } from "design-system"; import { boxShadowOptions } from "constants/ThemeConstants"; import CloseLineIcon from "remixicon-react/CloseLineIcon"; -import { ButtonTab } from "design-system"; import { DSEventDetail, DSEventTypes, @@ -58,7 +57,7 @@ class BoxShadowOptionsControl extends BaseControl< handleAdsEvent = (e: CustomEvent) => { if ( - e.detail.component === "ButtonTab" && + e.detail.component === "ButtonGroup" && e.detail.event === DSEventTypes.KEYPRESS ) { emitInteractionAnalyticsEvent(this.componentRef.current, { @@ -74,7 +73,7 @@ class BoxShadowOptionsControl extends BaseControl< public render() { return ( - { diff --git a/app/client/src/components/propertyControls/ButtonTabControl.tsx b/app/client/src/components/propertyControls/ButtonTabControl.tsx index 8b5020647e..1258cf88bd 100644 --- a/app/client/src/components/propertyControls/ButtonTabControl.tsx +++ b/app/client/src/components/propertyControls/ButtonTabControl.tsx @@ -1,6 +1,6 @@ import React from "react"; import BaseControl, { ControlData, ControlProps } from "./BaseControl"; -import { ButtonTab, ButtonTabOption } from "design-system"; +import { ButtonGroup, ButtonGroupOption } from "design-system"; import produce from "immer"; import { DSEventDetail, @@ -28,7 +28,7 @@ class ButtonTabControl extends BaseControl { handleAdsEvent = (e: CustomEvent) => { if ( - e.detail.component === "ButtonTab" && + e.detail.component === "ButtonGroup" && e.detail.event === DSEventTypes.KEYPRESS ) { emitInteractionAnalyticsEvent(this.componentRef.current, { @@ -67,7 +67,7 @@ class ButtonTabControl extends BaseControl { render() { const { options, propertyValue } = this.props; return ( - { } static getControlType() { - return "BUTTON_TABS"; + return "BUTTON_GROUP"; } static canDisplayValueInUI(config: ControlData, value: any): boolean { @@ -98,7 +98,7 @@ class ButtonTabControl extends BaseControl { } export interface ButtonTabControlProps extends ControlProps { - options: ButtonTabOption[]; + options: ButtonGroupOption[]; defaultValue: string; } diff --git a/app/client/src/components/propertyControls/IconTabControl.tsx b/app/client/src/components/propertyControls/IconTabControl.tsx index 50c7aa9aac..cb18d79b02 100644 --- a/app/client/src/components/propertyControls/IconTabControl.tsx +++ b/app/client/src/components/propertyControls/IconTabControl.tsx @@ -1,6 +1,6 @@ import React from "react"; import BaseControl, { ControlData, ControlProps } from "./BaseControl"; -import { ButtonTab, ButtonTabOption } from "design-system"; +import { ButtonGroup, ButtonGroupOption } from "design-system"; import { DSEventDetail, DSEventTypes, @@ -27,7 +27,7 @@ class IconTabControl extends BaseControl { handleAdsEvent = (e: CustomEvent) => { if ( - e.detail.component === "ButtonTab" && + e.detail.component === "ButtonGroup" && e.detail.event === DSEventTypes.KEYPRESS ) { emitInteractionAnalyticsEvent(this.componentRef.current, { @@ -52,7 +52,7 @@ class IconTabControl extends BaseControl { render() { const { fullWidth, options, propertyValue } = this.props; return ( - { } export interface IconTabControlProps extends ControlProps { - options: ButtonTabOption[]; + options: ButtonGroupOption[]; defaultValue: string; fullWidth: boolean; } diff --git a/app/client/src/components/propertyControls/LabelAlignmentOptionsControl.tsx b/app/client/src/components/propertyControls/LabelAlignmentOptionsControl.tsx index ffd8cfd6c5..9d5aa72c68 100644 --- a/app/client/src/components/propertyControls/LabelAlignmentOptionsControl.tsx +++ b/app/client/src/components/propertyControls/LabelAlignmentOptionsControl.tsx @@ -3,7 +3,7 @@ import styled from "styled-components"; import { Alignment } from "@blueprintjs/core"; import BaseControl, { ControlProps } from "./BaseControl"; -import { ButtonTab, ButtonTabOption } from "design-system"; +import { ButtonGroup, ButtonGroupOption } from "design-system"; import { DSEventDetail, DSEventTypes, @@ -22,7 +22,7 @@ const ControlContainer = styled.div` export interface LabelAlignmentOptionsControlProps extends ControlProps { propertyValue?: Alignment; - options: ButtonTabOption[]; + options: ButtonGroupOption[]; defaultValue: Alignment; } @@ -52,7 +52,7 @@ class LabelAlignmentOptionsControl extends BaseControl< handleAdsEvent = (e: CustomEvent) => { if ( - e.detail.component === "ButtonTab" && + e.detail.component === "ButtonGroup" && e.detail.event === DSEventTypes.KEYPRESS ) { emitInteractionAnalyticsEvent(this.componentRef.current, { @@ -70,7 +70,7 @@ class LabelAlignmentOptionsControl extends BaseControl< const { options, propertyValue } = this.props; return ( - { @@ -115,6 +133,7 @@ class MenuButtonDynamicItemsControl extends BaseControl< if (value && !propertyValue) { this.onTextChange(value); } + return ( { - return `{{${menuButtonId}.sourceData.map((currentItem, currentIndex) => ( `; + static getBindingPrefix = ( + widgetName: string, + widgetType?: string, + primaryColumns?: Record, + ) => { + if (widgetType === "TABLE_WIDGET_V2" && primaryColumns) { + const columnName = Object.keys(primaryColumns)?.[0]; + + return `{{${widgetName}.processedTableData.map((currentRow, currentRowIndex) => { + let primaryColumnData = []; + + if (${widgetName}.primaryColumns.${columnName}.sourceData[currentRowIndex].length) { + primaryColumnData = ${widgetName}.primaryColumns.${columnName}.sourceData[currentRowIndex]; + } else if (${widgetName}.primaryColumns.${columnName}.sourceData.length) { + primaryColumnData = ${widgetName}.primaryColumns.${columnName}.sourceData; + } + + return primaryColumnData.map((currentItem, currentIndex) => `; + } else { + return `{{${widgetName}.sourceData.map((currentItem, currentIndex) => ( `; + } }; - static bindingSuffix = `))}}`; + static getBindingSuffix = (widgetType?: string) => + widgetType === "TABLE_WIDGET_V2" ? `);});}}` : `))}}`; static getInputComputedValue = ( propertyValue: string, - menuButtonId: string, + widgetName: string, + widgetType?: string, + primaryColumns?: Record, ) => { - if (!propertyValue.includes(this.getBindingPrefix(menuButtonId))) { + if ( + !propertyValue.includes( + this.getBindingPrefix(widgetName, widgetType, primaryColumns), + ) + ) { return propertyValue; } const value = `${propertyValue.substring( - this.getBindingPrefix(menuButtonId).length, - propertyValue.length - this.bindingSuffix.length, + this.getBindingPrefix(widgetName, widgetType, primaryColumns).length, + propertyValue.length - this.getBindingSuffix(widgetType).length, )}`; const stringValue = JSToString(value); return stringValue; }; - getComputedValue = (value: string, menuButtonId: string) => { + getComputedValue = ( + value: string, + widgetName: string, + widgetType?: string, + primaryColumns?: Record, + ) => { if (!isDynamicValue(value)) { return value; } @@ -166,8 +216,12 @@ class MenuButtonDynamicItemsControl extends BaseControl< } return `${MenuButtonDynamicItemsControl.getBindingPrefix( - menuButtonId, - )}${stringToEvaluate}${MenuButtonDynamicItemsControl.bindingSuffix}`; + widgetName, + widgetType, + primaryColumns, + )}${stringToEvaluate}${MenuButtonDynamicItemsControl.getBindingSuffix( + widgetType, + )}`; }; onTextChange = (event: React.ChangeEvent | string) => { @@ -181,6 +235,8 @@ class MenuButtonDynamicItemsControl extends BaseControl< const output = this.getComputedValue( value, this.props.widgetProperties.widgetName, + this.props.widgetProperties.type, + this.props.widgetProperties.primaryColumns, ); this.updateProperty(this.props.propertyName, output); diff --git a/app/client/src/components/propertyControls/OpenConfigPanelControl.tsx b/app/client/src/components/propertyControls/OpenConfigPanelControl.tsx index 37705ca0da..cca6515a43 100644 --- a/app/client/src/components/propertyControls/OpenConfigPanelControl.tsx +++ b/app/client/src/components/propertyControls/OpenConfigPanelControl.tsx @@ -44,7 +44,7 @@ class OpenConfigPanelControl extends BaseControl { | string) => { let value = ""; + if (typeof event !== "string") { value = event.target?.value; } else { value = event; } + if (isString(value)) { const output = this.getComputedValue( value, diff --git a/app/client/src/components/wds/Checkbox/index.tsx b/app/client/src/components/wds/Checkbox/index.tsx index d5c27cb1a1..fbc6ba769f 100644 --- a/app/client/src/components/wds/Checkbox/index.tsx +++ b/app/client/src/components/wds/Checkbox/index.tsx @@ -59,7 +59,8 @@ const Checkbox = styled(BlueprintCheckbox)` } // hover - &.bp3-control.bp3-checkbox:hover input:not(:checked) ~ .bp3-control-indicator { + &.bp3-control.bp3-checkbox:hover input:not(:checked) ~ .bp3-control-indicator, + input:not(:checked):focus ~ .bp3-control-indicator { box-shadow: 0px 0px 0px 1px ${ hasError ? "var(--wds-color-border-danger-hover)" @@ -68,7 +69,10 @@ const Checkbox = styled(BlueprintCheckbox)` } // hover on checked - &.bp3-control.bp3-checkbox:hover input:checked ~ .bp3-control-indicator, &.bp3-control.bp3-checkbox:hover input:indeterminate ~ .bp3-control-indicator { + &.bp3-control.bp3-checkbox:hover input:checked ~ .bp3-control-indicator, + &.bp3-control.bp3-checkbox:hover input:indeterminate ~ .bp3-control-indicator, + &.bp3-control.bp3-checkbox input:checked:focus ~ .bp3-control-indicator, + &.bp3-control.bp3-checkbox input:indeterminate:focus ~ .bp3-control-indicator { box-shadow: none; background: ${darkenColor(accentColor)} !important; } diff --git a/app/client/src/constants/DefaultTheme.tsx b/app/client/src/constants/DefaultTheme.tsx index 15106c970a..d517d9a717 100644 --- a/app/client/src/constants/DefaultTheme.tsx +++ b/app/client/src/constants/DefaultTheme.tsx @@ -105,10 +105,9 @@ export const BlueprintControlTransform = css` border: 1px solid var(--wds-color-border-disabled) !important; } - &:hover { - & input:not(:checked):not(:disabled) ~ .bp3-control-indicator { - border: 1px solid ${Colors.GREY_6} !important; - } + &:hover input:not(:checked):not(:disabled) ~ .bp3-control-indicator, + & input:not(:checked):not(:disabled):focus ~ .bp3-control-indicator { + border: 1px solid var(--wds-color-bg-disabled-strong) !important; } } @@ -137,11 +136,10 @@ export const BlueprintControlTransform = css` } } - &:hover { - & input:not(:checked):not(:disabled) ~ .bp3-control-indicator { - background: var(--wds-color-bg-strong-hover); - border: 1px solid var(--wds-color-border-hover) !important; - } + &:hover input:not(:checked):not(:disabled) ~ .bp3-control-indicator, + input:not(:checked):not(:disabled):focus ~ .bp3-control-indicator { + background: var(--wds-color-bg-strong-hover); + border: 1px solid var(--wds-color-border-hover) !important; } } diff --git a/app/client/src/constants/WidgetConstants.tsx b/app/client/src/constants/WidgetConstants.tsx index bb2888ff07..b3b2173532 100644 --- a/app/client/src/constants/WidgetConstants.tsx +++ b/app/client/src/constants/WidgetConstants.tsx @@ -70,7 +70,7 @@ export const layoutConfigurations: LayoutConfigurations = { FLUID: { minWidth: -1, maxWidth: -1 }, }; -export const LATEST_PAGE_VERSION = 74; +export const LATEST_PAGE_VERSION = 75; export const GridDefaults = { DEFAULT_CELL_SIZE: 1, diff --git a/app/client/src/constants/componentClassNameConstants.ts b/app/client/src/constants/componentClassNameConstants.ts index fb79f40505..9a874924d8 100644 --- a/app/client/src/constants/componentClassNameConstants.ts +++ b/app/client/src/constants/componentClassNameConstants.ts @@ -1,8 +1,8 @@ -export function getStickyCanvasName(widgetId: string) { +export function getSlidingArenaName(widgetId: string) { return `div-selection-${widgetId}`; } -export function getSlidingCanvasName(widgetId: string) { +export function getStickyCanvasName(widgetId: string) { return `canvas-selection-${widgetId}`; } diff --git a/app/client/src/entities/Datasource/RestAPIForm.ts b/app/client/src/entities/Datasource/RestAPIForm.ts index 91189f5987..bc875bb082 100644 --- a/app/client/src/entities/Datasource/RestAPIForm.ts +++ b/app/client/src/entities/Datasource/RestAPIForm.ts @@ -70,7 +70,7 @@ export interface Oauth2Common { isAuthorizationHeader: boolean; audience: string; resource: string; - sendScopeWithRefreshToken: string; + sendScopeWithRefreshToken: boolean; refreshTokenClientCredentialsLocation: string; useSelfSignedCert?: boolean; } diff --git a/app/client/src/pages/AppViewer/index.tsx b/app/client/src/pages/AppViewer/index.tsx index 3ccf9c8b35..61bf7f1301 100644 --- a/app/client/src/pages/AppViewer/index.tsx +++ b/app/client/src/pages/AppViewer/index.tsx @@ -49,6 +49,7 @@ import { checkContainersForAutoHeightAction, updateWidgetAutoHeightAction, } from "actions/autoHeightActions"; +import useWidgetFocus from "utils/hooks/useWidgetFocus/useWidgetFocus"; const AppViewerBody = styled.section<{ hasPages: boolean; @@ -96,6 +97,8 @@ function AppViewer(props: Props) { const prevValues = usePrevious({ branch, location: props.location, pageId }); const { hideWatermark } = getAppsmithConfigs(); + const focusRef = useWidgetFocus(); + /** * initializes the widgets factory and registers all widgets */ @@ -268,6 +271,7 @@ function AppViewer(props: Props) { className={CANVAS_SELECTOR} hasPages={pages.length > 1} headerHeight={headerHeight} + ref={focusRef} showGuidedTourMessage={showGuidedTourMessage} > {isInitialized && registered && } diff --git a/app/client/src/pages/Applications/ImportApplicationModalOld.tsx b/app/client/src/pages/Applications/ImportApplicationModalOld.tsx index 4fca60fed5..1598153da0 100644 --- a/app/client/src/pages/Applications/ImportApplicationModalOld.tsx +++ b/app/client/src/pages/Applications/ImportApplicationModalOld.tsx @@ -104,7 +104,7 @@ function ImportApplicationModal(props: ImportApplicationModalProps) { { backgroundForCanvas = selectedTheme.properties.colors.backgroundColor; } + const focusRef = useWidgetFocus(); + try { return ( { ); !!data && delayedShareMousePointer(data); }} + ref={focusRef} style={{ width: canvasWidth, }} diff --git a/app/client/src/pages/Editor/DataSourceEditor/SaveOrDiscardDatasourceModal.tsx b/app/client/src/pages/Editor/DataSourceEditor/SaveOrDiscardDatasourceModal.tsx index cb12fbf1f2..f2fea68ae0 100644 --- a/app/client/src/pages/Editor/DataSourceEditor/SaveOrDiscardDatasourceModal.tsx +++ b/app/client/src/pages/Editor/DataSourceEditor/SaveOrDiscardDatasourceModal.tsx @@ -56,7 +56,7 @@ function SaveOrDiscardDatasourceModal(props: SaveOrDiscardModalProps) {
-

Authenticated API

+

Authenticated API

)} diff --git a/app/client/src/pages/Editor/ThemePropertyPane/controls/ThemeBorderRadiusControl.tsx b/app/client/src/pages/Editor/ThemePropertyPane/controls/ThemeBorderRadiusControl.tsx index bd7efbba71..6cc43f899d 100644 --- a/app/client/src/pages/Editor/ThemePropertyPane/controls/ThemeBorderRadiusControl.tsx +++ b/app/client/src/pages/Editor/ThemePropertyPane/controls/ThemeBorderRadiusControl.tsx @@ -1,7 +1,7 @@ import React, { useCallback } from "react"; import { AppTheme } from "entities/AppTheming"; -import { ButtonTab, TooltipComponent } from "design-system"; +import { ButtonGroup, TooltipComponent } from "design-system"; import { invertedBorderRadiusOptions } from "constants/ThemeConstants"; interface ThemeBorderRadiusControlProps { @@ -39,7 +39,7 @@ function ThemeBorderRadiusControl(props: ThemeBorderRadiusControlProps) { ? [invertedBorderRadiusOptions[selectedOption]] : []; - const buttonTabOptions = Object.keys(options).map((optionKey) => ({ + const buttonGroupOptions = Object.keys(options).map((optionKey) => ({ icon: ( diff --git a/app/client/src/pages/Editor/ThemePropertyPane/controls/ThemeShadowControl.tsx b/app/client/src/pages/Editor/ThemePropertyPane/controls/ThemeShadowControl.tsx index 8388b8cab7..76b5305354 100644 --- a/app/client/src/pages/Editor/ThemePropertyPane/controls/ThemeShadowControl.tsx +++ b/app/client/src/pages/Editor/ThemePropertyPane/controls/ThemeShadowControl.tsx @@ -1,8 +1,6 @@ import React, { useCallback } from "react"; -import { ButtonTab } from "design-system"; - import { AppTheme } from "entities/AppTheming"; -import { TooltipComponent } from "design-system"; +import { ButtonGroup, TooltipComponent } from "design-system"; import CloseLineIcon from "remixicon-react/CloseLineIcon"; import { invertedBoxShadowOptions } from "constants/ThemeConstants"; @@ -42,7 +40,7 @@ function ThemeBoxShadowControl(props: ThemeBoxShadowControlProps) { ? [invertedBoxShadowOptions[selectedOption]] : []; - const buttonTabOptions = Object.keys(options).map((optionKey) => ({ + const buttonGroupOptions = Object.keys(options).map((optionKey) => ({ icon: ( diff --git a/app/client/src/pages/Editor/gitSync/ReconnectDatasourceModal.tsx b/app/client/src/pages/Editor/gitSync/ReconnectDatasourceModal.tsx index d9faf16a21..0d4ea9ad25 100644 --- a/app/client/src/pages/Editor/gitSync/ReconnectDatasourceModal.tsx +++ b/app/client/src/pages/Editor/gitSync/ReconnectDatasourceModal.tsx @@ -316,6 +316,13 @@ function ReconnectDatasourceModal() { const [datasource, setDatasource] = useState(null); const [isImport, setIsImport] = useState(queryIsImport); const [isTesting, setIsTesting] = useState(false); + const queryDS = datasources.find((ds) => ds.id === queryDatasourceId); + const dsName = queryDS?.name; + const orgId = queryDS?.workspaceId; + let pluginName = ""; + if (!!queryDS?.pluginId) { + pluginName = pluginNames[queryDS.pluginId]; + } // when redirecting from oauth, processing the status if (isImport) { @@ -330,6 +337,13 @@ function ReconnectDatasourceModal() { ? OAUTH_AUTHORIZATION_APPSMITH_ERROR : OAUTH_AUTHORIZATION_FAILED; Toaster.show({ text: display_message || message, variant }); + const oAuthStatus = status; + AnalyticsUtil.logEvent("UPDATE_DATASOURCE", { + dsName, + oAuthStatus, + orgId, + pluginName, + }); } else if (queryDatasourceId) { dispatch(getOAuthAccessToken(queryDatasourceId)); } diff --git a/app/client/src/pages/Templates/Template/TemplateDescription.tsx b/app/client/src/pages/Templates/Template/TemplateDescription.tsx index e9ee451b1d..17d4750747 100644 --- a/app/client/src/pages/Templates/Template/TemplateDescription.tsx +++ b/app/client/src/pages/Templates/Template/TemplateDescription.tsx @@ -28,6 +28,8 @@ import WidgetInfo from "../WidgetInfo"; import ForkTemplate from "../ForkTemplate"; import { templateIdUrl } from "RouteBuilder"; import { useQuery } from "pages/Editor/utils"; +import { useSelector } from "react-redux"; +import { getForkableWorkspaces } from "selectors/templatesSelectors"; export const DescriptionWrapper = styled.div` display: flex; @@ -94,6 +96,7 @@ function TemplateDescription(props: TemplateDescriptionProps) { }>(); const history = useHistory(); const query = useQuery(); + const workspaceList = useSelector(getForkableWorkspaces); const onForkButtonTrigger = () => { history.replace( @@ -114,7 +117,7 @@ function TemplateDescription(props: TemplateDescriptionProps) { {template.description}
- {!props.hideForkButton && ( + {!props.hideForkButton && !!workspaceList.length && ( { if (props.onClick) { props.onClick(id); @@ -176,26 +179,29 @@ export function TemplateLayout(props: TemplateLayoutProps) { ); })} -
- - + - - - -
+ + + + + + )} diff --git a/app/client/src/pages/common/CanvasArenas/CanvasSelectionArena.tsx b/app/client/src/pages/common/CanvasArenas/CanvasSelectionArena.tsx index 1a3d15e68a..8002817ad4 100644 --- a/app/client/src/pages/common/CanvasArenas/CanvasSelectionArena.tsx +++ b/app/client/src/pages/common/CanvasArenas/CanvasSelectionArena.tsx @@ -23,8 +23,8 @@ import { getIsDraggingForSelection } from "selectors/canvasSelectors"; import { StickyCanvasArena } from "./StickyCanvasArena"; import { getAbsolutePixels } from "utils/helpers"; import { - getSlidingCanvasName, getStickyCanvasName, + getSlidingArenaName, } from "constants/componentClassNameConstants"; export interface SelectedArenaDimensions { @@ -482,10 +482,10 @@ export function CanvasSelectionArena({ return shouldShow ? ( +
{errorCode && (
{errorCode} diff --git a/app/client/src/pages/common/datasourceAuth/index.tsx b/app/client/src/pages/common/datasourceAuth/index.tsx index 5f649e6f66..59d0a0d94c 100644 --- a/app/client/src/pages/common/datasourceAuth/index.tsx +++ b/app/client/src/pages/common/datasourceAuth/index.tsx @@ -4,6 +4,7 @@ import { ActionButton } from "pages/Editor/DataSourceEditor/JSONtoForm"; import { useDispatch, useSelector } from "react-redux"; import { getEntities, + getPluginNameFromId, getPluginTypeFromDatasourceId, } from "selectors/entitiesSelector"; import { @@ -124,8 +125,11 @@ function DatasourceAuth({ ? formData?.authType : formData?.datasourceConfiguration?.authentication?.authenticationType; - const { id: datasourceId, isDeleting } = datasource; + const { id: datasourceId, isDeleting, pluginId } = datasource; const applicationId = useSelector(getCurrentApplicationId); + const pluginName = useSelector((state: AppState) => + getPluginNameFromId(state, pluginId), + ); const datasourcePermissions = datasource.userPermissions || []; @@ -144,6 +148,8 @@ function DatasourceAuth({ const pageId = (pageIdQuery || pageIdProp) as string; const [confirmDelete, setConfirmDelete] = useState(false); + const dsName = datasource?.name; + const orgId = datasource?.workspaceId; useEffect(() => { if (confirmDelete) { @@ -172,6 +178,13 @@ function DatasourceAuth({ ? OAUTH_AUTHORIZATION_APPSMITH_ERROR : OAUTH_AUTHORIZATION_FAILED; Toaster.show({ text: display_message || message, variant }); + const oAuthStatus = status; + AnalyticsUtil.logEvent("UPDATE_DATASOURCE", { + dsName, + oAuthStatus, + orgId, + pluginName, + }); } else { dispatch(getOAuthAccessToken(datasourceId)); } diff --git a/app/client/src/pages/workspace/AppInviteUsersForm.tsx b/app/client/src/pages/workspace/AppInviteUsersForm.tsx index 23fda61d1e..6797238be8 100644 --- a/app/client/src/pages/workspace/AppInviteUsersForm.tsx +++ b/app/client/src/pages/workspace/AppInviteUsersForm.tsx @@ -21,10 +21,14 @@ import useWorkspace from "utils/hooks/useWorkspace"; import TooltipWrapper from "pages/Applications/EmbedSnippet/TooltipWrapper"; import { createMessage, + INVITE_USERS_PLACEHOLDER, IN_APP_EMBED_SETTING, MAKE_APPLICATION_PUBLIC, MAKE_APPLICATION_PUBLIC_TOOLTIP, } from "@appsmith/constants/messages"; +import { getAppsmithConfigs } from "@appsmith/configs"; + +const { cloudHosting } = getAppsmithConfigs(); const ShareToggle = styled.div` flex-basis: 46px; @@ -85,6 +89,7 @@ function AppInviteUsersForm(props: any) { {canInviteToWorkspace && ( )} diff --git a/app/client/src/reducers/entityReducers/jsActionsReducer.tsx b/app/client/src/reducers/entityReducers/jsActionsReducer.tsx index 8c863dcfa1..acb48e2d39 100644 --- a/app/client/src/reducers/entityReducers/jsActionsReducer.tsx +++ b/app/client/src/reducers/entityReducers/jsActionsReducer.tsx @@ -26,6 +26,15 @@ export interface PartialActionData { isExecuting: Record; } +interface JSExecutionData { + data: unknown; + collectionId: string; + actionId: string; +} + +// Object of collectionIds to JSExecutionData[] +export type BatchedJSExecutionData = Record; + const jsActionsReducer = createReducer(initialState, { [ReduxActionTypes.FETCH_JS_ACTIONS_SUCCESS]: ( state: JSCollectionDataState, @@ -292,7 +301,6 @@ const jsActionsReducer = createReducer(initialState, { [ReduxActionTypes.EXECUTE_JS_FUNCTION_SUCCESS]: ( state: JSCollectionDataState, action: ReduxAction<{ - results: any; collectionId: string; actionId: string; isDirty: boolean; @@ -302,10 +310,6 @@ const jsActionsReducer = createReducer(initialState, { if (a.config.id === action.payload.collectionId) { return { ...a, - data: { - ...a.data, - [action.payload.actionId]: action.payload.results, - }, isExecuting: { ...a.isExecuting, [action.payload.actionId]: false, @@ -318,6 +322,26 @@ const jsActionsReducer = createReducer(initialState, { } return a; }), + [ReduxActionTypes.SET_JS_FUNCTION_EXECUTION_DATA]: ( + state: JSCollectionDataState, + action: ReduxAction, + ): JSCollectionDataState => + state.map((jsCollectionData) => { + const collectionId = jsCollectionData.config.id; + if (collectionId in action.payload) { + let data = { + ...jsCollectionData.data, + }; + action.payload[collectionId].forEach((item) => { + data = { ...data, [item.actionId]: item.data }; + }); + return { + ...jsCollectionData, + data, + }; + } + return jsCollectionData; + }), [ReduxActionTypes.UPDATE_JS_FUNCTION_PROPERTY_SUCCESS]: ( state: JSCollectionDataState, action: ReduxAction<{ collection: JSCollection }>, diff --git a/app/client/src/reducers/uiReducers/datasourcePaneReducer.ts b/app/client/src/reducers/uiReducers/datasourcePaneReducer.ts index 1bf8f7ad6f..6d73ffc7f6 100644 --- a/app/client/src/reducers/uiReducers/datasourcePaneReducer.ts +++ b/app/client/src/reducers/uiReducers/datasourcePaneReducer.ts @@ -13,6 +13,7 @@ const initialState: DatasourcePaneReduxState = { newDatasource: "", viewMode: true, collapsibleState: {}, + defaultKeyValueArrayConfig: [], }; export interface DatasourcePaneReduxState { @@ -27,6 +28,7 @@ export interface DatasourcePaneReduxState { newDatasource: string; viewMode: boolean; collapsibleState: Record; + defaultKeyValueArrayConfig: Array; } const datasourcePaneReducer = createReducer(initialState, { @@ -137,6 +139,25 @@ const datasourcePaneReducer = createReducer(initialState, { expandDatasourceId: action.payload, }; }, + [ReduxActionTypes.SET_DATASOURCE_DEFAULT_KEY_VALUE_PAIR_SET]: ( + state: DatasourcePaneReduxState, + action: ReduxAction, + ) => { + return { + ...state, + defaultKeyValueArrayConfig: state.defaultKeyValueArrayConfig.concat( + action.payload, + ), + }; + }, + [ReduxActionTypes.RESET_DATASOURCE_DEFAULT_KEY_VALUE_PAIR_SET]: ( + state: DatasourcePaneReduxState, + ) => { + return { + ...state, + defaultKeyValueArrayConfig: [], + }; + }, }); export default datasourcePaneReducer; diff --git a/app/client/src/sagas/DatasourcesSagas.ts b/app/client/src/sagas/DatasourcesSagas.ts index a2bb2bde99..26230cbc95 100644 --- a/app/client/src/sagas/DatasourcesSagas.ts +++ b/app/client/src/sagas/DatasourcesSagas.ts @@ -536,7 +536,9 @@ function* testDatasourceSaga(actionPayload: ReduxAction) { id: actionPayload.payload.id as any, }; - if (!equal(initialValues, values)) { + // when datasource is not yet saved by user, datasource id is temporary + // for temporary datasource, we do not need to pass datasource id in test api call + if (!equal(initialValues, values) || datasource?.id === TEMP_DATASOURCE_ID) { delete payload.id; } diff --git a/app/client/src/sagas/EvaluationsSaga.ts b/app/client/src/sagas/EvaluationsSaga.ts index 34da0cda6d..458d957044 100644 --- a/app/client/src/sagas/EvaluationsSaga.ts +++ b/app/client/src/sagas/EvaluationsSaga.ts @@ -111,6 +111,8 @@ import { EvalTreeRequestData, EvalTreeResponseData, } from "workers/Evaluation/types"; +import { BatchedJSExecutionData } from "reducers/entityReducers/jsActionsReducer"; +import { sortJSExecutionDataByCollectionId } from "workers/Evaluation/JSObject/utils"; import { MessageType, TMessage } from "utils/MessageUtil"; const evalWorker = new GracefulWorkerService( @@ -365,6 +367,16 @@ export function* handleEvalWorkerMessage(message: TMessage) { ); break; } + case MAIN_THREAD_ACTION.PROCESS_JS_FUNCTION_EXECUTION: { + const sortedData: BatchedJSExecutionData = yield sortJSExecutionDataByCollectionId( + data.JSData as Record, + ); + yield put({ + type: ReduxActionTypes.SET_JS_FUNCTION_EXECUTION_DATA, + payload: sortedData, + }); + break; + } case MAIN_THREAD_ACTION.PROCESS_TRIGGER: { const { eventType, trigger, triggerMeta } = data; log.debug({ trigger: data.trigger }); diff --git a/app/client/src/sagas/JSPaneSagas.ts b/app/client/src/sagas/JSPaneSagas.ts index 2018fb657b..127b5137d8 100644 --- a/app/client/src/sagas/JSPaneSagas.ts +++ b/app/client/src/sagas/JSPaneSagas.ts @@ -379,7 +379,6 @@ export function* handleExecuteJSFunctionSaga(data: { yield put({ type: ReduxActionTypes.EXECUTE_JS_FUNCTION_SUCCESS, payload: { - results: result, collectionId, actionId, isDirty, @@ -394,6 +393,20 @@ export function* handleExecuteJSFunctionSaga(data: { }, state: { response: result }, }); + if (!action.actionConfiguration.isAsync) { + yield put({ + type: ReduxActionTypes.SET_JS_FUNCTION_EXECUTION_DATA, + payload: { + [collectionId]: [ + { + data: result, + collectionId, + actionId, + }, + ], + }, + }); + } appMode === APP_MODE.EDIT && !isDirty && Toaster.show({ diff --git a/app/client/src/sagas/PostEvaluationSagas.ts b/app/client/src/sagas/PostEvaluationSagas.ts index a9e7081669..88aace254c 100644 --- a/app/client/src/sagas/PostEvaluationSagas.ts +++ b/app/client/src/sagas/PostEvaluationSagas.ts @@ -98,7 +98,7 @@ function logLatestEvalPropertyErrors( } const idField = isWidget(entity) ? entity.widgetId : entity.actionId; - const nameField = isWidget(entity) ? entity.widgetName : entity.name; + const nameField = isWidget(entity) ? entity.widgetName : entityName; const entityType = isWidget(entity) ? ENTITY_TYPE.WIDGET : isAction(entity) diff --git a/app/client/src/sagas/WidgetOperationSagas.tsx b/app/client/src/sagas/WidgetOperationSagas.tsx index 442e9dcb67..a951acebb9 100644 --- a/app/client/src/sagas/WidgetOperationSagas.tsx +++ b/app/client/src/sagas/WidgetOperationSagas.tsx @@ -139,7 +139,7 @@ import { WidgetSpace } from "constants/CanvasEditorConstants"; import { reflow } from "reflow"; import { getBottomMostRow } from "reflow/reflowUtils"; import { flashElementsById } from "utils/helpers"; -import { getSlidingCanvasName } from "constants/componentClassNameConstants"; +import { getSlidingArenaName } from "constants/componentClassNameConstants"; import { builderURL } from "RouteBuilder"; import history from "utils/history"; import { updateMultipleWidgetProperties } from "actions/widgetActions"; @@ -1106,9 +1106,7 @@ function* getNewPositionsBasedOnSelectedWidgets( const containerId = getContainerIdForCanvas(parentId); const containerWidget = canvasWidgets[containerId]; - const canvasDOM = document.querySelector( - `#${getSlidingCanvasName(parentId)}`, - ); + const canvasDOM = document.querySelector(`#${getSlidingArenaName(parentId)}`); if (!canvasDOM || !containerWidget) return {}; diff --git a/app/client/src/sagas/WidgetOperationUtils.ts b/app/client/src/sagas/WidgetOperationUtils.ts index 736aeb4b53..023892c756 100644 --- a/app/client/src/sagas/WidgetOperationUtils.ts +++ b/app/client/src/sagas/WidgetOperationUtils.ts @@ -44,8 +44,8 @@ import { } from "reflow/reflowTypes"; import { getBaseWidgetClassName, - getSlidingCanvasName, getStickyCanvasName, + getSlidingArenaName, POSITIONED_WIDGET, } from "constants/componentClassNameConstants"; import { getContainerWidgetSpacesSelector } from "selectors/editorSelectors"; @@ -705,7 +705,7 @@ export function getMousePositions( //get DOM of the overall canvas including it's total scroll height const stickyCanvasDOM = document.querySelector( - `#${getStickyCanvasName(canvasId)}`, + `#${getSlidingArenaName(canvasId)}`, ); if (!stickyCanvasDOM) return; @@ -780,7 +780,7 @@ export function getDefaultCanvas(canvasWidgets: CanvasWidgetsReduxState) { canvasId: MAIN_CONTAINER_WIDGET_ID, containerWidget: canvasWidgets[MAIN_CONTAINER_WIDGET_ID], canvasDOM: document.querySelector( - `#${getSlidingCanvasName(MAIN_CONTAINER_WIDGET_ID)}`, + `#${getSlidingArenaName(MAIN_CONTAINER_WIDGET_ID)}`, ), }; } @@ -795,7 +795,7 @@ export function getDefaultCanvas(canvasWidgets: CanvasWidgetsReduxState) { export function getContainerIdForCanvas(canvasId: string) { if (canvasId === MAIN_CONTAINER_WIDGET_ID) return canvasId; - const selector = `#${getSlidingCanvasName(canvasId)}`; + const selector = `#${getStickyCanvasName(canvasId)}`; const canvasDOM = document.querySelector(selector); if (!canvasDOM) return ""; //check for positionedWidget parent @@ -821,11 +821,12 @@ export function getCanvasIdForContainer(layoutWidget: WidgetProps) { )}`; const containerDOM = document.querySelector(selector); if (!containerDOM) return {}; + const dropTargetDOM = containerDOM.querySelector(".t--drop-target"); const canvasDOM = containerDOM.getElementsByTagName("canvas"); return { - canvasId: canvasDOM ? canvasDOM[0]?.id.split("-")[2] : undefined, - canvasDOM: canvasDOM[0], + canvasId: canvasDOM ? canvasDOM[0].id.split("-")[2] : undefined, + canvasDOM: dropTargetDOM, }; } @@ -1647,7 +1648,7 @@ export function getParentColumnSpace( const containerWidget = canvasWidgets[containerId]; const canvasDOM = document.querySelector( - `#${getSlidingCanvasName(pastingIntoWidgetId)}`, + `#${getStickyCanvasName(pastingIntoWidgetId)}`, ); if (!canvasDOM || !containerWidget) return; diff --git a/app/client/src/selectors/entitiesSelector.ts b/app/client/src/selectors/entitiesSelector.ts index 11ff753f38..e5be36dd8b 100644 --- a/app/client/src/selectors/entitiesSelector.ts +++ b/app/client/src/selectors/entitiesSelector.ts @@ -29,9 +29,11 @@ import { EVAL_ERROR_PATH, PropertyEvaluationErrorType, } from "utils/DynamicBindingUtils"; + import { InstallState } from "reducers/uiReducers/libraryReducer"; import recommendedLibraries from "pages/Editor/Explorer/Libraries/recommendedLibraries"; import { TJSLibrary } from "workers/common/JSLibrary"; +import { getEntityNameAndPropertyPath } from "@appsmith/workers/Evaluation/evaluationUtils"; export const getEntities = (state: AppState): AppState["entities"] => state.entities; @@ -415,6 +417,21 @@ export const getJSCollection = ( return jsaction ? jsaction.config : undefined; }; +export const getJSFunctionFromName = (state: AppState, name: string) => { + const { + entityName: collectionName, + propertyPath: functionName, + } = getEntityNameAndPropertyPath(name); + const jsCollection = find( + state.entities.jsActions, + (a) => a.config.name === collectionName, + ); + if (jsCollection) { + return find(jsCollection.config.actions, (a) => a.name === functionName); + } + return undefined; +}; + export function getCurrentPageNameByActionId( state: AppState, actionId: string, diff --git a/app/client/src/transformers/RestAPIDatasourceFormTransformer.ts b/app/client/src/transformers/RestAPIDatasourceFormTransformer.ts index b7987a5ec0..61881382d6 100644 --- a/app/client/src/transformers/RestAPIDatasourceFormTransformer.ts +++ b/app/client/src/transformers/RestAPIDatasourceFormTransformer.ts @@ -189,7 +189,8 @@ const datasourceToFormAuthentication = ( isAuthorizationHeader: !!authentication.isAuthorizationHeader, audience: authentication.audience || "", resource: authentication.resource || "", - sendScopeWithRefreshToken: authentication.sendScopeWithRefreshToken || "", + sendScopeWithRefreshToken: + authentication.sendScopeWithRefreshToken || false, refreshTokenClientCredentialsLocation: authentication.refreshTokenClientCredentialsLocation || "BODY", }; diff --git a/app/client/src/utils/AnalyticsUtil.tsx b/app/client/src/utils/AnalyticsUtil.tsx index 5ae6f0fde2..d125e3ede9 100644 --- a/app/client/src/utils/AnalyticsUtil.tsx +++ b/app/client/src/utils/AnalyticsUtil.tsx @@ -94,6 +94,7 @@ export type EventName = | "CONSOLE_LOG_CREATED" | "TEST_DATA_SOURCE_SUCCESS" | "TEST_DATA_SOURCE_CLICK" + | "UPDATE_DATASOURCE" | "CREATE_QUERY_CLICK" | "NAVIGATE" | "PAGE_LOAD" diff --git a/app/client/src/utils/DSLMigration.test.ts b/app/client/src/utils/DSLMigration.test.ts index a5d93d4a99..9a72ec66b8 100644 --- a/app/client/src/utils/DSLMigration.test.ts +++ b/app/client/src/utils/DSLMigration.test.ts @@ -718,6 +718,15 @@ const migrations: Migration[] = [ ], version: 73, }, + { + functionLookup: [ + { + moduleObj: tableMigrations, + functionName: "migrateMenuButtonDynamicItemsInsideTableWidget", + }, + ], + version: 74, + }, ]; const mockFnObj: Record = {}; diff --git a/app/client/src/utils/DSLMigrations.ts b/app/client/src/utils/DSLMigrations.ts index 290c1037e7..d80d541896 100644 --- a/app/client/src/utils/DSLMigrations.ts +++ b/app/client/src/utils/DSLMigrations.ts @@ -23,6 +23,7 @@ import { migrateTableWidgetIconButtonVariant, migrateTableWidgetV2Validation, migrateTableWidgetV2ValidationBinding, + migrateMenuButtonDynamicItemsInsideTableWidget, migrateTableWidgetV2SelectOption, } from "./migrations/TableWidget"; import { @@ -1155,6 +1156,11 @@ export const transformDSL = (currentDSL: DSLWidget, newPage = false) => { if (currentDSL.version === 73) { currentDSL = migrateInputWidgetShowStepArrows(currentDSL); + currentDSL.version = 74; + } + + if (currentDSL.version === 74) { + currentDSL = migrateMenuButtonDynamicItemsInsideTableWidget(currentDSL); currentDSL.version = LATEST_PAGE_VERSION; } diff --git a/app/client/src/utils/hooks/useWidgetFocus/handleTab.ts b/app/client/src/utils/hooks/useWidgetFocus/handleTab.ts new file mode 100644 index 0000000000..c09cc4cb7b --- /dev/null +++ b/app/client/src/utils/hooks/useWidgetFocus/handleTab.ts @@ -0,0 +1,49 @@ +import { + getTabbableDescendants, + getNextTabbableDescendant, + getFocussableElementOfWidget, + JSONFORM_WIDGET, + WIDGET_SELECTOR, + getNextTabbableDescendantForRegularWidgets, + CHECKBOXGROUP_WIDGET, + SWITCHGROUP_WIDGET, + BUTTONGROUP_WIDGET, +} from "./tabbable"; + +export function handleTab(event: KeyboardEvent) { + let nextTabbableDescendant; + const shiftKey = event.shiftKey; + const currentNode = event.target as HTMLElement; + const currentWidget = currentNode.closest(WIDGET_SELECTOR) as HTMLElement; + + switch (true) { + // when the current node is a widget, we want to do tabbing in regular way + // the elements will be in proper order in dom for thes widgets + case currentWidget && currentWidget.matches(JSONFORM_WIDGET): + case currentWidget && currentWidget.matches(CHECKBOXGROUP_WIDGET): + case currentWidget && currentWidget.matches(SWITCHGROUP_WIDGET): + case currentWidget && currentWidget.matches(BUTTONGROUP_WIDGET): + nextTabbableDescendant = getNextTabbableDescendantForRegularWidgets( + currentWidget, + shiftKey, + ); + break; + default: + const tabbable = getTabbableDescendants(currentNode, shiftKey); + + nextTabbableDescendant = getNextTabbableDescendant(tabbable, shiftKey); + } + + // if nextTabbableDescendant is found, focus + if (nextTabbableDescendant) { + event.preventDefault(); + + const focusableElement = getFocussableElementOfWidget( + nextTabbableDescendant, + ); + + if (focusableElement) { + focusableElement.focus(); + } + } +} diff --git a/app/client/src/utils/hooks/useWidgetFocus/index.tsx b/app/client/src/utils/hooks/useWidgetFocus/index.tsx new file mode 100644 index 0000000000..dbcdf74a62 --- /dev/null +++ b/app/client/src/utils/hooks/useWidgetFocus/index.tsx @@ -0,0 +1,3 @@ +import useWidgetFocus from "./useWidgetFocus"; + +export default useWidgetFocus; diff --git a/app/client/src/utils/hooks/useWidgetFocus/tabbable.ts b/app/client/src/utils/hooks/useWidgetFocus/tabbable.ts new file mode 100644 index 0000000000..05860cae3c --- /dev/null +++ b/app/client/src/utils/hooks/useWidgetFocus/tabbable.ts @@ -0,0 +1,316 @@ +export const CANVAS_WIDGET = '[type="CANVAS_WIDGET"]'; +// NOTE: This is a hack to exclude the current canvas from the query selector +// because when we use.closest, it returns the current element too +export const CANVAS_WIDGET_EXCLUDING_SCOPE = + '[type="CANVAS_WIDGET"]:not(:scope)'; +export const CONTAINER_SELECTOR = + ":is(.t--widget-containerwidget, .t--widget-formwidget)"; +const NON_FOCUSABLE_WIDGET_CLASS = + ".t--widget-textwidget, .t--widget-ratewidget, [disabled], [data-hidden]"; +export const JSONFORM_WIDGET = ".t--widget-jsonformwidget"; +export const MODAL_WIDGET = ".t--modal-widget"; +export const CHECKBOXGROUP_WIDGET = ".t--widget-checkboxgroupwidget"; +export const SWITCHGROUP_WIDGET = ".t--widget-switchgroupwidget"; +export const BUTTONGROUP_WIDGET = ".t--widget-buttongroupwidget"; +export const FOCUS_SELECTOR = + ":is(a, input, select, textarea, button, object, audio, video, [tabindex='-1']):not([data-tabbable='false'])"; +export const WIDGET_SELECTOR = `.positioned-widget:is(:not(${NON_FOCUSABLE_WIDGET_CLASS}))`; + +/** + * returns the tabbable descendants of the current node + * + * @param currentNode + * @param shiftKey + * @returns + */ +export function getTabbableDescendants( + currentNode: HTMLElement, + shiftKey = false, +): HTMLElement[] { + const activeWidget = currentNode.closest(WIDGET_SELECTOR) as HTMLElement; + + if (!activeWidget) { + const modal = currentNode.closest(MODAL_WIDGET) as HTMLElement; + + // if we are in modal, we have to trap the focus within the modal + if (modal) { + const tabbableDescendants = Array.from( + modal.querySelectorAll(WIDGET_SELECTOR), + ) as HTMLElement[]; + + const domRect = modal.getBoundingClientRect(); + + const sortedTabbableDescendants = sortWidgetsByPosition( + { + top: shiftKey ? domRect.bottom : domRect.top, + left: shiftKey ? domRect.right : domRect.left, + }, + tabbableDescendants, + shiftKey, + ); + + return sortedTabbableDescendants; + } + + // this case means the focus on the main container canvas + if (currentNode.matches(CANVAS_WIDGET)) { + const tabbableDescendants = Array.from( + currentNode.querySelectorAll(WIDGET_SELECTOR), + ) as HTMLElement[]; + + const domRect = currentNode.getBoundingClientRect(); + + const sortedTabbableDescendants = sortWidgetsByPosition( + { + top: shiftKey ? domRect.bottom : domRect.top, + left: shiftKey ? domRect.right : domRect.left, + }, + tabbableDescendants, + shiftKey, + ); + + return sortedTabbableDescendants; + } + } + + const siblings = getWidgetSiblingsOfNode(activeWidget); + const domRect = activeWidget.getBoundingClientRect(); + + const sortedSiblings = sortWidgetsByPosition( + { + top: domRect.top, + left: domRect.left, + }, + siblings, + shiftKey, + ); + + if (sortedSiblings.length) return sortedSiblings; + + // there are no siblings, which means we are at the end of the tabbable list + // we have to go to next sibling widget of current canvas + const currentCanvas = currentNode.closest( + CANVAS_WIDGET_EXCLUDING_SCOPE, + ) as HTMLElement; + + if (currentCanvas) { + return getTabbableDescendants(currentCanvas, shiftKey); + } + + return []; +} + +/** + * returns the next tabbable descendant from the list of descendants + * sorted by position and distance + * if the next tabbable descendant is JSONFORM, it returns the first tabbable + * + * @param descendants + * @param shiftKey + * @returns + */ +export function getNextTabbableDescendant( + descendants: HTMLElement[], + shiftKey = false, +) { + const nextTabbableDescendant = descendants[0]; + + // if nextTabbableDescendant is a container, + if (nextTabbableDescendant.matches(CONTAINER_SELECTOR)) { + const tabbableDescendants = getChildrenWidgetsOfNode( + nextTabbableDescendant, + ); + + const { + bottom, + left, + right, + top, + } = nextTabbableDescendant.getBoundingClientRect(); + + const sortedTabbableDescendants = sortWidgetsByPosition( + { + top: shiftKey ? bottom : top, + left: shiftKey ? right : left, + }, + tabbableDescendants, + shiftKey, + ); + + return sortedTabbableDescendants[0]; + } + + // if nextTabbableDescendant is a jsonform widget + if ( + nextTabbableDescendant.matches(JSONFORM_WIDGET) || + nextTabbableDescendant.matches(CHECKBOXGROUP_WIDGET) || + nextTabbableDescendant.matches(SWITCHGROUP_WIDGET) || + nextTabbableDescendant.matches(BUTTONGROUP_WIDGET) + ) { + const tabbable = Array.from( + nextTabbableDescendant.querySelectorAll(FOCUS_SELECTOR), + ); + + return shiftKey ? tabbable[tabbable.length - 1] : tabbable[0]; + } + + return nextTabbableDescendant; +} + +/** + * returns a focussable element within a widget + * + * @param node + * @returns + */ +export function getFocussableElementOfWidget(node: HTMLElement) { + if (node.matches(FOCUS_SELECTOR)) { + return node; + } + + return node.querySelector(FOCUS_SELECTOR) as HTMLElement; +} +/** + * get widgets of a given node + * + * @param node + * @returns + */ +export function getChildrenWidgetsOfNode(node: HTMLElement) { + const widgets = Array.from( + node.querySelectorAll(WIDGET_SELECTOR), + ) as HTMLElement[]; + + return widgets; +} + +/** + * get the siblings of the current node's widget + * + * @param node + * @returns + */ +function getWidgetSiblingsOfNode(node: HTMLElement) { + const canvas = node.closest(CANVAS_WIDGET_EXCLUDING_SCOPE) as HTMLElement; + + if (!canvas) return []; + + const widget = node.closest(WIDGET_SELECTOR) as HTMLElement; + const siblings = Array.from( + canvas.querySelectorAll(`:scope > ${WIDGET_SELECTOR}`), + ) as HTMLElement[]; + + return siblings.filter((sibling) => sibling !== widget); +} + +/** + * sorts the descendants by their position in the DOM + * + * @param currentElement + * @param tabbableDescendants + * @param shiftKey + * @returns + */ +export function sortWidgetsByPosition( + boundingClientRect: { + top: number; + left: number; + }, + tabbableDescendants: HTMLElement[], + shiftKey = false, +) { + const { left, top } = boundingClientRect; + const isTabbingForward = !shiftKey; + const isTabbingBackward = shiftKey; + + let tabbableElementsByPosition = Array.from(tabbableDescendants).map( + (element) => { + const { + left: elementLeft, + top: elementTop, + } = element.getBoundingClientRect(); + const topDiff = elementTop - top; + const leftDiff = elementLeft - left; + + return { + element, + topDiff, + leftDiff, + top, + left, + elementTop, + elementLeft, + }; + }, + ); + + tabbableElementsByPosition = tabbableElementsByPosition.filter((element) => { + // if tabbing forward, only consider elements below and to the right + if (isTabbingForward) { + if (element.topDiff === 0) { + return element.leftDiff > 0; + } + + return element.topDiff > 0; + } + + // if tabbing backward, only consider elements above and to the left + if (isTabbingBackward) { + if (element.topDiff === 0) { + return element.leftDiff < 0; + } + + return element.topDiff < 0; + } + }); + + tabbableElementsByPosition = tabbableElementsByPosition.sort((a, b) => { + if (isTabbingForward) { + return a.topDiff - b.topDiff || a.leftDiff - b.leftDiff; + } + + if (isTabbingBackward) { + return b.topDiff - a.topDiff || b.leftDiff - a.leftDiff; + } + + return 0; + }); + + return tabbableElementsByPosition.map((element) => element.element); +} + +/** + * get next item to focus if the current widget has relative positioned children + * + * Note: + * if the user is tabbing out, we need to get the next tabbable descendant of the current widget + * else tabbing will work as expected as widgets inside the widget are regular components + * and will be handled by the default tabbing logic + * + * + * @param currentWidget + * @param shiftKey + * @returns + */ +export function getNextTabbableDescendantForRegularWidgets( + currentWidget: HTMLElement, + shiftKey: boolean, +) { + let nextTabbableDescendant; + + const tabbable = Array.from( + currentWidget.querySelectorAll(FOCUS_SELECTOR), + ); + + const currentIndex = tabbable.indexOf(document.activeElement as HTMLElement); + const isTabbingOutOfJSONForm = shiftKey + ? currentIndex === 0 + : currentIndex === tabbable.length - 1; + + if (isTabbingOutOfJSONForm) { + const descendents = getTabbableDescendants(currentWidget, shiftKey); + nextTabbableDescendant = getNextTabbableDescendant(descendents, shiftKey); + } + + return nextTabbableDescendant; +} diff --git a/app/client/src/utils/hooks/useWidgetFocus/useWidgetFocus.tsx b/app/client/src/utils/hooks/useWidgetFocus/useWidgetFocus.tsx new file mode 100644 index 0000000000..b92ead9369 --- /dev/null +++ b/app/client/src/utils/hooks/useWidgetFocus/useWidgetFocus.tsx @@ -0,0 +1,46 @@ +import { useCallback, useEffect, useRef } from "react"; + +import { handleTab } from "./handleTab"; +import { CANVAS_WIDGET } from "./tabbable"; + +function useWidgetFocus(): (instance: HTMLElement | null) => void { + const ref = useRef(); + + // This is a callback that will be called when the ref is set + const setRef = useCallback((node: HTMLElement | null) => { + if (node === null) return; + + if (ref.current === node) return; + + ref.current = node; + + return ref; + }, []); + + useEffect(() => { + if (!ref.current) return; + + const handleKeyDown = (event: KeyboardEvent) => { + if (event.key === "Tab") handleTab(event); + }; + + const handleClick = (event: any) => { + const target = event.target as HTMLElement; + if (target.matches(CANVAS_WIDGET)) { + target.focus(); + } + }; + + ref.current.addEventListener("keydown", handleKeyDown); + ref.current.addEventListener("click", handleClick); + + return () => { + ref?.current && ref.current.removeEventListener("keydown", handleKeyDown); + ref?.current && ref.current.removeEventListener("click", handleClick); + }; + }, []); + + return setRef; +} + +export default useWidgetFocus; diff --git a/app/client/src/utils/migrations/TableWidget.ts b/app/client/src/utils/migrations/TableWidget.ts index 73a2363177..1f8b0faa68 100644 --- a/app/client/src/utils/migrations/TableWidget.ts +++ b/app/client/src/utils/migrations/TableWidget.ts @@ -670,3 +670,25 @@ export const migrateTableWidgetV2SelectOption = (currentDSL: DSLWidget) => { } }); }; + +export const migrateMenuButtonDynamicItemsInsideTableWidget = ( + currentDSL: DSLWidget, +) => { + return traverseDSLAndMigrate(currentDSL, (widget: WidgetProps) => { + if (widget.type === "TABLE_WIDGET_V2") { + const primaryColumns = widget.primaryColumns; + + if (primaryColumns) { + for (const column in primaryColumns) { + if ( + primaryColumns.hasOwnProperty(column) && + primaryColumns[column].columnType === "menuButton" && + !primaryColumns[column].menuItemsSource + ) { + primaryColumns[column].menuItemsSource = "STATIC"; + } + } + } + } + }); +}; diff --git a/app/client/src/widgets/BaseInputWidget/component/index.tsx b/app/client/src/widgets/BaseInputWidget/component/index.tsx index cc77741c9e..74c3737179 100644 --- a/app/client/src/widgets/BaseInputWidget/component/index.tsx +++ b/app/client/src/widgets/BaseInputWidget/component/index.tsx @@ -317,7 +317,8 @@ const StyledNumericInput = styled(NumericInput)` min-width: 24px; width: 24px; border-radius: 0; - &:hover { + &:hover, + &:focus { background: ${Colors.GREY_2}; span { color: ${Colors.GREY_10}; diff --git a/app/client/src/widgets/BaseInputWidget/widget/index.tsx b/app/client/src/widgets/BaseInputWidget/widget/index.tsx index 47e65fe7ff..df0f229fcb 100644 --- a/app/client/src/widgets/BaseInputWidget/widget/index.tsx +++ b/app/client/src/widgets/BaseInputWidget/widget/index.tsx @@ -376,7 +376,7 @@ class BaseInputWidget< propertyName: "labelStyle", label: "Emphasis", helpText: "Control if the label should be bold or italics", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/BaseWidget.tsx b/app/client/src/widgets/BaseWidget.tsx index a876c986da..45ac233f0b 100644 --- a/app/client/src/widgets/BaseWidget.tsx +++ b/app/client/src/widgets/BaseWidget.tsx @@ -343,6 +343,8 @@ abstract class BaseWidget< componentHeight={componentHeight} componentWidth={componentWidth} focused={this.props.focused} + isDisabled={this.props.isDisabled} + isVisible={this.props.isVisible} leftColumn={this.props.leftColumn} noContainerOffset={this.props.noContainerOffset} parentColumnSpace={this.props.parentColumnSpace} diff --git a/app/client/src/widgets/ButtonGroupWidget/component/index.tsx b/app/client/src/widgets/ButtonGroupWidget/component/index.tsx index c8f4957316..10d6f779d4 100644 --- a/app/client/src/widgets/ButtonGroupWidget/component/index.tsx +++ b/app/client/src/widgets/ButtonGroupWidget/component/index.tsx @@ -182,7 +182,8 @@ const StyledButton = styled.button` padding: 0px 10px; &:hover, - &:active { + &:active, + &:focus { ${buttonHoverActiveStyles} } @@ -268,7 +269,7 @@ const BaseMenuItem = styled(MenuItem)` backgroundColor ? ` background-color: ${backgroundColor} !important; - &:hover { + &:hover, &:focus { background-color: ${darkenHover(backgroundColor)} !important; } &:active { @@ -277,7 +278,7 @@ const BaseMenuItem = styled(MenuItem)` ` : ` background: none !important - &:hover { + &:hover, &:focus { background-color: ${tinycolor( theme.colors.button.primary.primary.textColor, ) diff --git a/app/client/src/widgets/ButtonWidget/component/index.tsx b/app/client/src/widgets/ButtonWidget/component/index.tsx index d186cc302a..0807621499 100644 --- a/app/client/src/widgets/ButtonWidget/component/index.tsx +++ b/app/client/src/widgets/ButtonWidget/component/index.tsx @@ -99,7 +99,7 @@ outline: none; padding: 0px 10px; gap: 8px; -&:hover, &:active { +&:hover, &:active, &:focus { ${buttonHoverActiveStyles} } diff --git a/app/client/src/widgets/CategorySliderWidget/widget/propertyConfig/styleConfig.ts b/app/client/src/widgets/CategorySliderWidget/widget/propertyConfig/styleConfig.ts index a03f244241..634586e40b 100644 --- a/app/client/src/widgets/CategorySliderWidget/widget/propertyConfig/styleConfig.ts +++ b/app/client/src/widgets/CategorySliderWidget/widget/propertyConfig/styleConfig.ts @@ -94,7 +94,7 @@ export default [ propertyName: "labelStyle", label: "Emphasis", helpText: "Control if the label should be bold or italics", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/CheckboxGroupWidget/component/index.tsx b/app/client/src/widgets/CheckboxGroupWidget/component/index.tsx index 43979bdf54..b6765bc319 100644 --- a/app/client/src/widgets/CheckboxGroupWidget/component/index.tsx +++ b/app/client/src/widgets/CheckboxGroupWidget/component/index.tsx @@ -4,7 +4,6 @@ import { Alignment } from "@blueprintjs/core"; import { Classes } from "@blueprintjs/core"; import { ComponentProps } from "widgets/BaseComponent"; -import { generateReactKey } from "utils/generators"; import { Colors } from "constants/Colors"; import { LabelPosition } from "components/constants"; import { TextSize } from "constants/WidgetConstants"; @@ -48,7 +47,6 @@ const InputContainer = styled.div` width: 100%; flex-grow: 1; height: 100%; - border: 1px solid transparent; .${Classes.CONTROL} { @@ -176,6 +174,7 @@ function CheckboxGroupComponent(props: CheckboxGroupComponentProps) { optionAlignment, options, selectedValues, + widgetId, } = props; const selectAllChecked = selectedValues.length === options.length; @@ -236,7 +235,7 @@ function CheckboxGroupComponent(props: CheckboxGroupComponentProps) { )} {options && options.length > 0 && - [...options].map((option: OptionProps) => ( + [...options].map((option: OptionProps, index: number) => ( { propertyName: "labelStyle", label: "Emphasis", helpText: "Control if the label should be bold or italics", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/ContainerWidget/component/index.tsx b/app/client/src/widgets/ContainerWidget/component/index.tsx index aa087cdfb8..714d3e546a 100644 --- a/app/client/src/widgets/ContainerWidget/component/index.tsx +++ b/app/client/src/widgets/ContainerWidget/component/index.tsx @@ -71,13 +71,14 @@ function ContainerComponentWrapper(props: ContainerComponentProps) { return ( {props.children} diff --git a/app/client/src/widgets/CurrencyInputWidget/component/CurrencyCodeDropdown.tsx b/app/client/src/widgets/CurrencyInputWidget/component/CurrencyCodeDropdown.tsx index 4f971005a4..c236983f60 100644 --- a/app/client/src/widgets/CurrencyInputWidget/component/CurrencyCodeDropdown.tsx +++ b/app/client/src/widgets/CurrencyInputWidget/component/CurrencyCodeDropdown.tsx @@ -225,6 +225,7 @@ export default function CurrencyTypeDropdown(props: CurrencyDropdownProps) { const dropdownTrigger = ( { propertyName: "labelStyle", label: "Emphasis", helpText: "Control if the label should be bold or italics", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/DropdownWidget/widget/index.tsx b/app/client/src/widgets/DropdownWidget/widget/index.tsx index a1f38d62a3..347806da4a 100644 --- a/app/client/src/widgets/DropdownWidget/widget/index.tsx +++ b/app/client/src/widgets/DropdownWidget/widget/index.tsx @@ -297,7 +297,7 @@ class DropdownWidget extends BaseWidget { { propertyName: "labelStyle", label: "Label Font Style", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/IconButtonWidget/component/index.tsx b/app/client/src/widgets/IconButtonWidget/component/index.tsx index e992019f0a..98ba1e7857 100644 --- a/app/client/src/widgets/IconButtonWidget/component/index.tsx +++ b/app/client/src/widgets/IconButtonWidget/component/index.tsx @@ -151,7 +151,7 @@ export const StyledButton = styled((props) => ( ${ hasOnClickAction - ? `&:hover:enabled, &:active:enabled { + ? `&:hover:enabled, &:active:enabled, &:focus:enabled { background: ${ getCustomHoverColor(theme, buttonVariant, buttonColor) !== "none" ? getCustomHoverColor(theme, buttonVariant, buttonColor) diff --git a/app/client/src/widgets/InputWidget/widget/index.tsx b/app/client/src/widgets/InputWidget/widget/index.tsx index cdad567b9c..620b8a0ebb 100644 --- a/app/client/src/widgets/InputWidget/widget/index.tsx +++ b/app/client/src/widgets/InputWidget/widget/index.tsx @@ -541,7 +541,7 @@ class InputWidget extends BaseWidget { { propertyName: "labelStyle", label: "Label Font Style", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/JSONFormWidget/helper.test.ts b/app/client/src/widgets/JSONFormWidget/helper.test.ts index 163bed3abf..b1e3368ed6 100644 --- a/app/client/src/widgets/JSONFormWidget/helper.test.ts +++ b/app/client/src/widgets/JSONFormWidget/helper.test.ts @@ -597,6 +597,24 @@ describe(".convertSchemaItemToFormData", () => { originalIdentifier: "customField2", isVisible: false, }, + hiddenNonASCII: { + isVisible: false, + children: {}, + dataType: DataType.STRING, + fieldType: FieldType.TEXT_INPUT, + accessor: "हिन्दि", + identifier: "hiddenNonASCII", + originalIdentifier: "हिन्दि", + }, + hiddenSmiley: { + isVisible: false, + children: {}, + dataType: DataType.STRING, + fieldType: FieldType.TEXT_INPUT, + accessor: "😀", + identifier: "hiddenSmiley", + originalIdentifier: "😀", + }, array: { children: { __array_item__: { @@ -824,4 +842,90 @@ describe(".convertSchemaItemToFormData", () => { expect(result).toEqual(expectedOutput); }); + + it("set hidden form field value to available source field value when useSourceData is set to true", () => { + const formData = { + customField1: "male", + customField2: "demo", + array: [{ name: "test1" }, { name: null, date: "10/12/2010" }], + hiddenArray: [{ name: "test1" }, { name: "test2" }], + visibleObject: { name: "test1", date: "10/12/2010" }, + hiddenObject: { name: "test1", date: "10/12/2010" }, + }; + + const sourceData = { + customField1: "soruceMale", + customField2: "sourceDemo", + array: [ + { name: "sourceTest1" }, + { name: "sourceTest2", date: "11/11/1111" }, + ], + hiddenArray: [{ name: "sourceTest1" }, { name: "sourceTest2" }], + hiddenObject: { name: "sourceTest1" }, + }; + + const expectedOutput = { + gender: "male", + age: "sourceDemo", + students: [ + { firstName: "test1" }, + { firstName: null, graduationDate: "11/11/1111" }, + ], + testHiddenArray: [ + { firstName: "sourceTest1" }, + { firstName: "sourceTest2" }, + ], + testVisibleObject: { firstName: "test1" }, + testHiddenObject: { firstName: "sourceTest1" }, + }; + + const result = convertSchemaItemToFormData( + schema[ROOT_SCHEMA_KEY], + formData, + { + fromId: "identifier", + toId: "accessor", + sourceData: sourceData, + useSourceData: true, + }, + ); + + expect(result).toEqual(expectedOutput); + }); + + it("return expected result for non-ASCII source value when field is hidden and useSourceData is set to true", () => { + const formData = { + customField1: "male", + array: [{ name: "test1" }, { name: null, date: "10/12/2010" }], + hiddenNonASCII: "test", + hiddenSmiley: "test 2", + visibleObject: { name: "test1", date: "10/12/2010" }, + }; + + const sourceData = { + हिन्दि: "हिन्दि", + "😀": "😀", + }; + + const expectedOutput = { + gender: "male", + students: [{ firstName: "test1" }, { firstName: null }], + हिन्दि: "हिन्दि", + "😀": "😀", + testVisibleObject: { firstName: "test1" }, + }; + + const result = convertSchemaItemToFormData( + schema[ROOT_SCHEMA_KEY], + formData, + { + fromId: "identifier", + toId: "accessor", + sourceData: sourceData, + useSourceData: true, + }, + ); + + expect(result).toEqual(expectedOutput); + }); }); diff --git a/app/client/src/widgets/JSONFormWidget/helper.ts b/app/client/src/widgets/JSONFormWidget/helper.ts index c9048086a6..becde3eebe 100644 --- a/app/client/src/widgets/JSONFormWidget/helper.ts +++ b/app/client/src/widgets/JSONFormWidget/helper.ts @@ -19,6 +19,8 @@ import { type ConvertFormDataOptions = { fromId: keyof SchemaItem | (keyof SchemaItem)[]; toId: keyof SchemaItem; + useSourceData?: boolean; + sourceData?: unknown; }; /** @@ -90,19 +92,33 @@ const convertObjectTypeToFormData = ( const formData: Record = {}; Object.values(schema).forEach((schemaItem) => { - if (schemaItem.isVisible) { - const value = valueLookup( + if (!schemaItem.isVisible && !options.useSourceData) return; + let sourceData; + if (options.sourceData) { + sourceData = valueLookup( + options.sourceData as Record, + schemaItem, + // sourceData lookup can only be done using originalIdentifier + // if sourceData lookup done using other id e.g. accessor or identify, the return + // value could be undefined. + "originalIdentifier", + ); + } + const toKey = schemaItem[options.toId]; + let value; + if (!schemaItem.isVisible) { + value = sourceData; + } else { + value = valueLookup( formValue as Record, schemaItem, options.fromId, ); - const toKey = schemaItem[options.toId]; - formData[toKey] = convertSchemaItemToFormData( - schemaItem, - value, - options, - ); } + formData[toKey] = convertSchemaItemToFormData(schemaItem, value, { + ...options, + sourceData, + }); }); return formData; @@ -121,10 +137,12 @@ const convertArrayTypeToFormData = ( const arraySchemaItem = schema[ARRAY_ITEM_KEY]; formValues.forEach((formValue, index) => { + const sourceData = (options?.sourceData as unknown[])?.[index]; + formData[index] = convertSchemaItemToFormData( arraySchemaItem, formValue, - options, + { ...options, sourceData }, ); }); diff --git a/app/client/src/widgets/JSONFormWidget/index.ts b/app/client/src/widgets/JSONFormWidget/index.ts index a6de1bb771..6cc8f6e410 100644 --- a/app/client/src/widgets/JSONFormWidget/index.ts +++ b/app/client/src/widgets/JSONFormWidget/index.ts @@ -26,6 +26,7 @@ export const CONFIG = { iconSVG: IconSVG, needsMeta: true, defaults: { + useSourceData: false, animateLoading: true, backgroundColor: "#fff", columns: 25, diff --git a/app/client/src/widgets/JSONFormWidget/widget/index.tsx b/app/client/src/widgets/JSONFormWidget/widget/index.tsx index f7611005fe..5a7ef42593 100644 --- a/app/client/src/widgets/JSONFormWidget/widget/index.tsx +++ b/app/client/src/widgets/JSONFormWidget/widget/index.tsx @@ -51,6 +51,7 @@ export interface JSONFormWidgetProps extends WidgetProps { scrollContents: boolean; showReset: boolean; sourceData?: Record; + useSourceData?: boolean; submitButtonLabel: string; submitButtonStyles: ButtonStyleProps; title: string; @@ -238,6 +239,11 @@ class JSONFormWidget extends BaseWidget< this.state.resetObserverCallback(this.props.schema); } + if (prevProps.useSourceData !== this.props.useSourceData) { + const { formData } = this.props; + this.updateFormData(formData); + } + const { schema } = this.constructAndSaveSchemaIfRequired(prevProps); this.debouncedParseAndSaveFieldState( this.state.metaInternalFieldState, @@ -342,12 +348,15 @@ class JSONFormWidget extends BaseWidget< updateFormData = (values: any, skipConversion = false) => { const rootSchemaItem = this.props.schema[ROOT_SCHEMA_KEY]; + const { sourceData, useSourceData } = this.props; let formData = values; if (!skipConversion) { formData = convertSchemaItemToFormData(rootSchemaItem, values, { fromId: "identifier", toId: "accessor", + useSourceData, + sourceData, }); } diff --git a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig.ts b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig.ts index d6e0d74fb9..537c362d2a 100644 --- a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig.ts +++ b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig.ts @@ -194,6 +194,16 @@ export const contentConfig = [ isTriggerProperty: false, validation: { type: ValidationTypes.BOOLEAN }, }, + { + propertyName: "useSourceData", + helpText: "Use source data when form has hidden fields", + label: "Use Source Data", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: { type: ValidationTypes.BOOLEAN }, + }, { propertyName: "animateLoading", label: "Animate Loading", diff --git a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/common.ts b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/common.ts index 08129fb711..8ce8effecd 100644 --- a/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/common.ts +++ b/app/client/src/widgets/JSONFormWidget/widget/propertyConfig/properties/common.ts @@ -366,7 +366,7 @@ const COMMON_PROPERTIES = { propertyName: "labelStyle", label: "Emphasis", helpText: "Control if the label should be bold or italics", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/MenuButtonWidget/component/index.tsx b/app/client/src/widgets/MenuButtonWidget/component/index.tsx index 7024be8330..e1a4346072 100644 --- a/app/client/src/widgets/MenuButtonWidget/component/index.tsx +++ b/app/client/src/widgets/MenuButtonWidget/component/index.tsx @@ -111,7 +111,7 @@ const BaseButton = styled(Button)` : "none" } !important; - &:hover, &:active { + &:hover, &:active, &:focus { background: ${ getCustomHoverColor(theme, buttonVariant, buttonColor) !== "none" ? getCustomHoverColor(theme, buttonVariant, buttonColor) diff --git a/app/client/src/widgets/MenuButtonWidget/constants.ts b/app/client/src/widgets/MenuButtonWidget/constants.ts index 8393fdf818..a5eacc7ded 100644 --- a/app/client/src/widgets/MenuButtonWidget/constants.ts +++ b/app/client/src/widgets/MenuButtonWidget/constants.ts @@ -34,12 +34,14 @@ export interface ConfigureMenuItems { config: MenuItem; } +export type MenuItems = Record; + export interface MenuButtonWidgetProps extends WidgetProps { label?: string; isDisabled?: boolean; isVisible?: boolean; isCompact?: boolean; - menuItems: Record; + menuItems: MenuItems; getVisibleItems: () => Array; menuVariant?: ButtonVariant; menuColor?: string; @@ -51,7 +53,6 @@ export interface MenuButtonWidgetProps extends WidgetProps { menuItemsSource: MenuItemsSource; configureMenuItems: ConfigureMenuItems; sourceData?: Array>; - sourceDataKeys?: Array; } export interface MenuButtonComponentProps { @@ -59,7 +60,7 @@ export interface MenuButtonComponentProps { isDisabled?: boolean; isVisible?: boolean; isCompact?: boolean; - menuItems: Record; + menuItems: MenuItems; getVisibleItems: () => Array; menuVariant?: ButtonVariant; menuColor?: string; @@ -77,11 +78,10 @@ export interface MenuButtonComponentProps { menuItemsSource: MenuItemsSource; configureMenuItems: ConfigureMenuItems; sourceData?: Array>; - sourceDataKeys?: Array; } export interface PopoverContentProps { - menuItems: Record; + menuItems: MenuItems; getVisibleItems: () => Array; onItemClicked: (onClick: string | undefined, index: number) => void; isCompact?: boolean; @@ -90,7 +90,6 @@ export interface PopoverContentProps { menuItemsSource: MenuItemsSource; configureMenuItems: ConfigureMenuItems; sourceData?: Array>; - sourceDataKeys?: Array; } export const ICON_NAMES = Object.keys(IconNames).map( diff --git a/app/client/src/widgets/MenuButtonWidget/validations.ts b/app/client/src/widgets/MenuButtonWidget/validations.ts index 1baabee778..af0b14a5f9 100644 --- a/app/client/src/widgets/MenuButtonWidget/validations.ts +++ b/app/client/src/widgets/MenuButtonWidget/validations.ts @@ -1,3 +1,4 @@ +import { ValidationConfig } from "constants/PropertyControlConstants"; import { ValidationResponse } from "constants/WidgetValidation"; import { MenuButtonWidgetProps } from "./constants"; @@ -43,3 +44,253 @@ export function sourceDataArrayValidation( return invalidResponse; } } + +export function textForEachRowValidation( + value: unknown, + props: MenuButtonWidgetProps, + _: any, +): ValidationResponse { + const generateResponseAndReturn = (isValid = false, message = "") => { + return { + isValid, + parsed: isValid ? value : [], + messages: [message], + }; + }; + + const DEFAULT_MESSAGE = + "The evaluated value should be either a string or a number."; + + if ( + _.isString(value) || + _.isNumber(value) || + Array.isArray(value) || + value === undefined + ) { + if (Array.isArray(value)) { + const isValid = value.every((item) => { + if (_.isString(item) || _.isNumber(item) || item === undefined) { + return true; + } + + if (Array.isArray(item)) { + return item.every( + (subItem) => + _.isString(subItem) || + _.isNumber(subItem) || + subItem === undefined, + ); + } + + return false; + }); + + return isValid + ? generateResponseAndReturn(true) + : generateResponseAndReturn(false, DEFAULT_MESSAGE); + } + + return generateResponseAndReturn(true); + } + + return generateResponseAndReturn(false, DEFAULT_MESSAGE); +} + +export function booleanForEachRowValidation( + value: unknown, +): ValidationResponse { + const generateResponseAndReturn = (isValid = false, message = "") => { + return { + isValid, + parsed: isValid ? value : true, + messages: [message], + }; + }; + + const isBoolean = (value: unknown) => { + const isABoolean = value === true || value === false; + const isStringTrueFalse = value === "true" || value === "false"; + + return isABoolean || isStringTrueFalse || value === undefined; + }; + + const DEFAULT_MESSAGE = "The evaluated value should be a boolean."; + + if (isBoolean(value)) { + return generateResponseAndReturn(true); + } + + if (Array.isArray(value)) { + const isValid = value.every((item) => { + if (isBoolean(item)) { + return true; + } + + if (Array.isArray(item)) { + return item.every((subItem) => isBoolean(subItem)); + } + + return false; + }); + + return isValid + ? generateResponseAndReturn(true) + : generateResponseAndReturn(false, DEFAULT_MESSAGE); + } + + return generateResponseAndReturn(false, DEFAULT_MESSAGE); +} + +export function iconNamesForEachRowValidation( + value: unknown, + props: MenuButtonWidgetProps, + _: any, + moment: any, + propertyPath: string, + config: ValidationConfig, +): ValidationResponse { + const generateResponseAndReturn = (isValid = false, message = "") => { + return { + isValid, + parsed: isValid ? value : true, + messages: [message], + }; + }; + + const DEFAULT_MESSAGE = + "The evaluated value should either be an icon name, undefined, null, or an empty string. We currently use the icons from the Blueprint library. You can see the list of icons at https://blueprintjs.com/docs/#icons"; + + const isIconName = (value: unknown) => { + return ( + config?.params?.allowedValues?.includes(value as string) || + value === undefined || + value === null || + value === "" + ); + }; + + if (isIconName(value)) { + return generateResponseAndReturn(true); + } + + if (Array.isArray(value)) { + const isValid = value.every((item) => { + if (isIconName(item)) { + return true; + } + + if (Array.isArray(item)) { + return item.every((subItem) => isIconName(subItem)); + } + + return false; + }); + + return isValid + ? generateResponseAndReturn(true) + : generateResponseAndReturn(false, DEFAULT_MESSAGE); + } + + return generateResponseAndReturn(false, DEFAULT_MESSAGE); +} + +export function iconPositionForEachRowValidation( + value: unknown, + props: MenuButtonWidgetProps, + _: any, + moment: any, + propertyPath: string, + config: ValidationConfig, +): ValidationResponse { + const generateResponseAndReturn = (isValid = false, message = "") => { + return { + isValid, + parsed: isValid ? value : true, + messages: [message], + }; + }; + + const DEFAULT_MESSAGE = `The evaluated value should be one of the allowed values => ${config?.params?.allowedValues?.join( + ", ", + )}, undefined, null, or an empty string`; + + const isIconPosition = (value: unknown) => { + return ( + config?.params?.allowedValues?.includes(value as string) || + value === undefined || + value === null || + value === "" + ); + }; + + if (isIconPosition(value)) { + return generateResponseAndReturn(true); + } + + if (Array.isArray(value)) { + const isValid = value.every((item) => { + if (isIconPosition(item)) { + return true; + } + + if (Array.isArray(item)) { + return item.every((subItem) => isIconPosition(subItem)); + } + + return false; + }); + + return isValid + ? generateResponseAndReturn(true) + : generateResponseAndReturn(false, DEFAULT_MESSAGE); + } + + return generateResponseAndReturn(false, DEFAULT_MESSAGE); +} + +export function colorForEachRowValidation( + value: unknown, + props: MenuButtonWidgetProps, + _: any, + moment: any, + propertyPath: string, + config: ValidationConfig, +): ValidationResponse { + const generateResponseAndReturn = (isValid = false, message = "") => { + return { + isValid, + parsed: isValid ? value : true, + messages: [message], + }; + }; + + const DEFAULT_MESSAGE = `The evaluated value should match ${config?.params?.regex}`; + + const isColor = (value: unknown) => { + return config?.params?.regex?.test(value as string); + }; + + if (isColor(value)) { + return generateResponseAndReturn(true); + } + + if (Array.isArray(value)) { + const isValid = value.every((item) => { + if (isColor(item)) { + return true; + } + + if (Array.isArray(item)) { + return item.every((subItem) => isColor(subItem)); + } + + return false; + }); + + return isValid + ? generateResponseAndReturn(true) + : generateResponseAndReturn(false, DEFAULT_MESSAGE); + } + + return generateResponseAndReturn(false, DEFAULT_MESSAGE); +} diff --git a/app/client/src/widgets/MenuButtonWidget/widget/helper.test.ts b/app/client/src/widgets/MenuButtonWidget/widget/helper.test.ts index 19d21c59a8..6d4e7352d4 100644 --- a/app/client/src/widgets/MenuButtonWidget/widget/helper.test.ts +++ b/app/client/src/widgets/MenuButtonWidget/widget/helper.test.ts @@ -1,13 +1,29 @@ -import { getSourceDataKeysForEventAutocomplete } from "./helper"; +import { getKeysFromSourceDataForEventAutocomplete } from "./helper"; -describe("getSourceDataKeysForEventAutocomplete", () => { - it("Should test with valid values", () => { - const mockProps = { - sourceDataKeys: ["step", "task", "status", "action"], - menuItemsSource: "DYANMIC", - }; +describe("getKeysFromSourceDataForEventAutocomplete", () => { + it("Should test with valid values - array of objects", () => { + const mockProps = [ + { + step: "#1", + task: "Drop a table", + status: "✅", + action: "", + }, + { + step: "#2", + task: "Create a query fetch_users with the Mock DB", + status: "--", + action: "", + }, + { + step: "#3", + task: "Bind the query using => fetch_users.data", + status: "--", + action: "", + }, + ]; - const result = getSourceDataKeysForEventAutocomplete(mockProps as any); + const result = getKeysFromSourceDataForEventAutocomplete(mockProps as any); const expected = { currentItem: { step: "", @@ -19,25 +35,169 @@ describe("getSourceDataKeysForEventAutocomplete", () => { expect(result).toStrictEqual(expected); }); - it("Should test with Static menuItemSource", () => { - const mockProps = { - sourceDataKeys: [], - menuItemsSource: "STATIC", - }; + it("Should test with valid values - array of arrays of objects", () => { + const mockProps = [ + [ + { + gender: "male", + name: "#1 Victor", + email: "victor.garrett@example.com", + phone: "011-800-3906", + id: "6125683T", + nat: "IE", + }, + { + gender: "male", + name: "#1 Tobias", + email: "tobias.hansen@example.com", + phone: "84467012", + id: "200247-8744", + nat: "DK", + }, + { + gender: "female", + name: "#1 Jane", + email: "jane.coleman@example.com", + phone: "(679) 516-8766", + id: "098-73-7712", + nat: "US", + }, + { + gender: "female", + name: "#1 Yaromira", + email: "yaromira.manuylenko@example.com", + phone: "(099) B82-8594", + id: null, + nat: "UA", + }, + { + gender: "male", + name: "#1 Andre", + email: "andre.ortiz@example.com", + phone: "08-3115-5776", + id: "876838842", + nat: "AU", + }, + ], + [ + { + gender: "male", + name: "#2 Victor", + email: "victor.garrett@example.com", + phone: "011-800-3906", + id: "6125683T", + nat: "IE", + }, + { + gender: "male", + name: "#2 Tobias", + email: "tobias.hansen@example.com", + phone: "84467012", + id: "200247-8744", + nat: "DK", + }, + { + gender: "female", + name: "#2 Jane", + email: "jane.coleman@example.com", + phone: "(679) 516-8766", + id: "098-73-7712", + nat: "US", + }, + { + gender: "female", + name: "#2 Yaromira", + email: "yaromira.manuylenko@example.com", + phone: "(099) B82-8594", + id: null, + nat: "UA", + }, + { + gender: "male", + name: "#2 Andre", + email: "andre.ortiz@example.com", + phone: "08-3115-5776", + id: "876838842", + nat: "AU", + }, + ], + [ + { + gender: "male", + name: "#3 Victor", + email: "victor.garrett@example.com", + phone: "011-800-3906", + id: "6125683T", + nat: "IE", + }, + { + gender: "male", + name: "#3 Tobias", + email: "tobias.hansen@example.com", + phone: "84467012", + id: "200247-8744", + nat: "DK", + }, + { + gender: "female", + name: "#3 Jane", + email: "jane.coleman@example.com", + phone: "(679) 516-8766", + id: "098-73-7712", + nat: "US", + }, + { + gender: "female", + name: "#3 Yaromira", + email: "yaromira.manuylenko@example.com", + phone: "(099) B82-8594", + id: null, + nat: "UA", + }, + { + gender: "male", + name: "#3 Andre", + email: "andre.ortiz@example.com", + phone: "08-3115-5776", + id: "876838842", + nat: "AU", + }, + ], + ]; - const result = getSourceDataKeysForEventAutocomplete(mockProps as any); - const expected = undefined; + const result = getKeysFromSourceDataForEventAutocomplete(mockProps as any); + const expected = { + currentItem: { + gender: "", + name: "", + email: "", + phone: "", + id: "", + nat: "", + }, + }; expect(result).toStrictEqual(expected); }); - it("Should test with empty sourceDataKeys", () => { + it("Should test with empty sourceData", () => { const mockProps = { - sourceDataKeys: [], - menuItemsSource: "DYANMIC", + __evaluation__: { + evaluatedValues: { + sourceData: [], + }, + }, }; - const result = getSourceDataKeysForEventAutocomplete(mockProps as any); - const expected = undefined; + const result = getKeysFromSourceDataForEventAutocomplete(mockProps as any); + const expected = { currentItem: {} }; + expect(result).toStrictEqual(expected); + }); + + it("Should test without sourceData", () => { + const mockProps = {}; + + const result = getKeysFromSourceDataForEventAutocomplete(mockProps as any); + const expected = { currentItem: {} }; expect(result).toStrictEqual(expected); }); }); diff --git a/app/client/src/widgets/MenuButtonWidget/widget/helper.ts b/app/client/src/widgets/MenuButtonWidget/widget/helper.ts index 92945730a3..5c8cb8f84a 100644 --- a/app/client/src/widgets/MenuButtonWidget/widget/helper.ts +++ b/app/client/src/widgets/MenuButtonWidget/widget/helper.ts @@ -1,34 +1,41 @@ import { isArray } from "lodash"; -import { MenuButtonWidgetProps, MenuItemsSource } from "../constants"; -export const getSourceDataKeysForEventAutocomplete = ( - props: MenuButtonWidgetProps, +export const getKeysFromSourceDataForEventAutocomplete = ( + sourceData?: Array> | unknown, ) => { - if ( - props.menuItemsSource === MenuItemsSource.STATIC || - !props.sourceDataKeys?.length - ) { - return; - } + if (isArray(sourceData) && sourceData?.length) { + const keys = getUniqueKeysFromSourceData(sourceData); - return { - currentItem: props.sourceDataKeys.reduce( - (prev, cur) => ({ ...prev, [cur]: "" }), - {}, - ), - }; + return { + currentItem: keys.reduce((prev, cur) => ({ ...prev, [cur]: "" }), {}), + }; + } else { + return { currentItem: {} }; + } }; -export const getSourceDataKeys = (props: MenuButtonWidgetProps) => { - if (!isArray(props.sourceData) || !props.sourceData?.length) { +export const getUniqueKeysFromSourceData = ( + sourceData?: Array>, +) => { + if (!isArray(sourceData) || !sourceData?.length) { return []; } const allKeys: string[] = []; // get all keys - props.sourceData?.forEach((item) => allKeys.push(...Object.keys(item))); + sourceData?.forEach((item) => { + if (isArray(item) && item?.length) { + item.forEach((subItem) => { + allKeys.push(...Object.keys(subItem)); + }); + } else { + allKeys.push(...Object.keys(item)); + } + }); // return unique keys - return [...new Set(allKeys)]; + const uniqueKeys = [...new Set(allKeys)]; + + return uniqueKeys; }; diff --git a/app/client/src/widgets/MenuButtonWidget/widget/index.tsx b/app/client/src/widgets/MenuButtonWidget/widget/index.tsx index 10816a2446..ae8e56cc2e 100644 --- a/app/client/src/widgets/MenuButtonWidget/widget/index.tsx +++ b/app/client/src/widgets/MenuButtonWidget/widget/index.tsx @@ -9,9 +9,7 @@ import { MinimumPopupRows } from "widgets/constants"; import { MenuButtonWidgetProps, MenuItem, MenuItemsSource } from "../constants"; import contentConfig from "./propertyConfig/contentConfig"; import styleConfig from "./propertyConfig/styleConfig"; -import equal from "fast-deep-equal/es6"; import { isArray, orderBy } from "lodash"; -import { getSourceDataKeys } from "./helper"; import { Stylesheet } from "entities/AppTheming"; class MenuButtonWidget extends BaseWidget { @@ -108,19 +106,6 @@ class MenuButtonWidget extends BaseWidget { return []; }; - componentDidMount = () => { - super.updateWidgetProperty("sourceDataKeys", getSourceDataKeys(this.props)); - }; - - componentDidUpdate = (prevProps: MenuButtonWidgetProps) => { - if (!equal(prevProps.sourceData, this.props.sourceData)) { - super.updateWidgetProperty( - "sourceDataKeys", - getSourceDataKeys(this.props), - ); - } - }; - getPageView() { const { componentWidth } = this.getComponentDimensions(); const menuDropDownWidth = MinimumPopupRows * this.props.parentColumnSpace; diff --git a/app/client/src/widgets/MenuButtonWidget/widget/propertyConfig/childPanels/configureMenuItemsConfig.ts b/app/client/src/widgets/MenuButtonWidget/widget/propertyConfig/childPanels/configureMenuItemsConfig.ts index 1992382ca6..3e27c77340 100644 --- a/app/client/src/widgets/MenuButtonWidget/widget/propertyConfig/childPanels/configureMenuItemsConfig.ts +++ b/app/client/src/widgets/MenuButtonWidget/widget/propertyConfig/childPanels/configureMenuItemsConfig.ts @@ -1,19 +1,11 @@ import { ValidationTypes } from "constants/WidgetValidation"; -import { ICON_NAMES } from "../../../constants"; -import { getSourceDataKeysForEventAutocomplete } from "../../helper"; +import { ICON_NAMES, MenuButtonWidgetProps } from "../../../constants"; +import { getKeysFromSourceDataForEventAutocomplete } from "../../helper"; export default { editableTitle: false, titlePropertyName: "label", panelIdPropertyName: "id", - updateHook: (props: any, propertyPath: string, propertyValue: string) => { - return [ - { - propertyPath, - propertyValue, - }, - ]; - }, contentChildren: [ { sectionName: "General", @@ -33,7 +25,7 @@ export default { type: ValidationTypes.TEXT, }, }, - dependencies: ["sourceDataKeys"], + evaluatedDependencies: ["sourceData"], }, { propertyName: "isVisible", @@ -51,7 +43,7 @@ export default { }, }, customJSControl: "MENU_BUTTON_DYNAMIC_ITEMS", - dependencies: ["sourceDataKeys"], + evaluatedDependencies: ["sourceData"], }, { propertyName: "isDisabled", @@ -69,7 +61,7 @@ export default { }, }, customJSControl: "MENU_BUTTON_DYNAMIC_ITEMS", - dependencies: ["sourceDataKeys"], + evaluatedDependencies: ["sourceData"], }, ], }, @@ -85,8 +77,12 @@ export default { isJSConvertible: true, isBindProperty: true, isTriggerProperty: true, - additionalAutoComplete: getSourceDataKeysForEventAutocomplete, - dependencies: ["sourceDataKeys"], + additionalAutoComplete: (props: MenuButtonWidgetProps) => { + return getKeysFromSourceDataForEventAutocomplete( + props?.__evaluation__?.evaluatedValues?.sourceData, + ); + }, + evaluatedDependencies: ["sourceData"], }, ], }, @@ -114,7 +110,7 @@ export default { }, }, customJSControl: "MENU_BUTTON_DYNAMIC_ITEMS", - dependencies: ["sourceDataKeys"], + evaluatedDependencies: ["sourceData"], }, { propertyName: "iconAlign", @@ -145,7 +141,7 @@ export default { }, }, customJSControl: "MENU_BUTTON_DYNAMIC_ITEMS", - dependencies: ["sourceDataKeys"], + evaluatedDependencies: ["sourceData"], }, ], }, @@ -162,7 +158,7 @@ export default { isTriggerProperty: false, isJSConvertible: true, customJSControl: "MENU_BUTTON_DYNAMIC_ITEMS", - dependencies: ["sourceDataKeys"], + evaluatedDependencies: ["sourceData"], validation: { type: ValidationTypes.ARRAY_OF_TYPE_OR_TYPE, params: { @@ -181,7 +177,7 @@ export default { isTriggerProperty: false, isJSConvertible: true, customJSControl: "MENU_BUTTON_DYNAMIC_ITEMS", - dependencies: ["sourceDataKeys"], + evaluatedDependencies: ["sourceData"], validation: { type: ValidationTypes.ARRAY_OF_TYPE_OR_TYPE, params: { @@ -200,7 +196,7 @@ export default { isTriggerProperty: false, isJSConvertible: true, customJSControl: "MENU_BUTTON_DYNAMIC_ITEMS", - dependencies: ["sourceDataKeys"], + evaluatedDependencies: ["sourceData"], validation: { type: ValidationTypes.ARRAY_OF_TYPE_OR_TYPE, params: { diff --git a/app/client/src/widgets/MenuButtonWidget/widget/propertyConfig/propertyUtils.ts b/app/client/src/widgets/MenuButtonWidget/widget/propertyConfig/propertyUtils.ts index e87a23c157..926c43b740 100644 --- a/app/client/src/widgets/MenuButtonWidget/widget/propertyConfig/propertyUtils.ts +++ b/app/client/src/widgets/MenuButtonWidget/widget/propertyConfig/propertyUtils.ts @@ -19,10 +19,6 @@ export const updateMenuItemsSource = ( propertyPath: "sourceData", propertyValue: [], }); - propertiesToUpdate.push({ - propertyPath: "sourceDataKeys", - propertyValue: [], - }); } if (!props.configureMenuItems) { diff --git a/app/client/src/widgets/ModalWidget/component/index.tsx b/app/client/src/widgets/ModalWidget/component/index.tsx index 3f9b30c2d1..06fa00cf71 100644 --- a/app/client/src/widgets/ModalWidget/component/index.tsx +++ b/app/client/src/widgets/ModalWidget/component/index.tsx @@ -171,6 +171,9 @@ export default function ModalComponent(props: ModalComponentProps) { setTimeout(() => { setModalPosition("unset"); }, 100); + + modalContentRef.current?.focus(); + return () => { // handle modal close events when this component unmounts // will be called in all cases :- @@ -248,6 +251,7 @@ export default function ModalComponent(props: ModalComponentProps) { id={props.widgetId} ref={modalContentRef} scroll={props.scrollContents} + tabIndex={0} > {props.children} diff --git a/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx b/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx index c32859c9b9..acfd02cdf2 100644 --- a/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx +++ b/app/client/src/widgets/MultiSelectTreeWidget/widget/index.tsx @@ -429,7 +429,7 @@ class MultiSelectTreeWidget extends BaseWidget< propertyName: "labelStyle", label: "Emphasis", helpText: "Control if the label should be bold or italics", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/MultiSelectWidget/widget/index.tsx b/app/client/src/widgets/MultiSelectWidget/widget/index.tsx index 496aa0ede2..d89bce1e0c 100644 --- a/app/client/src/widgets/MultiSelectWidget/widget/index.tsx +++ b/app/client/src/widgets/MultiSelectWidget/widget/index.tsx @@ -327,7 +327,7 @@ class MultiSelectWidget extends BaseWidget< { propertyName: "labelStyle", label: "Label Font Style", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx b/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx index 978893d2a8..98e5e47d3c 100644 --- a/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/MultiSelectWidgetV2/widget/index.tsx @@ -543,7 +543,7 @@ class MultiSelectWidget extends BaseWidget< propertyName: "labelStyle", label: "Emphasis", helpText: "Control if the label should be bold or italics", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/NumberSliderWidget/widget/propertyConfig/styleConfig.ts b/app/client/src/widgets/NumberSliderWidget/widget/propertyConfig/styleConfig.ts index a03f244241..634586e40b 100644 --- a/app/client/src/widgets/NumberSliderWidget/widget/propertyConfig/styleConfig.ts +++ b/app/client/src/widgets/NumberSliderWidget/widget/propertyConfig/styleConfig.ts @@ -94,7 +94,7 @@ export default [ propertyName: "labelStyle", label: "Emphasis", helpText: "Control if the label should be bold or italics", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/PhoneInputWidget/component/ISDCodeDropdown.tsx b/app/client/src/widgets/PhoneInputWidget/component/ISDCodeDropdown.tsx index a15c757fef..078e7c57df 100644 --- a/app/client/src/widgets/PhoneInputWidget/component/ISDCodeDropdown.tsx +++ b/app/client/src/widgets/PhoneInputWidget/component/ISDCodeDropdown.tsx @@ -246,6 +246,7 @@ export default function ISDCodeDropdown(props: ISDCodeDropdownProps) { className={`t--input-country-code-change isd-change-dropdown-trigger ${ !props.allowDialCodeChange ? "country-type-trigger" : "" }`} + data-tabbable={false} disabled={props.disabled} tabIndex={0} type="button" diff --git a/app/client/src/widgets/QRGeneratorWidget/widget/index.tsx b/app/client/src/widgets/QRGeneratorWidget/widget/index.tsx index bf08f89a3e..44663ad59c 100644 --- a/app/client/src/widgets/QRGeneratorWidget/widget/index.tsx +++ b/app/client/src/widgets/QRGeneratorWidget/widget/index.tsx @@ -267,7 +267,7 @@ class TextWidget extends BaseWidget { { propertyName: "fontStyle", label: "Font Style", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", @@ -590,7 +590,7 @@ class TextWidget extends BaseWidget { { propertyName: "fontStyle", label: "Emphasis", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/RadioGroupWidget/component/index.tsx b/app/client/src/widgets/RadioGroupWidget/component/index.tsx index 7ffe291311..e38a78a970 100644 --- a/app/client/src/widgets/RadioGroupWidget/component/index.tsx +++ b/app/client/src/widgets/RadioGroupWidget/component/index.tsx @@ -10,6 +10,7 @@ import LabelWithTooltip, { labelLayoutStyles, LABEL_CONTAINER_CLASS, } from "widgets/components/LabelWithTooltip"; +import { darkenColor } from "widgets/WidgetUtils"; export interface RadioGroupContainerProps { compactMode: boolean; @@ -46,6 +47,11 @@ const StyledRadioGroup = styled(RadioGroup)` border: 1px solid ${({ accentColor }) => `${accentColor}`} !important; } + & input:not(:disabled):checked:focus ~ .${Classes.CONTROL_INDICATOR} { + background: ${({ accentColor }) => + `${darkenColor(accentColor)}`} !important; + } + & input:disabled:checked ~ .${Classes.CONTROL_INDICATOR} { &:before { opacity: 1; diff --git a/app/client/src/widgets/RadioGroupWidget/widget/index.tsx b/app/client/src/widgets/RadioGroupWidget/widget/index.tsx index 8156eb032a..cf2fd8619f 100644 --- a/app/client/src/widgets/RadioGroupWidget/widget/index.tsx +++ b/app/client/src/widgets/RadioGroupWidget/widget/index.tsx @@ -427,7 +427,7 @@ class RadioGroupWidget extends BaseWidget { propertyName: "labelStyle", label: "Emphasis", helpText: "Control if the label should be bold or italics", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/RangeSliderWidget/widget/propertyConfig/styleConfig.ts b/app/client/src/widgets/RangeSliderWidget/widget/propertyConfig/styleConfig.ts index 8687e6263b..551f008456 100644 --- a/app/client/src/widgets/RangeSliderWidget/widget/propertyConfig/styleConfig.ts +++ b/app/client/src/widgets/RangeSliderWidget/widget/propertyConfig/styleConfig.ts @@ -94,7 +94,7 @@ export default [ propertyName: "labelStyle", label: "Emphasis", helpText: "Control if the label should be bold or italics", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/RichTextEditorWidget/widget/index.tsx b/app/client/src/widgets/RichTextEditorWidget/widget/index.tsx index 1a693b992b..ebcee1c9b0 100644 --- a/app/client/src/widgets/RichTextEditorWidget/widget/index.tsx +++ b/app/client/src/widgets/RichTextEditorWidget/widget/index.tsx @@ -288,7 +288,7 @@ class RichTextEditorWidget extends BaseWidget< propertyName: "labelStyle", label: "Emphasis", helpText: "Control if the label should be bold or italics", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/SelectWidget/widget/index.tsx b/app/client/src/widgets/SelectWidget/widget/index.tsx index 22bde6edf4..02d5957aa3 100644 --- a/app/client/src/widgets/SelectWidget/widget/index.tsx +++ b/app/client/src/widgets/SelectWidget/widget/index.tsx @@ -468,7 +468,7 @@ class SelectWidget extends BaseWidget { propertyName: "labelStyle", label: "Emphasis", helpText: "Control if the label should be bold or italics", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx b/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx index 0f48c97029..5926cac38d 100644 --- a/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx +++ b/app/client/src/widgets/SingleSelectTreeWidget/widget/index.tsx @@ -400,7 +400,7 @@ class SingleSelectTreeWidget extends BaseWidget< propertyName: "labelStyle", label: "Emphasis", helpText: "Control if the label should be bold or italics", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx b/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx index 7311bace94..90176d9bd5 100644 --- a/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx +++ b/app/client/src/widgets/SwitchGroupWidget/widget/index.tsx @@ -310,7 +310,7 @@ class SwitchGroupWidget extends BaseWidget< propertyName: "labelStyle", label: "Emphasis", helpText: "Control if the label should be bold or italics", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/SwitchWidget/component/index.tsx b/app/client/src/widgets/SwitchWidget/component/index.tsx index 0f2239ff33..8dad72786a 100644 --- a/app/client/src/widgets/SwitchWidget/component/index.tsx +++ b/app/client/src/widgets/SwitchWidget/component/index.tsx @@ -69,7 +69,8 @@ export const StyledSwitch = styled(Switch)<{ border: 1px solid ${({ $accentColor }) => `${$accentColor}`} !important; } - &:hover input:checked:not(:disabled) ~ .bp3-control-indicator { + &:hover input:checked:not(:disabled) ~ .bp3-control-indicator, + input:checked:not(:disabled):focus ~ .bp3-control-indicator { background: ${({ $accentColor }) => `${darkenColor($accentColor)}`} !important; border: 1px solid ${({ $accentColor }) => diff --git a/app/client/src/widgets/SwitchWidget/widget/index.tsx b/app/client/src/widgets/SwitchWidget/widget/index.tsx index 5f1213667c..891faa97b4 100644 --- a/app/client/src/widgets/SwitchWidget/widget/index.tsx +++ b/app/client/src/widgets/SwitchWidget/widget/index.tsx @@ -196,7 +196,7 @@ class SwitchWidget extends BaseWidget { propertyName: "labelStyle", label: "Emphasis", helpText: "Control if the label should be bold or italics", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/TableWidget/component/components/menuButtonTableComponent.tsx b/app/client/src/widgets/TableWidget/component/components/menuButtonTableComponent.tsx index a9e2edeb99..ce3c01874f 100644 --- a/app/client/src/widgets/TableWidget/component/components/menuButtonTableComponent.tsx +++ b/app/client/src/widgets/TableWidget/component/components/menuButtonTableComponent.tsx @@ -100,7 +100,7 @@ const BaseButton = styled(Button)` } !important; } - &:hover:enabled, &:active:enabled { + &:hover:enabled, &:active:enabled. &:focus:enabled { background: ${ getCustomHoverColor(theme, buttonVariant, buttonColor) !== "none" ? getCustomHoverColor(theme, buttonVariant, buttonColor) diff --git a/app/client/src/widgets/TableWidget/widget/propertyConfig.ts b/app/client/src/widgets/TableWidget/widget/propertyConfig.ts index cd2268d2d0..edae8e79df 100644 --- a/app/client/src/widgets/TableWidget/widget/propertyConfig.ts +++ b/app/client/src/widgets/TableWidget/widget/propertyConfig.ts @@ -662,7 +662,7 @@ export default [ { propertyName: "fontStyle", label: "Font Style", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", @@ -1693,7 +1693,7 @@ export default [ { propertyName: "fontStyle", label: "Font Style", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", updateHook: updateColumnStyles, dependencies: ["primaryColumns", "derivedColumns"], options: [ diff --git a/app/client/src/widgets/TableWidgetV2/component/Constants.ts b/app/client/src/widgets/TableWidgetV2/component/Constants.ts index fdeb52b71a..1d4f1e3603 100644 --- a/app/client/src/widgets/TableWidgetV2/component/Constants.ts +++ b/app/client/src/widgets/TableWidgetV2/component/Constants.ts @@ -8,6 +8,12 @@ import { ButtonVariant, } from "components/constants"; import { DropdownOption } from "widgets/SelectWidget/constants"; +import { + ConfigureMenuItems, + MenuItem, + MenuItems, + MenuItemsSource, +} from "widgets/MenuButtonWidget/constants"; import { ColumnTypes } from "../constants"; export type TableSizes = { @@ -155,6 +161,9 @@ export interface MenuButtonCellProperties { menuColor?: string; menuButtoniconName?: IconName; onItemClicked?: (onClick: string | undefined) => void; + menuItemsSource: MenuItemsSource; + configureMenuItems: ConfigureMenuItems; + sourceData?: Array>; } export interface URLCellProperties { @@ -199,24 +208,6 @@ export interface CellLayoutProperties ImageCellProperties, BaseCellProperties {} -export type MenuItems = Record< - string, - { - widgetId: string; - id: string; - index: number; - isVisible?: boolean; - isDisabled?: boolean; - label?: string; - backgroundColor?: string; - textColor?: string; - iconName?: IconName; - iconColor?: string; - iconAlign?: Alignment; - onClick?: string; - } ->; - export interface TableColumnMetaProps { isHidden: boolean; format?: string; @@ -339,6 +330,10 @@ export interface ColumnProperties onItemClicked?: (onClick: string | undefined) => void; iconButtonStyle?: ButtonStyleType; imageSize?: ImageSize; + getVisibleItems?: () => Array; + menuItemsSource?: MenuItemsSource; + configureMenuItems?: ConfigureMenuItems; + sourceData?: Array>; } export const ConditionFunctions: { diff --git a/app/client/src/widgets/TableWidgetV2/component/cellComponents/AutoToolTipComponent.tsx b/app/client/src/widgets/TableWidgetV2/component/cellComponents/AutoToolTipComponent.tsx index d0fb3e8e98..a1c5eb25c8 100644 --- a/app/client/src/widgets/TableWidgetV2/component/cellComponents/AutoToolTipComponent.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/cellComponents/AutoToolTipComponent.tsx @@ -29,6 +29,7 @@ export const Content = styled.span` const WIDTH_OFFSET = 32; const MAX_WIDTH = 300; +const TOOLTIP_OPEN_DELAY = 500; function useToolTip( children: React.ReactNode, @@ -36,19 +37,45 @@ function useToolTip( title?: string, ) { const ref = createRef(); - const [showTooltip, updateToolTip] = useState(false); + const [requiresTooltip, setRequiresTooltip] = useState(false); useEffect(() => { - const element = ref.current?.querySelector("div") as HTMLDivElement; + let timeout: number; - if (element && element.offsetWidth < element.scrollWidth) { - updateToolTip(true); - } else { - updateToolTip(false); - } + const mouseEnterHandler = () => { + const element = ref.current?.querySelector("div") as HTMLDivElement; + + /* + * Using setTimeout to simulate hoverOpenDelay of the tooltip + * during initial render + */ + timeout = setTimeout(() => { + if (element && element.offsetWidth < element.scrollWidth) { + setRequiresTooltip(true); + } else { + setRequiresTooltip(false); + } + + ref.current?.removeEventListener("mouseenter", mouseEnterHandler); + ref.current?.removeEventListener("mouseleave", mouseLeaveHandler); + }, TOOLTIP_OPEN_DELAY); + }; + + const mouseLeaveHandler = () => { + clearTimeout(timeout); + }; + + ref.current?.addEventListener("mouseenter", mouseEnterHandler); + ref.current?.addEventListener("mouseleave", mouseLeaveHandler); + + return () => { + ref.current?.removeEventListener("mouseenter", mouseEnterHandler); + ref.current?.removeEventListener("mouseleave", mouseLeaveHandler); + clearTimeout(timeout); + }; }, [children]); - return showTooltip && children ? ( + return requiresTooltip && children ? ( } - hoverOpenDelay={1000} + defaultIsOpen + hoverOpenDelay={TOOLTIP_OPEN_DELAY} position="top" > - {{children}} + { + + {children} + + } ) : ( - {children} + + {children} + ); } diff --git a/app/client/src/widgets/TableWidgetV2/component/cellComponents/MenuButtonCell.tsx b/app/client/src/widgets/TableWidgetV2/component/cellComponents/MenuButtonCell.tsx index b67742e918..19e684dd1d 100644 --- a/app/client/src/widgets/TableWidgetV2/component/cellComponents/MenuButtonCell.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/cellComponents/MenuButtonCell.tsx @@ -2,11 +2,17 @@ import React from "react"; import { IconName } from "@blueprintjs/icons"; import { Alignment } from "@blueprintjs/core"; -import { BaseCellComponentProps, MenuItems } from "../Constants"; +import { BaseCellComponentProps } from "../Constants"; import { ButtonVariant } from "components/constants"; import { CellWrapper } from "../TableStyledWrappers"; import { ColumnAction } from "components/propertyControls/ColumnActionSelectorControl"; import MenuButtonTableComponent from "./menuButtonTableComponent"; +import { + ConfigureMenuItems, + MenuItem, + MenuItems, + MenuItemsSource, +} from "widgets/MenuButtonWidget/constants"; interface MenuButtonProps extends Omit { action?: ColumnAction; @@ -16,6 +22,8 @@ function MenuButton({ borderRadius, boxShadow, compactMode, + configureMenuItems, + getVisibleItems, iconAlign, iconName, isCompact, @@ -24,9 +32,11 @@ function MenuButton({ label, menuColor, menuItems, + menuItemsSource, menuVariant, onCommandClick, rowIndex, + sourceData, }: MenuButtonProps): JSX.Element { const handlePropagation = ( e: React.MouseEvent, @@ -35,9 +45,9 @@ function MenuButton({ e.stopPropagation(); } }; - const onItemClicked = (onClick?: string) => { + const onItemClicked = (onClick?: string, index?: number) => { if (onClick) { - onCommandClick(onClick); + onCommandClick(onClick, index); } }; @@ -47,6 +57,8 @@ function MenuButton({ borderRadius={borderRadius} boxShadow={boxShadow} compactMode={compactMode} + configureMenuItems={configureMenuItems} + getVisibleItems={getVisibleItems} iconAlign={iconAlign} iconName={iconName} isCompact={isCompact} @@ -54,9 +66,11 @@ function MenuButton({ label={label} menuColor={menuColor} menuItems={{ ...menuItems }} + menuItemsSource={menuItemsSource} menuVariant={menuVariant} onItemClicked={onItemClicked} rowIndex={rowIndex} + sourceData={sourceData} />
); @@ -66,7 +80,11 @@ export interface RenderMenuButtonProps extends BaseCellComponentProps { isSelected: boolean; label: string; isDisabled: boolean; - onCommandClick: (dynamicTrigger: string, onComplete?: () => void) => void; + onCommandClick: ( + dynamicTrigger: string, + index?: number, + onComplete?: () => void, + ) => void; isCompact?: boolean; menuItems: MenuItems; menuVariant?: ButtonVariant; @@ -76,6 +94,10 @@ export interface RenderMenuButtonProps extends BaseCellComponentProps { iconName?: IconName; iconAlign?: Alignment; rowIndex: number; + getVisibleItems: (rowIndex: number) => Array; + menuItemsSource: MenuItemsSource; + configureMenuItems: ConfigureMenuItems; + sourceData?: Array>; } export function MenuButtonCell(props: RenderMenuButtonProps) { diff --git a/app/client/src/widgets/TableWidgetV2/component/cellComponents/menuButtonTableComponent.tsx b/app/client/src/widgets/TableWidgetV2/component/cellComponents/menuButtonTableComponent.tsx index 9cfe818778..d4b5ccced7 100644 --- a/app/client/src/widgets/TableWidgetV2/component/cellComponents/menuButtonTableComponent.tsx +++ b/app/client/src/widgets/TableWidgetV2/component/cellComponents/menuButtonTableComponent.tsx @@ -3,11 +3,11 @@ import styled, { createGlobalStyle } from "styled-components"; import { Alignment, Button, - Classes as CoreClasses, + Classes as BlueprintCoreClasses, Icon, Menu, - MenuItem, - Classes as BClasses, + MenuItem as BlueprintMenuItem, + Classes as BlueprintClasses, } from "@blueprintjs/core"; import { Classes, Popover2 } from "@blueprintjs/popover2"; import { IconName } from "@blueprintjs/icons"; @@ -20,15 +20,19 @@ import { } from "widgets/WidgetUtils"; import { darkenActive, darkenHover } from "constants/DefaultTheme"; import { ButtonVariant, ButtonVariantTypes } from "components/constants"; -import { MenuItems } from "../Constants"; import tinycolor from "tinycolor2"; import { Colors } from "constants/Colors"; -import orderBy from "lodash/orderBy"; import { getBooleanPropertyValue, getPropertyValue, } from "widgets/TableWidgetV2/widget/utilities"; import { ThemeProp } from "widgets/constants"; +import { + ConfigureMenuItems, + MenuItem, + MenuItems, + MenuItemsSource, +} from "widgets/MenuButtonWidget/constants"; const MenuButtonContainer = styled.div` width: 100%; @@ -54,7 +58,7 @@ const PopoverStyles = createGlobalStyle<{ borderRadius >= `1.5rem` ? `0.375rem` : borderRadius}; overflow: hidden; } - & .${BClasses.MENU_ITEM} { + & .${BlueprintClasses.MENU_ITEM} { padding: 9px 12px; border-radius: 0; &:hover { @@ -107,7 +111,7 @@ const BaseButton = styled(Button)` } !important; } - &:hover:enabled, &:active:enabled { + &:hover:enabled, &:active:enabled, &:focus:enabled { background: ${ getCustomHoverColor(theme, buttonVariant, buttonColor) !== "none" ? getCustomHoverColor(theme, buttonVariant, buttonColor) @@ -153,8 +157,8 @@ const BaseButton = styled(Button)` box-shadow: ${({ boxShadow }) => `${boxShadow}`} !important; `; -const BaseMenuItem = styled(MenuItem)` - &.${CoreClasses.MENU_ITEM}.${CoreClasses.DISABLED} { +const BaseMenuItem = styled(BlueprintMenuItem)` + &.${BlueprintCoreClasses.MENU_ITEM}.${BlueprintCoreClasses.DISABLED} { background-color: ${Colors.GREY_1} !important; } ${({ backgroundColor, theme }) => @@ -206,64 +210,71 @@ const StyledMenu = styled(Menu)` interface PopoverContentProps { menuItems: MenuItems; - onItemClicked: (onClick: string | undefined) => void; + onItemClicked: ( + onClick: string | undefined, + index?: number, + onComplete?: () => void, + ) => void; + getVisibleItems: (rowIndex: number) => Array; isCompact?: boolean; rowIndex: number; + menuItemsSource: MenuItemsSource; + configureMenuItems: ConfigureMenuItems; + sourceData?: Array>; } function PopoverContent(props: PopoverContentProps) { - const { isCompact, menuItems: itemsObj, onItemClicked, rowIndex } = props; + const { getVisibleItems, isCompact, onItemClicked, rowIndex } = props; - if (!itemsObj) return ; - const visibleItems = Object.keys(itemsObj) - .map((itemKey) => itemsObj[itemKey]) - .filter((item) => getBooleanPropertyValue(item.isVisible, rowIndex)); + const visibleItems = getVisibleItems(rowIndex); - const items = orderBy(visibleItems, ["index"], ["asc"]); + if (!visibleItems?.length) { + return ; + } else { + const listItems = visibleItems.map((item: MenuItem, index: number) => { + const { + backgroundColor, + iconAlign, + iconColor, + iconName, + id, + isDisabled, + label, + onClick, + textColor, + } = item; - const listItems = items.map((menuItem) => { - const { - backgroundColor, - iconAlign, - iconColor, - iconName, - id, - isDisabled, - label, - onClick, - textColor, - } = menuItem; + return ( + + ) : ( + undefined + ) + } + isCompact={isCompact} + key={id} + labelElement={ + iconAlign === Alignment.RIGHT && iconName ? ( + + ) : ( + undefined + ) + } + onClick={() => onItemClicked(onClick, index)} + text={label} + textColor={getPropertyValue(textColor, rowIndex)} + /> + ); + }); - return ( - - ) : ( - undefined - ) - } - isCompact={isCompact} - key={id} - labelElement={ - iconAlign === Alignment.RIGHT ? ( - - ) : ( - undefined - ) - } - onClick={() => onItemClicked(onClick)} - text={label} - textColor={getPropertyValue(textColor, rowIndex)} - /> - ); - }); - - return {listItems}; + return {listItems}; + } } interface PopoverTargetButtonProps { @@ -315,6 +326,7 @@ export interface MenuButtonComponentProps { isVisible?: boolean; isCompact?: boolean; menuItems: MenuItems; + getVisibleItems: (rowIndex: number) => Array; menuVariant?: ButtonVariant; menuColor?: string; borderRadius?: string; @@ -322,9 +334,16 @@ export interface MenuButtonComponentProps { boxShadowColor?: string; iconName?: IconName; iconAlign?: Alignment; - onItemClicked: (onClick: string | undefined) => void; + onItemClicked: ( + onClick: string | undefined, + index?: number, + onComplete?: () => void, + ) => void; rowIndex: number; compactMode?: string; + menuItemsSource: MenuItemsSource; + configureMenuItems: ConfigureMenuItems; + sourceData?: Array>; } function MenuButtonTableComponent(props: MenuButtonComponentProps) { @@ -333,6 +352,8 @@ function MenuButtonTableComponent(props: MenuButtonComponentProps) { boxShadow, boxShadowColor, compactMode, + configureMenuItems, + getVisibleItems, iconAlign, iconName, isCompact, @@ -340,9 +361,11 @@ function MenuButtonTableComponent(props: MenuButtonComponentProps) { label, menuColor = "#e1e1e1", menuItems, + menuItemsSource, menuVariant, onItemClicked, rowIndex, + sourceData, } = props; return ( @@ -359,10 +382,14 @@ function MenuButtonTableComponent(props: MenuButtonComponentProps) { }} content={ } disabled={isDisabled} diff --git a/app/client/src/widgets/TableWidgetV2/widget/index.tsx b/app/client/src/widgets/TableWidgetV2/widget/index.tsx index 942e1811f7..028c048c8b 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/index.tsx +++ b/app/client/src/widgets/TableWidgetV2/widget/index.tsx @@ -13,6 +13,7 @@ import _, { isEmpty, union, isObject, + orderBy, } from "lodash"; import BaseWidget, { WidgetState } from "widgets/BaseWidget"; @@ -58,6 +59,7 @@ import { getCellProperties, isColumnTypeEditable, getColumnType, + getBooleanPropertyValue, } from "./utilities"; import { ColumnProperties, @@ -85,6 +87,7 @@ import { SwitchCell } from "../component/cellComponents/SwitchCell"; import { SelectCell } from "../component/cellComponents/SelectCell"; import { CellWrapper } from "../component/TableStyledWrappers"; import { Stylesheet } from "entities/AppTheming"; +import { MenuItem, MenuItemsSource } from "widgets/MenuButtonWidget/constants"; const ReactTableComponent = lazy(() => retryPromise(() => import("../component")), @@ -1585,6 +1588,70 @@ class TableWidgetV2 extends BaseWidget { ); case ColumnTypes.MENU_BUTTON: + const getVisibleItems = (rowIndex: number) => { + const { + configureMenuItems, + menuItems, + menuItemsSource, + sourceData, + } = cellProperties; + + if (menuItemsSource === MenuItemsSource.STATIC && menuItems) { + const visibleItems = Object.values(menuItems)?.filter((item) => + getBooleanPropertyValue(item.isVisible, rowIndex), + ); + + return visibleItems?.length + ? orderBy(visibleItems, ["index"], ["asc"]) + : []; + } else if ( + menuItemsSource === MenuItemsSource.DYNAMIC && + isArray(sourceData) && + sourceData?.length && + configureMenuItems?.config + ) { + const { config } = configureMenuItems; + + const getValue = ( + propertyName: keyof MenuItem, + index: number, + rowIndex: number, + ) => { + const value = config[propertyName]; + + if (isArray(value) && isArray(value[rowIndex])) { + return value[rowIndex][index]; + } else if (isArray(value)) { + return value[index]; + } + + return value ?? null; + }; + + const visibleItems = sourceData + .map((item, index) => ({ + ...item, + id: index.toString(), + isVisible: getValue("isVisible", index, rowIndex), + isDisabled: getValue("isDisabled", index, rowIndex), + index: index, + widgetId: "", + label: getValue("label", index, rowIndex), + onClick: config?.onClick, + textColor: getValue("textColor", index, rowIndex), + backgroundColor: getValue("backgroundColor", index, rowIndex), + iconAlign: getValue("iconAlign", index, rowIndex), + iconColor: getValue("iconColor", index, rowIndex), + iconName: getValue("iconName", index, rowIndex), + })) + .filter((item) => item.isVisible === true); + + return visibleItems; + } + + return []; + }; + return ( { boxShadow={cellProperties.boxShadow} cellBackground={cellProperties.cellBackground} compactMode={compactMode} + configureMenuItems={cellProperties.configureMenuItems} fontStyle={cellProperties.fontStyle} + getVisibleItems={getVisibleItems} horizontalAlignment={cellProperties.horizontalAlignment} iconAlign={cellProperties.iconAlign} iconName={cellProperties.menuButtoniconName || undefined} @@ -1609,17 +1678,34 @@ class TableWidgetV2 extends BaseWidget { cellProperties.menuColor || this.props.accentColor || Colors.GREEN } menuItems={cellProperties.menuItems} + menuItemsSource={cellProperties.menuItemsSource} menuVariant={cellProperties.menuVariant ?? DEFAULT_MENU_VARIANT} - onCommandClick={(action: string, onComplete?: () => void) => - this.onColumnEvent({ + onCommandClick={( + action: string, + index?: number, + onComplete?: () => void, + ) => { + const additionalData: Record< + string, + string | number | Record + > = {}; + + if (cellProperties?.sourceData && _.isNumber(index)) { + additionalData.currentItem = cellProperties.sourceData[index]; + additionalData.currentIndex = index; + } + + return this.onColumnEvent({ rowIndex, action, onComplete, triggerPropertyName: "onClick", eventType: EventType.ON_CLICK, - }) - } + additionalData, + }); + }} rowIndex={originalIndex} + sourceData={cellProperties.sourceData} textColor={cellProperties.textColor} textSize={cellProperties.textSize} verticalAlignment={cellProperties.verticalAlignment} diff --git a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Basic.ts b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Basic.ts index 8b3c850fc5..4a9c5e59f5 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Basic.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Basic.ts @@ -4,8 +4,19 @@ import { ICON_NAMES, TableWidgetProps, } from "widgets/TableWidgetV2/constants"; -import { hideByColumnType, updateIconAlignment } from "../../propertyUtils"; +import { + hideByColumnType, + hideByMenuItemsSource, + hideIfMenuItemsSourceDataIsFalsy, + updateIconAlignment, + updateMenuItemsSource, +} from "../../propertyUtils"; import { IconNames } from "@blueprintjs/icons"; +import { MenuItemsSource } from "widgets/MenuButtonWidget/constants"; +import { EvaluationSubstitutionType } from "entities/DataTree/dataTreeFactory"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; +import { sourceDataArrayValidation } from "widgets/MenuButtonWidget/validations"; +import configureMenuItemsConfig from "./childPanels/configureMenuItemsConfig"; export default { sectionName: "Basic", @@ -70,6 +81,104 @@ export default { isBindProperty: true, isTriggerProperty: false, }, + { + propertyName: "menuItemsSource", + helpText: "Sets the source for the menu items", + label: "Menu Items Source", + controlType: "ICON_TABS", + fullWidth: true, + defaultValue: MenuItemsSource.STATIC, + options: [ + { + label: "Static", + value: MenuItemsSource.STATIC, + }, + { + label: "Dynamic", + value: MenuItemsSource.DYNAMIC, + }, + ], + isJSConvertible: false, + isBindProperty: false, + isTriggerProperty: false, + validation: { type: ValidationTypes.TEXT }, + updateHook: updateMenuItemsSource, + dependencies: [ + "primaryColumns", + "columnOrder", + "sourceData", + "configureMenuItems", + ], + hidden: (props: TableWidgetProps, propertyPath: string) => { + return hideByColumnType( + props, + propertyPath, + [ColumnTypes.MENU_BUTTON], + false, + ); + }, + }, + { + helpText: "Takes in an array of items to display the menu items.", + propertyName: "sourceData", + label: "Source Data", + controlType: "TABLE_COMPUTE_VALUE", + placeholderText: "{{Query1.data}}", + isBindProperty: true, + isTriggerProperty: false, + validation: { + type: ValidationTypes.FUNCTION, + params: { + expected: { + type: "Array of values", + example: `['option1', 'option2'] | [{ "label": "label1", "value": "value1" }]`, + autocompleteDataType: AutocompleteDataType.ARRAY, + }, + fnString: sourceDataArrayValidation.toString(), + }, + }, + evaluationSubstitutionType: EvaluationSubstitutionType.SMART_SUBSTITUTE, + hidden: (props: TableWidgetProps, propertyPath: string) => { + return ( + hideByColumnType( + props, + propertyPath, + [ColumnTypes.MENU_BUTTON], + false, + ) || + hideByMenuItemsSource(props, propertyPath, MenuItemsSource.STATIC) + ); + }, + dependencies: ["primaryColumns", "columnOrder", "menuItemsSource"], + }, + { + helpText: "Configure how each menu item will appear.", + propertyName: "configureMenuItems", + controlType: "OPEN_CONFIG_PANEL", + buttonConfig: { + label: "Item Configuration", + icon: "settings-2-line", + }, + label: "Configure Menu Items", + isBindProperty: false, + isTriggerProperty: false, + hidden: (props: TableWidgetProps, propertyPath: string) => + hideByColumnType( + props, + propertyPath, + [ColumnTypes.MENU_BUTTON], + false, + ) || + hideIfMenuItemsSourceDataIsFalsy(props, propertyPath) || + hideByMenuItemsSource(props, propertyPath, MenuItemsSource.STATIC), + dependencies: [ + "primaryColumns", + "columnOrder", + "menuItemsSource", + "sourceData", + ], + panelConfig: configureMenuItemsConfig, + }, { helpText: "Menu items", propertyName: "menuItems", @@ -78,11 +187,14 @@ export default { isBindProperty: false, isTriggerProperty: false, hidden: (props: TableWidgetProps, propertyPath: string) => { - return hideByColumnType( - props, - propertyPath, - [ColumnTypes.MENU_BUTTON], - false, + return ( + hideByColumnType( + props, + propertyPath, + [ColumnTypes.MENU_BUTTON], + false, + ) || + hideByMenuItemsSource(props, propertyPath, MenuItemsSource.DYNAMIC) ); }, dependencies: ["primaryColumns", "columnOrder"], diff --git a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Data.ts b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Data.ts index cf2809e41b..4a3a912eae 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Data.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/Data.ts @@ -10,6 +10,7 @@ import { hideByColumnType, showByColumnType, uniqueColumnAliasValidation, + updateMenuItemsSource, updateNumberColumnTypeTextAlignment, updateThemeStylesheetsInColumns, } from "../../propertyUtils"; @@ -78,6 +79,7 @@ export default { updateHook: composePropertyUpdateHook([ updateNumberColumnTypeTextAlignment, updateThemeStylesheetsInColumns, + updateMenuItemsSource, ]), dependencies: ["primaryColumns", "columnOrder", "childStylesheet"], isBindProperty: false, diff --git a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/TextFormatting.ts b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/TextFormatting.ts index 7f7ac2aa1c..7d86ed4b38 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/TextFormatting.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/TextFormatting.ts @@ -64,7 +64,7 @@ export default { propertyName: "fontStyle", label: "Emphasis", helpText: "Controls the style of the text in the column", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/childPanels/configureMenuItemsConfig.ts b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/childPanels/configureMenuItemsConfig.ts new file mode 100644 index 0000000000..3292e289e7 --- /dev/null +++ b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/PanelConfig/childPanels/configureMenuItemsConfig.ts @@ -0,0 +1,216 @@ +import { ValidationTypes } from "constants/WidgetValidation"; +import { AutocompleteDataType } from "utils/autocomplete/CodemirrorTernService"; +import { ICON_NAMES } from "widgets/MenuButtonWidget/constants"; +import { + booleanForEachRowValidation, + colorForEachRowValidation, + iconNamesForEachRowValidation, + iconPositionForEachRowValidation, + textForEachRowValidation, +} from "widgets/MenuButtonWidget/validations"; +import { getSourceDataAndCaluclateKeysForEventAutoComplete } from "widgets/TableWidgetV2/widget/utilities"; + +export default { + editableTitle: false, + titlePropertyName: "label", + panelIdPropertyName: "id", + contentChildren: [ + { + sectionName: "General", + children: [ + { + propertyName: "label", + helpText: + "Sets the label of a menu item using the {{currentItem}} binding.", + label: "Label", + controlType: "MENU_BUTTON_DYNAMIC_ITEMS", + placeholderText: "{{currentItem.name}}", + isBindProperty: true, + isTriggerProperty: false, + validation: { + type: ValidationTypes.FUNCTION, + params: { + expected: { + type: "Array of values", + example: `['option1', 'option2'] | [{ "label": "label1", "value": "value1" }]`, + autocompleteDataType: AutocompleteDataType.ARRAY, + }, + fnString: textForEachRowValidation.toString(), + }, + }, + evaluatedDependencies: ["primaryColumns"], + }, + { + propertyName: "isVisible", + helpText: + "Controls the visibility of the widget. Can also be configured the using {{currentItem}} binding.", + label: "Visible", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: { + type: ValidationTypes.FUNCTION, + params: { + fnString: booleanForEachRowValidation.toString(), + }, + }, + customJSControl: "MENU_BUTTON_DYNAMIC_ITEMS", + evaluatedDependencies: ["primaryColumns"], + }, + { + propertyName: "isDisabled", + helpText: + "Disables input to the widget. Can also be configured the using {{currentItem}} binding.", + label: "Disabled", + controlType: "SWITCH", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: false, + validation: { + type: ValidationTypes.FUNCTION, + params: { + fnString: booleanForEachRowValidation.toString(), + }, + }, + customJSControl: "MENU_BUTTON_DYNAMIC_ITEMS", + evaluatedDependencies: ["primaryColumns"], + }, + ], + }, + { + sectionName: "Events", + children: [ + { + helpText: + "Triggers an action when the menu item is clicked. Can also be configured the using {{currentItem}} binding.", + propertyName: "onClick", + label: "onClick", + controlType: "ACTION_SELECTOR", + isJSConvertible: true, + isBindProperty: true, + isTriggerProperty: true, + additionalAutoComplete: getSourceDataAndCaluclateKeysForEventAutoComplete, + evaluatedDependencies: ["primaryColumns"], + }, + ], + }, + ], + styleChildren: [ + { + sectionName: "Icon", + children: [ + { + propertyName: "iconName", + label: "Icon", + helpText: + "Sets the icon to be used for a menu item. Can also be configured the using {{currentItem}} binding.", + controlType: "ICON_SELECT", + isBindProperty: true, + isTriggerProperty: false, + isJSConvertible: true, + validation: { + type: ValidationTypes.FUNCTION, + params: { + allowedValues: ICON_NAMES, + fnString: iconNamesForEachRowValidation.toString(), + }, + }, + customJSControl: "MENU_BUTTON_DYNAMIC_ITEMS", + evaluatedDependencies: ["primaryColumns"], + }, + { + propertyName: "iconAlign", + label: "Position", + helpText: + "Sets the icon alignment of a menu item. Can also be configured the using {{currentItem}} binding.", + controlType: "ICON_TABS", + options: [ + { + icon: "VERTICAL_LEFT", + value: "left", + }, + { + icon: "VERTICAL_RIGHT", + value: "right", + }, + ], + isBindProperty: true, + isTriggerProperty: false, + isJSConvertible: true, + validation: { + type: ValidationTypes.FUNCTION, + params: { + allowedValues: ["center", "left", "right"], + fnString: iconPositionForEachRowValidation.toString(), + }, + }, + customJSControl: "MENU_BUTTON_DYNAMIC_ITEMS", + evaluatedDependencies: ["primaryColumns"], + }, + ], + }, + { + sectionName: "Color", + children: [ + { + propertyName: "iconColor", + helpText: + "Sets the icon color of a menu item. Can also be configured the using {{currentItem}} binding.", + label: "Icon color", + controlType: "COLOR_PICKER", + isBindProperty: true, + isTriggerProperty: false, + isJSConvertible: true, + customJSControl: "MENU_BUTTON_DYNAMIC_ITEMS", + evaluatedDependencies: ["primaryColumns"], + validation: { + type: ValidationTypes.FUNCTION, + params: { + regex: /^(?![<|{{]).+/, + fnString: colorForEachRowValidation.toString(), + }, + }, + }, + { + propertyName: "backgroundColor", + helpText: + "Sets the background color of a menu item. Can also be configured the using {{currentItem}} binding.", + label: "Background color", + controlType: "COLOR_PICKER", + isBindProperty: true, + isTriggerProperty: false, + isJSConvertible: true, + customJSControl: "MENU_BUTTON_DYNAMIC_ITEMS", + evaluatedDependencies: ["primaryColumns"], + validation: { + type: ValidationTypes.FUNCTION, + params: { + regex: /^(?![<|{{]).+/, + fnString: colorForEachRowValidation.toString(), + }, + }, + }, + { + propertyName: "textColor", + helpText: + "Sets the text color of a menu item. Can also be configured the using {{currentItem}} binding.", + label: "Text color", + controlType: "COLOR_PICKER", + isBindProperty: true, + isTriggerProperty: false, + isJSConvertible: true, + customJSControl: "MENU_BUTTON_DYNAMIC_ITEMS", + evaluatedDependencies: ["primaryColumns"], + validation: { + type: ValidationTypes.FUNCTION, + params: { + regex: /^(?![<|{{]).+/, + fnString: colorForEachRowValidation.toString(), + }, + }, + }, + ], + }, + ], +}; diff --git a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/styleConfig.ts b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/styleConfig.ts index 70617211be..4415f7f91f 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/styleConfig.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/propertyConfig/styleConfig.ts @@ -72,7 +72,7 @@ export default [ propertyName: "fontStyle", label: "Emphasis", helpText: "Controls the style of the text in the column", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", updateHook: updateColumnStyles, dependencies: ["primaryColumns"], options: [ diff --git a/app/client/src/widgets/TableWidgetV2/widget/propertyUtils.ts b/app/client/src/widgets/TableWidgetV2/widget/propertyUtils.ts index 56b594a7df..46c7985684 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/propertyUtils.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/propertyUtils.ts @@ -13,6 +13,7 @@ import { } from "utils/DynamicBindingUtils"; import { createEditActionColumn } from "./utilities"; import { PropertyHookUpdates } from "constants/PropertyControlConstants"; +import { MenuItemsSource } from "widgets/MenuButtonWidget/constants"; export function totalRecordsCountValidation( value: unknown, @@ -642,6 +643,86 @@ export const updateCustomColumnAliasOnLabelChange = ( } }; +export const hideByMenuItemsSource = ( + props: TableWidgetProps, + propertyPath: string, + menuItemsSource: MenuItemsSource, +) => { + const baseProperty = getBasePropertyPath(propertyPath); + const currentMenuItemsSource = get( + props, + `${baseProperty}.menuItemsSource`, + "", + ); + + return currentMenuItemsSource === menuItemsSource; +}; + +export const hideIfMenuItemsSourceDataIsFalsy = ( + props: TableWidgetProps, + propertyPath: string, +) => { + const baseProperty = getBasePropertyPath(propertyPath); + const sourceData = get(props, `${baseProperty}.sourceData`, ""); + + return !sourceData; +}; + +export const updateMenuItemsSource = ( + props: TableWidgetProps, + propertyPath: string, + propertyValue: unknown, +): Array<{ propertyPath: string; propertyValue: unknown }> | undefined => { + const propertiesToUpdate: Array<{ + propertyPath: string; + propertyValue: unknown; + }> = []; + const baseProperty = getBasePropertyPath(propertyPath); + const menuItemsSource = get(props, `${baseProperty}.menuItemsSource`); + + if (propertyValue === ColumnTypes.MENU_BUTTON && !menuItemsSource) { + // Sets the default value for menuItemsSource to static when + // selecting the menu button column type for the first time + propertiesToUpdate.push({ + propertyPath: `${baseProperty}.menuItemsSource`, + propertyValue: MenuItemsSource.STATIC, + }); + } else { + const sourceData = get(props, `${baseProperty}.sourceData`); + const configureMenuItems = get(props, `${baseProperty}.configureMenuItems`); + const isMenuItemsSourceChangedFromStaticToDynamic = + menuItemsSource === MenuItemsSource.STATIC && + propertyValue === MenuItemsSource.DYNAMIC; + + if (isMenuItemsSourceChangedFromStaticToDynamic) { + if (!sourceData) { + propertiesToUpdate.push({ + propertyPath: `${baseProperty}.sourceData`, + propertyValue: [], + }); + } + + if (!configureMenuItems) { + propertiesToUpdate.push({ + propertyPath: `${baseProperty}.configureMenuItems`, + propertyValue: { + label: "Configure Menu Items", + id: "config", + config: { + id: "config", + label: "Menu Item", + isVisible: true, + isDisabled: false, + }, + }, + }); + } + } + } + + return propertiesToUpdate?.length ? propertiesToUpdate : undefined; +}; + export function selectColumnOptionsValidation( value: unknown, props: TableWidgetProps, diff --git a/app/client/src/widgets/TableWidgetV2/widget/utilities.test.ts b/app/client/src/widgets/TableWidgetV2/widget/utilities.test.ts index c082031f8a..4fa85eb2e5 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/utilities.test.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/utilities.test.ts @@ -9,6 +9,7 @@ import { getOriginalRowIndex, getSelectRowIndex, getSelectRowIndices, + getSourceDataAndCaluclateKeysForEventAutoComplete, getTableStyles, reorderColumns, } from "./utilities"; @@ -1992,6 +1993,264 @@ describe("getColumnType", () => { }); }); +describe("getSourceDataAndCaluclateKeysForEventAutoComplete", () => { + it("Should test with valid values", () => { + const mockProps = { + type: "TABLE_WIDGET_V2", + widgetName: "Table1", + widgetId: "9oh3qyw84m", + primaryColumns: { + action: { + configureMenuItems: { + config: { + label: + "{{Table1.primaryColumns.action.sourceData.map((currentItem, currentIndex) => ( currentItem.))}}", + }, + }, + }, + }, + __evaluation__: { + errors: { + primaryColumns: [], + }, + evaluatedValues: { + primaryColumns: { + step: { + index: 0, + width: 150, + id: "step", + originalId: "step", + alias: "step", + columnType: "text", + label: "step", + computedValue: ["#1", "#2", "#3"], + validation: {}, + labelColor: "#FFFFFF", + }, + action: { + index: 3, + width: 150, + id: "action", + originalId: "action", + alias: "action", + columnType: "menuButton", + label: "action", + computedValue: ["", "", ""], + labelColor: "#FFFFFF", + buttonLabel: ["Action", "Action", "Action"], + menuColor: ["#553DE9", "#553DE9", "#553DE9"], + menuItemsSource: "DYNAMIC", + menuButtonLabel: ["Open Menu", "Open Menu", "Open Menu"], + sourceData: [ + { + gender: "male", + name: "Victor", + email: "victor.garrett@example.com", + }, + { + gender: "male", + name: "Tobias", + email: "tobias.hansen@example.com", + }, + { + gender: "female", + name: "Jane", + email: "jane.coleman@example.com", + }, + { + gender: "female", + name: "Yaromira", + email: "yaromira.manuylenko@example.com", + }, + { + gender: "male", + name: "Andre", + email: "andre.ortiz@example.com", + }, + ], + configureMenuItems: { + label: "Configure Menu Items", + id: "config", + config: { + id: "config", + label: ["male", "male", "female", "female", "male"], + isVisible: true, + isDisabled: false, + onClick: "", + backgroundColor: ["red", "red", "tan", "tan", "red"], + iconName: "add-row-top", + iconAlign: "right", + }, + }, + }, + }, + }, + }, + }; + + const result = getSourceDataAndCaluclateKeysForEventAutoComplete( + mockProps as any, + ); + const expected = { + currentItem: { + name: "", + email: "", + gender: "", + }, + }; + expect(result).toStrictEqual(expected); + }); + + it("Should test with empty sourceData", () => { + const mockProps = { + type: "TABLE_WIDGET_V2", + widgetName: "Table1", + widgetId: "9oh3qyw84m", + primaryColumns: { + action: { + configureMenuItems: { + config: { + label: + "{{Table1.primaryColumns.action.sourceData.map((currentItem, currentIndex) => ( currentItem.))}}", + }, + }, + }, + }, + __evaluation__: { + errors: { + primaryColumns: [], + }, + evaluatedValues: { + primaryColumns: { + step: { + index: 0, + width: 150, + id: "step", + originalId: "step", + alias: "step", + columnType: "text", + label: "step", + computedValue: ["#1", "#2", "#3"], + validation: {}, + labelColor: "#FFFFFF", + }, + action: { + index: 3, + width: 150, + id: "action", + originalId: "action", + alias: "action", + columnType: "menuButton", + label: "action", + computedValue: ["", "", ""], + labelColor: "#FFFFFF", + buttonLabel: ["Action", "Action", "Action"], + menuColor: ["#553DE9", "#553DE9", "#553DE9"], + menuItemsSource: "DYNAMIC", + menuButtonLabel: ["Open Menu", "Open Menu", "Open Menu"], + sourceData: [], + configureMenuItems: { + label: "Configure Menu Items", + id: "config", + config: { + id: "config", + label: ["male", "male", "female", "female", "male"], + isVisible: true, + isDisabled: false, + onClick: "", + backgroundColor: ["red", "red", "tan", "tan", "red"], + iconName: "add-row-top", + iconAlign: "right", + }, + }, + }, + }, + }, + }, + }; + + const result = getSourceDataAndCaluclateKeysForEventAutoComplete( + mockProps as any, + ); + const expected = { currentItem: {} }; + expect(result).toStrictEqual(expected); + }); + + it("Should test without sourceData", () => { + const mockProps = { + type: "TABLE_WIDGET_V2", + widgetName: "Table1", + widgetId: "9oh3qyw84m", + primaryColumns: { + action: { + configureMenuItems: { + config: { + label: + "{{Table1.primaryColumns.action.sourceData.map((currentItem, currentIndex) => ( currentItem.))}}", + }, + }, + }, + }, + __evaluation__: { + errors: { + primaryColumns: [], + }, + evaluatedValues: { + primaryColumns: { + step: { + index: 0, + width: 150, + id: "step", + originalId: "step", + alias: "step", + columnType: "text", + label: "step", + computedValue: ["#1", "#2", "#3"], + validation: {}, + labelColor: "#FFFFFF", + }, + action: { + index: 3, + width: 150, + id: "action", + originalId: "action", + alias: "action", + columnType: "menuButton", + label: "action", + computedValue: ["", "", ""], + labelColor: "#FFFFFF", + buttonLabel: ["Action", "Action", "Action"], + menuColor: ["#553DE9", "#553DE9", "#553DE9"], + menuItemsSource: "DYNAMIC", + menuButtonLabel: ["Open Menu", "Open Menu", "Open Menu"], + configureMenuItems: { + label: "Configure Menu Items", + id: "config", + config: { + id: "config", + label: ["male", "male", "female", "female", "male"], + isVisible: true, + isDisabled: false, + onClick: "", + backgroundColor: ["red", "red", "tan", "tan", "red"], + iconName: "add-row-top", + iconAlign: "right", + }, + }, + }, + }, + }, + }, + }; + + const result = getSourceDataAndCaluclateKeysForEventAutoComplete( + mockProps as any, + ); + const expected = { currentItem: {} }; + expect(result).toStrictEqual(expected); + }); +}); + describe("getArrayPropertyValue", () => { it("should test that it returns the same value when value is not of expected type", () => { expect(getArrayPropertyValue("test", 1)).toEqual("test"); diff --git a/app/client/src/widgets/TableWidgetV2/widget/utilities.ts b/app/client/src/widgets/TableWidgetV2/widget/utilities.ts index 0bf199d7bc..c6cac6e1eb 100644 --- a/app/client/src/widgets/TableWidgetV2/widget/utilities.ts +++ b/app/client/src/widgets/TableWidgetV2/widget/utilities.ts @@ -27,6 +27,7 @@ import { ButtonVariantTypes } from "components/constants"; import { dateFormatOptions } from "widgets/constants"; import moment from "moment"; import { Stylesheet } from "entities/AppTheming"; +import { getKeysFromSourceDataForEventAutocomplete } from "widgets/MenuButtonWidget/widget/helper"; type TableData = Array>; @@ -234,12 +235,19 @@ export const getPropertyValue = ( value: any, index: number, preserveCase = false, + isSourceData = false, ) => { if (value && isObject(value) && !Array.isArray(value)) { return value; } if (value && Array.isArray(value) && value[index]) { - return preserveCase + const getValueForSourceData = (value: any, index: number) => { + return Array.isArray(value[index]) ? value[index] : value; + }; + + return isSourceData + ? getValueForSourceData(value, index) + : preserveCase ? value[index].toString() : value[index].toString().toUpperCase(); } else if (value) { @@ -307,6 +315,18 @@ export const getCellProperties = ( rowIndex, true, ), + menuItemsSource: getPropertyValue( + columnProperties.menuItemsSource, + rowIndex, + true, + ), + sourceData: getPropertyValue( + columnProperties.sourceData, + rowIndex, + false, + true, + ), + configureMenuItems: columnProperties.configureMenuItems, buttonVariant: getPropertyValue( columnProperties.buttonVariant, rowIndex, @@ -697,3 +717,22 @@ export const getColumnType = ( return ColumnTypes.TEXT; } }; + +export const getSourceDataAndCaluclateKeysForEventAutoComplete = ( + props: TableWidgetProps, +): unknown => { + const { __evaluation__, primaryColumns } = props; + const primaryColumnKeys = primaryColumns ? Object.keys(primaryColumns) : []; + const columnName = primaryColumnKeys?.length ? primaryColumnKeys[0] : ""; + const evaluatedColumns: any = __evaluation__?.evaluatedValues?.primaryColumns; + + if (evaluatedColumns) { + const result = getKeysFromSourceDataForEventAutocomplete( + evaluatedColumns[columnName]?.sourceData || [], + ); + + return result; + } else { + return {}; + } +}; diff --git a/app/client/src/widgets/TextWidget/component/index.tsx b/app/client/src/widgets/TextWidget/component/index.tsx index a79f111d1c..5e3a4a65aa 100644 --- a/app/client/src/widgets/TextWidget/component/index.tsx +++ b/app/client/src/widgets/TextWidget/component/index.tsx @@ -144,7 +144,6 @@ export const StyledText = styled(Text)<{ width: 100%; line-height: 1.2; white-space: pre-wrap; - display: block; text-align: ${(props) => props.textAlign.toLowerCase()}; } `; diff --git a/app/client/src/widgets/TextWidget/widget/index.tsx b/app/client/src/widgets/TextWidget/widget/index.tsx index 3acf7fc42a..a1bc8d945b 100644 --- a/app/client/src/widgets/TextWidget/widget/index.tsx +++ b/app/client/src/widgets/TextWidget/widget/index.tsx @@ -313,7 +313,7 @@ class TextWidget extends BaseWidget { propertyName: "fontStyle", label: "Emphasis", helpText: "Controls the font emphasis of the text displayed", - controlType: "BUTTON_TABS", + controlType: "BUTTON_GROUP", options: [ { icon: "BOLD_FONT", diff --git a/app/client/src/widgets/VideoWidget/component/PopoverVideo.tsx b/app/client/src/widgets/VideoWidget/component/PopoverVideo.tsx index 888ab847ee..0952002fd4 100644 --- a/app/client/src/widgets/VideoWidget/component/PopoverVideo.tsx +++ b/app/client/src/widgets/VideoWidget/component/PopoverVideo.tsx @@ -20,7 +20,7 @@ const PlayIcon = styled(ControlIcons.PLAY_VIDEO as AnyStyledComponent)` } `; -const PlayerWrapper = styled.div` import React, { Ref } from "react"; +const PlayerWrapper = styled.div` width: 600px; height: 400px; `; diff --git a/app/client/src/workers/Evaluation/JSObject/JSProxy.ts b/app/client/src/workers/Evaluation/JSObject/JSProxy.ts new file mode 100644 index 0000000000..7f168f56c5 --- /dev/null +++ b/app/client/src/workers/Evaluation/JSObject/JSProxy.ts @@ -0,0 +1,123 @@ +import { isEmpty, set } from "lodash"; +import { MessageType, sendMessage } from "utils/MessageUtil"; +import { MAIN_THREAD_ACTION } from "../evalWorkerActions"; +import { isPromise } from "./utils"; + +export interface JSExecutionData { + data: unknown; + funcName: string; +} + +export type JSFunctionProxy = ( + JSFunction: (...args: unknown[]) => unknown, + fullName: string, +) => unknown; + +export const JSFunctionProxyHandler = ( + fullName: string, + funcExecutionStart: () => void, + funcExecutionEnd: (data: JSExecutionData) => void, +) => ({ + apply: function(target: any, thisArg: unknown, argumentsList: any) { + funcExecutionStart(); + let returnValue; + try { + returnValue = Reflect.apply(target, thisArg, argumentsList); + } catch (e) { + funcExecutionEnd({ + data: undefined, + funcName: fullName, + }); + throw e; + } + + if (isPromise(returnValue)) { + returnValue + .then((result) => { + funcExecutionEnd({ + data: result, + funcName: fullName, + }); + }) + .catch(() => { + funcExecutionEnd({ + data: undefined, + funcName: fullName, + }); + }); + return returnValue; + } + funcExecutionEnd({ + data: returnValue, + funcName: fullName, + }); + return returnValue; + }, +}); + +export class JSProxy { + // Holds list of all js execution data during an eval cycle + private dataStore: Record = {}; + // The number of functions called, which have not completed. + private pendingExecutionCount = 0; + // Has eval completed? + private evaluationEnded = false; + + constructor() { + this.JSFunctionProxy = this.JSFunctionProxy.bind(this); + this.postData = this.postData.bind(this); + this.functionExecutionStart = this.functionExecutionStart.bind(this); + this.functionExecutionEnd = this.functionExecutionEnd.bind(this); + this.setEvaluationEnd = this.setEvaluationEnd.bind(this); + } + + public setEvaluationEnd(val: boolean) { + this.evaluationEnded = val; + this.postData(); + } + + // When a function is called, increase number of pending functions; + private functionExecutionStart() { + this.pendingExecutionCount += 1; + this.postData(); + } + + // When a function has completed, decrease number of pending functions; + private functionExecutionEnd({ data, funcName }: JSExecutionData) { + set(this.dataStore, [funcName], data); + this.pendingExecutionCount -= 1; + this.postData(); + } + + private postData() { + // This method determines the right time to post message to the main thread + // We ensure that all function executions have completed. + const { dataStore, evaluationEnded, pendingExecutionCount } = this; + if (evaluationEnded && pendingExecutionCount === 0 && !isEmpty(dataStore)) { + sendMessage.call(self, { + messageType: MessageType.DEFAULT, + body: { + data: { + JSData: dataStore, + }, + method: MAIN_THREAD_ACTION.PROCESS_JS_FUNCTION_EXECUTION, + }, + }); + } + } + + // Wrapper around JS Functions + public JSFunctionProxy( + JSFunction: (...args: unknown[]) => unknown, + jsFunctionFullName: string, + ) { + return new Proxy( + JSFunction, + JSFunctionProxyHandler( + jsFunctionFullName, + this.functionExecutionStart, + this.functionExecutionEnd, + ), + ); + } +} diff --git a/app/client/src/workers/Evaluation/JSObject/index.ts b/app/client/src/workers/Evaluation/JSObject/index.ts index 6393dddf87..b05cde68b5 100644 --- a/app/client/src/workers/Evaluation/JSObject/index.ts +++ b/app/client/src/workers/Evaluation/JSObject/index.ts @@ -29,9 +29,9 @@ export const getUpdatedLocalUnEvalTreeAfterJSUpdates = ( localUnEvalTree: DataTree, ) => { if (!isEmpty(jsUpdates)) { - Object.keys(jsUpdates).forEach((jsEntity) => { - const entity = localUnEvalTree[jsEntity]; - const parsedBody = jsUpdates[jsEntity].parsedBody; + Object.keys(jsUpdates).forEach((jsEntityName) => { + const entity = localUnEvalTree[jsEntityName]; + const parsedBody = jsUpdates[jsEntityName].parsedBody; if (isJSAction(entity)) { if (!!parsedBody) { //add/delete/update functions from dataTree @@ -45,6 +45,7 @@ export const getUpdatedLocalUnEvalTreeAfterJSUpdates = ( localUnEvalTree = removeFunctionsAndVariableJSCollection( localUnEvalTree, entity, + jsEntityName, ); } } @@ -173,7 +174,7 @@ export function saveResolvedFunctionsAndJSUpdates( type: EvalErrorTypes.PARSE_JS_ERROR, context: { entity: entity, - propertyPath: entity.name + ".body", + propertyPath: entityName + ".body", }, message: "Start object with export default", }; @@ -196,9 +197,7 @@ export function parseJSActions( ); const entity = unEvalDataTree[entityName]; - if (!isJSAction(entity)) { - return false; - } + if (!isJSAction(entity)) return false; if (diff.event === DataTreeDiffEvent.DELETE) { // when JSObject is deleted, we remove it from currentJSCollectionState & resolvedFunctions @@ -275,10 +274,10 @@ export function parseJSActions( export function getJSEntities(dataTree: DataTree) { const jsCollections: Record = {}; - Object.keys(dataTree).forEach((key: string) => { - const entity = dataTree[key]; + Object.keys(dataTree).forEach((entityName: string) => { + const entity = dataTree[entityName]; if (isJSAction(entity)) { - jsCollections[entity.name] = entity; + jsCollections[entityName] = entity; } }); return jsCollections; diff --git a/app/client/src/workers/Evaluation/JSObject/utils.ts b/app/client/src/workers/Evaluation/JSObject/utils.ts index 0dcb27a601..0dfcc177b2 100644 --- a/app/client/src/workers/Evaluation/JSObject/utils.ts +++ b/app/client/src/workers/Evaluation/JSObject/utils.ts @@ -1,11 +1,18 @@ import { DataTree, + DataTreeAppsmith, DataTreeJSAction, EvaluationSubstitutionType, } from "entities/DataTree/dataTreeFactory"; import { ParsedBody, ParsedJSSubAction } from "utils/JSPaneUtils"; import { unset, set, get } from "lodash"; +import { BatchedJSExecutionData } from "reducers/entityReducers/jsActionsReducer"; +import { select } from "redux-saga/effects"; +import { AppState } from "@appsmith/reducers"; +import { JSAction } from "entities/JSCollection"; +import { getJSFunctionFromName } from "selectors/entitiesSelector"; import { isJSAction } from "@appsmith/workers/Evaluation/evaluationUtils"; +import { APP_MODE } from "entities/App"; /** * here we add/remove the properties (variables and actions) which got added/removed from the JSObject parsedBody. @@ -190,6 +197,7 @@ export const updateJSCollectionInUnEvalTree = ( export const removeFunctionsAndVariableJSCollection = ( unEvalTree: DataTree, entity: DataTreeJSAction, + jsEntityName: string, ) => { const oldConfig = Object.getPrototypeOf(entity) as DataTreeJSAction; const modifiedDataTree: DataTree = unEvalTree; @@ -199,10 +207,10 @@ export const removeFunctionsAndVariableJSCollection = ( }); //removed variables const varList: Array = entity.variables; - set(modifiedDataTree, `${entity.name}.variables`, []); + set(modifiedDataTree, `${jsEntityName}.variables`, []); for (let i = 0; i < varList.length; i++) { const varName = varList[i]; - unset(modifiedDataTree[entity.name], varName); + unset(modifiedDataTree[jsEntityName], varName); } //remove functions @@ -213,7 +221,7 @@ export const removeFunctionsAndVariableJSCollection = ( const actionName = functionsList[i]; delete reactivePaths[actionName]; delete meta[actionName]; - unset(modifiedDataTree[entity.name], actionName); + unset(modifiedDataTree[jsEntityName], actionName); oldConfig.dynamicBindingPathList = oldConfig.dynamicBindingPathList.filter( (path: any) => path["key"] !== actionName, @@ -238,3 +246,44 @@ export function isJSObjectFunction( } return false; } + +export function getAppMode(dataTree: DataTree) { + const appsmithObj = dataTree.appsmith as DataTreeAppsmith; + return appsmithObj.mode as APP_MODE; +} + +export function isPromise(value: any): value is Promise { + return Boolean(value && typeof value.then === "function"); +} + +export function* sortJSExecutionDataByCollectionId( + data: Record, +) { + // Sorted data by collectionId + const sortedData: BatchedJSExecutionData = {}; + for (const jsfuncFullName of Object.keys(data)) { + const jsAction: JSAction | undefined = yield select((state: AppState) => + getJSFunctionFromName(state, jsfuncFullName), + ); + + if (jsAction && jsAction.collectionId) { + if (sortedData[jsAction.collectionId]) { + sortedData[jsAction.collectionId].push({ + data: get(data, jsfuncFullName), + collectionId: jsAction.collectionId, + actionId: jsAction.id, + }); + } else { + sortedData[jsAction.collectionId] = [ + { + data: get(data, jsfuncFullName), + collectionId: jsAction.collectionId, + actionId: jsAction.id, + }, + ]; + } + } + } + + return sortedData; +} diff --git a/app/client/src/workers/Evaluation/evalWorkerActions.ts b/app/client/src/workers/Evaluation/evalWorkerActions.ts index 3cd1aafc2a..248400e34f 100644 --- a/app/client/src/workers/Evaluation/evalWorkerActions.ts +++ b/app/client/src/workers/Evaluation/evalWorkerActions.ts @@ -30,4 +30,5 @@ export const MAIN_THREAD_ACTION = { PROCESS_TRIGGER: "PROCESS_TRIGGER", PROCESS_LOGS: "PROCESS_LOGS", LINT_TREE: "LINT_TREE", + PROCESS_JS_FUNCTION_EXECUTION: "PROCESS_JS_FUNCTION_EXECUTION", }; diff --git a/app/client/src/workers/Evaluation/evaluate.ts b/app/client/src/workers/Evaluation/evaluate.ts index 6487e9d7bd..a652108b35 100644 --- a/app/client/src/workers/Evaluation/evaluate.ts +++ b/app/client/src/workers/Evaluation/evaluate.ts @@ -11,6 +11,7 @@ import userLogs from "./UserLog"; import { EventType } from "constants/AppsmithActionConstants/ActionConstants"; import { TriggerMeta } from "@appsmith/sagas/ActionExecution/ActionExecutionSagas"; import indirectEval from "./indirectEval"; +import { JSFunctionProxy, JSProxy } from "./JSObject/JSProxy"; import { DOM_APIS } from "./SetupDOM"; import { JSLibraries, libraryReservedIdentifiers } from "../common/JSLibrary"; import { errorModifier, FoundPromiseInSyncEvalError } from "./errorModifier"; @@ -130,6 +131,7 @@ export interface createEvaluationContextArgs { evalArguments?: Array; // Whether not to add functions like "run", "clear" to entity in global data skipEntityFunctions?: boolean; + JSFunctionProxy?: JSFunctionProxy; } /** * This method created an object with dataTree and appsmith's framework actions that needs to be added to worker global scope for the JS code evaluation to then consume it. @@ -143,6 +145,7 @@ export const createEvaluationContext = (args: createEvaluationContextArgs) => { dataTree, evalArguments, isTriggerBased, + JSFunctionProxy, resolvedFunctions, skipEntityFunctions, } = args; @@ -165,7 +168,7 @@ export const createEvaluationContext = (args: createEvaluationContextArgs) => { isTriggerBased, }); - assignJSFunctionsToContext(EVAL_CONTEXT, resolvedFunctions); + assignJSFunctionsToContext(EVAL_CONTEXT, resolvedFunctions, JSFunctionProxy); return EVAL_CONTEXT; }; @@ -173,6 +176,7 @@ export const createEvaluationContext = (args: createEvaluationContextArgs) => { export const assignJSFunctionsToContext = ( EVAL_CONTEXT: EvalContext, resolvedFunctions: ResolvedFunctions, + JSFunctionProxy?: JSFunctionProxy, ) => { const jsObjectNames = Object.keys(resolvedFunctions || {}); for (const jsObjectName of jsObjectNames) { @@ -187,7 +191,9 @@ export const assignJSFunctionsToContext = ( // Task: https://github.com/appsmithorg/appsmith/issues/13289 // Previous implementation commented code: https://github.com/appsmithorg/appsmith/pull/18471 const data = jsObject[fnName]?.data; - jsObjectFunction[fnName] = fn; + jsObjectFunction[fnName] = JSFunctionProxy + ? JSFunctionProxy(fn, jsObjectName + "." + fnName) + : fn; if (!!data) { jsObjectFunction[fnName]["data"] = data; } @@ -311,7 +317,6 @@ export default function evaluateSync( } } } - return { result, errors, logs }; })(); } @@ -328,6 +333,7 @@ export async function evaluateAsync( const errors: EvaluationError[] = []; let result; let logs; + const { JSFunctionProxy, setEvaluationEnd } = new JSProxy(); /**** Setting the eval context ****/ userLogs.resetLogs(); userLogs.setCurrentRequestInfo({ @@ -339,6 +345,7 @@ export async function evaluateAsync( resolvedFunctions, context, evalArguments, + JSFunctionProxy, isTriggerBased: true, }); const { script } = getUserScriptToEvaluate(userScript, true, evalArguments); @@ -352,10 +359,11 @@ export async function evaluateAsync( try { result = await indirectEval(script); logs = userLogs.flushLogs(); - } catch (error) { - const errorMessage = `UncaughtPromiseRejection: ${ - (error as Error).message - }`; + } catch (e) { + const error = e as Error; + const errorMessage = error.name + ? `${error.name}: ${error.message}` + : `UncaughtPromiseRejection: ${error.message}`; errors.push({ errorMessage: errorMessage, severity: Severity.ERROR, @@ -365,6 +373,7 @@ export async function evaluateAsync( }); logs = userLogs.flushLogs(); } finally { + setEvaluationEnd(true); // Adding this extra try catch because there are cases when logs have child objects // like functions or promises that cause issue in complete promise action, thus // leading the app into a bad state. diff --git a/app/client/src/workers/Evaluation/validations.ts b/app/client/src/workers/Evaluation/validations.ts index aa18e08a68..90fc11101d 100644 --- a/app/client/src/workers/Evaluation/validations.ts +++ b/app/client/src/workers/Evaluation/validations.ts @@ -942,7 +942,7 @@ export const VALIDATORS: Record = { {}, false, undefined, - [value, props, _, moment, propertyPath], + [value, props, _, moment, propertyPath, config], ); return result; } catch (e) { diff --git a/app/client/yarn.lock b/app/client/yarn.lock index e1f5672630..2a22a7463b 100644 --- a/app/client/yarn.lock +++ b/app/client/yarn.lock @@ -6202,10 +6202,10 @@ depd@~1.1.2: version "1.1.2" resolved "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz" -"design-system@npm:@appsmithorg/design-system@1.0.41": - version "1.0.41" - resolved "https://registry.yarnpkg.com/@appsmithorg/design-system/-/design-system-1.0.41.tgz#983f4ca2f99825842f80fd2b47cbaa14e67ea5c0" - integrity sha512-p99nx2N3pHHDioEH46vlmCyKmQXv3gGh3NGHlqbj3J2XTxReQypkQXcLxrgHC9/9LWvu5jU7ZLedoblSdIghgw== +"design-system@npm:@appsmithorg/design-system@1.0.44": + version "1.0.44" + resolved "https://registry.yarnpkg.com/@appsmithorg/design-system/-/design-system-1.0.44.tgz#22c061dada942ff947933bc92e96fff55fc006ba" + integrity sha512-HjqKtvIAdW2TkaG7iRc5HB4ieG+V1zTp9b2N98DCsNXjMzBUCvCH2afBzlDYYoL3r5GHAeMvZlTDFVME41I2yQ== dependencies: emoji-mart "3.0.1" diff --git a/app/rts/yarn.lock b/app/rts/yarn.lock index a14586637c..cfdb0188c4 100644 --- a/app/rts/yarn.lock +++ b/app/rts/yarn.lock @@ -2331,9 +2331,9 @@ json-parse-even-better-errors@^2.3.0: integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json5@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + version "2.2.2" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.2.tgz#64471c5bdcc564c18f7c1d4df2e2297f2457c5ab" + integrity sha512-46Tk9JiOL2z7ytNQWFLpj99RZkVgeHf87yGQKsIkaPz1qSH9UczKH1rO7K3wgRselo0tYMUNfecYpm/p1vC7tQ== kleur@^3.0.3: version "3.0.3" diff --git a/app/server/.run/ServerApplication.run.xml b/app/server/.run/ServerApplication.run.xml index 9948d93429..ae3fda8ef5 100644 --- a/app/server/.run/ServerApplication.run.xml +++ b/app/server/.run/ServerApplication.run.xml @@ -1,9 +1,11 @@ + - + \ No newline at end of file diff --git a/app/server/Dockerfile b/app/server/Dockerfile index c3a9382073..edd069498e 100644 --- a/app/server/Dockerfile +++ b/app/server/Dockerfile @@ -1,12 +1,12 @@ #When you are building, name it appsmith-server which is how it is referenced in docker-compose.yml -FROM adoptopenjdk/openjdk11:jdk-11.0.10_9-alpine +FROM eclipse-temurin:17-jdk-alpine as jdk-image RUN ${JAVA_HOME}/bin/jlink --module-path jmods --add-modules jdk.jcmd --output /jcmd -FROM adoptopenjdk/openjdk11:jre-11.0.10_9-alpine +FROM eclipse-temurin:17-jre-alpine -COPY --from=0 /jcmd /jcmd +COPY --from=jdk-image /jcmd /jcmd LABEL maintainer="tech@appsmith.com" diff --git a/app/server/appsmith-git/pom.xml b/app/server/appsmith-git/pom.xml index 0fcbe8561b..31a4b9de3c 100644 --- a/app/server/appsmith-git/pom.xml +++ b/app/server/appsmith-git/pom.xml @@ -17,11 +17,6 @@ appsmith-git This is the git server to handle all the git operations - - UTF-8 - 11 - - jgit-repository @@ -54,14 +49,16 @@ org.eclipse.jgit org.eclipse.jgit.junit.ssh - 5.13.0.202109080827-r + 6.4.0.202211300538-r + test + io.projectreactor reactor-test - 3.2.11.RELEASE + ${reactor-test.version} test diff --git a/app/server/appsmith-git/src/main/java/com/appsmith/git/helpers/FileUtilsImpl.java b/app/server/appsmith-git/src/main/java/com/appsmith/git/helpers/FileUtilsImpl.java index 1117c15c41..ba6cc32bf3 100644 --- a/app/server/appsmith-git/src/main/java/com/appsmith/git/helpers/FileUtilsImpl.java +++ b/app/server/appsmith-git/src/main/java/com/appsmith/git/helpers/FileUtilsImpl.java @@ -1,6 +1,6 @@ package com.appsmith.git.helpers; -import com.appsmith.external.converters.GsonISOStringToInstantConverter; +import com.appsmith.external.converters.ISOStringToInstantConverter; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; import com.appsmith.external.git.FileInterface; @@ -152,7 +152,7 @@ public class FileUtilsImpl implements FileInterface { .registerTypeAdapter(Double.class, new GsonDoubleToLongConverter()) .registerTypeAdapter(Set.class, new GsonUnorderedToOrderedConverter()) .registerTypeAdapter(Map.class, new GsonUnorderedToOrderedConverter()) - .registerTypeAdapter(Instant.class, new GsonISOStringToInstantConverter()) + .registerTypeAdapter(Instant.class, new ISOStringToInstantConverter()) .disableHtmlEscaping() .setPrettyPrinting() .create(); diff --git a/app/server/appsmith-interfaces/pom.xml b/app/server/appsmith-interfaces/pom.xml index bc4428b329..23a28b9a1f 100644 --- a/app/server/appsmith-interfaces/pom.xml +++ b/app/server/appsmith-interfaces/pom.xml @@ -15,8 +15,7 @@ interfaces - UTF-8 - 11 + 0.11.5 @@ -49,7 +48,14 @@ org.pf4j pf4j - 3.5.0 + 3.8.0 + + + + org.springframework.data + spring-data-mongodb + 4.0.0 + compile @@ -60,7 +66,7 @@ org.json json - 20190722 + 20220924 @@ -80,6 +86,11 @@ jackson-databind compile + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + compile + com.google.code.gson gson @@ -132,13 +143,12 @@ org.eclipse.jgit org.eclipse.jgit - 5.13.0.202109080827-r + 6.4.0.202211300538-r compile com.h2database h2 - ${h2.version} org.springframework @@ -152,7 +162,6 @@ io.projectreactor.netty reactor-netty-http - 1.0.17 io.projectreactor @@ -172,23 +181,24 @@ io.jsonwebtoken jjwt-api - 0.11.2 + ${jjwt.version} io.jsonwebtoken jjwt-impl - 0.11.2 + ${jjwt.version} io.jsonwebtoken jjwt-jackson - 0.11.2 + ${jjwt.version} io.projectreactor reactor-test + ${reactor-test.version} test diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/converters/HttpMethodConverter.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/converters/HttpMethodConverter.java new file mode 100644 index 0000000000..e736966b69 --- /dev/null +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/converters/HttpMethodConverter.java @@ -0,0 +1,52 @@ +package com.appsmith.external.converters; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.SerializerProvider; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import org.springframework.http.HttpMethod; + +import java.io.IOException; +import java.lang.reflect.Type; + +public class HttpMethodConverter implements JsonSerializer, JsonDeserializer { + @Override + public HttpMethod deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { + return HttpMethod.valueOf(jsonElement.getAsString()); + } + + @Override + public JsonElement serialize(HttpMethod httpMethod, Type type, JsonSerializationContext jsonSerializationContext) { + return new JsonPrimitive(httpMethod.name()); + } + + public static class HttpMethodModule extends SimpleModule { + public HttpMethodModule() { + this.addSerializer(HttpMethod.class, new HttpMethodSerializer()); + this.addDeserializer(HttpMethod.class, new HttpMethodDeserializer()); + } + } + + public static class HttpMethodSerializer extends com.fasterxml.jackson.databind.JsonSerializer { + @Override + public void serialize(HttpMethod httpMethod, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) throws IOException { + jsonGenerator.writeString(httpMethod.name()); + } + } + + public static class HttpMethodDeserializer extends com.fasterxml.jackson.databind.JsonDeserializer { + + @Override + public HttpMethod deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException { + return HttpMethod.valueOf(deserializationContext.readValue(jsonParser, String.class)); + } + } +} diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/converters/GsonISOStringToInstantConverter.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/converters/ISOStringToInstantConverter.java similarity index 84% rename from app/server/appsmith-interfaces/src/main/java/com/appsmith/external/converters/GsonISOStringToInstantConverter.java rename to app/server/appsmith-interfaces/src/main/java/com/appsmith/external/converters/ISOStringToInstantConverter.java index eb4f3191fd..964dca7cc2 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/converters/GsonISOStringToInstantConverter.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/converters/ISOStringToInstantConverter.java @@ -14,12 +14,12 @@ import java.time.Instant; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; -public class GsonISOStringToInstantConverter implements JsonSerializer, JsonDeserializer { - private DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssX").withZone(ZoneOffset.UTC); +public class ISOStringToInstantConverter implements JsonSerializer, JsonDeserializer { + private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ssX").withZone(ZoneOffset.UTC); @Override public Instant deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException { - if(jsonElement.isJsonNull()) { + if (jsonElement.isJsonNull()) { return null; } if (jsonElement.isJsonPrimitive()) { @@ -46,15 +46,11 @@ public class GsonISOStringToInstantConverter implements JsonSerializer, JsonElement nanoElt = timeObject.get("nano"); Long epochSecond = (epochSecondElt != null ? epochSecondElt.getAsLong() : null); Long nano = (nanoElt != null ? nanoElt.getAsLong() : null); - if (epochSecond != null) - { - if (nano != null) - { + if (epochSecond != null) { + if (nano != null) { Instant val = Instant.ofEpochSecond(epochSecond, nano); return val; - } - else - { + } else { Instant val = Instant.ofEpochSecond(epochSecond); return val; } diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/DataUtils.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/DataUtils.java index 77165a9af7..a87a506a44 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/DataUtils.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/DataUtils.java @@ -75,6 +75,8 @@ public class DataUtils { return BodyInserters.fromValue(formData); case MediaType.MULTIPART_FORM_DATA_VALUE: return parseMultipartFileData((List) body); + case MediaType.TEXT_PLAIN_VALUE: + return BodyInserters.fromValue((String) body); default: return BodyInserters.fromValue(((String) body).getBytes(StandardCharsets.ISO_8859_1)); } diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/RestAPIActivateUtils.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/RestAPIActivateUtils.java index 4ef49905f0..c93338613c 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/RestAPIActivateUtils.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/helpers/restApiUtils/helpers/RestAPIActivateUtils.java @@ -21,7 +21,7 @@ import lombok.NoArgsConstructor; import org.bson.internal.Base64; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; import org.springframework.http.client.reactive.ClientHttpRequest; import org.springframework.web.reactive.function.BodyInserter; @@ -58,11 +58,11 @@ public class RestAPIActivateUtils { public static HeaderUtils headerUtils = new HeaderUtils(); public Mono triggerApiCall(WebClient client, HttpMethod httpMethod, URI uri, - Object requestBody, - ActionExecutionRequest actionExecutionRequest, - ObjectMapper objectMapper, Set hintMessages, - ActionExecutionResult errorResult, - RequestCaptureFilter requestCaptureFilter) { + Object requestBody, + ActionExecutionRequest actionExecutionRequest, + ObjectMapper objectMapper, Set hintMessages, + ActionExecutionResult errorResult, + RequestCaptureFilter requestCaptureFilter) { return httpCall(client, httpMethod, uri, requestBody, 0) .flatMap(clientResponse -> clientResponse.toEntity(byte[].class)) .map(stringResponseEntity -> { @@ -78,7 +78,7 @@ public class RestAPIActivateUtils { contentType = MediaType.TEXT_PLAIN; } byte[] body = stringResponseEntity.getBody(); - HttpStatus statusCode = stringResponseEntity.getStatusCode(); + HttpStatusCode statusCode = stringResponseEntity.getStatusCode(); ActionExecutionResult result = new ActionExecutionResult(); @@ -174,7 +174,7 @@ public class RestAPIActivateUtils { } protected Mono httpCall(WebClient webClient, HttpMethod httpMethod, URI uri, Object requestBody, - int iteration) { + int iteration) { if (iteration == MAX_REDIRECTS) { return Mono.error(new AppsmithPluginException( AppsmithPluginError.PLUGIN_ERROR, @@ -215,8 +215,8 @@ public class RestAPIActivateUtils { } public WebClient getWebClient(WebClient.Builder webClientBuilder, APIConnection apiConnection, - String reqContentType, ObjectMapper objectMapper, - ExchangeStrategies EXCHANGE_STRATEGIES, RequestCaptureFilter requestCaptureFilter) { + String reqContentType, ObjectMapper objectMapper, + ExchangeStrategies EXCHANGE_STRATEGIES, RequestCaptureFilter requestCaptureFilter) { // Right before building the webclient object, we populate it with whatever mutation the APIConnection object demands if (apiConnection != null) { webClientBuilder.filter(apiConnection); @@ -232,7 +232,7 @@ public class RestAPIActivateUtils { } public WebClient.Builder getWebClientBuilder(ActionConfiguration actionConfiguration, - DatasourceConfiguration datasourceConfiguration) { + DatasourceConfiguration datasourceConfiguration) { HttpClient httpClient = getHttpClient(datasourceConfiguration); WebClient.Builder webClientBuilder = WebClientUtils.builder(httpClient); addAllHeaders(webClientBuilder, actionConfiguration, datasourceConfiguration); @@ -242,7 +242,7 @@ public class RestAPIActivateUtils { } protected void addSecretKey(WebClient.Builder webClientBuilder, - DatasourceConfiguration datasourceConfiguration) throws AppsmithPluginException { + DatasourceConfiguration datasourceConfiguration) throws AppsmithPluginException { // If users have chosen to share the Appsmith signature in the header, calculate and add that String secretKey; secretKey = headerUtils.getSignatureKey(datasourceConfiguration); @@ -262,7 +262,7 @@ public class RestAPIActivateUtils { } protected void addAllHeaders(WebClient.Builder webClientBuilder, ActionConfiguration actionConfiguration, - DatasourceConfiguration datasourceConfiguration) { + DatasourceConfiguration datasourceConfiguration) { /** * First, check if headers are defined in API datasource and add them. */ diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/ActionConfiguration.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/ActionConfiguration.java index d9cb811006..bdf50285c7 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/ActionConfiguration.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/ActionConfiguration.java @@ -1,5 +1,9 @@ package com.appsmith.external.models; +import com.appsmith.external.converters.HttpMethodConverter; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; +import com.google.gson.annotations.JsonAdapter; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; @@ -37,9 +41,9 @@ public class ActionConfiguration implements AppsmithDomain { * action execution. */ - @Range(min=MIN_TIMEOUT_VALUE, - max=MAX_TIMEOUT_VALUE, - message=TIMEOUT_OUT_OF_RANGE_MESSAGE) + @Range(min = MIN_TIMEOUT_VALUE, + max = MAX_TIMEOUT_VALUE, + message = TIMEOUT_OUT_OF_RANGE_MESSAGE) Integer timeoutInMillisecond; PaginationType paginationType = PaginationType.NONE; @@ -53,6 +57,11 @@ public class ActionConfiguration implements AppsmithDomain { List bodyFormData; // For route parameters extracted from rapid-api List routeParameters; + // All the following adapters are registered so that we can serialize between enum HttpMethod, + // and what is now the class HttpMethod + @JsonSerialize(using = HttpMethodConverter.HttpMethodSerializer.class) + @JsonDeserialize(using = HttpMethodConverter.HttpMethodDeserializer.class) + @JsonAdapter(HttpMethodConverter.class) HttpMethod httpMethod; // Paginated API fields String next; diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/ApiContentType.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/ApiContentType.java index cf8fb1b44e..e58d580b9b 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/ApiContentType.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/models/ApiContentType.java @@ -10,7 +10,7 @@ public enum ApiContentType { JSON("application/json"), FORM_URLENCODED("application/x-www-form-urlencoded"), MULTIPART_FORM_DATA("multipart/form-data"), - RAW("raw"), + RAW("text/plain"), GRAPHQL("application/graphql") ; diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/BasePlugin.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/BasePlugin.java index c1a0f109e2..3d38ef6d96 100644 --- a/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/BasePlugin.java +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/external/plugins/BasePlugin.java @@ -4,9 +4,6 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.pf4j.Plugin; import org.pf4j.PluginWrapper; -import java.util.ArrayList; -import java.util.List; - public abstract class BasePlugin extends Plugin { protected static final ObjectMapper objectMapper = new ObjectMapper(); diff --git a/app/server/appsmith-interfaces/src/main/java/com/appsmith/util/SerializationUtils.java b/app/server/appsmith-interfaces/src/main/java/com/appsmith/util/SerializationUtils.java new file mode 100644 index 0000000000..7f66c6965a --- /dev/null +++ b/app/server/appsmith-interfaces/src/main/java/com/appsmith/util/SerializationUtils.java @@ -0,0 +1,47 @@ +package com.appsmith.util; + +import com.appsmith.external.converters.HttpMethodConverter; +import com.appsmith.external.converters.ISOStringToInstantConverter; +import com.appsmith.external.models.DatasourceStructure; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.boot.autoconfigure.gson.GsonBuilderCustomizer; +import org.springframework.http.HttpMethod; + +import java.time.Instant; + +public class SerializationUtils { + + private static final JavaTimeModule JAVA_TIME_MODULE; + private static final HttpMethodConverter.HttpMethodModule HTTP_METHOD_MODULE; + + static { + JAVA_TIME_MODULE = new JavaTimeModule(); + HTTP_METHOD_MODULE = new HttpMethodConverter.HttpMethodModule(); + } + + public static ObjectMapper configureObjectMapper(ObjectMapper objectMapper) { + objectMapper.registerModule(JAVA_TIME_MODULE); + objectMapper.registerModule(HTTP_METHOD_MODULE); + objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); + objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + + return objectMapper; + } + + public static ObjectMapper getDefaultObjectMapper() { + return configureObjectMapper(new ObjectMapper()); + } + + public static GsonBuilderCustomizer typeAdapterRegistration() { + return builder -> { + builder.registerTypeAdapter(Instant.class, new ISOStringToInstantConverter()); + builder.registerTypeAdapter(DatasourceStructure.Key.class, new DatasourceStructure.KeyInstanceCreator()); + builder.registerTypeAdapter(HttpMethod.class, new HttpMethodConverter()); + }; + } +} diff --git a/app/server/appsmith-plugins/amazons3Plugin/pom.xml b/app/server/appsmith-plugins/amazons3Plugin/pom.xml index b5f94fb228..9847f41580 100644 --- a/app/server/appsmith-plugins/amazons3Plugin/pom.xml +++ b/app/server/appsmith-plugins/amazons3Plugin/pom.xml @@ -16,10 +16,6 @@ amazons3Plugin - UTF-8 - 11 - ${java.version} - ${java.version} amazons3-plugin com.external.plugins.AmazonS3Plugin 1.0-SNAPSHOT @@ -32,7 +28,7 @@ com.amazonaws aws-java-sdk-bom - 1.11.327 + 1.12.261 pom import diff --git a/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java b/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java index 3513ea9043..80176de05c 100644 --- a/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java +++ b/app/server/appsmith-plugins/amazons3Plugin/src/main/java/com/external/plugins/AmazonS3Plugin.java @@ -120,7 +120,7 @@ public class AmazonS3Plugin extends BasePlugin { @Extension public static class S3PluginExecutor implements PluginExecutor, SmartSubstitutionInterface { - private final Scheduler scheduler = Schedulers.elastic(); + private final Scheduler scheduler = Schedulers.boundedElastic(); private final FilterDataService filterDataService; private static final AmazonS3ErrorUtils amazonS3ErrorUtils; diff --git a/app/server/appsmith-plugins/arangoDBPlugin/pom.xml b/app/server/appsmith-plugins/arangoDBPlugin/pom.xml index ac34b83d4a..076d8e016b 100644 --- a/app/server/appsmith-plugins/arangoDBPlugin/pom.xml +++ b/app/server/appsmith-plugins/arangoDBPlugin/pom.xml @@ -16,10 +16,6 @@ arangodbPlugin - UTF-8 - 11 - ${java.version} - ${java.version} arangodb-plugin com.external.plugins.ArangoDBPlugin 1.0-SNAPSHOT diff --git a/app/server/appsmith-plugins/arangoDBPlugin/src/main/java/com/external/plugins/ArangoDBPlugin.java b/app/server/appsmith-plugins/arangoDBPlugin/src/main/java/com/external/plugins/ArangoDBPlugin.java index 3efccaf88f..05a65fcbd4 100644 --- a/app/server/appsmith-plugins/arangoDBPlugin/src/main/java/com/external/plugins/ArangoDBPlugin.java +++ b/app/server/appsmith-plugins/arangoDBPlugin/src/main/java/com/external/plugins/ArangoDBPlugin.java @@ -66,7 +66,7 @@ public class ArangoDBPlugin extends BasePlugin { @Extension public static class ArangoDBPluginExecutor implements PluginExecutor { - private final Scheduler scheduler = Schedulers.elastic(); + private final Scheduler scheduler = Schedulers.boundedElastic(); @Override public Mono execute(ArangoDatabase db, diff --git a/app/server/appsmith-plugins/dynamoPlugin/pom.xml b/app/server/appsmith-plugins/dynamoPlugin/pom.xml index df4d6c12a4..1c4a6ed1e1 100644 --- a/app/server/appsmith-plugins/dynamoPlugin/pom.xml +++ b/app/server/appsmith-plugins/dynamoPlugin/pom.xml @@ -16,10 +16,6 @@ dynamoPlugin - UTF-8 - 11 - ${java.version} - ${java.version} dynamo-plugin com.external.plugins.DynamoPlugin 1.0-SNAPSHOT diff --git a/app/server/appsmith-plugins/dynamoPlugin/src/main/java/com/external/plugins/DynamoPlugin.java b/app/server/appsmith-plugins/dynamoPlugin/src/main/java/com/external/plugins/DynamoPlugin.java index 0731bc8638..0aaf2a75ee 100644 --- a/app/server/appsmith-plugins/dynamoPlugin/src/main/java/com/external/plugins/DynamoPlugin.java +++ b/app/server/appsmith-plugins/dynamoPlugin/src/main/java/com/external/plugins/DynamoPlugin.java @@ -87,7 +87,7 @@ public class DynamoPlugin extends BasePlugin { @Extension public static class DynamoPluginExecutor implements PluginExecutor { - private final Scheduler scheduler = Schedulers.elastic(); + private final Scheduler scheduler = Schedulers.boundedElastic(); public Object extractValue(Object rawItem) { diff --git a/app/server/appsmith-plugins/elasticSearchPlugin/pom.xml b/app/server/appsmith-plugins/elasticSearchPlugin/pom.xml index 11d8a57bd5..e484e52bf5 100644 --- a/app/server/appsmith-plugins/elasticSearchPlugin/pom.xml +++ b/app/server/appsmith-plugins/elasticSearchPlugin/pom.xml @@ -16,10 +16,6 @@ elasticSearchPlugin - UTF-8 - 11 - ${java.version} - ${java.version} elasticsearch-plugin com.external.plugins.ElasticSearchPlugin 1.0-SNAPSHOT @@ -32,13 +28,12 @@ org.elasticsearch.client elasticsearch-rest-client - 7.17.5 + 8.5.3 org.springframework spring-web - 5.3.15 provided diff --git a/app/server/appsmith-plugins/firestorePlugin/pom.xml b/app/server/appsmith-plugins/firestorePlugin/pom.xml index 4f76247812..2d5f52a99b 100644 --- a/app/server/appsmith-plugins/firestorePlugin/pom.xml +++ b/app/server/appsmith-plugins/firestorePlugin/pom.xml @@ -16,10 +16,6 @@ firestorePlugin - UTF-8 - 11 - ${java.version} - ${java.version} firestore-plugin com.external.plugins.FirestorePlugin 1.0-SNAPSHOT @@ -32,7 +28,7 @@ com.google.firebase firebase-admin - 7.3.0 + 9.1.1 org.slf4j diff --git a/app/server/appsmith-plugins/firestorePlugin/src/main/java/com/external/plugins/FirestorePlugin.java b/app/server/appsmith-plugins/firestorePlugin/src/main/java/com/external/plugins/FirestorePlugin.java index 86209d674c..a8f303cac1 100644 --- a/app/server/appsmith-plugins/firestorePlugin/src/main/java/com/external/plugins/FirestorePlugin.java +++ b/app/server/appsmith-plugins/firestorePlugin/src/main/java/com/external/plugins/FirestorePlugin.java @@ -101,7 +101,7 @@ public class FirestorePlugin extends BasePlugin { @Extension public static class FirestorePluginExecutor implements PluginExecutor, SmartSubstitutionInterface { - private final Scheduler scheduler = Schedulers.elastic(); + private final Scheduler scheduler = Schedulers.boundedElastic(); @Override @Deprecated @@ -968,5 +968,10 @@ public class FirestorePlugin extends BasePlugin { }) .subscribeOn(scheduler); } + + @Override + public Set getSelfReferencingDataPaths() { + return Set.of("formData.prev.data", "formData.next.data", "formData.startAfter.data", "formData.endBefore.data"); + } } } diff --git a/app/server/appsmith-plugins/firestorePlugin/src/main/java/com/external/utils/WhereConditionUtils.java b/app/server/appsmith-plugins/firestorePlugin/src/main/java/com/external/utils/WhereConditionUtils.java index fad57455b7..eebca42cc2 100644 --- a/app/server/appsmith-plugins/firestorePlugin/src/main/java/com/external/utils/WhereConditionUtils.java +++ b/app/server/appsmith-plugins/firestorePlugin/src/main/java/com/external/utils/WhereConditionUtils.java @@ -1,9 +1,9 @@ package com.external.utils; +import com.appsmith.external.constants.ConditionalOperator; import com.appsmith.external.constants.DataType; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; -import com.appsmith.external.constants.ConditionalOperator; import com.appsmith.external.helpers.DataTypeStringUtils; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.cloud.firestore.FieldPath; @@ -14,7 +14,6 @@ import java.io.IOException; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; public class WhereConditionUtils { diff --git a/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/addToCollection.json b/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/addToCollection.json index f9c0d27f9b..d7c8829f06 100644 --- a/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/addToCollection.json +++ b/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/addToCollection.json @@ -13,20 +13,21 @@ "isRequired": true, "initialValue": "" }, - { - "label": "Timestamp Value Path (use dot(.) notation to reference nested key e.g. [\"key1.key2\"])", - "configProperty": "actionConfiguration.formData.timestampValuePath.data", - "controlType": "QUERY_DYNAMIC_TEXT", - "evaluationSubstitutionType": "TEMPLATE", - "isRequired": true, - "initialValue": "", - "placeholderText": "[\"checkinLog.timestampKey\", \"auditLog.timestampKey\"]" - }, { "label": "Body", "configProperty": "actionConfiguration.formData.body.data", "controlType": "QUERY_DYNAMIC_TEXT", - "initialValue": "" + "initialValue": "", + "placeholderText": "{\n \"name\": {{nameInput.text}},\n \"dob\": {{dobPicker.formattedDate}},\n \"gender\": {{genderSelect.selectedOptionValue}} \n}" + }, + { + "label": "Timestamp Path", + "configProperty": "actionConfiguration.formData.timestampValuePath.data", + "controlType": "QUERY_DYNAMIC_INPUT_TEXT", + "evaluationSubstitutionType": "TEMPLATE", + "isRequired": true, + "initialValue": "", + "placeholderText": "[ \"checkinLog.timestampKey\" ]" } ] } diff --git a/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/createDocument.json b/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/createDocument.json index fdb0ddb485..6c57ad3ab7 100644 --- a/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/createDocument.json +++ b/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/createDocument.json @@ -6,27 +6,28 @@ }, "children": [ { - "label": "Collection/Document Path", + "label": "Collection Name", "configProperty": "actionConfiguration.formData.path.data", "controlType": "QUERY_DYNAMIC_INPUT_TEXT", "evaluationSubstitutionType": "TEMPLATE", "isRequired": true, "initialValue": "" }, - { - "label": "Timestamp Value Path (use dot(.) notation to reference nested key e.g. [\"key1.key2\"])", - "configProperty": "actionConfiguration.formData.timestampValuePath.data", - "controlType": "QUERY_DYNAMIC_TEXT", - "evaluationSubstitutionType": "TEMPLATE", - "isRequired": true, - "initialValue": "", - "placeholderText": "[\"checkinLog.timestampKey\", \"auditLog.timestampKey\"]" - }, { "label": "Body", "configProperty": "actionConfiguration.formData.body.data", "controlType": "QUERY_DYNAMIC_TEXT", - "initialValue": "" + "initialValue": "", + "placeholderText": "{\n \"name\": {{nameInput.text}},\n \"dob\": {{dobPicker.formattedDate}},\n \"gender\": {{genderSelect.selectedOptionValue}} \n}" + }, + { + "label": "Timestamp Path", + "configProperty": "actionConfiguration.formData.timestampValuePath.data", + "controlType": "QUERY_DYNAMIC_INPUT_TEXT", + "evaluationSubstitutionType": "TEMPLATE", + "isRequired": true, + "initialValue": "", + "placeholderText": "[ \"checkinLog.timestampKey\" ]" } ] } diff --git a/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/deleteDocument.json b/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/deleteDocument.json index 269018e876..a102091a97 100644 --- a/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/deleteDocument.json +++ b/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/deleteDocument.json @@ -11,6 +11,7 @@ "controlType": "QUERY_DYNAMIC_INPUT_TEXT", "evaluationSubstitutionType": "TEMPLATE", "isRequired": true, + "placeholderText": "collection/{{Table1.selectedRow._ref}}", "initialValue": "" } ] diff --git a/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/getCollection.json b/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/getCollection.json index 4dd8db57b9..ef11956f38 100644 --- a/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/getCollection.json +++ b/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/getCollection.json @@ -6,46 +6,13 @@ }, "children": [ { - "label": "Collection/Document Path", + "label": "Collection Name", "configProperty": "actionConfiguration.formData.path.data", "controlType": "QUERY_DYNAMIC_INPUT_TEXT", "evaluationSubstitutionType": "TEMPLATE", "isRequired": true, "initialValue": "" }, - { - "label": "Order By (JSON array of field names to order by)", - "configProperty": "actionConfiguration.formData.orderBy.data", - "controlType": "QUERY_DYNAMIC_TEXT", - "evaluationSubstitutionType": "TEMPLATE", - "isRequired": false, - "initialValue": "", - "placeholderText": "[\"ascendingField\", \"-descendingField\", \"nestedObj.field\"]" - }, - { - "label": "Start After", - "configProperty": "actionConfiguration.formData.next.data", - "controlType": "QUERY_DYNAMIC_INPUT_TEXT", - "evaluationSubstitutionType": "TEMPLATE", - "isRequired": false, - "initialValue": "" - }, - { - "label": "End Before", - "configProperty": "actionConfiguration.formData.prev.data", - "controlType": "QUERY_DYNAMIC_INPUT_TEXT", - "evaluationSubstitutionType": "TEMPLATE", - "isRequired": false, - "initialValue": "" - }, - { - "label": "Limit Documents", - "configProperty": "actionConfiguration.formData.limitDocuments.data", - "controlType": "QUERY_DYNAMIC_INPUT_TEXT", - "evaluationSubstitutionType": "TEMPLATE", - "isRequired": false, - "initialValue": "10" - }, { "label": "Where", "configProperty": "actionConfiguration.formData.where.data", @@ -94,6 +61,41 @@ "value": "ARRAY_CONTAINS_ANY" } ] + }, + { + "label": "Order By", + "configProperty": "actionConfiguration.formData.orderBy.data", + "controlType": "QUERY_DYNAMIC_INPUT_TEXT", + "evaluationSubstitutionType": "TEMPLATE", + "isRequired": false, + "initialValue": "", + "placeholderText": "[\"ascKey\", \"-descKey\"]" + }, + { + "label": "Start After", + "configProperty": "actionConfiguration.formData.next.data", + "controlType": "QUERY_DYNAMIC_INPUT_TEXT", + "evaluationSubstitutionType": "TEMPLATE", + "isRequired": false, + "initialValue": "", + "palceholderText": "" + }, + { + "label": "End Before", + "configProperty": "actionConfiguration.formData.prev.data", + "controlType": "QUERY_DYNAMIC_INPUT_TEXT", + "evaluationSubstitutionType": "TEMPLATE", + "isRequired": false, + "initialValue": "" + }, + { + "label": "Limit", + "configProperty": "actionConfiguration.formData.limitDocuments.data", + "controlType": "QUERY_DYNAMIC_INPUT_TEXT", + "evaluationSubstitutionType": "TEMPLATE", + "isRequired": false, + "palceholderText": "{{Table1.pageSize}}", + "initialValue": "10" } ] } diff --git a/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/root.json b/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/root.json index 22e9c003c8..2efcc657da 100644 --- a/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/root.json +++ b/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/root.json @@ -9,28 +9,16 @@ "description": "Choose method you would like to use", "configProperty": "actionConfiguration.formData.command.data", "controlType": "DROP_DOWN", - "initialValue": "GET_DOCUMENT", + "initialValue": "GET_COLLECTION", "options": [ { - "label": "Get Single Document", - "value": "GET_DOCUMENT" - }, - { - "label": "Get Documents in Collection", + "label": "List Documents", "value": "GET_COLLECTION" }, - { - "label": "Set Document", - "value": "SET_DOCUMENT" - }, { "label": "Create Document", "value": "CREATE_DOCUMENT" }, - { - "label": "Add Document to Collection", - "value": "ADD_TO_COLLECTION" - }, { "label": "Update Document", "value": "UPDATE_DOCUMENT" @@ -38,6 +26,18 @@ { "label": "Delete Document", "value": "DELETE_DOCUMENT" + }, + { + "label": "Get Document", + "value": "GET_DOCUMENT" + }, + { + "label": "Upsert Document", + "value": "SET_DOCUMENT" + }, + { + "label": "Add Document to Collection", + "value": "ADD_TO_COLLECTION" } ] } diff --git a/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/setDocument.json b/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/setDocument.json index 3daebc665f..a21e03bab9 100644 --- a/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/setDocument.json +++ b/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/setDocument.json @@ -13,20 +13,21 @@ "isRequired": true, "initialValue": "" }, - { - "label": "Timestamp Value Path (use dot(.) notation to reference nested key e.g. [\"key1.key2\"])", - "configProperty": "actionConfiguration.formData.timestampValuePath.data", - "controlType": "QUERY_DYNAMIC_TEXT", - "evaluationSubstitutionType": "TEMPLATE", - "isRequired": true, - "placeholderText": "[\"checkinLog.timestampKey\", \"auditLog.timestampKey\"]", - "initialValue": "" - }, { "label": "Body", "configProperty": "actionConfiguration.formData.body.data", "controlType": "QUERY_DYNAMIC_TEXT", - "initialValue": "" + "initialValue": "", + "placeholderText": "{\n \"name\": {{nameInput.text}},\n \"dob\": {{dobPicker.formattedDate}},\n \"gender\": {{genderSelect.selectedOptionValue}} \n}" + }, + { + "label": "Timestamp Path", + "configProperty": "actionConfiguration.formData.timestampValuePath.data", + "controlType": "QUERY_DYNAMIC_INPUT_TEXT", + "evaluationSubstitutionType": "TEMPLATE", + "isRequired": true, + "initialValue": "", + "placeholderText": "[ \"checkinLog.timestampKey\" ]" } ] } diff --git a/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/updateDocument.json b/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/updateDocument.json index 5e23299e7d..f91b89ba00 100644 --- a/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/updateDocument.json +++ b/app/server/appsmith-plugins/firestorePlugin/src/main/resources/editor/updateDocument.json @@ -13,29 +13,30 @@ "isRequired": true, "initialValue": "" }, - { - "label": "Timestamp Value Path (use dot(.) notation to reference nested key e.g. [\"key1.key2\"])", - "configProperty": "actionConfiguration.formData.timestampValuePath.data", - "controlType": "QUERY_DYNAMIC_TEXT", - "evaluationSubstitutionType": "TEMPLATE", - "isRequired": true, - "initialValue": "", - "placeholderText": "[\"checkinLog.timestampKey\", \"auditLog.timestampKey\"]" - }, - { - "label": "Delete Key Value Pair Path (use dot(.) notation to reference nested key e.g. [\"key1.key2\"])", - "configProperty": "actionConfiguration.formData.deleteKeyPath.data", - "controlType": "QUERY_DYNAMIC_TEXT", - "evaluationSubstitutionType": "TEMPLATE", - "isRequired": true, - "initialValue": "", - "placeholderText": "[\"userKey.nestedNamekey\", \"cityKey.nestedPincodeKey\"]" - }, { "label": "Body", "configProperty": "actionConfiguration.formData.body.data", "controlType": "QUERY_DYNAMIC_TEXT", - "initialValue": "" + "initialValue": "", + "placeholderText": "{\n \"name\": {{nameInput.text}},\n \"dob\": {{dobPicker.formattedDate}},\n \"gender\": {{genderSelect.selectedOptionValue}} \n}" + }, + { + "label": "Delete Key Path", + "configProperty": "actionConfiguration.formData.deleteKeyPath.data", + "controlType": "QUERY_DYNAMIC_INPUT_TEXT", + "evaluationSubstitutionType": "TEMPLATE", + "isRequired": true, + "initialValue": "", + "placeholderText": "[\"userKey.nestedNamekey\"]" + }, + { + "label": "Timestamp Path", + "configProperty": "actionConfiguration.formData.timestampValuePath.data", + "controlType": "QUERY_DYNAMIC_INPUT_TEXT", + "evaluationSubstitutionType": "TEMPLATE", + "isRequired": true, + "initialValue": "", + "placeholderText": "[ \"checkinLog.timestampKey\" ]" } ] } diff --git a/app/server/appsmith-plugins/googleSheetsPlugin/pom.xml b/app/server/appsmith-plugins/googleSheetsPlugin/pom.xml index c143b3eb96..3b92992b47 100644 --- a/app/server/appsmith-plugins/googleSheetsPlugin/pom.xml +++ b/app/server/appsmith-plugins/googleSheetsPlugin/pom.xml @@ -16,10 +16,6 @@ googleSheetsPlugin - UTF-8 - 11 - ${java.version} - ${java.version} google-sheets-plugin com.external.plugins.GoogleSheetsPlugin 1.0-SNAPSHOT @@ -32,35 +28,33 @@ org.springframework spring-core - 5.3.20 provided org.springframework spring-web - 5.3.20 provided com.fasterxml.jackson.core jackson-databind - 2.13.4.1 + ${jackson-bom.version} provided com.fasterxml.jackson.datatype jackson-datatype-jdk8 - 2.10.2 + ${jackson-bom.version} provided com.fasterxml.jackson.datatype jackson-datatype-jsr310 - 2.10.2 + ${jackson-bom.version} provided @@ -73,23 +67,23 @@ io.jsonwebtoken jjwt-api - 0.11.2 + ${jjwt.version} io.jsonwebtoken jjwt-impl - 0.11.2 + ${jjwt.version} io.jsonwebtoken jjwt-jackson - 0.11.2 + ${jjwt.version} com.google.apis google-api-services-sheets - v4-rev493-1.23.0 + v4-rev20220927-2.0.0 commons-logging @@ -114,7 +108,7 @@ org.springframework.boot spring-boot-starter-webflux - 2.7.0 + ${spring-boot.version} test diff --git a/app/server/appsmith-plugins/googleSheetsPlugin/src/test/java/com/external/config/FileInfoMethodTest.java b/app/server/appsmith-plugins/googleSheetsPlugin/src/test/java/com/external/config/FileInfoMethodTest.java index 3d0f8b7c1f..38ec02900b 100644 --- a/app/server/appsmith-plugins/googleSheetsPlugin/src/test/java/com/external/config/FileInfoMethodTest.java +++ b/app/server/appsmith-plugins/googleSheetsPlugin/src/test/java/com/external/config/FileInfoMethodTest.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.ArrayList; @@ -18,9 +19,15 @@ import static org.junit.jupiter.api.Assertions.assertTrue; public class FileInfoMethodTest { + ObjectMapper objectMapper; + + @BeforeEach + public void setUp() { + this.objectMapper = new ObjectMapper(); + } + @Test public void testTransformExecutionResponse_missingJSON_throwsException() { - ObjectMapper objectMapper = new ObjectMapper(); FileInfoMethod fileInfoMethod = new FileInfoMethod(objectMapper); assertThrows(AppsmithPluginException.class, () -> { @@ -30,7 +37,6 @@ public class FileInfoMethodTest { @Test public void testTransformExecutionResponse_missingSheets_returnsEmpty() throws JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); final String jsonString = "{\"key1\":\"value1\",\"key2\":\"value2\"}"; @@ -51,7 +57,6 @@ public class FileInfoMethodTest { @Test public void testTransformExecutionResponse_emptySheets_returnsEmpty() throws JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); final String jsonString = "{\"key1\":\"value1\",\"key2\":\"value2\"}"; @@ -71,7 +76,6 @@ public class FileInfoMethodTest { @Test public void testTransformExecutionResponse_validSheets_toListOfSheets() throws JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); final String jsonString = "{\"key1\":\"value1\",\"key2\":\"value2\"}"; final String sheetMetadataString = "{\"sheetId\":\"1\", \"title\":\"test\", \"sheetType\":\"GRID\", \"index\":0}"; @@ -94,7 +98,6 @@ public class FileInfoMethodTest { @Test public void testTransformTriggerResponse_withoutSheets_returnsEmpty() throws JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); final String jsonString = "{\"sheets\":[]}"; @@ -113,7 +116,6 @@ public class FileInfoMethodTest { @Test public void testTransformTriggerResponse_withSheets_returnsDropdownOptions() throws JsonProcessingException { - ObjectMapper objectMapper = new ObjectMapper(); final String jsonString = "{\"sheets\":[{\"properties\": {\"title\": \"testSheetName\"}}]}"; diff --git a/app/server/appsmith-plugins/graphqlPlugin/pom.xml b/app/server/appsmith-plugins/graphqlPlugin/pom.xml index c8fa2c1f21..040726a495 100644 --- a/app/server/appsmith-plugins/graphqlPlugin/pom.xml +++ b/app/server/appsmith-plugins/graphqlPlugin/pom.xml @@ -16,10 +16,6 @@ graphqlPlugin - UTF-8 - 11 - ${java.version} - ${java.version} graphql-plugin com.external.plugins.GraphQLPlugin 1.0-SNAPSHOT @@ -32,21 +28,18 @@ org.springframework spring-core - 5.3.20 provided org.springframework spring-web - 5.3.20 provided org.springframework spring-webflux - 5.3.20 io.projectreactor @@ -66,36 +59,36 @@ com.fasterxml.jackson.core jackson-databind - 2.13.4.1 + ${jackson-bom.version} provided com.fasterxml.jackson.datatype jackson-datatype-jdk8 - 2.10.2 + ${jackson-bom.version} com.fasterxml.jackson.datatype jackson-datatype-jsr310 - 2.10.2 + ${jackson-bom.version} io.jsonwebtoken jjwt-api - 0.11.2 + ${jjwt.version} io.jsonwebtoken jjwt-impl - 0.11.2 + ${jjwt.version} io.jsonwebtoken jjwt-jackson - 0.11.2 + ${jjwt.version} + 0.8.8.RELEASE + + + io.netty + * + + + org.slf4j + slf4j-api + + + io.projectreactor + reactor-core + + + org.reactivestreams + reactive-streams + + + + mysql diff --git a/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java b/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java index ac09091a22..cf5ae754b0 100644 --- a/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java +++ b/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/plugins/MySqlPlugin.java @@ -10,26 +10,24 @@ import com.appsmith.external.helpers.MustacheHelper; import com.appsmith.external.models.ActionConfiguration; import com.appsmith.external.models.ActionExecutionRequest; import com.appsmith.external.models.ActionExecutionResult; -import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.DatasourceStructure; -import com.appsmith.external.models.Endpoint; +import com.appsmith.external.models.DatasourceTestResult; import com.appsmith.external.models.MustacheBindingToken; import com.appsmith.external.models.Param; import com.appsmith.external.models.Property; import com.appsmith.external.models.PsParameterDTO; import com.appsmith.external.models.RequestParamDTO; -import com.appsmith.external.models.SSLDetails; import com.appsmith.external.plugins.BasePlugin; import com.appsmith.external.plugins.PluginExecutor; import com.appsmith.external.plugins.SmartSubstitutionInterface; import com.external.plugins.datatypes.MySQLSpecificDataTypes; +import com.external.utils.MySqlDatasourceUtils; import com.external.utils.QueryUtils; +import io.r2dbc.pool.ConnectionPool; import io.r2dbc.spi.ColumnMetadata; import io.r2dbc.spi.Connection; -import io.r2dbc.spi.ConnectionFactories; -import io.r2dbc.spi.ConnectionFactoryOptions; -import io.r2dbc.spi.Option; +import io.r2dbc.spi.R2dbcNonTransientResourceException; import io.r2dbc.spi.Result; import io.r2dbc.spi.Row; import io.r2dbc.spi.RowMetadata; @@ -40,11 +38,11 @@ import org.apache.commons.lang.ObjectUtils; import org.pf4j.Extension; import org.pf4j.PluginWrapper; import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; +import reactor.pool.PoolShutdownException; import java.time.Duration; import java.time.LocalDate; @@ -63,7 +61,6 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.TimeoutException; -import java.util.stream.Collectors; import java.util.stream.IntStream; import static com.appsmith.external.constants.ActionConstants.ACTION_CONFIGURATION_BODY; @@ -71,16 +68,16 @@ import static com.appsmith.external.helpers.PluginUtils.MATCH_QUOTED_WORDS_REGEX import static com.appsmith.external.helpers.PluginUtils.getIdenticalColumns; import static com.appsmith.external.helpers.PluginUtils.getPSParamLabel; import static com.appsmith.external.helpers.SmartSubstitutionHelper.replaceQuestionMarkWithDollarIndex; -import static io.r2dbc.spi.ConnectionFactoryOptions.SSL; +import static com.external.utils.MySqlDatasourceUtils.getNewConnectionPool; +import static com.external.utils.MySqlGetStructureUtils.getKeyInfo; +import static com.external.utils.MySqlGetStructureUtils.getTableInfo; +import static com.external.utils.MySqlGetStructureUtils.getTemplates; import static java.lang.Boolean.FALSE; import static java.lang.Boolean.TRUE; @Slf4j public class MySqlPlugin extends BasePlugin { - private static final String DATE_COLUMN_TYPE_NAME = "date"; - private static final String DATETIME_COLUMN_TYPE_NAME = "datetime"; - private static final String TIMESTAMP_COLUMN_TYPE_NAME = "timestamp"; private static final int VALIDATION_CHECK_TIMEOUT = 4; // seconds private static final String IS_KEY = "is"; @@ -141,10 +138,10 @@ public class MySqlPlugin extends BasePlugin { } @Extension - public static class MySqlPluginExecutor implements PluginExecutor, SmartSubstitutionInterface { + public static class MySqlPluginExecutor implements PluginExecutor, SmartSubstitutionInterface { private static final int PREPARED_STATEMENT_INDEX = 0; - private final Scheduler scheduler = Schedulers.elastic(); + private final Scheduler scheduler = Schedulers.boundedElastic(); /** * Instead of using the default executeParametrized provided by pluginExecutor, this implementation affords an opportunity @@ -161,7 +158,7 @@ public class MySqlPlugin extends BasePlugin { * @return */ @Override - public Mono executeParameterized(Connection connection, + public Mono executeParameterized(ConnectionPool connection, ExecuteActionDTO executeActionDTO, DatasourceConfiguration datasourceConfiguration, ActionConfiguration actionConfiguration) { @@ -222,7 +219,7 @@ public class MySqlPlugin extends BasePlugin { return executeCommon(connection, actionConfiguration, TRUE, mustacheKeysInOrder, executeActionDTO, requestData); } - public Mono executeCommon(Connection connection, + public Mono executeCommon(ConnectionPool connectionPool, ActionConfiguration actionConfiguration, Boolean preparedStatement, List mustacheValuesInOrder, @@ -232,6 +229,7 @@ public class MySqlPlugin extends BasePlugin { String query = actionConfiguration.getBody(); /** + * TBD: check if this comment is resolved with the new MariaDB driver. * - MySQL r2dbc driver is not able to substitute the `True/False` value properly after the IS keyword. * Converting `True/False` to integer 1 or 0 also does not work in this case as MySQL syntax does not support * integers with IS keyword. @@ -259,87 +257,96 @@ public class MySqlPlugin extends BasePlugin { List requestParams = List.of(new RequestParamDTO(ACTION_CONFIGURATION_BODY, transformedQuery, null, null, psParams)); - // TODO: need to write a JUnit TC for VALIDATION_CHECK_TIMEOUT - Flux resultFlux = Mono.from(connection.validate(ValidationDepth.REMOTE)) - .timeout(Duration.ofSeconds(VALIDATION_CHECK_TIMEOUT)) - .onErrorMap(TimeoutException.class, error -> new StaleConnectionException()) - .flatMapMany(isValid -> { - if (isValid) { - return createAndExecuteQueryFromConnection(finalQuery, - connection, - preparedStatement, - mustacheValuesInOrder, - executeActionDTO, - requestData, - psParams); - } - return Flux.error(new StaleConnectionException()); - }); + return Mono.usingWhen( + connectionPool.create(), + connection -> { + // TODO: add JUnit TC for the `connection.validate` check. Not sure how to do it at the moment. + Flux resultFlux = Mono.from(connection.validate(ValidationDepth.REMOTE)) + .timeout(Duration.ofSeconds(VALIDATION_CHECK_TIMEOUT)) + .onErrorMap(TimeoutException.class, error -> new StaleConnectionException()) + .flatMapMany(isValid -> { + if (isValid) { + return createAndExecuteQueryFromConnection(finalQuery, + connection, + preparedStatement, + mustacheValuesInOrder, + executeActionDTO, + requestData, + psParams); + } + return Flux.error(new StaleConnectionException()); + }); - Mono>> resultMono; + Mono>> resultMono; + if (isSelectOrShowOrDescQuery) { + resultMono = resultFlux + .flatMap(result -> + result.map((row, meta) -> { + rowsList.add(getRow(row, meta)); - if (isSelectOrShowOrDescQuery) { - resultMono = resultFlux - .flatMap(result -> - result.map((row, meta) -> { - rowsList.add(getRow(row, meta)); + if (columnsList.isEmpty()) { + meta.getColumnMetadatas().stream().forEach(columnMetadata -> columnsList.add(columnMetadata.getName())); + } - if (columnsList.isEmpty()) { - columnsList.addAll(meta.getColumnNames()); - } - - return result; - } - ) - ) - .collectList() - .thenReturn(rowsList); - } else { - resultMono = resultFlux - .flatMap(Result::getRowsUpdated) - .collectList() - .flatMap(list -> Mono.just(list.get(list.size() - 1))) - .map(rowsUpdated -> { - rowsList.add( - Map.of( - "affectedRows", - ObjectUtils.defaultIfNull(rowsUpdated, 0) + return result; + } + ) ) - ); - return rowsList; - }); - } - - return resultMono - .map(res -> { - ActionExecutionResult result = new ActionExecutionResult(); - result.setBody(objectMapper.valueToTree(rowsList)); - result.setMessages(populateHintMessages(columnsList)); - result.setIsExecutionSuccess(true); - log.debug("In the MySqlPlugin, got action execution result"); - return result; - }) - .onErrorResume(error -> { - if (error instanceof StaleConnectionException) { - return Mono.error(error); + .collectList() + .thenReturn(rowsList); + } else { + resultMono = resultFlux + .flatMap(Result::getRowsUpdated) + .collectList() + .map(list -> list.get(list.size() - 1)) + .map(rowsUpdated -> { + rowsList.add( + Map.of( + "affectedRows", + ObjectUtils.defaultIfNull(rowsUpdated, 0) + ) + ); + return rowsList; + }); } - ActionExecutionResult result = new ActionExecutionResult(); - result.setIsExecutionSuccess(false); - result.setErrorInfo(error); - return Mono.just(result); - }) - // Now set the request in the result to be returned back to the server - .map(actionExecutionResult -> { - ActionExecutionRequest request = new ActionExecutionRequest(); - request.setQuery(finalQuery); - request.setProperties(requestData); - request.setRequestParams(requestParams); - ActionExecutionResult result = actionExecutionResult; - result.setRequest(request); - return result; - }) - .subscribeOn(scheduler); + return resultMono + .map(res -> { + ActionExecutionResult result = new ActionExecutionResult(); + result.setBody(objectMapper.valueToTree(rowsList)); + result.setMessages(populateHintMessages(columnsList)); + result.setIsExecutionSuccess(true); + log.debug("In the MySqlPlugin, got action execution result"); + return result; + }) + .onErrorResume(error -> { + if (error instanceof StaleConnectionException) { + return Mono.error(error); + } + ActionExecutionResult result = new ActionExecutionResult(); + result.setIsExecutionSuccess(false); + result.setErrorInfo(error); + return Mono.just(result); + }) + // Now set the request in the result to be returned back to the server + .map(actionExecutionResult -> { + ActionExecutionRequest request = new ActionExecutionRequest(); + request.setQuery(finalQuery); + request.setProperties(requestData); + request.setRequestParams(requestParams); + ActionExecutionResult result = actionExecutionResult; + result.setRequest(request); + + return result; + }); + }, + Connection::close + ) + .timeout(Duration.ofSeconds(VALIDATION_CHECK_TIMEOUT)) + .onErrorMap(TimeoutException.class, error -> new StaleConnectionException()) + .onErrorMap(PoolShutdownException.class, error -> new StaleConnectionException()) + .onErrorMap(R2dbcNonTransientResourceException.class, error -> new StaleConnectionException()) + .subscribeOn(scheduler); } boolean isIsOperatorUsed(String query) { @@ -387,6 +394,14 @@ public class MySqlPlugin extends BasePlugin { } + @Override + public Mono testDatasource(ConnectionPool pool) { + return Mono.just(pool) + .flatMap(p -> p.create()) + .flatMap(conn -> Mono.from(conn.close())) + .then(Mono.just(new DatasourceTestResult())); + } + @Override public Object substituteValueInInput(int index, String binding, @@ -404,7 +419,7 @@ public class MySqlPlugin extends BasePlugin { switch (appsmithType.type()) { case NULL: try { - connectionStatement.bindNull((index - 1), Object.class); + connectionStatement.bindNull((index - 1), String.class); } catch (UnsupportedOperationException e) { // Do nothing. Move on } @@ -499,110 +514,26 @@ public class MySqlPlugin extends BasePlugin { } @Override - public Mono execute(Connection connection, DatasourceConfiguration datasourceConfiguration, ActionConfiguration actionConfiguration) { + public Mono execute(ConnectionPool connection, + DatasourceConfiguration datasourceConfiguration, ActionConfiguration actionConfiguration) { // Unused function return Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, "Unsupported Operation")); } @Override - public Mono datasourceCreate(DatasourceConfiguration datasourceConfiguration) { - DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication(); - - StringBuilder urlBuilder = new StringBuilder(); - if (CollectionUtils.isEmpty(datasourceConfiguration.getEndpoints())) { - urlBuilder.append(datasourceConfiguration.getUrl()); - } else { - urlBuilder.append("r2dbc:mysql://"); - final List hosts = new ArrayList<>(); - - for (Endpoint endpoint : datasourceConfiguration.getEndpoints()) { - hosts.add(endpoint.getHost() + ":" + ObjectUtils.defaultIfNull(endpoint.getPort(), 3306L)); - } - - urlBuilder.append(String.join(",", hosts)).append("/"); - - if (!StringUtils.isEmpty(authentication.getDatabaseName())) { - urlBuilder.append(authentication.getDatabaseName()); - } - + public Mono datasourceCreate(DatasourceConfiguration datasourceConfiguration) { + ConnectionPool pool = null; + try { + pool = getNewConnectionPool(datasourceConfiguration); + } catch (AppsmithPluginException e) { + return Mono.error(e); } - - urlBuilder.append("?zeroDateTimeBehavior=convertToNull"); - final List dsProperties = datasourceConfiguration.getProperties(); - - if (dsProperties != null) { - for (Property property : dsProperties) { - if ("serverTimezone".equals(property.getKey()) && !StringUtils.isEmpty(property.getValue())) { - urlBuilder.append("&serverTimezone=").append(property.getValue()); - break; - } - } - } - - - ConnectionFactoryOptions baseOptions = ConnectionFactoryOptions.parse(urlBuilder.toString()); - ConnectionFactoryOptions.Builder ob = ConnectionFactoryOptions.builder().from(baseOptions) - .option(ConnectionFactoryOptions.USER, authentication.getUsername()) - .option(ConnectionFactoryOptions.PASSWORD, authentication.getPassword()); - - /* - * - Ideally, it is never expected to be null because the SSL dropdown is set to a initial value. - */ - if (datasourceConfiguration.getConnection() == null - || datasourceConfiguration.getConnection().getSsl() == null - || datasourceConfiguration.getConnection().getSsl().getAuthType() == null) { - return Mono.error( - new AppsmithPluginException( - AppsmithPluginError.PLUGIN_ERROR, - "Appsmith server has failed to fetch SSL configuration from datasource configuration form. " + - "Please reach out to Appsmith customer support to resolve this." - ) - ); - } - - /* - * - By default, the driver configures SSL in the preferred mode. - */ - SSLDetails.AuthType sslAuthType = datasourceConfiguration.getConnection().getSsl().getAuthType(); - switch (sslAuthType) { - case PREFERRED: - case REQUIRED: - ob = ob - .option(SSL, true) - .option(Option.valueOf("sslMode"), sslAuthType.toString().toLowerCase()); - - break; - case DISABLED: - ob = ob.option(SSL, false); - - break; - case DEFAULT: - /* do nothing - accept default driver setting*/ - - break; - default: - return Mono.error( - new AppsmithPluginException( - AppsmithPluginError.PLUGIN_ERROR, - "Appsmith server has found an unexpected SSL option: " + sslAuthType + ". Please reach out to" + - " Appsmith customer support to resolve this." - ) - ); - } - - return (Mono) Mono.from(ConnectionFactories.get(ob.build()).create()) - .onErrorResume(exception -> Mono.error(new AppsmithPluginException( - AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR, - exception - ))) - .subscribeOn(scheduler); + return Mono.just(pool); } - @Override - public void datasourceDestroy(Connection connection) { - - if (connection != null) { - Mono.from(connection.close()) + public void datasourceDestroy(ConnectionPool connectionPool) { + if (connectionPool != null) { + Mono.just(connectionPool.disposeLater()) .onErrorResume(exception -> { log.debug("In datasourceDestroy function error mode.", exception); return Mono.empty(); @@ -614,258 +545,72 @@ public class MySqlPlugin extends BasePlugin { @Override public Set validateDatasource(DatasourceConfiguration datasourceConfiguration) { - - Set invalids = new HashSet<>(); - - if (datasourceConfiguration.getConnection() != null - && datasourceConfiguration.getConnection().getMode() == null) { - invalids.add("Missing Connection Mode."); - } - - if (StringUtils.isEmpty(datasourceConfiguration.getUrl()) && - CollectionUtils.isEmpty(datasourceConfiguration.getEndpoints())) { - invalids.add("Missing endpoint and url"); - } else if (!CollectionUtils.isEmpty(datasourceConfiguration.getEndpoints())) { - for (final Endpoint endpoint : datasourceConfiguration.getEndpoints()) { - if (endpoint.getHost() == null || endpoint.getHost().isBlank()) { - invalids.add("Host value cannot be empty"); - } else if (endpoint.getHost().contains("/") || endpoint.getHost().contains(":")) { - invalids.add("Host value cannot contain `/` or `:` characters. Found `" + endpoint.getHost() + "`."); - } - } - } - - if (datasourceConfiguration.getAuthentication() == null) { - invalids.add("Missing authentication details."); - } else { - DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication(); - if (StringUtils.isEmpty(authentication.getUsername())) { - invalids.add("Missing username for authentication."); - } - - if (StringUtils.isEmpty(authentication.getPassword()) && StringUtils.isEmpty(authentication.getUsername())) { - invalids.add("Missing password for authentication."); - } else if (StringUtils.isEmpty(authentication.getPassword())) { - // it is valid if it has the username but not the password - authentication.setPassword(""); - } - - if (StringUtils.isEmpty(authentication.getDatabaseName())) { - invalids.add("Missing database name."); - } - } - - /* - * - Ideally, it is never expected to be null because the SSL dropdown is set to a initial value. - */ - if (datasourceConfiguration.getConnection() == null - || datasourceConfiguration.getConnection().getSsl() == null - || datasourceConfiguration.getConnection().getSsl().getAuthType() == null) { - invalids.add("Appsmith server has failed to fetch SSL configuration from datasource configuration form. " + - "Please reach out to Appsmith customer support to resolve this."); - } - - return invalids; - } - - /** - * 1. Parse results obtained by running COLUMNS_QUERY defined on top of the page. - * 2. A sample mysql output for the query is also given near COLUMNS_QUERY definition on top of the page. - */ - private void getTableInfo(Row row, RowMetadata meta, Map tablesByName) { - final String tableName = row.get("table_name", String.class); - - if (!tablesByName.containsKey(tableName)) { - tablesByName.put(tableName, new DatasourceStructure.Table( - DatasourceStructure.TableType.TABLE, - null, - tableName, - new ArrayList<>(), - new ArrayList<>(), - new ArrayList<>() - )); - } - - final DatasourceStructure.Table table = tablesByName.get(tableName); - table.getColumns().add(new DatasourceStructure.Column( - row.get("column_name", String.class), - row.get("column_type", String.class), - null, - row.get("extra", String.class).contains("auto_increment") - )); - - return; - } - - /** - * 1. Parse results obtained by running KEYS_QUERY defined on top of the page. - * 2. A sample mysql output for the query is also given near KEYS_QUERY definition on top of the page. - */ - private void getKeyInfo(Row row, RowMetadata meta, Map tablesByName, - Map keyRegistry) { - final String constraintName = row.get("constraint_name", String.class); - final char constraintType = row.get("constraint_type", String.class).charAt(0); - final String selfSchema = row.get("self_schema", String.class); - final String tableName = row.get("self_table", String.class); - - - if (!tablesByName.containsKey(tableName)) { - /* do nothing */ - return; - } - - final DatasourceStructure.Table table = tablesByName.get(tableName); - final String keyFullName = tableName + "." + row.get("constraint_name", String.class); - - if (constraintType == 'p') { - if (!keyRegistry.containsKey(keyFullName)) { - final DatasourceStructure.PrimaryKey key = new DatasourceStructure.PrimaryKey( - constraintName, - new ArrayList<>() - ); - keyRegistry.put(keyFullName, key); - table.getKeys().add(key); - } - ((DatasourceStructure.PrimaryKey) keyRegistry.get(keyFullName)).getColumnNames() - .add(row.get("self_column", String.class)); - } else if (constraintType == 'f') { - final String foreignSchema = row.get("foreign_schema", String.class); - final String prefix = (foreignSchema.equalsIgnoreCase(selfSchema) ? "" : foreignSchema + ".") - + row.get("foreign_table", String.class) + "."; - - if (!keyRegistry.containsKey(keyFullName)) { - final DatasourceStructure.ForeignKey key = new DatasourceStructure.ForeignKey( - constraintName, - new ArrayList<>(), - new ArrayList<>() - ); - keyRegistry.put(keyFullName, key); - table.getKeys().add(key); - } - - ((DatasourceStructure.ForeignKey) keyRegistry.get(keyFullName)).getFromColumns() - .add(row.get("self_column", String.class)); - ((DatasourceStructure.ForeignKey) keyRegistry.get(keyFullName)).getToColumns() - .add(prefix + row.get("foreign_column", String.class)); - } - - return; - } - - /** - * 1. Generate template for all tables in the database. - */ - private void getTemplates(Map tablesByName) { - for (DatasourceStructure.Table table : tablesByName.values()) { - final List columnsWithoutDefault = table.getColumns() - .stream() - .filter(column -> column.getDefaultValue() == null) - .collect(Collectors.toList()); - - final List columnNames = new ArrayList<>(); - final List columnValues = new ArrayList<>(); - final StringBuilder setFragments = new StringBuilder(); - - for (DatasourceStructure.Column column : columnsWithoutDefault) { - final String name = column.getName(); - final String type = column.getType(); - String value; - - if (type == null) { - value = "null"; - } else if ("text".equals(type) || "varchar".equals(type)) { - value = "''"; - } else if (type.startsWith("int")) { - value = "1"; - } else if (type.startsWith("double")) { - value = "1.0"; - } else if (DATE_COLUMN_TYPE_NAME.equals(type)) { - value = "'2019-07-01'"; - } else if (DATETIME_COLUMN_TYPE_NAME.equals(type) - || TIMESTAMP_COLUMN_TYPE_NAME.equals(type)) { - value = "'2019-07-01 10:00:00'"; - } else { - value = "''"; - } - - columnNames.add(name); - columnValues.add(value); - setFragments.append("\n ").append(name).append(" = ").append(value).append(","); - } - - // Delete the last comma - if (setFragments.length() > 0) { - setFragments.deleteCharAt(setFragments.length() - 1); - } - - final String tableName = table.getName(); - table.getTemplates().addAll(List.of( - new DatasourceStructure.Template("SELECT", "SELECT * FROM " + tableName + " LIMIT 10;"), - new DatasourceStructure.Template("INSERT", "INSERT INTO " + tableName - + " (" + String.join(", ", columnNames) + ")\n" - + " VALUES (" + String.join(", ", columnValues) + ");"), - new DatasourceStructure.Template("UPDATE", "UPDATE " + tableName + " SET" - + setFragments + "\n" - + " WHERE 1 = 0; -- Specify a valid condition here. Removing the condition may update every row in the table!"), - new DatasourceStructure.Template("DELETE", "DELETE FROM " + tableName - + "\n WHERE 1 = 0; -- Specify a valid condition here. Removing the condition may delete everything in the table!") - )); - } + return MySqlDatasourceUtils.validateDatasource(datasourceConfiguration); } @Override - public Mono getStructure(Connection connection, DatasourceConfiguration datasourceConfiguration) { + public Mono getStructure(ConnectionPool connectionPool, + DatasourceConfiguration datasourceConfiguration) { final DatasourceStructure structure = new DatasourceStructure(); final Map tablesByName = new LinkedHashMap<>(); final Map keyRegistry = new HashMap<>(); - return Mono.from(connection.validate(ValidationDepth.REMOTE)) + return Mono.usingWhen( + connectionPool.create(), + connection -> { + return Mono.from(connection.validate(ValidationDepth.REMOTE)) + .timeout(Duration.ofSeconds(VALIDATION_CHECK_TIMEOUT)) + .onErrorMap(TimeoutException.class, error -> new StaleConnectionException()) + .flatMapMany(isValid -> { + if (isValid) { + return connection.createStatement(COLUMNS_QUERY).execute(); + } else { + return Flux.error(new StaleConnectionException()); + } + }) + .flatMap(result -> { + return result.map((row, meta) -> { + getTableInfo(row, meta, tablesByName); + + return result; + }); + }) + .collectList() + .thenMany(Flux.from(connection.createStatement(KEYS_QUERY).execute())) + .flatMap(result -> { + return result.map((row, meta) -> { + getKeyInfo(row, meta, tablesByName, keyRegistry); + + return result; + }); + }) + .collectList() + .map(list -> { + /* Get templates for each table and put those in. */ + getTemplates(tablesByName); + structure.setTables(new ArrayList<>(tablesByName.values())); + for (DatasourceStructure.Table table : structure.getTables()) { + table.getKeys().sort(Comparator.naturalOrder()); + } + + return structure; + }) + .onErrorMap(e -> { + if (!(e instanceof AppsmithPluginException) && !(e instanceof StaleConnectionException)) { + return new AppsmithPluginException( + AppsmithPluginError.PLUGIN_ERROR, + e.getMessage() + ); + } + + return e; + }); + }, + Connection::close + ) .timeout(Duration.ofSeconds(VALIDATION_CHECK_TIMEOUT)) .onErrorMap(TimeoutException.class, error -> new StaleConnectionException()) - .flatMapMany(isValid -> { - if (isValid) { - return connection.createStatement(COLUMNS_QUERY).execute(); - } else { - return Flux.error(new StaleConnectionException()); - } - }) - .flatMap(result -> { - return result.map((row, meta) -> { - getTableInfo(row, meta, tablesByName); - - return result; - }); - }) - .collectList() - .thenMany(Flux.from(connection.createStatement(KEYS_QUERY).execute())) - .flatMap(result -> { - return result.map((row, meta) -> { - getKeyInfo(row, meta, tablesByName, keyRegistry); - - return result; - }); - }) - .collectList() - .map(list -> { - /* Get templates for each table and put those in. */ - getTemplates(tablesByName); - structure.setTables(new ArrayList<>(tablesByName.values())); - for (DatasourceStructure.Table table : structure.getTables()) { - table.getKeys().sort(Comparator.naturalOrder()); - } - - return structure; - }) - .onErrorMap(e -> { - if (!(e instanceof AppsmithPluginException) && !(e instanceof StaleConnectionException)) { - return new AppsmithPluginException( - AppsmithPluginError.PLUGIN_ERROR, - e.getMessage() - ); - } - - return e; - }) + .onErrorMap(PoolShutdownException.class, error -> new StaleConnectionException()) .subscribeOn(scheduler); } } diff --git a/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/utils/MySqlDatasourceUtils.java b/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/utils/MySqlDatasourceUtils.java new file mode 100644 index 0000000000..029b64ca8d --- /dev/null +++ b/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/utils/MySqlDatasourceUtils.java @@ -0,0 +1,193 @@ +package com.external.utils; + +import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError; +import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException; +import com.appsmith.external.models.DBAuth; +import com.appsmith.external.models.DatasourceConfiguration; +import com.appsmith.external.models.Endpoint; +import com.appsmith.external.models.Property; +import com.appsmith.external.models.SSLDetails; +import io.r2dbc.pool.ConnectionPool; +import io.r2dbc.pool.ConnectionPoolConfiguration; +import io.r2dbc.spi.ConnectionFactoryOptions; +import io.r2dbc.spi.Option; +import org.apache.commons.lang.ObjectUtils; +import org.mariadb.r2dbc.MariadbConnectionConfiguration; +import org.mariadb.r2dbc.MariadbConnectionFactory; +import org.springframework.util.CollectionUtils; +import org.springframework.util.StringUtils; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import static io.r2dbc.pool.PoolingConnectionFactoryProvider.MAX_SIZE; +import static io.r2dbc.spi.ConnectionFactoryOptions.SSL; + +public class MySqlDatasourceUtils { + + public static int MAX_CONNECTION_POOL_SIZE = 5; + + private static final Duration MAX_IDLE_TIME = Duration.ofMinutes(10); + + public static ConnectionFactoryOptions.Builder getBuilder(DatasourceConfiguration datasourceConfiguration) { + DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication(); + + StringBuilder urlBuilder = new StringBuilder(); + if (CollectionUtils.isEmpty(datasourceConfiguration.getEndpoints())) { + urlBuilder.append(datasourceConfiguration.getUrl()); + } else { + urlBuilder.append("r2dbc:pool:mariadb://"); + final List hosts = new ArrayList<>(); + + for (Endpoint endpoint : datasourceConfiguration.getEndpoints()) { + hosts.add(endpoint.getHost() + ":" + ObjectUtils.defaultIfNull(endpoint.getPort(), 3306L)); + } + + urlBuilder.append(String.join(",", hosts)).append("/"); + + if (!StringUtils.isEmpty(authentication.getDatabaseName())) { + urlBuilder.append(authentication.getDatabaseName()); + } + + } + + urlBuilder.append("?zeroDateTimeBehavior=convertToNull&allowMultiQueries=true"); + final List dsProperties = datasourceConfiguration.getProperties(); + + if (dsProperties != null) { + for (Property property : dsProperties) { + if ("serverTimezone".equals(property.getKey()) && !StringUtils.isEmpty(property.getValue())) { + urlBuilder.append("&serverTimezone=").append(property.getValue()); + break; + } + } + } + + ConnectionFactoryOptions baseOptions = ConnectionFactoryOptions.parse(urlBuilder.toString()); + ConnectionFactoryOptions.Builder ob = ConnectionFactoryOptions.builder().from(baseOptions) + .option(ConnectionFactoryOptions.USER, authentication.getUsername()) + .option(ConnectionFactoryOptions.PASSWORD, authentication.getPassword()); + + return ob; + } + + public static ConnectionFactoryOptions.Builder addSslOptionsToBuilder(DatasourceConfiguration datasourceConfiguration, + ConnectionFactoryOptions.Builder ob) throws AppsmithPluginException { + /* + * - Ideally, it is never expected to be null because the SSL dropdown is set to a initial value. + */ + if (datasourceConfiguration.getConnection() == null + || datasourceConfiguration.getConnection().getSsl() == null + || datasourceConfiguration.getConnection().getSsl().getAuthType() == null) { + throw new AppsmithPluginException( + AppsmithPluginError.PLUGIN_ERROR, + "Appsmith server has failed to fetch SSL configuration from datasource configuration form. " + + "Please reach out to Appsmith customer support to resolve this."); + } + + /* + * - By default, the driver configures SSL in the preferred mode. + */ + SSLDetails.AuthType sslAuthType = datasourceConfiguration.getConnection().getSsl().getAuthType(); + switch (sslAuthType) { + case REQUIRED: + ob = ob + .option(SSL, true) + .option(Option.valueOf("sslMode"), sslAuthType.toString().toLowerCase()); + + break; + case DISABLED: + ob = ob.option(SSL, false); + + break; + case DEFAULT: + /* do nothing - accept default driver setting*/ + + break; + default: + throw new AppsmithPluginException( + AppsmithPluginError.PLUGIN_ERROR, + "Appsmith server has found an unexpected SSL option: " + sslAuthType + ". Please reach out to" + + " Appsmith customer support to resolve this."); + } + + return ob; + } + + public static Set validateDatasource(DatasourceConfiguration datasourceConfiguration) { + Set invalids = new HashSet<>(); + + if (datasourceConfiguration.getConnection() != null + && datasourceConfiguration.getConnection().getMode() == null) { + invalids.add("Missing Connection Mode."); + } + + if (StringUtils.isEmpty(datasourceConfiguration.getUrl()) && + CollectionUtils.isEmpty(datasourceConfiguration.getEndpoints())) { + invalids.add("Missing endpoint and url"); + } else if (!CollectionUtils.isEmpty(datasourceConfiguration.getEndpoints())) { + for (final Endpoint endpoint : datasourceConfiguration.getEndpoints()) { + if (endpoint.getHost() == null || endpoint.getHost().isBlank()) { + invalids.add("Host value cannot be empty"); + } else if (endpoint.getHost().contains("/") || endpoint.getHost().contains(":")) { + invalids.add("Host value cannot contain `/` or `:` characters. Found `" + endpoint.getHost() + "`."); + } + } + } + + if (datasourceConfiguration.getAuthentication() == null) { + invalids.add("Missing authentication details."); + } else { + DBAuth authentication = (DBAuth) datasourceConfiguration.getAuthentication(); + if (StringUtils.isEmpty(authentication.getUsername())) { + invalids.add("Missing username for authentication."); + } + + if (StringUtils.isEmpty(authentication.getPassword()) && StringUtils.isEmpty(authentication.getUsername())) { + invalids.add("Missing password for authentication."); + } else if (StringUtils.isEmpty(authentication.getPassword())) { + // it is valid if it has the username but not the password + authentication.setPassword(""); + } + + if (StringUtils.isEmpty(authentication.getDatabaseName())) { + invalids.add("Missing database name."); + } + } + + /* + * - Ideally, it is never expected to be null because the SSL dropdown is set to a initial value. + */ + if (datasourceConfiguration.getConnection() == null + || datasourceConfiguration.getConnection().getSsl() == null + || datasourceConfiguration.getConnection().getSsl().getAuthType() == null) { + invalids.add("Appsmith server has failed to fetch SSL configuration from datasource configuration form. " + + "Please reach out to Appsmith customer support to resolve this."); + } + + return invalids; + } + + public static ConnectionPool getNewConnectionPool(DatasourceConfiguration datasourceConfiguration) throws AppsmithPluginException { + ConnectionFactoryOptions.Builder ob = getBuilder(datasourceConfiguration); + ob = addSslOptionsToBuilder(datasourceConfiguration, ob); + MariadbConnectionFactory connectionFactory = + MariadbConnectionFactory.from( + MariadbConnectionConfiguration.fromOptions(ob.build()) + .allowPublicKeyRetrieval(true).build() + ); + + /** + * The pool configuration object does not seem to have any option to set the minimum pool size, hence could + * not configure the minimum pool size. + */ + ConnectionPoolConfiguration configuration = ConnectionPoolConfiguration.builder(connectionFactory) + .maxIdleTime(MAX_IDLE_TIME) + .maxSize(MAX_CONNECTION_POOL_SIZE) + .build(); + return new ConnectionPool(configuration); + } +} diff --git a/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/utils/MySqlGetStructureUtils.java b/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/utils/MySqlGetStructureUtils.java new file mode 100644 index 0000000000..7bd5c6f403 --- /dev/null +++ b/app/server/appsmith-plugins/mysqlPlugin/src/main/java/com/external/utils/MySqlGetStructureUtils.java @@ -0,0 +1,162 @@ +package com.external.utils; + +import com.appsmith.external.models.DatasourceStructure; +import io.r2dbc.spi.Row; +import io.r2dbc.spi.RowMetadata; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class MySqlGetStructureUtils { + + private static final String DATE_COLUMN_TYPE_NAME = "date"; + private static final String DATETIME_COLUMN_TYPE_NAME = "datetime"; + private static final String TIMESTAMP_COLUMN_TYPE_NAME = "timestamp"; + + /** + * 1. Parse results obtained by running COLUMNS_QUERY defined on top of the page. + * 2. A sample mysql output for the query is also given near COLUMNS_QUERY definition on top of the page. + */ + public static void getTableInfo(Row row, RowMetadata meta, Map tablesByName) { + final String tableName = row.get("table_name", String.class); + + if (!tablesByName.containsKey(tableName)) { + tablesByName.put(tableName, new DatasourceStructure.Table( + DatasourceStructure.TableType.TABLE, + null, + tableName, + new ArrayList<>(), + new ArrayList<>(), + new ArrayList<>() + )); + } + + final DatasourceStructure.Table table = tablesByName.get(tableName); + table.getColumns().add(new DatasourceStructure.Column( + row.get("column_name", String.class), + row.get("column_type", String.class), + null, + row.get("extra", String.class).contains("auto_increment") + )); + + return; + } + + /** + * 1. Parse results obtained by running KEYS_QUERY defined on top of the page. + * 2. A sample mysql output for the query is also given near KEYS_QUERY definition on top of the page. + */ + public static void getKeyInfo(Row row, RowMetadata meta, Map tablesByName, + Map keyRegistry) { + final String constraintName = row.get("constraint_name", String.class); + final char constraintType = row.get("constraint_type", String.class).charAt(0); + final String selfSchema = row.get("self_schema", String.class); + final String tableName = row.get("self_table", String.class); + + + if (!tablesByName.containsKey(tableName)) { + /* do nothing */ + return; + } + + final DatasourceStructure.Table table = tablesByName.get(tableName); + final String keyFullName = tableName + "." + row.get("constraint_name", String.class); + + if (constraintType == 'p') { + if (!keyRegistry.containsKey(keyFullName)) { + final DatasourceStructure.PrimaryKey key = new DatasourceStructure.PrimaryKey( + constraintName, + new ArrayList<>() + ); + keyRegistry.put(keyFullName, key); + table.getKeys().add(key); + } + ((DatasourceStructure.PrimaryKey) keyRegistry.get(keyFullName)).getColumnNames() + .add(row.get("self_column", String.class)); + } else if (constraintType == 'f') { + final String foreignSchema = row.get("foreign_schema", String.class); + final String prefix = (foreignSchema.equalsIgnoreCase(selfSchema) ? "" : foreignSchema + ".") + + row.get("foreign_table", String.class) + "."; + + if (!keyRegistry.containsKey(keyFullName)) { + final DatasourceStructure.ForeignKey key = new DatasourceStructure.ForeignKey( + constraintName, + new ArrayList<>(), + new ArrayList<>() + ); + keyRegistry.put(keyFullName, key); + table.getKeys().add(key); + } + + ((DatasourceStructure.ForeignKey) keyRegistry.get(keyFullName)).getFromColumns() + .add(row.get("self_column", String.class)); + ((DatasourceStructure.ForeignKey) keyRegistry.get(keyFullName)).getToColumns() + .add(prefix + row.get("foreign_column", String.class)); + } + + return; + } + + /** + * 1. Generate template for all tables in the database. + */ + public static void getTemplates(Map tablesByName) { + for (DatasourceStructure.Table table : tablesByName.values()) { + final List columnsWithoutDefault = table.getColumns() + .stream() + .filter(column -> column.getDefaultValue() == null) + .collect(Collectors.toList()); + + final List columnNames = new ArrayList<>(); + final List columnValues = new ArrayList<>(); + final StringBuilder setFragments = new StringBuilder(); + + for (DatasourceStructure.Column column : columnsWithoutDefault) { + final String name = column.getName(); + final String type = column.getType(); + String value; + + if (type == null) { + value = "null"; + } else if ("text".equals(type) || "varchar".equals(type)) { + value = "''"; + } else if (type.startsWith("int")) { + value = "1"; + } else if (type.startsWith("double")) { + value = "1.0"; + } else if (DATE_COLUMN_TYPE_NAME.equals(type)) { + value = "'2019-07-01'"; + } else if (DATETIME_COLUMN_TYPE_NAME.equals(type) + || TIMESTAMP_COLUMN_TYPE_NAME.equals(type)) { + value = "'2019-07-01 10:00:00'"; + } else { + value = "''"; + } + + columnNames.add(name); + columnValues.add(value); + setFragments.append("\n ").append(name).append(" = ").append(value).append(","); + } + + // Delete the last comma + if (setFragments.length() > 0) { + setFragments.deleteCharAt(setFragments.length() - 1); + } + + final String tableName = table.getName(); + table.getTemplates().addAll(List.of( + new DatasourceStructure.Template("SELECT", "SELECT * FROM " + tableName + " LIMIT 10;"), + new DatasourceStructure.Template("INSERT", "INSERT INTO " + tableName + + " (" + String.join(", ", columnNames) + ")\n" + + " VALUES (" + String.join(", ", columnValues) + ");"), + new DatasourceStructure.Template("UPDATE", "UPDATE " + tableName + " SET" + + setFragments + "\n" + + " WHERE 1 = 0; -- Specify a valid condition here. Removing the condition may update every row in the table!"), + new DatasourceStructure.Template("DELETE", "DELETE FROM " + tableName + + "\n WHERE 1 = 0; -- Specify a valid condition here. Removing the condition may delete everything in the table!") + )); + } + } +} diff --git a/app/server/appsmith-plugins/mysqlPlugin/src/main/resources/form.json b/app/server/appsmith-plugins/mysqlPlugin/src/main/resources/form.json index b99c0a5ddc..f666e9a371 100644 --- a/app/server/appsmith-plugins/mysqlPlugin/src/main/resources/form.json +++ b/app/server/appsmith-plugins/mysqlPlugin/src/main/resources/form.json @@ -87,10 +87,6 @@ "label": "Default", "value": "DEFAULT" }, - { - "label": "Preferred", - "value": "PREFERRED" - }, { "label": "Required", "value": "REQUIRED" diff --git a/app/server/appsmith-plugins/mysqlPlugin/src/test/java/com/external/plugins/MySqlPluginTest.java b/app/server/appsmith-plugins/mysqlPlugin/src/test/java/com/external/plugins/MySqlPluginTest.java index 23b9e3cdbf..56ca2b702d 100755 --- a/app/server/appsmith-plugins/mysqlPlugin/src/test/java/com/external/plugins/MySqlPluginTest.java +++ b/app/server/appsmith-plugins/mysqlPlugin/src/test/java/com/external/plugins/MySqlPluginTest.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.JsonNodeType; +import io.r2dbc.pool.ConnectionPool; import io.r2dbc.spi.Connection; import io.r2dbc.spi.ConnectionFactories; import io.r2dbc.spi.ConnectionFactoryOptions; @@ -26,6 +27,9 @@ import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import org.mariadb.r2dbc.MariadbConnectionConfiguration; +import org.mariadb.r2dbc.MariadbConnectionFactory; +import org.reactivestreams.Publisher; import org.testcontainers.containers.MySQLContainer; import org.testcontainers.containers.MySQLR2DBCDatabaseContainer; import org.testcontainers.junit.jupiter.Container; @@ -60,27 +64,27 @@ import static org.mockito.Mockito.spy; @Testcontainers public class MySqlPluginTest { - MySqlPlugin.MySqlPluginExecutor pluginExecutor = new MySqlPlugin.MySqlPluginExecutor(); + static MySqlPlugin.MySqlPluginExecutor pluginExecutor = new MySqlPlugin.MySqlPluginExecutor(); @SuppressWarnings("rawtypes") // The type parameter for the container type is just itself and is - // pseudo-optional. + // pseudo-optional. @Container public static MySQLContainer mySQLContainer = new MySQLContainer( - DockerImageName.parse("mysql/mysql-server:8.0.25").asCompatibleSubstituteFor("mysql")) - .withUsername("mysql") - .withPassword("password") - .withDatabaseName("test_db"); + DockerImageName.parse("mysql/mysql-server:8.0.25").asCompatibleSubstituteFor("mysql")) + .withUsername("mysql") + .withPassword("password") + .withDatabaseName("test_db"); @SuppressWarnings("rawtypes") // The type parameter for the container type is just itself and is - // pseudo-optional. + // pseudo-optional. @Container public static MySQLContainer mySQLContainerWithInvalidTimezone = (MySQLContainer) new MySQLContainer( - DockerImageName.parse("mysql/mysql-server:8.0.25").asCompatibleSubstituteFor("mysql")) - .withUsername("root") - .withPassword("") - .withDatabaseName("test_db") - .withEnv("TZ", "PDT") - .withEnv("MYSQL_ROOT_HOST", "%"); + DockerImageName.parse("mysql/mysql-server:8.0.25").asCompatibleSubstituteFor("mysql")) + .withUsername("root") + .withPassword("") + .withDatabaseName("test_db") + .withEnv("TZ", "PDT") + .withEnv("MYSQL_ROOT_HOST", "%"); private static String address; private static Integer port; @@ -89,6 +93,16 @@ public class MySqlPluginTest { private static String database; private static DatasourceConfiguration dsConfig; + private static Mono getConnectionMonoFromContainer(MySQLContainer mySQLContainer) { + ConnectionFactoryOptions baseOptions = MySQLR2DBCDatabaseContainer.getOptions(mySQLContainer); + ConnectionFactoryOptions.Builder ob = ConnectionFactoryOptions.builder().from(baseOptions); + MariadbConnectionConfiguration conf = MariadbConnectionConfiguration.fromOptions(ob.build()) + .allowPublicKeyRetrieval(true) + .build(); + MariadbConnectionFactory connFactory = new MariadbConnectionFactory(conf); + return connFactory.create(); + } + @BeforeAll public static void setUp() { address = mySQLContainer.getContainerIpAddress(); @@ -98,57 +112,54 @@ public class MySqlPluginTest { database = mySQLContainer.getDatabaseName(); dsConfig = createDatasourceConfiguration(); - ConnectionFactoryOptions baseOptions = MySQLR2DBCDatabaseContainer.getOptions(mySQLContainer); - ConnectionFactoryOptions.Builder ob = ConnectionFactoryOptions.builder().from(baseOptions); - - Mono.from(ConnectionFactories.get(ob.build()).create()) - .map(connection -> { - return connection.createBatch() - .add("DROP TABLE IF EXISTS possessions") - .add("DROP TABLE IF EXISTS users") - .add("create table users (\n" + - " id int auto_increment primary key,\n" + - " username varchar (250) unique not null,\n" - + - " password varchar (250) not null,\n" + - " email varchar (250) unique not null,\n" + - " spouse_dob date,\n" + - " dob date not null,\n" + - " yob year not null,\n" + - " time1 time not null,\n" + - " created_on timestamp not null,\n" + - " updated_on datetime not null,\n" + - " constraint unique index (username, email)\n" - + - ")") - .add("create table possessions (\n" + - " id int primary key,\n" + - " title varchar (250) not null,\n" + - " user_id int not null,\n" + - " username varchar (250) not null,\n" + - " email varchar (250) not null\n" + - ")") - .add("alter table possessions add foreign key (username, email) \n" - + - "references users (username, email)") - .add("SET SESSION sql_mode = '';\n") - .add("INSERT INTO users VALUES (" + - "1, 'Jack', 'jill', 'jack@exemplars.com', NULL, '2018-12-31', 2018," - + - " '18:32:45'," + - " '2018-11-30 20:45:15', '0000-00-00 00:00:00'" - + - ")") - .add("INSERT INTO users VALUES (" + - "2, 'Jill', 'jack', 'jill@exemplars.com', NULL, '2019-12-31', 2019," - + - " '15:45:30'," + - " '2019-11-30 23:59:59', '2019-11-30 23:59:59'" - + - ")"); - }) - .flatMapMany(batch -> Flux.from(batch.execute())) - .blockLast(); // wait until completion of all the queries + Mono.from(getConnectionMonoFromContainer(mySQLContainer)) + .map(connection -> { + return connection.createBatch() + .add("DROP TABLE IF EXISTS possessions") + .add("DROP TABLE IF EXISTS users") + .add("create table users (\n" + + " id int auto_increment primary key,\n" + + " username varchar (250) unique not null,\n" + + + " password varchar (250) not null,\n" + + " email varchar (250) unique not null,\n" + + " spouse_dob date,\n" + + " dob date not null,\n" + + " yob year not null,\n" + + " time1 time not null,\n" + + " created_on timestamp not null,\n" + + " updated_on datetime not null,\n" + + " constraint unique index (username, email)\n" + + + ")") + .add("create table possessions (\n" + + " id int primary key,\n" + + " title varchar (250) not null,\n" + + " user_id int not null,\n" + + " username varchar (250) not null,\n" + + " email varchar (250) not null\n" + + ")") + .add("alter table possessions add foreign key (username, email) \n" + + + "references users (username, email)") + .add("SET SESSION sql_mode = '';\n") + .add("INSERT INTO users VALUES (" + + "1, 'Jack', 'jill', 'jack@exemplars.com', NULL, '2018-12-31', 2018," + + + " '18:32:45'," + + " '2018-11-30 20:45:15', '0000-00-00 00:00:00'" + + + ")") + .add("INSERT INTO users VALUES (" + + "2, 'Jill', 'jack', 'jill@exemplars.com', NULL, '2019-12-31', 2019," + + + " '15:45:30'," + + " '2019-11-30 23:59:59', '2019-11-30 23:59:59'" + + + ")"); + }) + .flatMapMany(batch -> Flux.from(batch.execute())) + .blockLast(); // wait until completion of all the queries return; } @@ -181,11 +192,11 @@ public class MySqlPluginTest { @Test public void testConnectMySQLContainer() { - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); StepVerifier.create(dsConnectionMono) - .assertNext(Assertions::assertNotNull) - .verifyComplete(); + .assertNext(Assertions::assertNotNull) + .verifyComplete(); } @Test @@ -193,13 +204,13 @@ public class MySqlPluginTest { final DatasourceConfiguration dsConfig = createDatasourceConfigForContainerWithInvalidTZ(); dsConfig.setProperties(List.of( - new Property("serverTimezone", "UTC"))); + new Property("serverTimezone", "UTC"))); - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); StepVerifier.create(dsConnectionMono) - .assertNext(Assertions::assertNotNull) - .verifyComplete(); + .assertNext(Assertions::assertNotNull) + .verifyComplete(); } @Test @@ -208,18 +219,18 @@ public class MySqlPluginTest { /* Expect no error */ StepVerifier.create(pluginExecutor.testDatasource(dsConfig)) - .assertNext(datasourceTestResult -> { - assertEquals(0, datasourceTestResult.getInvalids().size()); - }) - .verifyComplete(); + .assertNext(datasourceTestResult -> { + assertEquals(0, datasourceTestResult.getInvalids().size()); + }) + .verifyComplete(); /* Create bad datasource configuration and expect error */ dsConfig.getEndpoints().get(0).setHost("badHost"); StepVerifier.create(pluginExecutor.testDatasource(dsConfig)) - .assertNext(datasourceTestResult -> { - assertNotEquals(0, datasourceTestResult.getInvalids().size()); - }) - .verifyComplete(); + .assertNext(datasourceTestResult -> { + assertNotEquals(0, datasourceTestResult.getInvalids().size()); + }) + .verifyComplete(); /* Reset dsConfig */ dsConfig = createDatasourceConfiguration(); @@ -254,23 +265,20 @@ public class MySqlPluginTest { @Test public void testDatasourceWithNullPassword() { - final DatasourceConfiguration dsConfig = createDatasourceConfigForContainerWithInvalidTZ(); - // adding a user with empty password - ConnectionFactoryOptions baseOptions = MySQLR2DBCDatabaseContainer - .getOptions(mySQLContainerWithInvalidTimezone); - ConnectionFactoryOptions.Builder ob = ConnectionFactoryOptions.builder().from(baseOptions); - - Mono.from(ConnectionFactories.get(ob.build()).create()) - .map(connection -> connection.createBatch() - // adding a new user called 'mysql' with empty password - .add("CREATE USER 'mysql'@'%';\n" + - "GRANT ALL PRIVILEGES ON *.* TO 'mysql'@'%' WITH GRANT OPTION;\n" - + - "FLUSH PRIVILEGES;")) - .flatMapMany(batch -> Flux.from(batch.execute())) - .blockLast(); // wait until completion of all the queries + String sqlCmd = "CREATE USER 'mysql'@'%' IDENTIFIED BY '';" + + "GRANT ALL PRIVILEGES ON *.* TO 'mysql'@'%' WITH GRANT OPTION;" + + "FLUSH PRIVILEGES;"; + Mono.from(getConnectionMonoFromContainer(mySQLContainerWithInvalidTimezone)) + .map(connection -> connection.createBatch() + .add("CREATE USER 'mysql'@'%' IDENTIFIED BY '';") + .add("GRANT ALL PRIVILEGES ON *.* TO 'mysql'@'%' WITH GRANT OPTION;") + .add("FLUSH PRIVILEGES;") + ) + .flatMapMany(batch -> Flux.from(batch.execute())) + .blockLast(); // wait until completion of all the queries + final DatasourceConfiguration dsConfig = createDatasourceConfigForContainerWithInvalidTZ(); // change to ordinary user DBAuth auth = ((DBAuth) dsConfig.getAuthentication()); auth.setPassword(""); @@ -284,17 +292,17 @@ public class MySqlPluginTest { Set output = pluginExecutor.validateDatasource(dsConfig); assertTrue(output.isEmpty()); // test connect - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); StepVerifier.create(dsConnectionMono) - .assertNext(Assertions::assertNotNull) - .verifyComplete(); + .assertNext(Assertions::assertNotNull) + .verifyComplete(); /* Expect no error */ StepVerifier.create(pluginExecutor.testDatasource(dsConfig)) - .assertNext(datasourceTestResult -> assertEquals(0, - datasourceTestResult.getInvalids().size())) - .verifyComplete(); + .assertNext(datasourceTestResult -> assertEquals(0, + datasourceTestResult.getInvalids().size())) + .verifyComplete(); } @Test @@ -310,115 +318,114 @@ public class MySqlPluginTest { Set output = pluginExecutor.validateDatasource(dsConfig); assertTrue(output.isEmpty()); // test connect - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); StepVerifier.create(dsConnectionMono) - .assertNext(Assertions::assertNotNull) - .verifyComplete(); + .assertNext(Assertions::assertNotNull) + .verifyComplete(); /* Expect no error */ StepVerifier.create(pluginExecutor.testDatasource(dsConfig)) - .assertNext(datasourceTestResult -> assertEquals(0, - datasourceTestResult.getInvalids().size())) - .verifyComplete(); + .assertNext(datasourceTestResult -> assertEquals(0, + datasourceTestResult.getInvalids().size())) + .verifyComplete(); } @Test public void testExecute() { - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); ActionConfiguration actionConfiguration = new ActionConfiguration(); actionConfiguration.setBody("show databases"); Mono executeMono = dsConnectionMono.flatMap(conn -> pluginExecutor.executeParameterized(conn, - new ExecuteActionDTO(), dsConfig, actionConfiguration)); + new ExecuteActionDTO(), dsConfig, actionConfiguration)); StepVerifier.create(executeMono) - .assertNext(obj -> { - ActionExecutionResult result = (ActionExecutionResult) obj; - assertNotNull(result); - assertTrue(result.getIsExecutionSuccess()); - assertNotNull(result.getBody()); - }) - .verifyComplete(); + .assertNext(obj -> { + ActionExecutionResult result = (ActionExecutionResult) obj; + assertNotNull(result); + assertTrue(result.getIsExecutionSuccess()); + assertNotNull(result.getBody()); + }) + .verifyComplete(); } @Test public void testExecuteWithFormattingWithShowCmd() { dsConfig = createDatasourceConfiguration(); - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); ActionConfiguration actionConfiguration = new ActionConfiguration(); actionConfiguration.setBody("show\n\tdatabases"); Mono executeMono = dsConnectionMono.flatMap(conn -> pluginExecutor.executeParameterized(conn, - new ExecuteActionDTO(), dsConfig, actionConfiguration)); + new ExecuteActionDTO(), dsConfig, actionConfiguration)); StepVerifier.create(executeMono) - .assertNext(obj -> { - ActionExecutionResult result = (ActionExecutionResult) obj; - assertNotNull(result); - assertTrue(result.getIsExecutionSuccess()); - assertNotNull(result.getBody()); - String expectedBody = "[{\"Database\":\"information_schema\"},{\"Database\":\"test_db\"}]"; - assertEquals(expectedBody, result.getBody().toString()); - }) - .verifyComplete(); + .assertNext(obj -> { + ActionExecutionResult result = (ActionExecutionResult) obj; + assertNotNull(result); + assertTrue(result.getIsExecutionSuccess()); + assertNotNull(result.getBody()); + String expectedBody = "[{\"Database\":\"information_schema\"},{\"Database\":\"test_db\"}]"; + assertEquals(expectedBody, result.getBody().toString()); + }) + .verifyComplete(); } @Test public void testExecuteWithFormattingWithSelectCmd() { dsConfig = createDatasourceConfiguration(); - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); ActionConfiguration actionConfiguration = new ActionConfiguration(); actionConfiguration.setBody("select\n\t*\nfrom\nusers where id=1"); Mono executeMono = dsConnectionMono.flatMap(conn -> pluginExecutor.executeParameterized(conn, - new ExecuteActionDTO(), dsConfig, actionConfiguration)); + new ExecuteActionDTO(), dsConfig, actionConfiguration)); StepVerifier.create(executeMono) - .assertNext(obj -> { - ActionExecutionResult result = (ActionExecutionResult) obj; - assertNotNull(result); - assertTrue(result.getIsExecutionSuccess()); - assertNotNull(result.getBody()); - final JsonNode node = ((ArrayNode) result.getBody()).get(0); - assertEquals("2018-12-31", node.get("dob").asText()); - assertEquals("2018", node.get("yob").asText()); - assertEquals("Jack", node.get("username").asText()); - assertEquals("jill", node.get("password").asText()); - assertEquals("1", node.get("id").asText()); - assertEquals("jack@exemplars.com", node.get("email").asText()); - assertEquals("18:32:45", node.get("time1").asText()); - assertEquals("2018-11-30T20:45:15Z", node.get("created_on").asText()); + .assertNext(obj -> { + ActionExecutionResult result = (ActionExecutionResult) obj; + assertNotNull(result); + assertTrue(result.getIsExecutionSuccess()); + assertNotNull(result.getBody()); + final JsonNode node = ((ArrayNode) result.getBody()).get(0); + assertEquals("2018-12-31", node.get("dob").asText()); + assertEquals("2018", node.get("yob").asText()); + assertEquals("Jack", node.get("username").asText()); + assertEquals("jill", node.get("password").asText()); + assertEquals("1", node.get("id").asText()); + assertEquals("jack@exemplars.com", node.get("email").asText()); + assertEquals("18:32:45", node.get("time1").asText()); + assertEquals("2018-11-30T20:45:15Z", node.get("created_on").asText()); - /* - * - RequestParamDTO object only have attributes configProperty and value at - * this point. - * - The other two RequestParamDTO attributes - label and type are null at this - * point. - */ - List expectedRequestParams = new ArrayList<>(); - expectedRequestParams.add(new RequestParamDTO(ACTION_CONFIGURATION_BODY, - actionConfiguration.getBody(), null, null, new HashMap<>())); - assertEquals(result.getRequest().getRequestParams().toString(), - expectedRequestParams.toString()); - }) - .verifyComplete(); + /* + * - RequestParamDTO object only have attributes configProperty and value at + * this point. + * - The other two RequestParamDTO attributes - label and type are null at this + * point. + */ + List expectedRequestParams = new ArrayList<>(); + expectedRequestParams.add(new RequestParamDTO(ACTION_CONFIGURATION_BODY, + actionConfiguration.getBody(), null, null, new HashMap<>())); + assertEquals(result.getRequest().getRequestParams().toString(), + expectedRequestParams.toString()); + }) + .verifyComplete(); } @Test public void testStaleConnectionCheck() { ActionConfiguration actionConfiguration = new ActionConfiguration(); actionConfiguration.setBody("show databases"); - Connection connection = pluginExecutor.datasourceCreate(dsConfig).block(); - - Flux resultFlux = Mono.from(connection.close()) - .thenMany(pluginExecutor.executeParameterized(connection, new ExecuteActionDTO(), - dsConfig, actionConfiguration)); + ConnectionPool connectionPool = pluginExecutor.datasourceCreate(dsConfig).block(); + Flux resultFlux = Mono.from(connectionPool.disposeLater()) + .thenMany(pluginExecutor.executeParameterized(connectionPool, new ExecuteActionDTO(), + dsConfig, actionConfiguration)); StepVerifier.create(resultFlux) - .expectErrorMatches(throwable -> throwable instanceof StaleConnectionException) - .verify(); + .expectErrorMatches(throwable -> throwable instanceof StaleConnectionException) + .verify(); } @Test @@ -438,8 +445,8 @@ public class MySqlPluginTest { ((DBAuth) dsConfig.getAuthentication()).setDatabaseName(""); Set output = pluginExecutor.validateDatasource(dsConfig); assertTrue(output - .stream() - .anyMatch(error -> error.contains("Missing database name."))); + .stream() + .anyMatch(error -> error.contains("Missing database name."))); } @Test @@ -447,8 +454,8 @@ public class MySqlPluginTest { dsConfig.setEndpoints(null); Set output = pluginExecutor.validateDatasource(dsConfig); assertTrue(output - .stream() - .anyMatch(error -> error.contains("Missing endpoint and url"))); + .stream() + .anyMatch(error -> error.contains("Missing endpoint and url"))); } @Test @@ -456,8 +463,8 @@ public class MySqlPluginTest { dsConfig.setEndpoints(List.of(new Endpoint())); Set output = pluginExecutor.validateDatasource(dsConfig); assertTrue(output - .stream() - .anyMatch(error -> error.contains("Host value cannot be empty"))); + .stream() + .anyMatch(error -> error.contains("Host value cannot be empty"))); Endpoint endpoint = new Endpoint(); endpoint.setHost(address); @@ -471,34 +478,34 @@ public class MySqlPluginTest { dsConfig.getEndpoints().get(0).setHost(hostname); Set output = pluginExecutor.validateDatasource(dsConfig); assertTrue(output.contains( - "Host value cannot contain `/` or `:` characters. Found `" + hostname + "`.")); + "Host value cannot contain `/` or `:` characters. Found `" + hostname + "`.")); } @Test public void testAliasColumnNames() { DatasourceConfiguration dsConfig = createDatasourceConfiguration(); - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); ActionConfiguration actionConfiguration = new ActionConfiguration(); actionConfiguration.setBody("SELECT id as user_id FROM users WHERE id = 1"); Mono executeMono = dsConnectionMono - .flatMap(conn -> pluginExecutor.executeParameterized(conn, new ExecuteActionDTO(), - dsConfig, actionConfiguration)); + .flatMap(conn -> pluginExecutor.executeParameterized(conn, new ExecuteActionDTO(), + dsConfig, actionConfiguration)); StepVerifier.create(executeMono) - .assertNext(result -> { - final JsonNode node = ((ArrayNode) result.getBody()).get(0); - assertArrayEquals( - new String[] { - "user_id" - }, - new ObjectMapper() - .convertValue(node, LinkedHashMap.class) - .keySet() - .toArray()); - }) - .verifyComplete(); + .assertNext(result -> { + final JsonNode node = ((ArrayNode) result.getBody()).get(0); + assertArrayEquals( + new String[]{ + "user_id" + }, + new ObjectMapper() + .convertValue(node, LinkedHashMap.class) + .keySet() + .toArray()); + }) + .verifyComplete(); return; } @@ -506,7 +513,7 @@ public class MySqlPluginTest { @Test public void testPreparedStatementErrorWithIsKeyword() { DatasourceConfiguration dsConfig = createDatasourceConfiguration(); - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); ActionConfiguration actionConfiguration = new ActionConfiguration(); /** @@ -535,35 +542,33 @@ public class MySqlPluginTest { executeActionDTO.setParams(params); Mono executeMono = dsConnectionMono - .flatMap(conn -> pluginExecutor.executeParameterized(conn, executeActionDTO, dsConfig, - actionConfiguration)); + .flatMap(conn -> pluginExecutor.executeParameterized(conn, executeActionDTO, dsConfig, + actionConfiguration)); StepVerifier.create(executeMono) - .verifyErrorSatisfies(error -> { - assertTrue(error instanceof AppsmithPluginException); - String expectedMessage = "Appsmith currently does not support the IS keyword with the prepared " - + - "statement setting turned ON. Please re-write your SQL query without the IS keyword or " - + - "turn OFF (unsafe) the 'Use prepared statement' knob from the settings tab."; - assertTrue(expectedMessage.equals(error.getMessage())); - }); + .verifyErrorSatisfies(error -> { + assertTrue(error instanceof AppsmithPluginException); + String expectedMessage = "Appsmith currently does not support the IS keyword with the prepared " + + + "statement setting turned ON. Please re-write your SQL query without the IS keyword or " + + + "turn OFF (unsafe) the 'Use prepared statement' knob from the settings tab."; + assertTrue(expectedMessage.equals(error.getMessage())); + }); } @Test public void testPreparedStatementWithRealTypes() { - ConnectionFactoryOptions baseOptions = MySQLR2DBCDatabaseContainer.getOptions(mySQLContainer); - ConnectionFactoryOptions.Builder ob = ConnectionFactoryOptions.builder().from(baseOptions); - Mono.from(ConnectionFactories.get(ob.build()).create()) - .map(connection -> connection.createBatch() - .add("create table test_real_types(id int, c_float float, c_double double, c_real real)") - .add("insert into test_real_types values (1, 1.123, 3.123, 5.123)") - .add("insert into test_real_types values (2, 11.123, 13.123, 15.123)")) - .flatMapMany(batch -> Flux.from(batch.execute())) - .blockLast(); // wait until completion of all the queries + Mono.from(getConnectionMonoFromContainer(mySQLContainer)) + .map(connection -> connection.createBatch() + .add("create table test_real_types(id int, c_float float, c_double double, c_real real)") + .add("insert into test_real_types values (1, 1.123, 3.123, 5.123)") + .add("insert into test_real_types values (2, 11.123, 13.123, 15.123)")) + .flatMapMany(batch -> Flux.from(batch.execute())) + .blockLast(); // wait until completion of all the queries DatasourceConfiguration dsConfig = createDatasourceConfiguration(); - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); ActionConfiguration actionConfiguration = new ActionConfiguration(); /** @@ -575,8 +580,8 @@ public class MySqlPluginTest { * - Ref: https://dev.mysql.com/doc/refman/8.0/en/problems-with-float.html */ actionConfiguration.setBody( - "SELECT id FROM test_real_types WHERE ABS(c_float - {{binding1}}) < 0.1 AND ABS" + - "(c_double - {{binding2}}) < 0.1 AND ABS(c_real - {{binding3}}) < 0.1;"); + "SELECT id FROM test_real_types WHERE ABS(c_float - {{binding1}}) < 0.1 AND ABS" + + "(c_double - {{binding2}}) < 0.1 AND ABS(c_real - {{binding3}}) < 0.1;"); List pluginSpecifiedTemplates = new ArrayList<>(); pluginSpecifiedTemplates.add(new Property("preparedStatement", "true")); @@ -605,41 +610,43 @@ public class MySqlPluginTest { executeActionDTO.setParams(params); Mono executeMono = dsConnectionMono - .flatMap(conn -> pluginExecutor.executeParameterized(conn, executeActionDTO, dsConfig, - actionConfiguration)); + .flatMap(conn -> pluginExecutor.executeParameterized(conn, executeActionDTO, dsConfig, + actionConfiguration)); StepVerifier.create(executeMono) - .assertNext(result -> { - final JsonNode node = ((ArrayNode) result.getBody()); - assertEquals(1, node.size()); - // Verify selected row id. - assertEquals(1, node.get(0).get("id").asInt()); - }) - .verifyComplete(); + .assertNext(result -> { + final JsonNode node = ((ArrayNode) result.getBody()); + assertEquals(1, node.size()); + // Verify selected row id. + assertEquals(1, node.get(0).get("id").asInt()); + }) + .verifyComplete(); - Mono.from(ConnectionFactories.get(ob.build()).create()) - .map(connection -> connection.createBatch() - .add("drop table test_real_types")) - .flatMapMany(batch -> Flux.from(batch.execute())) - .blockLast(); // wait until completion of all the queries + Mono.from(getConnectionMonoFromContainer(mySQLContainer)) + .map(connection -> connection.createBatch() + .add("drop table test_real_types")) + .flatMapMany(batch -> Flux.from(batch.execute())) + .blockLast(); // wait until completion of all the queries + } + + private Publisher getConnectionFromBuilder(ConnectionFactoryOptions.Builder builder) { + return ConnectionFactories.get(builder.build()).create(); } @Test public void testPreparedStatementWithBooleanType() { // Create a new table with boolean type - ConnectionFactoryOptions baseOptions = MySQLR2DBCDatabaseContainer.getOptions(mySQLContainer); - ConnectionFactoryOptions.Builder ob = ConnectionFactoryOptions.builder().from(baseOptions); - Mono.from(ConnectionFactories.get(ob.build()).create()) - .map(connection -> connection.createBatch() - .add("create table test_boolean_type(id int, c_boolean boolean)") - .add("insert into test_boolean_type values (1, True)") - .add("insert into test_boolean_type values (2, True)") - .add("insert into test_boolean_type values (3, False)")) - .flatMapMany(batch -> Flux.from(batch.execute())) - .blockLast(); // wait until completion of all the queries + Mono.from(getConnectionMonoFromContainer(mySQLContainer)) + .map(connection -> connection.createBatch() + .add("create table test_boolean_type(id int, c_boolean boolean)") + .add("insert into test_boolean_type values (1, True)") + .add("insert into test_boolean_type values (2, True)") + .add("insert into test_boolean_type values (3, False)")) + .flatMapMany(batch -> Flux.from(batch.execute())) + .blockLast(); // wait until completion of all the queries DatasourceConfiguration dsConfig = createDatasourceConfiguration(); - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); ActionConfiguration actionConfiguration = new ActionConfiguration(); actionConfiguration.setBody("SELECT id FROM test_boolean_type WHERE c_boolean={{binding1}};"); @@ -658,34 +665,34 @@ public class MySqlPluginTest { executeActionDTO.setParams(params); Mono executeMono = dsConnectionMono - .flatMap(conn -> pluginExecutor.executeParameterized(conn, executeActionDTO, dsConfig, - actionConfiguration)); + .flatMap(conn -> pluginExecutor.executeParameterized(conn, executeActionDTO, dsConfig, + actionConfiguration)); StepVerifier.create(executeMono) - .assertNext(result -> { - final JsonNode node = ((ArrayNode) result.getBody()); - assertEquals(2, node.size()); - // Verify selected row id. - assertEquals(1, node.get(0).get("id").asInt()); - assertEquals(2, node.get(1).get("id").asInt()); - }) - .verifyComplete(); + .assertNext(result -> { + final JsonNode node = ((ArrayNode) result.getBody()); + assertEquals(2, node.size()); + // Verify selected row id. + assertEquals(1, node.get(0).get("id").asInt()); + assertEquals(2, node.get(1).get("id").asInt()); + }) + .verifyComplete(); - Mono.from(ConnectionFactories.get(ob.build()).create()) - .map(connection -> connection.createBatch() - .add("drop table test_boolean_type")) - .flatMapMany(batch -> Flux.from(batch.execute())) - .blockLast(); // wait until completion of all the queries + Mono.from(getConnectionMonoFromContainer(mySQLContainer)) + .map(connection -> connection.createBatch() + .add("drop table test_boolean_type")) + .flatMapMany(batch -> Flux.from(batch.execute())) + .blockLast(); // wait until completion of all the queries } @Test public void testExecuteWithPreparedStatement() { DatasourceConfiguration dsConfig = createDatasourceConfiguration(); - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); ActionConfiguration actionConfiguration = new ActionConfiguration(); actionConfiguration - .setBody("SELECT id FROM users WHERE id = {{binding1}} limit 1 offset {{binding2}};"); + .setBody("SELECT id FROM users WHERE id = {{binding1}} limit 1 offset {{binding2}};"); List pluginSpecifiedTemplates = new ArrayList<>(); pluginSpecifiedTemplates.add(new Property("preparedStatement", "true")); @@ -706,57 +713,57 @@ public class MySqlPluginTest { executeActionDTO.setParams(params); Mono executeMono = dsConnectionMono - .flatMap(conn -> pluginExecutor.executeParameterized(conn, executeActionDTO, dsConfig, - actionConfiguration)); + .flatMap(conn -> pluginExecutor.executeParameterized(conn, executeActionDTO, dsConfig, + actionConfiguration)); StepVerifier.create(executeMono) - .assertNext(result -> { - final JsonNode node = ((ArrayNode) result.getBody()).get(0); - assertArrayEquals( - new String[] { - "id" - }, - new ObjectMapper() - .convertValue(node, LinkedHashMap.class) - .keySet() - .toArray()); + .assertNext(result -> { + final JsonNode node = ((ArrayNode) result.getBody()).get(0); + assertArrayEquals( + new String[]{ + "id" + }, + new ObjectMapper() + .convertValue(node, LinkedHashMap.class) + .keySet() + .toArray()); - // Verify value - assertEquals(1, node.get("id").asInt()); + // Verify value + assertEquals(1, node.get("id").asInt()); - /* - * - Check if request params are sent back properly. - * - Not replicating the same to other tests as the overall flow remains the - * same w.r.t. request - * params. - */ + /* + * - Check if request params are sent back properly. + * - Not replicating the same to other tests as the overall flow remains the + * same w.r.t. request + * params. + */ - // Check if '?' is replaced by $i. - assertEquals("SELECT id FROM users WHERE id = $1 limit 1 offset $2;", - ((RequestParamDTO) (((List) result.getRequest() - .getRequestParams())).get(0)).getValue()); + // Check if '?' is replaced by $i. + assertEquals("SELECT id FROM users WHERE id = $1 limit 1 offset $2;", + ((RequestParamDTO) (((List) result.getRequest() + .getRequestParams())).get(0)).getValue()); - // Check 1st prepared statement parameter - PsParameterDTO expectedPsParam1 = new PsParameterDTO("1", "INTEGER"); - PsParameterDTO returnedPsParam1 = (PsParameterDTO) ((RequestParamDTO) (((List) result - .getRequest().getRequestParams())).get(0)) - .getSubstitutedParams().get("$1"); - // Check if prepared stmt param value is correctly sent back. - assertEquals(expectedPsParam1.getValue(), returnedPsParam1.getValue()); - // Check if prepared stmt param type is correctly sent back. - assertEquals(expectedPsParam1.getType(), returnedPsParam1.getType()); + // Check 1st prepared statement parameter + PsParameterDTO expectedPsParam1 = new PsParameterDTO("1", "INTEGER"); + PsParameterDTO returnedPsParam1 = (PsParameterDTO) ((RequestParamDTO) (((List) result + .getRequest().getRequestParams())).get(0)) + .getSubstitutedParams().get("$1"); + // Check if prepared stmt param value is correctly sent back. + assertEquals(expectedPsParam1.getValue(), returnedPsParam1.getValue()); + // Check if prepared stmt param type is correctly sent back. + assertEquals(expectedPsParam1.getType(), returnedPsParam1.getType()); - // Check 2nd prepared statement parameter - PsParameterDTO expectedPsParam2 = new PsParameterDTO("0", "INTEGER"); - PsParameterDTO returnedPsParam2 = (PsParameterDTO) ((RequestParamDTO) (((List) result - .getRequest().getRequestParams())).get(0)) - .getSubstitutedParams().get("$2"); - // Check if prepared stmt param value is correctly sent back. - assertEquals(expectedPsParam2.getValue(), returnedPsParam2.getValue()); - // Check if prepared stmt param type is correctly sent back. - assertEquals(expectedPsParam2.getType(), returnedPsParam2.getType()); - }) - .verifyComplete(); + // Check 2nd prepared statement parameter + PsParameterDTO expectedPsParam2 = new PsParameterDTO("0", "INTEGER"); + PsParameterDTO returnedPsParam2 = (PsParameterDTO) ((RequestParamDTO) (((List) result + .getRequest().getRequestParams())).get(0)) + .getSubstitutedParams().get("$2"); + // Check if prepared stmt param value is correctly sent back. + assertEquals(expectedPsParam2.getValue(), returnedPsParam2.getValue()); + // Check if prepared stmt param type is correctly sent back. + assertEquals(expectedPsParam2.getType(), returnedPsParam2.getType()); + }) + .verifyComplete(); return; } @@ -764,48 +771,48 @@ public class MySqlPluginTest { @Test public void testExecuteDataTypes() { DatasourceConfiguration dsConfig = createDatasourceConfiguration(); - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); ActionConfiguration actionConfiguration = new ActionConfiguration(); actionConfiguration.setBody("SELECT * FROM users WHERE id = 1"); Mono executeMono = dsConnectionMono - .flatMap(conn -> pluginExecutor.executeParameterized(conn, new ExecuteActionDTO(), - dsConfig, actionConfiguration)); + .flatMap(conn -> pluginExecutor.executeParameterized(conn, new ExecuteActionDTO(), + dsConfig, actionConfiguration)); StepVerifier.create(executeMono) - .assertNext(result -> { - assertNotNull(result); - assertTrue(result.getIsExecutionSuccess()); - assertNotNull(result.getBody()); + .assertNext(result -> { + assertNotNull(result); + assertTrue(result.getIsExecutionSuccess()); + assertNotNull(result.getBody()); - final JsonNode node = ((ArrayNode) result.getBody()).get(0); - assertEquals("2018-12-31", node.get("dob").asText()); - assertEquals("2018", node.get("yob").asText()); - assertTrue(node.get("time1").asText().matches("\\d{2}:\\d{2}:\\d{2}")); - assertTrue(node.get("created_on").asText() - .matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z")); - assertTrue(node.get("updated_on").isNull()); + final JsonNode node = ((ArrayNode) result.getBody()).get(0); + assertEquals("2018-12-31", node.get("dob").asText()); + assertEquals("2018", node.get("yob").asText()); + assertTrue(node.get("time1").asText().matches("\\d{2}:\\d{2}:\\d{2}")); + assertTrue(node.get("created_on").asText() + .matches("\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z")); + assertTrue(node.get("updated_on").isNull()); - assertArrayEquals( - new String[] { - "id", - "username", - "password", - "email", - "spouse_dob", - "dob", - "yob", - "time1", - "created_on", - "updated_on" - }, - new ObjectMapper() - .convertValue(node, LinkedHashMap.class) - .keySet() - .toArray()); - }) - .verifyComplete(); + assertArrayEquals( + new String[]{ + "id", + "username", + "password", + "email", + "spouse_dob", + "dob", + "yob", + "time1", + "created_on", + "updated_on" + }, + new ObjectMapper() + .convertValue(node, LinkedHashMap.class) + .keySet() + .toArray()); + }) + .verifyComplete(); } /** @@ -822,45 +829,45 @@ public class MySqlPluginTest { @Test public void testExecuteDataTypesExtensive() throws AppsmithPluginException { String query_create_table_numeric_types = "create table test_numeric_types (c_integer INTEGER, c_smallint " - + - "SMALLINT, c_tinyint TINYINT, c_mediumint MEDIUMINT, c_bigint BIGINT, c_decimal DECIMAL, c_float " - + - "FLOAT, c_double DOUBLE, c_bit BIT(10));"; + + + "SMALLINT, c_tinyint TINYINT, c_mediumint MEDIUMINT, c_bigint BIGINT, c_decimal DECIMAL, c_float " + + + "FLOAT, c_double DOUBLE, c_bit BIT(10));"; String query_insert_into_table_numeric_types = "insert into test_numeric_types values (-1, 1, 1, 10, 2000, 1" - + - ".02345, 0.1234, 1.0102344, b'0101010');"; + + + ".02345, 0.1234, 1.0102344, b'0101010');"; String query_create_table_date_time_types = "create table test_date_time_types (c_date DATE, c_datetime " - + - "DATETIME DEFAULT CURRENT_TIMESTAMP, c_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, c_time TIME, " - + - "c_year YEAR);"; + + + "DATETIME DEFAULT CURRENT_TIMESTAMP, c_timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, c_time TIME, " + + + "c_year YEAR);"; String query_insert_into_table_date_time_types = "insert into test_date_time_types values ('2020-12-01', " - + - "'2020-12-01 20:20:20', '2020-12-01 20:20:20', '20:20:20', 2020);"; + + + "'2020-12-01 20:20:20', '2020-12-01 20:20:20', '20:20:20', 2020);"; String query_create_table_data_types = "create table test_data_types (c_char CHAR(50), c_varchar VARCHAR(50)," - + - " c_binary BINARY(20), c_varbinary VARBINARY(20), c_tinyblob TINYBLOB, c_blob BLOB, c_mediumblob " - + - "MEDIUMBLOB, c_longblob LONGBLOB, c_tinytext TINYTEXT, c_text TEXT, c_mediumtext MEDIUMTEXT, " - + - "c_longtext LONGTEXT, c_enum ENUM('ONE'), c_set SET('a'));"; + + + " c_binary BINARY(20), c_varbinary VARBINARY(20), c_tinyblob TINYBLOB, c_blob BLOB, c_mediumblob " + + + "MEDIUMBLOB, c_longblob LONGBLOB, c_tinytext TINYTEXT, c_text TEXT, c_mediumtext MEDIUMTEXT, " + + + "c_longtext LONGTEXT, c_enum ENUM('ONE'), c_set SET('a'));"; String query_insert_data_types = "insert into test_data_types values ('test', 'test', 'a\\0\\t', 'a\\0\\t', " - + - "'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'ONE', 'a');"; + + + "'test', 'test', 'test', 'test', 'test', 'test', 'test', 'test', 'ONE', 'a');"; String query_create_table_json_data_type = "create table test_json_type (c_json JSON);"; String query_insert_json_data_type = "insert into test_json_type values ('{\"key1\": \"value1\", \"key2\": " - + - "\"value2\"}');"; + + + "\"value2\"}');"; String query_create_table_geometry_types = "create table test_geometry_types (c_geometry GEOMETRY, c_point " - + - "POINT);"; + + + "POINT);"; String query_insert_geometry_types = "insert into test_geometry_types values (ST_GeomFromText('POINT(1 1)'), " - + - "ST_PointFromText('POINT(1 100)'));"; + + + "ST_PointFromText('POINT(1 100)'));"; String query_select_from_test_numeric_types = "select * from test_numeric_types;"; String query_select_from_test_date_time_types = "select * from test_date_time_types;"; @@ -868,24 +875,22 @@ public class MySqlPluginTest { String query_select_from_test_data_types = "select * from test_data_types;"; String query_select_from_test_geometry_types = "select * from test_geometry_types;"; - ConnectionFactoryOptions baseOptions = MySQLR2DBCDatabaseContainer.getOptions(mySQLContainer); - ConnectionFactoryOptions.Builder ob = ConnectionFactoryOptions.builder().from(baseOptions); - Mono.from(ConnectionFactories.get(ob.build()).create()) - .map(connection -> { - return connection.createBatch() - .add(query_create_table_numeric_types) - .add(query_insert_into_table_numeric_types) - .add(query_create_table_date_time_types) - .add(query_insert_into_table_date_time_types) - .add(query_create_table_json_data_type) - .add(query_insert_json_data_type) - .add(query_create_table_data_types) - .add(query_insert_data_types) - .add(query_create_table_geometry_types) - .add(query_insert_geometry_types); - }) - .flatMapMany(batch -> Flux.from(batch.execute())) - .blockLast(); // wait until completion of all the queries + Mono.from(getConnectionMonoFromContainer(mySQLContainer)) + .map(connection -> { + return connection.createBatch() + .add(query_create_table_numeric_types) + .add(query_insert_into_table_numeric_types) + .add(query_create_table_date_time_types) + .add(query_insert_into_table_date_time_types) + .add(query_create_table_json_data_type) + .add(query_insert_json_data_type) + .add(query_create_table_data_types) + .add(query_insert_data_types) + .add(query_create_table_geometry_types) + .add(query_insert_geometry_types); + }) + .flatMapMany(batch -> Flux.from(batch.execute())) + .blockLast(); // wait until completion of all the queries /* Test numeric types */ testExecute(query_select_from_test_numeric_types); @@ -902,171 +907,171 @@ public class MySqlPluginTest { } private void testExecute(String query) { - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); ActionConfiguration actionConfiguration = new ActionConfiguration(); actionConfiguration.setBody(query); Mono executeMono = dsConnectionMono.flatMap(conn -> pluginExecutor.executeParameterized(conn, - new ExecuteActionDTO(), dsConfig, actionConfiguration)); + new ExecuteActionDTO(), dsConfig, actionConfiguration)); StepVerifier.create(executeMono) - .assertNext(obj -> { - ActionExecutionResult result = (ActionExecutionResult) obj; - assertNotNull(result); - assertTrue(result.getIsExecutionSuccess()); - assertNotNull(result.getBody()); - }) - .verifyComplete(); + .assertNext(obj -> { + ActionExecutionResult result = (ActionExecutionResult) obj; + assertNotNull(result); + assertTrue(result.getIsExecutionSuccess()); + assertNotNull(result.getBody()); + }) + .verifyComplete(); } @Test public void testStructure() { DatasourceConfiguration dsConfig = createDatasourceConfiguration(); Mono structureMono = pluginExecutor.datasourceCreate(dsConfig) - .flatMap(connection -> pluginExecutor.getStructure(connection, dsConfig)); + .flatMap(connection -> pluginExecutor.getStructure(connection, dsConfig)); StepVerifier.create(structureMono) - .assertNext(structure -> { - assertNotNull(structure); - assertEquals(2, structure.getTables().size()); + .assertNext(structure -> { + assertNotNull(structure); + assertEquals(2, structure.getTables().size()); - Optional possessionsTableOptional = structure - .getTables() - .stream() - .filter(table -> table.getName() - .equalsIgnoreCase("possessions")) - .findFirst(); - assertTrue(possessionsTableOptional.isPresent()); - final DatasourceStructure.Table possessionsTable = possessionsTableOptional - .get(); - assertEquals(DatasourceStructure.TableType.TABLE, possessionsTable.getType()); - assertArrayEquals( - new DatasourceStructure.Column[] { - new DatasourceStructure.Column("id", "int", - null, false), - new DatasourceStructure.Column("title", - "varchar", null, false), - new DatasourceStructure.Column("user_id", "int", - null, false), - new DatasourceStructure.Column("username", - "varchar", null, false), - new DatasourceStructure.Column("email", - "varchar", null, false), - }, - possessionsTable.getColumns().toArray()); + Optional possessionsTableOptional = structure + .getTables() + .stream() + .filter(table -> table.getName() + .equalsIgnoreCase("possessions")) + .findFirst(); + assertTrue(possessionsTableOptional.isPresent()); + final DatasourceStructure.Table possessionsTable = possessionsTableOptional + .get(); + assertEquals(DatasourceStructure.TableType.TABLE, possessionsTable.getType()); + assertArrayEquals( + new DatasourceStructure.Column[]{ + new DatasourceStructure.Column("id", "int", + null, false), + new DatasourceStructure.Column("title", + "varchar", null, false), + new DatasourceStructure.Column("user_id", "int", + null, false), + new DatasourceStructure.Column("username", + "varchar", null, false), + new DatasourceStructure.Column("email", + "varchar", null, false), + }, + possessionsTable.getColumns().toArray()); - final DatasourceStructure.PrimaryKey possessionsPrimaryKey = new DatasourceStructure.PrimaryKey( - "PRIMARY", List.of("id")); - final DatasourceStructure.ForeignKey possessionsUserForeignKey = new DatasourceStructure.ForeignKey( - "possessions_ibfk_1", - List.of("username", "email"), - List.of("users.username", "users.email")); - assertArrayEquals( - new DatasourceStructure.Key[] { possessionsPrimaryKey, - possessionsUserForeignKey }, - possessionsTable.getKeys().toArray()); + final DatasourceStructure.PrimaryKey possessionsPrimaryKey = new DatasourceStructure.PrimaryKey( + "PRIMARY", List.of("id")); + final DatasourceStructure.ForeignKey possessionsUserForeignKey = new DatasourceStructure.ForeignKey( + "possessions_ibfk_1", + List.of("username", "email"), + List.of("users.username", "users.email")); + assertArrayEquals( + new DatasourceStructure.Key[]{possessionsPrimaryKey, + possessionsUserForeignKey}, + possessionsTable.getKeys().toArray()); - assertArrayEquals( - new DatasourceStructure.Template[] { - new DatasourceStructure.Template("SELECT", - "SELECT * FROM possessions LIMIT 10;"), - new DatasourceStructure.Template("INSERT", - "INSERT INTO possessions (id, title, user_id, username, email)\n" - + - " VALUES (1, '', 1, '', '');"), - new DatasourceStructure.Template("UPDATE", - "UPDATE possessions SET\n" + - " id = 1,\n" - + - " title = '',\n" - + - " user_id = 1,\n" - + - " username = '',\n" - + - " email = ''\n" - + - " WHERE 1 = 0; -- Specify a valid condition here. Removing the condition may update every row in the table!"), - new DatasourceStructure.Template("DELETE", - "DELETE FROM possessions\n" + - " WHERE 1 = 0; -- Specify a valid condition here. Removing the condition may delete everything in the table!"), - }, - possessionsTable.getTemplates().toArray()); + assertArrayEquals( + new DatasourceStructure.Template[]{ + new DatasourceStructure.Template("SELECT", + "SELECT * FROM possessions LIMIT 10;"), + new DatasourceStructure.Template("INSERT", + "INSERT INTO possessions (id, title, user_id, username, email)\n" + + + " VALUES (1, '', 1, '', '');"), + new DatasourceStructure.Template("UPDATE", + "UPDATE possessions SET\n" + + " id = 1,\n" + + + " title = '',\n" + + + " user_id = 1,\n" + + + " username = '',\n" + + + " email = ''\n" + + + " WHERE 1 = 0; -- Specify a valid condition here. Removing the condition may update every row in the table!"), + new DatasourceStructure.Template("DELETE", + "DELETE FROM possessions\n" + + " WHERE 1 = 0; -- Specify a valid condition here. Removing the condition may delete everything in the table!"), + }, + possessionsTable.getTemplates().toArray()); - Optional usersTableOptional = structure.getTables() - .stream() - .filter(table -> table.getName().equalsIgnoreCase("users")) - .findFirst(); - assertTrue(usersTableOptional.isPresent()); - final DatasourceStructure.Table usersTable = usersTableOptional.get(); - assertEquals(DatasourceStructure.TableType.TABLE, usersTable.getType()); - assertArrayEquals( - new DatasourceStructure.Column[] { - new DatasourceStructure.Column("id", "int", - null, true), - new DatasourceStructure.Column("username", - "varchar", null, false), - new DatasourceStructure.Column("password", - "varchar", null, false), - new DatasourceStructure.Column("email", - "varchar", null, false), - new DatasourceStructure.Column("spouse_dob", - "date", null, false), - new DatasourceStructure.Column("dob", "date", - null, false), - new DatasourceStructure.Column("yob", "year", - null, false), - new DatasourceStructure.Column("time1", "time", - null, false), - new DatasourceStructure.Column("created_on", - "timestamp", null, false), - new DatasourceStructure.Column("updated_on", - "datetime", null, false) - }, - usersTable.getColumns().toArray()); + Optional usersTableOptional = structure.getTables() + .stream() + .filter(table -> table.getName().equalsIgnoreCase("users")) + .findFirst(); + assertTrue(usersTableOptional.isPresent()); + final DatasourceStructure.Table usersTable = usersTableOptional.get(); + assertEquals(DatasourceStructure.TableType.TABLE, usersTable.getType()); + assertArrayEquals( + new DatasourceStructure.Column[]{ + new DatasourceStructure.Column("id", "int", + null, true), + new DatasourceStructure.Column("username", + "varchar", null, false), + new DatasourceStructure.Column("password", + "varchar", null, false), + new DatasourceStructure.Column("email", + "varchar", null, false), + new DatasourceStructure.Column("spouse_dob", + "date", null, false), + new DatasourceStructure.Column("dob", "date", + null, false), + new DatasourceStructure.Column("yob", "year", + null, false), + new DatasourceStructure.Column("time1", "time", + null, false), + new DatasourceStructure.Column("created_on", + "timestamp", null, false), + new DatasourceStructure.Column("updated_on", + "datetime", null, false) + }, + usersTable.getColumns().toArray()); - final DatasourceStructure.PrimaryKey usersPrimaryKey = new DatasourceStructure.PrimaryKey( - "PRIMARY", List.of("id")); - assertArrayEquals( - new DatasourceStructure.Key[] { usersPrimaryKey }, - usersTable.getKeys().toArray()); + final DatasourceStructure.PrimaryKey usersPrimaryKey = new DatasourceStructure.PrimaryKey( + "PRIMARY", List.of("id")); + assertArrayEquals( + new DatasourceStructure.Key[]{usersPrimaryKey}, + usersTable.getKeys().toArray()); - assertArrayEquals( - new DatasourceStructure.Template[] { - new DatasourceStructure.Template("SELECT", - "SELECT * FROM users LIMIT 10;"), - new DatasourceStructure.Template("INSERT", - "INSERT INTO users (id, username, password, email, spouse_dob, dob, yob, time1, created_on, updated_on)\n" - + - " VALUES (1, '', '', '', '2019-07-01', '2019-07-01', '', '', '2019-07-01 10:00:00', '2019-07-01 10:00:00');"), - new DatasourceStructure.Template("UPDATE", - "UPDATE users SET\n" + - " id = 1,\n" - + - " username = '',\n" - + - " password = '',\n" - + - " email = '',\n" - + - " spouse_dob = '2019-07-01',\n" - + - " dob = '2019-07-01',\n" - + - " yob = '',\n" - + - " time1 = '',\n" - + - " created_on = '2019-07-01 10:00:00',\n" - + - " updated_on = '2019-07-01 10:00:00'\n" - + - " WHERE 1 = 0; -- Specify a valid condition here. Removing the condition may update every row in the table!"), - new DatasourceStructure.Template("DELETE", - "DELETE FROM users\n" + - " WHERE 1 = 0; -- Specify a valid condition here. Removing the condition may delete everything in the table!"), - }, - usersTable.getTemplates().toArray()); - }) - .verifyComplete(); + assertArrayEquals( + new DatasourceStructure.Template[]{ + new DatasourceStructure.Template("SELECT", + "SELECT * FROM users LIMIT 10;"), + new DatasourceStructure.Template("INSERT", + "INSERT INTO users (id, username, password, email, spouse_dob, dob, yob, time1, created_on, updated_on)\n" + + + " VALUES (1, '', '', '', '2019-07-01', '2019-07-01', '', '', '2019-07-01 10:00:00', '2019-07-01 10:00:00');"), + new DatasourceStructure.Template("UPDATE", + "UPDATE users SET\n" + + " id = 1,\n" + + + " username = '',\n" + + + " password = '',\n" + + + " email = '',\n" + + + " spouse_dob = '2019-07-01',\n" + + + " dob = '2019-07-01',\n" + + + " yob = '',\n" + + + " time1 = '',\n" + + + " created_on = '2019-07-01 10:00:00',\n" + + + " updated_on = '2019-07-01 10:00:00'\n" + + + " WHERE 1 = 0; -- Specify a valid condition here. Removing the condition may update every row in the table!"), + new DatasourceStructure.Template("DELETE", + "DELETE FROM users\n" + + " WHERE 1 = 0; -- Specify a valid condition here. Removing the condition may delete everything in the table!"), + }, + usersTable.getTemplates().toArray()); + }) + .verifyComplete(); } @Test @@ -1075,18 +1080,18 @@ public class MySqlPluginTest { datasourceConfiguration.getConnection().getSsl().setAuthType(null); Mono> invalidsMono = Mono.just(pluginExecutor) - .map(executor -> executor.validateDatasource(datasourceConfiguration)); + .map(executor -> executor.validateDatasource(datasourceConfiguration)); StepVerifier.create(invalidsMono) - .assertNext(invalids -> { - String expectedError = "Appsmith server has failed to fetch SSL configuration from datasource " - + - "configuration form. Please reach out to Appsmith customer support to resolve this."; - assertTrue(invalids - .stream() - .anyMatch(error -> expectedError.equals(error))); - }) - .verifyComplete(); + .assertNext(invalids -> { + String expectedError = "Appsmith server has failed to fetch SSL configuration from datasource " + + + "configuration form. Please reach out to Appsmith customer support to resolve this."; + assertTrue(invalids + .stream() + .anyMatch(error -> expectedError.equals(error))); + }) + .verifyComplete(); } @Test @@ -1096,22 +1101,22 @@ public class MySqlPluginTest { DatasourceConfiguration datasourceConfiguration = createDatasourceConfiguration(); datasourceConfiguration.getConnection().getSsl().setAuthType(SSLDetails.AuthType.DISABLED); - Mono dsConnectionMono = pluginExecutor.datasourceCreate(datasourceConfiguration); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(datasourceConfiguration); Mono executeMono = dsConnectionMono - .flatMap(conn -> pluginExecutor.executeParameterized(conn, new ExecuteActionDTO(), - dsConfig, - actionConfiguration)); + .flatMap(conn -> pluginExecutor.executeParameterized(conn, new ExecuteActionDTO(), + dsConfig, + actionConfiguration)); StepVerifier.create(executeMono) - .assertNext(obj -> { - ActionExecutionResult result = (ActionExecutionResult) obj; - assertNotNull(result); - assertTrue(result.getIsExecutionSuccess()); - Object body = result.getBody(); - assertNotNull(body); - assertEquals("[{\"Variable_name\":\"Ssl_cipher\",\"Value\":\"\"}]", - body.toString()); - }) - .verifyComplete(); + .assertNext(obj -> { + ActionExecutionResult result = (ActionExecutionResult) obj; + assertNotNull(result); + assertTrue(result.getIsExecutionSuccess()); + Object body = result.getBody(); + assertNotNull(body); + assertEquals("[{\"Variable_name\":\"Ssl_cipher\",\"Value\":\"\"}]", + body.toString()); + }) + .verifyComplete(); } @Test @@ -1121,47 +1126,22 @@ public class MySqlPluginTest { DatasourceConfiguration datasourceConfiguration = createDatasourceConfiguration(); datasourceConfiguration.getConnection().getSsl().setAuthType(SSLDetails.AuthType.REQUIRED); - Mono dsConnectionMono = pluginExecutor.datasourceCreate(datasourceConfiguration); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(datasourceConfiguration); Mono executeMono = dsConnectionMono - .flatMap(conn -> pluginExecutor.executeParameterized(conn, new ExecuteActionDTO(), - dsConfig, - actionConfiguration)); + .flatMap(conn -> pluginExecutor.executeParameterized(conn, new ExecuteActionDTO(), + dsConfig, + actionConfiguration)); StepVerifier.create(executeMono) - .assertNext(obj -> { - ActionExecutionResult result = (ActionExecutionResult) obj; - assertNotNull(result); - assertTrue(result.getIsExecutionSuccess()); - Object body = result.getBody(); - assertNotNull(body); - assertEquals("[{\"Variable_name\":\"Ssl_cipher\",\"Value\":\"ECDHE-RSA-AES128-GCM-SHA256\"}]", - body.toString()); - }) - .verifyComplete(); - } - - @Test - public void testSslPreferred() { - ActionConfiguration actionConfiguration = new ActionConfiguration(); - actionConfiguration.setBody("show session status like 'Ssl_cipher'"); - - DatasourceConfiguration datasourceConfiguration = createDatasourceConfiguration(); - datasourceConfiguration.getConnection().getSsl().setAuthType(SSLDetails.AuthType.PREFERRED); - Mono dsConnectionMono = pluginExecutor.datasourceCreate(datasourceConfiguration); - Mono executeMono = dsConnectionMono - .flatMap(conn -> pluginExecutor.executeParameterized(conn, new ExecuteActionDTO(), - dsConfig, - actionConfiguration)); - StepVerifier.create(executeMono) - .assertNext(obj -> { - ActionExecutionResult result = (ActionExecutionResult) obj; - assertNotNull(result); - assertTrue(result.getIsExecutionSuccess()); - Object body = result.getBody(); - assertNotNull(body); - assertEquals("[{\"Variable_name\":\"Ssl_cipher\",\"Value\":\"ECDHE-RSA-AES128-GCM-SHA256\"}]", - body.toString()); - }) - .verifyComplete(); + .assertNext(obj -> { + ActionExecutionResult result = (ActionExecutionResult) obj; + assertNotNull(result); + assertTrue(result.getIsExecutionSuccess()); + Object body = result.getBody(); + assertNotNull(body); + assertEquals("[{\"Variable_name\":\"Ssl_cipher\",\"Value\":\"TLS_AES_128_GCM_SHA256\"}]", + body.toString()); + }) + .verifyComplete(); } @Test @@ -1171,110 +1151,110 @@ public class MySqlPluginTest { DatasourceConfiguration datasourceConfiguration = createDatasourceConfiguration(); datasourceConfiguration.getConnection().getSsl().setAuthType(SSLDetails.AuthType.DEFAULT); - Mono dsConnectionMono = pluginExecutor.datasourceCreate(datasourceConfiguration); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(datasourceConfiguration); Mono executeMono = dsConnectionMono - .flatMap(conn -> pluginExecutor.executeParameterized(conn, new ExecuteActionDTO(), - dsConfig, - actionConfiguration)); + .flatMap(conn -> pluginExecutor.executeParameterized(conn, new ExecuteActionDTO(), + dsConfig, + actionConfiguration)); StepVerifier.create(executeMono) - .assertNext(obj -> { - ActionExecutionResult result = (ActionExecutionResult) obj; - assertNotNull(result); - assertTrue(result.getIsExecutionSuccess()); - Object body = result.getBody(); - assertNotNull(body); - assertEquals("[{\"Variable_name\":\"Ssl_cipher\",\"Value\":\"ECDHE-RSA-AES128-GCM-SHA256\"}]", - body.toString()); - }) - .verifyComplete(); + .assertNext(obj -> { + ActionExecutionResult result = (ActionExecutionResult) obj; + assertNotNull(result); + assertTrue(result.getIsExecutionSuccess()); + Object body = result.getBody(); + assertNotNull(body); + assertEquals("[{\"Variable_name\":\"Ssl_cipher\",\"Value\":\"\"}]", + body.toString()); + }) + .verifyComplete(); } @Test public void testDuplicateColumnNames() { DatasourceConfiguration dsConfig = createDatasourceConfiguration(); - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); ActionConfiguration actionConfiguration = new ActionConfiguration(); actionConfiguration.setBody( - "SELECT id, username as id, password, email as password FROM users WHERE id = 1"); + "SELECT id, username as id, password, email as password FROM users WHERE id = 1"); Mono executeMono = dsConnectionMono - .flatMap(conn -> pluginExecutor.executeParameterized(conn, new ExecuteActionDTO(), - dsConfig, actionConfiguration)); + .flatMap(conn -> pluginExecutor.executeParameterized(conn, new ExecuteActionDTO(), + dsConfig, actionConfiguration)); StepVerifier.create(executeMono) - .assertNext(result -> { - assertNotEquals(0, result.getMessages().size()); + .assertNext(result -> { + assertNotEquals(0, result.getMessages().size()); - String expectedMessage = "Your MySQL query result may not have all the columns because duplicate column names " - + - "were found for the column(s)"; - assertTrue( - result.getMessages().stream() - .anyMatch(message -> message - .contains(expectedMessage))); - - /* - * - Check if all of the duplicate column names are reported. - */ - Set expectedColumnNames = Stream.of("id", "password") - .collect(Collectors.toCollection(HashSet::new)); - Set foundColumnNames = new HashSet<>(); + String expectedMessage = "Your MySQL query result may not have all the columns because duplicate column names " + + + "were found for the column(s)"; + assertTrue( result.getMessages().stream() - .filter(message -> message.contains(expectedMessage)) - .forEach(message -> { - Arrays.stream(message.split(":")[1].split("\\.")[0] - .split(",")) - .forEach(columnName -> foundColumnNames - .add(columnName.trim())); - }); - assertTrue(expectedColumnNames.equals(foundColumnNames)); - }) - .verifyComplete(); + .anyMatch(message -> message + .contains(expectedMessage))); + + /* + * - Check if all of the duplicate column names are reported. + */ + Set expectedColumnNames = Stream.of("id", "password") + .collect(Collectors.toCollection(HashSet::new)); + Set foundColumnNames = new HashSet<>(); + result.getMessages().stream() + .filter(message -> message.contains(expectedMessage)) + .forEach(message -> { + Arrays.stream(message.split(":")[1].split("\\.")[0] + .split(",")) + .forEach(columnName -> foundColumnNames + .add(columnName.trim())); + }); + assertTrue(expectedColumnNames.equals(foundColumnNames)); + }) + .verifyComplete(); } @Test public void testExecuteDescribeTableCmd() { dsConfig = createDatasourceConfiguration(); - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); ActionConfiguration actionConfiguration = new ActionConfiguration(); actionConfiguration.setBody("describe users"); Mono executeMono = dsConnectionMono.flatMap(conn -> pluginExecutor.executeParameterized(conn, - new ExecuteActionDTO(), dsConfig, actionConfiguration)); + new ExecuteActionDTO(), dsConfig, actionConfiguration)); StepVerifier.create(executeMono) - .assertNext(obj -> { - ActionExecutionResult result = (ActionExecutionResult) obj; - assertNotNull(result); - assertTrue(result.getIsExecutionSuccess()); - assertNotNull(result.getBody()); - String expectedBody = "[{\"Field\":\"id\",\"Type\":\"int\",\"Null\":\"NO\",\"Key\":\"PRI\",\"Default\":null,\"Extra\":\"auto_increment\"},{\"Field\":\"username\",\"Type\":\"varchar(250)\",\"Null\":\"NO\",\"Key\":\"UNI\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"password\",\"Type\":\"varchar(250)\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"email\",\"Type\":\"varchar(250)\",\"Null\":\"NO\",\"Key\":\"UNI\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"spouse_dob\",\"Type\":\"date\",\"Null\":\"YES\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"dob\",\"Type\":\"date\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"yob\",\"Type\":\"year\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"time1\",\"Type\":\"time\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"created_on\",\"Type\":\"timestamp\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"updated_on\",\"Type\":\"datetime\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"}]"; - assertEquals(expectedBody, result.getBody().toString()); - }) - .verifyComplete(); + .assertNext(obj -> { + ActionExecutionResult result = (ActionExecutionResult) obj; + assertNotNull(result); + assertTrue(result.getIsExecutionSuccess()); + assertNotNull(result.getBody()); + String expectedBody = "[{\"Field\":\"id\",\"Type\":\"int\",\"Null\":\"NO\",\"Key\":\"PRI\",\"Default\":null,\"Extra\":\"auto_increment\"},{\"Field\":\"username\",\"Type\":\"varchar(250)\",\"Null\":\"NO\",\"Key\":\"UNI\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"password\",\"Type\":\"varchar(250)\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"email\",\"Type\":\"varchar(250)\",\"Null\":\"NO\",\"Key\":\"UNI\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"spouse_dob\",\"Type\":\"date\",\"Null\":\"YES\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"dob\",\"Type\":\"date\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"yob\",\"Type\":\"year\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"time1\",\"Type\":\"time\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"created_on\",\"Type\":\"timestamp\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"updated_on\",\"Type\":\"datetime\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"}]"; + assertEquals(expectedBody, result.getBody().toString()); + }) + .verifyComplete(); } @Test public void testExecuteDescTableCmd() { dsConfig = createDatasourceConfiguration(); - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); ActionConfiguration actionConfiguration = new ActionConfiguration(); actionConfiguration.setBody("desc users"); Mono executeMono = dsConnectionMono.flatMap(conn -> pluginExecutor.executeParameterized(conn, - new ExecuteActionDTO(), dsConfig, actionConfiguration)); + new ExecuteActionDTO(), dsConfig, actionConfiguration)); StepVerifier.create(executeMono) - .assertNext(obj -> { - ActionExecutionResult result = (ActionExecutionResult) obj; - assertNotNull(result); - assertTrue(result.getIsExecutionSuccess()); - assertNotNull(result.getBody()); - String expectedBody = "[{\"Field\":\"id\",\"Type\":\"int\",\"Null\":\"NO\",\"Key\":\"PRI\",\"Default\":null,\"Extra\":\"auto_increment\"},{\"Field\":\"username\",\"Type\":\"varchar(250)\",\"Null\":\"NO\",\"Key\":\"UNI\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"password\",\"Type\":\"varchar(250)\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"email\",\"Type\":\"varchar(250)\",\"Null\":\"NO\",\"Key\":\"UNI\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"spouse_dob\",\"Type\":\"date\",\"Null\":\"YES\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"dob\",\"Type\":\"date\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"yob\",\"Type\":\"year\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"time1\",\"Type\":\"time\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"created_on\",\"Type\":\"timestamp\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"updated_on\",\"Type\":\"datetime\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"}]"; - assertEquals(expectedBody, result.getBody().toString()); - }) - .verifyComplete(); + .assertNext(obj -> { + ActionExecutionResult result = (ActionExecutionResult) obj; + assertNotNull(result); + assertTrue(result.getIsExecutionSuccess()); + assertNotNull(result.getBody()); + String expectedBody = "[{\"Field\":\"id\",\"Type\":\"int\",\"Null\":\"NO\",\"Key\":\"PRI\",\"Default\":null,\"Extra\":\"auto_increment\"},{\"Field\":\"username\",\"Type\":\"varchar(250)\",\"Null\":\"NO\",\"Key\":\"UNI\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"password\",\"Type\":\"varchar(250)\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"email\",\"Type\":\"varchar(250)\",\"Null\":\"NO\",\"Key\":\"UNI\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"spouse_dob\",\"Type\":\"date\",\"Null\":\"YES\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"dob\",\"Type\":\"date\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"yob\",\"Type\":\"year\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"time1\",\"Type\":\"time\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"created_on\",\"Type\":\"timestamp\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"},{\"Field\":\"updated_on\",\"Type\":\"datetime\",\"Null\":\"NO\",\"Key\":\"\",\"Default\":null,\"Extra\":\"\"}]"; + assertEquals(expectedBody, result.getBody().toString()); + }) + .verifyComplete(); } @Test @@ -1282,17 +1262,17 @@ public class MySqlPluginTest { pluginExecutor = spy(new MySqlPlugin.MySqlPluginExecutor()); doReturn(false).when(pluginExecutor).isIsOperatorUsed(any()); DatasourceConfiguration dsConfig = createDatasourceConfiguration(); - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); ActionConfiguration actionConfiguration = new ActionConfiguration(); actionConfiguration.setBody("SELECT * from (\n" + - "\tselect 'Appsmith' as company_name, true as open_source\n" + - "\tunion\n" + - "\tselect 'Retool' as company_name, false as open_source\n" + - "\tunion\n" + - "\tselect 'XYZ' as company_name, null as open_source\n" + - ") t\n" + - "where t.open_source IS {{binding1}}"); + "\tselect 'Appsmith' as company_name, true as open_source\n" + + "\tunion\n" + + "\tselect 'Retool' as company_name, false as open_source\n" + + "\tunion\n" + + "\tselect 'XYZ' as company_name, null as open_source\n" + + ") t\n" + + "where t.open_source IS {{binding1}}"); List pluginSpecifiedTemplates = new ArrayList<>(); pluginSpecifiedTemplates.add(new Property("preparedStatement", "true")); @@ -1308,44 +1288,44 @@ public class MySqlPluginTest { executeActionDTO.setParams(params); Mono executeMono = dsConnectionMono - .flatMap(conn -> pluginExecutor.executeParameterized(conn, executeActionDTO, dsConfig, - actionConfiguration)); + .flatMap(conn -> pluginExecutor.executeParameterized(conn, executeActionDTO, dsConfig, + actionConfiguration)); StepVerifier.create(executeMono) - .assertNext(result -> { - assertTrue(result.getIsExecutionSuccess()); - final JsonNode node = ((ArrayNode) result.getBody()).get(0); - assertArrayEquals( - new String[] { - "company_name", - "open_source" - }, - new ObjectMapper() - .convertValue(node, LinkedHashMap.class) - .keySet() - .toArray()); + .assertNext(result -> { + assertTrue(result.getIsExecutionSuccess()); + final JsonNode node = ((ArrayNode) result.getBody()).get(0); + assertArrayEquals( + new String[]{ + "company_name", + "open_source" + }, + new ObjectMapper() + .convertValue(node, LinkedHashMap.class) + .keySet() + .toArray()); - // Verify value - assertEquals(JsonNodeType.NULL, node.get("open_source").getNodeType()); + // Verify value + assertEquals(JsonNodeType.NULL, node.get("open_source").getNodeType()); - }) - .verifyComplete(); + }) + .verifyComplete(); } @Test public void testNullAsStringWithPreparedStatement() { DatasourceConfiguration dsConfig = createDatasourceConfiguration(); - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); ActionConfiguration actionConfiguration = new ActionConfiguration(); actionConfiguration.setBody("SELECT * from (\n" + - "\tselect 'Appsmith' as company_name, true as open_source\n" + - "\tunion\n" + - "\tselect 'Retool' as company_name, false as open_source\n" + - "\tunion\n" + - "\tselect 'XYZ' as company_name, 'null' as open_source\n" + - ") t\n" + - "where t.open_source = {{binding1}};"); + "\tselect 'Appsmith' as company_name, true as open_source\n" + + "\tunion\n" + + "\tselect 'Retool' as company_name, false as open_source\n" + + "\tunion\n" + + "\tselect 'XYZ' as company_name, 'null' as open_source\n" + + ") t\n" + + "where t.open_source = {{binding1}};"); List pluginSpecifiedTemplates = new ArrayList<>(); pluginSpecifiedTemplates.add(new Property("preparedStatement", "true")); @@ -1362,34 +1342,34 @@ public class MySqlPluginTest { executeActionDTO.setParams(params); Mono executeMono = dsConnectionMono - .flatMap(conn -> pluginExecutor.executeParameterized(conn, executeActionDTO, dsConfig, - actionConfiguration)); + .flatMap(conn -> pluginExecutor.executeParameterized(conn, executeActionDTO, dsConfig, + actionConfiguration)); StepVerifier.create(executeMono) - .assertNext(result -> { - assertTrue(result.getIsExecutionSuccess()); - final JsonNode node = ((ArrayNode) result.getBody()).get(0); - assertArrayEquals( - new String[] { - "company_name", - "open_source" - }, - new ObjectMapper() - .convertValue(node, LinkedHashMap.class) - .keySet() - .toArray()); + .assertNext(result -> { + assertTrue(result.getIsExecutionSuccess()); + final JsonNode node = ((ArrayNode) result.getBody()).get(0); + assertArrayEquals( + new String[]{ + "company_name", + "open_source" + }, + new ObjectMapper() + .convertValue(node, LinkedHashMap.class) + .keySet() + .toArray()); - // Verify value - assertEquals(JsonNodeType.STRING, node.get("open_source").getNodeType()); + // Verify value + assertEquals(JsonNodeType.STRING, node.get("open_source").getNodeType()); - }) - .verifyComplete(); + }) + .verifyComplete(); } @Test public void testNumericValuesHavingLeadingZeroWithPreparedStatement() { DatasourceConfiguration dsConfig = createDatasourceConfiguration(); - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); ActionConfiguration actionConfiguration = new ActionConfiguration(); actionConfiguration.setBody("SELECT {{binding1}} as numeric_string;"); @@ -1408,34 +1388,34 @@ public class MySqlPluginTest { executeActionDTO.setParams(params); Mono executeMono = dsConnectionMono - .flatMap(conn -> pluginExecutor.executeParameterized(conn, executeActionDTO, dsConfig, - actionConfiguration)); + .flatMap(conn -> pluginExecutor.executeParameterized(conn, executeActionDTO, dsConfig, + actionConfiguration)); StepVerifier.create(executeMono) - .assertNext(result -> { - assertTrue(result.getIsExecutionSuccess()); - final JsonNode node = ((ArrayNode) result.getBody()).get(0); - assertArrayEquals( - new String[] { - "numeric_string" - }, - new ObjectMapper() - .convertValue(node, LinkedHashMap.class) - .keySet() - .toArray()); + .assertNext(result -> { + assertTrue(result.getIsExecutionSuccess()); + final JsonNode node = ((ArrayNode) result.getBody()).get(0); + assertArrayEquals( + new String[]{ + "numeric_string" + }, + new ObjectMapper() + .convertValue(node, LinkedHashMap.class) + .keySet() + .toArray()); - // Verify value - assertEquals(JsonNodeType.STRING, node.get("numeric_string").getNodeType()); - assertEquals(param1.getValue(), node.get("numeric_string").asText()); + // Verify value + assertEquals(JsonNodeType.STRING, node.get("numeric_string").getNodeType()); + assertEquals(param1.getValue(), node.get("numeric_string").asText()); - }) - .verifyComplete(); + }) + .verifyComplete(); } @Test public void testLongValueWithPreparedStatement() { DatasourceConfiguration dsConfig = createDatasourceConfiguration(); - Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); + Mono dsConnectionMono = pluginExecutor.datasourceCreate(dsConfig); ActionConfiguration actionConfiguration = new ActionConfiguration(); actionConfiguration.setBody("select id from users LIMIT {{binding1}}"); @@ -1454,26 +1434,26 @@ public class MySqlPluginTest { executeActionDTO.setParams(params); Mono executeMono = dsConnectionMono - .flatMap(conn -> pluginExecutor.executeParameterized(conn, executeActionDTO, dsConfig, - actionConfiguration)); + .flatMap(conn -> pluginExecutor.executeParameterized(conn, executeActionDTO, dsConfig, + actionConfiguration)); StepVerifier.create(executeMono) - .assertNext(result -> { - assertTrue(result.getIsExecutionSuccess()); - final JsonNode node = ((ArrayNode) result.getBody()).get(0); - assertArrayEquals( - new String[] { - "id" - }, - new ObjectMapper() - .convertValue(node, LinkedHashMap.class) - .keySet() - .toArray()); + .assertNext(result -> { + assertTrue(result.getIsExecutionSuccess()); + final JsonNode node = ((ArrayNode) result.getBody()).get(0); + assertArrayEquals( + new String[]{ + "id" + }, + new ObjectMapper() + .convertValue(node, LinkedHashMap.class) + .keySet() + .toArray()); - // Verify value - assertEquals(JsonNodeType.NUMBER, node.get("id").getNodeType()); + // Verify value + assertEquals(JsonNodeType.NUMBER, node.get("id").getNodeType()); - }) - .verifyComplete(); + }) + .verifyComplete(); } } \ No newline at end of file diff --git a/app/server/appsmith-plugins/pom.xml b/app/server/appsmith-plugins/pom.xml index 15680f1460..c66d77bc55 100644 --- a/app/server/appsmith-plugins/pom.xml +++ b/app/server/appsmith-plugins/pom.xml @@ -13,11 +13,15 @@ 1.0-SNAPSHOT pom + + 0.11.5 + + org.pf4j pf4j-spring - 0.7.0 + 0.8.0 org.slf4j @@ -69,7 +73,7 @@ io.projectreactor reactor-test - 3.3.5.RELEASE + ${reactor-test.version} test @@ -94,7 +98,7 @@ io.quarkus quarkus-junit4-mock - 2.11.2.Final + 2.14.2.Final test diff --git a/app/server/appsmith-plugins/postgresPlugin/pom.xml b/app/server/appsmith-plugins/postgresPlugin/pom.xml index b6a41436cb..efc5f22a20 100644 --- a/app/server/appsmith-plugins/postgresPlugin/pom.xml +++ b/app/server/appsmith-plugins/postgresPlugin/pom.xml @@ -17,10 +17,6 @@ postgresPlugin - UTF-8 - 11 - ${java.version} - ${java.version} postgres-plugin com.external.plugins.PostgresPlugin 1.0-SNAPSHOT @@ -33,7 +29,6 @@ org.postgresql postgresql - 42.4.3 @@ -51,7 +46,6 @@ org.springframework spring-web - 5.3.20 provided diff --git a/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java b/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java index 0a14f0f6a7..a8a5e042ca 100644 --- a/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java +++ b/app/server/appsmith-plugins/postgresPlugin/src/main/java/com/external/plugins/PostgresPlugin.java @@ -129,7 +129,7 @@ public class PostgresPlugin extends BasePlugin { @Extension public static class PostgresPluginExecutor implements SmartSubstitutionInterface, PluginExecutor { - private final Scheduler scheduler = Schedulers.elastic(); + private final Scheduler scheduler = Schedulers.boundedElastic(); private static final String TABLES_QUERY = "select a.attname as name,\n" + diff --git a/app/server/appsmith-plugins/postgresPlugin/src/test/java/com/external/plugins/PostgresPluginTest.java b/app/server/appsmith-plugins/postgresPlugin/src/test/java/com/external/plugins/PostgresPluginTest.java index 02e66f86ce..d2a6ab9ccb 100644 --- a/app/server/appsmith-plugins/postgresPlugin/src/test/java/com/external/plugins/PostgresPluginTest.java +++ b/app/server/appsmith-plugins/postgresPlugin/src/test/java/com/external/plugins/PostgresPluginTest.java @@ -1518,7 +1518,7 @@ public class PostgresPluginTest { assertTrue(result.getIsExecutionSuccess()); final JsonNode node = ((ArrayNode) result.getBody()).get(0); assertArrayEquals( - new String[] { + new String[]{ "numeric_string" }, new ObjectMapper() diff --git a/app/server/appsmith-plugins/redisPlugin/pom.xml b/app/server/appsmith-plugins/redisPlugin/pom.xml index 5f00f791a6..2c70d1e6a8 100644 --- a/app/server/appsmith-plugins/redisPlugin/pom.xml +++ b/app/server/appsmith-plugins/redisPlugin/pom.xml @@ -16,10 +16,6 @@ redisPlugin - UTF-8 - 11 - ${java.version} - ${java.version} redis-plugin com.external.plugins.RedisPlugin 1.0-SNAPSHOT diff --git a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/RedisPlugin.java b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/RedisPlugin.java index 42927ebb62..03bdf7d979 100644 --- a/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/RedisPlugin.java +++ b/app/server/appsmith-plugins/redisPlugin/src/main/java/com/external/plugins/RedisPlugin.java @@ -56,7 +56,7 @@ public class RedisPlugin extends BasePlugin { @Extension public static class RedisPluginExecutor implements PluginExecutor { - private final Scheduler scheduler = Schedulers.elastic(); + private final Scheduler scheduler = Schedulers.boundedElastic(); @Override public Mono execute(JedisPool jedisPool, diff --git a/app/server/appsmith-plugins/redshiftPlugin/pom.xml b/app/server/appsmith-plugins/redshiftPlugin/pom.xml index d2e236d9a5..24164104f4 100644 --- a/app/server/appsmith-plugins/redshiftPlugin/pom.xml +++ b/app/server/appsmith-plugins/redshiftPlugin/pom.xml @@ -16,10 +16,6 @@ redshiftPlugin - UTF-8 - 11 - ${java.version} - ${java.version} redshift-plugin com.external.plugins.RedshiftPlugin 1.0-SNAPSHOT diff --git a/app/server/appsmith-plugins/redshiftPlugin/src/main/java/com/external/plugins/RedshiftPlugin.java b/app/server/appsmith-plugins/redshiftPlugin/src/main/java/com/external/plugins/RedshiftPlugin.java index f052c03fc5..57c72f6c7e 100644 --- a/app/server/appsmith-plugins/redshiftPlugin/src/main/java/com/external/plugins/RedshiftPlugin.java +++ b/app/server/appsmith-plugins/redshiftPlugin/src/main/java/com/external/plugins/RedshiftPlugin.java @@ -62,7 +62,7 @@ public class RedshiftPlugin extends BasePlugin { @Extension public static class RedshiftPluginExecutor implements PluginExecutor { - private final Scheduler scheduler = Schedulers.elastic(); + private final Scheduler scheduler = Schedulers.boundedElastic(); private static final String TABLES_QUERY = "select a.attname as name,\n" + diff --git a/app/server/appsmith-plugins/restApiPlugin/pom.xml b/app/server/appsmith-plugins/restApiPlugin/pom.xml index b0fa1e7521..965eeec7cd 100644 --- a/app/server/appsmith-plugins/restApiPlugin/pom.xml +++ b/app/server/appsmith-plugins/restApiPlugin/pom.xml @@ -16,10 +16,6 @@ restApiPlugin - UTF-8 - 11 - ${java.version} - ${java.version} restapi-plugin com.external.plugins.RestApiPlugin 1.0-SNAPSHOT @@ -32,14 +28,12 @@ org.springframework spring-core - 5.3.20 provided org.springframework spring-web - 5.3.20 provided @@ -47,7 +41,6 @@ org.springframework spring-webflux provided - 5.3.20 io.projectreactor @@ -67,36 +60,36 @@ com.fasterxml.jackson.core jackson-databind - 2.13.4.1 + ${jackson-bom.version} provided com.fasterxml.jackson.datatype jackson-datatype-jdk8 - 2.10.2 + ${jackson-bom.version} com.fasterxml.jackson.datatype jackson-datatype-jsr310 - 2.10.2 + ${jackson-bom.version} io.jsonwebtoken jjwt-api - 0.11.2 + ${jjwt.version} io.jsonwebtoken jjwt-impl - 0.11.2 + ${jjwt.version} io.jsonwebtoken jjwt-jackson - 0.11.2 + ${jjwt.version} @@ -104,13 +97,12 @@ org.springframework.boot spring-boot-starter-webflux - 2.7.0 + ${spring-boot.version} test org.springframework spring-test - 5.3.20 test diff --git a/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/plugins/RestApiPluginTest.java b/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/plugins/RestApiPluginTest.java index 0f5c78c1c4..f2491b5b7c 100644 --- a/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/plugins/RestApiPluginTest.java +++ b/app/server/appsmith-plugins/restApiPlugin/src/test/java/com/external/plugins/RestApiPluginTest.java @@ -12,8 +12,8 @@ import com.appsmith.external.models.ApiKeyAuth; import com.appsmith.external.models.AuthenticationDTO; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.OAuth2; -import com.appsmith.external.models.PaginationField; import com.appsmith.external.models.PaginationType; +import com.appsmith.external.models.PaginationField; import com.appsmith.external.models.Param; import com.appsmith.external.models.Property; import com.appsmith.external.services.SharedConfig; @@ -26,7 +26,6 @@ import io.jsonwebtoken.JwtParser; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import io.jsonwebtoken.security.SignatureException; -import lombok.extern.slf4j.Slf4j; import mockwebserver3.MockResponse; import mockwebserver3.MockWebServer; import mockwebserver3.RecordedRequest; @@ -38,13 +37,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; import reactor.util.function.Tuple2; import javax.crypto.SecretKey; import java.io.IOException; -import java.net.URLDecoder; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -475,6 +474,79 @@ public class RestApiPluginTest { .verifyComplete(); } + @Test + public void testHttpGetRequestRawBody() { + + ExecuteActionDTO executeActionDTO = new ExecuteActionDTO(); + + Param param = new Param(); + param.setKey("Input1.text"); + param.setValue("123"); + param.setClientDataType(ClientDataType.STRING); + param.setPseudoBindingName("k0"); + + executeActionDTO.setParams(Collections.singletonList(param)); + executeActionDTO.setParamProperties(Collections.singletonMap("k0","string")); + executeActionDTO.setParameterMap(Collections.singletonMap("Input1.text","k0")); + executeActionDTO.setInvertParameterMap(Collections.singletonMap("k0","Input1.text")); + + DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration(); + datasourceConfiguration.setUrl("https://postman-echo.com/get"); + + final List headers = List.of( + new Property("content-type",MediaType.TEXT_PLAIN_VALUE)); + + final List queryParameters = List.of(); + + ActionConfiguration actionConfiguration = new ActionConfiguration(); + actionConfiguration.setHeaders(headers); + actionConfiguration.setQueryParameters(queryParameters); + actionConfiguration.setHttpMethod(HttpMethod.GET); + + actionConfiguration.setTimeoutInMillisecond("10000"); + + actionConfiguration.setPaginationType(PaginationType.NONE); + + actionConfiguration.setEncodeParamsToggle(true); + + actionConfiguration.setPluginSpecifiedTemplates(List.of(new Property(null,true))); + + actionConfiguration.setFormData(Collections.singletonMap("apiContentType", MediaType.TEXT_PLAIN_VALUE)); + + String[] requestBodyList = {"abc is equals to {{Input1.text}}","{ \"abc\": {{Input1.text}} }",""}; + + String[] finalRequestBodyList = {"abc is equals to \"123\"","{ \"abc\": \"123\" }",""}; + + for (int requestBodyIndex = 0; requestBodyIndex < requestBodyList.length; requestBodyIndex++) { + + actionConfiguration.setBody(requestBodyList[requestBodyIndex]); + Mono resultMono = pluginExecutor.executeParameterized(null, executeActionDTO, datasourceConfiguration, actionConfiguration); + + int currentIndex = requestBodyIndex; + StepVerifier.create(resultMono) + .assertNext(result -> { + assertTrue(result.getIsExecutionSuccess()); + JsonNode body = (JsonNode) result.getBody(); + assertNotNull(body); + JsonNode args = body.get("args"); + int index = 0; + StringBuilder actualRequestBody = new StringBuilder(); + while (true) { + if (!args.has(String.valueOf(index))) { + break; + } + JsonNode ans = args.get(String.valueOf(index)); + index++; + actualRequestBody.append(ans.asText()); + } + assertEquals(finalRequestBodyList[currentIndex],actualRequestBody.toString()); + final ActionExecutionRequest request = result.getRequest(); + assertEquals(HttpMethod.GET, request.getHttpMethod()); + }) + .verifyComplete(); + } + } + @Test public void testInvalidSignature() { DatasourceConfiguration dsConfig = new DatasourceConfiguration(); diff --git a/app/server/appsmith-plugins/saasPlugin/pom.xml b/app/server/appsmith-plugins/saasPlugin/pom.xml index f17a77bed8..1bafe02c1a 100644 --- a/app/server/appsmith-plugins/saasPlugin/pom.xml +++ b/app/server/appsmith-plugins/saasPlugin/pom.xml @@ -16,10 +16,6 @@ saasPlugin - UTF-8 - 11 - ${java.version} - ${java.version} saas-plugin com.external.plugins.SaasPlugin 1.0-SNAPSHOT @@ -32,14 +28,12 @@ org.springframework spring-core - 5.3.20 provided org.springframework spring-web - 5.3.20 provided diff --git a/app/server/appsmith-plugins/saasPlugin/src/main/java/com/external/plugins/SaasPlugin.java b/app/server/appsmith-plugins/saasPlugin/src/main/java/com/external/plugins/SaasPlugin.java index d335fac0ed..6e70d38269 100644 --- a/app/server/appsmith-plugins/saasPlugin/src/main/java/com/external/plugins/SaasPlugin.java +++ b/app/server/appsmith-plugins/saasPlugin/src/main/java/com/external/plugins/SaasPlugin.java @@ -22,13 +22,13 @@ import com.fasterxml.jackson.databind.ObjectMapper; import org.pf4j.Extension; import org.pf4j.PluginWrapper; import org.springframework.http.HttpMethod; -import org.springframework.http.HttpStatus; +import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; import org.springframework.http.client.reactive.ClientHttpRequest; import org.springframework.http.codec.json.Jackson2JsonEncoder; import org.springframework.web.reactive.function.BodyInserter; import org.springframework.web.reactive.function.BodyInserters; -import org.springframework.web.reactive.function.client.ClientResponse; import org.springframework.web.reactive.function.client.ExchangeStrategies; import org.springframework.web.reactive.function.client.WebClient; import org.springframework.web.util.UriComponentsBuilder; @@ -123,9 +123,8 @@ public class SaasPlugin extends BasePlugin { // Triggering the actual REST API call return httpCall(client, HttpMethod.POST, uri, requestBodyObj, 0, APPLICATION_JSON_VALUE) - .flatMap(clientResponse -> clientResponse.toEntity(byte[].class)) .map(stringResponseEntity -> { - final HttpStatus statusCode = stringResponseEntity.getStatusCode(); + final HttpStatusCode statusCode = stringResponseEntity.getStatusCode(); byte[] body = stringResponseEntity.getBody(); if (statusCode.is2xxSuccessful()) { try { @@ -157,8 +156,8 @@ public class SaasPlugin extends BasePlugin { } - private Mono httpCall(WebClient webClient, HttpMethod httpMethod, URI uri, Object requestBody, - int iteration, String contentType) { + private Mono> httpCall(WebClient webClient, HttpMethod httpMethod, URI uri, Object requestBody, + int iteration, String contentType) { if (iteration == MAX_REDIRECTS) { return Mono.error(new AppsmithPluginException( AppsmithPluginError.PLUGIN_ERROR, @@ -173,11 +172,12 @@ public class SaasPlugin extends BasePlugin { .method(httpMethod) .uri(uri) .body((BodyInserter) finalRequestBody) - .exchange() + .retrieve() + .toEntity(byte[].class) .doOnError(e -> Mono.error(new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, e))) .flatMap(response -> { - if (response.statusCode().is3xxRedirection()) { - String redirectUrl = response.headers().header("Location").get(0); + if (response.getStatusCode().is3xxRedirection()) { + String redirectUrl = response.getHeaders().getLocation().toString(); /** * TODO * In case the redirected URL is not absolute (complete), create the new URL using the relative path diff --git a/app/server/appsmith-plugins/smtpPlugin/pom.xml b/app/server/appsmith-plugins/smtpPlugin/pom.xml index 32d9519379..6de64531eb 100644 --- a/app/server/appsmith-plugins/smtpPlugin/pom.xml +++ b/app/server/appsmith-plugins/smtpPlugin/pom.xml @@ -16,10 +16,6 @@ smtpPlugin - UTF-8 - 11 - ${java.version} - ${java.version} smtp-plugin com.external.plugins.SmtpPlugin 1.0-SNAPSHOT @@ -29,9 +25,8 @@ - javax.mail - mail - 1.5.0-b01 + org.springframework.boot + spring-boot-starter-mail provided diff --git a/app/server/appsmith-plugins/smtpPlugin/src/main/java/com/external/plugins/SmtpPlugin.java b/app/server/appsmith-plugins/smtpPlugin/src/main/java/com/external/plugins/SmtpPlugin.java index 1d12b9a1a9..7a8c5f9a90 100644 --- a/app/server/appsmith-plugins/smtpPlugin/src/main/java/com/external/plugins/SmtpPlugin.java +++ b/app/server/appsmith-plugins/smtpPlugin/src/main/java/com/external/plugins/SmtpPlugin.java @@ -12,6 +12,21 @@ import com.appsmith.external.models.DatasourceTestResult; import com.appsmith.external.models.Endpoint; import com.appsmith.external.plugins.BasePlugin; import com.appsmith.external.plugins.PluginExecutor; +import jakarta.mail.AuthenticationFailedException; +import jakarta.mail.Authenticator; +import jakarta.mail.Message; +import jakarta.mail.MessagingException; +import jakarta.mail.Multipart; +import jakarta.mail.NoSuchProviderException; +import jakarta.mail.Part; +import jakarta.mail.PasswordAuthentication; +import jakarta.mail.Session; +import jakarta.mail.Transport; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeBodyPart; +import jakarta.mail.internet.MimeMessage; +import jakarta.mail.internet.MimeMultipart; +import jakarta.mail.util.ByteArrayDataSource; import lombok.extern.slf4j.Slf4j; import org.pf4j.Extension; import org.pf4j.PluginWrapper; @@ -19,23 +34,9 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; import reactor.core.publisher.Mono; -import javax.activation.DataHandler; -import javax.activation.DataSource; -import javax.mail.AuthenticationFailedException; -import javax.mail.Authenticator; -import javax.mail.Message; -import javax.mail.MessagingException; -import javax.mail.Multipart; -import javax.mail.NoSuchProviderException; -import javax.mail.Part; -import javax.mail.PasswordAuthentication; -import javax.mail.Session; -import javax.mail.Transport; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeBodyPart; -import javax.mail.internet.MimeMessage; -import javax.mail.internet.MimeMultipart; -import javax.mail.util.ByteArrayDataSource; +import jakarta.activation.DataHandler; +import jakarta.activation.DataSource; + import java.io.IOException; import java.util.Base64; import java.util.HashMap; diff --git a/app/server/appsmith-plugins/smtpPlugin/src/test/java/com/external/plugins/SmtpPluginTest.java b/app/server/appsmith-plugins/smtpPlugin/src/test/java/com/external/plugins/SmtpPluginTest.java index 2f4d2523f8..313350d5c2 100644 --- a/app/server/appsmith-plugins/smtpPlugin/src/test/java/com/external/plugins/SmtpPluginTest.java +++ b/app/server/appsmith-plugins/smtpPlugin/src/test/java/com/external/plugins/SmtpPluginTest.java @@ -18,7 +18,7 @@ import org.testcontainers.utility.DockerImageName; import reactor.core.publisher.Mono; import reactor.test.StepVerifier; -import javax.mail.Session; +import jakarta.mail.Session; import java.util.HashMap; import java.util.List; import java.util.Map; diff --git a/app/server/appsmith-plugins/snowflakePlugin/pom.xml b/app/server/appsmith-plugins/snowflakePlugin/pom.xml index 30dd5e3422..5e41557f64 100644 --- a/app/server/appsmith-plugins/snowflakePlugin/pom.xml +++ b/app/server/appsmith-plugins/snowflakePlugin/pom.xml @@ -16,10 +16,6 @@ snowflakePlugin - UTF-8 - 11 - ${java.version} - ${java.version} snowflake-plugin com.external.plugins.SnowflakePlugin 1.0-SNAPSHOT @@ -32,21 +28,18 @@ org.springframework spring-core - 5.3.20 provided org.springframework spring-web - 5.3.20 provided org.springframework spring-webflux - 5.3.20 io.projectreactor @@ -66,56 +59,55 @@ net.snowflake snowflake-jdbc - 3.13.4 + 3.13.25 com.fasterxml.jackson.core jackson-databind - 2.13.4.1 + ${jackson-bom.version} provided com.fasterxml.jackson.datatype jackson-datatype-jdk8 - 2.10.2 + ${jackson-bom.version} com.fasterxml.jackson.datatype jackson-datatype-jsr310 - 2.10.2 + ${jackson-bom.version} io.jsonwebtoken jjwt-api - 0.11.2 + ${jjwt.version} io.jsonwebtoken jjwt-impl - 0.11.2 + ${jjwt.version} io.jsonwebtoken jjwt-jackson - 0.11.2 + ${jjwt.version} org.assertj assertj-core - 3.13.2 test org.springframework.boot spring-boot-starter-webflux - 2.7.0 + ${spring-boot.version} test diff --git a/app/server/appsmith-plugins/snowflakePlugin/src/main/java/com/external/plugins/SnowflakePlugin.java b/app/server/appsmith-plugins/snowflakePlugin/src/main/java/com/external/plugins/SnowflakePlugin.java index 85a9dab841..d9ba6876f7 100644 --- a/app/server/appsmith-plugins/snowflakePlugin/src/main/java/com/external/plugins/SnowflakePlugin.java +++ b/app/server/appsmith-plugins/snowflakePlugin/src/main/java/com/external/plugins/SnowflakePlugin.java @@ -48,7 +48,7 @@ public class SnowflakePlugin extends BasePlugin { @Extension public static class SnowflakePluginExecutor implements PluginExecutor { - private final Scheduler scheduler = Schedulers.elastic(); + private final Scheduler scheduler = Schedulers.boundedElastic(); @Override public Mono execute(Connection connection, DatasourceConfiguration datasourceConfiguration, ActionConfiguration actionConfiguration) { diff --git a/app/server/appsmith-server/pom.xml b/app/server/appsmith-server/pom.xml index 1a60f1e050..70ac59b0af 100644 --- a/app/server/appsmith-server/pom.xml +++ b/app/server/appsmith-server/pom.xml @@ -16,8 +16,7 @@ This is the API server for the Appsmith project - 11 - 1.8.11 + 1.9 2.4.4 1.35 @@ -42,12 +41,13 @@ + + Ideally this dependency should have been added in the pom.xml file of GraphQLPlugin module, but it is + causing 'java.lang.NoClassDefFoundError' error. Hence, adding it here after many attempts of fixing it the right + way. Somehow adding it here makes it work. GraphQLPlugin module's pom.xml file also has this dependency + defined with 'provided' scope + --> com.graphql-java graphql-java @@ -102,7 +102,11 @@ org.springframework.boot spring-boot-starter-webflux - 2.7.0 + ${spring-boot.version} + + + io.projectreactor + reactor-core-micrometer org.springframework.boot @@ -118,12 +122,13 @@ spring-boot-starter-data-mongodb - com.github.cloudyrock.mongock - mongock-spring-v5 + io.mongock + mongock-springboot-v3 + - com.github.cloudyrock.mongock - mongodb-springdata-v3-driver + io.mongock + mongodb-springdata-v4-driver org.springframework.boot @@ -143,8 +148,8 @@ org.glassfish - javax.el - 3.0.0 + jakarta.el + 4.0.2 org.projectlombok @@ -159,8 +164,14 @@ de.flapdoodle.embed de.flapdoodle.embed.mongo + 4.3.1 + test + + + de.flapdoodle.embed + de.flapdoodle.embed.mongo.spring30x + 4.3.2 test - 3.5.0 com.google.guava @@ -185,6 +196,17 @@ io.micrometer micrometer-registry-prometheus + + + io.micrometer + micrometer-tracing-bridge-brave + 1.0.0 + + + io.micrometer + context-propagation + 1.0.0 + org.junit.jupiter @@ -217,7 +239,7 @@ org.pf4j pf4j-spring - 0.7.0 + 0.8.0 @@ -271,6 +293,7 @@ io.projectreactor reactor-test + ${reactor-test.version} test @@ -398,11 +421,10 @@ - - com.github.cloudyrock.mongock + io.mongock mongock-bom - 4.3.8 + 5.1.7 pom import diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/ce/PolicyGeneratorCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/ce/PolicyGeneratorCE.java index 7dc24ed12a..27f7b4daef 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/ce/PolicyGeneratorCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/ce/PolicyGeneratorCE.java @@ -12,7 +12,7 @@ import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.DirectedAcyclicGraph; import org.jgrapht.traverse.DepthFirstIterator; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.Collection; import java.util.Collections; import java.util.EnumSet; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/ce/RoleGraphCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/ce/RoleGraphCE.java index 3319353c4b..43d051d0a1 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/ce/RoleGraphCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/acl/ce/RoleGraphCE.java @@ -7,7 +7,7 @@ import org.jgrapht.graph.DefaultEdge; import org.jgrapht.graph.DirectedMultigraph; import org.jgrapht.traverse.BreadthFirstIterator; -import javax.annotation.PostConstruct; +import jakarta.annotation.PostConstruct; import java.util.EnumSet; import java.util.LinkedHashSet; import java.util.Set; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/ce/CustomServerOAuth2AuthorizationRequestResolverCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/ce/CustomServerOAuth2AuthorizationRequestResolverCE.java index fdd5cf008c..c102a47a8a 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/ce/CustomServerOAuth2AuthorizationRequestResolverCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/authentication/handlers/ce/CustomServerOAuth2AuthorizationRequestResolverCE.java @@ -76,7 +76,8 @@ public class CustomServerOAuth2AuthorizationRequestResolverCE implements ServerO /** * Creates a new instance - * @param clientRegistrationRepository the repository to resolve the {@link ClientRegistration} + * + * @param clientRegistrationRepository the repository to resolve the {@link ClientRegistration} * @param commonConfig * @param redirectHelper */ @@ -89,7 +90,8 @@ public class CustomServerOAuth2AuthorizationRequestResolverCE implements ServerO /** * Creates a new instance - * @param clientRegistrationRepository the repository to resolve the {@link ClientRegistration} + * + * @param clientRegistrationRepository the repository to resolve the {@link ClientRegistration} * @param authorizationRequestMatcher the matcher that determines if the request is a match and extracts the * {@link #DEFAULT_REGISTRATION_ID_URI_VARIABLE_NAME} from the path variables. * @param redirectHelper @@ -135,7 +137,7 @@ public class CustomServerOAuth2AuthorizationRequestResolverCE implements ServerO } private Mono authorizationRequest(ServerWebExchange exchange, - ClientRegistration clientRegistration) { + ClientRegistration clientRegistration) { String redirectUriStr = expandRedirectUri(exchange.getRequest(), clientRegistration); Map attributes = new HashMap<>(); @@ -167,8 +169,8 @@ public class CustomServerOAuth2AuthorizationRequestResolverCE implements ServerO } builder.additionalParameters(additionalParameters); - } else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) { - builder = OAuth2AuthorizationRequest.implicit(); +// } else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) { +// builder = OAuth2AuthorizationRequest.implicit(); } else { throw new IllegalArgumentException( "Invalid Authorization Grant Type (" + clientRegistration.getAuthorizationGrantType().getValue() @@ -204,7 +206,7 @@ public class CustomServerOAuth2AuthorizationRequestResolverCE implements ServerO } /** - * Expands the {@link ClientRegistration#getRedirectUriTemplate()} with following provided variables:
+ * Expands the {@link ClientRegistration#getRedirectUri()} with following provided variables:
* - baseUrl (e.g. https://localhost/app)
* - baseScheme (e.g. https)
* - baseHost (e.g. localhost)
@@ -237,7 +239,7 @@ public class CustomServerOAuth2AuthorizationRequestResolverCE implements ServerO uriVariables.put("basePort", port == -1 ? "" : ":" + port); String path = uriComponents.getPath(); if (StringUtils.hasLength(path) && path.charAt(0) != PATH_DELIMITER) { - path = PATH_DELIMITER + path; + path = PATH_DELIMITER + path; } uriVariables.put("basePath", path == null ? "" : path); uriVariables.put("baseUrl", uriComponents.toUriString()); @@ -248,7 +250,7 @@ public class CustomServerOAuth2AuthorizationRequestResolverCE implements ServerO } uriVariables.put("action", action); - return UriComponentsBuilder.fromUriString(clientRegistration.getRedirectUriTemplate()) + return UriComponentsBuilder.fromUriString(clientRegistration.getRedirectUri()) .buildAndExpand(uriVariables) .toUriString(); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/CommonConfig.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/CommonConfig.java index 648310f5fa..5994d499a0 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/CommonConfig.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/CommonConfig.java @@ -1,10 +1,12 @@ package com.appsmith.server.configurations; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.DeserializationFeature; +import com.appsmith.util.SerializationUtils; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import jakarta.validation.Validation; +import jakarta.validation.Validator; +import jakarta.validation.ValidatorFactory; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; @@ -16,8 +18,6 @@ import org.springframework.util.StringUtils; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; -import javax.validation.Validation; -import javax.validation.Validator; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -70,22 +70,29 @@ public class CommonConfig { @Bean public Scheduler scheduler() { - return Schedulers.newElastic(ELASTIC_THREAD_POOL_NAME); + return Schedulers.newBoundedElastic( + Schedulers.DEFAULT_BOUNDED_ELASTIC_SIZE, + Schedulers.DEFAULT_BOUNDED_ELASTIC_QUEUESIZE, + ELASTIC_THREAD_POOL_NAME); } @Bean public Validator validator() { - return Validation.buildDefaultValidatorFactory().getValidator(); + try (ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory()) { + return validatorFactory.getValidator(); + } } @Bean public ObjectMapper objectMapper() { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.registerModule(new JavaTimeModule()); - objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); - objectMapper.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false); - objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - return objectMapper; + return SerializationUtils.getDefaultObjectMapper(); + } + + @Bean + public Gson gsonInstance() { + GsonBuilder gsonBuilder = new GsonBuilder(); + SerializationUtils.typeAdapterRegistration().customize(gsonBuilder); + return gsonBuilder.create(); } public List getOauthAllowedDomains() { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/EmailConfig.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/EmailConfig.java index 91e12e1306..6caaa1fd76 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/EmailConfig.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/EmailConfig.java @@ -1,5 +1,6 @@ package com.appsmith.server.configurations; +import jakarta.mail.internet.InternetAddress; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; @@ -9,7 +10,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; import org.springframework.util.StringUtils; -import javax.mail.internet.InternetAddress; import java.io.UnsupportedEncodingException; @Getter diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/InstanceConfig.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/InstanceConfig.java index fce91ef75e..0ef9d81a6d 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/InstanceConfig.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/InstanceConfig.java @@ -10,7 +10,6 @@ import com.appsmith.util.WebClientUtils; import io.sentry.Sentry; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.SpringApplication; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.ApplicationContext; @@ -37,10 +36,9 @@ public class InstanceConfig implements ApplicationListener this.printReady()); - Mono.when(checkInstanceSchemaVersion(), registrationAndRtsCheckMono) + checkInstanceSchemaVersion() + .flatMap(signal -> registrationAndRtsCheckMono) .subscribe(null, e -> { log.debug("Application start up encountered an error: {}", e.getMessage()); Sentry.captureException(e); @@ -72,11 +71,13 @@ public class InstanceConfig implements ApplicationListener { - log.error("\n" + - "################################################\n" + - "Error while trying to start up Appsmith instance: \n" + - "{}\n" + - "################################################\n", + log.error(""" + + ################################################ + Error while trying to start up Appsmith instance:\s + {} + ################################################ + """, errorSignal.getMessage()); SpringApplication.exit(applicationContext, () -> 1); @@ -93,7 +94,7 @@ public class InstanceConfig implements ApplicationListener in your browser to experience Appsmith!\n" + """ + + █████╗ ██████╗ ██████╗ ███████╗███╗ ███╗██╗████████╗██╗ ██╗ ██╗███████╗ ██████╗ ██╗ ██╗███╗ ██╗███╗ ██╗██╗███╗ ██╗ ██████╗ ██╗ + ██╔══██╗██╔══██╗██╔══██╗██╔════╝████╗ ████║██║╚══██╔══╝██║ ██║ ██║██╔════╝ ██╔══██╗██║ ██║████╗ ██║████╗ ██║██║████╗ ██║██╔════╝ ██║ + ███████║██████╔╝██████╔╝███████╗██╔████╔██║██║ ██║ ███████║ ██║███████╗ ██████╔╝██║ ██║██╔██╗ ██║██╔██╗ ██║██║██╔██╗ ██║██║ ███╗██║ + ██╔══██║██╔═══╝ ██╔═══╝ ╚════██║██║╚██╔╝██║██║ ██║ ██╔══██║ ██║╚════██║ ██╔══██╗██║ ██║██║╚██╗██║██║╚██╗██║██║██║╚██╗██║██║ ██║╚═╝ + ██║ ██║██║ ██║ ███████║██║ ╚═╝ ██║██║ ██║ ██║ ██║ ██║███████║ ██║ ██║╚██████╔╝██║ ╚████║██║ ╚████║██║██║ ╚████║╚██████╔╝██╗ + ╚═╝ ╚═╝╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═══╝╚═╝ ╚═══╝╚═╝╚═╝ ╚═══╝ ╚═════╝ ╚═╝ + + Please open http://localhost: in your browser to experience Appsmith! + """ ); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MDCConfig.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MDCConfig.java index 6eaf36d8a5..00ada006bf 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MDCConfig.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MDCConfig.java @@ -2,6 +2,7 @@ package com.appsmith.server.configurations; import com.appsmith.server.filters.MDCFilter; import com.appsmith.server.helpers.LogHelper; +import jakarta.annotation.PostConstruct; import org.reactivestreams.Subscription; import org.slf4j.MDC; import org.springframework.context.annotation.Configuration; @@ -10,8 +11,7 @@ import reactor.core.publisher.Hooks; import reactor.core.publisher.Operators; import reactor.util.context.Context; -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; +import jakarta.annotation.PreDestroy; import java.util.Map; @Configuration diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MongoConfig.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MongoConfig.java index 7c454f7d6b..0b1609797e 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MongoConfig.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/MongoConfig.java @@ -7,10 +7,11 @@ import com.appsmith.external.services.EncryptionService; import com.appsmith.server.configurations.mongo.SoftDeleteMongoRepositoryFactoryBean; import com.appsmith.server.converters.StringToInstantConverter; import com.appsmith.server.repositories.BaseRepositoryImpl; -import com.github.cloudyrock.mongock.driver.mongodb.springdata.v3.SpringDataMongoV3Driver; -import com.github.cloudyrock.spring.v5.MongockSpring5; import com.mongodb.ReadConcern; import com.mongodb.WriteConcern; +import io.mongock.driver.mongodb.springdata.v4.SpringDataMongoV4Driver; +import io.mongock.runner.springboot.MongockSpringboot; +import io.mongock.runner.springboot.base.MongockInitializingBeanRunner; import lombok.extern.slf4j.Slf4j; import org.bson.conversions.Bson; import org.springframework.context.ApplicationContext; @@ -59,14 +60,15 @@ public class MongoConfig { Link to documentation: https://docs.mongock.io/v5/runner/springboot/index.html */ @Bean - public MongockSpring5.MongockInitializingBeanRunner mongockInitializingBeanRunner(ApplicationContext springContext, MongoTemplate mongoTemplate) { - SpringDataMongoV3Driver springDataMongoV3Driver = SpringDataMongoV3Driver.withDefaultLock(mongoTemplate); - springDataMongoV3Driver.setWriteConcern(WriteConcern.JOURNALED.withJournal(false)); - springDataMongoV3Driver.setReadConcern(ReadConcern.LOCAL); + public MongockInitializingBeanRunner mongockInitializingBeanRunner(ApplicationContext springContext, MongoTemplate mongoTemplate) { + SpringDataMongoV4Driver mongoDriver = SpringDataMongoV4Driver.withDefaultLock(mongoTemplate); + mongoDriver.setWriteConcern(WriteConcern.JOURNALED.withJournal(false)); + mongoDriver.setReadConcern(ReadConcern.LOCAL); - return MongockSpring5.builder() - .setDriver(springDataMongoV3Driver) + return MongockSpringboot.builder() + .setDriver(mongoDriver) .addChangeLogsScanPackages(List.of("com.appsmith.server.migrations")) + .addMigrationScanPackage("com.appsmith.server.migrations.db") .setSpringContext(springContext) // any extra configuration you need .buildInitializingBeanRunner(); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/SecurityConfig.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/SecurityConfig.java index 0a28feda07..2f1fbc943f 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/SecurityConfig.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/configurations/SecurityConfig.java @@ -13,6 +13,7 @@ import com.appsmith.server.services.UserService; import com.fasterxml.jackson.databind.ObjectMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; import org.springframework.http.HttpMethod; import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity; @@ -46,7 +47,8 @@ import static com.appsmith.server.constants.Url.USER_URL; import static java.time.temporal.ChronoUnit.DAYS; @EnableWebFluxSecurity -@EnableReactiveMethodSecurity(useAuthorizationManager = true) +@EnableReactiveMethodSecurity +@Configuration public class SecurityConfig { @Autowired @@ -120,6 +122,7 @@ public class SecurityConfig { .and() .authorizeExchange() .matchers(ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, Url.LOGIN_URL), + ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, Url.HEALTH_CHECK), ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, USER_URL), ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, USER_URL + "/super"), ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, USER_URL + "/forgotPassword"), diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/Url.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/Url.java index 32edcc3b33..43c809f5ec 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/Url.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/constants/Url.java @@ -3,6 +3,7 @@ package com.appsmith.server.constants; public interface Url { String BASE_URL = "/api"; String VERSION = "/v1"; + String HEALTH_CHECK = BASE_URL + VERSION + "/health"; String LOGIN_URL = BASE_URL + VERSION + "/login"; String LOGOUT_URL = BASE_URL + VERSION + "/logout"; String WORKSPACE_URL = BASE_URL + VERSION + "/workspaces"; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/HealthCheckController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/HealthCheckController.java new file mode 100644 index 0000000000..66aaa0ae4c --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/HealthCheckController.java @@ -0,0 +1,16 @@ +package com.appsmith.server.controllers; + +import com.appsmith.server.constants.Url; +import com.appsmith.server.controllers.ce.HealthCheckControllerCE; +import com.appsmith.server.services.HealthCheckService; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(Url.HEALTH_CHECK) +public class HealthCheckController extends HealthCheckControllerCE { + + public HealthCheckController(HealthCheckService healthCheckService) { + super(healthCheckService); + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ActionCollectionControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ActionCollectionControllerCE.java index e8590c420a..09485f496d 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ActionCollectionControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ActionCollectionControllerCE.java @@ -27,7 +27,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseStatus; import reactor.core.publisher.Mono; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.List; @Slf4j diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ActionControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ActionControllerCE.java index 800d2bcb70..d8de9abbfd 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ActionControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ActionControllerCE.java @@ -32,7 +32,7 @@ import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.List; @Slf4j diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java index e297ea4e19..f089c88111 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ApplicationControllerCE.java @@ -43,7 +43,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.List; @Slf4j diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/BaseController.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/BaseController.java index 159956eaa4..e5b6158d9d 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/BaseController.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/BaseController.java @@ -22,7 +22,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.List; @RequiredArgsConstructor diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/CommentControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/CommentControllerCE.java index 5b2dce5fec..5e4d8a96ef 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/CommentControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/CommentControllerCE.java @@ -23,7 +23,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.List; @Slf4j diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/CustomJSLibControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/CustomJSLibControllerCE.java index a25ef86996..d351540386 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/CustomJSLibControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/CustomJSLibControllerCE.java @@ -5,6 +5,7 @@ import com.appsmith.server.constants.Url; import com.appsmith.server.domains.CustomJSLib; import com.appsmith.server.dtos.ResponseDTO; import com.appsmith.server.services.CustomJSLibService; +import jakarta.validation.Valid; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; @@ -15,7 +16,6 @@ import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; import reactor.core.publisher.Mono; -import javax.validation.Valid; import java.util.List; @Slf4j diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/DatasourceControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/DatasourceControllerCE.java index fb1f4b9275..faf20bb852 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/DatasourceControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/DatasourceControllerCE.java @@ -33,7 +33,7 @@ import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.net.URI; import java.util.List; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/HealthCheckControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/HealthCheckControllerCE.java new file mode 100644 index 0000000000..34a8ad8619 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/HealthCheckControllerCE.java @@ -0,0 +1,21 @@ +package com.appsmith.server.controllers.ce; + +import com.appsmith.server.constants.Url; +import com.appsmith.server.dtos.ResponseDTO; +import com.appsmith.server.services.HealthCheckService; +import lombok.AllArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import reactor.core.publisher.Mono; + +@RequestMapping(Url.HEALTH_CHECK) +@AllArgsConstructor +public class HealthCheckControllerCE { + private final HealthCheckService healthCheckService; + + @GetMapping + public Mono> getHealth() { + return healthCheckService.getHealth().map(health -> new ResponseDTO<>(HttpStatus.OK.value(), health, null)); + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/InstanceAdminControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/InstanceAdminControllerCE.java index 304cee6fe3..6588beada4 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/InstanceAdminControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/InstanceAdminControllerCE.java @@ -17,7 +17,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.Map; @RequestMapping(Url.INSTANCE_ADMIN_URL) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/LayoutControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/LayoutControllerCE.java index fbc319cc3b..380370abfb 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/LayoutControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/LayoutControllerCE.java @@ -22,7 +22,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import reactor.core.publisher.Mono; -import javax.validation.Valid; +import jakarta.validation.Valid; @RequestMapping(Url.LAYOUT_URL) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/NotificationControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/NotificationControllerCE.java index d4559151f8..14efa34fc5 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/NotificationControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/NotificationControllerCE.java @@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -import javax.validation.Valid; +import jakarta.validation.Valid; import static com.appsmith.server.exceptions.AppsmithError.UNSUPPORTED_OPERATION; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/PageControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/PageControllerCE.java index 06345de396..c18e542aaf 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/PageControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/PageControllerCE.java @@ -30,7 +30,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -import javax.validation.Valid; +import jakarta.validation.Valid; @RequestMapping(Url.PAGE_URL) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/PluginControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/PluginControllerCE.java index 2178725918..186b4fdf0f 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/PluginControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/PluginControllerCE.java @@ -16,7 +16,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; import reactor.core.publisher.Mono; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.List; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ThemeControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ThemeControllerCE.java index 955923e1a6..fb0b839c19 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ThemeControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/ThemeControllerCE.java @@ -21,7 +21,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.List; @Slf4j diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/UserControllerCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/UserControllerCE.java index 841b905fc4..2553f6dd2e 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/UserControllerCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/controllers/ce/UserControllerCE.java @@ -36,7 +36,7 @@ import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; -import javax.validation.Valid; +import jakarta.validation.Valid; import java.util.List; import java.util.Map; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Application.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Application.java index 8b301d6333..b37eb57fa7 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Application.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Application.java @@ -14,7 +14,7 @@ import lombok.ToString; import org.springframework.data.annotation.Transient; import org.springframework.data.mongodb.core.mapping.Document; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; import java.io.Serializable; import java.time.Instant; import java.util.ArrayList; @@ -47,6 +47,7 @@ public class Application extends BaseDomain { TODO: remove default values from application. */ @JsonProperty(access = JsonProperty.Access.READ_ONLY) + @Deprecated(forRemoval = true) Boolean isPublic = false; List pages; @@ -109,6 +110,11 @@ public class Application extends BaseDomain { EmbedSetting embedSetting; + NavigationSetting unpublishedNavigationSetting; + + NavigationSetting publishedNavigationSetting; + + /** * Earlier this was returning value of the updatedAt property in the base domain. * As this property is modified by the framework when there is any change in domain, @@ -251,11 +257,28 @@ public class Application extends BaseDomain { * EmbedSetting is used for embedding Appsmith apps on other platforms */ @Data - @NoArgsConstructor - @AllArgsConstructor public static class EmbedSetting { private String height; private String width; private Boolean showNavigationBar; } + + + /** + * NavigationSetting stores the navigation configuration for the app + */ + @Data + public static class NavigationSetting { + private Boolean showNavbar; + private String orientation; + private String navStyle; + private String position; + private String itemStyle; + private String colorStyle; + private String logoAssetId; + private String logoConfiguration; + private Boolean showSignIn; + private Boolean showShareApp; + } + } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Group.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Group.java index 776512c834..e57a415386 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Group.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Group.java @@ -7,7 +7,7 @@ import lombok.ToString; import org.apache.commons.lang.StringUtils; import org.springframework.data.mongodb.core.mapping.Document; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; import java.util.Set; @Getter diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Organization.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Organization.java index bea945c252..f6434bf74c 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Organization.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Organization.java @@ -9,7 +9,7 @@ import lombok.Setter; import lombok.ToString; import org.springframework.data.mongodb.core.mapping.Document; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; import java.util.List; import java.util.Set; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Page.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Page.java index d70ea2b93b..100b18b22e 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Page.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Page.java @@ -7,7 +7,7 @@ import lombok.Setter; import lombok.ToString; import org.springframework.data.mongodb.core.mapping.Document; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; import java.util.List; @Getter diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/PermissionGroup.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/PermissionGroup.java index 4258a95d9c..1c77a97a01 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/PermissionGroup.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/PermissionGroup.java @@ -7,7 +7,7 @@ import lombok.NoArgsConstructor; import lombok.Setter; import org.springframework.data.mongodb.core.mapping.Document; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; import java.util.HashSet; import java.util.Set; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Role.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Role.java index eb116c018c..381c7efb82 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Role.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Role.java @@ -8,7 +8,7 @@ import lombok.Setter; import lombok.ToString; import org.springframework.data.mongodb.core.mapping.Document; -import javax.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotEmpty; @Document diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Workspace.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Workspace.java index 1c257c2e01..6e8f529f02 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Workspace.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/domains/Workspace.java @@ -9,7 +9,7 @@ import lombok.Setter; import lombok.ToString; import org.springframework.data.mongodb.core.mapping.Document; -import javax.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotBlank; import java.util.List; import java.util.Set; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ActionCollectionMoveDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ActionCollectionMoveDTO.java index a7aeb61774..aaa93255a5 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ActionCollectionMoveDTO.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ActionCollectionMoveDTO.java @@ -3,7 +3,7 @@ package com.appsmith.server.dtos; import lombok.Getter; import lombok.Setter; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; @Getter @Setter diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ActionMoveDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ActionMoveDTO.java index 0e50503e67..d68a8646d8 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ActionMoveDTO.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ActionMoveDTO.java @@ -4,7 +4,7 @@ import com.appsmith.external.models.ActionDTO; import lombok.Getter; import lombok.Setter; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; @Getter @Setter diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ApplicationAccessDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ApplicationAccessDTO.java index f3de864f5a..24407038e3 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ApplicationAccessDTO.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ApplicationAccessDTO.java @@ -4,7 +4,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; @Getter @Setter diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/CommentThreadFilterDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/CommentThreadFilterDTO.java index 328651221d..2424247ec1 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/CommentThreadFilterDTO.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/CommentThreadFilterDTO.java @@ -3,7 +3,7 @@ package com.appsmith.server.dtos; import com.appsmith.server.domains.ApplicationMode; import lombok.Data; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; @Data public class CommentThreadFilterDTO { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ResetUserPasswordDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ResetUserPasswordDTO.java index fca3eb317b..7c97fcbbda 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ResetUserPasswordDTO.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ResetUserPasswordDTO.java @@ -4,7 +4,7 @@ import com.appsmith.server.domains.User; import lombok.Getter; import lombok.Setter; -import javax.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotEmpty; @Getter @Setter diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/TestEmailConfigRequestDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/TestEmailConfigRequestDTO.java index f59885f73a..cbb6c75b6b 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/TestEmailConfigRequestDTO.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/TestEmailConfigRequestDTO.java @@ -2,9 +2,9 @@ package com.appsmith.server.dtos; import lombok.Data; -import javax.validation.constraints.Email; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; @Data public class TestEmailConfigRequestDTO { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/UpdateIsReadNotificationByIdDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/UpdateIsReadNotificationByIdDTO.java index 1b4aa2c812..32c4585582 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/UpdateIsReadNotificationByIdDTO.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/UpdateIsReadNotificationByIdDTO.java @@ -5,8 +5,8 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; -import javax.validation.constraints.NotEmpty; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import java.util.List; @Getter diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/UpdateIsReadNotificationDTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/UpdateIsReadNotificationDTO.java index fb85db61a1..c078ba49fa 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/UpdateIsReadNotificationDTO.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/UpdateIsReadNotificationDTO.java @@ -4,7 +4,7 @@ import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; @Getter @Setter diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/InviteUsersCE_DTO.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/InviteUsersCE_DTO.java index 78158c2e08..8737dc421e 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/InviteUsersCE_DTO.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/dtos/ce/InviteUsersCE_DTO.java @@ -3,7 +3,7 @@ package com.appsmith.server.dtos.ce; import lombok.Getter; import lombok.Setter; -import javax.validation.constraints.NotNull; +import jakarta.validation.constraints.NotNull; import java.util.List; @Getter diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppSmithErrorWebExceptionHandler.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppSmithErrorWebExceptionHandler.java index 22dd5d97a5..83ad44b3ae 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppSmithErrorWebExceptionHandler.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppSmithErrorWebExceptionHandler.java @@ -22,7 +22,7 @@ import org.springframework.web.reactive.function.server.ServerResponse; import org.springframework.web.reactive.result.view.ViewResolver; import reactor.core.publisher.Mono; -import javax.annotation.Nonnull; +import jakarta.annotation.Nonnull; import java.util.Map; import java.util.stream.Collectors; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java index a6a4fa0dd3..065d7fbfa1 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/exceptions/AppsmithError.java @@ -101,6 +101,7 @@ public enum AppsmithError { PLUGIN_RUN_FAILED(500, 5003, "Plugin execution failed with error {0}", AppsmithErrorAction.DEFAULT, null, ErrorType.INTERNAL_ERROR, null), PLUGIN_EXECUTION_TIMEOUT(504, 5040, "Plugin execution exceeded the maximum allowed time. Please increase the timeout in your action settings or check your backend action endpoint", AppsmithErrorAction.DEFAULT, null, ErrorType.CONNECTIVITY_ERROR, null), + HEALTHCHECK_TIMEOUT(408, 4080, "{0} connection timed out.", AppsmithErrorAction.DEFAULT, null, ErrorType.CONNECTIVITY_ERROR, null), PLUGIN_LOAD_FORM_JSON_FAIL(500, 5004, "[{0}] Unable to load datasource form configuration. Details: {1}.", AppsmithErrorAction.LOG_EXTERNALLY, null, ErrorType.INTERNAL_ERROR, null), PLUGIN_LOAD_TEMPLATES_FAIL(500, 5005, "Unable to load datasource templates. Details: {0}.", diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/filters/MDCFilter.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/filters/MDCFilter.java index 8414c0a9b0..81114a5774 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/filters/MDCFilter.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/filters/MDCFilter.java @@ -31,7 +31,7 @@ public class MDCFilter implements WebFilter { private static final String MDC_HEADER_PREFIX = "X-MDC-"; private static final String REQUEST_ID_HEADER = "X-REQUEST-ID"; public static final String USER_EMAIL = "userEmail"; - private static final String REQUEST_ID_LOG = "requestId"; + public static final String REQUEST_ID_LOG = "requestId"; private static final String SESSION_ID_LOG = "sessionId"; private static final String SESSION = "SESSION"; public static final String THREAD = "thread"; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/GitFileUtils.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/GitFileUtils.java index 2e6a4b4938..c04370e9db 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/GitFileUtils.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/GitFileUtils.java @@ -3,6 +3,7 @@ package com.appsmith.server.helpers; import com.appsmith.external.constants.AnalyticsEvents; import com.appsmith.external.git.FileInterface; import com.appsmith.external.helpers.Stopwatch; +import com.appsmith.external.models.ActionDTO; import com.appsmith.external.models.ApplicationGitReference; import com.appsmith.external.models.Datasource; import com.appsmith.git.helpers.FileUtilsImpl; @@ -14,7 +15,6 @@ import com.appsmith.server.domains.NewAction; import com.appsmith.server.domains.NewPage; import com.appsmith.server.domains.Theme; import com.appsmith.server.dtos.ActionCollectionDTO; -import com.appsmith.external.models.ActionDTO; import com.appsmith.server.dtos.ApplicationJson; import com.appsmith.server.dtos.PageDTO; import com.appsmith.server.exceptions.AppsmithError; @@ -64,15 +64,19 @@ public class GitFileUtils { private final AnalyticsService analyticsService; private final SessionUserService sessionUserService; + private final Gson gson; + // Only include the application helper fields in metadata object private static final Set blockedMetadataFields - = Set.of(EXPORTED_APPLICATION, DATASOURCE_LIST, PAGE_LIST, ACTION_LIST, ACTION_COLLECTION_LIST, DECRYPTED_FIELDS, EDIT_MODE_THEME); + = Set.of(EXPORTED_APPLICATION, DATASOURCE_LIST, PAGE_LIST, ACTION_LIST, ACTION_COLLECTION_LIST, DECRYPTED_FIELDS, EDIT_MODE_THEME); + /** * This method will save the complete application in the local repo directory. * Path to repo will be : ./container-volumes/git-repo/workspaceId/defaultApplicationId/repoName/{application_data} - * @param baseRepoSuffix path suffix used to create a local repo path + * + * @param baseRepoSuffix path suffix used to create a local repo path * @param applicationJson application reference object from which entire application can be rehydrated - * @param branchName name of the branch for the current application + * @param branchName name of the branch for the current application * @return repo path where the application is stored */ public Mono saveApplicationToLocalRepo(Path baseRepoSuffix, @@ -112,8 +116,9 @@ public class GitFileUtils { /** * Method to convert application resources to the structure which can be serialised by appsmith-git module for * serialisation + * * @param applicationJson application resource including actions, jsobjects, pages - * @return resource which can be saved to file system + * @return resource which can be saved to file system */ public ApplicationGitReference createApplicationReference(ApplicationJson applicationJson) { ApplicationGitReference applicationReference = new ApplicationGitReference(); @@ -221,9 +226,9 @@ public class GitFileUtils { /** * Method to reconstruct the application from the local git repo * - * @param workspaceId To which workspace application needs to be rehydrated + * @param workspaceId To which workspace application needs to be rehydrated * @param defaultApplicationId Root application for the current branched application - * @param branchName for which branch the application needs to rehydrate + * @param branchName for which branch the application needs to rehydrate * @return application reference from which entire application can be rehydrated */ public Mono reconstructApplicationJsonFromGitRepo(String workspaceId, @@ -267,7 +272,6 @@ public class GitFileUtils { if (resource == null) { return null; } - Gson gson = new Gson(); return gson.fromJson(gson.toJson(resource), type); } @@ -286,12 +290,13 @@ public class GitFileUtils { public Mono initializeReadme(Path baseRepoSuffix, String viewModeUrl, String editModeUrl) throws IOException { - return fileUtils.initializeReadme(baseRepoSuffix,viewModeUrl, editModeUrl) + return fileUtils.initializeReadme(baseRepoSuffix, viewModeUrl, editModeUrl) .onErrorResume(e -> Mono.error(new AppsmithException(AppsmithError.GIT_FILE_SYSTEM_ERROR, e))); } /** * When the user clicks on detach remote, we need to remove the repo from the file system + * * @param baseRepoSuffix path suffix used to create a branch repo path as per worktree implementation * @return success on remove of file system */ @@ -339,7 +344,6 @@ public class GitFileUtils { // Clone the edit mode theme to published theme as both should be same for git connected application because we // do deploy and push as a single operation applicationJson.setPublishedTheme(applicationJson.getEditModeTheme()); - Gson gson = new Gson(); if (application != null && !CollectionUtils.isNullOrEmpty(application.getPages())) { // Remove null values diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/LogHelper.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/LogHelper.java index 825d66da16..8d22111425 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/LogHelper.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/LogHelper.java @@ -37,9 +37,9 @@ public class LogHelper { return; } - Optional> maybeContextMap = signal.getContext().getOrEmpty(CONTEXT_MAP); + Optional> maybeContextMap = signal.getContextView().getOrEmpty(CONTEXT_MAP); - if (!maybeContextMap.isPresent()) { + if (maybeContextMap.isEmpty()) { log.accept(signal.get()); return; } @@ -60,9 +60,9 @@ public class LogHelper { } Optional> maybeContextMap - = signal.getContext().getOrEmpty(CONTEXT_MAP); + = signal.getContextView().getOrEmpty(CONTEXT_MAP); - if (!maybeContextMap.isPresent()) { + if (maybeContextMap.isEmpty()) { log.accept(signal.getThrowable()); return; } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/WidgetSpecificUtils.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/WidgetSpecificUtils.java index 8ec6ebcc9e..6ee12c0d7b 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/WidgetSpecificUtils.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/helpers/WidgetSpecificUtils.java @@ -1,10 +1,8 @@ package com.appsmith.server.helpers; import com.appsmith.server.constants.FieldName; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import net.minidev.json.JSONObject; -import net.minidev.json.parser.JSONParser; import java.util.HashMap; import java.util.Map; @@ -13,9 +11,6 @@ import java.util.Set; @Slf4j public class WidgetSpecificUtils { - private static ObjectMapper objectMapper = new ObjectMapper(); - private static JSONParser jsonParser = new JSONParser(JSONParser.MODE_PERMISSIVE); - public static JSONObject escapeTableWidgetPrimaryColumns(JSONObject dsl, Set escapedWidgetNames) { Set keySet = dsl.keySet(); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog0.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog0.java index 9b60531b9a..3eeca8e5e2 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog0.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog0.java @@ -6,9 +6,9 @@ import com.appsmith.server.domains.Config; import com.appsmith.server.domains.QConfig; import com.github.cloudyrock.mongock.ChangeLog; import com.github.cloudyrock.mongock.ChangeSet; -import com.github.cloudyrock.mongock.driver.mongodb.springdata.v3.decorator.impl.MongockTemplate; import lombok.extern.slf4j.Slf4j; import net.minidev.json.JSONObject; +import org.springframework.data.mongodb.core.MongoTemplate; import java.util.Map; @@ -27,23 +27,23 @@ public class DatabaseChangelog0 { * will check the current state and manage the migration accordingly */ @ChangeSet(order = "001", id = "initialize-schema-version", author = "") - public void initializeSchemaVersion(MongockTemplate mongockTemplate) { + public void initializeSchemaVersion(MongoTemplate mongoTemplate) { - Config instanceIdConfig = mongockTemplate.findOne( + Config instanceIdConfig = mongoTemplate.findOne( query(where(fieldName(QConfig.config1.name)).is("instance-id")), Config.class); if (instanceIdConfig != null) { // If instance id exists, this is an existing instance // Instantiate with the first version so that we expect to go through all the migrations - mongockTemplate.insert(new Config( + mongoTemplate.insert(new Config( new JSONObject(Map.of("value", 1)), Appsmith.INSTANCE_SCHEMA_VERSION )); } else { // Is no instance id exists, this is a new instance // Instantiate with latest schema version that this Appsmith release shipped with - mongockTemplate.insert(new Config( + mongoTemplate.insert(new Config( new JSONObject(Map.of("value", CommonConfig.LATEST_INSTANCE_SCHEMA_VERSION)), Appsmith.INSTANCE_SCHEMA_VERSION )); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog1.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog1.java index 7cdc55d560..8ca87827f1 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog1.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog1.java @@ -68,7 +68,6 @@ import com.appsmith.server.helpers.TextUtils; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.cloudyrock.mongock.ChangeLog; import com.github.cloudyrock.mongock.ChangeSet; -import com.github.cloudyrock.mongock.driver.mongodb.springdata.v3.decorator.impl.MongockTemplate; import com.mongodb.MongoException; import com.mongodb.client.MongoCollection; import com.mongodb.client.MongoCursor; @@ -90,6 +89,7 @@ import org.springframework.data.domain.Sort; import org.springframework.data.mongodb.UncategorizedMongoDbException; import org.springframework.data.mongodb.core.CollectionCallback; import org.springframework.data.mongodb.core.MongoOperations; +import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.index.CompoundIndexDefinition; import org.springframework.data.mongodb.core.index.Index; import org.springframework.data.mongodb.core.index.IndexOperations; @@ -188,17 +188,17 @@ public class DatabaseChangelog1 { } /** - * Given a MongockTemplate, a domain class and a bunch of Index definitions, this pure utility function will ensure - * those indexes on the database behind the MongockTemplate instance. + * Given a mongoTemplate, a domain class and a bunch of Index definitions, this pure utility function will ensure + * those indexes on the database behind the mongoTemplate instance. */ - public static void ensureIndexes(MongockTemplate mongoTemplate, Class entityClass, Index... indexes) { + public static void ensureIndexes(MongoTemplate mongoTemplate, Class entityClass, Index... indexes) { IndexOperations indexOps = mongoTemplate.indexOps(entityClass); for (Index index : indexes) { indexOps.ensureIndex(index); } } - public static void dropIndexIfExists(MongockTemplate mongoTemplate, Class entityClass, String name) { + public static void dropIndexIfExists(MongoTemplate mongoTemplate, Class entityClass, String name) { try { mongoTemplate.indexOps(entityClass).dropIndex(name); } catch (UncategorizedMongoDbException ignored) { @@ -225,7 +225,7 @@ public class DatabaseChangelog1 { return actionDTO; } - public static void installPluginToAllWorkspaces(MongockTemplate mongockTemplate, String pluginId) { + public static void installPluginToAllWorkspaces(MongoTemplate mongoTemplate, String pluginId) { Query queryToFetchWorkspacesWOPlugin = new Query(); /* Filter in only those workspaces that don't have the plugin installed */ @@ -235,11 +235,11 @@ public class DatabaseChangelog1 { Update update = new Update(); update.addToSet("plugins", new WorkspacePlugin(pluginId, WorkspacePluginStatus.FREE)); - mongockTemplate.updateMulti(queryToFetchWorkspacesWOPlugin, update, Workspace.class); + mongoTemplate.updateMulti(queryToFetchWorkspacesWOPlugin, update, Workspace.class); } @ChangeSet(order = "001", id = "initial-plugins", author = "") - public void initialPlugins(MongockTemplate mongoTemplate) { + public void initialPlugins(MongoTemplate mongoTemplate) { Plugin plugin1 = new Plugin(); plugin1.setName("PostgresDbPlugin"); plugin1.setType(PluginType.DB); @@ -290,7 +290,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "002", id = "remove-org-name-index", author = "") - public void removeOrgNameIndex(MongockTemplate mongoTemplate) { + public void removeOrgNameIndex(MongoTemplate mongoTemplate) { dropIndexIfExists(mongoTemplate, Organization.class, "name"); } @@ -306,7 +306,7 @@ public class DatabaseChangelog1 { * the `Action.datasource` field. */ @ChangeSet(order = "004", id = "initial-indexes", author = "") - public void addInitialIndexes(MongockTemplate mongoTemplate) { + public void addInitialIndexes(MongoTemplate mongoTemplate) { Index createdAtIndex = makeIndex("createdAt"); ensureIndexes(mongoTemplate, Action.class, @@ -373,7 +373,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "005", id = "application-deleted-at", author = "") - public void addApplicationDeletedAtFieldAndIndex(MongockTemplate mongoTemplate) { + public void addApplicationDeletedAtFieldAndIndex(MongoTemplate mongoTemplate) { dropIndexIfExists(mongoTemplate, Application.class, "organization_application_compound_index"); ensureIndexes(mongoTemplate, Application.class, @@ -390,7 +390,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "006", id = "hide-rapidapi-plugin", author = "") - public void hideRapidApiPluginFromCreateDatasource(MongockTemplate mongoTemplate) { + public void hideRapidApiPluginFromCreateDatasource(MongoTemplate mongoTemplate) { final Plugin rapidApiPlugin = mongoTemplate.findOne( query(where("packageName").is("rapidapi-plugin")), Plugin.class @@ -407,7 +407,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "007", id = "datasource-deleted-at", author = "") - public void addDatasourceDeletedAtFieldAndIndex(MongockTemplate mongoTemplate) { + public void addDatasourceDeletedAtFieldAndIndex(MongoTemplate mongoTemplate) { dropIndexIfExists(mongoTemplate, Datasource.class, "organization_datasource_compound_index"); ensureIndexes(mongoTemplate, Datasource.class, @@ -424,7 +424,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "008", id = "page-deleted-at", author = "") - public void addPageDeletedAtFieldAndIndex(MongockTemplate mongoTemplate) { + public void addPageDeletedAtFieldAndIndex(MongoTemplate mongoTemplate) { dropIndexIfExists(mongoTemplate, Page.class, "application_page_compound_index"); ensureIndexes(mongoTemplate, Page.class, @@ -441,7 +441,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "009", id = "friendly-plugin-names", author = "") - public void setFriendlyPluginNames(MongockTemplate mongoTemplate) { + public void setFriendlyPluginNames(MongoTemplate mongoTemplate) { for (Plugin plugin : mongoTemplate.findAll(Plugin.class)) { if ("postgres-plugin".equals(plugin.getPackageName())) { plugin.setName("PostgreSQL"); @@ -457,7 +457,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "010", id = "add-delete-datasource-perm-existing-groups", author = "") - public void addDeleteDatasourcePermToExistingGroups(MongockTemplate mongoTemplate) { + public void addDeleteDatasourcePermToExistingGroups(MongoTemplate mongoTemplate) { for (Group group : mongoTemplate.findAll(Group.class)) { if (CollectionUtils.isEmpty(group.getPermissions())) { group.setPermissions(new HashSet<>()); @@ -468,7 +468,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "011", id = "install-default-plugins-to-all-organizations", author = "") - public void installDefaultPluginsToAllOrganizations(MongockTemplate mongoTemplate) { + public void installDefaultPluginsToAllOrganizations(MongoTemplate mongoTemplate) { final List defaultPlugins = mongoTemplate.find( query(where("defaultInstall").is(true)), Plugin.class @@ -496,7 +496,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "012", id = "ensure-datasource-created-and-updated-at-fields", author = "") - public void ensureDatasourceCreatedAndUpdatedAt(MongockTemplate mongoTemplate) { + public void ensureDatasourceCreatedAndUpdatedAt(MongoTemplate mongoTemplate) { final List missingCreatedAt = mongoTemplate.find( query(where("createdAt").exists(false)), Datasource.class @@ -519,14 +519,14 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "013", id = "add-index-for-sequence-name", author = "") - public void addIndexForSequenceName(MongockTemplate mongoTemplate) { + public void addIndexForSequenceName(MongoTemplate mongoTemplate) { ensureIndexes(mongoTemplate, Sequence.class, makeIndex(FieldName.NAME).unique() ); } @ChangeSet(order = "014", id = "set-initial-sequence-for-datasource", author = "") - public void setInitialSequenceForDatasource(MongockTemplate mongoTemplate) { + public void setInitialSequenceForDatasource(MongoTemplate mongoTemplate) { final Long maxUntitledDatasourceNumber = mongoTemplate.find( query(where(FieldName.NAME).regex("^" + Datasource.DEFAULT_NAME_PREFIX + " \\d+$")), Datasource.class @@ -544,7 +544,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "015", id = "set-plugin-image-and-docs-link", author = "") - public void setPluginImageAndDocsLink(MongockTemplate mongoTemplate) { + public void setPluginImageAndDocsLink(MongoTemplate mongoTemplate) { for (Plugin plugin : mongoTemplate.findAll(Plugin.class)) { if ("postgres-plugin".equals(plugin.getPackageName())) { plugin.setIconLocation("https://s3.us-east-2.amazonaws.com/assets.appsmith.com/Postgress.png"); @@ -569,7 +569,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "016", id = "fix-double-escapes", author = "") - public void fixDoubleEscapes(MongockTemplate mongoTemplate) { + public void fixDoubleEscapes(MongoTemplate mongoTemplate) { final List actions = mongoTemplate.find( query(where("jsonPathKeys").exists(true)), Action.class @@ -602,7 +602,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "017", id = "encrypt-password", author = "") - public void encryptPassword(MongockTemplate mongoTemplate, EncryptionService encryptionService) { + public void encryptPassword(MongoTemplate mongoTemplate, EncryptionService encryptionService) { final List datasources = mongoTemplate.find( query(where("datasourceConfiguration.authentication.password").exists(true)), Datasource.class @@ -616,7 +616,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "018", id = "install-mysql-plugins", author = "") - public void mysqlPlugin(MongockTemplate mongoTemplate) { + public void mysqlPlugin(MongoTemplate mongoTemplate) { Plugin plugin1 = new Plugin(); plugin1.setName("Mysql"); plugin1.setType(PluginType.DB); @@ -635,7 +635,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "019", id = "update-database-documentation-links", author = "") - public void updateDatabaseDocumentationLinks(MongockTemplate mongoTemplate) { + public void updateDatabaseDocumentationLinks(MongoTemplate mongoTemplate) { for (Plugin plugin : mongoTemplate.findAll(Plugin.class)) { if ("postgres-plugin".equals(plugin.getPackageName())) { plugin.setDocumentationLink( @@ -655,7 +655,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "020", id = "execute-action-for-read-action", author = "") - public void giveExecutePermissionToReadActionUsers(MongockTemplate mongoTemplate) { + public void giveExecutePermissionToReadActionUsers(MongoTemplate mongoTemplate) { final List actions = mongoTemplate.find( query(where("policies").exists(true)), Action.class @@ -685,7 +685,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "021", id = "invite-and-public-permissions", author = "") - public void giveInvitePermissionToOrganizationsAndPublicPermissionsToApplications(MongockTemplate mongoTemplate) { + public void giveInvitePermissionToOrganizationsAndPublicPermissionsToApplications(MongoTemplate mongoTemplate) { final List organizations = mongoTemplate.find( query(where("userRoles").exists(true)), Organization.class @@ -758,7 +758,7 @@ public class DatabaseChangelog1 { // Examples organization is no longer getting used. Commenting out the migrations which add/update the same. // @SuppressWarnings({"unchecked", "rawtypes"}) // @ChangeSet(order = "022", id = "examples-organization", author = "") -// public void examplesOrganization(MongockTemplate mongoTemplate, EncryptionService encryptionService) throws IOException { +// public void examplesOrganization(MongoTemplate mongoTemplate, EncryptionService encryptionService) throws IOException { // // final Map plugins = new HashMap<>(); // @@ -887,7 +887,7 @@ public class DatabaseChangelog1 { // } // // @ChangeSet(order = "023", id = "set-example-apps-in-config", author = "") -// public void setExampleAppsInConfig(MongockTemplate mongoTemplate) { +// public void setExampleAppsInConfig(MongoTemplate mongoTemplate) { // // // final org.springframework.data.mongodb.core.query.Query configQuery = query(where("name").is("template-organization")); @@ -922,7 +922,7 @@ public class DatabaseChangelog1 { // } // // @ChangeSet(order = "024", id = "update-erroneous-action-ids", author = "") -// public void updateErroneousActionIdsInPage(MongockTemplate mongoTemplate) { +// public void updateErroneousActionIdsInPage(MongoTemplate mongoTemplate) { // final org.springframework.data.mongodb.core.query.Query configQuery = query(where("name").is("template-organization")); // // final Config config = mongoTemplate.findOne( @@ -1008,7 +1008,7 @@ public class DatabaseChangelog1 { // } @ChangeSet(order = "025", id = "generate-unique-id-for-instance", author = "") - public void generateUniqueIdForInstance(MongockTemplate mongoTemplate) { + public void generateUniqueIdForInstance(MongoTemplate mongoTemplate) { mongoTemplate.insert(new Config( new JSONObject(Map.of("value", new ObjectId().toHexString())), "instance-id" @@ -1016,7 +1016,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "026", id = "fix-password-reset-token-expiration", author = "") - public void fixTokenExpiration(MongockTemplate mongoTemplate) { + public void fixTokenExpiration(MongoTemplate mongoTemplate) { dropIndexIfExists(mongoTemplate, PasswordResetToken.class, FieldName.CREATED_AT); dropIndexIfExists(mongoTemplate, PasswordResetToken.class, FieldName.EMAIL); @@ -1028,7 +1028,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "027", id = "add-elastic-search-plugin", author = "") - public void addElasticSearchPlugin(MongockTemplate mongoTemplate) { + public void addElasticSearchPlugin(MongoTemplate mongoTemplate) { Plugin plugin1 = new Plugin(); plugin1.setName("ElasticSearch"); plugin1.setType(PluginType.DB); @@ -1048,7 +1048,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "028", id = "add-dynamo-plugin", author = "") - public void addDynamoPlugin(MongockTemplate mongoTemplate) { + public void addDynamoPlugin(MongoTemplate mongoTemplate) { Plugin plugin1 = new Plugin(); plugin1.setName("DynamoDB"); plugin1.setType(PluginType.DB); @@ -1068,7 +1068,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "029", id = "use-png-logos", author = "") - public void usePngLogos(MongockTemplate mongoTemplate) { + public void usePngLogos(MongoTemplate mongoTemplate) { mongoTemplate.updateFirst( query(where(fieldName(QPlugin.plugin.packageName)).is("elasticsearch-plugin")), update(fieldName(QPlugin.plugin.iconLocation), @@ -1078,7 +1078,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "030", id = "add-redis-plugin", author = "") - public void addRedisPlugin(MongockTemplate mongoTemplate) { + public void addRedisPlugin(MongoTemplate mongoTemplate) { Plugin plugin1 = new Plugin(); plugin1.setName("Redis"); plugin1.setType(PluginType.DB); @@ -1098,7 +1098,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "031", id = "add-msSql-plugin", author = "") - public void addMsSqlPlugin(MongockTemplate mongoTemplate) { + public void addMsSqlPlugin(MongoTemplate mongoTemplate) { Plugin plugin1 = new Plugin(); plugin1.setName("MsSQL"); plugin1.setType(PluginType.DB); @@ -1118,7 +1118,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "037", id = "createNewPageIndexAfterDroppingNewPage", author = "") - public void addNewPageIndexAfterDroppingNewPage(MongockTemplate mongoTemplate) { + public void addNewPageIndexAfterDroppingNewPage(MongoTemplate mongoTemplate) { Index createdAtIndex = makeIndex("createdAt"); // Drop existing NewPage class @@ -1131,7 +1131,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "038", id = "createNewActionIndexAfterDroppingNewAction", author = "") - public void addNewActionIndexAfterDroppingNewAction(MongockTemplate mongoTemplate) { + public void addNewActionIndexAfterDroppingNewAction(MongoTemplate mongoTemplate) { Index createdAtIndex = makeIndex("createdAt"); // Drop existing NewAction class @@ -1144,7 +1144,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "039", id = "migrate-page-and-actions", author = "") - public void migratePage(MongockTemplate mongoTemplate) { + public void migratePage(MongoTemplate mongoTemplate) { final List pages = mongoTemplate.find( query(where("deletedAt").is(null)), Page.class @@ -1255,7 +1255,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "040", id = "new-page-new-action-add-indexes", author = "") - public void addNewPageAndNewActionNewIndexes(MongockTemplate mongoTemplate) { + public void addNewPageAndNewActionNewIndexes(MongoTemplate mongoTemplate) { dropIndexIfExists(mongoTemplate, NewAction.class, "createdAt"); @@ -1274,7 +1274,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "041", id = "new-action-add-index-pageId", author = "") - public void addNewActionIndexForPageId(MongockTemplate mongoTemplate) { + public void addNewActionIndexForPageId(MongoTemplate mongoTemplate) { dropIndexIfExists(mongoTemplate, NewAction.class, "applicationId_deleted_createdAt_compound_index"); @@ -1285,7 +1285,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "042", id = "update-action-index-to-single-multiple-indices", author = "") - public void updateActionIndexToSingleMultipleIndices(MongockTemplate mongoTemplate) { + public void updateActionIndexToSingleMultipleIndices(MongoTemplate mongoTemplate) { dropIndexIfExists(mongoTemplate, NewAction.class, "applicationId_deleted_unpublishedPageId_compound_index"); @@ -1306,7 +1306,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "043", id = "add-firestore-plugin", author = "") - public void addFirestorePlugin(MongockTemplate mongoTemplate) { + public void addFirestorePlugin(MongoTemplate mongoTemplate) { Plugin plugin = new Plugin(); plugin.setName("Firestore"); plugin.setType(PluginType.DB); @@ -1326,7 +1326,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "044", id = "ensure-app-icons-and-colors", author = "") - public void ensureAppIconsAndColors(MongockTemplate mongoTemplate) { + public void ensureAppIconsAndColors(MongoTemplate mongoTemplate) { final String iconFieldName = fieldName(QApplication.application.icon); final String colorFieldName = fieldName(QApplication.application.color); @@ -1466,7 +1466,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "045", id = "update-authentication-type", author = "") - public void updateAuthenticationTypes(MongockTemplate mongoTemplate) { + public void updateAuthenticationTypes(MongoTemplate mongoTemplate) { mongoTemplate.execute("datasource", new CollectionCallback() { @Override public String doInCollection(MongoCollection collection) throws MongoException, DataAccessException { @@ -1547,7 +1547,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "047", id = "add-isSendSessionEnabled-key-for-datasources", author = "") - public void addIsSendSessionEnabledPropertyInDatasources(MongockTemplate mongoTemplate) { + public void addIsSendSessionEnabledPropertyInDatasources(MongoTemplate mongoTemplate) { String keyName = "isSendSessionEnabled"; @@ -1593,7 +1593,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "048", id = "add-redshift-plugin", author = "") - public void addRedshiftPlugin(MongockTemplate mongoTemplate) { + public void addRedshiftPlugin(MongoTemplate mongoTemplate) { Plugin plugin = new Plugin(); plugin.setName("Redshift"); plugin.setType(PluginType.DB); @@ -1613,12 +1613,12 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "049", id = "clear-userdata-collection", author = "") - public void clearUserDataCollection(MongockTemplate mongoTemplate) { + public void clearUserDataCollection(MongoTemplate mongoTemplate) { mongoTemplate.dropCollection(UserData.class); } @ChangeSet(order = "050", id = "update-database-documentation-links-v1-2-1", author = "") - public void updateDatabaseDocumentationLinks_v1_2_1(MongockTemplate mongoTemplate) { + public void updateDatabaseDocumentationLinks_v1_2_1(MongoTemplate mongoTemplate) { for (Plugin plugin : mongoTemplate.findAll(Plugin.class)) { switch (plugin.getPackageName()) { case "postgres-plugin": @@ -1657,7 +1657,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "051", id = "add-amazons3-plugin", author = "") - public void addAmazonS3Plugin(MongockTemplate mongoTemplate) { + public void addAmazonS3Plugin(MongoTemplate mongoTemplate) { Plugin plugin = new Plugin(); plugin.setName("Amazon S3"); plugin.setType(PluginType.DB); @@ -1677,7 +1677,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "052", id = "add-app-viewer-invite-policy", author = "") - public void addAppViewerInvitePolicy(MongockTemplate mongoTemplate) { + public void addAppViewerInvitePolicy(MongoTemplate mongoTemplate) { final List organizations = mongoTemplate.find( query(new Criteria().andOperator( where(fieldName(QOrganization.organization.userRoles) + ".role").is(AppsmithRole.ORGANIZATION_VIEWER.name()) @@ -1703,7 +1703,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "053", id = "update-plugin-datasource-form-components", author = "") - public void updatePluginDatasourceFormComponents(MongockTemplate mongoTemplate) { + public void updatePluginDatasourceFormComponents(MongoTemplate mongoTemplate) { for (Plugin plugin : mongoTemplate.findAll(Plugin.class)) { switch (plugin.getPackageName()) { case "postgres-plugin": @@ -1730,7 +1730,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "054", id = "update-database-encode-params-toggle", author = "") - public void updateEncodeParamsToggle(MongockTemplate mongoTemplate) { + public void updateEncodeParamsToggle(MongoTemplate mongoTemplate) { for (NewAction action : mongoTemplate.findAll(NewAction.class)) { if (action.getPluginType() != null && action.getPluginType().equals("API")) { @@ -1749,7 +1749,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "055", id = "update-postgres-plugin-preparedStatement-config", author = "") - public void updatePostgresActionsSetPreparedStatementConfiguration(MongockTemplate mongoTemplate) { + public void updatePostgresActionsSetPreparedStatementConfiguration(MongoTemplate mongoTemplate) { List plugins = mongoTemplate.find( query(new Criteria().andOperator( @@ -1789,10 +1789,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "056", id = "fix-dynamicBindingPathListForActions", author = "") - public void fixDynamicBindingPathListForExistingActions(MongockTemplate mongoTemplate) { - - ObjectMapper objectMapper = new ObjectMapper(); - + public void fixDynamicBindingPathListForExistingActions(MongoTemplate mongoTemplate) { for (NewAction action : mongoTemplate.findAll(NewAction.class)) { // We have found an action with dynamic binding path list set by the client. @@ -1900,7 +1897,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "057", id = "update-database-action-configuration-timeout", author = "") - public void updateActionConfigurationTimeout(MongockTemplate mongoTemplate) { + public void updateActionConfigurationTimeout(MongoTemplate mongoTemplate) { for (NewAction action : mongoTemplate.findAll(NewAction.class)) { boolean updateTimeout = false; @@ -1928,7 +1925,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "058", id = "update-s3-datasource-configuration-and-label", author = "") - public void updateS3DatasourceConfigurationAndLabel(MongockTemplate mongoTemplate) { + public void updateS3DatasourceConfigurationAndLabel(MongoTemplate mongoTemplate) { Plugin s3Plugin = mongoTemplate .find(query(where("name").is("Amazon S3")), Plugin.class).get(0); s3Plugin.setName("S3"); @@ -2024,7 +2021,7 @@ public class DatabaseChangelog1 { // Commenting out Example workspace related migrations since example workspaces are not used anymore // @ChangeSet(order = "060", id = "clear-example-apps", author = "") -// public void clearExampleApps(MongockTemplate mongoTemplate) { +// public void clearExampleApps(MongoTemplate mongoTemplate) { // mongoTemplate.updateFirst( // query(where(fieldName(QConfig.config1.name)).is("template-organization")), // update("config.applicationIds", Collections.emptyList()).set("config.organizationId", null), @@ -2033,7 +2030,7 @@ public class DatabaseChangelog1 { // } @ChangeSet(order = "061", id = "update-mysql-postgres-mongo-ssl-mode", author = "") - public void updateMysqlPostgresMongoSslMode(MongockTemplate mongoTemplate) { + public void updateMysqlPostgresMongoSslMode(MongoTemplate mongoTemplate) { Plugin mysqlPlugin = mongoTemplate .findOne(query(where("packageName").is("mysql-plugin")), Plugin.class); @@ -2108,7 +2105,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "062", id = "add-commenting-permissions", author = "") - public void addCommentingPermissions(MongockTemplate mongoTemplate) { + public void addCommentingPermissions(MongoTemplate mongoTemplate) { final List applications = mongoTemplate.findAll(Application.class); for (final Application application : applications) { @@ -2132,7 +2129,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "062", id = "add-google-sheets-plugin", author = "") - public void addGoogleSheetsPlugin(MongockTemplate mongoTemplate) { + public void addGoogleSheetsPlugin(MongoTemplate mongoTemplate) { Plugin plugin = new Plugin(); plugin.setName("Google Sheets"); plugin.setType(PluginType.SAAS); @@ -2153,7 +2150,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "063", id = "mark-instance-unregistered", author = "") - public void markInstanceAsUnregistered(MongockTemplate mongoTemplate) { + public void markInstanceAsUnregistered(MongoTemplate mongoTemplate) { mongoTemplate.insert(new Config( new JSONObject(Map.of("value", false)), Appsmith.APPSMITH_REGISTERED @@ -2161,7 +2158,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "065", id = "create-entry-in-sequence-per-organization-for-datasource", author = "") - public void createEntryInSequencePerOrganizationForDatasource(MongockTemplate mongoTemplate) { + public void createEntryInSequencePerOrganizationForDatasource(MongoTemplate mongoTemplate) { Map maxDatasourceCount = new HashMap<>(); mongoTemplate @@ -2188,7 +2185,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "066", id = "migrate-smartSubstitution-dataType", author = "") - public void migrateSmartSubstitutionDataTypeBoolean(MongockTemplate mongoTemplate, MongoOperations mongoOperations) { + public void migrateSmartSubstitutionDataTypeBoolean(MongoTemplate mongoTemplate, MongoOperations mongoOperations) { Set smartSubTurnedOn = new HashSet<>(); Set smartSubTurnedOff = new HashSet<>(); Set noSmartSubConfig = new HashSet<>(); @@ -2268,7 +2265,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "067", id = "update-mongo-import-from-srv-field", author = "") - public void updateMongoImportFromSrvField(MongockTemplate mongoTemplate) { + public void updateMongoImportFromSrvField(MongoTemplate mongoTemplate) { Plugin mongoPlugin = mongoTemplate .findOne(query(where("packageName").is("mongo-plugin")), Plugin.class); @@ -2284,7 +2281,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "068", id = "delete-mongo-datasource-structures", author = "") - public void deleteMongoDatasourceStructures(MongockTemplate mongoTemplate, MongoOperations mongoOperations) { + public void deleteMongoDatasourceStructures(MongoTemplate mongoTemplate, MongoOperations mongoOperations) { // Mongo Form requires the query templates to change as well. To ensure this, mongo datasources // must re-compute the structure. The following deletes all such structures. Whenever getStructure API call is @@ -2304,16 +2301,16 @@ public class DatabaseChangelog1 { @ChangeSet(order = "069", id = "set-mongo-actions-type-to-raw", author = "") - public void setMongoActionInputToRaw(MongockTemplate mongockTemplate) { + public void setMongoActionInputToRaw(MongoTemplate mongoTemplate) { // All the existing mongo actions at this point will only have ever been in the raw format // For these actions to be readily available to users, we need to set their input type to raw manually // This is required because since the mongo form, the default input type on the UI has been set to FORM - Plugin mongoPlugin = mongockTemplate.findOne(query(where("packageName").is("mongo-plugin")), Plugin.class); + Plugin mongoPlugin = mongoTemplate.findOne(query(where("packageName").is("mongo-plugin")), Plugin.class); // Fetch all the actions built on top of a mongo database, not having any value set for input type assert mongoPlugin != null; - List rawMongoActions = mongockTemplate.find( + List rawMongoActions = mongoTemplate.find( query(new Criteria().andOperator( where(fieldName(QNewAction.newAction.pluginId)).is(mongoPlugin.getId()))), NewAction.class @@ -2332,7 +2329,7 @@ public class DatabaseChangelog1 { List pluginSpecifiedTemplates = action.getUnpublishedAction().getActionConfiguration().getPluginSpecifiedTemplates(); pluginSpecifiedTemplates.add(new Property(null, "RAW")); - mongockTemplate.save(action); + mongoTemplate.save(action); } } @@ -2344,7 +2341,7 @@ public class DatabaseChangelog1 { * - [... path, operator, value, ...] --> [... [ {"path":path, "operator":operator, "value":value} ] ...] */ @ChangeSet(order = "070", id = "update-firestore-where-conditions-data", author = "") - public void updateFirestoreWhereConditionsData(MongockTemplate mongoTemplate) { + public void updateFirestoreWhereConditionsData(MongoTemplate mongoTemplate) { Plugin firestorePlugin = mongoTemplate .findOne(query(where("packageName").is("firestore-plugin")), Plugin.class); @@ -2432,7 +2429,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "071", id = "add-application-export-permissions", author = "") - public void addApplicationExportPermissions(MongockTemplate mongoTemplate) { + public void addApplicationExportPermissions(MongoTemplate mongoTemplate) { final List organizations = mongoTemplate.find( query(where("userRoles").exists(true)), Organization.class @@ -2515,7 +2512,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "072", id = "add-snowflake-plugin", author = "") - public void addSnowflakePlugin(MongockTemplate mongoTemplate) { + public void addSnowflakePlugin(MongoTemplate mongoTemplate) { Plugin plugin = new Plugin(); plugin.setName("Snowflake"); plugin.setType(PluginType.DB); @@ -2537,13 +2534,13 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "073", id = "mongo-form-merge-update-commands", author = "") - public void migrateUpdateOneToUpdateManyMongoFormCommand(MongockTemplate mongockTemplate) { + public void migrateUpdateOneToUpdateManyMongoFormCommand(MongoTemplate mongoTemplate) { - Plugin mongoPlugin = mongockTemplate.findOne(query(where("packageName").is("mongo-plugin")), Plugin.class); + Plugin mongoPlugin = mongoTemplate.findOne(query(where("packageName").is("mongo-plugin")), Plugin.class); // Fetch all the actions built on top of a mongo database with command type update_one or update_many assert mongoPlugin != null; - List updateMongoActions = mongockTemplate.find( + List updateMongoActions = mongoTemplate.find( query(new Criteria().andOperator( where(fieldName(QNewAction.newAction.pluginId)).is(mongoPlugin.getId()))), NewAction.class @@ -2615,13 +2612,13 @@ public class DatabaseChangelog1 { // Now that all the actions have been updated, save all the actions for (NewAction action : updateMongoActions) { - mongockTemplate.save(action); + mongoTemplate.save(action); } } @ChangeSet(order = "074", id = "ensure-user-created-and-updated-at-fields", author = "") - public void ensureUserCreatedAndUpdatedAt(MongockTemplate mongoTemplate) { + public void ensureUserCreatedAndUpdatedAt(MongoTemplate mongoTemplate) { final List missingCreatedAt = mongoTemplate.find( query(where("createdAt").exists(false)), User.class @@ -2652,7 +2649,7 @@ public class DatabaseChangelog1 { * - [] */ @ChangeSet(order = "075", id = "add-and-update-order-for-all-pages", author = "") - public void addOrderToAllPagesOfApplication(MongockTemplate mongoTemplate) { + public void addOrderToAllPagesOfApplication(MongoTemplate mongoTemplate) { for (Application application : mongoTemplate.findAll(Application.class)) { //Commenting out this piece code as we have decided to remove the order field from ApplicationPages /*if(application.getPages() != null) { @@ -2674,13 +2671,13 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "076", id = "mongo-form-migrate-raw", author = "") - public void migrateRawInputTypeToRawCommand(MongockTemplate mongockTemplate) { + public void migrateRawInputTypeToRawCommand(MongoTemplate mongoTemplate) { - Plugin mongoPlugin = mongockTemplate.findOne(query(where("packageName").is("mongo-plugin")), Plugin.class); + Plugin mongoPlugin = mongoTemplate.findOne(query(where("packageName").is("mongo-plugin")), Plugin.class); // Fetch all the actions built on top of a mongo database with input type set to raw. assert mongoPlugin != null; - List rawMongoQueryActions = mongockTemplate.find( + List rawMongoQueryActions = mongoTemplate.find( query(new Criteria().andOperator( where(fieldName(QNewAction.newAction.pluginId)).is(mongoPlugin.getId()))), NewAction.class @@ -2782,12 +2779,12 @@ public class DatabaseChangelog1 { // Now that all the actions have been updated, save all the actions for (NewAction action : rawMongoQueryActions) { - mongockTemplate.save(action); + mongoTemplate.save(action); } } @ChangeSet(order = "077", id = "add-arangodb-plugin", author = "") - public void addArangoDBPlugin(MongockTemplate mongoTemplate) { + public void addArangoDBPlugin(MongoTemplate mongoTemplate) { Plugin plugin = new Plugin(); plugin.setName("ArangoDB"); plugin.setType(PluginType.DB); @@ -2808,7 +2805,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "078", id = "set-svg-logo-to-plugins", author = "") - public void setSvgLogoToPluginIcons(MongockTemplate mongoTemplate) { + public void setSvgLogoToPluginIcons(MongoTemplate mongoTemplate) { for (Plugin plugin : mongoTemplate.findAll(Plugin.class)) { if ("postgres-plugin".equals(plugin.getPackageName())) { plugin.setIconLocation("https://s3.us-east-2.amazonaws.com/assets.appsmith.com/logo/postgresql.svg"); @@ -2843,7 +2840,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "079", id = "remove-order-field-from-application- pages", author = "") - public void removePageOrderFieldFromApplicationPages(MongockTemplate mongoTemplate) { + public void removePageOrderFieldFromApplicationPages(MongoTemplate mongoTemplate) { Query query = new Query(); query.addCriteria(Criteria.where("pages").exists(TRUE)); @@ -2856,7 +2853,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "080", id = "create-plugin-reference-for-genarate-CRUD-page", author = "") - public void createPluginReferenceForGenerateCRUDPage(MongockTemplate mongoTemplate) { + public void createPluginReferenceForGenerateCRUDPage(MongoTemplate mongoTemplate) { final String templatePageNameForSQLDatasource = "SQL"; final Set sqlPackageNames = Set.of("mysql-plugin", "mssql-plugin", "redshift-plugin", "snowflake-plugin"); @@ -2932,7 +2929,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "081", id = "encrypt-certificate", author = "") - public void encryptCertificateAndPassword(MongockTemplate mongoTemplate, EncryptionService encryptionService) { + public void encryptCertificateAndPassword(MongoTemplate mongoTemplate, EncryptionService encryptionService) { /** * - List of attributes that need to be encoded. @@ -2986,7 +2983,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "082", id = "create-plugin-reference-for-S3-GSheet-genarate-CRUD-page", author = "") - public void createPluginReferenceForS3AndGSheetGenerateCRUDPage(MongockTemplate mongoTemplate) { + public void createPluginReferenceForS3AndGSheetGenerateCRUDPage(MongoTemplate mongoTemplate) { Set validPackageNames = Set.of("amazons3-plugin", "google-sheets-plugin"); @@ -3000,7 +2997,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "083", id = "application-git-metadata", author = "") - public void addApplicationGitMetadataFieldAndIndex(MongockTemplate mongoTemplate) { + public void addApplicationGitMetadataFieldAndIndex(MongoTemplate mongoTemplate) { dropIndexIfExists(mongoTemplate, Application.class, "organization_application_compound_index"); dropIndexIfExists(mongoTemplate, Application.class, "organization_application_deleted_compound_index"); @@ -3011,7 +3008,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "084", id = "add-js-plugin", author = "") - public void addJSPlugin(MongockTemplate mongoTemplate) { + public void addJSPlugin(MongoTemplate mongoTemplate) { Plugin plugin = new Plugin(); plugin.setName("JS Functions"); plugin.setType(PluginType.JS); @@ -3031,7 +3028,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "085", id = "update-google-sheet-plugin-smartSubstitution-config", author = "") - public void updateGoogleSheetActionsSetSmartSubstitutionConfiguration(MongockTemplate mongoTemplate) { + public void updateGoogleSheetActionsSetSmartSubstitutionConfiguration(MongoTemplate mongoTemplate) { Plugin googleSheetPlugin = mongoTemplate.findOne( query(new Criteria().andOperator( @@ -3082,7 +3079,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "086", id = "uninstall-mongo-uqi-plugin", author = "") - public void uninstallMongoUqiPluginAndRemoveAllActions(MongockTemplate mongoTemplate) { + public void uninstallMongoUqiPluginAndRemoveAllActions(MongoTemplate mongoTemplate) { Plugin mongoUqiPlugin = mongoTemplate.findAndRemove( query(where("packageName").is("mongo-uqi-plugin")), @@ -3178,7 +3175,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "087", id = "migrate-mongo-to-uqi", author = "") - public void migrateMongoPluginToUqi(MongockTemplate mongoTemplate) { + public void migrateMongoPluginToUqi(MongoTemplate mongoTemplate) { // First update the UI component for the mongo plugin to UQI Plugin mongoPlugin = mongoTemplate.findOne( @@ -3224,7 +3221,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "088", id = "migrate-mongo-uqi-dynamicBindingPathList", author = "") - public void migrateMongoPluginDynamicBindingListUqi(MongockTemplate mongoTemplate) { + public void migrateMongoPluginDynamicBindingListUqi(MongoTemplate mongoTemplate) { Plugin mongoPlugin = mongoTemplate.findOne( query(where("packageName").is("mongo-plugin")), @@ -3277,8 +3274,8 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "089", id = "update-plugin-package-name-index", author = "") - public void updatePluginPackageNameIndexToPluginNamePackageNameAndVersion(MongockTemplate mongoTemplate) { -// MongoTemplate mongoTemplate = mongockTemplate.getImpl(); + public void updatePluginPackageNameIndexToPluginNamePackageNameAndVersion(MongoTemplate mongoTemplate) { +// MongoTemplate mongoTemplate = mongoTemplate.getImpl(); dropIndexIfExists(mongoTemplate, Plugin.class, "packageName"); ensureIndexes(mongoTemplate, Plugin.class, @@ -3288,7 +3285,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "090", id = "delete-orphan-actions", author = "") - public void deleteOrphanActions(MongockTemplate mongockTemplate) { + public void deleteOrphanActions(MongoTemplate mongoTemplate) { final Update deletionUpdates = new Update(); deletionUpdates.set(fieldName(QNewAction.newAction.deleted), true); deletionUpdates.set(fieldName(QNewAction.newAction.deletedAt), Instant.now()); @@ -3296,12 +3293,12 @@ public class DatabaseChangelog1 { final Query actionQuery = query(where(fieldName(QNewAction.newAction.deleted)).ne(true)); actionQuery.fields().include(fieldName(QNewAction.newAction.applicationId)); - final List actions = mongockTemplate.find(actionQuery, NewAction.class); + final List actions = mongoTemplate.find(actionQuery, NewAction.class); for (final NewAction action : actions) { final String applicationId = action.getApplicationId(); - final boolean shouldDelete = StringUtils.isEmpty(applicationId) || mongockTemplate.exists( + final boolean shouldDelete = StringUtils.isEmpty(applicationId) || mongoTemplate.exists( query( where(fieldName(QApplication.application.id)).is(applicationId) .and(fieldName(QApplication.application.deleted)).is(true) @@ -3310,7 +3307,7 @@ public class DatabaseChangelog1 { ); if (shouldDelete) { - mongockTemplate.updateFirst( + mongoTemplate.updateFirst( query(where(fieldName(QNewAction.newAction.id)).is(action.getId())), deletionUpdates, NewAction.class @@ -3320,7 +3317,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "091", id = "migrate-old-app-color-to-new-colors", author = "") - public void migrateOldAppColorsToNewColors(MongockTemplate mongoTemplate) { + public void migrateOldAppColorsToNewColors(MongoTemplate mongoTemplate) { String[] oldColors = { "#FF6786", "#FFAD5E", "#FCD43E", "#B0E968", "#5CE7EF", "#69B5FF", "#9177FF", "#FF76FE", "#61DF48", "#FF597B", "#6698FF", "#F8C356", "#6C4CF1", "#C5CD90", "#6272C8", "#4F70FD", @@ -3354,11 +3351,11 @@ public class DatabaseChangelog1 { * editor shows the toggle value as ON but behaves like the value is OFF. To fix this issue, this method adds * URL toggle as `NO` where no toggle value exists. * - * @param mongockTemplate : Mongo client + * @param mongoTemplate : Mongo client */ @ChangeSet(order = "092", id = "update-s3-permanent-url-toggle-default-value", author = "") - public void updateS3PermanentUrlToggleDefaultValue(MongockTemplate mongockTemplate) { - Plugin s3Plugin = mongockTemplate.findOne( + public void updateS3PermanentUrlToggleDefaultValue(MongoTemplate mongoTemplate) { + Plugin s3Plugin = mongoTemplate.findOne( query(where("packageName").is("amazons3-plugin")), Plugin.class ); @@ -3378,7 +3375,7 @@ public class DatabaseChangelog1 { where("unpublishedAction.actionConfiguration.pluginSpecifiedTemplates.8.value").is(null) ) )); - List s3ListActionObjectsWithNoToggleValue = mongockTemplate.find(missingToggleQuery, NewAction.class); + List s3ListActionObjectsWithNoToggleValue = mongoTemplate.find(missingToggleQuery, NewAction.class); // Replace old pluginSpecifiedTemplates with updated pluginSpecifiedTemplates. s3ListActionObjectsWithNoToggleValue.stream() @@ -3395,7 +3392,7 @@ public class DatabaseChangelog1 { * Write data back to db only if all data manipulations done above have succeeded. */ s3ListActionObjectsWithNoToggleValue.stream() - .forEach(action -> mongockTemplate.save(action)); + .forEach(action -> mongoTemplate.save(action)); } /** @@ -3446,8 +3443,8 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "093", id = "application-git-metadata-index", author = "") - public void updateGitApplicationMetadataIndex(MongockTemplate mongoTemplate) { - // MongoTemplate mongoTemplate = mongockTemplate.getImpl(); + public void updateGitApplicationMetadataIndex(MongoTemplate mongoTemplate) { + // MongoTemplate mongoTemplate = mongoTemplate.getImpl(); dropIndexIfExists(mongoTemplate, Application.class, "organization_application_compound_index"); dropIndexIfExists(mongoTemplate, Application.class, "organization_application_deleted_compound_index"); dropIndexIfExists(mongoTemplate, Application.class, "organization_application_deleted_gitRepo_gitBranch_compound_index"); @@ -3592,11 +3589,9 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "094", id = "migrate-s3-to-uqi", author = "") - public void migrateS3PluginToUqi(MongockTemplate mongockTemplate) { - - ObjectMapper objectMapper = new ObjectMapper(); + public void migrateS3PluginToUqi(MongoTemplate mongoTemplate) { // First update the UI component for the s3 plugin to UQI - Plugin s3Plugin = mongockTemplate.findOne( + Plugin s3Plugin = mongoTemplate.findOne( query(where("packageName").is("amazons3-plugin")), Plugin.class ); @@ -3605,7 +3600,7 @@ public class DatabaseChangelog1 { // Now migrate all the existing actions to the new UQI structure. - List s3Actions = mongockTemplate.find( + List s3Actions = mongoTemplate.find( query(new Criteria().andOperator( where(fieldName(QNewAction.newAction.pluginId)).is(s3Plugin.getId()))), NewAction.class @@ -3652,10 +3647,10 @@ public class DatabaseChangelog1 { // Now save the actions which have been migrated. for (NewAction s3Action : actionsToSave) { - mongockTemplate.save(s3Action); + mongoTemplate.save(s3Action); } // Now that the actions have completed the migrations, update the plugin to use the new UI form. - mongockTemplate.save(s3Plugin); + mongoTemplate.save(s3Plugin); } /** @@ -3730,16 +3725,16 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "094", id = "set-slug-to-application-and-page", author = "") - public void setSlugToApplicationAndPage(MongockTemplate mongockTemplate) { + public void setSlugToApplicationAndPage(MongoTemplate mongoTemplate) { // update applications final Query applicationQuery = query(where("deletedAt").is(null)); applicationQuery.fields() .include(fieldName(QApplication.application.name)); - List applications = mongockTemplate.find(applicationQuery, Application.class); + List applications = mongoTemplate.find(applicationQuery, Application.class); for (Application application : applications) { - mongockTemplate.updateFirst( + mongoTemplate.updateFirst( query(where(fieldName(QApplication.application.id)).is(application.getId())), new Update().set(fieldName(QApplication.application.slug), TextUtils.makeSlug(application.getName())), Application.class @@ -3756,7 +3751,7 @@ public class DatabaseChangelog1 { fieldName(QNewPage.newPage.publishedPage), fieldName(QNewPage.newPage.publishedPage.name) )); - List pages = mongockTemplate.find(pageQuery, NewPage.class); + List pages = mongoTemplate.find(pageQuery, NewPage.class); for (NewPage page : pages) { Update update = new Update(); @@ -3772,7 +3767,7 @@ public class DatabaseChangelog1 { ); update = update.set(fieldName, TextUtils.makeSlug(page.getPublishedPage().getName())); } - mongockTemplate.updateFirst( + mongoTemplate.updateFirst( query(where(fieldName(QNewPage.newPage.id)).is(page.getId())), update, NewPage.class @@ -3887,7 +3882,7 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "095", id = "update-list-widget-trigger-paths", author = "") - public void removeUnusedTriggerPathsListWidget(MongockTemplate mongockTemplate) { + public void removeUnusedTriggerPathsListWidget(MongoTemplate mongoTemplate) { // Find all the pages which haven't been deleted @@ -3901,7 +3896,7 @@ public class DatabaseChangelog1 { pageQuery.fields() .include(fieldName(QNewPage.newPage.id)); - final List pages = mongockTemplate.find( + final List pages = mongoTemplate.find( pageQuery, NewPage.class ); @@ -3909,7 +3904,7 @@ public class DatabaseChangelog1 { for (NewPage onlyIdPage : pages) { // Fetch one action at a time to avoid OOM. - NewPage page = mongockTemplate.findOne( + NewPage page = mongoTemplate.findOne( query(where(fieldName(QNewPage.newPage.id)).is(onlyIdPage.getId())), NewPage.class ); @@ -3932,7 +3927,7 @@ public class DatabaseChangelog1 { } if (dslUpdateDto.getUpdated().equals(TRUE)) { - mongockTemplate.save(page); + mongoTemplate.save(page); } } } @@ -3943,17 +3938,17 @@ public class DatabaseChangelog1 { * With this migration, the structure is expected to follow the * {@link com.appsmith.external.dtos.MultipartFormDataDTO} format * - * @param mongockTemplate + * @param mongoTemplate */ @ChangeSet(order = "096", id = "update-s3-action-configuration-for-type", author = "") - public void updateS3ActionConfigurationBodyForContentTypeSupport(MongockTemplate mongockTemplate) { - Plugin s3Plugin = mongockTemplate.findOne( + public void updateS3ActionConfigurationBodyForContentTypeSupport(MongoTemplate mongoTemplate) { + Plugin s3Plugin = mongoTemplate.findOne( query(where("packageName").is("amazons3-plugin")), Plugin.class ); // Find all S3 actions - List s3Actions = mongockTemplate.find( + List s3Actions = mongoTemplate.find( query(new Criteria().andOperator( where(fieldName(QNewAction.newAction.pluginId)).is(s3Plugin.getId()))), NewAction.class @@ -3984,7 +3979,7 @@ public class DatabaseChangelog1 { // Now save the actions which have been migrated. for (NewAction s3Action : actionsToSave) { - mongockTemplate.save(s3Action); + mongoTemplate.save(s3Action); } } @@ -3994,10 +3989,10 @@ public class DatabaseChangelog1 { * This migration will set isPublic=true to those applications which have isPublic=false but anonymousUser has * read:applications permission in policies * - * @param mongockTemplate + * @param mongoTemplate */ @ChangeSet(order = "097", id = "fix-ispublic-is-false-for-public-apps", author = "") - public void fixIsPublicIsSetFalseWhenAppIsPublic(MongockTemplate mongockTemplate) { + public void fixIsPublicIsSetFalseWhenAppIsPublic(MongoTemplate mongoTemplate) { Query query = query( where("isPublic").is(false) .and("deleted").is(false) @@ -4006,18 +4001,18 @@ public class DatabaseChangelog1 { ) ); Update update = new Update().set("isPublic", true); - mongockTemplate.updateMulti(query, update, Application.class); + mongoTemplate.updateMulti(query, update, Application.class); } @ChangeSet(order = "098", id = "update-js-action-client-side-execution", author = "") - public void updateJsActionsClientSideExecution(MongockTemplate mongockTemplate) { - Plugin jsPlugin = mongockTemplate.findOne( + public void updateJsActionsClientSideExecution(MongoTemplate mongoTemplate) { + Plugin jsPlugin = mongoTemplate.findOne( query(where("packageName").is("js-plugin")), Plugin.class ); // Find all JS actions - List jsActions = mongockTemplate.find( + List jsActions = mongoTemplate.find( query(new Criteria().andOperator( where(fieldName(QNewAction.newAction.pluginId)).is(jsPlugin.getId()))), NewAction.class @@ -4044,12 +4039,12 @@ public class DatabaseChangelog1 { // Now save the actions which have been migrated. for (NewAction jsAction : actionsToSave) { - mongockTemplate.save(jsAction); + mongoTemplate.save(jsAction); } } @ChangeSet(order = "099", id = "add-smtp-plugin", author = "") - public void addSmtpPluginPlugin(MongockTemplate mongoTemplate) { + public void addSmtpPluginPlugin(MongoTemplate mongoTemplate) { Plugin plugin = new Plugin(); plugin.setName("SMTP"); plugin.setType(PluginType.DB); @@ -4069,8 +4064,8 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "100", id = "update-mockdb-endpoint", author = "") - public void updateMockdbEndpoint(MongockTemplate mongockTemplate) { - mongockTemplate.updateMulti( + public void updateMockdbEndpoint(MongoTemplate mongoTemplate) { + mongoTemplate.updateMulti( query(where("datasourceConfiguration.endpoints.host").is("fake-api.cvuydmurdlas.us-east-1.rds.amazonaws.com")), update("datasourceConfiguration.endpoints.$.host", "mockdb.internal.appsmith.com"), Datasource.class @@ -4078,8 +4073,8 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "101", id = "add-google-sheets-plugin-name", author = "") - public void addPluginNameForGoogleSheets(MongockTemplate mongockTemplate) { - Plugin googleSheetsPlugin = mongockTemplate.findOne( + public void addPluginNameForGoogleSheets(MongoTemplate mongoTemplate) { + Plugin googleSheetsPlugin = mongoTemplate.findOne( query(where("packageName").is("google-sheets-plugin")), Plugin.class ); @@ -4087,11 +4082,11 @@ public class DatabaseChangelog1 { assert googleSheetsPlugin != null; googleSheetsPlugin.setPluginName("google-sheets-plugin"); - mongockTemplate.save(googleSheetsPlugin); + mongoTemplate.save(googleSheetsPlugin); } @ChangeSet(order = "102", id = "insert-default-resources", author = "") - public void insertDefaultResources(MongockTemplate mongockTemplate) { + public void insertDefaultResources(MongoTemplate mongoTemplate) { // Update datasources final Query datasourceQuery = query(where(fieldName(QDatasource.datasource.deleted)).ne(true)); @@ -4100,12 +4095,12 @@ public class DatabaseChangelog1 { .include(fieldName(QDatasource.datasource.id)) .include(fieldName(QDatasource.datasource.organizationId)); - List datasources = mongockTemplate.find(datasourceQuery, Datasource.class); + List datasources = mongoTemplate.find(datasourceQuery, Datasource.class); for (Datasource datasource : datasources) { final Update update = new Update(); final String gitSyncId = datasource.getOrganizationId() + "_" + new ObjectId(); update.set(fieldName(QDatasource.datasource.gitSyncId), gitSyncId); - mongockTemplate.updateFirst( + mongoTemplate.updateFirst( query(where(fieldName(QDatasource.datasource.id)).is(datasource.getId())), update, Datasource.class @@ -4115,7 +4110,7 @@ public class DatabaseChangelog1 { // Update default page Ids in pages and publishedPages for all existing applications final Query applicationQuery = query(where(fieldName(QApplication.application.deleted)).ne(true)) .addCriteria(where(fieldName(QApplication.application.pages)).exists(true)); - List applications = mongockTemplate.find(applicationQuery, Application.class); + List applications = mongoTemplate.find(applicationQuery, Application.class); for (Application application : applications) { application.getPages().forEach(page -> { @@ -4127,7 +4122,7 @@ public class DatabaseChangelog1 { page.setDefaultPageId(page.getId()); }); } - mongockTemplate.save(application); + mongoTemplate.save(application); } // Update pages for defaultIds (applicationId, pageId) along-with the defaultActionIds for onPageLoadActions @@ -4135,12 +4130,12 @@ public class DatabaseChangelog1 { pageQuery.fields() .include(fieldName(QNewPage.newPage.id)); - List pages = mongockTemplate.find(pageQuery, NewPage.class); + List pages = mongoTemplate.find(pageQuery, NewPage.class); for (NewPage onlyIdPage : pages) { // Fetch one page at a time to avoid OOM. - NewPage page = mongockTemplate.findOne( + NewPage page = mongoTemplate.findOne( query(where(fieldName(QNewPage.newPage.id)).is(onlyIdPage.getId())), NewPage.class ); @@ -4180,7 +4175,7 @@ public class DatabaseChangelog1 { } if (!StringUtils.isEmpty(applicationId)) { - mongockTemplate.updateFirst( + mongoTemplate.updateFirst( query(where(fieldName(QNewPage.newPage.id)).is(page.getId())), defaultResourceUpdates, NewPage.class @@ -4195,11 +4190,11 @@ public class DatabaseChangelog1 { actionQuery.fields() .include(fieldName(QNewAction.newAction.id)); - List actions = mongockTemplate.find(actionQuery, NewAction.class); + List actions = mongoTemplate.find(actionQuery, NewAction.class); for (NewAction actionIdOnly : actions) { // Fetch one action at a time to avoid OOM. - final NewAction action = mongockTemplate.findOne( + final NewAction action = mongoTemplate.findOne( query(where(fieldName(QNewAction.newAction.id)).is(actionIdOnly.getId())), NewAction.class ); @@ -4242,7 +4237,7 @@ public class DatabaseChangelog1 { defaultResourceUpdates.set(fieldName(QNewAction.newAction.gitSyncId), gitSyncId); - mongockTemplate.updateFirst( + mongoTemplate.updateFirst( query(where(fieldName(QNewAction.newAction.id)).is(action.getId())), defaultResourceUpdates, NewAction.class @@ -4263,7 +4258,7 @@ public class DatabaseChangelog1 { .include(fieldName(QActionCollection.actionCollection.publishedCollection) + "." + fieldName(QActionCollection.actionCollection.publishedCollection.archivedActionIds)); - List collections = mongockTemplate.find(actionCollectionQuery, ActionCollection.class); + List collections = mongoTemplate.find(actionCollectionQuery, ActionCollection.class); for (ActionCollection collection : collections) { @@ -4356,7 +4351,7 @@ public class DatabaseChangelog1 { defaultResourceUpdates.set(fieldName(QActionCollection.actionCollection.gitSyncId), gitSyncId); if (!StringUtils.isEmpty(applicationId)) { - mongockTemplate.updateFirst( + mongoTemplate.updateFirst( query(where(fieldName(QActionCollection.actionCollection.id)).is(collection.getId())), defaultResourceUpdates, ActionCollection.class @@ -4370,7 +4365,7 @@ public class DatabaseChangelog1 { .include(fieldName(QCommentThread.commentThread.applicationId)) .include(fieldName((QCommentThread.commentThread.pageId))); - List threads = mongockTemplate.find(threadQuery, CommentThread.class); + List threads = mongoTemplate.find(threadQuery, CommentThread.class); for (CommentThread thread : threads) { DefaultResources defaults = new DefaultResources(); @@ -4380,7 +4375,7 @@ public class DatabaseChangelog1 { final Update defaultResourceUpdates = new Update(); defaultResourceUpdates.set(fieldName(QCommentThread.commentThread.defaultResources), defaults); - mongockTemplate.updateFirst( + mongoTemplate.updateFirst( query(where(fieldName(QCommentThread.commentThread.id)).is(thread.getId())), defaultResourceUpdates, CommentThread.class @@ -4393,7 +4388,7 @@ public class DatabaseChangelog1 { .include(fieldName(QComment.comment.applicationId)) .include(fieldName((QComment.comment.pageId))); - List comments = mongockTemplate.find(commentQuery, Comment.class); + List comments = mongoTemplate.find(commentQuery, Comment.class); for (Comment comment : comments) { DefaultResources defaults = new DefaultResources(); @@ -4403,7 +4398,7 @@ public class DatabaseChangelog1 { final Update defaultResourceUpdates = new Update(); defaultResourceUpdates.set(fieldName(QComment.comment.defaultResources), defaults); - mongockTemplate.updateFirst( + mongoTemplate.updateFirst( query(where(fieldName(QComment.comment.id)).is(comment.getId())), defaultResourceUpdates, Comment.class @@ -4413,7 +4408,7 @@ public class DatabaseChangelog1 { // Update notification final Query notificationQuery = query(where(fieldName(QNotification.notification.deleted)).ne(true)); - List notifications = mongockTemplate.find(notificationQuery, Notification.class); + List notifications = mongoTemplate.find(notificationQuery, Notification.class); notifications.forEach(notification -> { final Update defaultResourceUpdates = new Update(); @@ -4438,7 +4433,7 @@ public class DatabaseChangelog1 { ); } - mongockTemplate.updateFirst( + mongoTemplate.updateFirst( query(where(fieldName(QNotification.notification.id)).is(notification.getId())), defaultResourceUpdates, Notification.class @@ -4488,10 +4483,10 @@ public class DatabaseChangelog1 { ); @ChangeSet(order = "103", id = "migrate-firestore-to-uqi", author = "") - public void migrateFirestorePluginToUqi(MongockTemplate mongockTemplate) { + public void migrateFirestorePluginToUqi(MongoTemplate mongoTemplate) { // Update Firestore plugin to indicate use of UQI schema - Plugin firestorePlugin = mongockTemplate.findOne( + Plugin firestorePlugin = mongoTemplate.findOne( query(where("packageName").is("firestore-plugin")), Plugin.class ); @@ -4505,22 +4500,22 @@ public class DatabaseChangelog1 { firestoreActionQuery.fields() .include(fieldName(QNewAction.newAction.id)); - List firestoreActions = mongockTemplate.find( + List firestoreActions = mongoTemplate.find( firestoreActionQuery, NewAction.class ); - migrateFirestoreToUQI(mongockTemplate, firestoreActions); + migrateFirestoreToUQI(mongoTemplate, firestoreActions); // Update plugin data. - mongockTemplate.save(firestorePlugin); + mongoTemplate.save(firestorePlugin); } - private void migrateFirestoreToUQI(MongockTemplate mongockTemplate, List firestoreActions) { + private void migrateFirestoreToUQI(MongoTemplate mongoTemplate, List firestoreActions) { for (NewAction firestoreActionId : firestoreActions) { // Fetch one page at a time to avoid OOM. - final NewAction firestoreAction = mongockTemplate.findOne( + final NewAction firestoreAction = mongoTemplate.findOne( query(where(fieldName(QNewAction.newAction.id)).is(firestoreActionId.getId())), NewAction.class ); @@ -4582,7 +4577,7 @@ public class DatabaseChangelog1 { objectMapper, firestoreAction, firestoreMigrationMap); unpublishedAction.setDynamicBindingPathList(newDynamicBindingPathList); - mongockTemplate.save(firestoreAction); + mongoTemplate.save(firestoreAction); } } @@ -4591,10 +4586,10 @@ public class DatabaseChangelog1 { * `deleted` set to true instead of the other way around. */ @ChangeSet(order = "104", id = "migrate-firestore-to-uqi-2", author = "") - public void migrateFirestorePluginToUqi2(MongockTemplate mongockTemplate) { + public void migrateFirestorePluginToUqi2(MongoTemplate mongoTemplate) { // Update Firestore plugin to indicate use of UQI schema - Plugin firestorePlugin = mongockTemplate.findOne( + Plugin firestorePlugin = mongoTemplate.findOne( query(where("packageName").is("firestore-plugin")), Plugin.class ); @@ -4606,17 +4601,17 @@ public class DatabaseChangelog1 { firestoreActionQuery.fields() .include(fieldName(QNewAction.newAction.id)); - List firestoreActions = mongockTemplate.find( + List firestoreActions = mongoTemplate.find( firestoreActionQuery, NewAction.class ); - migrateFirestoreToUQI(mongockTemplate, firestoreActions); + migrateFirestoreToUQI(mongoTemplate, firestoreActions); } @ChangeSet(order = "105", id = "migrate-firestore-pagination-data", author = "") - public void migrateFirestorePaginationData(MongockTemplate mongockTemplate) { - Plugin firestorePlugin = mongockTemplate.findOne( + public void migrateFirestorePaginationData(MongoTemplate mongoTemplate) { + Plugin firestorePlugin = mongoTemplate.findOne( query(where("packageName").is("firestore-plugin")), Plugin.class ); @@ -4630,7 +4625,7 @@ public class DatabaseChangelog1 { .include(fieldName(QNewAction.newAction.id)); // Get list of Firestore action ids - List firestoreActionIds = mongockTemplate.find( + List firestoreActionIds = mongoTemplate.find( queryToGetActionIds, NewAction.class ); @@ -4639,7 +4634,7 @@ public class DatabaseChangelog1 { for (NewAction firestoreActionId : firestoreActionIds) { // Fetch one action at a time to avoid OOM. - final NewAction firestoreAction = mongockTemplate.findOne( + final NewAction firestoreAction = mongoTemplate.findOne( query(where(fieldName(QNewAction.newAction.id)).is(firestoreActionId.getId())), NewAction.class ); @@ -4681,18 +4676,18 @@ public class DatabaseChangelog1 { publishedAction.getActionConfiguration().setPrev(endBefore); } - mongockTemplate.save(firestoreAction); + mongoTemplate.save(firestoreAction); } } @ChangeSet(order = "106", id = "update-mongodb-mockdb-endpoint", author = "") - public void updateMongoMockdbEndpoint(MongockTemplate mongockTemplate) { - mongockTemplate.updateMulti( + public void updateMongoMockdbEndpoint(MongoTemplate mongoTemplate) { + mongoTemplate.updateMulti( query(where("datasourceConfiguration.endpoints.host").is("mockdb.swrsq.mongodb.net")), update("datasourceConfiguration.endpoints.$.host", "mockdb.kce5o.mongodb.net"), Datasource.class ); - mongockTemplate.updateMulti( + mongoTemplate.updateMulti( query(where("datasourceConfiguration.properties.value").is("mongodb+srv://mockdb_super:****@mockdb.swrsq.mongodb.net/movies")), update("datasourceConfiguration.properties.$.value", "mongodb+srv://mockdb_super:****@mockdb.kce5o.mongodb.net/movies"), Datasource.class @@ -4704,9 +4699,9 @@ public class DatabaseChangelog1 { * unexpected / bad older data. */ @ChangeSet(order = "107", id = "migrate-firestore-to-uqi-3", author = "") - public void migrateFirestorePluginToUqi3(MongockTemplate mongockTemplate) { + public void migrateFirestorePluginToUqi3(MongoTemplate mongoTemplate) { // Update Firestore plugin to indicate use of UQI schema - Plugin firestorePlugin = mongockTemplate.findOne( + Plugin firestorePlugin = mongoTemplate.findOne( query(where("packageName").is("firestore-plugin")), Plugin.class ); @@ -4719,15 +4714,15 @@ public class DatabaseChangelog1 { firestoreActionQuery.fields() .include(fieldName(QNewAction.newAction.id)); - List firestoreActions = mongockTemplate.find( + List firestoreActions = mongoTemplate.find( firestoreActionQuery, NewAction.class ); - migrateFirestoreToUQI(mongockTemplate, firestoreActions); + migrateFirestoreToUQI(mongoTemplate, firestoreActions); // Update plugin data. - mongockTemplate.save(firestorePlugin); + mongoTemplate.save(firestorePlugin); } /** @@ -4735,12 +4730,12 @@ public class DatabaseChangelog1 { * It iterates over each action id one by one to avoid out of memory error. * * @param mongoActions - * @param mongockTemplate + * @param mongoTemplate */ - private void updateLimitFieldForEachAction(List mongoActions, MongockTemplate mongockTemplate) { + private void updateLimitFieldForEachAction(List mongoActions, MongoTemplate mongoTemplate) { mongoActions.stream() .map(NewAction::getId) // iterate over one action id at a time - .map(actionId -> fetchActionUsingId(actionId, mongockTemplate)) // fetch action using id + .map(actionId -> fetchActionUsingId(actionId, mongoTemplate)) // fetch action using id .filter(this::hasUnpublishedActionConfiguration) .forEachOrdered(mongoAction -> { /* set key for unpublished action */ @@ -4755,7 +4750,7 @@ public class DatabaseChangelog1 { setValueSafelyInFormData(publishedFormData, AGGREGATE_LIMIT, DEFAULT_BATCH_SIZE); } - mongockTemplate.save(mongoAction); + mongoTemplate.save(mongoAction); }); } @@ -4775,8 +4770,8 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "108", id = "create-system-themes", author = "") - public void createSystemThemes(MongockTemplate mongockTemplate) throws IOException { - createSystemThemes2(mongockTemplate); + public void createSystemThemes(MongoTemplate mongoTemplate) throws IOException { + createSystemThemes2(mongoTemplate); } /** @@ -4786,11 +4781,11 @@ public class DatabaseChangelog1 { * database. However, for any new action, this field's initial value is 10. * Ref: https://docs.mongodb.com/manual/tutorial/iterate-a-cursor/ * - * @param mongockTemplate + * @param mongoTemplate */ @ChangeSet(order = "109", id = "add-limit-field-data-to-mongo-aggregate-cmd", author = "") - public void addLimitFieldDataToMongoAggregateCommand(MongockTemplate mongockTemplate) { - Plugin mongoPlugin = mongockTemplate.findOne(query(where("packageName").is("mongo-plugin")), Plugin.class); + public void addLimitFieldDataToMongoAggregateCommand(MongoTemplate mongoTemplate) { + Plugin mongoPlugin = mongoTemplate.findOne(query(where("packageName").is("mongo-plugin")), Plugin.class); /* Query to get all Mongo actions which are not deleted */ Query queryToGetActions = getQueryToFetchAllPluginActionsWhichAreNotDeleted(mongoPlugin); @@ -4799,10 +4794,10 @@ public class DatabaseChangelog1 { queryToGetActions.fields().include(fieldName(QNewAction.newAction.id)); /* Fetch Mongo actions using the previous query */ - List mongoActions = mongockTemplate.find(queryToGetActions, NewAction.class); + List mongoActions = mongoTemplate.find(queryToGetActions, NewAction.class); /* insert key formData.aggregate.limit */ - updateLimitFieldForEachAction(mongoActions, mongockTemplate); + updateLimitFieldForEachAction(mongoActions, mongoTemplate); } /** @@ -4824,12 +4819,12 @@ public class DatabaseChangelog1 { * Fetch an action using id. * * @param actionId - * @param mongockTemplate + * @param mongoTemplate * @return action */ - public static NewAction fetchActionUsingId(String actionId, MongockTemplate mongockTemplate) { + public static NewAction fetchActionUsingId(String actionId, MongoTemplate mongoTemplate) { final NewAction action = - mongockTemplate.findOne(query(where(fieldName(QNewAction.newAction.id)).is(actionId)), NewAction.class); + mongoTemplate.findOne(query(where(fieldName(QNewAction.newAction.id)).is(actionId)), NewAction.class); return action; } @@ -4850,65 +4845,65 @@ public class DatabaseChangelog1 { * branchName param into consideration for optimising the find query for git connected applications */ @ChangeSet(order = "110", id = "update-index-for-git", author = "") - public void updateGitIndexes(MongockTemplate mongockTemplate) { + public void updateGitIndexes(MongoTemplate mongoTemplate) { // We can't set unique indexes for following as these requires the _id of the resource to be filled in for // defaultResourceId if the app is not connected to git. This results in handling the _id creation for resources // on our end instead of asking mongo driver to perform this operation - ensureIndexes(mongockTemplate, NewAction.class, + ensureIndexes(mongoTemplate, NewAction.class, makeIndex("defaultResources.actionId", "defaultResources.branchName", "deleted") .named("defaultActionId_branchName_deleted_compound_index") ); - ensureIndexes(mongockTemplate, ActionCollection.class, + ensureIndexes(mongoTemplate, ActionCollection.class, makeIndex("defaultResources.collectionId", "defaultResources.branchName", "deleted") .named("defaultCollectionId_branchName_deleted_compound_index") ); - ensureIndexes(mongockTemplate, NewPage.class, + ensureIndexes(mongoTemplate, NewPage.class, makeIndex("defaultResources.pageId", "defaultResources.branchName", "deleted") .named("defaultPageId_branchName_deleted_compound_index") ); - ensureIndexes(mongockTemplate, Application.class, + ensureIndexes(mongoTemplate, Application.class, makeIndex("gitApplicationMetadata.defaultApplicationId", "gitApplicationMetadata.branchName", "deleted") .named("defaultApplicationId_branchName_deleted_compound_index") ); } @ChangeSet(order = "111", id = "update-mockdb-endpoint-2", author = "") - public void updateMockdbEndpoint2(MongockTemplate mongockTemplate) { + public void updateMockdbEndpoint2(MongoTemplate mongoTemplate) { // Doing this again as another migration since it appears some new datasource were created with the old // endpoint around 14-Dec-2021 to 16-Dec-2021. - updateMockdbEndpoint(mongockTemplate); + updateMockdbEndpoint(mongoTemplate); } @ChangeSet(order = "112", id = "migrate-from-RSA-SHA1-to-ECDSA-SHA2-protocol-for-key-generation", author = "") - public void migrateFromRSASha1ToECDSASha2Protocol(MongockTemplate mongockTemplate) { + public void migrateFromRSASha1ToECDSASha2Protocol(MongoTemplate mongoTemplate) { Query query = new Query(); query.addCriteria(Criteria.where("gitApplicationMetadata.gitAuth").exists(TRUE)); query.addCriteria(Criteria.where("deleted").is(FALSE)); - for (Application application : mongockTemplate.find(query, Application.class)) { + for (Application application : mongoTemplate.find(query, Application.class)) { if (!Optional.ofNullable(application.getGitApplicationMetadata()).isEmpty()) { GitAuth gitAuth = GitDeployKeyGenerator.generateSSHKey(null); GitApplicationMetadata gitApplicationMetadata = application.getGitApplicationMetadata(); gitApplicationMetadata.setGitAuth(gitAuth); application.setGitApplicationMetadata(gitApplicationMetadata); - mongockTemplate.save(application); + mongoTemplate.save(application); } } } @ChangeSet(order = "113", id = "use-assets-cdn-for-plugin-icons", author = "") - public void useAssetsCDNForPluginIcons(MongockTemplate mongockTemplate) { + public void useAssetsCDNForPluginIcons(MongoTemplate mongoTemplate) { final Query query = query(new Criteria()); query.fields().include(fieldName(QPlugin.plugin.iconLocation)); - List plugins = mongockTemplate.find(query, Plugin.class); + List plugins = mongoTemplate.find(query, Plugin.class); for (final Plugin plugin : plugins) { if (plugin.getIconLocation() != null && plugin.getIconLocation().startsWith("https://s3.us-east-2.amazonaws.com/assets.appsmith.com")) { final String cdnUrl = plugin.getIconLocation().replace("s3.us-east-2.amazonaws.com/", ""); - mongockTemplate.updateFirst( + mongoTemplate.updateFirst( query(where(fieldName(QPlugin.plugin.id)).is(plugin.getId())), update(fieldName(QPlugin.plugin.iconLocation), cdnUrl), Plugin.class @@ -4921,30 +4916,30 @@ public class DatabaseChangelog1 { * This migration introduces indexes on newAction, actionCollection and userData to improve the query performance */ @ChangeSet(order = "114", id = "update-index-for-newAction-actionCollection-userData", author = "") - public void updateNewActionActionCollectionAndUserDataIndexes(MongockTemplate mongockTemplate) { + public void updateNewActionActionCollectionAndUserDataIndexes(MongoTemplate mongoTemplate) { - ensureIndexes(mongockTemplate, ActionCollection.class, + ensureIndexes(mongoTemplate, ActionCollection.class, makeIndex(FieldName.APPLICATION_ID) .named("applicationId") ); - ensureIndexes(mongockTemplate, ActionCollection.class, + ensureIndexes(mongoTemplate, ActionCollection.class, makeIndex(fieldName(QActionCollection.actionCollection.unpublishedCollection) + "." + FieldName.PAGE_ID) .named("unpublishedCollection_pageId") ); String defaultResources = fieldName(QBaseDomain.baseDomain.defaultResources); - ensureIndexes(mongockTemplate, ActionCollection.class, + ensureIndexes(mongoTemplate, ActionCollection.class, makeIndex(defaultResources + "." + FieldName.APPLICATION_ID, FieldName.GIT_SYNC_ID) .named("defaultApplicationId_gitSyncId_compound_index") ); - ensureIndexes(mongockTemplate, NewAction.class, + ensureIndexes(mongoTemplate, NewAction.class, makeIndex(defaultResources + "." + FieldName.APPLICATION_ID, FieldName.GIT_SYNC_ID) .named("defaultApplicationId_gitSyncId_compound_index") ); - ensureIndexes(mongockTemplate, UserData.class, + ensureIndexes(mongoTemplate, UserData.class, makeIndex(fieldName(QUserData.userData.userId)) .unique() .named("userId") @@ -4952,11 +4947,11 @@ public class DatabaseChangelog1 { } @ChangeSet(order = "115", id = "mark-mssql-crud-unavailable", author = "") - public void markMSSQLCrudUnavailable(MongockTemplate mongockTemplate) { - Plugin plugin = mongockTemplate.findOne(query(where("packageName").is("mssql-plugin")), Plugin.class); + public void markMSSQLCrudUnavailable(MongoTemplate mongoTemplate) { + Plugin plugin = mongoTemplate.findOne(query(where("packageName").is("mssql-plugin")), Plugin.class); assert plugin != null; plugin.setGenerateCRUDPageComponent(null); - mongockTemplate.save(plugin); + mongoTemplate.save(plugin); } /** @@ -4964,28 +4959,28 @@ public class DatabaseChangelog1 { * getResourceByPageId which excludes the deleted entries */ @ChangeSet(order = "116", id = "update-index-for-newAction-actionCollection", author = "") - public void updateNewActionActionCollectionIndexes(MongockTemplate mongockTemplate) { + public void updateNewActionActionCollectionIndexes(MongoTemplate mongoTemplate) { - dropIndexIfExists(mongockTemplate, NewAction.class, "unpublishedAction_pageId"); + dropIndexIfExists(mongoTemplate, NewAction.class, "unpublishedAction_pageId"); - ensureIndexes(mongockTemplate, NewAction.class, + ensureIndexes(mongoTemplate, NewAction.class, makeIndex(fieldName(QNewAction.newAction.unpublishedAction) + "." + FieldName.PAGE_ID, FieldName.DELETED) .named("unpublishedActionPageId_deleted_compound_index") ); - ensureIndexes(mongockTemplate, NewAction.class, + ensureIndexes(mongoTemplate, NewAction.class, makeIndex(fieldName(QNewAction.newAction.publishedAction) + "." + FieldName.PAGE_ID, FieldName.DELETED) .named("publishedActionPageId_deleted_compound_index") ); - dropIndexIfExists(mongockTemplate, ActionCollection.class, "unpublishedCollection_pageId"); + dropIndexIfExists(mongoTemplate, ActionCollection.class, "unpublishedCollection_pageId"); - ensureIndexes(mongockTemplate, ActionCollection.class, + ensureIndexes(mongoTemplate, ActionCollection.class, makeIndex(fieldName(QActionCollection.actionCollection.unpublishedCollection) + "." + FieldName.PAGE_ID, FieldName.DELETED) .named("unpublishedCollectionPageId_deleted_compound_index") ); - ensureIndexes(mongockTemplate, ActionCollection.class, + ensureIndexes(mongoTemplate, ActionCollection.class, makeIndex(fieldName(QActionCollection.actionCollection.publishedCollection) + "." + FieldName.PAGE_ID, FieldName.DELETED) .named("publishedCollectionPageId_deleted_compound_index") ); @@ -4995,11 +4990,11 @@ public class DatabaseChangelog1 { * Adding this migration again because we've added permission to themes. * Also there are couple of changes in the system theme properties. * - * @param mongockTemplate + * @param mongoTemplate * @throws IOException */ @ChangeSet(order = "117", id = "create-system-themes-v2", author = "", runAlways = false) - public void createSystemThemes2(MongockTemplate mongockTemplate) throws IOException { + public void createSystemThemes2(MongoTemplate mongoTemplate) throws IOException { // Commenting this out to ensure that system themes get added only once the baseline object creations are complete // This runs as part of `createSystemThemes3` @@ -5015,9 +5010,9 @@ public class DatabaseChangelog1 { // .named("application_id_index") // .background(); // -// dropIndexIfExists(mongockTemplate, Theme.class, "system_theme_index"); -// dropIndexIfExists(mongockTemplate, Theme.class, "application_id_index"); -// ensureIndexes(mongockTemplate, Theme.class, systemThemeIndex, applicationIdIndex); +// dropIndexIfExists(mongoTemplate, Theme.class, "system_theme_index"); +// dropIndexIfExists(mongoTemplate, Theme.class, "application_id_index"); +// ensureIndexes(mongoTemplate, Theme.class, systemThemeIndex, applicationIdIndex); // // final String themesJson = StreamUtils.copyToString( // new DefaultResourceLoader().getResource("system-themes.json").getInputStream(), @@ -5038,9 +5033,9 @@ public class DatabaseChangelog1 { // Query query = new Query(Criteria.where(fieldName(QTheme.theme.name)).is(theme.getName()) // .and(fieldName(QTheme.theme.isSystemTheme)).is(true)); // -// Theme savedTheme = mongockTemplate.findOne(query, Theme.class); +// Theme savedTheme = mongoTemplate.findOne(query, Theme.class); // if(savedTheme == null) { // this theme does not exist, create it -// savedTheme = mongockTemplate.save(theme); +// savedTheme = mongoTemplate.save(theme); // } else { // theme already found, update // themeExists = true; // savedTheme.setDisplayName(theme.getDisplayName()); @@ -5051,7 +5046,7 @@ public class DatabaseChangelog1 { // if(savedTheme.getCreatedAt() == null) { // savedTheme.setCreatedAt(Instant.now()); // } -// mongockTemplate.save(savedTheme); +// mongoTemplate.save(savedTheme); // } // // if(theme.getName().equalsIgnoreCase(Theme.LEGACY_THEME_NAME)) { @@ -5063,15 +5058,15 @@ public class DatabaseChangelog1 { // // migrate all applications and set legacy theme to them in both mode // Update update = new Update().set(fieldName(QApplication.application.publishedModeThemeId), legacyTheme.getId()) // .set(fieldName(QApplication.application.editModeThemeId), legacyTheme.getId()); -// mongockTemplate.updateMulti( +// mongoTemplate.updateMulti( // new Query(where(fieldName(QApplication.application.deleted)).is(false)), update, Application.class // ); // } } @ChangeSet(order = "118", id = "set-firestore-smart-substitution-to-false-for-old-cmds", author = "") - public void setFirestoreSmartSubstitutionToFalseForOldCommands(MongockTemplate mongockTemplate) { - Plugin firestorePlugin = mongockTemplate.findOne(query(where("packageName").is("firestore-plugin")), + public void setFirestoreSmartSubstitutionToFalseForOldCommands(MongoTemplate mongoTemplate) { + Plugin firestorePlugin = mongoTemplate.findOne(query(where("packageName").is("firestore-plugin")), Plugin.class); /* Query to get all Mongo actions which are not deleted */ @@ -5081,17 +5076,17 @@ public class DatabaseChangelog1 { queryToGetActions.fields().include(fieldName(QNewAction.newAction.id)); /* Fetch Firestore actions using the previous query */ - List firestoreActions = mongockTemplate.find(queryToGetActions, NewAction.class); + List firestoreActions = mongoTemplate.find(queryToGetActions, NewAction.class); /* set key formData.smartSubstitution */ - setSmartSubstitutionFieldForEachAction(firestoreActions, mongockTemplate); + setSmartSubstitutionFieldForEachAction(firestoreActions, mongoTemplate); } private void setSmartSubstitutionFieldForEachAction(List firestoreActions, - MongockTemplate mongockTemplate) { + MongoTemplate mongoTemplate) { firestoreActions.stream() .map(NewAction::getId) /* iterate over one action id at a time */ - .map(actionId -> fetchActionUsingId(actionId, mongockTemplate)) /* fetch action using id */ + .map(actionId -> fetchActionUsingId(actionId, mongoTemplate)) /* fetch action using id */ .filter(this::hasUnpublishedActionConfiguration) .forEachOrdered(firestoreAction -> { /* set key for unpublished action */ @@ -5106,7 +5101,7 @@ public class DatabaseChangelog1 { setValueSafelyInFormData(publishedFormData, SMART_SUBSTITUTION, FALSE.toString()); } - mongockTemplate.save(firestoreAction); + mongoTemplate.save(firestoreAction); }); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog2.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog2.java index 0c3adf966d..8372099ea2 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog2.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/DatabaseChangelog2.java @@ -1,6 +1,6 @@ package com.appsmith.server.migrations; -import com.appsmith.external.helpers.Stopwatch; +import com.appsmith.external.converters.ISOStringToInstantConverter; import com.appsmith.external.models.ActionDTO; import com.appsmith.external.models.BaseDomain; import com.appsmith.external.models.Datasource; @@ -9,12 +9,9 @@ import com.appsmith.external.models.Policy; import com.appsmith.external.models.Property; import com.appsmith.external.models.QBaseDomain; import com.appsmith.external.models.QDatasource; -import com.appsmith.external.services.EncryptionService; import com.appsmith.server.acl.AclPermission; import com.appsmith.server.acl.AppsmithRole; import com.appsmith.server.acl.PolicyGenerator; -import com.appsmith.server.configurations.EncryptionConfig; -import com.appsmith.server.constants.Appsmith; import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.Action; import com.appsmith.server.domains.ActionCollection; @@ -65,25 +62,18 @@ import com.appsmith.server.services.WorkspaceService; import com.fasterxml.jackson.databind.ObjectMapper; import com.github.cloudyrock.mongock.ChangeLog; import com.github.cloudyrock.mongock.ChangeSet; -import com.github.cloudyrock.mongock.driver.mongodb.springdata.v3.decorator.impl.MongockTemplate; import com.google.gson.Gson; -import com.mongodb.BasicDBObject; -import com.mongodb.client.MongoCollection; -import com.mongodb.client.MongoCursor; -import com.mongodb.client.model.Filters; +import com.google.gson.GsonBuilder; import com.querydsl.core.types.Path; import io.changock.migration.api.annotations.NonLockGuarded; +import io.mongock.api.annotations.ChangeUnit; import lombok.extern.slf4j.Slf4j; import net.minidev.json.JSONObject; -import org.apache.commons.codec.binary.Hex; -import org.apache.commons.lang.ArrayUtils; -import org.bson.Document; -import org.bson.conversions.Bson; import org.bson.types.ObjectId; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.dao.DuplicateKeyException; import org.springframework.data.domain.Sort; -import org.springframework.data.mongodb.core.CollectionCallback; +import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.aggregation.AggregationUpdate; import org.springframework.data.mongodb.core.aggregation.Fields; import org.springframework.data.mongodb.core.index.Index; @@ -92,8 +82,6 @@ import org.springframework.data.mongodb.core.query.Query; import org.springframework.data.mongodb.core.query.Update; import org.springframework.data.redis.core.ReactiveRedisOperations; import org.springframework.data.redis.core.script.RedisScript; -import org.springframework.security.crypto.encrypt.Encryptors; -import org.springframework.security.crypto.encrypt.TextEncryptor; import org.springframework.util.CollectionUtils; import org.springframework.util.ObjectUtils; import org.springframework.util.StreamUtils; @@ -154,25 +142,25 @@ public class DatabaseChangelog2 { private static final Pattern sheetRangePattern = Pattern.compile("https://docs.google.com/spreadsheets/d/([^/]+)/?[^\"]*"); @ChangeSet(order = "001", id = "fix-plugin-title-casing", author = "") - public void fixPluginTitleCasing(MongockTemplate mongockTemplate) { - mongockTemplate.updateFirst( + public void fixPluginTitleCasing(MongoTemplate mongoTemplate) { + mongoTemplate.updateFirst( query(where(fieldName(QPlugin.plugin.packageName)).is("mysql-plugin")), update(fieldName(QPlugin.plugin.name), "MySQL"), Plugin.class); - mongockTemplate.updateFirst( + mongoTemplate.updateFirst( query(where(fieldName(QPlugin.plugin.packageName)).is("mssql-plugin")), update(fieldName(QPlugin.plugin.name), "Microsoft SQL Server"), Plugin.class); - mongockTemplate.updateFirst( + mongoTemplate.updateFirst( query(where(fieldName(QPlugin.plugin.packageName)).is("elasticsearch-plugin")), update(fieldName(QPlugin.plugin.name), "Elasticsearch"), Plugin.class); } @ChangeSet(order = "002", id = "deprecate-archivedAt-in-action", author = "") - public void deprecateArchivedAtForNewAction(MongockTemplate mongockTemplate) { + public void deprecateArchivedAtForNewAction(MongoTemplate mongoTemplate) { // Update actions final Query actionQuery = query(where(fieldName(QNewAction.newAction.applicationId)).exists(true)) .addCriteria(where(fieldName(QNewAction.newAction.unpublishedAction) + "." @@ -183,7 +171,7 @@ public class DatabaseChangelog2 { .include(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.archivedAt)); - List actions = mongockTemplate.find(actionQuery, NewAction.class); + List actions = mongoTemplate.find(actionQuery, NewAction.class); for (NewAction action : actions) { @@ -199,7 +187,7 @@ public class DatabaseChangelog2 { update.unset(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.archivedAt)); } - mongockTemplate.updateFirst( + mongoTemplate.updateFirst( query(where(fieldName(QNewAction.newAction.id)).is(action.getId())), update, NewAction.class); @@ -214,13 +202,13 @@ public class DatabaseChangelog2 { * Example: formData.limit will transform to formData.limit.data and * formData.limit.formData * - * @param mongockTemplate + * @param mongoTemplate */ @ChangeSet(order = "003", id = "update-form-data-for-uqi-mode", author = "") - public void updateActionFormDataPath(MongockTemplate mongockTemplate) { + public void updateActionFormDataPath(MongoTemplate mongoTemplate) { // Get all plugin references to Mongo, S3 and Firestore actions - List uqiPlugins = mongockTemplate.find( + List uqiPlugins = mongoTemplate.find( query(where("packageName").in("mongo-plugin", "amazons3-plugin", "firestore-plugin")), Plugin.class); @@ -236,7 +224,7 @@ public class DatabaseChangelog2 { actionQuery.fields() .include(fieldName(QNewAction.newAction.id)); - List uqiActions = mongockTemplate.find( + List uqiActions = mongoTemplate.find( actionQuery, NewAction.class); @@ -244,7 +232,7 @@ public class DatabaseChangelog2 { for (NewAction uqiActionWithId : uqiActions) { // Fetch one action at a time to avoid OOM. - final NewAction uqiAction = mongockTemplate.findOne( + final NewAction uqiAction = mongoTemplate.findOne( query(where(fieldName(QNewAction.newAction.id)).is(uqiActionWithId.getId())), NewAction.class); @@ -270,7 +258,7 @@ public class DatabaseChangelog2 { log.error("Failing action: {}", uqiAction.getId()); continue; } - mongockTemplate.save(uqiAction); + mongoTemplate.save(uqiAction); } } @@ -748,20 +736,20 @@ public class DatabaseChangelog2 { * configured. This field will be used during * the file or git import to maintain the datasource configuration state * - * @param mongockTemplate + * @param mongoTemplate */ @ChangeSet(order = "004", id = "add-isConfigured-flag-for-all-datasources", author = "") - public void updateIsConfiguredFlagForAllTheExistingDatasources(MongockTemplate mongockTemplate) { + public void updateIsConfiguredFlagForAllTheExistingDatasources(MongoTemplate mongoTemplate) { final Query datasourceQuery = query(where(fieldName(QDatasource.datasource.deleted)).ne(true)) .addCriteria(where(fieldName(QDatasource.datasource.invalids)).size(0)); datasourceQuery.fields() .include(fieldName(QDatasource.datasource.id)); - List datasources = mongockTemplate.find(datasourceQuery, Datasource.class); + List datasources = mongoTemplate.find(datasourceQuery, Datasource.class); for (Datasource datasource : datasources) { final Update update = new Update(); update.set(fieldName(QDatasource.datasource.isConfigured), TRUE); - mongockTemplate.updateFirst( + mongoTemplate.updateFirst( query(where(fieldName(QDatasource.datasource.id)).is(datasource.getId())), update, Datasource.class); @@ -769,8 +757,8 @@ public class DatabaseChangelog2 { } @ChangeSet(order = "005", id = "set-application-version", author = "") - public void setDefaultApplicationVersion(MongockTemplate mongockTemplate) { - mongockTemplate.updateMulti( + public void setDefaultApplicationVersion(MongoTemplate mongoTemplate) { + mongoTemplate.updateMulti( Query.query(where(fieldName(QApplication.application.deleted)).is(false)), update(fieldName(QApplication.application.applicationVersion), ApplicationVersion.EARLIEST_VERSION), @@ -778,13 +766,13 @@ public class DatabaseChangelog2 { } @ChangeSet(order = "006", id = "delete-orphan-pages", author = "") - public void deleteOrphanPages(MongockTemplate mongockTemplate) { + public void deleteOrphanPages(MongoTemplate mongoTemplate) { final Query validPagesQuery = query(where(fieldName(QApplication.application.deleted)).ne(true)); validPagesQuery.fields().include(fieldName(QApplication.application.pages)); validPagesQuery.fields().include(fieldName(QApplication.application.publishedPages)); - final List applications = mongockTemplate.find(validPagesQuery, Application.class); + final List applications = mongoTemplate.find(validPagesQuery, Application.class); final Update deletionUpdates = new Update(); deletionUpdates.set(fieldName(QNewPage.newPage.deleted), true); @@ -807,10 +795,10 @@ public class DatabaseChangelog2 { pageQuery.addCriteria(where(fieldName(QNewPage.newPage.applicationId)).is(application.getId())); pageQuery.fields().include(fieldName(QNewPage.newPage.applicationId)); - final List pages = mongockTemplate.find(pageQuery, NewPage.class); + final List pages = mongoTemplate.find(pageQuery, NewPage.class); for (NewPage newPage : pages) { if (!validPageIds.contains(newPage.getId())) { - mongockTemplate.updateFirst( + mongoTemplate.updateFirst( query(where(fieldName(QNewPage.newPage.id)).is(newPage.getId())), deletionUpdates, NewPage.class @@ -825,13 +813,13 @@ public class DatabaseChangelog2 { * queries like getResourceByDefaultAppIdAndGitSyncId which excludes the deleted entries. */ @ChangeSet(order = "007", id = "update-git-indexes", author = "") - public void addIndexesForGit(MongockTemplate mongockTemplate) { + public void addIndexesForGit(MongoTemplate mongoTemplate) { - dropIndexIfExists(mongockTemplate, NewAction.class, "defaultApplicationId_gitSyncId_compound_index"); - dropIndexIfExists(mongockTemplate, ActionCollection.class, "defaultApplicationId_gitSyncId_compound_index"); + dropIndexIfExists(mongoTemplate, NewAction.class, "defaultApplicationId_gitSyncId_compound_index"); + dropIndexIfExists(mongoTemplate, ActionCollection.class, "defaultApplicationId_gitSyncId_compound_index"); String defaultResources = fieldName(QBaseDomain.baseDomain.defaultResources); - ensureIndexes(mongockTemplate, ActionCollection.class, + ensureIndexes(mongoTemplate, ActionCollection.class, makeIndex( defaultResources + "." + FieldName.APPLICATION_ID, fieldName(QBaseDomain.baseDomain.gitSyncId), @@ -840,7 +828,7 @@ public class DatabaseChangelog2 { .named("defaultApplicationId_gitSyncId_deleted_compound_index") ); - ensureIndexes(mongockTemplate, NewAction.class, + ensureIndexes(mongoTemplate, NewAction.class, makeIndex( defaultResources + "." + FieldName.APPLICATION_ID, fieldName(QBaseDomain.baseDomain.gitSyncId), @@ -849,7 +837,7 @@ public class DatabaseChangelog2 { .named("defaultApplicationId_gitSyncId_deleted_compound_index") ); - ensureIndexes(mongockTemplate, NewPage.class, + ensureIndexes(mongoTemplate, NewPage.class, makeIndex( defaultResources + "." + FieldName.APPLICATION_ID, fieldName(QBaseDomain.baseDomain.gitSyncId), @@ -864,21 +852,21 @@ public class DatabaseChangelog2 { * We'll remove the unique index on organization slugs. We'll also regenerate the slugs for all organizations as * most of them are outdated * - * @param mongockTemplate MongockTemplate instance + * @param mongoTemplate MongoTemplate instance */ @ChangeSet(order = "008", id = "update-organization-slugs", author = "") - public void updateOrganizationSlugs(MongockTemplate mongockTemplate) { - dropIndexIfExists(mongockTemplate, Organization.class, "slug"); + public void updateOrganizationSlugs(MongoTemplate mongoTemplate) { + dropIndexIfExists(mongoTemplate, Organization.class, "slug"); // update organizations final Query getAllOrganizationsQuery = query(where("deletedAt").is(null)); getAllOrganizationsQuery.fields() .include(fieldName(QOrganization.organization.name)); - List organizations = mongockTemplate.find(getAllOrganizationsQuery, Organization.class); + List organizations = mongoTemplate.find(getAllOrganizationsQuery, Organization.class); for (Organization organization : organizations) { - mongockTemplate.updateFirst( + mongoTemplate.updateFirst( query(where(fieldName(QOrganization.organization.id)).is(organization.getId())), new Update().set(fieldName(QOrganization.organization.slug), TextUtils.makeSlug(organization.getName())), Organization.class @@ -887,19 +875,18 @@ public class DatabaseChangelog2 { } @ChangeSet(order = "009", id = "copy-organization-to-workspaces", author = "") - public void copyOrganizationToWorkspaces(MongockTemplate mongockTemplate) { + public void copyOrganizationToWorkspaces(MongoTemplate mongoTemplate) { // Drop the workspace collection in case it has been partially run, otherwise it has no effect - mongockTemplate.dropCollection(Workspace.class); + mongoTemplate.dropCollection(Workspace.class); Gson gson = new Gson(); //Memory optimization note: //Call stream instead of findAll to avoid out of memory if the collection is big //stream implementation lazy loads the data using underlying cursor open on the collection //the data is loaded as and when needed by the pipeline - try (Stream stream = mongockTemplate.stream(new Query().cursorBatchSize(10000), Organization.class) - .stream()) { + try (Stream stream = mongoTemplate.stream(new Query().cursorBatchSize(10000), Organization.class)) { stream.forEach((organization) -> { Workspace workspace = gson.fromJson(gson.toJson(organization), Workspace.class); - mongockTemplate.insert(workspace); + mongoTemplate.insert(workspace); }); } } @@ -910,20 +897,20 @@ public class DatabaseChangelog2 { * the `Action.datasource` field. */ @ChangeSet(order = "010", id = "add-workspace-indexes", author = "") - public void addWorkspaceIndexes(MongockTemplate mongockTemplate) { - ensureIndexes(mongockTemplate, Workspace.class, + public void addWorkspaceIndexes(MongoTemplate mongoTemplate) { + ensureIndexes(mongoTemplate, Workspace.class, makeIndex("createdAt") ); } @ChangeSet(order = "011", id = "update-sequence-names-from-organization-to-workspace", author = "") - public void updateSequenceNamesFromOrganizationToWorkspace(MongockTemplate mongockTemplate) { - for (Sequence sequence : mongockTemplate.findAll(Sequence.class)) { + public void updateSequenceNamesFromOrganizationToWorkspace(MongoTemplate mongoTemplate) { + for (Sequence sequence : mongoTemplate.findAll(Sequence.class)) { String oldName = sequence.getName(); String newName = oldName.replaceAll("(.*) for organization with _id : (.*)", "$1 for workspace with _id : $2"); if (!newName.equals(oldName)) { //Using strings in the field names instead of QSequence becauce Sequence is not a AppsmithDomain - mongockTemplate.updateFirst(query(where("name").is(oldName)), + mongoTemplate.updateFirst(query(where("name").is(oldName)), update("name", newName), Sequence.class ); @@ -932,11 +919,11 @@ public class DatabaseChangelog2 { } @ChangeSet(order = "012", id = "add-default-tenant", author = "") - public void addDefaultTenant(MongockTemplate mongockTemplate) { + public void addDefaultTenant(MongoTemplate mongoTemplate) { Query tenantQuery = new Query(); tenantQuery.addCriteria(where(fieldName(QTenant.tenant.slug)).is("default")); - Tenant tenant = mongockTemplate.findOne(tenantQuery, Tenant.class); + Tenant tenant = mongoTemplate.findOne(tenantQuery, Tenant.class); // if tenant already exists, don't create a new one. if (tenant != null) { @@ -948,20 +935,20 @@ public class DatabaseChangelog2 { defaultTenant.setSlug("default"); defaultTenant.setPricingPlan(PricingPlan.FREE); - mongockTemplate.save(defaultTenant); + mongoTemplate.save(defaultTenant); } @ChangeSet(order = "013", id = "add-tenant-to-all-workspaces", author = "") - public void addTenantToWorkspaces(MongockTemplate mongockTemplate) { + public void addTenantToWorkspaces(MongoTemplate mongoTemplate) { Query tenantQuery = new Query(); tenantQuery.addCriteria(where(fieldName(QTenant.tenant.slug)).is("default")); - Tenant defaultTenant = mongockTemplate.findOne(tenantQuery, Tenant.class); + Tenant defaultTenant = mongoTemplate.findOne(tenantQuery, Tenant.class); assert (defaultTenant != null); // Set all the workspaces to be under the default tenant - mongockTemplate.updateMulti( + mongoTemplate.updateMulti( new Query(), new Update().set("tenantId", defaultTenant.getId()), Workspace.class @@ -970,15 +957,15 @@ public class DatabaseChangelog2 { } @ChangeSet(order = "014", id = "add-tenant-to-all-users-and-flush-redis", author = "") - public void addTenantToUsersAndFlushRedis(MongockTemplate mongockTemplate, ReactiveRedisOperations reactiveRedisOperations) { + public void addTenantToUsersAndFlushRedis(MongoTemplate mongoTemplate, ReactiveRedisOperations reactiveRedisOperations) { Query tenantQuery = new Query(); tenantQuery.addCriteria(where(fieldName(QTenant.tenant.slug)).is("default")); - Tenant defaultTenant = mongockTemplate.findOne(tenantQuery, Tenant.class); + Tenant defaultTenant = mongoTemplate.findOne(tenantQuery, Tenant.class); assert (defaultTenant != null); // Set all the users to be under the default tenant - mongockTemplate.updateMulti( + mongoTemplate.updateMulti( new Query(), new Update().set("tenantId", defaultTenant.getId()), User.class @@ -995,29 +982,29 @@ public class DatabaseChangelog2 { } @ChangeSet(order = "015", id = "migrate-organizationId-to-workspaceId-in-domain-objects", author = "") - public void migrateOrganizationIdToWorkspaceIdInDomainObjects(MongockTemplate mongockTemplate, ReactiveRedisOperations reactiveRedisOperations) { - mongockTemplate.updateMulti(new Query(), + public void migrateOrganizationIdToWorkspaceIdInDomainObjects(MongoTemplate mongoTemplate, ReactiveRedisOperations reactiveRedisOperations) { + mongoTemplate.updateMulti(new Query(), AggregationUpdate.update().set(fieldName(QDatasource.datasource.workspaceId)).toValueOf(Fields.field(fieldName(QDatasource.datasource.organizationId))), Datasource.class); - mongockTemplate.updateMulti(new Query(), + mongoTemplate.updateMulti(new Query(), AggregationUpdate.update().set(fieldName(QActionCollection.actionCollection.workspaceId)).toValueOf(Fields.field(fieldName(QActionCollection.actionCollection.organizationId))), ActionCollection.class); - mongockTemplate.updateMulti(new Query(), + mongoTemplate.updateMulti(new Query(), AggregationUpdate.update().set(fieldName(QApplication.application.workspaceId)).toValueOf(Fields.field(fieldName(QApplication.application.organizationId))), Application.class); - mongockTemplate.updateMulti(new Query(), + mongoTemplate.updateMulti(new Query(), AggregationUpdate.update().set(fieldName(QNewAction.newAction.workspaceId)).toValueOf(Fields.field(fieldName(QNewAction.newAction.organizationId))), NewAction.class); - mongockTemplate.updateMulti(new Query(), + mongoTemplate.updateMulti(new Query(), AggregationUpdate.update().set(fieldName(QTheme.theme.workspaceId)).toValueOf(Fields.field(fieldName(QTheme.theme.organizationId))), Theme.class); - mongockTemplate.updateMulti(new Query(), + mongoTemplate.updateMulti(new Query(), AggregationUpdate.update().set(fieldName(QUserData.userData.recentlyUsedWorkspaceIds)).toValueOf(Fields.field(fieldName(QUserData.userData.recentlyUsedOrgIds))), UserData.class); - mongockTemplate.updateMulti(new Query(), + mongoTemplate.updateMulti(new Query(), AggregationUpdate.update().set(fieldName(QWorkspace.workspace.isAutoGeneratedWorkspace)).toValueOf(Fields.field(fieldName(QWorkspace.workspace.isAutoGeneratedOrganization))), Workspace.class); - mongockTemplate.updateMulti(new Query(), + mongoTemplate.updateMulti(new Query(), AggregationUpdate.update() .set(fieldName(QUser.user.workspaceIds)).toValueOf(Fields.field(fieldName(QUser.user.organizationIds))) .set(fieldName(QUser.user.currentWorkspaceId)).toValueOf(Fields.field(fieldName(QUser.user.currentOrganizationId))) @@ -1033,24 +1020,24 @@ public class DatabaseChangelog2 { flushdb.subscribe(); - mongockTemplate.updateMulti(new Query(), + mongoTemplate.updateMulti(new Query(), AggregationUpdate.update().set(fieldName(QComment.comment.workspaceId)).toValueOf(Fields.field(fieldName(QComment.comment.workspaceId))), Comment.class); - mongockTemplate.updateMulti(new Query(), + mongoTemplate.updateMulti(new Query(), AggregationUpdate.update().set(fieldName(QCommentThread.commentThread.workspaceId)).toValueOf(Fields.field(fieldName(QCommentThread.commentThread.workspaceId))), CommentThread.class); } @ChangeSet(order = "016", id = "organization-to-workspace-indexes-recreate", author = "") - public void organizationToWorkspaceIndexesRecreate(MongockTemplate mongockTemplate) { - dropIndexIfExists(mongockTemplate, Application.class, "organization_application_deleted_gitApplicationMetadata_compound_index"); - dropIndexIfExists(mongockTemplate, Datasource.class, "organization_datasource_deleted_compound_index"); + public void organizationToWorkspaceIndexesRecreate(MongoTemplate mongoTemplate) { + dropIndexIfExists(mongoTemplate, Application.class, "organization_application_deleted_gitApplicationMetadata_compound_index"); + dropIndexIfExists(mongoTemplate, Datasource.class, "organization_datasource_deleted_compound_index"); //If this migration is re-run - dropIndexIfExists(mongockTemplate, Application.class, "workspace_application_deleted_gitApplicationMetadata_compound_index"); - dropIndexIfExists(mongockTemplate, Datasource.class, "workspace_datasource_deleted_compound_index"); + dropIndexIfExists(mongoTemplate, Application.class, "workspace_application_deleted_gitApplicationMetadata_compound_index"); + dropIndexIfExists(mongoTemplate, Datasource.class, "workspace_datasource_deleted_compound_index"); - ensureIndexes(mongockTemplate, Application.class, + ensureIndexes(mongoTemplate, Application.class, makeIndex( fieldName(QApplication.application.workspaceId), fieldName(QApplication.application.name), @@ -1059,7 +1046,7 @@ public class DatabaseChangelog2 { "gitApplicationMetadata.branchName") .unique().named("workspace_application_deleted_gitApplicationMetadata_compound_index") ); - ensureIndexes(mongockTemplate, Datasource.class, + ensureIndexes(mongoTemplate, Datasource.class, makeIndex(fieldName(QDatasource.datasource.workspaceId), fieldName(QDatasource.datasource.name), fieldName(QDatasource.datasource.deletedAt)) @@ -1068,75 +1055,75 @@ public class DatabaseChangelog2 { } @ChangeSet(order = "017", id = "migrate-permission-in-user", author = "") - public void migratePermissionsInUser(MongockTemplate mongockTemplate) { - mongockTemplate.updateMulti( + public void migratePermissionsInUser(MongoTemplate mongoTemplate) { + mongoTemplate.updateMulti( new Query().addCriteria(where("policies.permission").is("manage:userOrganization")), new Update().set("policies.$.permission", "manage:userWorkspace"), User.class); - mongockTemplate.updateMulti( + mongoTemplate.updateMulti( new Query().addCriteria(where("policies.permission").is("read:userOrganization")), new Update().set("policies.$.permission", "read:userWorkspace"), User.class); - mongockTemplate.updateMulti( + mongoTemplate.updateMulti( new Query().addCriteria(where("policies.permission").is("read:userOrganization")), new Update().set("policies.$.permission", "read:userWorkspace"), User.class); } @ChangeSet(order = "018", id = "migrate-permission-in-workspace", author = "") - public void migratePermissionsInWorkspace(MongockTemplate mongockTemplate) { - mongockTemplate.updateMulti( + public void migratePermissionsInWorkspace(MongoTemplate mongoTemplate) { + mongoTemplate.updateMulti( new Query().addCriteria(where("policies.permission").is("manage:organizations")), new Update().set("policies.$.permission", "manage:workspaces"), Workspace.class); - mongockTemplate.updateMulti( + mongoTemplate.updateMulti( new Query().addCriteria(where("policies.permission").is("read:organizations")), new Update().set("policies.$.permission", "read:workspaces"), Workspace.class); - mongockTemplate.updateMulti( + mongoTemplate.updateMulti( new Query().addCriteria(where("policies.permission").is("manage:orgApplications")), new Update().set("policies.$.permission", "manage:workspaceApplications"), Workspace.class); - mongockTemplate.updateMulti( + mongoTemplate.updateMulti( new Query().addCriteria(where("policies.permission").is("read:orgApplications")), new Update().set("policies.$.permission", "read:workspaceApplications"), Workspace.class); - mongockTemplate.updateMulti( + mongoTemplate.updateMulti( new Query().addCriteria(where("policies.permission").is("publish:orgApplications")), new Update().set("policies.$.permission", "publish:workspaceApplications"), Workspace.class); - mongockTemplate.updateMulti( + mongoTemplate.updateMulti( new Query().addCriteria(where("policies.permission").is("export:orgApplications")), new Update().set("policies.$.permission", "export:workspaceApplications"), Workspace.class); - mongockTemplate.updateMulti( + mongoTemplate.updateMulti( new Query().addCriteria(where("policies.permission").is("inviteUsers:organization")), new Update().set("policies.$.permission", "inviteUsers:workspace"), Workspace.class); } @ChangeSet(order = "019", id = "migrate-organizationId-to-workspaceId-in-newaction-datasource", author = "") - public void migrateOrganizationIdToWorkspaceIdInNewActionDatasource(MongockTemplate mongockTemplate, ReactiveRedisOperations reactiveRedisOperations) { - mongockTemplate.updateMulti(new Query(Criteria.where("unpublishedAction.datasource.organizationId").exists(true)), + public void migrateOrganizationIdToWorkspaceIdInNewActionDatasource(MongoTemplate mongoTemplate, ReactiveRedisOperations reactiveRedisOperations) { + mongoTemplate.updateMulti(new Query(Criteria.where("unpublishedAction.datasource.organizationId").exists(true)), AggregationUpdate.update().set("unpublishedAction.datasource.workspaceId").toValueOf(Fields.field("unpublishedAction.datasource.organizationId")), NewAction.class); - mongockTemplate.updateMulti(new Query(Criteria.where("publishedAction.datasource.organizationId").exists(true)), + mongoTemplate.updateMulti(new Query(Criteria.where("publishedAction.datasource.organizationId").exists(true)), AggregationUpdate.update().set("publishedAction.datasource.workspaceId").toValueOf(Fields.field("publishedAction.datasource.organizationId")), NewAction.class); } @ChangeSet(order = "020", id = "migrate-google-sheets-to-uqi", author = "") - public void migrateGoogleSheetsToUqi(MongockTemplate mongockTemplate) { + public void migrateGoogleSheetsToUqi(MongoTemplate mongoTemplate) { // Get plugin references to Google Sheets actions - Plugin uqiPlugin = mongockTemplate.findOne( + Plugin uqiPlugin = mongoTemplate.findOne( query(where("packageName").in("google-sheets-plugin")), Plugin.class ); assert uqiPlugin != null; uqiPlugin.setUiComponent("UQIDbEditorForm"); - mongockTemplate.save(uqiPlugin); + mongoTemplate.save(uqiPlugin); final String pluginId = uqiPlugin.getId(); @@ -1147,7 +1134,7 @@ public class DatabaseChangelog2 { actionQuery.fields() .include(fieldName(QNewAction.newAction.id)); - List uqiActions = mongockTemplate.find( + List uqiActions = mongoTemplate.find( actionQuery, NewAction.class ); @@ -1156,7 +1143,7 @@ public class DatabaseChangelog2 { for (NewAction uqiActionWithId : uqiActions) { // Fetch one action at a time to avoid OOM. - final NewAction uqiAction = mongockTemplate.findOne( + final NewAction uqiAction = mongoTemplate.findOne( query(where(fieldName(QNewAction.newAction.id)).is(uqiActionWithId.getId())), NewAction.class ); @@ -1177,7 +1164,7 @@ public class DatabaseChangelog2 { log.error("Failing action: {}", uqiAction.getId()); continue; } - mongockTemplate.save(uqiAction); + mongoTemplate.save(uqiAction); } } @@ -1441,7 +1428,7 @@ public class DatabaseChangelog2 { DatabaseChangelog1.doClearRedisKeys(reactiveRedisOperations); } - private List getCustomizedThemeIds(String fieldName, Function getThemeIdMethod, List systemThemeIds, MongockTemplate mongockTemplate) { + private List getCustomizedThemeIds(String fieldName, Function getThemeIdMethod, List systemThemeIds, MongoTemplate mongoTemplate) { // query to get application having a customized theme in the provided fieldName Query getAppsWithCustomTheme = new Query( Criteria.where(fieldName(QApplication.application.gitApplicationMetadata)).exists(true) @@ -1454,23 +1441,23 @@ public class DatabaseChangelog2 { // we need the provided field "fieldName" only getAppsWithCustomTheme.fields().include(fieldName); - List applications = mongockTemplate.find(getAppsWithCustomTheme, Application.class); + List applications = mongoTemplate.find(getAppsWithCustomTheme, Application.class); return applications.stream().map(getThemeIdMethod).collect(Collectors.toList()); } @ChangeSet(order = "022", id = "fix-deleted-themes-when-git-branch-deleted", author = "") - public void fixDeletedThemesWhenGitBranchDeleted(MongockTemplate mongockTemplate) { + public void fixDeletedThemesWhenGitBranchDeleted(MongoTemplate mongoTemplate) { Query getSystemThemesQuery = new Query(Criteria.where(fieldName(QTheme.theme.isSystemTheme)).is(TRUE)); getSystemThemesQuery.fields().include(fieldName(QTheme.theme.id)); - List systemThemes = mongockTemplate.find(getSystemThemesQuery, Theme.class); + List systemThemes = mongoTemplate.find(getSystemThemesQuery, Theme.class); List systemThemeIds = systemThemes.stream().map(BaseDomain::getId).collect(Collectors.toList()); List customizedEditModeThemeIds = getCustomizedThemeIds( - fieldName(QApplication.application.editModeThemeId), Application::getEditModeThemeId, systemThemeIds, mongockTemplate + fieldName(QApplication.application.editModeThemeId), Application::getEditModeThemeId, systemThemeIds, mongoTemplate ); List customizedPublishedModeThemeIds = getCustomizedThemeIds( - fieldName(QApplication.application.publishedModeThemeId), Application::getPublishedModeThemeId, systemThemeIds, mongockTemplate + fieldName(QApplication.application.publishedModeThemeId), Application::getPublishedModeThemeId, systemThemeIds, mongoTemplate ); // combine the theme ids @@ -1483,7 +1470,7 @@ public class DatabaseChangelog2 { Criteria deletedCustomThemes = Criteria.where(fieldName(QTheme.theme.id)).in(set) .and(fieldName(QTheme.theme.deleted)).is(true); - mongockTemplate.updateMulti(new Query(deletedCustomThemes), update, Theme.class); + mongoTemplate.updateMulti(new Query(deletedCustomThemes), update, Theme.class); for (String editModeThemeId : customizedEditModeThemeIds) { Query query = new Query(Criteria.where(fieldName(QApplication.application.editModeThemeId)).is(editModeThemeId)) @@ -1491,7 +1478,7 @@ public class DatabaseChangelog2 { .addCriteria(where(fieldName(QApplication.application.gitApplicationMetadata)).exists(true)); query.fields().include(fieldName(QApplication.application.id)); - List applicationList = mongockTemplate.find(query, Application.class); + List applicationList = mongoTemplate.find(query, Application.class); if (applicationList.size() > 1) { // same custom theme is set to more than one application // Remove one as we will create a new theme for all the other branch apps applicationList.remove(applicationList.size() - 1); @@ -1499,14 +1486,14 @@ public class DatabaseChangelog2 { // clone the custom theme for each of these applications Query themeQuery = new Query(Criteria.where(fieldName(QTheme.theme.id)).is(editModeThemeId)) .addCriteria(where(fieldName(QTheme.theme.deleted)).is(false)); - Theme theme = mongockTemplate.findOne(themeQuery, Theme.class); + Theme theme = mongoTemplate.findOne(themeQuery, Theme.class); for (Application application : applicationList) { Theme newTheme = new Theme(); copyNestedNonNullProperties(theme, newTheme); newTheme.setId(null); newTheme.setSystemTheme(false); - newTheme = mongockTemplate.insert(newTheme); - mongockTemplate.updateFirst( + newTheme = mongoTemplate.insert(newTheme); + mongoTemplate.updateFirst( new Query(Criteria.where(fieldName(QApplication.application.id)).is(application.getId())), new Update().set(fieldName(QApplication.application.editModeThemeId), newTheme.getId()), Application.class @@ -1517,15 +1504,15 @@ public class DatabaseChangelog2 { } @ChangeSet(order = "023", id = "add-anonymousUser", author = "") - public void addAnonymousUser(MongockTemplate mongockTemplate) { + public void addAnonymousUser(MongoTemplate mongoTemplate) { Query tenantQuery = new Query(); tenantQuery.addCriteria(where(fieldName(QTenant.tenant.slug)).is("default")); - Tenant tenant = mongockTemplate.findOne(tenantQuery, Tenant.class); + Tenant tenant = mongoTemplate.findOne(tenantQuery, Tenant.class); Query userQuery = new Query(); userQuery.addCriteria(where(fieldName(QUser.user.email)).is(FieldName.ANONYMOUS_USER)) .addCriteria(where(fieldName(QUser.user.tenantId)).is(tenant.getId())); - User anonymousUser = mongockTemplate.findOne(userQuery, User.class); + User anonymousUser = mongoTemplate.findOne(userQuery, User.class); if (anonymousUser == null) { anonymousUser = new User(); @@ -1536,7 +1523,7 @@ public class DatabaseChangelog2 { anonymousUser.setIsAnonymous(true); anonymousUser.setTenantId(tenant.getId()); - mongockTemplate.save(anonymousUser); + mongoTemplate.save(anonymousUser); } } @@ -1544,7 +1531,7 @@ public class DatabaseChangelog2 { return prefix + " - " + workspaceName; } - private Set generateDefaultPermissionGroupsWithoutPermissions(MongockTemplate mongockTemplate, Workspace workspace) { + private Set generateDefaultPermissionGroupsWithoutPermissions(MongoTemplate mongoTemplate, Workspace workspace) { String workspaceName = workspace.getName(); String workspaceId = workspace.getId(); Set permissions = new HashSet<>(); @@ -1554,11 +1541,11 @@ public class DatabaseChangelog2 { adminPermissionGroup.setDefaultWorkspaceId(workspaceId); adminPermissionGroup.setTenantId(workspace.getTenantId()); adminPermissionGroup.setDescription(FieldName.WORKSPACE_ADMINISTRATOR_DESCRIPTION); - adminPermissionGroup = mongockTemplate.save(adminPermissionGroup); + adminPermissionGroup = mongoTemplate.save(adminPermissionGroup); // This ensures that a user can leave a permission group permissions = Set.of(new Permission(adminPermissionGroup.getId(), AclPermission.UNASSIGN_PERMISSION_GROUPS)); adminPermissionGroup.setPermissions(permissions); - adminPermissionGroup = mongockTemplate.save(adminPermissionGroup); + adminPermissionGroup = mongoTemplate.save(adminPermissionGroup); // Developer permission group PermissionGroup developerPermissionGroup = new PermissionGroup(); @@ -1566,11 +1553,11 @@ public class DatabaseChangelog2 { developerPermissionGroup.setDefaultWorkspaceId(workspaceId); developerPermissionGroup.setTenantId(workspace.getTenantId()); developerPermissionGroup.setDescription(FieldName.WORKSPACE_DEVELOPER_DESCRIPTION); - developerPermissionGroup = mongockTemplate.save(developerPermissionGroup); + developerPermissionGroup = mongoTemplate.save(developerPermissionGroup); // This ensures that a user can leave a permission group permissions = Set.of(new Permission(developerPermissionGroup.getId(), AclPermission.UNASSIGN_PERMISSION_GROUPS)); developerPermissionGroup.setPermissions(permissions); - developerPermissionGroup = mongockTemplate.save(developerPermissionGroup); + developerPermissionGroup = mongoTemplate.save(developerPermissionGroup); // App viewer permission group PermissionGroup viewerPermissionGroup = new PermissionGroup(); @@ -1578,16 +1565,16 @@ public class DatabaseChangelog2 { viewerPermissionGroup.setDefaultWorkspaceId(workspaceId); viewerPermissionGroup.setTenantId(workspace.getTenantId()); viewerPermissionGroup.setDescription(FieldName.WORKSPACE_VIEWER_DESCRIPTION); - viewerPermissionGroup = mongockTemplate.save(viewerPermissionGroup); + viewerPermissionGroup = mongoTemplate.save(viewerPermissionGroup); // This ensures that a user can leave a permission group permissions = Set.of(new Permission(viewerPermissionGroup.getId(), AclPermission.UNASSIGN_PERMISSION_GROUPS)); viewerPermissionGroup.setPermissions(permissions); - viewerPermissionGroup = mongockTemplate.save(viewerPermissionGroup); + viewerPermissionGroup = mongoTemplate.save(viewerPermissionGroup); return Set.of(adminPermissionGroup, developerPermissionGroup, viewerPermissionGroup); } - private Set generatePermissionsForDefaultPermissionGroups(MongockTemplate mongockTemplate, PolicyUtils policyUtils, Set permissionGroups, Workspace workspace, Map userIdForEmail, Set validUserIds) { + private Set generatePermissionsForDefaultPermissionGroups(MongoTemplate mongoTemplate, PolicyUtils policyUtils, Set permissionGroups, Workspace workspace, Map userIdForEmail, Set validUserIds) { PermissionGroup adminPermissionGroup = permissionGroups.stream() .filter(permissionGroup -> permissionGroup.getName().startsWith(FieldName.ADMINISTRATOR)) .findFirst().get(); @@ -1714,51 +1701,48 @@ public class DatabaseChangelog2 { } // Save the permission groups - adminPermissionGroup = mongockTemplate.save(adminPermissionGroup); - developerPermissionGroup = mongockTemplate.save(developerPermissionGroup); - viewerPermissionGroup = mongockTemplate.save(viewerPermissionGroup); + adminPermissionGroup = mongoTemplate.save(adminPermissionGroup); + developerPermissionGroup = mongoTemplate.save(developerPermissionGroup); + viewerPermissionGroup = mongoTemplate.save(viewerPermissionGroup); return Set.of(adminPermissionGroup, developerPermissionGroup, viewerPermissionGroup); } - private void rollbackAddDefaultPermissionGroups(MongockTemplate mongockTemplate, Workspace workspace) { + private void rollbackAddDefaultPermissionGroups(MongoTemplate mongoTemplate, Workspace workspace) { // Delete the permission groups - mongockTemplate.remove(PermissionGroup.class) + mongoTemplate.remove(PermissionGroup.class) .matching(new Query(Criteria.where(fieldName(QPermissionGroup.permissionGroup.defaultWorkspaceId)).is(workspace.getId()))) .all(); } @ChangeSet(order = "024", id = "add-default-permission-groups", author = "") - public void addDefaultPermissionGroups(MongockTemplate mongockTemplate, WorkspaceService workspaceService, @NonLockGuarded PolicyUtils policyUtils, UserRepository userRepository) { + public void addDefaultPermissionGroups(MongoTemplate mongoTemplate, WorkspaceService workspaceService, @NonLockGuarded PolicyUtils policyUtils, UserRepository userRepository) { // Create a map of emails to userIds - Map userIdForEmail = mongockTemplate.stream(new Query(), User.class) - .stream() + Map userIdForEmail = mongoTemplate.stream(new Query(), User.class) .collect(Collectors.toMap(User::getEmail, User::getId, (value1, value2) -> value1, HashMap::new)); // Create a set of valid userIds Set validUserIds = userIdForEmail.values().stream().collect(Collectors.toCollection(HashSet::new)); // Rollback permission groups created on locked workspaces - mongockTemplate.stream(new Query(Criteria.where("locked").is(true)), Workspace.class) - .stream() + mongoTemplate.stream(new Query(Criteria.where("locked").is(true)), Workspace.class) .forEach(workspace -> { - rollbackAddDefaultPermissionGroups(mongockTemplate, workspace); + rollbackAddDefaultPermissionGroups(mongoTemplate, workspace); // unlock the workspace - mongockTemplate.update(Workspace.class) + mongoTemplate.update(Workspace.class) .matching(new Criteria("_id").is(new ObjectId(workspace.getId()))) .apply(new Update().unset("locked")) .first(); }); // Stream workspaces which does not have default permission groups - mongockTemplate.stream(new Query(Criteria.where(fieldName(QWorkspace.workspace.defaultPermissionGroups)).is(null)), Workspace.class) - .stream() + mongoTemplate.stream(new Query(Criteria.where(fieldName(QWorkspace.workspace.defaultPermissionGroups)).is(null)), Workspace.class) .forEach(workspace -> { if (workspace.getUserRoles() != null) { //lock the workspace - mongockTemplate.update(Workspace.class) + mongoTemplate.update(Workspace.class) .matching(new Criteria("_id").is(new ObjectId(workspace.getId()))) .apply(new Update().set("locked", true)) .first(); @@ -1768,11 +1752,11 @@ public class DatabaseChangelog2 { workspace.getPolicies().forEach(policy -> { policy.setPermissionGroups(new HashSet<>()); }); - Set permissionGroups = generateDefaultPermissionGroupsWithoutPermissions(mongockTemplate, workspace); + Set permissionGroups = generateDefaultPermissionGroupsWithoutPermissions(mongoTemplate, workspace); // Set default permission groups workspace.setDefaultPermissionGroups(permissionGroups.stream().map(PermissionGroup::getId).collect(Collectors.toSet())); // Generate permissions and policies for the default permission groups - permissionGroups = generatePermissionsForDefaultPermissionGroups(mongockTemplate, policyUtils, permissionGroups, workspace, userIdForEmail, validUserIds); + permissionGroups = generatePermissionsForDefaultPermissionGroups(mongoTemplate, policyUtils, permissionGroups, workspace, userIdForEmail, validUserIds); // Apply the permissions to the workspace for (PermissionGroup permissionGroup : permissionGroups) { // Apply the permissions to the workspace @@ -1780,10 +1764,10 @@ public class DatabaseChangelog2 { workspace = policyUtils.addPoliciesToExistingObject(policyMap, workspace); } // Save the workspace - mongockTemplate.save(workspace); + mongoTemplate.save(workspace); // unlock the workspace - mongockTemplate.update(Workspace.class) + mongoTemplate.update(Workspace.class) .matching(new Criteria("_id").is(new ObjectId(workspace.getId()))) .apply(new Update().unset("locked")) .first(); @@ -1792,73 +1776,70 @@ public class DatabaseChangelog2 { } @ChangeSet(order = "025", id = "mark-public-apps", author = "") - public void markPublicApps(MongockTemplate mongockTemplate) { + public void markPublicApps(MongoTemplate mongoTemplate) { //Temporarily mark public applications - mongockTemplate.updateMulti(new Query().addCriteria(Criteria.where("policies").elemMatch(Criteria.where("permission").is(AclPermission.READ_APPLICATIONS.getValue()).and("users").is("anonymousUser"))), + mongoTemplate.updateMulti(new Query().addCriteria(Criteria.where("policies").elemMatch(Criteria.where("permission").is(AclPermission.READ_APPLICATIONS.getValue()).and("users").is("anonymousUser"))), new Update().set("makePublic", true), Application.class); } @ChangeSet(order = "026", id = "mark-workspaces-for-inheritance", author = "") - public void markWorkspacesForInheritance(MongockTemplate mongockTemplate) { + public void markWorkspacesForInheritance(MongoTemplate mongoTemplate) { //Temporarily mark all workspaces for processing of permissions inheritance - mongockTemplate.updateMulti(new Query(), + mongoTemplate.updateMulti(new Query(), new Update().set("inheritPermissions", true), Workspace.class); } @ChangeSet(order = "027", id = "inherit-policies-to-every-child-object", author = "") - public void inheritPoliciesToEveryChildObject(MongockTemplate mongockTemplate, @NonLockGuarded PolicyGenerator policyGenerator) { + public void inheritPoliciesToEveryChildObject(MongoTemplate mongoTemplate, @NonLockGuarded PolicyGenerator policyGenerator) { - mongockTemplate.stream(new Query(Criteria.where("inheritPermissions").is(true)), Workspace.class) - .stream() + mongoTemplate.stream(new Query(Criteria.where("inheritPermissions").is(true)), Workspace.class) .forEach(workspace -> { // Process applications Set applicationPolicies = policyGenerator.getAllChildPolicies(workspace.getPolicies(), Workspace.class, Application.class); - mongockTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QApplication.application.workspaceId)).is(workspace.getId())), + mongoTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QApplication.application.workspaceId)).is(workspace.getId())), new Update().set("policies", applicationPolicies), Application.class); // Process datasources Set datasourcePolicies = policyGenerator.getAllChildPolicies(workspace.getPolicies(), Workspace.class, Datasource.class); - mongockTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QDatasource.datasource.workspaceId)).is(workspace.getId())), + mongoTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QDatasource.datasource.workspaceId)).is(workspace.getId())), new Update().set("policies", datasourcePolicies), Datasource.class); // Get application ids - Set applicationIds = mongockTemplate.stream(new Query().addCriteria(Criteria.where(fieldName(QApplication.application.workspaceId)).is(workspace.getId())), Application.class) - .stream() + Set applicationIds = mongoTemplate.stream(new Query().addCriteria(Criteria.where(fieldName(QApplication.application.workspaceId)).is(workspace.getId())), Application.class) .map(Application::getId) .collect(Collectors.toSet()); // Update pages Set pagePolicies = policyGenerator.getAllChildPolicies(applicationPolicies, Application.class, Page.class); - mongockTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QNewPage.newPage.applicationId)).in(applicationIds)), + mongoTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QNewPage.newPage.applicationId)).in(applicationIds)), new Update().set("policies", pagePolicies), NewPage.class); // Update NewActions Set actionPolicies = policyGenerator.getAllChildPolicies(pagePolicies, Page.class, Action.class); - mongockTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QNewAction.newAction.applicationId)).in(applicationIds)), + mongoTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QNewAction.newAction.applicationId)).in(applicationIds)), new Update().set("policies", actionPolicies), NewAction.class); // Update ActionCollections - mongockTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QActionCollection.actionCollection.applicationId)).in(applicationIds)), + mongoTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QActionCollection.actionCollection.applicationId)).in(applicationIds)), new Update().set("policies", actionPolicies), ActionCollection.class); // Update Themes // First update all the named themes with the new policies Set themePolicies = policyGenerator.getAllChildPolicies(applicationPolicies, Application.class, Theme.class); - mongockTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QTheme.theme.applicationId)).in(applicationIds)), + mongoTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QTheme.theme.applicationId)).in(applicationIds)), new Update().set("policies", themePolicies), Theme.class); // Also update the non-named themes. // Get the theme ids to update - Set themeIdSet = mongockTemplate.stream(new Query().addCriteria(Criteria.where(fieldName(QApplication.application.workspaceId)).is(workspace.getId())), Application.class) - .stream() + Set themeIdSet = mongoTemplate.stream(new Query().addCriteria(Criteria.where(fieldName(QApplication.application.workspaceId)).is(workspace.getId())), Application.class) .flatMap(application -> { Set themeIds = new HashSet<>(); if (application.getEditModeThemeId() != null) { @@ -1877,20 +1858,20 @@ public class DatabaseChangelog2 { Criteria queryCriteria = new Criteria().andOperator(nonSystemThemeCriteria, idCriteria); // Add the policies to the un-named themes as well. - mongockTemplate.updateMulti( + mongoTemplate.updateMulti( new Query(queryCriteria), new Update().set("policies", themePolicies), Theme.class); // Processed, remove temporary flag - mongockTemplate.update(Workspace.class) + mongoTemplate.update(Workspace.class) .matching(new Criteria("_id").is(new ObjectId(workspace.getId()))) .apply(new Update().unset("inheritPermissions")) .first(); }); } - private void makeApplicationPublic(PolicyUtils policyUtils, PolicyGenerator policyGenerator, NewPageRepository newPageRepository, Application application, Workspace workspace, MongockTemplate mongockTemplate, User anonymousUser) { + private void makeApplicationPublic(PolicyUtils policyUtils, PolicyGenerator policyGenerator, NewPageRepository newPageRepository, Application application, Workspace workspace, MongoTemplate mongoTemplate, User anonymousUser) { PermissionGroup publicPermissionGroup = new PermissionGroup(); publicPermissionGroup.setName(application.getName() + " Public"); publicPermissionGroup.setTenantId(workspace.getTenantId()); @@ -1918,7 +1899,7 @@ public class DatabaseChangelog2 { publicPermissionGroup.setPolicies(new HashSet<>(Set.of(assignPermissionGroup, unassignPermissionGroup))); publicPermissionGroup.setAssignedToUserIds(Set.of(anonymousUser.getId())); - publicPermissionGroup = mongockTemplate.save(publicPermissionGroup); + publicPermissionGroup = mongoTemplate.save(publicPermissionGroup); application.setDefaultPermissionGroup(publicPermissionGroup.getId()); @@ -1931,8 +1912,7 @@ public class DatabaseChangelog2 { Set datasourceIds = new HashSet<>(); - mongockTemplate.stream(new Query().addCriteria(Criteria.where(fieldName(QNewAction.newAction.applicationId)).is(application.getId())), NewAction.class) - .stream() + mongoTemplate.stream(new Query().addCriteria(Criteria.where(fieldName(QNewAction.newAction.applicationId)).is(application.getId())), NewAction.class) .forEach(newAction -> { ActionDTO unpublishedAction = newAction.getUnpublishedAction(); ActionDTO publishedAction = newAction.getPublishedAction(); @@ -1951,45 +1931,43 @@ public class DatabaseChangelog2 { // Update and save application application = policyUtils.addPoliciesToExistingObject(applicationPolicyMap, application); - mongockTemplate.save(application); + mongoTemplate.save(application); applicationPolicies = application.getPolicies(); // Update datasources - mongockTemplate.stream(new Query().addCriteria(Criteria.where(fieldName(QDatasource.datasource.id)).in(datasourceIds)), Datasource.class) - .stream() + mongoTemplate.stream(new Query().addCriteria(Criteria.where(fieldName(QDatasource.datasource.id)).in(datasourceIds)), Datasource.class) .forEach(datasource -> { datasource = policyUtils.addPoliciesToExistingObject(datasourcePolicyMap, datasource); - mongockTemplate.save(datasource); + mongoTemplate.save(datasource); }); // Update pages Set pagePolicies = policyGenerator.getAllChildPolicies(applicationPolicies, Application.class, Page.class); - mongockTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QNewPage.newPage.applicationId)).is(application.getId())), + mongoTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QNewPage.newPage.applicationId)).is(application.getId())), new Update().set("policies", pagePolicies), NewPage.class); // Update NewActions Set actionPolicies = policyGenerator.getAllChildPolicies(pagePolicies, Page.class, Action.class); - mongockTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QNewAction.newAction.applicationId)).is(application.getId())), + mongoTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QNewAction.newAction.applicationId)).is(application.getId())), new Update().set("policies", actionPolicies), NewAction.class); // Update ActionCollections - mongockTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QActionCollection.actionCollection.applicationId)).is(application.getId())), + mongoTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QActionCollection.actionCollection.applicationId)).is(application.getId())), new Update().set("policies", actionPolicies), ActionCollection.class); // Update Themes Set themePolicies = policyGenerator.getAllChildPolicies(applicationPolicies, Application.class, Theme.class); - mongockTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QTheme.theme.applicationId)).is(application.getId())), + mongoTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QTheme.theme.applicationId)).is(application.getId())), new Update().set("policies", themePolicies), Theme.class); } - private void rollbackMakeApplicationsPublic(Application application, MongockTemplate mongockTemplate) { - PermissionGroup publicPermissionGroup = mongockTemplate + private void rollbackMakeApplicationsPublic(Application application, MongoTemplate mongoTemplate) { + PermissionGroup publicPermissionGroup = mongoTemplate .stream(new Query().addCriteria(Criteria.where(fieldName(QPermissionGroup.permissionGroup.defaultWorkspaceId)).is(application.getId())), PermissionGroup.class) - .stream() .findFirst() .orElse(null); @@ -1999,11 +1977,10 @@ public class DatabaseChangelog2 { application.getPolicies().forEach(permissionGroup -> permissionGroup.getPermissionGroups().remove(publicPermissionGroup.getId()) ); - mongockTemplate.save(application); + mongoTemplate.save(application); Set datasourceIds = new HashSet<>(); - mongockTemplate.stream(new Query().addCriteria(Criteria.where(fieldName(QNewAction.newAction.applicationId)).is(application.getId())), NewAction.class) - .stream() + mongoTemplate.stream(new Query().addCriteria(Criteria.where(fieldName(QNewAction.newAction.applicationId)).is(application.getId())), NewAction.class) .forEach(newAction -> { ActionDTO unpublishedAction = newAction.getUnpublishedAction(); @@ -2022,55 +1999,52 @@ public class DatabaseChangelog2 { }); // Remove permission group from datasources policies - mongockTemplate.stream(new Query().addCriteria(Criteria.where(fieldName(QDatasource.datasource.id)).in(datasourceIds)), Datasource.class) - .stream() + mongoTemplate.stream(new Query().addCriteria(Criteria.where(fieldName(QDatasource.datasource.id)).in(datasourceIds)), Datasource.class) .forEach(datasource -> { datasource.getPolicies().forEach(permissionGroup -> permissionGroup.getPermissionGroups().remove(publicPermissionGroup.getId()) ); - mongockTemplate.save(datasource); + mongoTemplate.save(datasource); }); //remove permission group - mongockTemplate.remove(publicPermissionGroup); + mongoTemplate.remove(publicPermissionGroup); } } @ChangeSet(order = "028", id = "make-applications-public", author = "") - public void makeApplicationsPublic(MongockTemplate mongockTemplate, @NonLockGuarded PolicyUtils policyUtils, @NonLockGuarded PolicyGenerator policyGenerator, NewPageRepository newPageRepository) { - User anonymousUser = mongockTemplate.findOne(new Query().addCriteria(Criteria.where(fieldName(QUser.user.email)).is(FieldName.ANONYMOUS_USER)), User.class); + public void makeApplicationsPublic(MongoTemplate mongoTemplate, @NonLockGuarded PolicyUtils policyUtils, @NonLockGuarded PolicyGenerator policyGenerator, NewPageRepository newPageRepository) { + User anonymousUser = mongoTemplate.findOne(new Query().addCriteria(Criteria.where(fieldName(QUser.user.email)).is(FieldName.ANONYMOUS_USER)), User.class); // Rollback permission groups created on locked workspaces - mongockTemplate.stream(new Query(Criteria.where("locked").is(true)), Application.class) - .stream() + mongoTemplate.stream(new Query(Criteria.where("locked").is(true)), Application.class) .forEach(application -> { - rollbackMakeApplicationsPublic(application, mongockTemplate); + rollbackMakeApplicationsPublic(application, mongoTemplate); // unlock the workspace - mongockTemplate.update(Application.class) + mongoTemplate.update(Application.class) .matching(new Criteria("_id").is(new ObjectId(application.getId()))) .apply(new Update().unset("locked")) .first(); }); // Make all marked applications public - mongockTemplate.stream(new Query().addCriteria(Criteria.where("makePublic").is(true)), Application.class) - .stream() + mongoTemplate.stream(new Query().addCriteria(Criteria.where("makePublic").is(true)), Application.class) .forEach(application -> { // lock the application - mongockTemplate.update(Application.class) + mongoTemplate.update(Application.class) .matching(new Criteria("_id").is(new ObjectId(application.getId()))) .apply(new Update().set("locked", true)) .first(); - Workspace workspace = mongockTemplate.findOne(new Query().addCriteria(Criteria.where(fieldName(QBaseDomain.baseDomain.id)).is(application.getWorkspaceId())), Workspace.class); - makeApplicationPublic(policyUtils, policyGenerator, newPageRepository, application, workspace, mongockTemplate, anonymousUser); + Workspace workspace = mongoTemplate.findOne(new Query().addCriteria(Criteria.where(fieldName(QBaseDomain.baseDomain.id)).is(application.getWorkspaceId())), Workspace.class); + makeApplicationPublic(policyUtils, policyGenerator, newPageRepository, application, workspace, mongoTemplate, anonymousUser); // Remove makePublic flag from application - mongockTemplate.updateFirst(new Query().addCriteria(Criteria.where("_id").is(new ObjectId(application.getId()))), + mongoTemplate.updateFirst(new Query().addCriteria(Criteria.where("_id").is(new ObjectId(application.getId()))), new Update().unset("makePublic"), Application.class); // unlock the application - mongockTemplate.update(Application.class) + mongoTemplate.update(Application.class) .matching(new Criteria("_id").is(new ObjectId(application.getId()))) .apply(new Update().unset("locked")) .first(); @@ -2078,10 +2052,10 @@ public class DatabaseChangelog2 { } @ChangeSet(order = "029", id = "add-instance-config-object", author = "") - public void addInstanceConfigurationPlaceHolder(MongockTemplate mongockTemplate) { + public void addInstanceConfigurationPlaceHolder(MongoTemplate mongoTemplate) { Query instanceConfigurationQuery = new Query(); instanceConfigurationQuery.addCriteria(where(fieldName(QConfig.config1.name)).is(FieldName.INSTANCE_CONFIG)); - Config instanceAdminConfiguration = mongockTemplate.findOne(instanceConfigurationQuery, Config.class); + Config instanceAdminConfiguration = mongoTemplate.findOne(instanceConfigurationQuery, Config.class); if (instanceAdminConfiguration != null) { return; @@ -2089,7 +2063,7 @@ public class DatabaseChangelog2 { instanceAdminConfiguration = new Config(); instanceAdminConfiguration.setName(FieldName.INSTANCE_CONFIG); - Config savedInstanceConfig = mongockTemplate.save(instanceAdminConfiguration); + Config savedInstanceConfig = mongoTemplate.save(instanceAdminConfiguration); // Create instance management permission group PermissionGroup instanceManagerPermissionGroup = new PermissionGroup(); @@ -2103,13 +2077,13 @@ public class DatabaseChangelog2 { Query adminUserQuery = new Query(); adminUserQuery.addCriteria(where(fieldName(QBaseDomain.baseDomain.policies)) .elemMatch(where("permission").is(MANAGE_INSTANCE_ENV.getValue()))); - List adminUsers = mongockTemplate.find(adminUserQuery, User.class); + List adminUsers = mongoTemplate.find(adminUserQuery, User.class); instanceManagerPermissionGroup.setAssignedToUserIds( adminUsers.stream().map(User::getId).collect(Collectors.toSet()) ); - PermissionGroup savedPermissionGroup = mongockTemplate.save(instanceManagerPermissionGroup); + PermissionGroup savedPermissionGroup = mongoTemplate.save(instanceManagerPermissionGroup); // Update the instance config with the permission group id savedInstanceConfig.setConfig(new JSONObject(Map.of(DEFAULT_PERMISSION_GROUP, savedPermissionGroup.getId()))); @@ -2123,7 +2097,7 @@ public class DatabaseChangelog2 { savedInstanceConfig.setPolicies(new HashSet<>(Set.of(editConfigPolicy, readConfigPolicy))); - mongockTemplate.save(savedInstanceConfig); + mongoTemplate.save(savedInstanceConfig); // Also give the permission group permission to unassign & assign & read to itself Policy updatePermissionGroupPolicy = Policy.builder().permission(AclPermission.UNASSIGN_PERMISSION_GROUPS.getValue()) @@ -2150,15 +2124,15 @@ public class DatabaseChangelog2 { ); savedPermissionGroup.setPermissions(permissions); - mongockTemplate.save(savedPermissionGroup); + mongoTemplate.save(savedPermissionGroup); } @ChangeSet(order = "030", id = "add-anonymous-user-permission-group", author = "") - public void addAnonymousUserPermissionGroup(MongockTemplate mongockTemplate) { + public void addAnonymousUserPermissionGroup(MongoTemplate mongoTemplate) { Query anonymousUserPermissionConfig = new Query(); anonymousUserPermissionConfig.addCriteria(where(fieldName(QConfig.config1.name)).is(FieldName.PUBLIC_PERMISSION_GROUP)); - Config publicPermissionGroupConfig = mongockTemplate.findOne(anonymousUserPermissionConfig, Config.class); + Config publicPermissionGroupConfig = mongoTemplate.findOne(anonymousUserPermissionConfig, Config.class); if (publicPermissionGroupConfig != null) { return; @@ -2170,29 +2144,29 @@ public class DatabaseChangelog2 { Query tenantQuery = new Query(); tenantQuery.addCriteria(where(fieldName(QTenant.tenant.slug)).is("default")); - Tenant tenant = mongockTemplate.findOne(tenantQuery, Tenant.class); + Tenant tenant = mongoTemplate.findOne(tenantQuery, Tenant.class); Query userQuery = new Query(); userQuery.addCriteria(where(fieldName(QUser.user.email)).is(FieldName.ANONYMOUS_USER)) .addCriteria(where(fieldName(QUser.user.tenantId)).is(tenant.getId())); - User anonymousUser = mongockTemplate.findOne(userQuery, User.class); + User anonymousUser = mongoTemplate.findOne(userQuery, User.class); // Give access to anonymous user to the permission group. publicPermissionGroup.setAssignedToUserIds(Set.of(anonymousUser.getId())); - PermissionGroup savedPermissionGroup = mongockTemplate.save(publicPermissionGroup); + PermissionGroup savedPermissionGroup = mongoTemplate.save(publicPermissionGroup); publicPermissionGroupConfig = new Config(); publicPermissionGroupConfig.setName(FieldName.PUBLIC_PERMISSION_GROUP); publicPermissionGroupConfig.setConfig(new JSONObject(Map.of(PERMISSION_GROUP_ID, savedPermissionGroup.getId()))); - mongockTemplate.save(publicPermissionGroupConfig); + mongoTemplate.save(publicPermissionGroupConfig); return; } @ChangeSet(order = "031", id = "create-system-themes-v3", author = "", runAlways = true) - public void createSystemThemes3(MongockTemplate mongockTemplate) throws IOException { + public void createSystemThemes3(MongoTemplate mongoTemplate) throws IOException { Index systemThemeIndex = new Index() .on(fieldName(QTheme.theme.isSystemTheme), Sort.Direction.ASC) .named("system_theme_index") @@ -2204,15 +2178,16 @@ public class DatabaseChangelog2 { .named("application_id_index") .background(); - dropIndexIfExists(mongockTemplate, Theme.class, "system_theme_index"); - dropIndexIfExists(mongockTemplate, Theme.class, "application_id_index"); - ensureIndexes(mongockTemplate, Theme.class, systemThemeIndex, applicationIdIndex); + dropIndexIfExists(mongoTemplate, Theme.class, "system_theme_index"); + dropIndexIfExists(mongoTemplate, Theme.class, "application_id_index"); + ensureIndexes(mongoTemplate, Theme.class, systemThemeIndex, applicationIdIndex); final String themesJson = StreamUtils.copyToString( new DefaultResourceLoader().getResource("system-themes.json").getInputStream(), Charset.defaultCharset() ); - Theme[] themes = new Gson().fromJson(themesJson, Theme[].class); + + Theme[] themes = new GsonBuilder().registerTypeAdapter(Instant.class, new ISOStringToInstantConverter()).create().fromJson(themesJson, Theme[].class); Theme legacyTheme = null; boolean themeExists = false; @@ -2220,11 +2195,11 @@ public class DatabaseChangelog2 { // Make this theme accessible to anonymous users. Query anonymousUserPermissionConfig = new Query(); anonymousUserPermissionConfig.addCriteria(where(fieldName(QConfig.config1.name)).is(FieldName.PUBLIC_PERMISSION_GROUP)); - Config publicPermissionGroupConfig = mongockTemplate.findOne(anonymousUserPermissionConfig, Config.class); + Config publicPermissionGroupConfig = mongoTemplate.findOne(anonymousUserPermissionConfig, Config.class); String permissionGroupId = publicPermissionGroupConfig.getConfig().getAsString(PERMISSION_GROUP_ID); - PermissionGroup publicPermissionGroup = mongockTemplate.findOne(query(where("_id").is(permissionGroupId)), PermissionGroup.class); + PermissionGroup publicPermissionGroup = mongoTemplate.findOne(query(where("_id").is(permissionGroupId)), PermissionGroup.class); // Initialize the permissions for the role HashSet permissions = new HashSet<>(); @@ -2244,9 +2219,9 @@ public class DatabaseChangelog2 { Query query = new Query(Criteria.where(fieldName(QTheme.theme.name)).is(theme.getName()) .and(fieldName(QTheme.theme.isSystemTheme)).is(true)); - Theme savedTheme = mongockTemplate.findOne(query, Theme.class); + Theme savedTheme = mongoTemplate.findOne(query, Theme.class); if (savedTheme == null) { // this theme does not exist, create it - savedTheme = mongockTemplate.save(theme); + savedTheme = mongoTemplate.save(theme); } else { // theme already found, update themeExists = true; savedTheme.setDisplayName(theme.getDisplayName()); @@ -2257,7 +2232,7 @@ public class DatabaseChangelog2 { if (savedTheme.getCreatedAt() == null) { savedTheme.setCreatedAt(Instant.now()); } - mongockTemplate.save(savedTheme); + mongoTemplate.save(savedTheme); } if (theme.getName().equalsIgnoreCase(Theme.LEGACY_THEME_NAME)) { @@ -2279,21 +2254,21 @@ public class DatabaseChangelog2 { // migrate all applications and set legacy theme to them in both mode Update update = new Update().set(fieldName(QApplication.application.publishedModeThemeId), legacyTheme.getId()) .set(fieldName(QApplication.application.editModeThemeId), legacyTheme.getId()); - mongockTemplate.updateMulti( + mongoTemplate.updateMulti( new Query(where(fieldName(QApplication.application.deleted)).is(false)), update, Application.class ); } // Finally save the role which gives access to all the system themes to the anonymous user. publicPermissionGroup.setPermissions(permissions); - mongockTemplate.save(publicPermissionGroup); + mongoTemplate.save(publicPermissionGroup); } @ChangeSet(order = "32", id = "create-indices-on-permissions-for-performance", author = "") - public void addPermissionGroupIndex(MongockTemplate mongockTemplate) { + public void addPermissionGroupIndex(MongoTemplate mongoTemplate) { - dropIndexIfExists(mongockTemplate, PermissionGroup.class, "permission_group_workspace_deleted_compound_index"); - dropIndexIfExists(mongockTemplate, PermissionGroup.class, "permission_group_assignedUserIds_deleted_compound_index"); + dropIndexIfExists(mongoTemplate, PermissionGroup.class, "permission_group_workspace_deleted_compound_index"); + dropIndexIfExists(mongoTemplate, PermissionGroup.class, "permission_group_assignedUserIds_deleted_compound_index"); Index workspace_deleted_compound_index = makeIndex( fieldName(QPermissionGroup.permissionGroup.defaultWorkspaceId), @@ -2307,7 +2282,7 @@ public class DatabaseChangelog2 { ) .named("permission_group_assignedUserIds_deleted_compound_index"); - ensureIndexes(mongockTemplate, PermissionGroup.class, + ensureIndexes(mongoTemplate, PermissionGroup.class, workspace_deleted_compound_index, assignedToUserIds_deleted_compound_index ); @@ -2317,11 +2292,11 @@ public class DatabaseChangelog2 { * Changing the order of this function to 10000 so that it always gets executed at the end. * This ensures that any permission changes for super users happen once all other migrations are completed * - * @param mongockTemplate + * @param mongoTemplate * @param cacheableRepositoryHelper */ @ChangeSet(order = "10000", id = "update-super-users", author = "", runAlways = true) - public void updateSuperUsers(MongockTemplate mongockTemplate, CacheableRepositoryHelper cacheableRepositoryHelper) { + public void updateSuperUsers(MongoTemplate mongoTemplate, CacheableRepositoryHelper cacheableRepositoryHelper) { // Read the admin emails from the environment and update the super users accordingly String adminEmailsStr = System.getenv(String.valueOf(APPSMITH_ADMIN_EMAILS)); @@ -2329,7 +2304,7 @@ public class DatabaseChangelog2 { Query instanceConfigurationQuery = new Query(); instanceConfigurationQuery.addCriteria(where(fieldName(QConfig.config1.name)).is(FieldName.INSTANCE_CONFIG)); - Config instanceAdminConfiguration = mongockTemplate.findOne(instanceConfigurationQuery, Config.class); + Config instanceAdminConfiguration = mongoTemplate.findOne(instanceConfigurationQuery, Config.class); String instanceAdminPermissionGroupId = (String) instanceAdminConfiguration.getConfig().get(DEFAULT_PERMISSION_GROUP); @@ -2337,11 +2312,11 @@ public class DatabaseChangelog2 { permissionGroupQuery .addCriteria(where(fieldName(QPermissionGroup.permissionGroup.id)).is(instanceAdminPermissionGroupId)) .fields().include(fieldName(QPermissionGroup.permissionGroup.assignedToUserIds)); - PermissionGroup instanceAdminPG = mongockTemplate.findOne(permissionGroupQuery, PermissionGroup.class); + PermissionGroup instanceAdminPG = mongoTemplate.findOne(permissionGroupQuery, PermissionGroup.class); Query tenantQuery = new Query(); tenantQuery.addCriteria(where(fieldName(QTenant.tenant.slug)).is("default")); - Tenant tenant = mongockTemplate.findOne(tenantQuery, Tenant.class); + Tenant tenant = mongoTemplate.findOne(tenantQuery, Tenant.class); Set userIds = adminEmails.stream() .map(email -> email.trim()) @@ -2349,11 +2324,11 @@ public class DatabaseChangelog2 { .map(email -> { Query userQuery = new Query(); userQuery.addCriteria(where(fieldName(QUser.user.email)).is(email)); - User user = mongockTemplate.findOne(userQuery, User.class); + User user = mongoTemplate.findOne(userQuery, User.class); if (user == null) { log.info("Creating super user with username {}", email); - user = createNewUser(email, tenant.getId(), mongockTemplate); + user = createNewUser(email, tenant.getId(), mongoTemplate); } return user.getId(); @@ -2362,19 +2337,19 @@ public class DatabaseChangelog2 { Set oldSuperUsers = instanceAdminPG.getAssignedToUserIds(); Set updatedUserIds = findSymmetricDiff(oldSuperUsers, userIds); - evictPermissionCacheForUsers(updatedUserIds, mongockTemplate, cacheableRepositoryHelper); + evictPermissionCacheForUsers(updatedUserIds, mongoTemplate, cacheableRepositoryHelper); Update update = new Update().set(fieldName(QPermissionGroup.permissionGroup.assignedToUserIds), userIds); - mongockTemplate.updateFirst(permissionGroupQuery, update, PermissionGroup.class); + mongoTemplate.updateFirst(permissionGroupQuery, update, PermissionGroup.class); } - private User createNewUser(String email, String tenantId, MongockTemplate mongockTemplate) { + private User createNewUser(String email, String tenantId, MongoTemplate mongoTemplate) { User user = new User(); user.setEmail(email); user.setIsEnabled(false); user.setTenantId(tenantId); user.setCreatedAt(Instant.now()); - user = mongockTemplate.save(user); + user = mongoTemplate.save(user); // Assign the user to the default permissions PermissionGroup userManagementPermissionGroup = new PermissionGroup(); @@ -2389,7 +2364,7 @@ public class DatabaseChangelog2 { // Assign the permission group to the user userManagementPermissionGroup.setAssignedToUserIds(Set.of(user.getId())); - PermissionGroup savedPermissionGroup = mongockTemplate.save(userManagementPermissionGroup); + PermissionGroup savedPermissionGroup = mongoTemplate.save(userManagementPermissionGroup); Policy readUserPolicy = Policy.builder() .permission(READ_USERS.getValue()) @@ -2406,11 +2381,11 @@ public class DatabaseChangelog2 { user.setPolicies(Set.of(readUserPolicy, manageUserPolicy, resetPwdPolicy)); - return mongockTemplate.save(user); + return mongoTemplate.save(user); } @ChangeSet(order = "034", id = "update-bad-theme-state", author = "") - public void updateBadThemeState(MongockTemplate mongockTemplate, @NonLockGuarded PolicyGenerator policyGenerator, + public void updateBadThemeState(MongoTemplate mongoTemplate, @NonLockGuarded PolicyGenerator policyGenerator, CacheableRepositoryHelper cacheableRepositoryHelper) { Query query = new Query(); query.addCriteria( @@ -2420,14 +2395,13 @@ public class DatabaseChangelog2 { ) ); - mongockTemplate.stream(query, Theme.class) - .stream() + mongoTemplate.stream(query, Theme.class) .forEach(theme -> { Query applicationQuery = new Query(); Criteria themeCriteria = new Criteria(fieldName(QApplication.application.editModeThemeId)).is(theme.getId()) .orOperator(new Criteria(fieldName(QApplication.application.publishedModeThemeId)).is(theme.getId())); - List applications = mongockTemplate.find(applicationQuery.addCriteria(themeCriteria), Application.class); + List applications = mongoTemplate.find(applicationQuery.addCriteria(themeCriteria), Application.class); // This is an erroneous state where the theme is being used by multiple applications if (applications != null && applications.size() > 1) { // Create new themes for the rest of the applications which are copies of the original theme @@ -2439,7 +2413,7 @@ public class DatabaseChangelog2 { // Don't create a new theme for the first application // Just update the policies theme.setPolicies(themePolicies); - mongockTemplate.save(theme); + mongoTemplate.save(theme); } else { Theme newTheme = new Theme(); @@ -2453,7 +2427,7 @@ public class DatabaseChangelog2 { newTheme.setUpdatedAt(Instant.now()); newTheme.setPolicies(themePolicies); - newTheme = mongockTemplate.save(newTheme); + newTheme = mongoTemplate.save(newTheme); if (application.getEditModeThemeId().equals(theme.getId())) { application.setEditModeThemeId(newTheme.getId()); @@ -2461,7 +2435,7 @@ public class DatabaseChangelog2 { if (application.getPublishedModeThemeId().equals(theme.getId())) { application.setPublishedModeThemeId(newTheme.getId()); } - mongockTemplate.save(application); + mongoTemplate.save(application); } } } @@ -2469,11 +2443,11 @@ public class DatabaseChangelog2 { } @ChangeSet(order = "035", id = "migrate-public-apps-single-pg", author = "") - public void migratePublicAppsSinglePg(MongockTemplate mongockTemplate, @NonLockGuarded PolicyUtils policyUtils, @NonLockGuarded PolicyGenerator policyGenerator, CacheableRepositoryHelper cacheableRepositoryHelper) { + public void migratePublicAppsSinglePg(MongoTemplate mongoTemplate, @NonLockGuarded PolicyUtils policyUtils, @NonLockGuarded PolicyGenerator policyGenerator, CacheableRepositoryHelper cacheableRepositoryHelper) { Query anonymousUserPermissionConfig = new Query(); anonymousUserPermissionConfig.addCriteria(where(fieldName(QConfig.config1.name)).is(FieldName.PUBLIC_PERMISSION_GROUP)); - Config publicPermissionGroupConfig = mongockTemplate.findOne(anonymousUserPermissionConfig, Config.class); + Config publicPermissionGroupConfig = mongoTemplate.findOne(anonymousUserPermissionConfig, Config.class); String permissionGroupId = publicPermissionGroupConfig.getConfig().getAsString(PERMISSION_GROUP_ID); @@ -2483,7 +2457,7 @@ public class DatabaseChangelog2 { Query publicAppQuery = new Query(); publicAppQuery.addCriteria(where(fieldName(QApplication.application.defaultPermissionGroup)).exists(true)); - org.springframework.data.util.StreamUtils.createStreamFromIterator(mongockTemplate.stream(publicAppQuery, Application.class)) + mongoTemplate.stream(publicAppQuery, Application.class) .parallel() .forEach(application -> { String oldPermissionGroupId = application.getDefaultPermissionGroup(); @@ -2499,7 +2473,7 @@ public class DatabaseChangelog2 { policy.getPermissionGroups().remove(oldPermissionGroupId); policy.getPermissionGroups().add(permissionGroupId); }); - mongockTemplate.save(application); + mongoTemplate.save(application); Set datasourceIds = new HashSet<>(); Query applicationActionsQuery = new Query().addCriteria(where(fieldName(QNewAction.newAction.applicationId)).is(application.getId())); @@ -2508,8 +2482,7 @@ public class DatabaseChangelog2 { .include(fieldName(QNewAction.newAction.unpublishedAction) + "." + fieldName(QNewAction.newAction.unpublishedAction.datasource)) .include(fieldName(QNewAction.newAction.publishedAction) + "." + fieldName(QNewAction.newAction.publishedAction.datasource)); - mongockTemplate.stream(applicationActionsQuery, NewAction.class) - .stream() + mongoTemplate.stream(applicationActionsQuery, NewAction.class) .forEach(newAction -> { ActionDTO unpublishedAction = newAction.getUnpublishedAction(); ActionDTO publishedAction = newAction.getPublishedAction(); @@ -2528,8 +2501,7 @@ public class DatabaseChangelog2 { // Update datasources Query datasourceQuery = new Query().addCriteria(where(fieldName(QDatasource.datasource.id)).in(datasourceIds)); - mongockTemplate.stream(datasourceQuery, Datasource.class) - .stream() + mongoTemplate.stream(datasourceQuery, Datasource.class) .parallel() .forEach(datasource -> { // Update the datasource policies. @@ -2540,23 +2512,23 @@ public class DatabaseChangelog2 { policy.getPermissionGroups().remove(oldPermissionGroupId); policy.getPermissionGroups().add(permissionGroupId); }); - mongockTemplate.save(datasource); + mongoTemplate.save(datasource); }); // Update pages Set pagePolicies = policyGenerator.getAllChildPolicies(application.getPolicies(), Application.class, Page.class); - mongockTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QNewPage.newPage.applicationId)).is(application.getId())), + mongoTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QNewPage.newPage.applicationId)).is(application.getId())), new Update().set(fieldName(QNewPage.newPage.policies), pagePolicies), NewPage.class); // Update actions Set actionPolicies = policyGenerator.getAllChildPolicies(pagePolicies, Page.class, Action.class); - mongockTemplate.updateMulti(new Query().addCriteria(where(fieldName(QNewAction.newAction.applicationId)).is(application.getId())), + mongoTemplate.updateMulti(new Query().addCriteria(where(fieldName(QNewAction.newAction.applicationId)).is(application.getId())), new Update().set(fieldName(QNewAction.newAction.policies), actionPolicies), NewAction.class); // Update js objects - mongockTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QActionCollection.actionCollection.applicationId)).is(application.getId())), + mongoTemplate.updateMulti(new Query().addCriteria(Criteria.where(fieldName(QActionCollection.actionCollection.applicationId)).is(application.getId())), new Update().set(fieldName(QActionCollection.actionCollection.policies), actionPolicies), ActionCollection.class); @@ -2568,29 +2540,29 @@ public class DatabaseChangelog2 { ); Criteria queryCriteria = new Criteria().andOperator(nonSystemThemeCriteria, idCriteria); Set themePolicies = policyGenerator.getAllChildPolicies(application.getPolicies(), Application.class, Theme.class); - mongockTemplate.updateMulti(new Query().addCriteria(queryCriteria), + mongoTemplate.updateMulti(new Query().addCriteria(queryCriteria), new Update().set(fieldName(QTheme.theme.policies), themePolicies), Theme.class); }); // All the applications have been migrated. // Clean up all the permission groups which were created to provide views to public apps - mongockTemplate.findAllAndRemove(new Query().addCriteria(Criteria.where(fieldName(QPermissionGroup.permissionGroup.id)).in(oldPgIds)), PermissionGroup.class); + mongoTemplate.findAllAndRemove(new Query().addCriteria(Criteria.where(fieldName(QPermissionGroup.permissionGroup.id)).in(oldPgIds)), PermissionGroup.class); // Finally evict the anonymous user cache entry so that it gets recomputed on next use. Query tenantQuery = new Query(); tenantQuery.addCriteria(where(fieldName(QTenant.tenant.slug)).is("default")); - Tenant tenant = mongockTemplate.findOne(tenantQuery, Tenant.class); + Tenant tenant = mongoTemplate.findOne(tenantQuery, Tenant.class); Query userQuery = new Query(); userQuery.addCriteria(where(fieldName(QUser.user.email)).is(FieldName.ANONYMOUS_USER)) .addCriteria(where(fieldName(QUser.user.tenantId)).is(tenant.getId())); - User anonymousUser = mongockTemplate.findOne(userQuery, User.class); - evictPermissionCacheForUsers(Set.of(anonymousUser.getId()), mongockTemplate, cacheableRepositoryHelper); + User anonymousUser = mongoTemplate.findOne(userQuery, User.class); + evictPermissionCacheForUsers(Set.of(anonymousUser.getId()), mongoTemplate, cacheableRepositoryHelper); } @ChangeSet(order = "036", id = "add-graphql-plugin", author = "") - public void addGraphQLPlugin(MongockTemplate mongoTemplate) { + public void addGraphQLPlugin(MongoTemplate mongoTemplate) { Plugin plugin = new Plugin(); plugin.setName("Authenticated GraphQL API"); plugin.setType(PluginType.API); @@ -2617,47 +2589,47 @@ public class DatabaseChangelog2 { * missing. */ @ChangeSet(order = "037", id = "install-graphql-plugin-to-remaining-workspaces", author = "") - public void reInstallGraphQLPluginToWorkspaces(MongockTemplate mongoTemplate) { + public void reInstallGraphQLPluginToWorkspaces(MongoTemplate mongoTemplate) { Plugin graphQLPlugin = mongoTemplate .findOne(query(where("packageName").is("graphql-plugin")), Plugin.class); installPluginToAllWorkspaces(mongoTemplate, graphQLPlugin.getId()); } - public void softDeletePlugin(MongockTemplate mongockTemplate, Plugin plugin) { - softDeleteAllPluginActions(plugin, mongockTemplate); - softDeleteAllPluginDatasources(plugin, mongockTemplate); - softDeletePluginFromAllWorkspaces(plugin, mongockTemplate); - softDeleteInPluginCollection(plugin, mongockTemplate); + public void softDeletePlugin(MongoTemplate mongoTemplate, Plugin plugin) { + softDeleteAllPluginActions(plugin, mongoTemplate); + softDeleteAllPluginDatasources(plugin, mongoTemplate); + softDeletePluginFromAllWorkspaces(plugin, mongoTemplate); + softDeleteInPluginCollection(plugin, mongoTemplate); } @ChangeSet(order = "038", id = "delete-rapid-api-plugin-related-items", author = "") - public void deleteRapidApiPluginRelatedItems(MongockTemplate mongockTemplate) { - Plugin rapidApiPlugin = mongockTemplate.findOne(query(where("packageName").is("rapidapi-plugin")), + public void deleteRapidApiPluginRelatedItems(MongoTemplate mongoTemplate) { + Plugin rapidApiPlugin = mongoTemplate.findOne(query(where("packageName").is("rapidapi-plugin")), Plugin.class); if (rapidApiPlugin == null) { return; } - softDeletePlugin(mongockTemplate, rapidApiPlugin); + softDeletePlugin(mongoTemplate, rapidApiPlugin); } @ChangeSet(order = "035", id = "add-tenant-admin-permissions-instance-admin", author = "") - public void addTenantAdminPermissionsToInstanceAdmin(MongockTemplate mongockTemplate, @NonLockGuarded PolicyUtils policyUtils) { + public void addTenantAdminPermissionsToInstanceAdmin(MongoTemplate mongoTemplate, @NonLockGuarded PolicyUtils policyUtils) { Query tenantQuery = new Query(); tenantQuery.addCriteria(where(fieldName(QTenant.tenant.slug)).is("default")); - Tenant defaultTenant = mongockTemplate.findOne(tenantQuery, Tenant.class); + Tenant defaultTenant = mongoTemplate.findOne(tenantQuery, Tenant.class); Query instanceConfigurationQuery = new Query(); instanceConfigurationQuery.addCriteria(where(fieldName(QConfig.config1.name)).is(FieldName.INSTANCE_CONFIG)); - Config instanceAdminConfiguration = mongockTemplate.findOne(instanceConfigurationQuery, Config.class); + Config instanceAdminConfiguration = mongoTemplate.findOne(instanceConfigurationQuery, Config.class); String instanceAdminPermissionGroupId = (String) instanceAdminConfiguration.getConfig().get(DEFAULT_PERMISSION_GROUP); Query permissionGroupQuery = new Query(); permissionGroupQuery.addCriteria(where(fieldName(QPermissionGroup.permissionGroup.id)).is(instanceAdminPermissionGroupId)); - PermissionGroup instanceAdminPGBeforeChanges = mongockTemplate.findOne(permissionGroupQuery, PermissionGroup.class); + PermissionGroup instanceAdminPGBeforeChanges = mongoTemplate.findOne(permissionGroupQuery, PermissionGroup.class); // Give read permission to instanceAdminPg to all the users who have been assigned this permission group Map readPermissionGroupPolicyMap = Map.of( @@ -2676,35 +2648,35 @@ public class DatabaseChangelog2 { HashSet permissions = new HashSet<>(instanceAdminPG.getPermissions()); permissions.addAll(tenantPermissions); instanceAdminPG.setPermissions(permissions); - mongockTemplate.save(instanceAdminPG); + mongoTemplate.save(instanceAdminPG); Map tenantPolicy = policyUtils.generatePolicyFromPermissionGroupForObject(instanceAdminPG, defaultTenant.getId()); Tenant updatedTenant = policyUtils.addPoliciesToExistingObject(tenantPolicy, defaultTenant); - mongockTemplate.save(updatedTenant); + mongoTemplate.save(updatedTenant); } @ChangeSet(order = "039", id = "change-readPermissionGroup-to-readPermissionGroupMembers", author = "") - public void modifyReadPermissionGroupToReadPermissionGroupMembers(MongockTemplate mongockTemplate, @NonLockGuarded PolicyUtils policyUtils) { + public void modifyReadPermissionGroupToReadPermissionGroupMembers(MongoTemplate mongoTemplate, @NonLockGuarded PolicyUtils policyUtils) { Query query = new Query(Criteria.where("policies.permission").is("read:permissionGroups")); Update update = new Update().set("policies.$.permission", "read:permissionGroupMembers"); - mongockTemplate.updateMulti(query, update, PermissionGroup.class); + mongoTemplate.updateMulti(query, update, PermissionGroup.class); } @ChangeSet(order = "040", id = "delete-permissions-in-permissionGroups", author = "") - public void deletePermissionsInPermissionGroups(MongockTemplate mongockTemplate) { + public void deletePermissionsInPermissionGroups(MongoTemplate mongoTemplate) { Query query = new Query(); Update update = new Update().set("permissions", List.of()); - mongockTemplate.updateMulti(query, update, PermissionGroup.class); + mongoTemplate.updateMulti(query, update, PermissionGroup.class); } - private void softDeletePluginFromAllWorkspaces(Plugin plugin, MongockTemplate mongockTemplate) { + private void softDeletePluginFromAllWorkspaces(Plugin plugin, MongoTemplate mongoTemplate) { Query queryToGetNonDeletedWorkspaces = new Query(); queryToGetNonDeletedWorkspaces.fields().include(fieldName(QWorkspace.workspace.id)); - List workspaces = mongockTemplate.find(queryToGetNonDeletedWorkspaces, Workspace.class); + List workspaces = mongoTemplate.find(queryToGetNonDeletedWorkspaces, Workspace.class); workspaces.stream() .map(Workspace::getId) - .map(id -> fetchDomainObjectUsingId(id, mongockTemplate, QWorkspace.workspace.id, Workspace.class)) + .map(id -> fetchDomainObjectUsingId(id, mongoTemplate, QWorkspace.workspace.id, Workspace.class)) .forEachOrdered(workspace -> { workspace.getPlugins().stream() .filter(workspacePlugin -> workspacePlugin != null && workspacePlugin.getPluginId() != null) @@ -2713,17 +2685,17 @@ public class DatabaseChangelog2 { workspacePlugin.setDeleted(true); workspacePlugin.setDeletedAt(Instant.now()); }); - mongockTemplate.save(workspace); + mongoTemplate.save(workspace); }); } - private void softDeleteInPluginCollection(Plugin plugin, MongockTemplate mongockTemplate) { + private void softDeleteInPluginCollection(Plugin plugin, MongoTemplate mongoTemplate) { plugin.setDeleted(true); plugin.setDeletedAt(Instant.now()); - mongockTemplate.save(plugin); + mongoTemplate.save(plugin); } - private void softDeleteAllPluginDatasources(Plugin plugin, MongockTemplate mongockTemplate) { + private void softDeleteAllPluginDatasources(Plugin plugin, MongoTemplate mongoTemplate) { /* Query to get all plugin datasources which are not deleted */ Query queryToGetDatasources = getQueryToFetchAllDomainObjectsWhichAreNotDeletedUsingPluginId(plugin); @@ -2731,14 +2703,14 @@ public class DatabaseChangelog2 { queryToGetDatasources.fields().include(fieldName(QDatasource.datasource.id)); /* Fetch plugin datasources using the previous query */ - List datasources = mongockTemplate.find(queryToGetDatasources, Datasource.class); + List datasources = mongoTemplate.find(queryToGetDatasources, Datasource.class); /* Mark each selected datasource as deleted */ - updateDeleteAndDeletedAtFieldsForEachDomainObject(datasources, mongockTemplate, + updateDeleteAndDeletedAtFieldsForEachDomainObject(datasources, mongoTemplate, QDatasource.datasource.id, Datasource.class); } - private void softDeleteAllPluginActions(Plugin plugin, MongockTemplate mongockTemplate) { + private void softDeleteAllPluginActions(Plugin plugin, MongoTemplate mongoTemplate) { /* Query to get all plugin actions which are not deleted */ Query queryToGetActions = getQueryToFetchAllDomainObjectsWhichAreNotDeletedUsingPluginId(plugin); @@ -2746,10 +2718,10 @@ public class DatabaseChangelog2 { queryToGetActions.fields().include(fieldName(QNewAction.newAction.id)); /* Fetch plugin actions using the previous query */ - List actions = mongockTemplate.find(queryToGetActions, NewAction.class); + List actions = mongoTemplate.find(queryToGetActions, NewAction.class); /* Mark each selected action as deleted */ - updateDeleteAndDeletedAtFieldsForEachDomainObject(actions, mongockTemplate, QNewAction.newAction.id, + updateDeleteAndDeletedAtFieldsForEachDomainObject(actions, mongoTemplate, QNewAction.newAction.id, NewAction.class); } @@ -2760,15 +2732,15 @@ public class DatabaseChangelog2 { } private void updateDeleteAndDeletedAtFieldsForEachDomainObject(List domainObjects, - MongockTemplate mongockTemplate, Path path, + MongoTemplate mongoTemplate, Path path, Class type) { domainObjects.stream() .map(BaseDomain::getId) // iterate over id one by one - .map(id -> fetchDomainObjectUsingId(id, mongockTemplate, path, type)) // find object using id + .map(id -> fetchDomainObjectUsingId(id, mongoTemplate, path, type)) // find object using id .forEachOrdered(domainObject -> { domainObject.setDeleted(true); domainObject.setDeletedAt(Instant.now()); - mongockTemplate.save(domainObject); + mongoTemplate.save(domainObject); }); } @@ -2778,186 +2750,45 @@ public class DatabaseChangelog2 { * `type` is a POJO class type that indicates which collection we are interested in. eg. path=QNewAction * .newAction.id, type=NewAction.class */ - private T fetchDomainObjectUsingId(String id, MongockTemplate mongockTemplate, Path path, + private T fetchDomainObjectUsingId(String id, MongoTemplate mongoTemplate, Path path, Class type) { - final T domainObject = mongockTemplate.findOne(query(where(fieldName(path)).is(id)), type); + final T domainObject = mongoTemplate.findOne(query(where(fieldName(path)).is(id)), type); return domainObject; } @ChangeSet(order = "037", id = "indices-recommended-by-mongodb-cloud", author = "") - public void addIndicesRecommendedByMongoCloud(MongockTemplate mongockTemplate) { - dropIndexIfExists(mongockTemplate, NewPage.class, "deleted"); - ensureIndexes(mongockTemplate, NewPage.class, makeIndex("deleted")); + public void addIndicesRecommendedByMongoCloud(MongoTemplate mongoTemplate) { + dropIndexIfExists(mongoTemplate, NewPage.class, "deleted"); + ensureIndexes(mongoTemplate, NewPage.class, makeIndex("deleted")); - dropIndexIfExists(mongockTemplate, Application.class, "deleted"); - ensureIndexes(mongockTemplate, Application.class, makeIndex("deleted")); + dropIndexIfExists(mongoTemplate, Application.class, "deleted"); + ensureIndexes(mongoTemplate, Application.class, makeIndex("deleted")); - dropIndexIfExists(mongockTemplate, Workspace.class, "tenantId_deleted"); - ensureIndexes(mongockTemplate, Workspace.class, makeIndex("tenantId", "deleted").named("tenantId_deleted")); + dropIndexIfExists(mongoTemplate, Workspace.class, "tenantId_deleted"); + ensureIndexes(mongoTemplate, Workspace.class, makeIndex("tenantId", "deleted").named("tenantId_deleted")); } @ChangeSet(order = "038", id = "add-unique-index-for-uidstring", author = "") - public void addUniqueIndexOnUidString(MongockTemplate mongoTemplate) { + public void addUniqueIndexOnUidString(MongoTemplate mongoTemplate) { Index uidStringUniqueness = makeIndex("uidString").unique() .named("customjslibs_uidstring_index"); ensureIndexes(mongoTemplate, CustomJSLib.class, uidStringUniqueness); } - // TODO We'll be deleting this migration after upgrade to Spring 6.0 - @ChangeSet(order = "039", id = "deprecate-queryabletext-encryption", author = "") - public void deprecateQueryableTextEncryption(MongockTemplate mongockTemplate, - @NonLockGuarded EncryptionConfig encryptionConfig, - EncryptionService encryptionService) { - Stopwatch stopwatch = new Stopwatch("Instance Schema migration to v2"); + /** + * Since MySQL plugin's underlying driver has been changed to MariaDB driver, the `ssl-mode=preferred` is no + * longer supported. Hence, any such usage is being updated to `ssl-mode=default` by this method. + */ + @ChangeSet(order = "039", id = "remove-preferred-ssl-mode-from-mysql", author = "") + public void changeSSLModeFromPreferredToDefaultForMySQLPlugin(MongoTemplate mongoTemplate) { + Plugin mySQLPlugin = mongoTemplate.findOne(query(where("packageName").is("mysql-plugin")), + Plugin.class); + Query queryToGetDatasources = getQueryToFetchAllDomainObjectsWhichAreNotDeletedUsingPluginId(mySQLPlugin); + queryToGetDatasources.addCriteria(Criteria.where("datasourceConfiguration.connection.ssl.authType").is( + "PREFERRED")); - Config encryptionVersion = mongockTemplate.findOne( - query(where(fieldName(QConfig.config1.name)).is(Appsmith.INSTANCE_SCHEMA_VERSION)), - Config.class); - - if (encryptionVersion != null && (Integer) encryptionVersion.getConfig().get("value") < 2) { - String saltInHex = Hex.encodeHexString(encryptionConfig.getSalt().getBytes()); - TextEncryptor textEncryptor = Encryptors.queryableText(encryptionConfig.getPassword(), saltInHex); - - /** - * - List of attributes in datasources that need to be encoded. - * - Each path represents where the attribute exists in mongo db document. - */ - List datasourcePathList = new ArrayList<>(); - datasourcePathList.add("datasourceConfiguration.connection.ssl.keyFile.base64Content"); - datasourcePathList.add("datasourceConfiguration.connection.ssl.certificateFile.base64Content"); - datasourcePathList.add("datasourceConfiguration.connection.ssl.caCertificateFile.base64Content"); - datasourcePathList.add("datasourceConfiguration.connection.ssl.pemCertificate.file.base64Content"); - datasourcePathList.add("datasourceConfiguration.connection.ssl.pemCertificate.password"); - datasourcePathList.add("datasourceConfiguration.sshProxy.privateKey.keyFile.base64Content"); - datasourcePathList.add("datasourceConfiguration.sshProxy.privateKey.password"); - datasourcePathList.add("datasourceConfiguration.authentication.value"); - datasourcePathList.add("datasourceConfiguration.authentication.password"); - datasourcePathList.add("datasourceConfiguration.authentication.bearerToken"); - datasourcePathList.add("datasourceConfiguration.authentication.clientSecret"); - datasourcePathList.add("datasourceConfiguration.authentication.authenticationResponse.token"); - datasourcePathList.add("datasourceConfiguration.authentication.authenticationResponse.refreshToken"); - datasourcePathList.add("datasourceConfiguration.authentication.authenticationResponse.tokenResponse"); - List datasourcePathListExists = datasourcePathList - .stream() - .map(Filters::exists) - .collect(Collectors.toList()); - - List gitDeployKeysPathListExists = new ArrayList<>(); - ArrayList gitDeployKeysPathList = new ArrayList<>(); - gitDeployKeysPathList.add("gitAuth.privateKey"); - gitDeployKeysPathListExists.add(Filters.exists("gitAuth.privateKey")); - - List applicationPathListExists = new ArrayList<>(); - ArrayList applicationPathList = new ArrayList<>(); - applicationPathList.add("gitApplicationMetadata.gitAuth.privateKey"); - applicationPathListExists.add(Filters.exists("gitApplicationMetadata.gitAuth.privateKey")); - - mongockTemplate.execute("datasource", getNewEncryptionCallback(textEncryptor, encryptionService, datasourcePathListExists, datasourcePathList, stopwatch)); - mongockTemplate.execute("gitDeployKeys", getNewEncryptionCallback(textEncryptor, encryptionService, gitDeployKeysPathListExists, gitDeployKeysPathList, stopwatch)); - mongockTemplate.execute("application", getNewEncryptionCallback(textEncryptor, encryptionService, applicationPathListExists, applicationPathList, stopwatch)); - - mongockTemplate.upsert( - query(where(fieldName(QConfig.config1.name)).is(Appsmith.INSTANCE_SCHEMA_VERSION)), - update("config.value", 2), - Config.class); - } - stopwatch.stopAndLogTimeInMillis(); - } - - private CollectionCallback getNewEncryptionCallback( - TextEncryptor textEncryptor, - EncryptionService encryptionService, - Iterable collectionFilterIterable, - List pathList, - Stopwatch stopwatch) { - return new CollectionCallback() { - @Override - public String doInCollection(MongoCollection collection) { - MongoCursor cursor = collection - .find( - Filters.and( - Filters.or(collectionFilterIterable), - Filters.not(Filters.exists("encryptionVersion")))) - .cursor(); - - log.debug("collection callback start: {}ms", stopwatch.getExecutionTime()); - - List> documentPairList = new ArrayList<>(); - while (cursor.hasNext()) { - Document old = cursor.next(); - BasicDBObject query = new BasicDBObject(); - query.put("_id", old.getObjectId("_id")); - // This document will have the encrypted values. - BasicDBObject updated = new BasicDBObject(); - updated.put("$set", new BasicDBObject("encryptionVersion", 2)); - updated.put("$unset", new BasicDBObject()); - // Encrypt attributes - pathList.stream() - .forEach(path -> reapplyNewEncryptionToPathValueIfExists(old, updated, path, encryptionService, textEncryptor)); - // Since empty unset values are only allowed since Mongo v5+, - // Remove the operation if there is nothing to unset - if (((BasicDBObject) updated.get("$unset")).isEmpty()) { - updated.remove("$unset"); - } - documentPairList.add(List.of(query, updated)); - } - - log.debug("collection callback processing end: {}ms", stopwatch.getExecutionTime()); - log.debug("update will be run for {} documents", documentPairList.size()); - - /** - * - Replace old document with the updated document that has encrypted values. - * - Replacing here instead of the while loop above makes sure that we attempt replacement only if - * the encryption step succeeded without error for each selected document. - */ - documentPairList.stream().parallel() - .forEach(docPair -> collection.updateOne(docPair.get(0), docPair.get(1))); - - log.debug("collection callback update end: {}ms", stopwatch.getExecutionTime()); - - return null; - } - }; - } - - private void reapplyNewEncryptionToPathValueIfExists(Document document, BasicDBObject update, String path, - EncryptionService encryptionService, - TextEncryptor textEncryptor) { - String[] pathKeys = path.split("\\."); - /** - * - For attribute path "datasourceConfiguration.connection.ssl.keyFile.base64Content", first get the parent - * document that contains the attribute 'base64Content' i.e. fetch the document corresponding to path - * "datasourceConfiguration.connection.ssl.keyFile" - */ - String parentDocumentPath = org.apache.commons.lang.StringUtils.join(ArrayUtils.subarray(pathKeys, 0, pathKeys.length - 1), "."); - Document parentDocument = DatabaseChangelog1.getDocumentFromPath(document, parentDocumentPath); - - if (parentDocument != null) { - if (parentDocument.containsKey(pathKeys[pathKeys.length - 1])) { - String oldEncryptedValue = parentDocument.getString(pathKeys[pathKeys.length - 1]); - if (StringUtils.hasLength(String.valueOf(oldEncryptedValue))) { - String decryptedValue = null; - try { - decryptedValue = textEncryptor.decrypt(String.valueOf(oldEncryptedValue)); - } catch (IllegalArgumentException e) { - // This happens on release DB for some creds that are malformed - if ("Hex-encoded string must have an even number of characters".equals(e.getMessage())) { - decryptedValue = String.valueOf(oldEncryptedValue); - } - } catch (IllegalStateException e) { - // This means that the value in DB was already in a malformed state, - // we'll ignore these values under the assumption that the user is not using this workspace - log.debug("Encountered unexpected encrypted value at {} for document with id: {}", path, document.getObjectId("_id")); - log.debug("Permanently ignoring the value."); - return; - } - String newEncryptedValue = encryptionService.encryptString(decryptedValue); - ((BasicDBObject) update.get("$set")).put(path, newEncryptedValue); - if (path.startsWith("datasourceConfiguration.authentication")) { - ((BasicDBObject) update.get("$unset")).put("datasourceConfiguration.authentication.isEncrypted", 1); - } - } - } - } + Update update = new Update(); + update.set("datasourceConfiguration.connection.ssl.authType", "DEFAULT"); + mongoTemplate.updateMulti(queryToGetDatasources, update, Datasource.class); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/MigrationHelperMethods.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/MigrationHelperMethods.java index 8d2772e4ab..5d7340173d 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/MigrationHelperMethods.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/migrations/MigrationHelperMethods.java @@ -10,8 +10,8 @@ import com.appsmith.server.domains.User; import com.appsmith.server.dtos.ApplicationJson; import com.appsmith.server.helpers.CollectionUtils; import com.appsmith.server.repositories.CacheableRepositoryHelper; -import com.github.cloudyrock.mongock.driver.mongodb.springdata.v3.decorator.impl.MongockTemplate; import org.apache.commons.lang.StringUtils; +import org.springframework.data.mongodb.core.MongoTemplate; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; @@ -174,7 +174,7 @@ public class MigrationHelperMethods { } public static void evictPermissionCacheForUsers(Set userIds, - MongockTemplate mongockTemplate, + MongoTemplate mongoTemplate, CacheableRepositoryHelper cacheableRepositoryHelper) { if (userIds == null || userIds.isEmpty()) { @@ -184,7 +184,7 @@ public class MigrationHelperMethods { userIds.forEach(userId -> { Query query = new Query(new Criteria(fieldName(QUser.user.id)).is(userId)); - User user = mongockTemplate.findOne(query, User.class); + User user = mongoTemplate.findOne(query, User.class); if (user != null) { // blocking call for cache eviction to ensure its subscribed immediately before proceeding further. cacheableRepositoryHelper.evictPermissionGroupsUser(user.getEmail(), user.getTenantId()) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/notifications/EmailSender.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/notifications/EmailSender.java index 996816bbd2..8804d41698 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/notifications/EmailSender.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/notifications/EmailSender.java @@ -2,6 +2,9 @@ package com.appsmith.server.notifications; import com.appsmith.server.configurations.EmailConfig; import com.appsmith.server.helpers.TemplateUtils; +import jakarta.mail.MessagingException; +import jakarta.mail.internet.InternetAddress; +import jakarta.mail.internet.MimeMessage; import lombok.extern.slf4j.Slf4j; import org.springframework.mail.MailException; import org.springframework.mail.javamail.JavaMailSender; @@ -12,9 +15,6 @@ import reactor.core.Exceptions; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; -import javax.mail.MessagingException; -import javax.mail.internet.InternetAddress; -import javax.mail.internet.MimeMessage; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Map; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/BaseAppsmithRepositoryImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/BaseAppsmithRepositoryImpl.java index b542201af8..c3f7a3a5f2 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/BaseAppsmithRepositoryImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/BaseAppsmithRepositoryImpl.java @@ -15,4 +15,4 @@ public abstract class BaseAppsmithRepositoryImpl extends B super(mongoOperations, mongoConverter, cacheableRepositoryHelper); } -} +} \ No newline at end of file diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/BaseRepositoryImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/BaseRepositoryImpl.java index 70e6c43cad..7c5bff043d 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/BaseRepositoryImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/BaseRepositoryImpl.java @@ -2,9 +2,8 @@ package com.appsmith.server.repositories; import com.appsmith.external.models.BaseDomain; import com.appsmith.server.constants.FieldName; -import com.appsmith.server.acl.AclPermission; -import com.appsmith.server.domains.User; import com.mongodb.client.result.UpdateResult; +import jakarta.validation.constraints.NotNull; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; import org.springframework.data.domain.Example; @@ -20,7 +19,6 @@ import org.springframework.util.Assert; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import javax.validation.constraints.NotNull; import java.io.Serializable; import java.time.Instant; import java.util.List; @@ -42,23 +40,23 @@ import static org.springframework.data.mongodb.core.query.Criteria.where; * @param The domain class that extends {@link BaseDomain}. This is required because we use default fields in * {@link BaseDomain} such as `deleted` * @param The ID field that extends Serializable interface - * - * In case you are wondering why we have two different repository implementation classes i.e. - * BaseRepositoryImpl.java and BaseAppsmithRepositoryCEImpl.java, Arpit's comments on this might be helpful: - * ``` - * BaseRepository is required for running any JPA queries. This doesn’t invoke any ACL permissions. This is used when - * we wish to fetch data from the DB without ACL. For eg, Fetching a user by username during login - * Usage example: - * ActionCollectionRepositoryCE extends BaseRepository to power JPA queries using the ReactiveMongoRepository. - * AppsmithRepository is the one that we should use by default (unless the use case demands that we don’t need ACL). - * It is implemented by BaseAppsmithRepositoryCEImpl and BaseAppsmithRepositoryImpl. This interface allows us to - * define custom Mongo queries by including the delete functionality & ACL permissions. - * Usage example: - * CustomActionCollectionRepositoryCE extends AppsmithRepository and then implements the functions defined there. - * I agree that the naming is a little confusing. Open to hearing better naming suggestions so that we can improve - * the understanding of these interfaces. - * ``` - * Ref: https://theappsmith.slack.com/archives/CPQNLFHTN/p1669100205502599?thread_ts=1668753437.497369&cid=CPQNLFHTN + *

+ * In case you are wondering why we have two different repository implementation classes i.e. + * BaseRepositoryImpl.java and BaseAppsmithRepositoryCEImpl.java, Arpit's comments on this might be helpful: + * ``` + * BaseRepository is required for running any JPA queries. This doesn’t invoke any ACL permissions. This is used when + * we wish to fetch data from the DB without ACL. For eg, Fetching a user by username during login + * Usage example: + * ActionCollectionRepositoryCE extends BaseRepository to power JPA queries using the ReactiveMongoRepository. + * AppsmithRepository is the one that we should use by default (unless the use case demands that we don’t need ACL). + * It is implemented by BaseAppsmithRepositoryCEImpl and BaseAppsmithRepositoryImpl. This interface allows us to + * define custom Mongo queries by including the delete functionality & ACL permissions. + * Usage example: + * CustomActionCollectionRepositoryCE extends AppsmithRepository and then implements the functions defined there. + * I agree that the naming is a little confusing. Open to hearing better naming suggestions so that we can improve + * the understanding of these interfaces. + * ``` + * Ref: https://theappsmith.slack.com/archives/CPQNLFHTN/p1669100205502599?thread_ts=1668753437.497369&cid=CPQNLFHTN */ @Slf4j public class BaseRepositoryImpl extends SimpleReactiveMongoRepository diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/BaseAppsmithRepositoryCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/BaseAppsmithRepositoryCEImpl.java index 8fd387e827..cc194a4df0 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/BaseAppsmithRepositoryCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/BaseAppsmithRepositoryCEImpl.java @@ -135,9 +135,9 @@ public abstract class BaseAppsmithRepositoryCEImpl { return criteria.orElse(null); } - + public static final Optional userAcl(Set permissionGroups, Optional permission) { - if(permission.isEmpty()) { + if (permission.isEmpty()) { return Optional.empty(); } // Check if the permission is being provided by any of the permission groups @@ -167,22 +167,39 @@ public abstract class BaseAppsmithRepositoryCEImpl { if (id == null) { return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ID)); } - return findById(id, Optional.ofNullable(permission)); + return findById(id, projectionFieldNames, Optional.ofNullable(permission)); + } + + public Mono findById(String id, List projectionFieldNames, Optional permission) { + if (id == null) { + return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ID)); + } + + return getCurrentUserPermissionGroupsIfRequired(permission) + .flatMap(permissionGroups -> { + Query query = new Query(getIdCriteria(id)); + query.addCriteria(notDeleted()); + Optional userAcl = userAcl(permissionGroups, permission); + if (userAcl.isPresent()) { + query.addCriteria(userAcl.get()); + } + + if (!isEmpty(projectionFieldNames)) { + projectionFieldNames.stream() + .forEach(projectionFieldName -> { + query.fields().include(projectionFieldName); + }); + } + + return mongoOperations.query(this.genericDomain) + .matching(query) + .one() + .flatMap(obj -> setUserPermissionsInObject(obj, permissionGroups)); + }); } public Mono findById(String id, Optional permission) { - return getCurrentUserPermissionGroupsIfRequired(permission).flatMap(permissionGroups -> { - Query query = new Query(getIdCriteria(id)); - query.addCriteria(notDeleted()); - Optional userAcl = userAcl(permissionGroups, permission); - if(userAcl.isPresent()) { - query.addCriteria(userAcl.get()); - } - return mongoOperations.query(this.genericDomain) - .matching(query) - .one() - .flatMap(obj -> setUserPermissionsInObject(obj, permissionGroups)); - }); + return findById(id, null, permission); } @Deprecated @@ -216,7 +233,7 @@ public abstract class BaseAppsmithRepositoryCEImpl { .flatMap(permissionGroups -> { Optional userAcl = userAcl(permissionGroups, permission); query.addCriteria(notDeleted()); - if(userAcl.isPresent()) { + if (userAcl.isPresent()) { query.addCriteria(userAcl.get()); } return mongoOperations.updateFirst(query, updateObj, resource.getClass()) @@ -270,11 +287,12 @@ public abstract class BaseAppsmithRepositoryCEImpl { public Mono updateById(String id, Update updateObj, Optional permission) { Query query = new Query(Criteria.where("id").is(id)); - if(permission.isEmpty()) { + if (permission.isEmpty()) { return mongoOperations.updateFirst(query, updateObj, this.genericDomain); } - return getCurrentUserPermissionGroupsIfRequired(permission).flatMap(permissionGroups -> { + return getCurrentUserPermissionGroupsIfRequired(permission) + .flatMap(permissionGroups -> { query.addCriteria(new Criteria().andOperator(notDeleted(), userAcl(permissionGroups, permission.get()))); return mongoOperations.updateFirst(query, updateObj, this.genericDomain); }); @@ -295,11 +313,11 @@ public abstract class BaseAppsmithRepositoryCEImpl { @Deprecated protected Mono queryOne(List criterias, AclPermission aclPermission) { - return queryOne(criterias, Optional.ofNullable(aclPermission)); + return queryOne(criterias, null, Optional.ofNullable(aclPermission)); } protected Mono> getCurrentUserPermissionGroupsIfRequired(Optional permission) { - if(permission.isEmpty()) { + if (permission.isEmpty()) { return Mono.just(Set.of()); } return getCurrentUserPermissionGroups(); @@ -314,8 +332,9 @@ public abstract class BaseAppsmithRepositoryCEImpl { protected Mono queryOne(List criterias, List projectionFieldNames, AclPermission permission) { Mono> permissionGroupsMono = getCurrentUserPermissionGroupsIfRequired(Optional.ofNullable(permission)); - - return permissionGroupsMono.flatMap(permissionGroups -> { + + return permissionGroupsMono + .flatMap(permissionGroups -> { return mongoOperations.query(this.genericDomain) .matching(createQueryWithPermission(criterias, projectionFieldNames, permissionGroups, permission)) .one() @@ -323,12 +342,13 @@ public abstract class BaseAppsmithRepositoryCEImpl { }); } - protected Mono queryOne(List criterias, Optional permission) { + protected Mono queryOne(List criterias, List projectionFieldNames, Optional permission) { Mono> permissionGroupsMono = getCurrentUserPermissionGroupsIfRequired(permission); - - return permissionGroupsMono.flatMap(permissionGroups -> { + + return permissionGroupsMono + .flatMap(permissionGroups -> { return mongoOperations.query(this.genericDomain) - .matching(createQueryWithPermission(criterias, permissionGroups, permission)) + .matching(createQueryWithPermission(criterias, projectionFieldNames, permissionGroups, permission)) .one() .flatMap(obj -> setUserPermissionsInObject(obj, permissionGroups)); }); @@ -342,12 +362,13 @@ public abstract class BaseAppsmithRepositoryCEImpl { protected Mono queryFirst(List criterias, Optional permission) { Mono> permissionGroupsMono = getCurrentUserPermissionGroupsIfRequired(permission); - return permissionGroupsMono.flatMap(permissionGroups -> { - return mongoOperations.query(this.genericDomain) - .matching(createQueryWithPermission(criterias, permissionGroups, permission)) - .first() - .flatMap(obj -> setUserPermissionsInObject(obj, permissionGroups)); - }); + return permissionGroupsMono + .flatMap(permissionGroups -> { + return mongoOperations.query(this.genericDomain) + .matching(createQueryWithPermission(criterias, null, permissionGroups, permission)) + .first() + .flatMap(obj -> setUserPermissionsInObject(obj, permissionGroups)); + }); } @Deprecated @@ -359,7 +380,16 @@ public abstract class BaseAppsmithRepositoryCEImpl { return createQueryWithPermission(criterias, null, permissionGroups, permission.orElse(null)); } - protected Query createQueryWithPermission(List criterias, List projectionFieldNames, + protected Query createQueryWithPermission(List criterias, + List projectionFieldNames, + Set permissionGroups, + Optional permission) { + + return createQueryWithPermission(criterias, projectionFieldNames, permissionGroups, permission.orElse(null)); + } + + protected Query createQueryWithPermission(List criterias, + List projectionFieldNames, Set permissionGroups, AclPermission aclPermission) { Query query = new Query(); @@ -371,7 +401,7 @@ public abstract class BaseAppsmithRepositoryCEImpl { query.addCriteria(new Criteria().andOperator(notDeleted(), userAcl(permissionGroups, aclPermission))); } - if(!isEmpty(projectionFieldNames)) { + if (!isEmpty(projectionFieldNames)) { projectionFieldNames.stream() .forEach(fieldName -> query.fields().include(fieldName)); } @@ -387,9 +417,10 @@ public abstract class BaseAppsmithRepositoryCEImpl { protected Mono count(List criterias, Optional permission) { Mono> permissionGroupsMono = getCurrentUserPermissionGroupsIfRequired(permission); - return permissionGroupsMono.flatMap(permissionGroups -> - mongoOperations.count(createQueryWithPermission(criterias, permissionGroups, permission), this.genericDomain) - ); + return permissionGroupsMono + .flatMap(permissionGroups -> + mongoOperations.count(createQueryWithPermission(criterias, permissionGroups, permission), this.genericDomain) + ); } protected Mono count(List criteriaList) { @@ -428,10 +459,10 @@ public abstract class BaseAppsmithRepositoryCEImpl { return queryAll(criterias, Optional.ofNullable(includeFields), Optional.ofNullable(aclPermission), Optional.ofNullable(sort), limit); } - public Flux queryAll( List criterias, Optional> includeFields, Optional permission, Optional sort, int limit) { - final ArrayList criteriaList = new ArrayList<>(criterias); + public Flux queryAll(List criterias, Optional> includeFields, Optional permission, Optional sort, int limit) { Mono> permissionGroupsMono = getCurrentUserPermissionGroupsIfRequired(permission); - return permissionGroupsMono.flatMapMany(permissionGroups -> queryAllWithPermissionGroups(criterias, includeFields, permission, sort, permissionGroups, limit)); + return permissionGroupsMono + .flatMapMany(permissionGroups -> queryAllWithPermissionGroups(criterias, includeFields, permission, sort, permissionGroups, limit)); } @Deprecated @@ -441,7 +472,8 @@ public abstract class BaseAppsmithRepositoryCEImpl { Sort sort, Set permissionGroups, int limit) { - return queryAllWithPermissionGroups(criterias, Optional.ofNullable(includeFields), Optional.ofNullable(aclPermission), Optional.ofNullable(sort), permissionGroups, limit); + return queryAllWithPermissionGroups(criterias, Optional.ofNullable(includeFields), + Optional.ofNullable(aclPermission), Optional.ofNullable(sort), permissionGroups, limit); } public Flux queryAllWithPermissionGroups(List criterias, @@ -463,7 +495,7 @@ public abstract class BaseAppsmithRepositoryCEImpl { userAcl(permissionGroups, aclPermission).ifPresent(criteria -> criteriaList.add(criteria)); andCriteria.andOperator(criteriaList.toArray(new Criteria[0])); query.addCriteria(andCriteria); - sortOptional.ifPresent(sort-> query.with(sort)); + sortOptional.ifPresent(sort -> query.with(sort)); return mongoOperations.query(this.genericDomain) .matching(query) .all() @@ -562,7 +594,7 @@ public abstract class BaseAppsmithRepositoryCEImpl { criterias.add(notDeleted()); Query query = new Query(new Criteria().andOperator(criterias)); - if(!isEmpty(projectionFieldNames)) { + if (!isEmpty(projectionFieldNames)) { projectionFieldNames.stream() .forEach(projectionFieldName -> { query.fields().include(projectionFieldName); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomApplicationRepositoryCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomApplicationRepositoryCEImpl.java index 8c6638713d..81e93a553c 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomApplicationRepositoryCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomApplicationRepositoryCEImpl.java @@ -188,7 +188,7 @@ public class CustomApplicationRepositoryCEImpl extends BaseAppsmithRepositoryImp Criteria defaultAppCriteria = where(gitApplicationMetadata + "." + fieldName(QApplication.application.gitApplicationMetadata.defaultApplicationId)).is(defaultApplicationId); Criteria branchNameCriteria = where(gitApplicationMetadata + "." + fieldName(QApplication.application.gitApplicationMetadata.branchName)).is(branchName); - return queryOne(List.of(defaultAppCriteria, branchNameCriteria), aclPermission); + return queryOne(List.of(defaultAppCriteria, branchNameCriteria), null, aclPermission); } @Override diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomDatasourceRepositoryCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomDatasourceRepositoryCEImpl.java index 90f5caeffd..57aec77a27 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomDatasourceRepositoryCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomDatasourceRepositoryCEImpl.java @@ -53,7 +53,7 @@ public class CustomDatasourceRepositoryCEImpl extends BaseAppsmithRepositoryImpl public Mono findByNameAndWorkspaceId(String name, String workspaceId, Optional aclPermission) { Criteria nameCriteria = where(fieldName(QDatasource.datasource.name)).is(name); Criteria workspaceIdCriteria = where(fieldName(QDatasource.datasource.workspaceId)).is(workspaceId); - return queryOne(List.of(nameCriteria, workspaceIdCriteria), aclPermission); + return queryOne(List.of(nameCriteria, workspaceIdCriteria), null, aclPermission); } @Override diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewPageRepositoryCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewPageRepositoryCE.java index c6fc344ee5..0d06d2d8db 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewPageRepositoryCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewPageRepositoryCE.java @@ -31,6 +31,4 @@ public interface CustomNewPageRepositoryCE extends AppsmithRepository { Mono findPageByBranchNameAndDefaultPageId(String branchName, String defaultPageId, AclPermission permission); Flux findSlugsByApplicationIds(List applicationIds, AclPermission aclPermission); - - Mono findRootApplicationIdById(String defaultPageId, AclPermission readPermission); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewPageRepositoryCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewPageRepositoryCEImpl.java index a941382e4f..ae7a37d039 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewPageRepositoryCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/repositories/ce/CustomNewPageRepositoryCEImpl.java @@ -5,10 +5,7 @@ import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.NewPage; import com.appsmith.server.domains.QLayout; import com.appsmith.server.domains.QNewPage; -import com.appsmith.server.domains.User; import com.appsmith.server.dtos.PageDTO; -import com.appsmith.server.exceptions.AppsmithError; -import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.repositories.BaseAppsmithRepositoryImpl; import com.appsmith.server.repositories.CacheableRepositoryHelper; import lombok.extern.slf4j.Slf4j; @@ -16,7 +13,6 @@ import org.springframework.data.mongodb.core.ReactiveMongoOperations; import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.data.mongodb.core.query.Criteria; import org.springframework.data.mongodb.core.query.Query; -import org.springframework.security.core.context.ReactiveSecurityContextHolder; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -54,27 +50,6 @@ public class CustomNewPageRepositoryCEImpl extends BaseAppsmithRepositoryImpl findRootApplicationIdById(String id, AclPermission permission) { - if (id == null) { - return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, FieldName.ID)); - } - return ReactiveSecurityContextHolder.getContext() - .map(ctx -> ctx.getAuthentication()) - .map(auth -> auth.getPrincipal()) - .flatMap(principal -> getAllPermissionGroupsForUser((User) principal)) - .flatMap(permissionGroups -> { - Query query = new Query(getIdCriteria(id)); - query.fields().include(FieldName.APPLICATION_ID, FieldName.DEFAULT_RESOURCES); - query.addCriteria(new Criteria().andOperator(notDeleted(), userAcl(permissionGroups, permission))); - - return mongoOperations.query(this.genericDomain) - .matching(query) - .one() - .flatMap(obj -> setUserPermissionsInObject(obj, permissionGroups)); - }); - } - @Override public Mono findByIdAndLayoutsIdAndViewMode(String id, String layoutId, AclPermission aclPermission, Boolean viewMode) { String layoutsIdKey; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionCollectionServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionCollectionServiceImpl.java index 8231fd829c..9b18081309 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionCollectionServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ActionCollectionServiceImpl.java @@ -12,7 +12,7 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.stereotype.Service; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; @Service @Slf4j diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/AnalyticsServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/AnalyticsServiceImpl.java index 40ca6025d9..d228220624 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/AnalyticsServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/AnalyticsServiceImpl.java @@ -4,6 +4,7 @@ import com.appsmith.server.configurations.CommonConfig; import com.appsmith.server.helpers.PolicyUtils; import com.appsmith.server.helpers.UserUtils; import com.appsmith.server.services.ce.AnalyticsServiceCEImpl; +import com.google.gson.Gson; import com.segment.analytics.Analytics; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -19,9 +20,10 @@ public class AnalyticsServiceImpl extends AnalyticsServiceCEImpl implements Anal CommonConfig commonConfig, ConfigService configService, PolicyUtils policyUtils, - UserUtils userUtils) { + UserUtils userUtils, + Gson gson) { - super(analytics, sessionUserService, commonConfig, configService, policyUtils, userUtils); + super(analytics, sessionUserService, commonConfig, configService, policyUtils, userUtils, gson); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApiTemplateServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApiTemplateServiceImpl.java index aa03899b22..df7f544210 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApiTemplateServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApiTemplateServiceImpl.java @@ -8,7 +8,7 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.stereotype.Service; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; @Service @Slf4j diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java index 2ee8ef1933..cec7674292 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ApplicationServiceImpl.java @@ -14,7 +14,7 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.stereotype.Service; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; @Slf4j diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/BaseService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/BaseService.java index aba3678c5d..e42ba393b3 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/BaseService.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/BaseService.java @@ -21,7 +21,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; import java.io.Serializable; import java.time.Instant; import java.util.ArrayList; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CollectionServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CollectionServiceImpl.java index 77d1a567ad..7e19d796b1 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CollectionServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CollectionServiceImpl.java @@ -8,7 +8,7 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.stereotype.Service; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; @Slf4j @Service diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CommentServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CommentServiceImpl.java index 32bb54b6b2..1fd9292e90 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CommentServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CommentServiceImpl.java @@ -16,7 +16,7 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.stereotype.Service; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; @Slf4j @Service diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CustomJSLibServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CustomJSLibServiceImpl.java index 24f943f362..70464d9050 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CustomJSLibServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/CustomJSLibServiceImpl.java @@ -2,14 +2,13 @@ package com.appsmith.server.services; import com.appsmith.server.repositories.CustomJSLibRepository; import com.appsmith.server.services.ce.CustomJSLibServiceCEImpl; +import jakarta.validation.Validator; import lombok.extern.slf4j.Slf4j; import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.stereotype.Service; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; - @Service @Slf4j public class CustomJSLibServiceImpl extends CustomJSLibServiceCEImpl implements CustomJSLibService { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceServiceImpl.java index 6063f9a360..711dc41593 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/DatasourceServiceImpl.java @@ -13,7 +13,7 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.stereotype.Service; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; @Slf4j @Service diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/GroupServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/GroupServiceImpl.java index 3b093891d8..b83c4b1fe8 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/GroupServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/GroupServiceImpl.java @@ -8,7 +8,7 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.stereotype.Service; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; @Service @Slf4j diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/HealthCheckService.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/HealthCheckService.java new file mode 100644 index 0000000000..d02e539f76 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/HealthCheckService.java @@ -0,0 +1,6 @@ +package com.appsmith.server.services; + +import com.appsmith.server.services.ce.HealthCheckServiceCE; + +public interface HealthCheckService extends HealthCheckServiceCE { +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/HealthCheckServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/HealthCheckServiceImpl.java new file mode 100644 index 0000000000..2637c5a553 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/HealthCheckServiceImpl.java @@ -0,0 +1,15 @@ +package com.appsmith.server.services; + +import com.appsmith.server.services.ce.HealthCheckServiceCEImpl; +import org.springframework.data.mongodb.core.ReactiveMongoTemplate; +import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; +import org.springframework.stereotype.Component; + + +@Component +public class HealthCheckServiceImpl extends HealthCheckServiceCEImpl implements HealthCheckService { + public HealthCheckServiceImpl(ReactiveRedisConnectionFactory reactiveRedisConnectionFactory, + ReactiveMongoTemplate reactiveMongoTemplate) { + super(reactiveRedisConnectionFactory, reactiveMongoTemplate); + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionServiceImpl.java index 589bb8ee9b..57d02926ac 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewActionServiceImpl.java @@ -10,14 +10,13 @@ import com.appsmith.server.solutions.ActionPermission; import com.appsmith.server.solutions.ApplicationPermission; import com.appsmith.server.solutions.DatasourcePermission; import com.appsmith.server.solutions.PagePermission; +import jakarta.validation.Validator; import lombok.extern.slf4j.Slf4j; import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.stereotype.Service; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; - @Service @Slf4j public class NewActionServiceImpl extends NewActionServiceCEImpl implements NewActionService { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewPageServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewPageServiceImpl.java index a364346abd..591ca4f382 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewPageServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NewPageServiceImpl.java @@ -11,7 +11,7 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.stereotype.Service; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; @Service @Slf4j diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NotificationServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NotificationServiceImpl.java index 608a1efbb8..61f1f00fb0 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NotificationServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/NotificationServiceImpl.java @@ -9,7 +9,7 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.stereotype.Service; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; @Slf4j @Service diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PermissionGroupServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PermissionGroupServiceImpl.java index 88ac695ce3..e169dbfb54 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PermissionGroupServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PermissionGroupServiceImpl.java @@ -11,7 +11,7 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.stereotype.Service; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; @Service public class PermissionGroupServiceImpl extends PermissionGroupServiceCEImpl implements PermissionGroupService { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PluginServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PluginServiceImpl.java index 0d81a6230f..8cf090ce53 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PluginServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/PluginServiceImpl.java @@ -12,7 +12,7 @@ import org.springframework.data.redis.listener.ChannelTopic; import org.springframework.stereotype.Service; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; @Slf4j @Service diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ProviderServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ProviderServiceImpl.java index 01da7b18e3..edae8af4f2 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ProviderServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ProviderServiceImpl.java @@ -8,7 +8,7 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.stereotype.Service; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; @Service @Slf4j diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/TenantServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/TenantServiceImpl.java index ce6e9966bf..7677383427 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/TenantServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/TenantServiceImpl.java @@ -7,7 +7,7 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.stereotype.Service; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; @Service public class TenantServiceImpl extends TenantServiceCEImpl implements TenantService { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ThemeServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ThemeServiceImpl.java index f8154d6360..a18dbdb813 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ThemeServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ThemeServiceImpl.java @@ -11,7 +11,7 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.stereotype.Service; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; @Slf4j @Service diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserDataServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserDataServiceImpl.java index 9eb4fc951d..952a366ecc 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserDataServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserDataServiceImpl.java @@ -11,7 +11,7 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.stereotype.Service; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; @Service public class UserDataServiceImpl extends UserDataServiceCEImpl implements UserDataService { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java index 664d819e7c..059ae89647 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/UserServiceImpl.java @@ -18,7 +18,7 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; @Slf4j @Service diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/WorkspaceServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/WorkspaceServiceImpl.java index c7111ba392..d3c2b9f0d3 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/WorkspaceServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/WorkspaceServiceImpl.java @@ -18,7 +18,7 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; import org.springframework.stereotype.Service; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; @Slf4j @Service diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ActionCollectionServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ActionCollectionServiceCEImpl.java index 9e815906ca..f6524d5bf3 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ActionCollectionServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ActionCollectionServiceCEImpl.java @@ -38,7 +38,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; import java.time.Instant; import java.util.ArrayList; import java.util.HashMap; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AnalyticsServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AnalyticsServiceCEImpl.java index ed9798ebdd..94ba848212 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AnalyticsServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AnalyticsServiceCEImpl.java @@ -14,26 +14,20 @@ import com.appsmith.server.services.ConfigService; import com.appsmith.server.services.SessionUserService; import com.google.gson.Gson; import com.segment.analytics.Analytics; -import com.segment.analytics.Log; import com.segment.analytics.messages.IdentifyMessage; -import com.segment.analytics.messages.Message; import com.segment.analytics.messages.TrackMessage; import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.lang3.ObjectUtils; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import reactor.core.publisher.Mono; import reactor.core.scheduler.Schedulers; -import java.nio.charset.Charset; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import static com.segment.analytics.internal.AnalyticsClient.getGsonInstance; - @Slf4j public class AnalyticsServiceCEImpl implements AnalyticsServiceCE { @@ -44,18 +38,22 @@ public class AnalyticsServiceCEImpl implements AnalyticsServiceCE { private final UserUtils userUtils; + private final Gson gson; + @Autowired public AnalyticsServiceCEImpl(@Autowired(required = false) Analytics analytics, SessionUserService sessionUserService, CommonConfig commonConfig, ConfigService configService, PolicyUtils policyUtils, - UserUtils userUtils) { + UserUtils userUtils, + Gson gson) { this.analytics = analytics; this.sessionUserService = sessionUserService; this.commonConfig = commonConfig; this.configService = configService; this.userUtils = userUtils; + this.gson = gson; } public boolean isActive() { @@ -160,14 +158,17 @@ public class AnalyticsServiceCEImpl implements AnalyticsServiceCE { } final String finalUserId = userId; - configService.getInstanceId().map(instanceId -> { - TrackMessage.Builder messageBuilder = TrackMessage.builder(event).userId(finalUserId); - analyticsProperties.put("originService", "appsmith-server"); - analyticsProperties.put("instanceId", instanceId); - messageBuilder = messageBuilder.properties(analyticsProperties); - analytics.enqueue(messageBuilder); - return instanceId; - }).subscribeOn(Schedulers.boundedElastic()).subscribe(); + configService.getInstanceId() + .map(instanceId -> { + TrackMessage.Builder messageBuilder = TrackMessage.builder(event).userId(finalUserId); + analyticsProperties.put("originService", "appsmith-server"); + analyticsProperties.put("instanceId", instanceId); + messageBuilder = messageBuilder.properties(analyticsProperties); + analytics.enqueue(messageBuilder); + return instanceId; + }) + .subscribeOn(Schedulers.boundedElastic()) + .subscribe(); } @Override diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApiTemplateServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApiTemplateServiceCEImpl.java index 573001bfb9..ff9a74f448 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApiTemplateServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApiTemplateServiceCEImpl.java @@ -14,7 +14,7 @@ import org.springframework.util.MultiValueMap; import reactor.core.publisher.Flux; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; @Slf4j diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationPageServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationPageServiceCEImpl.java index d0c07fa335..c80f8c4070 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationPageServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationPageServiceCEImpl.java @@ -49,6 +49,7 @@ import com.appsmith.server.solutions.PagePermission; import com.appsmith.server.solutions.WorkspacePermission; import com.google.common.base.Strings; import com.mongodb.client.result.UpdateResult; +import jakarta.annotation.Nullable; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.bson.types.ObjectId; @@ -57,7 +58,6 @@ import org.springframework.util.StringUtils; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import javax.annotation.Nullable; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Instant; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCEImpl.java index 620413cbb8..a4d53c4f15 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationServiceCEImpl.java @@ -53,7 +53,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; import java.time.Instant; import java.util.HashSet; import java.util.List; @@ -281,6 +281,7 @@ public class ApplicationServiceCEImpl extends BaseService this.setTransientFields(application1)) .flatMap(application1 -> { final Map eventData = Map.of( FieldName.APP_MODE, ApplicationMode.EDIT.toString(), diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationTemplateServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationTemplateServiceCEImpl.java index 81e3382a67..a87a3ef090 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationTemplateServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ApplicationTemplateServiceCEImpl.java @@ -1,7 +1,7 @@ package com.appsmith.server.services.ce; import com.appsmith.external.constants.AnalyticsEvents; -import com.appsmith.external.converters.GsonISOStringToInstantConverter; +import com.appsmith.external.converters.ISOStringToInstantConverter; import com.appsmith.server.configurations.CloudServicesConfig; import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.Application; @@ -183,7 +183,7 @@ public class ApplicationTemplateServiceCEImpl implements ApplicationTemplateServ .bodyToMono(String.class) .map(jsonString -> { Gson gson = new GsonBuilder() - .registerTypeAdapter(Instant.class, new GsonISOStringToInstantConverter()) + .registerTypeAdapter(Instant.class, new ISOStringToInstantConverter()) .create(); Type fileType = new TypeToken() { }.getType(); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AssetServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AssetServiceCEImpl.java index bf97540bf2..99e8284815 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AssetServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/AssetServiceCEImpl.java @@ -98,6 +98,7 @@ public class AssetServiceCEImpl implements AssetServiceCE { * This function hard-deletes (read: not archive) the asset given by the ID. It is intended to be used to delete an * old asset when a user uploads a new one. For example, when a new profile photo or a workspace logo is, * uploaded, this method is used to completely delete the old one, if any. + * * @param assetId The ID string of the asset to delete. * @return empty Mono */ @@ -145,7 +146,7 @@ public class AssetServiceCEImpl implements AssetServiceCE { } Image scaledImage = bufferedImage.getScaledInstance(dimension, dimension, Image.SCALE_SMOOTH); BufferedImage imageBuff = new BufferedImage(dimension, dimension, BufferedImage.TYPE_INT_RGB); - imageBuff.getGraphics().drawImage(scaledImage, 0, 0, new Color(0,0,0), null); + imageBuff.getGraphics().drawImage(scaledImage, 0, 0, new Color(0, 0, 0), null); ByteArrayOutputStream buffer = new ByteArrayOutputStream(); ImageIO.write(imageBuff, "jpg", buffer); byte[] data = buffer.toByteArray(); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CollectionServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CollectionServiceCEImpl.java index 8be1c5ba76..5ecd1460b1 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CollectionServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CollectionServiceCEImpl.java @@ -15,7 +15,7 @@ import org.springframework.data.mongodb.core.convert.MongoConverter; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CommentServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CommentServiceCEImpl.java index 4506c8c173..57586b7593 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CommentServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CommentServiceCEImpl.java @@ -46,7 +46,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; import java.io.IOException; import java.time.Instant; import java.util.ArrayList; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CurlImporterServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CurlImporterServiceCEImpl.java index 6765e19bdd..340f2a9340 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CurlImporterServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CurlImporterServiceCEImpl.java @@ -1,13 +1,13 @@ package com.appsmith.server.services.ce; import com.appsmith.external.models.ActionConfiguration; +import com.appsmith.external.models.ActionDTO; import com.appsmith.external.models.Datasource; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.Property; import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.NewPage; import com.appsmith.server.domains.Plugin; -import com.appsmith.external.models.ActionDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.helpers.ResponseUtils; @@ -336,11 +336,14 @@ public class CurlImporterServiceCEImpl extends BaseApiImporter implements CurlIm boolean isStateProcessed = true; if (ARG_REQUEST.equals(state)) { - // The `token` is next to `--request`. - final HttpMethod method = HttpMethod.resolve(token.toUpperCase()); - if (method == null) { + // HttpMethod now supports custom verbs as well, + // so we limit our check to non-ASCII characters as the HTTP 1.1 RFC states + // Ref: https://www.rfc-editor.org/rfc/rfc7231#section-8.1 + if (token == null || !token.chars().allMatch(c -> c < 128)) { throw new AppsmithException(AppsmithError.INVALID_CURL_METHOD, token); } + // The `token` is next to `--request`. + final HttpMethod method = HttpMethod.valueOf(token.toUpperCase()); actionConfiguration.setHttpMethod(method); } else if (ARG_HEADER.equals(state)) { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CustomJSLibServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CustomJSLibServiceCE.java index cf212086a1..25b1a14fa9 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CustomJSLibServiceCE.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CustomJSLibServiceCE.java @@ -3,17 +3,19 @@ package com.appsmith.server.services.ce; import com.appsmith.server.domains.CustomJSLib; import com.appsmith.server.dtos.CustomJSLibApplicationDTO; import com.appsmith.server.services.CrudService; +import jakarta.validation.constraints.NotNull; import reactor.core.publisher.Mono; -import javax.validation.constraints.NotNull; import java.util.List; import java.util.Set; public interface CustomJSLibServiceCE extends CrudService { Mono addJSLibToApplication(@NotNull String applicationId, @NotNull CustomJSLib jsLib, String branchName, Boolean isForceInstall); + Mono removeJSLibFromApplication(@NotNull String applicationId, @NotNull CustomJSLib jsLib, String branchName, Boolean isForceRemove); + Mono> getAllJSLibsInApplication(@NotNull String applicationId, String branchName, Boolean isViewMode); diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CustomJSLibServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CustomJSLibServiceCEImpl.java index 3fc3b1c365..f48a25d698 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CustomJSLibServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/CustomJSLibServiceCEImpl.java @@ -11,6 +11,8 @@ import com.appsmith.server.services.AnalyticsService; import com.appsmith.server.services.ApplicationService; import com.appsmith.server.services.BaseService; import com.appsmith.server.services.FeatureFlagService; +import jakarta.validation.Validator; +import jakarta.validation.constraints.NotNull; import lombok.extern.slf4j.Slf4j; import org.springframework.data.mongodb.core.ReactiveMongoTemplate; import org.springframework.data.mongodb.core.convert.MongoConverter; @@ -18,8 +20,6 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; -import javax.validation.constraints.NotNull; import java.util.Collections; import java.util.Comparator; import java.util.HashSet; @@ -35,6 +35,7 @@ public class CustomJSLibServiceCEImpl extends BaseService repository.save(jsLib).map(savedJsLib -> getDTOFromCustomJSLib(savedJsLib)))); + .switchIfEmpty(Mono.defer(() -> repository.save(jsLib).map(savedJsLib -> getDTOFromCustomJSLib(savedJsLib)))); } @Override diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/DatasourceServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/DatasourceServiceCEImpl.java index c9527dbe30..27de2cd4dc 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/DatasourceServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/DatasourceServiceCEImpl.java @@ -46,8 +46,8 @@ import reactor.core.scheduler.Scheduler; import reactor.util.function.Tuple2; import reactor.util.function.Tuples; -import javax.validation.Validator; -import javax.validation.constraints.NotNull; +import jakarta.validation.Validator; +import jakarta.validation.constraints.NotNull; import java.time.Instant; import java.util.ArrayList; import java.util.Comparator; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/GroupServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/GroupServiceCEImpl.java index 1e15fbef63..852ab2b068 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/GroupServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/GroupServiceCEImpl.java @@ -18,7 +18,7 @@ import org.springframework.util.MultiValueMap; import reactor.core.publisher.Flux; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; import java.util.Set; import java.util.stream.Collectors; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/HealthCheckServiceCE.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/HealthCheckServiceCE.java new file mode 100644 index 0000000000..58322a4483 --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/HealthCheckServiceCE.java @@ -0,0 +1,11 @@ +package com.appsmith.server.services.ce; + +import org.springframework.boot.actuate.health.Health; +import reactor.core.publisher.Mono; + +public interface HealthCheckServiceCE { + + Mono getHealth(); + Mono getRedisHealth(); + Mono getMongoHealth(); +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/HealthCheckServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/HealthCheckServiceCEImpl.java new file mode 100644 index 0000000000..edecb5794e --- /dev/null +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/HealthCheckServiceCEImpl.java @@ -0,0 +1,58 @@ +package com.appsmith.server.services.ce; + +import com.appsmith.server.exceptions.AppsmithError; +import com.appsmith.server.exceptions.AppsmithException; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.actuate.health.Health; +import org.springframework.boot.actuate.health.Status; +import org.springframework.boot.actuate.data.mongo.MongoReactiveHealthIndicator; +import org.springframework.boot.actuate.data.redis.RedisReactiveHealthIndicator; +import org.springframework.data.mongodb.core.ReactiveMongoTemplate; +import org.springframework.data.redis.connection.ReactiveRedisConnectionFactory; +import reactor.core.publisher.Mono; + +import java.time.Duration; +import java.util.concurrent.TimeoutException; +import java.util.function.Function; + + +@Slf4j +public class HealthCheckServiceCEImpl implements HealthCheckServiceCE { + + private final ReactiveRedisConnectionFactory reactiveRedisConnectionFactory; + private final ReactiveMongoTemplate reactiveMongoTemplate; + public HealthCheckServiceCEImpl(ReactiveRedisConnectionFactory reactiveRedisConnectionFactory, + ReactiveMongoTemplate reactiveMongoTemplate) { + this.reactiveRedisConnectionFactory = reactiveRedisConnectionFactory; + this.reactiveMongoTemplate = reactiveMongoTemplate; + } + + @Override + public Mono getHealth() { + return Mono.zip(getRedisHealth(), getMongoHealth()) + .map(tuple -> "All systems are Up"); + } + + @Override + public Mono getRedisHealth() { + Function healthTimeout = error -> new AppsmithException( + AppsmithError.HEALTHCHECK_TIMEOUT, "Redis"); + RedisReactiveHealthIndicator redisReactiveHealthIndicator = new RedisReactiveHealthIndicator(reactiveRedisConnectionFactory); + return redisReactiveHealthIndicator.health().timeout(Duration.ofSeconds(1)).onErrorMap(TimeoutException.class, healthTimeout); + } + + @Override + public Mono getMongoHealth() { + Function healthTimeout = error -> new AppsmithException( + AppsmithError.HEALTHCHECK_TIMEOUT, "Mongo"); + MongoReactiveHealthIndicator mongoReactiveHealthIndicator = new MongoReactiveHealthIndicator(reactiveMongoTemplate); + return mongoReactiveHealthIndicator.health().timeout(Duration.ofSeconds(1)).onErrorMap(TimeoutException.class, healthTimeout); + } + + private boolean isUp(Health health) { + if (Status.UP.equals(health.getStatus())) { + return Boolean.TRUE; + } + return Boolean.FALSE; + } +} diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/MockDataServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/MockDataServiceCEImpl.java index 9f1e553c0a..bd9be7eb7a 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/MockDataServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/MockDataServiceCEImpl.java @@ -65,7 +65,7 @@ public class MockDataServiceCEImpl implements MockDataServiceCE { } final String baseUrl = cloudServicesConfig.getBaseUrl(); - if (StringUtils.isEmpty(baseUrl)) { + if (!StringUtils.hasLength(baseUrl)) { return Mono.justOrEmpty(mockData); } @@ -110,7 +110,7 @@ public class MockDataServiceCEImpl implements MockDataServiceCE { } else { datasourceConfiguration = getPostgresDataSourceConfiguration(mockDataSource.getName(), mockDataDTO); } - if( datasourceConfiguration.getAuthentication() == null) { + if (datasourceConfiguration.getAuthentication() == null) { return Mono.error(new AppsmithException(AppsmithError.INVALID_PARAMETER, " Couldn't find any mock datasource with the given name - " + mockDataSource.getName())); } @@ -137,7 +137,7 @@ public class MockDataServiceCEImpl implements MockDataServiceCE { SSLDetails sslDetails = new SSLDetails(); Optional credentialsList = mockDataSet.getCredentials().stream().filter(cred -> cred.getDbname().equalsIgnoreCase(name)).findFirst(); - if(Boolean.TRUE.equals(credentialsList.isEmpty())) { + if (Boolean.TRUE.equals(credentialsList.isEmpty())) { return datasourceConfiguration; } @@ -176,7 +176,7 @@ public class MockDataServiceCEImpl implements MockDataServiceCE { List endpointList = new ArrayList<>(); Optional credentialsList = mockDataSet.getCredentials().stream().filter(cred -> cred.getDbname().equalsIgnoreCase(name)).findFirst(); - if(Boolean.TRUE.equals(credentialsList.isEmpty())) { + if (Boolean.TRUE.equals(credentialsList.isEmpty())) { return datasourceConfiguration; } @@ -205,16 +205,17 @@ public class MockDataServiceCEImpl implements MockDataServiceCE { /** * Tries to create the given datasource with the name, over and over again with an incremented suffix, but **only** * if the error is because of a name clash. + * * @param datasource Datasource to try create. - * @param name Name of the datasource, to which numbered suffixes will be appended. - * @param suffix Suffix used for appending, recursion artifact. Usually set to 0. + * @param name Name of the datasource, to which numbered suffixes will be appended. + * @param suffix Suffix used for appending, recursion artifact. Usually set to 0. * @return A Mono that yields the created datasource. */ private Mono createSuffixedDatasource(Datasource datasource, String name, int suffix) { final String actualName = name + (suffix == 0 ? "" : " (" + suffix + ")"); datasource.setName(actualName); String password = null; - if( datasource.getDatasourceConfiguration().getAuthentication() instanceof DBAuth) { + if (datasource.getDatasourceConfiguration().getAuthentication() instanceof DBAuth) { password = ((DBAuth) datasource.getDatasourceConfiguration().getAuthentication()).getPassword(); } final String finalPassword = password; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/NewActionServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/NewActionServiceCEImpl.java index 84e4d58539..94f7e43ed4 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/NewActionServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/NewActionServiceCEImpl.java @@ -24,9 +24,6 @@ import com.appsmith.external.models.Policy; import com.appsmith.external.models.Property; import com.appsmith.external.models.Provider; import com.appsmith.external.models.RequestParamDTO; -import com.appsmith.external.models.ActionProvider; -import com.appsmith.external.models.PluginType; -import com.appsmith.external.models.ActionDTO; import com.appsmith.external.plugins.PluginExecutor; import com.appsmith.server.acl.AclPermission; import com.appsmith.server.acl.PolicyGenerator; @@ -68,6 +65,7 @@ import com.appsmith.server.solutions.PagePermission; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.validation.Validator; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang3.ObjectUtils; @@ -89,7 +87,6 @@ import reactor.util.function.Tuple2; import reactor.util.function.Tuple5; import javax.lang.model.SourceVersion; -import javax.validation.Validator; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.time.Duration; @@ -635,6 +632,7 @@ public class NewActionServiceCEImpl extends BaseService params) { @@ -643,7 +641,7 @@ public class NewActionServiceCEImpl extends BaseService datasourceService.getValidDatasourceFromActionMono(actionDTO, - datasourcePermission.getExecutePermission())) + datasourcePermission.getExecutePermission())) .flatMap(datasource -> { // For embedded datasource, validate the datasource for each execution if (datasource.getId() == null) { @@ -701,6 +702,7 @@ public class NewActionServiceCEImpl extends BaseService invalids = datasource.getInvalids(); if (!CollectionUtils.isEmpty(invalids)) { log.error("Unable to execute actionId: {} because it's datasource is not valid. Cause: {}", - actionId, ArrayUtils.toString(invalids)); + actionId, ArrayUtils.toString(invalids)); return Mono.error(new AppsmithException(AppsmithError.INVALID_DATASOURCE, - datasource.getName(), - ArrayUtils.toString(invalids))); + datasource.getName(), + ArrayUtils.toString(invalids))); } return pluginService.findById(datasource.getPluginId()); }) @@ -724,10 +726,11 @@ public class NewActionServiceCEImpl extends BaseService getEditorConfigLabelMap (Mono datasourceMono) { + protected Mono getEditorConfigLabelMap(Mono datasourceMono) { return datasourceMono .flatMap(datasource -> { @@ -740,7 +743,7 @@ public class NewActionServiceCEImpl extends BaseService * This method validates the datasource, retrieves context and subsequently passes the payload to pluginExecutor for * further execution of the request. @@ -755,28 +758,28 @@ public class NewActionServiceCEImpl extends BaseService verifyDatasourceAndMakeRequest (ExecuteActionDTO executeActionDTO, - ActionDTO actionDTO, - Datasource datasource, - Plugin plugin, - PluginExecutor pluginExecutor, - String environmentName) { + protected Mono verifyDatasourceAndMakeRequest(ExecuteActionDTO executeActionDTO, + ActionDTO actionDTO, + Datasource datasource, + Plugin plugin, + PluginExecutor pluginExecutor, + String environmentName) { // This method will be overridden in EE branch to make use of environmentName. Mono validatedDatasourceMono = getValidatedDatasourceForActionExecution(datasource, environmentName); Mono executionMono = validatedDatasourceMono .flatMap(datasource1 -> getDatasourceContextFromValidatedDatasourceForActionExecution(datasource1, - plugin, - environmentName)) + plugin, + environmentName)) // Now that we have the context (connection details), execute the action. .flatMap(resourceContext -> validatedDatasourceMono .flatMap(datasource1 -> { final Instant requestedAt = Instant.now(); return ((Mono) pluginExecutor. executeParameterized(resourceContext.getConnection(), - executeActionDTO, - datasource1.getDatasourceConfiguration(), - actionDTO.getActionConfiguration())) + executeActionDTO, + datasource1.getDatasourceConfiguration(), + actionDTO.getActionConfiguration())) .map(actionExecutionResult -> { ActionExecutionRequest actionExecutionRequest = actionExecutionResult.getRequest(); if (actionExecutionRequest == null) { @@ -787,22 +790,23 @@ public class NewActionServiceCEImpl extends BaseService { - log.info("Looks like the connection is stale. Retrying with a fresh context."); - return deleteDatasourceContextForRetry(datasource, environmentName) - .then(executionMono); + return executionMono.onErrorResume(StaleConnectionException.class, error -> { + log.info("Looks like the connection is stale. Retrying with a fresh context."); + return deleteDatasourceContextForRetry(datasource, environmentName) + .then(executionMono); }); } /** * Validates the datasource for further execution + * * @param datasource * @return */ - protected Mono getValidatedDatasourceForActionExecution (Datasource datasource, String environmentName) { + protected Mono getValidatedDatasourceForActionExecution(Datasource datasource, String environmentName) { // the environmentName argument is not consumed over here // See EE override for usage of variable return authenticationValidator.validateAuthentication(datasource).cache(); @@ -810,13 +814,14 @@ public class NewActionServiceCEImpl extends BaseService> getDatasourceContextFromValidatedDatasourceForActionExecution - (Datasource validatedDatasource, Plugin plugin, String environmentName) { + (Datasource validatedDatasource, Plugin plugin, String environmentName) { // the environmentName argument is not consumed over here // See EE override for usage of variable if (plugin.isRemotePlugin()) { @@ -828,6 +833,7 @@ public class NewActionServiceCEImpl extends BaseService new AppsmithPluginException(AppsmithPluginError.PLUGIN_QUERY_TIMEOUT_ERROR, - actionDTO.getName(), - timeoutDuration)) + actionDTO.getName(), + timeoutDuration)) .onErrorMap(StaleConnectionException.class, error -> new AppsmithPluginException(AppsmithPluginError.PLUGIN_ERROR, - "Secondary stale connection error.")) + "Secondary stale connection error.")) .onErrorResume(e -> { log.debug("{}: In the action execution error mode.", - Thread.currentThread().getName(), e); + Thread.currentThread().getName(), e); ActionExecutionResult result = new ActionExecutionResult(); result.setBody(e.getMessage()); result.setIsExecutionSuccess(false); @@ -880,6 +886,7 @@ public class NewActionServiceCEImpl extends BaseService getActionExecutionResult( ExecuteActionDTO executeActionDTO, - Mono actionMono, - Mono actionDTOMono, - Mono datasourceMono, - Mono pluginMono, - Mono pluginExecutorMono, - AtomicReference actionName, - String actionId, - String environmentName) { + protected Mono getActionExecutionResult(ExecuteActionDTO executeActionDTO, + Mono actionMono, + Mono actionDTOMono, + Mono datasourceMono, + Mono pluginMono, + Mono pluginExecutorMono, + AtomicReference actionName, + String actionId, + String environmentName) { Mono> executeActionPublishersCache = Mono.zip(actionDTOMono, datasourceMono, pluginExecutorMono, pluginMono, actionMono).cache(); @@ -923,26 +930,26 @@ public class NewActionServiceCEImpl extends BaseService actionExecutionResultMono = verifyDatasourceAndMakeRequest(executeActionDTO, actionDTO, datasource, - plugin, pluginExecutor, environmentName) + plugin, pluginExecutor, environmentName) .timeout(Duration.ofMillis(timeoutDuration)); - return handleExecutionErrors(actionExecutionResultMono, actionDTO ,timeoutDuration ,actionId) + return handleExecutionErrors(actionExecutionResultMono, actionDTO, timeoutDuration, actionId) .elapsed() // Now send the analytics event for this execution .flatMap(tuple1 -> { - Long timeElapsed = tuple1.getT1(); - ActionExecutionResult result = tuple1.getT2(); + Long timeElapsed = tuple1.getT1(); + ActionExecutionResult result = tuple1.getT2(); - log.debug("{}: Action {} with id {} execution time : {} ms", - Thread.currentThread().getName(), - actionName.get(), - actionId, - timeElapsed - ); + log.debug("{}: Action {} with id {} execution time : {} ms", + Thread.currentThread().getName(), + actionName.get(), + actionId, + timeElapsed + ); - return sendExecuteAnalyticsEvent(actionFromDb, actionDTO, datasource, - executeActionDTO, result, timeElapsed) - .then(Mono.just(result)); + return sendExecuteAnalyticsEvent(actionFromDb, actionDTO, datasource, + executeActionDTO, result, timeElapsed) + .then(Mono.just(result)); }); }) .onErrorResume(AppsmithException.class, error -> { @@ -958,6 +965,7 @@ public class NewActionServiceCEImpl extends BaseService actionExecutionResultMono = getActionExecutionResult(executeActionDTO, - actionMono, - actionDTOMono, - datasourceMono, - pluginMono, - pluginExecutorMono, - actionName, - actionId, - environmentName); + actionMono, + actionDTOMono, + datasourceMono, + pluginMono, + pluginExecutorMono, + actionName, + actionId, + environmentName); Mono editorConfigLabelMapMono = getEditorConfigLabelMap(datasourceMono); @@ -997,7 +1005,7 @@ public class NewActionServiceCEImpl extends BaseService { if (TRUE.equals(executeActionDTO.getViewMode())) { result.setRequest(null); - } else if (result.getRequest() != null && result.getRequest().getRequestParams()!= null) { + } else if (result.getRequest() != null && result.getRequest().getRequestParams() != null) { transformRequestParams(result, labelMap); } return result; @@ -1007,6 +1015,7 @@ public class NewActionServiceCEImpl extends BaseService visitedBindings = new HashSet<>(); /* Parts in multipart request can appear in any order. In order to avoid NPE original name of the parameters along with the client-side data type are set here as it's guaranteed at this point that the part having the parameterMap is already collected. @@ -1082,8 +1093,9 @@ public class NewActionServiceCEImpl extends BaseService { String pseudoBindingName = param.getPseudoBindingName(); - param.setKey(dto.getInvertParameterMap() - .get(pseudoBindingName)); + String bindingValue = dto.getInvertParameterMap().get(pseudoBindingName); + param.setKey(bindingValue); + visitedBindings.add(bindingValue); //if the type is not an array e.g. "k1": "string" or "k1": "boolean" if (dto.getParamProperties() .get(pseudoBindingName) instanceof String) { @@ -1114,6 +1126,20 @@ public class NewActionServiceCEImpl extends BaseService { + if (!visitedBindings.contains(parameter)) { + Param newParam = new Param(parameter, ""); + params.add(newParam); + } + }); + } dto.setParams(params); return Mono.just(dto); }); @@ -1121,6 +1147,7 @@ public class NewActionServiceCEImpl extends BaseService executeAction(Flux partFlux, String branchName, String environmentName) { return createExecuteActionDTO(partFlux) .flatMap(executeActionDTO -> findByBranchNameAndDefaultActionId(branchName, - executeActionDTO.getActionId(), - actionPermission.getExecutePermission()) + executeActionDTO.getActionId(), + actionPermission.getExecutePermission()) .map(branchedAction -> { executeActionDTO.setActionId(branchedAction.getId()); return executeActionDTO; @@ -1463,7 +1490,7 @@ public class NewActionServiceCEImpl extends BaseService findByPageId(String pageId, Optional permission) { return repository.findByPageId(pageId, permission) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/NewPageServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/NewPageServiceCEImpl.java index deb589754b..efd51fcec2 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/NewPageServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/NewPageServiceCEImpl.java @@ -35,7 +35,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -595,7 +595,10 @@ public class NewPageServiceCEImpl extends BaseService reactiveTemplate, - ChannelTopic topic, - ObjectMapper objectMapper) { + Validator validator, + MongoConverter mongoConverter, + ReactiveMongoTemplate reactiveMongoTemplate, + PluginRepository repository, + AnalyticsService analyticsService, + WorkspaceService workspaceService, + PluginManager pluginManager, + ReactiveRedisTemplate reactiveTemplate, + ChannelTopic topic, + ObjectMapper objectMapper) { super(scheduler, validator, mongoConverter, reactiveMongoTemplate, repository, analyticsService); this.workspaceService = workspaceService; this.pluginManager = pluginManager; @@ -469,7 +470,7 @@ public class PluginServiceCEImpl extends BaseService> mono = Mono.fromSupplier(() -> loadTemplatesFromPlugin(plugin)) - .onErrorReturn(FileNotFoundException.class, Collections.emptyMap()) + .onErrorResume(throwable -> throwable.getCause() instanceof FileNotFoundException, throwable -> Mono.just(Collections.emptyMap())) .doOnError(throwable -> // Remove this pluginId from the cache so it is tried again next time. templateCache.remove(pluginId) diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ProviderServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ProviderServiceCEImpl.java index 7e38a83306..d9d65735eb 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ProviderServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ProviderServiceCEImpl.java @@ -15,7 +15,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; import java.util.ArrayList; import java.util.Arrays; import java.util.List; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/TenantServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/TenantServiceCEImpl.java index e3406a69d3..5209858391 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/TenantServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/TenantServiceCEImpl.java @@ -16,7 +16,7 @@ import org.springframework.util.StringUtils; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; import static com.appsmith.server.acl.AclPermission.MANAGE_TENANT; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ThemeServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ThemeServiceCEImpl.java index a432e29666..dca5fb582c 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ThemeServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/ThemeServiceCEImpl.java @@ -27,7 +27,7 @@ import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; import reactor.util.function.Tuples; -import javax.validation.Validator; +import jakarta.validation.Validator; import static com.appsmith.server.acl.AclPermission.MANAGE_THEMES; import static com.appsmith.server.acl.AclPermission.READ_THEMES; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserDataServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserDataServiceCEImpl.java index e2fb69cc30..23253c7e60 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserDataServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserDataServiceCEImpl.java @@ -35,7 +35,7 @@ import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; import java.util.ArrayList; import java.util.List; import java.util.Map; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java index def4b785d6..c318eb9d35 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/UserServiceCEImpl.java @@ -39,6 +39,7 @@ import com.appsmith.server.services.TenantService; import com.appsmith.server.services.UserDataService; import com.appsmith.server.services.WorkspaceService; import com.appsmith.server.solutions.UserChangedHandler; +import jakarta.validation.Validator; import lombok.extern.slf4j.Slf4j; import org.apache.http.NameValuePair; import org.apache.http.client.utils.URLEncodedUtils; @@ -57,7 +58,6 @@ import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; import reactor.core.scheduler.Schedulers; -import javax.validation.Validator; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.time.Duration; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/WorkspaceServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/WorkspaceServiceCEImpl.java index 0f1f79122a..04b92b9135 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/WorkspaceServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/services/ce/WorkspaceServiceCEImpl.java @@ -45,7 +45,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; import java.util.Arrays; import java.util.HashSet; import java.util.List; diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationServiceImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationServiceImpl.java index a9ff7e1e23..611f794fdf 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationServiceImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationServiceImpl.java @@ -1,6 +1,5 @@ package com.appsmith.server.solutions; -import com.appsmith.server.helpers.PolicyUtils; import com.appsmith.server.repositories.ActionCollectionRepository; import com.appsmith.server.repositories.DatasourceRepository; import com.appsmith.server.repositories.NewActionRepository; @@ -19,6 +18,7 @@ import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.ThemeService; import com.appsmith.server.services.WorkspaceService; import com.appsmith.server.solutions.ce.ImportExportApplicationServiceCEImpl; +import com.google.gson.Gson; import lombok.extern.slf4j.Slf4j; import org.springframework.context.annotation.Primary; import org.springframework.stereotype.Component; @@ -44,19 +44,19 @@ public class ImportExportApplicationServiceImpl extends ImportExportApplicationS ActionCollectionRepository actionCollectionRepository, ActionCollectionService actionCollectionService, ThemeService themeService, - PolicyUtils policyUtils, AnalyticsService analyticsService, CustomJSLibService customJSLibService, DatasourcePermission datasourcePermission, WorkspacePermission workspacePermission, ApplicationPermission applicationPermission, PagePermission pagePermission, - ActionPermission actionPermission) { + ActionPermission actionPermission, + Gson gson) { super(datasourceService, sessionUserService, newActionRepository, datasourceRepository, pluginRepository, workspaceService, applicationService, newPageService, applicationPageService, newPageRepository, newActionService, sequenceService, examplesWorkspaceCloner, actionCollectionRepository, - actionCollectionService, themeService, policyUtils, analyticsService, customJSLibService, - datasourcePermission, workspacePermission, applicationPermission, pagePermission, actionPermission); + actionCollectionService, themeService, analyticsService, customJSLibService, datasourcePermission, + workspacePermission, applicationPermission, pagePermission, actionPermission, gson); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationServiceImplV2.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationServiceImplV2.java index e75fc4f51f..4168fda2c7 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationServiceImplV2.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ImportExportApplicationServiceImplV2.java @@ -1,6 +1,5 @@ package com.appsmith.server.solutions; -import com.appsmith.server.helpers.PolicyUtils; import com.appsmith.server.repositories.ActionCollectionRepository; import com.appsmith.server.repositories.DatasourceRepository; import com.appsmith.server.repositories.NewActionRepository; @@ -18,9 +17,8 @@ import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.ThemeService; import com.appsmith.server.services.WorkspaceService; import com.appsmith.server.solutions.ce.ImportExportApplicationServiceCEImplV2; - +import com.google.gson.Gson; import lombok.extern.slf4j.Slf4j; - import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; @@ -30,33 +28,33 @@ import org.springframework.stereotype.Component; public class ImportExportApplicationServiceImplV2 extends ImportExportApplicationServiceCEImplV2 implements ImportExportApplicationService { public ImportExportApplicationServiceImplV2(DatasourceService datasourceService, - SessionUserService sessionUserService, - NewActionRepository newActionRepository, - DatasourceRepository datasourceRepository, - PluginRepository pluginRepository, - WorkspaceService workspaceService, - ApplicationService applicationService, - NewPageService newPageService, - ApplicationPageService applicationPageService, - NewPageRepository newPageRepository, - NewActionService newActionService, - SequenceService sequenceService, - ExamplesWorkspaceCloner examplesWorkspaceCloner, - ActionCollectionRepository actionCollectionRepository, - ActionCollectionService actionCollectionService, - ThemeService themeService, - PolicyUtils policyUtils, - AnalyticsService analyticsService, - DatasourcePermission datasourcePermission, - WorkspacePermission workspacePermission, - ApplicationPermission applicationPermission, - PagePermission pagePermission, - ActionPermission actionPermission) { + SessionUserService sessionUserService, + NewActionRepository newActionRepository, + DatasourceRepository datasourceRepository, + PluginRepository pluginRepository, + WorkspaceService workspaceService, + ApplicationService applicationService, + NewPageService newPageService, + ApplicationPageService applicationPageService, + NewPageRepository newPageRepository, + NewActionService newActionService, + SequenceService sequenceService, + ExamplesWorkspaceCloner examplesWorkspaceCloner, + ActionCollectionRepository actionCollectionRepository, + ActionCollectionService actionCollectionService, + ThemeService themeService, + AnalyticsService analyticsService, + DatasourcePermission datasourcePermission, + WorkspacePermission workspacePermission, + ApplicationPermission applicationPermission, + PagePermission pagePermission, + ActionPermission actionPermission, + Gson gson) { super(datasourceService, sessionUserService, newActionRepository, datasourceRepository, pluginRepository, workspaceService, applicationService, newPageService, applicationPageService, newPageRepository, newActionService, sequenceService, examplesWorkspaceCloner, actionCollectionRepository, actionCollectionService, themeService, analyticsService, datasourcePermission, - workspacePermission, applicationPermission, pagePermission, actionPermission); + workspacePermission, applicationPermission, pagePermission, actionPermission, gson); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/PageLoadActionsUtilImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/PageLoadActionsUtilImpl.java index 53254b127a..2257692c71 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/PageLoadActionsUtilImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/PageLoadActionsUtilImpl.java @@ -1,9 +1,9 @@ package com.appsmith.server.solutions; -import com.appsmith.server.services.ApplicationService; import com.appsmith.server.services.AstService; import com.appsmith.server.services.NewActionService; import com.appsmith.server.solutions.ce.PageLoadActionsUtilCEImpl; +import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -13,7 +13,8 @@ public class PageLoadActionsUtilImpl extends PageLoadActionsUtilCEImpl implement public PageLoadActionsUtilImpl(NewActionService newActionService, AstService astService, - ActionPermission actionPermission) { - super(newActionService, astService, actionPermission); + ActionPermission actionPermission, + ObjectMapper objectMapper) { + super(newActionService, astService, actionPermission, objectMapper); } } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/CreateDBTablePageSolutionCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/CreateDBTablePageSolutionCEImpl.java index fe08d69d88..3717b52888 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/CreateDBTablePageSolutionCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/CreateDBTablePageSolutionCEImpl.java @@ -1,9 +1,11 @@ package com.appsmith.server.solutions.ce; import com.appsmith.external.constants.AnalyticsEvents; -import com.appsmith.external.converters.GsonISOStringToInstantConverter; +import com.appsmith.external.converters.HttpMethodConverter; +import com.appsmith.external.converters.ISOStringToInstantConverter; import com.appsmith.external.helpers.AppsmithBeanUtils; import com.appsmith.external.models.ActionConfiguration; +import com.appsmith.external.models.ActionDTO; import com.appsmith.external.models.Datasource; import com.appsmith.external.models.DatasourceStructure; import com.appsmith.external.models.DatasourceStructure.Column; @@ -18,7 +20,6 @@ import com.appsmith.server.domains.Layout; import com.appsmith.server.domains.NewAction; import com.appsmith.server.domains.NewPage; import com.appsmith.server.domains.Plugin; -import com.appsmith.external.models.ActionDTO; import com.appsmith.server.dtos.ApplicationJson; import com.appsmith.server.dtos.CRUDPageResourceDTO; import com.appsmith.server.dtos.CRUDPageResponseDTO; @@ -47,6 +48,7 @@ import net.minidev.json.JSONObject; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.WordUtils; import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.http.HttpMethod; import org.springframework.util.CollectionUtils; import org.springframework.util.StreamUtils; import reactor.core.publisher.Flux; @@ -133,6 +135,12 @@ public class CreateDBTablePageSolutionCEImpl implements CreateDBTablePageSolutio // Pattern to match all words in the text private static final Pattern WORD_PATTERN = Pattern.compile("\\w+"); + private final Gson gson = new GsonBuilder() + .registerTypeAdapter(DatasourceStructure.Key.class, new DatasourceStructure.KeyInstanceCreator()) + .registerTypeAdapter(Instant.class, new ISOStringToInstantConverter()) + .registerTypeAdapter(HttpMethod.class, new HttpMethodConverter()) + .create(); + /** * This function will clone template page along with the actions. DatasourceStructure is used to map the * templateColumns with the datasource under consideration @@ -495,11 +503,7 @@ public class CreateDBTablePageSolutionCEImpl implements CreateDBTablePageSolutio new DefaultResourceLoader().getResource(filePath).getInputStream(), Charset.defaultCharset() ); - GsonBuilder gsonBuilder = new GsonBuilder(); - Gson gson = gsonBuilder - .registerTypeAdapter(DatasourceStructure.Key.class, new DatasourceStructure.KeyInstanceCreator()) - .registerTypeAdapter(Instant.class, new GsonISOStringToInstantConverter()) - .create(); + ApplicationJson applicationJson = gson.fromJson(jsonContent, ApplicationJson.class); return JsonSchemaMigration.migrateApplicationToLatestSchema(applicationJson); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EmailEventHandlerCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EmailEventHandlerCEImpl.java index abf7ddd2fb..79245984b1 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EmailEventHandlerCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EmailEventHandlerCEImpl.java @@ -116,7 +116,7 @@ public class EmailEventHandlerCEImpl implements EmailEventHandlerCE { event.getOriginHeader(), event.getSubscribers(), event.getPageName() - ).subscribeOn(Schedulers.elastic()) + ).subscribeOn(Schedulers.boundedElastic()) .subscribe(); } @@ -131,7 +131,7 @@ public class EmailEventHandlerCEImpl implements EmailEventHandlerCE { event.getOriginHeader(), event.getPageName() ) - .subscribeOn(Schedulers.elastic()) + .subscribeOn(Schedulers.boundedElastic()) .subscribe(); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java index 4c826b9809..efd8b06fee 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/EnvManagerCEImpl.java @@ -13,7 +13,6 @@ import com.appsmith.server.dtos.EnvChangesResponseDTO; import com.appsmith.server.dtos.TestEmailConfigRequestDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; -import com.appsmith.server.helpers.CollectionUtils; import com.appsmith.server.helpers.FileUtils; import com.appsmith.server.helpers.PolicyUtils; import com.appsmith.server.helpers.TextUtils; @@ -28,8 +27,8 @@ import com.appsmith.server.services.SessionUserService; import com.appsmith.server.services.TenantService; import com.appsmith.server.services.UserService; import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.mail.MessagingException; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; @@ -51,7 +50,6 @@ import org.springframework.web.server.ServerWebExchange; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import javax.mail.MessagingException; import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -74,7 +72,6 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; import java.util.stream.Stream; -import static com.appsmith.server.acl.AclPermission.MANAGE_TENANT; import static com.appsmith.server.constants.EnvVariables.APPSMITH_ADMIN_EMAILS; import static com.appsmith.server.constants.EnvVariables.APPSMITH_DISABLE_TELEMETRY; import static com.appsmith.server.constants.EnvVariables.APPSMITH_INSTANCE_NAME; @@ -640,19 +637,19 @@ public class EnvManagerCEImpl implements EnvManagerCE { }); } - /** - * A filter function on getAll that returns env variables which are having non-empty values - */ + /** + * A filter function on getAll that returns env variables which are having non-empty values + */ @Override public Mono> getAllNonEmpty() { return getAll().flatMap(map -> { - Map nonEmptyValuesMap = new HashMap<>(); - for (Map.Entry entry: map.entrySet()) { - if (StringUtils.hasText(entry.getValue())) { - nonEmptyValuesMap.put(entry.getKey(), entry.getValue()); - } - } - return Mono.just(nonEmptyValuesMap); + Map nonEmptyValuesMap = new HashMap<>(); + for (Map.Entry entry : map.entrySet()) { + if (StringUtils.hasText(entry.getValue())) { + nonEmptyValuesMap.put(entry.getKey(), entry.getValue()); + } + } + return Mono.just(nonEmptyValuesMap); }); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImpl.java index ed000ea866..12dbb15fd9 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImpl.java @@ -1,8 +1,8 @@ package com.appsmith.server.solutions.ce; import com.appsmith.external.constants.AnalyticsEvents; -import com.appsmith.external.converters.GsonISOStringToInstantConverter; import com.appsmith.external.helpers.Stopwatch; +import com.appsmith.external.models.ActionDTO; import com.appsmith.external.models.AuthenticationDTO; import com.appsmith.external.models.AuthenticationResponse; import com.appsmith.external.models.BaseDomain; @@ -20,16 +20,15 @@ import com.appsmith.server.constants.SerialiseApplicationObjective; import com.appsmith.server.domains.ActionCollection; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.ApplicationPage; -import com.appsmith.server.domains.Layout; import com.appsmith.server.domains.CustomJSLib; +import com.appsmith.server.domains.GitApplicationMetadata; +import com.appsmith.server.domains.Layout; import com.appsmith.server.domains.NewAction; import com.appsmith.server.domains.NewPage; import com.appsmith.server.domains.Theme; import com.appsmith.server.domains.User; import com.appsmith.server.domains.Workspace; -import com.appsmith.server.domains.GitApplicationMetadata; import com.appsmith.server.dtos.ActionCollectionDTO; -import com.appsmith.external.models.ActionDTO; import com.appsmith.server.dtos.ApplicationImportDTO; import com.appsmith.server.dtos.ApplicationJson; import com.appsmith.server.dtos.CustomJSLibApplicationDTO; @@ -38,7 +37,6 @@ import com.appsmith.server.dtos.PageDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.helpers.DefaultResourcesUtils; -import com.appsmith.server.helpers.PolicyUtils; import com.appsmith.server.helpers.TextUtils; import com.appsmith.server.migrations.ApplicationVersion; import com.appsmith.server.migrations.JsonSchemaMigration; @@ -67,7 +65,6 @@ import com.appsmith.server.solutions.ExamplesWorkspaceCloner; import com.appsmith.server.solutions.PagePermission; import com.appsmith.server.solutions.WorkspacePermission; import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -98,21 +95,19 @@ import java.util.Map; import java.util.Set; import java.util.stream.Collectors; +import static com.appsmith.external.constants.GitConstants.NAME_SEPARATOR; import static com.appsmith.external.helpers.AppsmithBeanUtils.copyNestedNonNullProperties; -import static com.appsmith.server.acl.AclPermission.MANAGE_ACTIONS; -import static com.appsmith.server.acl.AclPermission.MANAGE_PAGES; -import static com.appsmith.server.acl.AclPermission.READ_ACTIONS; -import static com.appsmith.server.acl.AclPermission.READ_PAGES; import static com.appsmith.server.acl.AclPermission.READ_THEMES; import static com.appsmith.server.constants.ResourceModes.EDIT; import static com.appsmith.server.constants.ResourceModes.VIEW; -import static com.appsmith.external.constants.GitConstants.NAME_SEPARATOR; import static java.lang.Boolean.TRUE; @Slf4j @RequiredArgsConstructor public class ImportExportApplicationServiceCEImpl implements ImportExportApplicationServiceCE { + private static final Set ALLOWED_CONTENT_TYPES = Set.of(MediaType.APPLICATION_JSON); + private static final String INVALID_JSON_FILE = "invalid json file"; private final DatasourceService datasourceService; private final SessionUserService sessionUserService; private final NewActionRepository newActionRepository; @@ -129,7 +124,6 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica private final ActionCollectionRepository actionCollectionRepository; private final ActionCollectionService actionCollectionService; private final ThemeService themeService; - private final PolicyUtils policyUtils; private final AnalyticsService analyticsService; private final CustomJSLibService customJSLibService; private final DatasourcePermission datasourcePermission; @@ -137,9 +131,7 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica private final ApplicationPermission applicationPermission; private final PagePermission pagePermission; private final ActionPermission actionPermission; - - private static final Set ALLOWED_CONTENT_TYPES = Set.of(MediaType.APPLICATION_JSON); - private static final String INVALID_JSON_FILE = "invalid json file"; + private final Gson gson; /** * This function will give the application resource to rebuild the application in import application flow @@ -540,10 +532,6 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica public Mono getApplicationFile(String applicationId, String branchName) { return this.exportApplicationById(applicationId, branchName) .map(applicationJson -> { - Gson gson = new GsonBuilder() - .registerTypeAdapter(Instant.class, new GsonISOStringToInstantConverter()) - .create(); - String stringifiedFile = gson.toJson(applicationJson); String applicationName = applicationJson.getExportedApplication().getName(); Object jsonObject = gson.fromJson(stringifiedFile, Object.class); @@ -596,9 +584,6 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica Mono importedApplicationMono = stringifiedFile .flatMap(data -> { - Gson gson = new GsonBuilder() - .registerTypeAdapter(Instant.class, new GsonISOStringToInstantConverter()) - .create(); /* // Use JsonObject to migrate when we remove some field from the collection which is being exported JsonObject json = gson.fromJson(data, JsonObject.class); @@ -795,11 +780,7 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica existingDatasource.setStructure(null); // Don't update the datasource configuration for already available datasources existingDatasource.setDatasourceConfiguration(null); - return datasourceService.update(existingDatasource.getId(), existingDatasource) - .map(datasource1 -> { - datasourceMap.put(importedDatasourceName, datasource1.getId()); - return datasource1; - }); + return datasourceService.update(existingDatasource.getId(), existingDatasource); } // This is explicitly copied over from the map we created before @@ -816,19 +797,18 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica updateAuthenticationDTO(datasource, decryptedFields); } - return createUniqueDatasourceIfNotPresent(existingDatasourceFlux, datasource, workspaceId) - .map(datasource1 -> { - datasourceMap.put(importedDatasourceName, datasource1.getId()); - return datasource1; - }); + return createUniqueDatasourceIfNotPresent(existingDatasourceFlux, datasource, workspaceId); }); }) - .then( + .collectMap(Datasource::getName, Datasource::getId) + .flatMap(map -> { + + datasourceMap.putAll(map); // 1. Assign the policies for the imported application // 2. Check for possible duplicate names, // 3. Save the updated application - Mono.just(importedApplication) + return Mono.just(importedApplication) .zipWith(currUserMono) .map(objects -> { Application application = objects.getT1(); @@ -887,7 +867,8 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica }); } return applicationPageService.createOrUpdateSuffixedApplication(application, application.getName(), 0); - }) + }); + } ) .flatMap(savedApp -> importThemes(savedApp, importedDoc, appendToApp)) .flatMap(savedApp -> { @@ -1000,7 +981,7 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica Iterator publishedPagesItr; // Remove the newly added pages from merge app flow. Keep only the existing page from the old app - if(appendToApp) { + if (appendToApp) { List existingPagesId = savedApp.getPublishedPages().stream().map(applicationPage -> applicationPage.getId()).collect(Collectors.toList()); List publishedApplicationPages = publishedPages.stream().filter(applicationPage -> existingPagesId.contains(applicationPage.getId())).collect(Collectors.toList()); applicationPages.replace(VIEW, publishedApplicationPages); @@ -2030,16 +2011,13 @@ public class ImportExportApplicationServiceCEImpl implements ImportExportApplica ? new DecryptedSensitiveFields() : new DecryptedSensitiveFields(authentication.getAuthenticationResponse()); - if (authentication instanceof DBAuth) { - DBAuth auth = (DBAuth) authentication; + if (authentication instanceof DBAuth auth) { dsDecryptedFields.setPassword(auth.getPassword()); dsDecryptedFields.setDbAuth(auth); - } else if (authentication instanceof OAuth2) { - OAuth2 auth = (OAuth2) authentication; + } else if (authentication instanceof OAuth2 auth) { dsDecryptedFields.setPassword(auth.getClientSecret()); dsDecryptedFields.setOpenAuth2(auth); - } else if (authentication instanceof BasicAuth) { - BasicAuth auth = (BasicAuth) authentication; + } else if (authentication instanceof BasicAuth auth) { dsDecryptedFields.setPassword(auth.getPassword()); dsDecryptedFields.setBasicAuth(auth); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImplV2.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImplV2.java index 1157431e9f..d41671eb8b 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImplV2.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ImportExportApplicationServiceCEImplV2.java @@ -1,8 +1,8 @@ package com.appsmith.server.solutions.ce; import com.appsmith.external.constants.AnalyticsEvents; -import com.appsmith.external.converters.GsonISOStringToInstantConverter; import com.appsmith.external.helpers.Stopwatch; +import com.appsmith.external.models.ActionDTO; import com.appsmith.external.models.AuthenticationDTO; import com.appsmith.external.models.AuthenticationResponse; import com.appsmith.external.models.BaseDomain; @@ -21,15 +21,14 @@ import com.appsmith.server.constants.SerialiseApplicationObjective; import com.appsmith.server.domains.ActionCollection; import com.appsmith.server.domains.Application; import com.appsmith.server.domains.ApplicationPage; +import com.appsmith.server.domains.GitApplicationMetadata; import com.appsmith.server.domains.Layout; import com.appsmith.server.domains.NewAction; import com.appsmith.server.domains.NewPage; import com.appsmith.server.domains.Theme; import com.appsmith.server.domains.User; import com.appsmith.server.domains.Workspace; -import com.appsmith.server.domains.GitApplicationMetadata; import com.appsmith.server.dtos.ActionCollectionDTO; -import com.appsmith.external.models.ActionDTO; import com.appsmith.server.dtos.ApplicationImportDTO; import com.appsmith.server.dtos.ApplicationJson; import com.appsmith.server.dtos.ExportFileDTO; @@ -64,7 +63,6 @@ import com.appsmith.server.solutions.ExamplesWorkspaceCloner; import com.appsmith.server.solutions.PagePermission; import com.appsmith.server.solutions.WorkspacePermission; import com.google.gson.Gson; -import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -94,11 +92,11 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; +import static com.appsmith.external.constants.GitConstants.NAME_SEPARATOR; import static com.appsmith.external.helpers.AppsmithBeanUtils.copyNestedNonNullProperties; import static com.appsmith.server.acl.AclPermission.READ_THEMES; import static com.appsmith.server.constants.ResourceModes.EDIT; import static com.appsmith.server.constants.ResourceModes.VIEW; -import static com.appsmith.external.constants.GitConstants.NAME_SEPARATOR; import static java.lang.Boolean.TRUE; @Slf4j @@ -127,6 +125,7 @@ public class ImportExportApplicationServiceCEImplV2 implements ImportExportAppli private final ApplicationPermission applicationPermission; private final PagePermission pagePermission; private final ActionPermission actionPermission; + private final Gson gson; private static final Set ALLOWED_CONTENT_TYPES = Set.of(MediaType.APPLICATION_JSON); private static final String INVALID_JSON_FILE = "invalid json file"; @@ -512,10 +511,6 @@ public class ImportExportApplicationServiceCEImplV2 implements ImportExportAppli public Mono getApplicationFile(String applicationId, String branchName) { return this.exportApplicationById(applicationId, branchName) .map(applicationJson -> { - Gson gson = new GsonBuilder() - .registerTypeAdapter(Instant.class, new GsonISOStringToInstantConverter()) - .create(); - String stringifiedFile = gson.toJson(applicationJson); String applicationName = applicationJson.getExportedApplication().getName(); Object jsonObject = gson.fromJson(stringifiedFile, Object.class); @@ -568,9 +563,6 @@ public class ImportExportApplicationServiceCEImplV2 implements ImportExportAppli Mono importedApplicationMono = stringifiedFile .flatMap(data -> { - Gson gson = new GsonBuilder() - .registerTypeAdapter(Instant.class, new GsonISOStringToInstantConverter()) - .create(); /* // Use JsonObject to migrate when we remove some field from the collection which is being exported JsonObject json = gson.fromJson(data, JsonObject.class); @@ -764,11 +756,7 @@ public class ImportExportApplicationServiceCEImplV2 implements ImportExportAppli existingDatasource.setStructure(null); // Don't update the datasource configuration for already available datasources existingDatasource.setDatasourceConfiguration(null); - return datasourceService.update(existingDatasource.getId(), existingDatasource) - .map(datasource1 -> { - datasourceMap.put(importedDatasourceName, datasource1.getId()); - return datasource1; - }); + return datasourceService.update(existingDatasource.getId(), existingDatasource); } // This is explicitly copied over from the map we created before @@ -785,19 +773,17 @@ public class ImportExportApplicationServiceCEImplV2 implements ImportExportAppli updateAuthenticationDTO(datasource, decryptedFields); } - return createUniqueDatasourceIfNotPresent(existingDatasourceFlux, datasource, workspaceId) - .map(datasource1 -> { - datasourceMap.put(importedDatasourceName, datasource1.getId()); - return datasource1; - }); + return createUniqueDatasourceIfNotPresent(existingDatasourceFlux, datasource, workspaceId); }); }) - .then( + .collectMap(Datasource::getName, Datasource::getId) + .flatMap(map -> { + datasourceMap.putAll(map); // 1. Assign the policies for the imported application // 2. Check for possible duplicate names, // 3. Save the updated application - Mono.just(importedApplication) + return Mono.just(importedApplication) .zipWith(currUserMono) .map(objects -> { Application application = objects.getT1(); @@ -856,7 +842,8 @@ public class ImportExportApplicationServiceCEImplV2 implements ImportExportAppli }); } return applicationPageService.createOrUpdateSuffixedApplication(application, application.getName(), 0); - }) + }); + } ) .flatMap(savedApp -> importThemes(savedApp, importedDoc, appendToApp)) .flatMap(savedApp -> { diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/PageLoadActionsUtilCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/PageLoadActionsUtilCEImpl.java index d7cd479515..6bf45f4f70 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/PageLoadActionsUtilCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/PageLoadActionsUtilCEImpl.java @@ -49,10 +49,9 @@ import static java.lang.Boolean.TRUE; public class PageLoadActionsUtilCEImpl implements PageLoadActionsUtilCE { private final NewActionService newActionService; - private final AstService astService; private final ActionPermission actionPermission; - private final ObjectMapper objectMapper = new ObjectMapper(); + private final ObjectMapper objectMapper; /** * The following regex finds the immediate parent of an entity path. diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ReleaseNotesServiceCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ReleaseNotesServiceCEImpl.java index 0c13f6ba0c..af897033af 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ReleaseNotesServiceCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/ReleaseNotesServiceCEImpl.java @@ -126,7 +126,7 @@ public class ReleaseNotesServiceCEImpl implements ReleaseNotesServiceCE { public void refreshReleaseNotes() { cacheExpiryTime = null; // Bust the release notes cache to force fetching again. getReleaseNodes() - .subscribeOn(Schedulers.elastic()) + .subscribeOn(Schedulers.boundedElastic()) .subscribe(); } diff --git a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/UserChangedHandlerCEImpl.java b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/UserChangedHandlerCEImpl.java index ab8b446a37..8100d28930 100644 --- a/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/UserChangedHandlerCEImpl.java +++ b/app/server/appsmith-server/src/main/java/com/appsmith/server/solutions/ce/UserChangedHandlerCEImpl.java @@ -39,15 +39,15 @@ public class UserChangedHandlerCEImpl implements UserChangedHandlerCE { final User user = event.getUser(); log.debug("Handling user document changes {}", user); updateNameInComments(user) - .subscribeOn(Schedulers.elastic()) + .subscribeOn(Schedulers.boundedElastic()) .subscribe(); updateNameInUserRoles(user) - .subscribeOn(Schedulers.elastic()) + .subscribeOn(Schedulers.boundedElastic()) .subscribe(); updateNameInNotifications(user) - .subscribeOn(Schedulers.elastic()) + .subscribeOn(Schedulers.boundedElastic()) .subscribe(); } @@ -56,7 +56,7 @@ public class UserChangedHandlerCEImpl implements UserChangedHandlerCE { public void handle(UserPhotoChangedEvent event) { log.debug("Handling user photo changes {}", event.getUserId()); updatePhotoIdInComments(event.getUserId(), event.getPhotoAssetId()) - .subscribeOn(Schedulers.elastic()) + .subscribeOn(Schedulers.boundedElastic()) .subscribe(); } diff --git a/app/server/appsmith-server/src/main/resources/application-production.properties b/app/server/appsmith-server/src/main/resources/application-production.properties new file mode 100644 index 0000000000..b4cdd86c2c --- /dev/null +++ b/app/server/appsmith-server/src/main/resources/application-production.properties @@ -0,0 +1,2 @@ +# MongoDB Application Database +spring.data.mongodb.uri = ${APPSMITH_MONGODB_URI} \ No newline at end of file diff --git a/app/server/appsmith-server/src/main/resources/application.properties b/app/server/appsmith-server/src/main/resources/application.properties index f3b814b45a..fd55deb063 100644 --- a/app/server/appsmith-server/src/main/resources/application.properties +++ b/app/server/appsmith-server/src/main/resources/application.properties @@ -4,26 +4,24 @@ server.port=${PORT:8080} server.shutdown=graceful spring.lifecycle.timeout-per-shutdown-phase=20s +spring.profiles.active=${ACTIVE_PROFILE:production} + # This property allows us to override beans during testing. This is useful when we want to set different configurations # and different parameters during test as compared to production. If this property is disabled, some tests will fail. spring.main.allow-bean-definition-overriding=true +spring.data.redis.repositories.enabled=false + +#spring.main.web-application-type=reactive # This property allows the server to run behind a proxy server and still resolve all the urls correctly server.forward-headers-strategy=NATIVE spring.data.mongodb.auto-index-creation=false spring.data.mongodb.authentication-database=admin -mongock.change-logs-scan-package=com.appsmith.server.migrations # Ensures that the size of the request object that we handle is controlled. By default it's 212KB. spring.codec.max-in-memory-size=100MB appsmith.codec.max-in-memory-size=${APPSMITH_CODEC_SIZE:100} -# MongoDB Application Database -spring.data.mongodb.uri = ${APPSMITH_MONGODB_URI} - -# embedded mongo DB version which is used during junit tests -spring.mongodb.embedded.version=5.0.16 - # Log properties logging.level.root=info logging.level.com.appsmith=debug @@ -56,7 +54,7 @@ sentry.debug=off sentry.environment=${APPSMITH_SENTRY_ENVIRONMENT:} # Redis Properties -spring.redis.url=${APPSMITH_REDIS_URL} +spring.data.redis.url=${APPSMITH_REDIS_URL} # Mail Properties # Email defaults to false, because, when true and the other SMTP properties are not set, Spring will try to use a @@ -91,7 +89,7 @@ encrypt.salt=${APPSMITH_ENCRYPTION_SALT:} # The following configurations are to help support prometheus scraping for monitoring management.endpoints.web.exposure.include=prometheus management.metrics.web.server.request.autotime.enabled=true -management.metrics.export.prometheus.descriptions=true +management.prometheus.metrics.export.descriptions=true management.metrics.web.server.request.ignore-trailing-slash=true management.metrics.web.server.request.autotime.percentiles=0.5, 0.9, 0.95, 0.99 management.metrics.web.server.request.autotime.percentiles-histogram=true diff --git a/app/server/appsmith-server/src/main/resources/features/init-flags.yml b/app/server/appsmith-server/src/main/resources/features/init-flags.yml index 4ad809badf..e88b9e68c3 100644 --- a/app/server/appsmith-server/src/main/resources/features/init-flags.yml +++ b/app/server/appsmith-server/src/main/resources/features/init-flags.yml @@ -80,6 +80,15 @@ ff4j: - name: emailDomains value: appsmith.com,moolya.com + - uid: MULTIPLE_PANES + enable: true + description: Have multiple panes in the Appsmith IDE + flipstrategy: + class: com.appsmith.server.featureflags.strategies.EmailBasedRolloutStrategy + param: + - name: emails + value: multipanes@appsmith.com,ndx@appsmith.com + # Put EE flags below this line, to avoid conflicts. - uid: RBAC enable: true diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/ServerApplicationTests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/ServerApplicationTests.java index 150e1700a4..882b4a1c48 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/ServerApplicationTests.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/ServerApplicationTests.java @@ -1,12 +1,10 @@ package com.appsmith.server; import org.junit.jupiter.api.Test; -import org.junit.platform.suite.api.Suite; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest -@Suite public class ServerApplicationTests { @Test diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/configurations/EmbeddedMongoConfig.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/configurations/EmbeddedMongoConfig.java deleted file mode 100644 index 0bdb6fd5c4..0000000000 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/configurations/EmbeddedMongoConfig.java +++ /dev/null @@ -1,20 +0,0 @@ -package com.appsmith.server.configurations; - -import de.flapdoodle.embed.mongo.config.ImmutableMongodConfig; -import de.flapdoodle.embed.mongo.config.MongodConfig; -import de.flapdoodle.embed.mongo.distribution.Version; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -import java.io.IOException; - -@Configuration -public class EmbeddedMongoConfig { - @Bean - public ImmutableMongodConfig prepareMongodConfig() throws IOException { - ImmutableMongodConfig mongoConfigConfig = MongodConfig.builder() - .version(Version.Main.V4_2) - .build(); - return mongoConfigConfig; - } -} diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/configurations/SeedMongoData.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/configurations/SeedMongoData.java index cf7fb4bbf5..dbd5d6c612 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/configurations/SeedMongoData.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/configurations/SeedMongoData.java @@ -24,7 +24,6 @@ import com.appsmith.server.repositories.PluginRepository; import com.appsmith.server.repositories.TenantRepository; import com.appsmith.server.repositories.UserRepository; import com.appsmith.server.repositories.WorkspaceRepository; -import com.appsmith.server.services.UserService; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.ApplicationRunner; import org.springframework.context.annotation.Bean; @@ -68,8 +67,6 @@ public class SeedMongoData { PluginRepository pluginRepository, ReactiveMongoTemplate mongoTemplate, TenantRepository tenantRepository, - UserService userService, - CommonConfig commonConfig, PermissionGroupRepository permissionGroupRepository, PolicyUtils policyUtils) { diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/converters/GsonISOStringToInstantConverterTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/converters/ISOStringToInstantConverterTest.java similarity index 90% rename from app/server/appsmith-server/src/test/java/com/appsmith/server/converters/GsonISOStringToInstantConverterTest.java rename to app/server/appsmith-server/src/test/java/com/appsmith/server/converters/ISOStringToInstantConverterTest.java index 5e58d7901d..690cb8f017 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/converters/GsonISOStringToInstantConverterTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/converters/ISOStringToInstantConverterTest.java @@ -1,7 +1,7 @@ package com.appsmith.server.converters; -import com.appsmith.external.converters.GsonISOStringToInstantConverter; +import com.appsmith.external.converters.ISOStringToInstantConverter; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.google.gson.reflect.TypeToken; @@ -14,13 +14,13 @@ import java.time.Instant; import static org.assertj.core.api.Assertions.assertThat; -public class GsonISOStringToInstantConverterTest { +public class ISOStringToInstantConverterTest { private Gson gson; @BeforeEach public void setUp() { gson = new GsonBuilder() - .registerTypeAdapter(Instant.class, new GsonISOStringToInstantConverter()) + .registerTypeAdapter(Instant.class, new ISOStringToInstantConverter()) .create(); } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/git/FileFormatMigrationTests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/git/FileFormatMigrationTests.java index ef652ce70c..c7a3670d33 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/git/FileFormatMigrationTests.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/git/FileFormatMigrationTests.java @@ -12,6 +12,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.data.mongo.AutoConfigureDataMongo; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.annotation.DirtiesContext; @@ -23,6 +24,7 @@ import static org.assertj.core.api.Assertions.assertThat; @Slf4j @ExtendWith(SpringExtension.class) +@AutoConfigureDataMongo @SpringBootTest @DirtiesContext public class FileFormatMigrationTests { diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/GitFileUtilsTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/GitFileUtilsTest.java index dd3b7fa52c..18b7cc7b8a 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/GitFileUtilsTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/helpers/GitFileUtilsTest.java @@ -45,20 +45,18 @@ import static org.assertj.core.api.Assertions.assertThat; @DirtiesContext public class GitFileUtilsTest { + private static final Path localRepoPath = Path.of("localRepoPath"); + private static final String filePath = "test_assets/ImportExportServiceTest/valid-application.json"; @MockBean FileInterface fileInterface; - @Autowired GitFileUtils gitFileUtils; - @Autowired AnalyticsService analyticsService; - @Autowired SessionUserService userService; - - private static String filePath = "test_assets/ImportExportServiceTest/valid-application.json"; - private static final Path localRepoPath = Path.of("localRepoPath"); + @Autowired + Gson gson; private Mono createAppJson(String filePath) { FilePart filePart = Mockito.mock(FilePart.class, Mockito.RETURNS_DEEP_STUBS); @@ -82,7 +80,6 @@ public class GitFileUtilsTest { return stringifiedFile .map(data -> { - Gson gson = new Gson(); return gson.fromJson(data, ApplicationJson.class); }) .map(JsonSchemaMigration::migrateApplicationToLatestSchema); diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionCollectionServiceImplTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionCollectionServiceImplTest.java index a2d6bc8907..90356bf540 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionCollectionServiceImplTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ActionCollectionServiceImplTest.java @@ -46,7 +46,7 @@ import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; import reactor.test.StepVerifier; -import javax.validation.Validator; +import jakarta.validation.Validator; import java.io.File; import java.io.IOException; import java.time.Instant; diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/CurlImporterServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/CurlImporterServiceTest.java index 864bd4e9f6..2dc2605f9c 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/CurlImporterServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/CurlImporterServiceTest.java @@ -1,6 +1,7 @@ package com.appsmith.server.services; import com.appsmith.external.models.ActionConfiguration; +import com.appsmith.external.models.ActionDTO; import com.appsmith.external.models.Property; import com.appsmith.external.plugins.PluginExecutor; import com.appsmith.server.acl.AclPermission; @@ -9,7 +10,6 @@ import com.appsmith.server.domains.NewAction; import com.appsmith.server.domains.NewPage; import com.appsmith.server.domains.User; import com.appsmith.server.domains.Workspace; -import com.appsmith.external.models.ActionDTO; import com.appsmith.server.dtos.PageDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; @@ -764,7 +764,7 @@ public class CurlImporterServiceTest { assertMethod(action, HttpMethod.GET); assertUrl(action, "http://httpbin.org"); assertPath(action, "/get"); - assertHeaders(action,new Property("Accept", "application/json")); + assertHeaders(action, new Property("Accept", "application/json")); assertEmptyBody(action); } @@ -822,18 +822,14 @@ public class CurlImporterServiceTest { @Test public void importInvalidMethod() { - assertThatThrownBy(() -> { - curlImporterService.curlToAction("curl -X invalid-method http://httpbin.org/get"); - }) + assertThatThrownBy(() -> curlImporterService.curlToAction("curl -X incorrect-charactèrs http://httpbin.org/get")) .isInstanceOf(AppsmithException.class) .matches(err -> ((AppsmithException) err).getError() == AppsmithError.INVALID_CURL_METHOD); } @Test public void importInvalidHeader() { - assertThatThrownBy(() -> { - curlImporterService.curlToAction("curl -H x-custom http://httpbin.org/headers"); - }) + assertThatThrownBy(() -> curlImporterService.curlToAction("curl -H x-custom http://httpbin.org/headers")) .isInstanceOf(AppsmithException.class) .matches(err -> ((AppsmithException) err).getError() == AppsmithError.INVALID_CURL_HEADER); } @@ -859,14 +855,15 @@ public class CurlImporterServiceTest { ActionDTO actionDTO = curlImporterService.curlToAction(cURLCommand, name); assertThat(actionDTO).isNotNull(); assertThat(actionDTO.getActionConfiguration()).isNotNull(); - Map map = actionDTO.getActionConfiguration().getFormData(); + Map map = actionDTO.getActionConfiguration().getFormData(); - assert(map != null); - assert(!map.isEmpty()); - assert(map.containsKey(API_CONTENT_TYPE)); - assert(map.get(API_CONTENT_TYPE).equals(contentType)); + assert (map != null); + assert (!map.isEmpty()); + assert (map.containsKey(API_CONTENT_TYPE)); + assert (map.get(API_CONTENT_TYPE).equals(contentType)); } + // Assertion utilities for working with Action assertions. private static void assertMethod(ActionDTO action, HttpMethod method) { assertThat(action.getActionConfiguration().getHttpMethod()).isEqualByComparingTo(method); @@ -896,7 +893,7 @@ public class CurlImporterServiceTest { // this implementation only works if Property has a subclass of object which works implements equal function. // let's compare sizes of both first if (action.getActionConfiguration().getHeaders().size() != headers.length) { - assert(false); + assert (false); } HashMap> headerStore = new HashMap<>(); @@ -915,11 +912,11 @@ public class CurlImporterServiceTest { List headerStorePropertyList; // compare the hashMap headerStore with the varargs header - for ( int i = 0; i < headers.length; i++) { + for (int i = 0; i < headers.length; i++) { String key = headers[i].getKey().toLowerCase(); if (!headerStore.containsKey(key)) { - assert(false); + assert (false); } boolean matchFound = false; @@ -942,12 +939,12 @@ public class CurlImporterServiceTest { continue; } - assert(false); + assert (false); } // if headerStore has keys then it would mean that there are more headers than expected; if (headerStore.size() != 0) { - assert(false); + assert (false); } // if all header matches then only it will reach here. } @@ -955,6 +952,7 @@ public class CurlImporterServiceTest { private static void assertEmptyBody(ActionDTO action) { assertThat(action.getActionConfiguration().getBody()).isNullOrEmpty(); } + private static void assertEmptyBodyFormData(ActionDTO action) { assertThat(action.getActionConfiguration().getBodyFormData()).isNullOrEmpty(); } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/DatasourceServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/DatasourceServiceTest.java index 9cf43ac8aa..f56cb961ec 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/DatasourceServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/DatasourceServiceTest.java @@ -1,6 +1,7 @@ package com.appsmith.server.services; import com.appsmith.external.models.ActionConfiguration; +import com.appsmith.external.models.ActionDTO; import com.appsmith.external.models.Connection; import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.Datasource; @@ -20,7 +21,6 @@ import com.appsmith.server.domains.PermissionGroup; import com.appsmith.server.domains.Plugin; import com.appsmith.server.domains.User; import com.appsmith.server.domains.Workspace; -import com.appsmith.external.models.ActionDTO; import com.appsmith.server.dtos.PageDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/GitServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/GitServiceTest.java index 6e319d7d7b..9b872d8cc6 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/GitServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/GitServiceTest.java @@ -5,10 +5,12 @@ import com.appsmith.external.dtos.GitStatusDTO; import com.appsmith.external.dtos.MergeStatusDTO; import com.appsmith.external.git.GitExecutor; import com.appsmith.external.models.ActionConfiguration; +import com.appsmith.external.models.ActionDTO; import com.appsmith.external.models.Datasource; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.DefaultResources; import com.appsmith.external.models.JSValue; +import com.appsmith.external.models.PluginType; import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.ActionCollection; import com.appsmith.server.domains.Application; @@ -19,12 +21,10 @@ import com.appsmith.server.domains.GitProfile; import com.appsmith.server.domains.Layout; import com.appsmith.server.domains.NewAction; import com.appsmith.server.domains.NewPage; -import com.appsmith.external.models.PluginType; import com.appsmith.server.domains.Theme; import com.appsmith.server.domains.User; import com.appsmith.server.domains.Workspace; import com.appsmith.server.dtos.ActionCollectionDTO; -import com.appsmith.external.models.ActionDTO; import com.appsmith.server.dtos.ApplicationImportDTO; import com.appsmith.server.dtos.ApplicationJson; import com.appsmith.server.dtos.GitCommitDTO; @@ -107,6 +107,9 @@ public class GitServiceTest { @Autowired GitService gitService; + @Autowired + Gson gson; + @Autowired WorkspaceService workspaceService; @@ -225,7 +228,6 @@ public class GitServiceTest { return stringifiedFile .map(data -> { - Gson gson = new Gson(); return gson.fromJson(data, ApplicationJson.class); }) .map(JsonSchemaMigration::migrateApplicationToLatestSchema); diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/NotificationServiceImplTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/NotificationServiceImplTest.java index d015c8f0b3..bc1e2e5574 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/NotificationServiceImplTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/NotificationServiceImplTest.java @@ -24,7 +24,7 @@ import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; import reactor.test.StepVerifier; -import javax.validation.Validator; +import jakarta.validation.Validator; import java.time.Instant; import java.util.ArrayList; import java.util.List; diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ActionServiceCE_Test.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ActionServiceCE_Test.java index 6816e9dd8d..23d46917a1 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ActionServiceCE_Test.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ActionServiceCE_Test.java @@ -9,6 +9,7 @@ import com.appsmith.external.helpers.AppsmithBeanUtils; import com.appsmith.external.helpers.AppsmithEventContext; import com.appsmith.external.helpers.AppsmithEventContextType; import com.appsmith.external.models.ActionConfiguration; +import com.appsmith.external.models.ActionDTO; import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.external.models.Datasource; import com.appsmith.external.models.DatasourceConfiguration; @@ -31,7 +32,6 @@ import com.appsmith.server.domains.PermissionGroup; import com.appsmith.server.domains.Plugin; import com.appsmith.server.domains.User; import com.appsmith.server.domains.Workspace; -import com.appsmith.external.models.ActionDTO; import com.appsmith.server.dtos.ActionMoveDTO; import com.appsmith.server.dtos.ActionViewDTO; import com.appsmith.server.dtos.ApplicationAccessDTO; diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ApplicationServiceCETest.java similarity index 97% rename from app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java rename to app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ApplicationServiceCETest.java index aa16120797..10793194f7 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ApplicationServiceTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/ApplicationServiceCETest.java @@ -1,4 +1,4 @@ -package com.appsmith.server.services; +package com.appsmith.server.services.ce; import com.appsmith.external.helpers.AppsmithBeanUtils; import com.appsmith.external.models.ActionConfiguration; @@ -44,6 +44,19 @@ import com.appsmith.server.repositories.NewPageRepository; import com.appsmith.server.repositories.PermissionGroupRepository; import com.appsmith.server.repositories.PluginRepository; import com.appsmith.server.repositories.UserRepository; +import com.appsmith.server.services.ActionCollectionService; +import com.appsmith.server.services.ApplicationService; +import com.appsmith.server.services.DatasourceService; +import com.appsmith.server.services.LayoutActionService; +import com.appsmith.server.services.LayoutCollectionService; +import com.appsmith.server.services.NewActionService; +import com.appsmith.server.services.NewPageService; +import com.appsmith.server.services.PermissionGroupService; +import com.appsmith.server.services.PluginService; +import com.appsmith.server.services.SessionUserService; +import com.appsmith.server.services.ThemeService; +import com.appsmith.server.services.UserService; +import com.appsmith.server.services.WorkspaceService; import com.appsmith.server.solutions.ApplicationFetcher; import com.appsmith.server.solutions.ImportExportApplicationService; import com.appsmith.server.solutions.ReleaseNotesService; @@ -118,13 +131,13 @@ import static org.springframework.data.mongodb.core.query.Query.query; @SpringBootTest @Slf4j @DirtiesContext -public class ApplicationServiceTest { +public class ApplicationServiceCETest { @Autowired ApplicationService applicationService; @Autowired - ApplicationPageService applicationPageService; + ApplicationPageServiceCE applicationPageService; @Autowired UserService userService; @@ -3285,4 +3298,92 @@ public class ApplicationServiceTest { .verifyComplete(); } + + + /** + * Test case which proves the non-dependency of isPublic Field in Update Application API Response + * on the deprecated Application collection isPublic field for a public application + * The following steps are followed: + * 1. Create a new app + * 2. Invoke the changeViewAccess method to set the App "Public" + * 3. Invoke the update method and assert the "isPublic" field in the response + */ + @Test + @WithUserDetails(value = "api_user") + public void validPublicAppUpdateApplication() { + Application application = new Application(); + application.setName("validPublicAppUpdateApplication-Test"); + + Application createdApplication = applicationPageService.createApplication(application, workspaceId).block(); + + /** + * Making the App public using changeViewAccess method which changes the permission groups of the app to allow public access + */ + ApplicationAccessDTO applicationAccessDTO = new ApplicationAccessDTO(); + applicationAccessDTO.setPublicAccess(true); + Application publicAccessApplication = applicationService.changeViewAccess(createdApplication.getId(), applicationAccessDTO).block(); + + /** + * setIsPublic to False, purposely set to prove non-dependency on this field of the output + */ + publicAccessApplication.setIsPublic(false); + + /** + * Using the Update App method and asserting the response to verify the isPublic field in the response is True + * which proves it's non-dependency on the deprecated Application collection isPublic field + * and shows it dependency on the actual app permissions and state of the app which has been set public in this case + **/ + Mono updatedApplication = applicationService.update(createdApplication.getId(), publicAccessApplication); + StepVerifier.create(updatedApplication) + .assertNext(t -> { + assertThat(t).isNotNull(); + assertThat(t.getId()).isNotNull(); + assertThat(t.getIsPublic()).isTrue(); + }) + .verifyComplete(); + } + + /** + * Test case which proves the non-dependency of isPublic Field in Update Application API Response + * on the deprecated Application collection isPublic field for a public application + * The following steps are followed: + * 1. Create a new app + * 2. Invoke the changeViewAccess method to set the App "Public" + * 3. Invoke the update method and assert the "isPublic" field in the response + */ + @Test + @WithUserDetails(value = "api_user") + public void validPrivateAppUpdateApplication() { + Application application = new Application(); + application.setName("validPrivateAppUpdateApplication-Test"); + + Application createdApplication = applicationPageService.createApplication(application, workspaceId).block(); + + /** + * Making the App private using changeViewAccess method which changes the permission groups of the app to restrict public access + */ + ApplicationAccessDTO applicationAccessDTO = new ApplicationAccessDTO(); + applicationAccessDTO.setPublicAccess(false); + + Application privateAccessApplication = applicationService.changeViewAccess(createdApplication.getId(), applicationAccessDTO).block(); + + /** + * setIsPublic to True, purposely set to prove non-dependency on this field of the output + */ + privateAccessApplication.setIsPublic(true); + + /** + * Using the Update App method and asserting the response to verify the isPublic field in the response is False + * which proves it's non-dependency on the deprecated Application collection isPublic field + * and shows it dependency on the actual app permissions and state of the app which has been set private in this case + **/ + Mono updatedApplication = applicationService.update(createdApplication.getId(), privateAccessApplication); + StepVerifier.create(updatedApplication) + .assertNext(t -> { + assertThat(t).isNotNull(); + assertThat(t.getId()).isNotNull(); + assertThat(t.getIsPublic()).isFalse(); + }) + .verifyComplete(); + } } diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/NewActionServiceCEImplTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/NewActionServiceCEImplTest.java index 2070dd0d1b..007b12c58a 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/NewActionServiceCEImplTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/NewActionServiceCEImplTest.java @@ -1,14 +1,13 @@ package com.appsmith.server.services.ce; -import com.appsmith.external.dtos.ExecuteActionDTO; +import com.appsmith.external.models.ActionDTO; import com.appsmith.external.models.ActionExecutionResult; import com.appsmith.external.models.Datasource; +import com.appsmith.external.models.PluginType; import com.appsmith.server.acl.PolicyGenerator; import com.appsmith.server.constants.FieldName; import com.appsmith.server.domains.NewAction; import com.appsmith.server.domains.Plugin; -import com.appsmith.external.models.PluginType; -import com.appsmith.external.models.ActionDTO; import com.appsmith.server.exceptions.AppsmithError; import com.appsmith.server.exceptions.AppsmithException; import com.appsmith.server.helpers.PluginExecutorHelper; @@ -30,6 +29,8 @@ import com.appsmith.server.solutions.ActionPermission; import com.appsmith.server.solutions.ApplicationPermission; import com.appsmith.server.solutions.DatasourcePermission; import com.appsmith.server.solutions.PagePermission; +import io.micrometer.observation.ObservationRegistry; +import jakarta.validation.Validator; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -60,7 +61,6 @@ import reactor.core.publisher.Mono; import reactor.core.scheduler.Scheduler; import reactor.test.StepVerifier; -import javax.validation.Validator; import java.net.URI; import java.util.ArrayList; import java.util.HashMap; @@ -133,6 +133,8 @@ public class NewActionServiceCEImplTest { PagePermission pagePermission; @MockBean ActionPermission actionPermission; + @MockBean + ObservationRegistry observationRegistry; private BodyExtractor.Context context; @@ -170,7 +172,7 @@ public class NewActionServiceCEImplTest { public void createContext() { final List> messageReaders = new ArrayList<>(); messageReaders.add(new DecoderHttpMessageReader<>(new ByteBufferDecoder())); - messageReaders.add(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes(true))); + messageReaders.add(new DecoderHttpMessageReader<>(StringDecoder.allMimeTypes())); messageReaders.add(new DecoderHttpMessageReader<>(new Jaxb2XmlDecoder())); messageReaders.add(new DecoderHttpMessageReader<>(new Jackson2JsonDecoder())); messageReaders.add(new FormHttpMessageReader()); diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/PluginServiceCEImplTest.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/PluginServiceCEImplTest.java index 538545c173..3cb9a5c72a 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/PluginServiceCEImplTest.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/services/ce/PluginServiceCEImplTest.java @@ -20,7 +20,7 @@ import org.springframework.data.redis.listener.ChannelTopic; import org.springframework.test.context.junit.jupiter.SpringExtension; import reactor.core.scheduler.Scheduler; -import javax.validation.Validator; +import jakarta.validation.Validator; import java.io.IOException; import java.util.LinkedHashMap; import java.util.List; diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceTests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceTests.java index a94f6f3dff..bf55b9887e 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceTests.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceTests.java @@ -2,10 +2,12 @@ package com.appsmith.server.solutions; import com.appsmith.external.helpers.AppsmithBeanUtils; import com.appsmith.external.models.ActionConfiguration; +import com.appsmith.external.models.ActionDTO; import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.Datasource; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.InvisibleActionFields; +import com.appsmith.external.models.PluginType; import com.appsmith.external.models.Policy; import com.appsmith.external.models.Property; import com.appsmith.server.constants.FieldName; @@ -20,11 +22,9 @@ import com.appsmith.server.domains.NewAction; import com.appsmith.server.domains.NewPage; import com.appsmith.server.domains.PermissionGroup; import com.appsmith.server.domains.Plugin; -import com.appsmith.external.models.PluginType; import com.appsmith.server.domains.Theme; import com.appsmith.server.domains.Workspace; import com.appsmith.server.dtos.ActionCollectionDTO; -import com.appsmith.external.models.ActionDTO; import com.appsmith.server.dtos.ApplicationAccessDTO; import com.appsmith.server.dtos.ApplicationImportDTO; import com.appsmith.server.dtos.ApplicationJson; @@ -121,6 +121,9 @@ public class ImportExportApplicationServiceTests { @Autowired ImportExportApplicationService importExportApplicationService; + @Autowired + Gson gson; + @Autowired ApplicationPageService applicationPageService; @@ -273,7 +276,6 @@ public class ImportExportApplicationServiceTests { return stringifiedFile .map(data -> { - Gson gson = new Gson(); return gson.fromJson(data, ApplicationJson.class); }) .map(JsonSchemaMigration::migrateApplicationToLatestSchema); @@ -2118,7 +2120,6 @@ public class ImportExportApplicationServiceTests { }); Mono v1ApplicationMono = stringifiedFile .map(data -> { - Gson gson = new Gson(); return gson.fromJson(data, ApplicationJson.class); }).cache(); @@ -2989,8 +2990,8 @@ public class ImportExportApplicationServiceTests { NewPage page = pageList.stream().filter(newPage -> newPage.getUnpublishedPage().getName().equals("Page12")).collect(Collectors.toList()).get(0); // Verify the actions after merging the template actionList.forEach(newAction -> { - assertThat(newAction.getUnpublishedAction().getName()).containsAnyOf("api_wo_auth", "get_users", "run"); - assertThat(newAction.getUnpublishedAction().getPageId()).isEqualTo(page.getId()); + assertThat(newAction.getUnpublishedAction().getName()).containsAnyOf("api_wo_auth", "get_users", "run"); + assertThat(newAction.getUnpublishedAction().getPageId()).isEqualTo(page.getId()); }); // Verify the actionCollections after merging the template diff --git a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceV2Tests.java b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceV2Tests.java index e65d94f8e1..412c59ccb8 100644 --- a/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceV2Tests.java +++ b/app/server/appsmith-server/src/test/java/com/appsmith/server/solutions/ImportExportApplicationServiceV2Tests.java @@ -2,10 +2,12 @@ package com.appsmith.server.solutions; import com.appsmith.external.helpers.AppsmithBeanUtils; import com.appsmith.external.models.ActionConfiguration; +import com.appsmith.external.models.ActionDTO; import com.appsmith.external.models.DBAuth; import com.appsmith.external.models.Datasource; import com.appsmith.external.models.DatasourceConfiguration; import com.appsmith.external.models.InvisibleActionFields; +import com.appsmith.external.models.PluginType; import com.appsmith.external.models.Policy; import com.appsmith.external.models.Property; import com.appsmith.server.constants.FieldName; @@ -20,11 +22,9 @@ import com.appsmith.server.domains.NewAction; import com.appsmith.server.domains.NewPage; import com.appsmith.server.domains.PermissionGroup; import com.appsmith.server.domains.Plugin; -import com.appsmith.external.models.PluginType; import com.appsmith.server.domains.Theme; import com.appsmith.server.domains.Workspace; import com.appsmith.server.dtos.ActionCollectionDTO; -import com.appsmith.external.models.ActionDTO; import com.appsmith.server.dtos.ApplicationAccessDTO; import com.appsmith.server.dtos.ApplicationImportDTO; import com.appsmith.server.dtos.ApplicationJson; @@ -123,6 +123,9 @@ public class ImportExportApplicationServiceV2Tests { @Qualifier("importExportServiceCEImplV2") ImportExportApplicationService importExportApplicationService; + @Autowired + Gson gson; + @Autowired ApplicationPageService applicationPageService; @@ -275,7 +278,6 @@ public class ImportExportApplicationServiceV2Tests { return stringifiedFile .map(data -> { - Gson gson = new Gson(); return gson.fromJson(data, ApplicationJson.class); }) .map(JsonSchemaMigration::migrateApplicationToLatestSchema); @@ -2147,7 +2149,6 @@ public class ImportExportApplicationServiceV2Tests { }); Mono v1ApplicationMono = stringifiedFile .map(data -> { - Gson gson = new Gson(); return gson.fromJson(data, ApplicationJson.class); }).cache(); @@ -3018,8 +3019,8 @@ public class ImportExportApplicationServiceV2Tests { NewPage page = pageList.stream().filter(newPage -> newPage.getUnpublishedPage().getName().equals("Page12")).collect(Collectors.toList()).get(0); // Verify the actions after merging the template actionList.forEach(newAction -> { - assertThat(newAction.getUnpublishedAction().getName()).containsAnyOf("api_wo_auth", "get_users", "run"); - assertThat(newAction.getUnpublishedAction().getPageId()).isEqualTo(page.getId()); + assertThat(newAction.getUnpublishedAction().getName()).containsAnyOf("api_wo_auth", "get_users", "run"); + assertThat(newAction.getUnpublishedAction().getPageId()).isEqualTo(page.getId()); }); // Verify the actionCollections after merging the template diff --git a/app/server/appsmith-server/src/test/resources/application-test.properties b/app/server/appsmith-server/src/test/resources/application-test.properties new file mode 100644 index 0000000000..6aea70a3e6 --- /dev/null +++ b/app/server/appsmith-server/src/test/resources/application-test.properties @@ -0,0 +1,2 @@ +# embedded mongo DB version which is used during junit tests +de.flapdoodle.mongodb.embedded.version=5.0.14 \ No newline at end of file diff --git a/app/server/envs/test.env.example b/app/server/envs/test.env.example new file mode 100644 index 0000000000..fdeb33ce71 --- /dev/null +++ b/app/server/envs/test.env.example @@ -0,0 +1,32 @@ +#!/bin/sh + +ACTIVE_PROFILE=test + +APPSMITH_MONGODB_URI="mongodb://localhost:27017/appsmith" + +APPSMITH_REDIS_URL="redis://127.0.0.1:6379" + +APPSMITH_MAIL_ENABLED=false + +APPSMITH_ENCRYPTION_PASSWORD=abcd +APPSMITH_ENCRYPTION_SALT=abcd + +APPSMITH_CODEC_SIZE=10 + +APPSMITH_CLOUD_SERVICES_BASE_URL="https://release-cs.appsmith.com" +# APPSMITH_CLOUD_SERVICES_BASE_URL="http://localhost:8090" + +#APPSMITH_OAUTH2_GOOGLE_CLIENT_ID="" +#APPSMITH_OAUTH2_GOOGLE_CLIENT_SECRET="" + +#APPSMITH_OAUTH2_GITHUB_CLIENT_ID="" +#APPSMITH_OAUTH2_GITHUB_CLIENT_SECRET="" + +#APPSMITH_FORM_LOGIN_DISABLED= +#APPSMITH_SIGNUP_DISABLED= + +#APPSMITH_SENTRY_DSN= +#APPSMITH_SENTRY_ENVIRONMENT= + +#APPSMITH_RECAPTCHA_SITE_KEY="" +#APPSMITH_RECAPTCHA_SECRET_KEY="" diff --git a/app/server/pom.xml b/app/server/pom.xml index 11bc53fbde..f4d3d48e91 100644 --- a/app/server/pom.xml +++ b/app/server/pom.xml @@ -5,7 +5,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.1 + 3.0.1 @@ -17,6 +17,8 @@ Integrated Appsmith + UTF-8 + 17 true true true @@ -24,13 +26,18 @@ 1.0-SNAPSHOT true - 5.8.0 + 3.0.1 + + 4.1.86.Final 2.17.1 2.1.210 1.17.3 4.4.0 5.0.0-alpha.2 4.10.0 + 3.5.1 + ${java.version} + ${java.version} @@ -55,7 +62,7 @@ maven-dependency-plugin - 3.1.2 + 3.4.0 org.apache.maven.plugins diff --git a/app/server/reactive-caching/pom.xml b/app/server/reactive-caching/pom.xml index e1b9b741d9..ccfb5b2872 100644 --- a/app/server/reactive-caching/pom.xml +++ b/app/server/reactive-caching/pom.xml @@ -9,19 +9,16 @@ 4.0.0 - com.appsmith reactiveCaching 1.0-SNAPSHOT reactiveCaching - UTF-8 - 11 - 3.4.1 + 3.8.0 1.18.22 1.17.2 - 7.2.5.RELEASE + 7.2.11.RELEASE @@ -45,7 +42,7 @@ org.springframework.boot spring-boot-starter-webflux - 2.7.0 + ${spring-boot.version} @@ -81,7 +78,6 @@ org.projectlombok lombok - ${org.projectlombok.version} provided @@ -107,7 +103,7 @@ io.quarkus quarkus-junit4-mock - 2.11.2.Final + 2.14.2.Final test diff --git a/app/server/system.properties b/app/server/system.properties index 9146af5386..eafd676cd4 100644 --- a/app/server/system.properties +++ b/app/server/system.properties @@ -1 +1 @@ -java.runtime.version=11 +java.runtime.version=17 diff --git a/app/shared/ast/yarn.lock b/app/shared/ast/yarn.lock index a7fc9677e9..b29f4b6e36 100644 --- a/app/shared/ast/yarn.lock +++ b/app/shared/ast/yarn.lock @@ -2099,9 +2099,9 @@ json-parse-even-better-errors@^2.3.0: integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w== json5@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.1.tgz#655d50ed1e6f95ad1a3caababd2b0efda10b395c" - integrity sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA== + version "2.2.3" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283" + integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg== jsonfile@^6.0.1: version "6.1.0" diff --git a/contributions/ServerSetup.md b/contributions/ServerSetup.md index 2fbc45bbf9..862168e768 100644 --- a/contributions/ServerSetup.md +++ b/contributions/ServerSetup.md @@ -48,7 +48,7 @@ docker-compose up -d Before you can start to hack on the Appsmith server, your machine should have the following installed: -- Java - OpenJDK 11. +- Java - OpenJDK 17. - Maven - Version 3+ (preferably 3.6). - A MongoDB database - Refer to the [Setting up a local MongoDB instance](#setting-up-a-local-mongodb-instance) section to setup a MongoDB instance using `Docker`. - A Redis instance - Refer to the [Setting up a local Redis instance](#setting-up-a-local-redis-instance) section to setup a Redis instance using `Docker`. @@ -147,7 +147,7 @@ Before you can start to hack on the Appsmith server, your machine should have th - WSL2 with Linux distro (preferably Ubuntu LTS). Refer to [WSL2 installation on Windows](https://docs.microsoft.com/en-us/windows/wsl/install). - Docker Desktop for Windows (must be with WSL backed/based engine). Refer to [Install Docker Desktop on Windows](https://docs.docker.com/desktop/windows/install/). - An IDE - We use IntelliJ IDEA as our primary IDE for backend development. -- Java - OpenJDK 11 in WSL. +- Java - OpenJDK 17 in WSL. - Maven - Version 3+ (preferably 3.6) in WSL. This document doesn't provide instructions to install Java and Maven because these vary between different operating systems and distributions. Please refer to the documentation of your operating system or package manager to install these. diff --git a/deploy/docker/scripts/run-java.sh b/deploy/docker/scripts/run-java.sh index b92a0602ec..0bed6de037 100755 --- a/deploy/docker/scripts/run-java.sh +++ b/deploy/docker/scripts/run-java.sh @@ -28,8 +28,17 @@ if [[ $proxy_configured == 1 ]]; then proxy_args+=(-Djava.net.useSystemProxies=true -Dhttp.nonProxyHosts="${NO_PROXY/,/|}") fi +# Wait until RTS started and listens on port 8091 +while ! curl --fail --silent localhost/rts-api/v1/health-check; do + echo 'Waiting for RTS to start ...' + sleep 1 +done +echo 'RTS started.' + + # Ref -Dlog4j2.formatMsgNoLookups=true https://spring.io/blog/2021/12/10/log4j2-vulnerability-and-spring-boot exec java ${APPSMITH_JAVA_ARGS:-} ${APPSMITH_JAVA_HEAP_ARG:-} \ + --add-opens java.base/java.time=ALL-UNNAMED \ -Dserver.port=8080 \ -Djava.security.egd=file:/dev/./urandom \ -Dlog4j2.formatMsgNoLookups=true \ diff --git a/deploy/docker/templates/supervisord/application_process/backend.conf b/deploy/docker/templates/supervisord/application_process/backend.conf index ff582463b0..0c99eeb166 100644 --- a/deploy/docker/templates/supervisord/application_process/backend.conf +++ b/deploy/docker/templates/supervisord/application_process/backend.conf @@ -1,6 +1,6 @@ [program:backend] directory=/opt/appsmith/backend -command=/opt/appsmith/start-backend.sh +command=/opt/appsmith/run-with-env.sh /opt/appsmith/run-java.sh priority=20 autostart=true autorestart=true diff --git a/deploy/docker/utils/bin/backup.js b/deploy/docker/utils/bin/backup.js index c33483531c..22bf67d484 100644 --- a/deploy/docker/utils/bin/backup.js +++ b/deploy/docker/utils/bin/backup.js @@ -83,7 +83,7 @@ async function createGitStorageArchive(destFolder) { } async function createManifestFile(path) { - const version = await getCurrentVersion() + const version = await utils.getCurrentAppsmithVersion() const manifest_data = { "appsmithVersion": version } await fsPromises.writeFile(path + '/manifest.json', JSON.stringify(manifest_data)); } @@ -183,10 +183,7 @@ function checkAvailableBackupSpace(availSpaceInBytes) { } } -async function getCurrentVersion() { - const content = await fsPromises.readFile('/opt/appsmith/rts/version.js', { encoding: 'utf8' }); - return content.match(/\bexports\.VERSION\s*=\s*["']([^"]+)["']/)[1]; -} + module.exports = { run, @@ -198,7 +195,6 @@ module.exports = { executeMongoDumpCMD, getGitRoot, executeCopyCMD, - getCurrentVersion, removeEncryptionEnvData, getBackupArchiveLimit, removeOldBackups diff --git a/deploy/docker/utils/bin/backup.test.js b/deploy/docker/utils/bin/backup.test.js index 351752ce12..6eb8278f9c 100644 --- a/deploy/docker/utils/bin/backup.test.js +++ b/deploy/docker/utils/bin/backup.test.js @@ -83,7 +83,7 @@ it('Checks for the current Appsmith Version.', async () => { `Object.defineProperty(exports, "__esModule", { value: true }); exports.VERSION = void 0; exports.VERSION = "v0.0.0-SNAPSHOT";`); - const res = await backup.getCurrentVersion() + const res = await utils.getCurrentAppsmithVersion() expect(res).toBe("v0.0.0-SNAPSHOT") console.log(res) }) diff --git a/deploy/docker/utils/bin/index.js b/deploy/docker/utils/bin/index.js index 40b88c4686..15933fd408 100755 --- a/deploy/docker/utils/bin/index.js +++ b/deploy/docker/utils/bin/index.js @@ -7,6 +7,8 @@ const import_db = require("./import_db.js"); const migrate = require("./migrate.js"); const check_replica_set = require("./check_replica_set.js"); const estimate_billing = require("./estimate_billing.js"); +const version = require("./version.js"); +const mongo_shell_utils = require("./mongo_shell_utils.js"); const APPLICATION_CONFIG_PATH = "/appsmith-stacks/configuration/docker.env"; @@ -52,5 +54,13 @@ if (["estimate-billing", "estimate_billing"].includes(command)) { estimate_billing.run(process.argv.slice(3)); return; } +if (["appsmith-version", "appsmith_version", "version"].includes(command)) { + version.exec(); + return; +} +if (["mongo-eval", "mongo_eval", "mongoEval"].includes(command)) { + mongo_shell_utils.exec(process.argv.slice(3)); + return; +} utils.showHelp(); diff --git a/deploy/docker/utils/bin/mongo_shell_utils.js b/deploy/docker/utils/bin/mongo_shell_utils.js new file mode 100644 index 0000000000..142531ba96 --- /dev/null +++ b/deploy/docker/utils/bin/mongo_shell_utils.js @@ -0,0 +1,28 @@ + +const utils = require('./utils'); + +const command_args = process.argv.slice(3); + +async function exec() { + let errorCode = 0; + try { + await execMongoEval(command_args[0], process.env.APPSMITH_MONGODB_URI); + } catch (err) { + errorCode = 1; + console.error('Error evaluating the mongo query', err); + } finally { + process.exit(errorCode); + } +} + +async function execMongoEval(queryExpression, appsmithMongoURI) { + queryExpression = queryExpression.trim(); + if (command_args.includes('--pretty')) { + queryExpression += '.pretty()'; + } + return await utils.execCommand(['mongo', appsmithMongoURI, `--eval=${queryExpression}`]); +} + +module.exports = { + exec +}; diff --git a/deploy/docker/utils/bin/utils.js b/deploy/docker/utils/bin/utils.js index f838e2ad29..68b82c2b6c 100644 --- a/deploy/docker/utils/bin/utils.js +++ b/deploy/docker/utils/bin/utils.js @@ -58,7 +58,7 @@ function execCommand(cmd, options) { return; } isPromiseDone = true; - log.error("Error running command", err); + console.error("Error running command", err); reject(); }); }); @@ -97,6 +97,11 @@ async function getLastBackupErrorMailSentInMilliSec() { } } +async function getCurrentAppsmithVersion() { + const content = await fsPromises.readFile('/opt/appsmith/rts/version.js', { encoding: 'utf8' }); + return content.match(/\bexports\.VERSION\s*=\s*["']([^"]+)["']/)[1]; +} + module.exports = { showHelp, start, @@ -105,4 +110,5 @@ module.exports = { listLocalBackupFiles, updateLastBackupErrorMailSentInMilliSec, getLastBackupErrorMailSentInMilliSec, + getCurrentAppsmithVersion }; diff --git a/deploy/docker/utils/bin/version.js b/deploy/docker/utils/bin/version.js new file mode 100644 index 0000000000..b154e12443 --- /dev/null +++ b/deploy/docker/utils/bin/version.js @@ -0,0 +1,22 @@ +const utils = require('./utils'); + +async function exec() { + let version = null; + try { + version = await utils.getCurrentAppsmithVersion(); + } catch (err) { + console.error("Error fetching current Appsmith version", err); + process.exit(1); + } + if (version) { + console.log(version); + } + else { + console.error("Error: could not find the current Appsmith version") + process.exit(1); + } +} + +module.exports = { + exec, +}; diff --git a/deploy/docker/utils/package-lock.json b/deploy/docker/utils/package-lock.json index 1f7506dda2..b007923e60 100644 --- a/deploy/docker/utils/package-lock.json +++ b/deploy/docker/utils/package-lock.json @@ -2503,9 +2503,9 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "node_modules/json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==", + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", "bin": { "json5": "lib/cli.js" }, @@ -5269,9 +5269,9 @@ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "json5": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz", - "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==" + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==" }, "kleur": { "version": "3.0.3", diff --git a/deploy/helm/templates/statefulset.yaml b/deploy/helm/templates/statefulset.yaml index b8a55ad6e1..21bdb4960f 100644 --- a/deploy/helm/templates/statefulset.yaml +++ b/deploy/helm/templates/statefulset.yaml @@ -46,12 +46,20 @@ spec: initContainers: {{- if .Values.redis.enabled }} - name: redis-init-container - image: alpine + {{- if ((.Values.initContainer.redis).image) }} + image: {{ .Values.initContainer.redis.image }} + {{- else }} + image: "alpine" + {{- end }} command: ['sh', '-c', "apk add redis; until redis-cli -h {{ include "appsmith.fullname" . }}-redis-master.{{.Release.Namespace}}.svc.cluster.local ping; do echo waiting for redis; sleep 2; done"] {{- end }} {{- if .Values.mongodb.enabled }} - name: mongo-init-container - image: docker.io/bitnami/mongodb:4.4.11-debian-10-r12 + {{- if ((.Values.initContainer.mongodb).image) }} + image: {{ .Values.initContainer.mongodb.image }} + {{- else }} + image: "docker.io/bitnami/mongodb:4.4.11-debian-10-r12" + {{- end }} command: ['sh', '-c', "until mongo --host appsmith-mongodb.{{.Release.Namespace}}.svc.cluster.local --eval 'db.runCommand({ping:1})' ; do echo waiting for mongo; sleep 2; done"] {{- end }} containers: diff --git a/deploy/helm/values.yaml b/deploy/helm/values.yaml index 2d2b70cc42..701bbe0200 100644 --- a/deploy/helm/values.yaml +++ b/deploy/helm/values.yaml @@ -12,7 +12,7 @@ mongodb: nameOverride: appsmith-mongodb auth: rootUser: root - rootPassword : password + rootPassword: password replicaCount: 2 architecture: "replicaset" replicaSetName: rs0 @@ -49,6 +49,14 @@ schedulerName: "" ## It can be set to RollingUpdate or Recreate by default. ## strategyType: RollingUpdate +## +## Init containers for redis & mongodb +## +initContainer: {} + # redis: + # image: alpine + # mongodb: + # image: docker.io/bitnami/mongodb:4.4.11-debian-10-r12 ## Image ## image: diff --git a/scripts/start-backend.sh b/scripts/start-backend.sh deleted file mode 100644 index 28c96ec35c..0000000000 --- a/scripts/start-backend.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/bash - -set -o errexit - -# Wait until RTS started and listens on port 8091 -while ! curl --fail --silent localhost/rts-api/v1/health-check; do - echo 'Waiting for RTS to start ...' - sleep 1 -done -echo 'RTS started.' - -# Start server. -echo 'Starting Backend server...' -exec /opt/appsmith/run-with-env.sh /opt/appsmith/run-java.sh