ToolJet/frontend/src/_ui/WorkspaceBranchDropdown/SwitchBranchModal.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

227 lines
7.9 KiB
JavaScript

import React, { useState, useEffect } from 'react';
import AlertDialog from '@/_ui/AlertDialog';
import SolidIcon from '@/_ui/Icon/SolidIcons';
import { useWorkspaceBranchesStore } from '@/_stores/workspaceBranchesStore';
import { toast } from 'react-hot-toast';
import { WorkspaceCreateBranchModal } from './CreateBranchModal';
import { Alert } from '@/_ui/Alert';
import '@/_styles/switch-branch-modal.scss';
export function WorkspaceSwitchBranchModal({ show, onClose, onBranchSwitch }) {
const [searchTerm, setSearchTerm] = useState('');
const [isLoading, setIsLoading] = useState(false);
const [showCreateModal, setShowCreateModal] = useState(false);
const { branches, activeBranchId, orgGitConfig, currentBranch } = useWorkspaceBranchesStore((state) => ({
branches: state.branches,
activeBranchId: state.activeBranchId,
orgGitConfig: state.orgGitConfig,
currentBranch: state.currentBranch,
}));
const actions = useWorkspaceBranchesStore((state) => state.actions);
const defaultGitBranch = orgGitConfig?.default_git_branch || orgGitConfig?.defaultGitBranch || 'main';
const isOnDefaultBranch =
currentBranch?.is_default || currentBranch?.isDefault || currentBranch?.name === defaultGitBranch;
useEffect(() => {
if (show) {
setIsLoading(true);
actions.fetchBranches().finally(() => setIsLoading(false));
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [show]);
// Filter branches by search term
const filteredBranches = branches.filter((branch) => {
if (!branch.name.toLowerCase().includes(searchTerm.toLowerCase())) return false;
// When used for "create app" flow, hide the default branch (user is leaving it)
if (onBranchSwitch && (branch.is_default || branch.isDefault)) return false;
return true;
});
const handleBranchClick = async (branch) => {
if (branch.id === activeBranchId) {
onClose();
return;
}
try {
await actions.switchBranch(branch.id);
toast.success(`Switched to ${branch.name}`);
if (onBranchSwitch) {
onBranchSwitch(branch);
} else {
onClose();
window.location.reload();
}
} catch (error) {
console.error('Error switching branch:', error);
const errorMessage = error?.error || error?.message || 'Failed to switch branch';
toast.error(errorMessage);
}
};
const getRelativeTime = (dateString) => {
if (!dateString) return '';
const date = new Date(dateString);
const now = new Date();
const diffMs = now - date;
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
if (diffDays === 0) return 'today';
if (diffDays === 1) return 'yesterday';
if (diffDays < 7) return `${diffDays} days ago`;
if (diffDays < 30) return `${Math.floor(diffDays / 7)} weeks ago`;
return `${Math.floor(diffDays / 30)} months ago`;
};
const handleViewInGitRepo = () => {
const repoUrl = orgGitConfig?.repo_url || orgGitConfig?.repoUrl || '';
if (!repoUrl) {
toast.error('Git repository URL not available');
return;
}
// Extract web URL from repo URL
const githubMatch = repoUrl.match(/github\.com[:/]([^/]+)\/(.+?)(\.git)?$/);
const gitlabMatch = repoUrl.match(/gitlab\.com[:/]([^/]+)\/(.+?)(\.git)?$/);
const bitbucketMatch = repoUrl.match(/bitbucket\.org[:/]([^/]+)\/(.+?)(\.git)?$/);
let webUrl = null;
if (githubMatch) {
const [, owner, repo] = githubMatch;
webUrl = `https://github.com/${owner}/${repo}`;
} else if (gitlabMatch) {
const [, owner, repo] = gitlabMatch;
webUrl = `https://gitlab.com/${owner}/${repo}`;
} else if (bitbucketMatch) {
const [, owner, repo] = bitbucketMatch;
webUrl = `https://bitbucket.org/${owner}/${repo}`;
} else {
webUrl = repoUrl
.replace(/\.git$/, '')
.replace(/^git@/, 'https://')
.replace(/:([^/])/, '/$1');
}
if (webUrl) {
window.open(webUrl, '_blank');
} else {
toast.error('Could not parse repository URL');
}
};
return (
<AlertDialog
show={show}
closeModal={onClose}
title="Switch branch"
checkForBackground={true}
customClassName="switch-branch-modal"
>
<div className="switch-branch-modal-content">
{/* Info message - only shown on default branch */}
{isOnDefaultBranch && (
<Alert placeSvgTop={true} svg="warning-icon" cls="create-branch-info">
Default branch is locked. Switch branches to make changes.
</Alert>
)}
{/* Search Section */}
<div className="search-section">
<label className="section-label">ALL OPEN BRANCHES</label>
<div className="search-input-wrapper">
<SolidIcon name="search" width="16" fill="var(--slate11)" />
<input
type="text"
className="search-input"
placeholder="Search.."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
data-cy="workspace-branch-search-input"
/>
</div>
</div>
{/* Branch List */}
<div className="branch-list-section">
{isLoading ? (
<div className="loading-state">
<div className="spinner"></div>
<span>Loading branches...</span>
</div>
) : filteredBranches.length === 0 ? (
<div className="empty-state">
<p>No branches found</p>
</div>
) : (
filteredBranches.map((branch) => {
const isCurrentBranch = branch.id === activeBranchId;
return (
<div
key={branch.id || branch.name}
className={`branch-list-item ${isCurrentBranch ? 'active' : ''}`}
onClick={() => handleBranchClick(branch)}
data-cy={`workspace-branch-list-item-${branch.name}`}
>
<div className="branch-checkbox">
{isCurrentBranch && <SolidIcon name="check2" width="16" fill="var(--indigo9)" />}
</div>
<div className="branch-list-content">
<div className="branch-list-name">
{branch.name}
{(branch.is_default || branch.isDefault) && (
<span style={{ fontSize: 10, opacity: 0.6, marginLeft: 4 }}>(default)</span>
)}
</div>
<div className="branch-list-meta">
Created by {branch.author || branch.created_by || 'default'},{' '}
{getRelativeTime(branch.createdAt || branch.created_at)}
</div>
</div>
</div>
);
})
)}
</div>
{/* Footer Actions */}
<div className="modal-footer-actions">
<button
className="footer-btn secondary"
onClick={handleViewInGitRepo}
data-cy="workspace-view-in-git-repo-btn"
>
<span>View in git repo</span>
<SolidIcon name="newtab" width="14" fill="var(--icon-default)" />
</button>
<button
className="footer-btn accent"
onClick={() => {
setShowCreateModal(true);
}}
data-cy="workspace-create-branch-from-modal-btn"
>
<SolidIcon name="plusicon" width="14" fill="var(--indigo9)" />
<span>Create new branch</span>
</button>
</div>
</div>
{/* Create Branch Modal */}
{showCreateModal && (
<WorkspaceCreateBranchModal
onClose={() => setShowCreateModal(false)}
onSuccess={() => {
setShowCreateModal(false);
onClose();
}}
/>
)}
</AlertDialog>
);
}
export default WorkspaceSwitchBranchModal;