ProviderStore.js 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. /*
  2. Copyright (C) 2017 Cloudbase Solutions SRL
  3. This program is free software: you can redistribute it and/or modify
  4. it under the terms of the GNU Affero General Public License as
  5. published by the Free Software Foundation, either version 3 of the
  6. License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU Affero General Public License for more details.
  11. You should have received a copy of the GNU Affero General Public License
  12. along with this program. If not, see <http://www.gnu.org/licenses/>.
  13. */
  14. // @flow
  15. import { observable, action, computed, runInAction } from 'mobx'
  16. import ProviderSource from '../sources/ProviderSource'
  17. import apiCaller from '../utils/ApiCaller'
  18. import configLoader from '../utils/Config'
  19. import { providerTypes } from '../constants'
  20. import { OptionsSchemaPlugin } from '../plugins/endpoint'
  21. import type { OptionValues } from '../types/Endpoint'
  22. import type { Field } from '../types/Field'
  23. import type { Providers } from '../types/Providers'
  24. export const getFieldChangeOptions = (config: {
  25. providerName: ?string,
  26. schema: Field[],
  27. data: any,
  28. field: ?Field,
  29. type: 'source' | 'destination',
  30. }) => {
  31. let { providerName, schema, data, field, type } = config
  32. let providerWithEnvOptions = configLoader.config.extraOptionsApiCalls
  33. .find(p => p.name === providerName && p.types.find(t => t === type))
  34. if (!providerName || !providerWithEnvOptions) {
  35. return null
  36. }
  37. let requiredFields = providerWithEnvOptions.requiredFields
  38. let requiredValues = providerWithEnvOptions.requiredValues
  39. let findFieldInSchema = (name: string) => schema.find(f => f.name === name)
  40. let validFields = requiredFields.filter(fn => {
  41. let schemaField = findFieldInSchema(fn)
  42. if (data) {
  43. // This is for 'list_all_networks' field, which requires options calls after each value change
  44. if (schemaField && schemaField.type === 'boolean') {
  45. return true
  46. }
  47. if (data[fn] === null) {
  48. return false
  49. }
  50. let defaultValue = data[fn] === undefined && schemaField && schemaField.default
  51. let requiredValue = requiredValues && requiredValues.find(f => f.field === fn)
  52. if (defaultValue != null) {
  53. if (requiredValue) {
  54. return Boolean(requiredValue.values.find(v => v === defaultValue))
  55. }
  56. return true
  57. }
  58. if (requiredValue) {
  59. return Boolean(requiredValue.values.find(v => v === data[fn]))
  60. }
  61. return data[fn]
  62. }
  63. return false
  64. })
  65. let isCurrentFieldValid = field ? validFields.find(fn => field ? fn === field.name : false) : true
  66. if (validFields.length !== requiredFields.length || !isCurrentFieldValid) {
  67. return null
  68. }
  69. let envData = {}
  70. validFields.forEach(fn => {
  71. envData[fn] = data ? data[fn] : null
  72. if (envData[fn] == null) {
  73. let schemaField = findFieldInSchema(fn)
  74. if (schemaField && schemaField.default) {
  75. envData[fn] = schemaField.default
  76. }
  77. }
  78. })
  79. return envData
  80. }
  81. class ProviderStore {
  82. @observable connectionInfoSchema: Field[] = []
  83. @observable connectionSchemaLoading: boolean = false
  84. @observable providers: ?Providers
  85. @observable providersLoading: boolean = false
  86. @observable destinationSchema: Field[] = []
  87. @observable destinationSchemaLoading: boolean = false
  88. @observable destinationOptions: OptionValues[] = []
  89. // Set to true while loading the options call for the first set of options
  90. @observable destinationOptionsPrimaryLoading: boolean = false
  91. // Set to true while loading the options call with a set of values in the 'env' parameter
  92. @observable destinationOptionsSecondaryLoading: boolean = false
  93. @observable sourceOptions: OptionValues[] = []
  94. // Set to true while loading the options call for the first set of options
  95. @observable sourceOptionsPrimaryLoading: boolean = false
  96. // Set to true while loading the options call with a set of values in the 'env' parameter
  97. @observable sourceOptionsSecondaryLoading: boolean = false
  98. @observable sourceSchema: Field[] = []
  99. @observable sourceSchemaLoading: boolean = false
  100. lastDestinationSchemaType: 'replica' | 'migration' = 'replica'
  101. @computed
  102. get providerNames(): string[] {
  103. let sortPriority = configLoader.config.providerSortPriority
  104. let array = Object.keys(this.providers || {}).sort((a, b) => {
  105. if (sortPriority[a] && sortPriority[b]) {
  106. return (sortPriority[a] - sortPriority[b]) || a.localeCompare(b)
  107. }
  108. if (sortPriority[a]) {
  109. return -1
  110. }
  111. if (sortPriority[b]) {
  112. return 1
  113. }
  114. return a.localeCompare(b)
  115. })
  116. return array
  117. }
  118. @action async getConnectionInfoSchema(providerName: string): Promise<void> {
  119. this.connectionSchemaLoading = true
  120. try {
  121. let fields: Field[] = await ProviderSource.getConnectionInfoSchema(providerName)
  122. runInAction(() => { this.connectionInfoSchema = fields })
  123. } catch (err) {
  124. throw err
  125. } finally {
  126. runInAction(() => { this.connectionSchemaLoading = false })
  127. }
  128. }
  129. @action clearConnectionInfoSchema() {
  130. this.connectionInfoSchema = []
  131. }
  132. @action async loadProviders(): Promise<void> {
  133. if (this.providers) {
  134. return
  135. }
  136. this.providersLoading = true
  137. try {
  138. let providers: Providers = await ProviderSource.loadProviders()
  139. runInAction(() => { this.providers = providers })
  140. } finally {
  141. runInAction(() => { this.providersLoading = false })
  142. }
  143. }
  144. @action async loadOptionsSchema(options: {
  145. providerName: string,
  146. optionsType: 'source' | 'destination',
  147. useCache?: boolean,
  148. quietError?: boolean,
  149. }): Promise<Field[]> {
  150. let { providerName, optionsType, useCache, quietError } = options
  151. if (optionsType === 'source') {
  152. this.sourceSchemaLoading = true
  153. } else {
  154. this.destinationSchemaLoading = true
  155. }
  156. try {
  157. let fields: Field[] = await ProviderSource.loadOptionsSchema(providerName, optionsType, useCache, quietError)
  158. this.loadOptionsSchemaSuccess(fields, optionsType)
  159. return fields
  160. } catch (err) {
  161. throw err
  162. } finally {
  163. this.loadOptionsSchemaDone(optionsType)
  164. }
  165. }
  166. @action loadOptionsSchemaSuccess(fields: Field[], optionsType: 'source' | 'destination') {
  167. if (optionsType === 'source') {
  168. this.sourceSchema = fields
  169. } else {
  170. this.destinationSchema = fields
  171. }
  172. }
  173. @action loadOptionsSchemaDone(optionsType: 'source' | 'destination') {
  174. if (optionsType === 'source') {
  175. this.sourceSchemaLoading = false
  176. } else {
  177. this.destinationSchemaLoading = false
  178. }
  179. }
  180. async getOptionsValues(config: {
  181. optionsType: 'source' | 'destination',
  182. endpointId: string,
  183. providerName: string,
  184. envData?: ?{ [string]: mixed },
  185. useCache?: boolean,
  186. quietError?: boolean,
  187. allowMultiple?: boolean,
  188. }): Promise<OptionValues[]> {
  189. let { providerName, optionsType, endpointId, envData, useCache, quietError, allowMultiple } = config
  190. let providerType = optionsType === 'source' ? providerTypes.SOURCE_OPTIONS : providerTypes.DESTINATION_OPTIONS
  191. await this.loadProviders()
  192. if (!this.providers) {
  193. return []
  194. }
  195. let providerWithExtraOptions = this.providers[providerName].types.find(t => t === providerType)
  196. if (!providerWithExtraOptions) {
  197. return []
  198. }
  199. let canceled = false
  200. if (!allowMultiple) {
  201. apiCaller.cancelRequests(endpointId)
  202. }
  203. this.getOptionsValuesStart(optionsType, !envData)
  204. try {
  205. let options = await ProviderSource.getOptionsValues(optionsType, endpointId, envData, useCache, quietError)
  206. this.getOptionsValuesSuccess(optionsType, providerName, options)
  207. return options
  208. } catch (err) {
  209. console.error(err)
  210. canceled = err ? err.canceled : false
  211. if (canceled) {
  212. return optionsType === 'source' ? [...this.sourceOptions] : [...this.destinationOptions]
  213. }
  214. throw err
  215. } finally {
  216. if (!canceled) {
  217. this.getOptionsValuesDone(optionsType, !envData)
  218. }
  219. }
  220. }
  221. @action getOptionsValuesStart(optionsType: 'source' | 'destination', isPrimary: boolean) {
  222. if (optionsType === 'source') {
  223. if (isPrimary) {
  224. this.sourceOptions = []
  225. this.sourceOptionsPrimaryLoading = true
  226. } else {
  227. this.sourceOptionsSecondaryLoading = true
  228. }
  229. } else if (isPrimary) {
  230. this.destinationOptions = []
  231. this.destinationOptionsPrimaryLoading = true
  232. } else {
  233. this.destinationOptionsSecondaryLoading = true
  234. }
  235. }
  236. @action getOptionsValuesDone(optionsType: 'source' | 'destination', isPrimary: boolean) {
  237. if (optionsType === 'source') {
  238. if (isPrimary) {
  239. this.sourceOptionsPrimaryLoading = false
  240. } else {
  241. this.sourceOptionsSecondaryLoading = false
  242. }
  243. } else if (isPrimary) {
  244. this.destinationOptionsPrimaryLoading = false
  245. } else {
  246. this.destinationOptionsSecondaryLoading = false
  247. }
  248. }
  249. @action getOptionsValuesSuccess(optionsType: 'source' | 'destination', provider: string, options: OptionValues[]) {
  250. let schema = optionsType === 'source' ? this.sourceSchema : this.destinationSchema
  251. schema.forEach(field => {
  252. const parser = OptionsSchemaPlugin[provider] || OptionsSchemaPlugin.default
  253. parser.fillFieldValues(field, options)
  254. })
  255. if (optionsType === 'source') {
  256. this.sourceSchema = [...schema]
  257. this.sourceOptions = options
  258. } else {
  259. this.destinationSchema = [...schema]
  260. this.destinationOptions = options
  261. }
  262. }
  263. }
  264. export default new ProviderStore()