Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

update moveSink modal to support URI along with resources #6094

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
20 changes: 20 additions & 0 deletions frontend/packages/knative-plugin/src/actions/sink-pubsub.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { KebabOption } from '@console/internal/components/utils';
import { K8sKind, K8sResourceKind } from '@console/internal/module/k8s';
import { setSinkPubsubModal } from '../components/modals';

export const setSinkPubsub = (model: K8sKind, source: K8sResourceKind): KebabOption => {
return {
label: `Move ${model.kind}`,
callback: () =>
setSinkPubsubModal({
source,
}),
accessReview: {
group: model.apiGroup,
resource: model.plural,
name: source.metadata.name,
namespace: source.metadata.namespace,
verb: 'update',
},
};
};
5 changes: 1 addition & 4 deletions frontend/packages/knative-plugin/src/actions/sink-source.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import { KebabOption } from '@console/internal/components/utils';
import { K8sKind, K8sResourceKind } from '@console/internal/module/k8s';
import { setSinkSourceModal } from '../components/modals';
import { EventingSubscriptionModel, EventingTriggerModel } from '../models';

export const setSinkSource = (model: K8sKind, source: K8sResourceKind): KebabOption => {
const pubSubModelKinds = [EventingSubscriptionModel.kind, EventingTriggerModel.kind];
const label = pubSubModelKinds.includes(model.kind) ? `Move ${model.kind}` : 'Move Sink';
return {
label,
label: 'Move Sink',
callback: () =>
setSinkSourceModal({
source,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ interface SinkSectionProps {

interface SinkResourcesProps {
namespace: string;
isMoveSink?: boolean;
}

const SinkUri: React.FC = () => (
Expand All @@ -38,7 +39,7 @@ const SinkUri: React.FC = () => (
</>
);

const SinkResources: React.FC<SinkResourcesProps> = ({ namespace }) => {
const SinkResources: React.FC<SinkResourcesProps> = ({ namespace, isMoveSink }) => {
const [resourceAlert, setResourceAlert] = React.useState(false);
const { setFieldValue, setFieldTouched, validateForm, initialValues } = useFormikContext<
FormikValues
Expand All @@ -61,7 +62,7 @@ const SinkResources: React.FC<SinkResourcesProps> = ({ namespace }) => {
},
[setFieldValue, setFieldTouched, validateForm],
);
const contextAvailable = !!initialValues.sink.name;
const contextAvailable = isMoveSink ? false : !!initialValues.sink.name;
const resourcesData = [
...knativeServingResourcesServices(namespace),
...getDynamicChannelResourceList(namespace),
Expand Down Expand Up @@ -111,28 +112,32 @@ const SinkResources: React.FC<SinkResourcesProps> = ({ namespace }) => {
);
};

export const SinkUriResourcesGroup: React.FC<SinkResourcesProps> = ({ namespace, isMoveSink }) => (
<RadioGroupField
name="sinkType"
options={[
{
label: sourceSinkType.Resource.label,
value: sourceSinkType.Resource.value,
activeChildren: <SinkResources namespace={namespace} isMoveSink={isMoveSink} />,
},
{
label: sourceSinkType.Uri.label,
value: sourceSinkType.Uri.value,
activeChildren: <SinkUri />,
},
]}
/>
);

const SinkSection: React.FC<SinkSectionProps> = ({ namespace }) => {
return (
<FormSection
title="Sink"
subTitle="Add a Sink to route this Event Source to a Channel, Broker, Knative Service or another route."
extraMargin
>
<RadioGroupField
name="sinkType"
options={[
{
label: sourceSinkType.Resource.label,
value: sourceSinkType.Resource.value,
activeChildren: <SinkResources namespace={namespace} />,
},
{
label: sourceSinkType.Uri.label,
value: sourceSinkType.Uri.value,
activeChildren: <SinkUri />,
},
]}
/>
<SinkUriResourcesGroup namespace={namespace} />
</FormSection>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,16 @@ import { EventSources, SinkType } from './import-types';
import { isKnownEventSource } from '../../utils/create-eventsources-utils';
import { isDefaultChannel, getChannelKind } from '../../utils/create-channel-utils';

export const sinkTypeUriValidatiuon = yup.object().shape({
uri: yup
.string()
.max(2000, 'Please enter a URI that is less then 2000 characters.')
.test('validate-uri', 'Invalid URI.', function(value) {
return isValidUrl(value);
})
.required('Required'),
});

const sinkServiceSchema = yup
.object()
.when('sinkType', {
Expand All @@ -19,15 +29,7 @@ const sinkServiceSchema = yup
})
.when('sinkType', {
is: SinkType.Uri,
then: yup.object().shape({
uri: yup
.string()
.max(2000, 'Please enter a URI that is less then 2000 characters.')
.test('validate-uri', 'Invalid URI.', function(value) {
return isValidUrl(value);
})
.required('Required'),
}),
then: sinkTypeUriValidatiuon,
});

export const sourceDataSpecSchema = yup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ export const setSinkSourceModal = (props) =>
m.sinkModalLauncher(props),
);

export const setSinkPubsubModal = (props) =>
import('../sink-pubsub/SinkPubsubController' /* webpackChunkName: "sink-pubsub" */).then((m) =>
m.sinkPubsubModalLauncher(props),
);

export const deleteRevisionModal = (props) =>
import(
'../revisions/DeleteRevisionModalController' /* webpackChunkName: "delete-revision" */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as React from 'react';
import * as classNames from 'classnames';
import * as _ from 'lodash';
import { Model, Edge } from '@patternfly/react-topology';
import { referenceFor } from '@console/internal/module/k8s';
import { referenceFor, modelFor } from '@console/internal/module/k8s';
import {
ActionsMenu,
ResourceLink,
Expand All @@ -11,8 +11,8 @@ import {
} from '@console/internal/components/utils';
import { edgeActions } from '@console/dev-console/src/components/topology/actions/edgeActions';
import { TopologyDataObject } from '@console/dev-console/src/components/topology/topology-types';
import { NodeType } from '../../topology/knative-topology-utils';
import { TYPE_EVENT_SOURCE_LINK, TYPE_REVISION_TRAFFIC } from '../../topology/const';
import { setSinkSource } from '../../actions/sink-source';

export type TopologyEdgePanelProps = {
edge: Edge;
Expand All @@ -35,7 +35,12 @@ const KnativeTopologyEdgePanel: React.FC<TopologyEdgePanelProps> = ({ edge, mode
const target: TopologyDataObject = edge.getTarget().getData();
const resources = [source?.resources?.obj, target?.resources?.obj];
const nodes = model.nodes.map((n) => edge.getController().getNodeById(n.id));
const isConnectedToUri = NodeType.SinkUri === resources[1]?.type?.nodeType;
const isEventSourceConnector = edge.getType() === TYPE_EVENT_SOURCE_LINK;
const actions = [];
if (isEventSourceConnector && source.resource) {
const sourceModel = modelFor(referenceFor(source.resource));
actions.push(setSinkSource(sourceModel, source.resource));
}

return (
<div className="overview__sidebar-pane resource-overview">
Expand All @@ -45,7 +50,7 @@ const KnativeTopologyEdgePanel: React.FC<TopologyEdgePanelProps> = ({ edge, mode
{connectorTypeToTitle(edge.getType())}
</div>
<div className="co-actions">
<ActionsMenu actions={!isConnectedToUri ? edgeActions(edge, nodes) : []} />
<ActionsMenu actions={!isEventSourceConnector ? edgeActions(edge, nodes) : actions} />
</div>
</h1>
</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import * as React from 'react';
import { Formik, FormikValues, FormikHelpers } from 'formik';
import { K8sResourceKind, k8sUpdate, referenceFor, modelFor } from '@console/internal/module/k8s';
import SinkPubsubModal from './SinkPubsubModal';
import { knativeServingResourcesServices } from '../../utils/get-knative-resources';

export interface SinkPubsubProps {
source: K8sResourceKind;
cancel?: () => void;
close?: () => void;
}

const SinkPubsub: React.FC<SinkPubsubProps> = ({ source, cancel, close }) => {
const {
kind: sourceKind,
metadata: { namespace, name },
spec,
} = source;
const isSinkRef = !!spec?.subscriber?.ref;
const { name: sinkName = '', apiVersion = '', kind = '' } = isSinkRef
? spec?.subscriber?.ref
: {};
const initialValues = {
ref: {
apiVersion,
kind,
name: sinkName,
},
};
const resourcesDropdownField = knativeServingResourcesServices(namespace);
const handleSubmit = (values: FormikValues, action: FormikHelpers<FormikValues>) => {
const updatePayload = {
...source,
...(sinkName !== values?.ref?.name && {
spec: { ...source.spec, subscriber: { ...values } },
}),
};
k8sUpdate(modelFor(referenceFor(source)), updatePayload)
.then(() => {
action.setSubmitting(false);
action.setStatus({ error: '' });
close();
})
.catch((err) => {
action.setStatus({ error: err.message || 'An error occurred. Please try again' });
});
};

return (
<Formik
initialValues={initialValues}
onSubmit={handleSubmit}
onReset={cancel}
initialStatus={{ error: '' }}
>
{(formikProps) => (
<SinkPubsubModal
{...formikProps}
resourceName={name}
resourceDropdown={resourcesDropdownField}
labelTitle={`Move ${sourceKind}`}
cancel={cancel}
/>
)}
</Formik>
);
};

export default SinkPubsub;
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import * as React from 'react';
import { K8sResourceKind } from '@console/internal/module/k8s';
import { createModalLauncher, ModalComponentProps } from '@console/internal/components/factory';
import SinkPubsub from './SinkPubsub';

type SinkPubsubControllerProps = {
source: K8sResourceKind;
};

const SinkPubsubController: React.FC<SinkPubsubControllerProps> = ({ source, ...props }) => (
<SinkPubsub {...props} source={source} />
);

type Props = SinkPubsubControllerProps & ModalComponentProps;

export const sinkPubsubModalLauncher = createModalLauncher<Props>(SinkPubsubController);

export default SinkPubsubController;
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import * as React from 'react';
import * as fuzzy from 'fuzzysearch';
import { FormikProps, FormikValues } from 'formik';
import {
ModalTitle,
ModalBody,
ModalSubmitFooter,
} from '@console/internal/components/factory/modal';
import { ResourceDropdownField } from '@console/shared';
import { FirehoseResource } from '@console/internal/components/utils';
import FormSection from '@console/dev-console/src/components/import/section/FormSection';

export interface SinkPubsubModalProps {
resourceName: string;
resourceDropdown: FirehoseResource[];
labelTitle: string;
cancel?: () => void;
}

type Props = FormikProps<FormikValues> & SinkPubsubModalProps;

const SinkPubsubModal: React.FC<Props> = ({
resourceName,
resourceDropdown,
labelTitle,
handleSubmit,
cancel,
isSubmitting,
status,
setFieldValue,
setFieldTouched,
validateForm,
values,
initialValues,
}) => {
const autocompleteFilter = (strText, item): boolean => fuzzy(strText, item?.props?.name);
const onSinkChange = React.useCallback(
(selectedValue, target) => {
const modelResource = target?.props?.model;
if (selectedValue) {
setFieldTouched('ref.name', true);
setFieldValue('ref.name', selectedValue);
if (modelResource) {
const { apiGroup, apiVersion, kind } = modelResource;
const sinkApiversion = `${apiGroup}/${apiVersion}`;
setFieldValue('ref.apiVersion', sinkApiversion);
setFieldTouched('ref.apiVersion', true);
setFieldValue('ref.kind', kind);
setFieldTouched('ref.kind', true);
}
validateForm();
}
},
[setFieldValue, setFieldTouched, validateForm],
);
const dirty = values?.ref?.name !== initialValues.ref.name;
return (
<form className="modal-content modal-content--no-inner-scroll" onSubmit={handleSubmit}>
<ModalTitle>{labelTitle}</ModalTitle>
<ModalBody>
<p>
Connects <strong>{resourceName}</strong> to
</p>
<FormSection fullWidth>
<ResourceDropdownField
name="ref.name"
resources={resourceDropdown}
dataSelector={['metadata', 'name']}
fullWidth
required
placeholder="Select a sink"
showBadge
autocompleteFilter={autocompleteFilter}
onChange={onSinkChange}
autoSelect
selectedKey={values?.ref?.name}
/>
</FormSection>
</ModalBody>
<ModalSubmitFooter
inProgress={isSubmitting}
submitText="Save"
submitDisabled={!dirty}
cancel={cancel}
errorMessage={status.error}
/>
</form>
);
};

export default SinkPubsubModal;