Merge "Add support for deleting tags through the web ui"
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
index a850a9b..b44cd1c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.java
@@ -57,6 +57,10 @@
 
   String branchDeletionConfirmationMessage();
 
+  String tagDeletionDialogTitle();
+
+  String tagDeletionConfirmationMessage();
+
   String newUi();
 
   String notSignedInTitle();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
index cab988b..9aa4388 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/GerritConstants.properties
@@ -26,6 +26,9 @@
 branchDeletionDialogTitle = Branch Deletion
 branchDeletionConfirmationMessage = Do you really want to delete the following branches?
 
+tagDeletionDialogTitle = Tag Deletion
+tagDeletionConfirmationMessage = Do you really want to delete the following tags?
+
 newUi = New UI
 
 notSignedInTitle = Code Review - Session Expired
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
index d4b575f..d7fb072 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.java
@@ -185,6 +185,8 @@
 
   String buttonAddTag();
 
+  String buttonDeleteTag();
+
   String saveHeadButton();
 
   String cancelHeadButton();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
index 404d903..465bcfc 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/AdminConstants.properties
@@ -88,6 +88,7 @@
 buttonAddBranch = Create Branch
 buttonAddTag = Create Tag
 buttonDeleteBranch = Delete
+buttonDeleteTag = Delete
 saveHeadButton = Save
 cancelHeadButton = Cancel
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java
index de40a5d..b89139c 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/admin/ProjectTagsScreen.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.client.ConfirmationDialog;
 import com.google.gerrit.client.ErrorDialog;
 import com.google.gerrit.client.Gerrit;
+import com.google.gerrit.client.VoidResult;
 import com.google.gerrit.client.access.AccessMap;
 import com.google.gerrit.client.access.ProjectAccessInfo;
 import com.google.gerrit.client.projects.ProjectApi;
@@ -43,6 +44,8 @@
 import com.google.gwt.event.dom.client.KeyPressHandler;
 import com.google.gwt.event.dom.client.KeyUpEvent;
 import com.google.gwt.event.dom.client.KeyUpHandler;
+import com.google.gwt.event.logical.shared.ValueChangeEvent;
+import com.google.gwt.event.logical.shared.ValueChangeHandler;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.CheckBox;
 import com.google.gwt.user.client.ui.FlexTable.FlexCellFormatter;
@@ -52,6 +55,7 @@
 import com.google.gwt.user.client.ui.InlineHTML;
 import com.google.gwt.user.client.ui.Label;
 import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.Widget;
 import com.google.gwtexpui.globalkey.client.NpTextBox;
 import com.google.gwtexpui.safehtml.client.SafeHtmlBuilder;
 import java.util.HashSet;
@@ -62,6 +66,7 @@
   private Hyperlink prev;
   private Hyperlink next;
   private TagsTable tagTable;
+  private Button delTag;
   private Button addTag;
   private HintTextBox nameTxtBox;
   private HintTextBox irevTxtBox;
@@ -95,6 +100,7 @@
   }
 
   private void updateForm() {
+    tagTable.updateDeleteButton();
     addTag.setEnabled(true);
     nameTxtBox.setEnabled(true);
     irevTxtBox.setEnabled(true);
@@ -160,8 +166,19 @@
 
     tagTable = new TagsTable();
 
+    delTag = new Button(AdminConstants.I.buttonDeleteTag());
+    delTag.setStyleName(Gerrit.RESOURCES.css().branchTableDeleteButton());
+    delTag.addClickHandler(
+        new ClickHandler() {
+          @Override
+          public void onClick(ClickEvent event) {
+            tagTable.deleteChecked();
+          }
+        });
+
     HorizontalPanel buttons = new HorizontalPanel();
     buttons.setStyleName(Gerrit.RESOURCES.css().branchTablePrevNextLinks());
+    buttons.add(delTag);
     buttons.add(prev);
     buttons.add(next);
     add(tagTable);
@@ -272,6 +289,8 @@
   }
 
   private class TagsTable extends NavigationTable<TagInfo> {
+    private ValueChangeHandler<Boolean> updateDeleteHandler;
+    boolean canDelete;
 
     TagsTable() {
       table.setWidth("");
@@ -282,6 +301,14 @@
       fmt.addStyleName(0, 1, Gerrit.RESOURCES.css().iconHeader());
       fmt.addStyleName(0, 2, Gerrit.RESOURCES.css().dataHeader());
       fmt.addStyleName(0, 3, Gerrit.RESOURCES.css().dataHeader());
+
+      updateDeleteHandler =
+          new ValueChangeHandler<Boolean>() {
+            @Override
+            public void onValueChange(ValueChangeEvent<Boolean> event) {
+              updateDeleteButton();
+            }
+          };
     }
 
     Set<String> getCheckedRefs() {
@@ -306,11 +333,74 @@
       }
     }
 
