datajunction-ui 0.0.56 → 0.0.58
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.
- package/package.json +1 -1
- package/src/app/components/NamespaceHeader.jsx +43 -52
- package/src/app/components/__tests__/NamespaceHeader.test.jsx +146 -21
- package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +2 -56
- package/src/app/components/git/GitSettingsModal.jsx +109 -26
- package/src/app/components/git/__tests__/GitSettingsModal.test.jsx +301 -0
- package/src/app/services/DJService.js +20 -0
package/package.json
CHANGED
|
@@ -21,6 +21,7 @@ export default function NamespaceHeader({
|
|
|
21
21
|
|
|
22
22
|
// Git config state
|
|
23
23
|
const [gitConfig, setGitConfig] = useState(null);
|
|
24
|
+
const [gitConfigLoading, setGitConfigLoading] = useState(true);
|
|
24
25
|
const [parentGitConfig, setParentGitConfig] = useState(null);
|
|
25
26
|
const [existingPR, setExistingPR] = useState(null);
|
|
26
27
|
|
|
@@ -32,6 +33,9 @@ export default function NamespaceHeader({
|
|
|
32
33
|
const [showDeleteBranch, setShowDeleteBranch] = useState(false);
|
|
33
34
|
|
|
34
35
|
useEffect(() => {
|
|
36
|
+
// Reset loading state when namespace changes
|
|
37
|
+
setGitConfigLoading(true);
|
|
38
|
+
|
|
35
39
|
const fetchData = async () => {
|
|
36
40
|
if (namespace) {
|
|
37
41
|
// Fetch deployment sources
|
|
@@ -84,6 +88,8 @@ export default function NamespaceHeader({
|
|
|
84
88
|
if (onGitConfigLoaded) {
|
|
85
89
|
onGitConfigLoaded(null);
|
|
86
90
|
}
|
|
91
|
+
} finally {
|
|
92
|
+
setGitConfigLoading(false);
|
|
87
93
|
}
|
|
88
94
|
}
|
|
89
95
|
};
|
|
@@ -114,6 +120,12 @@ export default function NamespaceHeader({
|
|
|
114
120
|
}
|
|
115
121
|
return result;
|
|
116
122
|
};
|
|
123
|
+
const handleRemoveGitConfig = async () => {
|
|
124
|
+
const result = await djClient.deleteNamespaceGitConfig(namespace);
|
|
125
|
+
if (!result?._error) {
|
|
126
|
+
setGitConfig(null);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
117
129
|
|
|
118
130
|
const handleCreateBranch = async branchName => {
|
|
119
131
|
return await djClient.createBranch(namespace, branchName);
|
|
@@ -368,7 +380,7 @@ export default function NamespaceHeader({
|
|
|
368
380
|
<circle cx="6" cy="18" r="3"></circle>
|
|
369
381
|
<path d="M18 9a9 9 0 0 1-9 9"></path>
|
|
370
382
|
</svg>
|
|
371
|
-
Git
|
|
383
|
+
Deployed from Git
|
|
372
384
|
</>
|
|
373
385
|
) : (
|
|
374
386
|
<>
|
|
@@ -642,55 +654,33 @@ export default function NamespaceHeader({
|
|
|
642
654
|
{/* Right side: git actions + children */}
|
|
643
655
|
<div style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
|
|
644
656
|
{/* Git controls for non-branch namespaces */}
|
|
645
|
-
{namespace && !isBranchNamespace && (
|
|
657
|
+
{namespace && !isBranchNamespace && !gitConfigLoading && (
|
|
646
658
|
<>
|
|
659
|
+
<button
|
|
660
|
+
style={buttonStyle}
|
|
661
|
+
onClick={() => setShowGitSettings(true)}
|
|
662
|
+
title="Git Settings"
|
|
663
|
+
>
|
|
664
|
+
<svg
|
|
665
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
666
|
+
width="14"
|
|
667
|
+
height="14"
|
|
668
|
+
viewBox="0 0 24 24"
|
|
669
|
+
fill="none"
|
|
670
|
+
stroke="currentColor"
|
|
671
|
+
strokeWidth="2"
|
|
672
|
+
strokeLinecap="round"
|
|
673
|
+
strokeLinejoin="round"
|
|
674
|
+
>
|
|
675
|
+
<circle cx="12" cy="12" r="3" />
|
|
676
|
+
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
|
|
677
|
+
</svg>
|
|
678
|
+
Git Settings
|
|
679
|
+
</button>
|
|
647
680
|
{hasGitConfig ? (
|
|
648
|
-
<>
|
|
649
|
-
<button
|
|
650
|
-
style={buttonStyle}
|
|
651
|
-
onClick={() => setShowGitSettings(true)}
|
|
652
|
-
title="Git Settings"
|
|
653
|
-
>
|
|
654
|
-
<svg
|
|
655
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
656
|
-
width="14"
|
|
657
|
-
height="14"
|
|
658
|
-
viewBox="0 0 24 24"
|
|
659
|
-
fill="none"
|
|
660
|
-
stroke="currentColor"
|
|
661
|
-
strokeWidth="2"
|
|
662
|
-
strokeLinecap="round"
|
|
663
|
-
strokeLinejoin="round"
|
|
664
|
-
>
|
|
665
|
-
<circle cx="12" cy="12" r="3" />
|
|
666
|
-
<path d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z" />
|
|
667
|
-
</svg>
|
|
668
|
-
</button>
|
|
669
|
-
<button
|
|
670
|
-
style={primaryButtonStyle}
|
|
671
|
-
onClick={() => setShowCreateBranch(true)}
|
|
672
|
-
>
|
|
673
|
-
<svg
|
|
674
|
-
xmlns="http://www.w3.org/2000/svg"
|
|
675
|
-
width="14"
|
|
676
|
-
height="14"
|
|
677
|
-
viewBox="0 0 24 24"
|
|
678
|
-
fill="none"
|
|
679
|
-
stroke="currentColor"
|
|
680
|
-
strokeWidth="2"
|
|
681
|
-
strokeLinecap="round"
|
|
682
|
-
strokeLinejoin="round"
|
|
683
|
-
>
|
|
684
|
-
<line x1="12" y1="5" x2="12" y2="19" />
|
|
685
|
-
<line x1="5" y1="12" x2="19" y2="12" />
|
|
686
|
-
</svg>
|
|
687
|
-
New Branch
|
|
688
|
-
</button>
|
|
689
|
-
</>
|
|
690
|
-
) : (
|
|
691
681
|
<button
|
|
692
|
-
style={
|
|
693
|
-
onClick={() =>
|
|
682
|
+
style={primaryButtonStyle}
|
|
683
|
+
onClick={() => setShowCreateBranch(true)}
|
|
694
684
|
>
|
|
695
685
|
<svg
|
|
696
686
|
xmlns="http://www.w3.org/2000/svg"
|
|
@@ -703,13 +693,13 @@ export default function NamespaceHeader({
|
|
|
703
693
|
strokeLinecap="round"
|
|
704
694
|
strokeLinejoin="round"
|
|
705
695
|
>
|
|
706
|
-
<line x1="
|
|
707
|
-
<
|
|
708
|
-
<circle cx="6" cy="18" r="3" />
|
|
709
|
-
<path d="M18 9a9 9 0 0 1-9 9" />
|
|
696
|
+
<line x1="12" y1="5" x2="12" y2="19" />
|
|
697
|
+
<line x1="5" y1="12" x2="19" y2="12" />
|
|
710
698
|
</svg>
|
|
711
|
-
|
|
699
|
+
New Branch
|
|
712
700
|
</button>
|
|
701
|
+
) : (
|
|
702
|
+
<></>
|
|
713
703
|
)}
|
|
714
704
|
</>
|
|
715
705
|
)}
|
|
@@ -824,6 +814,7 @@ export default function NamespaceHeader({
|
|
|
824
814
|
isOpen={showGitSettings}
|
|
825
815
|
onClose={() => setShowGitSettings(false)}
|
|
826
816
|
onSave={handleSaveGitConfig}
|
|
817
|
+
onRemove={handleRemoveGitConfig}
|
|
827
818
|
currentConfig={gitConfig}
|
|
828
819
|
namespace={namespace}
|
|
829
820
|
/>
|
|
@@ -42,8 +42,8 @@ describe('<NamespaceHeader />', () => {
|
|
|
42
42
|
);
|
|
43
43
|
});
|
|
44
44
|
|
|
45
|
-
// Should render Git
|
|
46
|
-
expect(screen.getByText(/Git
|
|
45
|
+
// Should render Deployed from Git badge for git source
|
|
46
|
+
expect(screen.getByText(/Deployed from Git/)).toBeInTheDocument();
|
|
47
47
|
});
|
|
48
48
|
|
|
49
49
|
it('should render git source badge when source type is git without branch', async () => {
|
|
@@ -73,8 +73,8 @@ describe('<NamespaceHeader />', () => {
|
|
|
73
73
|
);
|
|
74
74
|
});
|
|
75
75
|
|
|
76
|
-
// Should render Git
|
|
77
|
-
expect(screen.getByText(/Git
|
|
76
|
+
// Should render Deployed from Git badge for git source even without branch
|
|
77
|
+
expect(screen.getByText(/Deployed from Git/)).toBeInTheDocument();
|
|
78
78
|
});
|
|
79
79
|
|
|
80
80
|
it('should render local source badge when source type is local', async () => {
|
|
@@ -131,7 +131,7 @@ describe('<NamespaceHeader />', () => {
|
|
|
131
131
|
});
|
|
132
132
|
|
|
133
133
|
// Should not render any source badge
|
|
134
|
-
expect(screen.queryByText(/Git
|
|
134
|
+
expect(screen.queryByText(/Deployed from Git/)).not.toBeInTheDocument();
|
|
135
135
|
expect(screen.queryByText(/Local Deploy/)).not.toBeInTheDocument();
|
|
136
136
|
});
|
|
137
137
|
|
|
@@ -158,7 +158,7 @@ describe('<NamespaceHeader />', () => {
|
|
|
158
158
|
// Should still render breadcrumb without badge
|
|
159
159
|
expect(screen.getByText('test')).toBeInTheDocument();
|
|
160
160
|
expect(screen.getByText('namespace')).toBeInTheDocument();
|
|
161
|
-
expect(screen.queryByText(/Git
|
|
161
|
+
expect(screen.queryByText(/Deployed from Git/)).not.toBeInTheDocument();
|
|
162
162
|
});
|
|
163
163
|
|
|
164
164
|
it('should open dropdown when clicking the git managed button', async () => {
|
|
@@ -195,11 +195,11 @@ describe('<NamespaceHeader />', () => {
|
|
|
195
195
|
);
|
|
196
196
|
|
|
197
197
|
await waitFor(() => {
|
|
198
|
-
expect(screen.getByText(/Git
|
|
198
|
+
expect(screen.getByText(/Deployed from Git/)).toBeInTheDocument();
|
|
199
199
|
});
|
|
200
200
|
|
|
201
201
|
// Click the dropdown button
|
|
202
|
-
fireEvent.click(screen.getByText(/Git
|
|
202
|
+
fireEvent.click(screen.getByText(/Deployed from Git/));
|
|
203
203
|
|
|
204
204
|
// Should show repository link in dropdown
|
|
205
205
|
await waitFor(() => {
|
|
@@ -297,10 +297,10 @@ describe('<NamespaceHeader />', () => {
|
|
|
297
297
|
);
|
|
298
298
|
|
|
299
299
|
await waitFor(() => {
|
|
300
|
-
expect(screen.getByText(/Git
|
|
300
|
+
expect(screen.getByText(/Deployed from Git/)).toBeInTheDocument();
|
|
301
301
|
});
|
|
302
302
|
|
|
303
|
-
fireEvent.click(screen.getByText(/Git
|
|
303
|
+
fireEvent.click(screen.getByText(/Deployed from Git/));
|
|
304
304
|
|
|
305
305
|
// Should show branch names in deployment list
|
|
306
306
|
await waitFor(() => {
|
|
@@ -375,11 +375,11 @@ describe('<NamespaceHeader />', () => {
|
|
|
375
375
|
);
|
|
376
376
|
|
|
377
377
|
await waitFor(() => {
|
|
378
|
-
expect(screen.getByText(/Git
|
|
378
|
+
expect(screen.getByText(/Deployed from Git/)).toBeInTheDocument();
|
|
379
379
|
});
|
|
380
380
|
|
|
381
381
|
// Open dropdown
|
|
382
|
-
fireEvent.click(screen.getByText(/Git
|
|
382
|
+
fireEvent.click(screen.getByText(/Deployed from Git/));
|
|
383
383
|
|
|
384
384
|
await waitFor(() => {
|
|
385
385
|
expect(screen.getByText(/github.com\/test\/repo/)).toBeInTheDocument();
|
|
@@ -418,14 +418,14 @@ describe('<NamespaceHeader />', () => {
|
|
|
418
418
|
);
|
|
419
419
|
|
|
420
420
|
await waitFor(() => {
|
|
421
|
-
expect(screen.getByText(/Git
|
|
421
|
+
expect(screen.getByText(/Deployed from Git/)).toBeInTheDocument();
|
|
422
422
|
});
|
|
423
423
|
|
|
424
424
|
// Initially shows down arrow
|
|
425
425
|
expect(screen.getByText('▼')).toBeInTheDocument();
|
|
426
426
|
|
|
427
427
|
// Click to open
|
|
428
|
-
fireEvent.click(screen.getByText(/Git
|
|
428
|
+
fireEvent.click(screen.getByText(/Deployed from Git/));
|
|
429
429
|
|
|
430
430
|
// Should show up arrow when open
|
|
431
431
|
await waitFor(() => {
|
|
@@ -455,10 +455,10 @@ describe('<NamespaceHeader />', () => {
|
|
|
455
455
|
);
|
|
456
456
|
|
|
457
457
|
await waitFor(() => {
|
|
458
|
-
expect(screen.getByText(/Git
|
|
458
|
+
expect(screen.getByText(/Deployed from Git/)).toBeInTheDocument();
|
|
459
459
|
});
|
|
460
460
|
|
|
461
|
-
fireEvent.click(screen.getByText(/Git
|
|
461
|
+
fireEvent.click(screen.getByText(/Deployed from Git/));
|
|
462
462
|
|
|
463
463
|
await waitFor(() => {
|
|
464
464
|
// Find link by its text content (repository URL)
|
|
@@ -509,7 +509,7 @@ describe('<NamespaceHeader />', () => {
|
|
|
509
509
|
});
|
|
510
510
|
});
|
|
511
511
|
|
|
512
|
-
it('should show
|
|
512
|
+
it('should show Git Settings button and open modal', async () => {
|
|
513
513
|
const mockDjClient = {
|
|
514
514
|
namespaceSources: jest.fn().mockResolvedValue({
|
|
515
515
|
total_deployments: 0,
|
|
@@ -528,10 +528,10 @@ describe('<NamespaceHeader />', () => {
|
|
|
528
528
|
);
|
|
529
529
|
|
|
530
530
|
await waitFor(() => {
|
|
531
|
-
expect(screen.getByText('
|
|
531
|
+
expect(screen.getByText('Git Settings')).toBeInTheDocument();
|
|
532
532
|
});
|
|
533
533
|
|
|
534
|
-
fireEvent.click(screen.getByText('
|
|
534
|
+
fireEvent.click(screen.getByText('Git Settings'));
|
|
535
535
|
|
|
536
536
|
await waitFor(() => {
|
|
537
537
|
expect(screen.getByText('Git Configuration')).toBeInTheDocument();
|
|
@@ -725,10 +725,10 @@ describe('<NamespaceHeader />', () => {
|
|
|
725
725
|
);
|
|
726
726
|
|
|
727
727
|
await waitFor(() => {
|
|
728
|
-
expect(screen.getByText('
|
|
728
|
+
expect(screen.getByText('Git Settings')).toBeInTheDocument();
|
|
729
729
|
});
|
|
730
730
|
|
|
731
|
-
fireEvent.click(screen.getByText('
|
|
731
|
+
fireEvent.click(screen.getByText('Git Settings'));
|
|
732
732
|
|
|
733
733
|
await waitFor(() => {
|
|
734
734
|
expect(screen.getByLabelText('Repository')).toBeInTheDocument();
|
|
@@ -1272,4 +1272,129 @@ describe('<NamespaceHeader />', () => {
|
|
|
1272
1272
|
expect(onGitConfigLoaded).toHaveBeenCalledWith(null);
|
|
1273
1273
|
});
|
|
1274
1274
|
});
|
|
1275
|
+
|
|
1276
|
+
it('should call deleteNamespaceGitConfig when removing git settings', async () => {
|
|
1277
|
+
// Mock window.confirm for this test
|
|
1278
|
+
global.confirm = jest.fn(() => true);
|
|
1279
|
+
|
|
1280
|
+
const mockDjClient = {
|
|
1281
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
1282
|
+
total_deployments: 0,
|
|
1283
|
+
primary_source: null,
|
|
1284
|
+
}),
|
|
1285
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
1286
|
+
getNamespaceGitConfig: jest.fn().mockResolvedValue({
|
|
1287
|
+
github_repo_path: 'test/repo',
|
|
1288
|
+
git_branch: 'main',
|
|
1289
|
+
git_path: 'nodes/',
|
|
1290
|
+
git_only: false,
|
|
1291
|
+
}),
|
|
1292
|
+
deleteNamespaceGitConfig: jest.fn().mockResolvedValue({ success: true }),
|
|
1293
|
+
};
|
|
1294
|
+
|
|
1295
|
+
render(
|
|
1296
|
+
<MemoryRouter>
|
|
1297
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
1298
|
+
<NamespaceHeader namespace="test.namespace" />
|
|
1299
|
+
</DJClientContext.Provider>
|
|
1300
|
+
</MemoryRouter>,
|
|
1301
|
+
);
|
|
1302
|
+
|
|
1303
|
+
await waitFor(() => {
|
|
1304
|
+
expect(screen.getByText('Git Settings')).toBeInTheDocument();
|
|
1305
|
+
});
|
|
1306
|
+
|
|
1307
|
+
fireEvent.click(screen.getByText('Git Settings'));
|
|
1308
|
+
|
|
1309
|
+
await waitFor(() => {
|
|
1310
|
+
expect(screen.getByText('Git Configuration')).toBeInTheDocument();
|
|
1311
|
+
});
|
|
1312
|
+
|
|
1313
|
+
// Click reset button in the modal (button text is "Reset")
|
|
1314
|
+
const removeButton = screen.getByText('Reset');
|
|
1315
|
+
fireEvent.click(removeButton);
|
|
1316
|
+
|
|
1317
|
+
await waitFor(() => {
|
|
1318
|
+
expect(mockDjClient.deleteNamespaceGitConfig).toHaveBeenCalledWith(
|
|
1319
|
+
'test.namespace',
|
|
1320
|
+
);
|
|
1321
|
+
});
|
|
1322
|
+
|
|
1323
|
+
// Clean up mock
|
|
1324
|
+
jest.restoreAllMocks();
|
|
1325
|
+
});
|
|
1326
|
+
|
|
1327
|
+
it('should handle sync error in handleCreatePR', async () => {
|
|
1328
|
+
const mockDjClient = {
|
|
1329
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
1330
|
+
total_deployments: 1,
|
|
1331
|
+
primary_source: {
|
|
1332
|
+
type: 'git',
|
|
1333
|
+
repository: 'test/repo',
|
|
1334
|
+
branch: 'feature',
|
|
1335
|
+
},
|
|
1336
|
+
}),
|
|
1337
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
1338
|
+
getNamespaceGitConfig: jest
|
|
1339
|
+
.fn()
|
|
1340
|
+
.mockResolvedValueOnce({
|
|
1341
|
+
github_repo_path: 'test/repo',
|
|
1342
|
+
git_branch: 'feature',
|
|
1343
|
+
git_path: 'nodes/',
|
|
1344
|
+
git_only: false,
|
|
1345
|
+
parent_namespace: 'test.main',
|
|
1346
|
+
})
|
|
1347
|
+
.mockResolvedValueOnce({
|
|
1348
|
+
github_repo_path: 'test/repo',
|
|
1349
|
+
git_branch: 'main',
|
|
1350
|
+
git_path: 'nodes/',
|
|
1351
|
+
}),
|
|
1352
|
+
getPullRequest: jest.fn().mockResolvedValue(null),
|
|
1353
|
+
syncNamespaceToGit: jest.fn().mockResolvedValue({
|
|
1354
|
+
_error: true,
|
|
1355
|
+
message: 'Sync failed: merge conflict',
|
|
1356
|
+
}),
|
|
1357
|
+
};
|
|
1358
|
+
|
|
1359
|
+
render(
|
|
1360
|
+
<MemoryRouter>
|
|
1361
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
1362
|
+
<NamespaceHeader namespace="test.feature" />
|
|
1363
|
+
</DJClientContext.Provider>
|
|
1364
|
+
</MemoryRouter>,
|
|
1365
|
+
);
|
|
1366
|
+
|
|
1367
|
+
await waitFor(() => {
|
|
1368
|
+
expect(screen.getByText('Create PR')).toBeInTheDocument();
|
|
1369
|
+
});
|
|
1370
|
+
|
|
1371
|
+
fireEvent.click(screen.getByText('Create PR'));
|
|
1372
|
+
|
|
1373
|
+
await waitFor(() => {
|
|
1374
|
+
expect(screen.getByLabelText(/Title/)).toBeInTheDocument();
|
|
1375
|
+
});
|
|
1376
|
+
|
|
1377
|
+
fireEvent.change(screen.getByLabelText(/Title/), {
|
|
1378
|
+
target: { value: 'My PR Title' },
|
|
1379
|
+
});
|
|
1380
|
+
|
|
1381
|
+
const createPRButtons = screen.getAllByRole('button', {
|
|
1382
|
+
name: 'Create PR',
|
|
1383
|
+
});
|
|
1384
|
+
fireEvent.click(createPRButtons[createPRButtons.length - 1]);
|
|
1385
|
+
|
|
1386
|
+
await waitFor(() => {
|
|
1387
|
+
expect(mockDjClient.syncNamespaceToGit).toHaveBeenCalledWith(
|
|
1388
|
+
'test.feature',
|
|
1389
|
+
'My PR Title',
|
|
1390
|
+
);
|
|
1391
|
+
});
|
|
1392
|
+
|
|
1393
|
+
// Should show error message from sync failure
|
|
1394
|
+
await waitFor(() => {
|
|
1395
|
+
expect(
|
|
1396
|
+
screen.getByText(/Sync failed: merge conflict/),
|
|
1397
|
+
).toBeInTheDocument();
|
|
1398
|
+
});
|
|
1399
|
+
});
|
|
1275
1400
|
});
|
|
@@ -156,67 +156,13 @@ exports[`<NamespaceHeader /> should render and match the snapshot 1`] = `
|
|
|
156
156
|
"gap": "8px",
|
|
157
157
|
}
|
|
158
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>
|
|
159
|
+
/>
|
|
215
160
|
<GitSettingsModal
|
|
216
161
|
currentConfig={null}
|
|
217
162
|
isOpen={false}
|
|
218
163
|
namespace="shared.dimensions.accounts"
|
|
219
164
|
onClose={[Function]}
|
|
165
|
+
onRemove={[Function]}
|
|
220
166
|
onSave={[Function]}
|
|
221
167
|
/>
|
|
222
168
|
<CreateBranchModal
|
|
@@ -7,6 +7,7 @@ export function GitSettingsModal({
|
|
|
7
7
|
isOpen,
|
|
8
8
|
onClose,
|
|
9
9
|
onSave,
|
|
10
|
+
onRemove,
|
|
10
11
|
currentConfig,
|
|
11
12
|
namespace,
|
|
12
13
|
}) {
|
|
@@ -15,8 +16,10 @@ export function GitSettingsModal({
|
|
|
15
16
|
const [path, setPath] = useState('');
|
|
16
17
|
const [gitOnly, setGitOnly] = useState(true);
|
|
17
18
|
const [saving, setSaving] = useState(false);
|
|
19
|
+
const [removing, setRemoving] = useState(false);
|
|
18
20
|
const [error, setError] = useState(null);
|
|
19
21
|
const [success, setSuccess] = useState(false);
|
|
22
|
+
const [wasRemoved, setWasRemoved] = useState(false);
|
|
20
23
|
|
|
21
24
|
useEffect(() => {
|
|
22
25
|
if (currentConfig) {
|
|
@@ -32,14 +35,17 @@ export function GitSettingsModal({
|
|
|
32
35
|
setPath('nodes/');
|
|
33
36
|
setGitOnly(true);
|
|
34
37
|
}
|
|
35
|
-
|
|
38
|
+
// Don't reset success here - it gets reset when modal closes
|
|
39
|
+
// Otherwise the success banner disappears when currentConfig updates after save
|
|
36
40
|
setError(null);
|
|
41
|
+
setWasRemoved(false);
|
|
37
42
|
}, [currentConfig]);
|
|
38
43
|
|
|
39
44
|
const handleSubmit = async e => {
|
|
40
45
|
e.preventDefault();
|
|
41
46
|
setError(null);
|
|
42
47
|
setSuccess(false);
|
|
48
|
+
setWasRemoved(false);
|
|
43
49
|
setSaving(true);
|
|
44
50
|
|
|
45
51
|
try {
|
|
@@ -65,9 +71,50 @@ export function GitSettingsModal({
|
|
|
65
71
|
}
|
|
66
72
|
};
|
|
67
73
|
|
|
74
|
+
const handleRemove = async () => {
|
|
75
|
+
if (
|
|
76
|
+
!window.confirm(
|
|
77
|
+
'Remove git configuration? This will disconnect this namespace from git but will not delete any files.',
|
|
78
|
+
)
|
|
79
|
+
) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
setError(null);
|
|
84
|
+
setSuccess(false);
|
|
85
|
+
setWasRemoved(false);
|
|
86
|
+
setRemoving(true);
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const config = {
|
|
90
|
+
github_repo_path: null,
|
|
91
|
+
git_branch: null,
|
|
92
|
+
git_path: null,
|
|
93
|
+
git_only: false,
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const result = await onRemove(config);
|
|
97
|
+
if (result?._error) {
|
|
98
|
+
setError(result.message);
|
|
99
|
+
} else {
|
|
100
|
+
setSuccess(true);
|
|
101
|
+
setWasRemoved(true);
|
|
102
|
+
// Close modal after successful removal
|
|
103
|
+
setTimeout(() => {
|
|
104
|
+
onClose();
|
|
105
|
+
}, 1500);
|
|
106
|
+
}
|
|
107
|
+
} catch (err) {
|
|
108
|
+
setError(err.message || 'Failed to remove git settings');
|
|
109
|
+
} finally {
|
|
110
|
+
setRemoving(false);
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
68
114
|
const handleClose = () => {
|
|
69
115
|
setError(null);
|
|
70
116
|
setSuccess(false);
|
|
117
|
+
setWasRemoved(false);
|
|
71
118
|
onClose();
|
|
72
119
|
};
|
|
73
120
|
|
|
@@ -250,38 +297,74 @@ export function GitSettingsModal({
|
|
|
250
297
|
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14" />
|
|
251
298
|
<polyline points="22 4 12 14.01 9 11.01" />
|
|
252
299
|
</svg>
|
|
253
|
-
|
|
300
|
+
{wasRemoved
|
|
301
|
+
? 'Git configuration removed successfully!'
|
|
302
|
+
: 'Git configuration saved successfully!'}
|
|
254
303
|
</div>
|
|
255
304
|
)}
|
|
256
305
|
</div>
|
|
257
306
|
|
|
258
|
-
<div
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
307
|
+
<div
|
|
308
|
+
className="modal-actions"
|
|
309
|
+
style={{
|
|
310
|
+
display: 'flex',
|
|
311
|
+
justifyContent: 'space-between',
|
|
312
|
+
alignItems: 'center',
|
|
313
|
+
}}
|
|
314
|
+
>
|
|
315
|
+
{/* Left side: Remove button (only show if git is configured) */}
|
|
316
|
+
<div>
|
|
317
|
+
{currentConfig?.github_repo_path && !success && (
|
|
318
|
+
<button
|
|
319
|
+
type="button"
|
|
320
|
+
onClick={handleRemove}
|
|
321
|
+
disabled={saving || removing}
|
|
322
|
+
style={{
|
|
323
|
+
padding: '8px 16px',
|
|
324
|
+
fontSize: '13px',
|
|
325
|
+
fontWeight: 500,
|
|
326
|
+
border: '1px solid #fca5a5',
|
|
327
|
+
borderRadius: '6px',
|
|
328
|
+
backgroundColor: removing ? '#fee2e2' : '#ffffff',
|
|
329
|
+
color: removing ? '#991b1b' : '#dc2626',
|
|
330
|
+
cursor: saving || removing ? 'not-allowed' : 'pointer',
|
|
331
|
+
opacity: saving || removing ? 0.6 : 1,
|
|
332
|
+
}}
|
|
333
|
+
>
|
|
334
|
+
{removing ? 'Removing...' : 'Reset'}
|
|
335
|
+
</button>
|
|
336
|
+
)}
|
|
337
|
+
</div>
|
|
338
|
+
|
|
339
|
+
{/* Right side: Save/Cancel buttons */}
|
|
340
|
+
<div style={{ display: 'flex', gap: '8px' }}>
|
|
268
341
|
<button
|
|
269
|
-
type="
|
|
270
|
-
className="btn-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
saving
|
|
274
|
-
? {
|
|
275
|
-
opacity: 0.7,
|
|
276
|
-
cursor: 'wait',
|
|
277
|
-
backgroundColor: '#9ca3af',
|
|
278
|
-
}
|
|
279
|
-
: {}
|
|
280
|
-
}
|
|
342
|
+
type="button"
|
|
343
|
+
className="btn-secondary"
|
|
344
|
+
onClick={handleClose}
|
|
345
|
+
disabled={saving || removing}
|
|
281
346
|
>
|
|
282
|
-
{
|
|
347
|
+
{success ? 'Close' : 'Cancel'}
|
|
283
348
|
</button>
|
|
284
|
-
|
|
349
|
+
{!success && (
|
|
350
|
+
<button
|
|
351
|
+
type="submit"
|
|
352
|
+
className="btn-primary"
|
|
353
|
+
disabled={saving || removing}
|
|
354
|
+
style={
|
|
355
|
+
saving
|
|
356
|
+
? {
|
|
357
|
+
opacity: 0.7,
|
|
358
|
+
cursor: 'wait',
|
|
359
|
+
backgroundColor: '#9ca3af',
|
|
360
|
+
}
|
|
361
|
+
: {}
|
|
362
|
+
}
|
|
363
|
+
>
|
|
364
|
+
{saving ? 'Saving...' : 'Save Settings'}
|
|
365
|
+
</button>
|
|
366
|
+
)}
|
|
367
|
+
</div>
|
|
285
368
|
</div>
|
|
286
369
|
</form>
|
|
287
370
|
</div>
|
|
@@ -0,0 +1,301 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
|
3
|
+
import GitSettingsModal from '../GitSettingsModal';
|
|
4
|
+
|
|
5
|
+
describe('<GitSettingsModal />', () => {
|
|
6
|
+
const mockOnClose = jest.fn();
|
|
7
|
+
const mockOnSave = jest.fn();
|
|
8
|
+
const mockOnRemove = jest.fn();
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
jest.clearAllMocks();
|
|
12
|
+
// Mock window.confirm
|
|
13
|
+
global.confirm = jest.fn(() => true);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
jest.restoreAllMocks();
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('should render with existing config', () => {
|
|
21
|
+
const currentConfig = {
|
|
22
|
+
github_repo_path: 'test/repo',
|
|
23
|
+
git_branch: 'main',
|
|
24
|
+
git_path: 'nodes/',
|
|
25
|
+
git_only: false,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
render(
|
|
29
|
+
<GitSettingsModal
|
|
30
|
+
isOpen={true}
|
|
31
|
+
onClose={mockOnClose}
|
|
32
|
+
onSave={mockOnSave}
|
|
33
|
+
onRemove={mockOnRemove}
|
|
34
|
+
currentConfig={currentConfig}
|
|
35
|
+
namespace="test.namespace"
|
|
36
|
+
/>,
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
expect(screen.getByDisplayValue('test/repo')).toBeInTheDocument();
|
|
40
|
+
expect(screen.getByDisplayValue('main')).toBeInTheDocument();
|
|
41
|
+
expect(screen.getByDisplayValue('nodes/')).toBeInTheDocument();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should call onRemove when Reset button is clicked and confirmed', async () => {
|
|
45
|
+
const currentConfig = {
|
|
46
|
+
github_repo_path: 'test/repo',
|
|
47
|
+
git_branch: 'main',
|
|
48
|
+
git_path: 'nodes/',
|
|
49
|
+
git_only: false,
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
mockOnRemove.mockResolvedValue({ success: true });
|
|
53
|
+
|
|
54
|
+
render(
|
|
55
|
+
<GitSettingsModal
|
|
56
|
+
isOpen={true}
|
|
57
|
+
onClose={mockOnClose}
|
|
58
|
+
onSave={mockOnSave}
|
|
59
|
+
onRemove={mockOnRemove}
|
|
60
|
+
currentConfig={currentConfig}
|
|
61
|
+
namespace="test.namespace"
|
|
62
|
+
/>,
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const removeButton = screen.getByText('Reset');
|
|
66
|
+
fireEvent.click(removeButton);
|
|
67
|
+
|
|
68
|
+
// Verify confirmation dialog was shown
|
|
69
|
+
expect(global.confirm).toHaveBeenCalledWith(
|
|
70
|
+
expect.stringContaining('Remove git configuration?'),
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
await waitFor(() => {
|
|
74
|
+
expect(mockOnRemove).toHaveBeenCalledWith({
|
|
75
|
+
github_repo_path: null,
|
|
76
|
+
git_branch: null,
|
|
77
|
+
git_path: null,
|
|
78
|
+
git_only: false,
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
it('should not call onRemove when user cancels confirmation', async () => {
|
|
84
|
+
global.confirm = jest.fn(() => false);
|
|
85
|
+
|
|
86
|
+
const currentConfig = {
|
|
87
|
+
github_repo_path: 'test/repo',
|
|
88
|
+
git_branch: 'main',
|
|
89
|
+
git_path: 'nodes/',
|
|
90
|
+
git_only: false,
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
render(
|
|
94
|
+
<GitSettingsModal
|
|
95
|
+
isOpen={true}
|
|
96
|
+
onClose={mockOnClose}
|
|
97
|
+
onSave={mockOnSave}
|
|
98
|
+
onRemove={mockOnRemove}
|
|
99
|
+
currentConfig={currentConfig}
|
|
100
|
+
namespace="test.namespace"
|
|
101
|
+
/>,
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const removeButton = screen.getByText('Reset');
|
|
105
|
+
fireEvent.click(removeButton);
|
|
106
|
+
|
|
107
|
+
expect(global.confirm).toHaveBeenCalled();
|
|
108
|
+
expect(mockOnRemove).not.toHaveBeenCalled();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('should show error message when remove fails', async () => {
|
|
112
|
+
const currentConfig = {
|
|
113
|
+
github_repo_path: 'test/repo',
|
|
114
|
+
git_branch: 'main',
|
|
115
|
+
git_path: 'nodes/',
|
|
116
|
+
git_only: false,
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
mockOnRemove.mockResolvedValue({
|
|
120
|
+
_error: true,
|
|
121
|
+
message: 'Failed to remove git configuration',
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
render(
|
|
125
|
+
<GitSettingsModal
|
|
126
|
+
isOpen={true}
|
|
127
|
+
onClose={mockOnClose}
|
|
128
|
+
onSave={mockOnSave}
|
|
129
|
+
onRemove={mockOnRemove}
|
|
130
|
+
currentConfig={currentConfig}
|
|
131
|
+
namespace="test.namespace"
|
|
132
|
+
/>,
|
|
133
|
+
);
|
|
134
|
+
|
|
135
|
+
const removeButton = screen.getByText('Reset');
|
|
136
|
+
fireEvent.click(removeButton);
|
|
137
|
+
|
|
138
|
+
await waitFor(() => {
|
|
139
|
+
expect(
|
|
140
|
+
screen.getByText(/Failed to remove git configuration/),
|
|
141
|
+
).toBeInTheDocument();
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
// Modal should stay open on error
|
|
145
|
+
expect(mockOnClose).not.toHaveBeenCalled();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('should show success message and close modal after successful removal', async () => {
|
|
149
|
+
jest.useFakeTimers();
|
|
150
|
+
|
|
151
|
+
const currentConfig = {
|
|
152
|
+
github_repo_path: 'test/repo',
|
|
153
|
+
git_branch: 'main',
|
|
154
|
+
git_path: 'nodes/',
|
|
155
|
+
git_only: false,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
mockOnRemove.mockResolvedValue({ success: true });
|
|
159
|
+
|
|
160
|
+
render(
|
|
161
|
+
<GitSettingsModal
|
|
162
|
+
isOpen={true}
|
|
163
|
+
onClose={mockOnClose}
|
|
164
|
+
onSave={mockOnSave}
|
|
165
|
+
onRemove={mockOnRemove}
|
|
166
|
+
currentConfig={currentConfig}
|
|
167
|
+
namespace="test.namespace"
|
|
168
|
+
/>,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const removeButton = screen.getByText('Reset');
|
|
172
|
+
fireEvent.click(removeButton);
|
|
173
|
+
|
|
174
|
+
await waitFor(() => {
|
|
175
|
+
expect(mockOnRemove).toHaveBeenCalled();
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Success message should appear
|
|
179
|
+
await waitFor(() => {
|
|
180
|
+
expect(
|
|
181
|
+
screen.getByText(/Git configuration removed successfully/),
|
|
182
|
+
).toBeInTheDocument();
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Fast-forward time to trigger modal close
|
|
186
|
+
jest.advanceTimersByTime(1500);
|
|
187
|
+
|
|
188
|
+
await waitFor(() => {
|
|
189
|
+
expect(mockOnClose).toHaveBeenCalled();
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
jest.useRealTimers();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
it('should handle exception during remove', async () => {
|
|
196
|
+
const currentConfig = {
|
|
197
|
+
github_repo_path: 'test/repo',
|
|
198
|
+
git_branch: 'main',
|
|
199
|
+
git_path: 'nodes/',
|
|
200
|
+
git_only: false,
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
mockOnRemove.mockRejectedValue(new Error('Network error'));
|
|
204
|
+
|
|
205
|
+
render(
|
|
206
|
+
<GitSettingsModal
|
|
207
|
+
isOpen={true}
|
|
208
|
+
onClose={mockOnClose}
|
|
209
|
+
onSave={mockOnSave}
|
|
210
|
+
onRemove={mockOnRemove}
|
|
211
|
+
currentConfig={currentConfig}
|
|
212
|
+
namespace="test.namespace"
|
|
213
|
+
/>,
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const removeButton = screen.getByText('Reset');
|
|
217
|
+
fireEvent.click(removeButton);
|
|
218
|
+
|
|
219
|
+
await waitFor(() => {
|
|
220
|
+
expect(screen.getByText(/Network error/)).toBeInTheDocument();
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
it('should show removing state while operation is in progress', async () => {
|
|
225
|
+
const currentConfig = {
|
|
226
|
+
github_repo_path: 'test/repo',
|
|
227
|
+
git_branch: 'main',
|
|
228
|
+
git_path: 'nodes/',
|
|
229
|
+
git_only: false,
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
// Create a promise that we can control
|
|
233
|
+
let resolveRemove;
|
|
234
|
+
const removePromise = new Promise(resolve => {
|
|
235
|
+
resolveRemove = resolve;
|
|
236
|
+
});
|
|
237
|
+
mockOnRemove.mockReturnValue(removePromise);
|
|
238
|
+
|
|
239
|
+
render(
|
|
240
|
+
<GitSettingsModal
|
|
241
|
+
isOpen={true}
|
|
242
|
+
onClose={mockOnClose}
|
|
243
|
+
onSave={mockOnSave}
|
|
244
|
+
onRemove={mockOnRemove}
|
|
245
|
+
currentConfig={currentConfig}
|
|
246
|
+
namespace="test.namespace"
|
|
247
|
+
/>,
|
|
248
|
+
);
|
|
249
|
+
|
|
250
|
+
const removeButton = screen.getByText('Reset');
|
|
251
|
+
fireEvent.click(removeButton);
|
|
252
|
+
|
|
253
|
+
// Button text should change to "Removing..." and be disabled
|
|
254
|
+
await waitFor(() => {
|
|
255
|
+
expect(screen.getByText('Removing...')).toBeInTheDocument();
|
|
256
|
+
expect(screen.getByText('Removing...')).toBeDisabled();
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Resolve the promise
|
|
260
|
+
resolveRemove({ success: true });
|
|
261
|
+
|
|
262
|
+
// Button should disappear after success (replaced by success message)
|
|
263
|
+
await waitFor(() => {
|
|
264
|
+
expect(screen.queryByText('Reset')).not.toBeInTheDocument();
|
|
265
|
+
expect(screen.queryByText('Removing...')).not.toBeInTheDocument();
|
|
266
|
+
});
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
it('should call onSave when Save Settings is clicked', async () => {
|
|
270
|
+
mockOnSave.mockResolvedValue({ success: true });
|
|
271
|
+
|
|
272
|
+
render(
|
|
273
|
+
<GitSettingsModal
|
|
274
|
+
isOpen={true}
|
|
275
|
+
onClose={mockOnClose}
|
|
276
|
+
onSave={mockOnSave}
|
|
277
|
+
onRemove={mockOnRemove}
|
|
278
|
+
currentConfig={null}
|
|
279
|
+
namespace="test.namespace"
|
|
280
|
+
/>,
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
fireEvent.change(screen.getByLabelText('Repository'), {
|
|
284
|
+
target: { value: 'myorg/repo' },
|
|
285
|
+
});
|
|
286
|
+
fireEvent.change(screen.getByLabelText('Branch'), {
|
|
287
|
+
target: { value: 'main' },
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
fireEvent.click(screen.getByText('Save Settings'));
|
|
291
|
+
|
|
292
|
+
await waitFor(() => {
|
|
293
|
+
expect(mockOnSave).toHaveBeenCalledWith(
|
|
294
|
+
expect.objectContaining({
|
|
295
|
+
github_repo_path: 'myorg/repo',
|
|
296
|
+
git_branch: 'main',
|
|
297
|
+
}),
|
|
298
|
+
);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
});
|
|
@@ -2275,6 +2275,26 @@ export const DataJunctionAPI = {
|
|
|
2275
2275
|
return result;
|
|
2276
2276
|
},
|
|
2277
2277
|
|
|
2278
|
+
// Delete git configuration for a namespace
|
|
2279
|
+
deleteNamespaceGitConfig: async function (namespace) {
|
|
2280
|
+
const response = await fetch(`${DJ_URL}/namespaces/${namespace}/git`, {
|
|
2281
|
+
method: 'DELETE',
|
|
2282
|
+
credentials: 'include',
|
|
2283
|
+
});
|
|
2284
|
+
// DELETE returns 204 No Content on success, so no JSON body
|
|
2285
|
+
if (!response.ok) {
|
|
2286
|
+
const result = await response.json().catch(() => ({}));
|
|
2287
|
+
return {
|
|
2288
|
+
...result,
|
|
2289
|
+
_error: true,
|
|
2290
|
+
_status: response.status,
|
|
2291
|
+
message: result.message || 'Failed to delete git config',
|
|
2292
|
+
};
|
|
2293
|
+
}
|
|
2294
|
+
// Success - return empty object since 204 has no body
|
|
2295
|
+
return {};
|
|
2296
|
+
},
|
|
2297
|
+
|
|
2278
2298
|
// List branch namespaces for a parent namespace
|
|
2279
2299
|
listBranches: async function (namespace) {
|
|
2280
2300
|
const response = await fetch(`${DJ_URL}/namespaces/${namespace}/branches`, {
|