diff --git a/app/kubernetes/converters/application.js b/app/kubernetes/converters/application.js
index 7555c66b3..d3161b410 100644
--- a/app/kubernetes/converters/application.js
+++ b/app/kubernetes/converters/application.js
@@ -325,7 +325,7 @@ class KubernetesApplicationConverter {
}
if (app.Pods && app.Pods.length) {
- KubernetesApplicationHelper.generatePlacementsFormValuesFromAffinity(res, app.Pods[0].Affinity, nodesLabels);
+ KubernetesApplicationHelper.generatePlacementsFormValuesFromAffinity(res, app.Pods[0].Affinity);
}
return res;
diff --git a/app/kubernetes/helpers/application/index.js b/app/kubernetes/helpers/application/index.js
index 46d780776..115f5208f 100644
--- a/app/kubernetes/helpers/application/index.js
+++ b/app/kubernetes/helpers/application/index.js
@@ -22,7 +22,7 @@ import {
KubernetesApplicationVolumeSecretPayload,
} from 'Kubernetes/models/application/payloads';
import KubernetesVolumeHelper from 'Kubernetes/helpers/volumeHelper';
-import { KubernetesApplicationDeploymentTypes, KubernetesApplicationPlacementTypes, KubernetesApplicationTypes, HelmApplication } from 'Kubernetes/models/application/models';
+import { KubernetesApplicationDeploymentTypes, KubernetesApplicationTypes, HelmApplication } from 'Kubernetes/models/application/models';
import { KubernetesPodAffinity, KubernetesPodNodeAffinityNodeSelectorRequirementOperators } from 'Kubernetes/pod/models';
import {
KubernetesNodeSelectorRequirementPayload,
@@ -429,31 +429,29 @@ class KubernetesApplicationHelper {
/* #endregion */
/* #region PLACEMENTS FV <> AFFINITY */
- static generatePlacementsFormValuesFromAffinity(formValues, podAffinity, nodesLabels) {
+ static generatePlacementsFormValuesFromAffinity(formValues, podAffinity) {
let placements = formValues.Placements;
let type = formValues.PlacementType;
const affinity = podAffinity.nodeAffinity;
if (affinity && affinity.requiredDuringSchedulingIgnoredDuringExecution) {
- type = KubernetesApplicationPlacementTypes.MANDATORY;
+ type = 'mandatory';
_.forEach(affinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms, (term) => {
_.forEach(term.matchExpressions, (exp) => {
const placement = new KubernetesApplicationPlacementFormValue();
- const label = _.find(nodesLabels, { Key: exp.key });
- placement.Label = label;
- placement.Value = exp.values[0];
- placement.IsNew = false;
+ placement.label = exp.key;
+ placement.value = exp.values[0];
+ placement.isNew = false;
placements.push(placement);
});
});
} else if (affinity && affinity.preferredDuringSchedulingIgnoredDuringExecution) {
- type = KubernetesApplicationPlacementTypes.PREFERRED;
+ type = 'preferred';
_.forEach(affinity.preferredDuringSchedulingIgnoredDuringExecution, (term) => {
_.forEach(term.preference.matchExpressions, (exp) => {
const placement = new KubernetesApplicationPlacementFormValue();
- const label = _.find(nodesLabels, { Key: exp.key });
- placement.Label = label;
- placement.Value = exp.values[0];
- placement.IsNew = false;
+ placement.label = exp.key;
+ placement.value = exp.values[0];
+ placement.isNew = false;
placements.push(placement);
});
});
@@ -467,12 +465,12 @@ class KubernetesApplicationHelper {
const placements = formValues.Placements;
const res = new KubernetesPodNodeAffinityPayload();
let expressions = _.map(placements, (p) => {
- if (!p.NeedsDeletion) {
+ if (!p.needsDeletion) {
const exp = new KubernetesNodeSelectorRequirementPayload();
- exp.key = p.Label.Key;
- if (p.Value) {
+ exp.key = p.label;
+ if (p.value) {
exp.operator = KubernetesPodNodeAffinityNodeSelectorRequirementOperators.IN;
- exp.values = [p.Value];
+ exp.values = [p.value];
} else {
exp.operator = KubernetesPodNodeAffinityNodeSelectorRequirementOperators.EXISTS;
delete exp.values;
@@ -482,12 +480,12 @@ class KubernetesApplicationHelper {
});
expressions = _.without(expressions, undefined);
if (expressions.length) {
- if (formValues.PlacementType === KubernetesApplicationPlacementTypes.MANDATORY) {
+ if (formValues.PlacementType === 'mandatory') {
const term = new KubernetesNodeSelectorTermPayload();
term.matchExpressions = expressions;
res.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms.push(term);
delete res.preferredDuringSchedulingIgnoredDuringExecution;
- } else if (formValues.PlacementType === KubernetesApplicationPlacementTypes.PREFERRED) {
+ } else if (formValues.PlacementType === 'preferred') {
const term = new KubernetesPreferredSchedulingTermPayload();
term.preference = new KubernetesNodeSelectorTermPayload();
term.preference.matchExpressions = expressions;
diff --git a/app/kubernetes/models/application/formValues.js b/app/kubernetes/models/application/formValues.js
index aa4f50563..57eefcade 100644
--- a/app/kubernetes/models/application/formValues.js
+++ b/app/kubernetes/models/application/formValues.js
@@ -1,5 +1,5 @@
import { PorImageRegistryModel } from '@/docker/models/porImageRegistry';
-import { KubernetesApplicationDataAccessPolicies, KubernetesApplicationDeploymentTypes, KubernetesApplicationPlacementTypes } from './models';
+import { KubernetesApplicationDataAccessPolicies, KubernetesApplicationDeploymentTypes } from './models';
/**
* KubernetesApplicationFormValues Model
@@ -25,7 +25,7 @@ export function KubernetesApplicationFormValues() {
this.ConfigMaps = [];
this.Secrets = [];
this.PublishedPorts = []; // KubernetesApplicationPublishedPortFormValue lis;
- this.PlacementType = KubernetesApplicationPlacementTypes.PREFERRED;
+ this.PlacementType = 'preferred';
this.Placements = []; // KubernetesApplicationPlacementFormValue lis;
this.OriginalIngresses = undefined;
}
@@ -119,10 +119,10 @@ export function KubernetesApplicationPublishedPortFormValue() {
export function KubernetesApplicationPlacementFormValue() {
return {
- Label: {},
- Value: '',
- NeedsDeletion: false,
- IsNew: true,
+ label: {},
+ value: '',
+ needsDeletion: false,
+ isNew: true,
};
}
diff --git a/app/kubernetes/models/application/models/constants.js b/app/kubernetes/models/application/models/constants.js
index dead6e7f3..aa06f7f25 100644
--- a/app/kubernetes/models/application/models/constants.js
+++ b/app/kubernetes/models/application/models/constants.js
@@ -30,11 +30,6 @@ export const KubernetesApplicationPublishingTypes = Object.freeze({
LOAD_BALANCER: 3,
});
-export const KubernetesApplicationPlacementTypes = Object.freeze({
- PREFERRED: 1,
- MANDATORY: 2,
-});
-
export const KubernetesApplicationQuotaDefaults = {
CpuLimit: 0.1,
MemoryLimit: 64, // MB
diff --git a/app/kubernetes/react/components/index.ts b/app/kubernetes/react/components/index.ts
index e9f986fbc..c4473c3b8 100644
--- a/app/kubernetes/react/components/index.ts
+++ b/app/kubernetes/react/components/index.ts
@@ -18,6 +18,10 @@ import {
ApplicationEventsDatatable,
} from '@/react/kubernetes/applications/DetailsView';
import { ApplicationContainersDatatable } from '@/react/kubernetes/applications/DetailsView/ApplicationContainersDatatable';
+import {
+ PlacementFormSection,
+ placementValidation,
+} from '@/react/kubernetes/applications/components/PlacementFormSection';
import { withFormValidation } from '@/react-tools/withFormValidation';
import { withCurrentUser } from '@/react-tools/withCurrentUser';
import { YAMLInspector } from '@/react/kubernetes/components/YAMLInspector';
@@ -267,3 +271,11 @@ withFormValidation(
['isMetricsEnabled'],
autoScalingValidation
);
+
+withFormValidation(
+ ngModule,
+ withUIRouter(withCurrentUser(withReactQuery(PlacementFormSection))),
+ 'placementFormSection',
+ [],
+ placementValidation
+);
diff --git a/app/kubernetes/views/applications/create/createApplication.html b/app/kubernetes/views/applications/create/createApplication.html
index a2afda66c..90093a1f8 100644
--- a/app/kubernetes/views/applications/create/createApplication.html
+++ b/app/kubernetes/views/applications/create/createApplication.html
@@ -475,105 +475,16 @@
>
-
-
-
Placement preferences and constraints
-
-
-
-
-
-
+
+
-
+
{
- this.formValues.PlacementType = value;
+ onChangePlacements(values) {
+ return this.$async(async () => {
+ this.formValues.Placements = values.placements;
+ this.formValues.PlacementType = values.placementType;
});
}
@@ -410,50 +405,6 @@ class KubernetesCreateApplicationController {
}
/* #endregion */
- /* #region PLACEMENT UI MANAGEMENT */
- addPlacement() {
- const placement = new KubernetesApplicationPlacementFormValue();
- const label = this.nodesLabels[0];
- placement.Label = label;
- placement.Value = label.Values[0];
- this.formValues.Placements.push(placement);
- this.onChangePlacement();
- }
-
- restorePlacement(index) {
- this.formValues.Placements[index].NeedsDeletion = false;
- this.onChangePlacement();
- }
-
- removePlacement(index) {
- if (this.state.isEdit && !this.formValues.Placements[index].IsNew) {
- this.formValues.Placements[index].NeedsDeletion = true;
- } else {
- this.formValues.Placements.splice(index, 1);
- }
- this.onChangePlacement();
- }
-
- // call all validation functions when a placement is added/removed/restored
- onChangePlacement() {
- this.onChangePlacementLabelValidate();
- }
-
- onChangePlacementLabel(index) {
- this.formValues.Placements[index].Value = this.formValues.Placements[index].Label.Values[0];
- this.onChangePlacementLabelValidate();
- }
-
- onChangePlacementLabelValidate() {
- const state = this.state.duplicates.placements;
- const source = _.map(this.formValues.Placements, (p) => (p.NeedsDeletion ? undefined : p.Label.Key));
- const duplicates = KubernetesFormValidationHelper.getDuplicates(source);
- state.refs = duplicates;
- state.hasRefs = Object.keys(duplicates).length > 0;
- }
-
- /* #endregion */
-
/* #region SERVICES UI MANAGEMENT */
onServicesChange(services) {
return this.$async(async () => {
@@ -675,15 +626,6 @@ class KubernetesCreateApplicationController {
}
/* #endregion */
- isEditAndNotNewPlacement(index) {
- return this.state.isEdit && !this.formValues.Placements[index].IsNew;
- }
-
- showPlacementPolicySection() {
- const placements = _.filter(this.formValues.Placements, { NeedsDeletion: false });
- return placements.length !== 0;
- }
-
isNonScalable() {
const scalable = this.supportScalableReplicaDeployment();
const global = this.supportGlobalDeployment();
diff --git a/app/react/components/form-components/InputList/InputList.tsx b/app/react/components/form-components/InputList/InputList.tsx
index dce0818d0..eef2d42ac 100644
--- a/app/react/components/form-components/InputList/InputList.tsx
+++ b/app/react/components/form-components/InputList/InputList.tsx
@@ -188,6 +188,7 @@ export function InputList({
initialItemsCount={initialItemsCount.current}
handleRemoveItem={handleRemoveItem}
handleToggleNeedsDeletion={handleToggleNeedsDeletion}
+ dataCy={`${deleteButtonDataCy}_${index}`}
/>
)}
@@ -325,6 +326,7 @@ type CanUndoDeleteButtonProps
= {
initialItemsCount: number;
handleRemoveItem(key: Key, item: T): void;
handleToggleNeedsDeletion(key: Key, item: T): void;
+ dataCy: string;
};
function CanUndoDeleteButton({
@@ -333,6 +335,7 @@ function CanUndoDeleteButton({
initialItemsCount,
handleRemoveItem,
handleToggleNeedsDeletion,
+ dataCy,
}: CanUndoDeleteButtonProps) {
return (
@@ -343,6 +346,7 @@ function CanUndoDeleteButton({
onClick={handleDeleteClick}
className="vertical-center btn-only-icon"
icon={Trash2}
+ data-cy={`${dataCy}_delete`}
/>
)}
{item.needsDeletion && (
@@ -352,6 +356,7 @@ function CanUndoDeleteButton({
onClick={handleDeleteClick}
className="vertical-center btn-only-icon"
icon={RotateCw}
+ data-cy={`${dataCy}_undo_delete`}
/>
)}
diff --git a/app/react/components/form-components/ReactSelect.tsx b/app/react/components/form-components/ReactSelect.tsx
index bb460e141..8b12edbb5 100644
--- a/app/react/components/form-components/ReactSelect.tsx
+++ b/app/react/components/form-components/ReactSelect.tsx
@@ -57,7 +57,7 @@ export function Select<
isCreatable = false,
size = 'md',
...props
-}: Props