Skip to content

Commit

Permalink
Merge pull request #1008 from CryptoRodeo/kfluxbugs-1651
Browse files Browse the repository at this point in the history
feat(KFLUXBUGS-1651): add contexts to show page
  • Loading branch information
openshift-merge-bot[bot] authored Nov 18, 2024
2 parents 9522a36 + 543156d commit 47c7e9c
Show file tree
Hide file tree
Showing 4 changed files with 271 additions and 2 deletions.
6 changes: 4 additions & 2 deletions src/components/IntegrationTest/ContextsField.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as React from 'react';
import { useParams } from 'react-router-dom';
import { FormGroup } from '@patternfly/react-core';
import { Bullseye, FormGroup, Spinner } from '@patternfly/react-core';
import { FieldArray, useField, FieldArrayRenderProps } from 'formik';
import { getFieldId } from '../../../src/shared/components/formik-fields/field-utils';
import { useComponents } from '../../hooks/useComponents';
Expand Down Expand Up @@ -106,7 +106,9 @@ const ContextsField: React.FC<IntegrationTestContextProps> = ({ heading, fieldNa
)}
/>
) : (
'Loading Additional Component Context options'
<Bullseye>
<Spinner size="xl" />
</Bullseye>
)}
</FormGroup>
);
Expand Down
122 changes: 122 additions & 0 deletions src/components/IntegrationTest/EditContextsModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import * as React from 'react';
import { k8sPatchResource } from '@openshift/dynamic-plugin-sdk-utils';
import {
Alert,
AlertVariant,
Button,
ButtonType,
ButtonVariant,
ModalVariant,
Stack,
StackItem,
} from '@patternfly/react-core';
import { Formik, FormikValues } from 'formik';
import { IntegrationTestScenarioModel } from '../../models';
import { IntegrationTestScenarioKind, Context } from '../../types/coreBuildService';
import { ComponentProps, createModalLauncher } from '../modal/createModalLauncher';
import ContextsField from './ContextsField';
import { 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) },
],
});
onClose(null, { submitClicked: true });
} catch (e) {
setError(e.message || e.toString());
}
};

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-testid={'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-testid={'update-contexts'}
>
Save
</Button>
<Button
variant={ButtonVariant.link}
onClick={handleReset}
data-testid={'cancel-update-contexts'}
>
Cancel
</Button>
</StackItem>
</Stack>
</div>
);
}}
</Formik>
);
};

export const createEditContextsModal = createModalLauncher(EditContextsModal, {
'data-testid': `edit-its-contexts`,
variant: ModalVariant.medium,
title: `Edit contexts`,
});
113 changes: 113 additions & 0 deletions src/components/IntegrationTest/__tests__/EditContextsModal.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import React from 'react';
import { k8sPatchResource } from '@openshift/dynamic-plugin-sdk-utils';
import { screen, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import { useComponents } from '../../../hooks/useComponents';
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('@openshift/dynamic-plugin-sdk-utils', () => ({
k8sPatchResource: jest.fn(),
}));
jest.mock('../../../hooks/useComponents', () => ({
useComponents: jest.fn(),
}));
jest.mock('../../../utils/workspace-context-utils', () => ({
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', async () => {
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();
});
});
});
32 changes: 32 additions & 0 deletions src/components/IntegrationTest/tabs/IntegrationTestOverviewTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { IntegrationTestScenarioKind } from '../../../types/coreBuildService';
import { useWorkspaceInfo } from '../../../utils/workspace-context-utils';
import { useModalLauncher } from '../../modal/ModalProvider';
import MetadataList from '../../PipelineRunDetailsView/MetadataList';
import { createEditContextsModal } from '../EditContextsModal';
import { createEditParamsModal } from '../EditParamsModal';
import { IntegrationTestLabels } from '../IntegrationTestForm/types';
import {
Expand All @@ -42,6 +43,7 @@ const IntegrationTestOverviewTab: React.FC<
const showModal = useModalLauncher();

const params = integrationTest?.spec?.params;
const contexts = integrationTest?.spec?.contexts;

return (
<>
Expand Down Expand Up @@ -138,6 +140,36 @@ const IntegrationTestOverviewTab: React.FC<
})}
</>
)}
{contexts && (
<DescriptionListGroup data-test="its-overview-contexts">
<DescriptionListTerm>
Contexts{' '}
<Tooltip content="Contexts where the integration test can be applied.">
<OutlinedQuestionCircleIcon />
</Tooltip>
</DescriptionListTerm>
<DescriptionListDescription>
{pluralize(contexts.length, 'context')}
<div>
{' '}
<Button
variant={ButtonVariant.link}
className="pf-v5-u-pl-0"
onClick={() =>
showModal(
createEditContextsModal({
intTest: integrationTest,
}),
)
}
data-test="edit-context-button"
>
Edit contexts
</Button>
</div>
</DescriptionListDescription>
</DescriptionListGroup>
)}
{params && (
<DescriptionListGroup data-test="its-overview-params">
<DescriptionListTerm>
Expand Down

0 comments on commit 47c7e9c

Please sign in to comment.