From 3933a9e7a25694d829cf563a452b546fcfd2a510 Mon Sep 17 00:00:00 2001
From: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
Date: Sat, 8 Mar 2025 00:25:24 +0530
Subject: [PATCH 01/16] Adds header and footer slots for Form
---
.../AppBuilder/AppCanvas/appCanvasUtils.js | 9 +-
.../Inspector/Components/Form.jsx | 52 +++-
.../AppBuilder/WidgetManager/widgets/form.js | 111 +++++--
frontend/src/AppBuilder/Widgets/Form/Form.jsx | 282 +++++++++++-------
.../src/Editor/WidgetManager/configs/form.js | 110 +++++--
.../apps/services/widget-config/form.js | 110 +++++--
6 files changed, 503 insertions(+), 171 deletions(-)
diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js
index 4e7b56ea70..41bc116ec3 100644
--- a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js
+++ b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js
@@ -206,6 +206,7 @@ export const getAllChildComponents = (allComponents, parentId) => {
allComponents[parentId]?.component?.component === 'Calendar' ||
allComponents[parentId]?.component?.component === 'Kanban' ||
allComponents[parentId]?.component?.component === 'Container' ||
+ allComponents[parentId]?.component?.component === 'Form' ||
allComponents[parentId]?.component?.component === 'ModalV2';
if (componentParentId && isParentTabORCalendar) {
@@ -327,6 +328,7 @@ const isChildOfTabsOrCalendar = (component, allComponents = [], componentParentI
parentComponent.component.component === 'Tabs' ||
parentComponent.component.component === 'Calendar' ||
parentComponent.component.component === 'Container' ||
+ parentComponent.component.component === 'Form' ||
parentComponent.component.component === 'ModalV2'
);
}
@@ -665,11 +667,14 @@ export const computeViewerBackgroundColor = (isAppDarkMode, canvasBgColor) => {
return canvasBgColor;
};
-export const getParentComponentIdByType = ({ child, parentComponent, parentId, slotName = 'header' }) => {
+export const getParentComponentIdByType = ({ child, parentComponent, parentId, slotName }) => {
const { tab } = child;
if (parentComponent === 'Tabs') return `${parentId}-${tab}`;
- else if (parentComponent === 'Container' || parentComponent === 'ModalV2') {
+ else if (
+ slotName &&
+ (parentComponent === 'Form' || parentComponent === 'Container' || parentComponent === 'ModalV2')
+ ) {
return `${parentId}-${slotName}`;
}
return parentId;
diff --git a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Form.jsx b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Form.jsx
index d4676ad4b6..b39924854e 100644
--- a/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Form.jsx
+++ b/frontend/src/AppBuilder/RightSideBar/Inspector/Components/Form.jsx
@@ -19,16 +19,34 @@ export const Form = ({
allComponents,
pages,
}) => {
- const properties = Object.keys(componentMeta.properties);
+ const tempComponentMeta = deepClone(componentMeta);
+
+ let properties = [];
+ let additionalActions = [];
+ let dataProperties = [];
+
const events = Object.keys(componentMeta.events);
const validations = Object.keys(componentMeta.validation || {});
- const tempComponentMeta = deepClone(componentMeta);
+
+ for (const [key] of Object.entries(componentMeta?.properties)) {
+ if (componentMeta?.properties[key]?.section === 'additionalActions') {
+ additionalActions.push(key);
+ } else if (componentMeta?.properties[key]?.accordian === 'Data') {
+ dataProperties.push(key);
+ } else {
+ properties.push(key);
+ }
+ }
const { id } = component;
const newOptions = [{ name: 'None', value: 'none' }];
- Object.entries(allComponents).forEach(([componentId, component]) => {
- if (component.component.parent === id && component?.component?.component === 'Button') {
- newOptions.push({ name: component.component.name, value: componentId });
+ Object.entries(allComponents).forEach(([componentId, _component]) => {
+ const validParent =
+ _component.component.parent === id ||
+ _component.component.parent === `${id}-footer` ||
+ _component.component.parent === `${id}-header`;
+ if (validParent && _component?.component?.component === 'Button') {
+ newOptions.push({ name: _component.component.name, value: componentId });
}
});
@@ -48,7 +66,8 @@ export const Form = ({
allComponents,
validations,
darkMode,
- pages
+ pages,
+ additionalActions
);
return ;
@@ -68,7 +87,8 @@ export const baseComponentProperties = (
allComponents,
validations,
darkMode,
- pages
+ pages,
+ additionalActions
) => {
let items = [];
if (properties.length > 0) {
@@ -90,6 +110,24 @@ export const baseComponentProperties = (
});
}
+ items.push({
+ title: 'Additional actions',
+ isOpen: true,
+ children: additionalActions?.map((property) =>
+ renderElement(
+ component,
+ componentMeta,
+ paramUpdated,
+ dataQueries,
+ property,
+ 'properties',
+ currentState,
+ allComponents,
+ darkMode
+ )
+ ),
+ });
+
if (events.length > 0) {
items.push({
title: `${i18next.t('widget.common.events', 'Events')}`,
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/form.js b/frontend/src/AppBuilder/WidgetManager/widgets/form.js
index 0e9f5f4ce3..7ab4bd26a6 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/form.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/form.js
@@ -4,9 +4,40 @@ export const formConfig = {
description: 'Wrapper for multiple components',
defaultSize: {
width: 13,
- height: 330,
+ height: 480,
},
defaultChildren: [
+ {
+ componentName: 'Text',
+ slotName: 'header',
+ layout: {
+ top: 10,
+ left: 1,
+ height: 40,
+ },
+ properties: ['text'],
+ accessorKey: 'text',
+ styles: ['fontWeight', 'textSize', 'textColor'],
+ defaultValue: {
+ text: 'Form title',
+ textSize: 20,
+ textColor: '#000',
+ },
+ },
+ {
+ componentName: 'Button',
+ slotName: 'footer',
+ layout: {
+ top: 12,
+ left: 32,
+ height: 36,
+ },
+ properties: ['text'],
+ defaultValue: {
+ text: 'Button2',
+ padding: 'none',
+ },
+ },
{
componentName: 'Text',
layout: {
@@ -225,6 +256,7 @@ export const formConfig = {
loadingState: {
type: 'toggle',
displayName: 'Loading state',
+ section: 'additionalActions',
validation: {
schema: { type: 'boolean' },
defaultValue: false,
@@ -242,12 +274,64 @@ export const formConfig = {
value: true,
},
},
+ showHeader: { type: 'toggle', displayName: 'Header' },
+ showFooter: { type: 'toggle', displayName: 'Footer' },
+ visibility: {
+ type: 'toggle',
+ displayName: 'Visibility',
+ section: 'additionalActions',
+ validation: {
+ schema: { type: 'boolean' },
+ defaultValue: true,
+ },
+ },
+ disabledState: {
+ type: 'toggle',
+ displayName: 'Disable',
+ section: 'additionalActions',
+ validation: {
+ schema: { type: 'boolean' },
+ defaultValue: false,
+ },
+ },
},
events: {
onSubmit: { displayName: 'On submit' },
onInvalid: { displayName: 'On invalid' },
},
styles: {
+ headerBackgroundColor: {
+ type: 'color',
+ displayName: 'Header background color',
+ validation: {
+ schema: { type: 'string' },
+ defaultValue: '#ffffffff',
+ },
+ },
+ footerBackgroundColor: {
+ type: 'color',
+ displayName: 'Footer background color',
+ validation: {
+ schema: { type: 'string' },
+ defaultValue: '#ffffffff',
+ },
+ },
+ headerHeight: {
+ type: 'code',
+ displayName: 'Header height',
+ validation: {
+ schema: { type: 'string' },
+ defaultValue: '80px',
+ },
+ },
+ footerHeight: {
+ type: 'code',
+ displayName: 'Footer height',
+ validation: {
+ schema: { type: 'string' },
+ defaultValue: '80px',
+ },
+ },
backgroundColor: {
type: 'color',
displayName: 'Background color',
@@ -274,22 +358,6 @@ export const formConfig = {
defaultValue: '#fff',
},
},
- visibility: {
- type: 'toggle',
- displayName: 'Visibility',
- validation: {
- schema: { type: 'boolean' },
- defaultValue: true,
- },
- },
- disabledState: {
- type: 'toggle',
- displayName: 'Disable',
- validation: {
- schema: { type: 'boolean' },
- defaultValue: false,
- },
- },
},
exposedVariables: {
data: {},
@@ -317,15 +385,18 @@ export const formConfig = {
value:
"{{ {title: 'User registration form', properties: {firstname: {type: 'textinput',value: 'Maria',label:'First name', validation:{maxLength:6}, styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},lastname:{type: 'textinput',value: 'Doe', label:'Last name', styles: {backgroundColor: '#f6f5ff',textColor: 'black'},},age:{type:'number', label:'Age'},}, submitButton: {value: 'Submit', styles: {backgroundColor: '#3a433b',borderColor:'#595959'}}} }}",
},
- buttonToSubmit: { value: '{{"none"}}' },
+ showHeader: { value: '{{false}}' },
+ showFooter: { value: '{{false}}' },
+ visibility: { value: '{{true}}' },
+ disabledState: { value: '{{false}}' },
},
events: [],
styles: {
backgroundColor: { value: '#fff' },
borderRadius: { value: '0' },
borderColor: { value: '#fff' },
- visibility: { value: '{{true}}' },
- disabledState: { value: '{{false}}' },
+ headerHeight: { value: '60px' },
+ footerHeight: { value: '60px' },
},
},
};
diff --git a/frontend/src/AppBuilder/Widgets/Form/Form.jsx b/frontend/src/AppBuilder/Widgets/Form/Form.jsx
index 674d707f03..c9ea9b7601 100644
--- a/frontend/src/AppBuilder/Widgets/Form/Form.jsx
+++ b/frontend/src/AppBuilder/Widgets/Form/Form.jsx
@@ -13,6 +13,12 @@ import RenderSchema from './RenderSchema';
import useStore from '@/AppBuilder/_stores/store';
import { shallow } from 'zustand/shallow';
+const getCanvasHeight = (height) => {
+ const parsedHeight = height.includes('px') ? parseInt(height, 10) : height;
+
+ return Math.ceil(parsedHeight);
+};
+
export const Form = function Form(props) {
const {
id,
@@ -29,8 +35,25 @@ export const Form = function Form(props) {
dataCy,
} = props;
const childComponents = useStore((state) => state.getChildComponents(id), shallow);
- const { visibility, disabledState, borderRadius, borderColor, boxShadow } = styles;
- const { buttonToSubmit, loadingState, advanced, JSONSchema } = properties;
+ const {
+ borderRadius,
+ borderColor,
+ boxShadow,
+ headerHeight,
+ footerHeight,
+ footerBackgroundColor,
+ headerBackgroundColor,
+ } = styles;
+ const {
+ buttonToSubmit,
+ loadingState,
+ advanced,
+ JSONSchema,
+ showHeader = false,
+ showFooter = false,
+ visibility,
+ disabledState,
+ } = properties;
const backgroundColor =
['#fff', '#ffffffff'].includes(styles.backgroundColor) && darkMode ? '#232E3C' : styles.backgroundColor;
const computedStyles = {
@@ -40,16 +63,32 @@ export const Form = function Form(props) {
height,
display: visibility ? 'flex' : 'none',
position: 'relative',
- overflow: 'hidden auto',
boxShadow,
+ flexDirection: 'column',
};
- const childIdNameMap = useMemo(() => {
- return Object.keys(childComponents).reduce((acc, id) => {
- const component = childComponents[id]?.component?.component;
- return { ...acc, [id]: component?.name };
- }, {});
- }, [childComponents]);
+ const formHeader = {
+ flexShrink: 0,
+ // height: headerHeight,
+ padding: '10px 6px',
+ borderBottom: '1px solid var(--border-weak)',
+ backgroundColor:
+ ['#fff', '#ffffffff'].includes(headerBackgroundColor) && darkMode ? '#1F2837' : headerBackgroundColor,
+ };
+ const formFooter = {
+ flexShrink: 0,
+ // height: footerHeight,
+ padding: '10px 6px',
+ borderTop: '1px solid var(--border-weak)',
+ backgroundColor:
+ ['#fff', '#ffffffff'].includes(footerBackgroundColor) && darkMode ? '#1F2837' : footerBackgroundColor,
+ };
+ const formContent = {
+ overflow: 'hidden auto',
+ display: 'flex',
+ height: '100%',
+ padding: '10px 6px',
+ };
const parentRef = useRef(null);
const childDataRef = useRef({});
@@ -58,6 +97,8 @@ export const Form = function Form(props) {
const [isValid, setValidation] = useState(true);
const [uiComponents, setUIComponents] = useState([]);
const mounted = useMounted();
+ const canvasHeaderHeight = getCanvasHeight(headerHeight) / 10;
+ const canvasFooterHeight = getCanvasHeight(footerHeight) / 10;
useEffect(() => {
const exposedVariables = {
@@ -155,7 +196,7 @@ export const Form = function Form(props) {
};
setExposedVariables(exposedVariables);
setValidation(childValidation);
- }, [childrenData, advanced, JSON.stringify(childIdNameMap)]);
+ }, [childrenData, advanced]);
useEffect(() => {
document.addEventListener('submitForm', handleFormSubmission);
@@ -245,105 +286,138 @@ export const Form = function Form(props) {
if (e.target.className === 'real-canvas') onComponentClick(id, component);
}} //Hack, should find a better solution - to prevent losing z index+1 when container element is clicked
>
- {loadingState ? (
-
-
-
-
+ {showHeader && (
+
+
- ) : (
-
{showFooter && (
-
+
)}
diff --git a/frontend/src/AppBuilder/Widgets/Form/form.scss b/frontend/src/AppBuilder/Widgets/Form/form.scss
new file mode 100644
index 0000000000..88a5ad055e
--- /dev/null
+++ b/frontend/src/AppBuilder/Widgets/Form/form.scss
@@ -0,0 +1,25 @@
+.wj-form-header {
+ position: relative;
+ &::after {
+ content: '';
+ position: absolute;
+ bottom: 0;
+ left: -7px;
+ right: -7px;
+ height: 1px;
+ background-color: var(--border-weak);
+ }
+}
+
+.wj-form-footer {
+ position: relative;
+ &::after {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: -7px;
+ right: -7px;
+ height: 1px;
+ background-color: var(--border-weak);
+ }
+}
diff --git a/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx b/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx
index 34efc57221..649e79f451 100644
--- a/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx
+++ b/frontend/src/AppBuilder/Widgets/Kanban/KanbanBoard.jsx
@@ -410,6 +410,7 @@ export function KanbanBoard({ widgetHeight, kanbanProps, parentRef, id }) {
width: `${(Number(cardWidth) || 300) + 48}px`,
}}
kanbanProps={kanbanProps}
+ componentType="Kanban"
>
{items[columnId] && (
diff --git a/frontend/src/AppBuilder/Widgets/Listview.jsx b/frontend/src/AppBuilder/Widgets/Listview.jsx
index 5ec5abbb17..285f9e5bf9 100644
--- a/frontend/src/AppBuilder/Widgets/Listview.jsx
+++ b/frontend/src/AppBuilder/Widgets/Listview.jsx
@@ -12,11 +12,8 @@ import { shallow } from 'zustand/shallow';
export const Listview = function Listview({
id,
- component,
width,
height,
- containerProps,
- removeComponent,
properties,
styles,
fireEvent,
@@ -270,38 +267,8 @@ export const Listview = function Listview({
columns={positiveColumns}
listViewMode={mode}
darkMode={darkMode}
+ componentType="Listview"
/>
- {/* {
- const changedData = { [component.name]: { [optionName]: value } };
- const existingDataAtIndex = prevData[index] ?? {};
- const newDataAtIndex = {
- ...prevData[index],
- [component.name]: {
- ...existingDataAtIndex[component.name],
- ...changedData[component.name],
- id: componentId,
- },
- };
- const newChildrenData = { ...prevData, [index]: newDataAtIndex };
- return { ...prevData, ...newChildrenData };
- });
- }}
- /> */}
))}
diff --git a/frontend/src/AppBuilder/Widgets/Tabs.jsx b/frontend/src/AppBuilder/Widgets/Tabs.jsx
index 3a93fa698b..7f4fb527e4 100644
--- a/frontend/src/AppBuilder/Widgets/Tabs.jsx
+++ b/frontend/src/AppBuilder/Widgets/Tabs.jsx
@@ -126,6 +126,7 @@ export const Tabs = function Tabs({
allowContainerSelect={true}
styles={{ backgroundColor: bgColor }}
darkMode={darkMode}
+ componentType="Tabs"
/>
);
diff --git a/frontend/src/AppBuilder/_helpers/editorHelpers.js b/frontend/src/AppBuilder/_helpers/editorHelpers.js
index 38e9abd955..5e817c7f7e 100644
--- a/frontend/src/AppBuilder/_helpers/editorHelpers.js
+++ b/frontend/src/AppBuilder/_helpers/editorHelpers.js
@@ -59,7 +59,7 @@ import { BoundedBox } from '@/Editor/Components/BoundedBox/BoundedBox';
import { isPDFSupported } from '@/_helpers/appUtils';
import { resolveWidgetFieldValue } from '@/_helpers/utils';
import { useEditorStore } from '@/_stores/editorStore';
-import { Container } from '@/AppBuilder/Widgets/Container';
+import { Container } from '@/AppBuilder/Widgets/Container/Container';
import { Listview } from '@/AppBuilder/Widgets/Listview';
import { Tabs } from '@/AppBuilder/Widgets/Tabs';
import { Kanban } from '@/AppBuilder/Widgets/Kanban/Kanban';
From c031d5971865c25e03919037fcaecdc763cf66b4 Mon Sep 17 00:00:00 2001
From: Nakul Nagargade
Date: Wed, 19 Feb 2025 21:00:12 +0530
Subject: [PATCH 07/16] replace settings icon with ne solid icon
---
.../AppCanvas/ConfigHandle/ConfigHandle.jsx | 62 ++++++++++---------
.../AppBuilder/AppCanvas/WidgetWrapper.jsx | 2 +
2 files changed, 34 insertions(+), 30 deletions(-)
diff --git a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx
index e3e38999f3..4deb50ea2e 100644
--- a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx
+++ b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx
@@ -3,6 +3,7 @@ import { shallow } from 'zustand/shallow';
import './configHandle.scss';
import useStore from '@/AppBuilder/_stores/store';
import { findHighestLevelofSelection } from '../Grid/gridUtils';
+import SolidIcon from '@/_ui/Icon/solidIcons/index';
const CONFIG_HANDLE_HEIGHT = 20;
const BUFFER_HEIGHT = 1;
@@ -73,6 +74,7 @@ export const ConfigHandle = ({
background:
visibility === false ? '#c6cad0' : componentType === 'Modal' && isModalOpen ? '#c6cad0' : '#4D72FA',
border: position === 'bottom' ? '1px solid white' : 'none',
+ color: visibility === false && 'var(--text-placeholder)',
}}
className="badge handle-content"
>
@@ -86,42 +88,42 @@ export const ConfigHandle = ({
data-cy={`${componentName?.toLowerCase()}-config-handle`}
className="text-truncate"
>
-
+ {/* Settings Icon */}
+
+
+
{componentName}
+ {/* Divider */}
+
+ {/* Delete Button */}
{!isMultipleComponentsSelected && !shouldFreeze && (
-
-

{
+ deleteComponents([id]);
+ }}
+ >
+
setComponentToInspect(componentName)}
- data-cy={`${componentName.toLowerCase()}-inspect-button`}
- className="config-handle-inspect"
+ fill={visibility === false ? 'var(--text-placeholder)' : '#fff'}
/>
-
{
- deleteComponents([id]);
- }}
- data-cy={`${componentName.toLowerCase()}-delete-button`}
- className="delete-icon"
- />
-
+
)}
diff --git a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx
index ff22963444..895697091f 100644
--- a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx
+++ b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx
@@ -52,7 +52,9 @@ const WidgetWrapper = memo(
height: visibility === false ? '10px' : `${height}px`,
transform: `translate(${layoutData.left * gridWidth}px, ${layoutData.top}px)`,
WebkitFontSmoothing: 'antialiased',
+ border: visibility === false ? `1px solid var(--border-default)` : 'none',
};
+
if (!componentType) return null;
return (
<>
From 53e7de02477c756b48de24f141dc55aec915ba75 Mon Sep 17 00:00:00 2001
From: Nakul Nagargade
Date: Mon, 10 Mar 2025 13:25:20 +0530
Subject: [PATCH 08/16] Fix config handle
---
.../AppCanvas/ConfigHandle/ConfigHandle.jsx | 34 +++++++++++++------
.../AppCanvas/ConfigHandle/configHandle.scss | 17 +---------
2 files changed, 25 insertions(+), 26 deletions(-)
diff --git a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx
index 4deb50ea2e..4c558f3cc2 100644
--- a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx
+++ b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/ConfigHandle.jsx
@@ -111,19 +111,33 @@ export const ConfigHandle = ({
{/* Delete Button */}
{!isMultipleComponentsSelected && !shouldFreeze && (
- {
- deleteComponents([id]);
- }}
- >
-
+
setComponentToInspect(componentName)}
+ data-cy={`${componentName.toLowerCase()}-inspect-button`}
+ className="config-handle-inspect"
/>
-
+ {
+ deleteComponents([id]);
+ }}
+ data-cy={`${componentName.toLowerCase()}-delete-button`}
+ >
+
+
+
)}
diff --git a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/configHandle.scss b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/configHandle.scss
index e7322959e5..5cb1b94268 100644
--- a/frontend/src/AppBuilder/AppCanvas/ConfigHandle/configHandle.scss
+++ b/frontend/src/AppBuilder/AppCanvas/ConfigHandle/configHandle.scss
@@ -31,22 +31,7 @@
.badge {
font-size: 9px;
border-bottom-left-radius: 0;
- border-bottom-right-radius: 0;
-
- .delete-part {
- margin-left: 10px;
- float: right;
- }
-
- .delete-part::before {
- height: 12px;
- display: inline-block;
- width: 2px;
- background-color: rgba(255, 255, 255, 0.8);
- opacity: 0.5;
- content: "";
- vertical-align: middle;
- }
+ border-bottom-right-radius: 0
}
}
From 753f3384e9623a9d73bcef10f1b2d405b86c5eb5 Mon Sep 17 00:00:00 2001
From: Nakul Nagargade
Date: Mon, 10 Mar 2025 13:27:19 +0530
Subject: [PATCH 09/16] fix conflict
---
frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx
index 6403d63201..559c70fe86 100644
--- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx
+++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx
@@ -884,7 +884,7 @@ export default function Grid({ gridWidth, currentLayout }) {
onDrag={(e) => {
// Since onDrag is called multiple times when dragging, hence we are using isDraggingRef to prevent setting state again and again
if (!isDraggingRef.current) {
- useGridStore.getState().actions.setDraggingComponentId(e.target.id);
+ useStore.getState().setDraggingComponentId(e.target.id);
showGridLines();
isDraggingRef.current = true;
}
From 3af5166422e53be73839c7ce70f7bb3733e7ef4b Mon Sep 17 00:00:00 2001
From: Nakul Nagargade
Date: Mon, 10 Mar 2025 13:42:21 +0530
Subject: [PATCH 10/16] Resolve conflict issues
---
frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx
index 559c70fe86..9964b06542 100644
--- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx
+++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx
@@ -56,7 +56,7 @@ export default function Grid({ gridWidth, currentLayout }) {
const getHoveredComponentForGrid = useStore((state) => state.getHoveredComponentForGrid, shallow);
const getResolvedComponent = useStore((state) => state.getResolvedComponent, shallow);
const [canvasBounds, setCanvasBounds] = useState(CANVAS_BOUNDS);
- const draggingComponentId = useGridStore((state) => state.draggingComponentId, shallow);
+ const draggingComponentId = useStore((state) => state.draggingComponentId, shallow);
const resizingComponentId = useGridStore((state) => state.resizingComponentId, shallow);
const [dragParentId, setDragParentId] = useState(null);
const [elementGuidelines, setElementGuidelines] = useState([]);
@@ -641,8 +641,7 @@ export default function Grid({ gridWidth, currentLayout }) {
const currentWidget = boxList.find(({ id }) => {
return id === e.target.id;
});
- document.getElementById('real-canvas')?.classList.remove('show-grid');
- document.getElementById('canvas-' + currentWidget.component?.parent)?.classList.remove('show-grid');
+ hideGridLines();
let _gridWidth = useGridStore.getState().subContainerWidths[currentWidget.component?.parent] || gridWidth;
let width = Math.round(e?.lastEvent?.width / _gridWidth) * _gridWidth;
const height = Math.round(e?.lastEvent?.height / GRID_HEIGHT) * GRID_HEIGHT;
From 7b0fda5037d92b62bc736cae6fd0779b8b478981 Mon Sep 17 00:00:00 2001
From: Nakul Nagargade
Date: Thu, 27 Feb 2025 15:04:25 +0530
Subject: [PATCH 11/16] [feat] : Highlight when the component will be placed
inside the container based components
---
.../src/AppBuilder/AppCanvas/Grid/Grid.css | 69 ++++-------------
.../src/AppBuilder/AppCanvas/Grid/Grid.jsx | 19 ++++-
.../AppBuilder/AppCanvas/Grid/gridUtils.js | 76 ++++++++++++++++++-
.../AppBuilder/AppCanvas/WidgetWrapper.jsx | 1 +
.../AppBuilder/AppCanvas/appCanvasUtils.js | 16 ++++
.../src/AppBuilder/AppCanvas/selecto.scss | 19 +++--
6 files changed, 135 insertions(+), 65 deletions(-)
diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.css b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.css
index 5abcc430d4..e1c6bb3baa 100644
--- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.css
+++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.css
@@ -1,17 +1,6 @@
.target, .nested-target {
position: absolute;
- /* width: 100px;
- height: 100px; */
- /* top: 150px;
- left: 100px; */
- /* line-height: 100px; */
- /* text-align: center; */
- /* background: #ee8; */
- /* color: #333; */
- /* font-weight: bold; */
box-sizing: border-box;
- /* transition: transform 0.1s; */
- /* z-index: 3001; */
}
.target.hovered{
@@ -76,43 +65,6 @@
background: #8DA4EF !important;
}
-
-
-/* Hides all the control lines*/
-/* .moveable-line {
- color: transparent !important;
- --moveable-color: transparent !important;
-}
-
-.moveable-control {
- visibility: hidden;
-}
-
-.target {
- outline: 1px solid #4af;
-} */
-
-
-.main-editor-canvas .widget-target:not(:has(.widget-target:hover)):hover {
- outline: 1px solid #4af;
- z-index: 4 !important;
-}
-
-.main-editor-canvas .widget-target:has(.nested-target:hover):hover {
- outline: 0px solid #4af;
-}
-
-
-.main-editor-canvas .nested-target:not(:has(.nested-target:hover)):hover {
- outline: 1px solid #4af;
- z-index: 4 !important;
-}
-
-.active-target, .resizing-target {
- outline: 1px solid #4af !important;
- /* z-index: 1000000 !important; */
-}
-
.moveable-control-box:not([data-able-groupable]) .moveable-control-box:not(:hover) {
opacity: 0;
}
@@ -141,10 +93,6 @@
height: 0px !important;
}
-.resizing-target * {
- opacity: 0;
-}
-
.moveable-control {
width: 8px !important;
@@ -210,4 +158,19 @@
.moveable-guideline-group {
z-index: 9999;
-}
\ No newline at end of file
+}
+
+.dragging-component-canvas {
+ outline: 1px solid var(--border-accent-strong) !important;
+ outline-offset: 0px; /* Creates space between element and outline */
+ z-index: 999 !important;
+}
+
+.non-dragging-component {
+ outline: 1px dotted var(--border-accent-weak) !important;
+ outline-offset: 0px; /* Creates space between element and outline */
+ z-index: 999 !important;
+}
+
+
+
diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx
index 9964b06542..44f148a000 100644
--- a/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx
+++ b/frontend/src/AppBuilder/AppCanvas/Grid/Grid.jsx
@@ -19,6 +19,9 @@ import {
adjustWidth,
hideGridLines,
showGridLines,
+ handleActivateTargets,
+ handleDeactivateTargets,
+ handleActivateNonDraggingComponents,
} from './gridUtils';
import { useAppVersionStore } from '@/_stores/appVersionStore';
import { resolveWidgetFieldValue } from '@/_helpers/utils';
@@ -580,7 +583,7 @@ export default function Grid({ gridWidth, currentLayout }) {
} else {
document.getElementById('real-canvas').classList.add('show-grid');
}
-
+ handleActivateTargets(currentWidget.component?.parent);
const currentWidth = currentWidget.width * _gridWidth;
const diffWidth = e.width - currentWidth;
const diffHeight = e.height - currentWidget.height;
@@ -632,6 +635,7 @@ export default function Grid({ gridWidth, currentLayout }) {
if (!isComponentVisible(e.target.id)) {
return false;
}
+ handleActivateNonDraggingComponents();
useGridStore.getState().actions.setResizingComponentId(e.target.id);
e.setMin([gridWidth, GRID_HEIGHT]);
}}
@@ -695,17 +699,19 @@ export default function Grid({ gridWidth, currentLayout }) {
} catch (error) {
console.error('ResizeEnd error ->', error);
}
+ handleDeactivateTargets();
setDragParentId(null);
toggleCanvasUpdater();
}}
onResizeGroupStart={({ events }) => {
showGridLines();
+ handleActivateNonDraggingComponents();
}}
onResizeGroup={({ events }) => {
const parentElm = events[0].target.closest('.real-canvas');
const parentWidth = parentElm?.clientWidth;
const parentHeight = parentElm?.clientHeight;
-
+ handleActivateTargets(parentElm?.id?.replace('canvas-', ''));
const { posRight, posLeft, posTop, posBottom } = getPositionForGroupDrag(events, parentWidth, parentHeight);
events.forEach((ev) => {
ev.target.style.width = `${ev.width}px`;
@@ -774,6 +780,7 @@ export default function Grid({ gridWidth, currentLayout }) {
} catch (error) {
console.error('Error resizing group', error);
}
+ handleDeactivateTargets();
toggleCanvasUpdater();
}}
checkInput
@@ -784,6 +791,7 @@ export default function Grid({ gridWidth, currentLayout }) {
}
newDragParentId.current = boxList.find((box) => box.id === e.target.id)?.parent;
e?.moveable?.controlBox?.removeAttribute('data-off-screen');
+
const box = boxList.find((box) => box.id === e.target.id);
// Prevent drag if shift is pressed for SUBCONTAINER_WIDGETS
if (SUBCONTAINER_WIDGETS.includes(box?.component?.component) && e.inputEvent.shiftKey) {
@@ -817,7 +825,6 @@ export default function Grid({ gridWidth, currentLayout }) {
container.contains(e.inputEvent.target)
);
}
-
if (['RangeSlider', 'BoundedBox'].includes(box?.component?.component) || isDragOnInnerElement) {
const targetElems = document.elementsFromPoint(e.clientX, e.clientY);
const isHandle = targetElems.find((ele) => ele.classList.contains('handle-content'));
@@ -825,8 +832,10 @@ export default function Grid({ gridWidth, currentLayout }) {
return false;
}
}
+ handleActivateNonDraggingComponents();
}}
onDragEnd={(e) => {
+ handleDeactivateTargets();
try {
if (isDraggingRef.current) {
useStore.getState().setDraggingComponentId(null);
@@ -961,6 +970,7 @@ export default function Grid({ gridWidth, currentLayout }) {
setDragParentId(newParentId === 'canvas' ? null : newParentId);
newDragParentId.current = newParentId === 'canvas' ? null : newParentId;
prevDragParentId.current = newParentId;
+ handleActivateTargets(newParentId);
}
}
// Postion ghost element exactly as same at dragged element
@@ -987,14 +997,17 @@ export default function Grid({ gridWidth, currentLayout }) {
ev.target.style.transform = `translate(${left}px, ${top}px)`;
});
+ handleActivateTargets(parentElm?.id?.replace('canvas-', ''));
updateNewPosition(events);
}}
onDragGroupStart={({ events }) => {
showGridLines();
setIsGroupDragging(true);
+ handleActivateNonDraggingComponents();
}}
onDragGroupEnd={(e) => {
handleDragGroupEnd(e);
+ handleDeactivateTargets();
toggleCanvasUpdater();
}}
onClickGroup={(e) => {
diff --git a/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js b/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js
index 2889fc06db..da179bc11d 100644
--- a/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js
+++ b/frontend/src/AppBuilder/AppCanvas/Grid/gridUtils.js
@@ -1,7 +1,7 @@
import { useGridStore } from '@/_stores/gridStore';
import { isEmpty } from 'lodash';
import useStore from '@/AppBuilder/_stores/store';
-
+import { getTabId, getSubContainerIdWithSlots } from '../appCanvasUtils';
export function correctBounds(layout, bounds) {
layout = scaleLayouts(layout);
const collidesWith = [];
@@ -414,3 +414,77 @@ export function hideGridLines() {
document.getElementById('real-canvas')?.classList.remove('show-grid');
document.getElementById('real-canvas')?.classList.add('hide-grid');
}
+
+// Track previously active elements for efficient cleanup
+let previousActiveWidgets = null;
+let previousActiveCanvas = null;
+
+export const handleActivateNonDraggingComponents = () => {
+ // Only add non-dragging class to visible components in viewport
+ document.querySelectorAll('.moveable-box:not(.active-target)').forEach((component) => {
+ // Check if element is visible in viewport
+ const rect = component.getBoundingClientRect();
+ const isVisible =
+ rect.top < window.innerHeight && rect.bottom > 0 && rect.left < window.innerWidth && rect.right > 0;
+
+ if (isVisible) {
+ component.classList.add('non-dragging-component');
+ }
+ });
+};
+
+export const handleActivateTargets = (parentId) => {
+ const WIDGETS_WITH_CANVAS_OUTLINE = ['Container', 'Modal', 'Form', 'Listview', 'Kanban'];
+
+ const newParentType = document.getElementById('canvas-' + parentId)?.getAttribute('component-type');
+ let _parentId = parentId;
+ if (newParentType === 'Tabs') {
+ _parentId = getTabId(parentId);
+ } else if (WIDGETS_WITH_CANVAS_OUTLINE.includes(newParentType)) {
+ _parentId = getSubContainerIdWithSlots(parentId);
+ }
+
+ // Clean up previous active elements
+ if (previousActiveWidgets) {
+ previousActiveWidgets.classList.remove('dragging-component-canvas');
+ previousActiveWidgets = null;
+ }
+
+ if (previousActiveCanvas) {
+ previousActiveCanvas.classList.remove('dragging-component-canvas');
+ previousActiveCanvas = null;
+ }
+
+ const parentComponent = document.getElementById(_parentId);
+ if (!parentComponent) return;
+
+ if (WIDGETS_WITH_CANVAS_OUTLINE?.includes(newParentType)) {
+ // If it's multiple canvas in single widget, highlight the specific canvas
+ const canvasElm = document.getElementById('canvas-' + parentId);
+ if (canvasElm) {
+ canvasElm.classList.add('dragging-component-canvas');
+ previousActiveCanvas = canvasElm;
+ }
+ } else {
+ // Otherwise highlight the component box
+ parentComponent.classList.remove('non-dragging-component');
+ parentComponent.classList.add('dragging-component-canvas');
+ previousActiveWidgets = parentComponent;
+ }
+};
+
+export const handleDeactivateTargets = () => {
+ if (previousActiveWidgets) {
+ previousActiveWidgets.classList.remove('dragging-component-canvas');
+ previousActiveWidgets = null;
+ }
+
+ if (previousActiveCanvas) {
+ previousActiveCanvas.classList.remove('dragging-component-canvas');
+ previousActiveCanvas = null;
+ }
+
+ document.querySelectorAll('.non-dragging-component').forEach((component) => {
+ component.classList.remove('non-dragging-component');
+ });
+};
diff --git a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx
index 895697091f..0f1cb3359f 100644
--- a/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx
+++ b/frontend/src/AppBuilder/AppCanvas/WidgetWrapper.jsx
@@ -69,6 +69,7 @@ const WidgetWrapper = memo(
data-id={`${id}`}
id={id}
widgetid={id}
+ component-type={componentType}
style={{
// zIndex: mode === 'view' && widget.component.component == 'Datepicker' ? 2 : null,
...styles,
diff --git a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js
index 41bc116ec3..8dae9e001c 100644
--- a/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js
+++ b/frontend/src/AppBuilder/AppCanvas/appCanvasUtils.js
@@ -690,3 +690,19 @@ export const getParentWidgetFromId = (parentType, parentId) => {
}
return parentType;
};
+
+export const getTabId = (parentId) => {
+ return parentId.split('-').slice(0, -1).join('-');
+};
+
+export const getSubContainerIdWithSlots = (parentId) => {
+ let cleanParentId = parentId;
+ if (parentId) {
+ if (parentId.includes('header')) {
+ cleanParentId = parentId.replace('-header', '');
+ } else if (parentId.includes('footer')) {
+ cleanParentId = parentId.replace('-footer', '');
+ }
+ }
+ return cleanParentId;
+};
diff --git a/frontend/src/AppBuilder/AppCanvas/selecto.scss b/frontend/src/AppBuilder/AppCanvas/selecto.scss
index 9ca8a37f41..5602b35d5a 100644
--- a/frontend/src/AppBuilder/AppCanvas/selecto.scss
+++ b/frontend/src/AppBuilder/AppCanvas/selecto.scss
@@ -3,15 +3,18 @@
}
.main-editor-canvas .widget-target:not(:has(.widget-target:hover)):hover {
- z-index: 4 !important;
-}
-
-.main-editor-canvas .widget-target:has(.nested-target:hover):hover {
- outline: 0px solid #4af;
-}
-
-.main-editor-canvas .nested-target:not(:has(.nested-target:hover)):hover {
outline: 1px solid #4af;
z-index: 4 !important;
}
+.main-editor-canvas .nested-target:not(:has(.nested-target:hover)):hover {
+ // outline: 1px solid #4af;
+ z-index: 4 !important;
+}
+
+// .main-editor-canvas .widget-target:hover {
+// outline: 1px solid #4af;
+// }
+
+
+
From ae3142dd4e00bd5b78f3414527b358d587e6b136 Mon Sep 17 00:00:00 2001
From: Rudhra Deep Biswas <98055396+rudeUltra@users.noreply.github.com>
Date: Mon, 10 Mar 2025 17:19:44 +0530
Subject: [PATCH 12/16] Permission FIxes including appId and DataSourceId
(#12184)
* init
* remove ForwardAbility
---
server/src/modules/app-git/ability/guard.ts | 4 --
server/src/modules/app-git/ability/index.ts | 40 +++++++++++--------
server/src/modules/app/ability-factory.ts | 1 -
.../src/modules/app/guards/ability.guard.ts | 2 +-
.../src/modules/data-sources/ability/index.ts | 28 +++++++++----
.../ability/app/guard.ts | 4 --
.../ability/data-source/guard.ts | 4 --
.../modules/licensing/constants/PlanTerms.ts | 2 +-
.../src/modules/tooljet-db/ability/guard.ts | 4 --
server/src/modules/versions/ability/index.ts | 21 +++++++---
.../modules/workflows/ability/app/guard.ts | 4 --
11 files changed, 61 insertions(+), 53 deletions(-)
diff --git a/server/src/modules/app-git/ability/guard.ts b/server/src/modules/app-git/ability/guard.ts
index a937a4c6d5..41c7bf9ed9 100644
--- a/server/src/modules/app-git/ability/guard.ts
+++ b/server/src/modules/app-git/ability/guard.ts
@@ -20,8 +20,4 @@ export class AppGitAbilityGuard extends AbilityGuard {
protected getSubjectType() {
return App;
}
-
- protected forwardAbility(): boolean {
- return true;
- }
}
diff --git a/server/src/modules/app-git/ability/index.ts b/server/src/modules/app-git/ability/index.ts
index 81d7bea81f..69207d6221 100644
--- a/server/src/modules/app-git/ability/index.ts
+++ b/server/src/modules/app-git/ability/index.ts
@@ -14,7 +14,13 @@ export class AppGitAbilityFactory extends AbilityFactory
return App;
}
- protected defineAbilityFor(can: AbilityBuilder['can'], UserAllPermissions: UserAllPermissions): void {
+ protected defineAbilityFor(
+ can: AbilityBuilder['can'],
+ UserAllPermissions: UserAllPermissions,
+ extractedMetadata: { moduleName: string; features: string[] },
+ request?: any
+ ): void {
+ const appId = request?.tj_resource_id;
const { superAdmin, isAdmin, userPermission } = UserAllPermissions;
const userAppGitPermissions = userPermission?.APP;
@@ -35,11 +41,12 @@ export class AppGitAbilityFactory extends AbilityFactory
}
// READ-based features
- if (isAllAppsViewable || userAppGitPermissions?.viewableAppsId?.length) {
+ if (
+ isAllAppsViewable ||
+ (userAppGitPermissions?.viewableAppsId?.length && appId && userAppGitPermissions?.viewableAppsId?.includes(appId))
+ ) {
can(FEATURE_KEY.GIT_GET_APPS, App);
- can(FEATURE_KEY.GIT_GET_APP, App, {
- id: { $in: isAllAppsViewable ? undefined : userAppGitPermissions?.viewableAppsId },
- });
+ can(FEATURE_KEY.GIT_GET_APP, App);
}
// CREATE-based features
@@ -48,20 +55,21 @@ export class AppGitAbilityFactory extends AbilityFactory
}
// UPDATE-based features
- if (isAllAppsEditable || userAppGitPermissions?.editableAppsId?.length) {
- can(FEATURE_KEY.GIT_UPDATE_APP, App, {
- id: { $in: isAllAppsEditable ? undefined : userAppGitPermissions?.editableAppsId },
- });
- can(FEATURE_KEY.GIT_SYNC_APP, App, {
- id: { $in: isAllAppsEditable ? undefined : userAppGitPermissions?.editableAppsId },
- });
+ if (
+ isAllAppsEditable ||
+ (userAppGitPermissions?.editableAppsId?.length && appId && userAppGitPermissions.editableAppsId.includes(appId))
+ ) {
+ can(FEATURE_KEY.GIT_UPDATE_APP, App);
+ can(FEATURE_KEY.GIT_SYNC_APP, App);
}
// Additional checks based on specific actions
- if (userAppGitPermissions?.editableAppsId?.length) {
- can(FEATURE_KEY.GIT_GET_APP_CONFIG, App, {
- id: { $in: userAppGitPermissions.editableAppsId },
- });
+ if (
+ userAppGitPermissions?.editableAppsId?.length &&
+ appId &&
+ userAppGitPermissions.editableAppsId.includes(appId)
+ ) {
+ can(FEATURE_KEY.GIT_GET_APP_CONFIG, App);
}
}
}
diff --git a/server/src/modules/app/ability-factory.ts b/server/src/modules/app/ability-factory.ts
index 9450f21906..d461d53788 100644
--- a/server/src/modules/app/ability-factory.ts
+++ b/server/src/modules/app/ability-factory.ts
@@ -35,7 +35,6 @@ export abstract class AbilityFactory {
}
: {}),
}));
-
if (request) {
request.tj_user_permissions = userPermission;
}
diff --git a/server/src/modules/app/guards/ability.guard.ts b/server/src/modules/app/guards/ability.guard.ts
index d6c35de5ae..690129f81c 100644
--- a/server/src/modules/app/guards/ability.guard.ts
+++ b/server/src/modules/app/guards/ability.guard.ts
@@ -83,7 +83,7 @@ export abstract class AbilityGuard implements CanActivate {
user,
{ moduleName: module, features },
resourceArray,
- this.forwardAbility() ? request : undefined
+ request
);
if (this.forwardAbility()) {
diff --git a/server/src/modules/data-sources/ability/index.ts b/server/src/modules/data-sources/ability/index.ts
index e1af13e5ab..a256bffabf 100644
--- a/server/src/modules/data-sources/ability/index.ts
+++ b/server/src/modules/data-sources/ability/index.ts
@@ -15,7 +15,12 @@ export class FeatureAbilityFactory extends AbilityFactory
return DataSource;
}
- protected defineAbilityFor(can: AbilityBuilder['can'], UserAllPermissions: UserAllPermissions): void {
+ protected defineAbilityFor(
+ can: AbilityBuilder['can'],
+ UserAllPermissions: UserAllPermissions,
+ extractedMetadata: { moduleName: string; features: string[] },
+ request?: any
+ ): void {
// Data source permissions
// EE - data source create/delete -> full access
// CE - Admin - full access. builder -> use access
@@ -27,6 +32,8 @@ export class FeatureAbilityFactory extends AbilityFactory
const isCanDelete = userPermission.dataSourceDelete;
const isAllViewable = !!resourcePermissions?.isAllUsable;
+ const dataSourceId = request?.tj_resource_id;
+
// Oauth end points available to all
can(FEATURE_KEY.GET_OAUTH2_BASE_URL, DataSource);
can(FEATURE_KEY.AUTHORIZE, DataSource);
@@ -81,11 +88,14 @@ export class FeatureAbilityFactory extends AbilityFactory
return;
}
- if (resourcePermissions?.configurableDataSourceId?.length) {
+ if (
+ resourcePermissions?.configurableDataSourceId?.length &&
+ dataSourceId &&
+ resourcePermissions?.configurableDataSourceId?.includes(dataSourceId)
+ ) {
can(
[FEATURE_KEY.GET, FEATURE_KEY.UPDATE, FEATURE_KEY.GET_BY_ENVIRONMENT, FEATURE_KEY.TEST_CONNECTION],
- DataSource,
- { id: { $in: resourcePermissions.configurableDataSourceId } }
+ DataSource
);
}
@@ -93,10 +103,12 @@ export class FeatureAbilityFactory extends AbilityFactory
can([FEATURE_KEY.GET, FEATURE_KEY.GET_BY_ENVIRONMENT], DataSource);
return;
}
- if (resourcePermissions.usableDataSourcesId?.length) {
- can([FEATURE_KEY.GET, FEATURE_KEY.GET_BY_ENVIRONMENT], DataSource, {
- id: { $in: resourcePermissions.usableDataSourcesId },
- });
+ if (
+ resourcePermissions.usableDataSourcesId?.length &&
+ dataSourceId &&
+ resourcePermissions?.usableDataSourcesId?.includes(dataSourceId)
+ ) {
+ can([FEATURE_KEY.GET, FEATURE_KEY.GET_BY_ENVIRONMENT], DataSource);
}
}
}
diff --git a/server/src/modules/import-export-resources/ability/app/guard.ts b/server/src/modules/import-export-resources/ability/app/guard.ts
index 5c0e3943bb..291e34cdd8 100644
--- a/server/src/modules/import-export-resources/ability/app/guard.ts
+++ b/server/src/modules/import-export-resources/ability/app/guard.ts
@@ -15,10 +15,6 @@ export class FeatureAbilityGuard extends AbilityGuard {
return App;
}
- protected forwardAbility(): boolean {
- return true;
- }
-
protected getResource(): ResourceDetails | ResourceDetails[] {
return [
{
diff --git a/server/src/modules/import-export-resources/ability/data-source/guard.ts b/server/src/modules/import-export-resources/ability/data-source/guard.ts
index cd858fbe45..5cb58f68f9 100644
--- a/server/src/modules/import-export-resources/ability/data-source/guard.ts
+++ b/server/src/modules/import-export-resources/ability/data-source/guard.ts
@@ -12,8 +12,4 @@ export class FeatureAbilityGuard extends AbilityGuard {
protected getAbilityFactory() {
return FeatureAbilityFactory;
}
-
- protected forwardAbility(): boolean {
- return true;
- }
}
diff --git a/server/src/modules/licensing/constants/PlanTerms.ts b/server/src/modules/licensing/constants/PlanTerms.ts
index 57b0ecb7bd..0eb05cfe6c 100644
--- a/server/src/modules/licensing/constants/PlanTerms.ts
+++ b/server/src/modules/licensing/constants/PlanTerms.ts
@@ -48,7 +48,7 @@ export const BASIC_PLAN_TERMS: Partial = {
export const BASIC_PLAN_SETTINGS = {
ALLOW_PERSONAL_WORKSPACE: {
- value: 'true',
+ value: 'false',
},
WHITE_LABEL_LOGO: {
value: '',
diff --git a/server/src/modules/tooljet-db/ability/guard.ts b/server/src/modules/tooljet-db/ability/guard.ts
index 564f05979d..b56572a62e 100644
--- a/server/src/modules/tooljet-db/ability/guard.ts
+++ b/server/src/modules/tooljet-db/ability/guard.ts
@@ -12,8 +12,4 @@ export class FeatureAbilityGuard extends AbilityGuard {
protected getSubjectType() {
return InternalTable;
}
-
- protected forwardAbility(): boolean {
- return true;
- }
}
diff --git a/server/src/modules/versions/ability/index.ts b/server/src/modules/versions/ability/index.ts
index 551526c208..5237a97ec9 100644
--- a/server/src/modules/versions/ability/index.ts
+++ b/server/src/modules/versions/ability/index.ts
@@ -15,7 +15,13 @@ export class FeatureAbilityFactory extends AbilityFactory
return App;
}
- protected defineAbilityFor(can: AbilityBuilder['can'], UserAllPermissions: UserAllPermissions): void {
+ protected defineAbilityFor(
+ can: AbilityBuilder['can'],
+ UserAllPermissions: UserAllPermissions,
+ extractedMetadata: { moduleName: string; features: string[] },
+ request?: any
+ ): void {
+ const appId = request?.tj_resource_id;
const { superAdmin, isAdmin, userPermission } = UserAllPermissions;
const userAppPermissions = userPermission?.[MODULES.APP];
@@ -52,7 +58,7 @@ export class FeatureAbilityFactory extends AbilityFactory
return;
}
- if (userAppPermissions?.editableAppsId?.length) {
+ if (userAppPermissions?.editableAppsId?.length && appId && userAppPermissions.editableAppsId.includes(appId)) {
can(
[
FEATURE_KEY.GET,
@@ -76,16 +82,19 @@ export class FeatureAbilityFactory extends AbilityFactory
FEATURE_KEY.UPDATE_EVENT,
FEATURE_KEY.DELETE_EVENT,
],
- App,
- { id: { $in: userAppPermissions.editableAppsId } }
+ App
);
}
if (isAllAppsViewable) {
// add view permissions for all apps
can([FEATURE_KEY.GET_EVENTS], App);
- } else if (userAppPermissions?.viewableAppsId?.length) {
- can([FEATURE_KEY.GET_EVENTS], App, { id: { $in: userAppPermissions.viewableAppsId } });
+ } else if (
+ userAppPermissions?.viewableAppsId?.length &&
+ appId &&
+ userAppPermissions.viewableAppsId.includes(appId)
+ ) {
+ can([FEATURE_KEY.GET_EVENTS], App);
}
}
}
diff --git a/server/src/modules/workflows/ability/app/guard.ts b/server/src/modules/workflows/ability/app/guard.ts
index 3f97520bbf..251e575a2a 100644
--- a/server/src/modules/workflows/ability/app/guard.ts
+++ b/server/src/modules/workflows/ability/app/guard.ts
@@ -15,10 +15,6 @@ export class FeatureAbilityGuard extends AbilityGuard {
return App;
}
- protected forwardAbility(): boolean {
- return true;
- }
-
protected getResource(): ResourceDetails | ResourceDetails[] {
return [
{
From 6a7d72f759c71e60ac1be945e7b7ec5705c89b77 Mon Sep 17 00:00:00 2001
From: Nithin David Thomas <1277421+nithindavid@users.noreply.github.com>
Date: Mon, 10 Mar 2025 21:27:31 +0530
Subject: [PATCH 13/16] Adds CSA for forms
---
.../AppBuilder/WidgetManager/widgets/form.js | 18 +++++++++
frontend/src/AppBuilder/Widgets/Form/Form.jsx | 38 +++++++++++++++----
.../src/AppBuilder/Widgets/Form/form.scss | 19 +++++++++-
.../src/Editor/WidgetManager/configs/form.js | 18 +++++++++
.../apps/services/widget-config/form.js | 18 +++++++++
5 files changed, 102 insertions(+), 9 deletions(-)
diff --git a/frontend/src/AppBuilder/WidgetManager/widgets/form.js b/frontend/src/AppBuilder/WidgetManager/widgets/form.js
index 7ab4bd26a6..2d8eb7f0a8 100644
--- a/frontend/src/AppBuilder/WidgetManager/widgets/form.js
+++ b/frontend/src/AppBuilder/WidgetManager/widgets/form.js
@@ -362,6 +362,9 @@ export const formConfig = {
exposedVariables: {
data: {},
isValid: true,
+ isVisible: true,
+ isDisabled: false,
+ isLoading: false,
},
actions: [
{
@@ -372,6 +375,21 @@ export const formConfig = {
handle: 'resetForm',
displayName: 'Reset Form',
},
+ {
+ handle: 'setVisibility',
+ displayName: 'Set visibility',
+ params: [{ handle: 'setVisibility', displayName: 'Set Visibility', defaultValue: '{{true}}', type: 'toggle' }],
+ },
+ {
+ handle: 'setDisable',
+ displayName: 'Set Disable',
+ params: [{ handle: 'setDisable', displayName: 'Set Disable', defaultValue: '{{false}}', type: 'toggle' }],
+ },
+ {
+ handle: 'setLoading',
+ displayName: 'Set Loading',
+ params: [{ handle: 'setLoading', displayName: 'Set Loading', defaultValue: '{{false}}', type: 'toggle' }],
+ },
],
definition: {
others: {
diff --git a/frontend/src/AppBuilder/Widgets/Form/Form.jsx b/frontend/src/AppBuilder/Widgets/Form/Form.jsx
index b61d40c194..afeb4cf844 100644
--- a/frontend/src/AppBuilder/Widgets/Form/Form.jsx
+++ b/frontend/src/AppBuilder/Widgets/Form/Form.jsx
@@ -8,6 +8,7 @@ import { onComponentClick, removeFunctionObjects } from '@/_helpers/appUtils';
import { deepClone } from '@/_helpers/utilities/utils.helpers';
import RenderSchema from './RenderSchema';
import useStore from '@/AppBuilder/_stores/store';
+import { useExposeState } from '@/AppBuilder/_hooks/useExposeVariables';
import { shallow } from 'zustand/shallow';
import {
CONTAINER_FORM_CANVAS_PADDING,
@@ -55,6 +56,13 @@ export const Form = function Form(props) {
visibility,
disabledState,
} = properties;
+ const { isDisabled, isVisible, isLoading } = useExposeState(
+ properties.loadingState,
+ properties.visibility,
+ properties.disabledState,
+ setExposedVariables,
+ setExposedVariable
+ );
const backgroundColor =
['#fff', '#ffffffff'].includes(styles.backgroundColor) && darkMode ? '#232E3C' : styles.backgroundColor;
const computedStyles = {
@@ -62,7 +70,7 @@ export const Form = function Form(props) {
borderRadius: borderRadius ? parseFloat(borderRadius) : 0,
border: `${SUBCONTAINER_CANVAS_BORDER_WIDTH}px solid ${borderColor}`,
height,
- display: visibility ? 'flex' : 'none',
+ display: isVisible ? 'flex' : 'none',
position: 'relative',
boxShadow,
flexDirection: 'column',
@@ -305,17 +313,24 @@ export const Form = function Form(props) {
}}
componentType="Form"
/>
+ {isDisabled && (
+
)}
- {loadingState ? (
-
-
-
-
+ {isLoading ? (
+
) : (
-
+
{!advanced && (
+ {isDisabled && (
+
)}
diff --git a/frontend/src/AppBuilder/Widgets/Form/form.scss b/frontend/src/AppBuilder/Widgets/Form/form.scss
index 88a5ad055e..530e837eb2 100644
--- a/frontend/src/AppBuilder/Widgets/Form/form.scss
+++ b/frontend/src/AppBuilder/Widgets/Form/form.scss
@@ -1,7 +1,7 @@
.wj-form-header {
position: relative;
&::after {
- content: '';
+ content: "";
position: absolute;
bottom: 0;
left: -7px;
@@ -14,7 +14,7 @@
.wj-form-footer {
position: relative;
&::after {
- content: '';
+ content: "";
position: absolute;
top: 0;
left: -7px;
@@ -23,3 +23,18 @@
background-color: var(--border-weak);
}
}
+
+.tj-form-disabled-overlay {
+ /* TODO: Make slot overlays common */
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ background-color: rgba(255, 255, 255, 0.8);
+ z-index: 1;
+ margin: 0;
+
+ box-sizing: content-box;
+ padding: 4px 0;
+}
diff --git a/frontend/src/Editor/WidgetManager/configs/form.js b/frontend/src/Editor/WidgetManager/configs/form.js
index 7ab4bd26a6..2d8eb7f0a8 100644
--- a/frontend/src/Editor/WidgetManager/configs/form.js
+++ b/frontend/src/Editor/WidgetManager/configs/form.js
@@ -362,6 +362,9 @@ export const formConfig = {
exposedVariables: {
data: {},
isValid: true,
+ isVisible: true,
+ isDisabled: false,
+ isLoading: false,
},
actions: [
{
@@ -372,6 +375,21 @@ export const formConfig = {
handle: 'resetForm',
displayName: 'Reset Form',
},
+ {
+ handle: 'setVisibility',
+ displayName: 'Set visibility',
+ params: [{ handle: 'setVisibility', displayName: 'Set Visibility', defaultValue: '{{true}}', type: 'toggle' }],
+ },
+ {
+ handle: 'setDisable',
+ displayName: 'Set Disable',
+ params: [{ handle: 'setDisable', displayName: 'Set Disable', defaultValue: '{{false}}', type: 'toggle' }],
+ },
+ {
+ handle: 'setLoading',
+ displayName: 'Set Loading',
+ params: [{ handle: 'setLoading', displayName: 'Set Loading', defaultValue: '{{false}}', type: 'toggle' }],
+ },
],
definition: {
others: {
diff --git a/server/src/modules/apps/services/widget-config/form.js b/server/src/modules/apps/services/widget-config/form.js
index 7ab4bd26a6..2d8eb7f0a8 100644
--- a/server/src/modules/apps/services/widget-config/form.js
+++ b/server/src/modules/apps/services/widget-config/form.js
@@ -362,6 +362,9 @@ export const formConfig = {
exposedVariables: {
data: {},
isValid: true,
+ isVisible: true,
+ isDisabled: false,
+ isLoading: false,
},
actions: [
{
@@ -372,6 +375,21 @@ export const formConfig = {
handle: 'resetForm',
displayName: 'Reset Form',
},
+ {
+ handle: 'setVisibility',
+ displayName: 'Set visibility',
+ params: [{ handle: 'setVisibility', displayName: 'Set Visibility', defaultValue: '{{true}}', type: 'toggle' }],
+ },
+ {
+ handle: 'setDisable',
+ displayName: 'Set Disable',
+ params: [{ handle: 'setDisable', displayName: 'Set Disable', defaultValue: '{{false}}', type: 'toggle' }],
+ },
+ {
+ handle: 'setLoading',
+ displayName: 'Set Loading',
+ params: [{ handle: 'setLoading', displayName: 'Set Loading', defaultValue: '{{false}}', type: 'toggle' }],
+ },
],
definition: {
others: {
From 528c354ed56bdf7c3c5aa4a18b8142955c92a9bd Mon Sep 17 00:00:00 2001
From: Akshay
Date: Mon, 10 Mar 2025 23:31:57 +0530
Subject: [PATCH 14/16] fix workflow executions in modularistion (#12124)
---
.../Components/DataSourceSelect.jsx | 10 +--
frontend/src/_helpers/constants.js | 1 +
server/src/dto/app-version-update.dto.ts | 13 ++++
server/src/modules/apps/dto/index.ts | 8 ++-
server/src/modules/apps/service.ts | 4 +-
server/src/modules/apps/util.service.ts | 67 ++++++++++++++++++-
server/src/modules/data-queries/service.ts | 11 +--
server/src/modules/versions/service.ts | 4 ++
server/src/modules/workflows/module.ts | 1 +
9 files changed, 102 insertions(+), 17 deletions(-)
diff --git a/frontend/src/Editor/QueryManager/Components/DataSourceSelect.jsx b/frontend/src/Editor/QueryManager/Components/DataSourceSelect.jsx
index 7e8865cdfe..d1afd378f4 100644
--- a/frontend/src/Editor/QueryManager/Components/DataSourceSelect.jsx
+++ b/frontend/src/Editor/QueryManager/Components/DataSourceSelect.jsx
@@ -7,7 +7,6 @@ import { getWorkspaceId, decodeEntities } from '@/_helpers/utils';
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
import { useDataSources, useGlobalDataSources, useSampleDataSource } from '@/_stores/dataSourcesStore';
import { useDataQueriesActions } from '@/_stores/dataQueriesStore';
-import { staticDataSources as staticDatasources } from '../constants';
import { useQueryPanelActions } from '@/_stores/queryPanelStore';
import Search from '@/_ui/Icon/solidIcons/Search';
import { Tooltip } from 'react-tooltip';
@@ -16,7 +15,7 @@ import { canCreateDataSource } from '@/_helpers';
import './../queryManager.theme.scss';
import { DATA_SOURCE_TYPE } from '@/_helpers/constants';
-function DataSourceSelect({ isDisabled, selectRef, closePopup, workflowDataSources, onNewNode, defaultDataSources }) {
+function DataSourceSelect({ isDisabled, selectRef, closePopup, workflowDataSources, onNewNode, staticDataSources }) {
const dataSources = useDataSources();
const globalDataSources = useGlobalDataSources();
const sampleDataSource = useSampleDataSource();
@@ -33,11 +32,6 @@ function DataSourceSelect({ isDisabled, selectRef, closePopup, workflowDataSourc
closePopup();
};
- const workflowsEnabled = window.public_config?.ENABLE_WORKFLOWS_FEATURE == 'true';
- const staticDataSources = workflowsEnabled
- ? staticDatasources
- : staticDatasources.filter((ds) => ds?.kind !== 'workflows');
-
useEffect(() => {
const shouldAddSampleDataSource = !!sampleDataSource;
const allDataSources = [...dataSources, ...globalDataSources, shouldAddSampleDataSource && sampleDataSource].filter(
@@ -148,7 +142,7 @@ function DataSourceSelect({ isDisabled, selectRef, closePopup, workflowDataSourc
),
isDisabled: true,
- options: defaultDataSources?.map((source) => ({
+ options: staticDataSources?.map((source) => ({
label: (
{' '}
diff --git a/frontend/src/_helpers/constants.js b/frontend/src/_helpers/constants.js
index 1c3c3d48b6..79275fc6df 100644
--- a/frontend/src/_helpers/constants.js
+++ b/frontend/src/_helpers/constants.js
@@ -129,6 +129,7 @@ export const DATA_SOURCE_TYPE = {
LOCAL: 'local',
GLOBAL: 'global',
STATIC: 'static',
+ DEFAULT: 'default',
};
export const SAMPLE_DB_KIND = {
diff --git a/server/src/dto/app-version-update.dto.ts b/server/src/dto/app-version-update.dto.ts
index 6c7aadb71c..cc83273ba0 100644
--- a/server/src/dto/app-version-update.dto.ts
+++ b/server/src/dto/app-version-update.dto.ts
@@ -26,4 +26,17 @@ export class AppVersionUpdateDto {
@IsOptional()
pageSettings: any;
+
+ // Workflow related fields
+ @IsOptional()
+ @IsString()
+ @IsUUID()
+ currentEnvironmentId: string;
+
+ @IsOptional()
+ definition: any;
+
+ @IsOptional()
+ @IsBoolean()
+ is_user_switched_version: boolean;
}
diff --git a/server/src/modules/apps/dto/index.ts b/server/src/modules/apps/dto/index.ts
index 63d93bd9d3..532740273a 100644
--- a/server/src/modules/apps/dto/index.ts
+++ b/server/src/modules/apps/dto/index.ts
@@ -1,6 +1,7 @@
import { sanitizeInput } from '@helpers/utils.helper';
-import { IsString, IsOptional, IsNotEmpty, MaxLength, IsBoolean, IsUUID } from 'class-validator';
+import { IsString, IsOptional, IsNotEmpty, MaxLength, IsBoolean, IsUUID, IsEnum } from 'class-validator';
import { Exclude, Expose, Transform } from 'class-transformer';
+import { APP_TYPES } from '../constants';
export class AppCreateDto {
@IsNotEmpty()
@@ -12,6 +13,11 @@ export class AppCreateDto {
@IsString()
@MaxLength(200, { message: 'Maximum length has been reached.' })
icon?: string;
+
+ @IsNotEmpty()
+ @IsString()
+ @IsEnum(APP_TYPES, { message: 'Invalid app type.' })
+ type: string;
}
export class AppUpdateDto {
diff --git a/server/src/modules/apps/service.ts b/server/src/modules/apps/service.ts
index 7ad0ca351a..60c6f8bc15 100644
--- a/server/src/modules/apps/service.ts
+++ b/server/src/modules/apps/service.ts
@@ -60,9 +60,9 @@ export class AppsService implements IAppsService {
protected readonly aiUtilService: AiUtilService
) {}
async create(user: User, appCreateDto: AppCreateDto) {
- const { name, icon } = appCreateDto;
+ const { name, icon, type } = appCreateDto;
return await dbTransactionWrap(async (manager: EntityManager) => {
- const app = await this.appsUtilService.create(name, user, APP_TYPES.FRONT_END, manager);
+ const app = await this.appsUtilService.create(name, user, type, manager);
const appUpdateDto = new AppUpdateDto();
appUpdateDto.name = name;
diff --git a/server/src/modules/apps/util.service.ts b/server/src/modules/apps/util.service.ts
index 7be1609b32..36def10913 100644
--- a/server/src/modules/apps/util.service.ts
+++ b/server/src/modules/apps/util.service.ts
@@ -11,7 +11,7 @@ import {
NotAcceptableException,
NotFoundException,
} from '@nestjs/common';
-import { EntityManager, SelectQueryBuilder } from 'typeorm';
+import { EntityManager, MoreThan, SelectQueryBuilder } from 'typeorm';
import { v4 as uuidv4 } from 'uuid';
import { AppsRepository } from './repository';
import { AppVersion } from '@entities/app_version.entity';
@@ -36,6 +36,7 @@ import { AbilityService } from '@modules/ability/interfaces/IService';
import { DataSourcesRepository } from '@modules/data-sources/repository';
import { IAppsUtilService } from './interfaces/IUtilService';
import { DataSourcesUtilService } from '@modules/data-sources/util.service';
+import { AppVersionUpdateDto } from '@dto/app-version-update.dto';
@Injectable()
export class AppsUtilService implements IAppsUtilService {
@@ -281,6 +282,70 @@ export class AppsUtilService implements IAppsUtilService {
}, manager);
}
+ async updateWorflowVersion(version: AppVersion, body: AppVersionUpdateDto, app: App) {
+ const { name, currentEnvironmentId, definition } = body;
+ const { currentVersionId, organizationId } = app;
+ let currentEnvironment: AppEnvironment;
+
+ if (version.id === currentVersionId && !body?.is_user_switched_version)
+ throw new BadRequestException('You cannot update a released version');
+
+ if (currentEnvironmentId || definition) {
+ currentEnvironment = await AppEnvironment.findOne({
+ where: { id: version.currentEnvironmentId },
+ });
+ }
+
+ const editableParams = {};
+ if (name) {
+ //means user is trying to update the name
+ const versionNameExists = await this.versionRepository.findOne({
+ where: { name, appId: version.appId },
+ });
+
+ if (versionNameExists) {
+ throw new BadRequestException('Version name already exists.');
+ }
+ editableParams['name'] = name;
+ }
+
+ //check if the user is trying to promote the environment & raise an error if the currentEnvironmentId is not correct
+ if (currentEnvironmentId) {
+ if (!(await this.licenseTermsService.getLicenseTerms(LICENSE_FIELD.MULTI_ENVIRONMENT))) {
+ throw new BadRequestException('You do not have permissions to perform this action');
+ }
+
+ if (version.currentEnvironmentId !== currentEnvironmentId) {
+ throw new NotAcceptableException();
+ }
+ const nextEnvironment = await AppEnvironment.findOne({
+ select: ['id'],
+ where: {
+ priority: MoreThan(currentEnvironment.priority),
+ organizationId,
+ },
+ order: { priority: 'ASC' },
+ });
+ editableParams['currentEnvironmentId'] = nextEnvironment.id;
+ }
+
+ if (definition) {
+ const environments = await AppEnvironment.count({
+ where: {
+ organizationId,
+ },
+ });
+ if (environments > 1 && currentEnvironment.priority !== 1 && !body?.is_user_switched_version) {
+ throw new BadRequestException('You cannot update a promoted version');
+ }
+ editableParams['definition'] = definition;
+ }
+
+ editableParams['updatedAt'] = new Date();
+
+ return await this.versionRepository.update(version.id, editableParams);
+ }
+
protected async getEnvironmentOfVersion(versionId: string, manager: EntityManager): Promise
{
return manager
.createQueryBuilder(AppEnvironment, 'app_environments')
diff --git a/server/src/modules/data-queries/service.ts b/server/src/modules/data-queries/service.ts
index 874b3b0ee2..e8852da22d 100644
--- a/server/src/modules/data-queries/service.ts
+++ b/server/src/modules/data-queries/service.ts
@@ -191,11 +191,12 @@ export class DataQueriesService implements IDataQueriesService {
async changeQueryDataSource(user: User, queryId: string, dataSource: DataSource, newDataSourceId: string) {
return dbTransactionWrap(async (manager: EntityManager) => {
- const newDataSource = await this.dataSourceRepository.findOne({ where: { id: newDataSourceId } });
- if (dataSource.kind !== newDataSource.kind) {
- throw new BadRequestException();
- }
- return this.dataQueryRepository.updateOne(queryId, { dataSourceId: newDataSourceId }, manager);
+ const newDataSource = await this.dataSourceRepository.findOneOrFail({ where: { id: newDataSourceId } });
+ // FIXME: Disabling this check as workflows can change data source of a query with different kind
+ // if (dataSource.kind !== newDataSource.kind && dataSource) {
+ // throw new BadRequestException();
+ // }
+ return this.dataQueryRepository.updateOne(queryId, { dataSourceId: newDataSource.id }, manager);
// TODO: Audit logs
});
diff --git a/server/src/modules/versions/service.ts b/server/src/modules/versions/service.ts
index c4e263456f..e50b66b50b 100644
--- a/server/src/modules/versions/service.ts
+++ b/server/src/modules/versions/service.ts
@@ -175,6 +175,10 @@ export class VersionService implements IVersionService {
await this.versionsUtilService.updateVersion(appVersion, appVersionUpdateDto);
+ if (app.type === 'workflow') {
+ await this.appUtilService.updateWorflowVersion(appVersion, appVersionUpdateDto, app);
+ }
+
this.eventEmitter.emit('auditLogEntry', {
userId: user.id,
organizationId: user.organizationId,
diff --git a/server/src/modules/workflows/module.ts b/server/src/modules/workflows/module.ts
index 1e39e5123c..e71c52df50 100644
--- a/server/src/modules/workflows/module.ts
+++ b/server/src/modules/workflows/module.ts
@@ -98,6 +98,7 @@ export class WorkflowsModule {
UserRepository,
DataSourcesRepository,
DataQueryRepository,
+ DataSourcesRepository,
OrganizationConstantRepository,
VersionRepository,
AppsService,
From 8ec28e6668823869b010d3ca08d81431e0302e94 Mon Sep 17 00:00:00 2001
From: Akshay Sasidharan
Date: Mon, 10 Mar 2025 23:35:11 +0530
Subject: [PATCH 15/16] update submodule
---
frontend/ee | 2 +-
server/ee | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/frontend/ee b/frontend/ee
index 48b34618a9..6da03a83bd 160000
--- a/frontend/ee
+++ b/frontend/ee
@@ -1 +1 @@
-Subproject commit 48b34618a96d70a64fa6579a54da941e457899d0
+Subproject commit 6da03a83bd7a804f102b143351e59cc518898411
diff --git a/server/ee b/server/ee
index 88dcd4e30b..4a7ef99d60 160000
--- a/server/ee
+++ b/server/ee
@@ -1 +1 @@
-Subproject commit 88dcd4e30b5e78df1b873189c8416dd26edcaa1c
+Subproject commit 4a7ef99d60c0b1c98558dafc73fbf72e05daf6cb
From b3c54f3b58680658a558093fea089286b93883ae Mon Sep 17 00:00:00 2001
From: Adish M
Date: Tue, 11 Mar 2025 12:39:30 +0530
Subject: [PATCH 16/16] Fix: Destroy render service according to Edition
- Miss match with render create service name and destory service
---
.github/workflows/render-preview-deploy.yml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/render-preview-deploy.yml b/.github/workflows/render-preview-deploy.yml
index a025290d39..ead9ba50bf 100644
--- a/.github/workflows/render-preview-deploy.yml
+++ b/.github/workflows/render-preview-deploy.yml
@@ -215,7 +215,7 @@ jobs:
- name: Delete service
run: |
export SERVICE_ID=$(curl --request GET \
- --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \
+ --url 'https://api.render.com/v1/services?name=ToolJet%20CE%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \
--header 'accept: application/json' \
--header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \
jq -r '.[0].service.id')
@@ -583,7 +583,7 @@ jobs:
- name: Delete service
run: |
export SERVICE_ID=$(curl --request GET \
- --url 'https://api.render.com/v1/services?name=ToolJet%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \
+ --url 'https://api.render.com/v1/services?name=ToolJet%20EE%20PR%20%23${{ env.PR_NUMBER }}&limit=1' \
--header 'accept: application/json' \
--header 'authorization: Bearer ${{ secrets.RENDER_API_KEY }}' | \
jq -r '.[0].service.id')