Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,8 @@
"maxChars": "Max length is {{maxLength}} characters.",
"userExists": "User with this name already exists!",
"atLeastOneUser": "You need to have at least one member assigned.",
"notValidChargingTargetFormat": "Use lowercase letters a-f, numbers 0-9, and hyphens (-) in the format: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
"notValidChargingTargetFormat": "Use lowercase letters a-f, numbers 0-9, and hyphens (-) in the format: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa",
"urlFormat": "Must be a valid HTTPS URL"
},
"common": {
"all": "All",
Expand Down Expand Up @@ -507,5 +508,18 @@
"addMembersButton0": "Add members",
"addMembersButton1": "Add member",
"addMembersButtonN": "Add {{count}} members"
},
"CreateGitRepositoryDialog": {
"dialogTitle": "Create Git Repository",
"metadataTitle": "Metadata",
"urlTitle": "URL",
"secretRefOptionalTitle": "SecretRef (Optional)",
"secretRefTitle": "SecretRef",
"nameTitle": "Name",
"intervalTitle": "Interval",
"specTitle": "Spec",
"branchTitle": "Branch",
"gitRepositoryCreated": "Git Repository created successfully",
"gitRepositoryCreationFailed": "Failed to create Git Repository: {{error}}"
}
}
60 changes: 40 additions & 20 deletions src/components/ControlPlane/GitRepositories.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import ConfiguredAnalyticstable from '../Shared/ConfiguredAnalyticsTable.tsx';
import { AnalyticalTableColumnDefinition, Panel, Title, Toolbar, ToolbarSpacer } from '@ui5/webcomponents-react';
import IllustratedError from '../Shared/IllustratedError.tsx';
import { useApiResource } from '../../lib/api/useApiResource';
import { FluxRequest } from '../../lib/api/types/flux/listGitRepo';
import { useTranslation } from 'react-i18next';
import { formatDateAsTimeAgo } from '../../utils/i18n/timeAgo.ts';

import { YamlViewButton } from '../Yaml/YamlViewButton.tsx';
import { Fragment, useCallback, useMemo, useRef } from 'react';
import StatusFilter from '../Shared/StatusFilter/StatusFilter.tsx';
import { Fragment, useCallback, useMemo, useRef, useState } from 'react';
import {
AnalyticalTableColumnDefinition,
Panel,
Title,
Toolbar,
ToolbarSpacer,
Button,
} from '@ui5/webcomponents-react';
import '@ui5/webcomponents-icons/dist/add';
import IllustratedError from '../Shared/IllustratedError.tsx';
import { ResourceStatusCell } from '../Shared/ResourceStatusCell.tsx';
import { Resource } from '../../utils/removeManagedFieldsAndFilterData.ts';
import { useSplitter } from '../Splitter/SplitterContext.tsx';
Expand All @@ -17,6 +24,8 @@ import { useHandleResourcePatch } from '../../hooks/useHandleResourcePatch.ts';
import { ErrorDialog, ErrorDialogHandle } from '../Shared/ErrorMessageBox.tsx';
import type { GitReposResponse } from '../../lib/api/types/flux/listGitRepo';
import { ActionsMenu, type ActionItem } from './ActionsMenu';
import StatusFilter from '../Shared/StatusFilter/StatusFilter.tsx';
import { CreateGitRepositoryDialog } from '../Dialogs/CreateGitRepositoryDialog.tsx';

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

type FluxRow = {
name: string;
Expand Down Expand Up @@ -152,21 +162,32 @@ export function GitRepositories() {
}) ?? [];

