// Copyright 2011 Google Inc. All Rights Reseved.
//
// 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.testing.testify.risk.frontend.client.view.impl;
import com.google.common.collect.Lists;
import com.google.gwt.core.client.GWT;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.uibinder.client.UiHandler;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.DisclosurePanel;
import com.google.gwt.user.client.ui.Grid;
import com.google.gwt.user.client.ui.HTML;
import com.google.gwt.user.client.ui.HorizontalPanel;
import com.google.gwt.user.client.ui.Image;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.testing.testify.risk.frontend.client.view.CapabilityDetailsView;
import com.google.testing.testify.risk.frontend.client.view.widgets.EditCapabilityWidget;
import com.google.testing.testify.risk.frontend.client.view.widgets.PageSectionVerticalPanel;
import com.google.testing.testify.risk.frontend.model.Attribute;
import com.google.testing.testify.risk.frontend.model.Bug;
import com.google.testing.testify.risk.frontend.model.Capability;
import com.google.testing.testify.risk.frontend.model.Checkin;
import com.google.testing.testify.risk.frontend.model.Component;
import com.google.testing.testify.risk.frontend.model.UploadedDatum;
import com.google.testing.testify.risk.frontend.model.TestCase;
import java.util.Collection;
import java.util.List;
/**
* View the details of a Capability, including attached data artifacts.
*
* @author jimr@google.com (Jim Reardon)
*/
public class CapabilityDetailsViewImpl extends Composite implements CapabilityDetailsView {
private static final String HEADER_TEXT = "Details for Capability: ";
/**
* Used to wire parent class to associated UI Binder.
*/
interface CapabilityDetailsViewImplUiBinder extends UiBinder<Widget, CapabilityDetailsViewImpl> {}
private static final CapabilityDetailsViewImplUiBinder uiBinder =
GWT.create(CapabilityDetailsViewImplUiBinder.class);
@UiField
public PageSectionVerticalPanel detailsSection;
@UiField
public CheckBox signoffBox;
@UiField
public Image testChart;
@UiField
public Grid testGrid;
@UiField
public HTML testNotRunCount;
@UiField
public HTML testPassedCount;
@UiField
public HTML testFailedCount;
@UiField
public Grid bugGrid;
@UiField
public Grid changeGrid;
// Will be constructed and added to the above panel once we know what capability we're showing.
public EditCapabilityWidget capabilityWidget;
boolean isEditable = false;
private CapabilityDetailsView.Presenter presenter;
// TODO(jimr): Reconsider this data model. Keeping each data item stored twice is not awesome.
private List<Attribute> attributes;
private List<Component> components;
private List<Bug> bugs;
private List<Bug> otherBugs = Lists.newArrayList();
private List<Bug> capabilityBugs = Lists.newArrayList();
private List<TestCase> tests;
private List<TestCase> otherTests = Lists.newArrayList();
private List<TestCase> capabilityTests = Lists.newArrayList();
private List<Checkin> checkins;
private List<Checkin> otherCheckins = Lists.newArrayList();
private List<Checkin> capabilityCheckins = Lists.newArrayList();
private Collection<String> projectLabels = Lists.newArrayList();
private Anchor addBugAnchor;
private Anchor addCheckinAnchor;
private Anchor addTestAnchor;
private Capability capability = null;
public CapabilityDetailsViewImpl() {
initWidget(uiBinder.createAndBindUi(this));
detailsSection.setHeaderText(HEADER_TEXT);
}
@UiHandler("signoffBox")
public void addSignoffClickHandler(ClickEvent click) {
presenter.setSignoff(capability.getCapabilityId(), signoffBox.getValue());
}
@Override
public Widget asWidget() {
return this;
}
@Override
public void setPresenter(Presenter presenter) {
this.presenter = presenter;
refresh();
}
@Override
public void setCapability(Capability capability) {
this.capability = capability;
refresh();
}
@Override
public void setAttributes(List<Attribute> attributes) {
this.attributes = attributes;
refresh();
}
@Override
public void setBugs(List<Bug> bugs) {
this.bugs = bugs;
refresh();
}
@Override
public void setCheckins(List<Checkin> checkins) {
this.checkins = checkins;
refresh();
}
@Override
public void setProjectLabels(Collection<String> labels) {
projectLabels.clear();
projectLabels.addAll(labels);
if (capabilityWidget != null) {
capabilityWidget.setLabelSuggestions(labels);
}
}
private <T extends UploadedDatum> void splitData(List<T> inItems,
List<T> otherItems, List<T> capabilityItems) {
otherItems.clear();
capabilityItems.clear();
for (T item : inItems) {
if (capability.getCapabilityId().equals(item.getTargetCapabilityId())) {
capabilityItems.add(item);
} else {
otherItems.add(item);
}
}
}
@Override
public void setTests(List<TestCase> tests) {
this.tests = tests;
refresh();
}
@Override
public void setComponents(List<Component> components) {
this.components = components;
refresh();
}
@SuppressWarnings("unchecked")
@Override
public void refresh() {
// Don't re-draw until all data has successfully loaded.
if (attributes != null && components != null && capability != null && bugs != null &&
tests != null && checkins != null) {
splitData(tests, otherTests, capabilityTests);
splitData(bugs, otherBugs, capabilityBugs);
splitData(checkins, otherCheckins, capabilityCheckins);
capabilityWidget = new EditCapabilityWidget(capability);
capabilityWidget.setLabelSuggestions(projectLabels);
capabilityWidget.setAttributes(attributes);
capabilityWidget.setComponents(components);
capabilityWidget.disableDelete();
if (isEditable) {
capabilityWidget.makeEditable();
}
capabilityWidget.expand();
capabilityWidget.addValueChangeHandler(new ValueChangeHandler<Capability>() {
@Override
public void onValueChange(ValueChangeEvent<Capability> event) {
capability = event.getValue();
presenter.updateCapability(capability);
refresh();
capabilityWidget.showSaved();
}
});
detailsSection.clear();
detailsSection.add(capabilityWidget);
updateTestSection();
updateBugSection();
updateCheckinsSection();
detailsSection.setHeaderText(HEADER_TEXT + capability.getName());
}
}
@Override
public void makeEditable() {
isEditable = true;
signoffBox.setEnabled(true);
if (capabilityWidget != null) {
capabilityWidget.makeEditable();
}
if (addTestAnchor != null) {
addTestAnchor.setVisible(true);
}
if (addBugAnchor != null) {
addBugAnchor.setVisible(true);
}
if (addCheckinAnchor != null) {
addCheckinAnchor.setVisible(true);
}
}
@Override
public void reset() {
attributes = null;
components = null;
capabilityWidget = null;
capability = null;
bugs = null;
tests = null;
checkins = null;
}
private TestCase getTestCaseById(long id) {
for (TestCase test : tests) {
if (test.getInternalId() == id) {
return test;
}
}
return null;
}
private Widget buildTestHeaderWidget(String header, String addText) {
final ListBox options = new ListBox();
for (TestCase test : otherTests) {
options.addItem(test.getExternalId() + " " + test.getTitle(),
String.valueOf(test.getInternalId()));
}
VerticalPanel addForm = new VerticalPanel();
addForm.add(options);
final DisclosurePanel disclosure = new DisclosurePanel();
Button button = new Button(" Add ", new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
long id = Long.parseLong((options.getValue(options.getSelectedIndex())));
presenter.assignTestCaseToCapability(capability.getCapabilityId(), id);
disclosure.setOpen(false);
TestCase test = getTestCaseById(id);
test.setTargetCapabilityId(capability.getCapabilityId());
refresh();
}
});
addForm.add(button);
disclosure.setAnimationEnabled(true);
disclosure.setOpen(false);
disclosure.setContent(addForm);
HorizontalPanel title = new HorizontalPanel();
title.add(new Label(header));
addTestAnchor = new Anchor(addText);
addTestAnchor.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
disclosure.setOpen(!disclosure.isOpen());
}
});
addTestAnchor.setVisible(isEditable);
title.add(addTestAnchor);
VerticalPanel everything = new VerticalPanel();
everything.add(title);
everything.add(disclosure);
return everything;
}
private void updateTestSection() {
testGrid.setTitle("Recent Test Activity");
testGrid.resize(capabilityTests.size() + 1, 1);
testGrid.setWidget(0, 0, buildTestHeaderWidget("Recent Test Activity", "add test"));
int passed = 0, failed = 0, notRun = 0;
for (int i = 0; i < capabilityTests.size(); i++) {
TestCase test = capabilityTests.get(i);
HorizontalPanel panel = new HorizontalPanel();
panel.add(getTestStateImage(test.getState()));
Anchor anchor = new Anchor(test.getLinkText(), test.getLinkUrl());
anchor.setTarget("_blank");
panel.add(anchor);
Label statusLabel = new Label();
int state = getTestState(test.getState());
if (state < 0) {
statusLabel.setText(" - failed " + getDateText(test.getStateDate()));
failed++;
} else if (state > 0) {
statusLabel.setText(" - passed " + getDateText(test.getStateDate()));
passed++;
} else {
statusLabel.setText(" - no result");
notRun++;
}
panel.add(statusLabel);
testGrid.setWidget(i + 1, 0, panel);
}
testNotRunCount.setHTML("not run <b>" + notRun + "</b>");
testPassedCount.setHTML("passed <b>" + passed + "</b>");
testFailedCount.setHTML("failed <b>" + failed + "</b>");
String imageUrl = getTestChartUrl(passed, failed, notRun);
if (imageUrl == null || "".equals(imageUrl)) {
testChart.setVisible(false);
} else {
testChart.setUrl(imageUrl);
testChart.setVisible(true);
}
}
private Bug getBugById(long id) {
for (Bug bug : bugs) {
if (bug.getInternalId() == id) {
return bug;
}
}
return null;
}
private Widget buildBugHeaderWidget(String header, String addText) {
final ListBox options = new ListBox();
for (Bug bug : otherBugs) {
options.addItem(bug.getExternalId() + " " + bug.getTitle(),
String.valueOf(bug.getInternalId()));
}
VerticalPanel addForm = new VerticalPanel();
addForm.add(options);
final DisclosurePanel disclosure = new DisclosurePanel();
Button button = new Button(" Add ", new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
long id = Long.parseLong((options.getValue(options.getSelectedIndex())));
presenter.assignBugToCapability(capability.getCapabilityId(), id);
disclosure.setOpen(false);
Bug bug = getBugById(id);
bug.setTargetCapabilityId(capability.getCapabilityId());
refresh();
}
});
addForm.add(button);
disclosure.setAnimationEnabled(true);
disclosure.setOpen(false);
disclosure.setContent(addForm);
HorizontalPanel title = new HorizontalPanel();
title.add(new Label(header));
addBugAnchor = new Anchor(addText);
addBugAnchor.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
disclosure.setOpen(!disclosure.isOpen());
}
});
addBugAnchor.setVisible(isEditable);
title.add(addBugAnchor);
VerticalPanel everything = new VerticalPanel();
everything.add(title);
everything.add(disclosure);
return everything;
}
private void updateBugSection() {
bugGrid.resize(capabilityBugs.size() + 1, 1);
bugGrid.setTitle("Bugs (" + capabilityBugs.size() + " total)");
bugGrid.setWidget(0, 0, buildBugHeaderWidget("Bugs (" + capabilityBugs.size() + " total)",
"add bug"));
for (int i = 0; i < capabilityBugs.size(); i++) {
Bug bug = capabilityBugs.get(i);
HorizontalPanel panel = new HorizontalPanel();
panel.add(getBugStateImage(bug.getState()));
Anchor anchor = new Anchor(bug.getLinkText(), bug.getLinkUrl());
anchor.setTarget("_blank");
panel.add(anchor);
Label statusLabel = new Label();
statusLabel.setText(" - filed " + getDateText(bug.getStateDate()));
panel.add(statusLabel);
bugGrid.setWidget(i + 1, 0, panel);
}
}
private Checkin getCheckinById(long id) {
for (Checkin checkin : checkins) {
if (checkin.getInternalId() == id) {
return checkin;
}
}
return null;
}
private Widget buildCheckinHeaderWidget(String header, String addText) {
final ListBox options = new ListBox();
for (Checkin checkin : otherCheckins) {
options.addItem(checkin.getExternalId() + " " + checkin.getSummary(),
String.valueOf(checkin.getInternalId()));
}
VerticalPanel addForm = new VerticalPanel();
addForm.add(options);
final DisclosurePanel disclosure = new DisclosurePanel();
Button button = new Button(" Add ", new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
long id = Long.parseLong((options.getValue(options.getSelectedIndex())));
presenter.assignCheckinToCapability(capability.getCapabilityId(), id);
disclosure.setOpen(false);
Checkin checkin = getCheckinById(id);
checkin.setTargetCapabilityId(capability.getCapabilityId());
refresh();
}
});
addForm.add(button);
disclosure.setAnimationEnabled(true);
disclosure.setOpen(false);
disclosure.setContent(addForm);
HorizontalPanel title = new HorizontalPanel();
title.add(new Label(header));
addCheckinAnchor = new Anchor(addText);
addCheckinAnchor.addClickHandler(new ClickHandler() {
@Override
public void onClick(ClickEvent event) {
disclosure.setOpen(!disclosure.isOpen());
}
});
addCheckinAnchor.setVisible(isEditable);
title.add(addCheckinAnchor);
VerticalPanel everything = new VerticalPanel();
everything.add(title);
everything.add(disclosure);
return everything;
}
private void updateCheckinsSection() {
changeGrid.setTitle("Recent Code Changes (" + capabilityCheckins.size() + " total)");
changeGrid.resize(capabilityCheckins.size() + 1, 1);
changeGrid.setWidget(0, 0, buildCheckinHeaderWidget(
"Recent Code Changes (" + capabilityCheckins.size() + " total)", "add code change"));
for (int i = 0; i < capabilityCheckins.size(); i++) {
Checkin checkin = capabilityCheckins.get(i);
HorizontalPanel panel = new HorizontalPanel();
panel.add(new Image("/images/teststate-passed.png"));
Anchor anchor = new Anchor(checkin.getLinkText(), checkin.getLinkUrl());
anchor.setTarget("_blank");
panel.add(anchor);
Label statusLabel = new Label();
statusLabel.setText(" - submitted " + getDateText(checkin.getStateDate()));
panel.add(statusLabel);
changeGrid.setWidget(i + 1, 0, panel);
}
}
private Image getBugStateImage(String state) {
Image image = new Image();
if (state != null && state.toLowerCase().equals("closed")) {
image.setUrl("/images/bugstate-closed.png");
} else {
image.setUrl("/images/bugstate-active.png");
}
return image;
}
/**
* Turns a number of days, eg: 3, into a string like "3 days ago" or "1 day ago" or "today".
* @param date date reported/filed/etc.
* @return string representation.
*/
private String getDateText(Long date) {
int days = -1;
if (date != null && date > 0) {
days = (int) ((double) System.currentTimeMillis() - date) / 86400000;
}
if (days == 0) {
return "today";
} else if (days == 1) {
return "1 day ago";
} else if (days > 1) {
return days + " days ago";
}
return "";
}
/**
* Determine from a text description what state a test is in.
*
* @param state the text state.
* @return -1 for failing test, 0 for unsure/not run, 1 for passing.
*/
private int getTestState(String state) {
if (state == null) {
return 0;
}
state = state.toLowerCase();
if (state.startsWith("pass")) {
return 1;
} else if (state.startsWith("fail")) {
return -1;
} else {
return 0;
}
}
private Image getTestStateImage(String state) {
Image image = new Image();
int stateVal = getTestState(state);
if (stateVal > 0) {
image.setUrl("/images/teststate-passed.png");
} else if (stateVal < 0) {
image.setUrl("/images/teststate-failed.png");
} else {
image.setUrl("/images/teststate-notrun.png");
}
return image;
}
private String getTestChartUrl(int passed, int failed, int notRun) {
int total = passed + failed + notRun;
if (total < 1) {
return null;
}
passed = passed * 100 / total;
failed = failed * 100 / total;
notRun = notRun * 100 / total;
String pStr = String.valueOf(passed);
String fStr = String.valueOf(failed);
String nStr = String.valueOf(notRun);
return "http://chart.apis.google.com/chart?chs=500x20&cht=bhs&chco=FFFFFF,008000,FF0000&chd=t:"
+ nStr + "|" + pStr + "|" + fStr;
}
@Override
public void setSignoff(boolean signoff) {
signoffBox.setValue(signoff);
}
}