Jelajahi Sumber

Add 'Used in ...' info to endpoint details page

It shows in how many replicas/migrations the endpoint is used and their
names.
This new field and the 'description' field is not ellipsed like the
other fields, instead they are rendered on multiple lines if they don't
fit in one line.
Sergiu Miclea 8 tahun lalu
induk
melakukan
e19ef98413

+ 44 - 4
src/components/organisms/EndpointDetailsContent/index.jsx

@@ -23,8 +23,12 @@ import PasswordValue from '../../atoms/PasswordValue'
 import Button from '../../atoms/Button'
 import Button from '../../atoms/Button'
 import CopyValue from '../../atoms/CopyValue'
 import CopyValue from '../../atoms/CopyValue'
 import StatusImage from '../../atoms/StatusImage'
 import StatusImage from '../../atoms/StatusImage'
+import CopyButton from '../../atoms/CopyButton'
+import NotificationStore from '../../../stores/NotificationStore'
+import DomUtils from '../../../utils/DomUtils'
 
 
 import type { Endpoint } from '../../../types/Endpoint'
 import type { Endpoint } from '../../../types/Endpoint'
+import type { MainItem } from '../../../types/MainItem'
 import StyleProps from '../../styleUtils/StyleProps'
 import StyleProps from '../../styleUtils/StyleProps'
 import Palette from '../../styleUtils/Palette'
 import Palette from '../../styleUtils/Palette'
 import DateUtils from '../../../utils/DateUtils'
 import DateUtils from '../../../utils/DateUtils'
@@ -36,14 +40,15 @@ const Wrapper = styled.div`
   padding-left: 126px;
   padding-left: 126px;
 `
 `
 const Info = styled.div`
 const Info = styled.div`
-  margin-top: 32px;
   display: flex;
   display: flex;
   flex-wrap: wrap;
   flex-wrap: wrap;
+  margin-top: 32px;
+  margin-left: -32px;  
 `
 `
 const Field = styled.div`
 const Field = styled.div`
+  ${StyleProps.exactWidth('calc(50% - 32px)')}
   margin-bottom: 32px;
   margin-bottom: 32px;
-  min-width: 50%;
-  max-width: 50%;
+  margin-left: 32px;  
 `
 `
 const Label = styled.div`
 const Label = styled.div`
   font-size: 10px;
   font-size: 10px;
@@ -53,6 +58,17 @@ const Label = styled.div`
   margin-bottom: 3px;
   margin-bottom: 3px;
 `
 `
 const Value = styled.div``
 const Value = styled.div``
+const MultilineValue = styled.div`
+  cursor: pointer;
+
+  &:hover > span {
+    opacity: 1;
+  }
+  > span {
+    background-position-y: 4px;
+    margin-left: 4px;
+  }
+`
 const Buttons = styled.div`
 const Buttons = styled.div`
   display: flex;
   display: flex;
   justify-content: space-between;
   justify-content: space-between;
