mirror of
https://github.com/ansible/awx.git
synced 2026-03-05 18:51:06 -03:30
Merge remote-tracking branch 'origin/master' into add-select-default-option
This commit is contained in:
@@ -8,19 +8,22 @@ describe('<AnsibleSelect />', () => {
|
|||||||
test('initially renders succesfully', async () => {
|
test('initially renders succesfully', async () => {
|
||||||
mount(
|
mount(
|
||||||
<AnsibleSelect
|
<AnsibleSelect
|
||||||
selected="foo"
|
value="foo"
|
||||||
selectChange={() => { }}
|
name="bar"
|
||||||
|
onChange={() => { }}
|
||||||
labelName={label}
|
labelName={label}
|
||||||
data={mockData}
|
data={mockData}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('calls "onSelectChange" on dropdown select change', () => {
|
test('calls "onSelectChange" on dropdown select change', () => {
|
||||||
const spy = jest.spyOn(AnsibleSelect.prototype, 'onSelectChange');
|
const spy = jest.spyOn(AnsibleSelect.prototype, 'onSelectChange');
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<AnsibleSelect
|
<AnsibleSelect
|
||||||
selected="foo"
|
value="foo"
|
||||||
selectChange={() => { }}
|
name="bar"
|
||||||
|
onChange={() => { }}
|
||||||
labelName={label}
|
labelName={label}
|
||||||
data={mockData}
|
data={mockData}
|
||||||
/>
|
/>
|
||||||
@@ -29,11 +32,13 @@ describe('<AnsibleSelect />', () => {
|
|||||||
wrapper.find('select').simulate('change');
|
wrapper.find('select').simulate('change');
|
||||||
expect(spy).toHaveBeenCalled();
|
expect(spy).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('content not rendered when data property is falsey', () => {
|
test('content not rendered when data property is falsey', () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<AnsibleSelect
|
<AnsibleSelect
|
||||||
selected="foo"
|
value="foo"
|
||||||
selectChange={() => { }}
|
name="bar"
|
||||||
|
onChange={() => { }}
|
||||||
labelName={label}
|
labelName={label}
|
||||||
data={null}
|
data={null}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -7,14 +7,35 @@ let mockData = [{ name: 'foo', id: 1, isChecked: false }];
|
|||||||
describe('<Lookup />', () => {
|
describe('<Lookup />', () => {
|
||||||
test('initially renders succesfully', () => {
|
test('initially renders succesfully', () => {
|
||||||
mount(
|
mount(
|
||||||
<Lookup
|
<I18nProvider>
|
||||||
lookup_header="Foo Bar"
|
<Lookup
|
||||||
onLookupSave={() => { }}
|
lookup_header="Foo Bar"
|
||||||
data={mockData}
|
name="fooBar"
|
||||||
selected={[]}
|
value={mockData}
|
||||||
/>
|
onLookupSave={() => { }}
|
||||||
|
getItems={() => { }}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
test('API response is formatted properly', (done) => {
|
||||||
|
const wrapper = mount(
|
||||||
|
<I18nProvider>
|
||||||
|
<Lookup
|
||||||
|
lookup_header="Foo Bar"
|
||||||
|
name="fooBar"
|
||||||
|
value={mockData}
|
||||||
|
onLookupSave={() => { }}
|
||||||
|
getItems={() => ({ data: { results: [{ name: 'test instance', id: 1 }] } })}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
|
).find('Lookup');
|
||||||
|
|
||||||
|
setImmediate(() => {
|
||||||
|
expect(wrapper.state().results).toEqual([{ id: 1, name: 'test instance' }]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
test('Opens modal when search icon is clicked', () => {
|
test('Opens modal when search icon is clicked', () => {
|
||||||
const spy = jest.spyOn(Lookup.prototype, 'handleModalToggle');
|
const spy = jest.spyOn(Lookup.prototype, 'handleModalToggle');
|
||||||
const mockSelected = [{ name: 'foo', id: 1 }];
|
const mockSelected = [{ name: 'foo', id: 1 }];
|
||||||
@@ -22,9 +43,10 @@ describe('<Lookup />', () => {
|
|||||||
<I18nProvider>
|
<I18nProvider>
|
||||||
<Lookup
|
<Lookup
|
||||||
lookup_header="Foo Bar"
|
lookup_header="Foo Bar"
|
||||||
|
name="fooBar"
|
||||||
|
value={mockSelected}
|
||||||
onLookupSave={() => { }}
|
onLookupSave={() => { }}
|
||||||
data={mockData}
|
getItems={() => { }}
|
||||||
selected={mockSelected}
|
|
||||||
/>
|
/>
|
||||||
</I18nProvider>
|
</I18nProvider>
|
||||||
).find('Lookup');
|
).find('Lookup');
|
||||||
@@ -39,34 +61,39 @@ describe('<Lookup />', () => {
|
|||||||
}]);
|
}]);
|
||||||
expect(wrapper.state('isModalOpen')).toEqual(true);
|
expect(wrapper.state('isModalOpen')).toEqual(true);
|
||||||
});
|
});
|
||||||
test('calls "toggleSelected" when a user changes a checkbox', () => {
|
test('calls "toggleSelected" when a user changes a checkbox', (done) => {
|
||||||
const spy = jest.spyOn(Lookup.prototype, 'toggleSelected');
|
const spy = jest.spyOn(Lookup.prototype, 'toggleSelected');
|
||||||
|
const mockSelected = [{ name: 'foo', id: 1 }];
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<I18nProvider>
|
<I18nProvider>
|
||||||
<Lookup
|
<Lookup
|
||||||
lookup_header="Foo Bar"
|
lookup_header="Foo Bar"
|
||||||
|
name="fooBar"
|
||||||
|
value={mockSelected}
|
||||||
onLookupSave={() => { }}
|
onLookupSave={() => { }}
|
||||||
data={mockData}
|
getItems={() => ({ data: { results: [{ name: 'test instance', id: 1 }] } })}
|
||||||
selected={[]}
|
|
||||||
/>
|
/>
|
||||||
</I18nProvider>
|
</I18nProvider>
|
||||||
);
|
);
|
||||||
const searchItem = wrapper.find('.pf-c-input-group__text#search');
|
setImmediate(() => {
|
||||||
searchItem.first().simulate('click');
|
const searchItem = wrapper.find('.pf-c-input-group__text#search');
|
||||||
wrapper.find('input[type="checkbox"]').simulate('change');
|
searchItem.first().simulate('click');
|
||||||
expect(spy).toHaveBeenCalled();
|
wrapper.find('input[type="checkbox"]').simulate('change');
|
||||||
|
expect(spy).toHaveBeenCalled();
|
||||||
|
done();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
test('calls "toggleSelected" when remove icon is clicked', () => {
|
test('calls "toggleSelected" when remove icon is clicked', () => {
|
||||||
const spy = jest.spyOn(Lookup.prototype, 'toggleSelected');
|
const spy = jest.spyOn(Lookup.prototype, 'toggleSelected');
|
||||||
mockData = [{ name: 'foo', id: 1, isChecked: false }, { name: 'bar', id: 2, isChecked: true }];
|
mockData = [{ name: 'foo', id: 1, isChecked: false }, { name: 'bar', id: 2, isChecked: true }];
|
||||||
const mockSelected = [{ name: 'foo', id: 1 }, { name: 'bar', id: 2 }];
|
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<I18nProvider>
|
<I18nProvider>
|
||||||
<Lookup
|
<Lookup
|
||||||
lookup_header="Foo Bar"
|
lookup_header="Foo Bar"
|
||||||
|
name="fooBar"
|
||||||
|
value={mockData}
|
||||||
onLookupSave={() => { }}
|
onLookupSave={() => { }}
|
||||||
data={mockData}
|
getItems={() => { }}
|
||||||
selected={mockSelected}
|
|
||||||
/>
|
/>
|
||||||
</I18nProvider>
|
</I18nProvider>
|
||||||
);
|
);
|
||||||
@@ -124,9 +151,10 @@ describe('<Lookup />', () => {
|
|||||||
<I18nProvider>
|
<I18nProvider>
|
||||||
<Lookup
|
<Lookup
|
||||||
lookup_header="Foo Bar"
|
lookup_header="Foo Bar"
|
||||||
|
name="fooBar"
|
||||||
|
value={mockData}
|
||||||
onLookupSave={onLookupSaveFn}
|
onLookupSave={onLookupSaveFn}
|
||||||
data={mockData}
|
getItems={() => { }}
|
||||||
selected={[]}
|
|
||||||
/>
|
/>
|
||||||
</I18nProvider>
|
</I18nProvider>
|
||||||
).find('Lookup');
|
).find('Lookup');
|
||||||
@@ -142,6 +170,6 @@ describe('<Lookup />', () => {
|
|||||||
expect(onLookupSaveFn).toHaveBeenCalledWith([{
|
expect(onLookupSaveFn).toHaveBeenCalledWith([{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'foo'
|
name: 'foo'
|
||||||
}]);
|
}], 'fooBar');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,27 +1,32 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { mount } from 'enzyme';
|
import { mount } from 'enzyme';
|
||||||
import { MemoryRouter } from 'react-router-dom';
|
import { MemoryRouter } from 'react-router-dom';
|
||||||
|
import { I18nProvider } from '@lingui/react';
|
||||||
import OrganizationAdd from '../../../../src/pages/Organizations/screens/OrganizationAdd';
|
import OrganizationAdd from '../../../../src/pages/Organizations/screens/OrganizationAdd';
|
||||||
|
|
||||||
describe('<OrganizationAdd />', () => {
|
describe('<OrganizationAdd />', () => {
|
||||||
test('initially renders succesfully', () => {
|
test('initially renders succesfully', () => {
|
||||||
mount(
|
mount(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<OrganizationAdd
|
<I18nProvider>
|
||||||
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
<OrganizationAdd
|
||||||
location={{ search: '', pathname: '/organizations/add' }}
|
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
||||||
/>
|
location={{ search: '', pathname: '/organizations/add' }}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
test('calls "handleChange" when input values change', () => {
|
test('calls "onFieldChange" when input values change', () => {
|
||||||
const spy = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'handleChange');
|
const spy = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'onFieldChange');
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<OrganizationAdd
|
<I18nProvider>
|
||||||
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
<OrganizationAdd
|
||||||
location={{ search: '', pathname: '/organizations/add' }}
|
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
||||||
/>
|
location={{ search: '', pathname: '/organizations/add' }}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
expect(spy).not.toHaveBeenCalled();
|
expect(spy).not.toHaveBeenCalled();
|
||||||
@@ -33,79 +38,69 @@ describe('<OrganizationAdd />', () => {
|
|||||||
const spy = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'onSubmit');
|
const spy = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'onSubmit');
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<OrganizationAdd
|
<I18nProvider>
|
||||||
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
<OrganizationAdd
|
||||||
location={{ search: '', pathname: '/organizations/add' }}
|
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
||||||
/>
|
location={{ search: '', pathname: '/organizations/add' }}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
expect(spy).not.toHaveBeenCalled();
|
expect(spy).not.toHaveBeenCalled();
|
||||||
wrapper.find('button.at-C-SubmitButton').prop('onClick')();
|
wrapper.find('button[aria-label="Save"]').prop('onClick')();
|
||||||
expect(spy).toBeCalled();
|
expect(spy).toBeCalled();
|
||||||
});
|
});
|
||||||
test('calls "onCancel" when Cancel button is clicked', () => {
|
test('calls "onCancel" when Cancel button is clicked', () => {
|
||||||
const spy = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'onCancel');
|
const spy = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'onCancel');
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<OrganizationAdd
|
<I18nProvider>
|
||||||
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
<OrganizationAdd
|
||||||
location={{ search: '', pathname: '/organizations/add' }}
|
match={{ path: '/organizations/add', url: '/organizations/add' }}
|
||||||
/>
|
location={{ search: '', pathname: '/organizations/add' }}
|
||||||
|
/>
|
||||||
|
</I18nProvider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
expect(spy).not.toHaveBeenCalled();
|
expect(spy).not.toHaveBeenCalled();
|
||||||
wrapper.find('button.at-C-CancelButton').prop('onClick')();
|
wrapper.find('button[aria-label="Cancel"]').prop('onClick')();
|
||||||
expect(spy).toBeCalled();
|
expect(spy).toBeCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('API response is formatted properly', (done) => {
|
|
||||||
const mockedResp = { data: { results: [{ name: 'test instance', id: 1 }] } };
|
|
||||||
const api = { getInstanceGroups: jest.fn().mockResolvedValue(mockedResp) };
|
|
||||||
const wrapper = mount(
|
|
||||||
<MemoryRouter>
|
|
||||||
<OrganizationAdd api={api} />
|
|
||||||
</MemoryRouter>
|
|
||||||
);
|
|
||||||
|
|
||||||
setImmediate(() => {
|
|
||||||
const orgAddElem = wrapper.find('OrganizationAdd');
|
|
||||||
expect([{ id: 1, isChecked: false, name: 'test instance' }]).toEqual(orgAddElem.state().results);
|
|
||||||
done();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
test('Successful form submission triggers redirect', (done) => {
|
test('Successful form submission triggers redirect', (done) => {
|
||||||
const onSuccess = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'onSuccess');
|
const onSuccess = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'onSuccess');
|
||||||
const resetForm = jest.spyOn(OrganizationAdd.WrappedComponent.prototype, 'resetForm');
|
|
||||||
const mockedResp = { data: { id: 1, related: { instance_groups: '/bar' } } };
|
const mockedResp = { data: { id: 1, related: { instance_groups: '/bar' } } };
|
||||||
const api = { createOrganization: jest.fn().mockResolvedValue(mockedResp), createInstanceGroups: jest.fn().mockResolvedValue('done') };
|
const api = { createOrganization: jest.fn().mockResolvedValue(mockedResp), createInstanceGroups: jest.fn().mockResolvedValue('done') };
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<OrganizationAdd api={api} />
|
<I18nProvider>
|
||||||
|
<OrganizationAdd api={api} />
|
||||||
|
</I18nProvider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
wrapper.find('input#add-org-form-name').simulate('change', { target: { value: 'foo' } });
|
wrapper.find('input#add-org-form-name').simulate('change', { target: { value: 'foo' } });
|
||||||
wrapper.find('button.at-C-SubmitButton').prop('onClick')();
|
wrapper.find('button[aria-label="Save"]').prop('onClick')();
|
||||||
setImmediate(() => {
|
setImmediate(() => {
|
||||||
expect(onSuccess).toHaveBeenCalled();
|
expect(onSuccess).toHaveBeenCalled();
|
||||||
expect(resetForm).toHaveBeenCalled();
|
|
||||||
done();
|
done();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('updateSelectedInstanceGroups successfully sets selectedInstanceGroups state', () => {
|
test('onLookupSave successfully sets instanceGroups state', () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<OrganizationAdd api={{}} />
|
<I18nProvider>
|
||||||
|
<OrganizationAdd api={{}} />
|
||||||
|
</I18nProvider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
).find('OrganizationAdd');
|
).find('OrganizationAdd');
|
||||||
wrapper.instance().updateSelectedInstanceGroups([
|
wrapper.instance().onLookupSave([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'foo'
|
name: 'foo'
|
||||||
}
|
}
|
||||||
]);
|
], 'instanceGroups');
|
||||||
expect(wrapper.state('selectedInstanceGroups')).toEqual([
|
expect(wrapper.state('instanceGroups')).toEqual([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'foo'
|
name: 'foo'
|
||||||
@@ -113,14 +108,16 @@ describe('<OrganizationAdd />', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('onSelectChange successfully sets custom_virtualenv state', () => {
|
test('onFieldChange successfully sets custom_virtualenv state', () => {
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<OrganizationAdd api={{}} />
|
<I18nProvider>
|
||||||
|
<OrganizationAdd api={{}} />
|
||||||
|
</I18nProvider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
).find('OrganizationAdd');
|
).find('OrganizationAdd');
|
||||||
wrapper.instance().onSelectChange('foobar');
|
wrapper.instance().onFieldChange('fooBar', { target: { name: 'custom_virtualenv' } });
|
||||||
expect(wrapper.state('custom_virtualenv')).toBe('foobar');
|
expect(wrapper.state('custom_virtualenv')).toBe('fooBar');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('onSubmit posts instance groups from selectedInstanceGroups', async () => {
|
test('onSubmit posts instance groups from selectedInstanceGroups', async () => {
|
||||||
@@ -140,12 +137,14 @@ describe('<OrganizationAdd />', () => {
|
|||||||
};
|
};
|
||||||
const wrapper = mount(
|
const wrapper = mount(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<OrganizationAdd api={api} />
|
<I18nProvider>
|
||||||
|
<OrganizationAdd api={api} />
|
||||||
|
</I18nProvider>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
).find('OrganizationAdd');
|
).find('OrganizationAdd');
|
||||||
wrapper.setState({
|
wrapper.setState({
|
||||||
name: 'mock org',
|
name: 'mock org',
|
||||||
selectedInstanceGroups: [{
|
instanceGroups: [{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: 'foo'
|
name: 'foo'
|
||||||
}]
|
}]
|
||||||
|
|||||||
@@ -106,8 +106,8 @@ class APIClient {
|
|||||||
return this.http.post(endpoint, data);
|
return this.http.post(endpoint, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
getInstanceGroups () {
|
getInstanceGroups (params) {
|
||||||
return this.http.get(API_INSTANCE_GROUPS);
|
return this.http.get(API_INSTANCE_GROUPS, { params });
|
||||||
}
|
}
|
||||||
|
|
||||||
createInstanceGroups (url, id) {
|
createInstanceGroups (url, id) {
|
||||||
|
|||||||
16
src/app.scss
16
src/app.scss
@@ -157,8 +157,11 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
.pf-c-modal-box__footer {
|
.pf-c-modal-box__footer {
|
||||||
--pf-c-modal-box__footer--PaddingTop: 0;
|
--pf-c-modal-box__footer--PaddingTop: 20px;
|
||||||
--pf-c-modal-box__footer--PaddingBottom: 0;
|
--pf-c-modal-box__footer--PaddingRight: 20px;
|
||||||
|
--pf-c-modal-box__footer--PaddingBottom: 20px;
|
||||||
|
--pf-c-modal-box__footer--PaddingLeft: 20px;
|
||||||
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
|
|
||||||
.pf-c-modal-box__header {
|
.pf-c-modal-box__header {
|
||||||
@@ -171,6 +174,7 @@
|
|||||||
.pf-c-modal-box__body {
|
.pf-c-modal-box__body {
|
||||||
--pf-c-modal-box__body--PaddingLeft: 20px;
|
--pf-c-modal-box__body--PaddingLeft: 20px;
|
||||||
--pf-c-modal-box__body--PaddingRight: 20px;
|
--pf-c-modal-box__body--PaddingRight: 20px;
|
||||||
|
--pf-c-modal-box__body--PaddingBottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
@@ -215,12 +219,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.at-align-right {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: row;
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.awx-c-list {
|
.awx-c-list {
|
||||||
border-top: 1px solid #d7d7d7;
|
border-top: 1px solid #d7d7d7;
|
||||||
border-bottom: 1px solid #d7d7d7;
|
border-bottom: 1px solid #d7d7d7;
|
||||||
@@ -240,4 +238,4 @@
|
|||||||
--pf-c-card__footer--PaddingY: 0;
|
--pf-c-card__footer--PaddingY: 0;
|
||||||
--pf-c-card__body--PaddingX: 0;
|
--pf-c-card__body--PaddingX: 0;
|
||||||
--pf-c-card__body--PaddingY: 0;
|
--pf-c-card__body--PaddingY: 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
FormGroup,
|
|
||||||
Select,
|
Select,
|
||||||
SelectOption,
|
SelectOption,
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
@@ -25,32 +24,28 @@ class AnsibleSelect extends React.Component {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelectChange (val) {
|
onSelectChange (val, event) {
|
||||||
const { selectChange } = this.props;
|
const { onChange, name } = this.props;
|
||||||
selectChange(val);
|
event.target.name = name;
|
||||||
|
onChange(val, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { count } = this.state;
|
const { count } = this.state;
|
||||||
const { labelName, selected, data, defaultSelected } = this.props;
|
const { label = '', value, data, defaultSelected } = this.props;
|
||||||
let elem;
|
let elem;
|
||||||
if (count > 1) {
|
if (count > 1) {
|
||||||
elem = (
|
elem = (
|
||||||
<FormGroup label={labelName} fieldId="ansible-select">
|
<Select value={value} onChange={this.onSelectChange} aria-label="Select Input">
|
||||||
<Select value={selected} onChange={this.onSelectChange} aria-label="Select Input">
|
{data.map((datum) => (datum === defaultSelected
|
||||||
<SelectOption key="" value="" label={`Use Default ${labelName}`} />
|
? (<SelectOption key="" value="" label={`Use Default ${label}`} />) : (<SelectOption key={datum} value={datum} label={datum} />)))
|
||||||
{data.map((datum) => (datum !== defaultSelected
|
}
|
||||||
? (<SelectOption key={datum} value={datum} label={datum} />) : null))
|
</Select>
|
||||||
}
|
|
||||||
</Select>
|
|
||||||
</FormGroup>
|
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
elem = null;
|
elem = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return elem;
|
return elem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default AnsibleSelect;
|
export default AnsibleSelect;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.awx-toolbar {
|
.awx-toolbar {
|
||||||
--awx-toolbar--BackgroundColor: var(--pf-global--BackgroundColor--light-100);
|
--awx-toolbar--BackgroundColor: var(--pf-global--BackgroundColor--light-100);
|
||||||
--awx-toolbar--BorderColor: var(--pf-global--Color--light-200);
|
--awx-toolbar--BorderColor: #ebebeb;
|
||||||
--awx-toolbar--BorderWidth: var(--pf-global--BorderWidth--sm);
|
--awx-toolbar--BorderWidth: var(--pf-global--BorderWidth--sm);
|
||||||
|
|
||||||
border-bottom: var(--awx-toolbar--BorderWidth) solid var(--awx-toolbar--BorderColor);
|
border-bottom: var(--awx-toolbar--BorderWidth) solid var(--awx-toolbar--BorderColor);
|
||||||
|
|||||||
39
src/components/FormActionGroup.jsx
Normal file
39
src/components/FormActionGroup.jsx
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import { I18n } from '@lingui/react';
|
||||||
|
import { t } from '@lingui/macro';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ActionGroup,
|
||||||
|
Toolbar,
|
||||||
|
ToolbarGroup,
|
||||||
|
Button
|
||||||
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
|
const formActionGroupStyle = {
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
marginTop: '10px'
|
||||||
|
};
|
||||||
|
|
||||||
|
const buttonGroupStyle = {
|
||||||
|
marginRight: '20px'
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ({ onSubmit, submitDisabled, onCancel }) => (
|
||||||
|
<I18n>
|
||||||
|
{({ i18n }) => (
|
||||||
|
<ActionGroup style={formActionGroupStyle}>
|
||||||
|
<Toolbar>
|
||||||
|
<ToolbarGroup style={buttonGroupStyle}>
|
||||||
|
<Button aria-label={i18n._(t`Save`)} variant="primary" onClick={onSubmit} isDisabled={submitDisabled}>{i18n._(t`Save`)}</Button>
|
||||||
|
</ToolbarGroup>
|
||||||
|
<ToolbarGroup>
|
||||||
|
<Button aria-label={i18n._(t`Cancel`)} variant="secondary" onClick={onCancel}>{i18n._(t`Cancel`)}</Button>
|
||||||
|
</ToolbarGroup>
|
||||||
|
</Toolbar>
|
||||||
|
</ActionGroup>
|
||||||
|
)}
|
||||||
|
</I18n>
|
||||||
|
);
|
||||||
@@ -1,33 +1,82 @@
|
|||||||
import React from 'react';
|
import React, { Fragment } from 'react';
|
||||||
|
|
||||||
import { SearchIcon } from '@patternfly/react-icons';
|
import { SearchIcon, CubesIcon } from '@patternfly/react-icons';
|
||||||
import {
|
import {
|
||||||
Modal,
|
Modal,
|
||||||
Button,
|
Button,
|
||||||
ActionGroup,
|
EmptyState,
|
||||||
Toolbar,
|
EmptyStateIcon,
|
||||||
ToolbarGroup,
|
EmptyStateBody,
|
||||||
|
Title
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
import { I18n } from '@lingui/react';
|
import { I18n } from '@lingui/react';
|
||||||
import { t, Trans } from '@lingui/macro';
|
import { Trans, t } from '@lingui/macro';
|
||||||
|
|
||||||
import CheckboxListItem from '../ListItem';
|
import CheckboxListItem from '../ListItem';
|
||||||
|
|
||||||
import SelectedList from '../SelectedList';
|
import SelectedList from '../SelectedList';
|
||||||
|
import Pagination from '../Pagination';
|
||||||
|
|
||||||
|
const paginationStyling = {
|
||||||
|
paddingLeft: '0',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
borderRight: '1px solid #ebebeb',
|
||||||
|
borderBottom: '1px solid #ebebeb',
|
||||||
|
borderTop: '0'
|
||||||
|
};
|
||||||
|
|
||||||
class Lookup extends React.Component {
|
class Lookup extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
isModalOpen: false,
|
isModalOpen: false,
|
||||||
lookupSelectedItems: []
|
lookupSelectedItems: [],
|
||||||
|
results: [],
|
||||||
|
count: 0,
|
||||||
|
page: 1,
|
||||||
|
page_size: 5,
|
||||||
|
error: null
|
||||||
};
|
};
|
||||||
|
this.onSetPage = this.onSetPage.bind(this);
|
||||||
this.handleModalToggle = this.handleModalToggle.bind(this);
|
this.handleModalToggle = this.handleModalToggle.bind(this);
|
||||||
this.wrapTags = this.wrapTags.bind(this);
|
this.wrapTags = this.wrapTags.bind(this);
|
||||||
this.toggleSelected = this.toggleSelected.bind(this);
|
this.toggleSelected = this.toggleSelected.bind(this);
|
||||||
this.saveModal = this.saveModal.bind(this);
|
this.saveModal = this.saveModal.bind(this);
|
||||||
|
this.getData = this.getData.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
componentDidMount () {
|
||||||
|
const { page_size, page } = this.state;
|
||||||
|
this.getData({ page_size, page });
|
||||||
|
}
|
||||||
|
|
||||||
|
async getData (queryParams) {
|
||||||
|
const { getItems } = this.props;
|
||||||
|
const { page } = queryParams;
|
||||||
|
|
||||||
|
this.setState({ error: false });
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { data } = await getItems(queryParams);
|
||||||
|
const { results, count } = data;
|
||||||
|
|
||||||
|
const stateToUpdate = {
|
||||||
|
page,
|
||||||
|
results,
|
||||||
|
count
|
||||||
|
};
|
||||||
|
|
||||||
|
this.setState(stateToUpdate);
|
||||||
|
} catch (err) {
|
||||||
|
this.setState({ error: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onSetPage = async (pageNumber, pageSize) => {
|
||||||
|
const page = parseInt(pageNumber, 10);
|
||||||
|
const page_size = parseInt(pageSize, 10);
|
||||||
|
this.getData({ page_size, page });
|
||||||
|
};
|
||||||
|
|
||||||
toggleSelected (row) {
|
toggleSelected (row) {
|
||||||
const { lookupSelectedItems } = this.state;
|
const { lookupSelectedItems } = this.state;
|
||||||
const selectedIndex = lookupSelectedItems
|
const selectedIndex = lookupSelectedItems
|
||||||
@@ -44,12 +93,12 @@ class Lookup extends React.Component {
|
|||||||
|
|
||||||
handleModalToggle () {
|
handleModalToggle () {
|
||||||
const { isModalOpen } = this.state;
|
const { isModalOpen } = this.state;
|
||||||
const { selected } = this.props;
|
const { value } = this.props;
|
||||||
// Resets the selected items from parent state whenever modal is opened
|
// Resets the selected items from parent state whenever modal is opened
|
||||||
// This handles the case where the user closes/cancels the modal and
|
// This handles the case where the user closes/cancels the modal and
|
||||||
// opens it again
|
// opens it again
|
||||||
if (!isModalOpen) {
|
if (!isModalOpen) {
|
||||||
this.setState({ lookupSelectedItems: [...selected] });
|
this.setState({ lookupSelectedItems: [...value] });
|
||||||
}
|
}
|
||||||
this.setState((prevState) => ({
|
this.setState((prevState) => ({
|
||||||
isModalOpen: !prevState.isModalOpen,
|
isModalOpen: !prevState.isModalOpen,
|
||||||
@@ -57,13 +106,13 @@ class Lookup extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
saveModal () {
|
saveModal () {
|
||||||
const { onLookupSave } = this.props;
|
const { onLookupSave, name } = this.props;
|
||||||
const { lookupSelectedItems } = this.state;
|
const { lookupSelectedItems } = this.state;
|
||||||
onLookupSave(lookupSelectedItems);
|
onLookupSave(lookupSelectedItems, name);
|
||||||
this.handleModalToggle();
|
this.handleModalToggle();
|
||||||
}
|
}
|
||||||
|
|
||||||
wrapTags (tags) {
|
wrapTags (tags = []) {
|
||||||
return tags.map(tag => (
|
return tags.map(tag => (
|
||||||
<span className="awx-c-tag--pill" key={tag.id}>
|
<span className="awx-c-tag--pill" key={tag.id}>
|
||||||
{tag.name}
|
{tag.name}
|
||||||
@@ -75,34 +124,61 @@ class Lookup extends React.Component {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { isModalOpen, lookupSelectedItems } = this.state;
|
const { isModalOpen, lookupSelectedItems, error, results, count, page, page_size } = this.state;
|
||||||
const { data, lookupHeader, selected } = this.props;
|
const { lookupHeader = 'items', value } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="pf-c-input-group awx-lookup">
|
<I18n>
|
||||||
<Button className="pf-c-input-group__text" aria-label="search" id="search" onClick={this.handleModalToggle}>
|
{({ i18n }) => (
|
||||||
<SearchIcon />
|
<div className="pf-c-input-group awx-lookup">
|
||||||
</Button>
|
<Button className="pf-c-input-group__text" aria-label="search" id="search" onClick={this.handleModalToggle}>
|
||||||
<div className="pf-c-form-control">{this.wrapTags(selected)}</div>
|
<SearchIcon />
|
||||||
<Modal
|
</Button>
|
||||||
className="awx-c-modal"
|
<div className="pf-c-form-control">{this.wrapTags(value)}</div>
|
||||||
title={`Select ${lookupHeader}`}
|
<Modal
|
||||||
isOpen={isModalOpen}
|
className="awx-c-modal"
|
||||||
onClose={this.handleModalToggle}
|
title={`Select ${lookupHeader}`}
|
||||||
>
|
isOpen={isModalOpen}
|
||||||
<ul className="pf-c-data-list awx-c-list">
|
onClose={this.handleModalToggle}
|
||||||
{data.map(i => (
|
actions={[
|
||||||
<CheckboxListItem
|
<Button key="save" variant="primary" onClick={this.saveModal} style={(results.length === 0) ? { display: 'none' } : {}}>{i18n._(t`Save`)}</Button>,
|
||||||
key={i.id}
|
<Button key="cancel" variant="secondary" onClick={this.handleModalToggle}>{(results.length === 0) ? i18n._(t`Close`) : i18n._(t`Cancel`)}</Button>
|
||||||
itemId={i.id}
|
]}
|
||||||
name={i.name}
|
>
|
||||||
isSelected={lookupSelectedItems.some(item => item.id === i.id)}
|
{(results.length === 0) ? (
|
||||||
onSelect={() => this.toggleSelected(i)}
|
<EmptyState>
|
||||||
/>
|
<EmptyStateIcon icon={CubesIcon} />
|
||||||
))}
|
<Title size="lg">
|
||||||
</ul>
|
<Trans>{`No ${lookupHeader} Found`}</Trans>
|
||||||
{lookupSelectedItems.length > 0 && (
|
</Title>
|
||||||
<I18n>
|
<EmptyStateBody>
|
||||||
{({ i18n }) => (
|
<Trans>{`Please add ${lookupHeader.toLowerCase()} to populate this list`}</Trans>
|
||||||
|
</EmptyStateBody>
|
||||||
|
</EmptyState>
|
||||||
|
) : (
|
||||||
|
<Fragment>
|
||||||
|
<ul className="pf-c-data-list awx-c-list">
|
||||||
|
{results.map(i => (
|
||||||
|
<CheckboxListItem
|
||||||
|
key={i.id}
|
||||||
|
itemId={i.id}
|
||||||
|
name={i.name}
|
||||||
|
isSelected={lookupSelectedItems.some(item => item.id === i.id)}
|
||||||
|
onSelect={() => this.toggleSelected(i)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<Pagination
|
||||||
|
count={count}
|
||||||
|
page={page}
|
||||||
|
pageCount={Math.ceil(count / page_size)}
|
||||||
|
page_size={page_size}
|
||||||
|
onSetPage={this.onSetPage}
|
||||||
|
style={paginationStyling}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
)}
|
||||||
|
{lookupSelectedItems.length > 0 && (
|
||||||
<SelectedList
|
<SelectedList
|
||||||
label={i18n._(t`Selected`)}
|
label={i18n._(t`Selected`)}
|
||||||
selected={lookupSelectedItems}
|
selected={lookupSelectedItems}
|
||||||
@@ -110,24 +186,11 @@ class Lookup extends React.Component {
|
|||||||
onRemove={this.toggleSelected}
|
onRemove={this.toggleSelected}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</I18n>
|
{ error ? <div>error</div> : '' }
|
||||||
)}
|
</Modal>
|
||||||
<ActionGroup className="at-align-right">
|
</div>
|
||||||
<Toolbar>
|
)}
|
||||||
<ToolbarGroup>
|
</I18n>
|
||||||
<Button className="at-C-SubmitButton" variant="primary" onClick={this.saveModal}>
|
|
||||||
<Trans>Select</Trans>
|
|
||||||
</Button>
|
|
||||||
</ToolbarGroup>
|
|
||||||
<ToolbarGroup>
|
|
||||||
<Button className="at-C-CancelButton" variant="secondary" onClick={this.handleModalToggle}>
|
|
||||||
<Trans>Cancel</Trans>
|
|
||||||
</Button>
|
|
||||||
</ToolbarGroup>
|
|
||||||
</Toolbar>
|
|
||||||
</ActionGroup>
|
|
||||||
</Modal>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,10 +105,13 @@ class Pagination extends Component {
|
|||||||
pageCount,
|
pageCount,
|
||||||
page_size,
|
page_size,
|
||||||
pageSizeOptions,
|
pageSizeOptions,
|
||||||
|
style
|
||||||
} = this.props;
|
} = this.props;
|
||||||
const { value, isOpen } = this.state;
|
const { value, isOpen } = this.state;
|
||||||
|
let opts;
|
||||||
const opts = pageSizeOptions.slice().reverse().filter(o => o !== page_size);
|
if (pageSizeOptions) {
|
||||||
|
opts = pageSizeOptions.slice().reverse().filter(o => o !== page_size);
|
||||||
|
}
|
||||||
const isOnFirst = page === 1;
|
const isOnFirst = page === 1;
|
||||||
const isOnLast = page === pageCount;
|
const isOnLast = page === pageCount;
|
||||||
|
|
||||||
@@ -119,33 +122,35 @@ class Pagination extends Component {
|
|||||||
return (
|
return (
|
||||||
<I18n>
|
<I18n>
|
||||||
{({ i18n }) => (
|
{({ i18n }) => (
|
||||||
<div className="awx-pagination">
|
<div className="awx-pagination" style={style}>
|
||||||
<div className="awx-pagination__page-size-selection">
|
{opts && (
|
||||||
<Trans>Items Per Page</Trans>
|
<div className="awx-pagination__page-size-selection">
|
||||||
<Dropdown
|
<Trans>Items Per Page</Trans>
|
||||||
onToggle={this.onTogglePageSize}
|
<Dropdown
|
||||||
onSelect={this.onSelectPageSize}
|
onToggle={this.onTogglePageSize}
|
||||||
direction={up}
|
onSelect={this.onSelectPageSize}
|
||||||
isOpen={isOpen}
|
direction={up}
|
||||||
toggle={(
|
isOpen={isOpen}
|
||||||
<DropdownToggle
|
toggle={(
|
||||||
className="togglePageSize"
|
<DropdownToggle
|
||||||
onToggle={this.onTogglePageSize}
|
className="togglePageSize"
|
||||||
>
|
onToggle={this.onTogglePageSize}
|
||||||
{page_size}
|
>
|
||||||
</DropdownToggle>
|
{page_size}
|
||||||
)}
|
</DropdownToggle>
|
||||||
>
|
)}
|
||||||
{opts.map(option => (
|
>
|
||||||
<DropdownItem
|
{opts.map(option => (
|
||||||
key={option}
|
<DropdownItem
|
||||||
component="button"
|
key={option}
|
||||||
>
|
component="button"
|
||||||
{option}
|
>
|
||||||
</DropdownItem>
|
{option}
|
||||||
))}
|
</DropdownItem>
|
||||||
</Dropdown>
|
))}
|
||||||
</div>
|
</Dropdown>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="awx-pagination__counts">
|
<div className="awx-pagination__counts">
|
||||||
<div className="awx-pagination__item-count">
|
<div className="awx-pagination__item-count">
|
||||||
<Trans>{`Items ${itemMin} - ${itemMax} of ${count}`}</Trans>
|
<Trans>{`Items ${itemMin} - ${itemMax} of ${count}`}</Trans>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.awx-pagination {
|
.awx-pagination {
|
||||||
--awx-pagination--BackgroundColor: var(--pf-global--BackgroundColor--light-100);
|
--awx-pagination--BackgroundColor: var(--pf-global--BackgroundColor--light-100);
|
||||||
--awx-pagination--BorderColor: var(--pf-global--BackgroundColor--light-300);
|
--awx-pagination--BorderColor: #dbdbdb;
|
||||||
--awx-pagination--disabled-BackgroundColor: #f2f2f2;
|
--awx-pagination--disabled-BackgroundColor: #f2f2f2;
|
||||||
--awx-pagination--disabled-Color: #C2C2CA;
|
--awx-pagination--disabled-Color: #C2C2CA;
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,18 @@ import {
|
|||||||
Chip
|
Chip
|
||||||
} from '@patternfly/react-core';
|
} from '@patternfly/react-core';
|
||||||
|
|
||||||
|
const selectedRowStyling = {
|
||||||
|
paddingTop: '15px',
|
||||||
|
paddingBottom: '5px',
|
||||||
|
borderLeft: '0',
|
||||||
|
borderRight: '0'
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectedLabelStyling = {
|
||||||
|
fontSize: '14px',
|
||||||
|
fontWeight: 'bold'
|
||||||
|
};
|
||||||
|
|
||||||
class SelectedList extends Component {
|
class SelectedList extends Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props);
|
super(props);
|
||||||
@@ -23,8 +35,8 @@ class SelectedList extends Component {
|
|||||||
const { showOverflow } = this.state;
|
const { showOverflow } = this.state;
|
||||||
return (
|
return (
|
||||||
<div className="awx-selectedList">
|
<div className="awx-selectedList">
|
||||||
<div className="pf-l-split">
|
<div className="pf-l-split" style={selectedRowStyling}>
|
||||||
<div className="pf-l-split__item pf-u-align-items-center">
|
<div className="pf-l-split__item pf-u-align-items-center" style={selectedLabelStyling}>
|
||||||
{label}
|
{label}
|
||||||
</div>
|
</div>
|
||||||
<div className="pf-l-split__item">
|
<div className="pf-l-split__item">
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
.awx-selectedList {
|
.awx-selectedList {
|
||||||
--awx-selectedList--BackgroundColor: var(--pf-global--BackgroundColor--light-100);
|
--awx-selectedList--BackgroundColor: var(--pf-global--BackgroundColor--light-100);
|
||||||
--awx-selectedList--BorderColor: #d7d7d7;
|
--awx-selectedList--BorderColor: #ebebeb;
|
||||||
--awx-selectedList--BorderWidth: var(--pf-global--BorderWidth--sm);
|
--awx-selectedList--BorderWidth: var(--pf-global--BorderWidth--sm);
|
||||||
--awx-selectedList--FontSize: var(--pf-c-chip__text--FontSize);
|
--awx-selectedList--FontSize: var(--pf-c-chip__text--FontSize);
|
||||||
|
|
||||||
|
|||||||
@@ -6,10 +6,6 @@ import {
|
|||||||
Form,
|
Form,
|
||||||
FormGroup,
|
FormGroup,
|
||||||
TextInput,
|
TextInput,
|
||||||
ActionGroup,
|
|
||||||
Toolbar,
|
|
||||||
ToolbarGroup,
|
|
||||||
Button,
|
|
||||||
Gallery,
|
Gallery,
|
||||||
Card,
|
Card,
|
||||||
CardBody,
|
CardBody,
|
||||||
@@ -18,76 +14,57 @@ import {
|
|||||||
import { ConfigContext } from '../../../context';
|
import { ConfigContext } from '../../../context';
|
||||||
import Lookup from '../../../components/Lookup';
|
import Lookup from '../../../components/Lookup';
|
||||||
import AnsibleSelect from '../../../components/AnsibleSelect';
|
import AnsibleSelect from '../../../components/AnsibleSelect';
|
||||||
|
import FormActionGroup from '../../../components/FormActionGroup';
|
||||||
const format = (data) => {
|
|
||||||
const results = data.results.map((result) => ({
|
|
||||||
id: result.id,
|
|
||||||
name: result.name,
|
|
||||||
isChecked: false
|
|
||||||
}));
|
|
||||||
return results;
|
|
||||||
};
|
|
||||||
|
|
||||||
class OrganizationAdd extends React.Component {
|
class OrganizationAdd extends React.Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props);
|
super(props);
|
||||||
|
|
||||||
this.handleChange = this.handleChange.bind(this);
|
this.getInstanceGroups = this.getInstanceGroups.bind(this);
|
||||||
this.onSelectChange = this.onSelectChange.bind(this);
|
this.onFieldChange = this.onFieldChange.bind(this);
|
||||||
|
this.onLookupSave = this.onLookupSave.bind(this);
|
||||||
this.onSubmit = this.onSubmit.bind(this);
|
this.onSubmit = this.onSubmit.bind(this);
|
||||||
this.resetForm = this.resetForm.bind(this);
|
|
||||||
this.onSuccess = this.onSuccess.bind(this);
|
|
||||||
this.onCancel = this.onCancel.bind(this);
|
this.onCancel = this.onCancel.bind(this);
|
||||||
this.updateSelectedInstanceGroups = this.updateSelectedInstanceGroups.bind(this);
|
this.onSuccess = this.onSuccess.bind(this);
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
custom_virtualenv: '',
|
||||||
|
instanceGroups: [],
|
||||||
|
error: '',
|
||||||
|
defaultEnv: '/venv/ansible/',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
state = {
|
onFieldChange (val, evt) {
|
||||||
name: '',
|
this.setState({ [evt.target.name]: val || evt.target.value });
|
||||||
description: '',
|
|
||||||
results: [],
|
|
||||||
custom_virtualenv: '',
|
|
||||||
error: '',
|
|
||||||
selectedInstanceGroups: [],
|
|
||||||
defaultEnv: '/venv/ansible/'
|
|
||||||
};
|
|
||||||
|
|
||||||
async componentDidMount () {
|
|
||||||
const { api } = this.props;
|
|
||||||
try {
|
|
||||||
const { data } = await api.getInstanceGroups();
|
|
||||||
const results = format(data);
|
|
||||||
this.setState({ results });
|
|
||||||
} catch (error) {
|
|
||||||
this.setState({ error });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onSelectChange (value) {
|
onLookupSave (val, targetName) {
|
||||||
this.setState({ custom_virtualenv: value });
|
this.setState({ [targetName]: val });
|
||||||
}
|
}
|
||||||
|
|
||||||
async onSubmit () {
|
async onSubmit () {
|
||||||
const { api } = this.props;
|
const { api } = this.props;
|
||||||
const { name, description, custom_virtualenv } = this.state;
|
const { name, description, custom_virtualenv, instanceGroups } = this.state;
|
||||||
const data = {
|
const data = {
|
||||||
name,
|
name,
|
||||||
description,
|
description,
|
||||||
custom_virtualenv
|
custom_virtualenv
|
||||||
};
|
};
|
||||||
const { selectedInstanceGroups } = this.state;
|
|
||||||
try {
|
try {
|
||||||
const { data: response } = await api.createOrganization(data);
|
const { data: response } = await api.createOrganization(data);
|
||||||
const url = response.related.instance_groups;
|
const instanceGroupsUrl = response.related.instance_groups;
|
||||||
try {
|
try {
|
||||||
if (selectedInstanceGroups.length > 0) {
|
if (instanceGroups.length > 0) {
|
||||||
selectedInstanceGroups.forEach(async (select) => {
|
instanceGroups.forEach(async (select) => {
|
||||||
await api.createInstanceGroups(url, select.id);
|
await api.createInstanceGroups(instanceGroupsUrl, select.id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
this.setState({ error: err });
|
this.setState({ error: err });
|
||||||
} finally {
|
} finally {
|
||||||
this.resetForm();
|
|
||||||
this.onSuccess(response.id);
|
this.onSuccess(response.id);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -105,32 +82,19 @@ class OrganizationAdd extends React.Component {
|
|||||||
history.push(`/organizations/${id}`);
|
history.push(`/organizations/${id}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
updateSelectedInstanceGroups (selectedInstanceGroups) {
|
async getInstanceGroups (params) {
|
||||||
this.setState({ selectedInstanceGroups });
|
const { api } = this.props;
|
||||||
}
|
const data = await api.getInstanceGroups(params);
|
||||||
|
return data;
|
||||||
handleChange (_, evt) {
|
|
||||||
this.setState({ [evt.target.name]: evt.target.value });
|
|
||||||
}
|
|
||||||
|
|
||||||
resetForm () {
|
|
||||||
this.setState({
|
|
||||||
name: '',
|
|
||||||
description: '',
|
|
||||||
});
|
|
||||||
const { results } = this.state;
|
|
||||||
const reset = results.map((result) => ({ id: result.id, name: result.name, isChecked: false }));
|
|
||||||
this.setState({ results: reset });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
results,
|
|
||||||
description,
|
description,
|
||||||
custom_virtualenv,
|
custom_virtualenv,
|
||||||
selectedInstanceGroups,
|
|
||||||
defaultEnv,
|
defaultEnv,
|
||||||
|
instanceGroups,
|
||||||
error
|
error
|
||||||
} = this.state;
|
} = this.state;
|
||||||
const enabled = name.length > 0; // TODO: add better form validation
|
const enabled = name.length > 0; // TODO: add better form validation
|
||||||
@@ -148,11 +112,10 @@ class OrganizationAdd extends React.Component {
|
|||||||
>
|
>
|
||||||
<TextInput
|
<TextInput
|
||||||
isRequired
|
isRequired
|
||||||
type="text"
|
|
||||||
id="add-org-form-name"
|
id="add-org-form-name"
|
||||||
name="name"
|
name="name"
|
||||||
value={name}
|
value={name}
|
||||||
onChange={this.handleChange}
|
onChange={this.onFieldChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup label="Description" fieldId="add-org-form-description">
|
<FormGroup label="Description" fieldId="add-org-form-description">
|
||||||
@@ -160,40 +123,39 @@ class OrganizationAdd extends React.Component {
|
|||||||
id="add-org-form-description"
|
id="add-org-form-description"
|
||||||
name="description"
|
name="description"
|
||||||
value={description}
|
value={description}
|
||||||
onChange={this.handleChange}
|
onChange={this.onFieldChange}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<FormGroup label="Instance Groups" fieldId="simple-form-instance-groups">
|
<FormGroup label="Instance Groups" fieldId="add-org-form-instance-groups">
|
||||||
<Lookup
|
<Lookup
|
||||||
lookupHeader="Instance Groups"
|
lookupHeader="Instance Groups"
|
||||||
onLookupSave={this.updateSelectedInstanceGroups}
|
name="instanceGroups"
|
||||||
data={results}
|
value={instanceGroups}
|
||||||
selected={selectedInstanceGroups}
|
onLookupSave={this.onLookupSave}
|
||||||
|
getItems={this.getInstanceGroups}
|
||||||
/>
|
/>
|
||||||
</FormGroup>
|
</FormGroup>
|
||||||
<ConfigContext.Consumer>
|
<ConfigContext.Consumer>
|
||||||
{({ custom_virtualenvs }) => (
|
{({ custom_virtualenvs }) => (
|
||||||
<AnsibleSelect
|
<FormGroup label="Ansible Environment" fieldId="add-org-form-custom-virtualenv">
|
||||||
labelName="Ansible Environment"
|
<AnsibleSelect
|
||||||
selected={custom_virtualenv}
|
label="Ansible Environment"
|
||||||
selectChange={this.onSelectChange}
|
name="custom_virtualenv"
|
||||||
data={custom_virtualenvs}
|
value={custom_virtualenv}
|
||||||
defaultSelected={defaultEnv}
|
onChange={this.onFieldChange}
|
||||||
/>
|
data={custom_virtualenvs}
|
||||||
|
defaultSelected={defaultEnv}
|
||||||
|
/>
|
||||||
|
</FormGroup>
|
||||||
)}
|
)}
|
||||||
</ConfigContext.Consumer>
|
</ConfigContext.Consumer>
|
||||||
</Gallery>
|
</Gallery>
|
||||||
<ActionGroup className="at-align-right">
|
<FormActionGroup
|
||||||
<Toolbar>
|
onSubmit={this.onSubmit}
|
||||||
<ToolbarGroup>
|
submitDisabled={!enabled}
|
||||||
<Button className="at-C-SubmitButton" variant="primary" onClick={this.onSubmit} isDisabled={!enabled}>Save</Button>
|
onCancel={this.onCancel}
|
||||||
</ToolbarGroup>
|
/>
|
||||||
<ToolbarGroup>
|
{error ? <div>error</div> : ''}
|
||||||
<Button className="at-C-CancelButton" variant="secondary" onClick={this.onCancel}>Cancel</Button>
|
|
||||||
</ToolbarGroup>
|
|
||||||
</Toolbar>
|
|
||||||
</ActionGroup>
|
|
||||||
{ error ? <div>error</div> : '' }
|
|
||||||
</Form>
|
</Form>
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
Reference in New Issue
Block a user