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 2018/01/11 16:18:47 UTC

[airavata-django-portal] branch master updated (08ef4ae -> d0b50f3)

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 08ef4ae  AIRAVATA-2598 Implement saving experiment
     new a103b6e  AIRAVATA-2598 Launch an experiment
     new 8615c84  AIRAVATA-2598 Display validation of required inputs
     new 26dc27f  AIRAVATA-2598 Disable save when app inputs are invalid
     new d0b50f3  AIRAVATA-2598 Allow optional CharFields to be null or blank

The 4 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:
 .../django_airavata_api/js/models/BaseModel.js     |  4 ++
 .../models/ComputationalResourceSchedulingModel.js | 30 ++--------
 .../django_airavata_api/js/models/Experiment.js    | 24 ++++++--
 .../js/models/InputDataObjectType.js               | 26 +++------
 .../django_airavata_api/js/models/Project.js       |  2 +-
 .../js/models/UserConfigurationData.js             | 18 +-----
 .../js/services/ApplicationInterfaceService.js     |  2 +-
 .../js/services/ExperimentService.js               | 30 +++++++++-
 .../django_airavata_api/js/utils/FetchUtils.js     | 23 +++++++-
 django_airavata/apps/api/thrift_utils.py           |  6 +-
 django_airavata/apps/api/views.py                  |  8 ++-
 .../js/components/experiment/ExperimentEditor.vue  | 68 +++++++++++++++-------
 django_airavata/settings_local.py.sample           |  1 +
 13 files changed, 147 insertions(+), 95 deletions(-)

-- 
To stop receiving notification emails like this one, please contact
['"commits@airavata.apache.org" <co...@airavata.apache.org>'].

[airavata-django-portal] 01/04: AIRAVATA-2598 Launch an experiment

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 a103b6e03b2a3751c14a99255d1079f85883ef4f
Author: Marcus Christie <ma...@iu.edu>
AuthorDate: Wed Jan 10 12:05:54 2018 -0500

    AIRAVATA-2598 Launch an experiment
---
 .../js/services/ApplicationInterfaceService.js     |  2 +-
 .../js/services/ExperimentService.js               | 30 ++++++++++++++++--
 .../django_airavata_api/js/utils/FetchUtils.js     | 23 +++++++++++++-
 django_airavata/apps/api/thrift_utils.py           | 12 +++++++
 django_airavata/apps/api/views.py                  |  8 +++--
 .../js/components/experiment/ExperimentEditor.vue  | 37 ++++++++++++----------
 django_airavata/settings_local.py.sample           |  1 +
 7 files changed, 89 insertions(+), 24 deletions(-)

diff --git a/django_airavata/apps/api/static/django_airavata_api/js/services/ApplicationInterfaceService.js b/django_airavata/apps/api/static/django_airavata_api/js/services/ApplicationInterfaceService.js
index f0eeb0c..796c666 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/services/ApplicationInterfaceService.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/services/ApplicationInterfaceService.js
@@ -25,7 +25,7 @@ class ApplicationIterfaceService {
     }
 
     getComputeResources(appInterfaceId) {
-        return FetchUtils.get('/api/application-interfaces/' + encodeURIComponent(appInterfaceId) + '/compute_resources');
+        return FetchUtils.get('/api/application-interfaces/' + encodeURIComponent(appInterfaceId) + '/compute_resources/');
     }
 }
 
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/services/ExperimentService.js b/django_airavata/apps/api/static/django_airavata_api/js/services/ExperimentService.js
index b840b86..0aa81b5 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/services/ExperimentService.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/services/ExperimentService.js
@@ -17,12 +17,36 @@ class ExperimentService {
             .then(result => new Experiment(result));
     }
 
