[Sahara] Unified job interface map UI

A UI to serve the "Unified Job Interface Map" feature in Sahara. This
feature adds an "interface" map to the API for job creation, such that
the operator registering a job can define a unified, human-readable way
to pass in all arguments, parameters, and configurations that the
execution of that job may require or accept. This will allow platform-
agnostic wizarding at the job execution phase and allows users to
document use of their own jobs once in a persistent, standardized format.

Change-Id: I81527405aa8038b13ff6e55ac5d972775ae810a6
Implements: blueprint unified-job-interface-map-ui
This commit is contained in:
Ethan Gafford 2015-08-05 17:54:37 -04:00
parent d83de86ab8
commit b400485ed0
11 changed files with 522 additions and 28 deletions

View File

@ -379,13 +379,14 @@ def job_binary_internal_delete(request, jbi_id):
client(request).job_binary_internals.delete(job_binary_id=jbi_id)
def job_create(request, name, j_type, mains, libs, description):
def job_create(request, name, j_type, mains, libs, description, interface):
return client(request).jobs.create(
name=name,
type=j_type,
mains=mains,
libs=libs,
description=description)
description=description,
interface=interface)
def job_list(request, search_opts=None):
@ -405,13 +406,15 @@ def job_get_configs(request, job_type):
def job_execution_create(request, job_id, cluster_id,
input_id, output_id, configs):
input_id, output_id, configs,
interface):
return client(request).job_executions.create(
job_id=job_id,
cluster_id=cluster_id,
input_id=input_id,
output_id=output_id,
configs=configs)
configs=configs,
interface=interface)
def _resolve_job_execution_names(job_execution, cluster=None,

View File

@ -0,0 +1,43 @@
{% load i18n %}
<input type="hidden" value="[]" name="argument_ids" id="argument_ids">
<label for="value_type">{% trans "Select a Value Type for your next argument:" %}</label>
<span class="row">
<span class="input-group col-sm-4">
<select id="value_type" name="value_type" class="form-control">
<option>{% trans "Select" %}</option>
<option value="string">{% trans "String" %}</option>
<option value="number">{% trans "Number" %}</option>
<option value="data_source">{% trans "Data Source" %}</option>
</select>
<span class="input-group-btn">
<button type="button" id="add_argument_button" class="btn btn-default disabled" onclick="horizon.job_interface_arguments.add_interface_argument();">
<span class="fa fa-plus"></span>
</button>
</span>
</span>
</span>
<div id="job_interface_arguments">
<div id="arguments_table">
</div>
</div>
<script type="text/javascript">
$(function () {
horizon.job_interface_arguments.init_arguments();
{% for argument in form.arguments %}
horizon.job_interface_arguments.add_argument_node(
horizon.job_interface_arguments.get_next_argument_id(),
"{{ argument.name }}",
"{{ argument.description }}",
"{{ argument.mapping_type }}",
"{{ argument.location }}",
"{{ argument.value_type }}",
"{{ argument.required }}",
"{{ argument.default_value }}");
{% endfor %}
{% for field_id in form.errors_fields %}
horizon.job_interface_arguments.mark_argument_element_as_wrong("{{ field_id }}");
{% endfor %}
});
</script>

View File

@ -34,9 +34,6 @@
$navbar.hide();
}
$(".hidden_nodegroups_field").val("");
$(".hidden_configure_field").val("");
lower_limit = 0;
$(".count-field").change();
if ($(modal).find(".hidden_create_field").length > 0) {

View File

@ -1,7 +1,7 @@
{% load i18n %}
<script type="text/javascript">
var remove_btn_label = '{% trans "Remove" %}';
var template = '<tr id_attr="$id">' +
var lib_template = '<tr id_attr="$id">' +
'<td>' +
'<div class="input form-group" style="padding-right:15px;"><input disabled value="$lib_name" class="form-control" /></div>' +
'</td>' +
@ -17,7 +17,7 @@
'</td>' +
'</tr>';
function get_next_id() {
function get_next_lib_id() {
var max = -1;
$("#libs tbody tr").each(function () {
max = Math.max(max, parseInt($(this).attr("id_attr")));
@ -33,8 +33,8 @@
$("#lib_ids").val(JSON.stringify(ids));
}
function add_node(lib_name, id) {
var tmp = template.
function add_lib_node(lib_name, id) {
var tmp = lib_template.
replace(/\$id/g, id).
replace(/\$lib_name/g, lib_name);
$("#libs tbody").append(tmp);
@ -50,12 +50,12 @@
if (lib_val == "") {
return;
}
add_node(chosen.html(), chosen.val());
add_lib_node(chosen.html(), chosen.val());
}
function delete_lib(el) {
var tr = $(el).parents("tr")[0];
tr.parentNode.removeChild(tr);
var id = get_next_id();
var id = get_next_lib_id();
if (id == 0) {
$("#libs").hide();
}

View File

@ -54,7 +54,8 @@ class DataProcessingJobTests(test.TestCase):
api.sahara.job_binary_list(IsA(http.HttpRequest)).AndReturn([])
api.sahara.job_binary_list(IsA(http.HttpRequest)).AndReturn([])
api.sahara.job_create(IsA(http.HttpRequest),
'test', 'Pig', [], [], 'test create')
'test', 'Pig', [], [], 'test create',
interface=[])
api.sahara.job_types_list(IsA(http.HttpRequest)) \
.AndReturn(self.job_types.list())
self.mox.ReplayAll()
@ -62,7 +63,66 @@ class DataProcessingJobTests(test.TestCase):
'job_type': 'pig',
'lib_binaries': [],
'lib_ids': '[]',
'job_description': 'test create'}
'job_description': 'test create',
'hidden_arguments_field': [],
'argument_ids': '[]'}
url = reverse('horizon:project:data_processing.jobs:create-job')
res = self.client.post(url, form_data)
self.assertNoFormErrors(res)
@test.create_stubs({api.sahara: ('job_binary_list',
'job_create',
'job_types_list')})
def test_create_with_interface(self):
api.sahara.job_binary_list(IsA(http.HttpRequest)).AndReturn([])
api.sahara.job_binary_list(IsA(http.HttpRequest)).AndReturn([])
api.sahara.job_create(IsA(http.HttpRequest),
'test_interface', 'Pig', [], [], 'test create',
interface=[
{
"name": "argument",
"description": None,
"mapping_type": "args",
"location": "0",
"value_type": "number",
"required": True,
"default": None
},
{
"name": "config",
"description": "Really great config",
"mapping_type": "configs",
"location": "edp.important.config",
"value_type": "string",
"required": False,
"default": "A value"
}])
api.sahara.job_types_list(IsA(http.HttpRequest)) \
.AndReturn(self.job_types.list())
self.mox.ReplayAll()
form_data = {'job_name': 'test_interface',
'job_type': 'pig',
'lib_binaries': [],
'lib_ids': '[]',
'job_description': 'test create',
'hidden_arguments_field': [],
'argument_ids': '["0", "1"]',
'argument_id_0': '0',
'argument_name_0': 'argument',
'argument_description_0': '',
'argument_mapping_type_0': 'args',
'argument_location_0': '0',
'argument_value_type_0': 'number',
'argument_required_0': True,
'argument_default_value_0': '',
'argument_id_1': '1',
'argument_name_1': 'config',
'argument_description_1': 'Really great config',
'argument_mapping_type_1': 'configs',
'argument_location_1': 'edp.important.config',
'argument_value_type_1': 'string',
'argument_default_value_1': 'A value'}
url = reverse('horizon:project:data_processing.jobs:create-job')
res = self.client.post(url, form_data)
@ -106,11 +166,14 @@ class DataProcessingJobTests(test.TestCase):
.MultipleTimes().AndReturn(self.data_sources.list())
api.sahara.job_list(IsA(http.HttpRequest)) \
.AndReturn(self.jobs.list())
api.sahara.job_get(IsA(http.HttpRequest), IsA(unicode)) \
.AndReturn(job)
api.sahara.job_execution_create(IsA(http.HttpRequest),
IsA(unicode),
IsA(unicode),
IsA(unicode),
IsA(unicode),
IsA(dict),
IsA(dict)).AndReturn(job_execution)
self.mox.ReplayAll()
@ -121,6 +184,7 @@ class DataProcessingJobTests(test.TestCase):
'job_input': input_ds.id,
'job_output': output_ds.id,
'config': {},
'argument_ids': '{}',
'adapt_oozie': 'on',
'hbase_common_lib': 'on',
'java_opts': '',

View File

@ -23,6 +23,8 @@ from horizon import workflows
from openstack_dashboard.contrib.sahara.content.data_processing \
.utils import helpers
import openstack_dashboard.contrib.sahara.content.data_processing \
.utils.workflow_helpers as whelpers
from openstack_dashboard.contrib.sahara.api import sahara as saharaclient
@ -140,6 +142,62 @@ class GeneralConfigAction(workflows.Action):
"project/data_processing.jobs/_create_job_help.html")
class ConfigureInterfaceArgumentsAction(workflows.Action):
hidden_arguments_field = forms.CharField(
required=False,
widget=forms.HiddenInput(attrs={"class": "hidden_arguments_field"}))
argument_ids = forms.CharField(
required=False,
widget=forms.HiddenInput())
def __init__(self, request, *args, **kwargs):
super(ConfigureInterfaceArgumentsAction, self).__init__(
request, *args, **kwargs)
request_source = None
if 'argument_ids' in request.POST:
request_source = request.POST
elif 'argument_ids' in request.REQUEST:
request_source = request.REQUEST
if request_source:
self.arguments = []
for id in json.loads(request_source['argument_ids']):
fields = {
"name": "argument_name_" + str(id),
"description": "argument_description_" + str(id),
"mapping_type": "argument_mapping_type_" + str(id),
"location": "argument_location_" + str(id),
"value_type": "argument_value_type_" + str(id),
"default_value": "argument_default_value_" + str(id)}
argument = {k: request_source[v]
for k, v in fields.items()}
required_field = "argument_required_" + str(id)
fields.update({"required": required_field})
argument.update(
{"required": required_field in request_source})
self.arguments.append(argument)
whelpers.build_interface_argument_fields(self, **fields)
def clean(self):
cleaned_data = super(ConfigureInterfaceArgumentsAction, self).clean()
return cleaned_data
class Meta(object):
name = _("Interface Arguments")
class ConfigureArguments(workflows.Step):
action_class = ConfigureInterfaceArgumentsAction
contributes = ("hidden_arguments_field", )
template_name = ("project/data_processing.jobs/"
"job_interface_arguments_template.html")
def contribute(self, data, context):
for k, v in data.items():
context[k] = v
return context
class GeneralConfig(workflows.Step):
action_class = GeneralConfigAction
contributes = ("job_name", "job_type", "job_description", "main_binary")
@ -171,7 +229,7 @@ class CreateJob(workflows.Workflow):
success_message = _("Job created")
failure_message = _("Could not create job template")
success_url = "horizon:project:data_processing.jobs:index"
default_steps = (GeneralConfig, ConfigureLibs)
default_steps = (GeneralConfig, ConfigureLibs, ConfigureArguments)
def handle(self, request, context):
main_locations = []
@ -184,6 +242,22 @@ class CreateJob(workflows.Workflow):
if context.get("main_binary", None):
main_locations.append(context["main_binary"])
argument_ids = json.loads(context['argument_ids'])
interface = [
{
"name": context['argument_name_' + str(arg_id)],
"description": (context['argument_description_' + str(arg_id)]
or None),
"mapping_type": context['argument_mapping_type_'
+ str(arg_id)],
"location": context['argument_location_' + str(arg_id)],
"value_type": context['argument_value_type_' + str(arg_id)],
"required": context['argument_required_' + str(arg_id)],
"default": (context['argument_default_value_' + str(arg_id)]
or None)
} for arg_id in argument_ids
]
try:
job = saharaclient.job_create(
request,
@ -191,7 +265,8 @@ class CreateJob(workflows.Workflow):
context["job_type"],
main_locations,
lib_locations,
context["job_description"])
context["job_description"],
interface=interface)
hlps = helpers.Helpers(request)
if hlps.is_from_guide():

View File

@ -127,6 +127,33 @@ class JobExecutionExistingGeneralConfigAction(JobExecutionGeneralConfigAction):
"project/data_processing.jobs/_launch_job_help.html")
def _merge_interface_with_configs(interface, job_configs):
interface_by_mapping = {(arg['mapping_type'], arg['location']): arg
for arg in interface}
mapped_types = ("configs", "params")
mapped_configs = {
(mapping_type, key): value for mapping_type in mapped_types
for key, value in job_configs.get(mapping_type, {}).items()
}
for index, arg in enumerate(job_configs.get('args', [])):
mapped_configs['args', str(index)] = arg
free_arguments, interface_arguments = {}, {}
for mapping, value in mapped_configs.items():
if mapping in interface_by_mapping:
arg = interface_by_mapping[mapping]
interface_arguments[arg['id']] = value
else:
free_arguments[mapping] = value
configs = {"configs": {}, "params": {}, "args": {}}
for mapping, value in free_arguments.items():
mapping_type, location = mapping
configs[mapping_type][location] = value
configs["args"] = [
value for key, value in sorted(configs["args"].items(),
key=lambda x: int(x[0]))]
return configs, interface_arguments
class JobConfigAction(workflows.Action):
MAIN_CLASS = "edp.java.main_class"
JAVA_OPTS = "edp.java.java_opts"
@ -182,9 +209,10 @@ class JobConfigAction(workflows.Action):
super(JobConfigAction, self).__init__(request, *args, **kwargs)
job_ex_id = request.REQUEST.get("job_execution_id")
if job_ex_id is not None:
job_ex_id = request.REQUEST.get("job_execution_id")
job_ex = saharaclient.job_execution_get(request, job_ex_id)
job_configs = job_ex.job_configs
job = saharaclient.job_get(request, job_ex.job_id)
job_configs, interface_args = _merge_interface_with_configs(
job.interface, job_ex.job_configs)
edp_configs = {}
if 'configs' in job_configs:
@ -365,6 +393,57 @@ class ClusterGeneralConfig(workflows.Step):
return context
class JobExecutionInterfaceConfigAction(workflows.Action):
def __init__(self, request, *args, **kwargs):
super(JobExecutionInterfaceConfigAction, self).__init__(
request, *args, **kwargs)
job_id = (request.GET.get("job_id")
or request.POST.get("job"))
job = saharaclient.job_get(request, job_id)
interface = job.interface or []
interface_args = {}
job_ex_id = request.REQUEST.get("job_execution_id")
if job_ex_id is not None:
job_ex = saharaclient.job_execution_get(request, job_ex_id)
job = saharaclient.job_get(request, job_ex.job_id)
job_configs, interface_args = _merge_interface_with_configs(
job.interface, job_ex.job_configs)
for argument in interface:
field = forms.CharField(
required=argument.get('required'),
label=argument['name'],
initial=(interface_args.get(argument['id']) or
argument.get('default')),
help_text=argument.get('description'),
widget=forms.TextInput()
)
self.fields['argument_%s' % argument['id']] = field
self.fields['argument_ids'] = forms.CharField(
initial=json.dumps({argument['id']: argument['name']
for argument in interface}),
widget=forms.HiddenInput()
)
def clean(self):
cleaned_data = super(JobExecutionInterfaceConfigAction, self).clean()
return cleaned_data
class Meta(object):
name = _("Interface Arguments")
class JobExecutionInterfaceConfig(workflows.Step):
action_class = JobExecutionInterfaceConfigAction
def contribute(self, data, context):
for k, v in data.items():
context[k] = v
return context
class LaunchJob(workflows.Workflow):
slug = "launch_job"
name = _("Launch Job")
@ -372,16 +451,22 @@ class LaunchJob(workflows.Workflow):
success_message = _("Job launched")
failure_message = _("Could not launch job")
success_url = "horizon:project:data_processing.job_executions:index"
default_steps = (JobExecutionExistingGeneralConfig, JobConfig)
default_steps = (JobExecutionExistingGeneralConfig, JobConfig,
JobExecutionInterfaceConfig)
def handle(self, request, context):
argument_ids = json.loads(context['argument_ids'])
interface = {name: context["argument_" + str(arg_id)]
for arg_id, name in argument_ids.items()}
saharaclient.job_execution_create(
request,
context["job_general_job"],
context["job_general_cluster"],
context["job_general_job_input"],
context["job_general_job_output"],
context["job_config"])
context["job_config"],
interface)
return True
@ -411,11 +496,10 @@ class SelectHadoopPluginAction(t_flows.SelectPluginAction):
if job_ex_id is not None:
self.fields["job_execution_id"] = forms.ChoiceField(
label=_("Job Execution ID"),
initial=request.REQUEST.get("job_execution_id"),
initial=job_ex_id,
widget=forms.HiddenInput(
attrs={"class": "hidden_create_field"}))
job_ex_id = request.REQUEST.get("job_execution_id")
job_configs = (
saharaclient.job_execution_get(request,
job_ex_id).job_configs)
@ -459,7 +543,8 @@ class LaunchJobNewCluster(workflows.Workflow):
success_url = "horizon:project:data_processing.jobs:index"
default_steps = (ClusterGeneralConfig,
JobExecutionGeneralConfig,
JobConfig)
JobConfig,
JobExecutionInterfaceConfig)
def handle(self, request, context):
node_groups = None
@ -470,6 +555,10 @@ class LaunchJobNewCluster(workflows.Workflow):
ct_id = context["cluster_general_cluster_template"] or None
user_keypair = context["cluster_general_keypair"] or None
argument_ids = json.loads(context['argument_ids'])
interface = {name: context["argument_" + str(arg_id)]
for arg_id, name in argument_ids.items()}
try:
cluster = saharaclient.cluster_create(
request,
@ -496,7 +585,8 @@ class LaunchJobNewCluster(workflows.Workflow):
cluster.id,
context["job_general_job_input"],
context["job_general_job_output"],
context["job_config"])
context["job_config"],
interface)
except Exception:
exceptions.handle(request,
_("Unable to launch job."))

