You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by ma...@apache.org on 2019/06/11 18:30:20 UTC

[airavata-django-portal] branch master updated (f3249df -> 8e26c80)

This is an automated email from the ASF dual-hosted git repository.

machristie pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git.


    from f3249df  AIRAVATA-3078 Send email to admins when new user account created
     new a0786dc  Regenerated Thrift stubs
     new 4ff60cf  AIRAVATA-3029 application output metadata editing
     new 8e26c80  AIRAVATA-3029 output view provider initial implementation

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 airavata/api/Airavata-remote                       |  16 +-
 airavata/api/Airavata.py                           | 227 +--------------
 airavata/api/constants.py                          |   2 +-
 airavata/api/sharing/SharingRegistryService-remote |   7 +
 airavata/api/sharing/SharingRegistryService.py     |  15 +-
 airavata/api/sharing/constants.py                  |   1 +
 airavata/api/sharing/ttypes.py                     |   1 +
 airavata/api/ttypes.py                             |   1 +
 .../base/migrations => airavata/base}/__init__.py  |   0
 .../api/BaseAPI-remote}                            |  55 +---
 airavata/base/api/BaseAPI.py                       | 198 +++++++++++++
 airavata/base/api/__init__.py                      |   1 +
 .../user/cpi/error => base/api}/constants.py       |   0
 airavata/{api/sharing => base/api}/ttypes.py       |   1 -
 airavata/model/application/io/ttypes.py            |  14 +-
 airavata/model/experiment/ttypes.py                |   3 +
 airavata/model/process/ttypes.py                   | 147 +++++++++-
 airavata/model/task/ttypes.py                      |  26 +-
 .../groupmanager/cpi/GroupManagerService-remote    |   7 +
 .../groupmanager/cpi/GroupManagerService.py        |  15 +-
 .../service/profile/groupmanager/cpi/constants.py  |   2 +-
 .../service/profile/groupmanager/cpi/ttypes.py     |   1 +
 .../iam/admin/services/cpi/IamAdminServices-remote |  16 +-
 .../iam/admin/services/cpi/IamAdminServices.py     | 183 +-----------
 .../profile/iam/admin/services/cpi/constants.py    |   2 +-
 .../profile/iam/admin/services/cpi/ttypes.py       |   1 +
 .../profile/tenant/cpi/TenantProfileService-remote |  16 +-
 .../profile/tenant/cpi/TenantProfileService.py     | 183 +-----------
 airavata/service/profile/tenant/cpi/constants.py   |   2 +-
 airavata/service/profile/tenant/cpi/ttypes.py      |   1 +
 airavata/service/profile/ttypes.py                 |   1 +
 .../profile/user/cpi/UserProfileService-remote     |   7 +
 .../service/profile/user/cpi/UserProfileService.py |  15 +-
 airavata/service/profile/user/cpi/constants.py     |   2 +-
 airavata/service/profile/user/cpi/ttypes.py        |   1 +
 .../applications/ApplicationOutputFieldEditor.vue  |   9 +-
 django_airavata/apps/api/output_views.py           |  76 +++++
 django_airavata/apps/api/serializers.py            |  22 +-
 .../js/models/FullExperiment.js                    | 113 ++++----
 .../js/models/OutputDataObjectType.js              |   1 +
 django_airavata/apps/api/views.py                  |  13 +-
 .../js/components/experiment/ExperimentSummary.vue | 315 ++++++++++++---------
 .../output-displays/DownloadOutputDisplay.vue      |  33 +++
 .../experiment/output-displays/LinkDisplay.vue     |  26 ++
 .../output-displays/OutputDisplayContainer.vue     |  69 +++++
 django_airavata/settings.py                        |   4 +
 46 files changed, 964 insertions(+), 887 deletions(-)
 copy {django_airavata/wagtailapps/base/migrations => airavata/base}/__init__.py (100%)
 copy airavata/{service/profile/tenant/cpi/TenantProfileService-remote => base/api/BaseAPI-remote} (56%)
 create mode 100644 airavata/base/api/BaseAPI.py
 create mode 100644 airavata/base/api/__init__.py
 copy airavata/{service/profile/user/cpi/error => base/api}/constants.py (100%)
 copy airavata/{api/sharing => base/api}/ttypes.py (90%)
 create mode 100644 django_airavata/apps/api/output_views.py
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/DownloadOutputDisplay.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/LinkDisplay.vue
 create mode 100644 django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/OutputDisplayContainer.vue


[airavata-django-portal] 01/03: Regenerated Thrift stubs

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit a0786dc53843b70ce21b5f2e590f5e8abc6a37f9
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Jun 11 09:34:50 2019 -0400

    Regenerated Thrift stubs
---
 airavata/api/Airavata-remote                       |  16 +-
 airavata/api/Airavata.py                           | 227 +--------------------
 airavata/api/constants.py                          |   2 +-
 airavata/api/sharing/SharingRegistryService-remote |   7 +
 airavata/api/sharing/SharingRegistryService.py     |  15 +-
 airavata/api/sharing/constants.py                  |   1 +
 airavata/api/sharing/ttypes.py                     |   1 +
 airavata/api/ttypes.py                             |   1 +
 airavata/base/__init__.py                          |   0
 .../api/BaseAPI-remote}                            |  55 +----
 airavata/base/api/BaseAPI.py                       | 198 ++++++++++++++++++
 airavata/base/api/__init__.py                      |   1 +
 airavata/{api/sharing => base/api}/constants.py    |   0
 airavata/{api/sharing => base/api}/ttypes.py       |   1 -
 airavata/model/application/io/ttypes.py            |  14 +-
 airavata/model/experiment/ttypes.py                |   3 +
 airavata/model/process/ttypes.py                   | 147 +++++++++++--
 airavata/model/task/ttypes.py                      |  26 ++-
 .../groupmanager/cpi/GroupManagerService-remote    |   7 +
 .../groupmanager/cpi/GroupManagerService.py        |  15 +-
 .../service/profile/groupmanager/cpi/constants.py  |   2 +-
 .../service/profile/groupmanager/cpi/ttypes.py     |   1 +
 .../iam/admin/services/cpi/IamAdminServices-remote |  16 +-
 .../iam/admin/services/cpi/IamAdminServices.py     | 183 +----------------
 .../profile/iam/admin/services/cpi/constants.py    |   2 +-
 .../profile/iam/admin/services/cpi/ttypes.py       |   1 +
 .../profile/tenant/cpi/TenantProfileService-remote |  16 +-
 .../profile/tenant/cpi/TenantProfileService.py     | 183 +----------------
 airavata/service/profile/tenant/cpi/constants.py   |   2 +-
 airavata/service/profile/tenant/cpi/ttypes.py      |   1 +
 airavata/service/profile/ttypes.py                 |   1 +
 .../profile/user/cpi/UserProfileService-remote     |   7 +
 .../service/profile/user/cpi/UserProfileService.py |  15 +-
 airavata/service/profile/user/cpi/constants.py     |   2 +-
 airavata/service/profile/user/cpi/ttypes.py        |   1 +
 35 files changed, 471 insertions(+), 699 deletions(-)

diff --git a/airavata/api/Airavata-remote b/airavata/api/Airavata-remote
index 6957696..2531223 100755
--- a/airavata/api/Airavata-remote
+++ b/airavata/api/Airavata-remote
@@ -24,7 +24,6 @@ if len(sys.argv) <= 1 or sys.argv[1] == '--help':
     print('Usage: ' + sys.argv[0] + ' [-h host[:port]] [-u url] [-f[ramed]] [-s[sl]] [-novalidate] [-ca_certs certs] [-keyfile keyfile] [-certfile certfile] function [arg1 [arg2...]]')
     print('')
     print('Functions:')
-    print('  string getAPIVersion()')
     print('  bool isUserExists(AuthzToken authzToken, string gatewayId, string userName)')
     print('  string addGateway(AuthzToken authzToken, Gateway gateway)')
     print('   getAllUsersInGateway(AuthzToken authzToken, string gatewayId)')
@@ -216,6 +215,7 @@ if len(sys.argv) <= 1 or sys.argv[1] == '--help':
     print('  string saveParsingTemplate(AuthzToken authzToken, ParsingTemplate parsingTemplate)')
     print('  bool removeParsingTemplate(AuthzToken authzToken, string templateId, string gatewayId)')
     print('   listAllParsingTemplates(AuthzToken authzToken, string gatewayId)')
+    print('  string getAPIVersion()')
     print('')
     sys.exit(0)
 
@@ -295,13 +295,7 @@ protocol = TBinaryProtocol(transport)
 client = Airavata.Client(protocol)
 transport.open()
 
-if cmd == 'getAPIVersion':
-    if len(args) != 0:
-        print('getAPIVersion requires 0 args')
-        sys.exit(1)
-    pp.pprint(client.getAPIVersion())
-
-elif cmd == 'isUserExists':
+if cmd == 'isUserExists':
     if len(args) != 3:
         print('isUserExists requires 3 args')
         sys.exit(1)
@@ -1447,6 +1441,12 @@ elif cmd == 'listAllParsingTemplates':
         sys.exit(1)
     pp.pprint(client.listAllParsingTemplates(eval(args[0]), args[1],))
 
+elif cmd == 'getAPIVersion':
+    if len(args) != 0:
+        print('getAPIVersion requires 0 args')
+        sys.exit(1)
+    pp.pprint(client.getAPIVersion())
+
 else:
     print('Unrecognized method %s' % cmd)
     sys.exit(1)
diff --git a/airavata/api/Airavata.py b/airavata/api/Airavata.py
index b6e7762..94b5d17 100644
--- a/airavata/api/Airavata.py
+++ b/airavata/api/Airavata.py
@@ -9,20 +9,14 @@
 from thrift.Thrift import TType, TMessageType, TFrozenDict, TException, TApplicationException
 from thrift.protocol.TProtocol import TProtocolException
 import sys
+import airavata.base.api.BaseAPI
 import logging
 from .ttypes import *
 from thrift.Thrift import TProcessor
 from thrift.transport import TTransport
 
 
-class Iface(object):
-    def getAPIVersion(self):
-        """
-        Fetch Apache Airavata API version
-
-        """
-        pass
-
+class Iface(airavata.base.api.BaseAPI.Iface):
     def isUserExists(self, authzToken, gatewayId, userName):
         """
         Verify if User Exists within Airavata.
@@ -3599,48 +3593,9 @@ class Iface(object):
         pass
 
 
-class Client(Iface):
+class Client(airavata.base.api.BaseAPI.Client, Iface):
     def __init__(self, iprot, oprot=None):
-        self._iprot = self._oprot = iprot
-        if oprot is not None:
-            self._oprot = oprot
-        self._seqid = 0
-
-    def getAPIVersion(self):
-        """
-        Fetch Apache Airavata API version
-
-        """
-        self.send_getAPIVersion()
-        return self.recv_getAPIVersion()
-
-    def send_getAPIVersion(self):
-        self._oprot.writeMessageBegin('getAPIVersion', TMessageType.CALL, self._seqid)
-        args = getAPIVersion_args()
-        args.write(self._oprot)
-        self._oprot.writeMessageEnd()
-        self._oprot.trans.flush()
-
-    def recv_getAPIVersion(self):
-        iprot = self._iprot
-        (fname, mtype, rseqid) = iprot.readMessageBegin()
-        if mtype == TMessageType.EXCEPTION:
-            x = TApplicationException()
-            x.read(iprot)
-            iprot.readMessageEnd()
-            raise x
-        result = getAPIVersion_result()
-        result.read(iprot)
-        iprot.readMessageEnd()
-        if result.success is not None:
-            return result.success
-        if result.ire is not None:
-            raise result.ire
-        if result.ace is not None:
-            raise result.ace
-        if result.ase is not None:
-            raise result.ase
-        raise TApplicationException(TApplicationException.MISSING_RESULT, "getAPIVersion failed: unknown result")
+        airavata.base.api.BaseAPI.Client.__init__(self, iprot, oprot)
 
     def isUserExists(self, authzToken, gatewayId, userName):
         """
@@ -13669,11 +13624,9 @@ class Client(Iface):
         raise TApplicationException(TApplicationException.MISSING_RESULT, "listAllParsingTemplates failed: unknown result")
 
 
-class Processor(Iface, TProcessor):
+class Processor(airavata.base.api.BaseAPI.Processor, Iface, TProcessor):
     def __init__(self, handler):
-        self._handler = handler
-        self._processMap = {}
-        self._processMap["getAPIVersion"] = Processor.process_getAPIVersion
+        airavata.base.api.BaseAPI.Processor.__init__(self, handler)
         self._processMap["isUserExists"] = Processor.process_isUserExists
         self._processMap["addGateway"] = Processor.process_addGateway
         self._processMap["getAllUsersInGateway"] = Processor.process_getAllUsersInGateway
@@ -13881,34 +13834,6 @@ class Processor(Iface, TProcessor):
             self._processMap[name](self, seqid, iprot, oprot)
         return True
 
-    def process_getAPIVersion(self, seqid, iprot, oprot):
-        args = getAPIVersion_args()
-        args.read(iprot)
-        iprot.readMessageEnd()
-        result = getAPIVersion_result()
-        try:
-            result.success = self._handler.getAPIVersion()
-            msg_type = TMessageType.REPLY
-        except (TTransport.TTransportException, KeyboardInterrupt, SystemExit):
-            raise
-        except airavata.api.error.ttypes.InvalidRequestException as ire:
-            msg_type = TMessageType.REPLY
-            result.ire = ire
-        except airavata.api.error.ttypes.AiravataClientException as ace:
-            msg_type = TMessageType.REPLY
-            result.ace = ace
-        except airavata.api.error.ttypes.AiravataSystemException as ase:
-            msg_type = TMessageType.REPLY
-            result.ase = ase
-        except Exception as ex:
-            msg_type = TMessageType.EXCEPTION
-            logging.exception(ex)
-            result = TApplicationException(TApplicationException.INTERNAL_ERROR, 'Internal error')
-        oprot.writeMessageBegin("getAPIVersion", msg_type, seqid)
-        result.write(oprot)
-        oprot.writeMessageEnd()
-        oprot.trans.flush()
-
     def process_isUserExists(self, seqid, iprot, oprot):
         args = isUserExists_args()
         args.read(iprot)
@@ -19866,146 +19791,6 @@ class Processor(Iface, TProcessor):
 # HELPER FUNCTIONS AND STRUCTURES
 
 
