Skip to content

Commit 79fc0f5

Browse files
committed
Converted User Visual Settings form to be API-driven with DDF
1 parent 28199e7 commit 79fc0f5

File tree

6 files changed

+247
-0
lines changed

6 files changed

+247
-0
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import React, { useState, useEffect } from 'react';
2+
import PropTypes from 'prop-types';
3+
4+
import MiqFormRenderer from '@@ddf';
5+
import createSchema from './visual-settings-form.schema';
6+
7+
const VisualSettingsForm = ({ recordId }) => {
8+
const [{ initialValues, isLoading }, setState] = useState({ isLoading: true });
9+
10+
useEffect(() => {
11+
API.get(`/api/users/${recordId}?attributes=settings`).then(({ settings }) => setState({
12+
initialValues: settings,
13+
isLoading: false,
14+
}));
15+
}, [recordId]);
16+
17+
const onSubmit = (settings) => {
18+
miqSparkleOn();
19+
API.patch(`/api/users/${recordId}`, { settings }).then(() => {
20+
// flash message
21+
}).catch(miqSparkleOff);
22+
};
23+
24+
return !isLoading && (
25+
<MiqFormRenderer
26+
schema={createSchema()}
27+
initialValues={initialValues}
28+
onSubmit={onSubmit}
29+
canReset
30+
/>
31+
);
32+
};
33+
34+
VisualSettingsForm.propTypes = {
35+
recordId: PropTypes.string.isRequired,
36+
};
37+
38+
export default VisualSettingsForm;
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
import { componentTypes } from '@@ddf';
2+
3+
const createSchema = () => ({
4+
fields: [
5+
{
6+
component: componentTypes.SUB_FORM,
7+
name: 'general-subform',
8+
title: __('General'),
9+
fields: [
10+
{
11+
component: componentTypes.SELECT,
12+
name: 'view.compare',
13+
id: 'view.compare',
14+
label: __('Compare'),
15+
initialValue: 'expanded',
16+
options: [
17+
{ label: __('Expanded'), value: 'expanded' },
18+
{ label: __('Compressed'), value: 'compressed' },
19+
],
20+
},
21+
{
22+
component: componentTypes.SELECT,
23+
name: 'view.compare__mode',
24+
id: 'view.compare__mode',
25+
label: __('Compare Mode'),
26+
initialValue: 'details',
27+
options: [
28+
{ label: __('Details'), value: 'details' },
29+
{ label: __('Exists'), value: 'exists' },
30+
],
31+
},
32+
{
33+
component: componentTypes.SELECT,
34+
name: 'view.drift',
35+
id: 'view.drift',
36+
label: __('Drift'),
37+
initialValue: 'expanded',
38+
options: [
39+
{ label: __('Expanded'), value: 'expanded' },
40+
{ label: __('Compressed'), value: 'compressed' },
41+
],
42+
},
43+
{
44+
component: componentTypes.SELECT,
45+
name: 'view.drift__mode',
46+
id: 'view.drift__mode',
47+
label: __('Drift Mode'),
48+
initialValue: 'details',
49+
options: [
50+
{ label: __('Details'), value: 'details' },
51+
{ label: __('Exists'), value: 'exists' },
52+
],
53+
},
54+
{
55+
component: componentTypes.SELECT,
56+
name: 'view.summary__mode',
57+
id: 'view.summary__mode',
58+
label: __('Summary Screens'),
59+
initialValue: 'dashboard',
60+
options: [
61+
{ label: __('Dashboard'), value: 'dashboard' },
62+
{ label: __('Textual'), value: 'textual' },
63+
],
64+
},
65+
],
66+
},
67+
{
68+
component: componentTypes.SUB_FORM,
69+
name: 'perpage-subform',
70+
title: __('Default Items Per Page'),
71+
fields: [
72+
{
73+
component: componentTypes.SELECT,
74+
name: 'perpage.list',
75+
id: 'perpage.list',
76+
label: __('List View'),
77+
initialValue: 20,
78+
options: [5, 10, 20, 50, 100, 200, 500, 1000].map((n) => ({ label: n, value: n })),
79+
},
80+
{
81+
component: componentTypes.SELECT,
82+
name: 'perpage.reports',
83+
id: 'perpage.reports',
84+
label: __('Reports'),
85+
initialValue: 20,
86+
options: [5, 10, 20, 50, 100, 200, 500, 1000].map((n) => ({ label: n, value: n })),
87+
},
88+
],
89+
},
90+
{
91+
component: componentTypes.SUB_FORM,
92+
name: 'topology-subform',
93+
title: __('Topology Default Items in View'),
94+
fields: [
95+
{
96+
component: componentTypes.SELECT,
97+
name: 'topology.containers_max_items',
98+
id: 'topology.containers_max_items',
99+
label: __('Containers'),
100+
options: [
101+
{ label: __('Unlimited'), value: 0 },
102+
...[5, 10, 20, 50, 100, 200, 500, 1000].map((n) => ({ label: n, value: n })),
103+
],
104+
},
105+
],
106+
},
107+
{
108+
component: componentTypes.SUB_FORM,
109+
name: 'display-subform',
110+
title: __('Miscellaneous'),
111+
fields: [
112+
{
113+
component: componentTypes.SELECT,
114+
name: 'display.startpage',
115+
id: 'display.startpage',
116+
label: __('Start Page'),
117+
isSearchable: true,
118+
loadOptions: () => API.get('/api/shortcuts?expand=resources&attributes=description,url')
119+
.then(({ resources }) => resources.map(({ url, description }) => ({ value: url, label: description }))),
120+
},
121+
{
122+
component: componentTypes.SELECT,
123+
name: 'display.timezone',
124+
id: 'display.timezone',
125+
label: __('Timezone'),
126+
isSearchable: true,
127+
loadOptions: () => API.get('/api').then(({ timezones }) => timezones.map(({ name, description }) => ({ label: description, value: name }))),
128+
},
129+
{
130+
component: componentTypes.SELECT,
131+
name: 'display.locale',
132+
id: 'display.locale',
133+
label: __('Locale'),
134+
initialValue: 'default',
135+
options: [
136+
{ label: __('Global Default'), value: 'default' },
137+
...Object.keys(window.locales).map((value) => ({ value, label: window.locales[value].locale_data.app.locale_name[0] })),
138+
],
139+
},
140+
],
141+
},
142+
],
143+
});
144+
145+
export default createSchema;

app/javascript/packs/component-definitions-common.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import VmServerRelationshipForm from '../components/vm-server-relationship-form'
4747
import VmSnapshotForm from '../components/vm-snapshot-form/vm-snapshot-form';
4848
import WorkersForm from '../components/workers-form/workers-form';
4949
import PhysicalStorageForm from '../components/physical-storage-form';
50+
import VisualSettingsForm from '../components/visual-settings-form';
5051

5152
/**
5253
* Add component definitions to this file.
@@ -103,3 +104,4 @@ ManageIQ.component.addReact('VmServerRelationshipForm', VmServerRelationshipForm
103104
ManageIQ.component.addReact('VmSnapshotForm', VmSnapshotForm);
104105
ManageIQ.component.addReact('WorkersForm', WorkersForm);
105106
ManageIQ.component.addReact('PhysicalStorageForm', PhysicalStorageForm);
107+
ManageIQ.component.addReact('VisualSettingsForm', VisualSettingsForm);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`visual settings form matches the snapshot 1`] = `""`;
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import React from 'react';
2+
import toJson from 'enzyme-to-json';
3+
import fetchMock from 'fetch-mock';
4+
import { shallow } from 'enzyme';
5+
import { act } from 'react-dom/test-utils';
6+
import VisualSettingsForm from '../../components/visual-settings-form';
7+
import { mount } from '../helpers/mountForm';
8+
9+
describe('visual settings form', () => {
10+
afterEach(() => {
11+
fetchMock.reset();
12+
fetchMock.restore();
13+
});
14+
15+
window.locales = [];
16+
17+
const shortcuts = '/api/shortcuts?expand=resources&attributes=description,url';
18+
const users = '/api/users/1?attributes=settings';
19+
20+
it('calls the API endpoints to preseed the form', async(done) => {
21+
fetchMock.get(shortcuts, {
22+
resources: [
23+
{
24+
url: 'foo',
25+
description: 'bar',
26+
},
27+
],
28+
});
29+
30+
fetchMock.get(users, { settings: {} });
31+
32+
fetchMock.get('/api', { timezones: [] });
33+
34+
let wrapper;
35+
await act(async() => {
36+
wrapper = mount(<VisualSettingsForm recordId="1" />);
37+
});
38+
39+
wrapper.update();
40+
41+
expect(fetchMock.called(shortcuts)).toBe(true);
42+
expect(fetchMock.called(users)).toBe(true);
43+
expect(fetchMock.called('/api')).toBe(true);
44+
45+
done();
46+
});
47+
48+
it('matches the snapshot', async(done) => {
49+
let wrapper;
50+
await act(async() => {
51+
wrapper = shallow(<VisualSettingsForm recordId="1" />);
52+
});
53+
54+
expect(toJson(wrapper)).toMatchSnapshot();
55+
done();
56+
});
57+
});

app/views/configuration/_ui_1.html.haml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
- url = url_for_only_path(:action => "form_field_changed")
22
= render :partial => "layouts/flash_msg"
33

4+
= react 'VisualSettingsForm', :recordId => current_user.id.to_s
5+
46
%div{:id => @tabs[0][1], 'role' => 'tabpanel', 'aria-labelledby' =>"#{@tabs[0][1]}_tab"}
57
= form_tag({:action => "update"},
68
:id => "config_form",

0 commit comments

Comments
 (0)