View File

@ -0,0 +1,175 @@
horizon.job_interface_arguments = {
argument_template: '' +
'<div id_attr="$id">' +
'<table class="argument-form table table-striped table-hover" id_attr="$id">' +
'<tr>' +
'<td class="col-sm-2 small-padding">' +
'<input type="button" class="btn btn-danger" id="delete_btn_$id" data-toggle="dropdown" onclick="horizon.job_interface_arguments.delete_interface_argument(this)" value="' + gettext('Remove') + '" />' +
'</td>' +
'<td class="col-sm-2 small-padding">' +
'<input id="argument_id_$id" value="$id" type="hidden" name="argument_id_$id">' +
'<label for="argument_name_$id">' + gettext("Name") + ':</label>' +
'</td>' +
'<td class="col-sm-4 small-padding">' +
'<input id="argument_name_$id" value="$name" type="text" name="argument_name_$id" class="form-control">' +
'</td>' +
'</tr>' +
'<tr>' +
'<td class="col-sm-2 small-padding"></td>' +
'<td class="col-sm-2 small-padding">' +
'<label for="argument_description_$id">' + gettext("Description") + ':</label>' +
'</td>' +
'<td class="col-sm-4 small-padding">' +
'<input id="argument_description_$id" value="$description" type="text" name="argument_description_$id" class="form-control">' +
'</td>' +
'</tr>' +
'<tr>' +
'<td class="col-sm-2 small-padding"></td>' +
'<td class="col-sm-2 small-padding">' +
'<label for="argument_mapping_type_$id">' + gettext("Mapping Type") + ':</label>' +
'<span class="help-icon" title="" data-placement="top" data-toggle="tooltip" data-original-title="' + gettext("See http://docs.openstack.org/developer/sahara/userdoc/edp.html for definitions.") + '">' +
'<span class="fa fa-question-circle"></span>' +
'</span>' +
'</td>' +
'<td class="col-sm-4 small-padding">' +
'<select id="argument_mapping_type_$id" selected="$mapping_type" name="argument_mapping_type_$id" class="form-control">' +
'<option value="args">' + gettext("Positional Argument") + '</option>' +
'<option value="params">' + gettext("Named Parameter") + '</option>' +
'<option value="configs">' + gettext("Configuration Value") + '</option>' +
'</select>' +
'</td>' +
'</tr>' +
'<tr>' +
'<td class="col-sm-2 small-padding"></td>' +
'<td class="col-sm-2 small-padding">' +
'<label for="argument_location_$id">' + gettext("Location") + ':</label>' +
'<span class="help-icon" title="" data-placement="top" data-toggle="tooltip" data-original-title="' + gettext("For configs and params, type the key name; for args, type the index as an integer, starting from 0.") + '">' +
'<span class="fa fa-question-circle"></span>' +
'</span>' +
'</td>' +
'<td class="col-sm-4 small-padding">' +
'<input id="argument_location_$id" value="$location" type="text" name="argument_location_$id" class="form-control">' +
'</td>' +
'</tr>' +
'<tr>' +
'<td class="col-sm-2 small-padding"></td>' +
'<td class="col-sm-2 small-padding">' +
'<label for="argument_value_type_$id">' + gettext("Value Type") + ':</label>' +
'</td>' +
'<td class="col-sm-4 small-padding">' +
'<select id="argument_value_type_$id" selected="$value_type" name="argument_value_type_$id" class="form-control">' +
'<option value="string">' + gettext("String") + '</option>' +
'<option value="number">' + gettext("Number") + '</option>' +
'<option value="data_source">' + gettext("Data Source") + '</option>' +
'</select>' +
'</td>' +
'</tr>' +
'<tr>' +
'<td class="col-sm-2 small-padding"></td>' +
'<td class="col-sm-2 small-padding">' +
'<label for="argument_required_$id">' + gettext("Required?") + ':</label>' +
'</td>' +
'<td class="col-sm-4 small-padding">' +
'<input id="argument_required_$id" type="checkbox" name="argument_required_$id" checked class="form-control">' +
'</td>' +
'</tr>' +
'<tr>' +
'<td class="col-sm-2 small-padding"></td>' +
'<td class="col-sm-2 small-padding">' +
'<label for="argument_default_value_$id">' + gettext("Default Value") + ':</label>' +
'<span class="help-icon" title="" data-placement="top" data-toggle="tooltip" data-original-title="' + gettext("For data sources, use a data source UUID or a path (as per data source creation.)") + '">' +
'<span class="fa fa-question-circle"></span>' +
'</span>' +
'</td>' +
'<td class="col-sm-4 small-padding">' +
'<input id="argument_default_value_$id" value="$default_value" type="text" name="argument_default_value_$id" class="form-control">' +
'</td>' +
'</tr>' +
'</table>' +
'</div>',
job_interface: null,
argument_ids: null,
value_type: null,
add_argument_button: null,
value_type_default: null,
current_value_type: function() {
return this.value_type.find("option:selected").html();
},
mark_argument_element_as_wrong: function(id) {
$("#" + id).addClass("error");
},
get_next_argument_id: function() {
var max = -1;
$(".argument-form").each(function () {
max = Math.max(max, parseInt($(this).attr("id_attr")));
});
return max + 1;
},
set_argument_ids: function() {
var ids = [];
$(".argument-form").each(function () {
var id = parseInt($(this).attr("id_attr"));
if (!isNaN(id)) {
ids.push(id);
}
});
this.argument_ids.val(JSON.stringify(ids));
},
add_argument_node: function(id, name, description, mapping_type, location, value_type, required, default_value) {
var tmp = this.argument_template.
replace(/\$id/g, id).
replace(/\$name/g, name).
replace(/\$description/g, description).
replace(/\$mapping_type/g, mapping_type).
replace(/\$location/g, location).
replace(/\$value_type/g, value_type).
replace(/\$required/g, required).
replace(/\$default_value/g, default_value);
this.job_interface.find("div:last").after(tmp);
this.job_interface.show();
this.set_argument_ids();
},
add_interface_argument: function() {
var value_type = this.current_value_type();
if (value_type === this.value_type_default) {
return;
}
this.add_argument_node(this.get_next_argument_id(), "", "", "args", "", value_type, true, "");
$(".count-field").change();
},
delete_interface_argument: function(el) {
$(el).closest("div").remove();
var id = this.get_next_argument_id();
if (id === 0) {
this.job_interface.hide();
}
this.set_argument_ids();
},
init_arguments: function() {
// This line enables tooltips on this tab to properly display their help text.
$("body").tooltip({selector: ".help-icon"});
this.job_interface = $("#job_interface_arguments");
this.argument_ids = $("#argument_ids");
this.value_type = $("#value_type");
this.add_argument_button = $("#add_argument_button");
this.value_type_default = this.current_value_type();
this.value_type.change(function () {
if (horizon.job_interface_arguments.current_value_type() === this.value_type_default) {
horizon.job_interface_arguments.add_argument_button.addClass("disabled");
} else {
horizon.job_interface_arguments.add_argument_button.removeClass("disabled");
}
});
this.job_interface.hide();
}
}

