You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ProjectPage.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374
  1. /*
  2. * Copyright 2012 gitblit.com.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.gitblit.wicket.pages;
  17. import java.io.File;
  18. import java.io.FileInputStream;
  19. import java.io.InputStreamReader;
  20. import java.text.MessageFormat;
  21. import java.text.SimpleDateFormat;
  22. import java.util.ArrayList;
  23. import java.util.Collections;
  24. import java.util.Comparator;
  25. import java.util.HashMap;
  26. import java.util.HashSet;
  27. import java.util.List;
  28. import java.util.Map;
  29. import java.util.Set;
  30. import org.apache.wicket.Component;
  31. import org.apache.wicket.PageParameters;
  32. import org.apache.wicket.RedirectException;
  33. import org.apache.wicket.behavior.HeaderContributor;
  34. import org.apache.wicket.markup.html.basic.Label;
  35. import org.apache.wicket.markup.html.link.ExternalLink;
  36. import org.apache.wicket.markup.repeater.Item;
  37. import org.apache.wicket.markup.repeater.data.DataView;
  38. import org.apache.wicket.markup.repeater.data.ListDataProvider;
  39. import org.eclipse.jgit.lib.Constants;
  40. import com.gitblit.GitBlit;
  41. import com.gitblit.Keys;
  42. import com.gitblit.SyndicationServlet;
  43. import com.gitblit.models.Activity;
  44. import com.gitblit.models.Metric;
  45. import com.gitblit.models.ProjectModel;
  46. import com.gitblit.models.RepositoryModel;
  47. import com.gitblit.models.UserModel;
  48. import com.gitblit.utils.ActivityUtils;
  49. import com.gitblit.utils.MarkdownUtils;
  50. import com.gitblit.utils.StringUtils;
  51. import com.gitblit.wicket.GitBlitWebApp;
  52. import com.gitblit.wicket.GitBlitWebSession;
  53. import com.gitblit.wicket.PageRegistration;
  54. import com.gitblit.wicket.PageRegistration.DropDownMenuItem;
  55. import com.gitblit.wicket.PageRegistration.DropDownMenuRegistration;
  56. import com.gitblit.wicket.WicketUtils;
  57. import com.gitblit.wicket.charting.GoogleChart;
  58. import com.gitblit.wicket.charting.GoogleCharts;
  59. import com.gitblit.wicket.charting.GoogleLineChart;
  60. import com.gitblit.wicket.charting.GooglePieChart;
  61. import com.gitblit.wicket.panels.ActivityPanel;
  62. import com.gitblit.wicket.panels.ProjectRepositoryPanel;
  63. public class ProjectPage extends RootPage {
  64. List<ProjectModel> projectModels = new ArrayList<ProjectModel>();
  65. public ProjectPage() {
  66. super();
  67. throw new RedirectException(GitBlitWebApp.get().getHomePage());
  68. }
  69. public ProjectPage(PageParameters params) {
  70. super(params);
  71. setup(params);
  72. }
  73. @Override
  74. protected boolean reusePageParameters() {
  75. return true;
  76. }
  77. private void setup(PageParameters params) {
  78. setupPage("", "");
  79. // check to see if we should display a login message
  80. boolean authenticateView = GitBlit.getBoolean(Keys.web.authenticateViewPages, true);
  81. if (authenticateView && !GitBlitWebSession.get().isLoggedIn()) {
  82. authenticationError("Please login");
  83. return;
  84. }
  85. String projectName = WicketUtils.getProjectName(params);
  86. if (StringUtils.isEmpty(projectName)) {
  87. throw new RedirectException(GitBlitWebApp.get().getHomePage());
  88. }
  89. ProjectModel project = getProjectModel(projectName);
  90. if (project == null) {
  91. throw new RedirectException(GitBlitWebApp.get().getHomePage());
  92. }
  93. add(new Label("projectTitle", project.getDisplayName()));
  94. add(new Label("projectDescription", project.description));
  95. String feedLink = SyndicationServlet.asLink(getRequest().getRelativePathPrefixToContextRoot(), projectName, null, 0);
  96. add(new ExternalLink("syndication", feedLink));
  97. add(WicketUtils.syndicationDiscoveryLink(SyndicationServlet.getTitle(project.getDisplayName(),
  98. null), feedLink));
  99. final String projectPath;
  100. if (project.isRoot) {
  101. projectPath = "";
  102. } else {
  103. projectPath = projectName + "/";
  104. }
  105. // project markdown message
  106. File pmkd = new File(GitBlit.getRepositoriesFolder(), projectPath + "project.mkd");
  107. String pmessage = readMarkdown(projectName, pmkd);
  108. Component projectMessage = new Label("projectMessage", pmessage)
  109. .setEscapeModelStrings(false).setVisible(pmessage.length() > 0);
  110. add(projectMessage);
  111. // markdown message above repositories list
  112. File rmkd = new File(GitBlit.getRepositoriesFolder(), projectPath + "repositories.mkd");
  113. String rmessage = readMarkdown(projectName, rmkd);
  114. Component repositoriesMessage = new Label("repositoriesMessage", rmessage)
  115. .setEscapeModelStrings(false).setVisible(rmessage.length() > 0);
  116. add(repositoriesMessage);
  117. List<RepositoryModel> repositories = getRepositories(params);
  118. Collections.sort(repositories, new Comparator<RepositoryModel>() {
  119. @Override
  120. public int compare(RepositoryModel o1, RepositoryModel o2) {
  121. // reverse-chronological sort
  122. return o2.lastChange.compareTo(o1.lastChange);
  123. }
  124. });
  125. final ListDataProvider<RepositoryModel> dp = new ListDataProvider<RepositoryModel>(repositories);
  126. DataView<RepositoryModel> dataView = new DataView<RepositoryModel>("repositoryList", dp) {
  127. private static final long serialVersionUID = 1L;
  128. public void populateItem(final Item<RepositoryModel> item) {
  129. final RepositoryModel entry = item.getModelObject();
  130. ProjectRepositoryPanel row = new ProjectRepositoryPanel("repository",
  131. getLocalizer(), this, showAdmin, entry, getAccessRestrictions());
  132. item.add(row);
  133. }
  134. };
  135. add(dataView);
  136. // project activity
  137. // parameters
  138. int daysBack = WicketUtils.getDaysBack(params);
  139. if (daysBack < 1) {
  140. daysBack = 14;
  141. }
  142. String objectId = WicketUtils.getObject(params);
  143. List<Activity> recentActivity = ActivityUtils.getRecentActivity(repositories,
  144. daysBack, objectId, getTimeZone());
  145. if (recentActivity.size() == 0) {
  146. // no activity, skip graphs and activity panel
  147. add(new Label("subheader", MessageFormat.format(getString("gb.recentActivityNone"),
  148. daysBack)));
  149. add(new Label("activityPanel"));
  150. } else {
  151. // calculate total commits and total authors
  152. int totalCommits = 0;
  153. Set<String> uniqueAuthors = new HashSet<String>();
  154. for (Activity activity : recentActivity) {
  155. totalCommits += activity.getCommitCount();
  156. uniqueAuthors.addAll(activity.getAuthorMetrics().keySet());
  157. }
  158. int totalAuthors = uniqueAuthors.size();
  159. // add the subheader with stat numbers
  160. add(new Label("subheader", MessageFormat.format(getString("gb.recentActivityStats"),
  161. daysBack, totalCommits, totalAuthors)));
  162. // create the activity charts
  163. GoogleCharts charts = createCharts(recentActivity);
  164. add(new HeaderContributor(charts));
  165. // add activity panel
  166. add(new ActivityPanel("activityPanel", recentActivity));
  167. }
  168. }
  169. /**
  170. * Creates the daily activity line chart, the active repositories pie chart,
  171. * and the active authors pie chart
  172. *
  173. * @param recentActivity
  174. * @return
  175. */
  176. private GoogleCharts createCharts(List<Activity> recentActivity) {
  177. // activity metrics
  178. Map<String, Metric> repositoryMetrics = new HashMap<String, Metric>();
  179. Map<String, Metric> authorMetrics = new HashMap<String, Metric>();
  180. // aggregate repository and author metrics
  181. for (Activity activity : recentActivity) {
  182. // aggregate author metrics
  183. for (Map.Entry<String, Metric> entry : activity.getAuthorMetrics().entrySet()) {
  184. String author = entry.getKey();
  185. if (!authorMetrics.containsKey(author)) {
  186. authorMetrics.put(author, new Metric(author));
  187. }
  188. authorMetrics.get(author).count += entry.getValue().count;
  189. }
  190. // aggregate repository metrics
  191. for (Map.Entry<String, Metric> entry : activity.getRepositoryMetrics().entrySet()) {
  192. String repository = StringUtils.stripDotGit(entry.getKey());
  193. if (!repositoryMetrics.containsKey(repository)) {
  194. repositoryMetrics.put(repository, new Metric(repository));
  195. }
  196. repositoryMetrics.get(repository).count += entry.getValue().count;
  197. }
  198. }
  199. // build google charts
  200. int w = 310;
  201. int h = 150;
  202. GoogleCharts charts = new GoogleCharts();
  203. // sort in reverse-chronological order and then reverse that
  204. Collections.sort(recentActivity);
  205. Collections.reverse(recentActivity);
  206. // daily line chart
  207. GoogleChart chart = new GoogleLineChart("chartDaily", getString("gb.dailyActivity"), "day",
  208. getString("gb.commits"));
  209. SimpleDateFormat df = new SimpleDateFormat("MMM dd");
  210. df.setTimeZone(getTimeZone());
  211. for (Activity metric : recentActivity) {
  212. chart.addValue(df.format(metric.startDate), metric.getCommitCount());
  213. }
  214. chart.setWidth(w);
  215. chart.setHeight(h);
  216. charts.addChart(chart);
  217. // active repositories pie chart
  218. chart = new GooglePieChart("chartRepositories", getString("gb.activeRepositories"),
  219. getString("gb.repository"), getString("gb.commits"));
  220. for (Metric metric : repositoryMetrics.values()) {
  221. chart.addValue(metric.name, metric.count);
  222. }
  223. chart.setWidth(w);
  224. chart.setHeight(h);
  225. charts.addChart(chart);
  226. // active authors pie chart
  227. chart = new GooglePieChart("chartAuthors", getString("gb.activeAuthors"),
  228. getString("gb.author"), getString("gb.commits"));
  229. for (Metric metric : authorMetrics.values()) {
  230. chart.addValue(metric.name, metric.count);
  231. }
  232. chart.setWidth(w);
  233. chart.setHeight(h);
  234. charts.addChart(chart);
  235. return charts;
  236. }
  237. @Override
  238. protected void addDropDownMenus(List<PageRegistration> pages) {
  239. PageParameters params = getPageParameters();
  240. DropDownMenuRegistration projects = new DropDownMenuRegistration("gb.projects",
  241. ProjectPage.class);
  242. projects.menuItems.addAll(getProjectsMenu());
  243. pages.add(0, projects);
  244. DropDownMenuRegistration menu = new DropDownMenuRegistration("gb.filters",
  245. ProjectPage.class);
  246. // preserve time filter option on repository choices
  247. menu.menuItems.addAll(getRepositoryFilterItems(params));
  248. // preserve repository filter option on time choices
  249. menu.menuItems.addAll(getTimeFilterItems(params));
  250. if (menu.menuItems.size() > 0) {
  251. // Reset Filter
  252. menu.menuItems.add(new DropDownMenuItem(getString("gb.reset"), null, null));
  253. }
  254. pages.add(menu);
  255. }
  256. @Override
  257. protected List<ProjectModel> getProjectModels() {
  258. if (projectModels.isEmpty()) {
  259. final UserModel user = GitBlitWebSession.get().getUser();
  260. List<ProjectModel> projects = GitBlit.self().getProjectModels(user, false);
  261. projectModels.addAll(projects);
  262. }
  263. return projectModels;
  264. }
  265. private ProjectModel getProjectModel(String name) {
  266. for (ProjectModel project : getProjectModels()) {
  267. if (name.equalsIgnoreCase(project.name)) {
  268. return project;
  269. }
  270. }
  271. return null;
  272. }
  273. protected List<DropDownMenuItem> getProjectsMenu() {
  274. List<DropDownMenuItem> menu = new ArrayList<DropDownMenuItem>();
  275. List<ProjectModel> projects = new ArrayList<ProjectModel>();
  276. for (ProjectModel model : getProjectModels()) {
  277. if (!model.isUserProject()) {
  278. projects.add(model);
  279. }
  280. }
  281. int maxProjects = 15;
  282. boolean showAllProjects = projects.size() > maxProjects;
  283. if (showAllProjects) {
  284. // sort by last changed
  285. Collections.sort(projects, new Comparator<ProjectModel>() {
  286. @Override
  287. public int compare(ProjectModel o1, ProjectModel o2) {
  288. return o2.lastChange.compareTo(o1.lastChange);
  289. }
  290. });
  291. // take most recent subset
  292. projects = projects.subList(0, maxProjects);
  293. // sort those by name
  294. Collections.sort(projects);
  295. }
  296. for (ProjectModel project : projects) {
  297. menu.add(new DropDownMenuItem(project.getDisplayName(), "p", project.name));
  298. }
  299. if (showAllProjects) {
  300. menu.add(new DropDownMenuItem());
  301. menu.add(new DropDownMenuItem("all projects", null, null));
  302. }
  303. return menu;
  304. }
  305. private String readMarkdown(String projectName, File projectMessage) {
  306. String message = "";
  307. if (projectMessage.exists()) {
  308. // Read user-supplied message
  309. try {
  310. FileInputStream fis = new FileInputStream(projectMessage);
  311. InputStreamReader reader = new InputStreamReader(fis,
  312. Constants.CHARACTER_ENCODING);
  313. message = MarkdownUtils.transformMarkdown(reader);
  314. reader.close();
  315. } catch (Throwable t) {
  316. message = getString("gb.failedToRead") + " " + projectMessage;
  317. warn(message, t);
  318. }
  319. }
  320. return message;
  321. }
  322. }