diff --git a/.github/workflows/render-suspend-labeler.yml b/.github/workflows/render-suspend-labeler.yml index b60dca1748..7860ae3ade 100644 --- a/.github/workflows/render-suspend-labeler.yml +++ b/.github/workflows/render-suspend-labeler.yml @@ -27,7 +27,7 @@ jobs: env: STALE_NUMBERS: ${{ steps.stale-label.outputs.stale-numbers }} with: - github-token: ${{ secrets.GITHUB_TOKEN }} + github-token: ${{ secrets.TJ_BOT_PAT }} script: | if (!process.env.STALE_NUMBERS) return diff --git a/cypress-tests/cypress/support/utils/common.js b/cypress-tests/cypress/support/utils/common.js index d83a7b7544..4e8cd6396e 100644 --- a/cypress-tests/cypress/support/utils/common.js +++ b/cypress-tests/cypress/support/utils/common.js @@ -43,7 +43,7 @@ export const randomDateOrTime = (format = "DD/MM/YYYY") => { let startDate = new Date(2018, 0, 1); startDate = new Date( startDate.getTime() + - Math.random() * (endDate.getTime() - startDate.getTime()) + Math.random() * (endDate.getTime() - startDate.getTime()) ); return moment(startDate).format(format); }; diff --git a/docs/docs/how-to/bulk-update-multiple-rows-in-table.md b/docs/docs/how-to/bulk-update-multiple-rows-in-table.md index af9ccf6693..437190160c 100644 --- a/docs/docs/how-to/bulk-update-multiple-rows-in-table.md +++ b/docs/docs/how-to/bulk-update-multiple-rows-in-table.md @@ -98,7 +98,7 @@ Let's create a new PostgreSQL query and name it `update`. In **SQL mode**, enter - Click on the handle of the **Table** widget to open its properties - Go to the **Events**, and add a handler -- Select **Bulk Update** in Events, **Run Query** in Actions, and then select the **runjs1** query in Query. Now whenever a user will edit the table and hit the **Save Changes** button runjs1 will run. +- Select **Save Changes** in Events, **Run Query** in Actions, and then select the **runjs1** query in Query. Now whenever a user will edit the table and hit the **Save Changes** button runjs1 will run.
@@ -112,4 +112,4 @@ Let's create a new PostgreSQL query and name it `update`. In **SQL mode**, enter ![ToolJet - How To - Bulk update multiple rows in table](/img/how-to/bulk-update-multiple/success.png) -
\ No newline at end of file + diff --git a/docs/src/css/custom.css b/docs/src/css/custom.css index 177315f318..a1d502859d 100644 --- a/docs/src/css/custom.css +++ b/docs/src/css/custom.css @@ -253,6 +253,10 @@ strong { var(--ifm-color-emphasis-100) 100%); } +.DocSearch-Button{ + border-radius: 5px !important; +} + @media screen and (min-width: 768px) { .DocSearch-Button-Container { min-width: 200px; diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 901f026bd4..e56b190dfe 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -22114,6 +22114,15 @@ "node": ">= 0.6.0" } }, + "node_modules/base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "optional": true, + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", @@ -53489,6 +53498,12 @@ "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", "optional": true }, + "base64-arraybuffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-1.0.2.tgz", + "integrity": "sha512-I3yl4r9QB5ZRY3XuJVEPfc2XhZO6YweFPI+UovAzn+8/hb3oJ6lnysaFcjVpkCPfVWFUDvoZ8kmVDP7WyRtYtQ==", + "optional": true + }, "base64-js": { "version": "1.5.1", "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" diff --git a/frontend/src/Editor/AppVersionsManager.jsx b/frontend/src/Editor/AppVersionsManager.jsx new file mode 100644 index 0000000000..3da14ab3f0 --- /dev/null +++ b/frontend/src/Editor/AppVersionsManager.jsx @@ -0,0 +1,505 @@ +import React, { useEffect, useState, useRef } from 'react'; +import Modal from '../HomePage/Modal'; +import { toast } from 'react-hot-toast'; +import { appVersionService } from '@/_services'; +import { Confirm } from './Viewer/Confirm'; +import Select from '../_ui/Select'; +import defaultStyle from '../_ui/Select/styles'; +import { useTranslation } from 'react-i18next'; + +export const AppVersionsManager = function AppVersionsManager({ + appId, + editingVersion, + releasedVersionId, + setAppDefinitionFromVersion, + showCreateVersionModalPrompt, + closeCreateVersionModalPrompt, +}) { + const { t } = useTranslation(); + const [showDropDown, setShowDropDown] = useState(false); + const [showModal, setShowModal] = useState(false); + const [isCreatingVersion, setIsCreatingVersion] = useState(false); + const [isEditingVersion, setIsEditingVersion] = useState(false); + const [deletingVersion, setDeletingVersion] = useState({ name: null, id: null }); + const [updatingVersionId, setUpdatingVersionId] = useState(null); + const [isDeletingVersion, setIsDeletingVersion] = useState(false); + const [editingAppVersion, setEditingAppVersion] = useState(editingVersion); + const [versionName, setVersionName] = useState(''); + const [appVersions, setAppVersions] = useState([]); + const [showVersionDeletionConfirmation, setShowVersionDeletionConfirmation] = useState(false); + const [showVersionUpdateModal, setShowVersionUpdateModal] = useState(false); + const [mouseHoveredOnVersion, setMouseHoveredOnVersion] = useState(null); + const [createAppVersionFrom, setCreateAppVersionFrom] = useState(editingAppVersion); + + useEffect(() => { + setCreateAppVersionFrom(editingAppVersion); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [appVersions]); + + useEffect(() => { + setVersionName(''); + setShowModal(showCreateVersionModalPrompt); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [showCreateVersionModalPrompt]); + + useEffect(() => { + appVersionService.getAll(appId).then((data) => { + setAppVersions(data.versions); + }); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + setEditingAppVersion(editingVersion); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [editingVersion]); + + useEffect(() => { + appVersions[appVersions.findIndex((appVersion) => appVersion.id === editingVersion.id)] = editingAppVersion; + setCreateAppVersionFrom(editingAppVersion); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [editingAppVersion]); + + const wrapperRef = useRef(null); + useEffect(() => { + const handleClickOutside = (event) => { + if (!showVersionDeletionConfirmation && wrapperRef.current && !wrapperRef.current.contains(event.target)) { + setShowDropDown(false); + } + }; + document.addEventListener('mousedown', handleClickOutside); + return () => { + // Unbind the event listener on clean up + document.removeEventListener('mousedown', handleClickOutside); + }; + }, [wrapperRef, showVersionDeletionConfirmation]); + + const closeModal = () => { + setShowModal(false); + closeCreateVersionModalPrompt(); + }; + + const createVersion = (versionName, createAppVersionFrom) => { + versionName = versionName.trim(); + if (versionName.length > 25) { + toast.error('The version name should not be longer than 25 characters'); + setIsCreatingVersion(false); + return; + } + if (versionName == '') { + toast.error('The version name should not be empty'); + setIsCreatingVersion(false); + return; + } + setIsCreatingVersion(true); + appVersionService + .create(appId, versionName, createAppVersionFrom.id) + .then(() => { + closeModal(); + toast.success('Version Created'); + + appVersionService.getAll(appId).then((data) => { + setAppVersions(data.versions); + + const latestVersion = data.versions.at(0); + setAppDefinitionFromVersion(latestVersion); + setEditingAppVersion(latestVersion); + setVersionName(''); + }); + + setIsCreatingVersion(false); + }) + .catch((_error) => { + setIsCreatingVersion(false); + toast.error(_error?.error); + }); + }; + + const deleteAppVersion = (versionId) => { + setIsDeletingVersion(true); + appVersionService + .del(appId, versionId) + .then(() => { + toast.success('Version Deleted'); + + appVersionService.getAll(appId).then((data) => { + setAppVersions(data.versions); + + if (editingAppVersion.id === versionId) { + const latestVersion = data.versions.at(0); + setAppDefinitionFromVersion(latestVersion); + setEditingAppVersion(latestVersion); + } + }); + + setIsDeletingVersion(false); + setShowVersionDeletionConfirmation(false); + }) + .catch((_error) => { + setIsDeletingVersion(false); + setShowVersionDeletionConfirmation(false); + toast.error('Oops, something went wrong'); + }); + }; + + const selectVersion = (version) => { + appVersionService.getOne(appId, version.id).then((data) => { + setAppDefinitionFromVersion(data); + }); + }; + + const editVersionName = () => { + if (versionName.trim() !== '') { + setIsEditingVersion(true); + appVersionService + .save(appId, updatingVersionId, { name: versionName }) + .then(() => { + toast.success('Version name updated'); + appVersionService.getAll(appId).then((data) => { + const versions = data.versions; + setAppVersions(versions); + updatingVersionId === editingAppVersion.id && + versions.map((appVersion) => { + if (appVersion.id === updatingVersionId) { + setEditingAppVersion(appVersion); + setUpdatingVersionId(null); + } + }); + }); + setIsEditingVersion(false); + setShowVersionUpdateModal(false); + }) + .catch((_error) => { + setIsEditingVersion(false); + setShowVersionDeletionConfirmation(false); + toast.error(_error?.error); + }); + } else { + toast.error('The name of version should not be empty'); + setIsEditingVersion(false); + } + }; + + return ( +
+ + {t('editor.appVersionManager.version', 'Version')} + + { + setShowDropDown(!showDropDown); + }} + > + + {releasedVersionId === editingAppVersion.id && } + + {editingAppVersion.name} + + + {showDropDown && ( + <> +
+
+ {appVersions.map((version) => + releasedVersionId == version.id ? ( + <> +
selectVersion(version)} + > +
{version.name}
+
+ + + {t('editor.appVersionManager.currentlyReleased', 'Currently Released')} + +
+
+
+ + ) : ( + <> +
selectVersion(version)} + onMouseEnter={() => setMouseHoveredOnVersion(version.id)} + onMouseLeave={() => setMouseHoveredOnVersion(null)} + > +
+ {version.name} +
+ +
+ + + +
+
+
+ + ) + )} +
+
{ + setVersionName(''); + setShowModal(true); + }} + > + + {t('editor.appVersionManager.createVersion', 'Create Version')} + +
+ deleteAppVersion(versionId)} + queryConfirmationData={deletingVersion.id} + onCancel={() => setShowVersionDeletionConfirmation(false)} + /> +
+ + )} + +
+ setShowVersionUpdateModal(false)} + title={t('editor.appVersionManager.editVersion', 'Edit Version')} + > +
+
+ setVersionName(e.target.value)} + className="form-control" + placeholder={t('editor.appVersionManager.versionName', 'Version name')} + disabled={isEditingVersion} + value={versionName} + maxLength={25} + /> +
+
+
+
+ + +
+
+
+
+ ); +}; + +const CreateVersionModal = function CreateVersionModal({ + showModal, + setShowModal, + versionName, + setVersionName, + createAppVersionFrom, + setCreateAppVersionFrom, + createVersion, + isCreatingVersion, + appVersions, + showCreateVersionModalPrompt, +}) { + const { t } = useTranslation(); + const handleKeyPress = (event) => { + if (event.key === 'Enter') { + // eslint-disable-next-line no-undef + createVersion(versionName, createAppVersionFrom); + } + }; + const options = appVersions.map((version) => { + return { ...version, label: version.name, value: version }; + }); + const width = '100%'; + const height = 32; + const darkMode = localStorage.getItem('darkMode') === 'true'; + const customStyles = { + ...defaultStyle(darkMode, width, height), + option: (provided, state) => { + return { + ...provided, + backgroundColor: darkMode + ? state.isSelected + ? '#3650AF' + : 'rgb(31,40,55)' + : state.isSelected + ? '#7A95FB' + : 'white', + color: darkMode ? '#fff' : '#232e3c', + '&:hover': { + backgroundColor: darkMode + ? state.isSelected + ? '#1F2E64' + : '#323C4B' + : state.isSelected + ? '#3650AF' + : '#d8dce9', + }, + }; + }, + }; + return ( + setShowModal(false)} + > +
+
+ + setVersionName(e.target.value)} + className="form-control" + data-cy="version-name-input-field" + placeholder={t('editor.appVersionManager.enterVersionName', 'Enter version name')} + disabled={isCreatingVersion} + value={versionName} + autoFocus={true} + onKeyPress={(e) => handleKeyPress(e)} + minLength="1" + maxLength="25" + /> +
+
+ +
+ +
+