Add parallelism calculator in Heron UI (#3401)

diff --git a/heron/tools/ui/resources/static/css/main.css b/heron/tools/ui/resources/static/css/main.css
index 0ae4425..f13666d 100644
--- a/heron/tools/ui/resources/static/css/main.css
+++ b/heron/tools/ui/resources/static/css/main.css
@@ -2291,4 +2291,10 @@
 div#topologydetails.display-counters div.display-info.display-counters,
 div#topologydetails.display-config div.display-info.display-config {
   display: block;
-}
\ No newline at end of file
+}
+
+/* Backdrop in bootstrap 3 seems to have solid color and we would like it to be half transparent */
+div.modal-backdrop.fade.in {
+  opacity: 0.5;
+  filter: alpha(opacity=50);
+}
diff --git a/heron/tools/ui/resources/static/js/topologies.js b/heron/tools/ui/resources/static/js/topologies.js
index 764e0aa..ff8dbbe 100644
--- a/heron/tools/ui/resources/static/js/topologies.js
+++ b/heron/tools/ui/resources/static/js/topologies.js
@@ -1067,8 +1067,34 @@
     });
     var setState = this.setState.bind(this);
 
-    const headings = ["Bolt", "Parallelism", "Execute Count", "Failure Count",
-                      "Capacity Utilization(min)", "Capacity Utilization(max)"];
+    const headings = [
+      { name: "Bolt", sortable: true },
+      { name: "Parallelism", sortable: true },
+      { name: "Execute Count", sortable: true },
+      { name: "Failure Count", sortable: true },
+      { name: "Capacity Utilization(min)", sortable: true },
+      { name: "Capacity Utilization(max)", sortable: true },
+      { name: "Parallelism Calculator", sortable: false }
+    ];
+
+    var openParallelismCalculator = function (e, index) {
+      var row = rows[index];
+
+      var calculator = $("#parallelism-calculator-modal");
+      calculator.find('#modal-component').text(row[0]);
+      calculator.attr('data-execute-count', row[2]);
+      calculator.find('#modal-execute-count').text(row[2]);
+      calculator.find('#target-execute-count').val(row[2]);
+      calculator.attr('data-max-utilization', row[5].replace("%", ""));
+      calculator.find('#modal-max-utilization').text(row[5]);
+      calculator.find('#target-max-utilization').val(row[5].replace("%", ""));
+      calculator.attr('data-parallelism', row[1]);
+      calculator.find('#modal-parallelism').text(row[1]);
+      calculator.find('#target-parallelism').val(row[1]);
+
+      calculator.modal({keyboard: true});
+    };
+
     return (
       <div id="componentrunninginfo">
         <div className="widget-header">
@@ -1100,16 +1126,27 @@
                     reverse: i === sortKey ? (!reverse) : true
                   });
                 }
-                return <th key={i} className={classNameVals} onClick={clicked}>{heading}</th>;
+
+                if (heading.sortable) {
+                  return <th key={i} className={classNameVals} onClick={clicked}>{heading.name}</th>;
+                } else {
+                  return <th key={i}>{heading.name}</th>;
+                }
               })}
             </tr>
           </thead>
           <tbody>
-            {rows.map(function (row) {
+            {rows.map(function (row, index) {
               return <tr key={row[0]}>{
-                row.map(function (value, i) {
-                  return <td className="col-md-2" key={i}>{value}</td>;
-                })}</tr>;
+                  row.map(function (value, i) {
+                    return <td className="col-md-2" key={i}>{value}</td>;
+                  })}
+                  <td className="col-md-1">
+                    <button type="button" className="btn btn-primary btn-xs" data-toggle="modal"
+                            onClick={(e) => openParallelismCalculator(e, index)}>
+                      Calculator
+                    </button>
+                  </td></tr>;
             })}
           </tbody>
         </table>
diff --git a/heron/tools/ui/resources/templates/topology.html b/heron/tools/ui/resources/templates/topology.html
index f743c78..c109625 100644
--- a/heron/tools/ui/resources/templates/topology.html
+++ b/heron/tools/ui/resources/templates/topology.html
@@ -182,7 +182,49 @@
       <div class="col-md-12">
         <div id="display-counters"></div>
       </div>
+      <!-- Popup dialog for parallelism calculator -->
+      <div class="modal fade" id="parallelism-calculator-modal" tabindex="-1" role="dialog" aria-labelledby="parallelismCalculatorModalLabel" aria-hidden="true">
+        <div class="modal-dialog modal-sm" role="document">
+          <div class="modal-content">
+            <div class="modal-header">
+              <h5 class="modal-title" id="exampleModalLabel">Parallelism Calculator: <span id="modal-component"></span></h5>
+            </div>
+            <div class="modal-body">
+              <form class="form-horizontal">
+                <h3 id="modal-component"></h3>
+                <div class="form-group">
+                  <label class="col-sm-8 control-label" for="target-execute-count">
+                    Target execute count (current: <b id="modal-execute-count"></b>)</label>
+                  <div class="col-sm-4">
+                    <input id="target-execute-count" class="form-control form-control-sm" value="100"></input>
+                  </div>
+                </div>
+                <div class="form-group">
+                  <label class="col-sm-8 control-label" for="target-max-utilization">
+                    Target max capacity utilization (current: <b id="modal-max-utilization"></b>)</label>
+                  <div class="col-sm-3">
+                    <input id="target-max-utilization" class="form-control form-control-sm" value=50 max=100 min=1></input>
+                  </div>
+                  <span class="col-sm-1 control-label">%</span>
+                </div>
+                <div class="form-group">
+                  <label class="col-sm-8 control-label" for="target-parallelism">
+                    Target parallelism (current: <b id="modal-parallelism"></b>)</label>
+                  <div class="col-sm-4">
+                    <input id="target-parallelism" class="form-control form-control-sm" readonly=true value="3"></input>
+                  </div>
+                </div>
+              </form>
+            </div>
+            <div class="modal-footer">
+              <button type="button" class="btn btn-default" onclick="calculateParallelism()">Calculate</button>
+              <button type="button" class="btn btn-primary" data-dismiss="modal">Close</button>
+            </div>
+          </div>
+        </div>
+      </div>
     </div>
+
     <!-- Config -->
     <div class="col-md-12">
       <div class="col-md-12">
@@ -205,6 +247,23 @@
 <script type="text/jsx" src="{{ static_url('js/config.js') }}"></script>
 
 <script type="application/javascript">
+  function calculateParallelism() {
+    var calculator = $("#parallelism-calculator-modal");
+
+    var execute_count = calculator.attr("data-execute-count");
+    var max_utilization = calculator.attr("data-max-utilization");
+    var parallelism = calculator.attr("data-parallelism");
+    var target_execute_count = calculator.find('#target-execute-count').val();
+    var target_max_utilization = calculator.find('#target-max-utilization').val();
+
+    // Estimate the target parallelism based on the current information.
+    var newParallelism = max_utilization * target_execute_count * parallelism /
+                         (execute_count * target_max_utilization);
+    calculator.find('#target-parallelism').val(Math.ceil(newParallelism));
+  }
+</script>
+
+<script type="application/javascript">
 
   // colors used shading nodes in logical and physical plan
   var colors = [