Skip to content

Commit

Permalink
B #6221: Add units selector MB..YB (#2819)
Browse files Browse the repository at this point in the history
  • Loading branch information
jloboescalona2 authored Nov 15, 2023
1 parent da5e716 commit d691152
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,186 @@
/* ------------------------------------------------------------------------- *
* Copyright 2002-2023, OpenNebula Project, OpenNebula Systems *
* *
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
* not use this file except in compliance with the License. You may obtain *
* a copy of the License at *
* *
* http://www.apache.org/licenses/LICENSE-2.0 *
* *
* Unless required by applicable law or agreed to in writing, software *
* distributed under the License is distributed on an "AS IS" BASIS, *
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import PropTypes from 'prop-types'
import { memo, useCallback, useEffect, useState } from 'react'

import { Grid, TextField } from '@mui/material'
import { useController, useWatch } from 'react-hook-form'

import { ErrorHelper, Tooltip } from 'client/components/FormControl'
import { Tr, labelCanBeTranslated } from 'client/components/HOC'
import { T, UNITS } from 'client/constants'
import { generateKey, prettyBytes } from 'client/utils'

const ARRAY_UNITS = Object.values(UNITS)
ARRAY_UNITS.splice(0, 1) // remove KB
const DEFAULT_UNIT = ARRAY_UNITS[0]

const valueInMB = (value = 0, unit = DEFAULT_UNIT) => {
const idxUnit = ARRAY_UNITS.indexOf(unit)
const numberValue = +value

return Math.round(numberValue * (idxUnit <= 0 ? 1 : 1024 ** idxUnit))
}

const InformationUnitController = memo(
({
control,
cy = `input-${generateKey()}`,
name = '',
label = '',
tooltip,
watcher,
dependencies,
fieldProps = {},
readOnly = false,
onConditionChange,
}) => {
const watch = useWatch({
name: dependencies,
disabled: dependencies == null,
defaultValue: Array.isArray(dependencies) ? [] : undefined,
})

const {
field: { ref, value = '', onChange, ...inputProps },
fieldState: { error },
} = useController({ name, control })

useEffect(() => {
if (!watcher || !dependencies || !watch) return

const watcherValue = watcher(watch)
watcherValue !== undefined && onChange(watcherValue)
}, [watch, watcher, dependencies])

const [internalValue, setInternalValue] = useState(+value)
const [unit, setUnit] = useState(DEFAULT_UNIT)

useEffect(() => {
const dataUnits = prettyBytes(value, DEFAULT_UNIT, 2, true)
setInternalValue(dataUnits.value)
setUnit(dataUnits.units)
}, [value])

const handleChange = useCallback(
(internalType, valueInput) => {
if (internalType === 'value') {
setInternalValue(valueInput)
} else {
setUnit(valueInput)
}

const valueMB =
internalType === 'value'
? valueInMB(valueInput, unit)
: valueInMB(internalValue, valueInput)

onChange(valueMB)
if (typeof onConditionChange === 'function') {
onConditionChange(valueMB)
}
},
[onChange, onConditionChange]
)

return (
<div>
<Grid container spacing={1} width={1}>
<Grid item style={{ flexGrow: 1 }}>
<TextField
{...inputProps}
fullWidth
inputRef={ref}
value={internalValue}
onChange={(e) => handleChange('value', e.target.value)}
rows={3}
type="number"
label={labelCanBeTranslated(label) ? Tr(label) : label}
InputProps={{
readOnly,
endAdornment: tooltip && <Tooltip title={tooltip} />,
}}
inputProps={{
'data-cy': cy,
...{
min: fieldProps.min,
max: fieldProps.max,
step: fieldProps.step,
},
}}
error={Boolean(error)}
helperText={
error ? (
<ErrorHelper label={error?.message} />
) : (
fieldProps.helperText
)
}
FormHelperTextProps={{ 'data-cy': `${cy}-error` }}
{...fieldProps}
/>
</Grid>
<Grid item>
<TextField
select
value={unit}
InputProps={{
readOnly,
}}
label={Tr(T.MemoryUnit)}
onChange={(e) => handleChange('unit', e.target.value)}
>
{ARRAY_UNITS.map((option, index) => (
<option key={`${option}-${index}`} value={option}>
{option}
</option>
))}
</TextField>
</Grid>
</Grid>
</div>
)
},
(prevProps, nextProps) =>
prevProps.type === nextProps.type &&
prevProps.label === nextProps.label &&
prevProps.tooltip === nextProps.tooltip &&
prevProps.fieldProps?.value === nextProps.fieldProps?.value &&
prevProps.fieldProps?.helperText === nextProps.fieldProps?.helperText &&
prevProps.readOnly === nextProps.readOnly
)

InformationUnitController.propTypes = {
control: PropTypes.object,
cy: PropTypes.string,
type: PropTypes.string,
multiline: PropTypes.bool,
name: PropTypes.string.isRequired,
label: PropTypes.any,
tooltip: PropTypes.any,
watcher: PropTypes.func,
dependencies: PropTypes.oneOfType([
PropTypes.string,
PropTypes.arrayOf(PropTypes.string),
]),
fieldProps: PropTypes.oneOfType([PropTypes.object, PropTypes.bool]),
readOnly: PropTypes.bool,
onConditionChange: PropTypes.func,
}

InformationUnitController.displayName = 'InformationUnitController'

export default InformationUnitController
18 changes: 10 additions & 8 deletions src/fireedge/src/client/components/FormControl/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import AutocompleteController from 'client/components/FormControl/AutocompleteController'
import CheckboxController from 'client/components/FormControl/CheckboxController'
import FileController from 'client/components/FormControl/FileController'
import InformationUnitController from 'client/components/FormControl/InformationUnitController'
import PasswordController from 'client/components/FormControl/PasswordController'
import SelectController from 'client/components/FormControl/SelectController'
import SliderController from 'client/components/FormControl/SliderController'
Expand All @@ -25,30 +26,31 @@ import TextController from 'client/components/FormControl/TextController'
import TimeController from 'client/components/FormControl/TimeController'
import ToggleController from 'client/components/FormControl/ToggleController'

import DockerfileController from 'client/components/FormControl/DockerfileController'
import ErrorHelper from 'client/components/FormControl/ErrorHelper'
import InputCode from 'client/components/FormControl/InputCode'
import SubmitButton, {
SubmitButtonPropTypes,
} from 'client/components/FormControl/SubmitButton'
import InputCode from 'client/components/FormControl/InputCode'
import DockerfileController from 'client/components/FormControl/DockerfileController'
import ErrorHelper from 'client/components/FormControl/ErrorHelper'
import Tooltip from 'client/components/FormControl/Tooltip'

export {
AutocompleteController,
CheckboxController,
DockerfileController,
ErrorHelper,
FileController,
InformationUnitController,
InputCode,
PasswordController,
SelectController,
SliderController,
SubmitButton,
SubmitButtonPropTypes,
SwitchController,
TableController,
TextController,
TimeController,
ToggleController,
SubmitButton,
SubmitButtonPropTypes,
InputCode,
DockerfileController,
ErrorHelper,
Tooltip,
}
1 change: 1 addition & 0 deletions src/fireedge/src/client/components/Forms/FormWithSchema.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ const INPUT_CONTROLLER = {
[INPUT_TYPES.TABLE]: FC.TableController,
[INPUT_TYPES.TOGGLE]: FC.ToggleController,
[INPUT_TYPES.DOCKERFILE]: FC.DockerfileController,
[INPUT_TYPES.UNITS]: FC.InformationUnitController,
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,9 @@ const ENFORCE = {

const MEMORY = {
name: 'MEMORY',
label: [T.MemoryWithUnit, '(MB)'],
label: T.Memory,
tooltip: T.MemoryConcept,
type: INPUT_TYPES.TEXT,
type: INPUT_TYPES.UNITS,
htmlType: 'number',
validation: number()
.required()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@
* See the License for the specific language governing permissions and *
* limitations under the License. *
* ------------------------------------------------------------------------- */
import { number, object } from 'yup'
import { INPUT_TYPES, T } from 'client/constants'
import { getValidationFromFields } from 'client/utils'
import { T, INPUT_TYPES } from 'client/constants'
import { number, object } from 'yup'