+    void deleteChecked() {
+      final Set<String> refs = getCheckedRefs();
+
+      SafeHtmlBuilder b = new SafeHtmlBuilder();
+      b.openElement("b");
+      b.append(Gerrit.C.tagDeletionConfirmationMessage());
+      b.closeElement("b");
+
+      b.openElement("p");
+      boolean first = true;
+      for (String ref : refs) {
+        if (!first) {
+          b.append(",").br();
+        }
+        b.append(ref);
+        first = false;
+      }
+      b.closeElement("p");
+
+      if (refs.isEmpty()) {
+        updateDeleteButton();
+        return;
+      }
+
+      delTag.setEnabled(false);
+      ConfirmationDialog confirmationDialog =
+          new ConfirmationDialog(
+              Gerrit.C.tagDeletionDialogTitle(),
+              b.toSafeHtml(),
+              new ConfirmationCallback() {
+                @Override
+                public void onOk() {
+                  deleteTags(refs);
+                }
+
+                @Override
+                public void onCancel() {
+                  tagTable.updateDeleteButton();
+                }
+              });
+      confirmationDialog.center();
+    }
+
+    private void deleteTags(final Set<String> tags) {
+      ProjectApi.deleteTags(
+          getProjectKey(),
+          tags,
+          new GerritCallback<VoidResult>() {
+            @Override
+            public void onSuccess(VoidResult result) {
+              query = new Query(match).start(start).run();
+            }
+
+            @Override
+            public void onFailure(Throwable caught) {
+              query = new Query(match).start(start).run();
+              super.onFailure(caught);
+            }
+          });
+    }
+
     void display(List<TagInfo> tags) {
       displaySubset(tags, 0, tags.size());
     }
 
     void displaySubset(List<TagInfo> tags, int fromIndex, int toIndex) {
+      canDelete = false;
+
       while (1 < table.getRowCount()) {
         table.removeRow(table.getRowCount() - 1);
       }
@@ -324,7 +414,14 @@
     }
 
     void populate(int row, TagInfo k) {
-      table.setText(row, 1, "");
+      if (k.canDelete()) {
+        CheckBox sel = new CheckBox();
+        sel.addValueChangeHandler(updateDeleteHandler);
+        table.setWidget(row, 1, sel);
+        canDelete = true;
+      } else {
+        table.setText(row, 1, "");
+      }
 
       table.setWidget(row, 2, new InlineHTML(highlight(k.getShortName(), match)));
 
@@ -344,6 +441,25 @@
       setRowItem(row, k);
     }
 
+    boolean hasTagCanDelete() {
+      return canDelete;
+    }
+
+    void updateDeleteButton() {
+      boolean on = false;
+      for (int row = 1; row < table.getRowCount(); row++) {
+        Widget w = table.getWidget(row, 1);
+        if (w != null && w instanceof CheckBox) {
+          CheckBox sel = (CheckBox) w;
+          if (sel.getValue()) {
+            on = true;
+            break;
+          }
+        }
+      }
+      delTag.setEnabled(on);
+    }
+
     @Override
     protected void onOpenRow(int row) {
       if (row > 0) {
@@ -421,6 +537,7 @@
         prev.setVisible(false);
       }
 
+      delTag.setVisible(tagTable.hasTagCanDelete());
       Set<String> checkedRefs = tagTable.getCheckedRefs();
       tagTable.setChecked(checkedRefs);
       updateForm();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
index 1350e1a..4be877e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/ProjectApi.java
@@ -82,6 +82,20 @@
     getRestApi(name, "tags", limit, start, match).get(cb);
   }
 
+  /** Delete tags. One call is fired to the server to delete all the tags. */
+  public static void deleteTags(
+      Project.NameKey name, Set<String> refs, AsyncCallback<VoidResult> cb) {
+    if (refs.size() == 1) {
+      project(name).view("tags").id(refs.iterator().next()).delete(cb);
+    } else {
+      DeleteTagsInput d = DeleteTagsInput.create();
+      for (String ref : refs) {
+        d.addTag(ref);
+      }
+      project(name).view("tags:delete").post(d, cb);
+    }
+  }
+
   /** Create a new branch */
   public static void createBranch(
       Project.NameKey name, String ref, String revision, AsyncCallback<BranchInfo> cb) {
@@ -373,6 +387,20 @@
     final native void setRef(String r) /*-{ if(r)this.ref=r; }-*/;
   }
 
+  private static class DeleteTagsInput extends JavaScriptObject {
+    static DeleteTagsInput create() {
+      DeleteTagsInput d = createObject().cast();
+      d.init();
+      return d;
+    }
+
+    protected DeleteTagsInput() {}
+
+    final native void init() /*-{ this.tags = []; }-*/;
+
+    final native void addTag(String b) /*-{ this.tags.push(b); }-*/;
+  }
+
   private static class DeleteBranchesInput extends JavaScriptObject {
     static DeleteBranchesInput create() {
       DeleteBranchesInput d = createObject().cast();
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/TagInfo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/TagInfo.java
index bccac9b..24487ae 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/TagInfo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/projects/TagInfo.java
@@ -15,6 +15,8 @@
 package com.google.gerrit.client.projects;
 
 public class TagInfo extends RefInfo {
+  public final native boolean canDelete() /*-{ return this['can_delete'] ? true : false; }-*/;
+
   // TODO(dpursehouse) add extra tag-related fields (message, tagger, etc)
   protected TagInfo() {}
 }