Show waiting bar modal when loading dashboard

When docker-compose up command is issued via 'analytics-wizard~server'
endpoint, the page keeps polling 'analytics-wizard~status' endpoint
to check the status of the import.

The polling continues until 202 is received (job is still in progress).
When 204 is received the user is redirected to the kibana dashboard.
Any other polling result is shown as error message.

Additionally, the analytics-wizard~server has been modified in order
to run the docker-compose in detached mode, to avoid the http request
hanging forever.

Feature: Issue 9870
Change-Id: I4116db8ae454e489522d97452f5eeb06f6c9cc6b
diff --git a/src/main/resources/static/analytics-dashboard-service.html b/src/main/resources/static/analytics-dashboard-service.html
index 03b8b8d..f7497ea 100644
--- a/src/main/resources/static/analytics-dashboard-service.html
+++ b/src/main/resources/static/analytics-dashboard-service.html
@@ -9,8 +9,10 @@
     <script type='text/javascript' src='js/jquery-3.3.1.min.js'></script>
     <script type='text/javascript' src="js/bootstrap.min.js"></script>
     <script type='text/javascript' src='js/analyticswizard.js'></script>
+    <script type='text/javascript' src='js/bootstrap-waitingfor.min.js'></script>
 </head>
 <body>
+
 <!--div.container start -->
 <div class="container">
     <div class="page-header">
@@ -20,20 +22,9 @@
         Project name: <input id="input-project-name" type="text">
     </div>
     <input type="button" class="btn btn-primary" value="Start dashboard" onClick="dashboardService('up')"/>
-    <input type="button" class="btn btn-primary" value="Stop dashboard" onClick="dashboardService('down')"/>
     <br><br>
-    <div id="up-ok-alert" class="alert alert-success alert-trim" role="alert" style="display: none;">
-        Dashboard started successfully.
-    </div>
-    <div id="down-ok-alert" class="alert alert-success alert-trim" role="alert" style="display: none;">
-        Dashboard stopped successfully.
-    </div>
-    <div id="up-error-alert" class="alert-danger alert-trim" role="alert" style="display: none;">
-        Error starting the dashboard.
-    </div>
-    <div id="down-error-alert" class="alert-danger alert-trim" role="alert" style="display: none;">
-        Error stopping the dashboard.
-    </div>
+    <div id="success-alert" class="alert alert-success alert-trim" role="alert" style="display: none;"></div>
+    <div id="error-alert" class="alert-danger alert-trim" role="alert" style="display: none;"></div>
 </div>
 
 <!--div.container end -->
diff --git a/src/main/resources/static/analytics-dashboard.html b/src/main/resources/static/analytics-dashboard.html
index 626e472..77984b3 100644
--- a/src/main/resources/static/analytics-dashboard.html
+++ b/src/main/resources/static/analytics-dashboard.html
@@ -25,12 +25,8 @@
     <br>
     <input type="button" class="btn btn-primary" value="Create dashboard configuration" onClick="submitDetailsForm()"/>
     <br><br>
-    <div id="config-created-alert" class="alert alert-success alert-trim" role="alert" style="display: none;">
-        Dashboard configuration successfully created.
-    </div>
-    <div id="config-error-alert" class="alert-danger alert-trim" role="alert" style="display: none;">
-        Something went wrong while creating Dashboard configuration.
-    </div>
+    <div id="success-alert" class="alert alert-success alert-trim" role="alert" style="display: none;"></div>
+    <div id="error-alert" class="alert-danger alert-trim" role="alert" style="display: none;"></div>
     <br><br>
 </div>
 
diff --git a/src/main/resources/static/js/analyticswizard.js b/src/main/resources/static/js/analyticswizard.js
index add1729..7bbb2cf 100644
--- a/src/main/resources/static/js/analyticswizard.js
+++ b/src/main/resources/static/js/analyticswizard.js
@@ -12,16 +12,40 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-var handle201 = function(data, textStatus, jqXHR) {
-    $("#config-error-alert").hide();
-    $("#config-created-alert").show();
-};
+function showSuccessWithText(text) {
+    $("#error-alert").hide();
+    $("#success-alert").html(text).show();
+}
 
