/// <summary> /// Displays statistics /// </summary> /// <param name="sourceUrl"> URL to use for the back link. Can be null. </param> /// <param name="history"> What type of history to display. Can be null. </param> private void DisplayStats(string sourceUrl, string history) { DisplayPageHeader("Code review statistics"); if (sourceUrl != null) { ActivePage.Controls.Add( CreateLinkButton("Back...", Server.UrlDecode(sourceUrl))); AddLabel("<br><br>"); } int activeReviews; int totalReviews; int totalFiles; int comments; int sourceControlId = GetSourceControlId(); if (sourceControlId == -1) { activeReviews = (from cl in DataContext.ChangeLists where cl.Stage == 0 select cl).Count(); totalReviews = (from cl in DataContext.ChangeLists select cl).Count(); totalFiles = (from fl in DataContext.ChangeFiles select fl).Count(); comments = (from cm in DataContext.Comments select cm).Count(); } else { activeReviews = (from cl in DataContext.ChangeLists where cl.SourceControlId == sourceControlId && cl.Stage == 0 select cl).Count(); totalReviews = (from cl in DataContext.ChangeLists where cl.SourceControlId == sourceControlId select cl).Count(); totalFiles = (from fl in DataContext.ChangeFiles join cl in DataContext.ChangeLists on fl.ChangeListId equals cl.Id where cl.SourceControlId == sourceControlId select fl).Count(); comments = (from cm in DataContext.Comments join rv in DataContext.Reviews on cm.ReviewId equals rv.Id join cl in DataContext.ChangeLists on rv.ChangeListId equals cl.Id where cl.SourceControlId == sourceControlId select cm).Count(); } Table header = new Table(); ActivePage.Controls.Add(header); TableRow row = new TableRow(); header.Rows.Add(row); TableCell cell = new TableCell(); cell.AppendCSSClass("CssStatsHeaderCell"); row.Cells.Add(cell); cell.Text = "<b>Database stats:</b><br><br>" + "Active Code reviews: " + activeReviews + "<br>" + "Total Code reviews: " + totalReviews + "<br>" + "Files reviewed: " + totalFiles + "<br>" + "Comments submitted: " + comments; cell = new TableCell(); cell.AppendCSSClass("CssStatsHeaderCell"); row.Cells.Add(cell); var topSubmitters = sourceControlId == -1 ? DataContext.ExecuteQuery<StatQueryData>( "SELECT TOP(5) * FROM (SELECT UserName, COUNT(*) AS Freq FROM ChangeList GROUP BY UserName) AS t " + "ORDER BY Freq DESC") : DataContext.ExecuteQuery<StatQueryData>( "SELECT TOP(5) * FROM (SELECT UserName, COUNT(*) AS Freq FROM ChangeList " + "WHERE SourceControlId = {0} GROUP BY UserName) AS t ORDER BY Freq DESC", sourceControlId); StringBuilder sb = new StringBuilder("<b>Most CLs:</b><br><br>"); foreach (StatQueryData stat in topSubmitters) sb.Append(stat.UserName + ": " + stat.Freq + "<br>"); cell.Text = sb.ToString(); cell = new TableCell(); cell.AppendCSSClass("CssStatsHeaderCell"); row.Cells.Add(cell); var topReviewers = sourceControlId == -1 ? DataContext.ExecuteQuery<StatQueryData>( "SELECT TOP(5) * FROM (SELECT Review.UserName, COUNT(*) AS Freq FROM Review " + "INNER JOIN ChangeList ON Review.ChangeListId = ChangeList.Id " + "WHERE ChangeList.UserName <> Review.UserName " + "GROUP BY Review.UserName) AS t " + "ORDER BY Freq DESC") : DataContext.ExecuteQuery<StatQueryData>( "SELECT TOP(5) * FROM (SELECT Review.UserName, COUNT(*) AS Freq FROM Review " + "INNER JOIN ChangeList ON Review.ChangeListId = ChangeList.Id " + "WHERE ChangeList.SourceControlId = {0} AND ChangeList.UserName <> Review.UserName " + "GROUP BY Review.UserName) AS t " + "ORDER BY Freq DESC", sourceControlId); sb = new StringBuilder("<b>Most reviews:</b><br><br>"); foreach (StatQueryData stat in topReviewers) sb.Append(stat.UserName + ": " + stat.Freq + "<br>"); cell.Text = sb.ToString(); Table reviewsHeader = new Table(); ActivePage.Controls.Add(reviewsHeader); TableRow reviewsHeaderRow = new TableRow(); reviewsHeader.Rows.Add(reviewsHeaderRow); TableCell firstCell = new TableCell(); reviewsHeaderRow.Cells.Add(firstCell); firstCell.AppendCSSClass("CssStatsHistoryHeader"); TableCell secondCell = new TableCell(); reviewsHeaderRow.Cells.Add(secondCell); secondCell.AppendCSSClass("CssStatsHistoryHeader"); TableCell thirdCell = new TableCell(); reviewsHeaderRow.Cells.Add(thirdCell); thirdCell.AppendCSSClass("CssStatsHistoryHeader"); HyperLink second = new HyperLink(); secondCell.Controls.Add(second); HyperLink third = new HyperLink(); thirdCell.Controls.Add(third); DateTime? date = null; if ("lastweek".Equals(history)) { date = DateTime.Now.AddDays(-7); firstCell.Text = "Reviews last week"; second.Text = "Last month..."; second.NavigateUrl = Request.FilePath + "?action=stats&history=lastmonth" + (sourceUrl != null ? "&sourceUrl=" + Server.UrlEncode(sourceUrl) : ""); third.Text = "Active..."; third.NavigateUrl = Request.FilePath + "?action=stats" + (sourceUrl != null ? "&sourceUrl=" + Server.UrlEncode(sourceUrl) : ""); } else if ("lastmonth".Equals(history)) { date = DateTime.Now.AddDays(-30); firstCell.Text = "Reviews last month"; second.Text = "Last week..."; second.NavigateUrl = Request.FilePath + "?action=stats&history=lastweek" + (sourceUrl != null ? "&sourceUrl=" + Server.UrlEncode(sourceUrl) : ""); third.Text = "Active..."; third.NavigateUrl = Request.FilePath + "?action=stats" + (sourceUrl != null ? "&sourceUrl=" + Server.UrlEncode(sourceUrl) : ""); } else { firstCell.Text = "Active reviews"; second.Text = "Last week..."; second.NavigateUrl = Request.FilePath + "?action=stats&history=lastweek" + (sourceUrl != null ? "&sourceUrl=" + Server.UrlEncode(sourceUrl) : ""); third.Text = "Last month..."; third.NavigateUrl = Request.FilePath + "?action=stats&history=lastmonth" + (sourceUrl != null ? "&sourceUrl=" + Server.UrlEncode(sourceUrl) : ""); } Table page = new Table(); ActivePage.Controls.Add(page); page.EnableViewState = false; var allChangesQuery = date == null ? (sourceControlId != -1 ? from mc in DataContext.ChangeLists where mc.Stage == 0 && mc.SourceControlId == sourceControlId select mc : from mc in DataContext.ChangeLists where mc.Stage == 0 orderby mc.TimeStamp descending select mc) : (sourceControlId != -1 ? from mc in DataContext.ChangeLists where mc.SourceControlId == sourceControlId && mc.TimeStamp > date select mc : from mc in DataContext.ChangeLists where mc.TimeStamp > date orderby mc.TimeStamp descending select mc); ActivePage.Controls.Add(CreateReviewsSectionCommon(null, allChangesQuery, true)); ApplyRowBackgroundCssStyle(page); if (sourceUrl != null) { AddLabel("<br>"); ActivePage.Controls.Add( CreateLinkButton("Back...", Server.UrlDecode(sourceUrl))); } }
/// <summary> /// Displays the change list composition. This is called when the main table shows the details of one /// change list. /// </summary> /// <param name="cid"> Change Id. This is relative to the database, not source control. </param> /// <param name="userName"> User name. </param> private void DisplayChange(int cid, string userName) { var changeQuery = from ch in DataContext.ChangeLists where ch.Id == cid select ch; if (changeQuery.Count() != 1) { ErrorOut("Could not find this change in the system!"); return; } HintsData.InChangeView = true; ChangeList changeList = changeQuery.Single(); DisplayPageHeader("Change list " + changeList.CL); Table table = new Table(); table.AppendCSSClass("CssChangeListDetail"); ActivePage.Controls.Add(table); table.Rows.Add(GetChangeDescriptionRow("Date:", WrapTimeStamp(changeList.TimeStamp))); if (changeList.UserClient != null && changeList.UserClient != String.Empty) table.Rows.Add(GetChangeDescriptionRow("Client:", changeList.UserClient)); var userRow = GetChangeDescriptionRow("User:"******"Status:", changeList.Stage == 0 ? "Pending" : "Submitted")); if (changeList.Description != null && changeList.Description != String.Empty) table.Rows.Add(GetChangeDescriptionRow("Description:", Server.HtmlEncode(changeList.Description))); table.Rows.Add(GetChangeDescriptionRow("Files:", "")); var latestReview = GetLatestUserReviewForChangeList(userName, cid); foreach (ChangeFile file in (from fl in DataContext.ChangeFiles where fl.ChangeListId == cid select fl)) { var versions = GetVersionsAbstract(file.Id); bool hasTextBody = (from ver in versions where ver.HasTextBody select ver).Count() != 0; table.Rows.Add(GetChangeFileRow(file, versions.LastOrDefault(), hasTextBody, latestReview)); } var attachments = (from ll in DataContext.Attachments where ll.ChangeListId == cid orderby ll.TimeStamp select ll); if (attachments.Count() > 0) { table.Rows.Add(GetChangeDescriptionRow("Links:", "")); foreach (Attachment a in attachments) AddAttachmentRow(table, a); } AddLabel("<h3>Review history</h3>"); Table reviewResults = new Table(); ActivePage.Controls.Add(reviewResults); reviewResults.AppendCSSClass("CssChangeListReviewHistory"); var allReviewsQuery = from rr in DataContext.Reviews where rr.ChangeListId == cid && rr.IsSubmitted orderby rr.TimeStamp select rr; foreach (Review review in allReviewsQuery) { TableRow row = new TableRow(); reviewResults.Rows.Add(row); row.AppendCSSClass("CssTopAligned"); TableCell dateCell = new TableCell(); row.Cells.Add(dateCell); dateCell.AppendCSSClass("CssDate"); dateCell.Text = WrapTimeStamp(review.TimeStamp); TableCell nameCell = new TableCell(); row.Cells.Add(nameCell); nameCell.AppendCSSClass("CssName"); nameCell.Text = review.UserName; TableCell verdictCell = new TableCell(); row.Cells.Add(verdictCell); verdictCell.AppendCSSClass("CssScore"); HyperLink reviewTarget = new HyperLink(); verdictCell.Controls.Add(reviewTarget); if (review.OverallStatus == 0) HintsData.HaveNeedsWorkVotes = true; reviewTarget.Text = Malevich.Util.CommonUtils.ReviewStatusToString(review.OverallStatus); reviewTarget.NavigateUrl = Request.FilePath + "?rid=" + review.Id; if (review.CommentText != null) { TableCell abbreviatedCommentCell = new TableCell(); row.Cells.Add(abbreviatedCommentCell); abbreviatedCommentCell.AppendCSSClass("CssComment"); abbreviatedCommentCell.Text = AbbreviateToOneLine(review.CommentText, MaxReviewCommentLength); } } //Todo: modify stage logic, stage 1 is pending... if (changeList.Stage != 0) { HintsData.IsChangeInactive = true; AddLabel(String.Format("<br>This review has been {0}.", (changeList.Stage == 2 ? "closed" : "deleted"))); return; } AddLabel("<h3>Vote so far</h3>"); TableGen.Table reviewerVote = new TableGen.Table(2) { CssClass = "CssChangeListVoteHistory" }; ActivePage.Controls.Add(reviewerVote); var reviewerQuery = from rv in DataContext.Reviewers where rv.ChangeListId == cid select rv; Reviewer[] reviewers = reviewerQuery.ToArray(); bool iAmAReviewer = false; foreach (Reviewer reviewer in reviewers) { var row = reviewerVote.CreateRow(); reviewerVote.AddItem(row); row[0].Text = reviewer.ReviewerAlias; if (userName.EqualsIgnoreCase(reviewer.ReviewerAlias)) { DropDownList list = new DropDownList() { ID = "verdictlist" }; row[1].Add(list); list.Items.Add(new ListItem() { Text = "Needs work" }); list.Items.Add(new ListItem() { Text = "LGTM with minor tweaks" }); list.Items.Add(new ListItem() { Text = "LGTM" }); list.Items.Add(new ListItem() { Text = "Non-scoring comment" }); iAmAReviewer = true; } else if (!Page.IsPostBack) { Review review = GetLastReview(cid, reviewer.ReviewerAlias); if (review == null) { row[1].Text = "Have not looked"; } else { row[1].Add(new HyperLink() { Text = Malevich.Util.CommonUtils.ReviewStatusToString(review.OverallStatus), NavigateUrl = Request.FilePath + "?rid=" + review.Id, }); } } } bool iOwnTheChange = changeList.UserName.EqualsIgnoreCase(userName); if (!iAmAReviewer && !iOwnTheChange) { AddLabel("<br>"); AddLink("I would like to review this change.", "?cid=" + changeList.Id + "&action=makemereviewer") .AppendCSSClass("button"); } if (iOwnTheChange && changeList.Stage != 2 && /* 2 = closed */ GetChangeListStatus(changeList) == ChangeListStatus.Closable) { AddLabel("<br>"); AddLink("Close Review", "?cid=" + changeList.Id + "&action=close") .AppendCSSClass("button"); } if (iOwnTheChange) { HintsData.IsChangeAuthor = true; AddLabel("<h3>My response</h3>"); } else { HintsData.IsChangeReviewer = true; AddLabel("<h3>My comments</h3>"); } TextBox commentTextBox = new TextBox(); ActivePage.Controls.Add(commentTextBox); commentTextBox.TextMode = TextBoxMode.MultiLine; commentTextBox.AppendCSSClass("CssGeneralCommentInputBox"); commentTextBox.ID = "reviewcommenttextbox"; AddLabel("<br>"); Button submitReviewButton = new Button() { ID = "submitreviewbutton", Text = "Submit", CssClass = "button" }; ActivePage.Controls.Add(submitReviewButton.As(HtmlTextWriterTag.P)); submitReviewButton.Click += new EventHandler( delegate(object sender, EventArgs e) { submitReview_Clicked(cid, sender, e); }); AddLabel("<br>"); int myReviewId = GetBaseReviewId(userName, cid); if (myReviewId == 0) return; DisplayReviewLineComments(myReviewId); }
/// <summary> /// Returns a row with title and body as two cells. /// </summary> /// <param name="title"> The text for the first cell (usually, a description). </param> /// <param name="body"> The text for the second cell (usually, the information). </param> private TableRow GetChangeDescriptionRow(string title, string body) { TableRow row = new TableRow(); row.AppendCSSClass("CssTopAligned"); TableCell cell = new TableCell() { Text = title }; row.Cells.Add(cell); cell.AppendCSSClass("CssTitle"); cell = new TableCell() { Text = "<pre>" + body + "</pre>" }; row.Cells.Add(cell); cell.AppendCSSClass("CssBody"); return row; }
/// <summary> /// Displays files from a change list by adding a row with details regarding the file to a table. /// </summary> /// <param name="table"> Table where the data goes. </param> /// <param name="attachment"> The attachment. </param> private void AddAttachmentRow(Table page, Attachment attachment) { TableRow row = new TableRow(); page.Rows.Add(row); TableCell cell = new TableCell(); row.Cells.Add(cell); cell = new TableCell(); row.Cells.Add(cell); cell.AppendCSSClass("CssAttachment"); HyperLink link = new HyperLink(); cell.Controls.Add(link); link.NavigateUrl = attachment.Link; link.Text = WrapTimeStamp(attachment.TimeStamp) + " " + (attachment.Description != null ? attachment.Description : Server.HtmlEncode(attachment.Link)); }
/// <summary> /// Displays files from a change list by adding a row with details regarding the file to a table. /// </summary> /// <param name="file"> The top-level file data (names). </param> /// <param name="lastVersion"> The last version of the file. </param> /// <param name="hasTextBody"> Whether to display the file as a hyperlink </param> /// <param name="latestReview">The latest review that the user has submitted for this changelist. May be null.</param> private TableRow GetChangeFileRow( DataModel.ChangeFile file, AbstractedFileVersion lastVersion, bool hasTextBody, Review latestReview) { TableRow row = new TableRow(); if (!file.IsActive) row.AppendCSSClass("CssInactiveFile"); TableCell cell = new TableCell(); // If the latest version of this file has a timestamp greater than the latest review, // then present a "new!" icon in the list to allow the user to quickly determine what // has changed since they last reviewed this change. if (latestReview != null && lastVersion.TimeStamp != null && latestReview.TimeStamp.CompareTo(lastVersion.TimeStamp) < 0) { cell.AppendCSSClass("CssNewIcon"); cell.Controls.Add(new Image() { ImageUrl = "~/images/new_icon.png", AlternateText = "This file has changed since your last review submission." }); } row.Cells.Add(cell); cell = new TableCell(); row.Cells.Add(cell); string moniker = null; if (lastVersion == null) { moniker = file.ServerFileName + "#" + " (no versions found)"; } else if (lastVersion.Action == SourceControlAction.DELETE) { moniker = file.ServerFileName + "#" + lastVersion.Revision + " DELETE"; } else if (lastVersion.Action == SourceControlAction.BRANCH) { moniker = file.ServerFileName + "#" + lastVersion.Revision + " BRANCH"; } else if (lastVersion.Action == SourceControlAction.INTEGRATE) { moniker = file.ServerFileName + "#" + lastVersion.Revision + " INTEGRATE"; } else if (lastVersion.Action == SourceControlAction.RENAME) { moniker = file.ServerFileName + "#" + lastVersion.Revision + " RENAME"; } else { moniker = file.ServerFileName + "#" + lastVersion.Revision + ((lastVersion.Action == SourceControlAction.ADD) ? " ADD" : " EDIT"); } if (hasTextBody) { HyperLink link = new HyperLink(); cell.Controls.Add(link); link.NavigateUrl = Request.FilePath + "?fid=" + file.Id; link.Text = moniker; } else { cell.Text = moniker; } return row; }