foreman_resource_quota 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +13 -6
- data/app/assets/javascripts/foreman_resource_quota/locale/bn/foreman_resource_quota.js +213 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/bn_IN/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/bqi/foreman_resource_quota.js +213 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/ca/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/cs_CZ/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/de/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/de_AT/foreman_resource_quota.js +213 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/de_DE/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/el/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/en/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/en_GB/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/en_US/foreman_resource_quota.js +213 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/es/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/et_EE/foreman_resource_quota.js +213 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/fr/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/gl/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/gu/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/he_IL/foreman_resource_quota.js +213 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/hi/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/id/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/it/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/ja/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/ka/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/kn/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/ko/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/ml_IN/foreman_resource_quota.js +213 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/mr/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/nl_NL/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/or/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/pa/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/pl/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/pl_PL/foreman_resource_quota.js +213 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/pt/foreman_resource_quota.js +213 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/pt_BR/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/ro/foreman_resource_quota.js +213 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/ro_RO/foreman_resource_quota.js +213 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/ru/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/sl/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/sv_SE/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/ta/foreman_resource_quota.js +213 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/ta_IN/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/te/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/tr/foreman_resource_quota.js +213 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/vi/foreman_resource_quota.js +213 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/vi_VN/foreman_resource_quota.js +213 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/zh/foreman_resource_quota.js +213 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/zh_CN/foreman_resource_quota.js +214 -0
- data/app/assets/javascripts/foreman_resource_quota/locale/zh_TW/foreman_resource_quota.js +214 -0
- data/app/controllers/foreman_resource_quota/api/v2/resource_quotas_controller.rb +17 -10
- data/app/controllers/foreman_resource_quota/concerns/api/v2/hosts_controller_extensions.rb +20 -0
- data/app/controllers/foreman_resource_quota/concerns/api/v2/usergroups_controller_extensions.rb +19 -0
- data/app/controllers/foreman_resource_quota/concerns/api/v2/users_controller_extensions.rb +22 -0
- data/app/models/concerns/foreman_resource_quota/host_managed_extensions.rb +9 -7
- data/app/views/foreman_resource_quota/api/v2/users/resource_quota.json.rabl +1 -1
- data/app/views/foreman_resource_quota/resource_quotas/index.html.erb +1 -1
- data/config/initializers/inflections.rb +1 -0
- data/lib/foreman_resource_quota/engine.rb +8 -1
- data/lib/foreman_resource_quota/register.rb +4 -2
- data/lib/foreman_resource_quota/version.rb +1 -1
- data/lib/tasks/foreman_resource_quota_tasks.rake +3 -3
- data/locale/Makefile +19 -6
- data/locale/bn/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/bn/foreman_resource_quota.po +214 -0
- data/locale/bn_IN/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/bn_IN/foreman_resource_quota.po +219 -0
- data/locale/bqi/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/bqi/foreman_resource_quota.po +215 -0
- data/locale/ca/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/ca/foreman_resource_quota.po +218 -0
- data/locale/cs_CZ/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/cs_CZ/foreman_resource_quota.po +221 -0
- data/locale/de/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/de/foreman_resource_quota.po +223 -0
- data/locale/de_AT/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/de_AT/foreman_resource_quota.po +215 -0
- data/locale/de_DE/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/de_DE/foreman_resource_quota.po +219 -0
- data/locale/el/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/el/foreman_resource_quota.po +218 -0
- data/locale/en/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/en/foreman_resource_quota.po +207 -9
- data/locale/en_GB/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/en_GB/foreman_resource_quota.po +220 -0
- data/locale/en_US/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/en_US/foreman_resource_quota.po +215 -0
- data/locale/es/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/es/foreman_resource_quota.po +223 -0
- data/locale/et_EE/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/et_EE/foreman_resource_quota.po +215 -0
- data/locale/foreman_resource_quota.pot +322 -8
- data/locale/fr/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/fr/foreman_resource_quota.po +222 -0
- data/locale/gl/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/gl/foreman_resource_quota.po +218 -0
- data/locale/gu/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/gu/foreman_resource_quota.po +218 -0
- data/locale/he_IL/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/he_IL/foreman_resource_quota.po +216 -0
- data/locale/hi/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/hi/foreman_resource_quota.po +218 -0
- data/locale/id/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/id/foreman_resource_quota.po +218 -0
- data/locale/it/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/it/foreman_resource_quota.po +221 -0
- data/locale/ja/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/ja/foreman_resource_quota.po +219 -0
- data/locale/ka/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/ka/foreman_resource_quota.po +218 -0
- data/locale/kn/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/kn/foreman_resource_quota.po +218 -0
- data/locale/ko/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/ko/foreman_resource_quota.po +220 -0
- data/locale/ml_IN/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/ml_IN/foreman_resource_quota.po +215 -0
- data/locale/mr/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/mr/foreman_resource_quota.po +218 -0
- data/locale/nl_NL/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/nl_NL/foreman_resource_quota.po +223 -0
- data/locale/or/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/or/foreman_resource_quota.po +218 -0
- data/locale/pa/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/pa/foreman_resource_quota.po +219 -0
- data/locale/pl/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/pl/foreman_resource_quota.po +221 -0
- data/locale/pl_PL/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/pl_PL/foreman_resource_quota.po +217 -0
- data/locale/pt/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/pt/foreman_resource_quota.po +215 -0
- data/locale/pt_BR/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/pt_BR/foreman_resource_quota.po +222 -0
- data/locale/ro/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/ro/foreman_resource_quota.po +215 -0
- data/locale/ro_RO/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/ro_RO/foreman_resource_quota.po +216 -0
- data/locale/ru/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/ru/foreman_resource_quota.po +222 -0
- data/locale/sl/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/sl/foreman_resource_quota.po +219 -0
- data/locale/sv_SE/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/sv_SE/foreman_resource_quota.po +221 -0
- data/locale/ta/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/ta/foreman_resource_quota.po +214 -0
- data/locale/ta_IN/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/ta_IN/foreman_resource_quota.po +219 -0
- data/locale/te/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/te/foreman_resource_quota.po +218 -0
- data/locale/tr/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/tr/foreman_resource_quota.po +214 -0
- data/locale/vi/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/vi/foreman_resource_quota.po +214 -0
- data/locale/vi_VN/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/vi_VN/foreman_resource_quota.po +215 -0
- data/locale/zh/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/zh/foreman_resource_quota.po +214 -0
- data/locale/zh_CN/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/zh_CN/foreman_resource_quota.po +221 -0
- data/locale/zh_TW/LC_MESSAGES/foreman_resource_quota.mo +0 -0
- data/locale/zh_TW/foreman_resource_quota.po +220 -0
- data/package.json +10 -10
- data/webpack/components/ResourceQuotaEmptyState/__test__/ResourceQuotaEmptyState.test.js +35 -0
- data/webpack/components/ResourceQuotaEmptyState/__test__/__snapshots__/ResourceQuotaEmptyState.test.js.snap +80 -0
- data/webpack/components/ResourceQuotaEmptyState/index.js +20 -3
- data/webpack/components/ResourceQuotaForm/ResourceQuotaFormConstants.js +6 -6
- data/webpack/components/ResourceQuotaForm/components/Properties/TextInputField.js +1 -1
- data/webpack/components/ResourceQuotaForm/components/Properties/index.js +1 -1
- data/webpack/components/ResourceQuotaForm/components/Resource/UnitInputField.js +23 -21
- data/webpack/components/ResourceQuotaForm/components/Resource/__test__/UnitInputField.test.js +108 -0
- data/webpack/components/ResourceQuotaForm/components/Resource/__test__/__snapshots__/UnitInputField.test.js.snap +155 -0
- data/webpack/components/ResourceQuotaForm/components/Resource/index.js +1 -1
- data/webpack/components/ResourceQuotaForm/components/Submit.js +1 -1
- data/webpack/test_helper.js +49 -0
- metadata +158 -7
@@ -0,0 +1,35 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import '@testing-library/jest-dom';
|
3
|
+
|
4
|
+
import { mount, testComponentSnapshotsWithFixtures } from '@theforeman/test';
|
5
|
+
// Notice: (not) importing Modal affects the snapshot test since it fills
|
6
|
+
// the components data dynamically in snapshots as soon as it can find the component.
|
7
|
+
import { Modal } from '@patternfly/react-core';
|
8
|
+
|
9
|
+
import { withMockedProvider, withRedux } from '../../../test_helper';
|
10
|
+
import ResourceQuotaForm from '../../ResourceQuotaForm';
|
11
|
+
import ResourceQuotaEmptyState from '../index';
|
12
|
+
|
13
|
+
const TestComponent = withRedux(withMockedProvider(ResourceQuotaEmptyState));
|
14
|
+
|
15
|
+
describe('ResourceQuotaEmptyState', () => {
|
16
|
+
testComponentSnapshotsWithFixtures(ResourceQuotaEmptyState, {
|
17
|
+
'should render': {}, // component has no props
|
18
|
+
});
|
19
|
+
|
20
|
+
test('opens the modal on clicking "Create resource quota" button', () => {
|
21
|
+
const wrapper = mount(<TestComponent />);
|
22
|
+
|
23
|
+
expect(wrapper.find(Modal).prop('isOpen')).toBe(false); // check we provide the correct input to Modal
|
24
|
+
expect(wrapper.find(ResourceQuotaForm).exists()).toBe(false);
|
25
|
+
|
26
|
+
wrapper
|
27
|
+
.find('button')
|
28
|
+
.filterWhere(button => button.text() === 'Create resource quota')
|
29
|
+
.simulate('click');
|
30
|
+
wrapper.update();
|
31
|
+
|
32
|
+
expect(wrapper.find(Modal).prop('isOpen')).toBe(true);
|
33
|
+
expect(wrapper.find(ResourceQuotaForm).exists()).toBe(true);
|
34
|
+
});
|
35
|
+
});
|
@@ -0,0 +1,80 @@
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
2
|
+
|
3
|
+
exports[`ResourceQuotaEmptyState should render 1`] = `
|
4
|
+
<div>
|
5
|
+
<EmptyStatePattern
|
6
|
+
action={
|
7
|
+
<Button
|
8
|
+
id="foreman-resource-quota-welcome-create-modal-button"
|
9
|
+
onClick={[Function]}
|
10
|
+
variant="primary"
|
11
|
+
>
|
12
|
+
Create resource quota
|
13
|
+
</Button>
|
14
|
+
}
|
15
|
+
description={
|
16
|
+
<span>
|
17
|
+
Resource Quotas help admins to manage resources including CPUs, memory, and disk space among users or user groups.
|
18
|
+
<br />
|
19
|
+
Define a Resource Quota here and apply it to users to guarantee a fair share of your resources.
|
20
|
+
<br />
|
21
|
+
</span>
|
22
|
+
}
|
23
|
+
documentation={
|
24
|
+
Object {
|
25
|
+
"url": "/links/docs/Administering_Project?chapter=limiting-host-resources",
|
26
|
+
}
|
27
|
+
}
|
28
|
+
header="Resource Quotas"
|
29
|
+
icon="pficon pficon-cluster"
|
30
|
+
iconType="pf"
|
31
|
+
secondaryActions={Array []}
|
32
|
+
/>
|
33
|
+
<Modal
|
34
|
+
actions={Array []}
|
35
|
+
appendTo={<body />}
|
36
|
+
aria-describedby=""
|
37
|
+
aria-label=""
|
38
|
+
aria-labelledby=""
|
39
|
+
className=""
|
40
|
+
hasNoBodyWrapper={false}
|
41
|
+
isOpen={false}
|
42
|
+
onClose={[Function]}
|
43
|
+
ouiaId="foreman-resource-quota-create-modal"
|
44
|
+
ouiaSafe={true}
|
45
|
+
showClose={true}
|
46
|
+
title="Create resource quota"
|
47
|
+
titleIconVariant={null}
|
48
|
+
titleLabel=""
|
49
|
+
variant="small"
|
50
|
+
>
|
51
|
+
<ResourceQuotaForm
|
52
|
+
initialProperties={
|
53
|
+
Object {
|
54
|
+
"cpu_cores": null,
|
55
|
+
"description": "",
|
56
|
+
"disk_gb": null,
|
57
|
+
"memory_mb": null,
|
58
|
+
"name": "",
|
59
|
+
}
|
60
|
+
}
|
61
|
+
initialStatus={
|
62
|
+
Object {
|
63
|
+
"missing_hosts": null,
|
64
|
+
"number_of_hosts": null,
|
65
|
+
"number_of_usergroups": null,
|
66
|
+
"number_of_users": null,
|
67
|
+
"utilization": Object {
|
68
|
+
"cpu_cores": null,
|
69
|
+
"disk_gb": null,
|
70
|
+
"memory_mb": null,
|
71
|
+
},
|
72
|
+
}
|
73
|
+
}
|
74
|
+
isNewQuota={true}
|
75
|
+
onSubmit={[Function]}
|
76
|
+
quotaChangesCallback={null}
|
77
|
+
/>
|
78
|
+
</Modal>
|
79
|
+
</div>
|
80
|
+
`;
|
@@ -2,6 +2,7 @@ import React, { useState } from 'react';
|
|
2
2
|
import { Button, Modal, ModalVariant } from '@patternfly/react-core';
|
3
3
|
|
4
4
|
import { translate as __ } from 'foremanReact/common/I18n';
|
5
|
+
import { getDocsURL } from 'foremanReact/common/helpers';
|
5
6
|
import EmptyStatePattern from 'foremanReact/components/common/EmptyState/EmptyStatePattern';
|
6
7
|
|
7
8
|
import ResourceQuotaForm from '../ResourceQuotaForm';
|
@@ -28,16 +29,32 @@ const ResourceQuotaEmptyState = () => {
|
|
28
29
|
{__('Create resource quota')}
|
29
30
|
</Button>
|
30
31
|
);
|
32
|
+
|
33
|
+
const description = (
|
34
|
+
<span>
|
35
|
+
{__(
|
36
|
+
'Resource Quotas help admins to manage resources including CPUs, memory, and disk space among users or user groups.'
|
37
|
+
)}
|
38
|
+
<br />
|
39
|
+
{__(
|
40
|
+
'Define a Resource Quota here and apply it to users to guarantee a fair share of your resources.'
|
41
|
+
)}
|
42
|
+
<br />
|
43
|
+
</span>
|
44
|
+
);
|
45
|
+
const documentation = {
|
46
|
+
url: getDocsURL('Administering_Project', 'limiting-host-resources'),
|
47
|
+
};
|
48
|
+
|
31
49
|
return (
|
32
50
|
<div>
|
33
51
|
<EmptyStatePattern
|
34
52
|
icon="pficon pficon-cluster"
|
35
53
|
iconType="pf"
|
36
54
|
header={__('Resource Quotas')}
|
37
|
-
description={
|
38
|
-
'Resource Quotas help admins to manage hardware resources (like CPUs, RAM, and disk space) among users or usergroups. \n\rDefine a Resource Quota here and apply it to users in order to guarantee a free share of your resources.'
|
39
|
-
)}
|
55
|
+
description={description}
|
40
56
|
action={ActionButton}
|
57
|
+
documentation={documentation}
|
41
58
|
/>
|
42
59
|
<Modal
|
43
60
|
ouiaId={MODAL_ID_CREATE_RESOURCE_QUOTA}
|
@@ -19,14 +19,14 @@ export const RESOURCE_NAME_DISK = 'Disk space';
|
|
19
19
|
/* Resource units (order the units with increasing factor!) */
|
20
20
|
export const RESOURCE_UNIT_CPU = [{ symbol: 'cores', factor: 1 }];
|
21
21
|
export const RESOURCE_UNIT_MEMORY = [
|
22
|
-
{ symbol: '
|
23
|
-
{ symbol: '
|
24
|
-
{ symbol: '
|
22
|
+
{ symbol: 'MiB', factor: 1 },
|
23
|
+
{ symbol: 'GiB', factor: 1024 },
|
24
|
+
{ symbol: 'TiB', factor: 1024 * 1024 },
|
25
25
|
];
|
26
26
|
export const RESOURCE_UNIT_DISK = [
|
27
|
-
{ symbol: '
|
28
|
-
{ symbol: '
|
29
|
-
{ symbol: '
|
27
|
+
{ symbol: 'GiB', factor: 1 },
|
28
|
+
{ symbol: 'TiB', factor: 1024 },
|
29
|
+
{ symbol: 'PiB', factor: 1024 * 1024 },
|
30
30
|
];
|
31
31
|
|
32
32
|
/* Resource value bounds */
|
@@ -2,6 +2,7 @@ import React, { useState, useEffect, useCallback } from 'react';
|
|
2
2
|
import PropTypes from 'prop-types';
|
3
3
|
import {
|
4
4
|
FormGroup,
|
5
|
+
FormHelperText,
|
5
6
|
TextInput,
|
6
7
|
InputGroup,
|
7
8
|
InputGroupText,
|
@@ -60,17 +61,19 @@ const UnitInputField = ({
|
|
60
61
|
}, [minValue, maxValue, selectedUnit]);
|
61
62
|
|
62
63
|
/* text for float errors */
|
63
|
-
const errorTextNatural = useCallback(
|
64
|
-
() => __('Value must be a natural number.'),
|
65
|
-
[]
|
66
|
-
);
|
64
|
+
const errorTextNatural = useCallback(() => __('Value must be a number.'), []);
|
67
65
|
|
68
|
-
/* text for float
|
69
|
-
const
|
70
|
-
|
66
|
+
/* text for float inputs (rounding) */
|
67
|
+
const warningTextRounded = useCallback(
|
68
|
+
roundedValue => __(`Rounding to: ${roundedValue} (${units[0].symbol}).`),
|
71
69
|
[units]
|
72
70
|
);
|
73
71
|
|
72
|
+
/* warning text displayed beneath value input field (built-in is used for errors) */
|
73
|
+
const helperTextWarning = (text, isHidden) => (
|
74
|
+
<FormHelperText isHidden={isHidden}>{text}</FormHelperText>
|
75
|
+
);
|
76
|
+
|
74
77
|
/* applies the selected unit and checks the bounds */
|
75
78
|
const isValid = useCallback(
|
76
79
|
val => {
|
@@ -83,20 +86,9 @@ const UnitInputField = ({
|
|
83
86
|
setErrorText(errorTextBounds());
|
84
87
|
return false;
|
85
88
|
}
|
86
|
-
if (baseValue !== Math.floor(baseValue)) {
|
87
|
-
setErrorText(errorTextFloating());
|
88
|
-
return false;
|
89
|
-
}
|
90
89
|
return true;
|
91
90
|
},
|
92
|
-
[
|
93
|
-
minValue,
|
94
|
-
maxValue,
|
95
|
-
valueToBaseUnit,
|
96
|
-
errorTextNatural,
|
97
|
-
errorTextBounds,
|
98
|
-
errorTextFloating,
|
99
|
-
]
|
91
|
+
[minValue, maxValue, valueToBaseUnit, errorTextNatural, errorTextBounds]
|
100
92
|
);
|
101
93
|
|
102
94
|
/* applies the selected unit and returns the base-unit value */
|
@@ -116,9 +108,17 @@ const UnitInputField = ({
|
|
116
108
|
setValidated('default');
|
117
109
|
} else if (isValid(inputValue)) {
|
118
110
|
const baseValue = valueToBaseUnit(inputValue);
|
119
|
-
|
111
|
+
let validatedValue = baseValue;
|
112
|
+
if (baseValue !== Math.floor(baseValue)) {
|
113
|
+
validatedValue = Math.floor(baseValue);
|
114
|
+
setErrorText(warningTextRounded(validatedValue));
|
115
|
+
setValidated('warning');
|
116
|
+
} else {
|
117
|
+
// Keep baseValue as validatedValue
|
118
|
+
setValidated('default');
|
119
|
+
}
|
120
|
+
onChange(validatedValue);
|
120
121
|
handleInputValidation(true);
|
121
|
-
setValidated('default');
|
122
122
|
} else {
|
123
123
|
handleInputValidation(false);
|
124
124
|
setValidated('error');
|
@@ -131,6 +131,7 @@ const UnitInputField = ({
|
|
131
131
|
onChange,
|
132
132
|
isValid,
|
133
133
|
valueToBaseUnit,
|
134
|
+
warningTextRounded,
|
134
135
|
]);
|
135
136
|
|
136
137
|
/* set the selected unit */
|
@@ -181,6 +182,7 @@ const UnitInputField = ({
|
|
181
182
|
label={__('Quota Limit')}
|
182
183
|
validated={validated}
|
183
184
|
helperTextInvalid={errorText}
|
185
|
+
helperText={helperTextWarning(errorText, validated !== 'warning')}
|
184
186
|
fieldId="quota-limit-resource-quota-form-group"
|
185
187
|
labelIcon={labelIcon || {}}
|
186
188
|
>
|
@@ -0,0 +1,108 @@
|
|
1
|
+
import React from 'react';
|
2
|
+
import '@testing-library/jest-dom';
|
3
|
+
import { render, screen, fireEvent } from '@testing-library/react';
|
4
|
+
|
5
|
+
import { testComponentSnapshotsWithFixtures } from '@theforeman/test';
|
6
|
+
import LabelIcon from 'foremanReact/components/common/LabelIcon';
|
7
|
+
|
8
|
+
import UnitInputField from '../UnitInputField';
|
9
|
+
|
10
|
+
const getDefaultProps = () => ({
|
11
|
+
initialValue: 0,
|
12
|
+
onChange: jest.fn(),
|
13
|
+
isDisabled: false,
|
14
|
+
handleInputValidation: jest.fn(),
|
15
|
+
units: [
|
16
|
+
{ symbol: 'MiB', factor: 1 },
|
17
|
+
{ symbol: 'GiB', factor: 1024 },
|
18
|
+
],
|
19
|
+
labelIcon: <LabelIcon text="Descriptive title." />,
|
20
|
+
minValue: 0,
|
21
|
+
maxValue: 5,
|
22
|
+
});
|
23
|
+
|
24
|
+
const fixtureDefault = {
|
25
|
+
'should render default': {
|
26
|
+
...getDefaultProps(),
|
27
|
+
},
|
28
|
+
};
|
29
|
+
|
30
|
+
const fixtureSingleUnit = {
|
31
|
+
'should render without dropdown (single unit)': {
|
32
|
+
...getDefaultProps(),
|
33
|
+
units: [{ symbol: 'cores', factor: 1 }],
|
34
|
+
},
|
35
|
+
};
|
36
|
+
|
37
|
+
const fixtureDisabled = {
|
38
|
+
'should render as disabled field': {
|
39
|
+
...getDefaultProps(),
|
40
|
+
isDisabled: true,
|
41
|
+
},
|
42
|
+
};
|
43
|
+
|
44
|
+
describe('UnitInputField', () => {
|
45
|
+
testComponentSnapshotsWithFixtures(UnitInputField, fixtureDefault);
|
46
|
+
testComponentSnapshotsWithFixtures(UnitInputField, fixtureSingleUnit);
|
47
|
+
testComponentSnapshotsWithFixtures(UnitInputField, fixtureDisabled);
|
48
|
+
|
49
|
+
it('triggers handleInputValidation on unit change', async () => {
|
50
|
+
const props = getDefaultProps();
|
51
|
+
|
52
|
+
render(<UnitInputField {...props} />);
|
53
|
+
const input = screen.getByRole('textbox');
|
54
|
+
fireEvent.change(input, { target: { value: 3 } });
|
55
|
+
|
56
|
+
// gets called (1.) with initialValue and (2.) the simulated change
|
57
|
+
expect(props.onChange).toHaveBeenCalledTimes(2);
|
58
|
+
expect(props.onChange).toHaveBeenCalledWith(props.initialValue);
|
59
|
+
expect(props.onChange).toHaveBeenLastCalledWith(3);
|
60
|
+
expect(props.handleInputValidation).toHaveBeenCalledTimes(2);
|
61
|
+
expect(props.handleInputValidation).toHaveBeenCalledWith(true);
|
62
|
+
});
|
63
|
+
|
64
|
+
test('triggers onChange with rounded value', () => {
|
65
|
+
const props = getDefaultProps();
|
66
|
+
|
67
|
+
render(<UnitInputField {...props} />);
|
68
|
+
const input = screen.getByRole('textbox');
|
69
|
+
fireEvent.change(input, { target: { value: 3.5 } });
|
70
|
+
|
71
|
+
// gets called (1.) with initialValue and (2.) the simulated change
|
72
|
+
expect(props.onChange).toHaveBeenCalledTimes(2);
|
73
|
+
expect(props.onChange).toHaveBeenCalledWith(props.initialValue);
|
74
|
+
expect(props.onChange).toHaveBeenLastCalledWith(3);
|
75
|
+
expect(props.handleInputValidation).toHaveBeenCalledTimes(2);
|
76
|
+
expect(props.handleInputValidation).toHaveBeenCalledWith(true);
|
77
|
+
});
|
78
|
+
|
79
|
+
test('does not trigger onChange when value out of bounds', () => {
|
80
|
+
const props = getDefaultProps();
|
81
|
+
|
82
|
+
render(<UnitInputField {...props} />);
|
83
|
+
const input = screen.getByRole('textbox');
|
84
|
+
fireEvent.change(input, { target: { value: props.maxValue + 1 } });
|
85
|
+
|
86
|
+
// onChange only called for initialValue
|
87
|
+
expect(props.onChange).toHaveBeenCalledTimes(1);
|
88
|
+
expect(props.onChange).toHaveBeenCalledWith(props.initialValue);
|
89
|
+
// handleInputValidation called with false => invalid
|
90
|
+
expect(props.handleInputValidation).toHaveBeenCalledTimes(2);
|
91
|
+
expect(props.handleInputValidation).toHaveBeenLastCalledWith(false);
|
92
|
+
});
|
93
|
+
|
94
|
+
test('does not trigger onChange when value is not a number', () => {
|
95
|
+
const props = getDefaultProps();
|
96
|
+
|
97
|
+
render(<UnitInputField {...props} />);
|
98
|
+
const input = screen.getByRole('textbox');
|
99
|
+
fireEvent.change(input, { target: { value: 'no number' } });
|
100
|
+
|
101
|
+
// onChange only called for initialValue
|
102
|
+
expect(props.onChange).toHaveBeenCalledTimes(1);
|
103
|
+
expect(props.onChange).toHaveBeenCalledWith(props.initialValue);
|
104
|
+
// handleInputValidation called with false => invalid
|
105
|
+
expect(props.handleInputValidation).toHaveBeenCalledTimes(2);
|
106
|
+
expect(props.handleInputValidation).toHaveBeenLastCalledWith(false);
|
107
|
+
});
|
108
|
+
});
|
@@ -0,0 +1,155 @@
|
|
1
|
+
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
2
|
+
|
3
|
+
exports[`UnitInputField should render as disabled field 1`] = `
|
4
|
+
<FormGroup
|
5
|
+
fieldId="quota-limit-resource-quota-form-group"
|
6
|
+
helperText={
|
7
|
+
<FormHelperText
|
8
|
+
isHidden={true}
|
9
|
+
>
|
10
|
+
|
11
|
+
</FormHelperText>
|
12
|
+
}
|
13
|
+
helperTextInvalid=""
|
14
|
+
label="Quota Limit"
|
15
|
+
labelIcon={
|
16
|
+
<LabelIcon
|
17
|
+
text="Descriptive title."
|
18
|
+
/>
|
19
|
+
}
|
20
|
+
validated="default"
|
21
|
+
>
|
22
|
+
<InputGroup>
|
23
|
+
<TextInput
|
24
|
+
id="reg_token_life_time_input"
|
25
|
+
isDisabled={true}
|
26
|
+
max={5}
|
27
|
+
min={0}
|
28
|
+
onChange={[Function]}
|
29
|
+
validated="default"
|
30
|
+
value={0}
|
31
|
+
/>
|
32
|
+
<Dropdown
|
33
|
+
dropdownItems={
|
34
|
+
Array [
|
35
|
+
<DropdownItem
|
36
|
+
id="unit-dropdownitem-mib"
|
37
|
+
>
|
38
|
+
MiB
|
39
|
+
</DropdownItem>,
|
40
|
+
<DropdownItem
|
41
|
+
id="unit-dropdownitem-gib"
|
42
|
+
>
|
43
|
+
GiB
|
44
|
+
</DropdownItem>,
|
45
|
+
]
|
46
|
+
}
|
47
|
+
isOpen={false}
|
48
|
+
onSelect={[Function]}
|
49
|
+
toggle={
|
50
|
+
<DropdownToggle
|
51
|
+
isDisabled={true}
|
52
|
+
onToggle={[Function]}
|
53
|
+
>
|
54
|
+
MiB
|
55
|
+
</DropdownToggle>
|
56
|
+
}
|
57
|
+
/>
|
58
|
+
</InputGroup>
|
59
|
+
</FormGroup>
|
60
|
+
`;
|
61
|
+
|
62
|
+
exports[`UnitInputField should render default 1`] = `
|
63
|
+
<FormGroup
|
64
|
+
fieldId="quota-limit-resource-quota-form-group"
|
65
|
+
helperText={
|
66
|
+
<FormHelperText
|
67
|
+
isHidden={true}
|
68
|
+
>
|
69
|
+
|
70
|
+
</FormHelperText>
|
71
|
+
}
|
72
|
+
helperTextInvalid=""
|
73
|
+
label="Quota Limit"
|
74
|
+
labelIcon={
|
75
|
+
<LabelIcon
|
76
|
+
text="Descriptive title."
|
77
|
+
/>
|
78
|
+
}
|
79
|
+
validated="default"
|
80
|
+
>
|
81
|
+
<InputGroup>
|
82
|
+
<TextInput
|
83
|
+
id="reg_token_life_time_input"
|
84
|
+
isDisabled={false}
|
85
|
+
max={5}
|
86
|
+
min={0}
|
87
|
+
onChange={[Function]}
|
88
|
+
validated="default"
|
89
|
+
value={0}
|
90
|
+
/>
|
91
|
+
<Dropdown
|
92
|
+
dropdownItems={
|
93
|
+
Array [
|
94
|
+
<DropdownItem
|
95
|
+
id="unit-dropdownitem-mib"
|
96
|
+
>
|
97
|
+
MiB
|
98
|
+
</DropdownItem>,
|
99
|
+
<DropdownItem
|
100
|
+
id="unit-dropdownitem-gib"
|
101
|
+
>
|
102
|
+
GiB
|
103
|
+
</DropdownItem>,
|
104
|
+
]
|
105
|
+
}
|
106
|
+
isOpen={false}
|
107
|
+
onSelect={[Function]}
|
108
|
+
toggle={
|
109
|
+
<DropdownToggle
|
110
|
+
isDisabled={false}
|
111
|
+
onToggle={[Function]}
|
112
|
+
>
|
113
|
+
MiB
|
114
|
+
</DropdownToggle>
|
115
|
+
}
|
116
|
+
/>
|
117
|
+
</InputGroup>
|
118
|
+
</FormGroup>
|
119
|
+
`;
|
120
|
+
|
121
|
+
exports[`UnitInputField should render without dropdown (single unit) 1`] = `
|
122
|
+
<FormGroup
|
123
|
+
fieldId="quota-limit-resource-quota-form-group"
|
124
|
+
helperText={
|
125
|
+
<FormHelperText
|
126
|
+
isHidden={true}
|
127
|
+
>
|
128
|
+
|
129
|
+
</FormHelperText>
|
130
|
+
}
|
131
|
+
helperTextInvalid=""
|
132
|
+
label="Quota Limit"
|
133
|
+
labelIcon={
|
134
|
+
<LabelIcon
|
135
|
+
text="Descriptive title."
|
136
|
+
/>
|
137
|
+
}
|
138
|
+
validated="default"
|
139
|
+
>
|
140
|
+
<InputGroup>
|
141
|
+
<TextInput
|
142
|
+
id="reg_token_life_time_input"
|
143
|
+
isDisabled={false}
|
144
|
+
max={5}
|
145
|
+
min={0}
|
146
|
+
onChange={[Function]}
|
147
|
+
validated="default"
|
148
|
+
value={0}
|
149
|
+
/>
|
150
|
+
<InputGroupText>
|
151
|
+
cores
|
152
|
+
</InputGroupText>
|
153
|
+
</InputGroup>
|
154
|
+
</FormGroup>
|
155
|
+
`;
|
@@ -31,7 +31,7 @@ const Submit = ({ isValid, onCreate, onSubmit }) => {
|
|
31
31
|
dispatchAPICallbackToast(
|
32
32
|
success,
|
33
33
|
response,
|
34
|
-
`
|
34
|
+
`Successfully created new Resource Quota`,
|
35
35
|
`An error occurred while creating new Resource Quota.`
|
36
36
|
)
|
37
37
|
);
|
@@ -0,0 +1,49 @@
|
|
1
|
+
/* Credits: https://github.com/theforeman/foreman_ansible/blob/master/webpack/testHelper.js */
|
2
|
+
import React, { useState } from 'react';
|
3
|
+
import { applyMiddleware, createStore, compose, combineReducers } from 'redux';
|
4
|
+
import { reducers as apiReducer, APIMiddleware } from 'foremanReact/redux/API';
|
5
|
+
import { Provider } from 'react-redux';
|
6
|
+
import { MockedProvider } from '@apollo/react-testing';
|
7
|
+
import thunk from 'redux-thunk';
|
8
|
+
|
9
|
+
import ConfirmModal, {
|
10
|
+
reducers as confirmModalReducers,
|
11
|
+
} from 'foremanReact/components/ConfirmModal';
|
12
|
+
import { getForemanContext } from 'foremanReact/Root/Context/ForemanContext';
|
13
|
+
|
14
|
+
const reducers = combineReducers({ ...apiReducer, ...confirmModalReducers });
|
15
|
+
export const generateStore = () =>
|
16
|
+
createStore(reducers, compose(applyMiddleware(thunk, APIMiddleware)));
|
17
|
+
|
18
|
+
// use to resolve async mock requests for apollo MockedProvider
|
19
|
+
export const tick = () => new Promise(resolve => setTimeout(resolve, 0));
|
20
|
+
|
21
|
+
export const withRedux = Component => props => (
|
22
|
+
<Provider store={generateStore()}>
|
23
|
+
<Component {...props} />
|
24
|
+
<ConfirmModal />
|
25
|
+
</Provider>
|
26
|
+
);
|
27
|
+
|
28
|
+
export const withMockedProvider = Component => props => {
|
29
|
+
const [context, setContext] = useState({
|
30
|
+
metadata: {
|
31
|
+
UISettings: {
|
32
|
+
perPage: 20,
|
33
|
+
},
|
34
|
+
},
|
35
|
+
});
|
36
|
+
const contextData = { context, setContext };
|
37
|
+
const ForemanContext = getForemanContext(contextData);
|
38
|
+
|
39
|
+
// eslint-disable-next-line react/prop-types
|
40
|
+
const { mocks, ...rest } = props;
|
41
|
+
|
42
|
+
return (
|
43
|
+
<ForemanContext.Provider value={contextData}>
|
44
|
+
<MockedProvider mocks={mocks}>
|
45
|
+
<Component {...rest} />
|
46
|
+
</MockedProvider>
|
47
|
+
</ForemanContext.Provider>
|
48
|
+
);
|
49
|
+
};
|