const SIZE = {
name: 'SIZE',
label: [T.SizeOnUnits, 'MB'],
type: INPUT_TYPES.TEXT,
label: T.Size,
type: INPUT_TYPES.UNITS,
htmlType: 'number',
grid: { md: 12 },
validation: number()
.required()
.positive()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ const { vcenter, lxc, firecracker } = HYPERVISORS
export const MEMORY = generateCapacityInput({
name: 'MEMORY',
label: T.Memory,
tooltip: T.MemoryConceptWithoutUnit,
tooltip: T.MemoryConcept,
validation: commonValidation
.integer()
.required()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { NumberSchema } from 'yup'

import {
HYPERVISORS,
INPUT_TYPES,
T,
USER_INPUT_TYPES,
VmTemplate,
Expand All @@ -36,7 +37,7 @@ const { number, numberFloat, range, rangeFloat } = USER_INPUT_TYPES
const TRANSLATES = {
MEMORY: {
name: 'MEMORY',
label: [T.MemoryWithUnit, '(MB)'],
label: T.Memory,
tooltip: T.MemoryConcept,
},
CPU: { name: 'CPU', label: T.PhysicalCpuWithPercent, tooltip: T.CpuConcept },
Expand Down Expand Up @@ -98,9 +99,12 @@ export const FIELDS = (
// add positive number validator
isNumber && (schemaUi.validation &&= schemaUi.validation.positive())

if (isMemory && isRange) {
// add label format on pretty bytes
schemaUi.fieldProps = { ...schemaUi.fieldProps, valueLabelFormat }
if (isMemory) {
schemaUi.type = INPUT_TYPES.UNITS
if (isRange) {
// add label format on pretty bytes
schemaUi.fieldProps = { ...schemaUi.fieldProps, valueLabelFormat }
}
}

if (isNumber && divisibleBy4) {
Expand Down
3 changes: 2 additions & 1 deletion src/fireedge/src/client/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ export const INPUT_TYPES = {
TABLE: 'table',
TOGGLE: 'toggle',
DOCKERFILE: 'dockerfile',
UNITS: 'units',
}

export const DEBUG_LEVEL = {
Expand Down Expand Up @@ -209,6 +210,6 @@ export * from 'client/constants/user'
export * from 'client/constants/userInput'
export * from 'client/constants/vdc'
export * from 'client/constants/vm'
export * from 'client/constants/vmTemplate'
export * from 'client/constants/vmGroup'
export * from 'client/constants/vmTemplate'
export * from 'client/constants/zone'
3 changes: 1 addition & 2 deletions src/fireedge/src/client/constants/translates.js
Original file line number Diff line number Diff line change
Expand Up @@ -852,8 +852,7 @@ module.exports = {
MemoryModification: 'Memory modification',
AllowUsersToModifyMemory:
"Allow users to modify this template's default memory on instantiate",
MemoryConcept: 'Amount of RAM required for the VM, in Megabytes',
MemoryConceptWithoutUnit: 'Amount of RAM required for the VM',
MemoryConcept: 'Amount of RAM required for the VM',
MemoryConceptUnit: 'Choose unit of memory',
CpuConcept: `
Percentage of CPU divided by 100 required for the
Expand Down
15 changes: 12 additions & 3 deletions src/fireedge/src/client/utils/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,20 @@ export const downloadFile = (file) => {
* @param {'KB'|'MB'|'GB'|'TB'|'PB'|'EB'|'ZB'|'YB'} unit - The unit of value. Defaults in KB
* @param {number} fractionDigits
* - Number of digits after the decimal point. Must be in the range 0 - 20, inclusive
* @param {boolean} json - return a json with data
* @returns {string} Returns an string displaying sizes for humans.
*/
export const prettyBytes = (value, unit = UNITS.KB, fractionDigits = 0) => {
export const prettyBytes = (
value,
unit = UNITS.KB,
fractionDigits = 0,
json = false
) => {
const units = Object.values(UNITS)
let ensuredValue = +value

if (Math.abs(ensuredValue) === 0) return `${value} ${units[0]}`
if (Math.abs(ensuredValue) === 0)
return json ? { value, units: unit } : `${value} ${units[0]}`

let idxUnit = units.indexOf(unit)

Expand All @@ -140,7 +147,9 @@ export const prettyBytes = (value, unit = UNITS.KB, fractionDigits = 0) => {

const decimals = fractionDigits && ensuredValue % 1 !== 0 ? fractionDigits : 0

return `${ensuredValue.toFixed(decimals)} ${units[idxUnit]}`
return json
? { value: ensuredValue.toFixed(decimals), units: units[idxUnit] }
: `${ensuredValue.toFixed(decimals)} ${units[idxUnit]}`
}

/**
Expand Down

0 comments on commit d691152

Please sign in to comment.