/*
* Copyright 2013 Hewlett-Packard Development Company, L.P
*
* 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.hp.alm.ali.idea.model;
import com.hp.alm.ali.idea.cfg.WorkspaceConfiguration;
import com.hp.alm.ali.idea.entity.EntityQuery;
import com.hp.alm.ali.idea.action.ActionUtil;
import com.hp.alm.ali.idea.content.AliContent;
import com.hp.alm.ali.idea.content.backlog.BacklogContent;
import com.hp.alm.ali.idea.content.builds.BuildsContent;
import com.hp.alm.ali.idea.content.defect.DefectsContent;
import com.hp.alm.ali.idea.content.settings.SettingsContent;
import com.hp.alm.ali.idea.content.taskboard.TaskBoardContent;
import com.hp.alm.ali.idea.content.detail.DetailContent;
import com.hp.alm.ali.idea.content.detail.TableContent;
import com.hp.alm.ali.idea.content.detail.TaskTableLoader;
import com.hp.alm.ali.idea.entity.EntityQueryProcessor;
import com.hp.alm.ali.idea.entity.EntityRef;
import com.hp.alm.ali.idea.entity.edit.DummyLock;
import com.hp.alm.ali.idea.entity.edit.EntityEditStrategy;
import com.hp.alm.ali.idea.entity.edit.HorizonEditStrategy;
import com.hp.alm.ali.idea.entity.edit.LockingStrategy;
import com.hp.alm.ali.idea.filter.FilterChooser;
import com.hp.alm.ali.idea.model.parser.AuditList;
import com.hp.alm.ali.idea.model.type.BacklogBlockedType;
import com.hp.alm.ali.idea.model.type.BacklogStatusDefectType;
import com.hp.alm.ali.idea.model.type.BacklogEntityType;
import com.hp.alm.ali.idea.model.type.DefectStatusType;
import com.hp.alm.ali.idea.model.type.FeatureType;
import com.hp.alm.ali.idea.model.type.ReleaseType;
import com.hp.alm.ali.idea.model.type.SprintType;
import com.hp.alm.ali.idea.model.type.TeamType;
import com.hp.alm.ali.idea.model.type.ThemeType;
import com.hp.alm.ali.idea.rest.RestService;
import com.hp.alm.ali.idea.services.ApmUIService;
import com.hp.alm.ali.idea.ui.HorizonWorkspaceSelector;
import com.hp.alm.ali.idea.ui.chooser.FlatChooser;
import com.hp.alm.ali.idea.ui.combo.LazyComboBoxModel;
import com.hp.alm.ali.idea.ui.combo.TeamMembersComboBoxModel;
import com.intellij.openapi.actionSystem.ActionToolbar;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.IconLoader;
import org.apache.commons.httpclient.Cookie;
import javax.swing.JComponent;
import javax.swing.SortOrder;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
public class HorizonStrategy extends ApolloStrategy {
private static Set<String> INVISIBLE_FIELDS = new HashSet<String>(Arrays.asList(
"release-backlog-item.product-group-id",
"release-backlog-item.id",
"release-backlog-item.ver-stamp",
"release-backlog-item.vts",
"release-backlog-item.is-leaf",
"release-backlog-item.aggr-assignee",
"release-backlog-item.bi-req-type",
"build-type.product-group-id",
"requirement.product-group-id",
"requirement.target-rel-varchar",
"requirement.target-rcyc-varchar",
"requirement.req-ver-stamp",
"requirement.check-out-user-name",
"requirement.failed-at",
"requirement.passed-at",
"requirement.not-run-at",
"requirement.has-linkage",
"requirement.has-rich-content",
"requirement.istemplate",
"requirement.req-type",
"requirement.type-id",
"requirement.parent-id",
"requirement.order-id",
"requirement.father-name",
"requirement.hierarchical-path",
"requirement.req-review",
"requirement.req-rich-content",
"requirement.target-rcyc", // duplicates release-backlog-item.sprint-id
"requirement.target-rel", // duplicates release-backlog-item.release-id
"requirement.product-id",
"requirement.application-name",
"requirement.theme-products",
// request-* fields excluded by code
// rbt-* fields excluded by code
// vc-* fields excluded by code
"defect.product-group-id",
"defect.target-rel", // duplicates release-backlog-item.release-id
"defect.target-rcyc", // duplicates release-backlog-item.sprint-id
"defect.project",
"defect.run-reference",
"defect.step-reference",
"defect.cycle-id",
"defect.test-reference",
"defect.cycle-reference",
"defect.to-mail",
"defect.bug-ver-stamp",
"defect.owner"));
private static Map<String, String> FIELD_ALIAS;
static {
FIELD_ALIAS = new HashMap<String, String>();
FIELD_ALIAS.put("defect.owner", "release-backlog-item.owner");
FIELD_ALIAS.put("requirement.owner", "release-backlog-item.owner");
}
private static Map<String, EntityQuery> horizonFilter;
static {
horizonFilter = new HashMap<String, EntityQuery>();
addHorizonFilter(backlogItemFilter());
addHorizonFilter(horizonDefectFilter());
addHorizonFilter(teamFilter());
addHorizonFilter(taskFilter());
addHorizonFilter(acceptanceTestFilter());
}
private static Map<String, List<String>> horizonFields;
static {
horizonFields = new HashMap<String, List<String>>();
horizonFields.put("team", teamFields());
horizonFields.put("requirement", horizonRequirementFields());
}
private static Map<String, String> orderMap;
static {
orderMap = new HashMap<String, String>();
orderMap.put("release-backlog-item.release-id", "target-rel");
orderMap.put("release-backlog-item.sprint-id", "target-rcyc");
}
private WorkspaceConfiguration workspaceConfiguration;
private AuthenticationInfo authenticationInfo;
private String tenantId;
public HorizonStrategy(Project project, RestService restService, WorkspaceConfiguration workspaceConfiguration) {
super(project, restService);
this.workspaceConfiguration = workspaceConfiguration;
}
@Override
public EntityQuery preProcess(EntityQuery query) {
EntityQuery clone = super.preProcess(query);
if(!clone.getColumns().isEmpty()) {
if("release-backlog-item".equals(query.getEntityType())) {
clone.addColumn("entity-type", 1);
clone.addColumn("entity-id", 1);
clone.addColumn("blocked", 1);
} else if("defect".equals(query.getEntityType()) || "requirement".equals(query.getEntityType())) {
clone.addColumn("release-backlog-item.id", 1);
clone.addColumn("release-backlog-item.blocked", 1);
LinkedHashMap<String,SortOrder> order = clone.getOrder();
if(containsAny(order.keySet(), orderMap.keySet())) {
remapOrder(order, orderMap);
clone.setOrder(order);
}
} else if("project-task".equals(query.getEntityType())) {
clone.addColumn("release-backlog-item-id", 1);
}
// workspace support: add workspace condition and retrieve field
if ("defect".equals(query.getEntityType()) ||
"requirement".equals(query.getEntityType()) ||
"release-backlog-item".equals(query.getEntityType()) ||
"build-type".equals(query.getEntityType()) ||
"release".equals(query.getEntityType())) {
clone.addColumn("product-group-id", 1);
try {
Integer.valueOf(clone.getValue("id"));
} catch (NumberFormatException e) {
// unless query is made against particular item, add workspace condition
clone.setValue("product-group-id", String.valueOf(workspaceConfiguration.getWorkspaceId()));
clone.setPropertyResolved("product-group-id", true);
}
}
// until build instances support workspaces we need to use cross-filter
if ("build-instance".equals(query.getEntityType())) {
try {
Integer.valueOf(clone.getValue("id"));
} catch (NumberFormatException e) {
// unless query is made against particular item, add workspace condition
clone.setValue("build-type.product-group-id", String.valueOf(workspaceConfiguration.getWorkspaceId()));
clone.setPropertyResolved("build-type.product-group-id", true);
}
}
}
return clone;
}
private boolean containsAny(Set<String> set, Collection<String> items) {
for(String item: items) {
if(set.contains(item)) {
return true;
}
}
return false;
}
private void remapOrder(LinkedHashMap<String,SortOrder> order, Map<String, String> orderMap) {
ArrayList<String> list = new ArrayList<String>(order.keySet());
for(String key: list) {
SortOrder value = order.remove(key);
String newKey = orderMap.get(key);
if(newKey != null) {
order.put(newKey, value);
} else {
order.put(key, value);
}
}
}
@Override
public LazyComboBoxModel getUserModel() {
return new TeamMembersComboBoxModel(project);
}
@Override
public LockingStrategy getLockingStrategy() {
return new DummyLock(project);
}
@Override
public EntityEditStrategy getEntityEditStrategy() {
return project.getComponent(HorizonEditStrategy.class);
}
@Override
public List<DetailContent> getDetailContent(Entity entity) {
List<DetailContent> ret = super.getDetailContent(entity);
aliStrategyUtil.addCodeChangesDetail(this, entity, ret);
if("defect".equals(entity.getType())) {
ret.add(taskTable(entity));
}
if("requirement".equals(entity.getType())) {
ret.add(taskTable(entity));
ret.add(acceptanceTests(entity));
}
return ret;
}
private DetailContent taskTable(Entity entity) {
ActionToolbar actionToolbar = ActionUtil.createActionToolbar("hpali.project-task", "detail", true);
return new TableContent(project, entity, "Tasks", IconLoader.getIcon("/fileTypes/text.png"), actionToolbar, new TaskTableLoader(project, entity));
}
private DetailContent acceptanceTests(Entity entity) {
ActionToolbar actionToolbar = ActionUtil.createActionToolbar("hpali.acceptance-test", "detail", true);
EntityQuery query = new EntityQuery("acceptance-test");
query.setValue("entity-id", String.valueOf(entity.getId()));
query.setPropertyResolved("entity-id", true);
query.addOrder("logical-order", SortOrder.ASCENDING);
return aliStrategyUtil.detailTable(entity, query, "Acceptance Tests", IconLoader.getIcon("/fileTypes/text.png"), actionToolbar);
}
@Override
public EntityQuery getDefaultTableFilter(String entityType) {
EntityQuery defaults = horizonFilter.get(entityType);
if(defaults != null) {
EntityQuery query = new EntityQuery(entityType);
query.copyFrom(defaults);
return query;
} else {
return super.getDefaultTableFilter(entityType);
}
}
@Override
public List<String> getDefaultFields(String entityType) {
List<String> defaults = horizonFields.get(entityType);
if(defaults != null) {
return defaults;
} else {
return super.getDefaultFields(entityType);
}
}
@Override
public void fixMetadata(Metadata metadata) {
super.fixMetadataInherited(metadata);
if("release-backlog-item".equals(metadata.getEntityType())) {
// theme and feature reference requirement entity
metadata.getField("theme-id").setReferencedType("requirement");
metadata.getField("theme-id").setClazz(ThemeType.class);
metadata.getField("feature-id").setReferencedType("requirement");
metadata.getField("feature-id").setClazz(FeatureType.class);
// custom enum-like handling
metadata.getField("entity-type").setClazz(BacklogEntityType.class);
metadata.getField("blocked").setClazz(BacklogBlockedType.class);
// override labels
metadata.getField("entity-id").setLabel("ID");
metadata.getField("entity-name").setLabel("Name");
metadata.getField("entity-type").setLabel("Type");
metadata.getField("defect.priority").setLabel("Defect Priority");
metadata.getField("requirement.req-priority").setLabel("Story Priority");
}
if("project-task".equals(metadata.getEntityType())) {
// avoid "no value" option in filter selector
metadata.getField("status").setRequired(true);
}
if("defect".equals(metadata.getEntityType()) || "requirement".equals(metadata.getEntityType())) {
metadata.getField("release-backlog-item.theme-id").setReferencedType("requirement");
metadata.getField("release-backlog-item.theme-id").setClazz(ThemeType.class);
metadata.getField("release-backlog-item.theme-id").setEditable(false); // must be set through feature
metadata.getField("release-backlog-item.feature-id").setReferencedType("requirement");
metadata.getField("release-backlog-item.feature-id").setClazz(FeatureType.class);
metadata.getField("release-backlog-item.sprint-id").setReferencedType("release-cycle");
metadata.getField("release-backlog-item.sprint-id").setClazz(SprintType.class);
metadata.getField("release-backlog-item.release-id").setReferencedType("release");
metadata.getField("release-backlog-item.release-id").setClazz(ReleaseType.class);
metadata.getField("release-backlog-item.team-id").setReferencedType("team");
metadata.getField("release-backlog-item.team-id").setClazz(TeamType.class);
metadata.getField("release-backlog-item.team-id").setNoSort(true);
}
if("defect".equals(metadata.getEntityType())) {
metadata.getField("status").setClazz(DefectStatusType.class);
metadata.getField("release-backlog-item.status").setClazz(BacklogStatusDefectType.class);
metadata.getField("release-backlog-item.blocked").setClazz(BacklogBlockedType.class);
}
setClazz(metadata, "sprint-id", SprintType.class);
setClazz(metadata, "team-id", TeamType.class);
if ("acceptance-test".equals(metadata.getEntityType())) {
metadata.getField("owner").setLabel("Author");
metadata.getField("status").setLabel("Status");
metadata.getField("logical-order").setLabel("Order");
}
// remove fields that should be obsolete in AgM
for(Field field: new LinkedList<Field>(metadata.getAllFields().values())) {
String name = field.getName();
if(isInvisible(metadata.getEntityType(), name)) {
metadata.removeField(name);
continue;
}
String relatedType = field.getRelatedType();
if(relatedType != null) {
String shortName = name.substring(relatedType.length() + 1);
if(isInvisible(relatedType, shortName)) {
metadata.removeField(name);
continue;
}
}
}
}
@Override
public String getFieldAlias(String entityType, String property) {
String alias = FIELD_ALIAS.get(entityType + "." + property);
if(alias != null) {
return alias;
} else {
return property;
}
}
@Override
public List<AliContent> getSupportedContent() {
return Arrays.asList(
BacklogContent.getInstance(),
TaskBoardContent.getInstance(),
DefectsContent.getInstance(),
BuildsContent.getInstance(),
SettingsContent.getInstance());
}
@Override
public boolean canEditAttachmentFileName() {
return false;
}
private boolean isInvisible(String entityType, String fieldName) {
if(fieldName.startsWith("rbt-") || fieldName.startsWith("vc-") || fieldName.startsWith("request-")) {
return true;
}
return INVISIBLE_FIELDS.contains(entityType + "." + fieldName) || INVISIBLE_FIELDS.contains("*." + fieldName);
}
@Override
public List<String> getCompoundEntityTypes(String entityType) {
if("release-backlog-item".equals(entityType)) {
return Arrays.asList("requirement", "defect");
} else if("requirement".equals(entityType) || "defect".equals(entityType)) {
return Arrays.asList("release-backlog-item");
} else {
return Collections.emptyList();
}
}
@Override
public FilterChooser getFilterChooser(String entityType, boolean multiple, boolean idSelection, boolean acceptEmpty, String value) {
if("requirement".equals(entityType)) {
FlatChooser popup = new FlatChooser(project, "requirement", multiple, acceptEmpty, new EntityQueryProcessor() {
@Override
public EntityQuery preProcess(EntityQuery query) {
EntityQuery clone = query.clone();
clone.setValue("type-id", String.valueOf(70));
clone.setPropertyResolved("type-id", true);
return clone;
}
});
popup.setValue(value);
return popup;
} else {
return super.getFilterChooser(entityType, multiple, idSelection, acceptEmpty, value);
}
}
@Override
public String getCheckinPrefix(EntityRef entityRef) {
if ("requirement".equals(entityRef.type)) {
// we can't use entity label because it starts with capital letter
return "user story #" + entityRef.id + ":";
} else {
return super.getCheckinPrefix(entityRef);
}
}
@Override
public AuditList getEntityAudit(Entity entity) {
InputStream is = restService.getForStream("{0}s/{1}/audits", entity.getType(), entity.getId());
AuditList auditList = AuditList.create(is);
String bliId = (String)entity.getProperty("release-backlog-item.id");
if (bliId != null) {
InputStream blis = restService.getForStream("release-backlog-items/{0}/audits", bliId);
mergeAuditLists(auditList, AuditList.create(blis));
}
return auditList;
}
@Override
public void beforeConnectionHandler() {
authenticationInfo = project.getComponent(ApmUIService.class).getAuthenticationInfo();
Collection<Integer> workspaces = authenticationInfo.getAssignedWorkspaces();
WorkspaceConfiguration workspaceConfiguration = project.getComponent(WorkspaceConfiguration.class);
Integer workspaceId = workspaceConfiguration.getWorkspaceId();
if (!workspaces.contains(workspaceId)) {
// select new workspace
Iterator<Integer> it = workspaces.iterator();
if (it.hasNext()) {
workspaceConfiguration.setWorkspaceId(it.next());
} else {
throw new RuntimeException("No workspace available");
}
}
// obtain tenant id
tenantId = null;
List<Cookie> cookies = restService.getRestClient().getCookies("TENANT_ID_COOKIE");
for (Cookie cookie: cookies) {
if ("0".equals(cookie.getValue())) {
// dummy tenant we use for auto-select
continue;
}
// read tenant from the TENANT_ID_COOKIE
tenantId = cookie.getValue();
break;
}
}
@Override
public JComponent getConnectionComponent() {
if (authenticationInfo.getAssignedWorkspaces().size() > 1) {
return new HorizonWorkspaceSelector(project, authenticationInfo);
} else {
return null;
}
}
public String getTenantId() {
return tenantId;
}
private void mergeAuditLists(AuditList target, AuditList source) {
target.addAll(source);
Collections.sort(target, new Comparator<Audit>() {
@Override
public int compare(Audit audit1, Audit audit2) {
long time2 = audit2.getDate().getTime();
long time1 = audit1.getDate().getTime();
if (time2 > time1) {
return 1;
} else if (time2 < time1) {
return -1;
} else {
return 0;
}
}
});
Audit current = null;
for (Iterator<Audit> it = target.iterator(); it.hasNext(); ) {
Audit next = it.next();
if (current != null && current.getUsername().equals(next.getUsername()) && current.getDate().equals(next.getDate())) {
for(String[] properties: next.getProperties()) {
current.addProperty(properties[0], properties[1], properties[2]);
}
it.remove();
} else {
current = next;
}
}
}
private void setClazz(Metadata metadata, String fieldName, Class clazz) {
Field field = metadata.getField(fieldName);
if(field != null) {
field.setClazz(clazz);
}
}
private static EntityQuery horizonDefectFilter() {
EntityQuery filter = new EntityQuery("defect");
filter.addColumn("id", 97);
filter.addColumn("release-backlog-item.blocked", 56);
filter.addColumn("name", 257);
filter.addColumn("severity", 118);
filter.addColumn("status", 128);
filter.addColumn("release-backlog-item.status", 99);
filter.addColumn("release-backlog-item.owner", 145);
filter.addColumn("release-backlog-item.team-id", 142);
filter.addColumn("release-backlog-item.feature-id", 136);
filter.addColumn("release-backlog-item.theme-id", 141);
filter.addColumn("detected-by", 110);
filter.addColumn("priority", 123);
filter.addColumn("release-backlog-item.release-id", 155);
filter.addColumn("release-backlog-item.sprint-id", 87);
return filter;
}
private static EntityQuery backlogItemFilter() {
EntityQuery filter = new EntityQuery("release-backlog-item");
filter.addColumn("entity-type", 146);
filter.addColumn("blocked", 58);
filter.addColumn("entity-name", 898);
filter.addColumn("status", 84);
filter.addColumn("story-points", 75);
filter.addColumn("owner", 119);
filter.addColumn("team-id", 96);
filter.addColumn("feature-id", 159);
filter.addColumn("theme-id", 159);
return filter;
}
private static EntityQuery teamFilter() {
EntityQuery filter = new EntityQuery("team");
filter.addColumn("id", 50);
filter.addColumn("name", 150);
filter.addColumn("description", 200);
filter.addColumn("release-id", 50);
return filter;
}
private static List<String> teamFields() {
LinkedList<String> fields = new LinkedList<String>();
fields.add("name");
fields.add("description");
fields.add("release-id");
return fields;
}
private static List<String> horizonRequirementFields() {
LinkedList<String> fields = new LinkedList<String>();
fields.add("id");
fields.add("name");
fields.add("release-backlog-item.release-id");
fields.add("release-backlog-item.team-id");
fields.add("release-backlog-item.sprint-id");
fields.add("release-backlog-item.owner");
fields.add("req-priority");
fields.add("description");
fields.add("comments");
return fields;
}
private static EntityQuery taskFilter() {
EntityQuery filter = new EntityQuery("project-task");
filter.addColumn("description", 310);
filter.addColumn("status", 99);
filter.addColumn("assigned-to", 105);
filter.addColumn("invested", 68);
filter.addColumn("remaining", 68);
return filter;
}
private static EntityQuery acceptanceTestFilter() {
EntityQuery filter = new EntityQuery("acceptance-test");
filter.addColumn("description", 310);
filter.addColumn("status", 99);
filter.addColumn("owner", 105);
return filter;
}
private static void addHorizonFilter(EntityQuery entityQuery) {
horizonFilter.put(entityQuery.getEntityType(), entityQuery);
}
}