// Copyright 2010 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.riskprovider.impl;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.ui.Anchor;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;
import com.google.testing.testify.risk.frontend.client.TaCallback;
import com.google.testing.testify.risk.frontend.client.riskprovider.RiskProvider;
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.CapabilityIntersectionData;
import com.google.testing.testify.risk.frontend.shared.rpc.DataRpc;
import com.google.testing.testify.risk.frontend.shared.rpc.DataRpcAsync;
import java.util.HashSet;
import java.util.List;
/**
* {@link RiskProvider} showing Risk due to outstanding code defects.
*
* For example, if outstanding bugs are spread across all existing Attribute x Component pairs, then
* there is relatively low risk due to bugs. However, if one Attribute x Component pair has a large
* concentration of bugs then it should be investigated.
*
* The higher the risk returned, the more risky that area is.
*
* @author chrsmith@google.com (Chris Smith)
*/
public class BugRiskProvider implements RiskProvider {
private final HashSet<Bug> unassignedBugs = new HashSet<Bug>();
private final Multimap<Long, Bug> lookupByAttribute = HashMultimap.create();
private final Multimap<Long, Bug> lookupByComponent = HashMultimap.create();
private final Multimap<Long, Bug> lookupByCapability = HashMultimap.create();
// Bugs not associated with an Attribute or Component. (General risk.)
private static final double RISK_FROM_UNASSIGNED = 0.00;
// Bugs associated with the Attribute.
private static final double RISK_FROM_ATTRIBUTE = 0.25;
// Bugs associated with the Component.
private static final double RISK_FROM_COMPONENT = 0.25;
// Bugs associated with the Capability.
private static final double RISK_FROM_CAPABILITY = 1.0;
@Override
public String getName() {
return "Bugs";
}
/**
* Asynchronously loads project bug information.
*/
@Override
public void initialize(List<CapabilityIntersectionData> projectData) {
DataRpcAsync bugService = GWT.create(DataRpc.class);
long projectId = projectData.get(0).getParentComponent().getParentProjectId();
bugService.getProjectBugsById(projectId,
new TaCallback<List<Bug>>("Querying Bugs") {
@Override
public void onSuccess(List<Bug> result) {
lookupByAttribute.clear();
lookupByComponent.clear();
lookupByCapability.clear();
unassignedBugs.clear();
for (Bug bug : result) {
lookupByAttribute.put(bug.getTargetAttributeId(), bug);
lookupByComponent.put(bug.getTargetComponentId(), bug);
lookupByCapability.put(bug.getTargetCapabilityId(), bug);
if (bug.getTargetAttributeId() == null &&
bug.getTargetComponentId() == null &&
bug.getTargetCapabilityId() == null) {
unassignedBugs.add(bug);
}
}
}
});
}
/**
* Returns the risk caused by outstanding bugs (based solely on bug count).
*/
@Override
public double calculateRisk(CapabilityIntersectionData targetCell) {
long attributeId = targetCell.getParentAttribute().getAttributeId();
long componentId = targetCell.getParentComponent().getComponentId();
// Bugs attached to Attributes or Components add risk to the whole Attribute and the whole
// Component. It is OK if we double-count risk.
double riskFromBugs = 0.0;
riskFromBugs += RISK_FROM_UNASSIGNED * unassignedBugs.size();
riskFromBugs += RISK_FROM_ATTRIBUTE * lookupByAttribute.get(attributeId).size();
riskFromBugs += RISK_FROM_COMPONENT * lookupByComponent.get(componentId).size();
for (Capability capability : targetCell.getCapabilities()) {
riskFromBugs += RISK_FROM_CAPABILITY
* lookupByCapability.get(capability.getCapabilityId()).size();
}
return riskFromBugs;
}
/**
* Returns a {@code Widget} to show when an Attribute x Component pair is clicked.
*/
@Override
public Widget onClick(CapabilityIntersectionData targetCell) {
VerticalPanel content = new VerticalPanel();
long attributeId = targetCell.getParentAttribute().getAttributeId();
long componentId = targetCell.getParentComponent().getComponentId();
for (Bug bug : lookupByComponent.get(componentId)) {
String linkText = "Component - " + Long.toString(bug.getExternalId()) + ": " + bug.getTitle();
Anchor anchor = new Anchor(linkText, bug.getBugUrl());
content.add(anchor);
}
for (Bug bug : lookupByAttribute.get(attributeId)) {
String linkText = "Attribute - " + Long.toString(bug.getExternalId()) + ": " + bug.getTitle();
Anchor anchor = new Anchor(linkText, bug.getBugUrl());
content.add(anchor);
}
for (Capability capability : targetCell.getCapabilities()) {
long capabilityId = capability.getCapabilityId();
for (Bug bug : lookupByCapability.get(capabilityId)) {
String linkText =
"Capability - " + Long.toString(bug.getExternalId()) + ": " + bug.getTitle();
Anchor anchor = new Anchor(linkText, bug.getBugUrl());
content.add(anchor);
}
}
String labelText = "Unassigned - " + unassignedBugs.size();
Label label = new Label(labelText);
content.add(label);
if (content.getWidgetCount() == 0) {
content.add(new Label("No bugs associated with this cell."));
}
return content;
}
}