diff --git a/app/javascript/components/set-service-ownership-form/index.jsx b/app/javascript/components/set-service-ownership-form/index.jsx
new file mode 100644
index 00000000000..a2286de7ad0
--- /dev/null
+++ b/app/javascript/components/set-service-ownership-form/index.jsx
@@ -0,0 +1,57 @@
+import React, { Component } from 'react';
+import PropTypes from 'prop-types';
+import { Grid } from 'patternfly-react';
+import MiqFormRenderer from '../../forms/data-driven-form';
+import { http } from '../../http_api';
+import { cleanVirtualDom } from '../../miq-component/helpers';
+import createSchema from './schema';
+
+class SetServiceOwnershipForm extends Component {
+ state = {
+ initialValues: {},
+ }
+
+ componentDidMount() {
+ cleanVirtualDom();
+ const { ownershipIds } = this.props;
+ this.loadInitialData(ownershipIds);
+ }
+
+ loadInitialData = objectIds =>
+ http.post('/service/ownership_form_fields', { object_ids: objectIds })
+ .then(data => this.setState({ initialValues: data }))
+
+
+ handleSubmit = (values, objectIds, url) => miqAjaxButton(url, {
+ objectIds,
+ ...values,
+ })
+
+ render() {
+ const { groupOptions, ownerOptions, ownershipIds } = this.props;
+ const { initialValues } = this.state;
+ const cancelUrl = '/service/ownership_update/?button=cancel';
+ const submitUrl = '/service/ownership_update/?button=save';
+
+ return (
+
+ this.handleSubmit(values, ownershipIds, submitUrl)}
+ onReset={() => add_flash(__('All changes have been reset'), 'warn')}
+ onCancel={() => miqAjaxButton(cancelUrl)}
+ canReset
+ />
+
+ );
+ }
+}
+
+SetServiceOwnershipForm.propTypes = {
+ ownershipIds: PropTypes.arrayOf(PropTypes.string).isRequired,
+ groupOptions: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)).isRequired,
+ ownerOptions: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)).isRequired,
+};
+
+export default SetServiceOwnershipForm;
diff --git a/app/javascript/components/set-service-ownership-form/schema.js b/app/javascript/components/set-service-ownership-form/schema.js
new file mode 100644
index 00000000000..8a1700bbfa2
--- /dev/null
+++ b/app/javascript/components/set-service-ownership-form/schema.js
@@ -0,0 +1,25 @@
+import { componentTypes } from '@data-driven-forms/react-form-renderer';
+
+const createSchema = (ownerOptions, groupOptions) => ({
+ fields: [{
+ component: componentTypes.SELECT_COMPONENT,
+ name: 'user',
+ id: 'user_name',
+ label: __('Select an Owner:'),
+ options: ownerOptions.map(([label, value]) => ({
+ label,
+ value,
+ })),
+ }, {
+ component: componentTypes.SELECT_COMPONENT,
+ name: 'group',
+ id: 'group_name',
+ label: __('Select a Group:'),
+ options: groupOptions.map(([label, value]) => ({
+ label,
+ value,
+ })),
+ }],
+});
+
+export default createSchema;
diff --git a/app/javascript/packs/component-definitions-common.js b/app/javascript/packs/component-definitions-common.js
index 78f8e4c0feb..6c524306a7d 100644
--- a/app/javascript/packs/component-definitions-common.js
+++ b/app/javascript/packs/component-definitions-common.js
@@ -8,6 +8,7 @@ import FormButtonsRedux from '../forms/form-buttons-redux';
import MiqAboutModal from '../components/miq-about-modal';
import CloudTenantForm from '../components/cloud-tenant-form/cloud-tenant-form';
import ServiceForm from '../components/service-form';
+import SetServiceOwnershipForm from '../components/set-service-ownership-form';
/**
* Add component definitions to this file.
@@ -26,3 +27,4 @@ ManageIQ.component.addReact('FormButtonsRedux', FormButtonsRedux);
ManageIQ.component.addReact('MiqAboutModal', MiqAboutModal);
ManageIQ.component.addReact('CloudTenantForm', CloudTenantForm);
ManageIQ.component.addReact('ServiceForm', ServiceForm);
+ManageIQ.component.addReact('SetServiceOwnershipForm', SetServiceOwnershipForm);
diff --git a/app/javascript/spec/set-service-ownership-form/set-service-ownership-form.spec.js b/app/javascript/spec/set-service-ownership-form/set-service-ownership-form.spec.js
new file mode 100644
index 00000000000..ae46c41cf8d
--- /dev/null
+++ b/app/javascript/spec/set-service-ownership-form/set-service-ownership-form.spec.js
@@ -0,0 +1,81 @@
+import React from 'react';
+import { mount } from 'enzyme';
+import FormRenderer from '@data-driven-forms/react-form-renderer';
+import SetServiceOwnershipForm from '../../components/set-service-ownership-form';
+import createSchema from '../../components/set-service-ownership-form/schema';
+import { http } from '../../http_api';
+
+describe('Set service ownership form component', () => {
+ let initialProps;
+ const httpSpy = jest.spyOn(http, 'post');
+ const submitSpy = jest.spyOn(window, 'miqAjaxButton');
+ const flashSpy = jest.spyOn(window, 'add_flash');
+
+ beforeEach(() => {
+ initialProps = {
+ ownershipIds: ['123456'],
+ groupOptions: [['Foo', '1'], ['Bar', '2']],
+ ownerOptions: [['Baz', '3'], ['Quxx', '4']],
+ };
+ });
+
+ afterEach(() => {
+ submitSpy.mockReset();
+ flashSpy.mockReset();
+ });
+
+ it('should correctly map group and owner options ', () => {
+ const expectedResult = [
+ expect.objectContaining({
+ options: [{
+ value: '3',
+ label: 'Baz',
+ }, {
+ value: '4',
+ label: 'Quxx',
+ }],
+ }),
+ expect.objectContaining({
+ options: [{
+ value: '1',
+ label: 'Foo',
+ }, {
+ value: '2',
+ label: 'Bar',
+ }],
+ }),
+ ];
+ const { fields } = createSchema(initialProps.ownerOptions, initialProps.groupOptions);
+ expect(fields).toEqual(expectedResult);
+ });
+
+ // TO DO replace with actual request mock
+ it('should request initialForm values after mount', () => {
+ mount();
+ expect(httpSpy).toHaveBeenCalledWith('/service/ownership_form_fields', { object_ids: ['123456'] });
+ });
+
+ it('should send correct data on save', () => {
+ const wrapper = mount();
+ const Form = wrapper.find(FormRenderer).childAt(0);
+ Form.instance().form.change('user', 'foo');
+ wrapper.find('button').at(0).simulate('click');
+ expect(submitSpy).toHaveBeenCalledWith('/service/ownership_update/?button=save', { objectIds: ['123456'], user: 'foo' });
+ });
+
+ it('should send correct data on cancel', () => {
+ const wrapper = mount();
+ wrapper.find('button').last().simulate('click');
+ expect(submitSpy).toHaveBeenCalledWith('/service/ownership_update/?button=cancel');
+ });
+
+ it('should reset formValues and add flash messages on reset click', () => {
+ const wrapper = mount();
+ const Form = wrapper.find(FormRenderer).childAt(0);
+ Form.instance().form.change('user', 'foo');
+ expect(Form.instance().form.getState().values).toEqual({ user: 'foo' });
+ wrapper.find('button').at(1).simulate('click');
+ expect(Form.instance().form.getState().values).toEqual({});
+ expect(flashSpy).toHaveBeenCalledWith(expect.any(String), 'warn');
+ });
+});