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 3578384
Show file tree
Hide file tree
Showing 12 changed files with 182 additions and 50 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 @@ -31,7 +37,10 @@ const EventSource: React.FC<Props> = ({
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,12 @@
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 { K8sResourceKind } from '@console/internal/module/k8s';
import { knativeServingResourcesServices } from '../../utils/get-knative-resources';
import { useK8sWatchResources } from '@console/internal/components/utils/k8s-watch-hook';
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 +15,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]: any;
}>(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 @@ -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,13 @@ 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 {
knativeServingResourcesServices,
knativeEventingResourcesBroker,
} from '../../../utils/get-knative-resources';
import { getDynamicChannelResourceList } from '../../../utils/fetch-dynamic-eventsources-utils';

interface SinkSectionProps {
namespace: string;
Expand All @@ -17,36 +22,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 !== 'Broker';
};
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
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ export interface EventSourceData {
[x: string]: any;
}

export interface KnativeServiceName {
knativeService: string;
export interface SinkResourceData {
apiVersion: string;
name: string;
kind: string;
}

export interface EventSourceFormData {
Expand All @@ -43,7 +45,7 @@ export interface EventSourceFormData {
name: string;
apiVersion: string;
type: string;
sink: KnativeServiceName;
sink: SinkResourceData;
limits: LimitsData;
data?: EventSourceData;
yamlData?: string;
Expand Down
1 change: 1 addition & 0 deletions frontend/packages/knative-plugin/src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ export const FLAG_KNATIVE_SERVING_SERVICE = 'KNATIVE_SERVING_SERVICE';
export const KNATIVE_SERVING_LABEL = 'serving.knative.dev/service';
export const KNATIVE_SERVING_APIGROUP = 'serving.knative.dev';
export const KNATIVE_EVENT_MESSAGE_APIGROUP = 'messaging.knative.dev';
export const KNATIVE_EVENT_EVENTING_APIGROUP = 'eventing.knative.dev';
export const KNATIVE_EVENT_SOURCE_APIGROUP_DEP = 'sources.eventing.knative.dev';
export const KNATIVE_EVENT_SOURCE_APIGROUP = 'sources.knative.dev';
15 changes: 15 additions & 0 deletions frontend/packages/knative-plugin/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
KNATIVE_EVENT_SOURCE_APIGROUP_DEP,
KNATIVE_SERVING_APIGROUP,
KNATIVE_EVENT_MESSAGE_APIGROUP,
KNATIVE_EVENT_EVENTING_APIGROUP,
} from './const';

const apiVersion = 'v1';
Expand Down Expand Up @@ -237,3 +238,17 @@ export const EventingChannelModel: K8sKind = {
crd: true,
color: knativeEventingColor.value,
};

export const EventingBrokerModel: K8sKind = {
apiGroup: KNATIVE_EVENT_EVENTING_APIGROUP,
apiVersion: 'v1beta1',
kind: 'Broker',
label: 'Broker',
labelPlural: 'brokers',
plural: 'brokers',
id: 'broker',
abbr: 'B',
namespaced: true,
crd: true,
color: knativeEventingColor.value,
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import {
Resources,
} from '@console/dev-console/src/components/import/import-types';
import { EventSourceFormData } from '../../components/add/import-types';
import { RevisionModel } from '../../models';
import { RevisionModel, ServiceModel } from '../../models';
import { healthChecksProbeInitialData } from '@console/dev-console/src/components/health-checks/health-checks-probe-utils';

export const defaultData: DeployImageFormData = {
Expand Down Expand Up @@ -334,7 +334,9 @@ export const getDefaultEventingData = (typeEventSource: string): EventSourceForm
},
name: 'esmyapp',
sink: {
knativeService: 'event-display',
apiVersion: `${ServiceModel.apiGroup}/${ServiceModel.apiVersion}`,
name: 'event-display',
kind: ServiceModel.kind,
},
limits: {
cpu: {
Expand Down

0 comments on commit 3578384

Please sign in to comment.