return (
<Panel
fixed
header={
<Toolbar>
<Title>{t('common.resourcesCount', { count: rows.length })}</Title>
<YamlViewButton variant="resource" resource={data as unknown as Resource} />
<ToolbarSpacer />
</Toolbar>
}
>
<>
<ConfiguredAnalyticstable columns={columns} isLoading={isLoading} data={rows} />
<ErrorDialog ref={errorDialogRef} />
</>
</Panel>
<>
<Panel
fixed
header={
<Toolbar>
<Title>{t('common.resourcesCount', { count: rows.length })}</Title>
<YamlViewButton variant="resource" resource={data as unknown as Resource} />
<ToolbarSpacer />
<Button icon="add" onClick={() => setIsCreateDialogOpen(true)}>
{t('buttons.create')}
</Button>
</Toolbar>
}
>
<>
<ConfiguredAnalyticstable columns={columns} isLoading={isLoading} data={rows} />
<ErrorDialog ref={errorDialogRef} />
</>
</Panel>

<CreateGitRepositoryDialog
isOpen={isCreateDialogOpen}
namespace={rows[0]?.item?.metadata?.namespace || 'default'}
onClose={() => setIsCreateDialogOpen(false)}
/>
</>
);
}

Expand All @@ -177,7 +198,6 @@ function shortenCommitHash(commitHash: string): string {
if (match && match[2]) {
return `${match[1]}@${match[2].slice(0, 7)}`;
}

//example output : master@b3396ad
return commitHash;
}
223 changes: 223 additions & 0 deletions src/components/Dialogs/CreateGitRepositoryDialog.cy.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
import { CreateGitRepositoryDialog } from './CreateGitRepositoryDialog';
import { CreateGitRepositoryParams } from '../../hooks/useCreateGitRepository';

