Explorar o código

Improve Wizard page reload state preservation

Reloading / Reopening of a Wizard page URL data link now fully supports
complex options (destination, source options API calls). Previously,
destination and source options API calls would not get made after page
reload.

Schedule and storage mapping is now also saved in URL data link.

Changing the Wizard type (i.e.: from Replica to Migration) now gets
properly reflected in the URL.
Sergiu Miclea %!s(int64=6) %!d(string=hai) anos
pai
achega
3795f09a3d

+ 26 - 25
src/components/pages/WizardPage/WizardPage.jsx

@@ -96,7 +96,7 @@ class WizardPage extends React.Component<Props, State> {
   }
 
   componentWillMount() {
-    this.initializeState()
+    this.initializeState(this.props.match)
     this.handleResize()
   }
 
@@ -108,11 +108,11 @@ class WizardPage extends React.Component<Props, State> {
   }
 
   componentWillReceiveProps(newProps: Props) {
-    if (newProps.location.search === this.props.location.search) {
+    if (newProps.location === this.props.location) {
       return
     }
     wizardStore.clearData()
-    this.initializeState()
+    this.initializeState(newProps.match)
   }
 
   componentWillUnmount() {
@@ -182,8 +182,8 @@ class WizardPage extends React.Component<Props, State> {
       source: null,
     })
     wizardStore.clearStorageMap()
-    wizardStore.setPermalink(wizardStore.data)
-    this.setState({ type: isReplica ? 'replica' : 'migration' })
+    let type = isReplica ? 'replica' : 'migration'
+    this.props.history.replace(`/wizard/${type}`)
   }
 
   handleBackClick() {
@@ -215,7 +215,7 @@ class WizardPage extends React.Component<Props, State> {
   async handleSourceEndpointChange(source: ?EndpointType) {
     wizardStore.updateData({ source, selectedInstances: null, networks: null, sourceOptions: null })
     wizardStore.clearStorageMap()
-    wizardStore.setPermalink(wizardStore.data)
+    wizardStore.updateUrlState()
 
     if (!source) {
       return
@@ -237,7 +237,7 @@ class WizardPage extends React.Component<Props, State> {
   async handleTargetEndpointChange(target: EndpointType) {
     wizardStore.updateData({ target, networks: null, destOptions: null })
     wizardStore.clearStorageMap()
-    wizardStore.setPermalink(wizardStore.data)
+    wizardStore.updateUrlState()
     if (this.pages.find(p => p.id === 'storage')) {
       endpointStore.loadStorage(target.id, {})
     }
@@ -273,7 +273,7 @@ class WizardPage extends React.Component<Props, State> {
         wizardStore.updateData({ target: endpointStore.endpoints[0] })
       }
     }
-    wizardStore.setPermalink(wizardStore.data)
+    wizardStore.updateUrlState()
     this.setState({ showNewEndpointModal: false })
   }
 
@@ -293,7 +293,7 @@ class WizardPage extends React.Component<Props, State> {
     wizardStore.updateData({ networks: null })
     wizardStore.clearStorageMap()
     wizardStore.toggleInstanceSelection(instance)
-    wizardStore.setPermalink(wizardStore.data)
+    wizardStore.updateUrlState()
   }
 
   handleInstancePageClick(page: number) {
@@ -312,7 +312,7 @@ class WizardPage extends React.Component<Props, State> {
     if (field.type !== 'string' || field.enum) {
       this.loadExtraOptions(field, 'destination')
     }
-    wizardStore.setPermalink(wizardStore.data)
+    wizardStore.updateUrlState()
   }
 
   handleSourceOptionsChange(field: Field, value: any) {
@@ -321,28 +321,32 @@ class WizardPage extends React.Component<Props, State> {
     if (field.type !== 'string' || field.enum) {
       this.loadExtraOptions(field, 'source')
     }
-    wizardStore.setPermalink(wizardStore.data)
+    wizardStore.updateUrlState()
   }
 
   handleNetworkChange(sourceNic: Nic, targetNetwork: Network, targetSecurityGroups: ?SecurityGroup[]) {
     wizardStore.updateNetworks({ sourceNic, targetNetwork, targetSecurityGroups })
-    wizardStore.setPermalink(wizardStore.data)
+    wizardStore.updateUrlState()
   }
 
   handleStorageChange(source: Disk, target: StorageBackend, type: 'backend' | 'disk') {
     wizardStore.updateStorage({ source, target, type })
+    wizardStore.updateUrlState()
   }
 
   handleAddScheduleClick(schedule: Schedule) {
     wizardStore.addSchedule(schedule)
+    wizardStore.updateUrlState()
   }
 
   handleScheduleChange(scheduleId: string, data: Schedule) {
     wizardStore.updateSchedule(scheduleId, data)
+    wizardStore.updateUrlState()
   }
 
   handleScheduleRemove(scheduleId: string) {
     wizardStore.removeSchedule(scheduleId)
+    wizardStore.updateUrlState()
   }
 
   async handleReloadOptionsClick() {
@@ -364,9 +368,9 @@ class WizardPage extends React.Component<Props, State> {
     await this.loadExtraOptions(undefined, optionsType, false)
   }
 
-  initializeState() {
-    wizardStore.getDataFromPermalink()
-    let type = this.props.match && this.props.match.params.type
+  initializeState(match: any) {
+    wizardStore.getUrlState()
+    let type = match && match.params && match.params.type
     if (type === 'migration' || type === 'replica') {
       this.setState({ type })
     }
@@ -409,16 +413,13 @@ class WizardPage extends React.Component<Props, State> {
         useCache: true,
       })
 
-      // Preload source options if data is set from 'Permalink'
-      if (providerStore.sourceOptions.length === 0) {
-        await providerStore.getOptionsValues({
-          optionsType,
-          endpointId: endpoint.id,
-          providerName: endpoint.type,
-          useCache: true,
-        })
-        await this.loadExtraOptions(undefined, optionsType)
-      }
+      await providerStore.getOptionsValues({
+        optionsType,
+        endpointId: endpoint.id,
+        providerName: endpoint.type,
+        useCache: true,
+      })
+      await this.loadExtraOptions(undefined, optionsType)
     }
 
     switch (page.id) {

+ 12 - 7
src/sources/WizardSource.js

@@ -73,19 +73,24 @@ class WizardSource {
     return mainItems.filter(Boolean).map(i => i)
   }
 
-  setPermalink(data: WizardData) {
-    // window.history.replaceState({}, null, `${window.location.href}?d=${btoa(JSON.stringify(data))}`)
-    let exp = /.*?(?:\?|$)/.exec(window.location.href)
-    if (!exp) {
+  setUrlState(data: any) {
+    let locationExp = /.*?(?:\?|$)/.exec(window.location.href)
+    if (!locationExp) {
       return
     }
-    let location = exp[0].replace('?', '')
+    let location = locationExp[0].replace('?', '')
     window.history.replaceState({}, null, `${location}?d=${btoa(JSON.stringify(data))}`)
   }
 
-  getDataFromPermalink() {
+  getUrlState() {
     let dataExpExec = /\?d=(.*)/.exec(window.location.href)
-    return dataExpExec && JSON.parse(atob(dataExpExec[1]))
+    let result = null
+    try {
+      result = dataExpExec && JSON.parse(atob(dataExpExec[1]))
+    } catch (err) {
+      console.error(err)
+    }
+    return result
   }
 }
 

+ 11 - 13
src/stores/WizardStore.js

@@ -24,7 +24,7 @@ import type { NetworkMap } from '../types/Network'
 import type { StorageMap } from '../types/Endpoint'
 import type { Schedule } from '../types/Schedule'
 import { wizardPages } from '../constants'
-import Source from '../sources/WizardSource'
+import source from '../sources/WizardSource'
 
 const updateOptions = (oldOptions: ?{ [string]: mixed }, data: { field: Field, value: any }) => {
   let options = { ...oldOptions }
@@ -141,7 +141,7 @@ class WizardStore {
     this.creatingItem = true
 
     try {
-      let item: MainItem = await Source.create(type, data, storageMap)
+      let item: MainItem = await source.create(type, data, storageMap)
       runInAction(() => { this.createdItem = item })
     } catch (err) {
       throw err
@@ -154,27 +154,25 @@ class WizardStore {
     this.creatingItems = true
 
     try {
-      let items: MainItem[] = await Source.createMultiple(type, data, storageMap)
+      let items: MainItem[] = await source.createMultiple(type, data, storageMap)
       runInAction(() => { this.createdItems = items })
     } finally {
       runInAction(() => { this.creatingItems = false })
     }
   }
 
-  setPermalink(data: WizardData) {
-    Source.setPermalink(data)
+  updateUrlState() {
+    source.setUrlState({ data: this.data, schedules: this.schedules, storageMap: this.storageMap })
   }
 
-  @action getDataFromPermalink() {
-    let data = Source.getDataFromPermalink()
-    if (!data) {
+  @action getUrlState() {
+    let state = source.getUrlState()
+    if (!state) {
       return
     }
-
-    this.data = {
-      ...this.data,
-      ...data,
-    }
+    this.data = state.data
+    this.schedules = state.schedules
+    this.storageMap = state.storageMap
   }
 }