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

Merge pull request #645 from smiclea/storage

Improve Wizard Storage loading UX
Nashwan Azhari 4 лет назад
Родитель
Сommit
58f87b3d06

+ 1 - 0
.eslintrc

@@ -18,6 +18,7 @@
     "src/**/package.json"
     "src/**/package.json"
   ],
   ],
   "rules": {
   "rules": {
+    "react/sort-comp": "off",
     "react/jsx-one-expression-per-line": "off",
     "react/jsx-one-expression-per-line": "off",
     "@typescript-eslint/semi": [
     "@typescript-eslint/semi": [
       2,
       2,

+ 2 - 1
.vscode/settings.json

@@ -16,5 +16,6 @@
     "source.fixAll.eslint": true
     "source.fixAll.eslint": true
   },
   },
   "files.eol": "\n",
   "files.eol": "\n",
-  "debug.javascript.autoAttachFilter": "always"
+  "debug.javascript.autoAttachFilter": "always",
+  "typescript.tsdk": "node_modules\\typescript\\lib"
 }
 }

+ 1 - 3
src/components/organisms/EditReplica/EditReplica.tsx

@@ -674,12 +674,10 @@ class EditReplica extends React.Component<Props, State> {
     if (this.props.instancesDetailsLoading) {
     if (this.props.instancesDetailsLoading) {
       return this.renderLoading('Loading instances details ...')
       return this.renderLoading('Loading instances details ...')
     }
     }
-    if (endpointStore.storageLoading) {
-      return this.renderLoading('Loading storage ...')
-    }
 
 
     return (
     return (
       <WizardStorage
       <WizardStorage
+        loading={endpointStore.storageLoading}
         defaultStorage={this.getDefaultStorage()}
         defaultStorage={this.getDefaultStorage()}
         onDefaultStorageChange={(value, busType) => { this.setState({ defaultStorage: { value, busType } }) }}
         onDefaultStorageChange={(value, busType) => { this.setState({ defaultStorage: { value, busType } }) }}
         defaultStorageLayout="modal"
         defaultStorageLayout="modal"

+ 3 - 0
src/components/organisms/WizardPageContent/WizardPageContent.tsx

@@ -177,6 +177,7 @@ type Props = {
   wizardData: WizardData,
   wizardData: WizardData,
   schedules: ScheduleType[],
   schedules: ScheduleType[],
   storageMap: StorageMap[],
   storageMap: StorageMap[],
+  onStorageReloadClick: () => void,
   defaultStorage: { value: string | null, busType?: string | null } | undefined,
   defaultStorage: { value: string | null, busType?: string | null } | undefined,
   hasStorageMap: boolean,
   hasStorageMap: boolean,
   hasSourceOptions: boolean,
   hasSourceOptions: boolean,
@@ -514,6 +515,8 @@ class WizardPageContent extends React.Component<Props, State> {
       case 'storage':
       case 'storage':
         body = (
         body = (
           <WizardStorage
           <WizardStorage
+            loading={endpointStore.storageLoading}
+            onReloadClick={this.props.onStorageReloadClick}
             storageBackends={this.props.endpointStore.storageBackends}
             storageBackends={this.props.endpointStore.storageBackends}
             instancesDetails={this.props.instanceStore.instancesDetails}
             instancesDetails={this.props.instanceStore.instancesDetails}
             storageMap={this.props.storageMap}
             storageMap={this.props.storageMap}

+ 36 - 74
src/components/organisms/WizardStorage/WizardStorage.tsx

@@ -29,6 +29,8 @@ import backendImage from './images/backend.svg'
 import diskImage from './images/disk.svg'
 import diskImage from './images/disk.svg'
 import bigStorageImage from './images/storage-big.svg'
 import bigStorageImage from './images/storage-big.svg'
 import arrowImage from './images/arrow.svg'
 import arrowImage from './images/arrow.svg'
+import StatusImage from '../../atoms/StatusImage/StatusImage'
+import Button from '../../atoms/Button/Button'
 
 
 const Wrapper = styled.div<any>`
 const Wrapper = styled.div<any>`
   width: 100%;
   width: 100%;
@@ -129,15 +131,26 @@ const NoStorageTitle = styled.div<any>`
   margin-bottom: 10px;
   margin-bottom: 10px;
   font-size: 18px;
   font-size: 18px;
 `
 `
-const NoStorageSubtitle = styled.div<any>`
+const NoStorageSubtitle = styled.div`
   color: ${Palette.grayscale[4]};
   color: ${Palette.grayscale[4]};
   text-align: center;
   text-align: center;
+  margin-bottom: 42px;
 `
 `
 const DiskDisabledMessage = styled.div<any>`
 const DiskDisabledMessage = styled.div<any>`
   width: 224px;
   width: 224px;
   text-align: center;
   text-align: center;
   color: gray;
   color: gray;
 `
 `
+const LoadingWrapper = styled.div`
+  margin-top: 32px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+`
+const LoadingText = styled.div`
+  margin-top: 38px;
+  font-size: 18px;
+`
 
 
 export const getDisks = (instancesDetails: Instance[], type: 'backend' | 'disk', storageMap?: StorageMap[] | null): Disk[] => {
 export const getDisks = (instancesDetails: Instance[], type: 'backend' | 'disk', storageMap?: StorageMap[] | null): Disk[] => {
   const fieldName = type === 'backend' ? 'storage_backend_identifier' : 'id'
   const fieldName = type === 'backend' ? 'storage_backend_identifier' : 'id'
@@ -164,6 +177,8 @@ export const TEST_ID = 'wizardStorage'
 
 
 export type Props = {
 export type Props = {
   storageBackends: StorageBackend[],
   storageBackends: StorageBackend[],
+  loading: boolean,
+  onReloadClick?: () => void
   instancesDetails: Instance[],
   instancesDetails: Instance[],
   storageMap: StorageMap[] | null | undefined,
   storageMap: StorageMap[] | null | undefined,
   defaultStorageLayout: 'modal' | 'page',
   defaultStorageLayout: 'modal' | 'page',
@@ -178,12 +193,15 @@ export type Props = {
 class WizardStorage extends React.Component<Props> {
 class WizardStorage extends React.Component<Props> {
   renderNoStorage() {
   renderNoStorage() {
     return (
     return (
-      <NoStorageMessage data-test-id={`${TEST_ID}-noStorage`}>
+      <NoStorageMessage>
         <BigStorageImage />
         <BigStorageImage />
         <NoStorageTitle>No storage backends were found</NoStorageTitle>
         <NoStorageTitle>No storage backends were found</NoStorageTitle>
         <NoStorageSubtitle>
         <NoStorageSubtitle>
           We could not find any storage backends. Coriolis will skip this step.
           We could not find any storage backends. Coriolis will skip this step.
         </NoStorageSubtitle>
         </NoStorageSubtitle>
+        {this.props.onReloadClick ? (
+          <Button hollow onClick={this.props.onReloadClick}>Try again</Button>
+        ) : null}
       </NoStorageMessage>
       </NoStorageMessage>
     )
     )
   }
   }
@@ -197,39 +215,6 @@ class WizardStorage extends React.Component<Props> {
     )
     )
   }
   }
 
 
-  // renderBusTypeDropdown(selectedStorageMap: StorageMap | null | undefined) {
-  //   if (!selectedStorageMap) {
-  //     return null
-  //   }
-  //   type DropdownItem = {label: string, value: string | null}
-  //   const storageBusTypes: DropdownItem[] | undefined = this.props.storageBackends
-  //     .find(s => s.id === selectedStorageMap.target.id)?.additional_provider_properties?.supported_bus_types?.map(value => ({
-  //       label: value,
-  //       value,
-  //     }))
-  //   if (!storageBusTypes || !storageBusTypes.length) {
-  //     return null
-  //   }
-  //   storageBusTypes.unshift({
-  //     label: 'Choose a Bus Type',
-  //     value: null,
-  //   })
-  //   const selectedBusType = selectedStorageMap?.targetBusType
-
-  //   return (
-  //     <Dropdown
-  //       width={StyleProps.inputSizes.large.width}
-  //       noSelectionMessage="Choose a Bus Type"
-  //       centered
-  //       items={storageBusTypes}
-  //       selectedItem={selectedBusType}
-  //       onChange={(item: DropdownItem) => {
-  //         this.props.onChange({ ...selectedStorageMap, targetBusType: item.value })
-  //       }}
-  //     />
-  //   )
-  // }
-
   renderStorageDropdown(
   renderStorageDropdown(
     storageItems: Array<StorageBackend>,
     storageItems: Array<StorageBackend>,
     selectedItem: StorageBackend | null | undefined,
     selectedItem: StorageBackend | null | undefined,
@@ -396,40 +381,6 @@ class WizardStorage extends React.Component<Props> {
         )
         )
     }
     }
 
 
-    // const renderDefaultBusTypeDropdown = () => {
-    //   if (!this.props.defaultStorage || !this.props.defaultStorage.value) {
-    //     return null
-    //   }
-
-    //   type DropdownItem = { label: string, value: string | null }
-    //   const storageBusTypes: DropdownItem[] | undefined = this.props.storageBackends
-    //     .find(s => s.id === this.props.defaultStorage?.value)?.additional_provider_properties?.supported_bus_types?.map(value => ({
-    //       label: value,
-    //       value,
-    //     }))
-    //   if (!storageBusTypes || !storageBusTypes.length) {
-    //     return null
-    //   }
-    //   storageBusTypes.unshift({
-    //     label: 'Choose a Bus Type',
-    //     value: null,
-    //   })
-    //   const selectedBusType = this.props.defaultStorage.busType
-
-    //   return (
-    //     <Dropdown
-    //       width={StyleProps.inputSizes.regular.width}
-    //       noSelectionMessage="Choose a Bus Type"
-    //       centered
-    //       items={storageBusTypes}
-    //       selectedItem={selectedBusType}
-    //       onChange={(item: DropdownItem) => {
-    //         this.props.onDefaultStorageChange(this.props.defaultStorage?.value || null, item.value)
-    //       }}
-    //     />
-    //   )
-    // }
-
     return (
     return (
       <StorageWrapper>
       <StorageWrapper>
         <StorageSection>
         <StorageSection>
@@ -457,14 +408,25 @@ class WizardStorage extends React.Component<Props> {
     )
     )
   }
   }
 
 
+  renderLoading() {
+    return (
+      <LoadingWrapper>
+        <StatusImage loading />
+        <LoadingText>Loading storage...</LoadingText>
+      </LoadingWrapper>
+    )
+  }
+
   render() {
   render() {
     return (
     return (
       <Wrapper style={this.props.style} ref={this.props.onScrollableRef}>
       <Wrapper style={this.props.style} ref={this.props.onScrollableRef}>
-        <Mapping>
-          {this.renderDefaultStorage()}
-          {this.renderBackendMapping()}
-          {this.renderDiskMapping()}
-        </Mapping>
+        {this.props.loading ? this.renderLoading() : (
+          <Mapping>
+            {this.renderDefaultStorage()}
+            {this.renderBackendMapping()}
+            {this.renderDiskMapping()}
+          </Mapping>
+        )}
       </Wrapper>
       </Wrapper>
     )
     )
   }
   }

+ 2 - 0
src/components/organisms/WizardStorage/story.tsx

@@ -64,6 +64,7 @@ const storageBackends: any = [
 storiesOf('WizardStorage', module)
 storiesOf('WizardStorage', module)
   .add('page', () => (
   .add('page', () => (
     <WizardStorage
     <WizardStorage
+      loading={false}
       storageBackends={storageBackends}
       storageBackends={storageBackends}
       instancesDetails={instancesDetails}
       instancesDetails={instancesDetails}
       storageMap={null}
       storageMap={null}
@@ -75,6 +76,7 @@ storiesOf('WizardStorage', module)
   ))
   ))
   .add('modal', () => (
   .add('modal', () => (
     <WizardStorage
     <WizardStorage
+      loading={false}
       storageBackends={storageBackends}
       storageBackends={storageBackends}
       instancesDetails={instancesDetails}
       instancesDetails={instancesDetails}
       storageMap={null}
       storageMap={null}

+ 5 - 0
src/components/pages/WizardPage/WizardPage.tsx

@@ -211,6 +211,10 @@ class WizardPage extends React.Component<Props, State> {
     this.props.history.replace(`/wizard/${type}`)
     this.props.history.replace(`/wizard/${type}`)
   }
   }
 
 
+  handleStorageReloadClick() {
+    endpointStore.loadStorage(wizardStore.data.target!.id, wizardStore.data.destOptions)
+  }
+
   handleBackClick() {
   handleBackClick() {
     const currentPageIndex = this.pages.findIndex(p => p.id === wizardStore.currentPage.id)
     const currentPageIndex = this.pages.findIndex(p => p.id === wizardStore.currentPage.id)
 
 
@@ -690,6 +694,7 @@ class WizardPage extends React.Component<Props, State> {
               hasSourceOptions={Boolean(this.pages.find(p => p.id === 'source-options'))}
               hasSourceOptions={Boolean(this.pages.find(p => p.id === 'source-options'))}
               defaultStorage={wizardStore.defaultStorage}
               defaultStorage={wizardStore.defaultStorage}
               storageMap={wizardStore.storageMap}
               storageMap={wizardStore.storageMap}
+              onStorageReloadClick={() => { this.handleStorageReloadClick() }}
               schedules={wizardStore.schedules}
               schedules={wizardStore.schedules}
               nextButtonDisabled={this.isNextButtonDisabled()}
               nextButtonDisabled={this.isNextButtonDisabled()}
               showLoadingButton={this.shouldShowLoadingButton()}
               showLoadingButton={this.shouldShowLoadingButton()}

+ 9 - 11
src/stores/EndpointStore.ts

@@ -17,7 +17,7 @@ import JSZip from 'jszip'
 import { saveAs } from 'file-saver'
 import { saveAs } from 'file-saver'
 
 
 import type {
 import type {
-  Endpoint, Validation, StorageBackend, Storage, MultiValidationItem,
+  Endpoint, Validation, StorageBackend, MultiValidationItem,
 } from '../@types/Endpoint'
 } from '../@types/Endpoint'
 
 
 import notificationStore from './NotificationStore'
 import notificationStore from './NotificationStore'
@@ -324,18 +324,16 @@ class EndpointStore {
 
 
     try {
     try {
       const storage = await EndpointSource.loadStorage(endpointId, data, options)
       const storage = await EndpointSource.loadStorage(endpointId, data, options)
-      this.loadStorageSuccess(storage)
-    } catch (ex) {
-      runInAction(() => { this.storageLoading = false })
-      throw ex
+      runInAction(() => {
+        this.storageBackends = storage.storage_backends
+        this.storageConfigDefault = storage.config_default || ''
+      })
+    } finally {
+      runInAction(() => {
+        this.storageLoading = false
+      })
     }
     }
   }
   }
-
-  @action loadStorageSuccess(storage: Storage) {
-    this.storageBackends = storage.storage_backends
-    this.storageConfigDefault = storage.config_default || ''
-    this.storageLoading = false
-  }
 }
 }
 
 
 export default new EndpointStore()
 export default new EndpointStore()