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

Merge pull request #352 from smiclea/CORWEB-196

Use 'Schedule' icon if replica is scheduled CORWEB-196
Dorin Paslaru 7 лет назад
Родитель
Сommit
da1251e3d7

+ 22 - 1
src/components/molecules/MainListItem/MainListItem.jsx

@@ -28,6 +28,7 @@ import type { MainItem } from '../../../types/MainItem'
 import type { Execution } from '../../../types/Execution'
 
 import arrowImage from './images/arrow.svg'
+import scheduleImage from './images/schedule.svg'
 
 const CheckboxStyled = styled(Checkbox)`
   opacity: ${props => props.checked ? 1 : 0};
@@ -79,6 +80,14 @@ const TitleLabel = styled.div`
   white-space: nowrap;
   text-overflow: ellipsis;
 `
+const StatusWrapper = styled.div`
+  display: flex;
+  margin-top: 8px;
+`
+const ScheduleImage = styled.div`
+  ${StyleProps.exactSize('16px')}
+  background: url('${scheduleImage}') center no-repeat;
+`
 const EndpointsImages = styled.div`
   display: flex;
   align-items: center;
@@ -111,6 +120,7 @@ type Props = {
   selected: boolean,
   useTasksRemaining?: boolean,
   image: string,
+  showScheduleIcon?: boolean,
   endpointType: (endpointId: string) => string,
   onSelectedChange: (value: boolean) => void,
 }
@@ -205,7 +215,18 @@ class MainListItem extends React.Component<Props> {
           <Image image={this.props.image} />
           <Title>
             <TitleLabel>{this.props.item.instances[0]}</TitleLabel>
-            {status ? <StatusPill data-test-id={`mainListItem-statusPill-${status}`} status={status} /> : null}
+            <StatusWrapper>
+              {status ? <StatusPill
+                status={status}
+                style={{ marginRight: '8px' }}
+                data-test-id={`mainListItem-statusPill-${status}`}
+              /> : null}
+              {this.props.showScheduleIcon ? (
+                <ScheduleImage
+                  data-tip="The Replica has scheduling enabled and will execute automatically"
+                />
+              ) : null}
+            </StatusWrapper>
           </Title>
           {endpointImages}
           {this.renderLastExecution()}

+ 17 - 0
src/components/molecules/MainListItem/images/schedule.svg

@@ -0,0 +1,17 @@
+<?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 53.2 (72643) - https://sketchapp.com -->
+    <title>Icons/Pending/Blue</title>
+    <desc>Created with Sketch.</desc>
+    <g id="Coriolis" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
+        <g id="Replica/List/New" transform="translate(-568.000000, -229.000000)">
+            <g id="Migration-Line" transform="translate(352.000000, 192.000000)">
+                <g id="Icon/Pending-Blue" transform="translate(216.000000, 37.000000)">
+                    <circle id="Oval-2-Copy" fill="#0044CA" fill-rule="evenodd" cx="8" cy="8" r="8"></circle>
+                    <path d="M8,8 L8,3" id="Line-Copy-2" stroke="#FFFFFF" stroke-width="1.5" stroke-linecap="round"></path>
+                    <path d="M8,8 L10.5,10.5" id="Line-Copy-3" stroke="#FFFFFF" stroke-width="1.5" stroke-linecap="round"></path>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>

+ 3 - 0
src/components/organisms/MainList/MainList.jsx

@@ -20,6 +20,7 @@ import styled from 'styled-components'
 
 import StatusImage from '../../atoms/StatusImage'
 import Button from '../../atoms/Button'
+import Tooltip from '../../atoms/Tooltip'
 
 import type { MainItem } from '../../../types/MainItem'
 import Palette from '../../styleUtils/Palette'
@@ -184,6 +185,8 @@ class MainList extends React.Component<Props> {
       <Wrapper>
         {this.props.loading || this.props.items.length === 0 || this.props.showEmptyList ? <Separator /> : null}
         {renderContent()}
+        <Tooltip />
+        {setTimeout(() => { Tooltip.rebuild() }, 500)}
       </Wrapper>
     )
   }

+ 30 - 0
src/components/pages/ReplicasPage/ReplicasPage.jsx

@@ -31,12 +31,15 @@ import replicaLargeImage from './images/replica-large.svg'
 
 import projectStore from '../../../stores/ProjectStore'
 import replicaStore from '../../../stores/ReplicaStore'
+import scheduleStore from '../../../stores/ScheduleStore'
 import endpointStore from '../../../stores/EndpointStore'
 import notificationStore from '../../../stores/NotificationStore'
 import configLoader from '../../../utils/Config'
 
 const Wrapper = styled.div``
 