describe('CreateGitRepositoryDialog', () => {
let capturedData: CreateGitRepositoryParams | null = null;

const fakeUseCreateGitRepository = () => ({
createGitRepository: async (data: CreateGitRepositoryParams): Promise<void> => {
capturedData = data;
},
isLoading: false,
});

beforeEach(() => {
capturedData = null;
});

it('creates a git repository with valid data', () => {
const onClose = cy.stub();

cy.mount(
<CreateGitRepositoryDialog
isOpen={true}
namespace="default"
useCreateGitRepository={fakeUseCreateGitRepository}
onClose={onClose}
/>,
);

const expectedPayload = {
namespace: 'default',
name: 'test-repo',
interval: '5m0s',
url: 'https://github.com/test/repo',
branch: 'develop',
secretRef: '',
};

// Fill in the form
cy.get('#name').find('input').type('test-repo');
cy.get('#interval').find('input').clear().type('5m0s');
cy.get('#url').find('input').type('https://github.com/test/repo');
cy.get('#branch').find('input').clear().type('develop');

// Submit the form
cy.get('ui5-button').contains('Create').click();

// Verify the hook was called with correct data
cy.then(() => cy.wrap(capturedData).deepEqualJson(expectedPayload));

// Dialog should close on success
cy.wrap(onClose).should('have.been.called');
});

it('includes secretRef when provided', () => {
const onClose = cy.stub();

cy.mount(
<CreateGitRepositoryDialog
isOpen={true}
namespace="default"
useCreateGitRepository={fakeUseCreateGitRepository}
onClose={onClose}
/>,
);

const expectedPayload = {
namespace: 'default',
name: 'test-repo',
interval: '1m0s',
url: 'https://github.com/test/repo',
branch: 'main',
secretRef: 'my-git-secret',
};

// Fill in the form
cy.get('#name').find('input').type('test-repo');
cy.get('#url').find('input').type('https://github.com/test/repo');
cy.get('#secretRef').find('input').type('my-git-secret');

// Submit the form
cy.get('ui5-button').contains('Create').click();

// Verify the hook was called with correct data
cy.then(() => cy.wrap(capturedData).deepEqualJson(expectedPayload));

// Dialog should close on success
cy.wrap(onClose).should('have.been.called');
});

it('validates required fields', () => {
const onClose = cy.stub();

cy.mount(
<CreateGitRepositoryDialog
isOpen={true}
namespace="default"
useCreateGitRepository={fakeUseCreateGitRepository}
onClose={onClose}
/>,
);

// Try to submit without filling required fields
cy.get('ui5-button').contains('Create').click();

// Should show validation errors
cy.get('#name').should('have.attr', 'value-state', 'Negative');
cy.contains('This field is required').should('exist');

// Dialog should not close
cy.wrap(onClose).should('not.have.been.called');
});

it('validates URL format', () => {
const onClose = cy.stub();

cy.mount(
<CreateGitRepositoryDialog
isOpen={true}
namespace="default"
useCreateGitRepository={fakeUseCreateGitRepository}
onClose={onClose}
/>,
);

cy.get('#name').find('input').type('test-repo');
cy.get('#interval').find('input').clear().type('1m0s');
cy.get('#branch').find('input').clear().type('main');

// Test 1: Invalid string
cy.get('#url').find('input').clear().type('not-a-valid-url');
cy.get('ui5-button').contains('Create').click();
cy.get('#url').should('have.attr', 'value-state', 'Negative');
cy.contains('Must be a valid HTTPS URL').should('exist');

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

// Test 3: Valid HTTPS URL (should pass)
cy.get('#url').find('input').clear().type('https://github.com/test/repo');
cy.get('ui5-button').contains('Create').click();

// Dialog should close on success
cy.wrap(onClose).should('have.been.called');
});

it('closes dialog when cancel is clicked', () => {
const onClose = cy.stub();

cy.mount(
<CreateGitRepositoryDialog
isOpen={true}
namespace="default"
useCreateGitRepository={fakeUseCreateGitRepository}
onClose={onClose}
/>,
);

// Fill in some data
cy.get('#name').find('input').type('test-repo');

// Click cancel
cy.get('ui5-button').contains('Cancel').click();

// Dialog should close without calling onSuccess
cy.wrap(onClose).should('have.been.called');
});

it('uses default values for interval and branch', () => {
const onClose = cy.stub();

cy.mount(
<CreateGitRepositoryDialog
isOpen={true}
namespace="default"
useCreateGitRepository={fakeUseCreateGitRepository}
onClose={onClose}
/>,
);

// Check default values
cy.get('#interval').find('input').should('have.value', '1m0s');
cy.get('#branch').find('input').should('have.value', 'main');
});

it('should not close dialog when creation fails', () => {
const failingUseCreateGitRepository = () => ({
createGitRepository: async (): Promise<void> => {
throw new Error('Creation failed');
},
isLoading: false,
});

const onClose = cy.stub();

cy.mount(
<CreateGitRepositoryDialog
isOpen={true}
namespace="default"
useCreateGitRepository={failingUseCreateGitRepository}
onClose={onClose}
/>,
);

// Fill in the form
cy.get('#name').find('input').type('test-repo');
cy.get('#interval').find('input').clear().type('1m0s');
cy.get('#url').find('input').type('https://github.com/test/repo');
cy.get('#branch').find('input').clear().type('main');

// Submit the form
cy.get('ui5-button').contains('Create').click();

// Dialog should NOT close on failure
cy.wrap(onClose).should('not.have.been.called');

// Dialog should still be visible
cy.contains('Create Git Repository').should('be.visible');
});
});
41 changes: 41 additions & 0 deletions src/components/Dialogs/CreateGitRepositoryDialog.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
.grid {
display: grid;
grid-template-columns: auto 1fr;
gap: 1rem;
padding: 1rem;
align-items: center;
}

.gridColumnLabel {
justify-self: end;
}

.inputField {
width: 25rem;
}

.sectionHeader {
grid-column: 1 / -1;
font-weight: bold;
font-size: var(--sapFontHeader6Size);
padding-top: 0.5rem;
padding-bottom: 0.5rem;
border-bottom: 1px solid var(--sapGroup_Title_BorderColor);
margin-bottom: 0.5rem;
}

.sectionHeader:first-child {
padding-top: 0;
}

.form {
width: 30rem;
}

.formField {
margin-bottom: 1.25rem;
}

.input {
width: 100%;
}
Loading
Loading