View File

@ -126,6 +126,46 @@ def build_node_group_fields(action, name, template, count, serialized=None):
widget=forms.HiddenInput())
def build_interface_argument_fields(
action, name, description, mapping_type, location, value_type,
required, default_value):
action.fields[name] = forms.CharField(
label=_("Name"),
widget=forms.TextInput(),
required=True)
action.fields[description] = forms.CharField(
label=_("Description"),
widget=forms.TextInput(),
required=False)
action.fields[mapping_type] = forms.ChoiceField(
label=_("Mapping Type"),
widget=forms.Select(),
required=True,
choices=[("args", _("Positional Argument")),
("configs", _("Configuration Value")),
("params", _("Named Parameter"))])
action.fields[location] = forms.CharField(
label=_("Location"),
widget=forms.TextInput(),
required=True)
action.fields[value_type] = forms.ChoiceField(
label=_("Value Type"),
widget=forms.Select(),
required=True,
choices=[("string", _("String")),
("number", _("Number")),
("data_source", _("Data Source"))])
action.fields[required] = forms.BooleanField(
widget=forms.CheckboxInput(),
label=_("Required"),
required=False,
initial=True)
action.fields[default_value] = forms.CharField(
label=_("Default Value"),
widget=forms.TextInput(),
required=False)
def parse_configs_from_context(context, defaults):
configs_dict = dict()
for key, val in context.items():

View File

@ -19,6 +19,11 @@ PANEL_DASHBOARD = 'project'
PANEL_GROUP = 'data_processing'
# Python panel class of the PANEL to be added.
ADD_PANEL = \
('openstack_dashboard.contrib.sahara.'
'content.data_processing.jobs.panel.JobsPanel')
ADD_PANEL = (
'openstack_dashboard.contrib.sahara.'
'content.data_processing.jobs.panel.JobsPanel')
ADD_JS_FILES = [
'dashboard/project/data_processing/'
'data_processing.job_interface_arguments.js'
]

View File

@ -452,6 +452,7 @@ def data(TEST):
"url": "internal-db://80121dea-f8bd-4ad3-bcc7-096f4bfc722d"
}
],
"interface": [],
"name": "pigjob",
"tenant_id": "429ad8447c2d47bc8e0382d244e1d1df",
"type": "Pig",
@ -537,6 +538,7 @@ def data(TEST):
"configs": {},
"params": {}
},
"interface": {},
"job_id": "a077b851-46be-4ad7-93c3-2d83894546ef",
"oozie_job_id": "0000000-140604200538581-oozie-hado-W",
"output_id": "426fb01c-5c7e-472d-bba2-b1f0fe7e0ede",