diff --git a/addon/flamenco/manager/__init__.py b/addon/flamenco/manager/__init__.py
index c64e2a85..af47b382 100644
--- a/addon/flamenco/manager/__init__.py
+++ b/addon/flamenco/manager/__init__.py
@@ -10,7 +10,7 @@
"""
-__version__ = "b699647e-dirty"
+__version__ = "e399b14e-dirty"
# import ApiClient
from flamenco.manager.api_client import ApiClient
diff --git a/addon/flamenco/manager/api/jobs_api.py b/addon/flamenco/manager/api/jobs_api.py
index 2d54d4a4..34082009 100644
--- a/addon/flamenco/manager/api/jobs_api.py
+++ b/addon/flamenco/manager/api/jobs_api.py
@@ -26,6 +26,7 @@ from flamenco.manager.model.available_job_types import AvailableJobTypes
from flamenco.manager.model.error import Error
from flamenco.manager.model.job import Job
from flamenco.manager.model.job_status_change import JobStatusChange
+from flamenco.manager.model.job_tasks_summary import JobTasksSummary
from flamenco.manager.model.jobs_query import JobsQuery
from flamenco.manager.model.jobs_query_result import JobsQueryResult
from flamenco.manager.model.submitted_job import SubmittedJob
@@ -91,6 +92,55 @@ class JobsApi(object):
},
api_client=api_client
)
+ self.fetch_job_tasks_endpoint = _Endpoint(
+ settings={
+ 'response_type': (JobTasksSummary,),
+ 'auth': [],
+ 'endpoint_path': '/api/jobs/{job_id}/tasks',
+ 'operation_id': 'fetch_job_tasks',
+ 'http_method': 'GET',
+ 'servers': None,
+ },
+ params_map={
+ 'all': [
+ 'job_id',
+ ],
+ 'required': [
+ 'job_id',
+ ],
+ 'nullable': [
+ ],
+ 'enum': [
+ ],
+ 'validation': [
+ ]
+ },
+ root_map={
+ 'validations': {
+ },
+ 'allowed_values': {
+ },
+ 'openapi_types': {
+ 'job_id':
+ (str,),
+ },
+ 'attribute_map': {
+ 'job_id': 'job_id',
+ },
+ 'location_map': {
+ 'job_id': 'path',
+ },
+ 'collection_format_map': {
+ }
+ },
+ headers_map={
+ 'accept': [
+ 'application/json'
+ ],
+ 'content_type': [],
+ },
+ api_client=api_client
+ )
self.get_job_type_endpoint = _Endpoint(
settings={
'response_type': (AvailableJobType,),
@@ -416,6 +466,83 @@ class JobsApi(object):
job_id
return self.fetch_job_endpoint.call_with_http_info(**kwargs)
+ def fetch_job_tasks(
+ self,
+ job_id,
+ **kwargs
+ ):
+ """Fetch a summary of all tasks of the given job. # noqa: E501
+
+ This method makes a synchronous HTTP request by default. To make an
+ asynchronous HTTP request, please pass async_req=True
+
+ >>> thread = api.fetch_job_tasks(job_id, async_req=True)
+ >>> result = thread.get()
+
+ Args:
+ job_id (str):
+
+ Keyword Args:
+ _return_http_data_only (bool): response data without head status
+ code and headers. Default is True.
+ _preload_content (bool): if False, the urllib3.HTTPResponse object
+ will be returned without reading/decoding response data.
+ Default is True.
+ _request_timeout (int/float/tuple): timeout setting for this request. If
+ one number provided, it will be total request timeout. It can also
+ be a pair (tuple) of (connection, read) timeouts.
+ Default is None.
+ _check_input_type (bool): specifies if type checking
+ should be done one the data sent to the server.
+ Default is True.
+ _check_return_type (bool): specifies if type checking
+ should be done one the data received from the server.
+ Default is True.
+ _spec_property_naming (bool): True if the variable names in the input data
+ are serialized names, as specified in the OpenAPI document.
+ False if the variable names in the input data
+ are pythonic names, e.g. snake case (default)
+ _content_type (str/None): force body content-type.
+ Default is None and content-type will be predicted by allowed
+ content-types and body.
+ _host_index (int/None): specifies the index of the server
+ that we want to use.
+ Default is read from the configuration.
+ async_req (bool): execute request asynchronously
+
+ Returns:
+ JobTasksSummary
+ If the method is called asynchronously, returns the request
+ thread.
+ """
+ kwargs['async_req'] = kwargs.get(
+ 'async_req', False
+ )
+ kwargs['_return_http_data_only'] = kwargs.get(
+ '_return_http_data_only', True
+ )
+ kwargs['_preload_content'] = kwargs.get(
+ '_preload_content', True
+ )
+ kwargs['_request_timeout'] = kwargs.get(
+ '_request_timeout', None
+ )
+ kwargs['_check_input_type'] = kwargs.get(
+ '_check_input_type', True
+ )
+ kwargs['_check_return_type'] = kwargs.get(
+ '_check_return_type', True
+ )
+ kwargs['_spec_property_naming'] = kwargs.get(
+ '_spec_property_naming', False
+ )
+ kwargs['_content_type'] = kwargs.get(
+ '_content_type')
+ kwargs['_host_index'] = kwargs.get('_host_index')
+ kwargs['job_id'] = \
+ job_id
+ return self.fetch_job_tasks_endpoint.call_with_http_info(**kwargs)
+
def get_job_type(
self,
type_name,
diff --git a/addon/flamenco/manager/api_client.py b/addon/flamenco/manager/api_client.py
index aa785e5b..44802d79 100644
--- a/addon/flamenco/manager/api_client.py
+++ b/addon/flamenco/manager/api_client.py
@@ -76,7 +76,7 @@ class ApiClient(object):
self.default_headers[header_name] = header_value
self.cookie = cookie
# Set default User-Agent.
- self.user_agent = 'Flamenco/b699647e-dirty (Blender add-on)'
+ self.user_agent = 'Flamenco/e399b14e-dirty (Blender add-on)'
def __enter__(self):
return self
diff --git a/addon/flamenco/manager/configuration.py b/addon/flamenco/manager/configuration.py
index fc712b51..f47e21a0 100644
--- a/addon/flamenco/manager/configuration.py
+++ b/addon/flamenco/manager/configuration.py
@@ -404,7 +404,7 @@ conf = flamenco.manager.Configuration(
"OS: {env}\n"\
"Python Version: {pyversion}\n"\
"Version of the API: 1.0.0\n"\
- "SDK Package Version: b699647e-dirty".\
+ "SDK Package Version: e399b14e-dirty".\
format(env=sys.platform, pyversion=sys.version)
def get_host_settings(self):
diff --git a/addon/flamenco/manager/docs/Job.md b/addon/flamenco/manager/docs/Job.md
index 7deac49e..92d47c60 100644
--- a/addon/flamenco/manager/docs/Job.md
+++ b/addon/flamenco/manager/docs/Job.md
@@ -8,7 +8,7 @@ Name | Type | Description | Notes
**type** | **str** | |
**id** | **str** | UUID of the Job |
**created** | **datetime** | Creation timestamp |
-**updated** | **datetime** | Creation timestamp |
+**updated** | **datetime** | Timestamp of last update. |
**status** | [**JobStatus**](JobStatus.md) | |
**activity** | **str** | Description of the last activity on this job. |
**priority** | **int** | | defaults to 50
diff --git a/addon/flamenco/manager/docs/JobAllOf.md b/addon/flamenco/manager/docs/JobAllOf.md
index 208d4cb3..2996067c 100644
--- a/addon/flamenco/manager/docs/JobAllOf.md
+++ b/addon/flamenco/manager/docs/JobAllOf.md
@@ -6,7 +6,7 @@ Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
**id** | **str** | UUID of the Job |
**created** | **datetime** | Creation timestamp |
-**updated** | **datetime** | Creation timestamp |
+**updated** | **datetime** | Timestamp of last update. |
**status** | [**JobStatus**](JobStatus.md) | |
**activity** | **str** | Description of the last activity on this job. |
**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
diff --git a/addon/flamenco/manager/docs/JobTasksSummary.md b/addon/flamenco/manager/docs/JobTasksSummary.md
new file mode 100644
index 00000000..ade466c7
--- /dev/null
+++ b/addon/flamenco/manager/docs/JobTasksSummary.md
@@ -0,0 +1,13 @@
+# JobTasksSummary
+
+Simplified list of tasks of a job. Contains all tasks, but not all info of each task.
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**tasks** | [**[TaskSummary]**](TaskSummary.md) | | [optional]
+**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/addon/flamenco/manager/docs/JobsApi.md b/addon/flamenco/manager/docs/JobsApi.md
index c9606119..9889a680 100644
--- a/addon/flamenco/manager/docs/JobsApi.md
+++ b/addon/flamenco/manager/docs/JobsApi.md
@@ -5,6 +5,7 @@ All URIs are relative to *http://localhost*
Method | HTTP request | Description
------------- | ------------- | -------------
[**fetch_job**](JobsApi.md#fetch_job) | **GET** /api/jobs/{job_id} | Fetch info about the job.
+[**fetch_job_tasks**](JobsApi.md#fetch_job_tasks) | **GET** /api/jobs/{job_id}/tasks | Fetch a summary of all tasks of the given job.
[**get_job_type**](JobsApi.md#get_job_type) | **GET** /api/jobs/type/{typeName} | Get single job type and its parameters.
[**get_job_types**](JobsApi.md#get_job_types) | **GET** /api/jobs/types | Get list of job types and their parameters.
[**query_jobs**](JobsApi.md#query_jobs) | **POST** /api/jobs/query | Fetch list of jobs.
@@ -77,6 +78,73 @@ No authorization required
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+# **fetch_job_tasks**
+> JobTasksSummary fetch_job_tasks(job_id)
+
+Fetch a summary of all tasks of the given job.
+
+### Example
+
+
+```python
+import time
+import flamenco.manager
+from flamenco.manager.api import jobs_api
+from flamenco.manager.model.error import Error
+from flamenco.manager.model.job_tasks_summary import JobTasksSummary
+from pprint import pprint
+# Defining the host is optional and defaults to http://localhost
+# See configuration.py for a list of all supported configuration parameters.
+configuration = flamenco.manager.Configuration(
+ host = "http://localhost"
+)
+
+
+# Enter a context with an instance of the API client
+with flamenco.manager.ApiClient() as api_client:
+ # Create an instance of the API class
+ api_instance = jobs_api.JobsApi(api_client)
+ job_id = "job_id_example" # str |
+
+ # example passing only required values which don't have defaults set
+ try:
+ # Fetch a summary of all tasks of the given job.
+ api_response = api_instance.fetch_job_tasks(job_id)
+ pprint(api_response)
+ except flamenco.manager.ApiException as e:
+ print("Exception when calling JobsApi->fetch_job_tasks: %s\n" % e)
+```
+
+
+### Parameters
+
+Name | Type | Description | Notes
+------------- | ------------- | ------------- | -------------
+ **job_id** | **str**| |
+
+### Return type
+
+[**JobTasksSummary**](JobTasksSummary.md)
+
+### Authorization
+
+No authorization required
+
+### HTTP request headers
+
+ - **Content-Type**: Not defined
+ - **Accept**: application/json
+
+
+### HTTP response details
+
+| Status code | Description | Response headers |
+|-------------|-------------|------------------|
+**200** | Get summaries of the tasks of this job. | - |
+**0** | Unexpected error. | - |
+
+[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
+
# **get_job_type**
> AvailableJobType get_job_type(type_name)
diff --git a/addon/flamenco/manager/docs/TaskSummary.md b/addon/flamenco/manager/docs/TaskSummary.md
new file mode 100644
index 00000000..ffc6bd46
--- /dev/null
+++ b/addon/flamenco/manager/docs/TaskSummary.md
@@ -0,0 +1,18 @@
+# TaskSummary
+
+Just enough information about the task to show in the job's task list.
+
+## Properties
+Name | Type | Description | Notes
+------------ | ------------- | ------------- | -------------
+**id** | **str** | |
+**name** | **str** | |
+**status** | [**TaskStatus**](TaskStatus.md) | |
+**priority** | **int** | |
+**task_type** | **str** | |
+**updated** | **datetime** | |
+**any string name** | **bool, date, datetime, dict, float, int, list, str, none_type** | any string name can be used but the value must be the correct type | [optional]
+
+[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
+
+
diff --git a/addon/flamenco/manager/model/job.py b/addon/flamenco/manager/model/job.py
index 174a8377..ae848061 100644
--- a/addon/flamenco/manager/model/job.py
+++ b/addon/flamenco/manager/model/job.py
@@ -139,7 +139,7 @@ class Job(ModelComposed):
priority (int): defaults to 50 # noqa: E501
id (str): UUID of the Job
created (datetime): Creation timestamp
- updated (datetime): Creation timestamp
+ updated (datetime): Timestamp of last update.
status (JobStatus):
activity (str): Description of the last activity on this job.
_check_type (bool): if True, values for parameters in openapi_types
@@ -249,7 +249,7 @@ class Job(ModelComposed):
priority (int): defaults to 50 # noqa: E501
id (str): UUID of the Job
created (datetime): Creation timestamp
- updated (datetime): Creation timestamp
+ updated (datetime): Timestamp of last update.
status (JobStatus):
activity (str): Description of the last activity on this job.
_check_type (bool): if True, values for parameters in openapi_types
diff --git a/addon/flamenco/manager/model/job_all_of.py b/addon/flamenco/manager/model/job_all_of.py
index 3ac458c0..6db70b3c 100644
--- a/addon/flamenco/manager/model/job_all_of.py
+++ b/addon/flamenco/manager/model/job_all_of.py
@@ -120,7 +120,7 @@ class JobAllOf(ModelNormal):
Args:
id (str): UUID of the Job
created (datetime): Creation timestamp
- updated (datetime): Creation timestamp
+ updated (datetime): Timestamp of last update.
status (JobStatus):
activity (str): Description of the last activity on this job.
@@ -213,7 +213,7 @@ class JobAllOf(ModelNormal):
Args:
id (str): UUID of the Job
created (datetime): Creation timestamp
- updated (datetime): Creation timestamp
+ updated (datetime): Timestamp of last update.
status (JobStatus):
activity (str): Description of the last activity on this job.
diff --git a/addon/flamenco/manager/model/job_tasks_summary.py b/addon/flamenco/manager/model/job_tasks_summary.py
new file mode 100644
index 00000000..aeccb7e2
--- /dev/null
+++ b/addon/flamenco/manager/model/job_tasks_summary.py
@@ -0,0 +1,261 @@
+"""
+ Flamenco manager
+
+ Render Farm manager API # noqa: E501
+
+ The version of the OpenAPI document: 1.0.0
+ Generated by: https://openapi-generator.tech
+"""
+
+
+import re # noqa: F401
+import sys # noqa: F401
+
+from flamenco.manager.model_utils import ( # noqa: F401
+ ApiTypeError,
+ ModelComposed,
+ ModelNormal,
+ ModelSimple,
+ cached_property,
+ change_keys_js_to_python,
+ convert_js_args_to_python_args,
+ date,
+ datetime,
+ file_type,
+ none_type,
+ validate_get_composed_info,
+ OpenApiModel
+)
+from flamenco.manager.exceptions import ApiAttributeError
+
+
+def lazy_import():
+ from flamenco.manager.model.task_summary import TaskSummary
+ globals()['TaskSummary'] = TaskSummary
+
+
+class JobTasksSummary(ModelNormal):
+ """NOTE: This class is auto generated by OpenAPI Generator.
+ Ref: https://openapi-generator.tech
+
+ Do not edit the class manually.
+
+ Attributes:
+ allowed_values (dict): The key is the tuple path to the attribute
+ and the for var_name this is (var_name,). The value is a dict
+ with a capitalized key describing the allowed value and an allowed
+ value. These dicts store the allowed enum values.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ discriminator_value_class_map (dict): A dict to go from the discriminator
+ variable value to the discriminator class name.
+ validations (dict): The key is the tuple path to the attribute
+ and the for var_name this is (var_name,). The value is a dict
+ that stores validations for max_length, min_length, max_items,
+ min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum,
+ inclusive_minimum, and regex.
+ additional_properties_type (tuple): A tuple of classes accepted
+ as additional properties values.
+ """
+
+ allowed_values = {
+ }
+
+ validations = {
+ }
+
+ @cached_property
+ def additional_properties_type():
+ """
+ This must be a method because a model may have properties that are
+ of type self, this must run after the class is loaded
+ """
+ lazy_import()
+ return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501
+
+ _nullable = False
+
+ @cached_property
+ def openapi_types():
+ """
+ This must be a method because a model may have properties that are
+ of type self, this must run after the class is loaded
+
+ Returns
+ openapi_types (dict): The key is attribute name
+ and the value is attribute type.
+ """
+ lazy_import()
+ return {
+ 'tasks': ([TaskSummary],), # noqa: E501
+ }
+
+ @cached_property
+ def discriminator():
+ return None
+
+
+ attribute_map = {
+ 'tasks': 'tasks', # noqa: E501
+ }
+
+ read_only_vars = {
+ }
+
+ _composed_schemas = {}
+
+ @classmethod
+ @convert_js_args_to_python_args
+ def _from_openapi_data(cls, *args, **kwargs): # noqa: E501
+ """JobTasksSummary - a model defined in OpenAPI
+
+ Keyword Args:
+ _check_type (bool): if True, values for parameters in openapi_types
+ will be type checked and a TypeError will be
+ raised if the wrong type is input.
+ Defaults to True
+ _path_to_item (tuple/list): This is a list of keys or values to
+ drill down to the model in received_data
+ when deserializing a response
+ _spec_property_naming (bool): True if the variable names in the input data
+ are serialized names, as specified in the OpenAPI document.
+ False if the variable names in the input data
+ are pythonic names, e.g. snake case (default)
+ _configuration (Configuration): the instance to use when
+ deserializing a file_type parameter.
+ If passed, type conversion is attempted
+ If omitted no type conversion is done.
+ _visited_composed_classes (tuple): This stores a tuple of
+ classes that we have traveled through so that
+ if we see that class again we will not use its
+ discriminator again.
+ When traveling through a discriminator, the
+ composed schema that is
+ is traveled through is added to this set.
+ For example if Animal has a discriminator
+ petType and we pass in "Dog", and the class Dog
+ allOf includes Animal, we move through Animal
+ once using the discriminator, and pick Dog.
+ Then in Dog, we will make an instance of the
+ Animal class but this time we won't travel
+ through its discriminator because we passed in
+ _visited_composed_classes = (Animal,)
+ tasks ([TaskSummary]): [optional] # noqa: E501
+ """
+
+ _check_type = kwargs.pop('_check_type', True)
+ _spec_property_naming = kwargs.pop('_spec_property_naming', False)
+ _path_to_item = kwargs.pop('_path_to_item', ())
+ _configuration = kwargs.pop('_configuration', None)
+ _visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
+
+ self = super(OpenApiModel, cls).__new__(cls)
+
+ if args:
+ raise ApiTypeError(
+ "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
+ args,
+ self.__class__.__name__,
+ ),
+ path_to_item=_path_to_item,
+ valid_classes=(self.__class__,),
+ )
+
+ self._data_store = {}
+ self._check_type = _check_type
+ self._spec_property_naming = _spec_property_naming
+ self._path_to_item = _path_to_item
+ self._configuration = _configuration
+ self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
+
+ for var_name, var_value in kwargs.items():
+ if var_name not in self.attribute_map and \
+ self._configuration is not None and \
+ self._configuration.discard_unknown_keys and \
+ self.additional_properties_type is None:
+ # discard variable.
+ continue
+ setattr(self, var_name, var_value)
+ return self
+
+ required_properties = set([
+ '_data_store',
+ '_check_type',
+ '_spec_property_naming',
+ '_path_to_item',
+ '_configuration',
+ '_visited_composed_classes',
+ ])
+
+ @convert_js_args_to_python_args
+ def __init__(self, *args, **kwargs): # noqa: E501
+ """JobTasksSummary - a model defined in OpenAPI
+
+ Keyword Args:
+ _check_type (bool): if True, values for parameters in openapi_types
+ will be type checked and a TypeError will be
+ raised if the wrong type is input.
+ Defaults to True
+ _path_to_item (tuple/list): This is a list of keys or values to
+ drill down to the model in received_data
+ when deserializing a response
+ _spec_property_naming (bool): True if the variable names in the input data
+ are serialized names, as specified in the OpenAPI document.
+ False if the variable names in the input data
+ are pythonic names, e.g. snake case (default)
+ _configuration (Configuration): the instance to use when
+ deserializing a file_type parameter.
+ If passed, type conversion is attempted
+ If omitted no type conversion is done.
+ _visited_composed_classes (tuple): This stores a tuple of
+ classes that we have traveled through so that
+ if we see that class again we will not use its
+ discriminator again.
+ When traveling through a discriminator, the
+ composed schema that is
+ is traveled through is added to this set.
+ For example if Animal has a discriminator
+ petType and we pass in "Dog", and the class Dog
+ allOf includes Animal, we move through Animal
+ once using the discriminator, and pick Dog.
+ Then in Dog, we will make an instance of the
+ Animal class but this time we won't travel
+ through its discriminator because we passed in
+ _visited_composed_classes = (Animal,)
+ tasks ([TaskSummary]): [optional] # noqa: E501
+ """
+
+ _check_type = kwargs.pop('_check_type', True)
+ _spec_property_naming = kwargs.pop('_spec_property_naming', False)
+ _path_to_item = kwargs.pop('_path_to_item', ())
+ _configuration = kwargs.pop('_configuration', None)
+ _visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
+
+ if args:
+ raise ApiTypeError(
+ "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
+ args,
+ self.__class__.__name__,
+ ),
+ path_to_item=_path_to_item,
+ valid_classes=(self.__class__,),
+ )
+
+ self._data_store = {}
+ self._check_type = _check_type
+ self._spec_property_naming = _spec_property_naming
+ self._path_to_item = _path_to_item
+ self._configuration = _configuration
+ self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
+
+ for var_name, var_value in kwargs.items():
+ if var_name not in self.attribute_map and \
+ self._configuration is not None and \
+ self._configuration.discard_unknown_keys and \
+ self.additional_properties_type is None:
+ # discard variable.
+ continue
+ setattr(self, var_name, var_value)
+ if var_name in self.read_only_vars:
+ raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate "
+ f"class with read only attributes.")
diff --git a/addon/flamenco/manager/model/task_summary.py b/addon/flamenco/manager/model/task_summary.py
new file mode 100644
index 00000000..81029867
--- /dev/null
+++ b/addon/flamenco/manager/model/task_summary.py
@@ -0,0 +1,297 @@
+"""
+ Flamenco manager
+
+ Render Farm manager API # noqa: E501
+
+ The version of the OpenAPI document: 1.0.0
+ Generated by: https://openapi-generator.tech
+"""
+
+
+import re # noqa: F401
+import sys # noqa: F401
+
+from flamenco.manager.model_utils import ( # noqa: F401
+ ApiTypeError,
+ ModelComposed,
+ ModelNormal,
+ ModelSimple,
+ cached_property,
+ change_keys_js_to_python,
+ convert_js_args_to_python_args,
+ date,
+ datetime,
+ file_type,
+ none_type,
+ validate_get_composed_info,
+ OpenApiModel
+)
+from flamenco.manager.exceptions import ApiAttributeError
+
+
+def lazy_import():
+ from flamenco.manager.model.task_status import TaskStatus
+ globals()['TaskStatus'] = TaskStatus
+
+
+class TaskSummary(ModelNormal):
+ """NOTE: This class is auto generated by OpenAPI Generator.
+ Ref: https://openapi-generator.tech
+
+ Do not edit the class manually.
+
+ Attributes:
+ allowed_values (dict): The key is the tuple path to the attribute
+ and the for var_name this is (var_name,). The value is a dict
+ with a capitalized key describing the allowed value and an allowed
+ value. These dicts store the allowed enum values.
+ attribute_map (dict): The key is attribute name
+ and the value is json key in definition.
+ discriminator_value_class_map (dict): A dict to go from the discriminator
+ variable value to the discriminator class name.
+ validations (dict): The key is the tuple path to the attribute
+ and the for var_name this is (var_name,). The value is a dict
+ that stores validations for max_length, min_length, max_items,
+ min_items, exclusive_maximum, inclusive_maximum, exclusive_minimum,
+ inclusive_minimum, and regex.
+ additional_properties_type (tuple): A tuple of classes accepted
+ as additional properties values.
+ """
+
+ allowed_values = {
+ }
+
+ validations = {
+ }
+
+ @cached_property
+ def additional_properties_type():
+ """
+ This must be a method because a model may have properties that are
+ of type self, this must run after the class is loaded
+ """
+ lazy_import()
+ return (bool, date, datetime, dict, float, int, list, str, none_type,) # noqa: E501
+
+ _nullable = False
+
+ @cached_property
+ def openapi_types():
+ """
+ This must be a method because a model may have properties that are
+ of type self, this must run after the class is loaded
+
+ Returns
+ openapi_types (dict): The key is attribute name
+ and the value is attribute type.
+ """
+ lazy_import()
+ return {
+ 'id': (str,), # noqa: E501
+ 'name': (str,), # noqa: E501
+ 'status': (TaskStatus,), # noqa: E501
+ 'priority': (int,), # noqa: E501
+ 'task_type': (str,), # noqa: E501
+ 'updated': (datetime,), # noqa: E501
+ }
+
+ @cached_property
+ def discriminator():
+ return None
+
+
+ attribute_map = {
+ 'id': 'id', # noqa: E501
+ 'name': 'name', # noqa: E501
+ 'status': 'status', # noqa: E501
+ 'priority': 'priority', # noqa: E501
+ 'task_type': 'task_type', # noqa: E501
+ 'updated': 'updated', # noqa: E501
+ }
+
+ read_only_vars = {
+ }
+
+ _composed_schemas = {}
+
+ @classmethod
+ @convert_js_args_to_python_args
+ def _from_openapi_data(cls, id, name, status, priority, task_type, updated, *args, **kwargs): # noqa: E501
+ """TaskSummary - a model defined in OpenAPI
+
+ Args:
+ id (str):
+ name (str):
+ status (TaskStatus):
+ priority (int):
+ task_type (str):
+ updated (datetime):
+
+ Keyword Args:
+ _check_type (bool): if True, values for parameters in openapi_types
+ will be type checked and a TypeError will be
+ raised if the wrong type is input.
+ Defaults to True
+ _path_to_item (tuple/list): This is a list of keys or values to
+ drill down to the model in received_data
+ when deserializing a response
+ _spec_property_naming (bool): True if the variable names in the input data
+ are serialized names, as specified in the OpenAPI document.
+ False if the variable names in the input data
+ are pythonic names, e.g. snake case (default)
+ _configuration (Configuration): the instance to use when
+ deserializing a file_type parameter.
+ If passed, type conversion is attempted
+ If omitted no type conversion is done.
+ _visited_composed_classes (tuple): This stores a tuple of
+ classes that we have traveled through so that
+ if we see that class again we will not use its
+ discriminator again.
+ When traveling through a discriminator, the
+ composed schema that is
+ is traveled through is added to this set.
+ For example if Animal has a discriminator
+ petType and we pass in "Dog", and the class Dog
+ allOf includes Animal, we move through Animal
+ once using the discriminator, and pick Dog.
+ Then in Dog, we will make an instance of the
+ Animal class but this time we won't travel
+ through its discriminator because we passed in
+ _visited_composed_classes = (Animal,)
+ """
+
+ _check_type = kwargs.pop('_check_type', True)
+ _spec_property_naming = kwargs.pop('_spec_property_naming', False)
+ _path_to_item = kwargs.pop('_path_to_item', ())
+ _configuration = kwargs.pop('_configuration', None)
+ _visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
+
+ self = super(OpenApiModel, cls).__new__(cls)
+
+ if args:
+ raise ApiTypeError(
+ "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
+ args,
+ self.__class__.__name__,
+ ),
+ path_to_item=_path_to_item,
+ valid_classes=(self.__class__,),
+ )
+
+ self._data_store = {}
+ self._check_type = _check_type
+ self._spec_property_naming = _spec_property_naming
+ self._path_to_item = _path_to_item
+ self._configuration = _configuration
+ self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
+
+ self.id = id
+ self.name = name
+ self.status = status
+ self.priority = priority
+ self.task_type = task_type
+ self.updated = updated
+ for var_name, var_value in kwargs.items():
+ if var_name not in self.attribute_map and \
+ self._configuration is not None and \
+ self._configuration.discard_unknown_keys and \
+ self.additional_properties_type is None:
+ # discard variable.
+ continue
+ setattr(self, var_name, var_value)
+ return self
+
+ required_properties = set([
+ '_data_store',
+ '_check_type',
+ '_spec_property_naming',
+ '_path_to_item',
+ '_configuration',
+ '_visited_composed_classes',
+ ])
+
+ @convert_js_args_to_python_args
+ def __init__(self, id, name, status, priority, task_type, updated, *args, **kwargs): # noqa: E501
+ """TaskSummary - a model defined in OpenAPI
+
+ Args:
+ id (str):
+ name (str):
+ status (TaskStatus):
+ priority (int):
+ task_type (str):
+ updated (datetime):
+
+ Keyword Args:
+ _check_type (bool): if True, values for parameters in openapi_types
+ will be type checked and a TypeError will be
+ raised if the wrong type is input.
+ Defaults to True
+ _path_to_item (tuple/list): This is a list of keys or values to
+ drill down to the model in received_data
+ when deserializing a response
+ _spec_property_naming (bool): True if the variable names in the input data
+ are serialized names, as specified in the OpenAPI document.
+ False if the variable names in the input data
+ are pythonic names, e.g. snake case (default)
+ _configuration (Configuration): the instance to use when
+ deserializing a file_type parameter.
+ If passed, type conversion is attempted
+ If omitted no type conversion is done.
+ _visited_composed_classes (tuple): This stores a tuple of
+ classes that we have traveled through so that
+ if we see that class again we will not use its
+ discriminator again.
+ When traveling through a discriminator, the
+ composed schema that is
+ is traveled through is added to this set.
+ For example if Animal has a discriminator
+ petType and we pass in "Dog", and the class Dog
+ allOf includes Animal, we move through Animal
+ once using the discriminator, and pick Dog.
+ Then in Dog, we will make an instance of the
+ Animal class but this time we won't travel
+ through its discriminator because we passed in
+ _visited_composed_classes = (Animal,)
+ """
+
+ _check_type = kwargs.pop('_check_type', True)
+ _spec_property_naming = kwargs.pop('_spec_property_naming', False)
+ _path_to_item = kwargs.pop('_path_to_item', ())
+ _configuration = kwargs.pop('_configuration', None)
+ _visited_composed_classes = kwargs.pop('_visited_composed_classes', ())
+
+ if args:
+ raise ApiTypeError(
+ "Invalid positional arguments=%s passed to %s. Remove those invalid positional arguments." % (
+ args,
+ self.__class__.__name__,
+ ),
+ path_to_item=_path_to_item,
+ valid_classes=(self.__class__,),
+ )
+
+ self._data_store = {}
+ self._check_type = _check_type
+ self._spec_property_naming = _spec_property_naming
+ self._path_to_item = _path_to_item
+ self._configuration = _configuration
+ self._visited_composed_classes = _visited_composed_classes + (self.__class__,)
+
+ self.id = id
+ self.name = name
+ self.status = status
+ self.priority = priority
+ self.task_type = task_type
+ self.updated = updated
+ for var_name, var_value in kwargs.items():
+ if var_name not in self.attribute_map and \
+ self._configuration is not None and \
+ self._configuration.discard_unknown_keys and \
+ self.additional_properties_type is None:
+ # discard variable.
+ continue
+ setattr(self, var_name, var_value)
+ if var_name in self.read_only_vars:
+ raise ApiAttributeError(f"`{var_name}` is a read-only attribute. Use `from_openapi_data` to instantiate "
+ f"class with read only attributes.")
diff --git a/addon/flamenco/manager/models/__init__.py b/addon/flamenco/manager/models/__init__.py
index 2557c62d..35d58e6c 100644
--- a/addon/flamenco/manager/models/__init__.py
+++ b/addon/flamenco/manager/models/__init__.py
@@ -24,6 +24,7 @@ from flamenco.manager.model.job_metadata import JobMetadata
from flamenco.manager.model.job_settings import JobSettings
from flamenco.manager.model.job_status import JobStatus
from flamenco.manager.model.job_status_change import JobStatusChange
+from flamenco.manager.model.job_tasks_summary import JobTasksSummary
from flamenco.manager.model.job_update import JobUpdate
from flamenco.manager.model.jobs_query import JobsQuery
from flamenco.manager.model.jobs_query_result import JobsQueryResult
@@ -40,6 +41,7 @@ from flamenco.manager.model.shaman_requirements_response import ShamanRequiremen
from flamenco.manager.model.shaman_single_file_status import ShamanSingleFileStatus
from flamenco.manager.model.submitted_job import SubmittedJob
from flamenco.manager.model.task_status import TaskStatus
+from flamenco.manager.model.task_summary import TaskSummary
from flamenco.manager.model.task_update import TaskUpdate
from flamenco.manager.model.worker_registration import WorkerRegistration
from flamenco.manager.model.worker_sign_on import WorkerSignOn
diff --git a/addon/flamenco/manager_README.md b/addon/flamenco/manager_README.md
index f8e66d5c..11138aed 100644
--- a/addon/flamenco/manager_README.md
+++ b/addon/flamenco/manager_README.md
@@ -4,7 +4,7 @@ Render Farm manager API
The `flamenco.manager` package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
- API version: 1.0.0
-- Package version: b699647e-dirty
+- Package version: e399b14e-dirty
- Build package: org.openapitools.codegen.languages.PythonClientCodegen
For more information, please visit [https://flamenco.io/](https://flamenco.io/)
@@ -37,6 +37,7 @@ from flamenco.manager.model.available_job_types import AvailableJobTypes
from flamenco.manager.model.error import Error
from flamenco.manager.model.job import Job
from flamenco.manager.model.job_status_change import JobStatusChange
+from flamenco.manager.model.job_tasks_summary import JobTasksSummary
from flamenco.manager.model.jobs_query import JobsQuery
from flamenco.manager.model.jobs_query_result import JobsQueryResult
from flamenco.manager.model.submitted_job import SubmittedJob
@@ -69,6 +70,7 @@ All URIs are relative to *http://localhost*
Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
*JobsApi* | [**fetch_job**](flamenco/manager/docs/JobsApi.md#fetch_job) | **GET** /api/jobs/{job_id} | Fetch info about the job.
+*JobsApi* | [**fetch_job_tasks**](flamenco/manager/docs/JobsApi.md#fetch_job_tasks) | **GET** /api/jobs/{job_id}/tasks | Fetch a summary of all tasks of the given job.
*JobsApi* | [**get_job_type**](flamenco/manager/docs/JobsApi.md#get_job_type) | **GET** /api/jobs/type/{typeName} | Get single job type and its parameters.
*JobsApi* | [**get_job_types**](flamenco/manager/docs/JobsApi.md#get_job_types) | **GET** /api/jobs/types | Get list of job types and their parameters.
*JobsApi* | [**query_jobs**](flamenco/manager/docs/JobsApi.md#query_jobs) | **POST** /api/jobs/query | Fetch list of jobs.
@@ -106,6 +108,7 @@ Class | Method | HTTP request | Description
- [JobSettings](flamenco/manager/docs/JobSettings.md)
- [JobStatus](flamenco/manager/docs/JobStatus.md)
- [JobStatusChange](flamenco/manager/docs/JobStatusChange.md)
+ - [JobTasksSummary](flamenco/manager/docs/JobTasksSummary.md)
- [JobUpdate](flamenco/manager/docs/JobUpdate.md)
- [JobsQuery](flamenco/manager/docs/JobsQuery.md)
- [JobsQueryResult](flamenco/manager/docs/JobsQueryResult.md)
@@ -122,6 +125,7 @@ Class | Method | HTTP request | Description
- [ShamanSingleFileStatus](flamenco/manager/docs/ShamanSingleFileStatus.md)
- [SubmittedJob](flamenco/manager/docs/SubmittedJob.md)
- [TaskStatus](flamenco/manager/docs/TaskStatus.md)
+ - [TaskSummary](flamenco/manager/docs/TaskSummary.md)
- [TaskUpdate](flamenco/manager/docs/TaskUpdate.md)
- [WorkerRegistration](flamenco/manager/docs/WorkerRegistration.md)
- [WorkerSignOn](flamenco/manager/docs/WorkerSignOn.md)
diff --git a/internal/manager/api_impl/api_impl.go b/internal/manager/api_impl/api_impl.go
index 778c147a..9c4aa73a 100644
--- a/internal/manager/api_impl/api_impl.go
+++ b/internal/manager/api_impl/api_impl.go
@@ -56,6 +56,7 @@ type PersistenceService interface {
// Database queries.
QueryJobs(ctx context.Context, query api.JobsQuery) ([]*persistence.Job, error)
+ QueryJobTaskSummaries(ctx context.Context, jobUUID string) ([]*persistence.Task, error)
}
var _ PersistenceService = (*persistence.DB)(nil)
diff --git a/internal/manager/api_impl/jobs_query.go b/internal/manager/api_impl/jobs_query.go
index 00127cc3..b2b03097 100644
--- a/internal/manager/api_impl/jobs_query.go
+++ b/internal/manager/api_impl/jobs_query.go
@@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
+ "git.blender.org/flamenco/internal/manager/persistence"
"git.blender.org/flamenco/pkg/api"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
@@ -58,3 +59,41 @@ func (f *Flamenco) QueryJobs(e echo.Context) error {
}
return e.JSON(http.StatusOK, result)
}
+
+func (f *Flamenco) FetchJobTasks(e echo.Context, jobID string) error {
+ logger := requestLogger(e).With().
+ Str("job", jobID).
+ Logger()
+ ctx := e.Request().Context()
+
+ if _, err := uuid.Parse(jobID); err != nil {
+ logger.Debug().Msg("invalid job ID received")
+ return sendAPIError(e, http.StatusBadRequest, "job ID not valid")
+ }
+
+ tasks, err := f.persist.QueryJobTaskSummaries(ctx, jobID)
+ if err != nil {
+ logger.Warn().Err(err).Msg("error querying for jobs")
+ return sendAPIError(e, http.StatusInternalServerError, "error querying for jobs")
+ }
+
+ summaries := make([]api.TaskSummary, len(tasks))
+ for i, task := range tasks {
+ summaries[i] = taskDBtoSummary(task)
+ }
+ result := api.JobTasksSummary{
+ Tasks: &summaries,
+ }
+ return e.JSON(http.StatusOK, result)
+}
+
+func taskDBtoSummary(task *persistence.Task) api.TaskSummary {
+ return api.TaskSummary{
+ Id: task.UUID,
+ Name: task.Name,
+ Priority: task.Priority,
+ Status: task.Status,
+ TaskType: task.Type,
+ Updated: task.UpdatedAt,
+ }
+}
diff --git a/internal/manager/api_impl/mocks/api_impl_mock.gen.go b/internal/manager/api_impl/mocks/api_impl_mock.gen.go
index 4a2930d4..b241cf37 100644
--- a/internal/manager/api_impl/mocks/api_impl_mock.gen.go
+++ b/internal/manager/api_impl/mocks/api_impl_mock.gen.go
@@ -114,6 +114,21 @@ func (mr *MockPersistenceServiceMockRecorder) FetchWorker(arg0, arg1 interface{}
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchWorker", reflect.TypeOf((*MockPersistenceService)(nil).FetchWorker), arg0, arg1)
}
+// QueryJobTaskSummaries mocks base method.
+func (m *MockPersistenceService) QueryJobTaskSummaries(arg0 context.Context, arg1 string) ([]*persistence.Task, error) {
+ m.ctrl.T.Helper()
+ ret := m.ctrl.Call(m, "QueryJobTaskSummaries", arg0, arg1)
+ ret0, _ := ret[0].([]*persistence.Task)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// QueryJobTaskSummaries indicates an expected call of QueryJobTaskSummaries.
+func (mr *MockPersistenceServiceMockRecorder) QueryJobTaskSummaries(arg0, arg1 interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryJobTaskSummaries", reflect.TypeOf((*MockPersistenceService)(nil).QueryJobTaskSummaries), arg0, arg1)
+}
+
// QueryJobs mocks base method.
func (m *MockPersistenceService) QueryJobs(arg0 context.Context, arg1 api.JobsQuery) ([]*persistence.Job, error) {
m.ctrl.T.Helper()
diff --git a/internal/manager/persistence/jobs_query.go b/internal/manager/persistence/jobs_query.go
index 062d79e2..314afbe1 100644
--- a/internal/manager/persistence/jobs_query.go
+++ b/internal/manager/persistence/jobs_query.go
@@ -68,3 +68,19 @@ func (db *DB) QueryJobs(ctx context.Context, apiQ api.JobsQuery) ([]*Job, error)
tx := q.Scan(&result)
return result, tx.Error
}
+
+// QueryJobTaskSummaries retrieves all tasks of the job, but not all fields of those tasks.
+// Fields are synchronised with api.TaskSummary.
+func (db *DB) QueryJobTaskSummaries(ctx context.Context, jobUUID string) ([]*Task, error) {
+ logger := log.Ctx(ctx)
+ logger.Debug().Str("job", jobUUID).Msg("queryingtask summaries")
+
+ var result []*Task
+ tx := db.gormDB.WithContext(ctx).Model(&Task{}).
+ Select("tasks.id", "tasks.uuid", "tasks.name", "tasks.priority", "tasks.status", "tasks.type", "tasks.updated_at").
+ Joins("left join jobs on jobs.uuid = ?", jobUUID).
+ // Where("jobs.uuid=?", jobUUID).
+ Scan(&result)
+
+ return result, tx.Error
+}
diff --git a/internal/manager/persistence/jobs_query_test.go b/internal/manager/persistence/jobs_query_test.go
index 03b741b6..385b4d2c 100644
--- a/internal/manager/persistence/jobs_query_test.go
+++ b/internal/manager/persistence/jobs_query_test.go
@@ -104,3 +104,29 @@ func TestQueryMetadata(t *testing.T) {
assert.Equal(t, otherJob.ID, result[0].ID, "status is %s", result[0].Status)
assert.Equal(t, testJob.ID, result[1].ID, "status is %s", result[1].Status)
}
+
+func TestFetchJobSummary(t *testing.T) {
+ ctx, close, db, job, authoredJob := jobTasksTestFixtures(t)
+ defer close()
+
+ expectTaskUUIDs := map[string]bool{}
+ for _, task := range authoredJob.Tasks {
+ expectTaskUUIDs[task.UUID] = true
+ }
+
+ // Create another test job, just to check we get the right tasks back.
+ otherAuthoredJob := createTestAuthoredJobWithTasks()
+ otherAuthoredJob.Status = api.JobStatusActive
+ otherAuthoredJob.Tasks = []job_compilers.AuthoredTask{}
+ otherAuthoredJob.JobID = "138678c8-efd0-452b-ac05-397ff4c02b26"
+ otherAuthoredJob.Metadata["project"] = "Other Project"
+ persistAuthoredJob(t, ctx, db, otherAuthoredJob)
+
+ summaries, err := db.QueryJobTaskSummaries(ctx, job.UUID)
+ assert.NoError(t, err)
+
+ assert.Len(t, summaries, len(expectTaskUUIDs))
+ for _, summary := range summaries {
+ assert.True(t, expectTaskUUIDs[summary.UUID], "%q should be in %v", summary.UUID, expectTaskUUIDs)
+ }
+}
diff --git a/internal/worker/mocks/client.gen.go b/internal/worker/mocks/client.gen.go
index c7386d3b..e93109f4 100644
--- a/internal/worker/mocks/client.gen.go
+++ b/internal/worker/mocks/client.gen.go
@@ -36,6 +36,26 @@ func (m *MockFlamencoClient) EXPECT() *MockFlamencoClientMockRecorder {
return m.recorder
}
+// FetchJobTasksWithResponse mocks base method.
+func (m *MockFlamencoClient) FetchJobTasksWithResponse(arg0 context.Context, arg1 string, arg2 ...api.RequestEditorFn) (*api.FetchJobTasksResponse, error) {
+ m.ctrl.T.Helper()
+ varargs := []interface{}{arg0, arg1}
+ for _, a := range arg2 {
+ varargs = append(varargs, a)
+ }
+ ret := m.ctrl.Call(m, "FetchJobTasksWithResponse", varargs...)
+ ret0, _ := ret[0].(*api.FetchJobTasksResponse)
+ ret1, _ := ret[1].(error)
+ return ret0, ret1
+}
+
+// FetchJobTasksWithResponse indicates an expected call of FetchJobTasksWithResponse.
+func (mr *MockFlamencoClientMockRecorder) FetchJobTasksWithResponse(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
+ mr.mock.ctrl.T.Helper()
+ varargs := append([]interface{}{arg0, arg1}, arg2...)
+ return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FetchJobTasksWithResponse", reflect.TypeOf((*MockFlamencoClient)(nil).FetchJobTasksWithResponse), varargs...)
+}
+
// FetchJobWithResponse mocks base method.
func (m *MockFlamencoClient) FetchJobWithResponse(arg0 context.Context, arg1 string, arg2 ...api.RequestEditorFn) (*api.FetchJobResponse, error) {
m.ctrl.T.Helper()
diff --git a/pkg/api/flamenco-manager.yaml b/pkg/api/flamenco-manager.yaml
index f8c60fae..770cd19c 100644
--- a/pkg/api/flamenco-manager.yaml
+++ b/pkg/api/flamenco-manager.yaml
@@ -351,6 +351,29 @@ paths:
schema:
$ref: "#/components/schemas/Error"
+ /api/jobs/{job_id}/tasks:
+ summary: Access tasks of this job.
+ get:
+ operationId: fetchJobTasks
+ summary: Fetch a summary of all tasks of the given job.
+ tags: [jobs]
+ parameters:
+ - name: job_id
+ in: path
+ required: true
+ schema: { type: string, format: uuid }
+ responses:
+ "200":
+ description: Get summaries of the tasks of this job.
+ content:
+ application/json:
+ schema: { $ref: "#/components/schemas/JobTasksSummary" }
+ default:
+ description: Unexpected error.
+ content:
+ application/json:
+ schema:
+ $ref: "#/components/schemas/Error"
## Shaman
@@ -783,7 +806,7 @@ components:
updated:
type: string
format: date-time
- description: Creation timestamp
+ description: Timestamp of last update.
status: { $ref: "#/components/schemas/JobStatus" }
activity: { type: string, description: "Description of the last activity on this job." }
required: [id, created, updated, status, activity]
@@ -841,6 +864,27 @@ components:
items: { $ref: "#/components/schemas/Job" }
required: [jobs]
+ JobTasksSummary:
+ description: "Simplified list of tasks of a job. Contains all tasks, but not all info of each task."
+ type: object
+ properties:
+ tasks:
+ type: array
+ items: { $ref: "#/components/schemas/TaskSummary" }
+ required: [jobs]
+
+ TaskSummary:
+ type: object
+ description: Just enough information about the task to show in the job's task list.
+ properties:
+ id: { type: string, format: uuid }
+ name: { type: string }
+ status: { $ref: "#/components/schemas/TaskStatus" }
+ priority: { type: integer }
+ task_type: { type: string }
+ updated: { type: string, format: date-time }
+ required: [id, name, status, priority, task_type, updated]
+
JobStatusChange:
type: object
properties:
diff --git a/pkg/api/openapi_client.gen.go b/pkg/api/openapi_client.gen.go
index 12ff9910..6ba7adb8 100644
--- a/pkg/api/openapi_client.gen.go
+++ b/pkg/api/openapi_client.gen.go
@@ -117,6 +117,9 @@ type ClientInterface interface {
SetJobStatus(ctx context.Context, jobId string, body SetJobStatusJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error)
+ // FetchJobTasks request
+ FetchJobTasks(ctx context.Context, jobId string, reqEditors ...RequestEditorFn) (*http.Response, error)
+
// GetVersion request
GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error)
@@ -286,6 +289,18 @@ func (c *Client) SetJobStatus(ctx context.Context, jobId string, body SetJobStat
return c.Client.Do(req)
}
+func (c *Client) FetchJobTasks(ctx context.Context, jobId string, reqEditors ...RequestEditorFn) (*http.Response, error) {
+ req, err := NewFetchJobTasksRequest(c.Server, jobId)
+ if err != nil {
+ return nil, err
+ }
+ req = req.WithContext(ctx)
+ if err := c.applyEditors(ctx, req, reqEditors); err != nil {
+ return nil, err
+ }
+ return c.Client.Do(req)
+}
+
func (c *Client) GetVersion(ctx context.Context, reqEditors ...RequestEditorFn) (*http.Response, error) {
req, err := NewGetVersionRequest(c.Server)
if err != nil {
@@ -751,6 +766,40 @@ func NewSetJobStatusRequestWithBody(server string, jobId string, contentType str
return req, nil
}
+// NewFetchJobTasksRequest generates requests for FetchJobTasks
+func NewFetchJobTasksRequest(server string, jobId string) (*http.Request, error) {
+ var err error
+
+ var pathParam0 string
+
+ pathParam0, err = runtime.StyleParamWithLocation("simple", false, "job_id", runtime.ParamLocationPath, jobId)
+ if err != nil {
+ return nil, err
+ }
+
+ serverURL, err := url.Parse(server)
+ if err != nil {
+ return nil, err
+ }
+
+ operationPath := fmt.Sprintf("/api/jobs/%s/tasks", pathParam0)
+ if operationPath[0] == '/' {
+ operationPath = "." + operationPath
+ }
+
+ queryURL, err := serverURL.Parse(operationPath)
+ if err != nil {
+ return nil, err
+ }
+
+ req, err := http.NewRequest("GET", queryURL.String(), nil)
+ if err != nil {
+ return nil, err
+ }
+
+ return req, nil
+}
+
// NewGetVersionRequest generates requests for GetVersion
func NewGetVersionRequest(server string) (*http.Request, error) {
var err error
@@ -1282,6 +1331,9 @@ type ClientWithResponsesInterface interface {
SetJobStatusWithResponse(ctx context.Context, jobId string, body SetJobStatusJSONRequestBody, reqEditors ...RequestEditorFn) (*SetJobStatusResponse, error)
+ // FetchJobTasks request
+ FetchJobTasksWithResponse(ctx context.Context, jobId string, reqEditors ...RequestEditorFn) (*FetchJobTasksResponse, error)
+
// GetVersion request
GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error)
@@ -1487,6 +1539,29 @@ func (r SetJobStatusResponse) StatusCode() int {
return 0
}
+type FetchJobTasksResponse struct {
+ Body []byte
+ HTTPResponse *http.Response
+ JSON200 *JobTasksSummary
+ JSONDefault *Error
+}
+
+// Status returns HTTPResponse.Status
+func (r FetchJobTasksResponse) Status() string {
+ if r.HTTPResponse != nil {
+ return r.HTTPResponse.Status
+ }
+ return http.StatusText(0)
+}
+
+// StatusCode returns HTTPResponse.StatusCode
+func (r FetchJobTasksResponse) StatusCode() int {
+ if r.HTTPResponse != nil {
+ return r.HTTPResponse.StatusCode
+ }
+ return 0
+}
+
type GetVersionResponse struct {
Body []byte
HTTPResponse *http.Response
@@ -1848,6 +1923,15 @@ func (c *ClientWithResponses) SetJobStatusWithResponse(ctx context.Context, jobI
return ParseSetJobStatusResponse(rsp)
}
+// FetchJobTasksWithResponse request returning *FetchJobTasksResponse
+func (c *ClientWithResponses) FetchJobTasksWithResponse(ctx context.Context, jobId string, reqEditors ...RequestEditorFn) (*FetchJobTasksResponse, error) {
+ rsp, err := c.FetchJobTasks(ctx, jobId, reqEditors...)
+ if err != nil {
+ return nil, err
+ }
+ return ParseFetchJobTasksResponse(rsp)
+}
+
// GetVersionWithResponse request returning *GetVersionResponse
func (c *ClientWithResponses) GetVersionWithResponse(ctx context.Context, reqEditors ...RequestEditorFn) (*GetVersionResponse, error) {
rsp, err := c.GetVersion(ctx, reqEditors...)
@@ -2200,6 +2284,39 @@ func ParseSetJobStatusResponse(rsp *http.Response) (*SetJobStatusResponse, error
return response, nil
}
+// ParseFetchJobTasksResponse parses an HTTP response from a FetchJobTasksWithResponse call
+func ParseFetchJobTasksResponse(rsp *http.Response) (*FetchJobTasksResponse, error) {
+ bodyBytes, err := ioutil.ReadAll(rsp.Body)
+ defer func() { _ = rsp.Body.Close() }()
+ if err != nil {
+ return nil, err
+ }
+
+ response := &FetchJobTasksResponse{
+ Body: bodyBytes,
+ HTTPResponse: rsp,
+ }
+
+ switch {
+ case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200:
+ var dest JobTasksSummary
+ if err := json.Unmarshal(bodyBytes, &dest); err != nil {
+ return nil, err
+ }
+ response.JSON200 = &dest
+
+ case strings.Contains(rsp.Header.Get("Content-Type"), "json") && true:
+ var dest Error
+ if err := json.Unmarshal(bodyBytes, &dest); err != nil {
+ return nil, err
+ }
+ response.JSONDefault = &dest
+
+ }
+
+ return response, nil
+}
+
// ParseGetVersionResponse parses an HTTP response from a GetVersionWithResponse call
func ParseGetVersionResponse(rsp *http.Response) (*GetVersionResponse, error) {
bodyBytes, err := ioutil.ReadAll(rsp.Body)
diff --git a/pkg/api/openapi_server.gen.go b/pkg/api/openapi_server.gen.go
index ea914967..4c13d3fc 100644
--- a/pkg/api/openapi_server.gen.go
+++ b/pkg/api/openapi_server.gen.go
@@ -34,6 +34,9 @@ type ServerInterface interface {
// (POST /api/jobs/{job_id}/setstatus)
SetJobStatus(ctx echo.Context, jobId string) error
+ // Fetch a summary of all tasks of the given job.
+ // (GET /api/jobs/{job_id}/tasks)
+ FetchJobTasks(ctx echo.Context, jobId string) error
// Get the Flamenco version of this Manager
// (GET /api/version)
GetVersion(ctx echo.Context) error
@@ -162,6 +165,22 @@ func (w *ServerInterfaceWrapper) SetJobStatus(ctx echo.Context) error {
return err
}
+// FetchJobTasks converts echo context to params.
+func (w *ServerInterfaceWrapper) FetchJobTasks(ctx echo.Context) error {
+ var err error
+ // ------------- Path parameter "job_id" -------------
+ var jobId string
+
+ err = runtime.BindStyledParameterWithLocation("simple", false, "job_id", runtime.ParamLocationPath, ctx.Param("job_id"), &jobId)
+ if err != nil {
+ return echo.NewHTTPError(http.StatusBadRequest, fmt.Sprintf("Invalid format for parameter job_id: %s", err))
+ }
+
+ // Invoke the callback with all the unmarshalled arguments
+ err = w.Handler.FetchJobTasks(ctx, jobId)
+ return err
+}
+
// GetVersion converts echo context to params.
func (w *ServerInterfaceWrapper) GetVersion(ctx echo.Context) error {
var err error
@@ -389,6 +408,7 @@ func RegisterHandlersWithBaseURL(router EchoRouter, si ServerInterface, baseURL
router.GET(baseURL+"/api/jobs/types", wrapper.GetJobTypes)
router.GET(baseURL+"/api/jobs/:job_id", wrapper.FetchJob)
router.POST(baseURL+"/api/jobs/:job_id/setstatus", wrapper.SetJobStatus)
+ router.GET(baseURL+"/api/jobs/:job_id/tasks", wrapper.FetchJobTasks)
router.GET(baseURL+"/api/version", wrapper.GetVersion)
router.POST(baseURL+"/api/worker/register-worker", wrapper.RegisterWorker)
router.POST(baseURL+"/api/worker/sign-off", wrapper.SignOff)
diff --git a/pkg/api/openapi_spec.gen.go b/pkg/api/openapi_spec.gen.go
index a9ecf5bb..b5b6b01f 100644
--- a/pkg/api/openapi_spec.gen.go
+++ b/pkg/api/openapi_spec.gen.go
@@ -18,109 +18,112 @@ import (
// Base64 encoded, gzipped, json marshaled Swagger object
var swaggerSpec = []string{
- "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",
- "vqPX9dn1/wQAAP//GzfZKqRpAAA=",
+ "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",
+ "1Gk663t6Xb69/J8AAAD//wGTUz2RbgAA",
}
// GetSwagger returns the content of the embedded swagger specification file
diff --git a/pkg/api/openapi_types.gen.go b/pkg/api/openapi_types.gen.go
index 64ebb470..f0c4ba51 100644
--- a/pkg/api/openapi_types.gen.go
+++ b/pkg/api/openapi_types.gen.go
@@ -213,7 +213,7 @@ type Job struct {
Id string `json:"id"`
Status JobStatus `json:"status"`
- // Creation timestamp
+ // Timestamp of last update.
Updated time.Time `json:"updated"`
}
@@ -237,6 +237,11 @@ type JobStatusChange struct {
Status JobStatus `json:"status"`
}
+// Simplified list of tasks of a job. Contains all tasks, but not all info of each task.
+type JobTasksSummary struct {
+ Tasks *[]TaskSummary `json:"tasks,omitempty"`
+}
+
// Subset of a Job, sent over SocketIO when a job changes. For new jobs, `previous_status` will be excluded.
type JobUpdate struct {
// UUID of the Job
@@ -379,6 +384,16 @@ type SubmittedJob struct {
// TaskStatus defines model for TaskStatus.
type TaskStatus string
+// Just enough information about the task to show in the job's task list.
+type TaskSummary struct {
+ Id string `json:"id"`
+ Name string `json:"name"`
+ Priority int `json:"priority"`
+ Status TaskStatus `json:"status"`
+ TaskType string `json:"task_type"`
+ Updated time.Time `json:"updated"`
+}
+
// TaskUpdate is sent by a Worker to update the status & logs of a task it's executing.
type TaskUpdate struct {
// One-liner to indicate what's currently happening with the task. Overwrites previously sent activity strings.
diff --git a/web/app/src/manager-api/ApiClient.js b/web/app/src/manager-api/ApiClient.js
index 327bcec9..bcf77e43 100644
--- a/web/app/src/manager-api/ApiClient.js
+++ b/web/app/src/manager-api/ApiClient.js
@@ -55,7 +55,7 @@ class ApiClient {
* @default {}
*/
this.defaultHeaders = {
- 'User-Agent': 'Flamenco/b699647e-dirty / webbrowser'
+ 'User-Agent': 'Flamenco/e399b14e-dirty / webbrowser'
};
/**
diff --git a/web/app/src/manager-api/index.js b/web/app/src/manager-api/index.js
index 7549a99c..53f46278 100644
--- a/web/app/src/manager-api/index.js
+++ b/web/app/src/manager-api/index.js
@@ -26,6 +26,7 @@ import Job from './model/Job';
import JobAllOf from './model/JobAllOf';
import JobStatus from './model/JobStatus';
import JobStatusChange from './model/JobStatusChange';
+import JobTasksSummary from './model/JobTasksSummary';
import JobUpdate from './model/JobUpdate';
import JobsQuery from './model/JobsQuery';
import JobsQueryResult from './model/JobsQueryResult';
@@ -42,6 +43,7 @@ import ShamanRequirementsResponse from './model/ShamanRequirementsResponse';
import ShamanSingleFileStatus from './model/ShamanSingleFileStatus';
import SubmittedJob from './model/SubmittedJob';
import TaskStatus from './model/TaskStatus';
+import TaskSummary from './model/TaskSummary';
import TaskUpdate from './model/TaskUpdate';
import WorkerRegistration from './model/WorkerRegistration';
import WorkerSignOn from './model/WorkerSignOn';
@@ -170,6 +172,12 @@ export {
*/
JobStatusChange,
+ /**
+ * The JobTasksSummary model constructor.
+ * @property {module:model/JobTasksSummary}
+ */
+ JobTasksSummary,
+
/**
* The JobUpdate model constructor.
* @property {module:model/JobUpdate}
@@ -266,6 +274,12 @@ export {
*/
TaskStatus,
+ /**
+ * The TaskSummary model constructor.
+ * @property {module:model/TaskSummary}
+ */
+ TaskSummary,
+
/**
* The TaskUpdate model constructor.
* @property {module:model/TaskUpdate}
diff --git a/web/app/src/manager-api/manager/JobsApi.js b/web/app/src/manager-api/manager/JobsApi.js
index bcbfb787..217f1baa 100644
--- a/web/app/src/manager-api/manager/JobsApi.js
+++ b/web/app/src/manager-api/manager/JobsApi.js
@@ -18,6 +18,7 @@ import AvailableJobTypes from '../model/AvailableJobTypes';
import Error from '../model/Error';
import Job from '../model/Job';
import JobStatusChange from '../model/JobStatusChange';
+import JobTasksSummary from '../model/JobTasksSummary';
import JobsQuery from '../model/JobsQuery';
import JobsQueryResult from '../model/JobsQueryResult';
import SubmittedJob from '../model/SubmittedJob';
@@ -88,6 +89,52 @@ export default class JobsApi {
}
+ /**
+ * Fetch a summary of all tasks of the given job.
+ * @param {String} jobId
+ * @return {Promise} a {@link https://www.promisejs.org/|Promise}, with an object containing data of type {@link module:model/JobTasksSummary} and HTTP response
+ */
+ fetchJobTasksWithHttpInfo(jobId) {
+ let postBody = null;
+ // verify the required parameter 'jobId' is set
+ if (jobId === undefined || jobId === null) {
+ throw new Error("Missing the required parameter 'jobId' when calling fetchJobTasks");
+ }
+
+ let pathParams = {
+ 'job_id': jobId
+ };
+ let queryParams = {
+ };
+ let headerParams = {
+ };
+ let formParams = {
+ };
+
+ let authNames = [];
+ let contentTypes = [];
+ let accepts = ['application/json'];
+ let returnType = JobTasksSummary;
+ return this.apiClient.callApi(
+ '/api/jobs/{job_id}/tasks', 'GET',
+ pathParams, queryParams, headerParams, formParams, postBody,
+ authNames, contentTypes, accepts, returnType, null
+ );
+ }
+
+ /**
+ * Fetch a summary of all tasks of the given job.
+ * @param {String} jobId
+ * @return {Promise} a {@link https://www.promisejs.org/|Promise}, with data of type {@link module:model/JobTasksSummary}
+ */
+ fetchJobTasks(jobId) {
+ return this.fetchJobTasksWithHttpInfo(jobId)
+ .then(function(response_and_data) {
+ return response_and_data.data;
+ });
+ }
+
+
/**
* Get single job type and its parameters.
* @param {String} typeName
diff --git a/web/app/src/manager-api/model/Job.js b/web/app/src/manager-api/model/Job.js
index 27d92293..c59940cb 100644
--- a/web/app/src/manager-api/model/Job.js
+++ b/web/app/src/manager-api/model/Job.js
@@ -32,7 +32,7 @@ class Job {
* @param priority {Number}
* @param id {String} UUID of the Job
* @param created {Date} Creation timestamp
- * @param updated {Date} Creation timestamp
+ * @param updated {Date} Timestamp of last update.
* @param status {module:model/JobStatus}
* @param activity {String} Description of the last activity on this job.
*/
@@ -147,7 +147,7 @@ Job.prototype['id'] = undefined;
Job.prototype['created'] = undefined;
/**
- * Creation timestamp
+ * Timestamp of last update.
* @member {Date} updated
*/
Job.prototype['updated'] = undefined;
@@ -199,7 +199,7 @@ JobAllOf.prototype['id'] = undefined;
*/
JobAllOf.prototype['created'] = undefined;
/**
- * Creation timestamp
+ * Timestamp of last update.
* @member {Date} updated
*/
JobAllOf.prototype['updated'] = undefined;
diff --git a/web/app/src/manager-api/model/JobAllOf.js b/web/app/src/manager-api/model/JobAllOf.js
index e8800ab7..2be915bb 100644
--- a/web/app/src/manager-api/model/JobAllOf.js
+++ b/web/app/src/manager-api/model/JobAllOf.js
@@ -25,7 +25,7 @@ class JobAllOf {
* @alias module:model/JobAllOf
* @param id {String} UUID of the Job
* @param created {Date} Creation timestamp
- * @param updated {Date} Creation timestamp
+ * @param updated {Date} Timestamp of last update.
* @param status {module:model/JobStatus}
* @param activity {String} Description of the last activity on this job.
*/
@@ -93,7 +93,7 @@ JobAllOf.prototype['id'] = undefined;
JobAllOf.prototype['created'] = undefined;
/**
- * Creation timestamp
+ * Timestamp of last update.
* @member {Date} updated
*/
JobAllOf.prototype['updated'] = undefined;
diff --git a/web/app/src/manager-api/model/JobTasksSummary.js b/web/app/src/manager-api/model/JobTasksSummary.js
new file mode 100644
index 00000000..4ae4da7c
--- /dev/null
+++ b/web/app/src/manager-api/model/JobTasksSummary.js
@@ -0,0 +1,73 @@
+/**
+ * Flamenco manager
+ * Render Farm manager API
+ *
+ * The version of the OpenAPI document: 1.0.0
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ *
+ */
+
+import ApiClient from '../ApiClient';
+import TaskSummary from './TaskSummary';
+
+/**
+ * The JobTasksSummary model module.
+ * @module model/JobTasksSummary
+ * @version 0.0.0
+ */
+class JobTasksSummary {
+ /**
+ * Constructs a new JobTasksSummary
.
+ * Simplified list of tasks of a job. Contains all tasks, but not all info of each task.
+ * @alias module:model/JobTasksSummary
+ */
+ constructor() {
+
+ JobTasksSummary.initialize(this);
+ }
+
+ /**
+ * Initializes the fields of this object.
+ * This method is used by the constructors of any subclasses, in order to implement multiple inheritance (mix-ins).
+ * Only for internal use.
+ */
+ static initialize(obj) {
+ }
+
+ /**
+ * Constructs a JobTasksSummary
from a plain JavaScript object, optionally creating a new instance.
+ * Copies all relevant properties from data
to obj
if supplied or a new instance if not.
+ * @param {Object} data The plain JavaScript object bearing properties of interest.
+ * @param {module:model/JobTasksSummary} obj Optional instance to populate.
+ * @return {module:model/JobTasksSummary} The populated JobTasksSummary
instance.
+ */
+ static constructFromObject(data, obj) {
+ if (data) {
+ obj = obj || new JobTasksSummary();
+
+ if (data.hasOwnProperty('tasks')) {
+ obj['tasks'] = ApiClient.convertToType(data['tasks'], [TaskSummary]);
+ }
+ }
+ return obj;
+ }
+
+
+}
+
+/**
+ * @member {Array.} tasks
+ */
+JobTasksSummary.prototype['tasks'] = undefined;
+
+
+
+
+
+
+export default JobTasksSummary;
+
diff --git a/web/app/src/manager-api/model/TaskSummary.js b/web/app/src/manager-api/model/TaskSummary.js
new file mode 100644
index 00000000..3f3c64a8
--- /dev/null
+++ b/web/app/src/manager-api/model/TaskSummary.js
@@ -0,0 +1,125 @@
+/**
+ * Flamenco manager
+ * Render Farm manager API
+ *
+ * The version of the OpenAPI document: 1.0.0
+ *
+ *
+ * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
+ * https://openapi-generator.tech
+ * Do not edit the class manually.
+ *
+ */
+
+import ApiClient from '../ApiClient';
+import TaskStatus from './TaskStatus';
+
+/**
+ * The TaskSummary model module.
+ * @module model/TaskSummary
+ * @version 0.0.0
+ */
+class TaskSummary {
+ /**
+ * Constructs a new TaskSummary
.
+ * Just enough information about the task to show in the job's task list.
+ * @alias module:model/TaskSummary
+ * @param id {String}
+ * @param name {String}
+ * @param status {module:model/TaskStatus}
+ * @param priority {Number}
+ * @param taskType {String}
+ * @param updated {Date}
+ */
+ constructor(id, name, status, priority, taskType, updated) {
+
+ TaskSummary.initialize(this, id, name, status, priority, taskType, updated);
+ }
+
+ /**
+ * Initializes the fields of this object.
+ * This method is used by the constructors of any subclasses, in order to implement multiple inheritance (mix-ins).
+ * Only for internal use.
+ */
+ static initialize(obj, id, name, status, priority, taskType, updated) {
+ obj['id'] = id;
+ obj['name'] = name;
+ obj['status'] = status;
+ obj['priority'] = priority;
+ obj['task_type'] = taskType;
+ obj['updated'] = updated;
+ }
+
+ /**
+ * Constructs a TaskSummary
from a plain JavaScript object, optionally creating a new instance.
+ * Copies all relevant properties from data
to obj
if supplied or a new instance if not.
+ * @param {Object} data The plain JavaScript object bearing properties of interest.
+ * @param {module:model/TaskSummary} obj Optional instance to populate.
+ * @return {module:model/TaskSummary} The populated TaskSummary
instance.
+ */
+ static constructFromObject(data, obj) {
+ if (data) {
+ obj = obj || new TaskSummary();
+
+ if (data.hasOwnProperty('id')) {
+ obj['id'] = ApiClient.convertToType(data['id'], 'String');
+ }
+ if (data.hasOwnProperty('name')) {
+ obj['name'] = ApiClient.convertToType(data['name'], 'String');
+ }
+ if (data.hasOwnProperty('status')) {
+ obj['status'] = TaskStatus.constructFromObject(data['status']);
+ }
+ if (data.hasOwnProperty('priority')) {
+ obj['priority'] = ApiClient.convertToType(data['priority'], 'Number');
+ }
+ if (data.hasOwnProperty('task_type')) {
+ obj['task_type'] = ApiClient.convertToType(data['task_type'], 'String');
+ }
+ if (data.hasOwnProperty('updated')) {
+ obj['updated'] = ApiClient.convertToType(data['updated'], 'Date');
+ }
+ }
+ return obj;
+ }
+
+
+}
+
+/**
+ * @member {String} id
+ */
+TaskSummary.prototype['id'] = undefined;
+
+/**
+ * @member {String} name
+ */
+TaskSummary.prototype['name'] = undefined;
+
+/**
+ * @member {module:model/TaskStatus} status
+ */
+TaskSummary.prototype['status'] = undefined;
+
+/**
+ * @member {Number} priority
+ */
+TaskSummary.prototype['priority'] = undefined;
+
+/**
+ * @member {String} task_type
+ */
+TaskSummary.prototype['task_type'] = undefined;
+
+/**
+ * @member {Date} updated
+ */
+TaskSummary.prototype['updated'] = undefined;
+
+
+
+
+
+
+export default TaskSummary;
+