diff --git a/module/mr/control.php b/module/mr/control.php
index b4c56e24d1..d5d7603126 100644
--- a/module/mr/control.php
+++ b/module/mr/control.php
@@ -106,6 +106,27 @@ class mr extends control
$gitlabUsers = $this->gitlab->getUserAccountIdPairs($MR->gitlabID);
+ /* Import lang for required modules. */
+ $this->loadModel('repo');
+ $this->loadModel('job');
+ $this->loadModel('compile');
+
+ $repoList = array();
+ $rawRepoList = $this->repo->getGitLabRepoList($MR->gitlabID, $MR->sourceProject);
+ foreach($rawRepoList as $rawRepo) $repoList[$rawRepo->id] = "[$rawRepo->id] $rawRepo->name";
+
+ $jobList = array();
+ $rawJobList = $this->job->getListByRepoID($MR->repoID);
+ foreach($rawJobList as $rawJob) $jobList[$rawJob->id] = "[$rawJob->id] $rawJob->name";
+
+ $compileList = array();
+ $rawCompileList = $this->compile->getListByJobID($MR->jobID);
+ foreach($rawCompileList as $rawCompile) $compileList[$rawCompile->id] = "[$rawCompile->id] [{$this->lang->compile->statusList[$rawCompile->status]}] $rawCompile->name";
+
+ $this->view->repoList = $repoList;
+ $this->view->jobList = !empty($MR->repoID) ? $jobList : array();
+ $this->view->compileList = !empty($MR->jobID) ? $compileList : array();
+
$this->view->title = $this->lang->mr->edit;
$this->view->MR = $MR;
$this->view->targetBranchList = $targetBranchList;
@@ -286,6 +307,8 @@ class mr extends control
}
}
+ $this->view->repo = $this->loadModel('repo')->getRepoByID($MR->repoID);
+ $this->view->repoID = $MR->repoID;
$this->view->title = $this->lang->mr->viewDiff;
$this->view->diffs = $diffs;
$this->view->encoding = $encoding;
@@ -353,6 +376,49 @@ class mr extends control
return $this->send($this->mr->reopen($MR));
}
+ /**
+ * Add a Bug for this review.
+ *
+ * @param int $repoID
+ * @param string $file
+ * @param int $v1
+ * @param int $v2
+ * @access public
+ * @return void
+ */
+ public function addBug($repoID, $file, $v1, $v2)
+ {
+ $this->loadModel('repo');
+ if($this->get->repoPath) $file = $this->get->repoPath;
+ if(!empty($_POST))
+ {
+ $result = $this->mr->saveBug($repoID, $file, $v1, $v2);
+ if(dao::isError()) die(json_encode($result));
+
+ $bugID = $result['id'];
+ $repo = $this->repo->getRepoById($repoID);
+ $entry = $repo->name . '/' . $this->repo->decodePath($file);
+ $location = sprintf($this->lang->repo->reviewLocation, $entry, $repo->SCM != 'Subversion' ? substr($v2, 0, 10) : $v2, $this->post->begin, $this->post->end);
+ if(empty($v1))
+ {
+ $revision = $repo->SCM != 'Subversion' ? substr($v2, 0, 10) : $v2;
+ $link = $this->repo->createLink('view', "repoID=$repoID&objectID=0&entry={$file}&revision=$v2&showBug=true") . '#L' . $this->post->begin;
+ }
+ else
+ {
+ $revision = $repo->SCM != 'Subversion' ? substr($v1, 0, 10) : $v1;
+ $revision .= ' : ';
+ $revision .= $repo->SCM != 'Subversion' ? substr($v2, 0, 10) : $v2;
+ $link = $this->repo->createLink('diff', "repoID=$repoID&objectID=0&entry={$file}&oldRevision=$v1&newRevision=$v2&showBug=true") . '#L' . $this->post->begin;
+ }
+
+ $actionID = $this->loadModel('action')->create('bug', $bugID, 'repoCreated', '', html::a($link, $location));
+ $this->loadModel('mail')->sendmail($bugID, $actionID);
+
+ echo json_encode($result);
+ }
+ }
+
/**
* AJAX: Get MR target projects.
*
@@ -441,7 +507,7 @@ class mr extends control
if(!$compileList) return $this->send(array('message' => array()));
$options = "";
- foreach($compileList as $compile) $options .= "";
+ foreach($compileList as $compile) $options .= "";
$this->send($options);
}
}
diff --git a/module/mr/css/common.css b/module/mr/css/common.css
new file mode 100644
index 0000000000..f53aab533d
--- /dev/null
+++ b/module/mr/css/common.css
@@ -0,0 +1,187 @@
+a {color: #169;}
+a:hover, a:active {text-decoration:underline; color: #C61A1A;}
+#swapper a {text-decoration: none;}
+#swapper .col-footer a:hover {color: #0c64eb;}
+h2,h3 {font-size: 20px; margin: 0; clear: both;}
+h3 {font-size: 16px;}
+.revision {font-size: 12px; line-height: 20px; text-align: right; padding-right: 8px;}
+.directory {background-image:url('theme/default/images/repo/dir.png');}
+.file {background-image:url('theme/default/images/repo/txt.png');}
+//.icon {width: 17px; padding-left: 10px; padding-right: 2px;}
+.mini-icon {display: inline-block; height: 16px; width: 16px; background-color: transparent; background-position: 0 0; background-repeat: no-repeat; vertical-align: text-bottom;}
+.action {float: left;}
+.input-group select#encrypt {border-left: 0px;}
+.arrange {float: right;}
+.versions {position: relative;}
+#diffRepo {position: absolute; left: 20px; z-index: 1000;}
+#repoID {display: inline-block; width: auto;}
+.repoCode a:hover {text-decoration: none;}
+.commentButton
+{
+ background-repeat: no-repeat;
+ position: absolute;
+ left: -28px;
+ width: 40px;
+ z-index: 10;
+ cursor:pointer;
+ font-size: 18px;
+ color: #4183C4;
+ display: none;
+}
+.repoCode tr.over .commentButton {display: block;}
+.bug
+{
+ background-repeat: no-repeat;
+ position: absolute;
+ left: -7px;
+ width: 20px;
+ z-index: 0;
+ cursor:pointer;
+ font-size: 18px;
+ color: #4183C4;
+ line-height: 18px;
+}
+.repoCode .icon {opacity: 1;}
+.icon-comment-add:before {content: '\e74c'; transform: scale(-1, 1); display: inline-block; font-weight: normal;}
+.icon-comment-add:after {content: '+'; display: block; font-weight: normal; position: absolute; left: 16px; top: 1px; font-family: Arial; font-weight: bold; font-size: 12px;}
+.icon-comments:before {content: '\e750'; transform: scale(-1, 1); display: inline-block; font-weight: normal; font-size: 18px; line-height: 18px;}
+.commentButton:hover,.bug:hover {color: #d20b0b;}
+.commentBoard {border-top: 1px solid #E4E4E4; border-bottom: 1px solid #E4E4E4; padding: 0; white-space: normal; background-color: #eee; padding: 10px;}
+.commentBoard .table-form th {background: none;}
+.lines input {width: 40px;}
+.commentSubmit, .commentCancel {margin-right: 10px;}
+.commentFoot .optional {float: left;}
+.commentBoard.using .bugContainer {border: 1px solid #ddd; background: #fff;}
+.commentBoard.using .bugContainer > .commentHeader {background: #f1f1f1; padding: 0 10px; border-bottom: 1px solid #ddd;}
+.commentHeaderAuthor {max-width: 600px; line-height: 33px; font-weight: bold; color: #222; overflow: hidden; white-space: nowrap; text-overflow: ellipsis;}
+.comment {width: 100%; word-break: keep-all; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;}
+.commentContent {margin: 10px;}
+.commentHeaderRight {float: right;}
+
+/* Sider */
+#mainContent > #sidebar > .side-body.affix {top: 0px; z-index: 10000;}
+
+/* Pre row */
+.repoCode table > tbody > tr.over td {background: #f8eec7;}
+.repoCode table > tbody > tr.over th {background: #cdcdcd; color: #333;}
+
+/* Comment-btn */
+.comment-btn {position: relative; margin: 0; padding: 0; display: none;}
+.repoCode tr.over .comment-btn {display: block;}
+.comment-btn .icon-wrapper {display: block; position: absolute; background: #4183C4; border-radius: 2px; width: 24px; height: 20px; left: -6px; top: 0; line-height: 20px; text-align: center; color: #fff; cursor: pointer; transition: transform 0.2s;}
+.comment-btn .icon-wrapper:before {display: block; content: ' '; right: -4px; top: 6px; position: absolute; border-left: 4px solid #4183C4; border-right: 0 solid transparent; border-bottom: 4px solid transparent; border-top: 4px solid transparent; width: 0; height: 0;}
+.comment-btn .icon-wrapper:hover {background: #169; transform: scale(1.1);}
+.comment-btn .icon-wrapper:hover:before {border-left-color: #169;}
+
+.repoCode tr.commented {cursor: pointer;}
+
+.repoCode tr.commented .comment-btn {display: block;}
+.repoCode tr.commented .comment-btn .icon-wrapper {background: none; border: none; width: 24px; line-height: 18px; height: 18px; left: -6px; color: #4183c4;}
+.repoCode tr.commented .comment-btn .icon-wrapper:hover {border-color: #169; color: #169;}
+.repoCode tr.commented .comment-btn .icon-wrapper > i:before {font-size: 18px; transform: scale(-1, 1); display: inline-block;}
+.repoCode tr.commented .comment-btn .icon-wrapper:before {display: none;}
+
+.repoCode tr.over.commented .comment-btn .icon-wrapper, .repoCode tr.selected.commented .comment-btn .icon-wrapper {line-height: 20px; height: 20px; background: #4183C4; color: #fff; left: -6px;}
+.repoCode tr.over.commented .comment-btn .icon-wrapper > i:before {font-size: 14px;}
+.repoCode tr.selected.commented .comment-btn .icon-wrapper > i:before {font-size: 14px;}
+.repoCode tr.over.commented .comment-btn .icon-wrapper:before, .repoCode tr.selected.commented .comment-btn .icon-wrapper:before {display: block;}
+
+/* repo action form */
+.repoCode .comment-list, .repoCode .comment-actions {max-width: 900px;}
+.repoCode .bugFormContainer {border: 1px solid #bbb; margin: 0 0 0 15px; padding: 10px 20px 10px 10px; max-width: 880px; background: #fff;}
+.repoCode .bugFormContainer th {width: 70px;}
+
+.repoCode .action-row {display: none;}
+.repoCode .with-action-row .action-row {display: table-row;}
+.repoCode .action-cell {background: #EEE; white-space: normal; padding: 10px 15px 10px 0;}
+.repoCode .with-action-row table tr.selected .comment-btn:last-child {display: block;}
+.repoCode .with-action-row table tr.selected td {background: #f8eec7;}
+.repoCode .with-action-row table tr.selected th {background: #cdcdcd;}
+.repoCode .with-action-row table tr.selected .comment-btn .icon-wrapper > i:before, .repoCode #diff.with-action-row tr.selected .comment-btn .icon-wrapper > i:before {content: '\d7';}
+
+.repoCode .comment-row {display: none;}
+.repoCode .comment-row.show {display: table-row !important;}
+.repoCode .comment-cell {background: #eee; white-space: normal;}
+.repoCode .comment-cell .panel {margin: 10px; border-color: #bbb;}
+.repoCode .comment-cell .panel-body {padding: 6px 10px;}
+.repoCode .comment-cell .panel-actions.pull-right {margin-right: 0; margin-top: 0;}
+.repoCode .comment-cell .editing .panel-body, .repoCode .comment-cell .commentContainer.show-form .panel-body {display: none;}
+.repoCode .comment-cell .bug-edit-form, {padding: 10px; display: none;}
+.repoCode .comment-cell .editing .bug-edit-form, .repoCode .comment-cell .commentContainer.show-form .comment-edit-form {display: block;}
+
+.repoCode .comment {border: 1px solid #e5e5e5; background: #fafafa; padding: 5px 10px; margin-bottom: 10px;}
+.repoCode .comment .comment-edit-form {margin-top: 10px;}
+.repoCode .panel-bug .steps {background: #f1f1f1; padding: 5px 10px;}
+.repoCode .panel-bug .bug-edit-form {margin-bottom: 10px;}
+.repoCode .panel-bug .panel-body {display: none;}
+.repoCode .panel-bug .panel-heading {cursor: pointer;}
+.repoCode .panel-bug.show .panel-body {display: block;}
+.repoCode .panel-bug.show .icon-chevron-sign-down:before {content: '\e711';}
+.repoCode .panel-bug.show-edit-form .bug-edit-form,
+.repoCode .panel-bug.show-form .commentForm,
+.repoCode .comment.show-form .comment-edit-form {display: block;}
+.repoCode .panel-bug .bug-edit-form,
+.repoCode .panel-bug.show-form .addComment,
+.repoCode .panel-bug .commentForm,
+.repoCode .comment .comment-edit-form,
+.repoCode .panel-bug.show-edit-form .panel-body .title,
+.repoCode .panel-bug.show-edit-form .bug-date,
+.repoCode .comment.show-form .comment-content, .repoCode .comment.show-form .comment-date {display: none;}
+
+.repoCode .text-content {white-space: normal; white-space: pre-line;}
+.repoCode .text-muted {color: #aaa;}
+
+.repoCode tr {transition: all 1s;}
+.repoCode tr.highlight {background: #fff4e5;}
+.repoCode tr.highlight td, .repoCode tr.highlight th {background: none; border-top: 1px solid #e48600; border-bottom: 1px solid #e48600;}
+.repoCode tr.highlight.commented th {color: #e48600;}
+.repoCode tr.highlight.commented td, .repoCode tr.highlight.commented th {border-bottom: none;}
+.repoCode tr.highlight + tr.highlight td, .repoCode tr.highlight + tr.highlight th {border-top: none;}
+
+.repoCode .row-tip {display: none;}
+.repoCode tr.commented .row-tip {display: block; position: relative; right: -3px; bottom: -1px;}
+.repoCode tr.commented .tip, .repoCode tr.commented.open .tip.on-collapse {display: block; position: absolute; right: 0; bottom: 0; color: #4183c4; opacity: 0; padding: 0 5px; background: #edf3ff; height: 20px; line-height: 20px; transition: opacity 0.2s;}
+.repoCode tr.commented:hover .tip.on-expand {opacity: 1;}
+.repoCode tr.commented.open .tip.on-collapse {opacity: 1;}
+.repoCode tr.commented.open .tip.on-expand {display: none;}
+.repoCode tr.commented.open .tip.on-collapse span {display: none;}
+.repoCode tr.commented.open:hover .tip.on-collapse span {display: inline;}
+.repoCode tr.commented.open {background: #f8fafe;}
+.repoCode tr.commented .preview-icon {position: absolute; left: -6px; bottom: 0; width: 20px; height: 20px; line-height: 20px; text-align: center; color: #4183c4; background: #edf3ff; display: none;}
+.repoCode tr.commented:hover .preview-icon {display: block; transform: scale(-1, 1);}
+.repoCode tr.commented .preview-icon:before {font-size: 18px;}
+
+.repoCode #diff tr.commented .row-tip {right: 0;}
+.repoCode #diff tr.commented .icon-chat-dot {left: 0;}
+
+.repoCode .panel, .bugFormContainer {transition: border 0.4s;}
+.repoCode .panel.highlight, #bugForm.highlight .bugFormContainer {border-color: #e48600;}
+
+#bugsPreview {white-space: normal;}
+#bugsPreview .dropdown-menu {top: -100%; left: 30%; padding-top: 0; min-width: 300px; max-width: 500px;}
+#bugsPreview .dropdown-menu > li.dropdown-header {background: #f1f1f1; padding-top: 8px;}
+#bugsPreview .dropdown-menu > li > a {border-top: 1px solid #e5e5e5; text-overflow : ellipsis; overflow: hidden;}
+#bugsPreview .dropdown-menu.show {display: block;}
+
+.icon-comments {position: relative; left: -50px;}
+
+/* bug form */
+#bugForm, #bugForm table {margin: 0; padding: 0;}
+
+.panel .table + .panel-footer {border-top: 0; background: #fff;}
+
+.transparent {border-color:transparent; background: none repeat scroll 0 0 transparent;}
+.transparent:hover {border-color:transparent; background: none repeat scroll 0 0 transparent;}
+
+.side-col {width: 600px;}
+#sidebar > .side-body {width: 580px;}
+.hide-sidebar #sidebar > .side-body {display: none;}
+
+#sidebar>.sidebar-toggle {left: 5px; right: auto;}
+#sidebar>.sidebar-toggle>.icon {right: -4px; left: auto;}
+
+#logForm .fixed-footer a.allLogs {color: #fff !important;}
+
+#submitLabel {text-align: left;}
+
+.header-btn .btn > .text {text-overflow: unset !important;}
diff --git a/module/mr/js/diff.js b/module/mr/js/diff.js
index e44d3a65cb..1267f00a1d 100644
--- a/module/mr/js/diff.js
+++ b/module/mr/js/diff.js
@@ -10,3 +10,499 @@ function changeEncoding(encoding)
$('#encoding').val(encoding);
$('#encoding').parents('form').submit();
}
+
+$(document).ready(function()
+{
+ var $diffCode = $('.diff');
+ var hidePreview;
+ var $bugsPreview = $('#bugsPreview');
+ var $bugsPreviewMenu = $('#bugsPreview').children('.dropdown-menu');
+ var $rows = $diffCode.find('tr');
+ var rowTip = $('#rowTip').html();
+ var lastLine;
+ $rows.each(function()
+ {
+ var $row = $(this);
+ if(!$row.hasClass('empty'))
+ {
+ $row.children('th').first().prepend("
");
+ $row.children('td').first().append(rowTip);
+ }
+
+ if(lastLine && !$row.data('line'))
+ {
+ $row.attr('data-line', lastLine);
+ }
+ else
+ {
+ lastLine = $row.data('line');
+ }
+ }).hover(function()
+ {
+ var $this = $(this);
+ if($this.hasClass('empty')) return;
+ $this.addClass("over");
+ },
+ function()
+ {
+ $(this).removeClass("over");
+ });
+
+ var isInline = $.cookie('arrange') == 'inline';
+ var $bugFormRow = $(' | ' + (isInline ? ' | | ' : ' | ') + '
');
+ var $bugForm = $('#bugForm');
+ var $commentCell = $('#commentCell');
+ var $bugPanel = $('#bugPanel');
+// $bugForm.find('input[name="begin"], input[name="end"]').attr('max', lastLine);
+ $bugFormRow.find('td').append($bugForm.removeClass('hide'));
+
+ var highlight = function($e)
+ {
+ $('.highlight').removeClass('highlight');
+ $e.addClass('highlight');
+ };
+
+ var createComment = function(comment, $comments)
+ {
+ console.log();
+ var $comment = $commentCell.clone()
+ .removeClass('hide')
+ .attr('id', 'comment-' + comment.id)
+ .attr('data-comment', comment.id);
+ $comment.find('.realname').text(comment.realname);
+ $comment.find('.comment-content').text(comment.comment);
+ $comment.find('.date').text(comment.date);
+ $comment.find('.edit').toggle(comment.edit);
+ $comment.find('.comment-edit-form').attr('action', createLink('repo', 'editComment', 'commentID=' + comment.id));
+
+ if($comments)
+ {
+ if(typeof $comments !== 'object') $comments = $('#bug-' + $comments + ' .comments');
+ ($comments.hasClass('comments') ? $comments : $comments.find('.comments')).append($comment);
+ }
+
+ return $comment;
+ };
+
+ var createBug = function(bug, line, $commentRow, show)
+ {
+ var commentCount, j;
+ var $bug = $bugPanel.clone().removeClass('hide').attr('id', 'bug-' + bug.id).attr('data-bug', bug.id);
+ $bug.find('.bugid').text(bug.id);
+ $bug.find('.realname').text(bug.realname);
+ $bug.find('.openedDate').text(bug.openedDate);
+ $bug.find('.title').text(bug.title);
+ $bug.find('.steps').toggle(bug.steps != '').html(bug.steps);
+ $bug.find('.edit').toggle(bug.edit);
+ $bug.find('.code-lines').text(bug.lines);
+ $bug.find('.delete').toggle(bug.delete);
+ $bug.find('input[name="objectID"]').val(bug.id);
+ $bug.find('.bug-edit-form').attr('action', createLink('repo', 'editBug', 'bugID=' + bug.id));
+ $bug.find('a.view-bug').attr('href', createLink('bug', 'view', "bugID=" + bug.id));
+ $bug.data('data', bug);
+ $bug.toggleClass('show', show > 1);
+ if(show > 2) highlight($bug);
+
+ if(bug.comments)
+ {
+ commentCount = bug.comments.length;
+ $bugComments = $bug.find('.comments');
+ for(j = 0; j < commentCount; j++)
+ {
+ createComment(bug.comments[j], $bugComments);
+ }
+ }
+
+ if(!line && bug.line) line = bug.line;
+ if(line)
+ {
+ if(!$commentRow)
+ {
+ var $row = $rows.filter('[data-line="' + line + '"]').last();
+ $commentRow = $row.next('tr');
+ if(!$commentRow.hasClass('comment-row'))
+ {
+ $commentRow = $('');
+ $row.addClass('commented').after($commentRow);
+ }
+ }
+ ($commentRow.hasClass('comment-list') ? $commentRow : $commentRow.find('.comment-list')).append($bug);
+
+ if(show && $commentRow.hasClass('comment-row')) $commentRow.addClass('show');
+ }
+
+ return $bug;
+ };
+
+ var toggleComment = function($row, show)
+ {
+ var $commentRow;
+ if($row.hasClass('comment-row'))
+ {
+ $commentRow = $row;
+ $row = $commentRow.prev('tr');
+ if($row.hasClass('action-row'))
+ {
+ $row = $row.prev('tr');
+ }
+ }
+ else
+ {
+ $commentRow = $row.next('tr');
+ if($commentRow.hasClass('action-row'))
+ {
+ $commentRow = $commentRow.next('tr');
+ }
+ }
+ if(show === undefined)
+ {
+ show = !$row.hasClass('open');
+ }
+ if($row.hasClass('commented') && $commentRow.hasClass('comment-row'))
+ {
+ $commentRow.toggleClass('show', show);
+ $row.toggleClass('open', show);
+ }
+ };
+
+ $diffCode.on('click', '.comment-btn', function(e)
+ {
+ $rows.removeClass('selected');
+ var $row = $(this).closest('tr');
+ if($diffCode.hasClass('with-action-row') && $row.hasClass('with-action-row'))
+ {
+ $diffCode.removeClass('with-action-row');
+ }
+ else
+ {
+ $diffCode.addClass('with-action-row');
+ var line = $row.data('line');
+ if(!$row.hasClass('with-action-row'))
+ {
+ $rows.removeClass('with-action-row')
+ $row.addClass('with-action-row');
+
+ $bugForm.find('input[name="begin"]').val(line);
+ $bugForm.find('input[name="end"]').attr('min', line).val(line);
+ $bugForm.find('select#assignedTo').val(blamePairs[line]);
+ $bugForm.find('select#assignedTo').trigger("chosen:updated");
+
+ $row.after($bugFormRow);
+
+ KindEditor.remove('#commentText');
+ $('#commentText').kindeditor();
+
+ var getCommiterLink = createLink('repo', 'ajaxgetcommitter', 'repoID=' + repoID + "&entry=" + file + "&revision=" + revision + "&line=" + line);
+ var connector = getCommiterLink.indexOf('&') >= 0 ? '&' : '?';
+ getCommiterLink = getCommiterLink + connector + 'entry=' + file;
+ $.ajax({url: getCommiterLink}).done(function(responseText)
+ {
+ $bugForm.find('#assignedTo').val(responseText).trigger("chosen:updated");
+ });
+ }
+ highlight($bugForm);
+ $bugForm.find('input[name="title"]').focus();
+ $row.addClass('selected');
+ }
+ e.stopPropagation();
+ }).on('click', '.bugCancel', function()
+ {
+ $rows.removeClass('selected');
+ $diffCode.removeClass('with-action-row');
+ }).on('click', '.bugEdit', function(e)
+ {
+ var $panelBug = $(this).closest('.panel-bug');
+
+ if($panelBug.hasClass('show-edit-form'))
+ {
+ $panelBug.removeClass('show-edit-form');
+ e.stopPropagation();
+ return;
+ }
+
+ $panelBug.addClass('show show-edit-form').find('input[name="commentText"]').val($panelBug.find('.title').first().text()).focus();
+ e.stopPropagation();
+ return false;
+ }).on('submit', '.bug-edit-form', function()
+ {
+ var $form = $(this);
+ $(this).ajaxSubmit(
+ {
+ success:function(text)
+ {
+ var $bug = $form.closest('.panel-bug');
+ $bug.find('.title').text(text);
+ $bug.removeClass('show-edit-form');
+ },
+ beforeSubmit:function(formData, jqForm)
+ {
+ var form = jqForm[0];
+ if(!form.commentText.value)
+ {
+ alert(contentError);
+ return false;
+ }
+ }
+ });
+ return false;
+ }).on('click', '.bugEditCancel', function()
+ {
+ $(this).closest('.panel-bug').removeClass('show-edit-form');
+ }).on('click', '.bugDelete', function(e)
+ {
+ var $bug = $(this).closest('.panel-bug');
+ if(!$bug.length) return;
+
+ if(confirm(confirmDelete))
+ {
+ var link = createLink('repo', 'deleteBug', 'bugID=' + $bug.data('bug') + '&confirm=yes');
+ $.get(link, function(data)
+ {
+ if(data == 'deleted')
+ {
+ var $commentRow = $bug.closest('.comment-row');
+ if($commentRow.find('.panel-bug').length === 1)
+ {
+ $commentRow.removeClass('show').prev('tr').removeClass('commented');
+ }
+ $bug.remove();
+ }
+ });
+ }
+ e.stopPropagation();
+ return false;
+ }).on('click', '.addComment', function()
+ {
+ $(this).closest('.panel-bug').addClass('show-form').find('.commentForm textarea').focus();
+ }).on('click', '.commentCancel', function()
+ {
+ $(this).closest('.panel-bug').removeClass('show-form');
+ }).on('submit', '.commentForm', function()
+ {
+ var $form = $(this);
+ $form.ajaxSubmit(
+ {
+ success:function(json)
+ {
+ var $panelBug = $form.closest('.panel-bug');
+ $form.find('textarea').val('');
+ $panelBug.removeClass('show-form');
+ createComment($.parseJSON(json), $panelBug.data('bug'));
+ },
+ beforeSubmit:function(formData, jqForm)
+ {
+ var form = jqForm[0];
+ if(!form.comment.value)
+ {
+ alert(commentError);
+ return false;
+ }
+ }
+ });
+ return false;
+ }).on('click', '.commentEdit', function()
+ {
+ var $comment = $(this).closest('.comment');
+
+ if($comment.hasClass('show-form'))
+ {
+ $comment.removeClass('show-form');
+ return;
+ }
+ $comment.addClass('show-form').find('textarea').val($comment.find('.comment-content').text()).focus();
+ }).on('click', '.commentEditCancel', function()
+ {
+ $(this).closest('.comment').removeClass('show-form');
+ }).on('submit', '.comment-edit-form', function()
+ {
+ var $form = $(this);
+ $form.ajaxSubmit(
+ {
+ success:function(html)
+ {
+ var $comment = $form.closest('.comment');
+ $comment.find('.comment-content').html(html);
+ $comment.removeClass('show-form');
+ },
+ beforeSubmit:function(formData, jqForm)
+ {
+ var form = jqForm[0];
+ if(!form.commentText.value)
+ {
+ alert(contentError);
+ return false;
+ }
+ }
+ });
+ return false;
+ }).on('click', '.commentDelete', function()
+ {
+ var $container = $(this).closest('.commentContainer');
+ if(!$container.length) return;
+
+ if(confirm(confirmDeleteComment))
+ {
+ var commentID = $container.data('comment');
+ var link = createLink('repo', 'deleteComment', 'commentID=' + commentID + '&confirm=yes');
+
+ $.get(link, function(data)
+ {
+ if(data == 'deleted')
+ {
+ var $commentRow = $container.closest('.comment-row');
+ if($commentRow.find('.bugContainer, .commentContainer').length === 1)
+ {
+ $commentRow.removeClass('show').prev('tr').removeClass('commented');
+ }
+ $container.remove();
+ }
+ });
+ }
+ return false;
+ }).on('click', 'tr.commented', function()
+ {
+ toggleComment($(this));
+ }).on('click', '.panel-bug > .panel-heading', function()
+ {
+ $(this).closest('.panel-bug').toggleClass('show');
+ }).on('mouseenter', 'tr.commented td .preview-icon', function(e)
+ {
+ var $cell = $(this).closest('td');
+ var $row = $cell.closest('tr');
+ var $commentRow = $row.next('tr');
+
+ var $bugs = $commentRow.find('.panel-bug'), line = '?';
+ $bugsPreviewMenu.children('li:not(.dropdown-header)').remove();
+ $bugsPreviewMenu.find('.bug-count').text($bugs.length);
+ $bugsPreviewMenu.find('.comment-count').text($commentRow.find('.comment').length);
+ $bugs.each(function()
+ {
+ var bug = $(this).data('data');
+ line = bug.line;
+ $bugsPreviewMenu.append('#' + bug.id + ' ' + bug.title + '');
+ });
+ $bugsPreviewMenu.find('.code-line').text(line);
+
+ $bugsPreview.prependTo($cell);
+ clearTimeout(hidePreview);
+ $bugsPreviewMenu.css({top: 0-$bugsPreviewMenu.outerHeight(), left: Math.max(0, e.offsetX-$bugsPreviewMenu.outerWidth())}).addClass('show');
+ setTimeout(function(){$bugsPreviewMenu.addClass('in');}, 50);
+ }).on('mouseleave', 'tr.commented td', function()
+ {
+ $bugsPreviewMenu.removeClass('in');
+ hidePreview = setTimeout(function(){$bugsPreviewMenu.removeClass('show');}, 200);
+ });
+
+ $bugsPreviewMenu.on('click', 'li', function(e)
+ {
+ var $bug = $($(this).find('a').data('id'));
+ if($bug.length)
+ {
+ $bug.addClass('show');
+ toggleComment($bug.closest('tr.comment-row'), true);
+ highlight($bug);
+
+ $bugsPreviewMenu.removeClass('in');
+ hidePreview = setTimeout(function(){$bugsPreviewMenu.removeClass('show');}, 200);
+ }
+ e.stopPropagation();
+ });
+
+ $bugForm.submit(function()
+ {
+ $(this).ajaxSubmit(
+ {
+ success:function(json)
+ {
+ json = $.parseJSON(json);
+ if(json.result == 'fail')
+ {
+ alert(json.message);
+ return false;
+ }
+
+ createBug(json, null, null, 3);
+ $diffCode.removeClass('with-action-row');
+ $diffCode.find('tr.with-action-row.selected').removeClass('selected');
+ $bugForm.find('#title').val('');
+ KindEditor.html('#commentText', '');
+ },
+ beforeSubmit:function(formData, jqForm)
+ {
+ var form = jqForm[0];
+ if(!form.product.value)
+ {
+ alert(productError);
+ return false;
+ }
+ if(!form.title.value)
+ {
+ alert(titleError);
+ $bugForm.find('input[name="title"]').focus();
+ return false;
+ }
+ }
+ });
+ return false;
+ }).on('change', 'input[name="begin"]', function()
+ {
+ var begin = $(this).val();
+ var $end = $bugForm.find('input[name="end"]').attr('min', begin);
+ if(parseInt($end.val()) < parseInt(begin))
+ {
+ $end.val(begin);
+ }
+ });
+
+ if(bugs)
+ {
+ var lineBugs, bugsCount, i;
+ for(var line in bugs)
+ {
+ if(line)
+ {
+ lineBugs = bugs[line];
+ bugsCount = lineBugs.length;
+
+ for(i = 0; i < bugsCount; i++)
+ {
+ createBug(lineBugs[i], line);
+ }
+ }
+ }
+ }
+
+ setTimeout(anchor, 200);
+
+ $(document).on('click', function()
+ {
+ $('.highlight').removeClass('highlight');
+ });
+
+ function anchor()
+ {
+ var hash = window.location.hash;
+ if(hash)
+ {
+ var line = hash.substr(1).replace('L', '');
+ var $row = $('.diff tr[data-line="' + line +'"]').first();
+ if($row.length)
+ {
+ var anchor = $row.offset().top;
+
+ $('body,html').animate({scrollTop:anchor - 50}, 500);
+
+ $row.addClass('highlight');
+ if($row.hasClass('commented'))
+ {
+ toggleComment($row, true);
+ var $commentRow = $row.next('tr');
+ if($commentRow.hasClass('comment-row'))
+ {
+ $commentRow.addClass('highlight');
+ }
+ }
+ }
+ }
+ }
+});
diff --git a/module/mr/model.php b/module/mr/model.php
index 4b3c52b943..de525ca26a 100644
--- a/module/mr/model.php
+++ b/module/mr/model.php
@@ -173,10 +173,29 @@ class mrModel extends model
*/
public function update($MRID)
{
- $MR = fixer::input('post')
- ->setDefault('editedBy', $this->app->user->account)
- ->setDefault('editedDate', helper::now())
- ->get();
+ if (!empty($_POST['compile']))
+ {
+ $repoID = $this->post->repo;
+ $jobID = $this->post->job;
+ $compileID = $this->post->compile;
+ $compileStatus = $this->loadModel('compile')->getByID($this->post->compile)->status;
+
+ $MR = fixer::input('post')
+ ->setDefault('editedBy', $this->app->user->account)
+ ->setDefault('editedDate', helper::now())
+ ->add('repoID', $repoID)
+ ->add('jobID', $jobID)
+ ->add('compileID', $compileID)
+ ->add('compileStatus', $compileStatus)
+ ->get();
+ }
+ else
+ {
+ $MR = fixer::input('post')
+ ->setDefault('editedBy', $this->app->user->account)
+ ->setDefault('editedDate', helper::now())
+ ->get();
+ }
/* Update MR in GitLab. */
$newMR = new stdclass;
@@ -195,7 +214,7 @@ class mrModel extends model
$MR->assignee = zget($gitlabUsers, $MR->assignee, '');
/* Update MR in Zentao database. */
- $this->dao->update(TABLE_MR)->data($MR)
+ $this->dao->update(TABLE_MR)->data($MR, $this->config->mr->edit->skippedFields)
->where('id')->eq($MRID)
->batchCheck($this->config->mr->edit->requiredFields, 'notempty')
->autoCheck()
@@ -736,4 +755,197 @@ class mrModel extends model
if(isset($rawMR->state) and $rawMR->state == 'opened') return array('result' => 'success', 'message' => $this->lang->saveSuccess, 'locate' => helper::createLink('mr', 'view', "mr={$MR->id}"));
return array('result' => 'fail', 'message' => $this->lang->fail, 'locate' => helper::createLink('mr', 'view', "mr={$MR->id}"));
}
+
+
+ /**
+ * Get review.
+ *
+ * @param int $repoID
+ * @param string $entry
+ * @param string $revision
+ * @access public
+ * @return array
+ */
+ public function getReview($repoID, $entry, $revision)
+ {
+ $reviews = array();
+ $bugs = $this->dao->select('t1.*, t2.realname')->from(TABLE_BUG)->alias('t1')
+ ->leftJoin(TABLE_USER)->alias('t2')
+ ->on('t1.openedBy = t2.account')
+ ->where('t1.repo')->eq($repoID)
+ ->andWhere('t1.entry')->eq($entry)
+ ->andWhere('t1.v2')->eq($revision)
+ ->andWhere('t1.deleted')->eq(0)
+ ->fetchAll('id');
+ $comments = $this->dao->select('t1.*, t2.realname')->from(TABLE_ACTION)->alias('t1')
+ ->leftJoin(TABLE_USER)->alias('t2')
+ ->on('t1.actor = t2.account')
+ ->where('t1.objectType')->eq('bug')
+ ->andWhere('t1.objectID')->in(array_keys($bugs))
+ ->andWhere('t1.action')->eq('commented')
+ ->fetchGroup('objectID', 'id');
+ foreach($bugs as $bug)
+ {
+ if(common::hasPriv('bug', 'edit')) $bug->edit = true;
+ if(common::hasPriv('bug', 'delete')) $bug->delete = true;
+ $lines = explode(',', trim($bug->lines, ','));
+ $line = $lines[0];
+ $reviews[$line]['bugs'][$bug->id] = $bug;
+
+ if(isset($comments[$bug->id]))
+ {
+ foreach($comments[$bug->id] as $key => $comment)
+ {
+ if($comment->actor == $this->app->user->account) $comment->edit = true;
+ }
+ $reviews[$line]['comments'] = $comments;
+ }
+ }
+
+ return $reviews;
+ }
+
+ /**
+ * Get bugs by repo.
+ *
+ * @param int $repoID
+ * @param string $browseType
+ * @param string $orderBy
+ * @param object $pager
+ * @access public
+ * @return array
+ */
+ public function getBugsByRepo($repoID, $browseType, $orderBy, $pager)
+ {
+ /* Get execution that user can access. */
+ $executions = $this->loadModel('execution')->getPairs($this->session->project, 'all', 'empty|withdelete');
+
+ $bugs = $this->dao->select('*')->from(TABLE_BUG)
+ ->where('repo')->eq($repoID)
+ ->andWhere('deleted')->eq('0')
+ ->beginIF(!$this->app->user->admin)->andWhere('product')->in($this->app->user->view->products)->fi()
+ ->beginIF(!$this->app->user->admin)->andWhere('execution')->in(array_keys($executions))->fi()
+ ->beginIF($browseType == 'assigntome')->andWhere('assignedTo')->eq($this->app->user->account)->fi()
+ ->beginIF($browseType == 'openedbyme')->andWhere('openedBy')->eq($this->app->user->account)->fi()
+ ->beginIF($browseType == 'resolvedbyme')->andWhere('resolvedBy')->eq($this->app->user->account)->fi()
+ ->beginIF($browseType == 'assigntonull')->andWhere('assignedTo')->eq('')->fi()
+ ->beginIF($browseType == 'unresolved')->andWhere('resolvedBy')->eq('')->fi()
+ ->beginIF($browseType == 'unclosed')->andWhere('status')->ne('closed')->fi()
+ ->orderBy($orderBy)
+ ->page($pager)
+ ->fetchAll();
+ return $bugs;
+ }
+
+ /**
+ * Get execution pairs.
+ *
+ * @param int $product
+ * @param int $branch
+ * @access public
+ * @return array
+ */
+ public function getExecutionPairs($product, $branch = 0)
+ {
+ $pairs = array();
+ $executions = $this->loadModel('execution')->getList(0, 'all', 'undone', 0, $product, $branch);
+ foreach($executions as $execution) $pairs[$execution->id] = $execution->name;
+ return $pairs;
+ }
+
+ /**
+ * Save bug.
+ *
+ * @param int $repoID
+ * @param string $file
+ * @param int $v1
+ * @param int $v2
+ * @access public
+ * @return array
+ */
+ public function saveBug($repoID, $file, $v1, $v2)
+ {
+ $now = helper::now();
+ $data = fixer::input('post')
+ ->add('severity', 3)
+ ->add('openedBy', $this->app->user->account)
+ ->add('openedDate', $now)
+ ->add('openedBuild', 'trunk')
+ ->add('assignedDate', $now)
+ ->add('type', 'codeimprovement')
+ ->add('repo', $repoID)
+ ->add('entry', $file)
+ ->add('lines', $this->post->begin . ',' . $this->post->end)
+ ->add('v1', $v1)
+ ->add('v2', $v2)
+ ->remove('commentText,begin,end,uid')
+ ->get();
+
+ $data->steps = $this->loadModel('file')->pasteImage($this->post->commentText, $this->post->uid);
+ $this->dao->insert(TABLE_BUG)->data($data)->exec();
+
+ if(!dao::isError())
+ {
+ $bugID = $this->dao->lastInsertID();
+ $this->file->updateObjectID($this->post->uid, $bugID, 'bug');
+ setcookie("repoPairs[$repoID]", $data->product);
+
+ return array('result' => 'success', 'id' => $bugID, 'realname' => $this->app->user->realname, 'openedDate' => substr($now, 5, 11), 'edit' => true, 'delete' => true, 'lines' => $data->lines, 'line' => $this->post->begin, 'steps' => $data->steps, 'title' => $data->title);
+ }
+
+ return array('result' => 'fail', 'message' => join("\n", dao::getError()));
+ }
+
+ /**
+ * Update bug.
+ *
+ * @param int $bugID
+ * @param string $title
+ * @access public
+ * @return string
+ */
+ public function updateBug($bugID, $title)
+ {
+ $this->dao->update(TABLE_BUG)->set('title')->eq($title)->where('id')->eq($bugID)->exec();
+ return $title;
+ }
+
+ /**
+ * Update comment.
+ *
+ * @param int $commentID
+ * @param string $comment
+ * @access public
+ * @return string
+ */
+ public function updateComment($commentID, $comment)
+ {
+ $this->dao->update(TABLE_ACTION)->set('comment')->eq($comment)->where('id')->eq($commentID)->exec();
+ return $comment;
+ }
+
+ /**
+ * Delete comment.
+ *
+ * @param int $commentID
+ * @access public
+ * @return void
+ */
+ public function deleteComment($commentID)
+ {
+ return $this->dao->delete()->from(TABLE_ACTION)->where('id')->eq($commentID)->exec();
+ }
+
+ /**
+ * Get last review info.
+ *
+ * @param string $entry
+ * @access public
+ * @return object
+ */
+ public function getLastReviewInfo($entry)
+ {
+ return $this->dao->select('*')->from(TABLE_BUG)->where('entry')->eq($entry)->orderby('id_desc')->fetch();
+ }
+
}
diff --git a/module/mr/view/diff.html.php b/module/mr/view/diff.html.php
index 185a6170e7..406aff76e7 100644
--- a/module/mr/view/diff.html.php
+++ b/module/mr/view/diff.html.php
@@ -10,6 +10,8 @@
?>
+
+
diff --git a/module/mr/view/header.review.html.php b/module/mr/view/header.review.html.php
new file mode 100644
index 0000000000..6aba0f3430
--- /dev/null
+++ b/module/mr/view/header.review.html.php
@@ -0,0 +1,272 @@
+mr->getLastReviewInfo($file);
+$repoModule = isset($lastReview) && isset($lastReview->module) ? $lastReview->module : '';
+
+/* Get product pairs. */
+if($repo->product)
+{
+ $products = $this->dao->select('id,name')->from(TABLE_PRODUCT)->where('`id`')->in($repo->product)->fetchPairs();
+}
+else
+{
+ $products = $this->loadModel('product')->getPairs();
+}
+
+/* get product by cookie or last review in this file. */
+$repoProduct = isset($_COOKIE['repoPairs'][$repoID]) ? $_COOKIE['repoPairs'][$repoID] : '';
+$repoProduct = isset($lastReview) && isset($lastReview->product) ? $lastReview->product : $repoProduct;
+$repoProduct = isset($products[$repoProduct]) ? $repoProduct : key($products);
+$executions = $this->mr->getExecutionPairs($repoProduct);
+$modules = $this->loadModel('tree')->getOptionMenu($repoProduct, $viewType = 'bug', $startModuleID = 0);
+$users = $this->loadModel('user')->getPairs('devfirst|nodeleted|noclosed');
+$products = array('' => '') + $products;
+$executions = array('' => '') + $executions;
+
+$cwd = getcwd();
+$commiters = $this->user->getCommiters();
+$blamePairs = array();
+if($suffix and $suffix != 'binary' and strpos($this->config->repo->images, "|$suffix|") === false)
+{
+ $blames = $this->scm->blame($entry, $info->revision);
+ foreach($blames as $line => $blame)
+ {
+ if(!isset($blame['committer']))
+ {
+ if(isset($blamePairs[$line - 1])) $blamePairs[$line] = $blamePairs[$line - 1];
+ continue;
+ }
+ $blamePairs[$line] = zget($commiters, $blame['committer'], $blame['committer']);
+ }
+}
+chdir($cwd);
+
+//$reviews = $this->mr->getReview($repoID, $file, $info->revision);
+$reviews = $this->mr->getReview($repoID, $file, '');
+$v1 = isset($oldRevision) ? $oldRevision : 0;
+$this->loadModel('repo');
+// $bugUrl = $this->repo->createLink('addBug', "repoID=$repoID&file=$file&v1=$v1&v2={$info->revision}");
+$bugUrl = $this->createLink('mr', 'addBug', "repoID=$repoID&file=$file&v1=$v1&v2=");
+$commentUrl = $this->createLink('mr', 'addComment');
+$productSelect = html::select('product', $products, $repoProduct, 'class="product form-control chosen" onchange="changeProduct(this)"');
+$branches = $this->loadModel('branch')->getPairs($repoProduct);
+$moduleSelect = html::select('module', $modules, $repoModule, 'class="form-control chosen"');
+$executionSelect = html::select('execution', $executions, '', 'class="form-control chosen"');
+$typeSelect = html::select('repoType', $lang->repo->typeList, '', 'class="form-control chosen"');
+$userSelect = html::select('assignedTo', $users, '', 'class="form-control chosen assignedTo"');
+$bugs = array();
+foreach($reviews as $line => $lineReview)
+{
+ $lineBugs = array();
+ foreach ($lineReview['bugs'] as $bugID => $bug)
+ {
+ $lineBug = array();
+ $lineBug['id'] = $bugID;
+ $lineBug['line'] = $line;
+ $lineBug['title'] = $bug->title;
+ $lineBug['steps'] = $bug->steps;
+ $lineBug['realname'] = $bug->realname;
+ $lineBug['openedDate'] = substr($bug->openedDate, 5, 11);
+ $lineBug['lines'] = $bug->lines;
+ if($bug->edit) $lineBug['edit'] = true;
+ if($bug->delete) $lineBug['delete'] = true;
+
+ if(isset($lineReview['comments']))
+ {
+ $comments = $lineReview['comments'][$bugID];
+ if(isset($comments))
+ {
+ $bugComments = array();
+ foreach ($comments as $commentID => $comment)
+ {
+ $bugComment = array(
+ 'id' => $comment->id,
+ 'edit' => $comment->edit,
+ 'realname' => $comment->realname,
+ 'date' => substr($comment->date, 5, 11),
+ 'comment' => $comment->comment,
+ );
+ $bugComments[] = $bugComment;
+ }
+ $lineBug['comments'] = $bugComments;
+ }
+ }
+ $lineBugs[] = $lineBug;
+ }
+
+ $bugs[$line] = $lineBugs;
+}
+
+js::set('bugs', $bugs);
+js::set('productError', $lang->repo->error->product);
+js::set('contentError', $lang->repo->error->commentText);
+js::set('titleError', $lang->repo->error->title);
+js::set('commentError', $lang->repo->error->comment);
+js::set('submit', $lang->repo->submit);
+js::set('cancel', $lang->repo->cancel);
+js::set('confirmDelete', $lang->repo->notice->deleteBug);
+js::set('confirmDeleteComment', $lang->repo->notice->deleteComment);
+js::set('repoID', $repoID);
+// js::set('revision', $info->revision);
+js::set('revision', '');
+js::set('file', $file);
+js::set('blamePairs', $blamePairs);
+?>
+
+
+
+
+
+
+
+
+
+
+
Bug#
+
+ Bug#
+
+
+
+
+
+
+
+
+
+
+
+
+
repo->lines?>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
repo->expand?>
repo->collapse?>
+