-var handleError = function(data, textStatus, jqXHR) {
-  $("#config-created-alert").hide();
-  $("#config-error-alert").show();
-};
+function showFailureWithText(text) {
+    $("#success-alert").hide();
+    $("#error-alert").html(text).show();
+}
 
+function hideAllAlerts() {
+    $("#success-alert").hide();
+    $("#error-alert").hide();
+}
+
+function waitForImport() {
+  hideAllAlerts();
+  waitingDialog.show(
+  'Importing analytics data. Be patient, this might take a while...', {
+    dialogSize: 'lg',
+    progressType: 'success'
+  });
+  pollStatusEndpoint();
+}
+
+function pollStatusEndpoint() {
+    setTimeout(function () { checkStatusRequest()}, 5000);
+}
+
+function wizardGoToDashboard() {
+    waitingDialog.hide();
+    var redirectLocation = location.protocol + "//" + location.hostname + ":5601/app/kibana#/dashboards";
+    window.location.replace(redirectLocation);
+}
 
 function submitDetailsForm() {
     var projectName = encodeURIComponent($("#input-project-name").val());
@@ -30,31 +54,24 @@
       url : `/a/projects/${projectName}/analytics-wizard~stack`,
       dataType: 'application/json',
       // Initially project-dashboard is a 1 to 1 relationship
-      data: "{'dashboard_name': '" + projectName + "}'}",
+      data: "{'dashboard_name': '" + projectName + "'}",
       contentType:"application/json; charset=utf-8",
       // Need to catch the status code since Gerrit doesn't return
       // a well formed JSON, hence Ajax treats it as an error
       statusCode: {
-        201: handle201
+        201: showSuccessWithText("Configuration created successfully")
       },
       error: function(jqXHR, textStatus, errorThrown) {
         if(jqXHR.status != 201) {
-          handleError()
+          showFailureWithText("Error creating configuration: " + errorThrown)
         }
       }
     });
 }
 
-var handle201Status = function(command) {
-  $("#up-error-alert").hide();
-  $("#down-error-alert").hide();
-  $("#up-ok-alert").hide();
-  $("#down-ok-alert").hide();
-  $("#" + command + "-ok-alert").show();
-};
-
 function dashboardService(command) {
     var projectName = encodeURIComponent($("#input-project-name").val());
+    hideAllAlerts();
     $.ajax({
       type : "POST",
       url : `/a/projects/${projectName}/analytics-wizard~server`,
@@ -65,18 +82,37 @@
       // Need to catch the status code since Gerrit doesn't return
       // a well formed JSON, hence Ajax treats it as an error
       statusCode: {
-        201: handle201Status(command)
+        201: waitForImport
       },
       error: function(jqXHR, textStatus, errorThrown) {
         if(jqXHR.status != 201) {
-          handleError()
+          showFailureWithText("Error starting your dashboard: " + errorThrown)
+        }
+      }
+    });
+}
+
+function checkStatusRequest() {
+    var projectName = encodeURIComponent($("#input-project-name").val());
+    $.ajax({
+      type : "GET",
+      url : `/a/projects/${projectName}/analytics-wizard~status`,
+      dataType: 'application/json',
+      contentType:"application/json; charset=utf-8",
+      statusCode: {
+        202: pollStatusEndpoint,
+        204: wizardGoToDashboard
+      },
+      error: function(jqXHR, textStatus, errorThrown) {
+        if(jqXHR.status != 202 && jqXHR.status != 204) {
+          showFailureWithText("Cannot start your dashboard: " + errorThrown);
+          waitingDialog.hide();
         }
       }
     });
 }
 
 $(document).ready(function () {
-  console.log("Starting Analytics wizard plugin...");
   $.ajaxSetup({
       dataFilter: function(data, type) {
         //Strip out Gerrit API prefix
@@ -93,7 +129,6 @@
           }
         }
 
-        console.log("Parsed data: " + JSON.stringify(data))
         return data;
       }
   });
