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

Merge pull request #630 from smiclea/transfer-details-loading

Improve transfer items details page loading flow CORWEB-251
Nashwan Azhari 5 лет назад
Родитель
Сommit
ce5c4bc1d3

+ 67 - 56
src/components/pages/MigrationDetailsPage/MigrationDetailsPage.tsx

@@ -56,6 +56,7 @@ type State = {
   showEditModal: boolean,
   showFromReplicaModal: boolean,
   pausePolling: boolean,
+  initialLoading: boolean
 }
 @observer
 class MigrationDetailsPage extends React.Component<Props, State> {
@@ -66,6 +67,7 @@ class MigrationDetailsPage extends React.Component<Props, State> {
     showEditModal: false,
     showFromReplicaModal: false,
     pausePolling: false,
+    initialLoading: true,
   }
 
   stopPolling: boolean | null = null
@@ -74,53 +76,60 @@ class MigrationDetailsPage extends React.Component<Props, State> {
     document.title = 'Migration Details'
 
     const loadMigration = async () => {
-      await Promise.all([
-        endpointStore.getEndpoints({ showLoading: true }),
-        this.loadMigrationWithInstances(this.props.match.params.id, true),
-      ])
-      const details = migrationStore.migrationDetails
-      if (!details) {
-        return
-      }
-      const sourceEndpoint = endpointStore.endpoints.find(e => e.id === details.origin_endpoint_id)
-      const destinationEndpoint = endpointStore.endpoints
-        .find(e => e.id === details.destination_endpoint_id)
-      if (!sourceEndpoint || !destinationEndpoint) {
-        return
-      }
-      const loadOptions = async (optionsType: 'source' | 'destination') => {
-        const providerName = optionsType === 'source' ? sourceEndpoint.type : destinationEndpoint.type
-        // This allows the values to be displayed with their allocated names instead of their IDs
-        await providerStore.loadOptionsSchema({
-          providerName,
-          optionsType,
-          useCache: true,
-          quietError: true,
-        })
-        const getOptionsValuesConfig = {
-          optionsType,
-          endpointId: optionsType === 'source' ? details.origin_endpoint_id : details.destination_endpoint_id,
-          providerName,
-          useCache: true,
-          quietError: true,
-          allowMultiple: true,
-        }
-        // For some providers, the API doesn't return the required fields values
-        // if those required fields are sent in env data,
-        // so to retrieve those values a request without env data must be made
-        await providerStore.getOptionsValues(getOptionsValuesConfig)
-        await providerStore.getOptionsValues({
-          ...getOptionsValuesConfig,
-          envData: optionsType === 'source' ? details.source_environment : details.destination_environment,
-        })
-      }
-
-      loadOptions('source')
-      loadOptions('destination')
+      await endpointStore.getEndpoints({ showLoading: true })
+      this.setState({ initialLoading: false })
+      await this.loadMigrationWithInstances({
+        migrationId: this.props.match.params.id,
+        cache: true,
+        onDetailsLoaded: async () => {
+          const details = migrationStore.migrationDetails
+          if (!details) {
+            return
+          }
+          const sourceEndpoint = endpointStore.endpoints.find(e => e.id === details.origin_endpoint_id)
+          const destinationEndpoint = endpointStore.endpoints.find(e => e.id === details.destination_endpoint_id)
+          if (!sourceEndpoint || !destinationEndpoint) {
+            return
+          }
+          const loadOptions = async (optionsType: 'source' | 'destination') => {
+            const providerName = optionsType === 'source' ? sourceEndpoint.type : destinationEndpoint.type
+            // This allows the values to be displayed with their allocated names instead of their IDs
+            await providerStore.loadOptionsSchema({
+              providerName,
+              optionsType,
+              useCache: true,
+              quietError: true,
+            })
+            const getOptionsValuesConfig = {
+              optionsType,
+              endpointId: optionsType === 'source' ? details.origin_endpoint_id : details.destination_endpoint_id,
+              providerName,
+              useCache: true,
+              quietError: true,
+              allowMultiple: true,
+            }
+            // For some providers, the API doesn't return the required fields values
+            // if those required fields are sent in env data,
+            // so to retrieve those values a request without env data must be made
+            await providerStore.getOptionsValues(getOptionsValuesConfig)
+            await providerStore.getOptionsValues({
+              ...getOptionsValuesConfig,
+              envData: optionsType === 'source' ? details.source_environment : details.destination_environment,
+            })
+          }
+
+          await Promise.all([
+            loadOptions('source'),
+            loadOptions('destination'),
+          ])
+        },
+      })
     }
-    loadMigration()
-
-    this.pollData()
+    const loadMigrationAndPollData = async () => {
+      await loadMigration()
+      this.pollData()
+    }
+    loadMigrationAndPollData()
   }
 
   UNSAFE_componentWillReceiveProps(newProps: any) {
@@ -129,10 +138,11 @@ class MigrationDetailsPage extends React.Component<Props, State> {
     }
 
     endpointStore.getEndpoints()
-    this.loadMigrationWithInstances(newProps.match.params.id, true)
+    this.loadMigrationWithInstances({ migrationId: newProps.match.params.id, cache: true })
   }
 
   componentWillUnmount() {
+    migrationStore.cancelMigrationDetails()
     migrationStore.clearDetails()
     this.stopPolling = true
   }
@@ -141,21 +151,22 @@ class MigrationDetailsPage extends React.Component<Props, State> {
     return migrationStore.migrationDetails?.last_execution_status
   }
 
-  async loadMigrationWithInstances(migrationId: string, cache: boolean) {
-    await migrationStore.getMigration(migrationId, { showLoading: true })
+  async loadMigrationWithInstances(options: { migrationId: string, cache: boolean, onDetailsLoaded?: () => void }) {
+    await migrationStore.getMigration(options.migrationId, { showLoading: true })
     const details = migrationStore.migrationDetails
     if (!details) {
       return
     }
-
+    if (options.onDetailsLoaded) {
+      options.onDetailsLoaded()
+    }
     if (details.origin_minion_pool_id || details.destination_minion_pool_id) {
       minionPoolStore.loadMinionPools()
     }
 
     await providerStore.loadProviders()
 
-    const targetEndpoint = endpointStore.endpoints
-      .find(e => e.id === details.destination_endpoint_id)
+    const targetEndpoint = endpointStore.endpoints.find(e => e.id === details.destination_endpoint_id)
     const hasStorageMap = targetEndpoint ? (providerStore.providers && providerStore.providers[targetEndpoint.type]
       ? !!providerStore.providers[targetEndpoint.type]
         .types.find(t => t === providerTypes.STORAGE)
@@ -166,13 +177,13 @@ class MigrationDetailsPage extends React.Component<Props, State> {
 
     networkStore.loadNetworks(details.destination_endpoint_id, details.destination_environment, {
       quietError: true,
-      cache,
+      cache: options.cache,
     })
 
     instanceStore.loadInstancesDetails({
       endpointId: details.origin_endpoint_id,
       instances: details.instances.map(n => ({ id: n })),
-      cache,
+      cache: options.cache,
       quietError: false,
       env: details.source_environment,
       targetProvider: targetEndpoint?.type,
@@ -292,7 +303,7 @@ class MigrationDetailsPage extends React.Component<Props, State> {
   }
 
   handleEditReplicaReload() {
-    this.loadMigrationWithInstances(this.props.match.params.id, false)
+    this.loadMigrationWithInstances({ migrationId: this.props.match.params.id, cache: false })
   }
 
   handleUpdateComplete(redirectTo: string) {
@@ -396,7 +407,7 @@ class MigrationDetailsPage extends React.Component<Props, State> {
               page={this.props.match.params.page || ''}
               minionPools={minionPoolStore.minionPools}
               detailsLoading={migrationStore.detailsLoading || endpointStore.loading
-                || minionPoolStore.loadingMinionPools}
+                || minionPoolStore.loadingMinionPools || this.state.initialLoading}
               onDeleteMigrationClick={() => { this.handleDeleteMigrationClick() }}
             />
 )}

+ 74 - 54
src/components/pages/ReplicaDetailsPage/ReplicaDetailsPage.tsx

@@ -70,6 +70,7 @@ type State = {
   showCancelConfirmation: boolean,
   isEditable: boolean,
   pausePolling: boolean,
+  initialLoading: boolean,
 }
 @observer
 class ReplicaDetailsPage extends React.Component<Props, State> {
@@ -85,6 +86,7 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
     showForceCancelConfirmation: false,
     isEditable: false,
     pausePolling: false,
+    initialLoading: true,
   }
 
   stopPolling: boolean | null = null
@@ -94,60 +96,71 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
 
     const loadReplica = async () => {
       await endpointStore.getEndpoints({ showLoading: true })
-      const replica = await this.loadReplicaWithInstances(true)
-      if (!replica) {
-        return
-      }
-      const sourceEndpoint = endpointStore.endpoints.find(e => e.id === replica.origin_endpoint_id)
-      const destinationEndpoint = endpointStore.endpoints
-        .find(e => e.id === replica.destination_endpoint_id)
-      if (!sourceEndpoint || !destinationEndpoint) {
-        return
-      }
-      const loadOptions = async (optionsType: 'source' | 'destination') => {
-        const providerName = optionsType === 'source' ? sourceEndpoint.type : destinationEndpoint.type
-        // This allows the values to be displayed with their allocated names instead of their IDs
-        await providerStore.loadOptionsSchema({
-          providerName,
-          optionsType,
-          useCache: true,
-          quietError: true,
-        })
-        const getOptionsValuesConfig = {
-          optionsType,
-          endpointId: optionsType === 'source' ? replica.origin_endpoint_id : replica.destination_endpoint_id,
-          providerName,
-          useCache: true,
-          quietError: true,
-          allowMultiple: true,
-        }
-        // For some providers, the API doesn't return the required fields values
-        // if those required fields are sent in env data,
-        // so to retrieve those values a request without env data must be made
-        await providerStore.getOptionsValues(getOptionsValuesConfig)
-        await providerStore.getOptionsValues({
-          ...getOptionsValuesConfig,
-          envData: optionsType === 'source' ? replica.source_environment : replica.destination_environment,
-        })
-      }
-
-      loadOptions('source')
-      loadOptions('destination')
+      this.setState({ initialLoading: false })
+      this.loadReplicaWithInstances({
+        cache: true,
+        showLoading: true,
+        onDetailsLoaded: async () => {
+          if (!this.replica) {
+            return
+          }
+          const sourceEndpoint = endpointStore.endpoints.find(e => e.id === this.replica!.origin_endpoint_id)
+          const destinationEndpoint = endpointStore.endpoints.find(e => e.id === this.replica!.destination_endpoint_id)
+          if (!sourceEndpoint || !destinationEndpoint) {
+            return
+          }
+          const loadOptions = async (optionsType: 'source' | 'destination') => {
+            const providerName = optionsType === 'source' ? sourceEndpoint.type : destinationEndpoint.type
+            // This allows the values to be displayed with their allocated names instead of their IDs
+            await providerStore.loadOptionsSchema({
+              providerName,
+              optionsType,
+              useCache: true,
+              quietError: true,
+            })
+            const getOptionsValuesConfig = {
+              optionsType,
+              endpointId: optionsType === 'source' ? this.replica!.origin_endpoint_id : this.replica!.destination_endpoint_id,
+              providerName,
+              useCache: true,
+              quietError: true,
+              allowMultiple: true,
+            }
+            // For some providers, the API doesn't return the required fields values
+            // if those required fields are sent in env data,
+            // so to retrieve those values a request without env data must be made
+            await providerStore.getOptionsValues(getOptionsValuesConfig)
+            await providerStore.getOptionsValues({
+              ...getOptionsValuesConfig,
+              envData: optionsType === 'source' ? this.replica!.source_environment : this.replica!.destination_environment,
+            })
+          }
+
+          await Promise.all([
+            loadOptions('source'),
+            loadOptions('destination'),
+          ])
+        },
+      })
     }
-    loadReplica()
 
+    const loadReplicaAndPollData = async () => {
+      await loadReplica()
+      this.pollData()
+    }
+    loadReplicaAndPollData()
     scheduleStore.getSchedules(this.replicaId)
-    this.pollData(true)
   }
 
   UNSAFE_componentWillReceiveProps(newProps: Props) {
     if (newProps.match.params.id !== this.props.match.params.id) {
-      this.loadReplicaWithInstances(true, newProps.match.params.id)
+      this.loadReplicaWithInstances({ cache: true, replicaId: newProps.match.params.id })
       scheduleStore.getSchedules(newProps.match.params.id)
     }
   }
 
   componentWillUnmount() {
+    replicaStore.cancelReplicaDetails()
     replicaStore.clearDetails()
     scheduleStore.clearUnsavedSchedules()
     this.stopPolling = true
@@ -196,12 +209,20 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
     this.setState({ isEditable })
   }
 
-  async loadReplicaWithInstances(cache: boolean, replicaId?: string) {
-    await replicaStore.getReplicaDetails({ replicaId: replicaId || this.replicaId })
+  async loadReplicaWithInstances(options: {
+    cache: boolean,
+    replicaId?: string,
+    showLoading?: boolean,
+    onDetailsLoaded?: () => void,
+  }) {
+    await replicaStore.getReplicaDetails({ replicaId: options.replicaId || this.replicaId, showLoading: options.showLoading })
     const replica = this.replica
     if (!replica) {
       return null
     }
+    if (options.onDetailsLoaded) {
+      options.onDetailsLoaded()
+    }
     minionPoolStore.loadMinionPools()
 
     await providerStore.loadProviders()
@@ -210,11 +231,10 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
 
     networkStore.loadNetworks(replica.destination_endpoint_id, replica.destination_environment, {
       quietError: true,
-      cache,
+      cache: options.cache,
     })
 
-    const targetEndpoint = endpointStore.endpoints
-      .find(e => e.id === replica.destination_endpoint_id)
+    const targetEndpoint = endpointStore.endpoints.find(e => e.id === replica.destination_endpoint_id)
 
     const hasStorageMap = targetEndpoint ? (providerStore.providers && providerStore.providers[targetEndpoint.type]
       ? !!providerStore.providers[targetEndpoint.type]
@@ -227,7 +247,7 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
     instanceStore.loadInstancesDetails({
       endpointId: replica.origin_endpoint_id,
       instances: replica.instances.map(n => ({ id: n })),
-      cache,
+      cache: options.cache,
       quietError: false,
       env: replica.source_environment,
       targetProvider: targetEndpoint?.type,
@@ -448,14 +468,14 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
     this.props.history.push(`/replicas/${replica.id}/executions`)
   }
 
-  async pollData(showLoading: boolean) {
+  async pollData() {
     if (this.state.pausePolling || this.stopPolling) {
       return
     }
 
     await Promise.all([
       replicaStore.getReplicaDetails({
-        replicaId: this.replicaId, showLoading, polling: true,
+        replicaId: this.replicaId, polling: true,
       }),
       (async () => {
         if (window.location.pathname.indexOf('executions') > -1) {
@@ -464,17 +484,17 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
       })(),
     ])
 
-    setTimeout(() => { this.pollData(false) }, configLoader.config.requestPollTimeout)
+    setTimeout(() => { this.pollData() }, configLoader.config.requestPollTimeout)
   }
 
   closeEditModal() {
     this.setState({ showEditModal: false, pausePolling: false }, () => {
-      this.pollData(false)
+      this.pollData()
     })
   }
 
   handleEditReplicaReload() {
-    this.loadReplicaWithInstances(false)
+    this.loadReplicaWithInstances({ cache: false })
   }
 
   handleUpdateComplete(redirectTo: string) {
@@ -596,7 +616,7 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
               networks={networkStore.networks}
               minionPools={minionPoolStore.minionPools}
               detailsLoading={replicaStore.replicaDetailsLoading || endpointStore.loading
-                || minionPoolStore.loadingMinionPools}
+                || minionPoolStore.loadingMinionPools || this.state.initialLoading}
               sourceSchema={providerStore.sourceSchema}
               sourceSchemaLoading={providerStore.sourceSchemaLoading
               || providerStore.sourceOptionsPrimaryLoading

+ 1 - 0
src/sources/MigrationSource.ts

@@ -70,6 +70,7 @@ class MigrationSource {
     const response = await Api.send({
       url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/migrations/${migrationId}`,
       skipLog,
+      cancelId: migrationId,
     })
     const migration = response.data.migration
     sortTasks(migration.tasks, MigrationSourceUtils.sortTaskUpdates)

+ 1 - 0
src/sources/ReplicaSource.ts

@@ -115,6 +115,7 @@ class ReplicaSource {
     const response = await Api.send({
       url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas/${replicaId}`,
       skipLog: polling,
+      cancelId: replicaId,
     })
     const replica: ReplicaItemDetails = response.data.replica
     replica.executions = ReplicaSourceUtils.filterDeletedExecutions(replica.executions)

+ 4 - 1
src/stores/EndpointStore.ts

@@ -63,7 +63,10 @@ class EndpointStore {
   @observable multiValidation: MultiValidationItem[] = []
 
   @action async getEndpoints(options?: { showLoading?: boolean, skipLog?: boolean }) {
-    if (options && options.showLoading) {
+    if (this.loading) {
+      return
+    }
+    if (options?.showLoading) {
       this.loading = true
     }
     try {

+ 2 - 4
src/stores/InstanceStore.ts

@@ -339,9 +339,7 @@ class InstanceStore {
     this.reqId = !this.reqId ? 1 : this.reqId + 1
     InstanceSource.cancelInstancesDetailsRequests(this.reqId - 1)
 
-    instances
-      .sort((a, b) => (a.instance_name || a.name || a.id)
-        .localeCompare(b.instance_name || b.name || b.id))
+    instances.sort((a, b) => (a.instance_name || a.name || a.id).localeCompare(b.instance_name || b.name || b.id))
 
     const count = instances.length
     if (count === 0) {
@@ -352,7 +350,7 @@ class InstanceStore {
     this.instancesDetailsCount = count
     this.instancesDetailsRemaining = count
 
-    await new Promise(resolve => {
+    await new Promise<void>(resolve => {
       Promise.all(instances.map(async instanceInfo => {
         try {
           const resp = await InstanceSource.loadInstanceDetails({

+ 8 - 0
src/stores/MigrationStore.ts

@@ -21,6 +21,7 @@ import type { Field } from '../@types/Field'
 import type { Endpoint } from '../@types/Endpoint'
 import type { InstanceScript } from '../@types/Instance'
 import MigrationSource from '../sources/MigrationSource'
+import apiCaller from '../utils/ApiCaller'
 
 class MigrationStore {
   @observable migrations: MigrationItem[] = []
@@ -147,6 +148,13 @@ class MigrationStore {
     return migration
   }
 
+  @action cancelMigrationDetails() {
+    if (this.migrationDetails) {
+      apiCaller.cancelRequests(this.migrationDetails.id)
+    }
+    this.detailsLoading = false
+  }
+
   @action clearDetails() {
     this.detailsLoading = true
     this.migrationDetails = null

+ 2 - 3
src/stores/ProviderStore.ts

@@ -187,7 +187,7 @@ class ProviderStore {
   }
 
   @action async loadProviders(): Promise<void> {
-    if (this.providers) {
+    if (this.providers || this.providersLoading) {
       return
     }
     this.providersLoading = true
@@ -233,8 +233,7 @@ class ProviderStore {
     }
 
     try {
-      const fields: Field[] = await ProviderSource
-        .loadOptionsSchema(providerName, optionsType, useCache, quietError)
+      const fields: Field[] = await ProviderSource.loadOptionsSchema(providerName, optionsType, useCache, quietError)
       this.loadOptionsSchemaSuccess(fields, optionsType, isValid())
       return fields
     } finally {

+ 8 - 0
src/stores/ReplicaStore.ts

@@ -22,6 +22,7 @@ import type {
 import type { Execution, ExecutionTasks } from '../@types/Execution'
 import type { Endpoint } from '../@types/Endpoint'
 import type { Field } from '../@types/Field'
+import apiCaller from '../utils/ApiCaller'
 
 class ReplicaStoreUtils {
   static getNewReplica(
@@ -85,6 +86,13 @@ class ReplicaStore {
     }
   }
 
+  @action cancelReplicaDetails() {
+    if (this.replicaDetails?.id) {
+      apiCaller.cancelRequests(this.replicaDetails?.id)
+    }
+    this.replicaDetailsLoading = false
+  }
+
   @action async getReplicaDetails(options: {
     replicaId: string, showLoading?: boolean, polling?: boolean,
   }) {