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

Load destination options values in wizard

After the destination options schema is loaded, load the destination
options values which complement the schema. For example, if using the
schema we render a text input for a string field, after loading the
destination options, we may have a dropdown instead with available
values for that field and a default value selected.
Sergiu Miclea 8 лет назад
Родитель
Сommit
fdf07014ae

+ 4 - 2
src/components/molecules/WizardOptionsField/index.jsx

@@ -16,6 +16,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import React from 'react'
 import styled from 'styled-components'
+import { observer } from 'mobx-react'
 
 import Switch from '../../atoms/Switch'
 import TextInput from '../../atoms/TextInput'
@@ -58,6 +59,7 @@ type Props = {
   enum: string[],
   required: boolean,
 }
+@observer
 class WizardOptionsField extends React.Component<Props> {
   renderSwitch(propss: { triState: boolean }) {
     return (
@@ -102,8 +104,8 @@ class WizardOptionsField extends React.Component<Props> {
   renderEnumDropdown() {
     let items = this.props.enum.map(e => {
       return {
-        label: LabelDictionary.get(e),
-        value: e,
+        label: typeof e === 'string' ? LabelDictionary.get(e) : e.name,
+        value: typeof e === 'string' ? e : e.id,
       }
     })
 

+ 43 - 2
src/components/organisms/WizardOptions/index.jsx

@@ -16,16 +16,19 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 import React from 'react'
 import styled from 'styled-components'
+import { observer } from 'mobx-react'
 
 import Tooltip from '../../atoms/Tooltip'
 import ToggleButtonBar from '../../atoms/ToggleButtonBar'
 import WizardOptionsField from '../../molecules/WizardOptionsField'
+import StatusImage from '../../atoms/StatusImage'
 import type { Field } from '../../../types/Field'
 import type { Instance } from '../../../types/Instance'
 
 import { executionOptions } from '../../../config'
 
 const Wrapper = styled.div``
+const Options = styled.div``
 const Fields = styled.div`
   margin-top: 46px;
   display: flex;
@@ -39,6 +42,16 @@ const WizardOptionsFieldStyled = styled(WizardOptionsField) `
   justify-content: space-between;
   margin-bottom: 39px;
 `
+const LoadingWrapper = styled.div`
+  margin-top: 32px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+`
+const LoadingText = styled.div`
+  margin-top: 38px;
+  font-size: 18px;
+`
 
 type Props = {
   fields: Field[],
@@ -48,7 +61,9 @@ type Props = {
   useAdvancedOptions: boolean,
   onAdvancedOptionsToggle: (showAdvanced: boolean) => void,
   wizardType: string,
+  loading: boolean,
 }
+@observer
 class WizardOptions extends React.Component<Props> {
   getFieldValue(fieldName: string, defaultValue: any) {
     if (!this.props.data || this.props.data[fieldName] === undefined) {
@@ -164,15 +179,41 @@ class WizardOptions extends React.Component<Props> {
     )
   }
 
-  render() {
+  renderLoading() {
+    if (!this.props.loading) {
+      return null
+    }
+
     return (
-      <Wrapper>
+      <LoadingWrapper>
+        <StatusImage loading />
+        <LoadingText>Loading options...</LoadingText>
+      </LoadingWrapper>
+    )
+  }
+
+  renderOptions() {
+    if (this.props.loading) {
+      return null
+    }
+
+    return (
+      <Options>
         <ToggleButtonBar
           items={[{ label: 'Simple', value: 'simple' }, { label: 'Advanced', value: 'advanced' }]}
           selectedValue={this.props.useAdvancedOptions ? 'advanced' : 'simple'}
           onChange={item => { this.props.onAdvancedOptionsToggle(item.value === 'advanced') }}
         />
         {this.renderOptionsFields()}
+      </Options>
+    )
+  }
+
+  render() {
+    return (
+      <Wrapper>
+        {this.renderOptions()}
+        {this.renderLoading()}
       </Wrapper>
     )
   }

+ 7 - 1
src/components/organisms/WizardPageContent/index.jsx

@@ -200,7 +200,12 @@ class WizardPageContent extends React.Component<Props, State> {
       let required = schema.filter(f => f.required && f.type !== 'object')
       let validFieldsCount = 0
       required.forEach(f => {
-        if (this.props.wizardData.options && this.props.wizardData.options[f.name] !== null && this.props.wizardData.options[f.name] !== undefined) {
+        if (
+          (this.props.wizardData.options &&
+          this.props.wizardData.options[f.name] !== null &&
+          this.props.wizardData.options[f.name] !== undefined) ||
+          (f.default !== null && f.default !== undefined)
+        ) {
           validFieldsCount += 1
         }
       })
@@ -313,6 +318,7 @@ class WizardPageContent extends React.Component<Props, State> {
       case 'options':
         body = (
           <WizardOptions
+            loading={this.props.providerStore.optionsSchemaLoading || this.props.providerStore.destinationOptionsLoading}
             selectedInstances={this.props.wizardData.selectedInstances}
             fields={this.props.providerStore.optionsSchema}
             onChange={this.props.onOptionsChange}

+ 15 - 3
src/components/pages/WizardPage/index.jsx

@@ -181,6 +181,11 @@ class WizardPage extends React.Component<Props, State> {
   handleTargetEndpointChange(target: EndpointType) {
     WizardStore.updateData({ target, networks: null, options: null })
     WizardStore.setPermalink(WizardStore.data)
+    // Preload destination options schema
+    ProviderStore.loadOptionsSchema(target.type, this.state.type).then(() => {
+      // Preload destination options values
+      return ProviderStore.getDestinationOptions(target.id, target.type)
+    })
   }
 
   handleAddEndpoint(newEndpointType: string, newEndpointFromSource: boolean) {
@@ -269,11 +274,18 @@ class WizardPage extends React.Component<Props, State> {
         }
         break
       }
-      case 'options':
-        if (WizardStore.data.target) {
-          ProviderStore.loadOptionsSchema(WizardStore.data.target.type, this.state.type)
+      case 'target': {
+        // Preload destination options if data is set from 'Permalink'
+        let target = WizardStore.data.target
+        if (ProviderStore.destinationOptions.length === 0 && target) {
+          ProviderStore.getDestinationOptions(target.id, target.type)
+        }
+        // Preload destination options schema
+        if (ProviderStore.optionsSchema.length === 0 && target) {
+          ProviderStore.loadOptionsSchema(target.type, this.state.type)
         }
         break
+      }
       case 'networks':
         if (WizardStore.data.source && WizardStore.data.selectedInstances) {
           InstanceStore.loadInstancesDetails(WizardStore.data.source.id, WizardStore.data.selectedInstances)

+ 3 - 0
src/config.js

@@ -72,4 +72,7 @@ export const wizardConfig = {
   instancesItemsPerPage: 6,
 }
 
+// Providers for which `destination-options` API call will be made
+export const providersWithExtraOptions = ['azure']
+
 export const basename = process.env.PUBLIC_PATH

+ 15 - 1
src/sources/ProviderSource.js

@@ -17,11 +17,11 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import cookie from 'js-cookie'
 
 import Api from '../utils/ApiCaller'
-
 import { servicesUrl, providerTypes } from '../config'
 import { SchemaParser } from './Schemas'
 import type { Field } from '../types/Field'
 import type { Providers } from '../types/Providers'
+import type { DestinationOption } from '../types/Endpoint'
 
 class ProviderSource {
   static getConnectionInfoSchema(providerName: string): Promise<Field[]> {
@@ -67,6 +67,20 @@ class ProviderSource {
       }, reject).catch(reject)
     })
   }
+
+  static getDestinationOptions(endpointId: string): Promise<DestinationOption[]> {
+    return new Promise((resolve, reject) => {
+      let projectId = cookie.get('projectId')
+
+      Api.sendAjaxRequest({
+        url: `${servicesUrl.coriolis}/${projectId || 'null'}/endpoints/${endpointId}/destination-options`,
+        method: 'GET',
+      }).then(response => {
+        let options = response.data.destination_options
+        resolve(options)
+      }).catch(() => { reject() })
+    })
+  }
 }
 
 export default ProviderSource

+ 26 - 0
src/stores/ProviderStore.js

@@ -17,6 +17,8 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import { observable, action } from 'mobx'
 
 import ProviderSource from '../sources/ProviderSource'
+import { providersWithExtraOptions } from '../config.js'
+import type { DestinationOption } from '../types/Endpoint'
 import type { Field } from '../types/Field'
 import type { Providers } from '../types/Providers'
 
@@ -27,6 +29,8 @@ class ProviderStore {
   @observable providersLoading: boolean = false
   @observable optionsSchema: Field[] = []
   @observable optionsSchemaLoading: boolean = false
+  @observable destinationOptions: DestinationOption[] = []
+  @observable destinationOptionsLoading: boolean = false
 
   @action getConnectionInfoSchema(providerName: string): Promise<void> {
     this.connectionSchemaLoading = true
@@ -65,6 +69,28 @@ class ProviderStore {
       this.optionsSchemaLoading = false
     })
   }
+
+   @action getDestinationOptions(endpointId: string, provider: string): Promise<void> {
+    if (!providersWithExtraOptions.find(p => p === provider)) {
+      return Promise.resolve()
+    }
+
+    this.destinationOptionsLoading = true
+    return ProviderSource.getDestinationOptions(endpointId).then(options => {
+      this.optionsSchema.forEach(field => {
+        let fieldValues = options.find(f => f.name === field.name)
+        if (fieldValues) {
+          // $FlowIgnore
+          field.enum = [...fieldValues.values]
+          if (fieldValues.config_default) {
+            field.default = typeof fieldValues.config_default === 'string' ? fieldValues.config_default : fieldValues.config_default.id
+          }
+        }
+      })
+      this.destinationOptions = options
+      this.destinationOptionsLoading = false
+    }).catch(() => { this.destinationOptionsLoading = false })
+  }
 }
 
 export default new ProviderStore()

+ 6 - 0
src/types/Endpoint.js

@@ -30,3 +30,9 @@ export type Endpoint = {
     [string]: mixed
   },
 }
+
+export type DestinationOption = {
+  name: string,
+  values: string[] | {name: string, id: string}[],
+  config_default: string | {name: string, id: string},
+}

+ 1 - 1
src/types/Field.js

@@ -18,7 +18,7 @@ export type Field = {
   name: string,
   type?: string,
   value?: any,
-  enum?: string[],
+  enum?: string[] | {name: string, id: string}[],
   required?: boolean | (value: any) => boolean,
   default?: any,
   items?: Field[],