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

Dynamic instances pagination based on provider

Some providers may prefer listing all instances at once ('OVM'), some
providers may prefer listing more instances than what fits in one UI
page, while other providers prefer listing as few instances as possible.

This is now supported and configurable in
`config.js`:`instancesListBackgroundLoading`. Check the comments of that
line for how the configuration works.
Sergiu Miclea 7 лет назад
Родитель
Сommit
14d6205fa5

+ 5 - 5
src/components/organisms/WizardInstances/WizardInstances.jsx

@@ -190,7 +190,7 @@ type Props = {
   instances: InstanceType[],
   instances: InstanceType[],
   selectedInstances: ?InstanceType[],
   selectedInstances: ?InstanceType[],
   currentPage: number,
   currentPage: number,
-  chunkSize: number,
+  instancesPerPage: number,
   loading: boolean,
   loading: boolean,
   chunksLoading: boolean,
   chunksLoading: boolean,
   searching: boolean,
   searching: boolean,
@@ -295,8 +295,8 @@ class WizardInstances extends React.Component<Props, State> {
     if (this.props.loading || this.props.searchNotFound || this.props.reloading || this.areNoInstances()) {
     if (this.props.loading || this.props.searchNotFound || this.props.reloading || this.areNoInstances()) {
       return null
       return null
     }
     }
-    let startIdx = (this.props.currentPage - 1) * this.props.chunkSize
-    let endIdx = startIdx + (this.props.chunkSize - 1)
+    let startIdx = (this.props.currentPage - 1) * this.props.instancesPerPage
+    let endIdx = startIdx + (this.props.instancesPerPage - 1)
     let filteredInstances = this.props.instances.filter((i, idx) => idx >= startIdx && idx <= endIdx)
     let filteredInstances = this.props.instances.filter((i, idx) => idx >= startIdx && idx <= endIdx)
 
 
     return (
     return (
@@ -359,7 +359,7 @@ class WizardInstances extends React.Component<Props, State> {
       return null
       return null
     }
     }
 
 
-    let hasNextPage = this.props.currentPage * this.props.chunkSize < this.props.instances.length
+    let hasNextPage = this.props.currentPage * this.props.instancesPerPage < this.props.instances.length
     let areAllDisabled = this.props.searching
     let areAllDisabled = this.props.searching
     let isPreviousDisabled = this.props.currentPage === 1 || areAllDisabled
     let isPreviousDisabled = this.props.currentPage === 1 || areAllDisabled
     let isNextDisabled = !hasNextPage || areAllDisabled
     let isNextDisabled = !hasNextPage || areAllDisabled
@@ -374,7 +374,7 @@ class WizardInstances extends React.Component<Props, State> {
           <Arrow orientation="left" disabled={isPreviousDisabled} color={Palette.black} thick />
           <Arrow orientation="left" disabled={isPreviousDisabled} color={Palette.black} thick />
         </PagePrevious>
         </PagePrevious>
         <PageNumber data-test-id="wInstances-currentPage">
         <PageNumber data-test-id="wInstances-currentPage">
-          {this.props.currentPage} of {Math.ceil(this.props.instances.length / this.props.chunkSize)}
+          {this.props.currentPage} of {Math.ceil(this.props.instances.length / this.props.instancesPerPage)}
           {this.props.chunksLoading ? (
           {this.props.chunksLoading ? (
             <HorizontalLoading style={{ width: '100%', top: '3px' }} data-test-id="wInstances-loadingChunks" />
             <HorizontalLoading style={{ width: '100%', top: '3px' }} data-test-id="wInstances-loadingChunks" />
           ) : null}
           ) : null}

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

@@ -315,7 +315,7 @@ class WizardPageContent extends React.Component<Props, State> {
         body = (
         body = (
           <WizardInstances
           <WizardInstances
             instances={this.props.instanceStore.instances}
             instances={this.props.instanceStore.instances}
-            chunkSize={this.props.instanceStore.chunkSize}
+            instancesPerPage={this.props.instanceStore.instancesPerPage}
             chunksLoading={this.props.instanceStore.chunksLoading}
             chunksLoading={this.props.instanceStore.chunksLoading}
             currentPage={this.props.instanceStore.currentPage}
             currentPage={this.props.instanceStore.currentPage}
             searchText={this.props.instanceStore.searchText}
             searchText={this.props.instanceStore.searchText}

+ 8 - 12
src/components/pages/WizardPage/WizardPage.jsx

@@ -70,8 +70,9 @@ class WizardPage extends React.Component<Props, State> {
 
 
   contentRef: WizardPageContent
   contentRef: WizardPageContent
 
 
-  get instancesChunkSize() {
-    let { min, max } = wizardConfig.instancesPerPage
+  get instancesPerPage() {
+    const min = 3
+    const max = Infinity
     const instancesTableDiff = 505
     const instancesTableDiff = 505
     const instancesItemHeight = 67
     const instancesItemHeight = 67
     return Math.min(max, Math.max(min, Math.floor((window.innerHeight - instancesTableDiff) / instancesItemHeight)))
     return Math.min(max, Math.max(min, Math.floor((window.innerHeight - instancesTableDiff) / instancesItemHeight)))
@@ -113,7 +114,7 @@ class WizardPage extends React.Component<Props, State> {
 
 
   @autobind
   @autobind
   handleResize() {
   handleResize() {
-    instanceStore.updateChunkSize(this.instancesChunkSize)
+    instanceStore.updateInstancesPerPage(this.instancesPerPage)
   }
   }
 
 
   handleEnterKey() {
   handleEnterKey() {
@@ -218,7 +219,7 @@ class WizardPage extends React.Component<Props, State> {
     endpointStore.getConnectionInfo(source).then(() => {
     endpointStore.getConnectionInfo(source).then(() => {
       if (source) {
       if (source) {
         // Preload instances for 'vms' page
         // Preload instances for 'vms' page
-        instanceStore.loadInstancesInChunks(source.id, this.instancesChunkSize)
+        instanceStore.loadInstancesInChunks(source, this.instancesPerPage)
       }
       }
     }).catch(() => {
     }).catch(() => {
       this.handleSourceEndpointChange(null)
       this.handleSourceEndpointChange(null)
@@ -263,13 +264,13 @@ class WizardPage extends React.Component<Props, State> {
 
 
   handleInstancesSearchInputChange(searchText: string) {
   handleInstancesSearchInputChange(searchText: string) {
     if (wizardStore.data.source) {
     if (wizardStore.data.source) {
-      instanceStore.searchInstances(wizardStore.data.source.id, searchText)
+      instanceStore.searchInstances(wizardStore.data.source, searchText)
     }
     }
   }
   }
 
 
   handleInstancesReloadClick() {
   handleInstancesReloadClick() {
     if (wizardStore.data.source) {
     if (wizardStore.data.source) {
-      instanceStore.reloadInstances(wizardStore.data.source.id, this.instancesChunkSize)
+      instanceStore.reloadInstances(wizardStore.data.source, this.instancesPerPage)
     }
     }
   }
   }
 
 
@@ -284,10 +285,6 @@ class WizardPage extends React.Component<Props, State> {
     instanceStore.setPage(page)
     instanceStore.setPage(page)
   }
   }
 
 
-  handleInstanceChunkSizeUpdate(chunkSize: number) {
-    instanceStore.updateChunkSize(chunkSize)
-  }
-
   handleDestOptionsChange(field: Field, value: any) {
   handleDestOptionsChange(field: Field, value: any) {
     wizardStore.updateData({ networks: null })
     wizardStore.updateData({ networks: null })
     wizardStore.clearStorageMap()
     wizardStore.clearStorageMap()
@@ -366,7 +363,7 @@ class WizardPage extends React.Component<Props, State> {
           // Check if user has permission for this endpoint
           // Check if user has permission for this endpoint
           endpointStore.getConnectionInfo(source).then(() => {
           endpointStore.getConnectionInfo(source).then(() => {
             // Preload instances for 'vms' page
             // Preload instances for 'vms' page
-            instanceStore.loadInstancesInChunks(source.id, this.instancesChunkSize)
+            instanceStore.loadInstancesInChunks(source, this.instancesPerPage)
           }).catch(() => {
           }).catch(() => {
             this.handleSourceEndpointChange(null)
             this.handleSourceEndpointChange(null)
           })
           })
@@ -517,7 +514,6 @@ class WizardPage extends React.Component<Props, State> {
             onInstancesReloadClick={() => { this.handleInstancesReloadClick() }}
             onInstancesReloadClick={() => { this.handleInstancesReloadClick() }}
             onInstanceClick={instance => { this.handleInstanceClick(instance) }}
             onInstanceClick={instance => { this.handleInstanceClick(instance) }}
             onInstancePageClick={page => { this.handleInstancePageClick(page) }}
             onInstancePageClick={page => { this.handleInstancePageClick(page) }}
-            onInstanceChunkSizeUpdate={chunkSize => { this.handleInstanceChunkSizeUpdate(chunkSize) }}
             onDestOptionsChange={(field, value) => { this.handleDestOptionsChange(field, value) }}
             onDestOptionsChange={(field, value) => { this.handleDestOptionsChange(field, value) }}
             onSourceOptionsChange={(field, value) => { this.handleSourceOptionsChange(field, value) }}
             onSourceOptionsChange={(field, value) => { this.handleSourceOptionsChange(field, value) }}
             onNetworkChange={(sourceNic, targetNetwork) => { this.handleNetworkChange(sourceNic, targetNetwork) }}
             onNetworkChange={(sourceNic, targetNetwork) => { this.handleNetworkChange(sourceNic, targetNetwork) }}

+ 6 - 1
src/config.js

@@ -112,9 +112,14 @@ export const wizardConfig = {
     { id: 'schedule', title: 'Schedule', breadcrumb: 'Schedule', excludeFrom: 'migration' },
     { id: 'schedule', title: 'Schedule', breadcrumb: 'Schedule', excludeFrom: 'migration' },
     { id: 'summary', title: 'Summary', breadcrumb: 'Summary' },
     { id: 'summary', title: 'Summary', breadcrumb: 'Summary' },
   ],
   ],
-  instancesPerPage: { min: 3, max: Infinity },
 }
 }
 
 
+// - Specifies the `limit` for each provider when listing all its VMs for pagination.
+// - If the provider is not in this list, the 'default' value will be used.
+// - If the `default` value is lower than the number of instances that fit into a page, the latter number will be used.
+// - `Infinity` value means no `limit` will be used, i.e. all VMs will be listed.
+export const instancesListBackgroundLoading = { default: 10, ovm: Infinity }
+
 // A list of providers for which `destination-options` API call(s) will be made in the Wizard
 // 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 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
 // If the item has `envRequiredFields`, an additional API call will be made once the specified fields are filled

+ 19 - 4
src/sources/InstanceSource.js

@@ -28,16 +28,31 @@ class InstanceSource {
     searchText?: string
     searchText?: string
   ): Promise<Instance[]> {
   ): Promise<Instance[]> {
     let url = `${servicesUrl.coriolis}/${Api.projectId}/endpoints/${endpointId}/instances`
     let url = `${servicesUrl.coriolis}/${Api.projectId}/endpoints/${endpointId}/instances`
-    url = `${url}?limit=${chunkSize}`
+    let queryParams: { [string]: string | number } = {}
 
 
-    if (lastInstanceId) {
-      url = `${url}&marker=${lastInstanceId}`
+    if (chunkSize !== Infinity) {
+      queryParams = {
+        limit: chunkSize,
+      }
+
+      if (lastInstanceId) {
+        queryParams = {
+          ...queryParams,
+          marker: lastInstanceId,
+        }
+      }
     }
     }
 
 
     if (searchText) {
     if (searchText) {
-      url = `${url}&name=${searchText}`
+      queryParams = {
+        ...queryParams,
+        name: searchText,
+      }
     }
     }
 
 
+    let keys = Object.keys(queryParams)
+    url = `${url}${keys.length > 0 ? '?' : ''}${keys.map(p => `${p}=${queryParams[p]}`).join('&')}`
+
     return Api.send({ url, cancelId }).then(response => {
     return Api.send({ url, cancelId }).then(response => {
       return response.data.instances
       return response.data.instances
     })
     })

+ 23 - 18
src/stores/InstanceStore.js

@@ -17,8 +17,10 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 import { observable, action, computed } from 'mobx'
 import { observable, action, computed } from 'mobx'
 
 
 import type { Instance } from '../types/Instance'
 import type { Instance } from '../types/Instance'
+import type { Endpoint } from '../types/Endpoint'
 import InstanceSource from '../sources/InstanceSource'
 import InstanceSource from '../sources/InstanceSource'
 import ApiCaller from '../utils/ApiCaller'
 import ApiCaller from '../utils/ApiCaller'
+import { instancesListBackgroundLoading as chunkSize } from '../config'
 
 
 class InstanceLocalStorage {
 class InstanceLocalStorage {
   static saveInstancesToLocalStorage(endpointId: string, instances: Instance[]) {
   static saveInstancesToLocalStorage(endpointId: string, instances: Instance[]) {
@@ -83,7 +85,7 @@ class InstanceLocalStorage {
 
 
 class InstanceStore {
 class InstanceStore {
   @observable instancesLoading = false
   @observable instancesLoading = false
-  @observable chunkSize = 6
+  @observable instancesPerPage = 6
   @observable currentPage = 1
   @observable currentPage = 1
   @observable searchChunksLoading = false
   @observable searchChunksLoading = false
   @observable searchedInstances: Instance[] = []
   @observable searchedInstances: Instance[] = []
@@ -115,8 +117,8 @@ class InstanceStore {
   lastEndpointId: string
   lastEndpointId: string
   reqId: number
   reqId: number
 
 
-  @action loadInstancesInChunks(endpointId: string, chunkSize?: number = 6, reload?: boolean) {
-    ApiCaller.cancelRequests(`${endpointId}-chunk`)
+  @action loadInstancesInChunks(endpoint: Endpoint, vmsPerPage?: number = 6, reload?: boolean) {
+    ApiCaller.cancelRequests(`${endpoint.id}-chunk`)
 
 
     this.backgroundInstances = []
     this.backgroundInstances = []
     if (reload) {
     if (reload) {
@@ -125,11 +127,13 @@ class InstanceStore {
       this.instancesLoading = true
       this.instancesLoading = true
     }
     }
     this.backgroundChunksLoading = true
     this.backgroundChunksLoading = true
-    this.lastEndpointId = endpointId
+    this.lastEndpointId = endpoint.id
+
+    let chunkCount = Math.max(chunkSize[endpoint.type] || chunkSize.default, vmsPerPage)
 
 
     let loadNextChunk = (lastEndpointId?: string) => {
     let loadNextChunk = (lastEndpointId?: string) => {
-      let currentEndpointId = endpointId
-      InstanceSource.loadInstancesChunk(currentEndpointId, chunkSize, lastEndpointId, `${endpointId}-chunk`)
+      let currentEndpointId = endpoint.id
+      InstanceSource.loadInstancesChunk(currentEndpointId, chunkCount, lastEndpointId, `${endpoint.id}-chunk`)
         .then(instances => {
         .then(instances => {
           if (currentEndpointId !== this.lastEndpointId) {
           if (currentEndpointId !== this.lastEndpointId) {
             return
             return
@@ -141,7 +145,7 @@ class InstanceStore {
           }
           }
           this.instancesLoading = false
           this.instancesLoading = false
 
 
-          if (instances.length < chunkSize) {
+          if (instances.length < chunkCount) {
             this.backgroundChunksLoading = false
             this.backgroundChunksLoading = false
             return
             return
           }
           }
@@ -177,8 +181,8 @@ class InstanceStore {
     })
     })
   }
   }
 
 
-  @action searchInstances(endpointId: string, searchText: string) {
-    ApiCaller.cancelRequests(`${endpointId}-chunk-search`)
+  @action searchInstances(endpoint: Endpoint, searchText: string) {
+    ApiCaller.cancelRequests(`${endpoint.id}-chunk-search`)
 
 
     this.searchText = searchText
     this.searchText = searchText
     this.searchNotFound = false
     this.searchNotFound = false
@@ -199,14 +203,15 @@ class InstanceStore {
 
 
     this.searching = true
     this.searching = true
     this.searchChunksLoading = true
     this.searchChunksLoading = true
-    let chunkSize = this.chunkSize
+
+    let chunkCount = Math.max(chunkSize[endpoint.type] || chunkSize.default, this.instancesPerPage)
 
 
     let loadNextChunk = (lastEndpointId?: string) => {
     let loadNextChunk = (lastEndpointId?: string) => {
       InstanceSource.loadInstancesChunk(
       InstanceSource.loadInstancesChunk(
-        endpointId,
-        chunkSize,
+        endpoint.id,
+        chunkCount,
         lastEndpointId,
         lastEndpointId,
-        `${endpointId}-chunk-search`,
+        `${endpoint.id}-chunk-search`,
         searchText
         searchText
       ).then(instances => {
       ).then(instances => {
         if (this.searching) {
         if (this.searching) {
@@ -217,7 +222,7 @@ class InstanceStore {
         this.searchedInstances = [...this.searchedInstances, ...instances]
         this.searchedInstances = [...this.searchedInstances, ...instances]
         this.searching = false
         this.searching = false
         this.searchNotFound = Boolean(this.searchedInstances.length === 0)
         this.searchNotFound = Boolean(this.searchedInstances.length === 0)
-        if (instances.length < chunkSize) {
+        if (instances.length < chunkCount) {
           this.searchChunksLoading = false
           this.searchChunksLoading = false
         }
         }
         return loadNextChunk(instances[instances.length - 1].id)
         return loadNextChunk(instances[instances.length - 1].id)
@@ -226,11 +231,11 @@ class InstanceStore {
     loadNextChunk()
     loadNextChunk()
   }
   }
 
 
-  @action reloadInstances(endpointId: string, chunkSize?: number) {
+  @action reloadInstances(endpoint: Endpoint, chunkSize?: number) {
     this.searchNotFound = false
     this.searchNotFound = false
     this.searchText = ''
     this.searchText = ''
     this.currentPage = 1
     this.currentPage = 1
-    this.loadInstancesInChunks(endpointId, chunkSize, true)
+    this.loadInstancesInChunks(endpoint, chunkSize, true)
   }
   }
 
 
   @action cancelIntancesChunksLoading() {
   @action cancelIntancesChunksLoading() {
@@ -245,9 +250,9 @@ class InstanceStore {
     this.currentPage = page
     this.currentPage = page
   }
   }
 
 
-  @action updateChunkSize(chunkSize: number) {
+  @action updateInstancesPerPage(instancesPerPage: number) {
     this.currentPage = 1
     this.currentPage = 1
-    this.chunkSize = chunkSize
+    this.instancesPerPage = instancesPerPage
   }
   }
 
 
   @action loadInstancesDetails(endpointId: string, instancesInfo: Instance[], useLocalStorage?: boolean, quietError?: boolean): Promise<void> {
   @action loadInstancesDetails(endpointId: string, instancesInfo: Instance[], useLocalStorage?: boolean, quietError?: boolean): Promise<void> {