datajunction-ui 0.0.54 → 0.0.56

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/package.json +1 -1
  2. package/src/app/components/NamespaceHeader.jsx +431 -11
  3. package/src/app/components/NodeListActions.jsx +10 -7
  4. package/src/app/components/__tests__/GitModals.test.jsx +1293 -0
  5. package/src/app/components/__tests__/NamespaceHeader.test.jsx +764 -0
  6. package/src/app/components/__tests__/NodeListActions.test.jsx +5 -3
  7. package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +95 -0
  8. package/src/app/components/git/CreateBranchModal.jsx +229 -0
  9. package/src/app/components/git/CreatePRModal.jsx +270 -0
  10. package/src/app/components/git/DeleteBranchModal.jsx +173 -0
  11. package/src/app/components/git/GitSettingsModal.jsx +292 -0
  12. package/src/app/components/git/SyncToGitModal.jsx +219 -0
  13. package/src/app/components/git/index.js +5 -0
  14. package/src/app/icons/DeleteIcon.jsx +3 -3
  15. package/src/app/icons/EditIcon.jsx +3 -3
  16. package/src/app/icons/EyeIcon.jsx +3 -4
  17. package/src/app/icons/JupyterExportIcon.jsx +3 -7
  18. package/src/app/icons/PythonIcon.jsx +3 -3
  19. package/src/app/pages/AddEditNodePage/index.jsx +8 -5
  20. package/src/app/pages/NamespacePage/index.jsx +34 -21
  21. package/src/app/pages/NodePage/ClientCodePopover.jsx +3 -7
  22. package/src/app/pages/NodePage/NodeInfoTab.jsx +10 -3
  23. package/src/app/pages/NodePage/NotebookDownload.jsx +4 -10
  24. package/src/app/pages/NodePage/WatchNodeButton.jsx +7 -12
  25. package/src/app/pages/NodePage/index.jsx +42 -13
  26. package/src/app/services/DJService.js +198 -1
  27. package/src/styles/index.css +3 -0
  28. package/src/styles/node-creation.scss +22 -0
  29. package/src/styles/settings.css +1 -1
