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

Merge pull request #305 from smiclea/wizard-instances

Show instances per page based on window height
Dorin Paslaru 7 лет назад
Родитель
Сommit
68c79c231e

+ 1 - 1
src/components/atoms/Arrow/images/arrow-thick.js

@@ -3,7 +3,7 @@ export default color => `<?xml version="1.0" encoding="UTF-8"?>
     <!-- Generator: Sketch 52.3 (67297) - http://www.bohemiancoding.com/sketch -->
     <title>Rectangle Copy</title>
     <desc>Created with Sketch.</desc>
-    <g id="Symbols" transform="translateY(-1px)" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
+    <g id="Symbols" transform="translate(0 -1)" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round">
         <g id="Icon/Chevron/Black" transform="translate(-5.000000, -1.000000)" stroke="${color}" stroke-width="1.5">
             <polyline id="Rectangle-Copy" transform="translate(6.000000, 7.500000) rotate(315.000000) translate(-6.000000, -7.500000) " points="9.8890873 3.6109127 9.8890873 11.3890873 2.1109127 11.3890873"></polyline>
         </g>

+ 1 - 1
src/components/atoms/Arrow/images/arrow.js

@@ -20,7 +20,7 @@ version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/
     <desc>Created with Sketch.</desc>
     <defs></defs>
     <g id="Coriolis" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"
-     stroke-linecap="round" stroke-linejoin="round" transform="rotate(90deg)">
+     stroke-linecap="round" stroke-linejoin="round">
         <g id="202-Replica-Executions" transform="translate(-1197.000000, -194.000000)" stroke="${color}">
             <g id="Icon/Chevron/Grey"
             transform="translate(1200.000000, 200.000000) rotate(-90.000000)

+ 16 - 14
src/components/organisms/WizardInstances/test.jsx

@@ -30,15 +30,16 @@ let instances = [
   { id: 'i-2', flavor_name: 'Flavor name', instance_name: 'Instance name 2', num_cpu: 3, memory_mb: 1024 },
   { id: 'i-3', flavor_name: 'Flavor name', instance_name: 'Instance name 3', num_cpu: 3, memory_mb: 1024 },
 ]
+let onChunkSizeUpdate = () => { }
 
 describe('WizardInstances Component', () => {
   it('has correct number of instances', () => {
-    let wrapper = wrap({ instances, currentPage: 1 })
+    let wrapper = wrap({ instances, currentPage: 1, onChunkSizeUpdate })
     expect(wrapper.find('item-', true).length).toBe(instances.length)
   })
 
   it('has correct instances info', () => {
-    let wrapper = wrap({ instances, currentPage: 1 })
+    let wrapper = wrap({ instances, currentPage: 1, onChunkSizeUpdate })
     instances.forEach(instance => {
       expect(wrapper.find(`item-${instance.id}`).findText('itemName')).toBe(instance.instance_name)
       expect(wrapper.find(`item-${instance.id}`).findText('itemDetails')).toBe(`${instance.num_cpu} vCPU | ${instance.memory_mb} MB RAM | ${instance.flavor_name}`)
@@ -53,6 +54,7 @@ describe('WizardInstances Component', () => {
         { ...instances[0] },
         { ...instances[2] },
       ],
+      onChunkSizeUpdate,
     })
     expect(wrapper.findText('selInfo')).toBe('2 instances selected')
     expect(wrapper.find('item-i-1').prop('selected')).toBe(true)
@@ -61,56 +63,56 @@ describe('WizardInstances Component', () => {
   })
 
   it('renders current page', () => {
-    let wrapper = wrap({ instances, currentPage: 2, chunkSize: 2 })
+    let wrapper = wrap({ instances, currentPage: 2, chunkSize: 2, onChunkSizeUpdate })
     expect(wrapper.findText('currentPage')).toBe('2 of 2')
   })
 
   it('renders previous page disabled if page is 1', () => {
-    let wrapper = wrap({ instances, currentPage: 1 })
+    let wrapper = wrap({ instances, currentPage: 1, onChunkSizeUpdate })
     expect(wrapper.find('prevPageButton').prop('disabled')).toBe(true)
   })
 
   it('renders previous page enabled if page is greater than 1', () => {
-    let wrapper = wrap({ instances, currentPage: 3 })
+    let wrapper = wrap({ instances, currentPage: 3, onChunkSizeUpdate })
     expect(wrapper.find('prevPageButton').prop('disabled')).toBeFalsy()
     expect(wrapper.find('loadingStatus').length).toBe(0)
   })
 
   it('renders loading', () => {
-    let wrapper = wrap({ instances, currentPage: 1, loading: true })
+    let wrapper = wrap({ instances, currentPage: 1, loading: true, onChunkSizeUpdate })
     expect(wrapper.find('loadingStatus').length).toBe(1)
   })
 
   it('renders searching', () => {
-    let wrapper = wrap({ instances, currentPage: 1, searching: true })
+    let wrapper = wrap({ instances, currentPage: 1, searching: true, onChunkSizeUpdate })
     expect(wrapper.find('searchInput').prop('loading')).toBe(true)
   })
 
   it('renders search not found', () => {
-    let wrapper = wrap({ instances: [], currentPage: 1, searchNotFound: true })
+    let wrapper = wrap({ instances: [], currentPage: 1, searchNotFound: true, onChunkSizeUpdate })
     expect(wrapper.findText('notFoundText')).toBe('Your search returned no results')
     expect(wrapper.find('loadingChunks').length).toBe(0)
   })
 
   it('renders loading page', () => {
-    let wrapper = wrap({ instances, currentPage: 1, chunksLoading: true })
+    let wrapper = wrap({ instances, currentPage: 1, chunksLoading: true, onChunkSizeUpdate })
     expect(wrapper.find('loadingChunks').length).toBe(1)
   })
 
   it('enabled next page', () => {
-    let wrapper = wrap({ instances, currentPage: 1 })
+    let wrapper = wrap({ instances, currentPage: 1, onChunkSizeUpdate })
     expect(wrapper.find('nextPageButton').prop('disabled')).toBe(true)
-    wrapper = wrap({ instances, currentPage: 1, chunkSize: 2 })
+    wrapper = wrap({ instances, currentPage: 1, chunkSize: 2, onChunkSizeUpdate })
     expect(wrapper.find('nextPageButton').prop('disabled')).toBeFalsy()
   })
 
   it('dispatches next and previous page click, if enabled', () => {
     let onPageClick = sinon.spy()
-    let wrapper = wrap({ instances, currentPage: 1, onPageClick })
+    let wrapper = wrap({ instances, currentPage: 1, onPageClick, onChunkSizeUpdate })
     wrapper.find('nextPageButton').click()
     wrapper.find('prevPageButton').click()
     expect(onPageClick.callCount).toBe(0)
-    wrapper = wrap({ instances, currentPage: 2, onPageClick, chunkSize: 1 })
+    wrapper = wrap({ instances, currentPage: 2, onPageClick, chunkSize: 1, onChunkSizeUpdate })
     wrapper.find('nextPageButton').click()
     wrapper.find('prevPageButton').click()
     expect(onPageClick.callCount).toBe(2)
@@ -118,7 +120,7 @@ describe('WizardInstances Component', () => {
 
   it('dispaches reload click', () => {
     let onReloadClick = sinon.spy()
-    let wrapper = wrap({ instances, currentPage: 1, onReloadClick })
+    let wrapper = wrap({ instances, currentPage: 1, onReloadClick, onChunkSizeUpdate })
     wrapper.find('reloadButton').click()
     expect(onReloadClick.calledOnce).toBe(true)
   })

+ 24 - 3
src/components/pages/WizardPage/WizardPage.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 autobind from 'autobind-decorator'
 import { observer } from 'mobx-react'
 
 import WizardTemplate from '../../templates/WizardTemplate'
@@ -68,14 +69,23 @@ class WizardPage extends React.Component<Props, State> {
 
   contentRef: WizardPageContent
 
+  get instancesChunkSize() {
+    let { min, max } = wizardConfig.instancesPerPage
+    const instancesTableDiff = 505
+    const instancesItemHeight = 67
+    return Math.min(max, Math.max(min, Math.floor((window.innerHeight - instancesTableDiff) / instancesItemHeight)))
+  }
+
   componentWillMount() {
     this.initializeState()
+    this.handleResize()
   }
 
   componentDidMount() {
     document.title = 'Coriolis Wizard'
     KeyboardManager.onEnter('wizard', () => { this.handleEnterKey() })
     KeyboardManager.onEsc('wizard', () => { this.handleEscKey() })
+    window.addEventListener('resize', this.handleResize)
   }
 
   componentWillReceiveProps(newProps: Props) {
@@ -90,6 +100,12 @@ class WizardPage extends React.Component<Props, State> {
     wizardStore.clearData()
     instanceStore.cancelIntancesChunksLoading()
     KeyboardManager.removeKeyDown('wizard')
+    window.removeEventListener('resize', this.handleResize, false)
+  }
+
+  @autobind
+  handleResize() {
+    instanceStore.updateChunkSize(this.instancesChunkSize)
   }
 
   handleEnterKey() {
@@ -182,7 +198,7 @@ class WizardPage extends React.Component<Props, State> {
       endpointStore.getConnectionInfo(source).then(() => {
         if (source) {
           // Preload instances for 'vms' page
-          instanceStore.loadInstancesInChunks(source.id)
+          instanceStore.loadInstancesInChunks(source.id, this.instancesChunkSize)
         }
       }).catch(() => {
         this.handleSourceEndpointChange(null)
@@ -228,7 +244,7 @@ class WizardPage extends React.Component<Props, State> {
 
   handleInstancesReloadClick() {
     if (wizardStore.data.source) {
-      instanceStore.reloadInstances(wizardStore.data.source.id)
+      instanceStore.reloadInstances(wizardStore.data.source.id, this.instancesChunkSize)
     }
   }
 
@@ -242,6 +258,10 @@ class WizardPage extends React.Component<Props, State> {
     instanceStore.setPage(page)
   }
 
+  handleInstanceChunkSizeUpdate(chunkSize: number) {
+    instanceStore.updateChunkSize(chunkSize)
+  }
+
   handleOptionsChange(field: Field, value: any) {
     wizardStore.updateData({ networks: null })
     wizardStore.updateOptions({ field, value })
@@ -333,7 +353,7 @@ class WizardPage extends React.Component<Props, State> {
           // Check if user has permission for this endpoint
           endpointStore.getConnectionInfo(source).then(() => {
             // Preload instances for 'vms' page
-            instanceStore.loadInstancesInChunks(source.id)
+            instanceStore.loadInstancesInChunks(source.id, this.instancesChunkSize)
           }).catch(() => {
             this.handleSourceEndpointChange(null)
           })
@@ -477,6 +497,7 @@ class WizardPage extends React.Component<Props, State> {
             onInstancesReloadClick={() => { this.handleInstancesReloadClick() }}
             onInstanceClick={instance => { this.handleInstanceClick(instance) }}
             onInstancePageClick={page => { this.handleInstancePageClick(page) }}
+            onInstanceChunkSizeUpdate={chunkSize => { this.handleInstanceChunkSizeUpdate(chunkSize) }}
             onOptionsChange={(field, value) => { this.handleOptionsChange(field, value) }}
             onNetworkChange={(sourceNic, targetNetwork) => { this.handleNetworkChange(sourceNic, targetNetwork) }}
             onAddScheduleClick={schedule => { this.handleAddScheduleClick(schedule) }}

+ 1 - 0
src/config.js

@@ -85,6 +85,7 @@ export const wizardConfig = {
     { id: 'schedule', title: 'Schedule', breadcrumb: 'Schedule', excludeFrom: 'migration' },
     { id: 'summary', title: 'Summary', breadcrumb: 'Summary' },
   ],
+  instancesPerPage: { min: 3, max: Infinity },
 }
 
 // A list of providers for which `destination-options` API call(s) will be made in the Wizard

+ 16 - 7
src/stores/InstanceStore.js

@@ -115,7 +115,7 @@ class InstanceStore {
   lastEndpointId: string
   reqId: number
 
-  @action loadInstancesInChunks(endpointId: string, reload?: boolean) {
+  @action loadInstancesInChunks(endpointId: string, chunkSize?: number = 6, reload?: boolean) {
     ApiCaller.cancelRequests(`${endpointId}-chunk`)
 
     this.backgroundInstances = []
@@ -129,7 +129,7 @@ class InstanceStore {
 
     let loadNextChunk = (lastEndpointId?: string) => {
       let currentEndpointId = endpointId
-      InstanceSource.loadInstancesChunk(currentEndpointId, this.chunkSize, lastEndpointId, `${endpointId}-chunk`)
+      InstanceSource.loadInstancesChunk(currentEndpointId, chunkSize, lastEndpointId, `${endpointId}-chunk`)
         .then(instances => {
           if (currentEndpointId !== this.lastEndpointId) {
             return
@@ -141,7 +141,7 @@ class InstanceStore {
           }
           this.instancesLoading = false
 
-          if (instances.length < this.chunkSize) {
+          if (instances.length < chunkSize) {
             this.backgroundChunksLoading = false
             return
           }
@@ -199,11 +199,12 @@ class InstanceStore {
 
     this.searching = true
     this.searchChunksLoading = true
+    let chunkSize = this.chunkSize
 
     let loadNextChunk = (lastEndpointId?: string) => {
       InstanceSource.loadInstancesChunk(
         endpointId,
-        this.chunkSize,
+        chunkSize,
         lastEndpointId,
         `${endpointId}-chunk-search`,
         searchText
@@ -216,7 +217,7 @@ class InstanceStore {
         this.searchedInstances = [...this.searchedInstances, ...instances]
         this.searching = false
         this.searchNotFound = Boolean(this.searchedInstances.length === 0)
-        if (instances.length < this.chunkSize) {
+        if (instances.length < chunkSize) {
           this.searchChunksLoading = false
         }
         return loadNextChunk(instances[instances.length - 1].id)
@@ -225,22 +226,30 @@ class InstanceStore {
     loadNextChunk()
   }
 
-  @action reloadInstances(endpointId: string) {
+  @action reloadInstances(endpointId: string, chunkSize?: number) {
     this.searchNotFound = false
     this.searchText = ''
     this.currentPage = 1
-    this.loadInstancesInChunks(endpointId, true)
+    this.loadInstancesInChunks(endpointId, chunkSize, true)
   }
 
   @action cancelIntancesChunksLoading() {
     ApiCaller.cancelRequests(`${this.lastEndpointId}-chunk`)
     this.lastEndpointId = ''
+    this.searchNotFound = false
+    this.searchText = ''
+    this.currentPage = 1
   }
 
   @action setPage(page: number) {
     this.currentPage = page
   }
 
+  @action updateChunkSize(chunkSize: number) {
+    this.currentPage = 1
+    this.chunkSize = chunkSize
+  }
+
   @action loadInstancesDetails(endpointId: string, instancesInfo: Instance[], useLocalStorage?: boolean, quietError?: boolean): Promise<void> {
     // Use reqId to be able to uniquely identify the request so all but the latest request can be igonred and canceled
     this.reqId = !this.reqId ? 1 : this.reqId + 1