diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f1b755e..3550acfa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Don't automatically stage files added to source control (#303) - Performance improvements (#269, #315) - Checkout of branches whose names contain slashes via Web UI no longer fails (#295) +- Display other developer's username in Web UI's Workspace when hovering over the name of a file they changed (#304) ## [2.3.0] - 2023-12-06 diff --git a/cls/SourceControl/Git/Change.cls b/cls/SourceControl/Git/Change.cls index 0caf80d3..e6b1e2e6 100644 --- a/cls/SourceControl/Git/Change.cls +++ b/cls/SourceControl/Git/Change.cls @@ -44,7 +44,9 @@ ClassMethod RemoveUncommitted(FileList, Display = 1, Revert = 0, ActiveCommit = set changeSourceClass=##class(%Studio.SourceControl.Interface).SourceControlClassGet() } if ('$get(^SYS("SourceControl","ChangeConfig","KeepHistory")))||(Revert) { - set sc=..%DeleteId(obj.%Id()) + if (obj.ChangedBy = $username) { + set sc=..%DeleteId(obj.%Id()) + } } else { if $get(CommitCCR)'="" set obj.CCR=CommitCCR set obj.P4Issued=$zdatetime($h,3) @@ -77,6 +79,29 @@ ClassMethod IsUncommitted(Filename, ByRef ID) As %Boolean } } +ClassMethod GetOtherDeveloperChanges() As %Boolean +{ + set numEntries = 0 + set fileToOtherDevelopers = {} + set query = "Select ItemFile, ChangedBy FROM SourceControl_Git.Change WHERE CHARINDEX('SourceControl.Git', Name)>0 AND Committed = '0' AND ChangedBy <> ?" + set statement = ##class(%SQL.Statement).%New() + set status = statement.%Prepare(query) + $$$ThrowOnError(status) + set rset = statement.%Execute($username) + if (rset.%SQLCODE < 0) { + throw ##class(%Exception.SQL).CreateFromSQLCODE(rset.%SQLCODE,rset.%Message) + } + while rset.%Next(.sc) { + $$$ThrowOnError(sc) + set filePath = "cls\"_$EXTRACT(rset.ItemFile, $FIND(rset.ItemFile,"cls\"),$LENGTH(rset.ItemFile)) + set otherDevelopers = fileToOtherDevelopers.%Get(filePath, []) + do otherDevelopers.%Push(rset.ChangedBy) + do fileToOtherDevelopers.%Set(filePath, otherDevelopers) + } + $$$ThrowOnError(sc) + return fileToOtherDevelopers +} + /// Goes through Uncommitted queue and removes any items of action 'edit' or 'add' which are ReadOnly or non-existent on the filesystem ClassMethod RefreshUncommitted(Display = 0, IncludeRevert = 0, Output gitFiles, Force As %Boolean = 0) As %Status { @@ -192,4 +217,3 @@ Storage Default } } - diff --git a/cls/SourceControl/Git/WebUIDriver.cls b/cls/SourceControl/Git/WebUIDriver.cls index acb05ff6..5a70b400 100644 --- a/cls/SourceControl/Git/WebUIDriver.cls +++ b/cls/SourceControl/Git/WebUIDriver.cls @@ -183,24 +183,26 @@ ClassMethod Uncommitted() As %SystemBase set output = "" set key = "" - set array = [] - set key = "" + set editedByCurrentUser = [] + set fileToOtherDevelopers = {} for { set key = $order(files(key),1,fileData) quit:key="" // Check that current user has files(key) uncommitted and only %Push if they do set filename = ##class(Utils).FullExternalName(key) if (($ISVALIDNUM(key)) && (files(key) '= "")){ - do array.%Push($listget(fileData,2)) + do editedByCurrentUser.%Push($listget(fileData,2)) } else{ set sc=##class(SourceControl.Git.Change).GetUncommitted(filename,.tAction,.tInternalName,.UncommittedUser,.tSource,.UncommittedLastUpdated) if ($$$ISOK(sc)) && ($data(tAction)&&(UncommittedUser=$username)) { - do array.%Push($listget(fileData,2)) + do editedByCurrentUser.%Push($listget(fileData,2)) } } } - quit array + do fileToOtherDevelopers.%Set("current user's changes", editedByCurrentUser) + do fileToOtherDevelopers.%Set("other users' changes", ##class(SourceControl.Git.Change).GetOtherDeveloperChanges()) + quit fileToOtherDevelopers } ClassMethod GetURLPrefix(%request As %CSP.Request, URL As %String) As %String diff --git a/git-webui/release/share/git-webui/webui/js/git-webui.js b/git-webui/release/share/git-webui/webui/js/git-webui.js index 3dbc6ad8..1010fbaa 100644 --- a/git-webui/release/share/git-webui/webui/js/git-webui.js +++ b/git-webui/release/share/git-webui/webui/js/git-webui.js @@ -414,7 +414,7 @@ webui.SideBarView = function(mainView, noEventHandlers) { var flag = 0; webui.git("status -u --porcelain", function(data) { $.get("api/uncommitted", function (uncommitted) { - var uncommittedItems = JSON.parse(uncommitted); + var uncommittedItems = JSON.parse(uncommitted)["current user's changes"]; var col = 1 webui.splitLines(data).forEach(function(line) { var status = line[col]; @@ -488,7 +488,7 @@ webui.SideBarView = function(mainView, noEventHandlers) { var flag = 0; webui.git("status -u --porcelain", function(data) { $.get("api/uncommitted", function (uncommitted) { - var uncommittedItems = JSON.parse(uncommitted); + var uncommittedItems = JSON.parse(uncommitted)["current user's changes"]; var col = 1 webui.splitLines(data).forEach(function(line) { var status = line[col]; @@ -2143,13 +2143,30 @@ webui.ChangedFilesView = function(workspaceView, type, label) { var col = type == "working-copy" ? 1 : 0; webui.git("status -u --porcelain", function(data) { $.get("api/uncommitted", function (uncommitted) { - var uncommittedItems = JSON.parse(uncommitted); + var uncommittedItems = JSON.parse(uncommitted)["current user's changes"]; + var otherDeveloperUncommittedItems = JSON.parse(uncommitted)["other users' changes"]; self.filesCount = 0; var filePaths = []; + function addItemToFileList(fileList, otherDeveloperUsername, model) { + var isForCurrentUser = otherDeveloperUsername === ""? true : false; + var cssClass = isForCurrentUser ? 'list-group-item available' : 'list-group-item unavailable'; + if(isForCurrentUser){ + var item = $('').prependTo(fileList)[0]; + } + else{ + var item = $(''+ + webui.peopleIcon).appendTo(fileList)[0]; + } + item.model = model; + item.appendChild(document.createTextNode(model)); + if (isForCurrentUser) { + $(item).click(self.select); + $(item).dblclick(self.process); + } + } webui.splitLines(data).forEach(function(line) { var indexStatus = line[0]; var workingTreeStatus = line[1]; - var status = line[col]; line = line.substring(3); var splitted = line.split(" -> "); var model; @@ -2159,10 +2176,15 @@ webui.ChangedFilesView = function(workspaceView, type, label) { model = line; } filePaths.push(model); + if (workingTreeStatus === "D") { localStorage.removeItem(model); } - if (col == 0 && indexStatus != " " && indexStatus != "?" && localStorage.getItem(model) !== null || col == 1 && workingTreeStatus != " " && workingTreeStatus != "D" && localStorage.getItem(model) === null) { + + var isNotStaged= workingTreeStatus != "D" && workingTreeStatus != " " && localStorage.getItem(model) === null; + var addUnstagedFile = col == 1 && isNotStaged; + var addStagedFile = col == 0 && indexStatus != " " && indexStatus != "?" && localStorage.getItem(model) !== null; + if (addUnstagedFile || addStagedFile) { ++self.filesCount; var isForCurrentUser; if(model.indexOf(" ") > -1){ @@ -2170,29 +2192,31 @@ webui.ChangedFilesView = function(workspaceView, type, label) { } else { isForCurrentUser = (uncommittedItems.indexOf(model) > -1); } - var cssClass = isForCurrentUser ? 'list-group-item available' : 'list-group-item unavailable'; - - if(isForCurrentUser){ - var item = $('').prependTo(fileList)[0]; - } - else{ - var item = $(''+ - webui.peopleIcon).appendTo(fileList)[0]; - } - item.model = model; - item.status = status; - item.appendChild(document.createTextNode(line)); - $(item).click(self.select); + if (isForCurrentUser) { - $(item).dblclick(self.process); + addItemToFileList(fileList, "", model); } } }); + + for (const otherDeveloperItem of Object.keys(otherDeveloperUncommittedItems)) { + for (const otherDeveloperUsername of otherDeveloperUncommittedItems[otherDeveloperItem]) { + if (col==1) { + addItemToFileList(fileList, otherDeveloperUsername, otherDeveloperItem); + } + } + } + + $(function () { + $('[data-toggle="tooltip"]').tooltip() + }); + Object.keys(localStorage).filter(function (key) { return !filePaths.includes(key); }).map(function (key) { localStorage.removeItem(key); }); + if (selectedIndex !== null && selectedIndex >= fileList.childElementCount) { selectedIndex = fileList.childElementCount - 1; if (selectedIndex == -1) { diff --git a/git-webui/src/share/git-webui/webui/js/git-webui.js b/git-webui/src/share/git-webui/webui/js/git-webui.js index bfe80019..1010fbaa 100644 --- a/git-webui/src/share/git-webui/webui/js/git-webui.js +++ b/git-webui/src/share/git-webui/webui/js/git-webui.js @@ -414,7 +414,7 @@ webui.SideBarView = function(mainView, noEventHandlers) { var flag = 0; webui.git("status -u --porcelain", function(data) { $.get("api/uncommitted", function (uncommitted) { - var uncommittedItems = JSON.parse(uncommitted); + var uncommittedItems = JSON.parse(uncommitted)["current user's changes"]; var col = 1 webui.splitLines(data).forEach(function(line) { var status = line[col]; @@ -488,7 +488,7 @@ webui.SideBarView = function(mainView, noEventHandlers) { var flag = 0; webui.git("status -u --porcelain", function(data) { $.get("api/uncommitted", function (uncommitted) { - var uncommittedItems = JSON.parse(uncommitted); + var uncommittedItems = JSON.parse(uncommitted)["current user's changes"]; var col = 1 webui.splitLines(data).forEach(function(line) { var status = line[col]; @@ -2143,13 +2143,30 @@ webui.ChangedFilesView = function(workspaceView, type, label) { var col = type == "working-copy" ? 1 : 0; webui.git("status -u --porcelain", function(data) { $.get("api/uncommitted", function (uncommitted) { - var uncommittedItems = JSON.parse(uncommitted); + var uncommittedItems = JSON.parse(uncommitted)["current user's changes"]; + var otherDeveloperUncommittedItems = JSON.parse(uncommitted)["other users' changes"]; self.filesCount = 0; var filePaths = []; + function addItemToFileList(fileList, otherDeveloperUsername, model) { + var isForCurrentUser = otherDeveloperUsername === ""? true : false; + var cssClass = isForCurrentUser ? 'list-group-item available' : 'list-group-item unavailable'; + if(isForCurrentUser){ + var item = $('').prependTo(fileList)[0]; + } + else{ + var item = $(''+ + webui.peopleIcon).appendTo(fileList)[0]; + } + item.model = model; + item.appendChild(document.createTextNode(model)); + if (isForCurrentUser) { + $(item).click(self.select); + $(item).dblclick(self.process); + } + } webui.splitLines(data).forEach(function(line) { var indexStatus = line[0]; var workingTreeStatus = line[1]; - var status = line[col]; line = line.substring(3); var splitted = line.split(" -> "); var model; @@ -2164,7 +2181,10 @@ webui.ChangedFilesView = function(workspaceView, type, label) { localStorage.removeItem(model); } - if (col == 0 && indexStatus != " " && indexStatus != "?" && localStorage.getItem(model) !== null || col == 1 && workingTreeStatus != " " && workingTreeStatus != "D" && localStorage.getItem(model) === null) { + var isNotStaged= workingTreeStatus != "D" && workingTreeStatus != " " && localStorage.getItem(model) === null; + var addUnstagedFile = col == 1 && isNotStaged; + var addStagedFile = col == 0 && indexStatus != " " && indexStatus != "?" && localStorage.getItem(model) !== null; + if (addUnstagedFile || addStagedFile) { ++self.filesCount; var isForCurrentUser; if(model.indexOf(" ") > -1){ @@ -2172,25 +2192,25 @@ webui.ChangedFilesView = function(workspaceView, type, label) { } else { isForCurrentUser = (uncommittedItems.indexOf(model) > -1); } - var cssClass = isForCurrentUser ? 'list-group-item available' : 'list-group-item unavailable'; - - if(isForCurrentUser){ - var item = $('').prependTo(fileList)[0]; - } - else{ - var item = $(''+ - webui.peopleIcon).appendTo(fileList)[0]; - } - item.model = model; - item.status = status; - item.appendChild(document.createTextNode(line)); - $(item).click(self.select); + if (isForCurrentUser) { - $(item).dblclick(self.process); + addItemToFileList(fileList, "", model); } } }); + for (const otherDeveloperItem of Object.keys(otherDeveloperUncommittedItems)) { + for (const otherDeveloperUsername of otherDeveloperUncommittedItems[otherDeveloperItem]) { + if (col==1) { + addItemToFileList(fileList, otherDeveloperUsername, otherDeveloperItem); + } + } + } + + $(function () { + $('[data-toggle="tooltip"]').tooltip() + }); + Object.keys(localStorage).filter(function (key) { return !filePaths.includes(key); }).map(function (key) {