فهرست منبع

Add dictionary support for fields with same name

Previously, if there were multiple fields with the same name, the label
for the first loaded field would be used for all the rest.

With this commit, you can have fields with the same name and different
labels as long as they belong to different providers or they have
different directions (i.e. one is for destination, one is for source).
Sergiu Miclea 6 سال پیش
والد
کامیت
1293baca57

+ 7 - 7
src/components/molecules/FieldInput/FieldInput.jsx

@@ -95,9 +95,9 @@ type Props = {
   layout: 'modal' | 'page',
   layout: 'modal' | 'page',
   width?: number,
   width?: number,
   label?: string,
   label?: string,
+  description?: string,
   addNullValue?: boolean,
   addNullValue?: boolean,
   nullableBoolean?: boolean,
   nullableBoolean?: boolean,
-  description?: string,
   style?: { [string]: mixed },
   style?: { [string]: mixed },
 }
 }
 @observer
 @observer
@@ -127,7 +127,7 @@ class FieldInput extends React.Component<Props> {
         type={this.props.password ? 'password' : 'text'}
         type={this.props.password ? 'password' : 'text'}
         value={this.props.value}
         value={this.props.value}
         onChange={e => { if (this.props.onChange) this.props.onChange(e.target.value) }}
         onChange={e => { if (this.props.onChange) this.props.onChange(e.target.value) }}
-        placeholder={LabelDictionary.get(this.props.name)}
+        placeholder={this.props.label}
         disabled={this.props.disabled}
         disabled={this.props.disabled}
         required={this.props.layout === 'page' ? false : this.props.required}
         required={this.props.layout === 'page' ? false : this.props.required}
         disabledLoading={this.props.disabledLoading}
         disabledLoading={this.props.disabledLoading}
@@ -201,7 +201,7 @@ class FieldInput extends React.Component<Props> {
         highlight={this.props.highlight}
         highlight={this.props.highlight}
         value={this.props.value}
         value={this.props.value}
         onChange={e => { if (this.props.onChange) this.props.onChange(e.target.value) }}
         onChange={e => { if (this.props.onChange) this.props.onChange(e.target.value) }}
-        placeholder={LabelDictionary.get(this.props.name)}
+        placeholder={this.props.label}
         disabled={this.props.disabled}
         disabled={this.props.disabled}
         disabledLoading={this.props.disabledLoading}
         disabledLoading={this.props.disabledLoading}
         required={this.props.layout === 'page' ? false : this.props.required}
         required={this.props.layout === 'page' ? false : this.props.required}
@@ -289,7 +289,7 @@ class FieldInput extends React.Component<Props> {
     return (
     return (
       <RadioInput
       <RadioInput
         checked={this.props.value}
         checked={this.props.value}
-        label={LabelDictionary.get(this.props.name)}
+        label={this.props.label || ''}
         onChange={checked => { if (this.props.onChange) this.props.onChange(checked) }}
         onChange={checked => { if (this.props.onChange) this.props.onChange(checked) }}
         disabled={this.props.disabled}
         disabled={this.props.disabled}
         disabledLoading={this.props.disabledLoading}
         disabledLoading={this.props.disabledLoading}
@@ -317,7 +317,7 @@ class FieldInput extends React.Component<Props> {
         onItemChange={item => { if (this.props.onChange) this.props.onChange(item.value) }}
         onItemChange={item => { if (this.props.onChange) this.props.onChange(item.value) }}
         inputValue={this.props.getFieldValue ? this.props.getFieldValue(fieldName) : ''}
         inputValue={this.props.getFieldValue ? this.props.getFieldValue(fieldName) : ''}
         onInputChange={value => { if (this.props.onFieldChange) this.props.onFieldChange(fieldName, value) }}
         onInputChange={value => { if (this.props.onFieldChange) this.props.onFieldChange(fieldName, value) }}
-        placeholder={LabelDictionary.get(fieldName)}
+        placeholder={this.props.label}
         highlight={this.props.highlight}
         highlight={this.props.highlight}
         disabled={this.props.disabled}
         disabled={this.props.disabled}
         disabledLoading={this.props.disabledLoading}
         disabledLoading={this.props.disabledLoading}
@@ -358,13 +358,13 @@ class FieldInput extends React.Component<Props> {
       return null
       return null
     }
     }
 
 
-    let description = LabelDictionary.getDescription(this.props.name) || this.props.description
+    let description = this.props.description
     let marginRight = this.props.layout === 'modal' || description || this.props.required ? '24px' : 0
     let marginRight = this.props.layout === 'modal' || description || this.props.required ? '24px' : 0
 
 
     return (
     return (
       <Label layout={this.props.layout} disabledLoading={this.props.disabledLoading}>
       <Label layout={this.props.layout} disabledLoading={this.props.disabledLoading}>
         <LabelText style={{ marginRight }}>
         <LabelText style={{ marginRight }}>
-          {this.props.label || LabelDictionary.get(this.props.name)}
+          {this.props.label}
         </LabelText>
         </LabelText>
         {description ? <InfoIcon text={description} marginLeft={-20} marginBottom={this.props.layout === 'page' ? null : 0} /> : null}
         {description ? <InfoIcon text={description} marginLeft={-20} marginBottom={this.props.layout === 'page' ? null : 0} /> : null}
         {this.props.layout === 'page' && Boolean(this.props.required) ? <Asterisk marginLeft={description ? '4px' : '-16px'} /> : null}
         {this.props.layout === 'page' && Boolean(this.props.required) ? <Asterisk marginLeft={description ? '4px' : '-16px'} /> : null}

+ 6 - 0
src/components/organisms/EditReplica/EditReplica.jsx

@@ -481,6 +481,11 @@ class EditReplica extends React.Component<Props, State> {
     if (extraOptionsConfig) {
     if (extraOptionsConfig) {
       optionsLoadingSkipFields = extraOptionsConfig.requiredFields
       optionsLoadingSkipFields = extraOptionsConfig.requiredFields
     }
     }
+    let endpoint = type === 'source' ? this.props.sourceEndpoint : this.props.destinationEndpoint
+    let dictionaryKey = ''
+    if (endpoint) {
+      dictionaryKey = `${endpoint.type}-${type}`
+    }
     return (
     return (
       <WizardOptions
       <WizardOptions
         wizardType={`${this.props.type || 'replica'}-${type}-options-edit`}
         wizardType={`${this.props.type || 'replica'}-${type}-options-edit`}
@@ -500,6 +505,7 @@ class EditReplica extends React.Component<Props, State> {
         optionsLoading={optionsLoading}
         optionsLoading={optionsLoading}
         optionsLoadingSkipFields={[...optionsLoadingSkipFields, 'description', 'execute_now',
         optionsLoadingSkipFields={[...optionsLoadingSkipFields, 'description', 'execute_now',
           'execute_now_options', ...migrationFields.map(f => f.name)]}
           'execute_now_options', ...migrationFields.map(f => f.name)]}
+        dictionaryKey={dictionaryKey}
       />
       />
     )
     )
   }
   }

+ 5 - 2
src/components/organisms/MainDetails/MainDetails.jsx

@@ -217,10 +217,13 @@ class MainDetails extends React.Component<Props> {
     let properties = []
     let properties = []
     let plugin = endpoint && (OptionsSchemaPlugin[endpoint.type] || OptionsSchemaPlugin.default)
     let plugin = endpoint && (OptionsSchemaPlugin[endpoint.type] || OptionsSchemaPlugin.default)
     let migrationImageMapFieldName = plugin && plugin.migrationImageMapFieldName
     let migrationImageMapFieldName = plugin && plugin.migrationImageMapFieldName
-
+    let dictionaryKey = ''
+    if (endpoint) {
+      dictionaryKey = `${endpoint.type}-destination`
+    }
     propertyNames.forEach(pn => {
     propertyNames.forEach(pn => {
       let value = this.props.item ? this.props.item.destination_environment[pn] : ''
       let value = this.props.item ? this.props.item.destination_environment[pn] : ''
-      let label = LabelDictionary.get(pn)
+      let label = LabelDictionary.get(pn, dictionaryKey)
 
 
       if (value && value.join) {
       if (value && value.join) {
         // $FlowIgnore
         // $FlowIgnore

+ 3 - 2
src/components/organisms/WizardOptions/WizardOptions.jsx

@@ -142,6 +142,7 @@ type Props = {
   loading?: boolean,
   loading?: boolean,
   optionsLoading?: boolean,
   optionsLoading?: boolean,
   optionsLoadingSkipFields?: string[],
   optionsLoadingSkipFields?: string[],
+  dictionaryKey: string,
 }
 }
 @observer
 @observer
 class WizardOptions extends React.Component<Props> {
 class WizardOptions extends React.Component<Props> {
@@ -261,14 +262,14 @@ class WizardOptions extends React.Component<Props> {
         type={field.type}
         type={field.type}
         minimum={field.minimum}
         minimum={field.minimum}
         maximum={field.maximum}
         maximum={field.maximum}
-        description={field.description}
+        label={field.label || LabelDictionary.get(field.name, this.props.dictionaryKey)}
+        description={field.description || LabelDictionary.getDescription(field.name, this.props.dictionaryKey)}
         password={this.isPassword(field.name)}
         password={this.isPassword(field.name)}
         enum={field.enum}
         enum={field.enum}
         addNullValue
         addNullValue
         required={field.required}
         required={field.required}
         data-test-id={`wOptions-field-${field.name}`}
         data-test-id={`wOptions-field-${field.name}`}
         width={this.props.fieldWidth || StyleProps.inputSizes.wizard.width}
         width={this.props.fieldWidth || StyleProps.inputSizes.wizard.width}
-        label={field.label}
         nullableBoolean={field.nullableBoolean}
         nullableBoolean={field.nullableBoolean}
         disabledLoading={this.props.optionsLoading && !optionsLoadingReqFields.find(fn => fn === field.name)}
         disabledLoading={this.props.optionsLoading && !optionsLoadingReqFields.find(fn => fn === field.name)}
         {...additionalProps}
         {...additionalProps}

+ 2 - 0
src/components/organisms/WizardPageContent/WizardPageContent.jsx

@@ -423,6 +423,7 @@ class WizardPageContent extends React.Component<Props, State> {
             wizardType={`${this.props.type}-source-options`}
             wizardType={`${this.props.type}-source-options`}
             layout="page"
             layout="page"
             isSource
             isSource
+            dictionaryKey={`${this.props.wizardData.source ? this.props.wizardData.source.type : ''}-source`}
           />
           />
         )
         )
         break
         break
@@ -445,6 +446,7 @@ class WizardPageContent extends React.Component<Props, State> {
             wizardType={this.props.type}
             wizardType={this.props.type}
             onAdvancedOptionsToggle={useAdvancedOptions => { this.handleAdvancedOptionsToggle(useAdvancedOptions) }}
             onAdvancedOptionsToggle={useAdvancedOptions => { this.handleAdvancedOptionsToggle(useAdvancedOptions) }}
             layout="page"
             layout="page"
+            dictionaryKey={`${this.props.wizardData.target ? this.props.wizardData.target.type : ''}-destination`}
           />
           />
         )
         )
         break
         break

+ 4 - 3
src/components/organisms/WizardSummary/WizardSummary.jsx

@@ -260,7 +260,6 @@ class WizardSummary extends React.Component<Props> {
     if (!data.sourceOptions) {
     if (!data.sourceOptions) {
       return null
       return null
     }
     }
-
     return (
     return (
       <Section>
       <Section>
         <SectionTitle>{type} Source Options</SectionTitle>
         <SectionTitle>{type} Source Options</SectionTitle>
@@ -269,7 +268,8 @@ class WizardSummary extends React.Component<Props> {
             if (!data.sourceOptions || data.sourceOptions[optionName] == null || data.sourceOptions[optionName] === '') {
             if (!data.sourceOptions || data.sourceOptions[optionName] == null || data.sourceOptions[optionName] === '') {
               return null
               return null
             }
             }
-            let optionLabel = optionName.split('/').map(n => LabelDictionary.get(n)).join(' - ')
+            let optionLabel = optionName.split('/')
+              .map(n => LabelDictionary.get(n, `${data.source ? data.source.type : ''}-source`)).join(' - ')
             let optionValue = fieldHelper.getValueAlias(optionName, data.sourceOptions && data.sourceOptions[optionName], this.props.sourceSchema, provider)
             let optionValue = fieldHelper.getValueAlias(optionName, data.sourceOptions && data.sourceOptions[optionName], this.props.sourceSchema, provider)
             return (
             return (
               <Option key={optionName}>
               <Option key={optionName}>
@@ -346,7 +346,8 @@ class WizardSummary extends React.Component<Props> {
               return null
               return null
             }
             }
 
 
-            let optionLabel = optionName.split('/').map(n => LabelDictionary.get(n)).join(' - ')
+            let optionLabel = optionName.split('/')
+              .map(n => LabelDictionary.get(n, `${data.target ? data.target.type : ''}-destination`)).join(' - ')
             let optionValue = fieldHelper.getValueAlias(optionName, data.destOptions && data.destOptions[optionName], this.props.destinationSchema, provider)
             let optionValue = fieldHelper.getValueAlias(optionName, data.destOptions && data.destOptions[optionName], this.props.destinationSchema, provider)
             return (
             return (
               <Option key={optionName}>
               <Option key={optionName}>

+ 4 - 4
src/plugins/endpoint/default/ConnectionSchemaPlugin.js

@@ -19,7 +19,7 @@ import Utils from '../../../utils/ObjectUtils'
 import type { Schema, SchemaProperties, SchemaDefinitions } from '../../../types/Schema'
 import type { Schema, SchemaProperties, SchemaDefinitions } from '../../../types/Schema'
 import type { Field } from '../../../types/Field'
 import type { Field } from '../../../types/Field'
 
 
-export const defaultSchemaToFields = (schema: SchemaProperties, schemaDefinitions?: ?SchemaDefinitions, parent?: string): any[] => {
+export const defaultSchemaToFields = (schema: SchemaProperties, schemaDefinitions?: ?SchemaDefinitions, parent?: ?string, dictionaryKey?: string): any[] => {
   if (!schema.properties) {
   if (!schema.properties) {
     return []
     return []
   }
   }
@@ -33,18 +33,18 @@ export const defaultSchemaToFields = (schema: SchemaProperties, schemaDefinition
       return {
       return {
         name: fieldName,
         name: fieldName,
         type: properties.type ? properties.type : '',
         type: properties.type ? properties.type : '',
-        properties: properties.properties ? defaultSchemaToFields(properties, null, fieldName) : [],
+        properties: properties.properties ? defaultSchemaToFields(properties, null, fieldName, dictionaryKey) : [],
       }
       }
     } else if (properties.type === 'object' && properties.properties && Object.keys(properties.properties).length) {
     } else if (properties.type === 'object' && properties.properties && Object.keys(properties.properties).length) {
       return {
       return {
         name: fieldName,
         name: fieldName,
         type: 'object',
         type: 'object',
-        properties: defaultSchemaToFields(properties, null, fieldName),
+        properties: defaultSchemaToFields(properties, null, fieldName, dictionaryKey),
       }
       }
     }
     }
 
 
     const name = parent ? `${parent}/${fieldName}` : fieldName
     const name = parent ? `${parent}/${fieldName}` : fieldName
-    LabelDictionary.pushToCache({ name, title: properties.title, description: properties.description })
+    LabelDictionary.pushToCache({ name, title: properties.title, description: properties.description }, dictionaryKey || '')
 
 
     return {
     return {
       ...properties,
       ...properties,

+ 2 - 2
src/plugins/endpoint/default/OptionsSchemaPlugin.js

@@ -122,8 +122,8 @@ export const defaultGetMigrationImageMap = (options: ?{ [string]: mixed }, migra
 export default class OptionsSchemaParser {
 export default class OptionsSchemaParser {
   static migrationImageMapFieldName = 'migr_image_map'
   static migrationImageMapFieldName = 'migr_image_map'
 
 
-  static parseSchemaToFields(schema: SchemaProperties, schemaDefinitions?: ?SchemaDefinitions) {
-    return defaultSchemaToFields(schema, schemaDefinitions)
+  static parseSchemaToFields(schema: SchemaProperties, schemaDefinitions?: ?SchemaDefinitions, dictionaryKey: string) {
+    return defaultSchemaToFields(schema, schemaDefinitions, null, dictionaryKey)
   }
   }
 
 
   static fillFieldValues(field: Field, options: OptionValues[], customFieldName?: string) {
   static fillFieldValues(field: Field, options: OptionValues[], customFieldName?: string) {

+ 2 - 2
src/plugins/endpoint/openstack/OptionsSchemaPlugin.js

@@ -26,8 +26,8 @@ import type { NetworkMap } from '../../../types/Network'
 export default class OptionsSchemaParser {
 export default class OptionsSchemaParser {
   static migrationImageMapFieldName = DefaultOptionsSchemaPlugin.migrationImageMapFieldName
   static migrationImageMapFieldName = DefaultOptionsSchemaPlugin.migrationImageMapFieldName
 
 
-  static parseSchemaToFields(schema: SchemaProperties, schemaDefinitions?: ?SchemaDefinitions) {
-    let fields = DefaultOptionsSchemaPlugin.parseSchemaToFields(schema, schemaDefinitions)
+  static parseSchemaToFields(schema: SchemaProperties, schemaDefinitions?: ?SchemaDefinitions, dictionaryKey: string) {
+    let fields = DefaultOptionsSchemaPlugin.parseSchemaToFields(schema, schemaDefinitions, dictionaryKey)
     let exportMechField = fields.find(f => f.name === 'replica_export_mechanism')
     let exportMechField = fields.find(f => f.name === 'replica_export_mechanism')
     if (exportMechField) {
     if (exportMechField) {
       exportMechField.subFields = []
       exportMechField.subFields = []

+ 2 - 2
src/plugins/endpoint/ovm/OptionsSchemaPlugin.js

@@ -30,8 +30,8 @@ import type { NetworkMap } from '../../../types/Network'
 export default class OptionsSchemaParser {
 export default class OptionsSchemaParser {
   static migrationImageMapFieldName = 'migr_template_map'
   static migrationImageMapFieldName = 'migr_template_map'
 
 
-  static parseSchemaToFields(schema: SchemaProperties, schemaDefinitions?: ?SchemaDefinitions) {
-    let fields = DefaultOptionsSchemaPlugin.parseSchemaToFields(schema, schemaDefinitions)
+  static parseSchemaToFields(schema: SchemaProperties, schemaDefinitions?: ?SchemaDefinitions, dictionaryKey: string) {
+    let fields = DefaultOptionsSchemaPlugin.parseSchemaToFields(schema, schemaDefinitions, dictionaryKey)
     fields.forEach(f => {
     fields.forEach(f => {
       if (
       if (
         f.name !== 'migr_template_username_map'
         f.name !== 'migr_template_username_map'

+ 1 - 1
src/sources/ProviderSource.js

@@ -47,7 +47,7 @@ class ProviderSource {
       let schema = optionsType === 'source' ? schemas.source_environment_schema : schemas.destination_environment_schema
       let schema = optionsType === 'source' ? schemas.source_environment_schema : schemas.destination_environment_schema
       let fields = []
       let fields = []
       if (schema) {
       if (schema) {
-        fields = SchemaParser.optionsSchemaToFields(providerName, schema)
+        fields = SchemaParser.optionsSchemaToFields(providerName, schema, `${providerName}-${optionsType}`)
       }
       }
       return fields
       return fields
     } catch (err) {
     } catch (err) {

+ 2 - 2
src/sources/Schemas.js

@@ -32,10 +32,10 @@ class SchemaParser {
     return fields
     return fields
   }
   }
 
 
-  static optionsSchemaToFields(provider: string, schema: any) {
+  static optionsSchemaToFields(provider: string, schema: any, dictionaryKey: string) {
     let parser = OptionsSchemaPlugin[provider] || OptionsSchemaPlugin.default
     let parser = OptionsSchemaPlugin[provider] || OptionsSchemaPlugin.default
     let schemaRoot = schema.oneOf ? schema.oneOf[0] : schema
     let schemaRoot = schema.oneOf ? schema.oneOf[0] : schema
-    let fields = parser.parseSchemaToFields(schemaRoot, schema.definitions)
+    let fields = parser.parseSchemaToFields(schemaRoot, schema.definitions, dictionaryKey)
     fields.sort((a, b) => {
     fields.sort((a, b) => {
       if (a.required && !b.required) {
       if (a.required && !b.required) {
         return -1
         return -1

+ 20 - 9
src/utils/LabelDictionary.js

@@ -69,19 +69,25 @@ const dictionary = {
   },
   },
 }
 }
 
 
-const cache: { name: string, label: ?string, description: ?string }[] = []
+const cache: { name: string, label: ?string, description: ?string, key: string }[] = []
 
 
 class LabelDictionary {
 class LabelDictionary {
   // Fields which have enums for which dictionary labels should be used.
   // Fields which have enums for which dictionary labels should be used.
   // If a field has enums and is not in this array, their values will be used as labels
   // If a field has enums and is not in this array, their values will be used as labels
   static enumFields = ['port_reuse_policy', 'replica_export_mechanism', 'virtual_disk_clone_type']
   static enumFields = ['port_reuse_policy', 'replica_export_mechanism', 'virtual_disk_clone_type']
 
 
-  static get(fieldName: ?string): string {
+  /**
+   *
+   * @param {string} fieldName The name of the field
+   * @param {string} dictionaryKey Optional key to more uniquely identify the field added to schema cache.
+   * The `dictionaryKey` is composed by `${provider}-${direction}.
+   * Direction is 'destination' or 'source'.
+   */
+  static get(fieldName: ?string, dictionaryKey?: string): string {
     if (!fieldName) {
     if (!fieldName) {
       return ''
       return ''
     }
     }
-
-    let cachItem = cache.find(i => i.name === fieldName)
+    let cachItem = cache.find(i => i.key === dictionaryKey && i.name === fieldName)
     if (cachItem && cachItem.label) {
     if (cachItem && cachItem.label) {
       return cachItem.label
       return cachItem.label
     }
     }
@@ -105,8 +111,8 @@ class LabelDictionary {
     return words.join(' ')
     return words.join(' ')
   }
   }
 
 
-  static getDescription(fieldName: string): string {
-    let cachItem = cache.find(i => i.name === fieldName)
+  static getDescription(fieldName: string, dictionaryKey?: string): string {
+    let cachItem = cache.find(i => i.key === dictionaryKey && i.name === fieldName)
     if (cachItem && cachItem.description) {
     if (cachItem && cachItem.description) {
       return cachItem.description
       return cachItem.description
     }
     }
@@ -120,9 +126,14 @@ class LabelDictionary {
     return ''
     return ''
   }
   }
 
 
-  static pushToCache(field: Field) {
-    if ((field.title || field.description) && !cache.find(i => i.name === field.name)) {
-      cache.push({ label: field.title, description: field.description, name: field.name })
+  static pushToCache(field: Field, dictionaryKey: string) {
+    if ((field.title || field.description) && !cache.find(i => i.key === dictionaryKey && i.name === field.name)) {
+      cache.push({
+        label: field.title,
+        description: field.description,
+        name: field.name,
+        key: dictionaryKey,
+      })
     }
     }
   }
   }
 }
 }