-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(KFLUXUI-217): add contexts to show page
- Add the context values to the integration show page. - Add a modal to allow easy edits. - Use spinner for loading instead of text.
- Loading branch information
1 parent
8259a84
commit 97fe747
Showing
5 changed files
with
280 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
import * as React from 'react'; | ||
import { | ||
Alert, | ||
AlertVariant, | ||
Button, | ||
ButtonType, | ||
ButtonVariant, | ||
ModalVariant, | ||
Stack, | ||
StackItem, | ||
} from '@patternfly/react-core'; | ||
import { Formik, FormikValues } from 'formik'; | ||
import { k8sPatchResource } from '../../k8s/k8s-fetch'; | ||
import { IntegrationTestScenarioModel } from '../../models'; | ||
import { IntegrationTestScenarioKind, Context } from '../../types/coreBuildService'; | ||
import { ComponentProps, createModalLauncher } from '../modal/createModalLauncher'; | ||
import ContextsField from './ContextsField'; | ||
import { UnformattedContexts, formatContexts } from './IntegrationTestForm/utils/create-utils'; | ||
|
||
type EditContextsModalProps = ComponentProps & { | ||
intTest: IntegrationTestScenarioKind; | ||
}; | ||
|
||
export const EditContextsModal: React.FC<React.PropsWithChildren<EditContextsModalProps>> = ({ | ||
intTest, | ||
onClose, | ||
}) => { | ||
const [error, setError] = React.useState<string>(); | ||
|
||
const getFormContextValues = (contexts: Context[] = []) => { | ||
return contexts.map(({ name, description }) => ({ name, description })); | ||
}; | ||
|
||
const updateIntegrationTest = async (values: FormikValues) => { | ||
try { | ||
await k8sPatchResource({ | ||
model: IntegrationTestScenarioModel, | ||
queryOptions: { | ||
name: intTest.metadata.name, | ||
ns: intTest.metadata.namespace, | ||
}, | ||
patches: [ | ||
{ | ||
op: 'replace', | ||
path: '/spec/contexts', | ||
value: formatContexts(values.contexts as UnformattedContexts), | ||
}, | ||
], | ||
}); | ||
onClose(null, { submitClicked: true }); | ||
} catch (e) { | ||
const errMsg = e.message || e.toString(); | ||
setError(errMsg as string); | ||
} | ||
}; | ||
|
||
const onReset = () => { | ||
onClose(null, { submitClicked: false }); | ||
}; | ||
|
||
const initialContexts = getFormContextValues(intTest?.spec?.contexts); | ||
|
||
// When a user presses enter, make sure the form doesn't submit. | ||
// Enter should be used to select values from the drop down, | ||
// when using the keyboard, not submit the form. | ||
const handleKeyDown = (e: React.KeyboardEvent) => { | ||
if (e.key === 'Enter') { | ||
e.preventDefault(); // Prevent form submission on Enter key | ||
} | ||
}; | ||
|
||
return ( | ||
<Formik | ||
onSubmit={updateIntegrationTest} | ||
initialValues={{ contexts: initialContexts, confirm: false }} | ||
onReset={onReset} | ||
> | ||
{({ handleSubmit, handleReset, isSubmitting, values }) => { | ||
const isChanged = values.contexts !== initialContexts; | ||
const showConfirmation = isChanged && values.strategy === 'Automatic'; | ||
const isValid = isChanged && (showConfirmation ? values.confirm : true); | ||
|
||
return ( | ||
<div data-test={'edit-contexts-modal'} onKeyDown={handleKeyDown}> | ||
<Stack hasGutter> | ||
<StackItem> | ||
<ContextsField fieldName="contexts" editing={true} /> | ||
</StackItem> | ||
<StackItem> | ||
{error && ( | ||
<Alert isInline variant={AlertVariant.danger} title="An error occurred"> | ||
{error} | ||
</Alert> | ||
)} | ||
<Button | ||
type={ButtonType.submit} | ||
isLoading={isSubmitting} | ||
onClick={(e) => { | ||
e.preventDefault(); | ||
handleSubmit(); | ||
}} | ||
isDisabled={!isValid || isSubmitting} | ||
data-test={'update-contexts'} | ||
> | ||
Save | ||
</Button> | ||
<Button | ||
variant={ButtonVariant.link} | ||
onClick={handleReset} | ||
data-test={'cancel-update-contexts'} | ||
> | ||
Cancel | ||
</Button> | ||
</StackItem> | ||
</Stack> | ||
</div> | ||
); | ||
}} | ||
</Formik> | ||
); | ||
}; | ||
|
||
export const createEditContextsModal = createModalLauncher(EditContextsModal, { | ||
'data-test': `edit-its-contexts`, | ||
variant: ModalVariant.medium, | ||
title: `Edit contexts`, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
112 changes: 112 additions & 0 deletions
112
src/components/IntegrationTests/__tests__/EditContextsModal.spec.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,112 @@ | ||
import { screen, fireEvent, waitFor } from '@testing-library/react'; | ||
import '@testing-library/jest-dom'; | ||
import { useComponents } from '../../../hooks/useComponents'; | ||
import { k8sPatchResource } from '../../../k8s/k8s-fetch'; | ||
import { formikRenderer } from '../../../utils/test-utils'; | ||
import { EditContextsModal } from '../EditContextsModal'; | ||
import { IntegrationTestFormValues } from '../IntegrationTestForm/types'; | ||
import { MockIntegrationTests } from '../IntegrationTestsListView/__data__/mock-integration-tests'; | ||
import { contextOptions } from '../utils'; | ||
|
||
// Mock external dependencies | ||
jest.mock('../../../k8s/k8s-fetch', () => ({ | ||
k8sPatchResource: jest.fn(), | ||
})); | ||
jest.mock('../../../hooks/useComponents', () => ({ | ||
useComponents: jest.fn(), | ||
})); | ||
jest.mock('../../Workspace/useWorkspaceInfo', () => ({ | ||
useWorkspaceInfo: jest.fn(() => ({ namespace: 'test-ns', workspace: 'test-ws' })), | ||
})); | ||
|
||
const useComponentsMock = useComponents as jest.Mock; | ||
const patchResourceMock = k8sPatchResource as jest.Mock; | ||
const onCloseMock = jest.fn(); | ||
|
||
const intTest = MockIntegrationTests[0]; | ||
const initialValues: IntegrationTestFormValues = { | ||
name: intTest.metadata.name, | ||
url: 'test-url', | ||
optional: true, | ||
contexts: intTest.spec.contexts, | ||
}; | ||
|
||
const setup = () => | ||
formikRenderer(<EditContextsModal intTest={intTest} onClose={onCloseMock} />, initialValues); | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks(); | ||
useComponentsMock.mockReturnValue([[], true]); | ||
}); | ||
|
||
describe('EditContextsModal', () => { | ||
it('should render correct contexts', () => { | ||
setup(); | ||
const contextOptionNames = contextOptions.map((ctx) => ctx.name); | ||
|
||
screen.getByText('Contexts'); | ||
contextOptionNames.forEach((ctxName) => screen.queryByText(ctxName)); | ||
}); | ||
|
||
it('should show Save and Cancel buttons', () => { | ||
setup(); | ||
// Save | ||
screen.getByTestId('update-contexts'); | ||
// Cancel | ||
screen.getByTestId('cancel-update-contexts'); | ||
}); | ||
|
||
it('should call onClose callback when cancel button is clicked', () => { | ||
setup(); | ||
fireEvent.click(screen.getByRole('button', { name: 'Cancel' })); | ||
expect(onCloseMock).toHaveBeenCalledWith(null, { submitClicked: false }); | ||
}); | ||
|
||
it('prevents form submission when pressing Enter', () => { | ||
setup(); | ||
const form = screen.getByTestId('edit-contexts-modal'); | ||
fireEvent.keyDown(form, { key: 'Enter', code: 'Enter' }); | ||
expect(k8sPatchResource).not.toHaveBeenCalled(); | ||
}); | ||
|
||
it('calls updateIntegrationTest and onClose on form submission', async () => { | ||
patchResourceMock.mockResolvedValue({}); | ||
|
||
setup(); | ||
const clearButton = screen.getByTestId('clear-button'); | ||
// Clear all selections | ||
fireEvent.click(clearButton); | ||
// Save button should now be active | ||
fireEvent.click(screen.getByRole('button', { name: 'Save' })); | ||
|
||
await waitFor(() => { | ||
expect(patchResourceMock).toHaveBeenCalledTimes(1); | ||
}); | ||
|
||
expect(patchResourceMock).toHaveBeenCalledWith( | ||
expect.objectContaining({ | ||
queryOptions: { name: 'test-app-test-1', ns: 'test-namespace' }, | ||
patches: [{ op: 'replace', path: '/spec/contexts', value: null }], | ||
}), | ||
); | ||
expect(onCloseMock).toHaveBeenCalledWith(null, { submitClicked: true }); | ||
}); | ||
|
||
it('displays an error message if k8sPatchResource fails', async () => { | ||
patchResourceMock.mockRejectedValue('Failed to update contexts'); | ||
setup(); | ||
|
||
const clearButton = screen.getByTestId('clear-button'); | ||
// Clear all selections | ||
fireEvent.click(clearButton); | ||
// Click Save button | ||
fireEvent.click(screen.getByRole('button', { name: 'Save' })); | ||
|
||
// wait for the error message to appear | ||
await waitFor(() => { | ||
expect(patchResourceMock).toHaveBeenCalledTimes(1); | ||
expect(screen.getByText('An error occurred')).toBeInTheDocument(); | ||
expect(screen.queryByText('Failed to update contexts')).toBeInTheDocument(); | ||
}); | ||
}); | ||
}); |