com.capitalone.dashboard.service.FeatureServiceImpl Maven / Gradle / Ivy
package com.capitalone.dashboard.service;
import com.capitalone.dashboard.model.Collector;
import com.capitalone.dashboard.model.CollectorItem;
import com.capitalone.dashboard.model.CollectorType;
import com.capitalone.dashboard.model.Component;
import com.capitalone.dashboard.model.DataResponse;
import com.capitalone.dashboard.model.Feature;
import com.capitalone.dashboard.model.QScopeOwner;
import com.capitalone.dashboard.repository.CollectorRepository;
import com.capitalone.dashboard.repository.ComponentRepository;
import com.capitalone.dashboard.repository.FeatureRepository;
import com.capitalone.dashboard.util.FeatureCollectorConstants;
import com.mysema.query.BooleanBuilder;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import javax.xml.bind.DatatypeConverter;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.TimeZone;
@Service
public class FeatureServiceImpl implements FeatureService {
private final ComponentRepository componentRepository;
private final FeatureRepository featureRepository;
private final CollectorRepository collectorRepository;
private final static String NOT_EQUAL = "$ne";
private final static String EQUAL = "$eq";
private enum Status {
TOTAL, DONE, InProgress
}
/**
* Default autowired constructor for repositories
*
* @param componentRepository
* Repository containing components used by the UI (populated by
* UI)
* @param collectorRepository
* Repository containing all registered collectors
* @param featureRepository
* Repository containing all features
*/
@Autowired
public FeatureServiceImpl(ComponentRepository componentRepository,
CollectorRepository collectorRepository, FeatureRepository featureRepository) {
this.componentRepository = componentRepository;
this.featureRepository = featureRepository;
this.collectorRepository = collectorRepository;
}
/**
* Retrieves a single story based on a back-end story number
*
* @param componentId
* The ID of the related UI component that will reference
* collector item content from this collector
* @param storyNumber
* A back-end story ID used by a source system
* @return A data response list of type Feature containing a single story
*/
@Override
public DataResponse> getStory(ObjectId componentId, String storyNumber) {
Component component = componentRepository.findOne(componentId);
if ((component == null) || CollectionUtils.isEmpty(component.getCollectorItems())
|| CollectionUtils
.isEmpty(component.getCollectorItems().get(CollectorType.ScopeOwner))
|| (component.getCollectorItems().get(CollectorType.ScopeOwner).get(0) == null)) {
return getEmptyDataResponse();
}
CollectorItem item = component.getCollectorItems().get(CollectorType.ScopeOwner).get(0);
QScopeOwner team = new QScopeOwner("team");
BooleanBuilder builder = new BooleanBuilder();
builder.and(team.collectorItemId.eq(item.getId()));
// Get one story based on story number, based on component
List story = featureRepository.getStoryByNumber(storyNumber);
Collector collector = collectorRepository.findOne(item.getCollectorId());
return new DataResponse<>(story, collector.getLastExecuted());
}
private DataResponse> getEmptyDataResponse() {
Feature f = new Feature();
List l = new ArrayList<>();
l.add(f);
return new DataResponse<>(l, 0);
}
/**
* Retrieves all stories for a given team and their current sprint
*
* @param componentId
* The ID of the related UI component that will reference
* collector item content from this collector
* @param teamId
* A given scope-owner's source-system ID
* @return A data response list of type Feature containing all features for
* the given team and current sprint
*/
@Override
public DataResponse> getRelevantStories(ObjectId componentId, String teamId,
Optional agileType) {
Component component = componentRepository.findOne(componentId);
if ((component == null) || CollectionUtils.isEmpty(component.getCollectorItems())
|| CollectionUtils
.isEmpty(component.getCollectorItems().get(CollectorType.ScopeOwner))
|| (component.getCollectorItems().get(CollectorType.ScopeOwner).get(0) == null)) {
return getEmptyDataResponse();
}
CollectorItem item = component.getCollectorItems().get(CollectorType.ScopeOwner).get(0);
QScopeOwner team = new QScopeOwner("team");
BooleanBuilder builder = new BooleanBuilder();
builder.and(team.collectorItemId.eq(item.getId()));
// Get teamId first from available collector item, based on
// component
List relevantStories = new ArrayList();
if (agileType.isPresent()
&& FeatureCollectorConstants.KANBAN_SPRINT_ID.equalsIgnoreCase(agileType.get())) {
// Kanban
relevantStories = featureRepository.queryByOrderBySStatusDesc(teamId,
getCurrentISODateTime(), EQUAL, FeatureCollectorConstants.KANBAN_SPRINT_ID);
} else if (agileType.isPresent()
&& FeatureCollectorConstants.SCRUM_SPRINT_ID.equalsIgnoreCase(agileType.get())) {
// Scrum
relevantStories = featureRepository.queryByOrderBySStatusDesc(teamId,
getCurrentISODateTime(), NOT_EQUAL, FeatureCollectorConstants.KANBAN_SPRINT_ID);
} else {
// Legacy
relevantStories = featureRepository.queryByOrderBySStatusDesc(teamId,
getCurrentISODateTime());
}
Collector collector = collectorRepository.findOne(item.getCollectorId());
return new DataResponse<>(relevantStories, collector.getLastExecuted());
}
/**
* Retrieves all unique super features and their total sub feature estimates
* for a given team and their current sprint
*
* @param componentId
* The ID of the related UI component that will reference
* collector item content from this collector
* @param teamId
* A given scope-owner's source-system ID
* @return A data response list of type Feature containing the unique
* features plus their sub features' estimates associated to the
* current sprint and team
*/
@Override
public DataResponse> getFeatureEstimates(ObjectId componentId, String teamId,
Optional agileType, Optional estimateMetricType) {
Component component = componentRepository.findOne(componentId);
if ((component == null) || CollectionUtils.isEmpty(component.getCollectorItems())
|| CollectionUtils
.isEmpty(component.getCollectorItems().get(CollectorType.ScopeOwner))
|| (component.getCollectorItems().get(CollectorType.ScopeOwner).get(0) == null)) {
return getEmptyDataResponse();
}
CollectorItem item = component.getCollectorItems().get(CollectorType.ScopeOwner).get(0);
QScopeOwner team = new QScopeOwner("team");
BooleanBuilder builder = new BooleanBuilder();
builder.and(team.collectorItemId.eq(item.getId()));
// Get teamId first from available collector item, based on component
List relevantFeatureEstimates = new ArrayList();
if (agileType.isPresent()
&& FeatureCollectorConstants.KANBAN_SPRINT_ID.equalsIgnoreCase(agileType.get())) {
// Kanban
relevantFeatureEstimates = featureRepository.getInProgressFeaturesEstimatesByTeamId(
teamId, getCurrentISODateTime(), EQUAL, FeatureCollectorConstants.KANBAN_SPRINT_ID);
} else if (agileType.isPresent()
&& FeatureCollectorConstants.SCRUM_SPRINT_ID.equalsIgnoreCase(agileType.get())) {
// Scrum
relevantFeatureEstimates = featureRepository.getInProgressFeaturesEstimatesByTeamId(
teamId, getCurrentISODateTime(), NOT_EQUAL, FeatureCollectorConstants.KANBAN_SPRINT_ID);
} else {
// Legacy
relevantFeatureEstimates = featureRepository
.getInProgressFeaturesEstimatesByTeamId(teamId, getCurrentISODateTime());
}
// epicID : epic information (in the form of a Feature object)
Map epicIDToEpicFeatureMap = new HashMap<>();
for (Feature tempRs : relevantFeatureEstimates) {
String epicID = tempRs.getsEpicID();
if (StringUtils.isEmpty(epicID))
continue;
Feature feature = epicIDToEpicFeatureMap.get(epicID);
if (feature == null) {
feature = new Feature();
feature.setId(null);
feature.setsEpicID(epicID);
feature.setsEpicNumber(tempRs.getsEpicNumber());
feature.setsEpicName(tempRs.getsEpicName());
feature.setsEstimate("0");
epicIDToEpicFeatureMap.put(epicID, feature);
}
// if estimateMetricType is hours accumulate time estimate in minutes for better precision ... divide by 60 later
int estimate = getEstimate(tempRs, estimateMetricType);
feature.setsEstimate(String.valueOf(Integer.valueOf(feature.getsEstimate()) + estimate));
}
if (isEstimateTime(estimateMetricType)) {
// time estimate is in minutes but we want to return in hours
for (Feature f : epicIDToEpicFeatureMap.values()) {
f.setsEstimate(String.valueOf(Integer.valueOf(f.getsEstimate()) / 60));
}
}
Collector collector = collectorRepository.findOne(item.getCollectorId());
return new DataResponse<>(new ArrayList<>(epicIDToEpicFeatureMap.values()), collector.getLastExecuted());
}
private DataResponse> getEstimate(ObjectId componentId, String teamId,
Status status, Optional agileType, Optional estimateMetricType) {
Component component = componentRepository.findOne(componentId);
if ((component == null) || CollectionUtils.isEmpty(component.getCollectorItems())
|| CollectionUtils
.isEmpty(component.getCollectorItems().get(CollectorType.ScopeOwner))
|| (component.getCollectorItems().get(CollectorType.ScopeOwner).get(0) == null)) {
return getEmptyDataResponse();
}
CollectorItem item = component.getCollectorItems().get(CollectorType.ScopeOwner).get(0);
QScopeOwner team = new QScopeOwner("team");
BooleanBuilder builder = new BooleanBuilder();
builder.and(team.collectorItemId.eq(item.getId()));
// Get teamId first from available collector item, based on component
List storyEstimates;
switch (status) {
case TOTAL:
if (agileType.isPresent() && FeatureCollectorConstants.KANBAN_SPRINT_ID
.equalsIgnoreCase(agileType.get())) {
// Kanban
storyEstimates = featureRepository.getSprintBacklogTotal(teamId,
getCurrentISODateTime(), EQUAL, FeatureCollectorConstants.KANBAN_SPRINT_ID);
} else if (agileType.isPresent() && FeatureCollectorConstants.SCRUM_SPRINT_ID
.equalsIgnoreCase(agileType.get())) {
// Scrum
storyEstimates = featureRepository.getSprintBacklogTotal(teamId,
getCurrentISODateTime(), NOT_EQUAL, FeatureCollectorConstants.KANBAN_SPRINT_ID);
} else {
// Legacy
storyEstimates = featureRepository.getSprintBacklogTotal(teamId,
getCurrentISODateTime());
}
break;
case DONE:
if (agileType.isPresent() && FeatureCollectorConstants.KANBAN_SPRINT_ID
.equalsIgnoreCase(agileType.get())) {
// Kanban
storyEstimates = featureRepository.getSprintBacklogDone(teamId,
getCurrentISODateTime(), EQUAL, FeatureCollectorConstants.KANBAN_SPRINT_ID);
} else if (agileType.isPresent() && FeatureCollectorConstants.SCRUM_SPRINT_ID
.equalsIgnoreCase(agileType.get())) {
// Scrum
storyEstimates = featureRepository.getSprintBacklogDone(teamId,
getCurrentISODateTime(), NOT_EQUAL, FeatureCollectorConstants.KANBAN_SPRINT_ID);
} else {
// Legacy
storyEstimates = featureRepository.getSprintBacklogDone(teamId,
getCurrentISODateTime());
}
break;
case InProgress:
if (agileType.isPresent() && FeatureCollectorConstants.KANBAN_SPRINT_ID
.equalsIgnoreCase(agileType.get())) {
// Kanban
storyEstimates = featureRepository.getSprintBacklogInProgress(teamId,
getCurrentISODateTime(), EQUAL, FeatureCollectorConstants.KANBAN_SPRINT_ID);
} else if (agileType.isPresent() && FeatureCollectorConstants.SCRUM_SPRINT_ID
.equalsIgnoreCase(agileType.get())) {
// Scrum
storyEstimates = featureRepository.getSprintBacklogInProgress(teamId,
getCurrentISODateTime(), NOT_EQUAL, FeatureCollectorConstants.KANBAN_SPRINT_ID);
} else {
// Legacy
storyEstimates = featureRepository.getSprintBacklogInProgress(teamId,
getCurrentISODateTime());
}
break;
default:
storyEstimates = new ArrayList<>();
break;
}
List cumulativeEstimate = new ArrayList<>();
Feature f = new Feature();
int lineTotalEstimate = 0;
for (Feature tempRs : storyEstimates) {
// if estimateMetricType is hours accumulate time estimate in minutes for better precision ... divide by 60 later
lineTotalEstimate += getEstimate(tempRs, estimateMetricType);
}
if (isEstimateTime(estimateMetricType)) {
// time estimate is in minutes but we want to return in hours
lineTotalEstimate /= 60;
}
f.setsEstimate(Integer.toString(lineTotalEstimate));
cumulativeEstimate.add(f);
Collector collector = collectorRepository.findOne(item.getCollectorId());
return new DataResponse<>(cumulativeEstimate, collector.getLastExecuted());
}
/**
* Retrieves estimate total of all features in the current sprint and for
* the current team.
*
* @param componentId
* The ID of the related UI component that will reference
* collector item content from this collector
* @param teamId
* A given scope-owner's source-system ID
* @return A data response list of type Feature containing the total
* estimate number for all features
*/
@Override
public DataResponse> getTotalEstimate(ObjectId componentId, String teamId,
Optional agileType, Optional estimateMetricType) {
return getEstimate(componentId, teamId, Status.TOTAL, agileType, estimateMetricType);
}
/**
* Retrieves estimate in-progress of all features in the current sprint and
* for the current team.
*
* @param componentId
* The ID of the related UI component that will reference
* collector item content from this collector
* @param teamId
* A given scope-owner's source-system ID
* @return A data response list of type Feature containing the in-progress
* estimate number for all features
*/
@Override
public DataResponse> getInProgressEstimate(ObjectId componentId, String teamId,
Optional agileType, Optional estimateMetricType) {
return getEstimate(componentId, teamId, Status.InProgress, agileType, estimateMetricType);
}
/**
* Retrieves estimate done of all features in the current sprint and for the
* current team.
*
* @param componentId
* The ID of the related UI component that will reference
* collector item content from this collector
* @param teamId
* A given scope-owner's source-system ID
* @return A data response list of type Feature containing the done estimate
* number for all features
*/
@Override
public DataResponse> getDoneEstimate(ObjectId componentId, String teamId,
Optional agileType, Optional estimateMetricType) {
return getEstimate(componentId, teamId, Status.DONE, agileType, estimateMetricType);
}
/**
* Retrieves the current sprint's detail for a given team.
*
* @param componentId
* The ID of the related UI component that will reference
* collector item content from this collector
* @param teamId
* A given scope-owner's source-system ID
* @return A data response list of type Feature containing several relevant
* sprint fields for the current team's sprint
*/
@Override
public DataResponse> getCurrentSprintDetail(ObjectId componentId, String teamId,
Optional agileType) {
Component component = componentRepository.findOne(componentId);
if ((component == null) || CollectionUtils.isEmpty(component.getCollectorItems())
|| CollectionUtils
.isEmpty(component.getCollectorItems().get(CollectorType.ScopeOwner))
|| (component.getCollectorItems().get(CollectorType.ScopeOwner).get(0) == null)) {
return getEmptyDataResponse();
}
CollectorItem item = component.getCollectorItems().get(CollectorType.ScopeOwner).get(0);
QScopeOwner team = new QScopeOwner("team");
BooleanBuilder builder = new BooleanBuilder();
builder.and(team.collectorItemId.eq(item.getId()));
// Get teamId first from available collector item, based on component
List sprintResponse = new ArrayList();
if (agileType.isPresent()
&& FeatureCollectorConstants.KANBAN_SPRINT_ID.equalsIgnoreCase(agileType.get())) {
// Kanban
sprintResponse = featureRepository.getCurrentSprintDetail(teamId,
getCurrentISODateTime(), EQUAL, FeatureCollectorConstants.KANBAN_SPRINT_ID);
} else if (agileType.isPresent()
&& FeatureCollectorConstants.SCRUM_SPRINT_ID.equalsIgnoreCase(agileType.get())) {
// Scrum
sprintResponse = featureRepository.getCurrentSprintDetail(teamId,
getCurrentISODateTime(), NOT_EQUAL, FeatureCollectorConstants.KANBAN_SPRINT_ID);
} else {
// Legacy
sprintResponse = featureRepository.getCurrentSprintDetail(teamId,
getCurrentISODateTime());
}
List sprintDetail = new ArrayList<>();
for (Feature f : sprintResponse) {
sprintDetail.add(f);
}
Collector collector = collectorRepository.findOne(item.getCollectorId());
return new DataResponse<>(sprintDetail, collector.getLastExecuted());
}
/**
* Retrieves the current system time stamp in ISO date time format. Because
* this is not using SimpleTimeFormat, this should be thread safe.
*
* @return A string representation of the current date time stamp in ISO
* format from the current time zone
*/
private String getCurrentISODateTime() {
return DatatypeConverter.printDateTime(Calendar.getInstance(TimeZone.getTimeZone("UTC")));
}
private boolean isEstimateTime(Optional estimateMetricType) {
return estimateMetricType.isPresent() && FeatureCollectorConstants.STORY_HOURS_ESTIMATE.equalsIgnoreCase(estimateMetricType.get());
}
private int getEstimate(Feature feature, Optional estimateMetricType) {
int rt = 0;
if (isEstimateTime(estimateMetricType)) {
if (feature.getsEstimateTime() != null) {
rt = feature.getsEstimateTime().intValue();
}
} else {
// default to story points since that should be the most common use case
if (!StringUtils.isEmpty(feature.getsEstimate())) {
rt = Integer.parseInt(feature.getsEstimate());
}
}
return rt;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy