Просмотр исходного кода

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 лет назад
Родитель
Сommit
1293baca57

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

@@ -95,9 +95,9 @@ type Props = {
   layout: 'modal' | 'page',
   width?: number,
   label?: string,
+  description?: string,
   addNullValue?: boolean,
   nullableBoolean?: boolean,
-  description?: string,
   style?: { [string]: mixed },
 }
 @observer
@@ -127,7 +127,7 @@ class FieldInput extends React.Component<Props> {
         type={this.props.password ? 'password' : 'text'}
         value={this.props.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}
         required={this.props.layout === 'page' ? false : this.props.required}
         disabledLoading={this.props.disabledLoading}
@@ -201,7 +201,7 @@ class FieldInput extends React.Component<Props> {
         highlight={this.props.highlight}
         value={this.props.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}
         disabledLoading={this.props.disabledLoading}
         required={this.props.layout === 'page' ? false : this.props.required}
@@ -289,7 +289,7 @@ class FieldInput extends React.Component<Props> {
     return (
       <RadioInput
         checked={this.props.value}
-        label={LabelDictionary.get(this.props.name)}
+        label={this.props.label || ''}
         onChange={checked => { if (this.props.onChange) this.props.onChange(checked) }}
         disabled={this.props.disabled}
         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) }}
         inputValue={this.props.getFieldValue ? this.props.getFieldValue(fieldName) : ''}
         onInputChange={value => { if (this.props.onFieldChange) this.props.onFieldChange(fieldName, value) }}
-        placeholder={LabelDictionary.get(fieldName)}
+        placeholder={this.props.label}
         highlight={this.props.highlight}
         disabled={this.props.disabled}
         disabledLoading={this.props.disabledLoading}
@@ -358,13 +358,13 @@ class FieldInput extends React.Component<Props> {
       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
 
     return (
       <Label layout={this.props.layout} disabledLoading={this.props.disabledLoading}>
         <LabelText style={{ marginRight }}>
-          {this.props.label || LabelDictionary.get(this.props.name)}
+          {this.props.label}
         </LabelText>
         {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}

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

@@ -481,6 +481,11 @@ class EditReplica extends React.Component<Props, State> {
     if (extraOptionsConfig) {
       optionsLoadingSkipFields = extraOptionsConfig.requiredFields
     }
+    let endpoint = type === 'source' ? this.props.sourceEndpoint : this.props.destinationEndpoint
+    let dictionaryKey = ''
+    if (endpoint) {
+      dictionaryKey = `${endpoint.type}-${type}`
+    }
     return (
       <WizardOptions
         wizardType={`${this.props.type || 'replica'}-${type}-options-edit`}
@@ -500,6 +505,7 @@ class EditReplica extends React.Component<Props, State> {
         optionsLoading={optionsLoading}
         optionsLoadingSkipFields={[...optionsLoadingSkipFields, 'description', 'execute_now',
           '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 plugin = endpoint && (OptionsSchemaPlugin[endpoint.type] || OptionsSchemaPlugin.default)
     let migrationImageMapFieldName = plugin && plugin.migrationImageMapFieldName
-
+    let dictionaryKey = ''
+    if (endpoint) {
+      dictionaryKey = `${endpoint.type}-destination`
+    }
     propertyNames.forEach(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) {
         // $FlowIgnore

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

@@ -142,6 +142,7 @@ type Props = {
   loading?: boolean,
   optionsLoading?: boolean,
   optionsLoadingSkipFields?: string[],
+  dictionaryKey: string,
 }
 @observer
 class WizardOptions extends React.Component<Props> {
@@ -261,14 +262,14 @@ class WizardOptions extends React.Component<Props> {
         type={field.type}
         minimum={field.minimum}
         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)}
         enum={field.enum}
         addNullValue
         required={field.required}
         data-test-id={`wOptions-field-${field.name}`}
         width={this.props.fieldWidth || StyleProps.inputSizes.wizard.width}
-        label={field.label}
         nullableBoolean={field.nullableBoolean}
         disabledLoading={this.props.optionsLoading && !optionsLoadingReqFields.find(fn => fn === field.name)}
         {...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`}
             layout="page"
             isSource
+            dictionaryKey={`${this.props.wizardData.source ? this.props.wizardData.source.type : ''}-source`}
           />
         )
         break
@@ -445,6 +446,7 @@ class WizardPageContent extends React.Component<Props, State> {
             wizardType={this.props.type}
             onAdvancedOptionsToggle={useAdvancedOptions => { this.handleAdvancedOptionsToggle(useAdvancedOptions) }}
             layout="page"
+            dictionaryKey={`${this.props.wizardData.target ? this.props.wizardData.target.type : ''}-destination`}
           />
         )
         break

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

@@ -260,7 +260,6 @@ class WizardSummary extends React.Component<Props> {
     if (!data.sourceOptions) {
       return null
     }
-
     return (
       <Section>
         <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] === '') {
               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)
             return (
               <Option key={optionName}>
@@ -346,7 +346,8 @@ class WizardSummary extends React.Component<Props> {
               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)
             return (
               <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 { 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) {
     return []
   }
@@ -33,18 +33,18 @@ export const defaultSchemaToFields = (schema: SchemaProperties, schemaDefinition
       return {
         name: fieldName,
         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) {
       return {
         name: fieldName,
         type: 'object',
-        properties: defaultSchemaToFields(properties, null, fieldName),
+        properties: defaultSchemaToFields(properties, null, fieldName, dictionaryKey),
       }
     }
 
     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 {
       ...properties,

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

@@ -122,8 +122,8 @@ export const defaultGetMigrationImageMap = (options: ?{ [string]: mixed }, migra
 export default class OptionsSchemaParser {
   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) {

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

@@ -26,8 +26,8 @@ import type { NetworkMap } from '../../../types/Network'
 export default class OptionsSchemaParser {
   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')
     if (exportMechField) {
       exportMechField.subFields = []

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

@@ -30,8 +30,8 @@ import type { NetworkMap } from '../../../types/Network'
 export default class OptionsSchemaParser {
   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 => {
       if (
         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 fields = []
       if (schema) {
-        fields = SchemaParser.optionsSchemaToFields(providerName, schema)
+        fields = SchemaParser.optionsSchemaToFields(providerName, schema, `${providerName}-${optionsType}`)
       }
       return fields
     } catch (err) {

+ 2 - 2
src/sources/Schemas.js

@@ -32,10 +32,10 @@ class SchemaParser {
     return fields
   }
 
-  static optionsSchemaToFields(provider: string, schema: any) {
+  static optionsSchemaToFields(provider: string, schema: any, dictionaryKey: string) {
     let parser = OptionsSchemaPlugin[provider] || OptionsSchemaPlugin.default
     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) => {
       if (a.required && !b.required) {
         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 {
   // 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
   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) {
       return ''
     }
-
-    let cachItem = cache.find(i => i.name === fieldName)
+    let cachItem = cache.find(i => i.key === dictionaryKey && i.name === fieldName)
     if (cachItem && cachItem.label) {
       return cachItem.label
     }
@@ -105,8 +111,8 @@ class LabelDictionary {
     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) {
       return cachItem.description
     }
@@ -120,9 +126,14 @@ class LabelDictionary {
     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,
+      })
     }
   }
 }