-class getAPIVersion_args(object):
-
-    thrift_spec = (
-    )
-
-    def read(self, iprot):
-        if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
-            iprot._fast_decode(self, iprot, (self.__class__, self.thrift_spec))
-            return
-        iprot.readStructBegin()
-        while True:
-            (fname, ftype, fid) = iprot.readFieldBegin()
-            if ftype == TType.STOP:
-                break
-            else:
-                iprot.skip(ftype)
-            iprot.readFieldEnd()
-        iprot.readStructEnd()
-
-    def write(self, oprot):
-        if oprot._fast_encode is not None and self.thrift_spec is not None:
-            oprot.trans.write(oprot._fast_encode(self, (self.__class__, self.thrift_spec)))
-            return
-        oprot.writeStructBegin('getAPIVersion_args')
-        oprot.writeFieldStop()
-        oprot.writeStructEnd()
-
-    def validate(self):
-        return
-
-    def __repr__(self):
-        L = ['%s=%r' % (key, value)
-             for key, value in self.__dict__.items()]
-        return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
-
-    def __eq__(self, other):
-        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
-
-    def __ne__(self, other):
-        return not (self == other)
-
-
-class getAPIVersion_result(object):
-    """
-    Attributes:
-     - success
-     - ire
-     - ace
-     - ase
-    """
-
-    thrift_spec = (
-        (0, TType.STRING, 'success', 'UTF8', None, ),  # 0
-        (1, TType.STRUCT, 'ire', (airavata.api.error.ttypes.InvalidRequestException, airavata.api.error.ttypes.InvalidRequestException.thrift_spec), None, ),  # 1
-        (2, TType.STRUCT, 'ace', (airavata.api.error.ttypes.AiravataClientException, airavata.api.error.ttypes.AiravataClientException.thrift_spec), None, ),  # 2
-        (3, TType.STRUCT, 'ase', (airavata.api.error.ttypes.AiravataSystemException, airavata.api.error.ttypes.AiravataSystemException.thrift_spec), None, ),  # 3
-    )
-
-    def __init__(self, success=None, ire=None, ace=None, ase=None,):
-        self.success = success
-        self.ire = ire
-        self.ace = ace
-        self.ase = ase
-
-    def read(self, iprot):
-        if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
-            iprot._fast_decode(self, iprot, (self.__class__, self.thrift_spec))
-            return
-        iprot.readStructBegin()
-        while True:
-            (fname, ftype, fid) = iprot.readFieldBegin()
-            if ftype == TType.STOP:
-                break
-            if fid == 0:
-                if ftype == TType.STRING:
-                    self.success = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
-                else:
-                    iprot.skip(ftype)
-            elif fid == 1:
-                if ftype == TType.STRUCT:
-                    self.ire = airavata.api.error.ttypes.InvalidRequestException()
-                    self.ire.read(iprot)
-                else:
-                    iprot.skip(ftype)
-            elif fid == 2:
-                if ftype == TType.STRUCT:
-                    self.ace = airavata.api.error.ttypes.AiravataClientException()
-                    self.ace.read(iprot)
-                else:
-                    iprot.skip(ftype)
-            elif fid == 3:
-                if ftype == TType.STRUCT:
-                    self.ase = airavata.api.error.ttypes.AiravataSystemException()
-                    self.ase.read(iprot)
-                else:
-                    iprot.skip(ftype)
-            else:
-                iprot.skip(ftype)
-            iprot.readFieldEnd()
-        iprot.readStructEnd()
-
-    def write(self, oprot):
-        if oprot._fast_encode is not None and self.thrift_spec is not None:
-            oprot.trans.write(oprot._fast_encode(self, (self.__class__, self.thrift_spec)))
-            return
-        oprot.writeStructBegin('getAPIVersion_result')
-        if self.success is not None:
-            oprot.writeFieldBegin('success', TType.STRING, 0)
-            oprot.writeString(self.success.encode('utf-8') if sys.version_info[0] == 2 else self.success)
-            oprot.writeFieldEnd()
-        if self.ire is not None:
-            oprot.writeFieldBegin('ire', TType.STRUCT, 1)
-            self.ire.write(oprot)
-            oprot.writeFieldEnd()
-        if self.ace is not None:
-            oprot.writeFieldBegin('ace', TType.STRUCT, 2)
-            self.ace.write(oprot)
-            oprot.writeFieldEnd()
-        if self.ase is not None:
-            oprot.writeFieldBegin('ase', TType.STRUCT, 3)
-            self.ase.write(oprot)
-            oprot.writeFieldEnd()
-        oprot.writeFieldStop()
-        oprot.writeStructEnd()
-
-    def validate(self):
-        return
-
-    def __repr__(self):
-        L = ['%s=%r' % (key, value)
-             for key, value in self.__dict__.items()]
-        return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
-
-    def __eq__(self, other):
-        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
-
-    def __ne__(self, other):
-        return not (self == other)
-
-
 class isUserExists_args(object):
     """
     Attributes:
diff --git a/airavata/api/constants.py b/airavata/api/constants.py
index b7efdcc..7d73bb5 100644
--- a/airavata/api/constants.py
+++ b/airavata/api/constants.py
@@ -10,4 +10,4 @@ from thrift.Thrift import TType, TMessageType, TFrozenDict, TException, TApplica
 from thrift.protocol.TProtocol import TProtocolException
 import sys
 from .ttypes import *
-AIRAVATA_API_VERSION = "0.17.0"
+AIRAVATA_API_VERSION = "0.18.0"
diff --git a/airavata/api/sharing/SharingRegistryService-remote b/airavata/api/sharing/SharingRegistryService-remote
index 4a0427e..85bd9f9 100755
--- a/airavata/api/sharing/SharingRegistryService-remote
+++ b/airavata/api/sharing/SharingRegistryService-remote
@@ -81,6 +81,7 @@ if len(sys.argv) <= 1 or sys.argv[1] == '--help':
     print('  bool shareEntityWithGroups(string domainId, string entityId,  groupList, string permissionTypeId, bool cascadePermission)')
     print('  bool revokeEntitySharingFromGroups(string domainId, string entityId,  groupList, string permissionTypeId)')
     print('  bool userHasAccess(string domainId, string userId, string entityId, string permissionTypeId)')
+    print('  string getAPIVersion()')
     print('')
     sys.exit(0)
 
@@ -502,6 +503,12 @@ elif cmd == 'userHasAccess':
         sys.exit(1)
     pp.pprint(client.userHasAccess(args[0], args[1], args[2], args[3],))
 
+elif cmd == 'getAPIVersion':
+    if len(args) != 0:
+        print('getAPIVersion requires 0 args')
+        sys.exit(1)
+    pp.pprint(client.getAPIVersion())
+
 else:
     print('Unrecognized method %s' % cmd)
     sys.exit(1)
diff --git a/airavata/api/sharing/SharingRegistryService.py b/airavata/api/sharing/SharingRegistryService.py
index 1c4f702..c500066 100644
--- a/airavata/api/sharing/SharingRegistryService.py
+++ b/airavata/api/sharing/SharingRegistryService.py
@@ -9,13 +9,14 @@
 from thrift.Thrift import TType, TMessageType, TFrozenDict, TException, TApplicationException
 from thrift.protocol.TProtocol import TProtocolException
 import sys
+import airavata.base.api.BaseAPI
 import logging
 from .ttypes import *
 from thrift.Thrift import TProcessor
 from thrift.transport import TTransport
 
 
-class Iface(object):
+class Iface(airavata.base.api.BaseAPI.Iface):
     def createDomain(self, domain):
         """
         <p>API method to create a new domain</p>
@@ -609,12 +610,9 @@ class Iface(object):
         pass
 
 
-class Client(Iface):
+class Client(airavata.base.api.BaseAPI.Client, Iface):
     def __init__(self, iprot, oprot=None):
-        self._iprot = self._oprot = iprot
-        if oprot is not None:
-            self._oprot = oprot
-        self._seqid = 0
+        airavata.base.api.BaseAPI.Client.__init__(self, iprot, oprot)
 
     def createDomain(self, domain):
         """
@@ -2775,10 +2773,9 @@ class Client(Iface):
         raise TApplicationException(TApplicationException.MISSING_RESULT, "userHasAccess failed: unknown result")
 
 
-class Processor(Iface, TProcessor):
+class Processor(airavata.base.api.BaseAPI.Processor, Iface, TProcessor):
     def __init__(self, handler):
-        self._handler = handler
-        self._processMap = {}
+        airavata.base.api.BaseAPI.Processor.__init__(self, handler)
         self._processMap["createDomain"] = Processor.process_createDomain
         self._processMap["updateDomain"] = Processor.process_updateDomain
         self._processMap["isDomainExists"] = Processor.process_isDomainExists
diff --git a/airavata/api/sharing/constants.py b/airavata/api/sharing/constants.py
index eb0d35a..6ac9983 100644
--- a/airavata/api/sharing/constants.py
+++ b/airavata/api/sharing/constants.py
@@ -10,3 +10,4 @@ from thrift.Thrift import TType, TMessageType, TFrozenDict, TException, TApplica
 from thrift.protocol.TProtocol import TProtocolException
 import sys
 from .ttypes import *
+SHARING_CPI_VERSION = "0.18.0"
diff --git a/airavata/api/sharing/ttypes.py b/airavata/api/sharing/ttypes.py
index 15b3eb3..43dc206 100644
--- a/airavata/api/sharing/ttypes.py
+++ b/airavata/api/sharing/ttypes.py
@@ -10,5 +10,6 @@ from thrift.Thrift import TType, TMessageType, TFrozenDict, TException, TApplica
 from thrift.protocol.TProtocol import TProtocolException
 import sys
 import airavata.model.sharing.ttypes
+import airavata.base.api.ttypes
 
 from thrift.transport import TTransport
diff --git a/airavata/api/ttypes.py b/airavata/api/ttypes.py
index 78d0ec9..5c809cc 100644
--- a/airavata/api/ttypes.py
+++ b/airavata/api/ttypes.py
@@ -33,5 +33,6 @@ import airavata.model.appcatalog.gatewaygroups.ttypes
 import airavata.model.data.replica.ttypes
 import airavata.model.group.ttypes
 import airavata.model.user.ttypes
+import airavata.base.api.ttypes
 
 from thrift.transport import TTransport
diff --git a/airavata/base/__init__.py b/airavata/base/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/airavata/service/profile/tenant/cpi/TenantProfileService-remote b/airavata/base/api/BaseAPI-remote
similarity index 56%
copy from airavata/service/profile/tenant/cpi/TenantProfileService-remote
copy to airavata/base/api/BaseAPI-remote
index 307dc4c..cdc3ed1 100755
--- a/airavata/service/profile/tenant/cpi/TenantProfileService-remote
+++ b/airavata/base/api/BaseAPI-remote
@@ -16,8 +16,8 @@ else:
 from thrift.transport import TTransport, TSocket, TSSLSocket, THttpClient
 from thrift.protocol.TBinaryProtocol import TBinaryProtocol
 
-from airavata.service.profile.tenant.cpi import TenantProfileService
-from airavata.service.profile.tenant.cpi.ttypes import *
+from airavata.base.api import BaseAPI
+from airavata.base.api.ttypes import *
 
 if len(sys.argv) <= 1 or sys.argv[1] == '--help':
     print('')
@@ -25,13 +25,6 @@ if len(sys.argv) <= 1 or sys.argv[1] == '--help':
     print('')
     print('Functions:')
     print('  string getAPIVersion()')
-    print('  string addGateway(AuthzToken authzToken, Gateway gateway)')
-    print('  bool updateGateway(AuthzToken authzToken, Gateway updatedGateway)')
-    print('  Gateway getGateway(AuthzToken authzToken, string airavataInternalGatewayId)')
-    print('  bool deleteGateway(AuthzToken authzToken, string airavataInternalGatewayId, string gatewayId)')
-    print('   getAllGateways(AuthzToken authzToken)')
-    print('  bool isGatewayExist(AuthzToken authzToken, string gatewayId)')
-    print('   getAllGatewaysForUser(AuthzToken authzToken, string requesterUsername)')
     print('')
     sys.exit(0)
 
@@ -108,7 +101,7 @@ else:
     else:
         transport = TTransport.TBufferedTransport(socket)
 protocol = TBinaryProtocol(transport)
-client = TenantProfileService.Client(protocol)
+client = BaseAPI.Client(protocol)
 transport.open()
 
 if cmd == 'getAPIVersion':
@@ -117,48 +110,6 @@ if cmd == 'getAPIVersion':
         sys.exit(1)
     pp.pprint(client.getAPIVersion())
 
-elif cmd == 'addGateway':
-    if len(args) != 2:
-        print('addGateway requires 2 args')
-        sys.exit(1)
-    pp.pprint(client.addGateway(eval(args[0]), eval(args[1]),))
-
-elif cmd == 'updateGateway':
-    if len(args) != 2:
-        print('updateGateway requires 2 args')
-        sys.exit(1)
-    pp.pprint(client.updateGateway(eval(args[0]), eval(args[1]),))
-
-elif cmd == 'getGateway':
-    if len(args) != 2:
-        print('getGateway requires 2 args')
-        sys.exit(1)
-    pp.pprint(client.getGateway(eval(args[0]), args[1],))
-
-elif cmd == 'deleteGateway':
-    if len(args) != 3:
-        print('deleteGateway requires 3 args')
-        sys.exit(1)
-    pp.pprint(client.deleteGateway(eval(args[0]), args[1], args[2],))
-
-elif cmd == 'getAllGateways':
-    if len(args) != 1:
-        print('getAllGateways requires 1 args')
-        sys.exit(1)
-    pp.pprint(client.getAllGateways(eval(args[0]),))
-
-elif cmd == 'isGatewayExist':
-    if len(args) != 2:
-        print('isGatewayExist requires 2 args')
-        sys.exit(1)
-    pp.pprint(client.isGatewayExist(eval(args[0]), args[1],))
-
-elif cmd == 'getAllGatewaysForUser':
-    if len(args) != 2:
-        print('getAllGatewaysForUser requires 2 args')
-        sys.exit(1)
-    pp.pprint(client.getAllGatewaysForUser(eval(args[0]), args[1],))
-
 else:
     print('Unrecognized method %s' % cmd)
     sys.exit(1)
diff --git a/airavata/base/api/BaseAPI.py b/airavata/base/api/BaseAPI.py
new file mode 100644
index 0000000..6e5e3ad
--- /dev/null
+++ b/airavata/base/api/BaseAPI.py
@@ -0,0 +1,198 @@
+#
+# Autogenerated by Thrift Compiler (0.10.0)
+#
+# DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING
+#
+#  options string: py
+#
+
+from thrift.Thrift import TType, TMessageType, TFrozenDict, TException, TApplicationException
+from thrift.protocol.TProtocol import TProtocolException
+import sys
+import logging
+from .ttypes import *
+from thrift.Thrift import TProcessor
+from thrift.transport import TTransport
+
+
+class Iface(object):
+    def getAPIVersion(self):
+        pass
+
+
+class Client(Iface):
+    def __init__(self, iprot, oprot=None):
+        self._iprot = self._oprot = iprot
+        if oprot is not None:
+            self._oprot = oprot
+        self._seqid = 0
+
+    def getAPIVersion(self):
+        self.send_getAPIVersion()
+        return self.recv_getAPIVersion()
+
+    def send_getAPIVersion(self):
+        self._oprot.writeMessageBegin('getAPIVersion', TMessageType.CALL, self._seqid)
+        args = getAPIVersion_args()
+        args.write(self._oprot)
+        self._oprot.writeMessageEnd()
+        self._oprot.trans.flush()
+
+    def recv_getAPIVersion(self):
+        iprot = self._iprot
+        (fname, mtype, rseqid) = iprot.readMessageBegin()
+        if mtype == TMessageType.EXCEPTION:
+            x = TApplicationException()
+            x.read(iprot)
+            iprot.readMessageEnd()
+            raise x
+        result = getAPIVersion_result()
+        result.read(iprot)
+        iprot.readMessageEnd()
+        if result.success is not None:
+            return result.success
+        raise TApplicationException(TApplicationException.MISSING_RESULT, "getAPIVersion failed: unknown result")
+
+
+class Processor(Iface, TProcessor):
+    def __init__(self, handler):
+        self._handler = handler
+        self._processMap = {}
+        self._processMap["getAPIVersion"] = Processor.process_getAPIVersion
+
+    def process(self, iprot, oprot):
+        (name, type, seqid) = iprot.readMessageBegin()
+        if name not in self._processMap:
+            iprot.skip(TType.STRUCT)
+            iprot.readMessageEnd()
+            x = TApplicationException(TApplicationException.UNKNOWN_METHOD, 'Unknown function %s' % (name))
+            oprot.writeMessageBegin(name, TMessageType.EXCEPTION, seqid)
+            x.write(oprot)
+            oprot.writeMessageEnd()
+            oprot.trans.flush()
+            return
+        else:
+            self._processMap[name](self, seqid, iprot, oprot)
+        return True
+
+    def process_getAPIVersion(self, seqid, iprot, oprot):
+        args = getAPIVersion_args()
+        args.read(iprot)
+        iprot.readMessageEnd()
+        result = getAPIVersion_result()
+        try:
+            result.success = self._handler.getAPIVersion()
+            msg_type = TMessageType.REPLY
+        except (TTransport.TTransportException, KeyboardInterrupt, SystemExit):
+            raise
+        except Exception as ex:
+            msg_type = TMessageType.EXCEPTION
+            logging.exception(ex)
+            result = TApplicationException(TApplicationException.INTERNAL_ERROR, 'Internal error')
+        oprot.writeMessageBegin("getAPIVersion", msg_type, seqid)
+        result.write(oprot)
+        oprot.writeMessageEnd()
+        oprot.trans.flush()
+
+# HELPER FUNCTIONS AND STRUCTURES
+
+
+class getAPIVersion_args(object):
+
+    thrift_spec = (
+    )
+
+    def read(self, iprot):
+        if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
+            iprot._fast_decode(self, iprot, (self.__class__, self.thrift_spec))
+            return
+        iprot.readStructBegin()
+        while True:
+            (fname, ftype, fid) = iprot.readFieldBegin()
+            if ftype == TType.STOP:
+                break
+            else:
+                iprot.skip(ftype)
+            iprot.readFieldEnd()
+        iprot.readStructEnd()
+
+    def write(self, oprot):
+        if oprot._fast_encode is not None and self.thrift_spec is not None:
+            oprot.trans.write(oprot._fast_encode(self, (self.__class__, self.thrift_spec)))
+            return
+        oprot.writeStructBegin('getAPIVersion_args')
+        oprot.writeFieldStop()
+        oprot.writeStructEnd()
+
+    def validate(self):
+        return
+
+    def __repr__(self):
+        L = ['%s=%r' % (key, value)
+             for key, value in self.__dict__.items()]
+        return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+    def __eq__(self, other):
+        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        return not (self == other)
+
+
+class getAPIVersion_result(object):
+    """
+    Attributes:
+     - success
+    """
+
+    thrift_spec = (
+        (0, TType.STRING, 'success', 'UTF8', None, ),  # 0
+    )
+
+    def __init__(self, success=None,):
+        self.success = success
+
+    def read(self, iprot):
+        if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
+            iprot._fast_decode(self, iprot, (self.__class__, self.thrift_spec))
+            return
+        iprot.readStructBegin()
+        while True:
+            (fname, ftype, fid) = iprot.readFieldBegin()
+            if ftype == TType.STOP:
+                break
+            if fid == 0:
+                if ftype == TType.STRING:
+                    self.success = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
+                else:
+                    iprot.skip(ftype)
+            else:
+                iprot.skip(ftype)
+            iprot.readFieldEnd()
+        iprot.readStructEnd()
+
+    def write(self, oprot):
+        if oprot._fast_encode is not None and self.thrift_spec is not None:
+            oprot.trans.write(oprot._fast_encode(self, (self.__class__, self.thrift_spec)))
+            return
+        oprot.writeStructBegin('getAPIVersion_result')
+        if self.success is not None:
+            oprot.writeFieldBegin('success', TType.STRING, 0)
+            oprot.writeString(self.success.encode('utf-8') if sys.version_info[0] == 2 else self.success)
+            oprot.writeFieldEnd()
+        oprot.writeFieldStop()
+        oprot.writeStructEnd()
+
+    def validate(self):
+        return
+
+    def __repr__(self):
+        L = ['%s=%r' % (key, value)
+             for key, value in self.__dict__.items()]
+        return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+    def __eq__(self, other):
+        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        return not (self == other)
diff --git a/airavata/base/api/__init__.py b/airavata/base/api/__init__.py
new file mode 100644
index 0000000..d9a4e6b
--- /dev/null
+++ b/airavata/base/api/__init__.py
@@ -0,0 +1 @@
+__all__ = ['ttypes', 'constants', 'BaseAPI']
diff --git a/airavata/api/sharing/constants.py b/airavata/base/api/constants.py
similarity index 100%
copy from airavata/api/sharing/constants.py
copy to airavata/base/api/constants.py
diff --git a/airavata/api/sharing/ttypes.py b/airavata/base/api/ttypes.py
similarity index 90%
copy from airavata/api/sharing/ttypes.py
copy to airavata/base/api/ttypes.py
index 15b3eb3..d7e97e9 100644
--- a/airavata/api/sharing/ttypes.py
+++ b/airavata/base/api/ttypes.py
@@ -9,6 +9,5 @@
 from thrift.Thrift import TType, TMessageType, TFrozenDict, TException, TApplicationException
 from thrift.protocol.TProtocol import TProtocolException
 import sys
-import airavata.model.sharing.ttypes
 
 from thrift.transport import TTransport
diff --git a/airavata/model/application/io/ttypes.py b/airavata/model/application/io/ttypes.py
index 10e1d47..8c9017a 100644
--- a/airavata/model/application/io/ttypes.py
+++ b/airavata/model/application/io/ttypes.py
@@ -318,6 +318,7 @@ class OutputDataObjectType(object):
      - searchQuery
      - outputStreaming
      - storageResourceId
+     - metaData
     """
 
     thrift_spec = (
@@ -333,9 +334,10 @@ class OutputDataObjectType(object):
         (9, TType.STRING, 'searchQuery', 'UTF8', None, ),  # 9
         (10, TType.BOOL, 'outputStreaming', None, None, ),  # 10
         (11, TType.STRING, 'storageResourceId', 'UTF8', None, ),  # 11
+        (12, TType.STRING, 'metaData', 'UTF8', None, ),  # 12
     )
 
