Bläddra i källkod

Merge pull request #543 from smiclea/empty-tasks

Refactor replica tasks loading and polling
Nashwan Azhari 6 år sedan
förälder
incheckning
4adc7e1b13

+ 4 - 9
src/components/atoms/StatusIcon/StatusIcon.jsx

@@ -22,7 +22,6 @@ import Palette from '../../styleUtils/Palette'
 import StyleProps from '../../styleUtils/StyleProps'
 
 import errorImage from './images/error.svg'
-import progressWithBackgroundImage from './images/progress-background.svg'
 import progressImage from './images/progress.js'
 import successImage from './images/success.svg'
 import warningImage from './images/warning.js'
@@ -37,17 +36,13 @@ type Props = {
   secondary?: boolean,
 }
 
-const getSpinnerUrl = (smallCircleColor: string) => {
-  return css`url('data:image/svg+xml;utf8,${encodeURIComponent(progressImage(Palette.grayscale[3], smallCircleColor))}')`
+const getSpinnerUrl = (smallCircleColor: string, useWhiteBackground: ?boolean) => {
+  return css`url('data:image/svg+xml;utf8,${encodeURIComponent(progressImage(Palette.grayscale[3], smallCircleColor, useWhiteBackground))}')`
 }
 
 const getRunningImageUrl = (props: Props) => {
-  if (props.useBackground) {
-    return css`url('${progressWithBackgroundImage}')`
-  }
-
   const smallCircleColor = props.secondary ? Palette.grayscale[0] : Palette.primary
-  return getSpinnerUrl(smallCircleColor)
+  return getSpinnerUrl(smallCircleColor, props.useBackground)
 }
 
 const getWarningUrl = (background: string) => {
@@ -69,7 +64,7 @@ const statuses = (status, props) => {
     case 'CANCELLING':
     case 'CANCELLING_AFTER_COMPLETION':
       return css`
-        background-image: ${getSpinnerUrl(Palette.warning)};
+        background-image: ${getSpinnerUrl(Palette.warning, props.useBackground)};
         ${StyleProps.animations.rotation}
       `
     case 'SCHEDULED':

+ 0 - 16
src/components/atoms/StatusIcon/images/progress-background.svg

@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
-    <!-- Generator: Sketch 47.1 (45422) - http://www.bohemiancoding.com/sketch -->
-
-    <desc>Created with Sketch.</desc>
-    <defs></defs>
-    <g id="Symbols" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
-        <g id="Icon/Progress/Default">
-            <g id="Group-2">
-                <circle id="Oval-2-Copy" fill="#C8CCD7" cx="8" cy="8" r="8"></circle>
-                <path d="M16,8 C16,3.581722 12.418278,0 8,0 L8,8 L16,8 Z" id="Combined-Shape" fill="#0044CA"></path>
-                <circle id="Oval-2-Copy" fill="#FFFFFF" cx="8" cy="8" r="6"></circle>
-            </g>
-        </g>
-    </g>
-</svg>

+ 4 - 1
src/components/atoms/StatusIcon/images/progress.js

@@ -12,11 +12,14 @@ You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
 
-const image = (bigColor, smallColor) => `
+// @flow
+
+const image = (bigColor: string, smallColor: string, useWhiteBackground: ?boolean) => `
   <svg width="16px" height="16px" viewBox="0 0 16 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
     <g>
       <circle fill="none" stroke="${bigColor}" stroke-width="2"  cx="8" cy="8" r="7"></circle>
       <path d="M 15 8 A 7 7 0 0 0 8 1" fill="none" stroke="${smallColor}" stroke-width="2" />
+      ${useWhiteBackground ? '<circle fill="white" cx="8" cy="8" r="6"></circle>' : ''}
     </g>
   </svg>
 `

+ 6 - 0
src/components/atoms/StatusIcon/story.jsx

@@ -62,3 +62,9 @@ storiesOf('StatusIcon', module)
   .add('error hollow', () => (
     <StatusIcon status="ERROR" hollow />
   ))
+  .add('running white background', () => (
+    <Wrapper>
+      <StatusIcon status="RUNNING" useBackground />
+      <StatusIcon status="CANCELLING" useBackground />
+    </Wrapper>
+  ))

+ 13 - 8
src/components/organisms/PageHeader/PageHeader.jsx

@@ -172,7 +172,7 @@ class PageHeader extends React.Component<Props, State> {
     if (this.props.onModalClose) {
       this.props.onModalClose()
     }
-    this.setState({ showChooseProviderModal: false })
+    this.setState({ showChooseProviderModal: false }, () => { this.pollData() })
   }
 
   handleProviderClick(providerType: string) {
@@ -213,17 +213,22 @@ class PageHeader extends React.Component<Props, State> {
     if (this.props.onModalClose) {
       this.props.onModalClose()
     }
-    this.setState({ showEndpointModal: false })
+    this.setState({ showEndpointModal: false }, () => { this.pollData() })
   }
 
   handleBackEndpointModal(options?: { autoClose?: boolean }) {
-    this.setState({ showChooseProviderModal: !options || !options.autoClose, showEndpointModal: false })
+    let showChooseProviderModal = !options || !options.autoClose
+    this.setState({ showChooseProviderModal, showEndpointModal: false }, () => {
+      if (!showChooseProviderModal) {
+        this.pollData()
+      }
+    })
   }
 
   async handleProjectChange(project: Project) {
     await userStore.switchProject(project.id)
     projectStore.getProjects()
-    notificationStore.loadData()
+    notificationStore.loadData(true)
 
     if (this.props.onProjectChange) {
       this.props.onProjectChange(project)
@@ -234,7 +239,7 @@ class PageHeader extends React.Component<Props, State> {
     if (this.props.onModalClose) {
       this.props.onModalClose()
     }
-    this.setState({ showUserModal: false })
+    this.setState({ showUserModal: false }, () => { this.pollData() })
   }
 
   async handleUserUpdateClick(user: User) {
@@ -242,14 +247,14 @@ class PageHeader extends React.Component<Props, State> {
     if (this.props.onModalClose) {
       this.props.onModalClose()
     }
-    this.setState({ showUserModal: false })
+    this.setState({ showUserModal: false }, () => { this.pollData() })
   }
 
   handleProjectModalClose() {
     if (this.props.onModalClose) {
       this.props.onModalClose()
     }
-    this.setState({ showProjectModal: false })
+    this.setState({ showProjectModal: false }, () => { this.pollData() })
   }
 
   async handleProjectModalUpdateClick(project: Project) {
@@ -257,7 +262,7 @@ class PageHeader extends React.Component<Props, State> {
     if (this.props.onModalClose) {
       this.props.onModalClose()
     }
-    this.setState({ showProjectModal: false })
+    this.setState({ showProjectModal: false }, () => { this.pollData() })
   }
 
   async pollData(showLoading?: boolean) {

+ 89 - 57
src/components/pages/ReplicaDetailsPage/ReplicaDetailsPage.jsx

@@ -45,6 +45,7 @@ import instanceStore from '../../../stores/InstanceStore'
 import networkStore from '../../../stores/NetworkStore'
 import notificationStore from '../../../stores/NotificationStore'
 import providerStore from '../../../stores/ProviderStore'
+
 import configLoader from '../../../utils/Config'
 import utils from '../../../utils/ObjectUtils'
 import { providerTypes } from '../../../constants'
@@ -55,7 +56,7 @@ import Palette from '../../styleUtils/Palette'
 const Wrapper = styled.div``
 
 type Props = {
-  match: any,
+  match: { params: { id: string, page: ?string } },
   history: any,
 }
 type State = {
@@ -89,18 +90,29 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
 
   stopPolling: ?boolean
 
+  get replicaId() {
+    if (!this.props.match || !this.props.match.params || !this.props.match.params.id) {
+      throw new Error('Invalid replica id')
+    }
+    return this.props.match.params.id
+  }
+
+  get replica() {
+    let replica = replicaStore.replicas.find(r => r.id === this.replicaId)
+    return replica
+  }
+
   componentWillMount() {
     document.title = 'Replica Details'
 
     let loadReplica = async () => {
       await endpointStore.getEndpoints({ showLoading: true })
-      await this.loadReplicaWithInstances(this.props.match.params.id, true)
-      let details = replicaStore.replicaDetails
-      if (!details) {
+      let replica = await this.loadReplicaWithInstances(this.replicaId, true)
+      if (!replica) {
         return
       }
-      let sourceEndpoint = endpointStore.endpoints.find(e => e.id === details.origin_endpoint_id)
-      let destinationEndpoint = endpointStore.endpoints.find(e => e.id === details.destination_endpoint_id)
+      let sourceEndpoint = endpointStore.endpoints.find(e => e.id === replica.origin_endpoint_id)
+      let destinationEndpoint = endpointStore.endpoints.find(e => e.id === replica.destination_endpoint_id)
       if (!sourceEndpoint || !destinationEndpoint) {
         return
       }
@@ -115,7 +127,7 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
         })
         let getOptionsValuesConfig = {
           optionsType,
-          endpointId: optionsType === 'source' ? details.origin_endpoint_id : details.destination_endpoint_id,
+          endpointId: optionsType === 'source' ? replica.origin_endpoint_id : replica.destination_endpoint_id,
           providerName,
           useCache: true,
           quietError: true,
@@ -127,7 +139,7 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
         await providerStore.getOptionsValues(getOptionsValuesConfig)
         await providerStore.getOptionsValues({
           ...getOptionsValuesConfig,
-          envData: optionsType === 'source' ? details.source_environment : details.destination_environment,
+          envData: optionsType === 'source' ? replica.source_environment : replica.destination_environment,
         })
       }
 
@@ -136,7 +148,7 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
     }
     loadReplica()
 
-    scheduleStore.getSchedules(this.props.match.params.id)
+    scheduleStore.getSchedules(this.replicaId)
     this.pollData(true)
   }
 
@@ -148,7 +160,6 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
   }
 
   componentWillUnmount() {
-    replicaStore.clearDetails()
     scheduleStore.clearUnsavedSchedules()
     this.stopPolling = true
   }
@@ -174,34 +185,35 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
   }
 
   async loadReplicaWithInstances(replicaId: string, cache: boolean) {
-    await replicaStore.getReplica(replicaId, { showLoading: true })
-    let details = replicaStore.replicaDetails
-    if (!details) {
-      return
+    await replicaStore.getReplicas({ showLoading: true })
+    let replica = this.replica
+    if (!replica) {
+      return null
     }
-    this.loadIsEditable(details)
-    networkStore.loadNetworks(details.destination_endpoint_id, details.destination_environment, {
+    this.loadIsEditable(replica)
+    networkStore.loadNetworks(replica.destination_endpoint_id, replica.destination_environment, {
       quietError: true,
       cache,
     })
 
-    let targetEndpoint = endpointStore.endpoints.find(e => e.id === details.destination_endpoint_id)
+    let targetEndpoint = endpointStore.endpoints.find(e => e.id === replica.destination_endpoint_id)
     instanceStore.loadInstancesDetails({
-      endpointId: details.origin_endpoint_id,
+      endpointId: replica.origin_endpoint_id,
       // $FlowIgnore
-      instancesInfo: details.instances.map(n => ({ instance_name: n })),
+      instancesInfo: replica.instances.map(n => ({ instance_name: n })),
       cache,
       quietError: false,
-      env: details.source_environment,
+      env: replica.source_environment,
       targetProvider: targetEndpoint ? targetEndpoint.type : '',
     })
+    return replica
   }
 
   getLastExecution() {
-    if (replicaStore.replicaDetails && replicaStore.replicaDetails.executions && replicaStore.replicaDetails.executions.length) {
-      return replicaStore.replicaDetails.executions[replicaStore.replicaDetails.executions.length - 1]
+    let replica = this.replica
+    if (replica && replica.executions && replica.executions.length) {
+      return replica.executions[replica.executions.length - 1]
     }
-
     return null
   }
 
@@ -211,8 +223,12 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
   }
 
   isExecuteDisabled() {
-    let originEndpoint = endpointStore.endpoints.find(e => replicaStore.replicaDetails && e.id === replicaStore.replicaDetails.origin_endpoint_id)
-    let targetEndpoint = endpointStore.endpoints.find(e => replicaStore.replicaDetails && e.id === replicaStore.replicaDetails.destination_endpoint_id)
+    let replica = this.replica
+    if (!replica) {
+      return true
+    }
+    let originEndpoint = endpointStore.endpoints.find(e => e.id === replica.origin_endpoint_id)
+    let targetEndpoint = endpointStore.endpoints.find(e => e.id === replica.destination_endpoint_id)
 
     return Boolean(!originEndpoint || !targetEndpoint || this.getStatus() === 'RUNNING' || this.getStatus() === 'CANCELLING')
   }
@@ -235,10 +251,11 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
   }
 
   handleDeleteExecutionConfirmation() {
-    if (!this.state.confirmationItem) {
+    let replica = this.replica
+    if (!this.state.confirmationItem || !replica) {
       return
     }
-    replicaStore.deleteExecution(replicaStore.replicaDetails ? replicaStore.replicaDetails.id : '', this.state.confirmationItem.id)
+    replicaStore.deleteExecution(replica.id, this.state.confirmationItem.id)
     this.handleCloseExecutionConfirmation()
   }
 
@@ -266,8 +283,12 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
 
   handleDeleteReplicaConfirmation() {
     this.setState({ showDeleteReplicaConfirmation: false })
+    let replica = this.replica
+    if (!replica) {
+      return
+    }
     this.props.history.push('/replicas')
-    replicaStore.delete(replicaStore.replicaDetails ? replicaStore.replicaDetails.id : '')
+    replicaStore.delete(replica.id)
   }
 
   handleCloseDeleteReplicaConfirmation() {
@@ -276,8 +297,12 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
 
   handleDeleteReplicaDisksConfirmation() {
     this.setState({ showDeleteReplicaDisksConfirmation: false, showDeleteReplicaConfirmation: false })
-    replicaStore.deleteDisks(replicaStore.replicaDetails ? replicaStore.replicaDetails.id : '')
-    this.props.history.push(`/replica/executions/${replicaStore.replicaDetails ? replicaStore.replicaDetails.id : ''}`)
+    let replica = this.replica
+    if (!replica) {
+      return
+    }
+    replicaStore.deleteDisks(replica.id)
+    this.props.history.push(`/replica/executions/${replica.id}`)
   }
 
   handleCloseDeleteReplicaDisksConfirmation() {
@@ -297,7 +322,7 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
   }
 
   handleAddScheduleClick(schedule: Schedule) {
-    scheduleStore.addSchedule(this.props.match.params.id, schedule)
+    scheduleStore.addSchedule(this.replicaId, schedule)
   }
 
   handleScheduleChange(scheduleId: ?string, data: Schedule, forceSave?: boolean) {
@@ -305,19 +330,19 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
     let unsavedData = scheduleStore.unsavedSchedules.find(s => s.id === scheduleId)
 
     if (scheduleId) {
-      scheduleStore.updateSchedule(this.props.match.params.id, scheduleId, data, oldData, unsavedData, forceSave)
+      scheduleStore.updateSchedule(this.replicaId, scheduleId, data, oldData, unsavedData, forceSave)
     }
   }
 
   handleScheduleSave(schedule: Schedule) {
     if (schedule.id) {
-      scheduleStore.updateSchedule(this.props.match.params.id, schedule.id, schedule, schedule, schedule, true)
+      scheduleStore.updateSchedule(this.replicaId, schedule.id, schedule, schedule, schedule, true)
     }
   }
 
   handleScheduleRemove(scheduleId: ?string) {
     if (scheduleId) {
-      scheduleStore.removeSchedule(this.props.match.params.id, scheduleId)
+      scheduleStore.removeSchedule(this.replicaId, scheduleId)
     }
   }
 
@@ -341,11 +366,12 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
   }
 
   handleCancelConfirmation(force?: boolean) {
-    if (!this.state.confirmationItem) {
+    let replica = this.replica
+    if (!this.state.confirmationItem || !replica) {
       return
     }
     replicaStore.cancelExecution(
-      replicaStore.replicaDetails ? replicaStore.replicaDetails.id : '',
+      replica.id,
       this.state.confirmationItem.id,
       force
     )
@@ -361,8 +387,12 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
   }
 
   async migrate(options: Field[], uploadedScripts: InstanceScript[]) {
+    let replica = this.replica
+    if (!replica) {
+      return
+    }
     let migration = await migrationStore.migrateReplica(
-      replicaStore.replicaDetails ? replicaStore.replicaDetails.id : '',
+      replica.id,
       options,
       uploadedScripts
     )
@@ -377,9 +407,13 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
   }
 
   executeReplica(fields: Field[]) {
-    replicaStore.execute(replicaStore.replicaDetails ? replicaStore.replicaDetails.id : '', fields)
+    let replica = this.replica
+    if (!replica) {
+      return
+    }
+    replicaStore.execute(replica.id, fields)
     this.handleCloseOptionsModal()
-    this.props.history.push(`/replica/executions/${replicaStore.replicaDetails ? replicaStore.replicaDetails.id : ''}`)
+    this.props.history.push(`/replica/executions/${replica.id}`)
   }
 
   async pollData(showLoading: boolean) {
@@ -387,11 +421,8 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
       return
     }
 
-    if (!this.props.match.params.page) {
-      replicaStore.getReplica(this.props.match.params.id, { showLoading, skipLog: true })
-    }
+    await replicaStore.getReplicas({ showLoading, skipLog: true })
 
-    await replicaStore.getReplicaExecutions(this.props.match.params.id, { showLoading, skipLog: true })
     setTimeout(() => { this.pollData(false) }, configLoader.config.requestPollTimeout)
   }
 
@@ -402,25 +433,25 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
   }
 
   handleEditReplicaReload() {
-    this.loadReplicaWithInstances(this.props.match.params.id, false)
+    this.loadReplicaWithInstances(this.replicaId, false)
   }
 
   handleUpdateComplete(redirectTo: string) {
-    if (!replicaStore.replicaDetails) {
-      return
-    }
-
     this.props.history.push(redirectTo)
     this.closeEditModal()
   }
 
   renderEditReplica() {
+    let replica = this.replica
+    if (!replica) {
+      return null
+    }
     let sourceEndpoint = endpointStore.endpoints
-      .find(e => replicaStore.replicaDetails && e.id === replicaStore.replicaDetails.origin_endpoint_id)
+      .find(e => e.id === replica.origin_endpoint_id)
     let destinationEndpoint = endpointStore.endpoints
-      .find(e => replicaStore.replicaDetails && e.id === replicaStore.replicaDetails.destination_endpoint_id)
+      .find(e => e.id === replica.destination_endpoint_id)
 
-    if (!this.state.showEditModal || !replicaStore.replicaDetails || !destinationEndpoint || !sourceEndpoint) {
+    if (!this.state.showEditModal || !destinationEndpoint || !sourceEndpoint) {
       return null
     }
 
@@ -431,7 +462,7 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
         sourceEndpoint={sourceEndpoint}
         onUpdateComplete={url => { this.handleUpdateComplete(url) }}
         onRequestClose={() => { this.closeEditModal() }}
-        replica={replicaStore.replicaDetails}
+        replica={replica}
         destinationEndpoint={destinationEndpoint}
         instancesDetails={instanceStore.instancesDetails}
         instancesDetailsLoading={instanceStore.loadingInstancesDetails}
@@ -480,6 +511,7 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
         action: () => { this.handleDeleteReplicaClick() },
       },
     ]
+    let replica = this.replica
 
     return (
       <Wrapper>
@@ -489,20 +521,20 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
             onUserItemClick={item => { this.handleUserItemClick(item) }}
           />}
           contentHeaderComponent={<DetailsContentHeader
-            item={replicaStore.replicaDetails}
+            item={replica}
             dropdownActions={dropdownActions}
             backLink="/replicas"
             typeImage={replicaImage}
             alertInfoPill
           />}
           contentComponent={<ReplicaDetailsContent
-            item={replicaStore.replicaDetails}
+            item={replica}
             instancesDetails={instanceStore.instancesDetails}
             instancesDetailsLoading={instanceStore.loadingInstancesDetails}
             endpoints={endpointStore.endpoints}
             scheduleStore={scheduleStore}
             networks={networkStore.networks}
-            detailsLoading={replicaStore.detailsLoading || endpointStore.loading}
+            detailsLoading={replicaStore.loading || endpointStore.loading}
             sourceSchema={providerStore.sourceSchema}
             sourceSchemaLoading={providerStore.sourceSchemaLoading
               || providerStore.sourceOptionsPrimaryLoading
@@ -511,7 +543,7 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
             destinationSchemaLoading={providerStore.destinationSchemaLoading
               || providerStore.destinationOptionsPrimaryLoading
               || providerStore.destinationOptionsSecondaryLoading}
-            executionsLoading={replicaStore.executionsLoading}
+            executionsLoading={replicaStore.startingExecution}
             page={this.props.match.params.page || ''}
             onCancelExecutionClick={(e, f) => { this.handleCancelExecution(e, f) }}
             onDeleteExecutionClick={execution => { this.handleDeleteExecutionClick(execution) }}
@@ -558,7 +590,7 @@ class ReplicaDetailsPage extends React.Component<Props, State> {
         />
         {this.state.showDeleteReplicaConfirmation ? (
           <DeleteReplicaModal
-            hasDisks={replicaStore.hasReplicaDisks(replicaStore.replicaDetails)}
+            hasDisks={replicaStore.hasReplicaDisks(this.replica)}
             onRequestClose={() => this.handleCloseDeleteReplicaConfirmation()}
             onDeleteReplica={() => { this.handleDeleteReplicaConfirmation() }}
             onDeleteDisks={() => { this.handleDeleteReplicaDisksConfirmation() }}

+ 4 - 25
src/sources/ReplicaSource.js

@@ -85,8 +85,8 @@ class ReplicaSourceUtils {
     }
 
     replicas.sort((a, b) => {
-      ReplicaSourceUtils.sortExecutions(a.executions)
-      ReplicaSourceUtils.sortExecutions(b.executions)
+      ReplicaSourceUtils.sortExecutionsAndTasks(a.executions)
+      ReplicaSourceUtils.sortExecutionsAndTasks(b.executions)
       let aLastExecution = a.executions && a.executions.length ? a.executions[a.executions.length - 1] : null
       let bLastExecution = b.executions && b.executions.length ? b.executions[b.executions.length - 1] : null
       let aLastTime = aLastExecution ? aLastExecution.updated_at || aLastExecution.created_at : null
@@ -119,10 +119,11 @@ class ReplicaSourceUtils {
 }
 
 class ReplicaSource {
-  async getReplicas(skipLog?: boolean): Promise<MainItem[]> {
+  async getReplicas(skipLog?: boolean, quietError?: boolean): Promise<MainItem[]> {
     let response = await Api.send({
       url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas/detail`,
       skipLog,
+      quietError,
     })
     let replicas = response.data.replicas
     replicas = ReplicaSourceUtils.filterDeletedExecutionsInReplicas(replicas)
@@ -130,28 +131,6 @@ class ReplicaSource {
     return replicas
   }
 
-  async getReplicaExecutions(replicaId: string, skipLog?: boolean): Promise<Execution[]> {
-    let response = await Api.send({
-      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas/${replicaId}/executions/detail`,
-      skipLog,
-    })
-    let executions = response.data.executions
-    ReplicaSourceUtils.sortExecutionsAndTasks(executions)
-
-    return executions
-  }
-
-  async getReplica(replicaId: string, skipLog?: boolean): Promise<MainItem> {
-    let response = await Api.send({
-      url: `${configLoader.config.servicesUrls.coriolis}/${Api.projectId}/replicas/${replicaId}`,
-      skipLog,
-    })
-    let replica = response.data.replica
-    replica.executions = ReplicaSourceUtils.filterDeletedExecutions(replica.executions)
-    ReplicaSourceUtils.sortExecutions(replica.executions)
-    return replica
-  }
-
   async execute(replicaId: string, fields?: Field[]): Promise<Execution> {
     let payload = { execution: { shutdown_instances: false } }
     if (fields) {

+ 40 - 69
src/stores/ReplicaStore.js

@@ -15,6 +15,7 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 // @flow
 
 import { observable, action, runInAction } from 'mobx'
+import moment from 'moment'
 
 import notificationStore from '../stores/NotificationStore'
 import ReplicaSource from '../sources/ReplicaSource'
@@ -39,17 +40,37 @@ class ReplicaStoreUtils {
   }
 }
 
+let checkAddExecution = (replicas: MainItem[], addExecution: ?{ replicaId: string, execution: Execution }) => {
+  let usableAddExecution = addExecution
+  if (!usableAddExecution) {
+    return
+  }
+  let executionTime = moment.utc(usableAddExecution.execution.created_at).local().toDate().getTime()
+  if (new Date().getTime() - executionTime > 5000) {
+    return
+  }
+  let replica = replicas.find(r => r.id === usableAddExecution.replicaId)
+  if (!replica) {
+    return
+  }
+  let execution = replica.executions.find(e => e.id === usableAddExecution.execution.id)
+  if (execution) {
+    return
+  }
+  replica.executions.push(usableAddExecution.execution)
+}
+
 class ReplicaStore {
   @observable replicas: MainItem[] = []
-  @observable replicaDetails: ?MainItem = null
-  @observable loading: boolean = true
+  @observable loading: boolean = false
   @observable backgroundLoading: boolean = false
-  @observable detailsLoading: boolean = true
-  @observable executionsLoading: boolean = false
+  @observable startingExecution: boolean = false
 
   replicasLoaded: boolean = false
 
-  @action async getReplicas(options?: { showLoading?: boolean, skipLog?: boolean }): Promise<void> {
+  addExecution: ?{ replicaId: string, execution: Execution } = null
+
+  @action async getReplicas(options?: { showLoading?: boolean, skipLog?: boolean, quietError?: boolean }): Promise<void> {
     this.backgroundLoading = true
 
     if ((options && options.showLoading) || !this.replicasLoaded) {
@@ -57,7 +78,8 @@ class ReplicaStore {
     }
 
     try {
-      let replicas = await ReplicaSource.getReplicas(options && options.skipLog)
+      let replicas = await ReplicaSource.getReplicas(options && options.skipLog, options && options.quietError)
+      checkAddExecution(replicas, this.addExecution)
       this.getReplicasSuccess(replicas)
     } finally {
       this.getReplicasDone()
@@ -74,62 +96,24 @@ class ReplicaStore {
     this.backgroundLoading = false
   }
 
-  @action async getReplicaExecutions(replicaId: string, options?: { showLoading?: boolean, skipLog?: boolean }): Promise<void> {
-    if (options && options.showLoading) this.executionsLoading = true
-
-    try {
-      let executions = await ReplicaSource.getReplicaExecutions(replicaId, options && options.skipLog)
-      this.getReplicaExecutionsSuccess(replicaId, executions)
-    } finally {
-      runInAction(() => { this.executionsLoading = false })
-    }
-  }
-
-  @action getReplicaExecutionsSuccess(replicaId: string, executions: Execution[]) {
-    let replica = this.replicas.find(replica => replica.id === replicaId)
-
-    if (replica) {
-      replica.executions = executions
-    }
-
-    if (this.replicaDetails && this.replicaDetails.id === replicaId) {
-      this.replicaDetails = {
-        ...this.replicaDetails,
-        executions,
-      }
-    }
-  }
-
-  @action async getReplica(replicaId: string, options?: { showLoading?: boolean, skipLog?: boolean }): Promise<void> {
-    this.detailsLoading = Boolean(options && options.showLoading)
-
-    try {
-      let replica = await ReplicaSource.getReplica(replicaId, options && options.skipLog)
-      runInAction(() => {
-        this.replicaDetails = replica
-        this.replicas = this.replicas.map(r => r.id === replica.id ? replica : r)
-      })
-    } finally {
-      runInAction(() => { this.detailsLoading = false })
-    }
-  }
-
   @action async execute(replicaId: string, fields?: Field[]): Promise<void> {
+    let replica = this.replicas.find(r => r.id === replicaId)
+    if (replica && replica.executions && replica.executions.length === 0) {
+      this.startingExecution = true
+    }
     let execution = await ReplicaSource.execute(replicaId, fields)
     this.executeSuccess(replicaId, execution)
   }
 
   @action executeSuccess(replicaId: string, execution: Execution) {
-    if (this.replicaDetails && this.replicaDetails.id === replicaId) {
-      this.replicaDetails = ReplicaStoreUtils.getNewReplica(this.replicaDetails, execution)
-    }
-
-    let replicasItemIndex = this.replicas ? this.replicas.findIndex(r => r.id === replicaId) : -1
+    this.addExecution = { replicaId, execution }
+    let replicasItemIndex = this.replicas.findIndex(r => r.id === replicaId)
 
     if (replicasItemIndex > -1) {
       const updatedReplica = ReplicaStoreUtils.getNewReplica(this.replicas[replicasItemIndex], execution)
       this.replicas[replicasItemIndex] = updatedReplica
     }
+    this.startingExecution = false
   }
 
   async cancelExecution(replicaId: string, executionId: string, force: ?boolean): Promise<void> {
@@ -149,15 +133,11 @@ class ReplicaStore {
   @action deleteExecutionSuccess(replicaId: string, executionId: string) {
     let executions = []
 
-    if (this.replicaDetails && this.replicaDetails.id === replicaId) {
-      if (this.replicaDetails.executions) {
-        executions = [...this.replicaDetails.executions.filter(e => e.id !== executionId)]
-      }
+    let replicasItemIndex = this.replicas ? this.replicas.findIndex(r => r.id === replicaId) : -1
 
-      this.replicaDetails = {
-        ...this.replicaDetails,
-        executions,
-      }
+    if (replicasItemIndex > -1) {
+      executions = [...this.replicas[replicasItemIndex].executions.filter(e => e.id !== executionId)]
+      this.replicas[replicasItemIndex].executions = executions
     }
   }
 
@@ -172,11 +152,7 @@ class ReplicaStore {
   }
 
   @action deleteDisksSuccess(replicaId: string, execution: Execution) {
-    if (this.replicaDetails && this.replicaDetails.id === replicaId) {
-      this.replicaDetails = ReplicaStoreUtils.getNewReplica(this.replicaDetails, execution)
-    }
-
-    let replicasItemIndex = this.replicas ? this.replicas.findIndex(r => r.id === replicaId) : -1
+    let replicasItemIndex = this.replicas.findIndex(r => r.id === replicaId)
 
     if (replicasItemIndex > -1) {
       const updatedReplica = ReplicaStoreUtils.getNewReplica(this.replicas[replicasItemIndex], execution)
@@ -184,11 +160,6 @@ class ReplicaStore {
     }
   }
 
-  @action clearDetails() {
-    this.detailsLoading = true
-    this.replicaDetails = null
-  }
-
   async update(replica: MainItem, destinationEndpoint: Endpoint, updateData: UpdateData, defaultStorage: ?string, storageConfigDefault: string) {
     await ReplicaSource.update(replica, destinationEndpoint, updateData, defaultStorage, storageConfigDefault)
   }