-    update() {
-        // TODO
+    update(experiment) {
+        return FetchUtils.put('/api/experiments/'
+                + encodeURIComponent(experiment.experimentId) + '/',
+                JSON.stringify(experiment))
+            .then(result => new Experiment(result));
+    }
+
+    save(experiment) {
+        if (experiment.experimentId) {
+            return this.update(experiment);
+        } else {
+            return this.create(experiment);
+        }
     }
 
     get() {
-        // TODO
+        return FetchUtils.get('/api/experiments/'
+                + encodeURIComponent(experiment.experimentId) + '/')
+            .then(result => new Experiment(result));
+    }
+
+    launch(experimentId) {
+        return FetchUtils.post('/api/experiments/' + encodeURIComponent(experimentId) + '/launch/')
+            .then(result => {
+                if (result.success) {
+                    return Promise.resolve(result);
+                } else {
+                    return Promise.reject(result);
+                }
+            });
     }
 }
 
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/utils/FetchUtils.js b/django_airavata/apps/api/static/django_airavata_api/js/utils/FetchUtils.js
index 8e09498..074e4ea 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/utils/FetchUtils.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/utils/FetchUtils.js
@@ -38,11 +38,32 @@ export default {
             }
         })
     },
+    put: function (url, body, mediaType = "application/json") {
+        var headers = this.createHeaders(mediaType)
+        return fetch(url, {
+            method: 'put',
+            body: typeof body !== 'string' ? JSON.stringify(body) : body,
+            headers: headers,
+            credentials: "same-origin"
+        }).then((response) => {
+            if (response.ok) {
+                return Promise.resolve(response.json())
+            } else {
+                let error = new Error(response.statusText);
+                return response.json().then(json => {
+                    error.data = json;
+                })
+                .then(() => Promise.reject(error),() => Promise.reject(error));
+            }
+        })
+    },
     get: function (url, queryParams = "", mediaType = "application/json") {
         if (queryParams && typeof(queryParams) != "string") {
             queryParams = Object.keys(queryParams).map(key => encodeURIComponent(key) + "=" + encodeURIComponent(queryParams[key])).join("&")
         }
-        url=url+"?"+queryParams
+        if (queryParams) {
+            url=url+"?"+queryParams
+        }
         var headers = this.createHeaders(mediaType)
         return fetch(url, {
             method: 'get',
diff --git a/django_airavata/apps/api/thrift_utils.py b/django_airavata/apps/api/thrift_utils.py
index 476cfbf..363a016 100644
--- a/django_airavata/apps/api/thrift_utils.py
+++ b/django_airavata/apps/api/thrift_utils.py
@@ -67,8 +67,20 @@ def create_serializer_class(thrift_data_type):
                     params[field_name] = serializer.create(params[field_name])
             return params
 
+        def process_empty_char_fields(self, validated_data):
+            """Convert empty CharFields to None."""
+            fields = self.fields
+            params = copy.deepcopy(validated_data)
+            for field_name, serializer in fields.items():
+                if isinstance(serializer, CharField) \
+                        and params.get(field_name, None) is not None \
+                        and params[field_name].strip() == '':
+                    params[field_name] = None
+            return params
+
         def create(self, validated_data):
             params = self.process_nested_fields(validated_data)
+            params = self.process_empty_char_fields(params)
             return thrift_data_type(**params)
 
         def update(self, instance, validated_data):
diff --git a/django_airavata/apps/api/views.py b/django_airavata/apps/api/views.py
index dcada13..7d2f6ed 100644
--- a/django_airavata/apps/api/views.py
+++ b/django_airavata/apps/api/views.py
@@ -239,6 +239,7 @@ class ExperimentViewSet(APIBackedViewSet):
 
     def perform_create(self, serializer):
         experiment = serializer.save()
+        experiment.userConfigurationData.storageId = settings.GATEWAY_DATA_STORE_RESOURCE_ID
         experiment_id = self.request.airavata_client.createExperiment(self.authz_token, self.gateway_id, experiment)
         experiment.experimentId = experiment_id
 
@@ -248,8 +249,11 @@ class ExperimentViewSet(APIBackedViewSet):
 
     @detail_route(methods=['post'])
     def launch(self, request, experiment_id=None):
-        request.airavata_client.launchExperiment(request.authz_token, experiment_id, self.gateway_id)
-        return Response({'success': True})
+        try:
+            request.airavata_client.launchExperiment(request.authz_token, experiment_id, self.gateway_id)
+            return Response({'success': True})
+        except Exception as e:
+            return Response({'success': False, 'errorMessage': e.message})
 
 
 class ApplicationModuleViewSet(APIBackedViewSet):
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentEditor.vue
index 8ec6543..76040ca 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentEditor.vue
@@ -126,27 +126,30 @@ export default {
             console.log(JSON.stringify(this.localExperiment));
             // TODO: validate experiment
             // save experiment
-            if (this.localExperiment.experimentId) {
-                services.ExperimentService.update(this.localExperiment)
-                    .then(experiment => {
-                        console.log(experiment);
-                        this.$emit('saved', experiment);
-                    });
-            } else {
-                services.ExperimentService.create(this.localExperiment)
-                    .then(experiment => {
-                        this.localExperiment.experimentId = experiment.experimentId;
-                        console.log(experiment);
-                        this.$emit('saved', experiment);
-                    });
-            }
+            services.ExperimentService.save(this.localExperiment)
+                .then(experiment => {
+                    this.localExperiment = experiment;
+                    console.log(experiment);
+                    alert('Experiment saved!');
+                    this.$emit('saved', experiment);
+                });
         },
         saveAndLaunchExperiment: function() {
             console.log(JSON.stringify(this.localExperiment));
             // TODO: validate experiment
-            // TODO: save experiment
-            // TODO: set the experiment ID on the new experiment
-            // TODO: dispatch save event with updated experiment
+            let savedExperiment = null;
+            services.ExperimentService.save(this.localExperiment)
+                .then(experiment => {
+                    this.localExperiment = experiment;
+                    return services.ExperimentService.launch(experiment.experimentId)
+                        .then(result => {
+                            alert('Experiment launched!');
+                            this.$emit('savedAndLaunched', experiment);
+                        });
+                    })
+                .catch(result => {
+                    console.log("Launch failed!", result);
+                });
         },
     },
     watch: {
diff --git a/django_airavata/settings_local.py.sample b/django_airavata/settings_local.py.sample
index c847179..30037c3 100644
--- a/django_airavata/settings_local.py.sample
+++ b/django_airavata/settings_local.py.sample
@@ -32,6 +32,7 @@ GATEWAY_ID = 'default'
 AIRAVATA_API_HOST = 'localhost'
 AIRAVATA_API_PORT = 8930
 AIRAVATA_API_SECURE = False
+GATEWAY_DATA_STORE_RESOURCE_ID = '...'
 
 # Sharing API Configuration
 SHARING_API_HOST = 'localhost'

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-django-portal] 03/04: AIRAVATA-2598 Disable save when app inputs are invalid

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 26dc27f1b81830d09fd8de935622d54f71f56c44
Author: Marcus Christie <ma...@iu.edu>
AuthorDate: Thu Jan 11 11:03:28 2018 -0500

    AIRAVATA-2598 Disable save when app inputs are invalid
---
 .../static/django_airavata_api/js/models/Experiment.js | 18 ++++++++++++++++++
 .../js/components/experiment/ExperimentEditor.vue      | 11 +++++++----
 2 files changed, 25 insertions(+), 4 deletions(-)

diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/Experiment.js b/django_airavata/apps/api/static/django_airavata_api/js/models/Experiment.js
index 89404c8..d635a5f 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/Experiment.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/Experiment.js
@@ -74,4 +74,22 @@ export default class Experiment extends BaseModel {
     constructor(data = {}) {
         super(FIELDS, data);
     }
+
+    validate() {
+        let validationResults = {};
+        let experimentInputsValidation = this.experimentInputs
+            .map(experimentInput => {
+                const validation = experimentInput.validate();
+                if (validation && 'value' in validation) {
+                    return {[experimentInput.name]: validation};
+                } else {
+                    return null;
+                }
+            })
+            .reduce((accumulator, currentValue) => Object.assign(accumulator, currentValue), {});
+        if (Object.keys(experimentInputsValidation).length > 0) {
+            validationResults['experimentInputs'] = experimentInputsValidation;
+        }
+        return validationResults;
+    }
 }
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentEditor.vue
index 77cbc00..e2f5581 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentEditor.vue
@@ -69,10 +69,10 @@
         </div>
         <div class="row">
             <div id="col-exp-buttons" class="col">
-                <b-button variant="success" @click="saveAndLaunchExperiment">
+                <b-button variant="success" @click="saveAndLaunchExperiment" :disabled="isSaveDisabled">
                     Save and Launch
                 </b-button>
-                <b-button variant="primary" @click="saveExperiment">
+                <b-button variant="primary" @click="saveExperiment" :disabled="isSaveDisabled">
                     Save
                 </b-button>
             </div>
@@ -87,7 +87,6 @@ import {models, services} from 'django-airavata-api'
 
 export default {
     name: 'edit-experiment',
-    // TODO: clone experiment instead of editing it directly
     props: {
         experiment: {
             type: models.Experiment,
@@ -123,6 +122,10 @@ export default {
                 text: project.name,
             }));
         },
+        isSaveDisabled: function() {
+            const validation = this.localExperiment.validate();
+            return Object.keys(validation).length > 0;
+        },
     },
     methods: {
         saveExperiment: function() {
@@ -160,7 +163,7 @@ export default {
         },
         getApplicationInputFeedback: function(applicationInput) {
             const validation = this.getApplicationInputValidation(applicationInput);
-            return validation !== null ? validation : null;
+            return validation !== null ? validation['value'] : null;
         },
         getApplicationInputValidation: function(applicationInput) {
             const validationResults = applicationInput.validate();

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-django-portal] 04/04: AIRAVATA-2598 Allow optional CharFields to be null or blank

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 d0b50f31d5e1291c094fcc4ac9cf50bb5c86f2fb
Author: Marcus Christie <ma...@iu.edu>
AuthorDate: Thu Jan 11 11:17:49 2018 -0500

    AIRAVATA-2598 Allow optional CharFields to be null or blank
---
 .../models/ComputationalResourceSchedulingModel.js | 30 ++++------------------
 .../django_airavata_api/js/models/Experiment.js    |  6 +----
 .../js/models/InputDataObjectType.js               | 20 ++-------------
 .../js/models/UserConfigurationData.js             | 18 +++----------
 django_airavata/apps/api/thrift_utils.py           | 18 +++----------
 5 files changed, 14 insertions(+), 78 deletions(-)

diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/ComputationalResourceSchedulingModel.js b/django_airavata/apps/api/static/django_airavata_api/js/models/ComputationalResourceSchedulingModel.js
index 03c4782..f9cc61f 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/ComputationalResourceSchedulingModel.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/ComputationalResourceSchedulingModel.js
@@ -8,31 +8,11 @@ const FIELDS = [
     'queueName',
     'wallTimeLimit',
     'totalPhysicalMemory',
-    {
-        name: 'chessisNumber',
-        type: 'string',
-        default: '',
-    },
-    {
-        name: 'staticWorkingDir',
-        type: 'string',
-        default: '',
-    },
-    {
-        name: 'overrideLoginUserName',
-        type: 'string',
-        default: '',
-    },
-    {
-        name: 'overrideScratchLocation',
-        type: 'string',
-        default: '',
-    },
-    {
-        name: 'overrideAllocationProjectNumber',
-        type: 'string',
-        default: '',
-    },
+    'chessisNumber',
+    'staticWorkingDir',
+    'overrideLoginUserName',
+    'overrideScratchLocation',
+    'overrideAllocationProjectNumber',
 ];
 
 export default class ComputationalResourceSchedulingModel extends BaseModel {
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/Experiment.js b/django_airavata/apps/api/static/django_airavata_api/js/models/Experiment.js
index d635a5f..6066c3a 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/Experiment.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/Experiment.js
@@ -22,11 +22,7 @@ const FIELDS = [
         name: 'creationTime',
         type: 'date'
     },
-    {
-        name: 'description',
-        type: 'string',
-        default: '',
-    },
+    'description',
     'executionId',
     {
         name: 'enableEmailNotification',
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/InputDataObjectType.js b/django_airavata/apps/api/static/django_airavata_api/js/models/InputDataObjectType.js
index 496de53..5969555 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/InputDataObjectType.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/InputDataObjectType.js
@@ -8,34 +8,18 @@ const FIELDS = [
     'applicationArgument',
     'standardInput',
     'userFriendlyDescription',
-    {
-        name: 'metaData',
-        type: 'string',
-        default: '',
-    },
+    'metaData',
     'inputOrder',
     'isRequired',
     'requiredToAddedToCommandLine',
     'dataStaged',
-    {
-        name: 'storageResourceId',
-        type: 'string',
-        default: '',
-    },
+    'storageResourceId',
     'isReadOnly',
 ];
 
 export default class InputDataObjectType extends BaseModel {
     constructor(data = {}) {
         super(FIELDS, data);
-        // TODO: move into BaseModel
-        // Convert null strings into empty strings
-        if ('metaData' in this && this.metaData === null) {
-            this.metaData = '';
-        }
-        if ('storageResourceId' in this && this.storageResourceId === null) {
-            this.storageResourceId = '';
-        }
     }
 
     validate() {
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/UserConfigurationData.js b/django_airavata/apps/api/static/django_airavata_api/js/models/UserConfigurationData.js
index f70ffdf..244d3fb 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/UserConfigurationData.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/UserConfigurationData.js
@@ -27,26 +27,14 @@ const FIELDS = [
         type: 'boolean',
         default: false,
     },
-    {
-        name: 'userDN',
-        type: 'string',
-        default: '',
-    },
+    'userDN',
     {
         name: 'generateCert',
         type: 'boolean',
         default: false,
     },
-    {
-        name: 'storageId',
-        type: 'string',
-        default: '',
-    },
-    {
-        name: 'experimentDataDir',
-        type: 'string',
-        default: '',
-    },
+    'storageId',
+    'experimentDataDir',
     {
         name: 'useUserCRPref',
         type: 'boolean',
diff --git a/django_airavata/apps/api/thrift_utils.py b/django_airavata/apps/api/thrift_utils.py
index 363a016..30ab400 100644
--- a/django_airavata/apps/api/thrift_utils.py
+++ b/django_airavata/apps/api/thrift_utils.py
@@ -67,20 +67,8 @@ def create_serializer_class(thrift_data_type):
                     params[field_name] = serializer.create(params[field_name])
             return params
 
-        def process_empty_char_fields(self, validated_data):
-            """Convert empty CharFields to None."""
-            fields = self.fields
-            params = copy.deepcopy(validated_data)
-            for field_name, serializer in fields.items():
-                if isinstance(serializer, CharField) \
-                        and params.get(field_name, None) is not None \
-                        and params[field_name].strip() == '':
-                    params[field_name] = None
-            return params
-
         def create(self, validated_data):
             params = self.process_nested_fields(validated_data)
-            params = self.process_empty_char_fields(params)
             return thrift_data_type(**params)
 
         def update(self, instance, validated_data):
@@ -103,10 +91,10 @@ def process_field(field, required=False, read_only=False, allow_null=False):
         # mapping
         field_class = mapping[field[1]]
         kwargs = dict(required=required, read_only=read_only)
-        # allow_null isn't allowed for BooleanField and we'll use allow_blank
-        # for CharField
-        if field_class not in (BooleanField, CharField):
+        # allow_null isn't allowed for BooleanField
+        if field_class not in (BooleanField,):
             kwargs['allow_null'] = allow_null
+        # allow_null CharField are also allowed to be blank
         if field_class == CharField:
             kwargs['allow_blank'] = allow_null
         return mapping[field[1]](**kwargs)

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.

[airavata-django-portal] 02/04: AIRAVATA-2598 Display validation of required inputs

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 8615c84ddc84affd5479c8e30191a3b15ab9b2d3
Author: Marcus Christie <ma...@iu.edu>
AuthorDate: Thu Jan 11 10:41:24 2018 -0500

    AIRAVATA-2598 Display validation of required inputs
---
 .../django_airavata_api/js/models/BaseModel.js     |  4 ++++
 .../js/models/InputDataObjectType.js               |  8 ++++++++
 .../django_airavata_api/js/models/Project.js       |  2 +-
 .../js/components/experiment/ExperimentEditor.vue  | 22 ++++++++++++++++++++--
 4 files changed, 33 insertions(+), 3 deletions(-)

diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/BaseModel.js b/django_airavata/apps/api/static/django_airavata_api/js/models/BaseModel.js
index 9745c90..49f773b 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/BaseModel.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/BaseModel.js
@@ -75,6 +75,10 @@ export default class BaseModel {
         return null;
     }
 
+    isEmpty(value) {
+        return value === null || (typeof value === 'string' && value.trim() === '');
+    }
+
     /**
      * Return a fully deep cloned instance of this instance.
      */
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/InputDataObjectType.js b/django_airavata/apps/api/static/django_airavata_api/js/models/InputDataObjectType.js
index cf3e349..496de53 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/InputDataObjectType.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/InputDataObjectType.js
@@ -37,4 +37,12 @@ export default class InputDataObjectType extends BaseModel {
             this.storageResourceId = '';
         }
     }
+
+    validate() {
+        let results = {};
+        if (this.isRequired && this.isEmpty(this.value)) {
+            results['value'] = 'This field is required.';
+        }
+        return results;
+    }
 }
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/Project.js b/django_airavata/apps/api/static/django_airavata_api/js/models/Project.js
index d6e9c99..59bc6be 100644
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/Project.js
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/Project.js
@@ -18,7 +18,7 @@ export default class Project extends BaseModel {
     }
 
     validate() {
-        if (this.name === null || this.name.trim() === "") {
+        if (this.isEmpty(this.name)) {
             return {
                 name: ["Please provide a name."]
             }
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentEditor.vue
index 76040ca..77cbc00 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/ExperimentEditor.vue
@@ -47,9 +47,12 @@
                         </h2>
                         <b-form novalidate>
                             <b-form-group v-for="experimentInput in localExperiment.experimentInputs"
-                                    :label="experimentInput.name" :label-for="experimentInput.name" :key="experimentInput.name">
+                                    :label="experimentInput.name" :label-for="experimentInput.name" :key="experimentInput.name"
+                                    :feedback="getApplicationInputFeedback(experimentInput)"
+                                    :state="getApplicationInputState(experimentInput)">
                                 <b-form-input :id="experimentInput.name" type="text" v-model="experimentInput.value" required
-                                    :placeholder="experimentInput.userFriendlyDescription"></b-form-input>
+                                    :placeholder="experimentInput.userFriendlyDescription"
+                                    :state="getApplicationInputState(experimentInput)"></b-form-input>
                             </b-form-group>
                         </b-form>
                         <h2 class="h5 mb-3">
@@ -151,6 +154,21 @@ export default {
                     console.log("Launch failed!", result);
                 });
         },
+        getApplicationInputState: function(applicationInput) {
+            const validation = this.getApplicationInputValidation(applicationInput);
+            return validation !== null ? 'invalid' : null;
+        },
+        getApplicationInputFeedback: function(applicationInput) {
+            const validation = this.getApplicationInputValidation(applicationInput);
+            return validation !== null ? validation : null;
+        },
+        getApplicationInputValidation: function(applicationInput) {
+            const validationResults = applicationInput.validate();
+            if (validationResults !== null && 'value' in validationResults) {
+                return validationResults;
+            }
+            return null;
+        }
     },
     watch: {
         experiment: function(newValue) {

-- 
To stop receiving notification emails like this one, please contact
"commits@airavata.apache.org" <co...@airavata.apache.org>.