From 16135b8e37d4baa885eb58bf725b1a304770afc6 Mon Sep 17 00:00:00 2001 From: "Rajaa.BARHTAOUI" Date: Fri, 12 Jul 2019 16:56:06 +0200 Subject: [PATCH] feat(xo-web/sr/general): improve SR usage graph (#3830) See #3608 --- CHANGELOG.unreleased.md | 2 + packages/xo-server/src/xapi-object-to-xo.js | 1 + packages/xo-web/src/common/intl/messages.js | 9 + packages/xo-web/src/xo-app/sr/tab-general.js | 264 ++++++++++++++++--- 4 files changed, 241 insertions(+), 35 deletions(-) diff --git a/CHANGELOG.unreleased.md b/CHANGELOG.unreleased.md index 5f18f8ade..1f5093e19 100644 --- a/CHANGELOG.unreleased.md +++ b/CHANGELOG.unreleased.md @@ -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” diff --git a/packages/xo-server/src/xapi-object-to-xo.js b/packages/xo-server/src/xapi-object-to-xo.js index e7ef80f12..8c4b8d5ce 100644 --- a/packages/xo-server/src/xapi-object-to-xo.js +++ b/packages/xo-server/src/xapi-object-to-xo.js @@ -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, diff --git a/packages/xo-web/src/common/intl/messages.js b/packages/xo-web/src/common/intl/messages.js index 64bd1b869..910ac0f86 100644 --- a/packages/xo-web/src/common/intl/messages.js +++ b/packages/xo-web/src/common/intl/messages.js @@ -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', diff --git a/packages/xo-web/src/xo-app/sr/tab-general.js b/packages/xo-web/src/xo-app/sr/tab-general.js index 820c760da..6409b6279 100644 --- a/packages/xo-web/src/xo-app/sr/tab-general.js +++ b/packages/xo-web/src/xo-app/sr/tab-general.js @@ -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 }) => ( - - {vdi.name_label} − {formatSize(vdi.usage)} - {vbd != null &&
} - {vbd != null && renderXoItemFromId(vbd.VM)} -
-)) +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 ( + + {vmName === undefined + ? _('diskTooltip', { + name: vdi.name_label, + usage: formatSize(vdi.usage), + }) + : _('vdiOnVmTooltip', { + name: vdi.name_label, + usage: formatSize(vdi.usage), + vmName, + })} + + ) + } + + render() { + const { group, state } = this.props + const { + baseCopies, + name_label: name, + snapshots, + type, + usage, + vdis, + } = group + return ( +
+ {type === 'orphanedSnapshot' ? ( + + {_('diskTooltip', { + name, + usage: formatSize(usage), + })} + + ) : baseCopies.length === 0 && snapshots.length === 0 ? ( + this._getVdiTooltip(vdis[0]) + ) : ( + + )} +
+ ) + } + }, +]) 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 ( @@ -68,28 +277,13 @@ export default class TabGeneral extends Component { - - {map(unmanagedVdis, vdi => ( + + {this._getDiskGroups().map(group => ( } - value={vdi.usage} - /> - ))} - {map(vdis, vdi => ( - } - value={vdi.usage} - /> - ))} - {map(vdiSnapshots, vdi => ( - } - value={vdi.usage} + highlight={group.type === 'orphanedSnapshot'} + key={group.id} + tooltip={} + value={group.usage} /> ))}