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

Merge pull request #165 from smiclea/destination-options

Load destination options values in wizard
Dorin Paslaru 8 лет назад
Родитель
Сommit
a0ff58daa1

+ 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[],