Skip to content

Commit

Permalink
adds capability for sources to sink to channel and broker along with …
Browse files Browse the repository at this point in the history
…ksvc
  • Loading branch information
invincibleJai committed Jul 1, 2020
1 parent 31b6f67 commit ddf8889
Show file tree
Hide file tree
Showing 14 changed files with 236 additions and 52 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,13 @@ import { history } from '@console/internal/components/utils';
import { getActiveApplication } from '@console/internal/reducers/ui';
import { RootState } from '@console/internal/redux';
import { ALL_APPLICATIONS_KEY } from '@console/shared';
import { K8sResourceKind, modelFor, referenceFor, k8sCreate } from '@console/internal/module/k8s';
import {
K8sResourceKind,
modelFor,
referenceFor,
k8sCreate,
getGroupVersionKind,
} from '@console/internal/module/k8s';
import { sanitizeApplicationValue } from '@console/dev-console/src/utils/application-utils';
import { eventSourceValidationSchema } from './eventSource-validation-utils';
import EventSourceForm from './EventSourceForm';
Expand All @@ -25,13 +31,16 @@ interface StateProps {

type Props = EventSourceProps & StateProps;

const EventSource: React.FC<Props> = ({
export const EventSource: React.FC<Props> = ({
namespace,
eventSourceStatus,
activeApplication,
contextSource,
}) => {
const serviceName = contextSource?.split('/').pop() || '';
const [sinkGroupVersionKind = '', sinkName = ''] = contextSource?.split('/') ?? [];
const [sinkGroup = '', sinkVersion = '', sinkKind = ''] =
getGroupVersionKind(sinkGroupVersionKind) ?? [];
const sinkApiVersion = sinkGroup ? `${sinkGroup}/${sinkVersion}` : '';
const initialValues: EventSourceFormData = {
project: {
name: namespace || '',
Expand All @@ -46,7 +55,9 @@ const EventSource: React.FC<Props> = ({
name: '',
apiVersion: '',
sink: {
knativeService: serviceName,
apiVersion: sinkApiVersion,
kind: sinkKind,
name: sinkName,
},
limits: {
cpu: {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import * as React from 'react';
import * as _ from 'lodash';
import { Alert } from '@patternfly/react-core';
import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook';
import { useK8sWatchResources } from '@console/internal/components/utils/k8s-watch-hook';
import { K8sResourceKind } from '@console/internal/module/k8s';
import { knativeServingResourcesServices } from '../../utils/get-knative-resources';
import {
knativeServingResourcesServicesWatchers,
knativeEventingResourcesBrokerWatchers,
} from '../../utils/get-knative-resources';
import { getDynamicEventingChannelWatchers } from '../../utils/fetch-dynamic-eventsources-utils';
import { EventSourceListData } from './import-types';

interface EventSourceAlertProps {
Expand All @@ -12,26 +16,43 @@ interface EventSourceAlertProps {
}

const EventSourceAlert: React.FC<EventSourceAlertProps> = ({ namespace, eventSourceStatus }) => {
const knServiceResource = React.useMemo(
() => ({ ...knativeServingResourcesServices(namespace)[0], limit: 1 }),
const getKnResources = React.useMemo(
() => ({
...knativeServingResourcesServicesWatchers(namespace),
...knativeEventingResourcesBrokerWatchers(namespace),
...getDynamicEventingChannelWatchers(namespace),
}),
[namespace],
);
const [data, loaded, loadError] = useK8sWatchResource<K8sResourceKind[]>(knServiceResource);
const resourcesData = useK8sWatchResources<{
[key: string]: K8sResourceKind[];
}>(getKnResources);
const { loaded, data } = _.reduce(
Object.values(resourcesData),
(acm, resData) => {
if (resData.loaded) {
acm.loaded = true;
acm.data = [...acm.data, ...resData.data];
}
return acm;
},
{ loaded: false, data: [] },
);

const noEventSources = eventSourceStatus === null;
const noEventSourceAccess =
!noEventSources && eventSourceStatus.loaded && _.isEmpty(eventSourceStatus.eventSourceList);
const noKnativeService = loaded && !loadError && !data?.length;
const showAlert = noKnativeService || noEventSources || noEventSourceAccess;
const noKnativeResources = loaded && _.isEmpty(data);
const showAlert = noKnativeResources || noEventSources || noEventSourceAccess;

return showAlert ? (
<Alert variant="default" title="Event Source cannot be created" isInline>
{noEventSourceAccess && 'You do not have write access in this project.'}
{noEventSources && 'Creation of event sources are not currently supported on this cluster.'}
{noKnativeService &&
{noKnativeResources &&
!noEventSourceAccess &&
!noEventSources &&
'Event Sources can only sink to Knative Services. No Knative Services exist in this project.'}
'Event Sources can only sink to Channel, Broker or Knative services. No Channels, Brokers or Knative services exist in this project.'}
</Alert>
) : null;
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import NamespacedPage, {
NamespacedPageVariants,
} from '@console/dev-console/src/components/NamespacedPage';
import { QUERY_PROPERTIES } from '@console/dev-console/src/const';
import EventSource from './EventSource';
import ConnectedEventSource from './EventSource';
import { KnativeEventingModel } from '../../models';
import EventSourceAlert from './EventSourceAlert';
import { useEventSourceList } from '../../utils/create-eventsources-utils';
Expand All @@ -28,7 +28,7 @@ const EventSourcePage: React.FC<EventSourcePageProps> = ({ match, location }) =>
</PageHeading>
<PageBody flexLayout>
<EventSourceAlert namespace={namespace} eventSourceStatus={eventSourceStatus} />
<EventSource
<ConnectedEventSource
namespace={namespace}
eventSourceStatus={eventSourceStatus}
selectedApplication={searchParams.get(QUERY_PROPERTIES.APPLICATION)}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import * as React from 'react';
import { shallow, ShallowWrapper } from 'enzyme';
import { Formik } from 'formik';
import { EventSource } from '../EventSource';

type EventSourceProps = React.ComponentProps<typeof EventSource>;
describe('EventSourceSpec', () => {
let wrapper: ShallowWrapper<EventSourceProps>;
const namespaceName = 'myApp';
const activeApplicationName = 'appGroup';
const eventSourceStatusData = null;

it('should render form with proper initialvalues if contextSource is not passed', () => {
wrapper = shallow(
<EventSource
namespace={namespaceName}
eventSourceStatus={eventSourceStatusData}
activeApplication={activeApplicationName}
/>,
);
const FormikField = wrapper.find(Formik);
expect(FormikField.exists()).toBe(true);
expect(FormikField.get(0).props.initialValues.project.name).toBe('myApp');
expect(FormikField.get(0).props.initialValues.sink).toEqual({
apiVersion: '',
kind: '',
name: '',
});
});

it('should render form with proper initialvalues for sink if contextSource is passed', () => {
const contextSourceData = 'serving.knative.dev~v1~Service/svc-display';
wrapper = shallow(
<EventSource
namespace={namespaceName}
eventSourceStatus={eventSourceStatusData}
contextSource={contextSourceData}
activeApplication={activeApplicationName}
/>,
);
const FormikField = wrapper.find(Formik);
expect(FormikField.exists()).toBe(true);
expect(FormikField.get(0).props.initialValues.project.name).toBe('myApp');
expect(FormikField.get(0).props.initialValues.sink).toEqual({
apiVersion: 'serving.knative.dev/v1',
kind: 'Service',
name: 'svc-display',
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import * as React from 'react';
import { shallow } from 'enzyme';
import { Alert } from '@patternfly/react-core';
import { referenceForModel } from '@console/internal/module/k8s';
import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook';
import { useK8sWatchResources } from '@console/internal/components/utils/k8s-watch-hook';
import EventSourceAlert from '../EventSourceAlert';
import { knativeServiceObj } from '../../../topology/__tests__/topology-knative-test-data';
import { getKnativeEventSourceIcon } from '../../../utils/get-knative-icon';
import { EventSourceContainerModel } from '../../../models';

jest.mock('@console/internal/components/utils/k8s-watch-hook', () => ({
useK8sWatchResource: jest.fn(),
useK8sWatchResources: jest.fn(),
}));

describe('EventSourceAlert', () => {
Expand All @@ -26,15 +26,19 @@ describe('EventSourceAlert', () => {
},
};
it('should not display alert if service data not loaded and eventSources are there', () => {
(useK8sWatchResource as jest.Mock).mockReturnValueOnce([null, false]);
(useK8sWatchResources as jest.Mock).mockReturnValueOnce({
ksservices: { loaded: false, data: [] },
});
const wrapper = shallow(
<EventSourceAlert namespace={namespaceName} eventSourceStatus={eventSourceStatusData} />,
);
expect(wrapper.find(Alert).exists()).toBe(false);
});

it('should display alert if service loaded with empty data and eventSources are there', () => {
(useK8sWatchResource as jest.Mock).mockReturnValueOnce([[], true]);
(useK8sWatchResources as jest.Mock).mockReturnValueOnce({
ksservices: { loaded: true, data: [] },
});
const wrapper = shallow(
<EventSourceAlert namespace={namespaceName} eventSourceStatus={eventSourceStatusData} />,
);
Expand All @@ -43,23 +47,29 @@ describe('EventSourceAlert', () => {
});

it('should not alert if service loaded with data and eventSources are there', () => {
(useK8sWatchResource as jest.Mock).mockReturnValueOnce([[knativeServiceObj], true]);
(useK8sWatchResources as jest.Mock).mockReturnValueOnce({
ksservices: { loaded: true, data: [knativeServiceObj] },
});
const wrapper = shallow(
<EventSourceAlert namespace={namespaceName} eventSourceStatus={eventSourceStatusData} />,
);
expect(wrapper.find(Alert).exists()).toBe(false);
});

it('should show alert if service loaded with data and eventSources is null', () => {
(useK8sWatchResource as jest.Mock).mockReturnValueOnce([[knativeServiceObj], true]);
(useK8sWatchResources as jest.Mock).mockReturnValueOnce({
ksservices: { loaded: true, data: [knativeServiceObj] },
});
const wrapper = shallow(
<EventSourceAlert namespace={namespaceName} eventSourceStatus={null} />,
);
expect(wrapper.find(Alert).exists()).toBe(true);
});

it('should show alert if service loaded with data and eventSources has loaded with no data', () => {
(useK8sWatchResource as jest.Mock).mockReturnValueOnce([[knativeServiceObj], true]);
(useK8sWatchResources as jest.Mock).mockReturnValueOnce({
ksservices: { loaded: true, data: [knativeServiceObj] },
});
const eventSourceStatus = { loaded: true, eventSourceList: {} };
const wrapper = shallow(
<EventSourceAlert namespace={namespaceName} eventSourceStatus={eventSourceStatus} />,
Expand All @@ -68,7 +78,9 @@ describe('EventSourceAlert', () => {
});

it('should not alert if service loaded with data and eventSources has not loaded', () => {
(useK8sWatchResource as jest.Mock).mockReturnValueOnce([[knativeServiceObj], true]);
(useK8sWatchResources as jest.Mock).mockReturnValueOnce({
ksservices: { loaded: true, data: [knativeServiceObj] },
});
const eventSourceStatus = { loaded: false, eventSourceList: {} };
const wrapper = shallow(
<EventSourceAlert namespace={namespaceName} eventSourceStatus={eventSourceStatus} />,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,11 @@ describe('Event Source ValidationUtils', () => {
it('should throw an error for required fields if empty', async () => {
const defaultEventingData = getDefaultEventingData(EventSources.CronJobSource);
const mockData = _.cloneDeep(defaultEventingData);
mockData.sink.knativeService = '';
mockData.sink = {
apiVersion: '',
name: '',
kind: '',
};
await eventSourceValidationSchema
.resolve({ value: mockData })
.isValid(mockData)
Expand All @@ -42,7 +46,11 @@ describe('Event Source ValidationUtils', () => {
it('should throw an error for required fields if empty', async () => {
const defaultEventingData = getDefaultEventingData(EventSources.ApiServerSource);
const mockData = _.cloneDeep(defaultEventingData);
mockData.sink.knativeService = '';
mockData.sink = {
apiVersion: '',
name: '',
kind: '',
};
mockData.data.apiserversource.resources[0] = { apiVersion: '', kind: '' };
await eventSourceValidationSchema
.resolve({ value: mockData })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,14 @@ import * as fuzzy from 'fuzzysearch';
import { useFormikContext, FormikValues } from 'formik';
import { FormGroup } from '@patternfly/react-core';
import { getFieldId, ResourceDropdownField } from '@console/shared';
import { K8sResourceKind } from '@console/internal/module/k8s';
import FormSection from '@console/dev-console/src/components/import/section/FormSection';
import { knativeServingResourcesServices } from '../../../utils/get-knative-resources';
import { EventingBrokerModel } from '../../../models';
import {
knativeServingResourcesServices,
knativeEventingResourcesBroker,
} from '../../../utils/get-knative-resources';
import { getDynamicChannelResourceList } from '../../../utils/fetch-dynamic-eventsources-utils';

interface SinkSectionProps {
namespace: string;
Expand All @@ -17,36 +23,60 @@ const SinkSection: React.FC<SinkSectionProps> = ({ namespace }) => {
const autocompleteFilter = (strText, item): boolean => fuzzy(strText, item?.props?.name);
const fieldId = getFieldId('sink-name', 'dropdown');
const onChange = React.useCallback(
(selectedValue) => {
if (selectedValue) {
setFieldTouched('sink.knativeService', true);
setFieldValue('sink.knativeService', selectedValue);
(selectedValue, valueObj) => {
const modelData = valueObj?.props?.model;
if (selectedValue && modelData) {
const { apiGroup, apiVersion, kind } = modelData;
setFieldValue('sink.name', selectedValue);
setFieldTouched('sink.name', true);
setFieldValue('sink.apiVersion', `${apiGroup}/${apiVersion}`);
setFieldTouched('sink.apiVersion', true);
setFieldValue('sink.kind', kind);
setFieldTouched('sink.kind', true);
validateForm();
}
},
[setFieldValue, setFieldTouched, validateForm],
);
const contextAvailable = !!initialValues.sink.knativeService;
const contextAvailable = !!initialValues.sink.name;
const resourcesData = [
...knativeServingResourcesServices(namespace),
...getDynamicChannelResourceList(namespace),
...knativeEventingResourcesBroker(namespace),
];

// filter out channels backing brokers
const resourceFilter = (resource: K8sResourceKind) => {
const {
metadata: { ownerReferences },
} = resource;
return !ownerReferences?.length || ownerReferences[0].kind !== EventingBrokerModel.kind;
};
return (
<FormSection title="Sink" extraMargin>
<FormSection
title="Sink"
subTitle="Add a sink to route this event source to a Channel, Broker or Knative service."
extraMargin
>
<FormGroup
fieldId={fieldId}
helperText={!contextAvailable ? 'Select a Service to sink to.' : ''}
helperText={!contextAvailable ? 'This resource will be the sink for the event source.' : ''}
isRequired
>
<ResourceDropdownField
name="sink.knativeService"
label="Knative Service"
resources={knativeServingResourcesServices(namespace)}
name="sink.name"
label="Resource"
resources={resourcesData}
dataSelector={['metadata', 'name']}
fullWidth
required
placeholder="Select Knative Service"
placeholder="Select resource"
showBadge
disabled={contextAvailable}
onChange={onChange}
autocompleteFilter={autocompleteFilter}
autoSelect
resourceFilter={resourceFilter}
/>
</FormGroup>
</FormSection>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { EventSources } from './import-types';
import { isKnownEventSource } from '../../utils/create-eventsources-utils';

const sinkServiceSchema = yup.object().shape({
knativeService: yup.string().required('Required'),
name: yup.string().required('Required'),
});

export const sourceDataSpecSchema = yup
Expand Down

0 comments on commit ddf8889

Please sign in to comment.