From 12abf5f19530f39ce2591ce4adc63b1253e606b1 Mon Sep 17 00:00:00 2001 From: Divyanshi Gupta Date: Mon, 13 Jan 2020 17:21:38 +0530 Subject: [PATCH 1/4] Add edit application from deploy image --- .../formik-fields/RadioButtonField.tsx | 1 + .../components/formik-fields/field-types.ts | 1 + .../src/actions/modify-application.ts | 6 +- .../edit-application/EditApplication.tsx | 203 +++++++++++------- .../edit-application/EditApplicationForm.tsx | 47 ++-- .../edit-application/EditApplicationPage.tsx | 89 ++++++++ .../EditApplicationWrapper.tsx | 76 ------- .../edit-application-types.ts | 11 +- .../edit-application-utils.ts | 176 ++++++++++++++- .../src/components/import/DeployImage.tsx | 11 +- .../src/components/import/ImportForm.tsx | 2 +- .../deployImage-submit-utils.spec.ts | 18 +- .../__tests__/import-submit-utils.spec.ts | 2 +- .../import/advanced/AdvancedSection.tsx | 2 +- .../import/deployImage-submit-utils.ts | 115 +++++++--- .../image-search/ImageSearchSection.tsx | 2 + .../image-search/ImageStreamDropdown.tsx | 2 +- .../image-search/ImageStreamTagDropdown.tsx | 19 +- .../import/image-search/SearchResults.tsx | 6 +- .../components/import/import-submit-utils.ts | 95 ++++---- .../src/components/import/import-types.ts | 2 +- .../components/import/route/RouteCheckbox.tsx | 7 +- .../src/components/modals/index.ts | 5 - frontend/packages/dev-console/src/plugin.tsx | 12 ++ .../src/utils/resource-label-utils.ts | 4 + .../src/utils/shared-submit-utils.ts | 15 +- .../src/utils/create-knative-utils.ts | 9 +- 27 files changed, 648 insertions(+), 290 deletions(-) create mode 100644 frontend/packages/dev-console/src/components/edit-application/EditApplicationPage.tsx delete mode 100644 frontend/packages/dev-console/src/components/edit-application/EditApplicationWrapper.tsx diff --git a/frontend/packages/console-shared/src/components/formik-fields/RadioButtonField.tsx b/frontend/packages/console-shared/src/components/formik-fields/RadioButtonField.tsx index e37170595ba..f1b1c284236 100644 --- a/frontend/packages/console-shared/src/components/formik-fields/RadioButtonField.tsx +++ b/frontend/packages/console-shared/src/components/formik-fields/RadioButtonField.tsx @@ -40,6 +40,7 @@ const RadioButtonField: React.FC = ({ label={option.label} isChecked={field.value === option.value} isValid={isValid} + isDisabled={option.isDisabled} aria-describedby={`${fieldId}-helper`} onChange={(val, event) => { field.onChange(event); diff --git a/frontend/packages/console-shared/src/components/formik-fields/field-types.ts b/frontend/packages/console-shared/src/components/formik-fields/field-types.ts index 89599e08d90..7eab0c04844 100644 --- a/frontend/packages/console-shared/src/components/formik-fields/field-types.ts +++ b/frontend/packages/console-shared/src/components/formik-fields/field-types.ts @@ -94,6 +94,7 @@ export interface RadioButtonProps extends FieldProps { export interface RadioOption { value: string; label: React.ReactNode; + isDisabled?: boolean; children?: React.ReactNode; activeChildren?: React.ReactElement; } diff --git a/frontend/packages/dev-console/src/actions/modify-application.ts b/frontend/packages/dev-console/src/actions/modify-application.ts index 555782906c9..ffeb53b3f9f 100644 --- a/frontend/packages/dev-console/src/actions/modify-application.ts +++ b/frontend/packages/dev-console/src/actions/modify-application.ts @@ -1,6 +1,6 @@ import { KebabOption } from '@console/internal/components/utils'; import { K8sResourceKind, K8sKind } from '@console/internal/module/k8s'; -import { editApplicationModal, editApplication } from '../components/modals'; +import { editApplicationModal } from '../components/modals'; export const ModifyApplication = (kind: K8sKind, obj: K8sResourceKind): KebabOption => { return { @@ -24,8 +24,8 @@ export const ModifyApplication = (kind: K8sKind, obj: K8sResourceKind): KebabOpt export const EditApplication = (model: K8sKind, obj: K8sResourceKind): KebabOption => { return { - label: 'Edit Application', - callback: () => editApplication({ editAppResource: obj }), + label: 'Edit', + href: `/edit?name=${obj.metadata.name}&kind=${obj.kind}`, accessReview: { group: model.apiGroup, resource: model.plural, diff --git a/frontend/packages/dev-console/src/components/edit-application/EditApplication.tsx b/frontend/packages/dev-console/src/components/edit-application/EditApplication.tsx index 1acb51cb75b..a8d3b9a0485 100644 --- a/frontend/packages/dev-console/src/components/edit-application/EditApplication.tsx +++ b/frontend/packages/dev-console/src/components/edit-application/EditApplication.tsx @@ -1,113 +1,158 @@ import * as React from 'react'; import { Formik } from 'formik'; import * as _ from 'lodash'; +import { connect } from 'react-redux'; +import * as plugins from '@console/internal/plugins'; +import { getActivePerspective } from '@console/internal/reducers/ui'; +import { RootState } from '@console/internal/redux'; +import { history } from '@console/internal/components/utils'; import { NormalizedBuilderImages, normalizeBuilderImages } from '../../utils/imagestream-utils'; import { createOrUpdateResources } from '../import/import-submit-utils'; import { validationSchema } from '../import/import-validation-utils'; +import { createOrUpdateDeployImageResources } from '../import/deployImage-submit-utils'; +import { deployValidationSchema } from '../import/deployImage-validation-utils'; import EditApplicationForm from './EditApplicationForm'; import { EditApplicationProps } from './edit-application-types'; -import * as EditApplicationUtils from './edit-application-utils'; +import { + getPageHeading, + getCommonInitialValues, + getGitAndDockerfileInitialValues, + getInternalImageInitialValues, + getExternalImageInitialValues, +} from './edit-application-utils'; -const EditApplication: React.FC = ({ +export interface StateProps { + perspective: string; +} + +const EditApplication: React.FC = ({ + perspective, namespace, appName, - editAppResource, resources: appResources, - onCancel, - onSubmit, }) => { - const builderImages: NormalizedBuilderImages = + const imageStreamsData = appResources.imageStreams && appResources.imageStreams.loaded - ? normalizeBuilderImages(appResources.imageStreams.data) - : null; + ? appResources.imageStreams.data + : []; - const currentImage = _.split( - _.get(appResources, 'buildConfig.data.spec.strategy.sourceStrategy.from.name', ''), - ':', - ); + const builderImages: NormalizedBuilderImages = !_.isEmpty(imageStreamsData) + ? normalizeBuilderImages(imageStreamsData) + : null; - const appGroupName = _.get(editAppResource, 'metadata.labels["app.kubernetes.io/part-of"]'); + const getInitialValues = () => { + const commonValues = getCommonInitialValues( + appResources.editAppResource.data, + appResources.route.data, + appName, + namespace, + ); - const initialValues = { - formType: 'edit', - name: appName, - application: { - name: appGroupName, - selectedKey: appGroupName, - }, - project: { - name: namespace, - }, - git: EditApplicationUtils.getGitData(_.get(appResources, 'buildConfig.data')), - docker: { - dockerfilePath: _.get( - appResources, - 'buildConfig.data.spec.strategy.dockerStrategy.dockerfilePath', - 'Dockerfile', - ), - containerPort: parseInt( - _.split(_.get(appResources, 'route.data.spec.port.targetPort'), '-')[0], - 10, - ), - }, - image: { - selected: currentImage[0] || '', - recommended: '', - tag: currentImage[1] || '', - tagObj: {}, - ports: [], - isRecommending: false, - couldNotRecommend: false, - }, - route: EditApplicationUtils.getRouteData(_.get(appResources, 'route.data'), editAppResource), - resources: EditApplicationUtils.getResourcesType(editAppResource), - serverless: EditApplicationUtils.getServerlessData(editAppResource), - pipeline: { - enabled: false, - }, - build: EditApplicationUtils.getBuildData(_.get(appResources, 'buildConfig.data')), - deployment: EditApplicationUtils.getDeploymentData(editAppResource), - labels: EditApplicationUtils.getUserLabels(editAppResource), - limits: EditApplicationUtils.getLimitsData(editAppResource), + const gitDockerValues = getGitAndDockerfileInitialValues( + appResources.buildConfig.data, + appResources.route.data, + ); + + let externalImageValues = {}; + let internalImageValues = {}; + + if (_.isEmpty(gitDockerValues)) { + externalImageValues = getExternalImageInitialValues( + appResources.imageStream.data, + appResources.editAppResource.data, + ); + internalImageValues = _.isEmpty(externalImageValues) + ? getInternalImageInitialValues(appResources.editAppResource.data) + : {}; + } + console.log('internal values ----', internalImageValues); + return { + ...commonValues, + ...gitDockerValues, + ...externalImageValues, + ...internalImageValues, + }; }; - const handleSubmit = (values, actions) => { - const imageStream = - values.image.selected && builderImages ? builderImages[values.image.selected].obj : null; + const initialValues = getInitialValues(); + console.log('values ------------------', initialValues); + + const pageHeading = getPageHeading(_.get(initialValues, 'build.strategy', '')); - createOrUpdateResources( - values, - imageStream, - false, - false, - 'update', - appResources, - editAppResource, - ) - .then(() => { - actions.setSubmitting(false); - actions.setStatus({ error: '' }); - onSubmit(); - }) - .catch((err) => { - actions.setSubmitting(false); - actions.setStatus({ error: err.message }); - }); + const handleRedirect = (project: string) => { + const perspectiveData = plugins.registry + .getPerspectives() + .find((item) => item.properties.id === perspective); + const redirectURL = perspectiveData.properties.getImportRedirectURL(project); + history.push(redirectURL); + }; + + const handleSubmit = (values, actions) => { + if (!_.isEmpty(values.build.strategy)) { + const imageStream = + values.image.selected && builderImages ? builderImages[values.image.selected].obj : null; + createOrUpdateResources(values, imageStream, false, false, 'update', appResources) + .then(() => { + actions.setSubmitting(false); + actions.setStatus({ error: '' }); + handleRedirect(namespace); + }) + .catch((err) => { + actions.setSubmitting(false); + actions.setStatus({ error: err.message }); + }); + } else { + createOrUpdateDeployImageResources( + values, + false, + 'update', + appResources, + _.get(initialValues, 'imageStream.namespace'), + ) + .then(() => { + actions.setSubmitting(false); + actions.setStatus({ error: '' }); + handleRedirect(namespace); + }) + .catch((err) => { + actions.setSubmitting(false); + actions.setStatus({ error: err.message }); + }); + } }; const renderForm = (props) => { - return ; + return ( + + ); }; return ( ); }; -export default EditApplication; +const mapStateToProps = (state: RootState) => { + const perspective = getActivePerspective(state); + return { + perspective, + }; +}; + +export default connect(mapStateToProps)(EditApplication); diff --git a/frontend/packages/dev-console/src/components/edit-application/EditApplicationForm.tsx b/frontend/packages/dev-console/src/components/edit-application/EditApplicationForm.tsx index 7ab1f8c91c6..2411dd693f5 100644 --- a/frontend/packages/dev-console/src/components/edit-application/EditApplicationForm.tsx +++ b/frontend/packages/dev-console/src/components/edit-application/EditApplicationForm.tsx @@ -1,16 +1,19 @@ import * as React from 'react'; import * as _ from 'lodash'; import { FormikProps, FormikValues } from 'formik'; -import { ModalTitle, ModalBody, ModalSubmitFooter } from '@console/internal/components/factory'; -import { BuildStrategyType } from '@console/internal/components/build'; +import { Form, ActionGroup, Button, ButtonVariant } from '@patternfly/react-core'; +import { ButtonBar, PageHeading } from '@console/internal/components/utils'; import GitSection from '../import/git/GitSection'; import BuilderSection from '../import/builder/BuilderSection'; import DockerSection from '../import/git/DockerSection'; import AdvancedSection from '../import/advanced/AdvancedSection'; import AppSection from '../import/app/AppSection'; import { NormalizedBuilderImages } from '../../utils/imagestream-utils'; +import ImageSearchSection from '../import/image-search/ImageSearchSection'; +import { CreateApplicationFlow } from './edit-application-utils'; export interface EditApplicationFormProps { + pageHeading: string; builderImages?: NormalizedBuilderImages; } @@ -18,33 +21,43 @@ const EditApplicationForm: React.FC & EditApplicationF handleSubmit, handleReset, values, + pageHeading, builderImages, dirty, errors, status, isSubmitting, }) => ( -
- Edit Application - - {!_.isEmpty(values.build.strategy) && } - {values.build.strategy === BuildStrategyType.Source && ( + <> + + + {pageHeading !== CreateApplicationFlow.Container && } + {pageHeading === CreateApplicationFlow.Git && ( )} - {values.build.strategy === BuildStrategyType.Docker && ( + {pageHeading === CreateApplicationFlow.Dockerfile && ( )} + {pageHeading === CreateApplicationFlow.Container && } - - - + + + + + + + + ); export default EditApplicationForm; diff --git a/frontend/packages/dev-console/src/components/edit-application/EditApplicationPage.tsx b/frontend/packages/dev-console/src/components/edit-application/EditApplicationPage.tsx new file mode 100644 index 00000000000..e22328a1b86 --- /dev/null +++ b/frontend/packages/dev-console/src/components/edit-application/EditApplicationPage.tsx @@ -0,0 +1,89 @@ +import * as React from 'react'; +import { Firehose, FirehoseResource, LoadingBox } from '@console/internal/components/utils'; +import { ImageStreamModel } from '@console/internal/models'; +import { RouteComponentProps } from 'react-router-dom'; +import { Helmet } from 'react-helmet'; +import { ServiceModel } from '@console/knative-plugin'; +import { referenceForModel } from '@console/internal/module/k8s'; +import NamespacedPage, { NamespacedPageVariants } from '../NamespacedPage'; +import EditApplication from './EditApplication'; +import { EditApplicationProps } from './edit-application-types'; + +const EditApplicationComponentLoader: React.FunctionComponent = ( + props: EditApplicationProps, +) => { + const { loaded } = props; + return loaded ? : ; +}; + +export type ImportPageProps = RouteComponentProps<{ ns?: string }>; + +const EditApplicationPage: React.FunctionComponent = ({ match, location }) => { + const namespace = match.params.ns; + const queryParams = new URLSearchParams(location.search); + const editAppResourceKind = queryParams.get('kind'); + const appName = queryParams.get('name'); + const appResources: FirehoseResource[] = [ + { + kind: 'Service', + prop: 'service', + name: appName, + namespace, + optional: true, + }, + { + kind: 'BuildConfig', + prop: 'buildConfig', + name: appName, + namespace, + optional: true, + }, + { + kind: 'Route', + prop: 'route', + name: appName, + namespace, + optional: true, + }, + { + kind: 'ImageStream', + prop: 'imageStream', + name: appName, + namespace, + optional: true, + }, + { + kind: ImageStreamModel.kind, + prop: 'imageStreams', + isList: true, + namespace: 'openshift', + optional: true, + }, + ]; + let kind = editAppResourceKind; + if (kind === ServiceModel.kind) { + kind = referenceForModel(ServiceModel); + } + appResources.push({ + kind, + prop: 'editAppResource', + name: appName, + namespace, + optional: true, + }); + + return ( + + + Edit + +
+ + + +
+
+ ); +}; + +export default EditApplicationPage; diff --git a/frontend/packages/dev-console/src/components/edit-application/EditApplicationWrapper.tsx b/frontend/packages/dev-console/src/components/edit-application/EditApplicationWrapper.tsx deleted file mode 100644 index 4f4196bc700..00000000000 --- a/frontend/packages/dev-console/src/components/edit-application/EditApplicationWrapper.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { K8sResourceKind } from '@console/internal/module/k8s'; -import * as React from 'react'; -import { Firehose, FirehoseResource } from '@console/internal/components/utils'; -import { ImageStreamModel } from '@console/internal/models'; -import { createModalLauncher, ModalComponentProps } from '@console/internal/components/factory'; -import EditApplication from './EditApplication'; -import { EditApplicationProps } from './edit-application-types'; - -interface EditApplicationWrapperProps { - editAppResource: K8sResourceKind; -} - -type Props = EditApplicationWrapperProps & ModalComponentProps; - -const EditApplicationComponentLoader: React.FunctionComponent = ( - props: EditApplicationProps, -) => { - const { loaded } = props; - return loaded && ; -}; - -const EditApplicationWrapper: React.FunctionComponent = ({ - editAppResource, - cancel, - close, -}) => { - const { name, namespace } = editAppResource.metadata; - const appResources: FirehoseResource[] = [ - { - kind: 'Service', - prop: 'service', - name, - namespace, - optional: true, - }, - { - kind: 'BuildConfig', - prop: 'buildConfig', - name, - namespace, - optional: true, - }, - { - kind: 'Route', - prop: 'route', - name, - namespace, - optional: true, - }, - { - kind: ImageStreamModel.kind, - prop: 'imageStreams', - isList: true, - namespace: 'openshift', - optional: true, - }, - ]; - - return ( - - - - ); -}; - -export const editApplication = createModalLauncher((props: Props) => ( - -)); - -export default EditApplicationWrapper; diff --git a/frontend/packages/dev-console/src/components/edit-application/edit-application-types.ts b/frontend/packages/dev-console/src/components/edit-application/edit-application-types.ts index 2028d5f3218..a60481e2a64 100644 --- a/frontend/packages/dev-console/src/components/edit-application/edit-application-types.ts +++ b/frontend/packages/dev-console/src/components/edit-application/edit-application-types.ts @@ -2,18 +2,17 @@ import { K8sResourceKind } from '@console/internal/module/k8s'; import { FirehoseResult } from '@console/internal/components/utils'; export interface AppResources { - service?: FirehoseResult; - route?: FirehoseResult; - buildConfig?: FirehoseResult; + service?: FirehoseResult; + route?: FirehoseResult; + buildConfig?: FirehoseResult; + imageStream?: FirehoseResult; + editAppResource?: FirehoseResult; imageStreams?: FirehoseResult; } export interface EditApplicationProps { namespace: string; appName: string; - editAppResource: K8sResourceKind; resources?: AppResources; loaded?: boolean; - onCancel?: () => void; - onSubmit?: () => void; } diff --git a/frontend/packages/dev-console/src/components/edit-application/edit-application-utils.ts b/frontend/packages/dev-console/src/components/edit-application/edit-application-utils.ts index cd64f30c321..cd5aa08bcae 100644 --- a/frontend/packages/dev-console/src/components/edit-application/edit-application-utils.ts +++ b/frontend/packages/dev-console/src/components/edit-application/edit-application-utils.ts @@ -5,6 +5,12 @@ import { DeploymentConfigModel, DeploymentModel } from '@console/internal/models import { ServiceModel } from '@console/knative-plugin'; import { Resources } from '../import/import-types'; +export enum CreateApplicationFlow { + Git = 'Import from Git', + Dockerfile = 'Import from Dockerfile', + Container = 'Deploy Image', +} + export const getResourcesType = (resource: K8sResourceKind): string => { switch (resource.kind) { case DeploymentConfigModel.kind: @@ -18,6 +24,17 @@ export const getResourcesType = (resource: K8sResourceKind): string => { } }; +export const getPageHeading = (buildStrategy: string): string => { + switch (buildStrategy) { + case BuildStrategyType.Source: + return CreateApplicationFlow.Git; + case BuildStrategyType.Docker: + return CreateApplicationFlow.Dockerfile; + default: + return CreateApplicationFlow.Container; + } +}; + const checkIfTriggerExists = (triggers: { [key: string]: string | {} }[], type: string) => { return !!_.find(triggers, (trigger) => { return trigger.type === type; @@ -40,10 +57,11 @@ export const getGitData = (buildConfig: K8sResourceKind) => { export const getRouteData = (route: K8sResourceKind, resource: K8sResourceKind) => { let routeData = { - show: _.isEmpty(route), + disable: !_.isEmpty(route), create: true, targetPort: _.get(route, 'spec.port.targetPort', ''), unknownTargetPort: '', + defaultUnknownPort: 8080, path: _.get(route, 'spec.path', ''), hostname: _.get(route, 'spec.host', ''), secure: _.has(route, 'spec.termination'), @@ -61,8 +79,8 @@ export const getRouteData = (route: K8sResourceKind, resource: K8sResourceKind) const port = _.get(containers[0], 'ports[0].containerPort', ''); routeData = { ...routeData, - show: - _.get(resource, 'metadata.labels["serving.knative.dev/visibility"]', '') === + disable: + _.get(resource, 'metadata.labels["serving.knative.dev/visibility"]', '') !== 'cluster-local', unknownTargetPort: _.toString(port), }; @@ -166,8 +184,160 @@ export const getUserLabels = (resource: K8sResourceKind) => { 'app.openshift.io/runtime', 'app.kubernetes.io/part-of', 'app.openshift.io/runtime-version', + 'app/runtime-namespace', ]; const allLabels = _.get(resource, 'metadata.labels', {}); const userLabels = _.omit(allLabels, defaultLabels); return userLabels; }; + +export const getCommonInitialValues = ( + editAppResource: K8sResourceKind, + route: K8sResourceKind, + name: string, + namespace: string, +) => { + const appGroupName = _.get(editAppResource, 'metadata.labels["app.kubernetes.io/part-of"]', ''); + const commonInitialValues = { + formType: 'edit', + name, + application: { + name: appGroupName, + selectedKey: appGroupName, + }, + project: { + name: namespace, + }, + route: getRouteData(route, editAppResource), + resources: getResourcesType(editAppResource), + serverless: getServerlessData(editAppResource), + pipeline: { + enabled: false, + }, + deployment: getDeploymentData(editAppResource), + labels: getUserLabels(editAppResource), + limits: getLimitsData(editAppResource), + }; + return commonInitialValues; +}; + +export const getGitAndDockerfileInitialValues = ( + buildConfig: K8sResourceKind, + route: K8sResourceKind, +) => { + if (_.isEmpty(buildConfig)) { + return {}; + } + const currentImage = _.split( + _.get(buildConfig, 'spec.strategy.sourceStrategy.from.name', ''), + ':', + ); + const initialValues = { + git: getGitData(buildConfig), + docker: { + dockerfilePath: _.get( + buildConfig, + 'spec.strategy.dockerStrategy.dockerfilePath', + 'Dockerfile', + ), + containerPort: parseInt(_.split(_.get(route, 'spec.port.targetPort'), '-')[0], 10), + }, + image: { + selected: currentImage[0] || '', + recommended: '', + tag: currentImage[1] || '', + tagObj: {}, + ports: [], + isRecommending: false, + couldNotRecommend: false, + }, + build: getBuildData(buildConfig), + }; + return initialValues; +}; + +export const getExternalImageInitialValues = ( + imageStream: K8sResourceKind, + editAppResource: K8sResourceKind, +) => { + if (_.isEmpty(imageStream)) { + return {}; + } + const name = _.get(imageStream, 'spec.tags[0].from.name'); + const tag = _.get(imageStream, 'status.tags[0].tag'); + const imageRef = _.get(imageStream, 'status.tags[0].items[0].dockerImageReference'); + const ports = _.get(editAppResource, 'spec.template.spec.containers[0].ports'); + const deployImageInitialValues = { + searchTerm: name, + registry: 'external', + imageStream: { + image: '', + tag: '', + namespace: '', + grantAccess: true, + }, + isi: { + name, + image: { + dockerImageReference: imageRef, + }, + tag, + status: { metadata: {}, status: '' }, + ports, + }, + image: { + name: '', + image: {}, + tag, + status: { metadata: {}, status: '' }, + ports, + }, + build: { + strategy: '', + }, + isSearchingForImage: false, + }; + return deployImageInitialValues; +}; + +export const getInternalImageInitialValues = (editAppResource: K8sResourceKind) => { + const imageStreamNamespace = _.get( + editAppResource, + 'metadata.labels["app/runtime-namespace"]', + '', + ); + const imageStreamName = _.get(editAppResource, 'metadata.labels["app.openshift.io/runtime"]', ''); + const internalImageStreamTag = _.get( + editAppResource, + 'metadata.labels["app.openshift.io/runtime-version"]', + '', + ); + const deployImageInitialValues = { + searchTerm: '', + registry: 'internal', + imageStream: { + image: imageStreamName, + tag: internalImageStreamTag, + namespace: imageStreamNamespace, + }, + isi: { + name: '', + image: {}, + tag: '', + status: { metadata: {}, status: '' }, + ports: '', + }, + image: { + name: '', + image: {}, + tag: '', + status: { metadata: {}, status: '' }, + ports: '', + }, + build: { + strategy: '', + }, + isSearchingForImage: false, + }; + return deployImageInitialValues; +}; diff --git a/frontend/packages/dev-console/src/components/import/DeployImage.tsx b/frontend/packages/dev-console/src/components/import/DeployImage.tsx index 3f8a96b8391..a4516d75d38 100644 --- a/frontend/packages/dev-console/src/components/import/DeployImage.tsx +++ b/frontend/packages/dev-console/src/components/import/DeployImage.tsx @@ -7,7 +7,7 @@ import { connect } from 'react-redux'; import { ALL_APPLICATIONS_KEY } from '@console/shared'; import { K8sResourceKind } from '@console/internal/module/k8s'; import { DeployImageFormData, FirehoseList, Resources } from './import-types'; -import { createResources } from './deployImage-submit-utils'; +import { createOrUpdateDeployImageResources } from './deployImage-submit-utils'; import { deployValidationSchema } from './deployImage-validation-utils'; import DeployImageForm from './DeployImageForm'; @@ -67,7 +67,7 @@ const DeployImage: React.FC = ({ namespace, projects, activeApplication } }, }, route: { - show: true, + disable: false, create: true, targetPort: '', unknownTargetPort: '', @@ -129,10 +129,13 @@ const DeployImage: React.FC = ({ namespace, projects, activeApplication } project: { name: projectName }, } = values; - const dryRunRequests: Promise = createResources(values, true); + const dryRunRequests: Promise = createOrUpdateDeployImageResources( + values, + true, + ); dryRunRequests .then(() => { - const requests: Promise = createResources(values); + const requests: Promise = createOrUpdateDeployImageResources(values); return requests; }) .then(() => { diff --git a/frontend/packages/dev-console/src/components/import/ImportForm.tsx b/frontend/packages/dev-console/src/components/import/ImportForm.tsx index 4e490c55ad6..5a36d032c26 100644 --- a/frontend/packages/dev-console/src/components/import/ImportForm.tsx +++ b/frontend/packages/dev-console/src/components/import/ImportForm.tsx @@ -71,7 +71,7 @@ const ImportForm: React.FC = ({ couldNotRecommend: false, }, route: { - show: true, + disable: false, create: true, targetPort: '', path: '', diff --git a/frontend/packages/dev-console/src/components/import/__tests__/deployImage-submit-utils.spec.ts b/frontend/packages/dev-console/src/components/import/__tests__/deployImage-submit-utils.spec.ts index ca66f2404ba..7669647356f 100644 --- a/frontend/packages/dev-console/src/components/import/__tests__/deployImage-submit-utils.spec.ts +++ b/frontend/packages/dev-console/src/components/import/__tests__/deployImage-submit-utils.spec.ts @@ -18,7 +18,11 @@ import { internalImageData, } from './deployImage-submit-utils-data'; -const { ensurePortExists, createDeployment, createResources } = submitUtils; +const { + ensurePortExists, + createOrUpdateDeployment, + createOrUpdateDeployImageResources, +} = submitUtils; describe('DeployImage Submit Utils', () => { describe('Ensure Port Exists', () => { @@ -71,7 +75,7 @@ describe('DeployImage Submit Utils', () => { }); it('should choose image from dockerImageReference when creating Deployment using internal imagestream', (done) => { - createDeployment(internalImageData, false) + createOrUpdateDeployment(internalImageData, false) .then((returnValue) => { expect(_.get(returnValue, 'model.kind')).toEqual(DeploymentModel.kind); expect(_.get(returnValue, 'data.spec.template.spec.containers[0].image')).toEqual( @@ -105,7 +109,7 @@ describe('DeployImage Submit Utils', () => { }, }; - createDeployment(data, false) + createOrUpdateDeployment(data, false) .then((returnValue) => { expect(_.get(returnValue, 'data.spec.template.spec.containers[0].resources')).toEqual({ limits: { cpu: '10m', memory: '200Mi' }, @@ -131,7 +135,7 @@ describe('DeployImage Submit Utils', () => { }); it('should call createImageStream when creating Resources using external image', (done) => { - createResources(defaultData, false) + createOrUpdateDeployImageResources(defaultData, false) .then((returnValue) => { expect(returnValue).toHaveLength(4); const models = returnValue.map((data) => _.get(data, 'model.kind')); @@ -149,7 +153,7 @@ describe('DeployImage Submit Utils', () => { }); it('should not call createImageStream when creating Resources using internal imagestream', (done) => { - createResources(internalImageData, false) + createOrUpdateDeployImageResources(internalImageData, false) .then((returnValue) => { expect(returnValue).toHaveLength(3); const models = returnValue.map((data) => _.get(data, 'model.kind')); @@ -166,14 +170,14 @@ describe('DeployImage Submit Utils', () => { mockData.resources = Resources.KnativeService; const imageStreamSpy = jest - .spyOn(submitUtils, 'createImageStream') + .spyOn(submitUtils, 'createOrUpdateImageStream') .mockImplementation(() => ({ status: { dockerImageReference: 'test:1234', }, })); - createResources(mockData, false) + createOrUpdateDeployImageResources(mockData, false) .then((returnValue) => { // createImageStream is called as separate entity expect(imageStreamSpy).toHaveBeenCalled(); diff --git a/frontend/packages/dev-console/src/components/import/__tests__/import-submit-utils.spec.ts b/frontend/packages/dev-console/src/components/import/__tests__/import-submit-utils.spec.ts index 208dcca634e..bb0c61dcbe3 100644 --- a/frontend/packages/dev-console/src/components/import/__tests__/import-submit-utils.spec.ts +++ b/frontend/packages/dev-console/src/components/import/__tests__/import-submit-utils.spec.ts @@ -143,7 +143,7 @@ describe('Import Submit Utils', () => { mockData.resources = Resources.KnativeService; const imageStreamSpy = jest - .spyOn(submitUtils, 'createImageStream') + .spyOn(submitUtils, 'createOrUpdateImageStream') .mockImplementation(() => ({ status: { dockerImageReference: 'test:1234', diff --git a/frontend/packages/dev-console/src/components/import/advanced/AdvancedSection.tsx b/frontend/packages/dev-console/src/components/import/advanced/AdvancedSection.tsx index 3c5598247cc..a13705bfaa9 100644 --- a/frontend/packages/dev-console/src/components/import/advanced/AdvancedSection.tsx +++ b/frontend/packages/dev-console/src/components/import/advanced/AdvancedSection.tsx @@ -26,7 +26,7 @@ const AdvancedSection: React.FC = ({ values }) => { return ( - {values.route.show && } + => { const { project: { name: namespace }, @@ -62,7 +65,7 @@ export const createImageStream = ( labels: userLabels, } = formData; const defaultLabels = getAppLabels(name, application); - const imageStream = { + const newImageStream = { apiVersion: 'image.openshift.io/v1', kind: 'ImageStream', metadata: { @@ -88,7 +91,11 @@ export const createImageStream = ( }, }; - return k8sCreate(ImageStreamModel, imageStream, dryRun ? dryRunOpt : {}); + const imageStream = _.merge({}, originalImageStream || {}, newImageStream); + + return verb === 'update' + ? k8sUpdate(ImageStreamModel, imageStream) + : k8sCreate(ImageStreamModel, imageStream, dryRun ? dryRunOpt : {}); }; const getMetadata = (formData: DeployImageFormData) => { @@ -97,9 +104,16 @@ const getMetadata = (formData: DeployImageFormData) => { name, isi: { image }, labels: userLabels, + imageStream: { image: imgName, tag: imgTag, namespace: imgNamespace }, } = formData; - const defaultLabels = getAppLabels(name, application); + const defaultLabels = getAppLabels( + name, + application, + !_.isEmpty(imgName) ? imgName : undefined, + !_.isEmpty(imgTag) ? imgTag : undefined, + !_.isEmpty(imgNamespace) ? imgNamespace : undefined, + ); const labels = { ...defaultLabels, ...userLabels }; const podLabels = getPodLabels(name); @@ -122,9 +136,11 @@ const getMetadata = (formData: DeployImageFormData) => { return { labels, podLabels, volumes, volumeMounts }; }; -export const createDeployment = ( +export const createOrUpdateDeployment = ( formData: DeployImageFormData, dryRun: boolean, + originalDeployment?: K8sResourceKind, + verb: K8sVerb = 'create', ): Promise => { const { registry, @@ -159,7 +175,7 @@ export const createDeployment = ( ? `${imgName || name}:${tag}` : _.get(image, 'dockerImageReference'); - const deployment = { + const newDeployment = { kind: 'Deployment', apiVersion: 'apps/v1', metadata: { @@ -209,12 +225,19 @@ export const createDeployment = ( }, }, }; - return k8sCreate(DeploymentModel, deployment, dryRun ? dryRunOpt : {}); + + const deployment = _.merge({}, originalDeployment || {}, newDeployment); + + return verb === 'update' + ? k8sUpdate(DeploymentModel, deployment) + : k8sCreate(DeploymentModel, deployment, dryRun ? dryRunOpt : {}); }; -export const createDeploymentConfig = ( +export const createOrUpdateDeploymentConfig = ( formData: DeployImageFormData, dryRun: boolean, + originalDeploymentConfig?: K8sResourceKind, + verb: K8sVerb = 'create', ): Promise => { const { project: { name: namespace }, @@ -228,7 +251,7 @@ export const createDeploymentConfig = ( const { labels, podLabels, volumes, volumeMounts } = getMetadata(formData); - const deploymentConfig = { + const newDeploymentConfig = { kind: 'DeploymentConfig', apiVersion: 'apps.openshift.io/v1', metadata: { @@ -290,7 +313,11 @@ export const createDeploymentConfig = ( }, }; - return k8sCreate(DeploymentConfigModel, deploymentConfig, dryRun ? dryRunOpt : {}); + const deploymentConfig = _.merge({}, originalDeploymentConfig || {}, newDeploymentConfig); + + return verb === 'update' + ? k8sUpdate(DeploymentConfigModel, deploymentConfig) + : k8sCreate(DeploymentConfigModel, deploymentConfig, dryRun ? dryRunOpt : {}); }; export const ensurePortExists = (formData: DeployImageFormData): DeployImageFormData => { @@ -317,45 +344,85 @@ export const ensurePortExists = (formData: DeployImageFormData): DeployImageForm return values; }; -export const createResources = async ( +export const createOrUpdateDeployImageResources = async ( rawFormData: DeployImageFormData, dryRun: boolean = false, + verb: K8sVerb = 'create', + appResources?: AppResources, + initialInternalImageStreamNamespace: string = '', ): Promise => { const formData = ensurePortExists(rawFormData); const { registry, - route: { create: canCreateRoute }, + route: { create: canCreateRoute, disable }, isi: { ports, tag: imageStreamTag }, + imageStream: { namespace: imgNamespace }, } = formData; const requests: Promise[] = []; - if (registry === RegistryType.Internal) { + if (registry === RegistryType.Internal && initialInternalImageStreamNamespace !== imgNamespace) { formData.imageStream.grantAccess && requests.push(createSystemImagePullerRoleBinding(formData, dryRun)); } if (formData.resources !== Resources.KnativeService) { - registry === RegistryType.External && requests.push(createImageStream(formData, dryRun)); + registry === RegistryType.External && + requests.push( + createOrUpdateImageStream(formData, dryRun, _.get(appResources, 'imageStream.data'), verb), + ); if (formData.resources === Resources.Kubernetes) { - requests.push(createDeployment(formData, dryRun)); + requests.push( + createOrUpdateDeployment( + formData, + dryRun, + _.get(appResources, 'editAppResource.data'), + verb, + ), + ); } else { - requests.push(createDeploymentConfig(formData, dryRun)); + requests.push( + createOrUpdateDeploymentConfig( + formData, + dryRun, + _.get(appResources, 'editAppResource.data'), + verb, + ), + ); } if (!_.isEmpty(ports)) { - const service = createService(formData); - requests.push(k8sCreate(ServiceModel, service, dryRun ? dryRunOpt : {})); - if (canCreateRoute) { - const route = createRoute(formData); + const service = createService(formData, undefined, _.get(appResources, 'service.data')); + requests.push( + verb === 'update' + ? k8sUpdate(ServiceModel, service) + : k8sCreate(ServiceModel, service, dryRun ? dryRunOpt : {}), + ); + const route = createRoute(formData, undefined, _.get(appResources, 'route.data')); + if (verb === 'update' && disable) { + requests.push(k8sUpdate(RouteModel, route)); + } else if (canCreateRoute) { requests.push(k8sCreate(RouteModel, route, dryRun ? dryRunOpt : {})); } } } else if (!dryRun) { // Do not run serverless call during the dry run. - const imageStreamResponse = await createImageStream(formData, dryRun); + const imageStreamResponse = await createOrUpdateImageStream( + formData, + dryRun, + _.get(appResources, 'imageStream.data'), + verb, + ); const imageStreamUrl = imageStreamTag ? `${imageStreamResponse.status.dockerImageRepository}:${imageStreamTag}` : imageStreamResponse.status.dockerImageRepository; - const knDeploymentResource = getKnativeServiceDepResource(formData, imageStreamUrl); - requests.push(k8sCreate(KnServiceModel, knDeploymentResource)); + const knDeploymentResource = getKnativeServiceDepResource( + formData, + imageStreamUrl, + _.get(appResources, 'editAppResource.data'), + ); + requests.push( + verb === 'update' + ? k8sUpdate(KnServiceModel, knDeploymentResource) + : k8sCreate(KnServiceModel, knDeploymentResource), + ); } return Promise.all(requests); diff --git a/frontend/packages/dev-console/src/components/import/image-search/ImageSearchSection.tsx b/frontend/packages/dev-console/src/components/import/image-search/ImageSearchSection.tsx index 0304ecbb335..38d4e13689e 100644 --- a/frontend/packages/dev-console/src/components/import/image-search/ImageSearchSection.tsx +++ b/frontend/packages/dev-console/src/components/import/image-search/ImageSearchSection.tsx @@ -38,11 +38,13 @@ const ImageSearchSection: React.FC = () => { { label: imageRegistryType.External.label, value: imageRegistryType.External.value, + isDisabled: values.formType === 'edit' && values.registry === 'internal', activeChildren: , }, { label: imageRegistryType.Internal.label, value: imageRegistryType.Internal.value, + isDisabled: values.formType === 'edit' && values.registry === 'external', activeChildren: , }, ]} diff --git a/frontend/packages/dev-console/src/components/import/image-search/ImageStreamDropdown.tsx b/frontend/packages/dev-console/src/components/import/image-search/ImageStreamDropdown.tsx index f2118babb3b..fa5d609c2fb 100644 --- a/frontend/packages/dev-console/src/components/import/image-search/ImageStreamDropdown.tsx +++ b/frontend/packages/dev-console/src/components/import/image-search/ImageStreamDropdown.tsx @@ -63,7 +63,7 @@ const ImageStreamDropdown: React.FC = () => { key={imageStream.namespace} fullWidth required - title={getTitle()} + title={!_.isEmpty(imageStream.image) ? imageStream.image : getTitle()} disabled={!hasCreateAccess || !isStreamsAvailable} onChange={onDropdownChange} onLoad={onLoad} diff --git a/frontend/packages/dev-console/src/components/import/image-search/ImageStreamTagDropdown.tsx b/frontend/packages/dev-console/src/components/import/image-search/ImageStreamTagDropdown.tsx index 7d6b049703f..c39d6179673 100644 --- a/frontend/packages/dev-console/src/components/import/image-search/ImageStreamTagDropdown.tsx +++ b/frontend/packages/dev-console/src/components/import/image-search/ImageStreamTagDropdown.tsx @@ -15,7 +15,7 @@ import { ImageStreamContext } from './ImageStreamContext'; const ImageStreamTagDropdown: React.FC = () => { let imageStreamTagList = {}; const { - values: { imageStream, application }, + values: { imageStream, application, formType }, setFieldValue, setFieldError, } = useFormikContext(); @@ -42,7 +42,7 @@ const ImageStreamTagDropdown: React.FC = () => { setFieldValue('isi.tag', selectedTag); setFieldValue('isi.ports', ports); setFieldValue('image.ports', ports); - setFieldValue('name', getSuggestedName(name)); + formType !== 'edit' && setFieldValue('name', getSuggestedName(name)); !application.name && setFieldValue('application.name', `${getSuggestedName(name)}-app`); // set default port value const targetPort = _.head(ports); @@ -54,7 +54,14 @@ const ImageStreamTagDropdown: React.FC = () => { setFieldValue('isSearchingForImage', false); }); }, - [setFieldValue, imageStream, application.name, setFieldError], + [ + setFieldValue, + imageStream.image, + imageStream.namespace, + formType, + application.name, + setFieldError, + ], ); return ( { items={imageStreamTagList} key={imageStream.image} title={ - isNamespaceSelected && isImageStreamSelected && !isTagsAvailable ? 'No Tag' : 'Select Tag' + !_.isEmpty(imageStream.tag) + ? imageStream.tag + : isNamespaceSelected && isImageStreamSelected && !isTagsAvailable + ? 'No Tag' + : 'Select Tag' } disabled={!isImageStreamSelected || !isTagsAvailable} fullWidth diff --git a/frontend/packages/dev-console/src/components/import/image-search/SearchResults.tsx b/frontend/packages/dev-console/src/components/import/image-search/SearchResults.tsx index 6703744ae37..d8d3326c395 100644 --- a/frontend/packages/dev-console/src/components/import/image-search/SearchResults.tsx +++ b/frontend/packages/dev-console/src/components/import/image-search/SearchResults.tsx @@ -47,7 +47,9 @@ const SearchResults: React.FC = () => { {_.get(values.isi, 'result.ref.registry') && ( from {values.isi.result.ref.registry}, )} - ,{' '} + {_.get(values.isi, 'image.dockerImageMetadata.Created') && ( + + )} {_.get(values.isi, 'image.dockerImageMetadata.Size') && ( { @@ -80,7 +82,7 @@ const SearchResults: React.FC = () => { )} - {!_.isEmpty(values.isi.image.dockerImageMetadata.Config.Volumes) && ( + {!_.isEmpty(_.get(values.isi, 'image.dockerImageMetadata.Config.Volumes')) && (

This image declares volumes and will default to use non-persistent, host-local storage. You can add persistent storage later to the deployment config. diff --git a/frontend/packages/dev-console/src/components/import/import-submit-utils.ts b/frontend/packages/dev-console/src/components/import/import-submit-utils.ts index 0b244eca65d..1b2c4a46ac7 100644 --- a/frontend/packages/dev-console/src/components/import/import-submit-utils.ts +++ b/frontend/packages/dev-console/src/components/import/import-submit-utils.ts @@ -48,10 +48,12 @@ export const createProject = (projectData: ProjectData): Promise => { const { name, @@ -64,7 +66,7 @@ export const createImageStream = ( const imageStreamName = imageStreamData && imageStreamData.metadata.name; const defaultLabels = getAppLabels(name, application, imageStreamName, tag); const defaultAnnotations = getAppAnnotations(repository, ref); - const imageStream = { + const newImageStream = { apiVersion: 'image.openshift.io/v1', kind: 'ImageStream', metadata: { @@ -75,7 +77,11 @@ export const createImageStream = ( }, }; - return k8sCreate(ImageStreamModel, imageStream, dryRun ? dryRunOpt : {}); + const imageStream = _.merge({}, originalImageStream || {}, newImageStream); + + return verb === 'update' + ? k8sUpdate(ImageStreamModel, imageStream) + : k8sCreate(ImageStreamModel, imageStream, dryRun ? dryRunOpt : {}); }; export const createWebhookSecret = ( @@ -155,19 +161,16 @@ export const createOrUpdateBuildConfig = ( }, }; - const buildConfig = { - ...(originalBuildConfig || {}), + const newBuildConfig = { apiVersion: 'build.openshift.io/v1', kind: 'BuildConfig', metadata: { - ...(originalBuildConfig ? originalBuildConfig.metadata : {}), name, namespace, labels: { ...defaultLabels, ...userLabels }, annotations: defaultAnnotations, }, spec: { - ...(originalBuildConfig ? originalBuildConfig.spec : {}), output: { to: { kind: 'ImageStreamTag', @@ -201,6 +204,8 @@ export const createOrUpdateBuildConfig = ( }, }; + const buildConfig = _.merge({}, originalBuildConfig || {}, newBuildConfig); + return verb === 'update' ? k8sUpdate(BuildConfigModel, buildConfig) : k8sCreate(BuildConfigModel, buildConfig, dryRun ? dryRunOpt : {}); @@ -238,19 +243,16 @@ export const createOrUpdateDeployment = ( }; const podLabels = getPodLabels(name); - const deployment = { - ...(originalDeployment || {}), + const newDeployment = { apiVersion: 'apps/v1', kind: 'Deployment', metadata: { - ...(originalDeployment ? originalDeployment.metadata : {}), name, namespace, labels: { ...defaultLabels, ...userLabels }, annotations, }, spec: { - ...(originalDeployment ? originalDeployment.spec : {}), selector: { matchLabels: { app: name, @@ -289,6 +291,8 @@ export const createOrUpdateDeployment = ( }, }; + const deployment = _.merge({}, originalDeployment || {}, newDeployment); + return verb === 'update' ? k8sUpdate(DeploymentModel, deployment) : k8sCreate(DeploymentModel, deployment, dryRun ? dryRunOpt : {}); @@ -317,19 +321,16 @@ export const createOrUpdateDeploymentConfig = ( const defaultAnnotations = getAppAnnotations(repository, ref); const podLabels = getPodLabels(name); - const deploymentConfig = { - ...(originalDeploymentConfig || {}), + const newDeploymentConfig = { apiVersion: 'apps.openshift.io/v1', kind: 'DeploymentConfig', metadata: { - ...(originalDeploymentConfig ? originalDeploymentConfig.metadata : {}), name, namespace, labels: { ...defaultLabels, ...userLabels }, annotations: defaultAnnotations, }, spec: { - ...(originalDeploymentConfig ? originalDeploymentConfig.spec : {}), selector: podLabels, replicas, template: { @@ -378,6 +379,8 @@ export const createOrUpdateDeploymentConfig = ( }, }; + const deploymentConfig = _.merge({}, originalDeploymentConfig || {}, newDeploymentConfig); + return verb === 'update' ? k8sUpdate(DeploymentConfigModel, deploymentConfig) : k8sCreate(DeploymentConfigModel, deploymentConfig, dryRun ? dryRunOpt : {}); @@ -390,12 +393,11 @@ export const createOrUpdateResources = async ( dryRun: boolean = false, verb: K8sVerb = 'create', appResources?: AppResources, - editAppResource?: K8sResourceKind, ): Promise => { const { name, project: { name: namespace }, - route: { create: canCreateRoute, show: showRouteCheckbox }, + route: { create: canCreateRoute, disable }, image: { ports }, build: { strategy: buildStrategy, @@ -410,22 +412,25 @@ export const createOrUpdateResources = async ( const requests: Promise[] = []; - verb === 'create' && - requests.push( - createImageStream(formData, imageStream, dryRun), - createWebhookSecret(formData, 'generic', dryRun), - ); - requests.push( + createOrUpdateImageStream( + formData, + imageStream, + dryRun, + _.get(appResources, 'imageStream.data'), + verb, + ), createOrUpdateBuildConfig( formData, imageStream, dryRun, - _.get(appResources, 'buildConfig.data', null), + _.get(appResources, 'buildConfig.data'), verb, ), ); + verb === 'create' && requests.push(createWebhookSecret(formData, 'generic', dryRun)); + const defaultAnnotations = getAppAnnotations(repository, ref); if (formData.resources === Resources.KnativeService) { @@ -433,49 +438,55 @@ export const createOrUpdateResources = async ( if (dryRun) { return Promise.all(requests); } - let imageStreamURL: string; - const knativeRequests = []; - if (verb === 'update') { - imageStreamURL = _.get(editAppResource, 'spec.template.spec.containers[0].image', ''); - knativeRequests.push(...requests); - } else { - const [imageStreamResponse] = await Promise.all(requests); - imageStreamURL = imageStreamResponse.status.dockerImageRepository; - } + const [imageStreamResponse] = await Promise.all(requests); + const imageStreamURL = imageStreamResponse.status.dockerImageRepository; const knDeploymentResource = getKnativeServiceDepResource( formData, imageStreamURL, imageStreamName, defaultAnnotations, - editAppResource, + _.get(appResources, 'editAppResource.data'), ); - knativeRequests.push( + return Promise.all([ verb === 'update' ? k8sUpdate(KnServiceModel, knDeploymentResource) : k8sCreate(KnServiceModel, knDeploymentResource), - ); - return Promise.all(knativeRequests); + ]); } if (formData.resources === Resources.Kubernetes) { - requests.push(createOrUpdateDeployment(formData, imageStream, dryRun, editAppResource, verb)); + requests.push( + createOrUpdateDeployment( + formData, + imageStream, + dryRun, + _.get(appResources, 'editAppResource.data'), + verb, + ), + ); } else if (formData.resources === Resources.OpenShift) { requests.push( - createOrUpdateDeploymentConfig(formData, imageStream, dryRun, editAppResource, verb), + createOrUpdateDeploymentConfig( + formData, + imageStream, + dryRun, + _.get(appResources, 'editAppResource.data'), + verb, + ), ); } if (!_.isEmpty(ports) || buildStrategy === 'Docker') { - const originalService = _.get(appResources, 'service.data', null); + const originalService = _.get(appResources, 'service.data'); const service = createService(formData, imageStream, originalService); requests.push( verb === 'update' ? k8sUpdate(ServiceModel, service) : k8sCreate(ServiceModel, service, dryRun ? dryRunOpt : {}), ); - const originalRoute = _.get(appResources, 'route.data', null); + const originalRoute = _.get(appResources, 'route.data'); const route = createRoute(formData, imageStream, originalRoute); - if (verb === 'update' && !showRouteCheckbox) { + if (verb === 'update' && disable) { requests.push(k8sUpdate(RouteModel, route, namespace, name)); } else if (canCreateRoute) { requests.push(k8sCreate(RouteModel, route, dryRun ? dryRunOpt : {})); diff --git a/frontend/packages/dev-console/src/components/import/import-types.ts b/frontend/packages/dev-console/src/components/import/import-types.ts index 9a9fcd6beb3..77531f2c081 100644 --- a/frontend/packages/dev-console/src/components/import/import-types.ts +++ b/frontend/packages/dev-console/src/components/import/import-types.ts @@ -142,7 +142,7 @@ export interface DockerData { } export interface RouteData { - show?: boolean; + disable?: boolean; create: boolean; targetPort: string; unknownTargetPort?: string; diff --git a/frontend/packages/dev-console/src/components/import/route/RouteCheckbox.tsx b/frontend/packages/dev-console/src/components/import/route/RouteCheckbox.tsx index 32346636dd1..c6d0d4a9088 100644 --- a/frontend/packages/dev-console/src/components/import/route/RouteCheckbox.tsx +++ b/frontend/packages/dev-console/src/components/import/route/RouteCheckbox.tsx @@ -1,12 +1,17 @@ import * as React from 'react'; import { CheckboxField } from '@console/shared'; -const RouteCheckbox: React.FC = () => { +interface RouteCheckboxProps { + isDisabled?: boolean; +} + +const RouteCheckbox: React.FC = ({ isDisabled }) => { return ( ); }; diff --git a/frontend/packages/dev-console/src/components/modals/index.ts b/frontend/packages/dev-console/src/components/modals/index.ts index e6fe7b36228..b4affe7cf89 100644 --- a/frontend/packages/dev-console/src/components/modals/index.ts +++ b/frontend/packages/dev-console/src/components/modals/index.ts @@ -7,8 +7,3 @@ export const deleteApplicationModal = (props) => import('./DeleteApplicationModal' /* webpackChunkName: "delete-application-modal" */).then((m) => m.deleteApplicationModal(props), ); - -export const editApplication = (props) => - import( - '../edit-application/EditApplicationWrapper' /* webpackChunkName: "edit-application-modal" */ - ).then((m) => m.editApplication(props)); diff --git a/frontend/packages/dev-console/src/plugin.tsx b/frontend/packages/dev-console/src/plugin.tsx index caea61b3fd7..230a41b2dfc 100644 --- a/frontend/packages/dev-console/src/plugin.tsx +++ b/frontend/packages/dev-console/src/plugin.tsx @@ -377,6 +377,7 @@ const plugin: Plugin = [ '/metrics', '/project-access', '/dev-monitoring', + '/edit', ], component: NamespaceRedirect, }, @@ -408,6 +409,17 @@ const plugin: Plugin = [ ).default, }, }, + { + type: 'Page/Route', + properties: { + exact: true, + path: '/edit/ns/:ns', + loader: async () => + (await import( + './components/edit-application/EditApplicationPage' /* webpackChunkName: "dev-console-edit" */ + )).default, + }, + }, { type: 'Page/Route', properties: { diff --git a/frontend/packages/dev-console/src/utils/resource-label-utils.ts b/frontend/packages/dev-console/src/utils/resource-label-utils.ts index 06f2138bfdd..1e82de42f7d 100644 --- a/frontend/packages/dev-console/src/utils/resource-label-utils.ts +++ b/frontend/packages/dev-console/src/utils/resource-label-utils.ts @@ -3,6 +3,7 @@ export const getAppLabels = ( application?: string, imageStreamName?: string, selectedTag?: string, + namespace?: string, ) => { const labels = { app: name, @@ -18,6 +19,9 @@ export const getAppLabels = ( if (selectedTag) { labels['app.openshift.io/runtime-version'] = selectedTag; } + if (namespace) { + labels['app/runtime-namespace'] = namespace; + } return labels; }; diff --git a/frontend/packages/dev-console/src/utils/shared-submit-utils.ts b/frontend/packages/dev-console/src/utils/shared-submit-utils.ts index 689476b9f31..de24d60d4a6 100644 --- a/frontend/packages/dev-console/src/utils/shared-submit-utils.ts +++ b/frontend/packages/dev-console/src/utils/shared-submit-utils.ts @@ -47,19 +47,16 @@ export const createService = ( ports = isiPorts; } - const service: any = { - ...(originalService || {}), + const newService: any = { kind: 'Service', apiVersion: 'v1', metadata: { - ...(originalService ? originalService.metadata : {}), name, namespace, labels: { ...defaultLabels, ...userLabels }, annotations: { ...defaultAnnotations }, }, spec: { - ...(originalService ? originalService.spec : {}), selector: podLabels, ports: _.map(ports, (port) => ({ port: port.containerPort, @@ -71,6 +68,8 @@ export const createService = ( }, }; + const service = _.merge({}, originalService || {}, newService); + return service; }; @@ -113,19 +112,16 @@ export const createRoute = ( targetPort = routeTargetPort || makePortName(_.head(ports)); } - const route: any = { - ...(originalRoute || {}), + const newRoute: any = { kind: 'Route', apiVersion: 'route.openshift.io/v1', metadata: { - ...(originalRoute ? originalRoute.metadata : {}), name, namespace, labels: { ...defaultLabels, ...userLabels }, defaultAnnotations, }, spec: { - ...(originalRoute ? originalRoute.spec : {}), to: { kind: 'Service', name, @@ -144,5 +140,8 @@ export const createRoute = ( wildcardPolicy: 'None', }, }; + + const route = _.merge({}, originalRoute || {}, newRoute); + return route; }; diff --git a/frontend/packages/knative-plugin/src/utils/create-knative-utils.ts b/frontend/packages/knative-plugin/src/utils/create-knative-utils.ts index 8e713a5abbb..ac8b86c5fc2 100644 --- a/frontend/packages/knative-plugin/src/utils/create-knative-utils.ts +++ b/frontend/packages/knative-plugin/src/utils/create-knative-utils.ts @@ -16,6 +16,7 @@ import { DeployImageFormData, GitImportFormData, } from '@console/dev-console/src/components/import/import-types'; +import * as _ from 'lodash'; export const getKnativeServiceDepResource = ( formData: GitImportFormData | DeployImageFormData, @@ -52,12 +53,10 @@ export const getKnativeServiceDepResource = ( } = limits; const defaultLabel = getAppLabels(name, applicationName, imageStreamName, imageTag); delete defaultLabel.app; - const knativeDeployResource: K8sResourceKind = { - ...(originalKnativeService || {}), + const newKnativeDeployResource: K8sResourceKind = { kind: ServiceModel.kind, apiVersion: `${ServiceModel.apiGroup}/${ServiceModel.apiVersion}`, metadata: { - ...(originalKnativeService ? originalKnativeService.metadata : {}), name, namespace, labels: { @@ -67,7 +66,6 @@ export const getKnativeServiceDepResource = ( }, }, spec: { - ...(originalKnativeService ? originalKnativeService.spec : {}), template: { metadata: { labels: { @@ -115,6 +113,9 @@ export const getKnativeServiceDepResource = ( }, }, }; + + const knativeDeployResource = _.merge({}, originalKnativeService || {}, newKnativeDeployResource); + return knativeDeployResource; }; From cf4f9baede5fc7a4f5ddcf39fe42d8d430c7af1e Mon Sep 17 00:00:00 2001 From: Divyanshi Gupta Date: Wed, 15 Jan 2020 00:56:54 +0530 Subject: [PATCH 2/4] Add unit tests for edit-application-utils --- .../edit-application/EditApplication.tsx | 51 +- .../__tests__/edit-application-data.ts | 585 ++++++++++++++++++ .../__tests__/edit-application-utils.spec.ts | 38 ++ .../edit-application-utils.ts | 79 ++- .../src/components/import/import-types.ts | 15 +- 5 files changed, 698 insertions(+), 70 deletions(-) create mode 100644 frontend/packages/dev-console/src/components/edit-application/__tests__/edit-application-data.ts create mode 100644 frontend/packages/dev-console/src/components/edit-application/__tests__/edit-application-utils.spec.ts diff --git a/frontend/packages/dev-console/src/components/edit-application/EditApplication.tsx b/frontend/packages/dev-console/src/components/edit-application/EditApplication.tsx index a8d3b9a0485..606c9b465a3 100644 --- a/frontend/packages/dev-console/src/components/edit-application/EditApplication.tsx +++ b/frontend/packages/dev-console/src/components/edit-application/EditApplication.tsx @@ -13,13 +13,7 @@ import { createOrUpdateDeployImageResources } from '../import/deployImage-submit import { deployValidationSchema } from '../import/deployImage-validation-utils'; import EditApplicationForm from './EditApplicationForm'; import { EditApplicationProps } from './edit-application-types'; -import { - getPageHeading, - getCommonInitialValues, - getGitAndDockerfileInitialValues, - getInternalImageInitialValues, - getExternalImageInitialValues, -} from './edit-application-utils'; +import { getPageHeading, getInitialValues } from './edit-application-utils'; export interface StateProps { perspective: string; @@ -35,50 +29,11 @@ const EditApplication: React.FC = ({ appResources.imageStreams && appResources.imageStreams.loaded ? appResources.imageStreams.data : []; - const builderImages: NormalizedBuilderImages = !_.isEmpty(imageStreamsData) ? normalizeBuilderImages(imageStreamsData) : null; - - const getInitialValues = () => { - const commonValues = getCommonInitialValues( - appResources.editAppResource.data, - appResources.route.data, - appName, - namespace, - ); - - const gitDockerValues = getGitAndDockerfileInitialValues( - appResources.buildConfig.data, - appResources.route.data, - ); - - let externalImageValues = {}; - let internalImageValues = {}; - - if (_.isEmpty(gitDockerValues)) { - externalImageValues = getExternalImageInitialValues( - appResources.imageStream.data, - appResources.editAppResource.data, - ); - internalImageValues = _.isEmpty(externalImageValues) - ? getInternalImageInitialValues(appResources.editAppResource.data) - : {}; - } - console.log('internal values ----', internalImageValues); - return { - ...commonValues, - ...gitDockerValues, - ...externalImageValues, - ...internalImageValues, - }; - }; - - const initialValues = getInitialValues(); - console.log('values ------------------', initialValues); - + const initialValues = getInitialValues(appResources, appName, namespace); const pageHeading = getPageHeading(_.get(initialValues, 'build.strategy', '')); - const handleRedirect = (project: string) => { const perspectiveData = plugins.registry .getPerspectives() @@ -86,7 +41,6 @@ const EditApplication: React.FC = ({ const redirectURL = perspectiveData.properties.getImportRedirectURL(project); history.push(redirectURL); }; - const handleSubmit = (values, actions) => { if (!_.isEmpty(values.build.strategy)) { const imageStream = @@ -120,7 +74,6 @@ const EditApplication: React.FC = ({ }); } }; - const renderForm = (props) => { return ( { + it('getResourcesType should return resource type based on resource kind', () => { + expect(getResourcesType(knativeService)).toEqual(Resources.KnativeService); + }); + + it('getPageHeading should return page heading based on the create flow used to create the application', () => { + expect(getPageHeading(BuildStrategyType.Source)).toEqual(CreateApplicationFlow.Git); + }); + + it('getInitialValues should return values based on the resources and the create flow used to create the application', () => { + const { route, editAppResource, buildConfig, imageStream } = appResources; + expect( + getInitialValues({ buildConfig, editAppResource, route }, 'nationalparks-py', 'div'), + ).toEqual(gitImportInitialValues); + expect( + getInitialValues({ editAppResource, route, imageStream }, 'nationalparks-py', 'div'), + ).toEqual(externalImageValues); + expect(getInitialValues({ editAppResource, route }, 'nationalparks-py', 'div')).toEqual( + internalImageValues, + ); + }); +}); diff --git a/frontend/packages/dev-console/src/components/edit-application/edit-application-utils.ts b/frontend/packages/dev-console/src/components/edit-application/edit-application-utils.ts index cd5aa08bcae..ae79e6ac752 100644 --- a/frontend/packages/dev-console/src/components/edit-application/edit-application-utils.ts +++ b/frontend/packages/dev-console/src/components/edit-application/edit-application-utils.ts @@ -4,6 +4,8 @@ import { BuildStrategyType } from '@console/internal/components/build'; import { DeploymentConfigModel, DeploymentModel } from '@console/internal/models'; import { ServiceModel } from '@console/knative-plugin'; import { Resources } from '../import/import-types'; +import { UNASSIGNED_KEY } from '../import/app/ApplicationSelector'; +import { AppResources } from './edit-application-types'; export enum CreateApplicationFlow { Git = 'Import from Git', @@ -103,7 +105,7 @@ export const getBuildData = (buildConfig: K8sResourceKind) => { } const triggers = _.get(buildConfig, 'spec.triggers'); const buildData = { - env: buildStrategyData.env, + env: buildStrategyData.env || [], triggers: { webhook: checkIfTriggerExists(triggers, 'GitHub'), image: checkIfTriggerExists(triggers, 'ImageChange'), @@ -115,7 +117,14 @@ export const getBuildData = (buildConfig: K8sResourceKind) => { }; export const getServerlessData = (resource: K8sResourceKind) => { - let serverlessData = {}; + let serverlessData = { + scaling: { + minpods: 0, + maxpods: '', + concurrencytarget: '', + concurrencylimit: '', + }, + }; if (getResourcesType(resource) === Resources.KnativeService) { const annotations = _.get(resource, 'spec.template.metadata.annotations'); serverlessData = { @@ -197,13 +206,13 @@ export const getCommonInitialValues = ( name: string, namespace: string, ) => { - const appGroupName = _.get(editAppResource, 'metadata.labels["app.kubernetes.io/part-of"]', ''); + const appGroupName = _.get(editAppResource, 'metadata.labels["app.kubernetes.io/part-of"]'); const commonInitialValues = { formType: 'edit', name, application: { - name: appGroupName, - selectedKey: appGroupName, + name: appGroupName || '', + selectedKey: appGroupName || UNASSIGNED_KEY, }, project: { name: namespace, @@ -265,7 +274,8 @@ export const getExternalImageInitialValues = ( } const name = _.get(imageStream, 'spec.tags[0].from.name'); const tag = _.get(imageStream, 'status.tags[0].tag'); - const imageRef = _.get(imageStream, 'status.tags[0].items[0].dockerImageReference'); + const imageRef = _.get(editAppResource, 'spec.template.spec.containers[0].image'); + const creationTimestamp = _.get(imageStream, 'status.tags[0].items[0].created'); const ports = _.get(editAppResource, 'spec.template.spec.containers[0].ports'); const deployImageInitialValues = { searchTerm: name, @@ -280,6 +290,9 @@ export const getExternalImageInitialValues = ( name, image: { dockerImageReference: imageRef, + dockerImageMetadata: { + Created: creationTimestamp, + }, }, tag, status: { metadata: {}, status: '' }, @@ -307,32 +320,34 @@ export const getInternalImageInitialValues = (editAppResource: K8sResourceKind) '', ); const imageStreamName = _.get(editAppResource, 'metadata.labels["app.openshift.io/runtime"]', ''); - const internalImageStreamTag = _.get( + const imageStreamTag = _.get( editAppResource, 'metadata.labels["app.openshift.io/runtime-version"]', '', ); + const ports = _.get(editAppResource, 'spec.template.spec.containers[0].ports'); + const imageRef = _.get(editAppResource, 'spec.template.spec.containers[0].image'); const deployImageInitialValues = { searchTerm: '', registry: 'internal', imageStream: { image: imageStreamName, - tag: internalImageStreamTag, + tag: imageStreamTag, namespace: imageStreamNamespace, }, isi: { - name: '', - image: {}, - tag: '', + name: imageStreamName, + image: { dockerImageReference: imageRef }, + tag: imageStreamTag, status: { metadata: {}, status: '' }, - ports: '', + ports, }, image: { name: '', image: {}, - tag: '', + tag: imageStreamTag, status: { metadata: {}, status: '' }, - ports: '', + ports, }, build: { strategy: '', @@ -341,3 +356,39 @@ export const getInternalImageInitialValues = (editAppResource: K8sResourceKind) }; return deployImageInitialValues; }; + +export const getInitialValues = ( + appResources: AppResources, + appName: string, + namespace: string, +) => { + const commonValues = getCommonInitialValues( + appResources.editAppResource.data, + appResources.route.data, + appName, + namespace, + ); + const gitDockerValues = getGitAndDockerfileInitialValues( + appResources.buildConfig.data, + appResources.route.data, + ); + let externalImageValues = {}; + let internalImageValues = {}; + + if (_.isEmpty(gitDockerValues)) { + externalImageValues = getExternalImageInitialValues( + appResources.imageStream.data, + appResources.editAppResource.data, + ); + internalImageValues = _.isEmpty(externalImageValues) + ? getInternalImageInitialValues(appResources.editAppResource.data) + : {}; + } + + return { + ...commonValues, + ...gitDockerValues, + ...externalImageValues, + ...internalImageValues, + }; +}; diff --git a/frontend/packages/dev-console/src/components/import/import-types.ts b/frontend/packages/dev-console/src/components/import/import-types.ts index 77531f2c081..8f85375f729 100644 --- a/frontend/packages/dev-console/src/components/import/import-types.ts +++ b/frontend/packages/dev-console/src/components/import/import-types.ts @@ -52,6 +52,7 @@ export interface FirehoseList { } export interface DeployImageFormData { + formType?: string; project: ProjectData; application: ApplicationData; name: string; @@ -70,7 +71,7 @@ export interface DeployImageFormData { serverless?: ServerlessData; pipeline?: PipelineData; labels: { [name: string]: string }; - env: { [name: string]: string }; + env?: { [name: string]: string }; route: RouteData; build: BuildData; deployment: DeploymentData; @@ -96,7 +97,7 @@ export interface GitImportFormData { } export interface ApplicationData { - initial: string; + initial?: string; name: string; selectedKey: string; } @@ -121,8 +122,8 @@ export interface ImageStreamImageData { export interface ProjectData { name: string; - displayName: string; - description: string; + displayName?: string; + description?: string; } export interface GitData { @@ -164,9 +165,9 @@ export interface TLSData { export interface BuildData { triggers: { - webhook: boolean; - image: boolean; - config: boolean; + webhook?: boolean; + image?: boolean; + config?: boolean; }; env: (NameValuePair | NameValueFromPair)[]; strategy: string; From 64e1d0c5a10fac98b5945d8573abd86bf6cf90bc Mon Sep 17 00:00:00 2001 From: Divyanshi Gupta Date: Wed, 15 Jan 2020 21:49:22 +0530 Subject: [PATCH 3/4] Set image-stream values on populating the form with app data --- .../edit-application/EditApplication.tsx | 11 +- .../edit-application-utils.ts | 59 ++++----- .../import/deployImage-submit-utils.ts | 12 +- .../import/image-search/ImageSearch.tsx | 120 ++++++++++-------- .../image-search/ImageStreamDropdown.tsx | 35 ++++- .../image-search/ImageStreamNsDropdown.tsx | 92 +++++++++----- .../image-search/ImageStreamTagDropdown.tsx | 5 + 7 files changed, 184 insertions(+), 150 deletions(-) diff --git a/frontend/packages/dev-console/src/components/edit-application/EditApplication.tsx b/frontend/packages/dev-console/src/components/edit-application/EditApplication.tsx index 606c9b465a3..b04800c87c6 100644 --- a/frontend/packages/dev-console/src/components/edit-application/EditApplication.tsx +++ b/frontend/packages/dev-console/src/components/edit-application/EditApplication.tsx @@ -56,13 +56,7 @@ const EditApplication: React.FC = ({ actions.setStatus({ error: err.message }); }); } else { - createOrUpdateDeployImageResources( - values, - false, - 'update', - appResources, - _.get(initialValues, 'imageStream.namespace'), - ) + createOrUpdateDeployImageResources(values, false, 'update', appResources) .then(() => { actions.setSubmitting(false); actions.setStatus({ error: '' }); @@ -78,10 +72,9 @@ const EditApplication: React.FC = ({ return ( ); }; diff --git a/frontend/packages/dev-console/src/components/edit-application/edit-application-utils.ts b/frontend/packages/dev-console/src/components/edit-application/edit-application-utils.ts index ae79e6ac752..91fe34784e6 100644 --- a/frontend/packages/dev-console/src/components/edit-application/edit-application-utils.ts +++ b/frontend/packages/dev-console/src/components/edit-application/edit-application-utils.ts @@ -265,18 +265,11 @@ export const getGitAndDockerfileInitialValues = ( return initialValues; }; -export const getExternalImageInitialValues = ( - imageStream: K8sResourceKind, - editAppResource: K8sResourceKind, -) => { +export const getExternalImageInitialValues = (imageStream: K8sResourceKind) => { if (_.isEmpty(imageStream)) { return {}; } const name = _.get(imageStream, 'spec.tags[0].from.name'); - const tag = _.get(imageStream, 'status.tags[0].tag'); - const imageRef = _.get(editAppResource, 'spec.template.spec.containers[0].image'); - const creationTimestamp = _.get(imageStream, 'status.tags[0].items[0].created'); - const ports = _.get(editAppResource, 'spec.template.spec.containers[0].ports'); const deployImageInitialValues = { searchTerm: name, registry: 'external', @@ -287,25 +280,22 @@ export const getExternalImageInitialValues = ( grantAccess: true, }, isi: { - name, - image: { - dockerImageReference: imageRef, - dockerImageMetadata: { - Created: creationTimestamp, - }, - }, - tag, + name: '', + image: {}, + tag: '', status: { metadata: {}, status: '' }, - ports, + ports: [], }, image: { name: '', image: {}, - tag, + tag: '', status: { metadata: {}, status: '' }, - ports, + ports: [], }, build: { + env: [], + triggers: {}, strategy: '', }, isSearchingForImage: false, @@ -325,8 +315,6 @@ export const getInternalImageInitialValues = (editAppResource: K8sResourceKind) 'metadata.labels["app.openshift.io/runtime-version"]', '', ); - const ports = _.get(editAppResource, 'spec.template.spec.containers[0].ports'); - const imageRef = _.get(editAppResource, 'spec.template.spec.containers[0].image'); const deployImageInitialValues = { searchTerm: '', registry: 'internal', @@ -336,20 +324,22 @@ export const getInternalImageInitialValues = (editAppResource: K8sResourceKind) namespace: imageStreamNamespace, }, isi: { - name: imageStreamName, - image: { dockerImageReference: imageRef }, - tag: imageStreamTag, + name: '', + image: {}, + tag: '', status: { metadata: {}, status: '' }, - ports, + ports: [], }, image: { name: '', image: {}, - tag: imageStreamTag, + tag: '', status: { metadata: {}, status: '' }, - ports, + ports: [], }, build: { + env: [], + triggers: {}, strategy: '', }, isSearchingForImage: false, @@ -363,25 +353,22 @@ export const getInitialValues = ( namespace: string, ) => { const commonValues = getCommonInitialValues( - appResources.editAppResource.data, - appResources.route.data, + _.get(appResources, 'editAppResource.data'), + _.get(appResources, 'route.data'), appName, namespace, ); const gitDockerValues = getGitAndDockerfileInitialValues( - appResources.buildConfig.data, - appResources.route.data, + _.get(appResources, 'buildConfig.data'), + _.get(appResources, 'route.data'), ); let externalImageValues = {}; let internalImageValues = {}; if (_.isEmpty(gitDockerValues)) { - externalImageValues = getExternalImageInitialValues( - appResources.imageStream.data, - appResources.editAppResource.data, - ); + externalImageValues = getExternalImageInitialValues(_.get(appResources, 'imageStream.data')); internalImageValues = _.isEmpty(externalImageValues) - ? getInternalImageInitialValues(appResources.editAppResource.data) + ? getInternalImageInitialValues(_.get(appResources, 'editAppResource.data')) : {}; } diff --git a/frontend/packages/dev-console/src/components/import/deployImage-submit-utils.ts b/frontend/packages/dev-console/src/components/import/deployImage-submit-utils.ts index 8edbeb71905..dc35d989ec4 100644 --- a/frontend/packages/dev-console/src/components/import/deployImage-submit-utils.ts +++ b/frontend/packages/dev-console/src/components/import/deployImage-submit-utils.ts @@ -107,13 +107,7 @@ const getMetadata = (formData: DeployImageFormData) => { imageStream: { image: imgName, tag: imgTag, namespace: imgNamespace }, } = formData; - const defaultLabels = getAppLabels( - name, - application, - !_.isEmpty(imgName) ? imgName : undefined, - !_.isEmpty(imgTag) ? imgTag : undefined, - !_.isEmpty(imgNamespace) ? imgNamespace : undefined, - ); + const defaultLabels = getAppLabels(name, application, imgName || undefined, imgTag, imgNamespace); const labels = { ...defaultLabels, ...userLabels }; const podLabels = getPodLabels(name); @@ -349,18 +343,16 @@ export const createOrUpdateDeployImageResources = async ( dryRun: boolean = false, verb: K8sVerb = 'create', appResources?: AppResources, - initialInternalImageStreamNamespace: string = '', ): Promise => { const formData = ensurePortExists(rawFormData); const { registry, route: { create: canCreateRoute, disable }, isi: { ports, tag: imageStreamTag }, - imageStream: { namespace: imgNamespace }, } = formData; const requests: Promise[] = []; - if (registry === RegistryType.Internal && initialInternalImageStreamNamespace !== imgNamespace) { + if (registry === RegistryType.Internal) { formData.imageStream.grantAccess && requests.push(createSystemImagePullerRoleBinding(formData, dryRun)); } diff --git a/frontend/packages/dev-console/src/components/import/image-search/ImageSearch.tsx b/frontend/packages/dev-console/src/components/import/image-search/ImageSearch.tsx index 0608efb5ef6..d6dc362189d 100644 --- a/frontend/packages/dev-console/src/components/import/image-search/ImageSearch.tsx +++ b/frontend/packages/dev-console/src/components/import/image-search/ImageSearch.tsx @@ -10,74 +10,82 @@ import { getSuggestedName, getPorts, makePortName } from '../../../utils/imagest import { secretModalLauncher } from '../CreateSecretModal'; const ImageSearch: React.FC = () => { - const { values, setFieldValue, setFieldError } = useFormikContext(); + const { values, setFieldValue, setFieldError, dirty } = useFormikContext(); const [newImageSecret, setNewImageSecret] = React.useState(''); const [alertVisible, shouldHideAlert] = React.useState(true); const namespace = values.project.name; - const handleSearch = (searchTerm: string) => { - const importImage = { - kind: 'ImageStreamImport', - apiVersion: 'image.openshift.io/v1', - metadata: { - name: 'newapp', - namespace: values.project.name, - }, - spec: { - import: false, - images: [ - { - from: { - kind: 'DockerImage', - name: _.trim(searchTerm), + + const handleSearch = React.useCallback( + (searchTerm: string) => { + const importImage = { + kind: 'ImageStreamImport', + apiVersion: 'image.openshift.io/v1', + metadata: { + name: 'newapp', + namespace: values.project.name, + }, + spec: { + import: false, + images: [ + { + from: { + kind: 'DockerImage', + name: _.trim(searchTerm), + }, }, - }, - ], - }, - status: {}, - }; + ], + }, + status: {}, + }; - k8sCreate(ImageStreamImportsModel, importImage) - .then((imageStreamImport) => { - const status = _.get(imageStreamImport, 'status.images[0].status'); - if (status.status === 'Success') { - const name = _.get(imageStreamImport, 'spec.images[0].from.name'); - const image = _.get(imageStreamImport, 'status.images[0].image'); - const tag = _.get(imageStreamImport, 'status.images[0].tag'); - const isi = { name, image, tag, status }; - const ports = getPorts(isi); - setFieldValue('isSearchingForImage', false); - setFieldValue('isi.name', name); - setFieldValue('isi.image', image); - setFieldValue('isi.tag', tag); - setFieldValue('isi.status', status); - setFieldValue('isi.ports', ports); - setFieldValue('image.ports', ports); - setFieldValue('image.tag', tag); - !values.name && setFieldValue('name', getSuggestedName(name)); - !values.application.name && - setFieldValue('application.name', `${getSuggestedName(name)}-app`); - // set default port value - const targetPort = _.head(ports); - targetPort && setFieldValue('route.targetPort', makePortName(targetPort)); - } else { - setFieldValue('isSearchingForImage', false); + k8sCreate(ImageStreamImportsModel, importImage) + .then((imageStreamImport) => { + const status = _.get(imageStreamImport, 'status.images[0].status'); + if (status.status === 'Success') { + const name = _.get(imageStreamImport, 'spec.images[0].from.name'); + const image = _.get(imageStreamImport, 'status.images[0].image'); + const tag = _.get(imageStreamImport, 'status.images[0].tag'); + const isi = { name, image, tag, status }; + const ports = getPorts(isi); + setFieldValue('isSearchingForImage', false); + setFieldValue('isi.name', name); + setFieldValue('isi.image', image); + setFieldValue('isi.tag', tag); + setFieldValue('isi.status', status); + setFieldValue('isi.ports', ports); + setFieldValue('image.ports', ports); + setFieldValue('image.tag', tag); + !values.name && setFieldValue('name', getSuggestedName(name)); + !values.application.name && + setFieldValue('application.name', `${getSuggestedName(name)}-app`); + // set default port value + const targetPort = _.head(ports); + targetPort && setFieldValue('route.targetPort', makePortName(targetPort)); + } else { + setFieldValue('isSearchingForImage', false); + setFieldValue('isi', {}); + setFieldError('isi.image', status.message); + setFieldValue('route.targetPort', null); + } + }) + .catch((error) => { + setFieldError('isi.image', error.message); setFieldValue('isi', {}); - setFieldError('isi.image', status.message); - setFieldValue('route.targetPort', null); - } - }) - .catch((error) => { - setFieldError('isi.image', error.message); - setFieldValue('isi', {}); - setFieldValue('isSearchingForImage', false); - }); - }; + setFieldValue('isSearchingForImage', false); + }); + }, + [setFieldError, setFieldValue, values.application.name, values.name, values.project.name], + ); const handleSave = (name: string) => { setNewImageSecret(name); values.searchTerm && handleSearch(values.searchTerm); }; + React.useEffect(() => { + !dirty && values.searchTerm && handleSearch(values.searchTerm); + }, [dirty, handleSearch, values.searchTerm]); + return ( <> { ? 'No Image Stream' : 'Select Image Stream'; }; - const onDropdownChange = (img: string) => { - setFieldValue('imageStream.tag', ''); - setFieldValue('isi', initialValues.isi); - const image = imgCollection[imageStream.namespace][img]; - dispatch({ type: ImageStreamActions.setSelectedImageStream, value: image }); - }; + + const onDropdownChange = React.useCallback( + (img: string) => { + setFieldValue('imageStream.tag', initialValues.imageStream.tag); + setFieldValue('isi', initialValues.isi); + const image = _.get(imgCollection, [imageStream.namespace, img], {}); + dispatch({ type: ImageStreamActions.setSelectedImageStream, value: image }); + }, + [ + setFieldValue, + initialValues.imageStream.tag, + initialValues.isi, + imgCollection, + imageStream.namespace, + dispatch, + ], + ); const onLoad = (imgstreams) => { const imageStreamAvailable = !_.isEmpty(imgstreams); setHasImageStreams(imageStreamAvailable); @@ -54,6 +65,18 @@ const ImageStreamDropdown: React.FC = () => { collectImageStreams(namespace, resource); return namespace === imageStream.namespace; }; + + React.useEffect(() => { + imageStream.image && onDropdownChange(imageStream.image); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [imageStream.image, isStreamsAvailable]); + + React.useEffect(() => { + if (initialValues.imageStream.image !== imageStream.image) { + initialValues.imageStream.tag = ''; + } + }, [imageStream.image, initialValues.imageStream.image, initialValues.imageStream.tag]); + return ( { - const { setFieldValue, initialValues } = useFormikContext(); + const { values, setFieldValue, initialValues } = useFormikContext(); const { dispatch } = React.useContext(ImageStreamContext); - const onDropdownChange = (selectedProject: string) => { - const promiseArr = []; - setFieldValue('imageStream.image', ''); - setFieldValue('imageStream.tag', ''); - setFieldValue('isi', initialValues.isi); - dispatch({ type: Action.setLoading, value: true }); - dispatch({ type: Action.setAccessLoading, value: true }); - promiseArr.push( - checkAccess({ - group: RoleBindingModel.apiGroup, - resource: RoleBindingModel.plural, - verb: 'create', - name: 'system:image-puller', - namespace: selectedProject, - }) - .then((resp) => dispatch({ type: Action.setHasCreateAccess, value: resp.status.allowed })) - .catch(() => dispatch({ type: Action.setHasAccessToPullImage, value: false })), - ); - promiseArr.push( - k8sGet(RoleBindingModel, 'system:image-puller', selectedProject) - .then(() => { - dispatch({ - type: Action.setHasAccessToPullImage, - value: true, - }); - setFieldValue('imageStream.grantAccess', false); + const onDropdownChange = React.useCallback( + (selectedProject: string) => { + const promiseArr = []; + setFieldValue('imageStream.image', initialValues.imageStream.image); + setFieldValue('imageStream.tag', initialValues.imageStream.tag); + setFieldValue('isi', initialValues.isi); + dispatch({ type: Action.setLoading, value: true }); + dispatch({ type: Action.setAccessLoading, value: true }); + promiseArr.push( + checkAccess({ + group: RoleBindingModel.apiGroup, + resource: RoleBindingModel.plural, + verb: 'create', + name: 'system:image-puller', + namespace: selectedProject, }) - .catch(() => dispatch({ type: Action.setHasAccessToPullImage, value: false })), - ); - return Promise.all(promiseArr).then(() => - dispatch({ type: Action.setAccessLoading, value: false }), - ); - }; + .then((resp) => dispatch({ type: Action.setHasCreateAccess, value: resp.status.allowed })) + .catch(() => dispatch({ type: Action.setHasAccessToPullImage, value: false })), + ); + promiseArr.push( + k8sGet(RoleBindingModel, 'system:image-puller', selectedProject) + .then(() => { + dispatch({ + type: Action.setHasAccessToPullImage, + value: true, + }); + setFieldValue('imageStream.grantAccess', false); + }) + .catch(() => dispatch({ type: Action.setHasAccessToPullImage, value: false })), + ); + return Promise.all(promiseArr).then(() => + dispatch({ type: Action.setAccessLoading, value: false }), + ); + }, + [ + dispatch, + initialValues.imageStream.image, + initialValues.imageStream.tag, + initialValues.isi, + setFieldValue, + ], + ); + + React.useEffect(() => { + values.imageStream.namespace && onDropdownChange(values.imageStream.namespace); + }, [onDropdownChange, values.imageStream.namespace]); + + React.useEffect(() => { + if (initialValues.imageStream.namespace !== values.imageStream.namespace) { + initialValues.imageStream.image = ''; + initialValues.imageStream.tag = ''; + } + }, [ + initialValues.imageStream.image, + initialValues.imageStream.namespace, + initialValues.imageStream.tag, + values.imageStream.namespace, + ]); + return ( { setFieldError, ], ); + + React.useEffect(() => { + imageStream.tag && searchImageTag(imageStream.tag); + }, [imageStream.tag, searchImageTag]); + return ( Date: Mon, 20 Jan 2020 21:49:12 +0530 Subject: [PATCH 4/4] Fix bug wherein for image from internal registry new imagestream was created --- .../edit-application/EditApplication.tsx | 64 ++++++++----------- .../edit-application/EditApplicationForm.tsx | 42 ++++++------ .../__tests__/edit-application-data.ts | 2 +- .../edit-application-utils.ts | 4 +- .../src/components/import/ImportForm.tsx | 13 +--- .../import/deployImage-submit-utils.ts | 28 +++++--- .../image-search/ImageStreamDropdown.tsx | 2 +- .../image-search/ImageStreamTagDropdown.tsx | 7 +- .../components/import/import-submit-utils.ts | 12 ++++ frontend/packages/dev-console/src/plugin.tsx | 8 ++- .../src/utils/resource-label-utils.ts | 2 +- .../src/utils/create-knative-utils.ts | 10 ++- 12 files changed, 99 insertions(+), 95 deletions(-) diff --git a/frontend/packages/dev-console/src/components/edit-application/EditApplication.tsx b/frontend/packages/dev-console/src/components/edit-application/EditApplication.tsx index b04800c87c6..8432ea0a039 100644 --- a/frontend/packages/dev-console/src/components/edit-application/EditApplication.tsx +++ b/frontend/packages/dev-console/src/components/edit-application/EditApplication.tsx @@ -2,13 +2,15 @@ import * as React from 'react'; import { Formik } from 'formik'; import * as _ from 'lodash'; import { connect } from 'react-redux'; -import * as plugins from '@console/internal/plugins'; import { getActivePerspective } from '@console/internal/reducers/ui'; import { RootState } from '@console/internal/redux'; import { history } from '@console/internal/components/utils'; import { NormalizedBuilderImages, normalizeBuilderImages } from '../../utils/imagestream-utils'; -import { createOrUpdateResources } from '../import/import-submit-utils'; -import { validationSchema } from '../import/import-validation-utils'; +import { + createOrUpdateResources as createOrUpdateGitResources, + handleRedirect, +} from '../import/import-submit-utils'; +import { validationSchema as gitValidationSchema } from '../import/import-validation-utils'; import { createOrUpdateDeployImageResources } from '../import/deployImage-submit-utils'; import { deployValidationSchema } from '../import/deployImage-validation-utils'; import EditApplicationForm from './EditApplicationForm'; @@ -32,48 +34,38 @@ const EditApplication: React.FC = ({ const builderImages: NormalizedBuilderImages = !_.isEmpty(imageStreamsData) ? normalizeBuilderImages(imageStreamsData) : null; + const initialValues = getInitialValues(appResources, appName, namespace); const pageHeading = getPageHeading(_.get(initialValues, 'build.strategy', '')); - const handleRedirect = (project: string) => { - const perspectiveData = plugins.registry - .getPerspectives() - .find((item) => item.properties.id === perspective); - const redirectURL = perspectiveData.properties.getImportRedirectURL(project); - history.push(redirectURL); - }; - const handleSubmit = (values, actions) => { - if (!_.isEmpty(values.build.strategy)) { + + const updateResources = (values) => { + if (values.build.strategy) { const imageStream = values.image.selected && builderImages ? builderImages[values.image.selected].obj : null; - createOrUpdateResources(values, imageStream, false, false, 'update', appResources) - .then(() => { - actions.setSubmitting(false); - actions.setStatus({ error: '' }); - handleRedirect(namespace); - }) - .catch((err) => { - actions.setSubmitting(false); - actions.setStatus({ error: err.message }); - }); - } else { - createOrUpdateDeployImageResources(values, false, 'update', appResources) - .then(() => { - actions.setSubmitting(false); - actions.setStatus({ error: '' }); - handleRedirect(namespace); - }) - .catch((err) => { - actions.setSubmitting(false); - actions.setStatus({ error: err.message }); - }); + return createOrUpdateGitResources(values, imageStream, false, false, 'update', appResources); } + return createOrUpdateDeployImageResources(values, false, 'update', appResources); }; + + const handleSubmit = (values, actions) => { + updateResources(values) + .then(() => { + actions.setSubmitting(false); + actions.setStatus({ error: '' }); + handleRedirect(namespace, perspective); + }) + .catch((err) => { + actions.setSubmitting(false); + actions.setStatus({ error: err.message }); + }); + }; + const renderForm = (props) => { return ( ); @@ -85,9 +77,7 @@ const EditApplication: React.FC = ({ onSubmit={handleSubmit} onReset={history.goBack} validationSchema={ - !_.isEmpty(_.get(initialValues, 'build.strategy')) - ? validationSchema - : deployValidationSchema + _.get(initialValues, 'build.strategy') ? gitValidationSchema : deployValidationSchema } render={renderForm} /> diff --git a/frontend/packages/dev-console/src/components/edit-application/EditApplicationForm.tsx b/frontend/packages/dev-console/src/components/edit-application/EditApplicationForm.tsx index 2411dd693f5..95d92293623 100644 --- a/frontend/packages/dev-console/src/components/edit-application/EditApplicationForm.tsx +++ b/frontend/packages/dev-console/src/components/edit-application/EditApplicationForm.tsx @@ -1,8 +1,9 @@ import * as React from 'react'; import * as _ from 'lodash'; import { FormikProps, FormikValues } from 'formik'; -import { Form, ActionGroup, Button, ButtonVariant } from '@patternfly/react-core'; -import { ButtonBar, PageHeading } from '@console/internal/components/utils'; +import { Form } from '@patternfly/react-core'; +import { PageHeading } from '@console/internal/components/utils'; +import { FormFooter } from '@console/shared'; import GitSection from '../import/git/GitSection'; import BuilderSection from '../import/builder/BuilderSection'; import DockerSection from '../import/git/DockerSection'; @@ -13,7 +14,7 @@ import ImageSearchSection from '../import/image-search/ImageSearchSection'; import { CreateApplicationFlow } from './edit-application-utils'; export interface EditApplicationFormProps { - pageHeading: string; + createFlowType: string; builderImages?: NormalizedBuilderImages; } @@ -21,7 +22,7 @@ const EditApplicationForm: React.FC & EditApplicationF handleSubmit, handleReset, values, - pageHeading, + createFlowType, builderImages, dirty, errors, @@ -29,33 +30,26 @@ const EditApplicationForm: React.FC & EditApplicationF isSubmitting, }) => ( <> - +

- {pageHeading !== CreateApplicationFlow.Container && } - {pageHeading === CreateApplicationFlow.Git && ( + {createFlowType !== CreateApplicationFlow.Container && } + {createFlowType === CreateApplicationFlow.Git && ( )} - {pageHeading === CreateApplicationFlow.Dockerfile && ( + {createFlowType === CreateApplicationFlow.Dockerfile && ( )} - {pageHeading === CreateApplicationFlow.Container && } + {createFlowType === CreateApplicationFlow.Container && } - - - - - - + ); diff --git a/frontend/packages/dev-console/src/components/edit-application/__tests__/edit-application-data.ts b/frontend/packages/dev-console/src/components/edit-application/__tests__/edit-application-data.ts index 76c0dd177be..f0f35b35bc7 100644 --- a/frontend/packages/dev-console/src/components/edit-application/__tests__/edit-application-data.ts +++ b/frontend/packages/dev-console/src/components/edit-application/__tests__/edit-application-data.ts @@ -50,7 +50,7 @@ export const appResources: AppResources = { 'app.kubernetes.io/name': 'python', 'app.openshift.io/runtime': 'python', 'app.openshift.io/runtime-version': '3.6', - 'app/runtime-namespace': 'div', + 'app.openshift.io/runtime-namespace': 'div', }, }, spec: { diff --git a/frontend/packages/dev-console/src/components/edit-application/edit-application-utils.ts b/frontend/packages/dev-console/src/components/edit-application/edit-application-utils.ts index 91fe34784e6..6f532386ff6 100644 --- a/frontend/packages/dev-console/src/components/edit-application/edit-application-utils.ts +++ b/frontend/packages/dev-console/src/components/edit-application/edit-application-utils.ts @@ -193,7 +193,7 @@ export const getUserLabels = (resource: K8sResourceKind) => { 'app.openshift.io/runtime', 'app.kubernetes.io/part-of', 'app.openshift.io/runtime-version', - 'app/runtime-namespace', + 'app.openshift.io/runtime-namespace', ]; const allLabels = _.get(resource, 'metadata.labels', {}); const userLabels = _.omit(allLabels, defaultLabels); @@ -306,7 +306,7 @@ export const getExternalImageInitialValues = (imageStream: K8sResourceKind) => { export const getInternalImageInitialValues = (editAppResource: K8sResourceKind) => { const imageStreamNamespace = _.get( editAppResource, - 'metadata.labels["app/runtime-namespace"]', + 'metadata.labels["app.openshift.io/runtime-namespace"]', '', ); const imageStreamName = _.get(editAppResource, 'metadata.labels["app.openshift.io/runtime"]', ''); diff --git a/frontend/packages/dev-console/src/components/import/ImportForm.tsx b/frontend/packages/dev-console/src/components/import/ImportForm.tsx index 5a36d032c26..714a44a3395 100644 --- a/frontend/packages/dev-console/src/components/import/ImportForm.tsx +++ b/frontend/packages/dev-console/src/components/import/ImportForm.tsx @@ -1,5 +1,4 @@ import * as React from 'react'; -import * as plugins from '@console/internal/plugins'; import { Formik } from 'formik'; import * as _ from 'lodash'; import { history, AsyncComponent } from '@console/internal/components/utils'; @@ -9,7 +8,7 @@ import { connect } from 'react-redux'; import { ALL_APPLICATIONS_KEY } from '@console/shared'; import { NormalizedBuilderImages, normalizeBuilderImages } from '../../utils/imagestream-utils'; import { GitImportFormData, FirehoseList, ImportData, Resources } from './import-types'; -import { createOrUpdateResources } from './import-submit-utils'; +import { createOrUpdateResources, handleRedirect } from './import-submit-utils'; import { validationSchema } from './import-validation-utils'; export interface ImportFormProps { @@ -138,14 +137,6 @@ const ImportForm: React.FC = ({ const builderImages: NormalizedBuilderImages = imageStreams && imageStreams.loaded && normalizeBuilderImages(imageStreams.data); - const handleRedirect = (project: string) => { - const perspectiveData = plugins.registry - .getPerspectives() - .find((item) => item.properties.id === perspective); - const redirectURL = perspectiveData.properties.getImportRedirectURL(project); - history.push(redirectURL); - }; - const handleSubmit = (values, actions) => { const imageStream = builderImages && builderImages[values.image.selected].obj; const createNewProject = projects.loaded && _.isEmpty(projects.data); @@ -157,7 +148,7 @@ const ImportForm: React.FC = ({ .then(() => createOrUpdateResources(values, imageStream)) .then(() => { actions.setSubmitting(false); - handleRedirect(projectName); + handleRedirect(projectName, perspective); }) .catch((err) => { actions.setSubmitting(false); diff --git a/frontend/packages/dev-console/src/components/import/deployImage-submit-utils.ts b/frontend/packages/dev-console/src/components/import/deployImage-submit-utils.ts index dc35d989ec4..07170b68f7b 100644 --- a/frontend/packages/dev-console/src/components/import/deployImage-submit-utils.ts +++ b/frontend/packages/dev-console/src/components/import/deployImage-submit-utils.ts @@ -346,9 +346,11 @@ export const createOrUpdateDeployImageResources = async ( ): Promise => { const formData = ensurePortExists(rawFormData); const { + name, registry, route: { create: canCreateRoute, disable }, - isi: { ports, tag: imageStreamTag }, + isi: { ports, tag: imageStreamTag, image }, + imageStream: { image: internalImageName, namespace: internalImageNamespace }, } = formData; const requests: Promise[] = []; @@ -396,18 +398,26 @@ export const createOrUpdateDeployImageResources = async ( } } else if (!dryRun) { // Do not run serverless call during the dry run. - const imageStreamResponse = await createOrUpdateImageStream( - formData, - dryRun, - _.get(appResources, 'imageStream.data'), - verb, - ); + let imageStreamRepo: string = _.split(_.get(image, 'dockerImageReference', ''), '@')[0]; + if (registry === RegistryType.External) { + const imageStreamResponse = await createOrUpdateImageStream( + formData, + dryRun, + _.get(appResources, 'imageStream.data'), + verb, + ); + imageStreamRepo = imageStreamResponse.status.dockerImageRepository; + } const imageStreamUrl = imageStreamTag - ? `${imageStreamResponse.status.dockerImageRepository}:${imageStreamTag}` - : imageStreamResponse.status.dockerImageRepository; + ? `${imageStreamRepo}:${imageStreamTag}` + : imageStreamRepo; const knDeploymentResource = getKnativeServiceDepResource( formData, imageStreamUrl, + internalImageName || name, + imageStreamTag, + internalImageNamespace, + undefined, _.get(appResources, 'editAppResource.data'), ); requests.push( diff --git a/frontend/packages/dev-console/src/components/import/image-search/ImageStreamDropdown.tsx b/frontend/packages/dev-console/src/components/import/image-search/ImageStreamDropdown.tsx index 916883e3896..cb7de89c278 100644 --- a/frontend/packages/dev-console/src/components/import/image-search/ImageStreamDropdown.tsx +++ b/frontend/packages/dev-console/src/components/import/image-search/ImageStreamDropdown.tsx @@ -86,7 +86,7 @@ const ImageStreamDropdown: React.FC = () => { key={imageStream.namespace} fullWidth required - title={!_.isEmpty(imageStream.image) ? imageStream.image : getTitle()} + title={imageStream.image || getTitle()} disabled={!hasCreateAccess || !isStreamsAvailable} onChange={onDropdownChange} onLoad={onLoad} diff --git a/frontend/packages/dev-console/src/components/import/image-search/ImageStreamTagDropdown.tsx b/frontend/packages/dev-console/src/components/import/image-search/ImageStreamTagDropdown.tsx index 5f1d87c513c..a223e2aa3db 100644 --- a/frontend/packages/dev-console/src/components/import/image-search/ImageStreamTagDropdown.tsx +++ b/frontend/packages/dev-console/src/components/import/image-search/ImageStreamTagDropdown.tsx @@ -75,11 +75,8 @@ const ImageStreamTagDropdown: React.FC = () => { items={imageStreamTagList} key={imageStream.image} title={ - !_.isEmpty(imageStream.tag) - ? imageStream.tag - : isNamespaceSelected && isImageStreamSelected && !isTagsAvailable - ? 'No Tag' - : 'Select Tag' + imageStream.tag || + (isNamespaceSelected && isImageStreamSelected && !isTagsAvailable ? 'No Tag' : 'Select Tag') } disabled={!isImageStreamSelected || !isTagsAvailable} fullWidth diff --git a/frontend/packages/dev-console/src/components/import/import-submit-utils.ts b/frontend/packages/dev-console/src/components/import/import-submit-utils.ts index 1b2c4a46ac7..0adb9209ef4 100644 --- a/frontend/packages/dev-console/src/components/import/import-submit-utils.ts +++ b/frontend/packages/dev-console/src/components/import/import-submit-utils.ts @@ -15,6 +15,8 @@ import { ServiceModel as KnServiceModel, } from '@console/knative-plugin'; import { SecretType } from '@console/internal/components/secrets/create-secret'; +import * as plugins from '@console/internal/plugins'; +import { history } from '@console/internal/components/utils'; import { getAppLabels, getPodLabels, getAppAnnotations } from '../../utils/resource-label-utils'; import { createService, createRoute, dryRunOpt } from '../../utils/shared-submit-utils'; import { AppResources } from '../edit-application/edit-application-types'; @@ -444,6 +446,8 @@ export const createOrUpdateResources = async ( formData, imageStreamURL, imageStreamName, + undefined, + undefined, defaultAnnotations, _.get(appResources, 'editAppResource.data'), ); @@ -503,3 +507,11 @@ export const createOrUpdateResources = async ( return Promise.all(requests); }; + +export const handleRedirect = (project: string, perspective: string) => { + const perspectiveData = plugins.registry + .getPerspectives() + .find((item) => item.properties.id === perspective); + const redirectURL = perspectiveData.properties.getImportRedirectURL(project); + history.push(redirectURL); +}; diff --git a/frontend/packages/dev-console/src/plugin.tsx b/frontend/packages/dev-console/src/plugin.tsx index 230a41b2dfc..c8ca27f2ac9 100644 --- a/frontend/packages/dev-console/src/plugin.tsx +++ b/frontend/packages/dev-console/src/plugin.tsx @@ -415,9 +415,11 @@ const plugin: Plugin = [ exact: true, path: '/edit/ns/:ns', loader: async () => - (await import( - './components/edit-application/EditApplicationPage' /* webpackChunkName: "dev-console-edit" */ - )).default, + ( + await import( + './components/edit-application/EditApplicationPage' /* webpackChunkName: "dev-console-edit" */ + ) + ).default, }, }, { diff --git a/frontend/packages/dev-console/src/utils/resource-label-utils.ts b/frontend/packages/dev-console/src/utils/resource-label-utils.ts index 1e82de42f7d..cafbec16474 100644 --- a/frontend/packages/dev-console/src/utils/resource-label-utils.ts +++ b/frontend/packages/dev-console/src/utils/resource-label-utils.ts @@ -20,7 +20,7 @@ export const getAppLabels = ( labels['app.openshift.io/runtime-version'] = selectedTag; } if (namespace) { - labels['app/runtime-namespace'] = namespace; + labels['app.openshift.io/runtime-namespace'] = namespace; } return labels; diff --git a/frontend/packages/knative-plugin/src/utils/create-knative-utils.ts b/frontend/packages/knative-plugin/src/utils/create-knative-utils.ts index ac8b86c5fc2..c1bcec6ecde 100644 --- a/frontend/packages/knative-plugin/src/utils/create-knative-utils.ts +++ b/frontend/packages/knative-plugin/src/utils/create-knative-utils.ts @@ -22,6 +22,8 @@ export const getKnativeServiceDepResource = ( formData: GitImportFormData | DeployImageFormData, imageStreamUrl: string, imageStreamName?: string, + imageStreamTag?: string, + imageNamespace?: string, annotations?: { [name: string]: string }, originalKnativeService?: K8sResourceKind, ): K8sResourceKind => { @@ -51,7 +53,13 @@ export const getKnativeServiceDepResource = ( limitUnit: memoryLimitUnit, }, } = limits; - const defaultLabel = getAppLabels(name, applicationName, imageStreamName, imageTag); + const defaultLabel = getAppLabels( + name, + applicationName, + imageStreamName, + imageStreamTag || imageTag, + imageNamespace, + ); delete defaultLabel.app; const newKnativeDeployResource: K8sResourceKind = { kind: ServiceModel.kind,