-    def __init__(self, name=None, value=None, type=None, applicationArgument=None, isRequired=None, requiredToAddedToCommandLine=None, dataMovement=None, location=None, searchQuery=None, outputStreaming=None, storageResourceId=None,):
+    def __init__(self, name=None, value=None, type=None, applicationArgument=None, isRequired=None, requiredToAddedToCommandLine=None, dataMovement=None, location=None, searchQuery=None, outputStreaming=None, storageResourceId=None, metaData=None,):
         self.name = name
         self.value = value
         self.type = type
@@ -347,6 +349,7 @@ class OutputDataObjectType(object):
         self.searchQuery = searchQuery
         self.outputStreaming = outputStreaming
         self.storageResourceId = storageResourceId
+        self.metaData = metaData
 
     def read(self, iprot):
         if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
@@ -412,6 +415,11 @@ class OutputDataObjectType(object):
                     self.storageResourceId = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
                 else:
                     iprot.skip(ftype)
+            elif fid == 12:
+                if ftype == TType.STRING:
+                    self.metaData = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
+                else:
+                    iprot.skip(ftype)
             else:
                 iprot.skip(ftype)
             iprot.readFieldEnd()
@@ -466,6 +474,10 @@ class OutputDataObjectType(object):
             oprot.writeFieldBegin('storageResourceId', TType.STRING, 11)
             oprot.writeString(self.storageResourceId.encode('utf-8') if sys.version_info[0] == 2 else self.storageResourceId)
             oprot.writeFieldEnd()
+        if self.metaData is not None:
+            oprot.writeFieldBegin('metaData', TType.STRING, 12)
+            oprot.writeString(self.metaData.encode('utf-8') if sys.version_info[0] == 2 else self.metaData)
+            oprot.writeFieldEnd()
         oprot.writeFieldStop()
         oprot.writeStructEnd()
 
diff --git a/airavata/model/experiment/ttypes.py b/airavata/model/experiment/ttypes.py
index 2a2aad4..d5cb425 100644
--- a/airavata/model/experiment/ttypes.py
+++ b/airavata/model/experiment/ttypes.py
@@ -43,6 +43,7 @@ class ExperimentSearchFields(object):
     STATUS = 5
     PROJECT_ID = 6
     USER_NAME = 7
+    JOB_ID = 8
 
     _VALUES_TO_NAMES = {
         0: "EXPERIMENT_NAME",
@@ -53,6 +54,7 @@ class ExperimentSearchFields(object):
         5: "STATUS",
         6: "PROJECT_ID",
         7: "USER_NAME",
+        8: "JOB_ID",
     }
 
     _NAMES_TO_VALUES = {
@@ -64,6 +66,7 @@ class ExperimentSearchFields(object):
         "STATUS": 5,
         "PROJECT_ID": 6,
         "USER_NAME": 7,
+        "JOB_ID": 8,
     }
 
 
diff --git a/airavata/model/process/ttypes.py b/airavata/model/process/ttypes.py
index 38c5ad3..e9be0bf 100644
--- a/airavata/model/process/ttypes.py
+++ b/airavata/model/process/ttypes.py
@@ -18,6 +18,106 @@ import airavata.model.scheduling.ttypes
 from thrift.transport import TTransport
 
 
+class ProcessWorkflow(object):
+    """
+    Attributes:
+     - processId
+     - workflowId
+     - creationTime
+     - type
+    """
+
+    thrift_spec = (
+        None,  # 0
+        (1, TType.STRING, 'processId', 'UTF8', None, ),  # 1
+        (2, TType.STRING, 'workflowId', 'UTF8', None, ),  # 2
+        (3, TType.I64, 'creationTime', None, None, ),  # 3
+        (4, TType.STRING, 'type', 'UTF8', None, ),  # 4
+    )
+
+    def __init__(self, processId=None, workflowId=None, creationTime=None, type=None,):
+        self.processId = processId
+        self.workflowId = workflowId
+        self.creationTime = creationTime
+        self.type = type
+
+    def read(self, iprot):
+        if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
+            iprot._fast_decode(self, iprot, (self.__class__, self.thrift_spec))
+            return
+        iprot.readStructBegin()
+        while True:
+            (fname, ftype, fid) = iprot.readFieldBegin()
+            if ftype == TType.STOP:
+                break
+            if fid == 1:
+                if ftype == TType.STRING:
+                    self.processId = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
+                else:
+                    iprot.skip(ftype)
+            elif fid == 2:
+                if ftype == TType.STRING:
+                    self.workflowId = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
+                else:
+                    iprot.skip(ftype)
+            elif fid == 3:
+                if ftype == TType.I64:
+                    self.creationTime = iprot.readI64()
+                else:
+                    iprot.skip(ftype)
+            elif fid == 4:
+                if ftype == TType.STRING:
+                    self.type = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
+                else:
+                    iprot.skip(ftype)
+            else:
+                iprot.skip(ftype)
+            iprot.readFieldEnd()
+        iprot.readStructEnd()
+
+    def write(self, oprot):
+        if oprot._fast_encode is not None and self.thrift_spec is not None:
+            oprot.trans.write(oprot._fast_encode(self, (self.__class__, self.thrift_spec)))
+            return
+        oprot.writeStructBegin('ProcessWorkflow')
+        if self.processId is not None:
+            oprot.writeFieldBegin('processId', TType.STRING, 1)
+            oprot.writeString(self.processId.encode('utf-8') if sys.version_info[0] == 2 else self.processId)
+            oprot.writeFieldEnd()
+        if self.workflowId is not None:
+            oprot.writeFieldBegin('workflowId', TType.STRING, 2)
+            oprot.writeString(self.workflowId.encode('utf-8') if sys.version_info[0] == 2 else self.workflowId)
+            oprot.writeFieldEnd()
+        if self.creationTime is not None:
+            oprot.writeFieldBegin('creationTime', TType.I64, 3)
+            oprot.writeI64(self.creationTime)
+            oprot.writeFieldEnd()
+        if self.type is not None:
+            oprot.writeFieldBegin('type', TType.STRING, 4)
+            oprot.writeString(self.type.encode('utf-8') if sys.version_info[0] == 2 else self.type)
+            oprot.writeFieldEnd()
+        oprot.writeFieldStop()
+        oprot.writeStructEnd()
+
+    def validate(self):
+        if self.processId is None:
+            raise TProtocolException(message='Required field processId is unset!')
+        if self.workflowId is None:
+            raise TProtocolException(message='Required field workflowId is unset!')
+        return
+
+    def __repr__(self):
+        L = ['%s=%r' % (key, value)
+             for key, value in self.__dict__.items()]
+        return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
+
+    def __eq__(self, other):
+        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
+
+    def __ne__(self, other):
+        return not (self == other)
+
+
 class ProcessModel(object):
     """
     ProcessModel: A structure holding the process details. The infromation is derived based on user provided
@@ -54,6 +154,7 @@ class ProcessModel(object):
      - userName
      - useUserCRPref
      - groupResourceProfileId
+     - processWorkflows
     """
 
     thrift_spec = (
@@ -83,9 +184,10 @@ class ProcessModel(object):
         (23, TType.STRING, 'userName', 'UTF8', None, ),  # 23
         (24, TType.BOOL, 'useUserCRPref', None, None, ),  # 24
         (25, TType.STRING, 'groupResourceProfileId', 'UTF8', None, ),  # 25
+        (26, TType.LIST, 'processWorkflows', (TType.STRUCT, (ProcessWorkflow, ProcessWorkflow.thrift_spec), False), None, ),  # 26
     )
 
