Skip to content

Commit

Permalink
add Kamelets- schema generated forms & yaml/form switching
Browse files Browse the repository at this point in the history
  • Loading branch information
vikram-raj committed Dec 1, 2020
1 parent c456683 commit dcc1738
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 20 deletions.
Expand Up @@ -35,6 +35,7 @@ export const DynamicForm: React.FC<DynamicFormProps> = ({
widgets = {},
customUISchema,
noActions,
showAlert = true,
...restProps
}) => {
const { t } = useTranslation();
Expand All @@ -56,14 +57,16 @@ export const DynamicForm: React.FC<DynamicFormProps> = ({
}
return (
<>
<Alert
isInline
className="co-alert co-break-word"
variant="info"
title={t(
'console-shared~Note: Some fields may not be represented in this form view. Please select "YAML view" for full control.',
)}
/>
{showAlert && (
<Alert
isInline
className="co-alert co-break-word"
variant="info"
title={t(
'console-shared~Note: Some fields may not be represented in this form view. Please select "YAML view" for full control.',
)}
/>
)}
<Accordion asDefinitionList={false} className="co-dynamic-form__accordion">
<Form
{...restProps}
Expand Down Expand Up @@ -109,6 +112,7 @@ type DynamicFormProps = FormProps<any> & {
ErrorTemplate?: React.FC<{ errors: string[] }>;
noActions?: boolean;
customUISchema?: boolean;
showAlert?: boolean;
onCancel?: () => void;
};

Expand Down
@@ -1,12 +1,15 @@
import * as React from 'react';
import { FormProps } from 'react-jsonschema-form';
import cx from 'classnames';
import { useField, useFormikContext, FormikValues } from 'formik';
import { AsyncComponent } from '@console/internal/components/utils';

type DynamicFormFieldProps = FormProps<any> & {
name: string;
errors?: string[];
formDescription?: React.ReactNode;
fullWidth?: boolean;
showAlert?: boolean;
};

const DynamicFormField: React.FC<DynamicFormFieldProps> = ({
Expand All @@ -16,20 +19,27 @@ const DynamicFormField: React.FC<DynamicFormFieldProps> = ({
errors,
formDescription,
formContext,
fullWidth = false,
showAlert,
}) => {
const [field] = useField(name);
const { setFieldValue } = useFormikContext<FormikValues>();

return (
<div className="row">
<div className="col-sm-12 col-md-4 col-md-push-8 col-lg-5 col-lg-push-7">
<div className={cx({ row: !fullWidth })}>
<div
className={cx({ 'col-sm-12 col-md-4 col-md-push-8 col-lg-5 col-lg-push-7': !fullWidth })}
>
{formDescription}
</div>
<div className="col-sm-12 col-md-8 col-md-pull-4 col-lg-7 col-lg-pull-5">
<div
className={cx({ 'col-sm-12 col-md-8 col-md-pull-4 col-lg-7 col-lg-pull-5': !fullWidth })}
>
<AsyncComponent
loader={() => import('../dynamic-form').then((c) => c.DynamicForm)}
errors={errors}
formContext={formContext}
showAlert={showAlert}
uiSchema={uiSchema}
formData={field.value}
onChange={(data) => setFieldValue(name, data)}
Expand Down
Expand Up @@ -25,6 +25,7 @@ interface OwnProps {
namespace: string;
eventSourceStatus: EventSourceListData | null;
eventSourceMetaDescription: React.ReactNode;
kameletSource: K8sResourceKind;
}

const CatalogEventSourceForm: React.FC<FormikProps<FormikValues> & OwnProps> = ({
Expand All @@ -39,6 +40,7 @@ const CatalogEventSourceForm: React.FC<FormikProps<FormikValues> & OwnProps> = (
namespace,
eventSourceStatus,
eventSourceMetaDescription,
kameletSource,
}) => {
const { t } = useTranslation();
const yamlEditor = <YAMLEditorField name="yamlData" onSave={handleSubmit} />;
Expand Down Expand Up @@ -108,15 +110,20 @@ const CatalogEventSourceForm: React.FC<FormikProps<FormikValues> & OwnProps> = (
variant="info"
/>
)}
<EventSourceSection namespace={namespace} catalogFlow fullWidth />{' '}
<EventSourceSection
namespace={namespace}
kameletSource={kameletSource}
catalogFlow
fullWidth
/>{' '}
</div>
</div>
)}
</>
);
return (
<FlexForm onSubmit={handleSubmit}>
{isDynamicEventSourceKind(values.formData.type) && (
{(isDynamicEventSourceKind(values.formData.type) || kameletSource) && (
<SyncedEditorField
name="editorType"
formContext={{
Expand Down
Expand Up @@ -26,6 +26,7 @@ import {
isKnownEventSource,
getEventSourceData,
handleRedirect,
getKameletSourceData,
} from '../../utils/create-eventsources-utils';
import { getEventSourceModels } from '../../utils/fetch-dynamic-eventsources-utils';
import { KNATIVE_EVENT_SOURCE_APIGROUP } from '../../const';
Expand All @@ -35,6 +36,7 @@ import {
SinkType,
EVENT_SOURCES_APP,
} from './import-types';
import { CamelKameletBindingModel } from '../../models';
import EventSourceMetaDescription from './EventSourceMetadataDescription';

interface EventSourceProps {
Expand All @@ -44,6 +46,8 @@ interface EventSourceProps {
contextSource?: string;
selectedApplication?: string;
sourceKind?: string;
kameletName?: string;
kameletSource?: K8sResourceKind;
}

interface StateProps {
Expand All @@ -61,6 +65,8 @@ export const EventSource: React.FC<Props> = ({
contextSource,
perspective,
sourceKind = '',
kameletSource,
kameletName,
}) => {
const perpectiveExtension = useExtensions<Perspective>(isPerspective);
const { t } = useTranslation();
Expand All @@ -71,11 +77,18 @@ export const EventSource: React.FC<Props> = ({
const selDataModel = _.find(getEventSourceModels(), { kind: sourceKind });
selApiVersion = selDataModel
? `${selDataModel?.apiGroup}/${selDataModel?.apiVersion}`
: sourceKind === CamelKameletBindingModel.kind
? `${CamelKameletBindingModel.apiGroup}/${CamelKameletBindingModel.apiVersion}`
: `${KNATIVE_EVENT_SOURCE_APIGROUP}/v1alpha2`;
sourceData = isKnownEventSource(sourceKind)
? { [sourceKind]: getEventSourceData(sourceKind) }
? sourceKind === CamelKameletBindingModel.kind
? { [sourceKind]: getKameletSourceData(kameletSource) }
: { [sourceKind]: getEventSourceData(sourceKind) }
: {};
selSourceName = _.kebabCase(sourceKind);
selSourceName =
kameletName && sourceKind === CamelKameletBindingModel.kind
? `kamelet-${kameletName}`
: _.kebabCase(sourceKind);
}
const [sinkGroupVersionKind = '', sinkName = ''] = contextSource?.split('/') ?? [];
const [sinkGroup = '', sinkVersion = '', sinkKind = ''] =
Expand All @@ -86,7 +99,6 @@ export const EventSource: React.FC<Props> = ({
const eventSourceMetaDescription = (
<EventSourceMetaDescription eventSourceStatus={eventSourceStatus} sourceKind={sourceKind} />
);

const catalogInitialValues: EventSourceSyncFormData = {
editorType: EditorType.Form,
showCanUseYAMLMessage: true,
Expand Down Expand Up @@ -167,6 +179,7 @@ export const EventSource: React.FC<Props> = ({
namespace={namespace}
eventSourceStatus={eventSourceStatus}
eventSourceMetaDescription={eventSourceMetaDescription}
kameletSource={kameletSource}
/>
) : (
<EventSourceForm
Expand Down
Expand Up @@ -4,6 +4,8 @@ import { RouteComponentProps } from 'react-router';
import { useTranslation } from 'react-i18next';
import { PageBody } from '@console/shared';
import { LoadingInline, PageHeading } from '@console/internal/components/utils';
import { K8sResourceKind, referenceForModel } from '@console/internal/module/k8s';
import { useK8sWatchResources } from '@console/internal/components/utils/k8s-watch-hook';
import NamespacedPage, {
NamespacedPageVariants,
} from '@console/dev-console/src/components/NamespacedPage';
Expand All @@ -12,18 +14,60 @@ import ConnectedEventSource from './EventSource';
import EventSourceAlert from './EventSourceAlert';
import { useEventSourceList } from '../../utils/create-eventsources-utils';
import { isDynamicEventSourceKind } from '../../utils/fetch-dynamic-eventsources-utils';
import { CamelKameletBindingModel, CamelKameletModel } from '../../models';

type EventSourcePageProps = RouteComponentProps<{ ns?: string }>;

const EventSourcePage: React.FC<EventSourcePageProps> = ({ match, location }) => {
const { t } = useTranslation();
const namespace = match.params.ns;
const eventSourceStatus = useEventSourceList(namespace);
const eventSourceList = useEventSourceList(namespace);
const searchParams = new URLSearchParams(location.search);
const showCatalog = location.pathname.includes('/extensible-catalog/');
const sourceKindProp = showCatalog && searchParams.get('sourceKind');
const isSourceKindPresent = sourceKindProp && isDynamicEventSourceKind(sourceKindProp);

const kameletName = showCatalog && sourceKindProp && searchParams.get('name');
const watchResource = React.useMemo(
() => ({
kameletResource: {
kind: referenceForModel(CamelKameletModel),
name: kameletName,
namespace,
optional: true,
selector: {
matchLabels: {
'camel.apache.org/kamelet.type': 'source',
},
},
},
}),
[kameletName, namespace],
);
const { kameletResource } = useK8sWatchResources<{ [key: string]: K8sResourceKind }>(
watchResource,
);
const { data: kamelet, loaded: kameletLoaded } = kameletResource;
const eventSourceStatus = {
...eventSourceList,
...(kameletName && kameletLoaded
? { loaded: kameletLoaded }
: { loaded: eventSourceList.loaded }),
eventSourceList: {
...eventSourceList.eventSourceList,
...(kameletName &&
kameletLoaded && {
[CamelKameletBindingModel.kind]: {
name: kamelet.metadata.name,
iconUrl: kamelet.metadata.annotations['camel.apache.org/kamelet.icon'],
displayName: kamelet.spec.definition.title,
title: kamelet.spec.definition.title,
provider: kamelet.metadata.annotations['camel.apache.org/provider'] || '',
description: kamelet.spec.definition.description,
},
}),
},
};
const isSourceKindPresent =
(sourceKindProp && isDynamicEventSourceKind(sourceKindProp)) || (kameletName && kameletLoaded);
return (
<NamespacedPage disabled variant={NamespacedPageVariants.light}>
<Helmet>
Expand Down Expand Up @@ -57,6 +101,8 @@ const EventSourcePage: React.FC<EventSourcePageProps> = ({ match, location }) =>
selectedApplication={searchParams.get(QUERY_PROPERTIES.APPLICATION)}
contextSource={searchParams.get(QUERY_PROPERTIES.CONTEXT_SOURCE)}
sourceKind={searchParams.get('sourceKind')}
kameletName={kameletName}
kameletSource={kamelet}
/>
) : (
<LoadingInline />
Expand Down
@@ -1,7 +1,9 @@
import * as React from 'react';
import { useFormikContext, FormikValues } from 'formik';
import * as _ from 'lodash';
import { useFormikValidationFix } from '@console/shared';
import { JSONSchema6 } from 'json-schema';
import { TextVariants, Text } from '@patternfly/react-core';
import { DynamicFormField, useFormikValidationFix } from '@console/shared';
import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook';
import { K8sResourceKind } from '@console/internal/module/k8s';
import AppSection from '@console/dev-console/src/components/import/app/AppSection';
Expand All @@ -16,17 +18,36 @@ import YAMLEditorSection from './YAMLEditorSection';
import { EventSources } from '../import-types';
import SinkSection from './SinkSection';
import { isKnownEventSource } from '../../../utils/create-eventsources-utils';
import { capabilityWidgetMap } from '@console/operator-lifecycle-manager/src/components/descriptors/spec/spec-descriptor-input';

interface EventSourceSectionProps {
namespace: string;
fullWidth?: boolean;
catalogFlow?: boolean;
kameletSource?: K8sResourceKind;
}

const getUISchema = (formSchema) => {
const uiSchema = {};
for (const k in formSchema.properties) {
if (formSchema.properties.hasOwnProperty(k)) {
uiSchema[k] = {
'ui:title': formSchema.properties[k].title,
'ui:description': formSchema.properties[k].description,
...(formSchema.properties[k].hasOwnProperty('x-descriptors')
? { 'ui:widget': capabilityWidgetMap.get(formSchema.properties[k]['x-descriptors'][0]) }
: {}),
};
}
}
return uiSchema;
};

const EventSourceSection: React.FC<EventSourceSectionProps> = ({
namespace,
fullWidth = false,
catalogFlow = false,
kameletSource,
}) => {
const { values } = useFormikContext<FormikValues>();
const projectResource = { kind: ProjectModel.kind, prop: ProjectModel.id, isList: true };
Expand All @@ -48,6 +69,11 @@ const EventSourceSection: React.FC<EventSourceSectionProps> = ({
/>
</>
);
const formSchema: JSONSchema6 = {
type: 'object',
required: kameletSource?.spec?.definition?.required,
properties: kameletSource?.spec?.definition?.properties,
};
let EventSource: React.ReactElement;
const sectionTitle = values.formData.data?.itemData?.title ?? values.formData.type;
switch (values.formData.type) {
Expand All @@ -69,6 +95,20 @@ const EventSourceSection: React.FC<EventSourceSectionProps> = ({
case EventSources.PingSource:
EventSource = <PingSourceSection title={sectionTitle} fullWidth={fullWidth} />;
break;
case EventSources.KameletBinding:
EventSource = (
<>
<Text component={TextVariants.h2}>{kameletSource?.spec?.definition?.title}</Text>
<DynamicFormField
name="formData.data.KameletBinding.source.properties"
schema={formSchema}
uiSchema={getUISchema(formSchema)}
showAlert={false}
fullWidth
/>
</>
);
break;
default:
EventSource = !catalogFlow ? <YAMLEditorSection title={sectionTitle} /> : null;
}
Expand Down
Expand Up @@ -7,6 +7,7 @@ import {
EventSourceSinkBindingModel,
EventingIMCModel,
EventingKafkaChannelModel,
CamelKameletBindingModel,
} from '../../models';

export const EventSources = {
Expand All @@ -16,6 +17,7 @@ export const EventSources = {
KafkaSource: EventSourceKafkaModel.kind,
PingSource: EventSourcePingModel.kind,
SinkBinding: EventSourceSinkBindingModel.kind,
KameletBinding: CamelKameletBindingModel.kind,
};
export const defaultChannels = {
InMemoryChannel: EventingIMCModel,
Expand Down
14 changes: 14 additions & 0 deletions frontend/packages/knative-plugin/src/models.ts
Expand Up @@ -334,3 +334,17 @@ export const CamelKameletBindingModel: K8sKind = {
crd: true,
color: knativeEventingColor.value,
};

export const CamelKameletModel: K8sKind = {
apiGroup: CAMEL_APIGROUP,
apiVersion: 'v1alpha1',
kind: 'Kamelet',
label: 'Kamelet',
labelPlural: 'Kamelets',
plural: 'kamelets',
id: 'kamelet',
abbr: 'K',
namespaced: true,
crd: true,
color: knativeEventingColor.value,
};

0 comments on commit dcc1738

Please sign in to comment.