Skip to content

Commit

Permalink
Merge pull request #4810 from invincibleJai/feat-apiserversource
Browse files Browse the repository at this point in the history
adds support for apiServerSource
  • Loading branch information
openshift-merge-robot committed Apr 3, 2020
2 parents 8673440 + 7f54810 commit 229af0a
Show file tree
Hide file tree
Showing 15 changed files with 266 additions and 54 deletions.
Expand Up @@ -147,7 +147,7 @@ class ResourceDropdown extends React.Component<ResourceDropdownProps, State> {
updateSelection: boolean,
) => {
const unsortedList = {};
_.each(resources, ({ data }) => {
_.each(resources, ({ data, kind }) => {
_.reduce(
data,
(acc, resource) => {
Expand All @@ -159,12 +159,11 @@ class ResourceDropdown extends React.Component<ResourceDropdownProps, State> {
}
if (dataValue) {
if (showBadge) {
acc[dataValue] = (
<DropdownItem
key={resource.metadata.uid}
model={modelFor(referenceFor(resource))}
name={dataValue}
/>
const model = modelFor(referenceFor(resource)) || (kind && modelFor(kind));
acc[dataValue] = model ? (
<DropdownItem key={resource.metadata.uid} model={model} name={dataValue} />
) : (
dataValue
);
} else {
acc[dataValue] = transformLabel ? transformLabel(resource) : dataValue;
Expand Down
Expand Up @@ -7,6 +7,7 @@ import AppSection from '@console/dev-console/src/components/import/app/AppSectio
import { FirehoseList } from '@console/dev-console/src/components/import/import-types';
import CronJobSection from './event-sources/CronJobSection';
import SinkBindingSection from './event-sources/SinkBindingSection';
import ApiServerSection from './event-sources/ApiServerSection';
import SinkSection from './event-sources/SinkSection';
import { EventSources } from './import-types';
import EventSourcesSelector from './event-sources/EventSourcesSelector';
Expand All @@ -29,9 +30,10 @@ const EventSourceForm: React.FC<FormikProps<FormikValues> & OwnProps> = ({
projects,
}) => (
<Form className="co-deploy-image" onSubmit={handleSubmit}>
<EventSourcesSelector eventSourceList={useEventSourceList()} />
<EventSourcesSelector eventSourceList={useEventSourceList(namespace)} />
{values.type === EventSources.CronJobSource && <CronJobSection />}
{values.type === EventSources.SinkBinding && <SinkBindingSection />}
{values.type === EventSources.ApiServerSource && <ApiServerSection namespace={namespace} />}
<SinkSection namespace={namespace} />
<AppSection
project={values.project}
Expand All @@ -42,6 +44,7 @@ const EventSourceForm: React.FC<FormikProps<FormikValues> & OwnProps> = ({
errorMessage={status && status.submitError}
isSubmitting={isSubmitting}
submitLabel="Create"
sticky
disableSubmit={!dirty || !_.isEmpty(errors)}
resetLabel="Cancel"
/>
Expand Down
Expand Up @@ -5,12 +5,14 @@ import EventSourceForm from '../EventSourceForm';
import EventSourcesSelector from '../event-sources/EventSourcesSelector';
import CronJobSection from '../event-sources/CronJobSection';
import SinkSection from '../event-sources/SinkSection';
import { defaultEventingData } from '../../../utils/__tests__/knative-serving-data';
import { getDefaultEventingData } from '../../../utils/__tests__/knative-serving-data';
import { EventSources } from '../import-types';

type EventSourceFormProps = React.ComponentProps<typeof EventSourceForm>;
let formProps: EventSourceFormProps;

describe('EventSource Form', () => {
const defaultEventingData = getDefaultEventingData(EventSources.CronJobSource);
beforeEach(() => {
formProps = {
values: {
Expand Down
@@ -1,16 +1,19 @@
import { cloneDeep } from 'lodash';
import { eventSourceValidationSchema } from '../eventSource-validation-utils';
import { defaultEventingData } from '../../../utils/__tests__/knative-serving-data';
import { getDefaultEventingData } from '../../../utils/__tests__/knative-serving-data';
import { EventSources } from '../import-types';

describe('Event Source ValidationUtils', () => {
it('should validate the form data', async () => {
const defaultEventingData = getDefaultEventingData(EventSources.CronJobSource);
const mockData = cloneDeep(defaultEventingData);
await eventSourceValidationSchema
.isValid(mockData)
.then((valid) => expect(valid).toEqual(true));
});

it('should throw an error for required fields if empty', async () => {
const defaultEventingData = getDefaultEventingData(EventSources.CronJobSource);
const mockData = cloneDeep(defaultEventingData);
mockData.sink.knativeService = '';
await eventSourceValidationSchema
Expand All @@ -21,4 +24,27 @@ describe('Event Source ValidationUtils', () => {
expect(err.type).toBe('required');
});
});

describe('ApiServerSource : Event Source Validation', () => {
it('should validate the form data', async () => {
const defaultEventingData = getDefaultEventingData(EventSources.ApiServerSource);
const mockData = cloneDeep(defaultEventingData);
await eventSourceValidationSchema
.isValid(mockData)
.then((valid) => expect(valid).toEqual(true));
});

it('should throw an error for required fields if empty', async () => {
const defaultEventingData = getDefaultEventingData(EventSources.ApiServerSource);
const mockData = cloneDeep(defaultEventingData);
mockData.sink.knativeService = '';
await eventSourceValidationSchema
.isValid(mockData)
.then((valid) => expect(valid).toEqual(false));
await eventSourceValidationSchema.validate(mockData).catch((err) => {
expect(err.message).toBe('Required');
expect(err.type).toBe('required');
});
});
});
});
@@ -0,0 +1,98 @@
import * as React from 'react';
import * as _ from 'lodash';
import * as fuzzy from 'fuzzysearch';
import { useFormikContext, FormikValues } from 'formik';
import { FormGroup } from '@patternfly/react-core';
import { ServiceAccountModel } from '@console/internal/models';
import { NameValueEditor } from '@console/internal/components/utils/name-value-editor';
import { ResourceDropdownField, DropdownField, getFieldId } from '@console/shared';
import FormSection from '@console/dev-console/src/components/import/section/FormSection';

interface ApiServerSectionProps {
namespace: string;
}

const ApiServerSection: React.FC<ApiServerSectionProps> = ({ namespace }) => {
const { values, setFieldValue, setFieldTouched, validateForm } = useFormikContext<FormikValues>();
const initVal = values?.data?.apiserversource?.resources || [];
const initialValueResources = !_.isEmpty(initVal)
? initVal.map((val) => _.values(val))
: [['', '']];
const [nameValue, setNameValue] = React.useState(initialValueResources);
const autocompleteFilter = (strText, item): boolean => fuzzy(strText, item?.props?.name);
const handleNameValuePairs = React.useCallback(
({ nameValuePairs }) => {
const updatedNameValuePairs = _.compact(
nameValuePairs.map(([name, value]) => {
if (value.length) {
return { apiVersion: name, kind: value };
}
return null;
}),
);
setNameValue(nameValuePairs);
setFieldValue('data.apiserversource.resources', updatedNameValuePairs);
},
[setFieldValue],
);
const onChange = React.useCallback(
(selectedValue) => {
if (selectedValue) {
setFieldTouched('data.apiserversource.serviceAccountName', true);
setFieldValue('data.apiserversource.serviceAccountName', selectedValue);
validateForm();
}
},
[setFieldValue, setFieldTouched, validateForm],
);
const resources = [
{
isList: true,
kind: ServiceAccountModel.kind,
namespace,
prop: ServiceAccountModel.id,
optional: true,
},
];
const modeItems = {
Ref: 'Ref',
Resource: 'Resource',
};
const fieldId = getFieldId(values.type, 'res-input');
return (
<FormSection title="ApiServerSource">
<FormGroup fieldId={fieldId} label="Resource" isRequired>
<NameValueEditor
nameValuePairs={nameValue}
valueString="kind"
nameString="apiVersion"
addString="Add Resource"
readOnly={false}
allowSorting={false}
updateParentData={handleNameValuePairs}
/>
</FormGroup>
<DropdownField
name="data.apiserversource.mode"
label="Mode"
items={modeItems}
title={modeItems.Ref}
fullWidth
/>
<ResourceDropdownField
name="data.apiserversource.serviceAccountName"
label="Service Account Name"
resources={resources}
dataSelector={['metadata', 'name']}
fullWidth
placeholder="Select Service Account Name"
onChange={onChange}
autocompleteFilter={autocompleteFilter}
autoSelect
showBadge
/>
</FormSection>
);
};

export default ApiServerSection;
Expand Up @@ -6,7 +6,7 @@ import { getFieldId, ResourceDropdownField } from '@console/shared';
import FormSection from '@console/dev-console/src/components/import/section/FormSection';
import { knativeServingResourcesServices } from '../../../utils/create-knative-utils';

export interface SinkSectionProps {
interface SinkSectionProps {
namespace: string;
}

Expand Down
@@ -0,0 +1,38 @@
import * as React from 'react';
import { shallow } from 'enzyme';
import { ResourceDropdownField } from '@console/shared';
import { NameValueEditor } from '@console/internal/components/utils/name-value-editor';
import FormSection from '@console/dev-console/src/components/import/section/FormSection';
import ApiServerSection from '../ApiServerSection';

jest.mock('formik', () => ({
useField: jest.fn(() => [{}, {}]),
useFormikContext: jest.fn(() => ({
setFieldValue: jest.fn(),
setFieldTouched: jest.fn(),
validateForm: jest.fn(),
values: {
type: 'ApiServerSource',
},
})),
getFieldId: jest.fn(),
}));
describe('ApiServerSection', () => {
it('should render FormSection', () => {
const wrapper = shallow(<ApiServerSection namespace="test-project" />);
expect(wrapper.find(FormSection)).toHaveLength(1);
expect(wrapper.find(FormSection).props().title).toBe('ApiServerSource');
});

it('should render NameValueEditor', () => {
const wrapper = shallow(<ApiServerSection namespace="test-project" />);
const nameValueEditorField = wrapper.find(NameValueEditor);
expect(nameValueEditorField).toHaveLength(1);
expect(nameValueEditorField.props().nameString).toBe('apiVersion');
expect(nameValueEditorField.props().valueString).toBe('kind');
});
it('should render ResourceDropdownField', () => {
const wrapper = shallow(<ApiServerSection namespace="test-project" />);
expect(wrapper.find(ResourceDropdownField)).toHaveLength(1);
});
});
Expand Up @@ -46,6 +46,22 @@ export const sourceDataSpecSchema = yup
}),
}),
}),
})
.when('type', {
is: EventSources.ApiServerSource,
then: yup.object().shape({
apiserversource: yup.object().shape({
resources: yup
.array()
.of(
yup.object({
apiVersion: yup.string().required('Required'),
kind: yup.string().required('Required'),
}),
)
.required('Required'),
}),
}),
});

export const eventSourceValidationSchema = yup.object().shape({
Expand Down
Expand Up @@ -60,7 +60,7 @@ const EventSinkServicesOverviewList: React.FC<EventSinkServicesOverviewListProps
) : (
<span className="text-muted">No services found for this resource.</span>
)}
{pods?.length && <PodsOverview pods={pods} obj={obj} allPodsLink={linkUrl} />}
{pods?.length > 0 && <PodsOverview pods={pods} obj={obj} allPodsLink={linkUrl} />}
{deploymentData?.name && (
<>
<SidebarSectionHeading text="Deployment" />
Expand Down
2 changes: 1 addition & 1 deletion frontend/packages/knative-plugin/src/models.ts
Expand Up @@ -144,7 +144,7 @@ export const EventSourceApiServerModel: K8sKind = {
};

export const EventSourceCamelModel: K8sKind = {
apiGroup: KNATIVE_EVENT_SOURCE_APIGROUP_DEP,
apiGroup: KNATIVE_EVENT_SOURCE_APIGROUP,
apiVersion: 'v1alpha1',
kind: 'CamelSource',
label: 'CamelSource',
Expand Down
Expand Up @@ -2,10 +2,12 @@ import { cloneDeep } from 'lodash';
import { K8sResourceKind } from '@console/internal/module/k8s';
import { ServiceModel, EventSourceCronJobModel, EventSourceSinkBindingModel } from '../../models';
import { getEventSourcesDepResource } from '../create-eventsources-utils';
import { defaultEventingData } from './knative-serving-data';
import { getDefaultEventingData } from './knative-serving-data';
import { EventSources } from '../../components/add/import-types';

describe('Create knative Utils', () => {
it('expect response to be of kind CronJobSource with proper ApiGroup', () => {
const defaultEventingData = getDefaultEventingData(EventSources.CronJobSource);
const mockData = cloneDeep(defaultEventingData);
const knEventingResource: K8sResourceKind = getEventSourcesDepResource(mockData);
expect(knEventingResource.kind).toBe(EventSourceCronJobModel.kind);
Expand All @@ -14,14 +16,16 @@ describe('Create knative Utils', () => {
);
});

it('expect response to data and schedule in spec', () => {
it('expect response to data and schedule in spec for CronJobSource', () => {
const defaultEventingData = getDefaultEventingData(EventSources.CronJobSource);
const mockData = cloneDeep(defaultEventingData);
const knEventingResource: K8sResourceKind = getEventSourcesDepResource(mockData);
expect(knEventingResource.spec.data).toBe('hello');
expect(knEventingResource.spec.schedule).toBe('* * * * *');
});

it('expect response for sink to be of kind knative service', () => {
const defaultEventingData = getDefaultEventingData(EventSources.CronJobSource);
const mockData = cloneDeep(defaultEventingData);
const knEventingResource: K8sResourceKind = getEventSourcesDepResource(mockData);
expect(knEventingResource.spec.sink.ref.kind).toBe(ServiceModel.kind);
Expand All @@ -31,6 +35,7 @@ describe('Create knative Utils', () => {
});

it('expect response to be of kind sinkBinding with proper ApiGroup', () => {
const defaultEventingData = getDefaultEventingData(EventSources.CronJobSource);
const mockData = cloneDeep(defaultEventingData);
mockData.type = 'SinkBinding';
const knEventingResource: K8sResourceKind = getEventSourcesDepResource(mockData);
Expand Down

0 comments on commit 229af0a

Please sign in to comment.