@@ -0,0 +1,173 @@
1
+ import React, { useState } from 'react';
2
+
3
+ /**
4
+ * Modal for confirming branch deletion.
5
+ */
6
+ export function DeleteBranchModal({
7
+ isOpen,
8
+ onClose,
9
+ onDelete,
10
+ namespace,
11
+ gitBranch,
12
+ parentNamespace,
13
+ }) {
14
+ const [deleteGitBranch, setDeleteGitBranch] = useState(true);
15
+ const [deleting, setDeleting] = useState(false);
16
+ const [error, setError] = useState(null);
17
+
18
+ const handleSubmit = async e => {
19
+ e.preventDefault();
20
+ setError(null);
21
+ setDeleting(true);
22
+
23
+ try {
24
+ const result = await onDelete(deleteGitBranch);
25
+ if (result?._error) {
26
+ setError(result.message);
27
+ } else {
28
+ onClose();
29
+ // Redirect to parent namespace
30
+ window.location.href = `/namespaces/${parentNamespace}`;
31
+ }
32
+ } catch (err) {
33
+ setError(err.message || 'Failed to delete branch');
34
+ } finally {
35
+ setDeleting(false);
36
+ }
37
+ };
38
+
39
+ const handleClose = () => {
40
+ setDeleteGitBranch(false);
41
+ setError(null);
42
+ onClose();
43
+ };
44
+
45
+ if (!isOpen) return null;
46
+
47
+ return (
48
+ <div className="modal-overlay" onClick={handleClose}>
49
+ <div className="modal-content" onClick={e => e.stopPropagation()}>
50
+ <div className="modal-header">
51
+ <h3>Delete Branch</h3>
52
+ <button
53
+ className="btn-close-modal"
54
+ onClick={handleClose}
55
+ title="Close"
56
+ >
57
+ ×
58
+ </button>
59
+ </div>
60
+
61
+ <form onSubmit={handleSubmit}>
62
+ <div className="modal-body">
63
+ <div
64
+ style={{
65
+ display: 'flex',
66
+ alignItems: 'flex-start',
67
+ gap: '12px',
68
+ padding: '12px 12px 0 12px',
69
+ backgroundColor: '#fef3c7',
70
+ border: '1px solid #fcd34d',
71
+ borderRadius: '6px',
72
+ marginBottom: '16px',
73
+ }}
74
+ >
75
+ <span
76
+ style={{
77
+ fontSize: '20px',
78
+ paddingBottom: '12px',
79
+ margin: '-5px -1px 0 0',
80
+ }}
81
+ >
82
+ ⚠️
83
+ </span>
84
+ <div style={{ fontSize: '13px' }}>
85
+ Are you sure you want to delete this branch namespace?
86
+ </div>
87
+ </div>
88
+
89
+ {error && (
90
+ <div
91
+ style={{
92
+ padding: '12px',
93
+ backgroundColor: '#fef2f2',
94
+ border: '1px solid #fecaca',
95
+ borderRadius: '6px',
96
+ color: '#dc2626',
97
+ fontSize: '13px',
98
+ marginBottom: '16px',
99
+ }}
100
+ >
101
+ {error}
102
+ </div>
103
+ )}
104
+
105
+ <div
106
+ style={{
107
+ backgroundColor: '#f8fafc',
108
+ borderRadius: '6px',
109
+ padding: '12px',
110
+ fontSize: '13px',
111
+ marginBottom: '16px',
112
+ }}
113
+ >
114
+ <div style={{ marginBottom: '8px' }}>
115
+ <strong>Namespace:</strong> {namespace}
116
+ </div>
117
+ <div style={{ marginBottom: '8px' }}>
118
+ <strong>Git Branch:</strong> {gitBranch}
119
+ </div>
120
+ <div>
121
+ <strong>Parent:</strong> {parentNamespace}
122
+ </div>
123
+ </div>
124
+
125
+ <label
126
+ style={{
127
+ display: 'flex',
128
+ alignItems: 'center',
129
+ gap: '8px',
130
+ fontSize: '13px',
131
+ cursor: 'pointer',
132
+ fontWeight: 'none',
133
+ textTransform: 'none',
134
+ }}
135
+ >
136
+ <input
137
+ type="checkbox"
138
+ checked={deleteGitBranch}
139
+ onChange={e => setDeleteGitBranch(e.target.checked)}
140
+ disabled={deleting}
141
+ />
142
+ Also delete git branch on GitHub
143
+ </label>
144
+ </div>
145
+
146
+ <div className="modal-actions">
147
+ <button
148
+ type="button"
149
+ className="btn-secondary"
150
+ onClick={handleClose}
151
+ disabled={deleting}
152
+ >
153
+ Cancel
154
+ </button>
155
+ <button
156
+ type="submit"
157
+ className="btn-primary"
158
+ disabled={deleting}
159
+ style={{
160
+ backgroundColor: '#dc2626',
161
+ borderColor: '#dc2626',
162
+ }}
163
+ >
164
+ {deleting ? 'Deleting...' : 'Delete Branch'}
165
+ </button>
166
+ </div>
167
+ </form>
168
+ </div>
169
+ </div>
170
+ );
171
+ }
172
+
173
+ export default DeleteBranchModal;
@@ -0,0 +1,292 @@
1
+ import React, { useState, useEffect } from 'react';
2
+
3
+ /**
4
+ * Modal for configuring git settings for a namespace.
5
+ */
6
+ export function GitSettingsModal({
7
+ isOpen,
8
+ onClose,
9
+ onSave,
10
+ currentConfig,
11
+ namespace,
12
+ }) {
13
+ const [repoPath, setRepoPath] = useState('');
14
+ const [branch, setBranch] = useState('');
15
+ const [path, setPath] = useState('');
16
+ const [gitOnly, setGitOnly] = useState(true);
17
+ const [saving, setSaving] = useState(false);
18
+ const [error, setError] = useState(null);
19
+ const [success, setSuccess] = useState(false);
20
+
21
+ useEffect(() => {
22
+ if (currentConfig) {
23
+ setRepoPath(currentConfig.github_repo_path || '');
24
+ setBranch(currentConfig.git_branch || '');
25
+ setPath(currentConfig.git_path || 'nodes/');
26
+ // If git is already configured (has repo path), use the existing git_only value
27
+ // Otherwise, default to read-only (true) for new git configurations
28
+ const hasExistingGitConfig = !!currentConfig.github_repo_path;
29
+ setGitOnly(hasExistingGitConfig ? currentConfig.git_only : true);
30
+ } else {
31
+ // New configuration - default to read-only and nodes/ path
32
+ setPath('nodes/');
33
+ setGitOnly(true);
34
+ }
35
+ setSuccess(false);
36
+ setError(null);
37
+ }, [currentConfig]);
38
+
39
+ const handleSubmit = async e => {
40
+ e.preventDefault();
41
+ setError(null);
42
+ setSuccess(false);
43
+ setSaving(true);
44
+
45
+ try {
46
+ const config = {
47
+ github_repo_path: repoPath.trim() || null,
48
+ git_branch: branch.trim() || null,
49
+ git_path: path.trim() || null,
50
+ git_only: gitOnly,
51
+ };
52
+
53
+ const result = await onSave(config);
54
+ if (result?._error) {
55
+ setError(result.message);
56
+ } else {
57
+ setSuccess(true);
58
+ // Keep modal open so user can see success message
59
+ // User can close manually via Close button or X
60
+ }
61
+ } catch (err) {
62
+ setError(err.message || 'Failed to save git settings');
63
+ } finally {
64
+ setSaving(false);
65
+ }
66
+ };
67
+
68
+ const handleClose = () => {
69
+ setError(null);
70
+ setSuccess(false);
71
+ onClose();
72
+ };
73
+
74
+ if (!isOpen) return null;
75
+
76
+ return (
77
+ <div className="modal-overlay" onClick={handleClose}>
78
+ <div className="modal-content" onClick={e => e.stopPropagation()}>
79
+ <div className="modal-header">
80
+ <h3>Git Configuration</h3>
81
+ <button
82
+ className="btn-close-modal"
83
+ onClick={handleClose}
84
+ title="Close"
85
+ >
86
+ ×
87
+ </button>
88
+ </div>
89
+
90
+ <form onSubmit={handleSubmit}>
91
+ <div className="modal-body">
92
+ <p
93
+ style={{
94
+ color: '#64748b',
95
+ fontSize: '13px',
96
+ marginBottom: '16px',
97
+ }}
98
+ >
99
+ Configure git integration for <strong>{namespace}</strong> to
100
+ enable branch management and sync changes to GitHub.
101
+ </p>
102
+
103
+ {error && (
104
+ <div
105
+ style={{
106
+ padding: '12px',
107
+ backgroundColor: '#fef2f2',
108
+ border: '1px solid #fecaca',
109
+ borderRadius: '6px',
110
+ color: '#dc2626',
111
+ fontSize: '13px',
112
+ marginBottom: '16px',
113
+ }}
114
+ >
115
+ {error}
116
+ </div>
117
+ )}
118
+
119
+ <div className="form-group">
120
+ <label htmlFor="git-repo-path">Repository</label>
121
+ <input
122
+ id="git-repo-path"
123
+ type="text"
124
+ placeholder="owner/repo"
125
+ value={repoPath}
126
+ onChange={e => setRepoPath(e.target.value)}
127
+ disabled={saving}
128
+ />
129
+ <span className="form-hint">
130
+ GitHub repository path (e.g., "myorg/dj-definitions")
131
+ </span>
132
+ </div>
133
+
134
+ <div className="form-group">
135
+ <label htmlFor="git-branch">Branch</label>
136
+ <input
137
+ id="git-branch"
138
+ type="text"
139
+ placeholder="main"
140
+ value={branch}
141
+ onChange={e => setBranch(e.target.value)}
142
+ disabled={saving}
143
+ />
144
+ <span className="form-hint">
145
+ Git branch for this namespace (e.g., "main" or "production")
146
+ </span>
147
+ </div>
148
+
149
+ <div className="form-group">
150
+ <label htmlFor="git-path">Path</label>
151
+ <input
152
+ id="git-path"
153
+ type="text"
154
+ placeholder="nodes/"
155
+ value={path}
156
+ onChange={e => setPath(e.target.value)}
157
+ disabled={saving}
158
+ required
159
+ />
160
+ <span className="form-hint">
161
+ Subdirectory within the repo for node YAML files
162
+ </span>
163
+ </div>
164
+
165
+ <div
166
+ style={{
167
+ marginTop: '16px',
168
+ padding: '12px',
169
+ backgroundColor: gitOnly ? '#fef3c7' : '#f0fdf4',
170
+ borderRadius: '6px',
171
+ border: `1px solid ${gitOnly ? '#fcd34d' : '#86efac'}`,
172
+ }}
173
+ >
174
+ <label
175
+ style={{
176
+ display: 'flex',
177
+ alignItems: 'flex-start',
178
+ gap: '10px',
179
+ cursor: 'pointer',
180
+ margin: 0,
181
+ textTransform: 'none',
182
+ letterSpacing: 'normal',
183
+ fontSize: '14px',
184
+ fontWeight: 'normal',
185
+ }}
186
+ >
187
+ <input
188
+ type="checkbox"
189
+ checked={gitOnly}
190
+ onChange={e => setGitOnly(e.target.checked)}
191
+ disabled={saving}
192
+ style={{ marginTop: '3px' }}
193
+ />
194
+ <span>
195
+ <span
196
+ style={{
197
+ fontWeight: 600,
198
+ color: gitOnly ? '#92400e' : '#166534',
199
+ textTransform: 'none',
200
+ }}
201
+ >
202
+ {gitOnly
203
+ ? 'Read-only (Git is source of truth)'
204
+ : 'Editable (UI edits allowed)'}
205
+ </span>
206
+ <span
207
+ style={{
208
+ display: 'block',
209
+ marginTop: '4px',
210
+ fontSize: '12px',
211
+ color: '#64748b',
212
+ fontWeight: 'normal',
213
+ textTransform: 'none',
214
+ }}
215
+ >
216
+ {gitOnly
217
+ ? 'Changes must be made via git and deployed through CI/CD. UI editing is disabled.'
218
+ : 'Users can edit nodes in the UI. Changes can be synced to git.'}
219
+ </span>
220
+ </span>
221
+ </label>
222
+ </div>
223
+
224
+ {success && (
225
+ <div
226
+ style={{
227
+ marginTop: '16px',
228
+ padding: '12px',
229
+ backgroundColor: '#f0fdf4',
230
+ border: '1px solid #86efac',
231
+ borderRadius: '6px',
232
+ color: '#166534',
233
+ fontSize: '13px',
234
+ display: 'flex',
235
+ alignItems: 'center',
236
+ gap: '8px',
237
+ }}
238
+ >
239
+ <svg
240
+ xmlns="http://www.w3.org/2000/svg"
241
+ width="16"
242
+ height="16"
243
+ viewBox="0 0 24 24"
244
+ fill="none"
245
+ stroke="currentColor"
246
+ strokeWidth="2"
247
+ strokeLinecap="round"
248
+ strokeLinejoin="round"
249
+ >
250
+ <path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
251
+ <polyline points="22 4 12 14.01 9 11.01" />
252
+ </svg>
253
+ Git configuration saved successfully!
254
+ </div>
255
+ )}
256
+ </div>
257
+
258
+ <div className="modal-actions">
259
+ <button
260
+ type="button"
261
+ className="btn-secondary"
262
+ onClick={handleClose}
263
+ disabled={saving}
264
+ >
265
+ {success ? 'Close' : 'Cancel'}
266
+ </button>
267
+ {!success && (
268
+ <button
269
+ type="submit"
270
+ className="btn-primary"
271
+ disabled={saving}
272
+ style={
273
+ saving
274
+ ? {
275
+ opacity: 0.7,
276
+ cursor: 'wait',
277
+ backgroundColor: '#9ca3af',
278
+ }
279
+ : {}
280
+ }
281
+ >
282
+ {saving ? 'Saving...' : 'Save Settings'}
283
+ </button>
284
+ )}
285
+ </div>
286
+ </form>
287
+ </div>
288
+ </div>
289
+ );
290
+ }
291
+
292
+ export default GitSettingsModal;
@@ -0,0 +1,219 @@
1
+ import React, { useState } from 'react';
2
+
3
+ /**
4
+ * Modal for syncing namespace to git.
5
+ */
6
+ export function SyncToGitModal({
7
+ isOpen,
8
+ onClose,
9
+ onSync,
10
+ namespace,
11
+ gitBranch,
12
+ repoPath,
13
+ }) {
14
+ const [commitMessage, setCommitMessage] = useState('');
15
+ const [syncing, setSyncing] = useState(false);
16
+ const [error, setError] = useState(null);
17
+ const [result, setResult] = useState(null);
18
+
19
+ const handleSubmit = async e => {
20
+ e.preventDefault();
21
+ setError(null);
22
+ setSyncing(true);
23
+
24
+ try {
25
+ const res = await onSync(commitMessage.trim() || null);
26
+ if (res?._error) {
27
+ setError(res.message);
28
+ } else {
29
+ setResult(res);
30
+ }
31
+ } catch (err) {
32
+ setError(err.message || 'Failed to sync to git');
33
+ } finally {
34
+ setSyncing(false);
35
+ }
36
+ };
37
+
38
+ const handleClose = () => {
39
+ setCommitMessage('');
40
+ setError(null);
41
+ setResult(null);
42
+ onClose();
43
+ };
44
+
45
+ if (!isOpen) return null;
46
+
47
+ return (
48
+ <div className="modal-overlay" onClick={handleClose}>
49
+ <div className="modal-content" onClick={e => e.stopPropagation()}>
50
+ <div className="modal-header">
51
+ <h3>Sync to Git</h3>
52
+ <button
53
+ className="btn-close-modal"
54
+ onClick={handleClose}
55
+ title="Close"
56
+ >
57
+ ×
58
+ </button>
59
+ </div>
60
+
61
+ {result ? (
62
+ <div className="modal-body">
63
+ <div
64
+ style={{
65
+ textAlign: 'center',
66
+ padding: '16px 0',
67
+ }}
68
+ >
69
+ <span
70
+ style={{
71
+ display: 'inline-flex',
72
+ alignItems: 'center',
73
+ justifyContent: 'center',
74
+ width: '48px',
75
+ height: '48px',
76
+ borderRadius: '50%',
77
+ backgroundColor: '#dcfce7',
78
+ color: '#16a34a',
79
+ fontSize: '24px',
80
+ marginBottom: '12px',
81
+ }}
82
+ >
83
+
84
+ </span>
85
+ <h4 style={{ margin: '0 0 8px 0' }}>
86
+ Synced {result.files_synced} file
87
+ {result.files_synced !== 1 ? 's' : ''}!
88
+ </h4>
89
+ </div>
90
+
91
+ <div
92
+ style={{
93
+ backgroundColor: '#f8fafc',
94
+ borderRadius: '6px',
95
+ padding: '12px',
96
+ fontSize: '13px',
97
+ }}
98
+ >
99
+ <div style={{ marginBottom: '8px' }}>
100
+ <strong>Commit:</strong>{' '}
101
+ <a
102
+ href={result.commit_url}
103
+ target="_blank"
104
+ rel="noopener noreferrer"
105
+ style={{
106
+ fontFamily: 'monospace',
107
+ fontSize: '12px',
108
+ color: '#3b82f6',
109
+ }}
110
+ >
111
+ {result.commit_sha?.slice(0, 7)}
112
+ </a>
113
+ </div>
114
+ <div>
115
+ <strong>Branch:</strong> {gitBranch}
116
+ </div>
117
+ </div>
118
+
119
+ <div className="modal-actions">
120
+ <button className="btn-secondary" onClick={handleClose}>
121
+ Close
122
+ </button>
123
+ <a
124
+ href={result.commit_url}
125
+ target="_blank"
126
+ rel="noopener noreferrer"
127
+ className="btn-primary"
128
+ style={{ textDecoration: 'none' }}
129
+ >
130
+ View Commit
131
+ </a>
132
+ </div>
133
+ </div>
134
+ ) : (
135
+ <form onSubmit={handleSubmit}>
136
+ <div className="modal-body">
137
+ <p
138
+ style={{
139
+ color: '#64748b',
140
+ fontSize: '13px',
141
+ marginBottom: '16px',
142
+ }}
143
+ >
144
+ Sync all nodes in <strong>{namespace}</strong> to git as YAML
145
+ files.
146
+ </p>
147
+
148
+ {error && (
149
+ <div
150
+ style={{
151
+ padding: '12px',
152
+ backgroundColor: '#fef2f2',
153
+ border: '1px solid #fecaca',
154
+ borderRadius: '6px',
155
+ color: '#dc2626',
156
+ fontSize: '13px',
157
+ marginBottom: '16px',
158
+ }}
159
+ >
160
+ {error}
161
+ </div>
162
+ )}
163
+
164
+ <div
165
+ style={{
166
+ backgroundColor: '#f8fafc',
167
+ borderRadius: '6px',
168
+ padding: '12px',
169
+ fontSize: '13px',
170
+ marginBottom: '16px',
171
+ }}
172
+ >
173
+ <div style={{ marginBottom: '4px' }}>
174
+ <strong>Repository:</strong> {repoPath}
175
+ </div>
176
+ <div>
177
+ <strong>Branch:</strong> {gitBranch}
178
+ </div>
179
+ </div>
180
+
181
+ <div className="form-group">
182
+ <label htmlFor="commit-message">
183
+ Commit Message (optional)
184
+ </label>
185
+ <input
186
+ id="commit-message"
187
+ type="text"
188
+ placeholder={`Sync ${namespace}`}
189
+ value={commitMessage}
190
+ onChange={e => setCommitMessage(e.target.value)}
191
+ disabled={syncing}
192
+ />
193
+ <span className="form-hint">
194
+ Leave blank to use default message
195
+ </span>
196
+ </div>
197
+ </div>
198
+
199
+ <div className="modal-actions">
200
+ <button
201
+ type="button"
202
+ className="btn-secondary"
203
+ onClick={handleClose}
204
+ disabled={syncing}
205
+ >
206
+ Cancel
207
+ </button>
208
+ <button type="submit" className="btn-primary" disabled={syncing}>
209
+ {syncing ? 'Syncing...' : 'Sync Now'}
210
+ </button>
211
+ </div>
212
+ </form>
213
+ )}
214
+ </div>
215
+ </div>
216
+ );
217
+ }
218
+
219
+ export default SyncToGitModal;
@@ -0,0 +1,5 @@
1
+ export { GitSettingsModal } from './GitSettingsModal';
2
+ export { CreateBranchModal } from './CreateBranchModal';
3
+ export { SyncToGitModal } from './SyncToGitModal';
4
+ export { CreatePRModal } from './CreatePRModal';
5
+ export { DeleteBranchModal } from './DeleteBranchModal';
@@ -1,14 +1,14 @@
1
- const DeleteIcon = props => (
1
+ const DeleteIcon = ({ size = 14 }) => (
2
2
  <svg
3
3
  className="feather feather-trash-2"
4
4
  fill="none"
5
- height="24"
5
+ height={size}
6
6
  stroke="currentColor"
7
7
  strokeLinecap="round"
8
8
  strokeLinejoin="round"
9
9
  strokeWidth="2"
10
10
  viewBox="0 0 24 24"
11
- width="24"
11
+ width={size}
12
12
  xmlns="http://www.w3.org/2000/svg"
13
13
  >
14
14
  <polyline points="3 6 5 6 21 6" />