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/12/20 21:47:19 UTC

[airavata-django-portal] branch airavata-2761 updated (8ba5b9c -> a8d6637)

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

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


 discard 8ba5b9c  WIP
     new 25f35f6  AIRAVATA-2761 Initial implementation of input dependencies
     new a8d6637  AIRAVATA-2761 Unit tests for boolean expression evaluation

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (8ba5b9c)
            \
             N -- N -- N   refs/heads/airavata-2761 (a8d6637)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 2 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:
 .travis.yml                                        |   1 +
 django_airavata/apps/api/package.json              |   7 +-
 .../django_airavata_api/js/models/Experiment.js    |  23 ++-
 .../js/models/InputDataObjectType.js               | 146 +++++++++-----
 .../dependencies/BooleanExpressionEvaluator.js     | 101 ++++++++++
 .../js/models/dependencies/DependencyEvaluator.js  |  14 --
 .../BooleanExpressionEvaluator.test.js             | 213 +++++++++++++++++++++
 .../js/input-editors/InputEditorMixin.js           |  18 +-
 .../js/components/experiment/ExperimentEditor.vue  |   8 +-
 .../input-editors/InputEditorContainer.vue         |   7 +-
 .../experiment/input-editors/StringInputEditor.vue |   3 +-
 11 files changed, 458 insertions(+), 83 deletions(-)
 create mode 100644 django_airavata/apps/api/static/django_airavata_api/js/models/dependencies/BooleanExpressionEvaluator.js
 delete mode 100644 django_airavata/apps/api/static/django_airavata_api/js/models/dependencies/DependencyEvaluator.js
 create mode 100644 django_airavata/apps/api/static/django_airavata_api/tests/models/dependencies/BooleanExpressionEvaluator.test.js


[airavata-django-portal] 02/02: AIRAVATA-2761 Unit tests for boolean expression evaluation

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

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

commit a8d663700ee4c6b85cdec6c78b17a237ad4584c0
Author: Marcus Christie <ma...@iu.edu>
AuthorDate: Thu Dec 20 16:47:00 2018 -0500

    AIRAVATA-2761 Unit tests for boolean expression evaluation
---
 .travis.yml                                        |   1 +
 django_airavata/apps/api/package.json              |   7 +-
 .../js/models/InputDataObjectType.js               |  13 +-
 .../dependencies/BooleanExpressionEvaluator.js     | 101 ++++++++++
 .../js/models/dependencies/DependencyEvaluator.js  |  94 ---------
 .../BooleanExpressionEvaluator.test.js             | 213 +++++++++++++++++++++
 6 files changed, 326 insertions(+), 103 deletions(-)

diff --git a/.travis.yml b/.travis.yml
index c628f90..2939209 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -10,3 +10,4 @@ script:
   # For now ignore long line endings
   - flake8 --ignore=E501 .
   - ./build_js.sh
+  - cd django_airavata/apps/api && npm run test
diff --git a/django_airavata/apps/api/package.json b/django_airavata/apps/api/package.json
index a595c69..6009c29 100644
--- a/django_airavata/apps/api/package.json
+++ b/django_airavata/apps/api/package.json
@@ -9,7 +9,9 @@
   "repository": "https://github.com/apache/airavata-django-portal",
   "scripts": {
     "build": "babel static/django_airavata_api/js -d static/django_airavata_api/dist",
-    "watch": "babel static/django_airavata_api/js --watch -d static/django_airavata_api/dist"
+    "watch": "babel static/django_airavata_api/js --watch -d static/django_airavata_api/dist",
+    "test": "jest",
+    "test:watch": "jest --watch"
   },
   "dependencies": {
     "babel-runtime": "^6.26.0",
@@ -19,6 +21,7 @@
   "devDependencies": {
     "babel-cli": "^6.26.0",
     "babel-plugin-transform-runtime": "^6.23.0",
-    "babel-preset-env": "^1.7.0"
+    "babel-preset-env": "^1.7.0",
+    "jest": "^23.6.0"
   }
 }
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 f3b9867..2a15279 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
@@ -1,6 +1,6 @@
 import BaseModel from "./BaseModel";
 import DataType from "./DataType";
-import DependencyEvaluator from "./dependencies/DependencyEvaluator";
+import BooleanExpressionEvaluator from "./dependencies/BooleanExpressionEvaluator";
 import uuidv4 from "uuid/v4";
 import ValidatorFactory from "./validators/ValidatorFactory";
 
