Explorar el Código

Use a plugin system for wizard options behaviour

Some providers may need different UI representation in the Wizard
Options for their schema and/or destination options API calls.
Sergiu Miclea hace 8 años
padre
commit
d8e2b0d417

+ 31 - 3
src/components/molecules/Dropdown/index.jsx

@@ -99,6 +99,12 @@ const DuplicatedLabel = styled.div`
     overflow: hidden;
   }
 `
+const Separator = styled.div`
+  width: calc(100% - 32px);
+  height: 1px;
+  margin: 8px 16px;
+  background: ${Palette.grayscale[3]};
+`
 
 type Props = {
   selectedItem: any,
@@ -127,6 +133,7 @@ class Dropdown extends React.Component<Props, State> {
 
   buttonRef: HTMLElement
   listRef: HTMLElement
+  listItemsRef: HTMLElement
   tipRef: HTMLElement
   scrollableParent: HTMLElement
   buttonRect: ClientRect
@@ -213,7 +220,9 @@ class Dropdown extends React.Component<Props, State> {
       return
     }
 
-    this.setState({ showDropdownList: !this.state.showDropdownList })
+    this.setState({ showDropdownList: !this.state.showDropdownList }, () => {
+      this.scrollIntoView()
+    })
   }
 
   handleItemClick(item: any) {
@@ -259,8 +268,23 @@ class Dropdown extends React.Component<Props, State> {
       scrollOffset = -parseInt(document.body && document.body.style.top, 10)
     }
 
+    let widthDiff = this.listRef.offsetWidth - this.buttonRef.offsetWidth
     this.listRef.style.top = `${listTop + (window.pageYOffset || scrollOffset)}px`
-    this.listRef.style.left = `${this.buttonRect.left + window.pageXOffset}px`
+    this.listRef.style.left = `${(this.buttonRect.left + window.pageXOffset) - widthDiff}px`
+  }
+
+  scrollIntoView() {
+    if (!this.listRef || !this.listItemsRef) {
+      return
+    }
+
+    let itemIndex = this.props.items.findIndex(i => this.getValue(i) === this.getValue(this.props.selectedItem))
+    if (itemIndex === -1 || !this.listItemsRef.children[itemIndex]) {
+      return
+    }
+
+    // $FlowIssue
+    this.listItemsRef.children[itemIndex].parentNode.scrollTop = this.listItemsRef.children[itemIndex].offsetTop - this.listItemsRef.children[itemIndex].parentNode.offsetTop - 32
   }
 
   renderList() {
@@ -283,8 +307,12 @@ class Dropdown extends React.Component<Props, State> {
     let list = ReactDOM.createPortal((
       <List {...this.props} innerRef={ref => { this.listRef = ref }}>
         <Tip innerRef={ref => { this.tipRef = ref }} primary={this.state.firstItemHover} />
-        <ListItems>
+        <ListItems innerRef={ref => { this.listItemsRef = ref }}>
           {this.props.items.map((item, i) => {
+            if (item.separator === true) {
+              return <Separator />
+            }
+
             let label = this.getLabel(item)
             let value = this.getValue(item)
             let duplicatedLabel = duplicatedLabels.find(l => l === label)

+ 1 - 0
src/components/molecules/Dropdown/story.jsx

@@ -61,6 +61,7 @@ storiesOf('Dropdown', module)
         { label: 'Item 2', value: 'item-2' },
         { label: 'Item 3', value: 'item-3' },
         { label: 'Item 4', value: 'item-4' },
+        { separator: true },
         { label: 'Item 1', value: 'item-1' },
         { label: 'Item 2', value: 'item-2' },
         { label: 'Item 3', value: 'item-3' },

+ 6 - 2
src/components/molecules/DropdownLink/story.jsx

@@ -12,11 +12,13 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+// @flow
+
 import React from 'react'
 import { storiesOf } from '@storybook/react'
 import DropdownLink from '.'
 
-class Wrapper extends React.Component {
+class Wrapper extends React.Component<any, any> {
   constructor() {
     super()
     this.state = {
@@ -52,5 +54,7 @@ storiesOf('DropdownLink', module)
     <Wrapper />
   ))
   .add('searchable', () => (
-    <Wrapper searchable />
+    <Wrapper
+      searchable
+    />
   ))

+ 2 - 0
src/components/molecules/PropertiesTable/index.jsx

@@ -110,6 +110,8 @@ class PropertiesTable extends React.Component<Props> {
           label: this.getName(e),
           value: e,
         }
+      } else if (e.separator === true) {
+        return e
       }
       return {
         label: e.name,

+ 5 - 1
src/components/molecules/WizardOptionsField/index.jsx

@@ -93,7 +93,7 @@ class WizardOptionsField extends React.Component<Props> {
   }
 
   renderObjectTable() {
-    if (!this.props.properties) {
+    if (!this.props.properties || !this.props.properties.length) {
       return null
     }
 
@@ -109,6 +109,10 @@ class WizardOptionsField extends React.Component<Props> {
 
   renderEnumDropdown() {
     let items = this.props.enum.map(e => {
+      if (typeof e !== 'string' && e.separator === true) {
+        return e
+      }
+
       return {
         label: typeof e === 'string' ? e : e.name,
         value: typeof e === 'string' ? e : e.id,

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

@@ -207,7 +207,7 @@ class WizardPageContent extends React.Component<Props, State> {
     if (schema && schema.length > 0) {
       let required = schema.filter(f => f.required && f.type !== 'object')
       schema.forEach(f => {
-        if (f.type === 'object' && f.properties && f.properties.filter(p => isValid(p)).length > 0) {
+        if (f.type === 'object' && f.properties && f.properties.length && f.properties.filter(p => isValid(p)).length > 0) {
           required = required.concat(f.properties.filter(p => p.required))
         }
       })

+ 40 - 20
src/components/pages/WizardPage/index.jsx

@@ -239,21 +239,7 @@ class WizardPage extends React.Component<Props, State> {
   handleOptionsChange(field: Field, value: any) {
     wizardStore.updateData({ networks: null })
     wizardStore.updateOptions({ field, value })
-
-    let provider = wizardStore.data.target && wizardStore.data.target.type
-    let providerWithExtraOptions = providersWithExtraOptions.find(p => typeof p !== 'string' && p.name === provider)
-    if (provider && providerWithExtraOptions && typeof providerWithExtraOptions !== 'string' && providerWithExtraOptions.envRequestFields) {
-      let validFields = providerWithExtraOptions.envRequestFields.filter(fn => wizardStore.data.options ? O.isValid(wizardStore.data.options[fn]) : false)
-      if (
-        validFields.length === providerWithExtraOptions.envRequestFields.length &&
-        wizardStore.data.options &&
-        wizardStore.data.target &&
-        validFields.find(fn => fn === field.name)
-      ) {
-        providerStore.getDestinationOptions(wizardStore.data.target.id, provider, wizardStore.data.options)
-      }
-    }
-
+    this.loadEnvDestinationOptions(field)
     wizardStore.setPermalink(wizardStore.data)
   }
 
@@ -277,6 +263,37 @@ class WizardPage extends React.Component<Props, State> {
     wizardStore.setPermalink(wizardStore.data)
   }
 
+  loadEnvDestinationOptions(field?: Field) {
+    let provider = wizardStore.data.target && wizardStore.data.target.type
+    let providerWithExtraOptions = providersWithExtraOptions.find(p => typeof p !== 'string' && p.name === provider)
+    if (provider && providerWithExtraOptions && typeof providerWithExtraOptions !== 'string' && providerWithExtraOptions.envRequiredFields) {
+      let findFieldInSchema = (name: string) => providerStore.optionsSchema.find(f => f.name === name)
+      let validFields = providerWithExtraOptions.envRequiredFields.filter(fn => {
+        let schemaField = findFieldInSchema(fn)
+        return wizardStore.data.options ? O.isValid(wizardStore.data.options[fn]) || (schemaField && schemaField.default) : false
+      })
+      let currentFieldValied = field ? validFields.find(fn => field ? fn === field.name : false) : true
+      if (
+        validFields.length === providerWithExtraOptions.envRequiredFields.length &&
+        currentFieldValied
+      ) {
+        let envData = {}
+        validFields.forEach(fn => {
+          envData[fn] = wizardStore.data.options ? wizardStore.data.options[fn] : null
+          if (!O.isValid(envData[fn])) {
+            let schemaField = findFieldInSchema(fn)
+            if (schemaField && schemaField.default) {
+              envData[fn] = schemaField.default
+            }
+          }
+        })
+        if (wizardStore.data.target) {
+          providerStore.getDestinationOptions(wizardStore.data.target.id, provider, envData)
+        }
+      }
+    }
+  }
+
   loadDataForPage(page: WizardPageType) {
     switch (page.id) {
       case 'source': {
@@ -290,14 +307,17 @@ class WizardPage extends React.Component<Props, State> {
         break
       }
       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)
+          providerStore.loadOptionsSchema(target.type, this.state.type).then(() => {
+            // Preload destination options if data is set from 'Permalink'
+            if (providerStore.destinationOptions.length === 0 && target) {
+              providerStore.getDestinationOptions(target.id, target.type).then(() => {
+                this.loadEnvDestinationOptions()
+              })
+            }
+          })
         }
         break
       }

+ 8 - 4
src/config.js

@@ -81,14 +81,18 @@ export const wizardConfig = {
   instancesItemsPerPage: 6,
 }
 
-// Providers for which `destination-options` API call(s) will be made
-// If item is an object and has `envRequestFields` array,
-// subsequent requests to `destination-options` will be made with those fields, if they are set
+// A list of providers for which `destination-options` API call(s) will be made in the Wizard
+// If the item is just a string with the provider name, only one API call will be made
+// If the item has `envRequiredFields`, an additional API call will be made once the specified fields are filled
 export const providersWithExtraOptions = [
   'openstack',
+  {
+    name: 'azure',
+    envRequiredFields: ['location', 'resource_group'],
+  },
   {
     name: 'oci',
-    envRequestFields: ['compartment', 'availability_domain'],
+    envRequiredFields: ['compartment', 'availability_domain'],
   },
 ]
 

+ 1 - 1
src/plugins/endpoint/azure/SchemaPlugin.js → src/plugins/endpoint/azure/ConnectionSchemaPlugin.js

@@ -17,7 +17,7 @@ import {
   defaultSchemaToFields,
   generateField,
   fieldsToPayload,
-} from '../default/SchemaPlugin'
+} from '../default/ConnectionSchemaPlugin'
 
 const fieldsToPayloadUseDefaults = (data, schema) => {
   let info = {}

+ 0 - 0
src/plugins/endpoint/default/SchemaPlugin.js → src/plugins/endpoint/default/ConnectionSchemaPlugin.js


+ 135 - 0
src/plugins/endpoint/default/OptionsSchemaPlugin.js

@@ -0,0 +1,135 @@
+/*
+Copyright (C) 2017  Cloudbase Solutions SRL
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU Affero General Public License as
+published by the Free Software Foundation, either version 3 of the
+License, or (at your option) any later version.
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU Affero General Public License for more details.
+You should have received a copy of the GNU Affero General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+
+// @flow
+
+import type { Field } from '../../../types/Field'
+import type { DestinationOption } from '../../../types/Endpoint'
+import type { WizardData } from '../../../types/WizardData'
+import { executionOptions } from '../../../config'
+
+const migrationImageOsTypes = ['windows', 'linux']
+
+export const defaultFillFieldValues = (field: Field, option: DestinationOption) => {
+  if (field.type === 'string') {
+    // $FlowIgnore
+    field.enum = [...option.values]
+    if (option.config_default) {
+      field.default = typeof option.config_default === 'string' ? option.config_default : option.config_default.id
+    }
+  }
+}
+
+export const defaultFillMigrationImageMapValues = (field: Field, option: DestinationOption): boolean => {
+  if (field.name === 'migr_image_map') {
+    field.properties = migrationImageOsTypes.map(os => {
+      let values = option.values
+        .filter(v => v.os_type === os || v.os_type === 'unknown')
+        .sort((v1, v2) => {
+          if (v1.os_type === 'unknown' && v2.os_type !== 'unknown') {
+            return 1
+          } else if (v1.os_type !== 'unknown' && v2.os_type === 'unknown') {
+            return -1
+          }
+          return 0
+        })
+      let unknownIndex = values.findIndex(v => v.os_type === 'unknown')
+      if (unknownIndex > -1 && values.filter(v => v.os_type === 'unknown').length < values.length) {
+        values.splice(unknownIndex, 0, { separator: true })
+      }
+
+      return {
+        name: `${os}_os_image`,
+        type: 'string',
+        enum: values,
+      }
+    })
+    return true
+  }
+  return false
+}
+
+export const defaultGetDestinationEnv = (data: WizardData): any => {
+  let env = {}
+  let specialOptions = ['execute_now', 'separate_vm', 'skip_os_morphing']
+    .concat(executionOptions.map(o => o.name))
+    .concat(migrationImageOsTypes.map(o => `${o}_os_image`))
+
+
+  if (data.options) {
+    Object.keys(data.options).forEach(optionName => {
+      if (specialOptions.find(o => o === optionName)
+        // $FlowIssue
+        || data.options[optionName] === null || data.options[optionName] === undefined) {
+        return
+      }
+      if (optionName.indexOf('/') > 0) {
+        let parentName = optionName.substr(0, optionName.lastIndexOf('/'))
+        if (!env[parentName]) {
+          env[parentName] = {}
+        }
+        env[parentName][optionName.substr(optionName.lastIndexOf('/') + 1)] = data.options ? data.options[optionName] : null
+      } else {
+        env[optionName] = data.options ? data.options[optionName] : null
+      }
+    })
+  }
+}
+
+export const defaultGetNetworkMap = (data: WizardData) => {
+  let env = {}
+  env.network_map = {}
+  if (data.networks && data.networks.length) {
+    data.networks.forEach(mapping => {
+      env.network_map[mapping.sourceNic.network_name] = mapping.targetNetwork.id
+    })
+  }
+  return env
+}
+
+export const defaultGetMigrationImageMap = (data: WizardData) => {
+  let env = {}
+  env.migr_image_map = {}
+  if (data.options) {
+    migrationImageOsTypes.forEach(os => {
+      if (data.options && data.options[`${os}_os_image`]) {
+        env.migr_image_map[os] = data.options[`${os}_os_image`]
+      }
+    })
+  }
+
+  return env
+}
+
+export default class OptionsSchemaParser {
+  static fillFieldValues(field: Field, options: DestinationOption[]) {
+    let option = options.find(f => f.name === field.name)
+    if (!option) {
+      return
+    }
+    if (!defaultFillMigrationImageMapValues(field, option)) {
+      defaultFillFieldValues(field, option)
+    }
+  }
+
+  static getDestinationEnv(data: WizardData) {
+    let env = {
+      ...defaultGetDestinationEnv(data),
+      ...defaultGetNetworkMap(data),
+      ...defaultGetMigrationImageMap(data),
+    }
+    return env
+  }
+}
+

+ 15 - 9
src/plugins/endpoint/index.js

@@ -14,19 +14,25 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 // @flow
 
-import DefaultSchemaPlugin from './default/SchemaPlugin'
-import AzureSchemaPlugin from './azure/SchemaPlugin'
+import DefaultConnectionSchemaPlugin from './default/ConnectionSchemaPlugin'
+import AzureConnectionSchemaPlugin from './azure/ConnectionSchemaPlugin'
+import OpenstackConnectionSchemaPlugin from './openstack/ConnectionSchemaPlugin'
+import OciConnectionSchemaPlugin from './oci/ConnectionSchemaPlugin'
 import DefaultContentPlugin from './default/ContentPlugin'
 import AzureContentPlugin from './azure/ContentPlugin'
 import OpenstackContentPlugin from './openstack/ContentPlugin'
-import OpenstackSchemaPlugin from './openstack/SchemaPlugin'
-import OciSchemaPlugin from './oci/SchemaPlugin'
 
-export const SchemaPlugin = {
-  default: DefaultSchemaPlugin,
-  azure: AzureSchemaPlugin,
-  openstack: OpenstackSchemaPlugin,
-  oci: OciSchemaPlugin,
+import DefaultOptionsSchemaPlugin from './default/OptionsSchemaPlugin'
+
+export const ConnectionSchemaPlugin = {
+  default: DefaultConnectionSchemaPlugin,
+  azure: AzureConnectionSchemaPlugin,
+  openstack: OpenstackConnectionSchemaPlugin,
+  oci: OciConnectionSchemaPlugin,
+}
+
+export const OptionsSchemaPlugin = {
+  default: DefaultOptionsSchemaPlugin,
 }
 
 export const ContentPlugin = {

+ 1 - 1
src/plugins/endpoint/oci/SchemaPlugin.js → src/plugins/endpoint/oci/ConnectionSchemaPlugin.js

@@ -17,7 +17,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import type { Schema } from '../../../types/Schema'
 import type { Field } from '../../../types/Field'
 
-import DefaultConnectionSchemaParser from '../default/SchemaPlugin'
+import DefaultConnectionSchemaParser from '../default/ConnectionSchemaPlugin'
 
 export default class ConnectionSchemaParser {
   static parseSchemaToFields(schema: Schema): Field[] {

+ 1 - 1
src/plugins/endpoint/openstack/SchemaPlugin.js → src/plugins/endpoint/openstack/ConnectionSchemaPlugin.js

@@ -17,7 +17,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import type { Schema } from '../../../types/Schema'
 import type { Field } from '../../../types/Field'
 
-import DefaultConnectionSchemaParser from '../default/SchemaPlugin'
+import DefaultConnectionSchemaParser from '../default/ConnectionSchemaPlugin'
 
 const customSort = (fields: Field[]) => {
   const sortPriority = {

+ 4 - 4
src/sources/Schemas.js

@@ -14,8 +14,8 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 // @flow
 
-import { SchemaPlugin } from '../plugins/endpoint'
-import { defaultSchemaToFields } from '../plugins/endpoint/default/SchemaPlugin'
+import { ConnectionSchemaPlugin } from '../plugins/endpoint'
+import { defaultSchemaToFields } from '../plugins/endpoint/default/ConnectionSchemaPlugin'
 import type { Schema } from '../types/Schema'
 
 class SchemaParser {
@@ -26,7 +26,7 @@ class SchemaParser {
       this.storedConnectionsSchemas[provider] = schema
     }
 
-    let parsers = SchemaPlugin[provider] || SchemaPlugin.default
+    let parsers = ConnectionSchemaPlugin[provider] || ConnectionSchemaPlugin.default
     let fields = parsers.parseSchemaToFields(schema)
 
     return fields
@@ -50,7 +50,7 @@ class SchemaParser {
 
   static fieldsToPayload(data: { [string]: any }) {
     let storedSchema = this.storedConnectionsSchemas[data.type] || this.storedConnectionsSchemas.general
-    let parsers = SchemaPlugin[data.type] || SchemaPlugin.default
+    let parsers = ConnectionSchemaPlugin[data.type] || ConnectionSchemaPlugin.default
     let payload = parsers.parseFieldsToPayload(data, storedSchema)
 
     return payload

+ 5 - 44
src/sources/WizardSource.js

@@ -18,62 +18,23 @@ import cookie from 'js-cookie'
 
 import Api from '../utils/ApiCaller'
 import notificationStore from '../stores/NotificationStore'
-import { servicesUrl, executionOptions } from '../config'
+import { OptionsSchemaPlugin } from '../plugins/endpoint'
+
+import { servicesUrl } from '../config'
 import type { WizardData } from '../types/WizardData'
 import type { MainItem } from '../types/MainItem'
 
-class WizardSourceUtils {
-  static getDestinationEnv(data) {
-    let env = {}
-    let specialOptions = ['execute_now', 'separate_vm', 'skip_os_morphing', 'windows_image', 'linux_image'].concat(executionOptions.map(o => o.name))
-
-    if (data.options) {
-      Object.keys(data.options).forEach(optionName => {
-        if (specialOptions.find(o => o === optionName)
-          // $FlowIssue
-          || data.options[optionName] === null || data.options[optionName] === undefined) {
-          return
-        }
-        if (optionName.indexOf('/') > 0) {
-          let parentName = optionName.substr(0, optionName.lastIndexOf('/'))
-          if (!env[parentName]) {
-            env[parentName] = {}
-          }
-          env[parentName][optionName.substr(optionName.lastIndexOf('/') + 1)] = data.options ? data.options[optionName] : null
-        } else {
-          env[optionName] = data.options ? data.options[optionName] : null
-        }
-      })
-    }
-
-    env.network_map = {}
-    if (data.networks && data.networks.length) {
-      data.networks.forEach(mapping => {
-        env.network_map[mapping.sourceNic.network_name] = mapping.targetNetwork.id
-      })
-    }
-    env.migr_image_map = {}
-    if (data.options && data.options.windows_image) {
-      env.migr_image_map.windows = data.options.windows_image
-    }
-    if (data.options && data.options.linux_image) {
-      env.migr_image_map.linux = data.options.linux_image
-    }
-
-    return env
-  }
-}
-
 class WizardSource {
   static create(type: string, data: WizardData): Promise<MainItem> {
     return new Promise((resolve, reject) => {
       let projectId = cookie.get('projectId')
 
+      const parser = data.target ? OptionsSchemaPlugin[data.target.type] || OptionsSchemaPlugin.default : OptionsSchemaPlugin.default
       let payload = {}
       payload[type] = {
         origin_endpoint_id: data.source ? data.source.id : 'null',
         destination_endpoint_id: data.target ? data.target.id : 'null',
-        destination_environment: WizardSourceUtils.getDestinationEnv(data),
+        destination_environment: parser.getDestinationEnv(data),
         instances: data.selectedInstances ? data.selectedInstances.map(i => i.instance_name) : 'null',
         notes: '',
         security_groups: ['testgroup'],

+ 5 - 25
src/stores/ProviderStore.js

@@ -18,6 +18,7 @@ import { observable, action } from 'mobx'
 
 import ProviderSource from '../sources/ProviderSource'
 import { providersWithExtraOptions } from '../config.js'
+import { OptionsSchemaPlugin } from '../plugins/endpoint'
 import type { DestinationOption } from '../types/Endpoint'
 import type { Field } from '../types/Field'
 import type { Providers } from '../types/Providers'
@@ -82,34 +83,13 @@ class ProviderStore {
     this.destinationOptionsLoading = true
     return ProviderSource.getDestinationOptions(endpointId, envData).then(options => {
       this.optionsSchema.forEach(field => {
-        let fieldValues = options.find(f => f.name === field.name)
-        if (fieldValues) {
-          if (field.type === 'string') {
-            // $FlowIgnore
-            field.enum = [...fieldValues.values]
-            if (fieldValues.config_default) {
-              field.default = typeof fieldValues.config_default === 'string' ? fieldValues.config_default : fieldValues.config_default.id
-            }
-            // the `migr_image_map` field is special since it needs to group the values by OS type
-          } else if (field.name === 'migr_image_map') {
-            field.properties = [
-              {
-                name: 'windows_image',
-                type: 'string',
-                enum: fieldValues.values.filter(v => typeof v !== 'string' && v.os_type === 'windows'),
-              },
-              {
-                name: 'linux_image',
-                type: 'string',
-                enum: fieldValues.values.filter(v => typeof v !== 'string' && v.os_type === 'linux'),
-              },
-            ]
-          }
-        }
+        const parser = OptionsSchemaPlugin[provider] || OptionsSchemaPlugin.default
+        parser.fillFieldValues(field, options)
       })
       this.destinationOptions = options
       this.destinationOptionsLoading = false
-    }).catch(() => {
+    }).catch(err => {
+      console.error(err)
       if (envData) {
         return this.loadOptionsSchema(provider, this.lastOptionsSchemaType).then(() => {
           return this.getDestinationOptions(endpointId, provider)