+const SCHEDULE_POLL_TIMEOUT = 10000
+
 const BulkActions = [
   { label: 'Execute', value: 'execute' },
   { label: 'Delete', value: 'delete' },
@@ -57,6 +60,8 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
 
   pollTimeout: TimeoutID
   stopPolling: boolean
+  schedulePolling: boolean
+  schedulePollTimeout: TimeoutID
 
   componentDidMount() {
     document.title = 'Coriolis Replicas'
@@ -70,6 +75,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
 
   componentWillUnmount() {
     clearTimeout(this.pollTimeout)
+    clearTimeout(this.schedulePollTimeout)
     this.stopPolling = true
   }
 
@@ -164,10 +170,25 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
     }
 
     Promise.all([replicaStore.getReplicas(), endpointStore.getEndpoints()]).then(() => {
+      if (!this.schedulePolling) {
+        this.pollSchedule()
+      }
       this.pollTimeout = setTimeout(() => { this.pollData() }, configLoader.config.requestPollTimeout)
     })
   }
 
+  pollSchedule() {
+    if (this.state.modalIsOpen || this.stopPolling || replicaStore.replicas.length === 0) {
+      return
+    }
+    this.schedulePolling = true
+    scheduleStore.getSchedulesBulk(replicaStore.replicas.map(r => r.id)).then(() => {
+      this.schedulePollTimeout = setTimeout(() => {
+        this.pollSchedule()
+      }, SCHEDULE_POLL_TIMEOUT)
+    })
+  }
+
   searchText(item: MainItem, text: ?string) {
     let result = false
     if (item.instances[0].toLowerCase().indexOf(text || '') > -1) {
@@ -196,6 +217,14 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
     return true
   }
 
+  isReplicaScheduled(replicaId: string): boolean {
+    let bulkScheduleItem = scheduleStore.bulkSchedules.find(b => b.replicaId === replicaId)
+    if (!bulkScheduleItem) {
+      return false
+    }
+    return Boolean(bulkScheduleItem.schedules.find(s => s.enabled))
+  }
+
   render() {
     return (
       <Wrapper>
@@ -216,6 +245,7 @@ class ReplicasPage extends React.Component<{ history: any }, State> {
                 (<MainListItem
                   {...options}
                   image={replicaItemImage}
+                  showScheduleIcon={this.isReplicaScheduled(options.item.id)}
                   endpointType={id => {
                     let endpoint = this.getEndpoint(id)
                     if (endpoint) {

+ 5 - 2
src/sources/ScheduleSource.js

@@ -56,8 +56,11 @@ class ScheduleSource {
     }))
   }
 
-  static getSchedules(replicaId: string): Promise<Schedule[]> {
-    return Api.get(`${servicesUrl.coriolis}/${Api.projectId}/replicas/${replicaId}/schedules`).then(response => {
+  static getSchedules(replicaId: string, opts?: { skipLog?: boolean }): Promise<Schedule[]> {
+    return Api.send({
+      url: `${servicesUrl.coriolis}/${Api.projectId}/replicas/${replicaId}/schedules`,
+      skipLog: opts && opts.skipLog,
+    }).then(response => {
       let schedules = [...response.data.schedules]
       schedules.forEach(s => {
         if (s.expiration_date) {

+ 13 - 2
src/stores/ScheduleStore.js

@@ -14,9 +14,9 @@ along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
 // @flow
 
-import { observable, action } from 'mobx'
+import { observable, action, runInAction } from 'mobx'
 
-import type { Schedule } from '../types/Schedule'
+import type { Schedule, ScheduleBulkItem } from '../types/Schedule'
 import Source from '../sources/ScheduleSource'
 
 const updateSchedule = (schedules, id, data) => {
@@ -36,6 +36,7 @@ const updateSchedule = (schedules, id, data) => {
 class ScheduleStore {
   @observable loading: boolean = false
   @observable schedules: Schedule[] = []
+  @observable bulkSchedules: ScheduleBulkItem[] = []
   @observable unsavedSchedules: Schedule[] = []
   @observable scheduling: boolean = false
   @observable adding: boolean = false
@@ -62,6 +63,16 @@ class ScheduleStore {
     })
   }
 
+  getSchedulesBulk(replicaIds: string[]): Promise<void> {
+    return Promise.all(replicaIds.map(replicaId => {
+      return Source.getSchedules(replicaId, { skipLog: true }).then(schedules => {
+        return { replicaId, schedules }
+      })
+    })).then(bulkSchedules => {
+      runInAction(() => { this.bulkSchedules = bulkSchedules })
+    })
+  }
+
   @action addSchedule(replicaId: string, schedule: Schedule): Promise<void> {
     this.adding = true
 

+ 5 - 0
src/types/Schedule.js

@@ -29,3 +29,8 @@ export type Schedule = {
   expiration_date?: Date,
   shutdown_instances?: boolean,
 }
+
+export type ScheduleBulkItem = {
+  replicaId: string,
+  schedules: Schedule[],
+}