-    def __init__(self, processId=thrift_spec[1][4], experimentId=None, creationTime=None, lastUpdateTime=None, processStatuses=None, processDetail=None, applicationInterfaceId=None, applicationDeploymentId=None, computeResourceId=None, processInputs=None, processOutputs=None, processResourceSchedule=None, tasks=None, taskDag=None, processErrors=None, gatewayExecutionId=None, enableEmailNotification=None, emailAddresses=None, storageResourceId=None, userDn=None, generateCert=thrift_spec[2 [...]
+    def __init__(self, processId=thrift_spec[1][4], experimentId=None, creationTime=None, lastUpdateTime=None, processStatuses=None, processDetail=None, applicationInterfaceId=None, applicationDeploymentId=None, computeResourceId=None, processInputs=None, processOutputs=None, processResourceSchedule=None, tasks=None, taskDag=None, processErrors=None, gatewayExecutionId=None, enableEmailNotification=None, emailAddresses=None, storageResourceId=None, userDn=None, generateCert=thrift_spec[2 [...]
         self.processId = processId
         self.experimentId = experimentId
         self.creationTime = creationTime
@@ -111,6 +213,7 @@ class ProcessModel(object):
         self.userName = userName
         self.useUserCRPref = useUserCRPref
         self.groupResourceProfileId = groupResourceProfileId
+        self.processWorkflows = processWorkflows
 
     def read(self, iprot):
         if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
@@ -282,6 +385,17 @@ class ProcessModel(object):
                     self.groupResourceProfileId = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
                 else:
                     iprot.skip(ftype)
+            elif fid == 26:
+                if ftype == TType.LIST:
+                    self.processWorkflows = []
+                    (_etype39, _size36) = iprot.readListBegin()
+                    for _i40 in range(_size36):
+                        _elem41 = ProcessWorkflow()
+                        _elem41.read(iprot)
+                        self.processWorkflows.append(_elem41)
+                    iprot.readListEnd()
+                else:
+                    iprot.skip(ftype)
             else:
                 iprot.skip(ftype)
             iprot.readFieldEnd()
@@ -311,8 +425,8 @@ class ProcessModel(object):
         if self.processStatuses is not None:
             oprot.writeFieldBegin('processStatuses', TType.LIST, 5)
             oprot.writeListBegin(TType.STRUCT, len(self.processStatuses))
-            for iter36 in self.processStatuses:
-                iter36.write(oprot)
+            for iter42 in self.processStatuses:
+                iter42.write(oprot)
             oprot.writeListEnd()
             oprot.writeFieldEnd()
         if self.processDetail is not None:
@@ -334,15 +448,15 @@ class ProcessModel(object):
         if self.processInputs is not None:
             oprot.writeFieldBegin('processInputs', TType.LIST, 10)
             oprot.writeListBegin(TType.STRUCT, len(self.processInputs))
-            for iter37 in self.processInputs:
-                iter37.write(oprot)
+            for iter43 in self.processInputs:
+                iter43.write(oprot)
             oprot.writeListEnd()
             oprot.writeFieldEnd()
         if self.processOutputs is not None:
             oprot.writeFieldBegin('processOutputs', TType.LIST, 11)
             oprot.writeListBegin(TType.STRUCT, len(self.processOutputs))
-            for iter38 in self.processOutputs:
-                iter38.write(oprot)
+            for iter44 in self.processOutputs:
+                iter44.write(oprot)
             oprot.writeListEnd()
             oprot.writeFieldEnd()
         if self.processResourceSchedule is not None:
@@ -352,8 +466,8 @@ class ProcessModel(object):
         if self.tasks is not None:
             oprot.writeFieldBegin('tasks', TType.LIST, 13)
             oprot.writeListBegin(TType.STRUCT, len(self.tasks))
-            for iter39 in self.tasks:
-                iter39.write(oprot)
+            for iter45 in self.tasks:
+                iter45.write(oprot)
             oprot.writeListEnd()
             oprot.writeFieldEnd()
         if self.taskDag is not None:
@@ -363,8 +477,8 @@ class ProcessModel(object):
         if self.processErrors is not None:
             oprot.writeFieldBegin('processErrors', TType.LIST, 15)
             oprot.writeListBegin(TType.STRUCT, len(self.processErrors))
-            for iter40 in self.processErrors:
-                iter40.write(oprot)
+            for iter46 in self.processErrors:
+                iter46.write(oprot)
             oprot.writeListEnd()
             oprot.writeFieldEnd()
         if self.gatewayExecutionId is not None:
@@ -378,8 +492,8 @@ class ProcessModel(object):
         if self.emailAddresses is not None:
             oprot.writeFieldBegin('emailAddresses', TType.LIST, 18)
             oprot.writeListBegin(TType.STRING, len(self.emailAddresses))
-            for iter41 in self.emailAddresses:
-                oprot.writeString(iter41.encode('utf-8') if sys.version_info[0] == 2 else iter41)
+            for iter47 in self.emailAddresses:
+                oprot.writeString(iter47.encode('utf-8') if sys.version_info[0] == 2 else iter47)
             oprot.writeListEnd()
             oprot.writeFieldEnd()
         if self.storageResourceId is not None:
@@ -410,6 +524,13 @@ class ProcessModel(object):
             oprot.writeFieldBegin('groupResourceProfileId', TType.STRING, 25)
             oprot.writeString(self.groupResourceProfileId.encode('utf-8') if sys.version_info[0] == 2 else self.groupResourceProfileId)
             oprot.writeFieldEnd()
+        if self.processWorkflows is not None:
+            oprot.writeFieldBegin('processWorkflows', TType.LIST, 26)
+            oprot.writeListBegin(TType.STRUCT, len(self.processWorkflows))
+            for iter48 in self.processWorkflows:
+                iter48.write(oprot)
+            oprot.writeListEnd()
+            oprot.writeFieldEnd()
         oprot.writeFieldStop()
         oprot.writeStructEnd()
 
diff --git a/airavata/model/task/ttypes.py b/airavata/model/task/ttypes.py
index 353df69..c066efe 100644
--- a/airavata/model/task/ttypes.py
+++ b/airavata/model/task/ttypes.py
@@ -96,6 +96,8 @@ class TaskModel(object):
      - subTaskModel
      - taskErrors
      - jobs
+     - maxRetry
+     - currentRetry
     """
 
     thrift_spec = (
@@ -110,9 +112,11 @@ class TaskModel(object):
         (8, TType.STRING, 'subTaskModel', 'BINARY', None, ),  # 8
         (9, TType.LIST, 'taskErrors', (TType.STRUCT, (airavata.model.commons.ttypes.ErrorModel, airavata.model.commons.ttypes.ErrorModel.thrift_spec), False), None, ),  # 9
         (10, TType.LIST, 'jobs', (TType.STRUCT, (airavata.model.job.ttypes.JobModel, airavata.model.job.ttypes.JobModel.thrift_spec), False), None, ),  # 10
+        (11, TType.I32, 'maxRetry', None, None, ),  # 11
+        (12, TType.I32, 'currentRetry', None, None, ),  # 12
     )
 
-    def __init__(self, taskId=thrift_spec[1][4], taskType=None, parentProcessId=None, creationTime=None, lastUpdateTime=None, taskStatuses=None, taskDetail=None, subTaskModel=None, taskErrors=None, jobs=None,):
+    def __init__(self, taskId=thrift_spec[1][4], taskType=None, parentProcessId=None, creationTime=None, lastUpdateTime=None, taskStatuses=None, taskDetail=None, subTaskModel=None, taskErrors=None, jobs=None, maxRetry=None, currentRetry=None,):
         self.taskId = taskId
         self.taskType = taskType
         self.parentProcessId = parentProcessId
@@ -123,6 +127,8 @@ class TaskModel(object):
         self.subTaskModel = subTaskModel
         self.taskErrors = taskErrors
         self.jobs = jobs
+        self.maxRetry = maxRetry
+        self.currentRetry = currentRetry
 
     def read(self, iprot):
         if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
@@ -201,6 +207,16 @@ class TaskModel(object):
                     iprot.readListEnd()
                 else:
                     iprot.skip(ftype)
+            elif fid == 11:
+                if ftype == TType.I32:
+                    self.maxRetry = iprot.readI32()
+                else:
+                    iprot.skip(ftype)
+            elif fid == 12:
+                if ftype == TType.I32:
+                    self.currentRetry = iprot.readI32()
+                else:
+                    iprot.skip(ftype)
             else:
                 iprot.skip(ftype)
             iprot.readFieldEnd()
@@ -260,6 +276,14 @@ class TaskModel(object):
                 iter20.write(oprot)
             oprot.writeListEnd()
             oprot.writeFieldEnd()
+        if self.maxRetry is not None:
+            oprot.writeFieldBegin('maxRetry', TType.I32, 11)
+            oprot.writeI32(self.maxRetry)
+            oprot.writeFieldEnd()
+        if self.currentRetry is not None:
+            oprot.writeFieldBegin('currentRetry', TType.I32, 12)
+            oprot.writeI32(self.currentRetry)
+            oprot.writeFieldEnd()
         oprot.writeFieldStop()
         oprot.writeStructEnd()
 
diff --git a/airavata/service/profile/groupmanager/cpi/GroupManagerService-remote b/airavata/service/profile/groupmanager/cpi/GroupManagerService-remote
index ab732bf..fcc0a27 100755
--- a/airavata/service/profile/groupmanager/cpi/GroupManagerService-remote
+++ b/airavata/service/profile/groupmanager/cpi/GroupManagerService-remote
@@ -38,6 +38,7 @@ if len(sys.argv) <= 1 or sys.argv[1] == '--help':
     print('  bool removeGroupAdmins(AuthzToken authzToken, string groupId,  adminIds)')
     print('  bool hasAdminAccess(AuthzToken authzToken, string groupId, string adminId)')
     print('  bool hasOwnerAccess(AuthzToken authzToken, string groupId, string ownerId)')
+    print('  string getAPIVersion()')
     print('')
     sys.exit(0)
 
@@ -201,6 +202,12 @@ elif cmd == 'hasOwnerAccess':
         sys.exit(1)
     pp.pprint(client.hasOwnerAccess(eval(args[0]), args[1], args[2],))
 
+elif cmd == 'getAPIVersion':
+    if len(args) != 0:
+        print('getAPIVersion requires 0 args')
+        sys.exit(1)
+    pp.pprint(client.getAPIVersion())
+
 else:
     print('Unrecognized method %s' % cmd)
     sys.exit(1)
diff --git a/airavata/service/profile/groupmanager/cpi/GroupManagerService.py b/airavata/service/profile/groupmanager/cpi/GroupManagerService.py
index e0d3fd7..0bc86da 100644
--- a/airavata/service/profile/groupmanager/cpi/GroupManagerService.py
+++ b/airavata/service/profile/groupmanager/cpi/GroupManagerService.py
@@ -9,13 +9,14 @@
 from thrift.Thrift import TType, TMessageType, TFrozenDict, TException, TApplicationException
 from thrift.protocol.TProtocol import TProtocolException
 import sys
+import airavata.base.api.BaseAPI
 import logging
 from .ttypes import *
 from thrift.Thrift import TProcessor
 from thrift.transport import TTransport
 
 
-class Iface(object):
+class Iface(airavata.base.api.BaseAPI.Iface):
     def getAPIVersion(self):
         pass
 
@@ -131,12 +132,9 @@ class Iface(object):
         pass
 
 
-class Client(Iface):
+class Client(airavata.base.api.BaseAPI.Client, Iface):
     def __init__(self, iprot, oprot=None):
-        self._iprot = self._oprot = iprot
-        if oprot is not None:
-            self._oprot = oprot
-        self._seqid = 0
+        airavata.base.api.BaseAPI.Client.__init__(self, iprot, oprot)
 
     def getAPIVersion(self):
         self.send_getAPIVersion()
@@ -662,10 +660,9 @@ class Client(Iface):
         raise TApplicationException(TApplicationException.MISSING_RESULT, "hasOwnerAccess failed: unknown result")
 
 
-class Processor(Iface, TProcessor):
+class Processor(airavata.base.api.BaseAPI.Processor, Iface, TProcessor):
     def __init__(self, handler):
-        self._handler = handler
-        self._processMap = {}
+        airavata.base.api.BaseAPI.Processor.__init__(self, handler)
         self._processMap["getAPIVersion"] = Processor.process_getAPIVersion
         self._processMap["createGroup"] = Processor.process_createGroup
         self._processMap["updateGroup"] = Processor.process_updateGroup
diff --git a/airavata/service/profile/groupmanager/cpi/constants.py b/airavata/service/profile/groupmanager/cpi/constants.py
index b7cf40e..bdceed5 100644
--- a/airavata/service/profile/groupmanager/cpi/constants.py
+++ b/airavata/service/profile/groupmanager/cpi/constants.py
@@ -10,5 +10,5 @@ from thrift.Thrift import TType, TMessageType, TFrozenDict, TException, TApplica
 from thrift.protocol.TProtocol import TProtocolException
 import sys
 from .ttypes import *
-GROUP_MANAGER_CPI_VERSION = "0.17"
+GROUP_MANAGER_CPI_VERSION = "0.18.0"
 GROUP_MANAGER_CPI_NAME = "GroupManagerService"
diff --git a/airavata/service/profile/groupmanager/cpi/ttypes.py b/airavata/service/profile/groupmanager/cpi/ttypes.py
index 5762ed8..0904d71 100644
--- a/airavata/service/profile/groupmanager/cpi/ttypes.py
+++ b/airavata/service/profile/groupmanager/cpi/ttypes.py
@@ -13,5 +13,6 @@ import airavata.api.error.ttypes
 import airavata.model.security.ttypes
 import airavata.model.group.ttypes
 import airavata.service.profile.groupmanager.cpi.error.ttypes
+import airavata.base.api.ttypes
 
 from thrift.transport import TTransport
diff --git a/airavata/service/profile/iam/admin/services/cpi/IamAdminServices-remote b/airavata/service/profile/iam/admin/services/cpi/IamAdminServices-remote
index b08c976..f4a3f2b 100755
--- a/airavata/service/profile/iam/admin/services/cpi/IamAdminServices-remote
+++ b/airavata/service/profile/iam/admin/services/cpi/IamAdminServices-remote
@@ -24,7 +24,6 @@ if len(sys.argv) <= 1 or sys.argv[1] == '--help':
     print('Usage: ' + sys.argv[0] + ' [-h host[:port]] [-u url] [-f[ramed]] [-s[sl]] [-novalidate] [-ca_certs certs] [-keyfile keyfile] [-certfile certfile] function [arg1 [arg2...]]')
     print('')
     print('Functions:')
-    print('  string getAPIVersion()')
     print('  Gateway setUpGateway(AuthzToken authzToken, Gateway gateway)')
     print('  bool isUsernameAvailable(AuthzToken authzToken, string username)')
     print('  bool registerUser(AuthzToken authzToken, string username, string emailAddress, string firstName, string lastName, string newPassword)')
@@ -39,6 +38,7 @@ if len(sys.argv) <= 1 or sys.argv[1] == '--help':
     print('  bool addRoleToUser(AuthzToken authzToken, string username, string roleName)')
     print('  bool removeRoleFromUser(AuthzToken authzToken, string username, string roleName)')
     print('   getUsersWithRole(AuthzToken authzToken, string roleName)')
+    print('  string getAPIVersion()')
     print('')
     sys.exit(0)
 
@@ -118,13 +118,7 @@ protocol = TBinaryProtocol(transport)
 client = IamAdminServices.Client(protocol)
 transport.open()
 
-if cmd == 'getAPIVersion':
-    if len(args) != 0:
-        print('getAPIVersion requires 0 args')
-        sys.exit(1)
-    pp.pprint(client.getAPIVersion())
-
-elif cmd == 'setUpGateway':
+if cmd == 'setUpGateway':
     if len(args) != 2:
         print('setUpGateway requires 2 args')
         sys.exit(1)
@@ -208,6 +202,12 @@ elif cmd == 'getUsersWithRole':
         sys.exit(1)
     pp.pprint(client.getUsersWithRole(eval(args[0]), args[1],))
 
+elif cmd == 'getAPIVersion':
+    if len(args) != 0:
+        print('getAPIVersion requires 0 args')
+        sys.exit(1)
+    pp.pprint(client.getAPIVersion())
+
 else:
     print('Unrecognized method %s' % cmd)
     sys.exit(1)
diff --git a/airavata/service/profile/iam/admin/services/cpi/IamAdminServices.py b/airavata/service/profile/iam/admin/services/cpi/IamAdminServices.py
index 7a5ac8b..1288c82 100644
--- a/airavata/service/profile/iam/admin/services/cpi/IamAdminServices.py
+++ b/airavata/service/profile/iam/admin/services/cpi/IamAdminServices.py
@@ -9,16 +9,14 @@
 from thrift.Thrift import TType, TMessageType, TFrozenDict, TException, TApplicationException
 from thrift.protocol.TProtocol import TProtocolException
 import sys
+import airavata.base.api.BaseAPI
 import logging
 from .ttypes import *
 from thrift.Thrift import TProcessor
 from thrift.transport import TTransport
 
 
-class Iface(object):
-    def getAPIVersion(self):
-        pass
-
+class Iface(airavata.base.api.BaseAPI.Iface):
     def setUpGateway(self, authzToken, gateway):
         """
         Parameters:
@@ -142,40 +140,9 @@ class Iface(object):
         pass
 
 
-class Client(Iface):
+class Client(airavata.base.api.BaseAPI.Client, Iface):
     def __init__(self, iprot, oprot=None):
-        self._iprot = self._oprot = iprot
-        if oprot is not None:
-            self._oprot = oprot
-        self._seqid = 0
-
-    def getAPIVersion(self):
-        self.send_getAPIVersion()
-        return self.recv_getAPIVersion()
-
-    def send_getAPIVersion(self):
-        self._oprot.writeMessageBegin('getAPIVersion', TMessageType.CALL, self._seqid)
-        args = getAPIVersion_args()
-        args.write(self._oprot)
-        self._oprot.writeMessageEnd()
-        self._oprot.trans.flush()
-
-    def recv_getAPIVersion(self):
-        iprot = self._iprot
-        (fname, mtype, rseqid) = iprot.readMessageBegin()
-        if mtype == TMessageType.EXCEPTION:
-            x = TApplicationException()
-            x.read(iprot)
-            iprot.readMessageEnd()
-            raise x
-        result = getAPIVersion_result()
-        result.read(iprot)
-        iprot.readMessageEnd()
-        if result.success is not None:
-            return result.success
-        if result.Idse is not None:
-            raise result.Idse
-        raise TApplicationException(TApplicationException.MISSING_RESULT, "getAPIVersion failed: unknown result")
+        airavata.base.api.BaseAPI.Client.__init__(self, iprot, oprot)
 
     def setUpGateway(self, authzToken, gateway):
         """
@@ -714,11 +681,9 @@ class Client(Iface):
         raise TApplicationException(TApplicationException.MISSING_RESULT, "getUsersWithRole failed: unknown result")
 
 
-class Processor(Iface, TProcessor):
+class Processor(airavata.base.api.BaseAPI.Processor, Iface, TProcessor):
     def __init__(self, handler):
-        self._handler = handler
-        self._processMap = {}
-        self._processMap["getAPIVersion"] = Processor.process_getAPIVersion
+        airavata.base.api.BaseAPI.Processor.__init__(self, handler)
         self._processMap["setUpGateway"] = Processor.process_setUpGateway
         self._processMap["isUsernameAvailable"] = Processor.process_isUsernameAvailable
         self._processMap["registerUser"] = Processor.process_registerUser
@@ -749,28 +714,6 @@ class Processor(Iface, TProcessor):
             self._processMap[name](self, seqid, iprot, oprot)
         return True
 
-    def process_getAPIVersion(self, seqid, iprot, oprot):
-        args = getAPIVersion_args()
-        args.read(iprot)
-        iprot.readMessageEnd()
-        result = getAPIVersion_result()
-        try:
-            result.success = self._handler.getAPIVersion()
-            msg_type = TMessageType.REPLY
-        except (TTransport.TTransportException, KeyboardInterrupt, SystemExit):
-            raise
-        except airavata.service.profile.iam.admin.services.cpi.error.ttypes.IamAdminServicesException as Idse:
-            msg_type = TMessageType.REPLY
-            result.Idse = Idse
-        except Exception as ex:
-            msg_type = TMessageType.EXCEPTION
-            logging.exception(ex)
-            result = TApplicationException(TApplicationException.INTERNAL_ERROR, 'Internal error')
-        oprot.writeMessageBegin("getAPIVersion", msg_type, seqid)
-        result.write(oprot)
-        oprot.writeMessageEnd()
-        oprot.trans.flush()
-
     def process_setUpGateway(self, seqid, iprot, oprot):
         args = setUpGateway_args()
         args.read(iprot)
@@ -1124,120 +1067,6 @@ class Processor(Iface, TProcessor):
 # HELPER FUNCTIONS AND STRUCTURES
 
 
-class getAPIVersion_args(object):
-
-    thrift_spec = (
-    )
-
-    def read(self, iprot):
-        if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
-            iprot._fast_decode(self, iprot, (self.__class__, self.thrift_spec))
-            return
-        iprot.readStructBegin()
-        while True:
-            (fname, ftype, fid) = iprot.readFieldBegin()
-            if ftype == TType.STOP:
-                break
-            else:
-                iprot.skip(ftype)
-            iprot.readFieldEnd()
-        iprot.readStructEnd()
-
-    def write(self, oprot):
-        if oprot._fast_encode is not None and self.thrift_spec is not None:
-            oprot.trans.write(oprot._fast_encode(self, (self.__class__, self.thrift_spec)))
-            return
-        oprot.writeStructBegin('getAPIVersion_args')
-        oprot.writeFieldStop()
-        oprot.writeStructEnd()
-
-    def validate(self):
-        return
-
-    def __repr__(self):
-        L = ['%s=%r' % (key, value)
-             for key, value in self.__dict__.items()]
-        return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
-
-    def __eq__(self, other):
-        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
-
-    def __ne__(self, other):
-        return not (self == other)
-
-
-class getAPIVersion_result(object):
-    """
-    Attributes:
-     - success
-     - Idse
-    """
-
-    thrift_spec = (
-        (0, TType.STRING, 'success', 'UTF8', None, ),  # 0
-        (1, TType.STRUCT, 'Idse', (airavata.service.profile.iam.admin.services.cpi.error.ttypes.IamAdminServicesException, airavata.service.profile.iam.admin.services.cpi.error.ttypes.IamAdminServicesException.thrift_spec), None, ),  # 1
-    )
-
-    def __init__(self, success=None, Idse=None,):
-        self.success = success
-        self.Idse = Idse
-
-    def read(self, iprot):
-        if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
-            iprot._fast_decode(self, iprot, (self.__class__, self.thrift_spec))
-            return
-        iprot.readStructBegin()
-        while True:
-            (fname, ftype, fid) = iprot.readFieldBegin()
-            if ftype == TType.STOP:
-                break
-            if fid == 0:
-                if ftype == TType.STRING:
-                    self.success = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
-                else:
-                    iprot.skip(ftype)
-            elif fid == 1:
-                if ftype == TType.STRUCT:
-                    self.Idse = airavata.service.profile.iam.admin.services.cpi.error.ttypes.IamAdminServicesException()
-                    self.Idse.read(iprot)
-                else:
-                    iprot.skip(ftype)
-            else:
-                iprot.skip(ftype)
-            iprot.readFieldEnd()
-        iprot.readStructEnd()
-
-    def write(self, oprot):
-        if oprot._fast_encode is not None and self.thrift_spec is not None:
-            oprot.trans.write(oprot._fast_encode(self, (self.__class__, self.thrift_spec)))
-            return
-        oprot.writeStructBegin('getAPIVersion_result')
-        if self.success is not None:
-            oprot.writeFieldBegin('success', TType.STRING, 0)
-            oprot.writeString(self.success.encode('utf-8') if sys.version_info[0] == 2 else self.success)
-            oprot.writeFieldEnd()
-        if self.Idse is not None:
-            oprot.writeFieldBegin('Idse', TType.STRUCT, 1)
-            self.Idse.write(oprot)
-            oprot.writeFieldEnd()
-        oprot.writeFieldStop()
-        oprot.writeStructEnd()
-
-    def validate(self):
-        return
-
-    def __repr__(self):
-        L = ['%s=%r' % (key, value)
-             for key, value in self.__dict__.items()]
-        return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
-
-    def __eq__(self, other):
-        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
-
-    def __ne__(self, other):
-        return not (self == other)
-
-
 class setUpGateway_args(object):
     """
     Attributes:
diff --git a/airavata/service/profile/iam/admin/services/cpi/constants.py b/airavata/service/profile/iam/admin/services/cpi/constants.py
index 56e62fb..bf5320d 100644
--- a/airavata/service/profile/iam/admin/services/cpi/constants.py
+++ b/airavata/service/profile/iam/admin/services/cpi/constants.py
@@ -10,5 +10,5 @@ from thrift.Thrift import TType, TMessageType, TFrozenDict, TException, TApplica
 from thrift.protocol.TProtocol import TProtocolException
 import sys
 from .ttypes import *
-IAM_ADMIN_SERVICES_CPI_VERSION = "0.17"
+IAM_ADMIN_SERVICES_CPI_VERSION = "0.18.0"
 IAM_ADMIN_SERVICES_CPI_NAME = "IamAdminServices"
diff --git a/airavata/service/profile/iam/admin/services/cpi/ttypes.py b/airavata/service/profile/iam/admin/services/cpi/ttypes.py
index a20bb26..a564541 100644
--- a/airavata/service/profile/iam/admin/services/cpi/ttypes.py
+++ b/airavata/service/profile/iam/admin/services/cpi/ttypes.py
@@ -15,5 +15,6 @@ import airavata.model.workspace.ttypes
 import airavata.model.user.ttypes
 import airavata.model.credential.store.ttypes
 import airavata.service.profile.iam.admin.services.cpi.error.ttypes
+import airavata.base.api.ttypes
 
 from thrift.transport import TTransport
diff --git a/airavata/service/profile/tenant/cpi/TenantProfileService-remote b/airavata/service/profile/tenant/cpi/TenantProfileService-remote
index 307dc4c..4283ca1 100755
--- a/airavata/service/profile/tenant/cpi/TenantProfileService-remote
+++ b/airavata/service/profile/tenant/cpi/TenantProfileService-remote
@@ -24,7 +24,6 @@ if len(sys.argv) <= 1 or sys.argv[1] == '--help':
     print('Usage: ' + sys.argv[0] + ' [-h host[:port]] [-u url] [-f[ramed]] [-s[sl]] [-novalidate] [-ca_certs certs] [-keyfile keyfile] [-certfile certfile] function [arg1 [arg2...]]')
     print('')
     print('Functions:')
-    print('  string getAPIVersion()')
     print('  string addGateway(AuthzToken authzToken, Gateway gateway)')
     print('  bool updateGateway(AuthzToken authzToken, Gateway updatedGateway)')
     print('  Gateway getGateway(AuthzToken authzToken, string airavataInternalGatewayId)')
@@ -32,6 +31,7 @@ if len(sys.argv) <= 1 or sys.argv[1] == '--help':
     print('   getAllGateways(AuthzToken authzToken)')
     print('  bool isGatewayExist(AuthzToken authzToken, string gatewayId)')
     print('   getAllGatewaysForUser(AuthzToken authzToken, string requesterUsername)')
+    print('  string getAPIVersion()')
     print('')
     sys.exit(0)
 
@@ -111,13 +111,7 @@ protocol = TBinaryProtocol(transport)
 client = TenantProfileService.Client(protocol)
 transport.open()
 
-if cmd == 'getAPIVersion':
-    if len(args) != 0:
-        print('getAPIVersion requires 0 args')
-        sys.exit(1)
-    pp.pprint(client.getAPIVersion())
-
-elif cmd == 'addGateway':
+if cmd == 'addGateway':
     if len(args) != 2:
         print('addGateway requires 2 args')
         sys.exit(1)
@@ -159,6 +153,12 @@ elif cmd == 'getAllGatewaysForUser':
         sys.exit(1)
     pp.pprint(client.getAllGatewaysForUser(eval(args[0]), args[1],))
 
+elif cmd == 'getAPIVersion':
+    if len(args) != 0:
+        print('getAPIVersion requires 0 args')
+        sys.exit(1)
+    pp.pprint(client.getAPIVersion())
+
 else:
     print('Unrecognized method %s' % cmd)
     sys.exit(1)
diff --git a/airavata/service/profile/tenant/cpi/TenantProfileService.py b/airavata/service/profile/tenant/cpi/TenantProfileService.py
index 2328e0f..be0a5ec 100644
--- a/airavata/service/profile/tenant/cpi/TenantProfileService.py
+++ b/airavata/service/profile/tenant/cpi/TenantProfileService.py
@@ -9,16 +9,14 @@
 from thrift.Thrift import TType, TMessageType, TFrozenDict, TException, TApplicationException
 from thrift.protocol.TProtocol import TProtocolException
 import sys
+import airavata.base.api.BaseAPI
 import logging
 from .ttypes import *
 from thrift.Thrift import TProcessor
 from thrift.transport import TTransport
 
 
-class Iface(object):
-    def getAPIVersion(self):
-        pass
-
+class Iface(airavata.base.api.BaseAPI.Iface):
     def addGateway(self, authzToken, gateway):
         """
         Return the airavataInternalGatewayId assigned to given gateway.
@@ -78,40 +76,9 @@ class Iface(object):
         pass
 
 
-class Client(Iface):
+class Client(airavata.base.api.BaseAPI.Client, Iface):
     def __init__(self, iprot, oprot=None):
-        self._iprot = self._oprot = iprot
-        if oprot is not None:
-            self._oprot = oprot
-        self._seqid = 0
-
-    def getAPIVersion(self):
-        self.send_getAPIVersion()
-        return self.recv_getAPIVersion()
-
-    def send_getAPIVersion(self):
-        self._oprot.writeMessageBegin('getAPIVersion', TMessageType.CALL, self._seqid)
-        args = getAPIVersion_args()
-        args.write(self._oprot)
-        self._oprot.writeMessageEnd()
-        self._oprot.trans.flush()
-
-    def recv_getAPIVersion(self):
-        iprot = self._iprot
-        (fname, mtype, rseqid) = iprot.readMessageBegin()
-        if mtype == TMessageType.EXCEPTION:
-            x = TApplicationException()
-            x.read(iprot)
-            iprot.readMessageEnd()
-            raise x
-        result = getAPIVersion_result()
-        result.read(iprot)
-        iprot.readMessageEnd()
-        if result.success is not None:
-            return result.success
-        if result.tpe is not None:
-            raise result.tpe
-        raise TApplicationException(TApplicationException.MISSING_RESULT, "getAPIVersion failed: unknown result")
+        airavata.base.api.BaseAPI.Client.__init__(self, iprot, oprot)
 
     def addGateway(self, authzToken, gateway):
         """
@@ -375,11 +342,9 @@ class Client(Iface):
         raise TApplicationException(TApplicationException.MISSING_RESULT, "getAllGatewaysForUser failed: unknown result")
 
 
-class Processor(Iface, TProcessor):
+class Processor(airavata.base.api.BaseAPI.Processor, Iface, TProcessor):
     def __init__(self, handler):
-        self._handler = handler
-        self._processMap = {}
-        self._processMap["getAPIVersion"] = Processor.process_getAPIVersion
+        airavata.base.api.BaseAPI.Processor.__init__(self, handler)
         self._processMap["addGateway"] = Processor.process_addGateway
         self._processMap["updateGateway"] = Processor.process_updateGateway
         self._processMap["getGateway"] = Processor.process_getGateway
@@ -403,28 +368,6 @@ class Processor(Iface, TProcessor):
             self._processMap[name](self, seqid, iprot, oprot)
         return True
 
-    def process_getAPIVersion(self, seqid, iprot, oprot):
-        args = getAPIVersion_args()
-        args.read(iprot)
-        iprot.readMessageEnd()
-        result = getAPIVersion_result()
-        try:
-            result.success = self._handler.getAPIVersion()
-            msg_type = TMessageType.REPLY
-        except (TTransport.TTransportException, KeyboardInterrupt, SystemExit):
-            raise
-        except airavata.service.profile.tenant.cpi.error.ttypes.TenantProfileServiceException as tpe:
-            msg_type = TMessageType.REPLY
-            result.tpe = tpe
-        except Exception as ex:
-            msg_type = TMessageType.EXCEPTION
-            logging.exception(ex)
-            result = TApplicationException(TApplicationException.INTERNAL_ERROR, 'Internal error')
-        oprot.writeMessageBegin("getAPIVersion", msg_type, seqid)
-        result.write(oprot)
-        oprot.writeMessageEnd()
-        oprot.trans.flush()
-
     def process_addGateway(self, seqid, iprot, oprot):
         args = addGateway_args()
         args.read(iprot)
@@ -603,120 +546,6 @@ class Processor(Iface, TProcessor):
 # HELPER FUNCTIONS AND STRUCTURES
 
 
-class getAPIVersion_args(object):
-
-    thrift_spec = (
-    )
-
-    def read(self, iprot):
-        if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
-            iprot._fast_decode(self, iprot, (self.__class__, self.thrift_spec))
-            return
-        iprot.readStructBegin()
-        while True:
-            (fname, ftype, fid) = iprot.readFieldBegin()
-            if ftype == TType.STOP:
-                break
-            else:
-                iprot.skip(ftype)
-            iprot.readFieldEnd()
-        iprot.readStructEnd()
-
-    def write(self, oprot):
-        if oprot._fast_encode is not None and self.thrift_spec is not None:
-            oprot.trans.write(oprot._fast_encode(self, (self.__class__, self.thrift_spec)))
-            return
-        oprot.writeStructBegin('getAPIVersion_args')
-        oprot.writeFieldStop()
-        oprot.writeStructEnd()
-
-    def validate(self):
-        return
-
-    def __repr__(self):
-        L = ['%s=%r' % (key, value)
-             for key, value in self.__dict__.items()]
-        return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
-
-    def __eq__(self, other):
-        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
-
-    def __ne__(self, other):
-        return not (self == other)
-
-
-class getAPIVersion_result(object):
-    """
-    Attributes:
-     - success
-     - tpe
-    """
-
-    thrift_spec = (
-        (0, TType.STRING, 'success', 'UTF8', None, ),  # 0
-        (1, TType.STRUCT, 'tpe', (airavata.service.profile.tenant.cpi.error.ttypes.TenantProfileServiceException, airavata.service.profile.tenant.cpi.error.ttypes.TenantProfileServiceException.thrift_spec), None, ),  # 1
-    )
-
-    def __init__(self, success=None, tpe=None,):
-        self.success = success
-        self.tpe = tpe
-
-    def read(self, iprot):
-        if iprot._fast_decode is not None and isinstance(iprot.trans, TTransport.CReadableTransport) and self.thrift_spec is not None:
-            iprot._fast_decode(self, iprot, (self.__class__, self.thrift_spec))
-            return
-        iprot.readStructBegin()
-        while True:
-            (fname, ftype, fid) = iprot.readFieldBegin()
-            if ftype == TType.STOP:
-                break
-            if fid == 0:
-                if ftype == TType.STRING:
-                    self.success = iprot.readString().decode('utf-8') if sys.version_info[0] == 2 else iprot.readString()
-                else:
-                    iprot.skip(ftype)
-            elif fid == 1:
-                if ftype == TType.STRUCT:
-                    self.tpe = airavata.service.profile.tenant.cpi.error.ttypes.TenantProfileServiceException()
-                    self.tpe.read(iprot)
-                else:
-                    iprot.skip(ftype)
-            else:
-                iprot.skip(ftype)
-            iprot.readFieldEnd()
-        iprot.readStructEnd()
-
-    def write(self, oprot):
-        if oprot._fast_encode is not None and self.thrift_spec is not None:
-            oprot.trans.write(oprot._fast_encode(self, (self.__class__, self.thrift_spec)))
-            return
-        oprot.writeStructBegin('getAPIVersion_result')
-        if self.success is not None:
-            oprot.writeFieldBegin('success', TType.STRING, 0)
-            oprot.writeString(self.success.encode('utf-8') if sys.version_info[0] == 2 else self.success)
-            oprot.writeFieldEnd()
-        if self.tpe is not None:
-            oprot.writeFieldBegin('tpe', TType.STRUCT, 1)
-            self.tpe.write(oprot)
-            oprot.writeFieldEnd()
-        oprot.writeFieldStop()
-        oprot.writeStructEnd()
-
-    def validate(self):
-        return
-
-    def __repr__(self):
-        L = ['%s=%r' % (key, value)
-             for key, value in self.__dict__.items()]
-        return '%s(%s)' % (self.__class__.__name__, ', '.join(L))
-
-    def __eq__(self, other):
-        return isinstance(other, self.__class__) and self.__dict__ == other.__dict__
-
-    def __ne__(self, other):
-        return not (self == other)
-
-
 class addGateway_args(object):
     """
     Attributes:
diff --git a/airavata/service/profile/tenant/cpi/constants.py b/airavata/service/profile/tenant/cpi/constants.py
index 184bd4d..c863ee3 100644
--- a/airavata/service/profile/tenant/cpi/constants.py
+++ b/airavata/service/profile/tenant/cpi/constants.py
@@ -10,5 +10,5 @@ from thrift.Thrift import TType, TMessageType, TFrozenDict, TException, TApplica
 from thrift.protocol.TProtocol import TProtocolException
 import sys
 from .ttypes import *
-TENANT_PROFILE_CPI_VERSION = "0.17"
+TENANT_PROFILE_CPI_VERSION = "0.18.0"
 TENANT_PROFILE_CPI_NAME = "TenantProfileService"
diff --git a/airavata/service/profile/tenant/cpi/ttypes.py b/airavata/service/profile/tenant/cpi/ttypes.py
index 01908eb..0ff6cbb 100644
--- a/airavata/service/profile/tenant/cpi/ttypes.py
+++ b/airavata/service/profile/tenant/cpi/ttypes.py
@@ -13,5 +13,6 @@ import airavata.api.error.ttypes
 import airavata.model.security.ttypes
 import airavata.model.workspace.ttypes
 import airavata.service.profile.tenant.cpi.error.ttypes
+import airavata.base.api.ttypes
 
 from thrift.transport import TTransport
diff --git a/airavata/service/profile/ttypes.py b/airavata/service/profile/ttypes.py
index 6e27f2c..3bf806e 100644
--- a/airavata/service/profile/ttypes.py
+++ b/airavata/service/profile/ttypes.py
@@ -13,5 +13,6 @@ import airavata.service.profile.user.cpi.ttypes
 import airavata.service.profile.tenant.cpi.ttypes
 import airavata.service.profile.iam.admin.services.cpi.ttypes
 import airavata.service.profile.groupmanager.cpi.ttypes
+import airavata.base.api.ttypes
 
 from thrift.transport import TTransport
diff --git a/airavata/service/profile/user/cpi/UserProfileService-remote b/airavata/service/profile/user/cpi/UserProfileService-remote
index 3883c1b..728ade5 100755
--- a/airavata/service/profile/user/cpi/UserProfileService-remote
+++ b/airavata/service/profile/user/cpi/UserProfileService-remote
@@ -32,6 +32,7 @@ if len(sys.argv) <= 1 or sys.argv[1] == '--help':
     print('  bool deleteUserProfile(AuthzToken authzToken, string userId, string gatewayId)')
     print('   getAllUserProfilesInGateway(AuthzToken authzToken, string gatewayId, i32 offset, i32 limit)')
     print('  bool doesUserExist(AuthzToken authzToken, string userId, string gatewayId)')
+    print('  string getAPIVersion()')
     print('')
     sys.exit(0)
 
@@ -159,6 +160,12 @@ elif cmd == 'doesUserExist':
         sys.exit(1)
     pp.pprint(client.doesUserExist(eval(args[0]), args[1], args[2],))
 
+elif cmd == 'getAPIVersion':
+    if len(args) != 0:
+        print('getAPIVersion requires 0 args')
+        sys.exit(1)
+    pp.pprint(client.getAPIVersion())
+
 else:
     print('Unrecognized method %s' % cmd)
     sys.exit(1)
diff --git a/airavata/service/profile/user/cpi/UserProfileService.py b/airavata/service/profile/user/cpi/UserProfileService.py
index a5d06cb..ca8b8b0 100644
--- a/airavata/service/profile/user/cpi/UserProfileService.py
+++ b/airavata/service/profile/user/cpi/UserProfileService.py
@@ -9,13 +9,14 @@
 from thrift.Thrift import TType, TMessageType, TFrozenDict, TException, TApplicationException
 from thrift.protocol.TProtocol import TProtocolException
 import sys
+import airavata.base.api.BaseAPI
 import logging
 from .ttypes import *
 from thrift.Thrift import TProcessor
 from thrift.transport import TTransport
 
 
-class Iface(object):
+class Iface(airavata.base.api.BaseAPI.Iface):
     def getAPIVersion(self):
         pass
 
@@ -82,12 +83,9 @@ class Iface(object):
         pass
 
 
-class Client(Iface):
+class Client(airavata.base.api.BaseAPI.Client, Iface):
     def __init__(self, iprot, oprot=None):
-        self._iprot = self._oprot = iprot
-        if oprot is not None:
-            self._oprot = oprot
-        self._seqid = 0
+        airavata.base.api.BaseAPI.Client.__init__(self, iprot, oprot)
 
     def getAPIVersion(self):
         self.send_getAPIVersion()
@@ -387,10 +385,9 @@ class Client(Iface):
         raise TApplicationException(TApplicationException.MISSING_RESULT, "doesUserExist failed: unknown result")
 
 
-class Processor(Iface, TProcessor):
+class Processor(airavata.base.api.BaseAPI.Processor, Iface, TProcessor):
     def __init__(self, handler):
-        self._handler = handler
-        self._processMap = {}
+        airavata.base.api.BaseAPI.Processor.__init__(self, handler)
         self._processMap["getAPIVersion"] = Processor.process_getAPIVersion
         self._processMap["initializeUserProfile"] = Processor.process_initializeUserProfile
         self._processMap["addUserProfile"] = Processor.process_addUserProfile
diff --git a/airavata/service/profile/user/cpi/constants.py b/airavata/service/profile/user/cpi/constants.py
index d80d748..e2feb32 100644
--- a/airavata/service/profile/user/cpi/constants.py
+++ b/airavata/service/profile/user/cpi/constants.py
@@ -10,5 +10,5 @@ from thrift.Thrift import TType, TMessageType, TFrozenDict, TException, TApplica
 from thrift.protocol.TProtocol import TProtocolException
 import sys
 from .ttypes import *
-USER_PROFILE_CPI_VERSION = "0.17"
+USER_PROFILE_CPI_VERSION = "0.18.0"
 USER_PROFILE_CPI_NAME = "UserProfileService"
diff --git a/airavata/service/profile/user/cpi/ttypes.py b/airavata/service/profile/user/cpi/ttypes.py
index 2653182..980f2b5 100644
--- a/airavata/service/profile/user/cpi/ttypes.py
+++ b/airavata/service/profile/user/cpi/ttypes.py
@@ -13,5 +13,6 @@ import airavata.api.error.ttypes
 import airavata.model.security.ttypes
 import airavata.model.user.ttypes
 import airavata.service.profile.user.cpi.error.ttypes
+import airavata.base.api.ttypes
 
 from thrift.transport import TTransport


[airavata-django-portal] 02/03: AIRAVATA-3029 application output metadata editing

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 4ff60cf04dc42163cfffe87447eddd6cc2377328
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Jun 11 14:27:27 2019 -0400

    AIRAVATA-3029 application output metadata editing
---
 .../applications/ApplicationOutputFieldEditor.vue      |  9 ++++++++-
 django_airavata/apps/api/serializers.py                | 18 +++++++++++++++++-
 .../js/models/OutputDataObjectType.js                  |  1 +
 3 files changed, 26 insertions(+), 2 deletions(-)

diff --git a/django_airavata/apps/admin/static/django_airavata_admin/src/components/applications/ApplicationOutputFieldEditor.vue b/django_airavata/apps/admin/static/django_airavata_admin/src/components/applications/ApplicationOutputFieldEditor.vue
index e9db427..e8a73fd 100644
--- a/django_airavata/apps/admin/static/django_airavata_admin/src/components/applications/ApplicationOutputFieldEditor.vue
+++ b/django_airavata/apps/admin/static/django_airavata_admin/src/components/applications/ApplicationOutputFieldEditor.vue
@@ -30,12 +30,16 @@
         </b-form-radio-group>
       </b-form-group>
     </div>
+    <b-form-group label="Metadata" :label-for="id+'-metadata'" description="Metadata for this output, in the JSON format">
+      <json-editor :id="id+'-metadata'" v-model="data.metaData" :rows="5" :disabled="readonly" />
+    </b-form-group>
   </b-card>
 </template>
 
 <script>
 import { models } from "django-airavata-api";
-import { mixins } from "django-airavata-common-ui"
+import { mixins } from "django-airavata-common-ui";
+import JSONEditor from "./JSONEditor.vue";
 export default {
   name: "application-output-field-editor",
   mixins: [mixins.VModelMixin],
@@ -51,6 +55,9 @@ export default {
       default: false
     }
   },
+  components: {
+    'json-editor': JSONEditor
+  },
   computed: {
     outputTypeOptions() {
       return models.OutputDataObjectType.VALID_DATA_TYPES.map(dataType => {
diff --git a/django_airavata/apps/api/serializers.py b/django_airavata/apps/api/serializers.py
index 614330c..9420cf2 100644
--- a/django_airavata/apps/api/serializers.py
+++ b/django_airavata/apps/api/serializers.py
@@ -33,7 +33,10 @@ from airavata.model.appcatalog.parser.ttypes import Parser
 from airavata.model.appcatalog.storageresource.ttypes import (
     StorageResourceDescription
 )
-from airavata.model.application.io.ttypes import InputDataObjectType
+from airavata.model.application.io.ttypes import (
+    InputDataObjectType,
+    OutputDataObjectType
+)
 from airavata.model.credential.store.ttypes import (
     CredentialSummary,
     SummaryType
@@ -298,6 +301,15 @@ class InputDataObjectTypeSerializer(
         required = ('name',)
 
 
+class OutputDataObjectTypeSerializer(
+        thrift_utils.create_serializer_class(OutputDataObjectType)):
+
+    metaData = StoredJSONField(required=False, allow_null=True)
+
+    class Meta:
+        required = ('name',)
+
+
 class ApplicationInterfaceDescriptionSerializer(
         thrift_utils.create_serializer_class(ApplicationInterfaceDescription)):
 
@@ -309,6 +321,7 @@ class ApplicationInterfaceDescriptionSerializer(
         order_by='inputOrder',
         child=InputDataObjectTypeSerializer(),
         allow_null=True)
+    applicationOutputs = OutputDataObjectTypeSerializer(many=True)
     userHasWriteAccess = serializers.SerializerMethodField()
 
     def get_userHasWriteAccess(self, appDeployment):
@@ -416,6 +429,9 @@ class ExperimentSerializer(
     experimentInputs = serializers.ListField(
         child=InputDataObjectTypeSerializer(),
         allow_null=True)
+    experimentOutputs = serializers.ListField(
+        child=OutputDataObjectTypeSerializer(),
+        allow_null=True)
     creationTime = UTCPosixTimestampDateTimeField(allow_null=True)
     experimentStatus = ExperimentStatusSerializer(many=True, allow_null=True)
     userHasWriteAccess = serializers.SerializerMethodField()
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/OutputDataObjectType.js b/django_airavata/apps/api/static/django_airavata_api/js/models/OutputDataObjectType.js
index f312dda..5aaad3b 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/OutputDataObjectType.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/OutputDataObjectType.js
@@ -35,6 +35,7 @@ const FIELDS = [
     default: false
   },
   'storageResourceId',
+  'metaData'
 ];
 
 export default class OutputDataObjectType extends BaseModel {


[airavata-django-portal] 03/03: AIRAVATA-3029 output view provider initial implementation

Posted by ma...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

machristie pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata-django-portal.git

commit 8e26c80cb6b294cffa89fee35eb9e2a85d3f57c8
Author: Marcus Christie <ma...@apache.org>
AuthorDate: Tue Jun 11 14:29:44 2019 -0400

    AIRAVATA-3029 output view provider initial implementation
---
 django_airavata/apps/api/output_views.py           |  76 +++++
 django_airavata/apps/api/serializers.py            |   4 +-
 .../js/models/FullExperiment.js                    | 113 ++++----
 django_airavata/apps/api/views.py                  |  13 +-
 .../js/components/experiment/ExperimentSummary.vue | 315 ++++++++++++---------
 .../output-displays/DownloadOutputDisplay.vue      |  33 +++
 .../experiment/output-displays/LinkDisplay.vue     |  26 ++
 .../output-displays/OutputDisplayContainer.vue     |  69 +++++
 django_airavata/settings.py                        |   4 +
 9 files changed, 467 insertions(+), 186 deletions(-)

diff --git a/django_airavata/apps/api/output_views.py b/django_airavata/apps/api/output_views.py
new file mode 100644
index 0000000..4d42446
--- /dev/null
+++ b/django_airavata/apps/api/output_views.py
@@ -0,0 +1,76 @@
+import json
+import logging
+
+from django.conf import settings
+
+logger = logging.getLogger(__name__)
+
+
+class DownloadLinkViewProvider:
+    display_type = 'download'
+    immediate = True
+
+    def generate_data(self, experiment_output, experiment, output_file=None):
+        return {
+        }
+
+
+DEFAULT_VIEW_PROVIDERS = {
+    'download': DownloadLinkViewProvider()
+}
+
+
+def get_output_views(experiment):
+    output_views = {}
+    for output in experiment.experimentOutputs:
+        output_views[output.name] = []
+        output_view_provider_names = _get_output_view_providers(output)
+        for output_view_provider_name in output_view_provider_names:
+            output_view_provider = None
+            if output_view_provider_name in DEFAULT_VIEW_PROVIDERS:
+                output_view_provider = DEFAULT_VIEW_PROVIDERS[
+                    output_view_provider_name]
+            elif output_view_provider_name in settings.OUTPUT_VIEW_PROVIDERS:
+                output_view_provider = settings.OUTPUT_VIEWER_PROVIDERS[
+                    output_view_provider_name]
+            else:
+                logger.error("Unable to find output view provider with "
+                             "name '{}'".format(output_view_provider_name))
+            if output_view_provider is not None:
+                if getattr(output_view_provider, 'immediate', False):
+                    # Immediately call generate_data function
+                    # TODO: also pass a file object if URI (and handle
+                    # URI_COLLECTION)
+                    data = output_view_provider.generate_data(
+                        output, experiment)
+                    output_views[output.name].append({
+                        'provider-id': output_view_provider_name,
+                        'display-type': output_view_provider.display_type,
+                        'data': data
+                    })
+                else:
+                    output_views[output.name].append({
+                        'provider-id': output_view_provider_name,
+                        'display-type': output_view_provider.display_type,
+                    })
+    return output_views
+
+
+def _get_output_view_providers(experiment_output):
+    output_view_providers = []
+    logger.debug("experiment_output={}".format(experiment_output))
+    if experiment_output.metaData:
+        try:
+            output_metadata = json.loads(experiment_output.metaData)
+            output_view_providers.extend(
+                output_metadata['output-view-providers'])
+            logger.debug("output_metadata={}".format(output_metadata))
+        except Exception as e:
+            logger.exception(
+                "Failed to parse metadata for output {}".format(
+                    experiment_output.name))
+    if 'download' not in output_view_providers:
+        output_view_providers.insert(0, 'download')
+    # if len(output_view_providers) == 0:
+    #     output_view_providers.extend(_get_default_view_providers())
+    return output_view_providers
diff --git a/django_airavata/apps/api/serializers.py b/django_airavata/apps/api/serializers.py
index 9420cf2..90fd2ed 100644
--- a/django_airavata/apps/api/serializers.py
+++ b/django_airavata/apps/api/serializers.py
@@ -492,7 +492,7 @@ class FullExperiment:
 
     def __init__(self, experimentModel, project=None, outputDataProducts=None,
                  inputDataProducts=None, applicationModule=None,
-                 computeResource=None, jobDetails=None):
+                 computeResource=None, jobDetails=None, outputViews=None):
         self.experiment = experimentModel
         self.experimentId = experimentModel.experimentId
         self.project = project
@@ -501,6 +501,7 @@ class FullExperiment:
         self.applicationModule = applicationModule
         self.computeResource = computeResource
         self.jobDetails = jobDetails
+        self.outputViews = outputViews
 
 
 class JobSerializer(thrift_utils.create_serializer_class(JobModel)):
@@ -519,6 +520,7 @@ class FullExperimentSerializer(serializers.Serializer):
     computeResource = ComputeResourceDescriptionSerializer(read_only=True)
     project = ProjectSerializer(read_only=True)
     jobDetails = JobSerializer(many=True, read_only=True)
+    outputViews = serializers.DictField(read_only=True)
 
     def create(self, validated_data):
         raise Exception("Not implemented")
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/FullExperiment.js b/django_airavata/apps/api/static/django_airavata_api/js/models/FullExperiment.js
index 60b0cfb..f657762 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/FullExperiment.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/FullExperiment.js
@@ -1,63 +1,66 @@
-
-import ApplicationModule from './ApplicationModule'
-import BaseModel from './BaseModel'
-import ComputeResourceDescription from './ComputeResourceDescription'
-import DataProduct from './DataProduct'
-import Experiment from './Experiment'
-import Job from './Job'
-import Project from './Project'
+import ApplicationModule from "./ApplicationModule";
+import BaseModel from "./BaseModel";
+import ComputeResourceDescription from "./ComputeResourceDescription";
+import DataProduct from "./DataProduct";
+import Experiment from "./Experiment";
+import Job from "./Job";
+import Project from "./Project";
 
 const FIELDS = [
-    'experimentId',
-    {
-        name: 'experiment',
-        type: Experiment,
-    },
-    {
-        name: 'project',
-        type: Project
-    },
-    {
-        name: 'applicationModule',
-        type: ApplicationModule
-    },
-    {
-        name: 'computeResource',
-        type: ComputeResourceDescription
-    },
-    {
-        name: 'outputDataProducts',
-        type: DataProduct,
-        list: true
-    },
-    {
-        name: 'inputDataProducts',
-        type: DataProduct,
-        list: true
-    },
-    {
-        name: 'jobDetails',
-        type: Job,
-        list: true,
-    },
+  "experimentId",
+  {
+    name: "experiment",
+    type: Experiment
+  },
+  {
+    name: "project",
+    type: Project
+  },
+  {
+    name: "applicationModule",
+    type: ApplicationModule
+  },
+  {
+    name: "computeResource",
+    type: ComputeResourceDescription
+  },
+  {
+    name: "outputDataProducts",
+    type: DataProduct,
+    list: true
+  },
+  {
+    name: "inputDataProducts",
+    type: DataProduct,
+    list: true
+  },
+  {
+    name: "jobDetails",
+    type: Job,
+    list: true
+  },
+  {
+    name: "outputViews",
+    type: Object
+  }
 ];
 
 export default class FullExperiment extends BaseModel {
-    constructor(data = {}) {
-        super(FIELDS, data);
-    }
+  constructor(data = {}) {
+    super(FIELDS, data);
+  }
 
-    get projectName() {
-        return this.project ? this.project.name : null;
-    }
+  get projectName() {
+    return this.project ? this.project.name : null;
+  }
 
-    get applicationName() {
-        return this.applicationModule ? this.applicationModule.appModuleName : null;
-    }
+  get applicationName() {
+    return this.applicationModule ? this.applicationModule.appModuleName : null;
+  }
 
-    get computeHostName() {
-        return this.computeResource ? this.computeResource.hostName : null;
-    }
+  get computeHostName() {
+    return this.computeResource ? this.computeResource.hostName : null;
+  }
 
     get resourceHostId() {
       return this.experiment.resourceHostId;
@@ -67,7 +70,7 @@ export default class FullExperiment extends BaseModel {
         return this.experiment.latestStatus;
     }
 
-    get experimentStatusName() {
-        return this.experimentStatus ? this.experimentStatus.state.name : null;
-    }
+  get experimentStatusName() {
+    return this.experimentStatus ? this.experimentStatus.state.name : null;
+  }
 }
diff --git a/django_airavata/apps/api/views.py b/django_airavata/apps/api/views.py
index 7d8caf7..0fe5192 100644
--- a/django_airavata/apps/api/views.py
+++ b/django_airavata/apps/api/views.py
@@ -40,7 +40,14 @@ from django_airavata.apps.api.view_utils import (
 )
 from django_airavata.apps.auth import iam_admin_client
 
-from . import data_products_helper, helpers, models, serializers, thrift_utils
+from . import (
+    data_products_helper,
+    helpers,
+    models,
+    output_views,
+    serializers,
+    thrift_utils
+)
 
 READ_PERMISSION_TYPE = '{}:READ'
 
@@ -419,6 +426,7 @@ class FullExperimentViewSet(mixins.RetrieveModelMixin,
                 output.type == DataType.URI_COLLECTION)
             for dp in output.value.split(',')
             if output.value.startswith('airavata-dp')]
+        exp_output_views = output_views.get_output_views(experimentModel)
         inputDataProducts = [
             self.request.airavata_client.getDataProduct(self.authz_token,
                                                         inp.value)
@@ -474,7 +482,8 @@ class FullExperimentViewSet(mixins.RetrieveModelMixin,
             inputDataProducts=inputDataProducts,
             applicationModule=applicationModule,
             computeResource=compute_resource,
-            jobDetails=job_details)
+            jobDetails=job_details,
+            outputViews=exp_output_views)
         return full_experiment
 
 
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentSummary.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentSummary.vue
index 424afb6..0339172 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentSummary.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentSummary.vue
@@ -7,15 +7,55 @@
         </h1>
       </div>
       <div class="col-auto">
-          <share-button :entity-id="experiment.experimentId" />
-          <b-link v-if="isEditable" class="btn btn-primary" :href="editLink">
-            Edit
-            <i class="fa fa-edit" aria-hidden="true"></i>
-          </b-link>
-          <b-btn v-if="isClonable" variant="primary" @click="clone">
-            Clone
-            <i class="fa fa-copy" aria-hidden="true"></i>
-          </b-btn>
+        <share-button :entity-id="experiment.experimentId" />
+        <b-link
+          v-if="isEditable"
+          class="btn btn-primary"
+          :href="editLink"
+        >
+          Edit
+          <i
+            class="fa fa-edit"
+            aria-hidden="true"
+          ></i>
+        </b-link>
+        <b-btn
+          v-if="isClonable"
+          variant="primary"
+          @click="clone"
+        >
+          Clone
+          <i
+            class="fa fa-copy"
+            aria-hidden="true"
+          ></i>
+        </b-btn>
+      </div>
+    </div>
+    <template v-for="output in experiment.experimentOutputs">
+
+      <div
+        class="row"
+        v-if="outputDataProducts[output.name].length > 0"
+        :key="output.name"
+      >
+        <div class="col">
+          <output-display-container
+            :experiment-output="output"
+            :data-products="outputDataProducts[output.name]"
+            :output-views="localFullExperiment.outputViews[output.name]"
+          />
+        </div>
+      </div>
+    </template>
+    <div class="row">
+      <div class="col">
+        <b-card header="Other Files">
+          <b-link
+            v-if="storageDirLink"
+            :href="storageDirLink"
+          >Storage Directory</b-link>
+        </b-card>
       </div>
     </div>
     <div class="row">
@@ -29,12 +69,15 @@
                   <td>
                     <div :title="experiment.experimentId">{{ experiment.experimentName }}</div>
                     <small class="text-muted">
-                    ID: {{ experiment.experimentId }}
-                    (<clipboard-copy-link :text="experiment.experimentId" :link-classes="['text-reset']">
-                      copy
-                      <span slot="icon"></span>
-                      <span slot="tooltip">Copied ID!</span>
-                    </clipboard-copy-link>)
+                      ID: {{ experiment.experimentId }}
+                      (<clipboard-copy-link
+                        :text="experiment.experimentId"
+                        :link-classes="['text-reset']"
+                      >
+                        copy
+                        <span slot="icon"></span>
+                        <span slot="tooltip">Copied ID!</span>
+                      </clipboard-copy-link>)
                     </small>
                   </td>
                 </tr>
@@ -50,41 +93,24 @@
                   </td>
                 </tr>
                 <tr>
-                  <th scope="row">Outputs</th>
-                  <td>
-                    <ul>
-                      <li v-for="output in experiment.experimentOutputs" :key="output.name">
-                        {{ output.name }}:
-                        <template v-if="output.type.isSimpleValueType">
-                          {{ output.value }}
-                        </template>
-                        <template v-else-if="output.type.isFileValueType">
-                          <data-product-viewer v-for="dp in outputDataProducts[output.name]"
-                            :data-product="dp" class="data-product" :key="dp.productUri"/>
-                        </template>
-                      </li>
-                    </ul>
-                    <b-link v-if="storageDirLink" :href="storageDirLink">Storage Directory</b-link>
-                  </td>
-                </tr>
-                <!-- Going to leave this out for now -->
-                <!-- <tr>
-                                    <th scope="row">Storage Directory</th>
-                                    <td></td>
-                                </tr> -->
-                <tr>
                   <th scope="row">Owner</th>
                   <td>{{ experiment.userName }}</td>
                 </tr>
                 <tr>
                   <th scope="row">Application</th>
                   <td v-if="localFullExperiment.applicationName">{{ localFullExperiment.applicationName }}</td>
-                  <td v-else class="font-italic text-muted">Unable to load interface {{ localFullExperiment.experiment.executionId }}</td>
+                  <td
+                    v-else
+                    class="font-italic text-muted"
+                  >Unable to load interface {{ localFullExperiment.experiment.executionId }}</td>
                 </tr>
                 <tr>
                   <th scope="row">Compute Resource</th>
                   <td v-if="localFullExperiment.computeHostName">{{ localFullExperiment.computeHostName }}</td>
-                  <td v-else class="font-italic text-muted">Unable to load compute resource {{ localFullExperiment.resourceHostId }}</td>
+                  <td
+                    v-else
+                    class="font-italic text-muted"
+                  >Unable to load compute resource {{ localFullExperiment.resourceHostId }}</td>
                 </tr>
                 <tr>
                   <th scope="row">Experiment Status</th>
@@ -101,79 +127,90 @@
                   <td>
                     <table class="table">
                       <thead>
-                        <th>Name</th>
-                        <th>ID</th>
-                        <th>Status</th>
-                        <th>Creation Time</th>
-                      </thead>
-                      <tr v-for="(jobDetail, index) in localFullExperiment.jobDetails" :key="jobDetail.jobId">
-                        <td>{{ jobDetail.jobName }}</td>
-                        <td>{{ jobDetail.jobId }}</td>
-                        <td>{{ jobDetail.jobStatusStateName }}</td>
-                        <td>
-                          <span :title="jobDetail.creationTime.toString()">{{ jobCreationTimes[index] }}</span>
-                        </td>
-                      </tr>
-                    </table>
+                  <th>Name</th>
+                  <th>ID</th>
+                  <th>Status</th>
+                  <th>Creation Time</th>
+                  </thead>
+                <tr
+                  v-for="(jobDetail, index) in localFullExperiment.jobDetails"
+                  :key="jobDetail.jobId"
+                >
+                  <td>{{ jobDetail.jobName }}</td>
+                  <td>{{ jobDetail.jobId }}</td>
+                  <td>{{ jobDetail.jobStatusStateName }}</td>
+                  <td>
+                    <span :title="jobDetail.creationTime.toString()">{{ jobCreationTimes[index] }}</span>
                   </td>
                 </tr>
-                <!--  TODO: leave this out for now -->
-                <!-- <tr>
+            </table>
+            </td>
+            </tr>
+            <!--  TODO: leave this out for now -->
+            <!-- <tr>
                                     <th scope="row">Notification List</th>
                                     <td>{{ experiment.emailAddresses
                                             ? experiment.emailAddresses.join(", ")
                                             : '' }}</td>
                                 </tr> -->
-                <tr>
-                  <th scope="row">Creation Time</th>
-                  <td>
-                    <span :title="experiment.creationTime.toString()">{{ creationTime }}</span>
-                  </td>
-                </tr>
-                <tr>
-                  <th scope="row">Last Modified Time</th>
-                  <td>
-                    <span :title="localFullExperiment.experimentStatus.timeOfStateChange.toString()">{{ lastModifiedTime }}</span>
-                  </td>
-                </tr>
-                <tr>
-                  <th scope="row">Wall Time Limit</th>
-                  <td>{{ experiment.userConfigurationData.computationalResourceScheduling.wallTimeLimit }} minutes</td>
-                </tr>
-                <tr>
-                  <th scope="row">CPU Count</th>
-                  <td>{{ experiment.userConfigurationData.computationalResourceScheduling.totalCPUCount }}</td>
-                </tr>
-                <tr>
-                  <th scope="row">Node Count</th>
-                  <td>{{ experiment.userConfigurationData.computationalResourceScheduling.nodeCount }}</td>
-                </tr>
-                <tr>
-                  <th scope="row">Queue</th>
-                  <td>{{ experiment.userConfigurationData.computationalResourceScheduling.queueName }}</td>
-                </tr>
-                <tr>
-                  <th scope="row">Inputs</th>
-                  <td>
-                    <ul>
-                      <li v-for="input in experiment.experimentInputs" :key="input.name">
-                        {{ input.name }}:
-                        <template v-if="input.type.isSimpleValueType">
-                          <span class="text-break">{{ input.value }}</span>
-                        </template>
-                        <data-product-viewer v-for="dp in inputDataProducts[input.name]"
-                          v-else-if="input.type.isFileValueType"
-                          :data-product="dp" :input-file="true" class="data-product" :key="dp.productUri"/>
-                      </li>
-                    </ul>
-                  </td>
-                </tr>
-                <tr>
-                  <!-- TODO -->
-                  <th scope="row">Errors</th>
-                  <td></td>
-                </tr>
-              </tbody>
+            <tr>
+              <th scope="row">Creation Time</th>
+              <td>
+                <span :title="experiment.creationTime.toString()">{{ creationTime }}</span>
+              </td>
+            </tr>
+            <tr>
+              <th scope="row">Last Modified Time</th>
+              <td>
+                <span :title="localFullExperiment.experimentStatus.timeOfStateChange.toString()">{{ lastModifiedTime }}</span>
+              </td>
+            </tr>
+            <tr>
+              <th scope="row">Wall Time Limit</th>
+              <td>{{ experiment.userConfigurationData.computationalResourceScheduling.wallTimeLimit }} minutes</td>
+            </tr>
+            <tr>
+              <th scope="row">CPU Count</th>
+              <td>{{ experiment.userConfigurationData.computationalResourceScheduling.totalCPUCount }}</td>
+            </tr>
+            <tr>
+              <th scope="row">Node Count</th>
+              <td>{{ experiment.userConfigurationData.computationalResourceScheduling.nodeCount }}</td>
+            </tr>
+            <tr>
+              <th scope="row">Queue</th>
+              <td>{{ experiment.userConfigurationData.computationalResourceScheduling.queueName }}</td>
+            </tr>
+            <tr>
+              <th scope="row">Inputs</th>
+              <td>
+                <ul>
+                  <li
+                    v-for="input in experiment.experimentInputs"
+                    :key="input.name"
+                  >
+                    {{ input.name }}:
+                    <template v-if="input.type.isSimpleValueType">
+                      <span class="text-break">{{ input.value }}</span>
+                    </template>
+                    <data-product-viewer
+                      v-for="dp in inputDataProducts[input.name]"
+                      v-else-if="input.type.isFileValueType"
+                      :data-product="dp"
+                      :input-file="true"
+                      class="data-product"
+                      :key="dp.productUri"
+                    />
+                  </li>
+                </ul>
+              </td>
+            </tr>
+            <tr>
+              <!-- TODO -->
+              <th scope="row">Errors</th>
+              <td></td>
+            </tr>
+            </tbody>
             </table>
           </div>
         </div>
@@ -186,6 +223,7 @@
 import { models, services } from "django-airavata-api";
 import { components } from "django-airavata-common-ui";
 import DataProductViewer from "./DataProductViewer.vue";
+import OutputDisplayContainer from "./output-displays/OutputDisplayContainer";
 import urls from "../../utils/urls";
 
 import moment from "moment";
@@ -203,33 +241,46 @@ export default {
     }
   },
   data() {
-
     return {
       localFullExperiment: this.fullExperiment.clone()
     };
-
   },
   components: {
     DataProductViewer,
     "clipboard-copy-link": components.ClipboardCopyLink,
-    "share-button": components.ShareButton
+    "share-button": components.ShareButton,
+    OutputDisplayContainer
   },
   computed: {
     inputDataProducts() {
       const result = {};
-      if (this.localFullExperiment && this.localFullExperiment.inputDataProducts) {
+      if (
+        this.localFullExperiment &&
+        this.localFullExperiment.inputDataProducts
+      ) {
         this.localFullExperiment.experiment.experimentInputs.forEach(input => {
-          result[input.name] = this.getDataProducts(input, this.localFullExperiment.inputDataProducts);
+          result[input.name] = this.getDataProducts(
+            input,
+            this.localFullExperiment.inputDataProducts
+          );
         });
       }
       return result;
     },
     outputDataProducts() {
       const result = {};
-      if (this.localFullExperiment && this.localFullExperiment.outputDataProducts) {
-        this.localFullExperiment.experiment.experimentOutputs.forEach(output => {
-          result[output.name] = this.getDataProducts(output, this.localFullExperiment.outputDataProducts);
-        });
+      if (
+        this.localFullExperiment &&
+        this.localFullExperiment.outputDataProducts
+      ) {
+        this.localFullExperiment.experiment.experimentOutputs.forEach(
+          output => {
+            result[output.name] = this.getDataProducts(
+              output,
+              this.localFullExperiment.outputDataProducts
+            );
+          }
+        );
       }
       return result;
     },
@@ -253,14 +304,16 @@ export default {
       return urls.editExperiment(this.experiment);
     },
     isEditable() {
-      return this.experiment.isEditable && this.localFullExperiment.applicationName;
+      return (
+        this.experiment.isEditable && this.localFullExperiment.applicationName
+      );
     },
     isClonable() {
       return this.localFullExperiment.applicationName;
     },
     storageDirLink() {
       if (this.experiment.relativeExperimentDataDir) {
-        return urls.storageDirectory(this.experiment.relativeExperimentDataDir)
+        return urls.storageDirectory(this.experiment.relativeExperimentDataDir);
       } else {
         return null;
       }
@@ -280,12 +333,14 @@ export default {
             !this.localFullExperiment.experiment.hasLaunched) ||
           this.localFullExperiment.experiment.isProgressing
         ) {
-          this.loadExperiment().then(() => {
-            setTimeout(pollExperiment.bind(this), 3000);
-          }).catch(() => {
-            // Wait 30 seconds after an error and then try again
-            setTimeout(pollExperiment.bind(this), 30000);
-          });
+          this.loadExperiment()
+            .then(() => {
+              setTimeout(pollExperiment.bind(this), 3000);
+            })
+            .catch(() => {
+              // Wait 30 seconds after an error and then try again
+              setTimeout(pollExperiment.bind(this), 30000);
+            });
         }
       }.bind(this);
       setTimeout(pollExperiment, 3000);
@@ -295,7 +350,7 @@ export default {
         lookup: this.experiment.experimentId
       }).then(clonedExperiment => {
         urls.navigateToEditExperiment(clonedExperiment);
-      })
+      });
     },
     getDataProducts(io, collection) {
       if (!io.value || !collection) {
@@ -303,13 +358,17 @@ export default {
       }
       let dataProducts = null;
       if (io.type === models.DataType.URI_COLLECTION) {
-        const dataProductURIs = io.value.split(',');
-        dataProducts = dataProductURIs.map(uri => collection.find(dp => dp.productUri === uri));
+        const dataProductURIs = io.value.split(",");
+        dataProducts = dataProductURIs.map(uri =>
+          collection.find(dp => dp.productUri === uri)
+        );
       } else {
         const dataProductURI = io.value;
-        dataProducts = collection.filter(dp => dp.productUri === dataProductURI);
+        dataProducts = collection.filter(
+          dp => dp.productUri === dataProductURI
+        );
       }
-      return dataProducts ? dataProducts.filter(dp => dp ? true : false) : [];
+      return dataProducts ? dataProducts.filter(dp => (dp ? true : false)) : [];
     }
   },
   watch: {},
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/DownloadOutputDisplay.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/DownloadOutputDisplay.vue
new file mode 100644
index 0000000..51657c5
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/DownloadOutputDisplay.vue
@@ -0,0 +1,33 @@
+<template>
+  <div>
+    <data-product-viewer v-for="dp in dataProducts"
+      :data-product="dp" class="data-product" :key="dp.productUri"/>
+  </div>
+</template>
+
+<script>
+import { models } from "django-airavata-api"
+import DataProductViewer from "../DataProductViewer.vue";
+
+export default {
+  name: "download-output-viewer",
+  props: {
+    experimentOutput: {
+      type: models.OutputDataObjectType,
+      required: true
+    },
+    dataProducts: {
+      type: Array,
+      required: true
+    },
+    data: {
+      type: Object
+    }
+  },
+  components: {
+    DataProductViewer
+  }
+}
+</script>
+
+
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/LinkDisplay.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/LinkDisplay.vue
new file mode 100644
index 0000000..7afd4ca
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/LinkDisplay.vue
@@ -0,0 +1,26 @@
+<template>
+  <a :href="data.url">{{ data.label }}</a>
+</template>
+
+<script>
+import { models } from "django-airavata-api"
+
+export default {
+  name: "link-viewer",
+  props: {
+    experimentOutput: {
+      type: models.OutputDataObjectType,
+      required: true
+    },
+    dataProducts: {
+      type: Array,
+      required: true
+    },
+    data: {
+      type: Object
+    }
+  },
+}
+</script>
+
+
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/OutputDisplayContainer.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/OutputDisplayContainer.vue
new file mode 100644
index 0000000..3b4b2ea
--- /dev/null
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/output-displays/OutputDisplayContainer.vue
@@ -0,0 +1,69 @@
+<template>
+  <!-- TODO: Add menu when there are more than one outputViews -->
+  <b-card :title="experimentOutput.name">
+    <component
+      :is="outputDisplayComponentName"
+      :experiment-output="experimentOutput"
+      :data-products="dataProducts"
+      :data="outputViewData"
+    />
+  </b-card>
+</template>
+
+<script>
+import { models } from "django-airavata-api";
+import DownloadOutputDisplay from "./DownloadOutputDisplay";
+import LinkDisplay from "./LinkDisplay";
+import DataProductViewer from "../DataProductViewer";
+
+export default {
+  name: "output-viewer-container",
+  props: {
+    experimentOutput: {
+      type: models.OutputDataObjectType,
+      required: true
+    },
+    outputViews: {
+      type: Array,
+      required: true
+    },
+    dataProducts: {
+      type: Array,
+      required: false,
+      default: null
+    }
+  },
+  components: {
+    DataProductViewer,
+    DownloadOutputDisplay,
+    LinkDisplay
+  },
+  computed: {
+    // TODO: support multiple output views
+    outputViewData() {
+      return this.outputView ? this.outputView["data"] : null;
+    },
+    outputDisplayComponentName() {
+      if (this.outputView) {
+        if (this.outputView["display-type"] === "download") {
+          return "download-output-display";
+        } else if (this.outputView["display-type"] === "link") {
+          return "link-display";
+        } else {
+          return null;
+        }
+      } else {
+        return null;
+      }
+    },
+    outputView() {
+      if (this.outputViews && this.outputViews.length > 0) {
+        return this.outputViews[0];
+      } else {
+        return null;
+      }
+    }
+  }
+};
+</script>
+
diff --git a/django_airavata/settings.py b/django_airavata/settings.py
index 041b594..1c528c6 100644
--- a/django_airavata/settings.py
+++ b/django_airavata/settings.py
@@ -100,6 +100,10 @@ for entry_point in iter_entry_points(group='airavata.djangoapp'):
     INSTALLED_APPS.append("{}.{}".format(entry_point.module_name,
                                          entry_point.attrs[0]))
 
+OUTPUT_VIEW_PROVIDERS = {}
+for entry_point in iter_entry_points(group='airavata.output_view_providers'):
+    OUTPUT_VIEW_PROVIDERS[entry_point.name] = entry_point.load()
+
 MIDDLEWARE = [
     'django.middleware.security.SecurityMiddleware',
     'django.contrib.sessions.middleware.SessionMiddleware',