Skip to content

Commit 4596684

Browse files
creating git repo
1 parent 2422f8f commit 4596684

File tree

8 files changed

+907
-76
lines changed

8 files changed

+907
-76
lines changed

package-lock.json

Lines changed: 99 additions & 56 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/components/ControlPlane/GitRepositories.tsx

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,21 @@
11
import ConfiguredAnalyticstable from '../Shared/ConfiguredAnalyticsTable.tsx';
2-
import { AnalyticalTableColumnDefinition, Panel, Title, Toolbar, ToolbarSpacer } from '@ui5/webcomponents-react';
3-
import IllustratedError from '../Shared/IllustratedError.tsx';
42
import { useApiResource } from '../../lib/api/useApiResource';
53
import { FluxRequest } from '../../lib/api/types/flux/listGitRepo';
64
import { useTranslation } from 'react-i18next';
75
import { formatDateAsTimeAgo } from '../../utils/i18n/timeAgo.ts';
86

97
import { YamlViewButton } from '../Yaml/YamlViewButton.tsx';
10-
import { Fragment, useCallback, useMemo, useRef } from 'react';
11-
import StatusFilter from '../Shared/StatusFilter/StatusFilter.tsx';
8+
import { Fragment, useCallback, useMemo, useRef, useState } from 'react';
9+
import {
10+
AnalyticalTableColumnDefinition,
11+
Panel,
12+
Title,
13+
Toolbar,
14+
ToolbarSpacer,
15+
Button,
16+
} from '@ui5/webcomponents-react';
17+
import '@ui5/webcomponents-icons/dist/add';
18+
import IllustratedError from '../Shared/IllustratedError.tsx';
1219
import { ResourceStatusCell } from '../Shared/ResourceStatusCell.tsx';
1320
import { Resource } from '../../utils/removeManagedFieldsAndFilterData.ts';
1421
import { useSplitter } from '../Splitter/SplitterContext.tsx';
@@ -17,6 +24,8 @@ import { useHandleResourcePatch } from '../../hooks/useHandleResourcePatch.ts';
1724
import { ErrorDialog, ErrorDialogHandle } from '../Shared/ErrorMessageBox.tsx';
1825
import type { GitReposResponse } from '../../lib/api/types/flux/listGitRepo';
1926
import { ActionsMenu, type ActionItem } from './ActionsMenu';
27+
import StatusFilter from '../Shared/StatusFilter/StatusFilter.tsx';
28+
import { CreateGitRepositoryDialog } from '../Dialogs/CreateGitRepositoryDialog.tsx';
2029

