Merge pull request #8022 from jmchilton/vuejs_show_params_1

Rework dataset job properties tables into components+APIs
This commit is contained in:
Dannon
2019-05-24 10:04:43 -04:00
committed by GitHub
12 changed files with 468 additions and 193 deletions
+2
View File
@@ -100,6 +100,8 @@ export const chartUtilities = {
export { initMasthead } from "components/Masthead/initMasthead";
export { panelManagement } from "onload/globalInits/panelManagement";
export { mountMakoTags } from "components/Tags";
export { mountJobMetrics } from "components/JobMetrics";
export { mountJobParameters } from "components/JobParameters";
// Used in common.mako
export { default as store } from "storemodern";
@@ -0,0 +1,80 @@
<template>
<div v-if="metricsByPlugins">
<h3 v-if="includeTitle">Job Metrics</h3>
<div v-for="plugin in orderedPlugins" v-bind:key="plugin">
<h4>{{ plugin }}</h4>
<table class="tabletip info_data_table">
<tbody>
<tr v-for="(metricValue, metricTitle) in metricsByPlugins[plugin]" v-bind:key="metricTitle">
<td>{{ metricTitle }}</td>
<td>{{ metricValue }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</template>
<script>
import { getAppRoot } from "onload/loadConfig";
import axios from "axios";
export default {
props: {
jobId: {
type: String
},
datasetId: {
type: String
},
datasetType: {
type: String,
default: "hda"
},
includeTitle: {
type: Boolean,
default: true
}
},
data() {
return {
metricsByPlugins: {}
};
},
created: function() {
let url;
if (this.jobId) {
url = `${getAppRoot()}api/jobs/${this.jobId}/metrics`;
} else {
url = `${getAppRoot()}api/datasets/${this.datasetId}/metrics?hda_ldda=${this.datasetType}`;
}
this.ajaxCall(url);
},
computed: {
orderedPlugins: function() {
return Object.keys(this.metricsByPlugins).sort();
}
},
methods: {
ajaxCall: function(url) {
axios
.get(url)
.then(response => {
const metricsByPlugins = {};
const metrics = response.data;
metrics.forEach(metric => {
if (!(metric.plugin in metricsByPlugins)) {
metricsByPlugins[metric.plugin] = {};
}
const metricsForPlugin = metricsByPlugins[metric.plugin];
metricsForPlugin[metric.title] = metric.value;
});
this.metricsByPlugins = metricsByPlugins;
})
.catch(e => {
console.error(e);
});
}
}
};
</script>
@@ -0,0 +1,4 @@
export { default as JobMetrics } from "./JobMetrics";
// functions for mounting job metrics in non-Vue environments
export { mountJobMetrics } from "./mount";
@@ -0,0 +1,19 @@
/**
* Endpoint for mounting job metrics from non-Vue environment.
*/
import $ from "jquery";
import Vue from "vue";
import JobMetrics from "./JobMetrics.vue";
export const mountJobMetrics = (propsData = {}) => {
$(".job-metrics").each((index, el) => {
const jobId = $(el).attr("job_id");
const datasetId = $(el).attr("dataset_id");
const datasetType = $(el).attr("dataset_type") || "hda";
const component = Vue.extend(JobMetrics);
propsData.jobId = jobId;
propsData.datasetId = datasetId;
propsData.datasetType = datasetType;
return new component({ propsData: propsData }).$mount(el);
});
};
@@ -0,0 +1,122 @@
<template>
<div class="tool-parameters">
<h3 v-if="includeTitle">Tool Parameters</h3>
<table class="tabletip" id="tool-parameters">
<thead>
<tr>
<th>Input Parameter</th>
<th>Value</th>
<th v-if="anyNotes">Note for rerun</th>
</tr>
</thead>
<tbody>
<tr v-for="(parameter, index) in parameters" v-bind:key="index">
<td v-bind:style="{ 'padding-left': `${(parameter.depth - 1) * 10}px` }">
{{ parameter.text }}
</td>
<td v-if="Array.isArray(parameter.value)">
<ul style="padding-inline-start: 25px;">
<li v-for="(elVal, index) in parameter.value" v-bind:key="index">
<span v-if="elVal.src == 'hda'">
<a :href="appRoot() + 'datasets/' + elVal.id + '/show_params'">
{{ elVal.hid }}: {{ elVal.name }}
</a>
</span>
<span v-else>
{{ elVal.hid }}: {{ elVal.name }}
</span>
</li>
</ul>
</td>
<td v-else>
{{ parameter.value }}
</td>
<td v-if="anyNotes">
<em v-if="parameter.notes">{{ parameter.notes }}</em>
</td>
</tr>
</tbody>
</table>
<b-alert :show="hasParameterErrors" variant="danger">
One or more of your original parameters may no longer be valid or displayed properly.
</b-alert>
</div>
</template>
<script>
import { getAppRoot } from "onload/loadConfig";
import axios from "axios";
import Vue from "vue";
import BootstrapVue from "bootstrap-vue";
Vue.use(BootstrapVue);
export default {
props: {
jobId: {
type: String
},
datasetId: {
type: String
},
datasetType: {
type: String,
default: "hda"
},
includeTitle: {
type: Boolean,
default: true
}
},
data() {
return {
parameters: [],
hasParameterErrors: false
};
},
created: function() {
let url;
if (this.jobId) {
url = `${getAppRoot()}api/jobs/${this.jobId}/parameters_display`;
} else {
url = `${getAppRoot()}api/datasets/${this.datasetId}/parameters_display?hda_ldda=${this.datasetType}`;
}
this.ajaxCall(url);
},
computed: {
anyNotes: function() {
let hasNotes = false;
this.parameters.forEach(parameter => {
hasNotes = hasNotes && parameter.note;
});
return hasNotes;
}
},
methods: {
appRoot: function() {
return getAppRoot();
},
ajaxCall: function(url) {
axios
.get(url)
.then(response => {
this.hasParameterErrors = response.data.has_parameter_errors;
this.parameters = response.data.parameters;
})
.catch(e => {
console.error(e);
});
}
}
};
</script>
<style>
table.info_data_table {
table-layout: fixed;
word-break: break-word;
}
table.info_data_table td:nth-child(1) {
width: 25%;
}
</style>
@@ -0,0 +1,4 @@
export { default as JobParameters } from "./JobParameters";
// functions for mounting job parameters display in non-Vue environments
export { mountJobParameters } from "./mount";
@@ -0,0 +1,19 @@
/**
* Endpoint for mounting job parameters from non-Vue environment.
*/
import $ from "jquery";
import Vue from "vue";
import JobParameters from "./JobParameters.vue";
export const mountJobParameters = (propsData = {}) => {
$(".job-parameters").each((index, el) => {
const jobId = $(el).attr("job_id");
const datasetId = $(el).attr("dataset_id");
const datasetType = $(el).attr("dataset_type") || "hda";
const component = Vue.extend(JobParameters);
propsData.jobId = jobId;
propsData.datasetId = datasetId;
propsData.datasetType = datasetType;
return new component({ propsData: propsData }).$mount(el);
});
};
+27 -1
View File
@@ -8,8 +8,13 @@ from sqlalchemy.orm import aliased
from sqlalchemy.sql import select
from galaxy import model
from galaxy.exceptions import RequestParameterInvalidException
from galaxy.exceptions import (
ItemAccessibilityException,
ObjectNotFound,
RequestParameterInvalidException,
)
from galaxy.managers.collections import DatasetCollectionManager
from galaxy.managers.datasets import DatasetManager
from galaxy.managers.hdas import HDAManager
from galaxy.managers.lddas import LDDAManager
from galaxy.util import (
@@ -39,6 +44,27 @@ def get_path_key(path_tuple):
return path_key
class JobManager(object):
def __init__(self, app):
self.app = app
self.dataset_manager = DatasetManager(app)
def get_accessible_job(self, trans, decoded_job_id):
job = trans.sa_session.query(trans.app.model.Job).filter(trans.app.model.Job.id == decoded_job_id).first()
if job is None:
raise ObjectNotFound()
belongs_to_user = (job.user == trans.user) if job.user else (job.session_id == trans.get_galaxy_session().id)
if not trans.user_is_admin and not belongs_to_user:
# Check access granted via output datasets.
if not job.output_datasets:
raise ItemAccessibilityException("Job has no output datasets.")
for data_assoc in job.output_datasets:
if not self.dataset_manager.is_accessible(data_assoc.dataset.dataset, trans.user):
raise ItemAccessibilityException("You are not allowed to rerun this job.")
return job
class JobSearch(object):
"""Search for jobs using tool inputs or other jobs"""
def __init__(self, app):
+177 -24
View File
@@ -12,24 +12,25 @@ from sqlalchemy import or_
from galaxy import exceptions
from galaxy import model
from galaxy import util
from galaxy.managers.datasets import DatasetManager
from galaxy.managers.jobs import JobSearch
from galaxy.managers.jobs import JobManager, JobSearch
from galaxy.web import (
expose_api,
expose_api_anonymous,
)
from galaxy.web.base.controller import BaseAPIController
from galaxy.web.base.controller import UsesLibraryMixinItems
from galaxy.web.base.controller import (
BaseAPIController,
UsesVisualizationMixin
)
from galaxy.work.context import WorkRequestContext
log = logging.getLogger(__name__)
class JobController(BaseAPIController, UsesLibraryMixinItems):
class JobController(BaseAPIController, UsesVisualizationMixin):
def __init__(self, app):
super(JobController, self).__init__(app)
self.dataset_manager = DatasetManager(app)
self.job_manager = JobManager(app)
self.job_search = JobSearch(app)
@expose_api
@@ -166,7 +167,7 @@ class JobController(BaseAPIController, UsesLibraryMixinItems):
raw_value=str(metric_value),
)
job_dict['job_metrics'] = [metric_to_dict(metric) for metric in job.metrics]
job_dict['job_metrics'] = self._metrics_as_dict(trans, job)
return job_dict
@expose_api
@@ -265,6 +266,163 @@ class JobController(BaseAPIController, UsesLibraryMixinItems):
exceptions.RequestParameterInvalidException("Job with id '%s' is not paused" % (job.tool_id))
return self.__dictify_associations(trans, job.output_datasets, job.output_library_datasets)
@expose_api_anonymous
def metrics(self, trans, **kwd):
"""
* GET /api/jobs/{job_id}/metrics
* GET /api/datasets/{dataset_id}/metrics
Return job metrics for specified job. Job accessibility checks are slightly
different than dataset checks, so both methods are available.
:type job_id: string
:param job_id: Encoded job id
:type dataset_id: string
:param dataset_id: Encoded HDA or LDDA id
:type hda_ldda: string
:param hda_ldda: hda if dataset_id is an HDA id (default), ldda if
it is an ldda id.
:rtype: list
:returns: list containing job metrics
"""
job = self.__get_job(trans, **kwd)
if not trans.user_is_admin and not trans.app.config.expose_potentially_sensitive_job_metrics:
return []
return self._metrics_as_dict(trans, job)
def _metrics_as_dict(self, trans, job):
def metric_to_dict(metric):
metric_name = metric.metric_name
metric_value = metric.metric_value
metric_plugin = metric.plugin
title, value = trans.app.job_metrics.format(metric_plugin, metric_name, metric_value)
return dict(
title=title,
value=value,
plugin=metric_plugin,
name=metric_name,
raw_value=str(metric_value),
)
metrics = [m for m in job.metrics if m.plugin != 'env' or trans.user_is_admin]
return list(map(metric_to_dict, metrics))
@expose_api_anonymous
def parameters_display(self, trans, **kwd):
"""
* GET /api/jobs/{job_id}/parameters_display
* GET /api/datasets/{dataset_id}/parameters_display
Resolve parameters as a list for nested display. More client logic
here than is ideal but it is hard to reason about tool parameter
types on the client relative to the server. Job accessibility checks
are slightly different than dataset checks, so both methods are
available.
This API endpoint is unstable and tied heavily to Galaxy's JS client code,
this endpoint will change frequently.
:type job_id: string
:param job_id: Encoded job id
:type dataset_id: string
:param dataset_id: Encoded HDA or LDDA id
:type hda_ldda: string
:param hda_ldda: hda if dataset_id is an HDA id (default), ldda if
it is an ldda id.
:rtype: list
:returns: job parameters for for display
"""
job = self.__get_job(trans, **kwd)
def inputs_recursive(input_params, param_values, depth=1, upgrade_messages=None):
if upgrade_messages is None:
upgrade_messages = {}
rval = []
for input_index, input in enumerate(input_params.values()):
if input.name in param_values:
if input.type == "repeat":
for i in range(len(param_values[input.name])):
rval.extend(inputs_recursive(input.inputs, param_values[input.name][i], depth=depth + 1))
elif input.type == "section":
# Get the value of the current Section parameter
rval.append(dict(text=input.name, depth=depth))
rval.extend(inputs_recursive(input.inputs, param_values[input.name], depth=depth + 1, upgrade_messages=upgrade_messages.get(input.name)))
elif input.type == "conditional":
try:
current_case = param_values[input.name]['__current_case__']
is_valid = True
except Exception:
current_case = None
is_valid = False
if is_valid:
rval.append(dict(text=input.test_param.label, depth=depth, value=input.cases[current_case].value))
rval.extend(inputs_recursive(input.cases[current_case].inputs, param_values[input.name], depth=depth + 1, upgrade_messages=upgrade_messages.get(input.name)))
else:
rval.append(dict(text=input.name, depth=depth, notes="The previously used value is no longer valid.", error=True))
elif input.type == "upload_dataset":
rval.append(dict(text=input.group_title(param_values), depth=depth, value="%s uploaded datasets" % len(param_values[input.name])))
elif input.type == "data":
value = []
for i, element in enumerate(util.listify(param_values[input.name])):
if element.history_content_type == "dataset":
hda = element
encoded_id = trans.security.encode_id(hda.id)
value.append({"src": "hda", "id": encoded_id, "hid": hda.hid, "name": hda.name})
else:
value.append({"hid": element.hid, "name": element.name})
rval.append(dict(text=input.label, depth=depth, value=value))
elif input.visible:
if hasattr(input, "label") and input.label:
label = input.label
else:
# value for label not required, fallback to input name (same as tool panel)
label = input.name
rval.append(dict(text=label, depth=depth, value=input.value_to_display_text(param_values[input.name]), notes=upgrade_messages.get(input.name, '')))
else:
# Parameter does not have a stored value.
# Get parameter label.
if input.type == "conditional":
label = input.test_param.label
elif input.type == "repeat":
label = input.label()
else:
label = input.label or input.name
rval.append(dict(text=label, depth=depth, notes="not used (parameter was added after this job was run)"))
return rval
# Load the tool
toolbox = self.app.toolbox
tool = toolbox.get_tool(job.tool_id, job.tool_version)
assert tool is not None, 'Requested tool has not been loaded.'
params_objects = None
upgrade_messages = {}
has_parameter_errors = False
# Load parameter objects, if a parameter type has changed, it's possible for the value to no longer be valid
try:
params_objects = job.get_param_values(self.app, ignore_errors=False)
except Exception:
params_objects = job.get_param_values(self.app, ignore_errors=True)
# use different param_objects in the following line, since we want to display original values as much as possible
upgrade_messages = tool.check_and_update_param_values(job.get_param_values(self.app, ignore_errors=True),
trans,
update_values=False)
has_parameter_errors = True
parameters = inputs_recursive(tool.inputs, params_objects, depth=1, upgrade_messages=upgrade_messages)
return {"parameters": parameters, "has_parameter_errors": has_parameter_errors}
@expose_api_anonymous
def build_for_rerun(self, trans, id, **kwd):
"""
@@ -306,23 +464,18 @@ class JobController(BaseAPIController, UsesLibraryMixinItems):
dataset_dict = dict(src="ldda", id=trans.security.encode_id(dataset.id))
return dict(name=job_dataset_association.name, dataset=dataset_dict)
def __get_job(self, trans, id):
try:
decoded_job_id = self.decode_id(id)
except Exception:
raise exceptions.MalformedId()
job = trans.sa_session.query(trans.app.model.Job).filter(trans.app.model.Job.id == decoded_job_id).first()
if job is None:
raise exceptions.ObjectNotFound()
belongs_to_user = (job.user == trans.user) if job.user else (job.session_id == trans.get_galaxy_session().id)
if not trans.user_is_admin and not belongs_to_user:
# Check access granted via output datasets.
if not job.output_datasets:
raise exceptions.ItemAccessibilityException("Job has no output datasets.")
for data_assoc in job.output_datasets:
if not self.dataset_manager.is_accessible(data_assoc.dataset.dataset, trans.user):
raise exceptions.ItemAccessibilityException("You are not allowed to rerun this job.")
return job
def __get_job(self, trans, job_id=None, dataset_id=None, **kwd):
if job_id is not None:
try:
decoded_job_id = self.decode_id(job_id)
except Exception:
raise exceptions.MalformedId()
return self.job_manager.get_accessible_job(trans, decoded_job_id)
else:
hda_ldda = kwd.get("hda_ldda", "hda")
# Following checks dataset accessible
dataset_instance = self.get_hda_or_ldda(trans, hda_ldda=hda_ldda, dataset_id=dataset_id)
return dataset_instance.creating_job
@expose_api
def create(self, trans, payload, **kwd):
+5
View File
@@ -890,6 +890,11 @@ def populate_api_routes(webapp, app):
webapp.mapper.connect('resume', '/api/jobs/{id}/resume', controller='jobs', action='resume', conditions=dict(method=['PUT']))
webapp.mapper.connect('job_error', '/api/jobs/{id}/error', controller='jobs', action='error', conditions=dict(method=['POST']))
webapp.mapper.connect('common_problems', '/api/jobs/{id}/common_problems', controller='jobs', action='common_problems', conditions=dict(method=['GET']))
# Job metrics and parameters by job id or dataset id (for slightly different accessibility checking)
webapp.mapper.connect('metrics', '/api/jobs/{job_id}/metrics', controller='jobs', action='metrics', conditions=dict(method=['GET']))
webapp.mapper.connect('dataset_metrics', '/api/datasets/{dataset_id}/metrics', controller='jobs', action='metrics', conditions=dict(method=['GET']))
webapp.mapper.connect('parameters_display', '/api/jobs/{job_id}/parameters_display', controller='jobs', action='parameters_display', conditions=dict(method=['GET']))
webapp.mapper.connect('dataset_parameters_display', '/api/datasets/{dataset_id}/parameters_display', controller='jobs', action='parameters_display', conditions=dict(method=['GET']))
# Job files controllers. Only for consumption by remote job runners.
webapp.mapper.resource('file',
+8 -167
View File
@@ -9,133 +9,8 @@
text-align: center;
background-color: #eee;
}
table.info_data_table {
table-layout: fixed;
word-break: break-word;
}
table.info_data_table td:nth-child(1) {
width: 25%;
}
</style>
<%def name="inputs_recursive( input_params, param_values, depth=1, upgrade_messages=None )">
<%
from galaxy.util import listify
if upgrade_messages is None:
upgrade_messages = {}
%>
%for input_index, input in enumerate( input_params.values() ):
%if input.name in param_values:
%if input.type == "repeat":
%for i in range( len(param_values[input.name]) ):
${ inputs_recursive(input.inputs, param_values[input.name][i], depth=depth+1) }
%endfor
%elif input.type == "section":
<tr>
##<!-- Get the value of the current Section parameter -->
${inputs_recursive_indent( text=input.name, depth=depth )}
<td></td>
</tr>
${ inputs_recursive( input.inputs, param_values[input.name], depth=depth+1, upgrade_messages=upgrade_messages.get( input.name ) ) }
%elif input.type == "conditional":
<%
try:
current_case = param_values[input.name]['__current_case__']
is_valid = True
except:
current_case = None
is_valid = False
%>
%if is_valid:
<tr>
${ inputs_recursive_indent( text=input.test_param.label, depth=depth )}
##<!-- Get the value of the current Conditional parameter -->
<td>${input.cases[current_case].value | h}</td>
<td></td>
</tr>
${ inputs_recursive( input.cases[current_case].inputs, param_values[input.name], depth=depth+1, upgrade_messages=upgrade_messages.get( input.name ) ) }
%else:
<tr>
${ inputs_recursive_indent( text=input.name, depth=depth )}
<td><em>The previously used value is no longer valid</em></td>
<td></td>
</tr>
%endif
%elif input.type == "upload_dataset":
<tr>
${inputs_recursive_indent( text=input.group_title( param_values ), depth=depth )}
<td>${ len( param_values[input.name] ) } uploaded datasets</td>
<td></td>
</tr>
## files used for inputs
%elif input.type == "data":
<tr>
${inputs_recursive_indent( text=input.label, depth=depth )}
<td>
%for i, element in enumerate(listify(param_values[input.name])):
%if i > 0:
,
%endif
%if element.history_content_type == "dataset":
<%
hda = element
encoded_id = trans.security.encode_id( hda.id )
show_params_url = h.url_for( controller='dataset', action='show_params', dataset_id=encoded_id )
%>
<a class="input-dataset-show-params" data-hda-id="${encoded_id}"
href="${show_params_url}">${hda.hid}: ${hda.name | h}</a>
%else:
${element.hid}: ${element.name | h}
%endif
%endfor
</td>
<td></td>
</tr>
%elif input.visible:
<%
if hasattr( input, "label" ) and input.label:
label = input.label
else:
#value for label not required, fallback to input name (same as tool panel)
label = input.name
%>
<tr>
${inputs_recursive_indent( text=label, depth=depth )}
<td>${input.value_to_display_text( param_values[input.name] ) | h}</td>
<td>${ upgrade_messages.get( input.name, '' ) | h }</td>
</tr>
%endif
%else:
## Parameter does not have a stored value.
<tr>
<%
# Get parameter label.
if input.type == "conditional":
label = input.test_param.label
elif input.type == "repeat":
label = input.label()
else:
label = input.label or input.name
%>
${inputs_recursive_indent( text=label, depth=depth )}
<td><em>not used (parameter was added after this job was run)</em></td>
<td></td>
</tr>
%endif
%endfor
</%def>
## function to add a indentation depending on the depth in a <tr>
<%def name="inputs_recursive_indent( text, depth )">
<td style="padding-left: ${ ( depth - 1 ) * 10 }px">
${text | h}
</td>
</%def>
<h2>
% if tool:
${tool.name | h}
@@ -212,31 +87,13 @@
</tbody>
</table>
<h3>Tool Parameters</h3>
<table class="tabletip" id="tool-parameters">
<thead>
<tr>
<th>Input Parameter</th>
<th>Value</th>
<th>Note for rerun</th>
</tr>
</thead>
<tbody>
% if params_objects and tool:
${ inputs_recursive( tool.inputs, params_objects, depth=1, upgrade_messages=upgrade_messages ) }
%elif params_objects is None:
<tr><td colspan="3">Unable to load parameters.</td></tr>
% else:
<tr><td colspan="3">No parameters.</td></tr>
% endif
</tbody>
</table>
%if has_parameter_errors:
<br />
${ render_msg( 'One or more of your original parameters may no longer be valid or displayed properly.', status='warning' ) }
%if job:
<div class="job-parameters" dataset_id="${encoded_hda_id}" dataset_type="hda">
</div>
%endif
<h3>Inheritance Chain</h3>
<div class="inherit" style="background-color: #fff; font-weight:bold;">${hda.name | h}</div>
@@ -256,26 +113,8 @@ ${ job.command_line | h }</pre>
%endif
%if job and (trans.user_is_admin or trans.app.config.expose_potentially_sensitive_job_metrics):
<h3>Job Metrics</h3>
<% job_metrics = trans.app.job_metrics %>
<% plugins = set([metric.plugin for metric in job.metrics]) %>
%for plugin in sorted(plugins):
%if trans.user_is_admin or plugin != 'env':
<h4>${ plugin | h }</h4>
<table class="tabletip info_data_table">
<tbody>
<%
plugin_metrics = filter(lambda x: x.plugin == plugin, job.metrics)
plugin_metric_displays = [job_metrics.format( metric.plugin, metric.metric_name, metric.metric_value ) for metric in plugin_metrics]
plugin_metric_displays = sorted(plugin_metric_displays, key=lambda pair: pair[0]) # Sort on displayed title
%>
%for metric_title, metric_value in plugin_metric_displays:
<tr><td>${ metric_title | h }</td><td>${ metric_value | h }</td></tr>
%endfor
</tbody>
</table>
%endif
%endfor
<div class="job-metrics" dataset_id="${encoded_hda_id}" dataset_type="hda">
</div>
%endif
%if trans.user_is_admin:
@@ -352,5 +191,7 @@ $(function(){
window.parent.Galaxy.currHistoryPanel.scrollToId( 'dataset-' + $( this ).data( 'hda-id' ) );
}
})
window.bundleEntries.mountJobMetrics();
window.bundleEntries.mountJobParameters();
});
</script>
+1 -1
View File
@@ -1372,7 +1372,7 @@ def load_data_dict(history_id, test_data, dataset_populator, dataset_collection_
def wait_on_state(state_func, desc="state", skip_states=["running", "queued", "new", "ready"], assert_ok=False, timeout=DEFAULT_TIMEOUT):
def get_state():
response = state_func()
assert response.status_code == 200, "Failed to fetch state update while waiting."
assert response.status_code == 200, "Failed to fetch state update while waiting. [%s]" % response.content
state = response.json()["state"]
if state in skip_states:
return None