datajunction-ui 0.0.55 → 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.
- package/package.json +1 -1
- package/src/app/components/NamespaceHeader.jsx +431 -11
- package/src/app/components/NodeListActions.jsx +10 -7
- package/src/app/components/__tests__/GitModals.test.jsx +1293 -0
- package/src/app/components/__tests__/NamespaceHeader.test.jsx +764 -0
- package/src/app/components/__tests__/NodeListActions.test.jsx +5 -3
- package/src/app/components/__tests__/__snapshots__/NamespaceHeader.test.jsx.snap +95 -0
- package/src/app/components/git/CreateBranchModal.jsx +229 -0
- package/src/app/components/git/CreatePRModal.jsx +270 -0
- package/src/app/components/git/DeleteBranchModal.jsx +173 -0
- package/src/app/components/git/GitSettingsModal.jsx +292 -0
- package/src/app/components/git/SyncToGitModal.jsx +219 -0
- package/src/app/components/git/index.js +5 -0
- package/src/app/icons/DeleteIcon.jsx +3 -3
- package/src/app/icons/EditIcon.jsx +3 -3
- package/src/app/icons/EyeIcon.jsx +3 -4
- package/src/app/icons/JupyterExportIcon.jsx +3 -7
- package/src/app/icons/PythonIcon.jsx +3 -3
- package/src/app/pages/AddEditNodePage/index.jsx +8 -5
- package/src/app/pages/NamespacePage/index.jsx +34 -21
- package/src/app/pages/NodePage/ClientCodePopover.jsx +3 -7
- package/src/app/pages/NodePage/NodeInfoTab.jsx +10 -3
- package/src/app/pages/NodePage/NotebookDownload.jsx +4 -10
- package/src/app/pages/NodePage/WatchNodeButton.jsx +7 -12
- package/src/app/pages/NodePage/index.jsx +42 -13
- package/src/app/services/DJService.js +198 -1
- package/src/styles/index.css +3 -0
- package/src/styles/node-creation.scss +22 -0
- package/src/styles/settings.css +1 -1
|
@@ -508,4 +508,768 @@ describe('<NamespaceHeader />', () => {
|
|
|
508
508
|
expect(screen.getByText(/Local\/adhoc deployments/)).toBeInTheDocument();
|
|
509
509
|
});
|
|
510
510
|
});
|
|
511
|
+
|
|
512
|
+
it('should show Configure Git button and open modal', async () => {
|
|
513
|
+
const mockDjClient = {
|
|
514
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
515
|
+
total_deployments: 0,
|
|
516
|
+
primary_source: null,
|
|
517
|
+
}),
|
|
518
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
519
|
+
getNamespaceGitConfig: jest.fn().mockResolvedValue(null),
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
render(
|
|
523
|
+
<MemoryRouter>
|
|
524
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
525
|
+
<NamespaceHeader namespace="test.namespace" />
|
|
526
|
+
</DJClientContext.Provider>
|
|
527
|
+
</MemoryRouter>,
|
|
528
|
+
);
|
|
529
|
+
|
|
530
|
+
await waitFor(() => {
|
|
531
|
+
expect(screen.getByText('Configure Git')).toBeInTheDocument();
|
|
532
|
+
});
|
|
533
|
+
|
|
534
|
+
fireEvent.click(screen.getByText('Configure Git'));
|
|
535
|
+
|
|
536
|
+
await waitFor(() => {
|
|
537
|
+
expect(screen.getByText('Git Configuration')).toBeInTheDocument();
|
|
538
|
+
});
|
|
539
|
+
});
|
|
540
|
+
|
|
541
|
+
it('should show git action buttons when git is configured', async () => {
|
|
542
|
+
const mockDjClient = {
|
|
543
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
544
|
+
total_deployments: 1,
|
|
545
|
+
primary_source: {
|
|
546
|
+
type: 'git',
|
|
547
|
+
repository: 'test/repo',
|
|
548
|
+
branch: 'main',
|
|
549
|
+
},
|
|
550
|
+
}),
|
|
551
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
552
|
+
getNamespaceGitConfig: jest.fn().mockResolvedValue({
|
|
553
|
+
github_repo_path: 'test/repo',
|
|
554
|
+
git_branch: 'main',
|
|
555
|
+
git_path: 'nodes/',
|
|
556
|
+
git_only: false,
|
|
557
|
+
}),
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
render(
|
|
561
|
+
<MemoryRouter>
|
|
562
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
563
|
+
<NamespaceHeader namespace="test.namespace" />
|
|
564
|
+
</DJClientContext.Provider>
|
|
565
|
+
</MemoryRouter>,
|
|
566
|
+
);
|
|
567
|
+
|
|
568
|
+
// For non-branch namespaces, button is labeled "New Branch"
|
|
569
|
+
await waitFor(() => {
|
|
570
|
+
expect(screen.getByText('New Branch')).toBeInTheDocument();
|
|
571
|
+
});
|
|
572
|
+
});
|
|
573
|
+
|
|
574
|
+
it('should show Create PR and Delete Branch for branch namespaces', async () => {
|
|
575
|
+
const mockDjClient = {
|
|
576
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
577
|
+
total_deployments: 1,
|
|
578
|
+
primary_source: {
|
|
579
|
+
type: 'git',
|
|
580
|
+
repository: 'test/repo',
|
|
581
|
+
branch: 'feature',
|
|
582
|
+
},
|
|
583
|
+
}),
|
|
584
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
585
|
+
getNamespaceGitConfig: jest
|
|
586
|
+
.fn()
|
|
587
|
+
.mockResolvedValueOnce({
|
|
588
|
+
github_repo_path: 'test/repo',
|
|
589
|
+
git_branch: 'feature',
|
|
590
|
+
git_path: 'nodes/',
|
|
591
|
+
git_only: false,
|
|
592
|
+
parent_namespace: 'test.main',
|
|
593
|
+
})
|
|
594
|
+
.mockResolvedValueOnce({
|
|
595
|
+
github_repo_path: 'test/repo',
|
|
596
|
+
git_branch: 'main',
|
|
597
|
+
git_path: 'nodes/',
|
|
598
|
+
}),
|
|
599
|
+
getPullRequest: jest.fn().mockResolvedValue(null),
|
|
600
|
+
};
|
|
601
|
+
|
|
602
|
+
render(
|
|
603
|
+
<MemoryRouter>
|
|
604
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
605
|
+
<NamespaceHeader namespace="test.feature" />
|
|
606
|
+
</DJClientContext.Provider>
|
|
607
|
+
</MemoryRouter>,
|
|
608
|
+
);
|
|
609
|
+
|
|
610
|
+
await waitFor(() => {
|
|
611
|
+
expect(screen.getByText('Create PR')).toBeInTheDocument();
|
|
612
|
+
});
|
|
613
|
+
// Delete Branch button only has an icon with title attribute
|
|
614
|
+
expect(screen.getByTitle('Delete Branch')).toBeInTheDocument();
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
it('should open Create Branch modal when button is clicked', async () => {
|
|
618
|
+
const mockDjClient = {
|
|
619
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
620
|
+
total_deployments: 1,
|
|
621
|
+
primary_source: {
|
|
622
|
+
type: 'git',
|
|
623
|
+
repository: 'test/repo',
|
|
624
|
+
branch: 'main',
|
|
625
|
+
},
|
|
626
|
+
}),
|
|
627
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
628
|
+
getNamespaceGitConfig: jest.fn().mockResolvedValue({
|
|
629
|
+
github_repo_path: 'test/repo',
|
|
630
|
+
git_branch: 'main',
|
|
631
|
+
git_path: 'nodes/',
|
|
632
|
+
git_only: false,
|
|
633
|
+
}),
|
|
634
|
+
};
|
|
635
|
+
|
|
636
|
+
render(
|
|
637
|
+
<MemoryRouter>
|
|
638
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
639
|
+
<NamespaceHeader namespace="test.namespace" />
|
|
640
|
+
</DJClientContext.Provider>
|
|
641
|
+
</MemoryRouter>,
|
|
642
|
+
);
|
|
643
|
+
|
|
644
|
+
await waitFor(() => {
|
|
645
|
+
expect(screen.getByText('New Branch')).toBeInTheDocument();
|
|
646
|
+
});
|
|
647
|
+
|
|
648
|
+
fireEvent.click(screen.getByText('New Branch'));
|
|
649
|
+
|
|
650
|
+
await waitFor(() => {
|
|
651
|
+
expect(screen.getByLabelText('Branch Name')).toBeInTheDocument();
|
|
652
|
+
});
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
it('should open Sync to Git modal when button is clicked', async () => {
|
|
656
|
+
// Sync to Git only shows for branch namespaces
|
|
657
|
+
const mockDjClient = {
|
|
658
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
659
|
+
total_deployments: 1,
|
|
660
|
+
primary_source: {
|
|
661
|
+
type: 'git',
|
|
662
|
+
repository: 'test/repo',
|
|
663
|
+
branch: 'feature',
|
|
664
|
+
},
|
|
665
|
+
}),
|
|
666
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
667
|
+
getNamespaceGitConfig: jest
|
|
668
|
+
.fn()
|
|
669
|
+
.mockResolvedValueOnce({
|
|
670
|
+
github_repo_path: 'test/repo',
|
|
671
|
+
git_branch: 'feature',
|
|
672
|
+
git_path: 'nodes/',
|
|
673
|
+
git_only: false,
|
|
674
|
+
parent_namespace: 'test.main',
|
|
675
|
+
})
|
|
676
|
+
.mockResolvedValueOnce({
|
|
677
|
+
github_repo_path: 'test/repo',
|
|
678
|
+
git_branch: 'main',
|
|
679
|
+
git_path: 'nodes/',
|
|
680
|
+
}),
|
|
681
|
+
getPullRequest: jest.fn().mockResolvedValue(null),
|
|
682
|
+
};
|
|
683
|
+
|
|
684
|
+
render(
|
|
685
|
+
<MemoryRouter>
|
|
686
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
687
|
+
<NamespaceHeader namespace="test.feature" />
|
|
688
|
+
</DJClientContext.Provider>
|
|
689
|
+
</MemoryRouter>,
|
|
690
|
+
);
|
|
691
|
+
|
|
692
|
+
await waitFor(() => {
|
|
693
|
+
expect(screen.getByText('Sync to Git')).toBeInTheDocument();
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
fireEvent.click(screen.getByText('Sync to Git'));
|
|
697
|
+
|
|
698
|
+
await waitFor(() => {
|
|
699
|
+
expect(screen.getByText(/Sync all nodes in/)).toBeInTheDocument();
|
|
700
|
+
});
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
it('should call updateNamespaceGitConfig when saving git settings', async () => {
|
|
704
|
+
const mockDjClient = {
|
|
705
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
706
|
+
total_deployments: 0,
|
|
707
|
+
primary_source: null,
|
|
708
|
+
}),
|
|
709
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
710
|
+
getNamespaceGitConfig: jest.fn().mockResolvedValue(null),
|
|
711
|
+
updateNamespaceGitConfig: jest.fn().mockResolvedValue({
|
|
712
|
+
github_repo_path: 'myorg/repo',
|
|
713
|
+
git_branch: 'main',
|
|
714
|
+
git_path: 'nodes/',
|
|
715
|
+
git_only: true,
|
|
716
|
+
}),
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
render(
|
|
720
|
+
<MemoryRouter>
|
|
721
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
722
|
+
<NamespaceHeader namespace="test.namespace" />
|
|
723
|
+
</DJClientContext.Provider>
|
|
724
|
+
</MemoryRouter>,
|
|
725
|
+
);
|
|
726
|
+
|
|
727
|
+
await waitFor(() => {
|
|
728
|
+
expect(screen.getByText('Configure Git')).toBeInTheDocument();
|
|
729
|
+
});
|
|
730
|
+
|
|
731
|
+
fireEvent.click(screen.getByText('Configure Git'));
|
|
732
|
+
|
|
733
|
+
await waitFor(() => {
|
|
734
|
+
expect(screen.getByLabelText('Repository')).toBeInTheDocument();
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
fireEvent.change(screen.getByLabelText('Repository'), {
|
|
738
|
+
target: { value: 'myorg/repo' },
|
|
739
|
+
});
|
|
740
|
+
fireEvent.change(screen.getByLabelText('Branch'), {
|
|
741
|
+
target: { value: 'main' },
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
fireEvent.click(screen.getByText('Save Settings'));
|
|
745
|
+
|
|
746
|
+
await waitFor(() => {
|
|
747
|
+
expect(mockDjClient.updateNamespaceGitConfig).toHaveBeenCalledWith(
|
|
748
|
+
'test.namespace',
|
|
749
|
+
expect.objectContaining({
|
|
750
|
+
github_repo_path: 'myorg/repo',
|
|
751
|
+
git_branch: 'main',
|
|
752
|
+
}),
|
|
753
|
+
);
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
it('should call createBranch when creating a branch', async () => {
|
|
758
|
+
const mockDjClient = {
|
|
759
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
760
|
+
total_deployments: 1,
|
|
761
|
+
primary_source: {
|
|
762
|
+
type: 'git',
|
|
763
|
+
repository: 'test/repo',
|
|
764
|
+
branch: 'main',
|
|
765
|
+
},
|
|
766
|
+
}),
|
|
767
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
768
|
+
getNamespaceGitConfig: jest.fn().mockResolvedValue({
|
|
769
|
+
github_repo_path: 'test/repo',
|
|
770
|
+
git_branch: 'main',
|
|
771
|
+
git_path: 'nodes/',
|
|
772
|
+
git_only: false,
|
|
773
|
+
}),
|
|
774
|
+
createBranch: jest.fn().mockResolvedValue({
|
|
775
|
+
branch: {
|
|
776
|
+
namespace: 'test.feature_xyz',
|
|
777
|
+
git_branch: 'feature-xyz',
|
|
778
|
+
parent_namespace: 'test.namespace',
|
|
779
|
+
},
|
|
780
|
+
deployment_results: [],
|
|
781
|
+
}),
|
|
782
|
+
};
|
|
783
|
+
|
|
784
|
+
render(
|
|
785
|
+
<MemoryRouter>
|
|
786
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
787
|
+
<NamespaceHeader namespace="test.namespace" />
|
|
788
|
+
</DJClientContext.Provider>
|
|
789
|
+
</MemoryRouter>,
|
|
790
|
+
);
|
|
791
|
+
|
|
792
|
+
await waitFor(() => {
|
|
793
|
+
expect(screen.getByText('New Branch')).toBeInTheDocument();
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
fireEvent.click(screen.getByText('New Branch'));
|
|
797
|
+
|
|
798
|
+
await waitFor(() => {
|
|
799
|
+
expect(screen.getByLabelText('Branch Name')).toBeInTheDocument();
|
|
800
|
+
});
|
|
801
|
+
|
|
802
|
+
fireEvent.change(screen.getByLabelText('Branch Name'), {
|
|
803
|
+
target: { value: 'feature-xyz' },
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
// The button inside the modal is labeled "Create Branch"
|
|
807
|
+
fireEvent.click(screen.getByRole('button', { name: 'Create Branch' }));
|
|
808
|
+
|
|
809
|
+
await waitFor(() => {
|
|
810
|
+
expect(mockDjClient.createBranch).toHaveBeenCalledWith(
|
|
811
|
+
'test.namespace',
|
|
812
|
+
'feature-xyz',
|
|
813
|
+
);
|
|
814
|
+
});
|
|
815
|
+
});
|
|
816
|
+
|
|
817
|
+
it('should call syncNamespaceToGit when syncing', async () => {
|
|
818
|
+
// Sync to Git only shows for branch namespaces
|
|
819
|
+
const mockDjClient = {
|
|
820
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
821
|
+
total_deployments: 1,
|
|
822
|
+
primary_source: {
|
|
823
|
+
type: 'git',
|
|
824
|
+
repository: 'test/repo',
|
|
825
|
+
branch: 'feature',
|
|
826
|
+
},
|
|
827
|
+
}),
|
|
828
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
829
|
+
getNamespaceGitConfig: jest
|
|
830
|
+
.fn()
|
|
831
|
+
.mockResolvedValueOnce({
|
|
832
|
+
github_repo_path: 'test/repo',
|
|
833
|
+
git_branch: 'feature',
|
|
834
|
+
git_path: 'nodes/',
|
|
835
|
+
git_only: false,
|
|
836
|
+
parent_namespace: 'test.main',
|
|
837
|
+
})
|
|
838
|
+
.mockResolvedValueOnce({
|
|
839
|
+
github_repo_path: 'test/repo',
|
|
840
|
+
git_branch: 'main',
|
|
841
|
+
git_path: 'nodes/',
|
|
842
|
+
}),
|
|
843
|
+
getPullRequest: jest.fn().mockResolvedValue(null),
|
|
844
|
+
syncNamespaceToGit: jest.fn().mockResolvedValue({
|
|
845
|
+
files_synced: 5,
|
|
846
|
+
commit_sha: 'abc123',
|
|
847
|
+
commit_url: 'https://github.com/test/repo/commit/abc123',
|
|
848
|
+
}),
|
|
849
|
+
};
|
|
850
|
+
|
|
851
|
+
render(
|
|
852
|
+
<MemoryRouter>
|
|
853
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
854
|
+
<NamespaceHeader namespace="test.feature" />
|
|
855
|
+
</DJClientContext.Provider>
|
|
856
|
+
</MemoryRouter>,
|
|
857
|
+
);
|
|
858
|
+
|
|
859
|
+
await waitFor(() => {
|
|
860
|
+
expect(screen.getByText('Sync to Git')).toBeInTheDocument();
|
|
861
|
+
});
|
|
862
|
+
|
|
863
|
+
fireEvent.click(screen.getByText('Sync to Git'));
|
|
864
|
+
|
|
865
|
+
await waitFor(() => {
|
|
866
|
+
expect(screen.getByLabelText(/Commit Message/)).toBeInTheDocument();
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
fireEvent.change(screen.getByLabelText(/Commit Message/), {
|
|
870
|
+
target: { value: 'Test commit' },
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
fireEvent.click(screen.getByRole('button', { name: 'Sync Now' }));
|
|
874
|
+
|
|
875
|
+
await waitFor(() => {
|
|
876
|
+
expect(mockDjClient.syncNamespaceToGit).toHaveBeenCalledWith(
|
|
877
|
+
'test.feature',
|
|
878
|
+
'Test commit',
|
|
879
|
+
);
|
|
880
|
+
});
|
|
881
|
+
});
|
|
882
|
+
|
|
883
|
+
it('should show View PR button when PR exists', async () => {
|
|
884
|
+
const mockDjClient = {
|
|
885
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
886
|
+
total_deployments: 1,
|
|
887
|
+
primary_source: {
|
|
888
|
+
type: 'git',
|
|
889
|
+
repository: 'test/repo',
|
|
890
|
+
branch: 'feature',
|
|
891
|
+
},
|
|
892
|
+
}),
|
|
893
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
894
|
+
getNamespaceGitConfig: jest.fn().mockResolvedValue({
|
|
895
|
+
github_repo_path: 'test/repo',
|
|
896
|
+
git_branch: 'feature',
|
|
897
|
+
git_path: 'nodes/',
|
|
898
|
+
git_only: false,
|
|
899
|
+
parent_namespace: 'test.main',
|
|
900
|
+
}),
|
|
901
|
+
getPullRequest: jest.fn().mockResolvedValue({
|
|
902
|
+
pr_number: 42,
|
|
903
|
+
pr_url: 'https://github.com/test/repo/pull/42',
|
|
904
|
+
}),
|
|
905
|
+
};
|
|
906
|
+
|
|
907
|
+
render(
|
|
908
|
+
<MemoryRouter>
|
|
909
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
910
|
+
<NamespaceHeader namespace="test.feature" />
|
|
911
|
+
</DJClientContext.Provider>
|
|
912
|
+
</MemoryRouter>,
|
|
913
|
+
);
|
|
914
|
+
|
|
915
|
+
await waitFor(() => {
|
|
916
|
+
expect(screen.getByText(/View PR #42/)).toBeInTheDocument();
|
|
917
|
+
});
|
|
918
|
+
});
|
|
919
|
+
|
|
920
|
+
it('should call createPullRequest when creating a PR', async () => {
|
|
921
|
+
const mockDjClient = {
|
|
922
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
923
|
+
total_deployments: 1,
|
|
924
|
+
primary_source: {
|
|
925
|
+
type: 'git',
|
|
926
|
+
repository: 'test/repo',
|
|
927
|
+
branch: 'feature',
|
|
928
|
+
},
|
|
929
|
+
}),
|
|
930
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
931
|
+
getNamespaceGitConfig: jest
|
|
932
|
+
.fn()
|
|
933
|
+
.mockResolvedValueOnce({
|
|
934
|
+
github_repo_path: 'test/repo',
|
|
935
|
+
git_branch: 'feature',
|
|
936
|
+
git_path: 'nodes/',
|
|
937
|
+
git_only: false,
|
|
938
|
+
parent_namespace: 'test.main',
|
|
939
|
+
})
|
|
940
|
+
.mockResolvedValueOnce({
|
|
941
|
+
github_repo_path: 'test/repo',
|
|
942
|
+
git_branch: 'main',
|
|
943
|
+
git_path: 'nodes/',
|
|
944
|
+
}),
|
|
945
|
+
getPullRequest: jest.fn().mockResolvedValue(null),
|
|
946
|
+
syncNamespaceToGit: jest.fn().mockResolvedValue({
|
|
947
|
+
files_synced: 3,
|
|
948
|
+
commit_sha: 'abc123',
|
|
949
|
+
commit_url: 'https://github.com/test/repo/commit/abc123',
|
|
950
|
+
}),
|
|
951
|
+
createPullRequest: jest.fn().mockResolvedValue({
|
|
952
|
+
pr_number: 99,
|
|
953
|
+
pr_url: 'https://github.com/test/repo/pull/99',
|
|
954
|
+
head_branch: 'feature',
|
|
955
|
+
base_branch: 'main',
|
|
956
|
+
}),
|
|
957
|
+
};
|
|
958
|
+
|
|
959
|
+
render(
|
|
960
|
+
<MemoryRouter>
|
|
961
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
962
|
+
<NamespaceHeader namespace="test.feature" />
|
|
963
|
+
</DJClientContext.Provider>
|
|
964
|
+
</MemoryRouter>,
|
|
965
|
+
);
|
|
966
|
+
|
|
967
|
+
await waitFor(() => {
|
|
968
|
+
expect(screen.getByText('Create PR')).toBeInTheDocument();
|
|
969
|
+
});
|
|
970
|
+
|
|
971
|
+
fireEvent.click(screen.getByText('Create PR'));
|
|
972
|
+
|
|
973
|
+
await waitFor(() => {
|
|
974
|
+
expect(screen.getByLabelText(/Title/)).toBeInTheDocument();
|
|
975
|
+
});
|
|
976
|
+
|
|
977
|
+
fireEvent.change(screen.getByLabelText(/Title/), {
|
|
978
|
+
target: { value: 'My PR Title' },
|
|
979
|
+
});
|
|
980
|
+
fireEvent.change(screen.getByLabelText(/Description/), {
|
|
981
|
+
target: { value: 'PR description' },
|
|
982
|
+
});
|
|
983
|
+
|
|
984
|
+
// There are two "Create PR" buttons - one in header, one in modal
|
|
985
|
+
// Get all and click the last one (modal's submit button)
|
|
986
|
+
const createPRButtons = screen.getAllByRole('button', {
|
|
987
|
+
name: 'Create PR',
|
|
988
|
+
});
|
|
989
|
+
fireEvent.click(createPRButtons[createPRButtons.length - 1]);
|
|
990
|
+
|
|
991
|
+
await waitFor(() => {
|
|
992
|
+
expect(mockDjClient.syncNamespaceToGit).toHaveBeenCalledWith(
|
|
993
|
+
'test.feature',
|
|
994
|
+
'My PR Title',
|
|
995
|
+
);
|
|
996
|
+
});
|
|
997
|
+
|
|
998
|
+
await waitFor(() => {
|
|
999
|
+
expect(mockDjClient.createPullRequest).toHaveBeenCalledWith(
|
|
1000
|
+
'test.feature',
|
|
1001
|
+
'My PR Title',
|
|
1002
|
+
'PR description',
|
|
1003
|
+
);
|
|
1004
|
+
});
|
|
1005
|
+
});
|
|
1006
|
+
|
|
1007
|
+
it('should call deleteBranch when deleting a branch', async () => {
|
|
1008
|
+
const mockDjClient = {
|
|
1009
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
1010
|
+
total_deployments: 1,
|
|
1011
|
+
primary_source: {
|
|
1012
|
+
type: 'git',
|
|
1013
|
+
repository: 'test/repo',
|
|
1014
|
+
branch: 'feature',
|
|
1015
|
+
},
|
|
1016
|
+
}),
|
|
1017
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
1018
|
+
getNamespaceGitConfig: jest
|
|
1019
|
+
.fn()
|
|
1020
|
+
.mockResolvedValueOnce({
|
|
1021
|
+
github_repo_path: 'test/repo',
|
|
1022
|
+
git_branch: 'feature',
|
|
1023
|
+
git_path: 'nodes/',
|
|
1024
|
+
git_only: false,
|
|
1025
|
+
parent_namespace: 'test.main',
|
|
1026
|
+
})
|
|
1027
|
+
.mockResolvedValueOnce({
|
|
1028
|
+
github_repo_path: 'test/repo',
|
|
1029
|
+
git_branch: 'main',
|
|
1030
|
+
git_path: 'nodes/',
|
|
1031
|
+
}),
|
|
1032
|
+
getPullRequest: jest.fn().mockResolvedValue(null),
|
|
1033
|
+
deleteBranch: jest.fn().mockResolvedValue({ success: true }),
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
// Mock window.location
|
|
1037
|
+
delete window.location;
|
|
1038
|
+
window.location = { href: '' };
|
|
1039
|
+
|
|
1040
|
+
render(
|
|
1041
|
+
<MemoryRouter>
|
|
1042
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
1043
|
+
<NamespaceHeader namespace="test.feature" />
|
|
1044
|
+
</DJClientContext.Provider>
|
|
1045
|
+
</MemoryRouter>,
|
|
1046
|
+
);
|
|
1047
|
+
|
|
1048
|
+
await waitFor(() => {
|
|
1049
|
+
// Delete Branch button in header only has icon with title attribute
|
|
1050
|
+
expect(screen.getByTitle('Delete Branch')).toBeInTheDocument();
|
|
1051
|
+
});
|
|
1052
|
+
|
|
1053
|
+
fireEvent.click(screen.getByTitle('Delete Branch'));
|
|
1054
|
+
|
|
1055
|
+
await waitFor(() => {
|
|
1056
|
+
expect(screen.getByRole('checkbox')).toBeInTheDocument();
|
|
1057
|
+
});
|
|
1058
|
+
|
|
1059
|
+
// There are two buttons with "Delete Branch" - header icon and modal button
|
|
1060
|
+
// Get all and click the last one (modal's submit button)
|
|
1061
|
+
const deleteBranchButtons = screen.getAllByRole('button', {
|
|
1062
|
+
name: 'Delete Branch',
|
|
1063
|
+
});
|
|
1064
|
+
fireEvent.click(deleteBranchButtons[deleteBranchButtons.length - 1]);
|
|
1065
|
+
|
|
1066
|
+
await waitFor(() => {
|
|
1067
|
+
expect(mockDjClient.deleteBranch).toHaveBeenCalledWith(
|
|
1068
|
+
'test.main',
|
|
1069
|
+
'test.feature',
|
|
1070
|
+
true,
|
|
1071
|
+
);
|
|
1072
|
+
});
|
|
1073
|
+
});
|
|
1074
|
+
|
|
1075
|
+
it('should fetch parent git config for branch namespace', async () => {
|
|
1076
|
+
const mockDjClient = {
|
|
1077
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
1078
|
+
total_deployments: 1,
|
|
1079
|
+
primary_source: {
|
|
1080
|
+
type: 'git',
|
|
1081
|
+
repository: 'test/repo',
|
|
1082
|
+
branch: 'feature',
|
|
1083
|
+
},
|
|
1084
|
+
}),
|
|
1085
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
1086
|
+
getNamespaceGitConfig: jest
|
|
1087
|
+
.fn()
|
|
1088
|
+
.mockResolvedValueOnce({
|
|
1089
|
+
github_repo_path: 'test/repo',
|
|
1090
|
+
git_branch: 'feature',
|
|
1091
|
+
git_path: 'nodes/',
|
|
1092
|
+
git_only: false,
|
|
1093
|
+
parent_namespace: 'test.main',
|
|
1094
|
+
})
|
|
1095
|
+
.mockResolvedValueOnce({
|
|
1096
|
+
github_repo_path: 'test/repo',
|
|
1097
|
+
git_branch: 'main',
|
|
1098
|
+
git_path: 'nodes/',
|
|
1099
|
+
}),
|
|
1100
|
+
getPullRequest: jest.fn().mockResolvedValue(null),
|
|
1101
|
+
};
|
|
1102
|
+
|
|
1103
|
+
render(
|
|
1104
|
+
<MemoryRouter>
|
|
1105
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
1106
|
+
<NamespaceHeader namespace="test.feature" />
|
|
1107
|
+
</DJClientContext.Provider>
|
|
1108
|
+
</MemoryRouter>,
|
|
1109
|
+
);
|
|
1110
|
+
|
|
1111
|
+
await waitFor(() => {
|
|
1112
|
+
expect(mockDjClient.getNamespaceGitConfig).toHaveBeenCalledWith(
|
|
1113
|
+
'test.feature',
|
|
1114
|
+
);
|
|
1115
|
+
});
|
|
1116
|
+
|
|
1117
|
+
await waitFor(() => {
|
|
1118
|
+
expect(mockDjClient.getNamespaceGitConfig).toHaveBeenCalledWith(
|
|
1119
|
+
'test.main',
|
|
1120
|
+
);
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
await waitFor(() => {
|
|
1124
|
+
expect(mockDjClient.getPullRequest).toHaveBeenCalledWith('test.feature');
|
|
1125
|
+
});
|
|
1126
|
+
});
|
|
1127
|
+
|
|
1128
|
+
it('should handle error fetching parent git config gracefully', async () => {
|
|
1129
|
+
const consoleSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
1130
|
+
|
|
1131
|
+
const mockDjClient = {
|
|
1132
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
1133
|
+
total_deployments: 1,
|
|
1134
|
+
primary_source: {
|
|
1135
|
+
type: 'git',
|
|
1136
|
+
repository: 'test/repo',
|
|
1137
|
+
branch: 'feature',
|
|
1138
|
+
},
|
|
1139
|
+
}),
|
|
1140
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
1141
|
+
getNamespaceGitConfig: jest
|
|
1142
|
+
.fn()
|
|
1143
|
+
.mockResolvedValueOnce({
|
|
1144
|
+
github_repo_path: 'test/repo',
|
|
1145
|
+
git_branch: 'feature',
|
|
1146
|
+
git_path: 'nodes/',
|
|
1147
|
+
git_only: false,
|
|
1148
|
+
parent_namespace: 'test.main',
|
|
1149
|
+
})
|
|
1150
|
+
.mockRejectedValueOnce(new Error('Parent not found')),
|
|
1151
|
+
getPullRequest: jest.fn().mockResolvedValue(null),
|
|
1152
|
+
};
|
|
1153
|
+
|
|
1154
|
+
render(
|
|
1155
|
+
<MemoryRouter>
|
|
1156
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
1157
|
+
<NamespaceHeader namespace="test.feature" />
|
|
1158
|
+
</DJClientContext.Provider>
|
|
1159
|
+
</MemoryRouter>,
|
|
1160
|
+
);
|
|
1161
|
+
|
|
1162
|
+
await waitFor(() => {
|
|
1163
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
1164
|
+
'Failed to fetch parent git config:',
|
|
1165
|
+
expect.any(Error),
|
|
1166
|
+
);
|
|
1167
|
+
});
|
|
1168
|
+
|
|
1169
|
+
consoleSpy.mockRestore();
|
|
1170
|
+
});
|
|
1171
|
+
|
|
1172
|
+
it('should handle error fetching PR gracefully', async () => {
|
|
1173
|
+
const mockDjClient = {
|
|
1174
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
1175
|
+
total_deployments: 1,
|
|
1176
|
+
primary_source: {
|
|
1177
|
+
type: 'git',
|
|
1178
|
+
repository: 'test/repo',
|
|
1179
|
+
branch: 'feature',
|
|
1180
|
+
},
|
|
1181
|
+
}),
|
|
1182
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
1183
|
+
getNamespaceGitConfig: jest
|
|
1184
|
+
.fn()
|
|
1185
|
+
.mockResolvedValueOnce({
|
|
1186
|
+
github_repo_path: 'test/repo',
|
|
1187
|
+
git_branch: 'feature',
|
|
1188
|
+
git_path: 'nodes/',
|
|
1189
|
+
git_only: false,
|
|
1190
|
+
parent_namespace: 'test.main',
|
|
1191
|
+
})
|
|
1192
|
+
.mockResolvedValueOnce({
|
|
1193
|
+
github_repo_path: 'test/repo',
|
|
1194
|
+
git_branch: 'main',
|
|
1195
|
+
git_path: 'nodes/',
|
|
1196
|
+
}),
|
|
1197
|
+
getPullRequest: jest.fn().mockRejectedValue(new Error('API Error')),
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
render(
|
|
1201
|
+
<MemoryRouter>
|
|
1202
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
1203
|
+
<NamespaceHeader namespace="test.feature" />
|
|
1204
|
+
</DJClientContext.Provider>
|
|
1205
|
+
</MemoryRouter>,
|
|
1206
|
+
);
|
|
1207
|
+
|
|
1208
|
+
// Should render without crashing and show Create PR button
|
|
1209
|
+
await waitFor(() => {
|
|
1210
|
+
expect(screen.getByText('Create PR')).toBeInTheDocument();
|
|
1211
|
+
});
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
it('should call onGitConfigLoaded callback when config is fetched', async () => {
|
|
1215
|
+
const onGitConfigLoaded = jest.fn();
|
|
1216
|
+
const mockDjClient = {
|
|
1217
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
1218
|
+
total_deployments: 0,
|
|
1219
|
+
primary_source: null,
|
|
1220
|
+
}),
|
|
1221
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
1222
|
+
getNamespaceGitConfig: jest.fn().mockResolvedValue({
|
|
1223
|
+
github_repo_path: 'test/repo',
|
|
1224
|
+
git_branch: 'main',
|
|
1225
|
+
}),
|
|
1226
|
+
};
|
|
1227
|
+
|
|
1228
|
+
render(
|
|
1229
|
+
<MemoryRouter>
|
|
1230
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
1231
|
+
<NamespaceHeader
|
|
1232
|
+
namespace="test.namespace"
|
|
1233
|
+
onGitConfigLoaded={onGitConfigLoaded}
|
|
1234
|
+
/>
|
|
1235
|
+
</DJClientContext.Provider>
|
|
1236
|
+
</MemoryRouter>,
|
|
1237
|
+
);
|
|
1238
|
+
|
|
1239
|
+
await waitFor(() => {
|
|
1240
|
+
expect(onGitConfigLoaded).toHaveBeenCalledWith({
|
|
1241
|
+
github_repo_path: 'test/repo',
|
|
1242
|
+
git_branch: 'main',
|
|
1243
|
+
});
|
|
1244
|
+
});
|
|
1245
|
+
});
|
|
1246
|
+
|
|
1247
|
+
it('should call onGitConfigLoaded with null when git config fetch fails', async () => {
|
|
1248
|
+
const onGitConfigLoaded = jest.fn();
|
|
1249
|
+
const mockDjClient = {
|
|
1250
|
+
namespaceSources: jest.fn().mockResolvedValue({
|
|
1251
|
+
total_deployments: 0,
|
|
1252
|
+
primary_source: null,
|
|
1253
|
+
}),
|
|
1254
|
+
listDeployments: jest.fn().mockResolvedValue([]),
|
|
1255
|
+
getNamespaceGitConfig: jest
|
|
1256
|
+
.fn()
|
|
1257
|
+
.mockRejectedValue(new Error('Config not found')),
|
|
1258
|
+
};
|
|
1259
|
+
|
|
1260
|
+
render(
|
|
1261
|
+
<MemoryRouter>
|
|
1262
|
+
<DJClientContext.Provider value={{ DataJunctionAPI: mockDjClient }}>
|
|
1263
|
+
<NamespaceHeader
|
|
1264
|
+
namespace="test.namespace"
|
|
1265
|
+
onGitConfigLoaded={onGitConfigLoaded}
|
|
1266
|
+
/>
|
|
1267
|
+
</DJClientContext.Provider>
|
|
1268
|
+
</MemoryRouter>,
|
|
1269
|
+
);
|
|
1270
|
+
|
|
1271
|
+
await waitFor(() => {
|
|
1272
|
+
expect(onGitConfigLoaded).toHaveBeenCalledWith(null);
|
|
1273
|
+
});
|
|
1274
|
+
});
|
|
511
1275
|
});
|