2130
export type GitRepoItem = GitReposResponse['items'][0] & {
2231
apiVersion?: string;
@@ -29,6 +38,7 @@ export function GitRepositories() {
2938
const { openInAside } = useSplitter();
3039
const errorDialogRef = useRef<ErrorDialogHandle>(null);
3140
const handlePatch = useHandleResourcePatch(errorDialogRef);
41+
const [isCreateDialogOpen, setIsCreateDialogOpen] = useState(false);
3242

3343
type FluxRow = {
3444
name: string;
@@ -152,21 +162,31 @@ export function GitRepositories() {
152162
}) ?? [];
153163

154164
return (
155-
<Panel
156-
fixed
157-
header={
158-
<Toolbar>
159-
<Title>{t('common.resourcesCount', { count: rows.length })}</Title>
160-
<YamlViewButton variant="resource" resource={data as unknown as Resource} />
161-
<ToolbarSpacer />
162-
</Toolbar>
163-
}
164-
>
165-
<>
166-
<ConfiguredAnalyticstable columns={columns} isLoading={isLoading} data={rows} />
167-
<ErrorDialog ref={errorDialogRef} />
168-
</>
169-
</Panel>
165+
<>
166+
<Panel
167+
fixed
168+
header={
169+
<Toolbar>
170+
<Title>{t('common.resourcesCount', { count: rows.length })}</Title>
171+
<ToolbarSpacer />
172+
<Button icon="add" onClick={() => setIsCreateDialogOpen(true)}>
173+
{t('buttons.create', 'Create')}
174+
</Button>
175+
</Toolbar>
176+
}
177+
>
178+
<>
179+
<ConfiguredAnalyticstable columns={columns} isLoading={isLoading} data={rows} />
180+
<ErrorDialog ref={errorDialogRef} />
181+
</>
182+
</Panel>
183+
184+
<CreateGitRepositoryDialog
185+
isOpen={isCreateDialogOpen}
186+
namespace={rows[0]?.item?.metadata?.namespace || 'default'}
187+
onClose={() => setIsCreateDialogOpen(false)}
188+
/>
189+
</>
170190
);
171191
}
172192

@@ -177,7 +197,6 @@ function shortenCommitHash(commitHash: string): string {
177197
if (match && match[2]) {
178198
return `${match[1]}@${match[2].slice(0, 7)}`;
179199
}
180-
181200
//example output : master@b3396ad
182201
return commitHash;
183202
}
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
import { CreateGitRepositoryDialog } from './CreateGitRepositoryDialog';
2+
import { CreateGitRepositoryParams } from '../../hooks/useCreateGitRepository';
3+
4+
describe('CreateGitRepositoryDialog', () => {
5+
let capturedData: CreateGitRepositoryParams | null = null;
6+
7+
const fakeUseCreateGitRepository = () => ({
8+
createGitRepository: async (data: CreateGitRepositoryParams): Promise<void> => {
9+
capturedData = data;
10+
},
11+
isLoading: false,
12+
});
13+
14+
beforeEach(() => {
15+
capturedData = null;
16+
});
17+
18+
it('creates a git repository with valid data', () => {
19+
const onClose = cy.stub();
20+
const onSuccess = cy.stub();
21+
22+
cy.mount(
23+
<CreateGitRepositoryDialog
24+
isOpen={true}
25+
namespace="default"
26+
useCreateGitRepository={fakeUseCreateGitRepository}
27+
onClose={onClose}
28+
onSuccess={onSuccess}
29+
/>,
30+
);
31+
32+
const expectedPayload = {
33+
name: 'test-repo',
34+
interval: '5m0s',
35+
url: 'https://github.com/test/repo',
36+
branch: 'develop',
37+
secretRef: '',
38+
};
39+
40+
// Fill in the form
41+
cy.get('#name').find('input').type('test-repo');
42+
cy.get('#interval').find('input').clear().type('5m0s');
43+
cy.get('#url').find('input').type('https://github.com/test/repo');
44+
cy.get('#branch').find('input').clear().type('develop');
45+
46+
// Submit the form
47+
cy.get('ui5-button').contains('Create').click();
48+
49+
// Verify the hook was called with correct data
50+
cy.then(() => cy.wrap(capturedData).deepEqualJson(expectedPayload));
51+
52+
// Dialog should close on success
53+
cy.wrap(onClose).should('have.been.called');
54+
cy.wrap(onSuccess).should('have.been.called');
55+
});
56+
57+
it('includes secretRef when provided', () => {
58+
const onClose = cy.stub();
59+
const onSuccess = cy.stub();
60+
61+
cy.mount(
62+
<CreateGitRepositoryDialog
63+
isOpen={true}
64+
namespace="default"
65+
useCreateGitRepository={fakeUseCreateGitRepository}
66+
onClose={onClose}
67+
onSuccess={onSuccess}
68+
/>,
69+
);
70+
71+
const expectedPayload = {
72+
name: 'test-repo',
73+
interval: '1m0s',
74+
url: 'https://github.com/test/repo',
75+
branch: 'main',
76+
secretRef: 'my-git-secret',
77+
};
78+
79+
// Fill in the form
80+
cy.get('#name').find('input').type('test-repo');
81+
cy.get('#url').find('input').type('https://github.com/test/repo');
82+
cy.get('#secretRef').find('input').type('my-git-secret');
83+
84+
// Submit the form
85+
cy.get('ui5-button').contains('Create').click();
86+
87+
// Verify the hook was called with correct data
88+
cy.then(() => cy.wrap(capturedData).deepEqualJson(expectedPayload));
89+
90+
// Dialog should close on success
91+
cy.wrap(onClose).should('have.been.called');
92+
cy.wrap(onSuccess).should('have.been.called');
93+
});
94+
95+
it('validates required fields', () => {
96+
const onClose = cy.stub();
97+
const onSuccess = cy.stub();
98+
99+
cy.mount(
100+
<CreateGitRepositoryDialog
101+
isOpen={true}
102+
namespace="default"
103+
useCreateGitRepository={fakeUseCreateGitRepository}
104+
onClose={onClose}
105+
onSuccess={onSuccess}
106+
/>,
107+
);
108+
109+
// Try to submit without filling required fields
110+
cy.get('ui5-button').contains('Create').click();
111+
112+
// Should show validation errors
113+
cy.get('#name').should('have.attr', 'value-state', 'Negative');
114+
cy.contains('This field is required').should('exist');
115+
116+
// Dialog should not close
117+
cy.wrap(onClose).should('not.have.been.called');
118+
cy.wrap(onSuccess).should('not.have.been.called');
119+
});
120+
121+
it('validates URL format', () => {
122+
const onClose = cy.stub();
123+
const onSuccess = cy.stub();
124+
125+
cy.mount(
126+
<CreateGitRepositoryDialog
127+
isOpen={true}
128+
namespace="default"
129+
useCreateGitRepository={fakeUseCreateGitRepository}
130+
onClose={onClose}
131+
onSuccess={onSuccess}
132+
/>,
133+
);
134+
135+
cy.get('#name').find('input').type('test-repo');
136+
cy.get('#interval').find('input').clear().type('1m0s');
137+
cy.get('#url').find('input').type('not-a-valid-url');
138+
cy.get('#branch').find('input').clear().type('main');
139+
140+
// Submit the form
141+
cy.get('ui5-button').contains('Create').click();
142+
143+
// Should show validation error
144+
cy.get('#url').should('have.attr', 'value-state', 'Negative');
145+
cy.contains('Must be a valid HTTPS URL').should('exist');
146+
147+
// Dialog should not close
148+
cy.wrap(onClose).should('not.have.been.called');
149+
cy.wrap(onSuccess).should('not.have.been.called');
150+
});
151+
152+
it('closes dialog when cancel is clicked', () => {
153+
const onClose = cy.stub();
154+
const onSuccess = cy.stub();
155+
156+
cy.mount(
157+
<CreateGitRepositoryDialog
158+
isOpen={true}
159+
namespace="default"
160+
useCreateGitRepository={fakeUseCreateGitRepository}
161+
onClose={onClose}
162+
onSuccess={onSuccess}
163+
/>,
164+
);
165+
166+
// Fill in some data
167+
cy.get('#name').find('input').type('test-repo');
168+
169+
// Click cancel
170+
cy.get('ui5-button').contains('Cancel').click();
171+
172+
// Dialog should close without calling onSuccess
173+
cy.wrap(onClose).should('have.been.called');
174+
cy.wrap(onSuccess).should('not.have.been.called');
175+
});
176+
177+
it('uses default values for interval and branch', () => {
178+
const onClose = cy.stub();
179+
const onSuccess = cy.stub();
180+
181+
cy.mount(
182+
<CreateGitRepositoryDialog
183+
isOpen={true}
184+
namespace="default"
185+
useCreateGitRepository={fakeUseCreateGitRepository}
186+
onClose={onClose}
187+
onSuccess={onSuccess}
188+
/>,
189+
);
190+
191+
// Check default values
192+
cy.get('#interval').find('input').should('have.value', '1m0s');
193+
cy.get('#branch').find('input').should('have.value', 'main');
194+
});
195+
196+
it('should not close dialog when creation fails', () => {
197+
const failingUseCreateGitRepository = () => ({
198+
createGitRepository: async (): Promise<void> => {
199+
throw new Error('Creation failed');
200+
},
201+
isLoading: false,
202+
});
203+
204+
const onClose = cy.stub();
205+
const onSuccess = cy.stub();
206+
207+
cy.mount(
208+
<CreateGitRepositoryDialog
209+
isOpen={true}
210+
namespace="default"
211+
useCreateGitRepository={failingUseCreateGitRepository}
212+
onClose={onClose}
213+
onSuccess={onSuccess}
214+
/>,
215+
);
216+
217+
// Fill in the form
218+
cy.get('#name').find('input').type('test-repo');
219+
cy.get('#interval').find('input').clear().type('1m0s');
220+
cy.get('#url').find('input').type('https://github.com/test/repo');
221+
cy.get('#branch').find('input').clear().type('main');
222+
223+
// Submit the form
224+
cy.get('ui5-button').contains('Create').click();
225+
226+
// Dialog should NOT close on failure
227+
cy.wrap(onClose).should('not.have.been.called');
228+
cy.wrap(onSuccess).should('not.have.been.called');
229+
230+
// Dialog should still be visible
231+
cy.contains('Create Git Repository').should('be.visible');
232+
});
233+
});
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
.grid {
2+
display: grid;
3+
grid-template-columns: auto 1fr;
4+
gap: 1rem;
5+
padding: 1rem;
6+
align-items: center;
7+
}
8+
9+
.gridColumnLabel {
10+
justify-self: end;
11+
}
12+
13+
.inputField {
14+
width: 25rem;
15+
}
16+
17+
.sectionHeader {
18+
grid-column: 1 / -1;
19+
font-weight: bold;
20+
font-size: var(--sapFontHeader6Size);
21+
padding-top: 0.5rem;
22+
padding-bottom: 0.5rem;
23+
border-bottom: 1px solid var(--sapGroup_Title_BorderColor);
24+
margin-bottom: 0.5rem;
25+
}
26+
27+
.sectionHeader:first-child {
28+
padding-top: 0;
29+
}
30+
31+
.form {
32+
width: 30rem;
33+
}
34+
35+
.formField {
36+
margin-bottom: 1.25rem;
37+
}
38+
39+
.input {
40+
width: 100%;
41+
}

0 commit comments

Comments
 (0)