Skip to content

Commit 13117fb

Browse files
PR changes
1 parent 2cf0e87 commit 13117fb

File tree

6 files changed

+80
-39
lines changed

6 files changed

+80
-39
lines changed

public/locales/en.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -510,7 +510,7 @@
510510
"addMembersButtonN": "Add {{count}} members"
511511
},
512512
"CreateGitRepositoryDialog": {
513-
"dialogTitle": "Create Git Repository'",
513+
"dialogTitle": "Create Git Repository",
514514
"metadataTitle": "Metadata",
515515
"urlTitle": "URL",
516516
"secretRefOptionalTitle": "SecretRef (Optional)",

src/components/ControlPlane/GitRepositories.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ export function GitRepositories() {
169169
<Toolbar>
170170
<Title>{t('common.resourcesCount', { count: rows.length })}</Title>
171171
<ToolbarSpacer />
172+
<YamlViewButton variant="resource" resource={data as unknown as Resource} />
172173
<Button icon="add" onClick={() => setIsCreateDialogOpen(true)}>
173174
{t('buttons.create')}
174175
</Button>

src/components/Dialogs/CreateGitRepositoryDialog.cy.tsx

Lines changed: 15 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,18 @@ describe('CreateGitRepositoryDialog', () => {
1717

1818
it('creates a git repository with valid data', () => {
1919
const onClose = cy.stub();
20-
const onSuccess = cy.stub();
2120

2221
cy.mount(
2322
<CreateGitRepositoryDialog
2423
isOpen={true}
2524
namespace="default"
2625
useCreateGitRepository={fakeUseCreateGitRepository}
2726
onClose={onClose}
28-
onSuccess={onSuccess}
2927
/>,
3028
);
3129

3230
const expectedPayload = {
31+
namespace: 'default',
3332
name: 'test-repo',
3433
interval: '5m0s',
3534
url: 'https://github.com/test/repo',
@@ -51,24 +50,22 @@ describe('CreateGitRepositoryDialog', () => {
5150

5251
// Dialog should close on success
5352
cy.wrap(onClose).should('have.been.called');
54-
cy.wrap(onSuccess).should('have.been.called');
5553
});
5654

5755
it('includes secretRef when provided', () => {
5856
const onClose = cy.stub();
59-
const onSuccess = cy.stub();
6057

6158
cy.mount(
6259
<CreateGitRepositoryDialog
6360
isOpen={true}
6461
namespace="default"
6562
useCreateGitRepository={fakeUseCreateGitRepository}
6663
onClose={onClose}
67-
onSuccess={onSuccess}
6864
/>,
6965
);
7066

7167
const expectedPayload = {
68+
namespace: 'default',
7269
name: 'test-repo',
7370
interval: '1m0s',
7471
url: 'https://github.com/test/repo',
@@ -89,20 +86,17 @@ describe('CreateGitRepositoryDialog', () => {
8986

9087
// Dialog should close on success
9188
cy.wrap(onClose).should('have.been.called');
92-
cy.wrap(onSuccess).should('have.been.called');
9389
});
9490

9591
it('validates required fields', () => {
9692
const onClose = cy.stub();
97-
const onSuccess = cy.stub();
9893

9994
cy.mount(
10095
<CreateGitRepositoryDialog
10196
isOpen={true}
10297
namespace="default"
10398
useCreateGitRepository={fakeUseCreateGitRepository}
10499
onClose={onClose}
105-
onSuccess={onSuccess}
106100
/>,
107101
);
108102

@@ -115,51 +109,53 @@ describe('CreateGitRepositoryDialog', () => {
115109

116110
// Dialog should not close
117111
cy.wrap(onClose).should('not.have.been.called');
118-
cy.wrap(onSuccess).should('not.have.been.called');
119112
});
120113

121114
it('validates URL format', () => {
122115
const onClose = cy.stub();
123-
const onSuccess = cy.stub();
124116

125117
cy.mount(
126118
<CreateGitRepositoryDialog
127119
isOpen={true}
128120
namespace="default"
129121
useCreateGitRepository={fakeUseCreateGitRepository}
130122
onClose={onClose}
131-
onSuccess={onSuccess}
132123
/>,
133124
);
134125

135126
cy.get('#name').find('input').type('test-repo');
136127
cy.get('#interval').find('input').clear().type('1m0s');
137-
cy.get('#url').find('input').type('not-a-valid-url');
138128
cy.get('#branch').find('input').clear().type('main');
139129

140-
// Submit the form
130+
// Test 1: Invalid string
131+
cy.get('#url').find('input').clear().type('not-a-valid-url');
141132
cy.get('ui5-button').contains('Create').click();
133+
cy.get('#url').should('have.attr', 'value-state', 'Negative');
134+
cy.contains('Must be a valid HTTPS URL').should('exist');
142135

143-
// Should show validation error
136+
// Test 2: HTTP protocol (should fail if we require HTTPS)
137+
cy.get('#url').find('input').clear().type('http://github.com/test/repo');
138+
cy.get('ui5-button').contains('Create').click();
144139
cy.get('#url').should('have.attr', 'value-state', 'Negative');
145140
cy.contains('Must be a valid HTTPS URL').should('exist');
146141

147-
// Dialog should not close
148-
cy.wrap(onClose).should('not.have.been.called');
149-
cy.wrap(onSuccess).should('not.have.been.called');
142+
// Test 3: Valid HTTPS URL (should pass)
143+
cy.get('#url').find('input').clear().type('https://github.com/test/repo');
144+
cy.get('ui5-button').contains('Create').click();
145+
146+
// Dialog should close on success
147+
cy.wrap(onClose).should('have.been.called');
150148
});
151149

152150
it('closes dialog when cancel is clicked', () => {
153151
const onClose = cy.stub();
154-
const onSuccess = cy.stub();
155152

156153
cy.mount(
157154
<CreateGitRepositoryDialog
158155
isOpen={true}
159156
namespace="default"
160157
useCreateGitRepository={fakeUseCreateGitRepository}
161158
onClose={onClose}
162-
onSuccess={onSuccess}
163159
/>,
164160
);
165161

@@ -171,20 +167,17 @@ describe('CreateGitRepositoryDialog', () => {
171167

172168
// Dialog should close without calling onSuccess
173169
cy.wrap(onClose).should('have.been.called');
174-
cy.wrap(onSuccess).should('not.have.been.called');
175170
});
176171

177172
it('uses default values for interval and branch', () => {
178173
const onClose = cy.stub();
179-
const onSuccess = cy.stub();
180174

181175
cy.mount(
182176
<CreateGitRepositoryDialog
183177
isOpen={true}
184178
namespace="default"
185179
useCreateGitRepository={fakeUseCreateGitRepository}
186180
onClose={onClose}
187-
onSuccess={onSuccess}
188181
/>,
189182
);
190183

@@ -202,15 +195,13 @@ describe('CreateGitRepositoryDialog', () => {
202195
});
203196

204197
const onClose = cy.stub();
205-
const onSuccess = cy.stub();
206198

207199
cy.mount(
208200
<CreateGitRepositoryDialog
209201
isOpen={true}
210202
namespace="default"
211203
useCreateGitRepository={failingUseCreateGitRepository}
212204
onClose={onClose}
213-
onSuccess={onSuccess}
214205
/>,
215206
);
216207

@@ -225,7 +216,6 @@ describe('CreateGitRepositoryDialog', () => {
225216

226217
// Dialog should NOT close on failure
227218
cy.wrap(onClose).should('not.have.been.called');
228-
cy.wrap(onSuccess).should('not.have.been.called');
229219

230220
// Dialog should still be visible
231221
cy.contains('Create Git Repository').should('be.visible');

src/components/Dialogs/CreateGitRepositoryDialog.tsx

Lines changed: 52 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,13 @@ interface CreateGitRepositoryDialogProps {
1212
isOpen: boolean;
1313
onClose: () => void;
1414
namespace?: string;
15-
onSuccess?: () => void;
1615
useCreateGitRepository?: typeof defaultUseCreateGitRepository;
1716
}
1817

1918
export function CreateGitRepositoryDialog({
2019
isOpen,
2120
onClose,
2221
namespace = 'default',
23-
onSuccess,
2422
useCreateGitRepository = defaultUseCreateGitRepository,
2523
}: CreateGitRepositoryDialogProps) {
2624
const { t } = useTranslation();
@@ -33,6 +31,7 @@ export function CreateGitRepositoryDialog({
3331
formState: { errors },
3432
} = useForm<CreateGitRepositoryParams>({
3533
defaultValues: {
34+
namespace,
3635
name: '',
3736
interval: '1m0s',
3837
url: '',
@@ -43,12 +42,26 @@ export function CreateGitRepositoryDialog({
4342

4443
useEffect(() => {
4544
if (!isOpen) {
46-
reset();
45+
reset({
46+
namespace,
47+
name: '',
48+
interval: '1m0s',
49+
url: '',
50+
branch: 'main',
51+
secretRef: '',
52+
});
4753
}
48-
}, [isOpen, reset]);
54+
}, [isOpen, namespace, reset]);
4955

5056
const handleClose = () => {
51-
reset();
57+
reset({
58+
namespace,
59+
name: '',
60+
interval: '1m0s',
61+
url: '',
62+
branch: 'main',
63+
secretRef: '',
64+
});
5265
onClose();
5366
};
5467

@@ -70,9 +83,15 @@ export function CreateGitRepositoryDialog({
7083
void handleSubmit(async (data) => {
7184
try {
7285
await createGitRepository(data);
73-
reset();
86+
reset({
87+
namespace,
88+
name: '',
89+
interval: '1m0s',
90+
url: '',
91+
branch: 'main',
92+
secretRef: '',
93+
});
7494
onClose();
75-
onSuccess?.();
7695
} catch {
7796
// Error handled by hook
7897
}
@@ -89,6 +108,24 @@ export function CreateGitRepositoryDialog({
89108
>
90109
<Form className={styles.form}>
91110
<FormGroup headerText={t('CreateGitRepositoryDialog.metadataTitle')}>
111+
<div className={styles.formField}>
112+
<Label required>{t('common.namespace', 'Namespace')}</Label>
113+
<Controller
114+
name="namespace"
115+
control={control}
116+
rules={{ required: t('validationErrors.required') }}
117+
render={({ field }) => (
118+
<Input
119+
{...field}
120+
id="namespace"
121+
valueState={errors.namespace ? 'Negative' : 'None'}
122+
valueStateMessage={<span>{errors.namespace?.message}</span>}
123+
className={styles.input}
124+
/>
125+
)}
126+
/>
127+
</div>
128+
92129
<div className={styles.formField}>
93130
<Label required>{t('CreateGitRepositoryDialog.nameTitle')}</Label>
94131
<Controller
@@ -135,7 +172,14 @@ export function CreateGitRepositoryDialog({
135172
control={control}
136173
rules={{
137174
required: t('validationErrors.required'),
138-
pattern: { value: /^https:\/\/.+/, message: t('validationErrors.urlFormat') },
175+
validate: (value: string) => {
176+
try {
177+
const url = new URL(value);
178+
return url.protocol === 'https:' || t('validationErrors.urlFormat');
179+
} catch {
180+
return t('validationErrors.urlFormat');
181+
}
182+
},
139183
}}
140184
render={({ field }) => (
141185
<Input

src/hooks/useCreateGitRepository.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,35 @@ import { useTranslation } from 'react-i18next';
33
import { useToast } from '../context/ToastContext';
44
import { fetchApiServerJson } from '../lib/api/fetch';
55
import { ApiConfigContext } from '../components/Shared/k8s/index';
6+
import { useRevalidateApiResource } from '../lib/api/useApiResource';
7+
import { FluxRequest } from '../lib/api/types/flux/listGitRepo';
68

79
export interface CreateGitRepositoryParams {
10+
namespace?: string;
811
name: string;
912
interval: string;
1013
url: string;
1114
branch: string;
1215
secretRef?: string;
1316
}
1417

15-
export const useCreateGitRepository = (namespace: string = 'default') => {
18+
export const useCreateGitRepository = (defaultNamespace: string = 'default') => {
1619
const [isLoading, setIsLoading] = useState(false);
1720
const { t } = useTranslation();
1821
const toast = useToast();
1922
const apiConfig = useContext(ApiConfigContext);
23+
const revalidate = useRevalidateApiResource(FluxRequest);
2024

2125
const createGitRepository = async (data: CreateGitRepositoryParams) => {
2226
setIsLoading(true);
2327
try {
28+
const targetNamespace = data.namespace || defaultNamespace;
2429
const payload = {
2530
apiVersion: 'source.toolkit.fluxcd.io/v1',
2631
kind: 'GitRepository',
2732
metadata: {
2833
name: data.name,
29-
namespace,
34+
namespace: targetNamespace,
3035
},
3136
spec: {
3237
interval: data.interval,
@@ -36,9 +41,10 @@ export const useCreateGitRepository = (namespace: string = 'default') => {
3641
},
3742
};
3843

39-
const path = `/apis/source.toolkit.fluxcd.io/v1/namespaces/${namespace}/gitrepositories`;
44+
const path = `/apis/source.toolkit.fluxcd.io/v1/namespaces/${targetNamespace}/gitrepositories`;
4045

4146
await fetchApiServerJson(path, apiConfig, undefined, 'POST', JSON.stringify(payload));
47+
await revalidate();
4248

4349
toast.show(t('CreateGitRepositoryDialog.gitRepositoryCreated'));
4450
} catch (error) {

src/lib/api/types/flux/createGitRepository.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ export type CreateGitRepositoryType = {
1919
};
2020
};
2121

22-
export const CreateGitRepositoryResource = (namespace: string, name: string): Resource<CreateGitRepositoryType> => ({
23-
path: `/apis/source.toolkit.fluxcd.io/v1/namespaces/${namespace}/gitrepositories/${name}`,
22+
export const CreateGitRepositoryResource = (namespace: string): Resource<CreateGitRepositoryType> => ({
23+
path: `/apis/source.toolkit.fluxcd.io/v1/namespaces/${namespace}/gitrepositories`,
2424
method: 'POST',
2525
});

0 commit comments

Comments
 (0)