blob: 2c528002319d6b320b21b9e4dffb67889fc3a599 [file] [log] [blame]
// Copyright (C) 2013 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.gerrit.client.change;
import static com.google.gerrit.common.PageLinks.op;
import com.google.gerrit.client.Gerrit;
import com.google.gerrit.client.changes.ChangeApi;
import com.google.gerrit.client.changes.ChangeList;
import com.google.gerrit.client.info.ChangeInfo;
import com.google.gerrit.client.info.ChangeInfo.CommitInfo;
import com.google.gerrit.client.info.ChangeInfo.RevisionInfo;
import com.google.gerrit.client.rpc.Natives;
import com.google.gerrit.extensions.client.ListChangesOption;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gwt.core.client.GWT;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.event.logical.shared.SelectionEvent;
import com.google.gwt.event.logical.shared.SelectionHandler;
import com.google.gwt.resources.client.ClientBundle;
import com.google.gwt.resources.client.CssResource;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.TabBar;
import com.google.gwt.user.client.ui.TabPanel;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
public class RelatedChanges extends TabPanel {
static final RelatedChangesResources R = GWT.create(RelatedChangesResources.class);
interface RelatedChangesResources extends ClientBundle {
@Source("related_changes.css")
RelatedChangesCss css();
}
interface RelatedChangesCss extends CssResource {
String activeRow();
String current();
String gitweb();
String indirect();
String notCurrent();
String pointer();
String row();
String subject();
String strikedSubject();
String submittable();
String tabPanel();
}
enum Tab {
RELATED_CHANGES(Resources.C.relatedChanges(), Resources.C.relatedChangesTooltip()) {
@Override
String getTitle(int count) {
return Resources.M.relatedChanges(count);
}
@Override
String getTitle(String count) {
return Resources.M.relatedChanges(count);
}
},
SUBMITTED_TOGETHER(Resources.C.submittedTogether(), Resources.C.submittedTogether()) {
@Override
String getTitle(int count) {
return Resources.M.submittedTogether(count);
}
@Override
String getTitle(String count) {
return Resources.M.submittedTogether(count);
}
},
SAME_TOPIC(Resources.C.sameTopic(), Resources.C.sameTopicTooltip()) {
@Override
String getTitle(int count) {
return Resources.M.sameTopic(count);
}
@Override
String getTitle(String count) {
return Resources.M.sameTopic(count);
}
},
CONFLICTING_CHANGES(Resources.C.conflictingChanges(), Resources.C.conflictingChangesTooltip()) {
@Override
String getTitle(int count) {
return Resources.M.conflictingChanges(count);
}
@Override
String getTitle(String count) {
return Resources.M.conflictingChanges(count);
}
},
CHERRY_PICKS(Resources.C.cherryPicks(), Resources.C.cherryPicksTooltip()) {
@Override
String getTitle(int count) {
return Resources.M.cherryPicks(count);
}
@Override
String getTitle(String count) {
return Resources.M.cherryPicks(count);
}
};
final String defaultTitle;
final String tooltip;
abstract String getTitle(int count);
abstract String getTitle(String count);
Tab(String defaultTitle, String tooltip) {
this.defaultTitle = defaultTitle;
this.tooltip = tooltip;
}
}
private static Tab savedTab;
private final List<RelatedChangesTab> tabs;
private int maxHeightWithHeader;
private int selectedTab;
private int outstandingCallbacks;
RelatedChanges() {
tabs = new ArrayList<>(Tab.values().length);
selectedTab = -1;
setVisible(false);
addStyleName(R.css().tabPanel());
initTabBar();
}
private void initTabBar() {
TabBar tabBar = getTabBar();
tabBar.addSelectionHandler(
new SelectionHandler<Integer>() {
@Override
public void onSelection(SelectionEvent<Integer> event) {
if (selectedTab >= 0) {
tabs.get(selectedTab).registerKeys(false);
}
selectedTab = event.getSelectedItem();
tabs.get(selectedTab).registerKeys(true);
}
});
for (Tab tabInfo : Tab.values()) {
RelatedChangesTab panel = new RelatedChangesTab(tabInfo);
add(panel, tabInfo.defaultTitle);
tabs.add(panel);
TabBar.Tab tab = tabBar.getTab(tabInfo.ordinal());
tab.setWordWrap(false);
((Composite) tab).setTitle(tabInfo.tooltip);
setTabEnabled(tabInfo, false);
}
getTab(Tab.RELATED_CHANGES).setShowIndirectAncestors(true);
getTab(Tab.CHERRY_PICKS).setShowBranches(true);
getTab(Tab.SAME_TOPIC).setShowBranches(true);
getTab(Tab.SAME_TOPIC).setShowProjects(true);
getTab(Tab.SAME_TOPIC).setShowSubmittable(true);
getTab(Tab.SUBMITTED_TOGETHER).setShowBranches(true);
getTab(Tab.SUBMITTED_TOGETHER).setShowProjects(true);
getTab(Tab.SUBMITTED_TOGETHER).setShowSubmittable(true);
}
void set(ChangeInfo info, String revision) {
if (info.status().isOpen()) {
setForOpenChange(info, revision);
}
ChangeApi.revision(info.project(), info.legacyId().get(), revision)
.view("related")
.get(
new TabCallback<RelatedInfo>(Tab.RELATED_CHANGES, info.project(), revision) {
@Override
public JsArray<ChangeAndCommit> convert(RelatedInfo result) {
return result.changes();
}
});
StringBuilder cherryPicksQuery = new StringBuilder();
cherryPicksQuery.append(op("project", info.project()));
cherryPicksQuery.append(" ").append(op("change", info.changeId()));
cherryPicksQuery.append(" ").append(op("-change", info.legacyId().get()));
cherryPicksQuery.append(" -is:abandoned");
ChangeList.query(
cherryPicksQuery.toString(),
EnumSet.of(ListChangesOption.CURRENT_REVISION, ListChangesOption.CURRENT_COMMIT),
new TabChangeListCallback(Tab.CHERRY_PICKS, info.project(), revision));
if (info.currentRevision() != null && info.currentRevision().equals(revision)) {
ChangeApi.change(info.project(), info.legacyId().get())
.view("submitted_together")
.addParameter("o", "CURRENT_COMMIT")
.get(new TabChangeListCallback(Tab.SUBMITTED_TOGETHER, info.project(), revision));
}
if (!Gerrit.info().change().isSubmitWholeTopicEnabled()
&& info.topic() != null
&& !"".equals(info.topic())) {
StringBuilder topicQuery =
new StringBuilder()
.append("status:open")
.append(" ")
.append(op("-change", info.legacyId().get()))
.append(" ")
.append(op("topic", info.topic()));
ChangeList.query(
topicQuery.toString(),
EnumSet.of(
ListChangesOption.CURRENT_REVISION,
ListChangesOption.CURRENT_COMMIT,
ListChangesOption.DETAILED_LABELS,
ListChangesOption.LABELS),
new TabChangeListCallback(Tab.SAME_TOPIC, info.project(), revision));
}
}
private void setForOpenChange(ChangeInfo info, String revision) {
if (info.mergeable()) {
StringBuilder conflictsQuery = new StringBuilder();
conflictsQuery.append("status:open");
conflictsQuery.append(" is:mergeable");
conflictsQuery.append(" ").append(op("conflicts", info.legacyId().get()));
ChangeList.query(
conflictsQuery.toString(),
EnumSet.of(ListChangesOption.CURRENT_REVISION, ListChangesOption.CURRENT_COMMIT),
new TabChangeListCallback(Tab.CONFLICTING_CHANGES, info.project(), revision));
}
}
@Override
protected void onLoad() {
super.onLoad();
R.css().ensureInjected();
}
static void setSavedTab(Tab subject) {
savedTab = subject;
}
private RelatedChangesTab getTab(Tab tabInfo) {
return tabs.get(tabInfo.ordinal());
}
private void setTabTitle(Tab tabInfo, String title) {
getTabBar().setTabText(tabInfo.ordinal(), title);
}
private void setTabEnabled(Tab tabInfo, boolean enabled) {
getTabBar().setTabEnabled(tabInfo.ordinal(), enabled);
}
void setMaxHeight(int height) {
maxHeightWithHeader = height;
if (isVisible()) {
applyMaxHeight();
}
}
private void applyMaxHeight() {
int header = getTabBar().getOffsetHeight() + 2 /* padding */;
for (int i = 0; i < getTabBar().getTabCount(); i++) {
tabs.get(i).setMaxHeight(maxHeightWithHeader - header);
}
}
private abstract class TabCallback<T> implements AsyncCallback<T> {
private final Tab tabInfo;
private final String project;
private final String revision;
TabCallback(Tab tabInfo, String project, String revision) {
this.tabInfo = tabInfo;
this.project = project;
this.revision = revision;
outstandingCallbacks++;
}
protected abstract JsArray<ChangeAndCommit> convert(T result);
@Override
public void onSuccess(T result) {
if (isAttached()) {
JsArray<ChangeAndCommit> changes = convert(result);
if (changes.length() > 0) {
setTabTitle(tabInfo, tabInfo.getTitle(changes.length()));
getTab(tabInfo).setChanges(project, revision, changes);
}
onDone(changes.length() > 0);
}
}
@Override
public void onFailure(Throwable err) {
if (isAttached()) {
setTabTitle(tabInfo, tabInfo.getTitle(Resources.C.notAvailable()));
getTab(tabInfo).setError(err.getMessage());
onDone(true);
}
}
private void onDone(boolean enabled) {
setTabEnabled(tabInfo, enabled);
outstandingCallbacks--;
if (outstandingCallbacks == 0 || (enabled && tabInfo == Tab.RELATED_CHANGES)) {
outstandingCallbacks = 0; // Only execute this block once
for (int i = 0; i < getTabBar().getTabCount(); i++) {
if (getTabBar().isTabEnabled(i)) {
selectTab(i);
setVisible(true);
applyMaxHeight();
break;
}
}
}
if (tabInfo == savedTab && enabled) {
selectTab(savedTab.ordinal());
}
}
}
private class TabChangeListCallback extends TabCallback<ChangeList> {
TabChangeListCallback(Tab tabInfo, String project, String revision) {
super(tabInfo, project, revision);
}
@Override
protected JsArray<ChangeAndCommit> convert(ChangeList l) {
JsArray<ChangeAndCommit> arr = JavaScriptObject.createArray().cast();
for (ChangeInfo i : Natives.asList(l)) {
if (i.currentRevision() != null && i.revisions().containsKey(i.currentRevision())) {
RevisionInfo currentRevision = i.revision(i.currentRevision());
ChangeAndCommit c = ChangeAndCommit.create();
c.setId(i.id());
c.setCommit(currentRevision.commit());
c.setChangeNumber(i.legacyId().get());
c.setRevisionNumber(currentRevision._number());
c.setBranch(i.branch());
c.setProject(i.project());
c.setSubmittable(i.submittable() && i.mergeable());
c.setStatus(i.status().asChangeStatus().toString());
arr.push(c);
}
}
return arr;
}
}
public static class RelatedInfo extends JavaScriptObject {
public final native JsArray<ChangeAndCommit> changes() /*-{ return this.changes }-*/;
protected RelatedInfo() {}
}
public static class ChangeAndCommit extends JavaScriptObject {
static ChangeAndCommit create() {
return (ChangeAndCommit) createObject();
}
public final native String id() /*-{ return this.change_id }-*/;
public final native CommitInfo commit() /*-{ return this.commit }-*/;
final native String branch() /*-{ return this.branch }-*/;
final native String project() /*-{ return this.project }-*/;
final native boolean submittable() /*-{ return this._submittable ? true : false; }-*/;
final Change.Status status() {
String s = statusRaw();
return s != null ? Change.Status.valueOf(s) : null;
}
private native String statusRaw() /*-{ return this.status; }-*/;
final native void setId(String i) /*-{ if(i)this.change_id=i; }-*/;
final native void setCommit(CommitInfo c) /*-{ if(c)this.commit=c; }-*/;
final native void setBranch(String b) /*-{ if(b)this.branch=b; }-*/;
final native void setProject(String b) /*-{ if(b)this.project=b; }-*/;
public final Change.Id legacyId() {
return hasChangeNumber() ? new Change.Id(_changeNumber()) : null;
}
public final PatchSet.Id patchSetId() {
return hasChangeNumber() && hasRevisionNumber()
? new PatchSet.Id(legacyId(), _revisionNumber())
: null;
}
public final native boolean hasChangeNumber()
/*-{ return this.hasOwnProperty('_change_number') }-*/ ;
final native boolean hasRevisionNumber()
/*-{ return this.hasOwnProperty('_revision_number') }-*/ ;
final native boolean hasCurrentRevisionNumber()
/*-{ return this.hasOwnProperty('_current_revision_number') }-*/ ;
final native int _changeNumber() /*-{ return this._change_number }-*/;
final native int _revisionNumber() /*-{ return this._revision_number }-*/;
final native int _currentRevisionNumber() /*-{ return this._current_revision_number }-*/;
final native void setChangeNumber(int n) /*-{ this._change_number=n; }-*/;
final native void setRevisionNumber(int n) /*-{ this._revision_number=n; }-*/;
final native void setCurrentRevisionNumber(int n) /*-{ this._current_revision_number=n; }-*/;
final native void setSubmittable(boolean s) /*-{ this._submittable=s; }-*/;
final native void setStatus(String s) /*-{ if(s)this.status=s; }-*/;
protected ChangeAndCommit() {}
}
}