Skip to content

Commit

Permalink
adds support for apiServerSource
Browse files Browse the repository at this point in the history
  • Loading branch information
invincibleJai committed Mar 27, 2020
1 parent 8871465 commit 8c76f5f
Show file tree
Hide file tree
Showing 15 changed files with 299 additions and 75 deletions.
Expand Up @@ -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));
acc[dataValue] = model ? (
<DropdownItem key={resource.metadata.uid} model={model} name={dataValue} />
) : (
dataValue
);
} else {
acc[dataValue] = transformLabel ? transformLabel(resource) : dataValue;
Expand Down
Expand Up @@ -10,7 +10,10 @@ import { FirehoseList } from '@console/dev-console/src/components/import/import-
import { eventSourceValidationSchema } from './eventSource-validation-utils';
import EventSourceForm from './EventSourceForm';
import { EventSources, EventSourceFormData } from './import-types';
import { getEventSourcesDepResource } from '../../utils/create-eventsources-utils';
import {
getEventSourcesDepResource,
getEventSourceData,
} from '../../utils/create-eventsources-utils';

interface EventSourceProps {
namespace: string;
Expand All @@ -24,12 +27,6 @@ interface StateProps {
type Props = EventSourceProps & StateProps;

const EventSource: React.FC<Props> = ({ namespace, projects, activeApplication }) => {
const eventSourceData = {
cronjobsource: {
data: '',
schedule: '',
},
};
const typeEventSource = EventSources.CronJobSource;
const initialValues: EventSourceFormData = {
project: {
Expand All @@ -44,11 +41,11 @@ const EventSource: React.FC<Props> = ({ namespace, projects, activeApplication }
},
name: '',
sink: {
knativeService: 'event-greeter',
knativeService: '',
},
type: typeEventSource,
data: {
[typeEventSource.toLowerCase()]: eventSourceData[typeEventSource.toLowerCase()],
[typeEventSource.toLowerCase()]: getEventSourceData(typeEventSource.toLowerCase()),
},
};

Expand Down
Expand Up @@ -6,6 +6,7 @@ import { Form } from '@patternfly/react-core';
import AppSection from '@console/dev-console/src/components/import/app/AppSection';
import { FirehoseList } from '@console/dev-console/src/components/import/import-types';
import CronJobSection from './event-sources/CronJobSection';
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 @@ -28,8 +29,9 @@ 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.ApiServerSource && <ApiServerSection namespace={namespace} />}
<SinkSection namespace={namespace} />
<AppSection
project={values.project}
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,93 @@
import * as React from 'react';
import * as _ from 'lodash';
import { useFormikContext, FormikValues } from 'formik';
import { FormGroup } from '@patternfly/react-core';
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 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: 'ServiceAccount',
namespace,
prop: 'serviceaccount',
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}
autoSelect
/>
</FormSection>
);
};

export default ApiServerSection;
@@ -1,16 +1,30 @@
import * as React from 'react';
import { useFormikContext, FormikValues } from 'formik';
import { ItemSelectorField } from '@console/shared';
import FormSection from '@console/dev-console/src/components/import/section/FormSection';
import { NormalizedEventSources } from '../import-types';
import { getEventSourceData } from '../../../utils/create-eventsources-utils';

interface EventSourcesSelectorProps {
eventSourceList: NormalizedEventSources;
}

const EventSourcesSelector: React.FC<EventSourcesSelectorProps> = ({ eventSourceList }) => (
<FormSection title="Type" fullWidth>
<ItemSelectorField itemList={eventSourceList} name="type" />
</FormSection>
);
const EventSourcesSelector: React.FC<EventSourcesSelectorProps> = ({ eventSourceList }) => {
const { setFieldValue, setFieldTouched, validateForm } = useFormikContext<FormikValues>();
const handleItemChange = React.useCallback(
(item: string) => {
const name = `data.${item.toLowerCase()}`;
setFieldValue(name, getEventSourceData(item.toLowerCase()));
setFieldTouched(name, true);
validateForm();
},
[setFieldValue, setFieldTouched, validateForm],
);
return (
<FormSection title="Type" fullWidth>
<ItemSelectorField itemList={eventSourceList} name="type" onSelect={handleItemChange} />
</FormSection>
);
};

export default EventSourcesSelector;
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 @@ -10,26 +10,44 @@ const sinkServiceSchema = yup.object().shape({
knativeService: yup.string().required('Required'),
});

export const cronJobSpecSchema = yup.object().when('type', {
is: EventSources.CronJobSource,
then: yup.object().shape({
cronjobsource: yup.object().shape({
data: yup
.string()
.max(253, 'Cannot be longer than 253 characters.')
.required('Required'),
schedule: yup
.string()
.max(253, 'Cannot be longer than 253 characters.')
.required('Required'),
export const sourceDataSpecSchema = yup
.object()
.when('type', {
is: EventSources.CronJobSource,
then: yup.object().shape({
cronjobsource: yup.object().shape({
data: yup
.string()
.max(253, 'Cannot be longer than 253 characters.')
.required('Required'),
schedule: yup
.string()
.max(253, 'Cannot be longer than 253 characters.')
.required('Required'),
}),
}),
}),
});
})
.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({
project: projectNameValidationSchema,
application: applicationNameValidationSchema,
name: nameValidationSchema,
sink: sinkServiceSchema,
data: cronJobSpecSchema,
data: sourceDataSpecSchema,
});
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

0 comments on commit 8c76f5f

Please sign in to comment.