UNPKG

datajunction-ui

Version:
276 lines (261 loc) 8.77 kB
import * as React from 'react'; import DashboardCard from '../../components/DashboardCard'; import { formatRelativeTime } from '../../utils/date'; // Git Namespaces Section - shows git-managed namespaces with their branches export function ActiveBranchesSection({ ownedNodes, recentlyEdited, loading }) { // Combine owned and edited nodes to get all user's nodes const allNodes = [...ownedNodes, ...recentlyEdited]; // Group nodes by base git namespace (the root namespace that's git-managed) const gitNamespaceMap = new Map(); allNodes.forEach(node => { if (node.gitInfo && node.gitInfo.repo) { // Extract the base namespace (everything before the branch part) const fullNamespace = node.name.split('.').slice(0, -1).join('.'); // For git-managed namespaces, the base is the parent namespace // If not available, we derive it by removing the branch from the full namespace let baseNamespace; if (node.gitInfo.parentNamespace) { baseNamespace = node.gitInfo.parentNamespace; } else { // Fallback: remove the branch part from fullNamespace // fullNamespace is like "myproject.main", we want "myproject" const dotIdx = fullNamespace.lastIndexOf('.'); baseNamespace = dotIdx > 0 ? fullNamespace.slice(0, dotIdx) : fullNamespace; } if (!gitNamespaceMap.has(baseNamespace)) { gitNamespaceMap.set(baseNamespace, { baseNamespace, repo: node.gitInfo.repo, defaultBranch: node.gitInfo.defaultBranch, branches: new Map(), }); } const gitNs = gitNamespaceMap.get(baseNamespace); const branchKey = node.gitInfo.branch; if (!gitNs.branches.has(branchKey)) { gitNs.branches.set(branchKey, { branch: node.gitInfo.branch, isDefault: node.gitInfo.isDefaultBranch, namespace: fullNamespace, nodes: [], lastActivity: node.current?.updatedAt, }); } const branchInfo = gitNs.branches.get(branchKey); branchInfo.nodes.push(node); // Track most recent activity if ( node.current?.updatedAt && (!branchInfo.lastActivity || node.current.updatedAt > branchInfo.lastActivity) ) { branchInfo.lastActivity = node.current.updatedAt; } } }); // Ensure default branch is always present (even if user has no nodes there) gitNamespaceMap.forEach(gitNs => { if (gitNs.defaultBranch && !gitNs.branches.has(gitNs.defaultBranch)) { gitNs.branches.set(gitNs.defaultBranch, { branch: gitNs.defaultBranch, isDefault: true, namespace: `${gitNs.baseNamespace}.${gitNs.defaultBranch}`, nodes: [], lastActivity: null, }); } }); // Convert to array and sort const gitNamespaces = Array.from(gitNamespaceMap.values()) .map(ns => ({ ...ns, branches: Array.from(ns.branches.values()).sort((a, b) => { // Default branch first if (a.isDefault && !b.isDefault) return -1; if (!a.isDefault && b.isDefault) return 1; // Then by last activity if (!a.lastActivity && !b.lastActivity) return 0; if (!a.lastActivity) return 1; if (!b.lastActivity) return -1; return new Date(b.lastActivity) - new Date(a.lastActivity); }), })) .sort((a, b) => { // Sort git namespaces by most recent activity across all branches const aLatest = Math.max( ...a.branches.map(b => b.lastActivity ? new Date(b.lastActivity).getTime() : 0, ), ); const bLatest = Math.max( ...b.branches.map(b => b.lastActivity ? new Date(b.lastActivity).getTime() : 0, ), ); return bLatest - aLatest; }); const maxDisplay = 3; // Show top 3 git namespaces const gitNamespacesList = gitNamespaces .slice(0, maxDisplay) .map((gitNs, gitNsIdx) => { const isLastGitNs = gitNsIdx === gitNamespaces.slice(0, maxDisplay).length - 1; return ( <div key={gitNs.baseNamespace}> {/* Base namespace and repository header */} <div style={{ display: 'flex', alignItems: 'center', gap: '8px', padding: '0.75rem 0 0.5rem 0', marginTop: gitNsIdx === 0 ? '0' : '0', }} > <a href={`/namespaces/${gitNs.baseNamespace}`} style={{ fontSize: '12px', fontWeight: '600', textDecoration: 'none', }} > {gitNs.baseNamespace} </a> <span style={{ fontSize: '9px', padding: '2px 6px', backgroundColor: '#f0f0f0', color: '#666', borderRadius: '3px', fontWeight: '500', }} > {gitNs.repo} </span> </div> {/* Branch list */} {gitNs.branches.map(branchInfo => ( <div key={branchInfo.branch} style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '0px 1em 0.4em 1em', }} > <div style={{ display: 'flex', alignItems: 'center', gap: '6px', minWidth: 0, flex: 1, }} > <a href={`/namespaces/${branchInfo.namespace}`} style={{ fontSize: '12px', fontWeight: '500', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', }} > {branchInfo.branch} </a> {branchInfo.isDefault && ( <span style={{ fontSize: '10px' }}></span> )} </div> {branchInfo.lastActivity && ( <span style={{ fontSize: '10px', color: '#888', whiteSpace: 'nowrap', }} > updated {formatRelativeTime(branchInfo.lastActivity)} </span> )} </div> ))} {/* Horizontal line between namespaces */} {!isLastGitNs && ( <div style={{ borderBottom: '1px solid var(--border-color, #ddd)', margin: '0.5rem 0', }} /> )} </div> ); }); return ( <DashboardCard title="Git Namespaces" loading={loading} cardStyle={{ padding: '0.25rem 0.25rem 0.5em 0.75rem', maxHeight: '300px', overflowY: 'auto', }} emptyState={ <div style={{ padding: '0', textAlign: 'center' }}> <p style={{ fontSize: '12px', color: '#666', marginBottom: '0.75rem' }} > No git-managed namespaces. </p> <div style={{ padding: '0.75rem', backgroundColor: 'var(--card-bg, #f8f9fa)', border: '1px dashed var(--border-color, #dee2e6)', borderRadius: '6px', }} > <div style={{ fontSize: '16px', marginBottom: '0.25rem' }}>🌿</div> <div style={{ fontSize: '11px', fontWeight: '500', marginBottom: '0.25rem', }} > Set up git-backed namespaces </div> <p style={{ fontSize: '10px', color: '#666', marginBottom: '0' }}> Manage your nodes with version control </p> </div> </div> } > {gitNamespaces.length > 0 && ( <div style={{ display: 'flex', flexDirection: 'column' }}> {gitNamespacesList} {gitNamespaces.length > maxDisplay && ( <div style={{ textAlign: 'center', padding: '0.5rem', fontSize: '12px', color: '#666', }} > +{gitNamespaces.length - maxDisplay} more git namespace {gitNamespaces.length - maxDisplay !== 1 ? 's' : ''} </div> )} </div> )} </DashboardCard> ); }