ToolJet/frontend/src/_ui/WorkspaceBranchDropdown/CreateBranchModal.jsx
vjaris42 58ba6b8563
Enable Git Sync for Datasources, constants and dashboard (#15434)
* 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>
2026-03-27 23:23:23 +05:30

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;