mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-03 21:38:42 +00:00
* feat: Folder permission system * fix(group-permissions): resolve custom group validation, folder edit check, and UI inconsistencie * edit folder container && no folder in custom resource * fix the ui for custom in empty state * fix: coercion logic for folder permissions * feat: enhance folder permissions handling in app components * feat: add folder granular permissions handling in user apps permissions * feat: implement granular folder permissions in ability guard and service * feat: improve error handling for folder permissions with specific messages * feat: enhance EnvironmentSelect component to handle disabled state and improve display logic * chore: bump ee submodules * add basic framework to support platform git * feat: Update permission prop to isEditable in BaseManageGranularAccess component * chore: bump ee server submodule * fix: refine folder visibility logic based on user permissions * feat: enhance MultiValue rendering and styling for "All environments" option * fix:Uniqueness-of-data-source * revert folder changes * fix folder imports * feat: allow app lazy loading feat: import all apps of branches * feat: implement folder ownership checks and enhance app permissions handling * fix:ui changes * feat: update WorkspaceGitSyncModal UI * feat: enhance folder permissions handling for app ownership and actions * chore: clarify folder creation and deletion permissions in workspace context * fix: pull commit button & swtich branch visibility * feat: import app from git repo * fix: freezed state * remove reference of activebranchId * fix linting * fix: update folder permission labels * fixed folder permission cases * fixed css class issue * fix: datasource UI * minor fix * feat: streamline folder permissions handling by removing redundant checks and simplifying access logic * refactor: made error message consistent * fix:ui changes and PR fetching on master * fix: datasource and snapshot creation * fix: app rendering and stub loading * fix: add missing permission message for folder deletion action * refactor: consolidate forbidden messages for folder actions and maintain consistency * fix: allow pull into current branch * fix renaming of tags and reload on branch switch * fix: allow branches import from git * fix:push or tab removed * feat: streamline permission handling and improve app visibility logic * fix: remove default access denial message in AbilityGuard * fixed all user page functionality falky case * feat: add workspace-level PR fetch endpoint (returns all repo PRs without app filtering) * fix: remove app_branch_table * Fixed profile flaky case * fixed granular access flaky case * fix: allow branch creation from tags * fix: update default branch creation logic to use provider config * fix: dso and dsv operations on codebase * fix: constants reloading and refetch org git details on data * uniquness per branch * removed comment * fix: update app version handling and add is_stub column for branch-level tracking * fix workspace branch backfilling for scoped branches * added unique constraint - migration * fix: update app version unique constraint to include branchId for branch-aware handling * fix: update subproject commit reference in server/ee * chore: revert package-lock.json * chore: revert frontend/package-lock.json to main * removed banner and changed migration * minor fix * fix: remove unused import and handle UUID parse error gracefully in AppsUtilService * fix: update app stub checks to safely access app_versions * refactor: revert folder operations * fix: removed branch id logic * fix: ds migration * fix encrypted diff logic * fix: update openCreateAppModal to handle workspace branch lock * fix: subscriber filtering, freeze priority, meta hash optimization, and co_relation_id backfill * feat: add script to generate app metadata from app.json files * fix: meta script fix: backfilling of co-realtion-ids * refactor: streamline parameter formatting in workspace git sync adapter methods * Improves data source handling for workspace git sync Fixes workspace git sync to properly recognize data sources across branches by improving correlation ID handling and branch-aware data source version creation. Uses strict equality comparison in deep equal utility to prevent type coercion issues. Excludes credential_id from data source comparison to prevent unnecessary save button states. Removes is_active filter from branch data source queries to include all versions for proper synchronization. * refactor: update branch switching logic and improve error handling for data source creation * fix: migration order * 🚀 chore: update submodules to latest main after auto-merge (#15628) Co-authored-by: gsmithun4 <3417097+gsmithun4@users.noreply.github.com> * chore: update version to 3.21.8-beta across all components * fix:import app from device * fix:ui Edit&launch,folderCopy,branching dropdown in apps and ds * fix:encrypted helper text on master * fix: import from git flow * logs cleanup * fix:migration-datasource-uniqueness * fix: app on pull * chore: update server submodule hash * fix: corelation-id generation and version naming * fix: last versions deletion error fix: no multiple version creation * fix:ui and toast * chore: update server submodule hash * feat: add branch handling for app availability and improve error handling * fix: update encrypted value handling in DynamicForm and improve workspace constant validation logic * fix: improve formatting of help text in DynamicForm and enhance error message for adding constants on master branch * fix: correct version creation and pull in default branch * chore: update server submodule hash fix: remove logs from other PR * fix:data source uniquness at workspace level * fix: update header component logic for path validation and improve version import handling * chore: update server submodule to latest commit * fixed folder modal changes * fix:failed to create a query error inside apps * feat: add branchId support for data source versioning in app import/export service * fix: push & pull of tags and versions * fix: update subproject commit reference in server/ee * fix:removed gitSync logic from module rename * fix:removed switchbranch modal & allowed renaming from masted module&workflow creation * chore: Update server submodule hash * fix: change stub button to edit * refactor/git-sync-remove-modules-workflows * fix:version name for module and workflo w * fix:templet app creation * fix: add author details for branch --------- Co-authored-by: gsmithun4 <gsmithun4@gmail.com> Co-authored-by: Pratush <pratush@Pratushs-MBP.lan> Co-authored-by: Shantanu Mane <maneshantanu.20@gmail.com> Co-authored-by: parthy007 <parthadhikari1812@gmail.com> Co-authored-by: Yukti Goyal <yuktigoyal02@gmail.com> Co-authored-by: Muhsin Shah <muhsinshah21@gmail.com> Co-authored-by: Adish M <44204658+adishM98@users.noreply.github.com> Co-authored-by: gsmithun4 <3417097+gsmithun4@users.noreply.github.com> Co-authored-by: Parth <108089718+parthy007@users.noreply.github.com>
239 lines
9 KiB
JavaScript
239 lines
9 KiB
JavaScript
import React, { useState, useEffect, useRef } from 'react';
|
|
import AlertDialog from '@/_ui/AlertDialog';
|
|
import { ButtonSolid } from '@/_ui/AppButton/AppButton';
|
|
import SolidIcon from '@/_ui/Icon/SolidIcons';
|
|
import { useWorkspaceBranchesStore } from '@/_stores/workspaceBranchesStore';
|
|
import { toast } from 'react-hot-toast';
|
|
import { Alert } from '@/_ui/Alert';
|
|
import cx from 'classnames';
|
|
import '@/_styles/create-branch-modal.scss';
|
|
|
|
const RESERVED_NAMES = ['main', 'master', 'head', 'origin'];
|
|
|
|
export function WorkspaceCreateBranchModal({ onClose, onSuccess }) {
|
|
const [branchName, setBranchName] = useState('');
|
|
const [autoCommit, setAutoCommit] = useState(true);
|
|
const [isCreating, setIsCreating] = useState(false);
|
|
const [validationError, setValidationError] = useState('');
|
|
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
|
const [sourceBranchId, setSourceBranchId] = useState('');
|
|
const dropdownRef = useRef(null);
|
|
|
|
const { branches, activeBranchId } = useWorkspaceBranchesStore((state) => ({
|
|
branches: state.branches,
|
|
activeBranchId: state.activeBranchId,
|
|
}));
|
|
const actions = useWorkspaceBranchesStore((state) => state.actions);
|
|
|
|
// Always create from the default (main) branch
|
|
const defaultBranch = branches.find((b) => b.is_default || b.isDefault);
|
|
const selectedSourceBranchId = defaultBranch?.id || sourceBranchId || activeBranchId;
|
|
const selectedSourceBranch = branches.find((b) => b.id === selectedSourceBranchId);
|
|
|
|
// Close dropdown when clicking outside
|
|
useEffect(() => {
|
|
const handleClickOutside = (event) => {
|
|
if (dropdownRef.current && !dropdownRef.current.contains(event.target)) {
|
|
setIsDropdownOpen(false);
|
|
}
|
|
};
|
|
document.addEventListener('mousedown', handleClickOutside);
|
|
return () => document.removeEventListener('mousedown', handleClickOutside);
|
|
}, []);
|
|
|
|
// Always force source to the default (main) branch
|
|
useEffect(() => {
|
|
if (branches.length > 0) {
|
|
const mainBranch = branches.find((b) => b.is_default || b.isDefault);
|
|
if (mainBranch) {
|
|
setSourceBranchId(mainBranch.id);
|
|
}
|
|
}
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, [branches]);
|
|
|
|
const validateBranchName = (name) => {
|
|
if (!name || name.trim().length === 0) return 'Branch name is required';
|
|
if (/\s/.test(name)) return 'Branch name cannot contain spaces';
|
|
if (!/^[a-zA-Z0-9_-]+$/.test(name))
|
|
return 'Branch name can only contain letters, numbers, hyphens, and underscores';
|
|
if (branches.some((b) => b.name?.toLowerCase() === name.toLowerCase()))
|
|
return 'A branch with this name already exists';
|
|
if (RESERVED_NAMES.includes(name.toLowerCase())) return 'This branch name is reserved';
|
|
return '';
|
|
};
|
|
|
|
const handleBranchNameChange = (e) => {
|
|
const newName = e.target.value;
|
|
setBranchName(newName);
|
|
if (validationError) setValidationError('');
|
|
};
|
|
|
|
const handleCreate = async () => {
|
|
const error = validateBranchName(branchName);
|
|
if (error) {
|
|
setValidationError(error);
|
|
return;
|
|
}
|
|
|
|
setIsCreating(true);
|
|
try {
|
|
const newBranch = await actions.createBranch(branchName.trim(), selectedSourceBranchId);
|
|
// toast.success(`Branch "${branchName}" created successfully`);
|
|
toast.success(`Branch was created successfully`);
|
|
await actions.switchBranch(newBranch.id);
|
|
onSuccess?.();
|
|
onClose();
|
|
} catch (error) {
|
|
console.error('Error creating branch:', error);
|
|
setValidationError(error?.message || 'An unexpected error occurred');
|
|
toast.error(error?.message || 'Failed to create branch');
|
|
setIsCreating(false);
|
|
}
|
|
};
|
|
|
|
const handleKeyDown = (e) => {
|
|
if (e.key === 'Enter' && !isCreating && !isDropdownOpen) {
|
|
handleCreate();
|
|
} else if (e.key === 'Escape' && isDropdownOpen) {
|
|
setIsDropdownOpen(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<AlertDialog
|
|
show={true}
|
|
closeModal={onClose}
|
|
title="Create branch"
|
|
checkForBackground={true}
|
|
customClassName="create-branch-modal"
|
|
>
|
|
<div className="create-branch-modal-body">
|
|
{/* Create from dropdown */}
|
|
{/* <div className="form-group">
|
|
<label htmlFor="create-from-select" className="form-label">
|
|
Create from branch
|
|
</label>
|
|
<div className="custom-dropdown" ref={dropdownRef}>
|
|
<button
|
|
type="button"
|
|
className={cx('custom-dropdown-trigger', { 'is-open': isDropdownOpen })}
|
|
onClick={() => setIsDropdownOpen(!isDropdownOpen)}
|
|
disabled={isCreating}
|
|
>
|
|
<div className="custom-dropdown-value">
|
|
{selectedSourceBranch ? (
|
|
<>
|
|
<span className="version-name">{selectedSourceBranch.name}</span>
|
|
{(selectedSourceBranch.is_default || selectedSourceBranch.isDefault) && (
|
|
<span className={cx('status-badge', 'status-badge-released')}>Default</span>
|
|
)}
|
|
</>
|
|
) : (
|
|
<span className="version-name">Select branch...</span>
|
|
)}
|
|
</div>
|
|
<SolidIcon name="cheverondown" width="16" />
|
|
</button>
|
|
{isDropdownOpen && (
|
|
<div className="custom-dropdown-menu">
|
|
{branches.map((branch) => {
|
|
const isSelected = branch.id === selectedSourceBranchId;
|
|
return (
|
|
<div
|
|
key={branch.id}
|
|
className={cx('dropdown-item', { 'is-selected': isSelected })}
|
|
onClick={() => {
|
|
setSourceBranchId(branch.id);
|
|
setIsDropdownOpen(false);
|
|
}}
|
|
>
|
|
{isSelected && (
|
|
<div className="check-icon">
|
|
<SolidIcon name="tick" width="16" />
|
|
</div>
|
|
)}
|
|
{!isSelected && <div className="check-icon-placeholder" />}
|
|
<div className="item-content">
|
|
<div className="item-header">
|
|
<span className="item-name">{branch.name}</span>
|
|
{(branch.is_default || branch.isDefault) && (
|
|
<span className={cx('status-badge', 'status-badge-released')}>Default</span>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
})}
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div> */}
|
|
|
|
{/* Branch name input */}
|
|
<div className="form-group">
|
|
<label htmlFor="branch-name-input" className="form-label">
|
|
Branch name
|
|
</label>
|
|
<input
|
|
id="branch-name-input"
|
|
type="text"
|
|
className={`branch-modal-form-input ${validationError ? 'form-input-error' : ''}`}
|
|
placeholder="Enter branch name"
|
|
value={branchName}
|
|
onChange={handleBranchNameChange}
|
|
onKeyDown={handleKeyDown}
|
|
disabled={isCreating}
|
|
autoFocus
|
|
/>
|
|
{validationError && <div className="form-error-message">{validationError}</div>}
|
|
<div className="form-helper-text">
|
|
{/* Branch name must be unique and contain only letters, numbers, hyphens, and underscores */}
|
|
Branch name must be unique and max 50 characters
|
|
</div>
|
|
</div>
|
|
{/* Auto-commit checkbox */}
|
|
<div className="form-group">
|
|
<label className="checkbox-label">
|
|
<input
|
|
type="checkbox"
|
|
className="form-checkbox"
|
|
checked={autoCommit}
|
|
onChange={(e) => setAutoCommit(e.target.checked)}
|
|
disabled={true}
|
|
/>
|
|
<span className="checkbox-text">
|
|
Commit changes
|
|
<span className="checkbox-helper">Branch will always be created in git to ensure sync with ToolJet</span>
|
|
</span>
|
|
</label>
|
|
</div>
|
|
{/* Info message */}
|
|
<Alert placeSvgTop={true} svg="warning-icon" cls="create-branch-info">
|
|
{/* Branch can only be created from the default branch */}
|
|
Branch can only be created from the master
|
|
</Alert>
|
|
|
|
{/* Footer buttons */}
|
|
<div className="col d-flex justify-content-end gap-2 mt-3">
|
|
<ButtonSolid variant="tertiary" onClick={onClose} disabled={isCreating} size="md">
|
|
Cancel
|
|
</ButtonSolid>
|
|
<ButtonSolid
|
|
variant="primary"
|
|
onClick={handleCreate}
|
|
disabled={isCreating || !branchName.trim()}
|
|
isLoading={isCreating}
|
|
size="md"
|
|
>
|
|
Create branch
|
|
</ButtonSolid>
|
|
</div>
|
|
</div>
|
|
</AlertDialog>
|
|
);
|
|
}
|
|
|
|
// Keep backward compatibility
|
|
export { WorkspaceCreateBranchModal as CreateBranchModal };
|
|
export default WorkspaceCreateBranchModal;
|