Explorar o código

Fix disabled 'Update' button in replica edit modal

This happens, for example, when editing a replica with Openstack as the
source provider. The issue was caused by not properly handling object
validation in the case of nested objects inside replica's source or
destination environment.
Sergiu Miclea %!s(int64=3) %!d(string=hai) anos
pai
achega
f11aa4e2b9

+ 3 - 8
src/components/modules/TransferModule/TransferItemModal/TransferItemModal.tsx

@@ -51,6 +51,7 @@ import minionPoolStore from '@src/stores/MinionPoolStore'
 import WizardScripts from '@src/components/modules/WizardModule/WizardScripts'
 import WizardScripts from '@src/components/modules/WizardModule/WizardScripts'
 import networkStore from '@src/stores/NetworkStore'
 import networkStore from '@src/stores/NetworkStore'
 import { ThemeProps } from '@src/components/Theme'
 import { ThemeProps } from '@src/components/Theme'
+import ObjectUtils from '@src/utils/ObjectUtils'
 
 
 const PanelContent = styled.div<any>`
 const PanelContent = styled.div<any>`
   display: flex;
   display: flex;
@@ -392,10 +393,7 @@ class TransferItemModal extends React.Component<Props, State> {
     const envData = getFieldChangeOptions({
     const envData = getFieldChangeOptions({
       providerName: endpoint.type,
       providerName: endpoint.type,
       schema: type === 'source' ? providerStore.sourceSchema : providerStore.destinationSchema,
       schema: type === 'source' ? providerStore.sourceSchema : providerStore.destinationSchema,
-      data: {
-        ...env,
-        ...stateEnv,
-      },
+      data: ObjectUtils.mergeDeep(env, stateEnv),
       field: field || null,
       field: field || null,
       type,
       type,
       parentFieldName,
       parentFieldName,
@@ -458,10 +456,7 @@ class TransferItemModal extends React.Component<Props, State> {
     const env = type === 'source' ? this.props.replica.source_environment : this.props.replica.destination_environment
     const env = type === 'source' ? this.props.replica.source_environment : this.props.replica.destination_environment
     const data = type === 'source' ? this.state.sourceData : this.state.destinationData
     const data = type === 'source' ? this.state.sourceData : this.state.destinationData
     const schema = type === 'source' ? providerStore.sourceSchema : providerStore.destinationSchema
     const schema = type === 'source' ? providerStore.sourceSchema : providerStore.destinationSchema
-    const invalidFields = findInvalidFields({
-      ...env,
-      ...data,
-    }, schema)
+    const invalidFields = findInvalidFields(ObjectUtils.mergeDeep(env, data), schema)
 
 
     this.setState({ updateDisabled: invalidFields.length > 0 })
     this.setState({ updateDisabled: invalidFields.length > 0 })
   }
   }

+ 44 - 4
src/utils/ObjectUtils.ts

@@ -24,7 +24,11 @@ class ObjectUtils {
     return value != null
     return value != null
   }
   }
 
 
-  static flatten(object: any, appendParentPath?: boolean, parent?: string): any {
+  static flatten(
+    object: any,
+    appendParentPath?: boolean,
+    parent?: string,
+  ): any {
     let result: any = {}
     let result: any = {}
 
 
     Object.keys(object).forEach(k => {
     Object.keys(object).forEach(k => {
@@ -72,10 +76,16 @@ class ObjectUtils {
   }
   }
 
 
   static async wait(ms: number) {
   static async wait(ms: number) {
-    return new Promise<void>(r => { setTimeout(() => r(), ms) })
+    return new Promise<void>(r => {
+      setTimeout(() => r(), ms)
+    })
   }
   }
 
 
-  static async waitFor(predicate: () => boolean, timeoutMs: number = 15000, tryEvery: number = 1000) {
+  static async waitFor(
+    predicate: () => boolean,
+    timeoutMs: number = 15000,
+    tryEvery: number = 1000,
+  ) {
     const startTime = new Date().getTime()
     const startTime = new Date().getTime()
     const testLoop = async () => {
     const testLoop = async () => {
       if (predicate()) {
       if (predicate()) {
@@ -100,7 +110,11 @@ class ObjectUtils {
     return value.charAt(0).toUpperCase() + value.slice(1)
     return value.charAt(0).toUpperCase() + value.slice(1)
   }
   }
 
 
-  static async retry(retryFunction: () => Promise<any>, retryEvery: number = 1000, retryCount: number = 3): Promise<any> {
+  static async retry(
+    retryFunction: () => Promise<any>,
+    retryEvery: number = 1000,
+    retryCount: number = 3,
+  ): Promise<any> {
     let currentTry = 0
     let currentTry = 0
     const retryLoop = async (): Promise<any> => {
     const retryLoop = async (): Promise<any> => {
       try {
       try {
@@ -122,6 +136,32 @@ class ObjectUtils {
 
 
     return retryLoop()
     return retryLoop()
   }
   }
+
+  static isObject(item: any) {
+    return item && typeof item === 'object' && !Array.isArray(item)
+  }
+
+  static mergeDeep(target: any, ...sources: any[]): any {
+    if (!sources.length) {
+      return target
+    }
+    const source = sources.shift()
+
+    if (this.isObject(target) && this.isObject(source)) {
+      Object.keys(source).forEach(key => {
+        if (this.isObject(source[key])) {
+          if (!target[key]) {
+            Object.assign(target, { [key]: {} })
+          }
+          this.mergeDeep(target[key], source[key])
+        } else {
+          Object.assign(target, { [key]: source[key] })
+        }
+      })
+    }
+
+    return this.mergeDeep(target, ...sources)
+  }
 }
 }
 
 
 export default ObjectUtils
 export default ObjectUtils