diff --git a/src/main/resources/static/js/bootstrap-waitingfor.min.js b/src/main/resources/static/js/bootstrap-waitingfor.min.js
new file mode 100644
index 0000000..52e8643
--- /dev/null
+++ b/src/main/resources/static/js/bootstrap-waitingfor.min.js
@@ -0,0 +1 @@
+!function(e,d){"use strict";"function"==typeof define&&define.amd?define(["jquery"],function(a){return e.waitingDialog=d(a)}):e.waitingDialog=e.waitingDialog||d(e.jQuery)}(this,function(e){"use strict";function d(d){return d&&d.remove(),e('<div class="modal fade" data-backdrop="static" data-keyboard="false" tabindex="-1" role="dialog" aria-hidden="true" style="padding-top:15%; overflow-y:visible;"><div class="modal-dialog modal-m"><div class="modal-content"><div class="modal-header" style="display: none;"></div><div class="modal-body"><div class="progress progress-striped active" style="margin-bottom:0;"><div class="progress-bar" style="width: 100%"></div></div></div></div></div></div>')}var a,o;return{show:function(n,i){"undefined"==typeof i&&(i={}),"undefined"==typeof n&&(n="Loading"),o=e.extend({headerText:"",headerSize:3,headerClass:"",dialogSize:"m",progressType:"",contentElement:"p",contentClass:"content",onHide:null,onShow:null},i);var s,t;a=d(a),a.find(".modal-dialog").attr("class","modal-dialog").addClass("modal-"+o.dialogSize),a.find(".progress-bar").attr("class","progress-bar progress-bar-striped progress-bar-animated"),o.progressType&&a.find(".progress-bar").addClass("progress-bar-"+o.progressType),s=e("<h"+o.headerSize+" />"),s.css({margin:0}),o.headerClass&&s.addClass(o.headerClass),t=e("<"+o.contentElement+" />"),o.contentClass&&t.addClass(o.contentClass),o.headerText===!1?(t.html(n),a.find(".modal-body").prepend(t)):o.headerText?(s.html(o.headerText),a.find(".modal-header").html(s).show(),t.html(n),a.find(".modal-body").prepend(t)):(s.html(n),a.find(".modal-header").html(s).show()),"function"==typeof o.onHide&&a.off("hidden.bs.modal").on("hidden.bs.modal",function(){o.onHide.call(a)}),"function"==typeof o.onShow&&a.off("shown.bs.modal").on("shown.bs.modal",function(){o.onShow.call(a)}),a.modal()},hide:function(){"undefined"!=typeof a&&a.modal("hide")},message:function(e){return"undefined"!=typeof a?"undefined"!=typeof e?a.find(".modal-header>h"+o.headerSize).html(e):a.find(".modal-header>h"+o.headerSize).html():void 0}}});
\ No newline at end of file
diff --git a/src/main/scala/com/googlesource/gerrit/plugins/analytics/wizard/AnalyticsWizardActions.scala b/src/main/scala/com/googlesource/gerrit/plugins/analytics/wizard/AnalyticsWizardActions.scala
index 5a73f68..e424681 100644
--- a/src/main/scala/com/googlesource/gerrit/plugins/analytics/wizard/AnalyticsWizardActions.scala
+++ b/src/main/scala/com/googlesource/gerrit/plugins/analytics/wizard/AnalyticsWizardActions.scala
@@ -79,7 +79,9 @@
       "docker-compose",
       "-f",
       s"${dataPath.toFile.getAbsolutePath}/docker-compose.${encodedName}.yaml",
-      input.action.toLowerCase)
+      input.action.toLowerCase,
+      "--detach"
+    )
     pb.redirectErrorStream(true)
 
     val ps: Process = pb.start