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
@@ -47,9 +47,11 @@ describe('<NodeListActions />', () => {
47
47
  'default.hard_hat',
48
48
  );
49
49
  });
50
- expect(
51
- screen.getByText('Successfully deleted node default.hard_hat'),
52
- ).toBeInTheDocument();
50
+ await waitFor(() => {
51
+ expect(
52
+ screen.getByText('Successfully deleted node default.hard_hat'),
53
+ ).toBeInTheDocument();
54
+ });
53
55
  }, 60000);
54
56
 
55
57
  it('skips a node deletion during confirm', async () => {
@@ -148,5 +148,100 @@ exports[`<NamespaceHeader /> should render and match the snapshot 1`] = `
148
148
  </a>
149
149
  </span>
150
150
  </div>
151
+ <div
152
+ style={
153
+ Object {
154
+ "alignItems": "center",
155
+ "display": "flex",
156
+ "gap": "8px",
157
+ }
158
+ }
159
+ >
160
+ <React.Fragment>
161
+ <button
162
+ onClick={[Function]}
163
+ style={
164
+ Object {
165
+ "alignItems": "center",
166
+ "backgroundColor": "#ffffff",
167
+ "border": "1px solid #e2e8f0",
168
+ "borderRadius": "4px",
169
+ "color": "#475569",
170
+ "cursor": "pointer",
171
+ "display": "flex",
172
+ "fontSize": "12px",
173
+ "gap": "4px",
174
+ "height": "28px",
175
+ "padding": "0 10px",
176
+ "whiteSpace": "nowrap",
177
+ }
178
+ }
179
+ >
180
+ <svg
181
+ fill="none"
182
+ height="14"
183
+ stroke="currentColor"
184
+ strokeLinecap="round"
185
+ strokeLinejoin="round"
186
+ strokeWidth="2"
187
+ viewBox="0 0 24 24"
188
+ width="14"
189
+ xmlns="http://www.w3.org/2000/svg"
190
+ >
191
+ <line
192
+ x1="6"
193
+ x2="6"
194
+ y1="3"
195
+ y2="15"
196
+ />
197
+ <circle
198
+ cx="18"
199
+ cy="6"
200
+ r="3"
201
+ />
202
+ <circle
203
+ cx="6"
204
+ cy="18"
205
+ r="3"
206
+ />
207
+ <path
208
+ d="M18 9a9 9 0 0 1-9 9"
209
+ />
210
+ </svg>
211
+ Configure Git
212
+ </button>
213
+ </React.Fragment>
214
+ </div>
215
+ <GitSettingsModal
216
+ currentConfig={null}
217
+ isOpen={false}
218
+ namespace="shared.dimensions.accounts"
219
+ onClose={[Function]}
220
+ onSave={[Function]}
221
+ />
222
+ <CreateBranchModal
223
+ isOpen={false}
224
+ namespace="shared.dimensions.accounts"
225
+ onClose={[Function]}
226
+ onCreate={[Function]}
227
+ />
228
+ <SyncToGitModal
229
+ isOpen={false}
230
+ namespace="shared.dimensions.accounts"
231
+ onClose={[Function]}
232
+ onSync={[Function]}
233
+ />
234
+ <CreatePRModal
235
+ isOpen={false}
236
+ namespace="shared.dimensions.accounts"
237
+ onClose={[Function]}
238
+ onCreate={[Function]}
239
+ />
240
+ <DeleteBranchModal
241
+ isOpen={false}
242
+ namespace="shared.dimensions.accounts"
243
+ onClose={[Function]}
244
+ onDelete={[Function]}
245
+ />
151
246
  </div>
152
247
  `;
@@ -0,0 +1,229 @@
1
+ import React, { useState } from 'react';
2
+
3
+ /**
4
+ * Modal for creating a new branch namespace.
5
+ */
6
+ export function CreateBranchModal({
7
+ isOpen,
8
+ onClose,
9
+ onCreate,
10
+ namespace,
11
+ gitBranch,
12
+ }) {
13
+ const [branchName, setBranchName] = useState('');
14
+ const [creating, setCreating] = useState(false);
15
+ const [error, setError] = useState(null);
16
+ const [result, setResult] = useState(null);
17
+
18
+ // Convert branch name to expected namespace suffix for preview
19
+ const previewNamespace = branchName
20
+ ? `${namespace.split('.').slice(0, -1).join('.') || namespace}.${branchName
21
+ .replace(/-/g, '_')
22
+ .replace(/\//g, '_')}`
23
+ : '';
24
+
25
+ const handleSubmit = async e => {
26
+ e.preventDefault();
27
+ if (!branchName.trim()) return;
28
+
29
+ setError(null);
30
+ setCreating(true);
31
+
32
+ try {
33
+ const res = await onCreate(branchName.trim());
34
+ if (res?._error) {
35
+ setError(res.message);
36
+ } else {
37
+ setResult(res);
38
+ }
39
+ } catch (err) {
40
+ setError(err.message || 'Failed to create branch');
41
+ } finally {
42
+ setCreating(false);
43
+ }
44
+ };
45
+
46
+ const handleClose = () => {
47
+ setBranchName('');
48
+ setError(null);
49
+ setResult(null);
50
+ onClose();
51
+ };
52
+
53
+ if (!isOpen) return null;
54
+
55
+ return (
56
+ <div className="modal-overlay" onClick={handleClose}>
57
+ <div className="modal-content" onClick={e => e.stopPropagation()}>
58
+ <div className="modal-header">
59
+ <h3>Create Branch</h3>
60
+ <button
61
+ className="btn-close-modal"
62
+ onClick={handleClose}
63
+ title="Close"
64
+ >
65
+ ×
66
+ </button>
67
+ </div>
68
+
69
+ {result ? (
70
+ <div className="modal-body">
71
+ <div
72
+ style={{
73
+ textAlign: 'center',
74
+ padding: '16px 0',
75
+ }}
76
+ >
77
+ <span
78
+ style={{
79
+ display: 'inline-flex',
80
+ alignItems: 'center',
81
+ justifyContent: 'center',
82
+ width: '48px',
83
+ height: '48px',
84
+ borderRadius: '50%',
85
+ backgroundColor: '#dcfce7',
86
+ color: '#16a34a',
87
+ fontSize: '24px',
88
+ marginBottom: '12px',
89
+ }}
90
+ >
91
+
92
+ </span>
93
+ <h4 style={{ margin: '0 0 8px 0' }}>Branch Created!</h4>
94
+ </div>
95
+
96
+ <div
97
+ style={{
98
+ backgroundColor: '#f8fafc',
99
+ borderRadius: '6px',
100
+ padding: '12px',
101
+ fontSize: '13px',
102
+ }}
103
+ >
104
+ <div style={{ marginBottom: '8px' }}>
105
+ <strong>Namespace:</strong>{' '}
106
+ <a
107
+ href={`/namespaces/${result.branch.namespace}`}
108
+ style={{ color: '#3b82f6' }}
109
+ >
110
+ {result.branch.namespace}
111
+ </a>
112
+ </div>
113
+ <div style={{ marginBottom: '8px' }}>
114
+ <strong>Git Branch:</strong> {result.branch.git_branch}
115
+ </div>
116
+ <div>
117
+ <strong>Parent:</strong> {result.branch.parent_namespace}
118
+ </div>
119
+ {result.deployment_results?.length > 0 && (
120
+ <div style={{ marginTop: '12px' }}>
121
+ <strong>Nodes copied:</strong>{' '}
122
+ {result.deployment_results.length}
123
+ </div>
124
+ )}
125
+ </div>
126
+
127
+ <div className="modal-actions">
128
+ <button className="btn-secondary" onClick={handleClose}>
129
+ Close
130
+ </button>
131
+ <a
132
+ href={`/namespaces/${result.branch.namespace}`}
133
+ className="btn-primary"
134
+ style={{ textDecoration: 'none' }}
135
+ >
136
+ Go to Branch
137
+ </a>
138
+ </div>
139
+ </div>
140
+ ) : (
141
+ <form onSubmit={handleSubmit}>
142
+ <div className="modal-body">
143
+ {error && (
144
+ <div
145
+ style={{
146
+ padding: '12px',
147
+ backgroundColor: '#fef2f2',
148
+ border: '1px solid #fecaca',
149
+ borderRadius: '6px',
150
+ color: '#dc2626',
151
+ fontSize: '13px',
152
+ marginBottom: '16px',
153
+ }}
154
+ >
155
+ {error}
156
+ </div>
157
+ )}
158
+
159
+ <div className="form-group">
160
+ <label htmlFor="branch-name">Branch Name</label>
161
+ <input
162
+ id="branch-name"
163
+ type="text"
164
+ placeholder="feature-xyz"
165
+ value={branchName}
166
+ onChange={e => setBranchName(e.target.value)}
167
+ disabled={creating}
168
+ autoFocus
169
+ />
170
+ <span className="form-hint">
171
+ Name for the new git branch (e.g., "feature-xyz", "fix-bug")
172
+ </span>
173
+ </div>
174
+
175
+ {branchName && (
176
+ <div
177
+ style={{
178
+ backgroundColor: '#f0f9ff',
179
+ border: '1px solid #bae6fd',
180
+ borderRadius: '6px',
181
+ padding: '12px',
182
+ fontSize: '13px',
183
+ marginTop: '16px',
184
+ }}
185
+ >
186
+ <div style={{ fontWeight: 500, marginBottom: '8px' }}>
187
+ This will:
188
+ </div>
189
+ <ul
190
+ style={{
191
+ margin: 0,
192
+ paddingLeft: '20px',
193
+ color: '#475569',
194
+ }}
195
+ >
196
+ <li>
197
+ Create git branch "{branchName}" from "{gitBranch}"
198
+ </li>
199
+ <li>Create namespace "{previewNamespace}"</li>
200
+ </ul>
201
+ </div>
202
+ )}
203
+ </div>
204
+
205
+ <div className="modal-actions">
206
+ <button
207
+ type="button"
208
+ className="btn-secondary"
209
+ onClick={handleClose}
210
+ disabled={creating}
211
+ >
212
+ Cancel
213
+ </button>
214
+ <button
215
+ type="submit"
216
+ className="btn-primary"
217
+ disabled={creating || !branchName.trim()}
218
+ >
219
+ {creating ? 'Creating...' : 'Create Branch'}
220
+ </button>
221
+ </div>
222
+ </form>
223
+ )}
224
+ </div>
225
+ </div>
226
+ );
227
+ }
228
+
229
+ export default CreateBranchModal;
@@ -0,0 +1,270 @@
1
+ import React, { useState } from 'react';
2
+
3
+ /**
4
+ * Modal for creating a pull request from a branch namespace.
5
+ */
6
+ export function CreatePRModal({
7
+ isOpen,
8
+ onClose,
9
+ onCreate,
10
+ namespace,
11
+ gitBranch,
12
+ parentBranch,
13
+ repoPath,
14
+ }) {
15
+ const [title, setTitle] = useState('');
16
+ const [body, setBody] = useState('');
17
+ const [progress, setProgress] = useState(null); // null | 'syncing' | 'creating'
18
+ const [error, setError] = useState(null);
19
+ const [result, setResult] = useState(null);
20
+
21
+ const handleSubmit = async e => {
22
+ e.preventDefault();
23
+ if (!title.trim()) return;
24
+
25
+ setError(null);
26
+ setProgress('syncing');
27
+
28
+ try {
29
+ const res = await onCreate(
30
+ title.trim(),
31
+ body.trim() || null,
32
+ setProgress,
33
+ );
34
+ if (res?._error) {
35
+ setError(res.message);
36
+ } else {
37
+ setResult(res);
38
+ }
39
+ } catch (err) {
40
+ setError(err.message || 'Failed to create pull request');
41
+ } finally {
42
+ setProgress(null);
43
+ }
44
+ };
45
+
46
+ const getProgressText = () => {
47
+ if (progress === 'syncing') return 'Syncing to git...';
48
+ if (progress === 'creating') return 'Creating PR...';
49
+ return 'Create PR';
50
+ };
51
+
52
+ const handleClose = () => {
53
+ setTitle('');
54
+ setBody('');
55
+ setError(null);
56
+ setResult(null);
57
+ setProgress(null);
58
+ onClose();
59
+ };
60
+
61
+ const isWorking = progress !== null;
62
+
63
+ if (!isOpen) return null;
64
+
65
+ return (
66
+ <div className="modal-overlay" onClick={handleClose}>
67
+ <div
68
+ className="modal-content"
69
+ onClick={e => e.stopPropagation()}
70
+ style={{ maxWidth: '560px' }}
71
+ >
72
+ <div className="modal-header">
73
+ <h3>Create Pull Request</h3>
74
+ <button
75
+ className="btn-close-modal"
76
+ onClick={handleClose}
77
+ title="Close"
78
+ >
79
+ ×
80
+ </button>
81
+ </div>
82
+
83
+ {result ? (
84
+ <div className="modal-body">
85
+ <div
86
+ style={{
87
+ textAlign: 'center',
88
+ padding: '16px 0',
89
+ }}
90
+ >
91
+ <span
92
+ style={{
93
+ display: 'inline-flex',
94
+ alignItems: 'center',
95
+ justifyContent: 'center',
96
+ width: '48px',
97
+ height: '48px',
98
+ borderRadius: '50%',
99
+ backgroundColor: '#dcfce7',
100
+ color: '#16a34a',
101
+ fontSize: '24px',
102
+ marginBottom: '12px',
103
+ }}
104
+ >
105
+
106
+ </span>
107
+ <h4 style={{ margin: '0 0 8px 0' }}>
108
+ Pull Request #{result.pr_number} Created!
109
+ </h4>
110
+ </div>
111
+
112
+ <div
113
+ style={{
114
+ backgroundColor: '#f8fafc',
115
+ borderRadius: '6px',
116
+ padding: '12px',
117
+ fontSize: '13px',
118
+ }}
119
+ >
120
+ <div style={{ marginBottom: '8px' }}>
121
+ <strong>Title:</strong> {title}
122
+ </div>
123
+ <div style={{ marginBottom: '8px' }}>
124
+ <strong>Branch:</strong> {result.head_branch} →{' '}
125
+ {result.base_branch}
126
+ </div>
127
+ <div>
128
+ <strong>URL:</strong>{' '}
129
+ <a
130
+ href={result.pr_url}
131
+ target="_blank"
132
+ rel="noopener noreferrer"
133
+ style={{ color: '#3b82f6' }}
134
+ >
135
+ {result.pr_url}
136
+ </a>
137
+ </div>
138
+ </div>
139
+
140
+ <div className="modal-actions">
141
+ <button className="btn-secondary" onClick={handleClose}>
142
+ Close
143
+ </button>
144
+ <a
145
+ href={result.pr_url}
146
+ target="_blank"
147
+ rel="noopener noreferrer"
148
+ className="btn-primary"
149
+ style={{ textDecoration: 'none' }}
150
+ >
151
+ View on GitHub
152
+ </a>
153
+ </div>
154
+ </div>
155
+ ) : (
156
+ <form onSubmit={handleSubmit}>
157
+ <div className="modal-body">
158
+ {/* Branch flow indicator */}
159
+ <div
160
+ style={{
161
+ display: 'flex',
162
+ alignItems: 'center',
163
+ justifyContent: 'center',
164
+ gap: '12px',
165
+ padding: '12px',
166
+ backgroundColor: '#f8fafc',
167
+ borderRadius: '6px',
168
+ marginBottom: '16px',
169
+ fontSize: '13px',
170
+ }}
171
+ >
172
+ <span
173
+ style={{
174
+ padding: '4px 8px',
175
+ backgroundColor: '#dbeafe',
176
+ borderRadius: '4px',
177
+ fontFamily: 'monospace',
178
+ }}
179
+ >
180
+ {gitBranch}
181
+ </span>
182
+ <span style={{ color: '#64748b' }}>→</span>
183
+ <span
184
+ style={{
185
+ padding: '4px 8px',
186
+ backgroundColor: '#dcfce7',
187
+ borderRadius: '4px',
188
+ fontFamily: 'monospace',
189
+ }}
190
+ >
191
+ {parentBranch}
192
+ </span>
193
+ </div>
194
+
195
+ {error && (
196
+ <div
197
+ style={{
198
+ padding: '12px',
199
+ backgroundColor: '#fef2f2',
200
+ border: '1px solid #fecaca',
201
+ borderRadius: '6px',
202
+ color: '#dc2626',
203
+ fontSize: '13px',
204
+ marginBottom: '16px',
205
+ }}
206
+ >
207
+ {error}
208
+ </div>
209
+ )}
210
+
211
+ <div className="form-group">
212
+ <label htmlFor="pr-title">Title *</label>
213
+ <input
214
+ id="pr-title"
215
+ type="text"
216
+ placeholder="Add new metrics for revenue tracking"
217
+ value={title}
218
+ onChange={e => setTitle(e.target.value)}
219
+ disabled={isWorking}
220
+ autoFocus
221
+ />
222
+ </div>
223
+
224
+ <div className="form-group">
225
+ <label htmlFor="pr-body">Description</label>
226
+ <textarea
227
+ id="pr-body"
228
+ placeholder="Describe the changes in this pull request..."
229
+ value={body}
230
+ onChange={e => setBody(e.target.value)}
231
+ disabled={isWorking}
232
+ rows={4}
233
+ style={{
234
+ width: '100%',
235
+ padding: '8px 12px',
236
+ border: '1px solid #e2e8f0',
237
+ borderRadius: '6px',
238
+ fontSize: '14px',
239
+ fontFamily: 'inherit',
240
+ resize: 'vertical',
241
+ }}
242
+ />
243
+ </div>
244
+ </div>
245
+
246
+ <div className="modal-actions">
247
+ <button
248
+ type="button"
249
+ className="btn-secondary"
250
+ onClick={handleClose}
251
+ disabled={isWorking}
252
+ >
253
+ Cancel
254
+ </button>
255
+ <button
256
+ type="submit"
257
+ className="btn-primary"
258
+ disabled={isWorking || !title.trim()}
259
+ >
260
+ {getProgressText()}
261
+ </button>
262
+ </div>
263
+ </form>
264
+ )}
265
+ </div>
266
+ </div>
267
+ );
268
+ }
269
+
270
+ export default CreatePRModal;