@@ -76,6 +92,7 @@ type Props = {
   item: ?Endpoint,
   item: ?Endpoint,
   connectionInfo: ?$PropertyType<Endpoint, 'connection_info'>,
   connectionInfo: ?$PropertyType<Endpoint, 'connection_info'>,
   loading: boolean,
   loading: boolean,
+  usage: { migrations: MainItem[], replicas: MainItem[] },
   onDeleteClick: () => void,
   onDeleteClick: () => void,
   onValidateClick: () => void,
   onValidateClick: () => void,
   onEditClick: () => void,
   onEditClick: () => void,
@@ -84,6 +101,14 @@ type Props = {
 class EndpointDetailsContent extends React.Component<Props> {
 class EndpointDetailsContent extends React.Component<Props> {
   renderedKeys: { [string]: boolean }
   renderedKeys: { [string]: boolean }
 
 
+  handleCopy(text: string) {
+    let succesful = DomUtils.copyTextToClipboard(text)
+
+    if (succesful) {
+      NotificationStore.notify('The message has been copied to clipboard.')
+    }
+  }
+
   renderConnectionInfoLoading() {
   renderConnectionInfoLoading() {
     if (!this.props.loading) {
     if (!this.props.loading) {
       return null
       return null
@@ -157,6 +182,15 @@ class EndpointDetailsContent extends React.Component<Props> {
     )
     )
   }
   }
 
 
+  renderMultilineValue(value: string, dataTestId?: string) {
+    return (
+      <MultilineValue onClick={() => { this.handleCopy(value) }} data-test-id={dataTestId}>
+        {value}
+        <CopyButton />
+      </MultilineValue>
+    )
+  }
+
   renderValue(value: string, dataTestId?: string) {
   renderValue(value: string, dataTestId?: string) {
     return <CopyValue data-test-id={dataTestId} value={value} maxWidth="90%" />
     return <CopyValue data-test-id={dataTestId} value={value} maxWidth="90%" />
   }
   }
@@ -164,6 +198,8 @@ class EndpointDetailsContent extends React.Component<Props> {
   render() {
   render() {
     this.renderedKeys = {}
     this.renderedKeys = {}
     const { type, name, description, created_at } = this.props.item || {}
     const { type, name, description, created_at } = this.props.item || {}
+    const usage = this.props.usage.replicas.concat(this.props.usage.migrations)
+
     return (
     return (
       <Wrapper>
       <Wrapper>
         <EndpointLogos endpoint={type} />
         <EndpointLogos endpoint={type} />
@@ -178,12 +214,16 @@ class EndpointDetailsContent extends React.Component<Props> {
           </Field>
           </Field>
           <Field>
           <Field>
             <Label>Description</Label>
             <Label>Description</Label>
-            {description ? this.renderValue(description, 'description') : <Value>-</Value>}
+            {description ? this.renderMultilineValue(description, 'description') : <Value>-</Value>}
           </Field>
           </Field>
           <Field>
           <Field>
             <Label>Created</Label>
             <Label>Created</Label>
             {this.renderValue(DateUtils.getLocalTime(created_at).format('DD/MM/YYYY HH:mm'), 'created')}
             {this.renderValue(DateUtils.getLocalTime(created_at).format('DD/MM/YYYY HH:mm'), 'created')}
           </Field>
           </Field>
+          <Field>
+            <Label>Used in replicas/migrations ({usage.length})</Label>
+            {usage.length > 0 ? this.renderMultilineValue(usage.map(i => i.instances.join(', ')).join(', ')) : <Value>-</Value>}
+          </Field>
           {this.renderConnectionInfoLoading()}
           {this.renderConnectionInfoLoading()}
           {this.renderConnectionInfo(this.props.connectionInfo)}
           {this.renderConnectionInfo(this.props.connectionInfo)}
         </Info>
         </Info>

+ 16 - 8
src/components/pages/EndpointDetailsPage/index.jsx

@@ -32,6 +32,7 @@ import MigrationStore from '../../../stores/MigrationStore'
 import ReplicaStore from '../../../stores/ReplicaStore'
 import ReplicaStore from '../../../stores/ReplicaStore'
 import UserStore from '../../../stores/UserStore'
 import UserStore from '../../../stores/UserStore'
 import type { Endpoint as EndpointType } from '../../../types/Endpoint'
 import type { Endpoint as EndpointType } from '../../../types/Endpoint'
+import type { MainItem } from '../../../types/MainItem'
 
 
 import endpointImage from './images/endpoint.svg'
 import endpointImage from './images/endpoint.svg'
 
 
@@ -46,6 +47,7 @@ type State = {
   showEndpointModal: boolean,
   showEndpointModal: boolean,
   showEndpointInUseModal: boolean,
   showEndpointInUseModal: boolean,
   showEndpointInUseLoadingModal: boolean,
   showEndpointInUseLoadingModal: boolean,
+  endpointUsage: {replicas: MainItem[], migrations: MainItem[]}
 }
 }
 @observer
 @observer
 class EndpointDetailsPage extends React.Component<Props, State> {
 class EndpointDetailsPage extends React.Component<Props, State> {
@@ -58,6 +60,7 @@ class EndpointDetailsPage extends React.Component<Props, State> {
       showEndpointModal: false,
       showEndpointModal: false,
       showEndpointInUseModal: false,
       showEndpointInUseModal: false,
       showEndpointInUseLoadingModal: false,
       showEndpointInUseLoadingModal: false,
+      endpointUsage: { replicas: [], migrations: [] },
     }
     }
   }
   }
 
 
@@ -75,14 +78,14 @@ class EndpointDetailsPage extends React.Component<Props, State> {
     return EndpointStore.endpoints.find(e => e.id === this.props.match.params.id) || null
     return EndpointStore.endpoints.find(e => e.id === this.props.match.params.id) || null
   }
   }
 
 
-  getEndpointUsage() {
+  getEndpointUsage(): {migrations: MainItem[], replicas: MainItem[]} {
     let endpointId = this.props.match.params.id
     let endpointId = this.props.match.params.id
-    let replicasCount = ReplicaStore.replicas.filter(
-      r => r.origin_endpoint_id === endpointId || r.destination_endpoint_id === endpointId).length
-    let migrationsCount = MigrationStore.migrations.filter(
-      r => r.origin_endpoint_id === endpointId || r.destination_endpoint_id === endpointId).length
+    let replicas = ReplicaStore.replicas.filter(
+      r => r.origin_endpoint_id === endpointId || r.destination_endpoint_id === endpointId)
+    let migrations = MigrationStore.migrations.filter(
+      r => r.origin_endpoint_id === endpointId || r.destination_endpoint_id === endpointId)
 
 
-    return { migrationsCount, replicasCount }
+    return { migrations, replicas }
   }
   }
 
 
   handleUserItemClick(item: { value: string }) {
   handleUserItemClick(item: { value: string }) {
@@ -105,9 +108,9 @@ class EndpointDetailsPage extends React.Component<Props, State> {
     this.setState({ showEndpointInUseLoadingModal: true })
     this.setState({ showEndpointInUseLoadingModal: true })
 
 
     Promise.all([ReplicaStore.getReplicas(), MigrationStore.getMigrations()]).then(() => {
     Promise.all([ReplicaStore.getReplicas(), MigrationStore.getMigrations()]).then(() => {
-      let endpointUsage = this.getEndpointUsage()
+      const endpointUsage = this.getEndpointUsage()
 
 
-      if (endpointUsage.migrationsCount === 0 && endpointUsage.replicasCount === 0) {
+      if (endpointUsage.migrations.length === 0 && endpointUsage.replicas.length === 0) {
         this.setState({ showDeleteEndpointConfirmation: true, showEndpointInUseLoadingModal: false })
         this.setState({ showDeleteEndpointConfirmation: true, showEndpointInUseLoadingModal: false })
       } else {
       } else {
         this.setState({ showEndpointInUseModal: true, showEndpointInUseLoadingModal: false })
         this.setState({ showEndpointInUseModal: true, showEndpointInUseLoadingModal: false })
@@ -174,6 +177,10 @@ class EndpointDetailsPage extends React.Component<Props, State> {
         EndpointStore.setConnectionInfo(endpoint.connection_info)
         EndpointStore.setConnectionInfo(endpoint.connection_info)
       }
       }
     })
     })
+
+    Promise.all([ReplicaStore.getReplicas(), MigrationStore.getMigrations()]).then(() => {
+      this.setState({ endpointUsage: this.getEndpointUsage() })
+    })
   }
   }
 
 
   render() {
   render() {
@@ -194,6 +201,7 @@ class EndpointDetailsPage extends React.Component<Props, State> {
           />}
           />}
           contentComponent={<EndpointDetailsContent
           contentComponent={<EndpointDetailsContent
             item={endpoint}
             item={endpoint}
+            usage={this.state.endpointUsage}
             loading={EndpointStore.connectionInfoLoading || EndpointStore.loading}
             loading={EndpointStore.connectionInfoLoading || EndpointStore.loading}
             connectionInfo={EndpointStore.connectionInfo}
             connectionInfo={EndpointStore.connectionInfo}
             onDeleteClick={() => { this.handleDeleteEndpointClick() }}
             onDeleteClick={() => { this.handleDeleteEndpointClick() }}