PageRenderTime 48ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/jira-project/jira-components/jira-core/src/main/java/com/atlassian/jira/charts/TimeSinceChart.java

https://bitbucket.org/ahmed_bilal_360factors/jira7-core
Java | 262 lines | 221 code | 29 blank | 12 comment | 10 complexity | f499228e5deb6c16b6facdcaf906f79e MD5 | raw file
Possible License(s): Apache-2.0
  1. package com.atlassian.jira.charts;
  2. import com.atlassian.jira.bc.issue.search.SearchService;
  3. import com.atlassian.jira.charts.jfreechart.ChartHelper;
  4. import com.atlassian.jira.charts.jfreechart.HistogramChartGenerator;
  5. import com.atlassian.jira.charts.jfreechart.util.ChartUtil;
  6. import com.atlassian.jira.charts.util.ChartUtils;
  7. import com.atlassian.jira.charts.util.DataUtils;
  8. import com.atlassian.jira.component.ComponentAccessor;
  9. import com.atlassian.jira.config.properties.ApplicationProperties;
  10. import com.atlassian.jira.datetime.LocalDate;
  11. import com.atlassian.jira.issue.fields.CustomField;
  12. import com.atlassian.jira.issue.fields.Field;
  13. import com.atlassian.jira.issue.fields.FieldManager;
  14. import com.atlassian.jira.issue.fields.NavigableField;
  15. import com.atlassian.jira.issue.index.IssueIndexManager;
  16. import com.atlassian.jira.issue.search.LuceneFieldSorter;
  17. import com.atlassian.jira.issue.search.SearchException;
  18. import com.atlassian.jira.issue.search.SearchProvider;
  19. import com.atlassian.jira.issue.search.SearchRequest;
  20. import com.atlassian.jira.issue.search.util.QueryOptimizer;
  21. import com.atlassian.jira.issue.search.util.RedundantClausesQueryOptimizer;
  22. import com.atlassian.jira.issue.statistics.util.FieldableDocumentHitCollector;
  23. import com.atlassian.jira.jql.builder.JqlClauseBuilder;
  24. import com.atlassian.jira.jql.builder.JqlQueryBuilder;
  25. import com.atlassian.jira.jql.util.JqlCustomFieldId;
  26. import com.atlassian.jira.user.ApplicationUser;
  27. import com.atlassian.jira.util.dbc.Assertions;
  28. import com.atlassian.jira.util.velocity.DefaultVelocityRequestContextFactory;
  29. import com.atlassian.jira.util.velocity.VelocityRequestContext;
  30. import com.atlassian.jira.web.bean.I18nBean;
  31. import com.atlassian.query.Query;
  32. import com.atlassian.query.order.SortOrder;
  33. import com.google.common.collect.Lists;
  34. import org.apache.commons.lang.StringEscapeUtils;
  35. import org.apache.lucene.document.Document;
  36. import org.apache.lucene.document.FieldSelector;
  37. import org.apache.lucene.document.MapFieldSelector;
  38. import org.apache.lucene.search.Collector;
  39. import org.apache.lucene.search.IndexSearcher;
  40. import org.jfree.chart.JFreeChart;
  41. import org.jfree.chart.labels.XYToolTipGenerator;
  42. import org.jfree.chart.plot.XYPlot;
  43. import org.jfree.chart.renderer.xy.XYBarRenderer;
  44. import org.jfree.chart.urls.TimeSeriesURLGenerator;
  45. import org.jfree.chart.urls.XYURLGenerator;
  46. import org.jfree.data.time.RegularTimePeriod;
  47. import org.jfree.data.time.TimeSeries;
  48. import org.jfree.data.time.TimeSeriesCollection;
  49. import org.jfree.data.time.TimeSeriesDataItem;
  50. import org.jfree.data.xy.XYDataset;
  51. import org.joda.time.DateTime;
  52. import java.io.IOException;
  53. import java.util.Date;
  54. import java.util.HashMap;
  55. import java.util.Map;
  56. import java.util.TimeZone;
  57. import java.util.TreeMap;
  58. /**
  59. * Produces a chart showing the number of issues based on a particular datefield.
  60. *
  61. * @since v4.0
  62. */
  63. class TimeSinceChart {
  64. private final FieldManager fieldManager;
  65. private final SearchProvider searchProvider;
  66. private final IssueIndexManager issueIndexManager;
  67. private final SearchService searchService;
  68. private final ApplicationProperties applicationProperties;
  69. private final TimeZone chartTimeZone;
  70. public TimeSinceChart(FieldManager fieldManager, SearchProvider searchProvider, IssueIndexManager issueIndexManager,
  71. SearchService searchService, ApplicationProperties applicationProperties, TimeZone chartTimeZone) {
  72. this.fieldManager = fieldManager;
  73. this.searchProvider = searchProvider;
  74. this.issueIndexManager = issueIndexManager;
  75. this.searchService = searchService;
  76. this.applicationProperties = applicationProperties;
  77. this.chartTimeZone = chartTimeZone;
  78. }
  79. public Chart generateInlineChart(final ApplicationUser remoteUser, final SearchRequest searchRequest, int days,
  80. final ChartFactory.PeriodName periodName, int width, int height, boolean cumulative, final String dateFieldId) {
  81. return generateChartInternal(remoteUser, searchRequest, days, periodName, width, height, cumulative, dateFieldId, true);
  82. }
  83. public Chart generateChart(final ApplicationUser remoteUser, final SearchRequest searchRequest, int days,
  84. final ChartFactory.PeriodName periodName, int width, int height, boolean cumulative, final String dateFieldId) {
  85. return generateChartInternal(remoteUser, searchRequest, days, periodName, width, height, cumulative, dateFieldId, false);
  86. }
  87. public Chart generateChartInternal(final ApplicationUser remoteUser, final SearchRequest searchRequest, int days,
  88. final ChartFactory.PeriodName periodName, int width, int height, boolean cumulative, final String dateFieldId, boolean inline) {
  89. Assertions.notNull("searchRequest", searchRequest);
  90. Assertions.notNull("dateFieldId", dateFieldId);
  91. try {
  92. final NavigableField dateField = fieldManager.getNavigableField(dateFieldId);
  93. days = DataUtils.normalizeDaysValue(days, periodName);
  94. final JqlQueryBuilder queryBuilder = JqlQueryBuilder.newBuilder(searchRequest.getQuery());
  95. final JqlClauseBuilder whereClauseBuilder = queryBuilder.where().defaultAnd();
  96. whereClauseBuilder.field(getJqlFieldIdFor(dateField)).gtEq().string("-" + days + "d");
  97. final Map<RegularTimePeriod, Number> matchingIssues = new TreeMap<RegularTimePeriod, Number>();
  98. final Class timePeriodClass = ChartUtil.getTimePeriodClass(periodName);
  99. final Collector hitCollector = new GenericDateFieldIssuesHitCollector(matchingIssues, issueIndexManager.getIssueSearcher(),
  100. dateField.getSorter(), timePeriodClass, dateFieldId, chartTimeZone);
  101. final Query query = whereClauseBuilder.buildQuery();
  102. searchProvider.search(query, remoteUser, hitCollector);
  103. DataUtils.normaliseDateRangeCount(matchingIssues, days - 1, timePeriodClass, chartTimeZone);
  104. final I18nBean i18nBean = new I18nBean(remoteUser);
  105. final TimeSeriesCollection originalDataset = DataUtils.getTimeSeriesCollection(Lists.newArrayList(matchingIssues),
  106. new String[]{dateField.getName()}, timePeriodClass);
  107. //need to calculate the number of total issues before potentially making the values cumulative, which
  108. //would yield the wrong total.
  109. int numIssues = DataUtils.getTotalNumber(matchingIssues);
  110. if (cumulative) {
  111. DataUtils.makeCumulative(matchingIssues);
  112. }
  113. final TimeSeriesCollection dataset = DataUtils.getTimeSeriesCollection(Lists.newArrayList(matchingIssues),
  114. new String[]{i18nBean.getText("datacollector.createdresolved")}, timePeriodClass);
  115. ChartHelper helper = new HistogramChartGenerator(dataset, i18nBean.getText("common.concepts.issues"), i18nBean).generateChart();
  116. JFreeChart chart = helper.getChart();
  117. XYPlot plot = (XYPlot) chart.getPlot();
  118. XYBarRenderer renderer = (XYBarRenderer) plot.getRenderer();
  119. renderer.setToolTipGenerator(new XYToolTipGenerator() {
  120. public String generateToolTip(XYDataset xyDataset, int row, int col) {
  121. final TimeSeriesCollection timeSeriesCollection = (TimeSeriesCollection) xyDataset;
  122. final TimeSeries timeSeries = timeSeriesCollection.getSeries(row);
  123. final TimeSeriesDataItem item = timeSeries.getDataItem(col);
  124. final RegularTimePeriod period = item.getPeriod();
  125. int total = matchingIssues.get(period).intValue();
  126. return period.toString() + ": " + total + " " + StringEscapeUtils.escapeHtml(dateField.getName()) + " " + i18nBean.getText("datacollector.tooltip.issues") + ".";
  127. }
  128. });
  129. final VelocityRequestContext velocityRequestContext = new DefaultVelocityRequestContextFactory(applicationProperties).getJiraVelocityRequestContext();
  130. XYURLGenerator urlGenerator = new TimeSeriesURLGenerator() {
  131. public String generateURL(XYDataset xyDataset, int row, int col) {
  132. final TimeSeriesCollection timeSeriesCollection = (TimeSeriesCollection) xyDataset;
  133. final TimeSeries timeSeries = timeSeriesCollection.getSeries(row);
  134. final TimeSeriesDataItem item = timeSeries.getDataItem(col);
  135. final RegularTimePeriod period = item.getPeriod();
  136. final JqlQueryBuilder jqlQueryBuilder = JqlQueryBuilder.newBuilder(searchRequest.getQuery());
  137. final Date startDate = period.getStart();
  138. final Date endDate = new Date(period.getEnd().getTime());
  139. jqlQueryBuilder.where().defaultAnd().addDateRangeCondition(getJqlFieldIdFor(dateField), startDate, endDate);
  140. jqlQueryBuilder.orderBy().addSortForFieldName(dateFieldId, SortOrder.DESC, true);
  141. final QueryOptimizer optimizer = new RedundantClausesQueryOptimizer();
  142. final Query query = optimizer.optimizeQuery(jqlQueryBuilder.buildQuery());
  143. return velocityRequestContext.getCanonicalBaseUrl() + searchService.getIssueSearchPath(remoteUser, SearchService.IssueSearchParameters.builder().query(query).build());
  144. }
  145. };
  146. renderer.setURLGenerator(urlGenerator);
  147. plot.setRenderer(renderer);
  148. if (inline) {
  149. helper.generateInline(width, height);
  150. } else {
  151. helper.generate(width, height);
  152. }
  153. final Map<String, Object> params = new HashMap<String, Object>();
  154. params.put("chart", helper.getLocation());
  155. params.put("chartDataset", dataset);
  156. params.put("completeDataset", originalDataset);
  157. params.put("completeDatasetUrlGenerator", urlGenerator);
  158. params.put("numIssues", numIssues);
  159. params.put("period", periodName.toString());
  160. params.put("imagemap", helper.getImageMap());
  161. params.put("imagemapName", helper.getImageMapName());
  162. params.put("field", dateField);
  163. params.put("daysPrevious", days);
  164. params.put("cumulative", cumulative);
  165. params.put("dateField", dateFieldId);
  166. params.put("chartFilterUrl", searchService.getIssueSearchPath(remoteUser, SearchService.IssueSearchParameters.builder().query(query).build()));
  167. if (inline) {
  168. String base64Image = ComponentAccessor.getComponent(ChartUtils.class).renderBase64Chart(helper.getImage(), "Time Since Chart");
  169. params.put("base64Image", base64Image);
  170. }
  171. return new Chart(helper.getLocation(), helper.getImageMap(), helper.getImageMapName(), params);
  172. } catch (IOException e) {
  173. throw new RuntimeException("Error generating chart", e);
  174. } catch (SearchException e) {
  175. throw new RuntimeException("Error generating chart", e);
  176. }
  177. }
  178. private String getJqlFieldIdFor(final Field field) {
  179. if (field instanceof CustomField) {
  180. final CustomField asCustomField = (CustomField) field;
  181. return JqlCustomFieldId.toString(asCustomField.getIdAsLong());
  182. } else {
  183. return field.getId();
  184. }
  185. }
  186. /**
  187. * Creates a mapping of RegularTimePeriod -> Issuecount.
  188. */
  189. static class GenericDateFieldIssuesHitCollector extends FieldableDocumentHitCollector {
  190. private final Map<RegularTimePeriod, Number> resolvedMap;
  191. private final LuceneFieldSorter fieldSorter;
  192. private final Class timePeriodClass;
  193. private final String dateFieldId;
  194. private final TimeZone timeZone;
  195. private final FieldSelector fieldSelector;
  196. public GenericDateFieldIssuesHitCollector(Map<RegularTimePeriod, Number> resolvedMap, IndexSearcher searcher,
  197. LuceneFieldSorter fieldSorter,
  198. Class timePeriodClass, final String dateFieldId, TimeZone timeZone) {
  199. super(searcher);
  200. this.resolvedMap = resolvedMap;
  201. this.fieldSorter = fieldSorter;
  202. this.timePeriodClass = timePeriodClass;
  203. this.dateFieldId = dateFieldId;
  204. this.timeZone = timeZone;
  205. fieldSelector = new MapFieldSelector(dateFieldId);
  206. }
  207. @Override
  208. protected FieldSelector getFieldSelector() {
  209. return fieldSelector;
  210. }
  211. public void collect(Document d) {
  212. final Object dateObject = fieldSorter.getValueFromLuceneField(d.get(dateFieldId));
  213. final Date date;
  214. if (dateObject instanceof LocalDate) {
  215. final LocalDate localDate = (LocalDate) dateObject;
  216. // special handling for Due Date which is not a real date
  217. date = new DateTime(localDate.getYear(), localDate.getMonth(), localDate.getDay(), 0, 0).toDate();
  218. } else {
  219. // dateObject is Date always, but if it ever changes we can try to handle it (or throw an exception if we fail)
  220. date = new DateTime(dateObject).toDate();
  221. }
  222. final RegularTimePeriod period = RegularTimePeriod.createInstance(timePeriodClass, date, timeZone);
  223. incrementMap(resolvedMap, period);
  224. }
  225. private void incrementMap(Map<RegularTimePeriod, Number> map, RegularTimePeriod period) {
  226. Number count = map.get(period);
  227. if (count == null) {
  228. count = 0;
  229. }
  230. map.put(period, count.intValue() + 1);
  231. }
  232. }
  233. }