Skip to content

Commit

Permalink
Rewriting Tenant Manage Quotas Form to React
Browse files Browse the repository at this point in the history
  • Loading branch information
DavidResende0 committed Sep 28, 2022
1 parent 005e2d5 commit 938a7f9
Show file tree
Hide file tree
Showing 15 changed files with 3,918 additions and 371 deletions.
38 changes: 2 additions & 36 deletions app/controllers/ops_controller/ops_rbac.rb
Original file line number Diff line number Diff line change
Expand Up @@ -140,35 +140,13 @@ def rbac_tenant_edit
end

def rbac_tenant_manage_quotas_cancel
@tenant = Tenant.find(params[:id])
add_flash(_("Manage quotas for %{model}\ \"%{name}\" was cancelled by the user") %
{:model => tenant_type_title_string(@tenant.divisible), :name => @tenant.name})
get_node_info(x_node)
replace_right_cell(:nodetype => x_node)
end

def rbac_tenant_manage_quotas_save_add
tenant = Tenant.find(params[:id])
begin
tenant.set_quotas(rbac_tenant_manage_quotas_params.to_h.deep_symbolize_keys)
rescue => bang
add_flash(_("Error when saving tenant quota: %{message}") % {:message => bang.message}, :error)
javascript_flash
else
add_flash(_("Quotas for %{model} \"%{name}\" were saved") %
{:model => tenant_type_title_string(tenant.divisible), :name => tenant.name})
get_node_info(x_node)
replace_right_cell(:nodetype => "root", :replace_trees => [:rbac])
end
end

private def rbac_tenant_manage_quotas_params
if params[:quotas]
permitted_attrs = TenantQuota::NAMES.index_with { %i[unit value warn_value] }
params.require(:quotas).permit(permitted_attrs)
else
{}
end
get_node_info(x_node)
replace_right_cell(:nodetype => "root", :replace_trees => [:rbac])
end

def rbac_tenant_manage_quotas_reset
Expand All @@ -177,7 +155,6 @@ def rbac_tenant_manage_quotas_reset
@edit = {:tenant_id => @tenant.id}
session[:edit] = {:key => "tenant_manage_quotas__#{@tenant.id}"}
session[:changed] = false
add_flash(_("All changes have been reset"), :warning) if params[:button] == 'reset'
replace_right_cell(:nodetype => "tenant_manage_quotas")
end

Expand All @@ -193,17 +170,6 @@ def rbac_tenant_manage_quotas
end
end

def tenant_quotas_form_fields
assert_privileges("rbac_tenant_manage_quotas")

tenant = Tenant.find(params[:id])
tenant_quotas = tenant.get_quotas
render :json => {
:name => tenant.name,
:quotas => tenant_quotas
}
end

# Edit user or group tags
def rbac_tenant_tags_edit
case params[:button]
Expand Down
4 changes: 4 additions & 0 deletions app/javascript/components/miq-data-table/helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export const CellElements = {
icon: 'icon',
image: 'image',
text: 'text',
textinput: 'is_textinput',
toggle: 'is_toggle',
};

/** Click events in a cell. */
Expand Down Expand Up @@ -36,6 +38,8 @@ export const isNumber = (data) => typeof (data) === 'number';
export const isNull = (data) => (data === null);
export const hasImage = (keys, data) => keys.includes(CellElements.image) && data.image && data.image.length > 0;
export const hasButton = (keys) => keys.includes(CellElements.button);
export const hasTextInput = (keys) => keys.includes(CellElements.textinput);
export const hasToggle = (keys) => keys.includes(CellElements.toggle);
export const hasText = (data) => Object.keys(data).includes(CellElements.text);
const hasValue = (data) => Object.keys(data).includes('value');

Expand Down
46 changes: 43 additions & 3 deletions app/javascript/components/miq-data-table/miq-table-cell.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Button, TableCell } from 'carbon-components-react';
import { Button, TableCell, TextInput, Toggle } from 'carbon-components-react';
import classNames from 'classnames';
import {
CellAction, hasIcon, hasImage, hasButton, isObject, isArray, isNumber, decimalCount,
CellAction, hasIcon, hasImage, hasButton, hasTextInput, hasToggle, isObject, isArray, isNumber, decimalCount,
} from './helper';

const MiqTableCell = ({
Expand Down Expand Up @@ -92,10 +92,46 @@ const MiqTableCell = ({
</div>
);

/** Function to render a Text Box inside cell. */
/* eslint-disable no-eval */
const cellTextInput = (item, id) => (
<div className={cellClass}>
<TextInput
id={id}
className={item.className}
labelText={truncateText}
placeholder={item.placeholder}
defaultValue={item.value}
invalid={item.invalid}
invalidText={item.invalidText}
type={item.type}
readOnly={item.readonly}
disabled={item.disabled}
tabIndex={0}
onChange={item.onchange}
/>
</div>
);

/** Function to render a Toggle inside cell. */
/* eslint-disable no-eval */
const cellToggle = (item, id) => (
<div className={cellClass}>
<Toggle
id={id}
labelText={truncateText}
toggled={item.toggled}
onToggle={() => (item.ontoggle() ? item.ontoggle() : undefined)}
disabled={item.disabled}
tabIndex={0}
/>
</div>
);

/** Determines which component has to be rendered inside a cell.
* Also to determine if a click event necesseary for a cell or its component . */
const cellComponent = () => {
const { data } = cell;
const { data, id } = cell;
const keys = Object.keys(data);
const content = { component: '', cellClick: !!onCellClick, showText: true };
if (isObject(data)) {
Expand All @@ -108,6 +144,10 @@ const MiqTableCell = ({

if (hasButton(keys)) return { ...content, component: cellButton(data), cellClick: false };

if (hasToggle(keys)) return { ...content, component: cellToggle(data, id), cellClick: false };

if (hasTextInput(keys)) return { ...content, component: cellTextInput(data, id), cellClick: false };

return { ...content, component: cellText() };
}
return { ...content, component: cellText() };
Expand Down
171 changes: 171 additions & 0 deletions app/javascript/components/tenant-quota-form/helper.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
const GIGABYTE = 1024 * 1024 * 1024;

// Creates the Rows of the MiqDataTable
export const createRows = (initialValues, enforced, setEnforced, values, setValues, setDisabled, invalid, setInvalid) => {
/* Determines whether the reset/save buttons should be disabled
based on whether the form has changed or if an inputted value is invalid */
const isDisabled = () => {
let fresh = true;
if (Object.values(invalid).every((value) => value.bool === false)) {
// eslint-disable-next-line no-restricted-syntax
for (const key in enforced) {
if (initialValues.enforced[key] !== enforced[key] || initialValues.values[key] !== values[key]) {
fresh = false;
break;
}
}
}
setDisabled(fresh);
};

/* Determines whether the "value" text input of a particular row should be readonly
based on the value of the enforced toggle */
const enforce = (index) => {
enforced[index] = !enforced[index];

if (enforced[index] === true) {
values[index] = '';
Array.from(document.querySelectorAll('.quota-table-input')).forEach((input, key) => {
if (key === index) {
input.value = '';
}
});
setValues(() => ({ ...values }));
}

setEnforced(() => ({ ...enforced }));
isDisabled();
};

// Updates the value of a particular row to state
const updateValues = (index, target) => {
values[index] = target.value;
setValues(() => ({ ...values }));
isDisabled();
};

// Validates text input by ensuring no negative numbers are submitted or non-integer values when unit = fixnum
const validate = (index, value) => {
if (values[index] !== '') {
if (values[index] <= 0) {
invalid[index].bool = true;
} else if (!Number.isInteger(parseFloat(values[index])) && value.unit === 'fixnum') {
invalid[index].bool = true;
} else {
invalid[index].bool = false;
}
} else {
invalid[index].bool = false;
}

setInvalid(() => ({ ...invalid }));
isDisabled();
};

// The rows of the table are created here (quotas)
const quotas = Object.values(initialValues.data.quota_definitions);

quotas.forEach((value, index, array) => {
array[index] = {
id: index.toString(),
Enforced: {
is_toggle: true,
text: null,
title: __('Enforce a Value'),
alt: __('Enforce a Value'),
ontoggle: () => enforce(index),
toggled: !enforced[index],
},
Description: {
text: value.description,
},
Value: {
is_textinput: true,
className: 'quota-table-input',
text: null,
placeholder: __('Not Enforced'),
value: values[index],
invalid: invalid[index].bool,
invalidText: invalid[index].text,
type: 'number',
readonly: enforced[index],
onchange: ({ target }) => {
updateValues(index, target);
validate(index, value);
},
},
Units: {
text: value.text_modifier,
},
};
});

return quotas;
};

// Manipulates the data retrived from the API before it's saved to state in the useEffect
export const setupForm = (initialValues, resources, name) => {
const modifiedInitialValues = {
name, values: [], enforced: [], invalid: [], ...initialValues,
};

Object.keys(modifiedInitialValues.data.quota_definitions).forEach((key, index) => {
for (let x = 0; x < resources.length; x += 1) {
if (resources[x].name === key) {
modifiedInitialValues.data.quota_definitions[key] = {
...modifiedInitialValues.data.quota_definitions[key],
value: resources[x].value,
id: resources[x].id,
href: resources[x].href,
};
break;
}
}

modifiedInitialValues.enforced[index] = !!!modifiedInitialValues.data.quota_definitions[key].value;
if (modifiedInitialValues.data.quota_definitions[key].unit === 'bytes') {
modifiedInitialValues.invalid[index] = { bool: false, text: __('Value must be a positive number') };
modifiedInitialValues.values[index] = modifiedInitialValues.data.quota_definitions[key].value
? (modifiedInitialValues.data.quota_definitions[key].value / GIGABYTE).toString() : '';
} else {
modifiedInitialValues.invalid[index] = { bool: false, text: __('Value must be a positive integer') };
modifiedInitialValues.values[index] = modifiedInitialValues.data.quota_definitions[key].value
? modifiedInitialValues.data.quota_definitions[key].value.toString() : '';
}
});

return modifiedInitialValues;
};

// Takes all the quotas to be created/edited/deleted and seperates them into three different API calls
export const prepareData = (initialValues = {}, values = {}) => {
const quotasToCreate = { action: 'create', resources: [] };
const quotasToEdit = { action: 'edit', resources: [] };
const quotasToDelete = { action: 'delete', resources: [] };

Object.keys(initialValues.data.quota_definitions).forEach((key, index) => {
let value = values[index];

if (value === '') {
if (initialValues.data.quota_definitions[key].id !== undefined) {
quotasToDelete.resources.push({ href: initialValues.data.quota_definitions[key].href });
}
// eslint-disable-next-line no-empty
} else if (value === initialValues.values[index]) {
} else {
if (initialValues.data.quota_definitions[key].unit === 'bytes') {
value = parseFloat(value) * GIGABYTE;
} else if (initialValues.data.quota_definitions[key].unit === 'fixnum') {
value = parseInt(value, 10);
}

if (initialValues.data.quota_definitions[key].id === undefined) {
quotasToCreate.resources.push({ name: key, value });
} else {
quotasToEdit.resources.push({ href: initialValues.data.quota_definitions[key].href, value });
}
}
});

return { quotasToCreate, quotasToEdit, quotasToDelete };
};
Loading

0 comments on commit 938a7f9

Please sign in to comment.