feat(xo-web/sr/general): improve SR usage graph (#3830)

See #3608
This commit is contained in:
Rajaa.BARHTAOUI
2019-07-12 16:56:06 +02:00
committed by Pierre Donias
parent 215432be6c
commit 16135b8e37
4 changed files with 241 additions and 35 deletions

View File

@@ -7,6 +7,8 @@
> Users must be able to say: “Nice enhancement, I'm eager to test it”
- [SR/General] Improve SR usage graph [#3608](https://github.com/vatesfr/xen-orchestra/issues/3608) (PR [#3830](https://github.com/vatesfr/xen-orchestra/pull/3830))
### Bug fixes
> Users must be able to say: “I had this issue, happy to know it's fixed”

View File

@@ -528,6 +528,7 @@ const TRANSFORMS = {
name_description: obj.name_description,
name_label: obj.name_label,
parent: obj.sm_config['vhd-parent'],
size: +obj.virtual_size,
snapshots: link(obj, 'snapshots'),
tags: obj.tags,

View File

@@ -684,6 +684,15 @@ const messages = {
vmConsoleLabel: 'Console',
backupLabel: 'Backup',
// ----- SR general tab -----
baseCopyTooltip:
'{n, number} base cop{n, plural, one {y} other {ies}} ({usage})',
diskTooltip: '{name} ({usage})',
snapshotsTooltip:
'{n, number} snapshot{n, plural, one {} other {s}} ({usage})',
vdiOnVmTooltip: '{name} ({usage}) on {vmName}',
vdisTooltip: '{n, number} VDI{n, plural, one {} other {s}} ({usage})',
// ----- SR advanced tab -----
srUnhealthyVdiDepth: 'Depth',

View File

@@ -1,25 +1,155 @@
import _ from 'intl'
import Component from 'base-component'
import decorate from 'apply-decorators'
import HomeTags from 'home-tags'
import Icon from 'icon'
import map from 'lodash/map'
import React from 'react'
import Usage, { UsageElement } from 'usage'
import { addTag, removeTag, getLicense } from 'xo'
import { connectStore, formatSize } from 'utils'
import { Container, Row, Col } from 'grid'
import { createGetObject } from 'selectors'
import { renderXoItemFromId } from 'render-xo-item'
import {
createCollectionWrapper,
createGetObjectsOfType,
createSelector,
} from 'selectors'
import {
flatMap,
forEach,
groupBy,
keyBy,
map,
mapValues,
sumBy,
uniq,
} from 'lodash'
import { get } from '@xen-orchestra/defined'
import { injectState, provideState } from 'reaclette'
const UsageTooltip = connectStore(() => ({
vbd: createGetObject((_, { vdi }) => vdi.$VBDs[0]),
}))(({ vbd, vdi }) => (
<span>
{vdi.name_label} {formatSize(vdi.usage)}
{vbd != null && <br />}
{vbd != null && renderXoItemFromId(vbd.VM)}
</span>
))
const nestedUlStyle = { margin: '0.1em', marginLeft: '0.5em', padding: 0 }
const ulStyle = { margin: 0, padding: 0 }
const UsageTooltip = decorate([
connectStore(() => {
const getVbds = createGetObjectsOfType('VBD').pick(
createCollectionWrapper(
createSelector(
(_, { group }) => group.vdis,
vdis => flatMap(vdis, '$VBDs')
)
)
)
const getVms = createGetObjectsOfType('VM').pick(
createCollectionWrapper(
createSelector(
getVbds,
vbds => map(vbds, 'VM')
)
)
)
return {
vbds: getVbds,
vms: getVms,
}
}),
provideState({
computed: {
baseCopiesUsage: (_, { group: { baseCopies } }) =>
formatSize(sumBy(baseCopies, 'usage')),
vdis: (_, { group: { vdis } }) => keyBy(vdis, 'id'),
vdisUsage: (_, { group: { vdis } }) => formatSize(sumBy(vdis, 'usage')),
snapshotsUsage: (_, { group: { snapshots } }) =>
formatSize(sumBy(snapshots, 'usage')),
vmNamesByVdi: createCollectionWrapper(({ vdis }, { vbds, vms }) =>
mapValues(vdis, vdi => get(() => vms[vbds[vdi.VBD].VM]))
),
},
}),
injectState,
class extends Component {
_getVdiTooltip = vdi => {
const vmName = this.props.state.vmNamesByVdi[vdi.id]
return (
<span>
{vmName === undefined
? _('diskTooltip', {
name: vdi.name_label,
usage: formatSize(vdi.usage),
})
: _('vdiOnVmTooltip', {
name: vdi.name_label,
usage: formatSize(vdi.usage),
vmName,
})}
</span>
)
}
render() {
const { group, state } = this.props
const {
baseCopies,
name_label: name,
snapshots,
type,
usage,
vdis,
} = group
return (
<div>
{type === 'orphanedSnapshot' ? (
<span>
{_('diskTooltip', {
name,
usage: formatSize(usage),
})}
</span>
) : baseCopies.length === 0 && snapshots.length === 0 ? (
this._getVdiTooltip(vdis[0])
) : (
<ul style={ulStyle}>
<li>
{_('baseCopyTooltip', {
n: baseCopies.length,
usage: state.baseCopiesUsage,
})}
</li>
<li>
{_('snapshotsTooltip', {
n: snapshots.length,
usage: state.snapshotsUsage,
})}
<ul style={nestedUlStyle}>
{snapshots.map(snapshot => (
<li key={snapshot.id}>
{_('diskTooltip', {
name: snapshot.name_label,
usage: formatSize(snapshot.usage),
})}
</li>
))}
</ul>
</li>
<li>
{_('vdisTooltip', {
n: vdis.length,
usage: state.vdisUsage,
})}
<ul style={nestedUlStyle}>
{vdis.map(vdi => (
<li key={vdi.id}>{this._getVdiTooltip(vdi)}</li>
))}
</ul>
</li>
</ul>
)}
</div>
)
}
},
])
export default class TabGeneral extends Component {
componentDidMount() {
@@ -32,9 +162,88 @@ export default class TabGeneral extends Component {
}
}
render() {
const { sr, vdis, vdiSnapshots, unmanagedVdis } = this.props
_getDiskGroups = createSelector(
() => this.props.vdis,
() => this.props.vdiSnapshots,
() => this.props.unmanagedVdis,
(vdis, vdiSnapshots, unmanagedVdis) => {
const groups = []
const snapshotsByVdi = groupBy(vdiSnapshots, '$snapshot_of')
const unmanagedVdisById = keyBy(unmanagedVdis, 'id')
let orphanedVdiSnapshots
if ((orphanedVdiSnapshots = snapshotsByVdi[undefined]) !== undefined) {
groups.push(
...orphanedVdiSnapshots.map(snapshot => ({
id: snapshot.id,
name_label: snapshot.name_label,
usage: snapshot.usage,
type: 'orphanedSnapshot',
}))
)
}
// search root base copy for each VDI
const vdisInfo = vdis.map(({ id, parent, name_label, usage }) => {
const baseCopies = new Set()
let baseCopy
let root = id
while ((baseCopy = unmanagedVdisById[parent]) !== undefined) {
root = baseCopy.id
parent = baseCopy.parent
baseCopies.add(baseCopy)
}
let snapshots
if ((snapshots = snapshotsByVdi[id]) !== undefined) {
// snapshot can have base copy without active VDI
snapshots.forEach(({ parent }) => {
while (
(baseCopy = unmanagedVdisById[parent]) !== undefined &&
!baseCopies.has(baseCopy)
) {
parent = baseCopy.parent
baseCopies.add(baseCopy)
}
})
}
return {
baseCopies,
id,
name_label,
root,
snapshots: snapshots === undefined ? [] : snapshots,
usage,
}
})
// group VDIs by their root base copy.
const vdisByRoot = groupBy(vdisInfo, 'root')
// group collection of VDIs and their snapshots and base copies.
forEach(vdisByRoot, vdis => {
let baseCopies = []
let snapshots = []
let vdisUsage = 0
vdis.forEach(vdi => {
vdisUsage += vdi.usage
baseCopies = baseCopies.concat(...vdi.baseCopies)
snapshots = snapshots.concat(vdi.snapshots)
})
baseCopies = uniq(baseCopies)
snapshots = uniq(snapshots)
groups.push({
id: vdis[0].id,
vdis,
baseCopies,
usage:
vdisUsage + sumBy(baseCopies, 'usage') + sumBy(snapshots, 'usage'),
snapshots,
})
})
return groups
}
)
render() {
const { sr } = this.props
return (
<Container>
<Row className='text-xs-center'>
@@ -68,28 +277,13 @@ export default class TabGeneral extends Component {
</Row>
<Row>
<Col smallOffset={1} mediumSize={10}>
<Usage total={sr.size}>
{map(unmanagedVdis, vdi => (
<Usage total={sr.size} type='disk'>
{this._getDiskGroups().map(group => (
<UsageElement
highlight
key={vdi.id}
tooltip={<UsageTooltip vdi={vdi} />}
value={vdi.usage}
/>
))}
{map(vdis, vdi => (
<UsageElement
key={vdi.id}
tooltip={<UsageTooltip vdi={vdi} />}
value={vdi.usage}
/>
))}
{map(vdiSnapshots, vdi => (
<UsageElement
highlight
key={vdi.id}
tooltip={<UsageTooltip vdi={vdi} />}
value={vdi.usage}
highlight={group.type === 'orphanedSnapshot'}
key={group.id}
tooltip={<UsageTooltip group={group} />}
value={group.usage}
/>
))}
</Usage>