@@ -219,14 +219,13 @@ export default class InputDataObjectType extends BaseModel {
    */
   evaluateDependencies(inputValues) {
     if (Object.keys(this.editorDependencies).length > 0) {
-      const dependencyEvaluator = new DependencyEvaluator();
-      const dependencyEvaluations = dependencyEvaluator.evaluateDependencies(
-        inputValues,
-        this.editorDependencies
-      );
+      const booleanExpressionEvaluator = new BooleanExpressionEvaluator();
       // TODO: if show changes to false, set value to null? Save off current value to restore when shown again?
       if ("show" in dependencyEvaluations) {
-        this.show = dependencyEvaluations["show"];
+        this.show = booleanExpressionEvaluator.evaluate(
+          dependencyEvaluations.show,
+          inputValues
+        );
       }
     }
   }
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/dependencies/BooleanExpressionEvaluator.js b/django_airavata/apps/api/static/django_airavata_api/js/models/dependencies/BooleanExpressionEvaluator.js
new file mode 100644
index 0000000..1962d80
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/dependencies/BooleanExpressionEvaluator.js
@@ -0,0 +1,101 @@
+export default class BooleanExpressionEvaluator {
+
+  /**
+   * Context to use for looking up values of variables in expressions.
+   * @param {object} context
+   */
+  constructor(context) {
+    this.context = context;
+  }
+  /**
+   * Evaluates boolean expression and returns boolean result.
+   * @param {object} expression
+   */
+  evaluate(expression) {
+    const keys = Object.keys(expression);
+    if (keys.length > 1) {
+      // Implicitly AND together several expressions
+      return this.evaluate(
+        {
+          AND: keys.map(k => {
+            const exp = {};
+            exp[k] = expression[k];
+            return exp;
+          })
+        }
+      );
+    }
+    if (keys.length < 1) {
+      throw new Error(
+        "Expression does not contain a key: " + JSON.stringify(expression)
+      );
+    }
+
+    const key = keys[0];
+    const value = expression[key];
+    if (key === "AND") {
+      if (value instanceof Array) {
+        const evaluations = value.map(exp => this.evaluate(exp));
+        return evaluations.reduce((acc, curr) => acc && curr);
+      } else {
+        throw new Error(
+          "Unrecognized operand value for AND: " + JSON.stringify(value)
+        );
+      }
+    } else if (key === "OR") {
+      if (value instanceof Array) {
+        const evaluations = value.map(exp => this.evaluate(exp));
+        return evaluations.reduce((acc, curr) => acc || curr);
+      } else {
+        throw new Error(
+          "Unrecognized operand value for OR: " + JSON.stringify(value)
+        );
+      }
+    } else if (key === "NOT") {
+      if (typeof value === "object" && !(value instanceof Array)) {
+        return !this.evaluate(value);
+      } else {
+        throw new Error(
+          "Unrecognized operand value for NOT: " + JSON.stringify(value)
+        );
+      }
+    }
+
+    if (typeof value === "object") {
+      if (!(key in this.context)) {
+        throw new Error(
+          "Missing context value for expression " +
+            JSON.stringify(expression) +
+            " in context " +
+            JSON.stringify(this.context)
+        );
+      }
+      const contextValue = this.context[key];
+      return this._evaluateComparison(contextValue, value);
+    }
+  }
+
+  _evaluateComparison(value, comparisonDefinition) {
+    const comparison = comparisonDefinition["comparison"];
+    if (!comparison) {
+      throw new Error(
+        "Expression definition is missing 'comparison' property: " +
+          JSON.stringify(comparisonDefinition)
+      );
+    }
+    if (comparison === "equals") {
+      return value === this._getComparisonValue(comparisonDefinition);
+    }
+    throw new Error("Unrecognized comparison " + JSON.stringify(comparison));
+  }
+
+  _getComparisonValue(comparisonDefinition) {
+    if (!("value" in comparisonDefinition)) {
+      throw new Error(
+        "Missing required 'value' property in comparison definition: " +
+          JSON.stringify(comparisonDefinition)
+      );
+    }
+    return comparisonDefinition["value"];
+  }
+}
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/dependencies/DependencyEvaluator.js b/django_airavata/apps/api/static/django_airavata_api/js/models/dependencies/DependencyEvaluator.js
deleted file mode 100644
index 0a4f04c..0000000
--- a/django_airavata/apps/api/static/django_airavata_api/js/models/dependencies/DependencyEvaluator.js
+++ /dev/null
@@ -1,94 +0,0 @@
-
-// TODO: rename: BooleanExpressionEvaluator? There's really nothing related to "app input dependencies" in here.
-export default class DependencyEvaluator {
-
-  /**
-   * Evaluate all dependency expressions and return an object with the same
-   * keys as dependenciesConfig but with the result of each expression.
-   *
-   * @param inputValues
-   * @param dependenciesConfig
-   */
-  evaluateDependencies(inputValues, dependenciesConfig) {
-
-    const result = {};
-    for (const k of Object.keys(dependenciesConfig)) {
-      result[k] = this._evaluateExpression(dependenciesConfig[k], inputValues);
-    }
-    return result;
-  }
-
-  /**
-   * Evaluates boolean expression for given context and returns boolean result.
-   */
-  _evaluateExpression(expression, context) {
-    console.log("expression: " + JSON.stringify(expression));
-    console.log("context: " + JSON.stringify(context));
-    const keys = Object.keys(expression);
-    if (keys.length > 1) {
-      throw new Error("Expression contains more than one key: " + JSON.stringify(expression));
-    }
-    if (keys.length < 1) {
-      throw new Error("Expression does not contain a key: " + JSON.stringify(expression));
-    }
-
-    const key = keys[0];
-    const value = expression[key];
-    if (key === "AND") {
-      if (value instanceof Array) {
-        const evaluations = value.map(exp => this._evaluateExpression(exp, context));
-        return evaluations.reduce((acc, curr) => acc && curr);
-      } else if (typeof value === 'object') {
-        return this._evaluateExpression(exp, context);
-      } else {
-        throw new Error("Unrecognized operand value for AND: " + JSON.stringify(value));
-      }
-    } else if (key === "OR") {
-      if (value instanceof Array) {
-        const evaluations = value.map(exp => this._evaluateExpression(exp, context));
-        return evaluations.reduce((acc, curr) => acc || curr);
-      } else if (typeof value === 'object') {
-        return this._evaluateExpression(exp, context);
-      } else {
-        throw new Error("Unrecognized operand value for OR: " + JSON.stringify(value));
-      }
-    } else if (key === "NOT") {
-      if (typeof value === 'object') {
-        return !this._evaluateExpression(value, context);
-      } else {
-        throw new Error("Unrecognized operand value for NOT: " + JSON.stringify(value));
-      }
-    }
-
-    if (typeof value === 'object') {
-      if (!(key in context)) {
-        throw new Error("Missing context value for expression " + JSON.stringify(expression) + " in context " + JSON.stringify(context));
-      }
-      const contextValue = context[key];
-      return this._evaluateExpressionType(contextValue, value);
-    }
-  }
-
-  // TODO: rename: _evaluateExpressionDefinition
-  // TODO: rename: _evaluateComparison
-  _evaluateExpressionType(value, expressionType) {
-    const type = expressionType["type"];
-    if (!type) {
-      throw new Error("Expression definition is missing 'type' property: " + JSON.stringify(expressionType));
-    }
-    if (type === "equals") {
-      return value === this._getExpressionTypeValue(expressionType);
-    }
-    throw new Error("Unrecognized expression type " + JSON.stringify(expressionType));
-  }
-
-  // TODO: rename: _getExpressionDefinitionValue(expressionDefiniton)
-  // TODO: rename: _getComparisonTarget
-  _getExpressionTypeValue(expressionType) {
-
-    if (!("value" in expressionType)) {
-      throw new Error("Missing required 'value' property in expression definition: " + JSON.stringify(expressionType));
-    }
-    return expressionType["value"];
-  }
-}
diff --git a/django_airavata/apps/api/static/django_airavata_api/tests/models/dependencies/BooleanExpressionEvaluator.test.js b/django_airavata/apps/api/static/django_airavata_api/tests/models/dependencies/BooleanExpressionEvaluator.test.js
new file mode 100644
index 0000000..f1d37eb
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/tests/models/dependencies/BooleanExpressionEvaluator.test.js
@@ -0,0 +1,213 @@
+import BooleanExpressionEvaluator from "../../../js/models/dependencies/BooleanExpressionEvaluator";
+
+const context = {
+  INPUT1: "1",
+  INPUT2: "2",
+  INPUT3: "3"
+};
+const booleanExpressionEvaluator = new BooleanExpressionEvaluator(context);
+
+test("throws error when expression is empty", () => {
+  expect(() => booleanExpressionEvaluator.evaluate({})).toThrow();
+});
+
+test("INPUT1 == 1 AND INPUT2 == 2 is TRUE", () => {
+  const result = booleanExpressionEvaluator.evaluate({
+    AND: [
+      {
+        INPUT1: {
+          comparison: "equals",
+          value: "1"
+        }
+      },
+      {
+        INPUT2: {
+          comparison: "equals",
+          value: "2"
+        }
+      }
+    ]
+  });
+  expect(result).toBe(true);
+});
+
+test("INPUT1 == 2 AND INPUT2 == 2 is FALSE", () => {
+  const result = booleanExpressionEvaluator.evaluate({
+    AND: [
+      {
+        INPUT1: {
+          comparison: "equals",
+          value: "2" // "1" !== "2"
+        }
+      },
+      {
+        INPUT2: {
+          comparison: "equals",
+          value: "2"
+        }
+      }
+    ]
+  });
+  expect(result).toBe(false);
+});
+
+test("INPUT1 == 2 OR INPUT2 == 2 is TRUE", () => {
+  const result = booleanExpressionEvaluator.evaluate({
+    OR: [
+      {
+        INPUT1: {
+          comparison: "equals",
+          value: "2"
+        }
+      },
+      {
+        INPUT2: {
+          comparison: "equals",
+          value: "2"
+        }
+      }
+    ]
+  });
+  expect(result).toBe(true);
+});
+
+test("INPUT1 == 2 OR INPUT2 == 1 is FALSE", () => {
+  const result = booleanExpressionEvaluator.evaluate({
+    OR: [
+      {
+        INPUT1: {
+          comparison: "equals",
+          value: "2"
+        }
+      },
+      {
+        INPUT2: {
+          comparison: "equals",
+          value: "1"
+        }
+      }
+    ]
+  });
+  expect(result).toBe(false);
+});
+
+test("(NOT INPUT1 == 2) AND INPUT2 == 2 is TRUE", () => {
+  const result = booleanExpressionEvaluator.evaluate({
+    AND: [
+      {
+        NOT: {
+          INPUT1: {
+            comparison: "equals",
+            value: "2"
+          }
+        }
+      },
+      {
+        INPUT2: {
+          comparison: "equals",
+          value: "2"
+        }
+      }
+    ]
+  });
+  expect(result).toBe(true);
+});
+
+// single comparison
+test("INPUT1 == 1 is TRUE", () => {
+  const result = booleanExpressionEvaluator.evaluate({
+    INPUT1: {
+      comparison: "equals",
+      value: "1"
+    }
+  });
+  expect(result).toBe(true);
+});
+
+// single comparison
+test("INPUT1 == 2 is FALSE", () => {
+  const result = booleanExpressionEvaluator.evaluate({
+    INPUT1: {
+      comparison: "equals",
+      value: "2"
+    }
+  });
+  expect(result).toBe(false);
+});
+
+test("non-array given for AND throws Error", () => {
+  expect(() =>
+    booleanExpressionEvaluator.evaluate({
+      AND: "a"
+    })
+  ).toThrow();
+});
+
+test("non-array given for OR throws Error", () => {
+  expect(() =>
+    booleanExpressionEvaluator.evaluate({
+      OR: "a"
+    })
+  ).toThrow();
+});
+
+test("non-object given for NOT throws Error", () => {
+  expect(() =>
+    booleanExpressionEvaluator.evaluate({
+      NOT: [
+        {
+          INPUT1: {
+            comparison: "equals",
+            value: 1
+          }
+        }
+      ]
+    })
+  ).toThrow();
+});
+
+test("referenced variable not in context throws Error", () => {
+  expect(() =>
+    booleanExpressionEvaluator.evaluate({
+      ZINPUT1: {
+        comparison: "equals",
+        value: 1
+      }
+    })
+  ).toThrow(/missing context value/i);
+});
+
+test("missing 'comparison' property throws Error", () => {
+  expect(() =>
+    booleanExpressionEvaluator.evaluate({
+      INPUT1: {
+        value: 1
+      }
+    })
+  ).toThrow(/missing 'comparison' property/i);
+});
+
+test("unrecognized 'comparison' property throws Error", () => {
+  expect(() =>
+    booleanExpressionEvaluator.evaluate({
+      INPUT1: {
+        comparison: "foo",
+        value: 1
+      }
+    })
+  ).toThrow(/unrecognized comparison/i);
+});
+
+test("Implicitly ANDed INPUT1 == 1 AND INPUT2 == 2 is TRUE", () => {
+  const result = booleanExpressionEvaluator.evaluate({
+    INPUT1: {
+      comparison: "equals",
+      value: "1"
+    },
+    INPUT2: {
+      comparison: "equals",
+      value: "2"
+    }
+  });
+  expect(result).toBe(true);
+});


[airavata-django-portal] 01/02: AIRAVATA-2761 Initial implementation of input dependencies

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

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

commit 25f35f61273ca2cb591783c468c923b9e6d38e89
Author: Marcus Christie <ma...@iu.edu>
AuthorDate: Thu Dec 20 12:48:38 2018 -0500

    AIRAVATA-2761 Initial implementation of input dependencies
---
 .../django_airavata_api/js/models/Experiment.js    |  23 ++-
 .../js/models/InputDataObjectType.js               | 157 +++++++++++++++------
 .../js/models/dependencies/DependencyEvaluator.js  |  94 ++++++++++++
 .../js/input-editors/InputEditorMixin.js           |  18 +--
 .../js/components/experiment/ExperimentEditor.vue  |   8 +-
 .../input-editors/InputEditorContainer.vue         |   7 +-
 .../experiment/input-editors/StringInputEditor.vue |   3 +-
 7 files changed, 247 insertions(+), 63 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 8c1bd30..2adac92 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
@@ -42,7 +42,8 @@ const FIELDS = [
   {
     name: "experimentInputs",
     type: InputDataObjectType,
-    list: true
+    list: true,
+    default: BaseModel.defaultNewInstance(Array)
   },
   {
     name: "experimentOutputs",
@@ -75,6 +76,7 @@ const FIELDS = [
 export default class Experiment extends BaseModel {
   constructor(data = {}) {
     super(FIELDS, data);
+    this.evaluateInputDependencies();
   }
 
   validate() {
@@ -125,7 +127,8 @@ export default class Experiment extends BaseModel {
 
   get isEditable() {
     return (
-      (!this.latestStatus || this.latestStatus.state === ExperimentState.CREATED) &&
+      (!this.latestStatus ||
+        this.latestStatus.state === ExperimentState.CREATED) &&
       this.userHasWriteAccess
     );
   }
@@ -135,6 +138,22 @@ export default class Experiment extends BaseModel {
     this.experimentInputs = applicationInterface.applicationInputs.map(input =>
       input.clone()
     );
+    this.evaluateInputDependencies();
     this.experimentOutputs = applicationInterface.applicationOutputs.slice();
   }
+
+  evaluateInputDependencies() {
+    const inputValues = this._collectInputValues(this.experimentInputs);
+    for (const input of this.experimentInputs) {
+      input.evaluateDependencies(inputValues);
+    }
+  }
+
+  _collectInputValues() {
+    const result = {};
+    this.experimentInputs.forEach(inp => {
+      result[inp.name] = inp.value;
+    });
+    return result;
+  }
 }
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 07ce6ef..f3b9867 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
@@ -1,53 +1,54 @@
-
-import BaseModel from './BaseModel';
-import DataType from './DataType';
-import ValidatorFactory from './validators/ValidatorFactory';
-import uuidv4 from 'uuid/v4';
+import BaseModel from "./BaseModel";
+import DataType from "./DataType";
+import DependencyEvaluator from "./dependencies/DependencyEvaluator";
+import uuidv4 from "uuid/v4";
+import ValidatorFactory from "./validators/ValidatorFactory";
 
 const FIELDS = [
-  'name',
-  'value',
+  "name",
+  "value",
   {
-    name: 'type',
+    name: "type",
     type: DataType,
-    default: DataType.STRING,
+    default: DataType.STRING
   },
-  'applicationArgument',
+  "applicationArgument",
   {
-    name: 'standardInput',
-    type: 'boolean',
-    default: false,
+    name: "standardInput",
+    type: "boolean",
+    default: false
   },
-  'userFriendlyDescription',
-  'metaData',
-  'inputOrder',
+  "userFriendlyDescription",
+  "metaData",
+  "inputOrder",
   {
-    name: 'isRequired',
-    type: 'boolean',
-    default: false,
+    name: "isRequired",
+    type: "boolean",
+    default: false
   },
   {
-    name: 'requiredToAddedToCommandLine',
-    type: 'boolean',
-    default: false,
+    name: "requiredToAddedToCommandLine",
+    type: "boolean",
+    default: false
   },
   {
-    name: 'dataStaged',
-    type: 'boolean',
-    default: false,
+    name: "dataStaged",
+    type: "boolean",
+    default: false
   },
-  'storageResourceId',
+  "storageResourceId",
   {
-    name: 'isReadOnly',
-    type: 'boolean',
-    default: false,
-  },
+    name: "isReadOnly",
+    type: "boolean",
+    default: false
+  }
 ];
 
 export default class InputDataObjectType extends BaseModel {
   constructor(data = {}) {
     super(FIELDS, data);
     this._key = data.key ? data.key : uuidv4();
+    this.show = true;
   }
 
   get key() {
@@ -68,8 +69,12 @@ export default class InputDataObjectType extends BaseModel {
    */
   get editorUIComponentId() {
     const metadata = this._getMetadata();
-    if (metadata && 'editor' in metadata && 'ui-component-id' in metadata['editor']) {
-      return metadata['editor']['ui-component-id'];
+    if (
+      metadata &&
+      "editor" in metadata &&
+      "ui-component-id" in metadata["editor"]
+    ) {
+      return metadata["editor"]["ui-component-id"];
     } else {
       return null;
     }
@@ -92,8 +97,8 @@ export default class InputDataObjectType extends BaseModel {
    */
   get editorConfig() {
     const metadata = this._getMetadata();
-    if (metadata && 'editor' in metadata && 'config' in metadata['editor']) {
-      return metadata['editor']['config'];
+    if (metadata && "editor" in metadata && "config" in metadata["editor"]) {
+      return metadata["editor"]["config"];
     } else {
       return {};
     }
@@ -123,38 +128,106 @@ export default class InputDataObjectType extends BaseModel {
    */
   get editorValidations() {
     const metadata = this._getMetadata();
-    if (metadata && 'editor' in metadata && 'validations' in metadata['editor']) {
-      return metadata['editor']['validations'];
+    if (
+      metadata &&
+      "editor" in metadata &&
+      "validations" in metadata["editor"]
+    ) {
+      return metadata["editor"]["validations"];
     } else {
       return [];
     }
   }
 
+  /**
+   * Get the dependencies for the editor component. Returns empty object if
+   * there are no dependencies. See evaluateDependencies for a list of
+   * available kinds of dependencies.
+   *
+   * The expected JSON schema for the editor validations is the following:
+   * {
+   *   "editor": {
+   *     "dependencies": {
+   *       "show": {
+   *         "AND": [              // Boolean operator ("AND", "OR")
+   *           "INPUT_1": {        // Name of other application input
+   *             "type": "equals", // Name of comparison type
+   *             "value": "1"      // Value to compare with
+   *           },
+   *           "NOT": {            // "NOT" is given a single input comparison or "AND" or "OR" expression
+   *             "INPUT_2": {
+   *               ...
+   *             }
+   *           }
+   *           ... additional boolean expressions ("AND", "OR", "NOT")
+   *           ... additional application input comparisons
+   *         ]
+   *       }
+   *     }
+   *   }
+   * }
+   */
+  get editorDependencies() {
+    const metadata = this._getMetadata();
+    if (
+      metadata &&
+      "editor" in metadata &&
+      "dependencies" in metadata["editor"]
+    ) {
+      return metadata["editor"]["dependencies"];
+    } else {
+      return {};
+    }
+  }
+
   _getMetadata() {
     // metaData could really be anything, here we expect it to be an object
     // so safely check if it is first
-    if (this.metaData && typeof this.metaData === 'object') {
+    if (this.metaData && typeof this.metaData === "object") {
       return this.metaData;
     } else {
       return null;
     }
   }
 
-  validate(experiment, value = undefined) {
-    let inputValue = typeof value != 'undefined' ? value : this.value;
+  validate(value = undefined) {
+    let inputValue = typeof value != "undefined" ? value : this.value;
     let results = {};
+    // Skip running validations when the input isn't shown
+    if (!this.show) {
+      return results;
+    }
     let valueErrorMessages = [];
     if (this.isRequired && this.isEmpty(inputValue)) {
-      valueErrorMessages.push('This field is required.');
+      valueErrorMessages.push("This field is required.");
     }
     // Run through any validations if configured
     if (this.editorValidations.length > 0) {
       const validatorFactory = new ValidatorFactory();
-      valueErrorMessages = valueErrorMessages.concat(validatorFactory.validate(this.editorValidations, inputValue));
+      valueErrorMessages = valueErrorMessages.concat(
+        validatorFactory.validate(this.editorValidations, inputValue)
+      );
     }
     if (valueErrorMessages.length > 0) {
-      results['value'] = valueErrorMessages;
+      results["value"] = valueErrorMessages;
     }
     return results;
   }
+
+  /**
+   * Evaluate dependencies on the values of other application inputs.
+   */
+  evaluateDependencies(inputValues) {
+    if (Object.keys(this.editorDependencies).length > 0) {
+      const dependencyEvaluator = new DependencyEvaluator();
+      const dependencyEvaluations = dependencyEvaluator.evaluateDependencies(
+        inputValues,
+        this.editorDependencies
+      );
+      // TODO: if show changes to false, set value to null? Save off current value to restore when shown again?
+      if ("show" in dependencyEvaluations) {
+        this.show = dependencyEvaluations["show"];
+      }
+    }
+  }
 }
diff --git a/django_airavata/apps/api/static/django_airavata_api/js/models/dependencies/DependencyEvaluator.js b/django_airavata/apps/api/static/django_airavata_api/js/models/dependencies/DependencyEvaluator.js
new file mode 100644
index 0000000..0a4f04c
--- /dev/null
+++ b/django_airavata/apps/api/static/django_airavata_api/js/models/dependencies/DependencyEvaluator.js
@@ -0,0 +1,94 @@
+
+// TODO: rename: BooleanExpressionEvaluator? There's really nothing related to "app input dependencies" in here.
+export default class DependencyEvaluator {
+
+  /**
+   * Evaluate all dependency expressions and return an object with the same
+   * keys as dependenciesConfig but with the result of each expression.
+   *
+   * @param inputValues
+   * @param dependenciesConfig
+   */
+  evaluateDependencies(inputValues, dependenciesConfig) {
+
+    const result = {};
+    for (const k of Object.keys(dependenciesConfig)) {
+      result[k] = this._evaluateExpression(dependenciesConfig[k], inputValues);
+    }
+    return result;
+  }
+
+  /**
+   * Evaluates boolean expression for given context and returns boolean result.
+   */
+  _evaluateExpression(expression, context) {
+    console.log("expression: " + JSON.stringify(expression));
+    console.log("context: " + JSON.stringify(context));
+    const keys = Object.keys(expression);
+    if (keys.length > 1) {
+      throw new Error("Expression contains more than one key: " + JSON.stringify(expression));
+    }
+    if (keys.length < 1) {
+      throw new Error("Expression does not contain a key: " + JSON.stringify(expression));
+    }
+
+    const key = keys[0];
+    const value = expression[key];
+    if (key === "AND") {
+      if (value instanceof Array) {
+        const evaluations = value.map(exp => this._evaluateExpression(exp, context));
+        return evaluations.reduce((acc, curr) => acc && curr);
+      } else if (typeof value === 'object') {
+        return this._evaluateExpression(exp, context);
+      } else {
+        throw new Error("Unrecognized operand value for AND: " + JSON.stringify(value));
+      }
+    } else if (key === "OR") {
+      if (value instanceof Array) {
+        const evaluations = value.map(exp => this._evaluateExpression(exp, context));
+        return evaluations.reduce((acc, curr) => acc || curr);
+      } else if (typeof value === 'object') {
+        return this._evaluateExpression(exp, context);
+      } else {
+        throw new Error("Unrecognized operand value for OR: " + JSON.stringify(value));
+      }
+    } else if (key === "NOT") {
+      if (typeof value === 'object') {
+        return !this._evaluateExpression(value, context);
+      } else {
+        throw new Error("Unrecognized operand value for NOT: " + JSON.stringify(value));
+      }
+    }
+
+    if (typeof value === 'object') {
+      if (!(key in context)) {
+        throw new Error("Missing context value for expression " + JSON.stringify(expression) + " in context " + JSON.stringify(context));
+      }
+      const contextValue = context[key];
+      return this._evaluateExpressionType(contextValue, value);
+    }
+  }
+
+  // TODO: rename: _evaluateExpressionDefinition
+  // TODO: rename: _evaluateComparison
+  _evaluateExpressionType(value, expressionType) {
+    const type = expressionType["type"];
+    if (!type) {
+      throw new Error("Expression definition is missing 'type' property: " + JSON.stringify(expressionType));
+    }
+    if (type === "equals") {
+      return value === this._getExpressionTypeValue(expressionType);
+    }
+    throw new Error("Unrecognized expression type " + JSON.stringify(expressionType));
+  }
+
+  // TODO: rename: _getExpressionDefinitionValue(expressionDefiniton)
+  // TODO: rename: _getComparisonTarget
+  _getExpressionTypeValue(expressionType) {
+
+    if (!("value" in expressionType)) {
+      throw new Error("Missing required 'value' property in expression definition: " + JSON.stringify(expressionType));
+    }
+    return expressionType["value"];
+  }
+}
diff --git a/django_airavata/apps/workspace/django-airavata-workspace-plugin-api/js/input-editors/InputEditorMixin.js b/django_airavata/apps/workspace/django-airavata-workspace-plugin-api/js/input-editors/InputEditorMixin.js
index 446b46e..8d8ebfb 100644
--- a/django_airavata/apps/workspace/django-airavata-workspace-plugin-api/js/input-editors/InputEditorMixin.js
+++ b/django_airavata/apps/workspace/django-airavata-workspace-plugin-api/js/input-editors/InputEditorMixin.js
@@ -1,16 +1,12 @@
 // InputEditorMixin: mixin for experiment InputEditors, provides basic v-model
 // and validation functionality and defines the basic props interface
-// (experimentInput and experiment).
+// (experimentInput and id).
 import {models} from 'django-airavata-api'
 export default {
     props: {
         value: {
             required: true,
         },
-        experiment: {
-            type: models.Experiment,
-            required: true,
-        },
         experimentInput: {
             type: models.InputDataObjectType,
             required: true,
@@ -28,7 +24,7 @@ export default {
     },
     computed: {
         validationResults: function() {
-            return this.experimentInput.validate(this.experiment, this.data);
+            return this.experimentInput.validate(this.data);
         },
         validationMessages: function() {
             return 'value' in this.validationResults ? this.validationResults['value'] : [];
@@ -47,7 +43,6 @@ export default {
         valueChanged: function() {
             this.inputHasBegun = true;
             this.$emit('input', this.data);
-            this.checkValidation();
         },
         checkValidation: function() {
             if (this.valid) {
@@ -57,7 +52,12 @@ export default {
             }
         }
     },
-    mounted: function() {
+    created: function() {
         this.checkValidation();
+    },
+    watch: {
+        valid() {
+          this.checkValidation();
+        }
     }
-}
\ No newline at end of file
+}
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 95661ba..f184d81 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
@@ -75,12 +75,13 @@
               </h2>
               <input-editor-container
                 v-for="experimentInput in localExperiment.experimentInputs"
-                :experiment="localExperiment"
                 :experiment-input="experimentInput"
                 v-model="experimentInput.value"
+                v-show="experimentInput.show"
                 :key="experimentInput.name"
                 @invalid="recordInvalidInputEditorValue(experimentInput.name)"
                 @valid="recordValidInputEditorValue(experimentInput.name)"
+                @input="inputValueChanged"
               />
             </div>
           </div>
@@ -260,7 +261,10 @@ export default {
         const index = this.invalidInputs.indexOf(experimentInputName);
         this.invalidInputs.splice(index, 1);
       }
-    }
+    },
+    inputValueChanged: function() {
+      this.localExperiment.evaluateInputDependencies();
+    },
   },
   watch: {
     experiment: function(newValue) {
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/InputEditorContainer.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/InputEditorContainer.vue
index 8f36b1d..4fbb0d0 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/InputEditorContainer.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/InputEditorContainer.vue
@@ -3,7 +3,6 @@
         :state="validationState" :feedback-messages="validationFeedback">
         <component :is="inputEditorComponentName"
             :id="inputEditorComponentId"
-            :experiment="experiment"
             :experiment-input="experimentInput"
             v-model="data"
             @invalid="recordInvalidInputEditorValue"
@@ -27,10 +26,6 @@ export default {
         value: {
             required: true,
         },
-        experiment: {
-            type: models.Experiment,
-            required: true,
-        },
         experimentInput: {
             type: models.InputDataObjectType,
             required: true,
@@ -95,4 +90,4 @@ export default {
         },
     }
 }
-</script>
\ No newline at end of file
+</script>
diff --git a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/StringInputEditor.vue b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/StringInputEditor.vue
index 7162152..04ab99d 100644
--- a/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/StringInputEditor.vue
+++ b/django_airavata/apps/workspace/static/django_airavata_workspace/js/components/experiment/input-editors/StringInputEditor.vue
@@ -14,8 +14,7 @@ export default {
     props: {
         value: {
             type: String,
-            required: true,
         },
     },
 }
-</script>
\ No newline at end of file
+</script>