[space plugin] simplify space project context

GitOrigin-RevId: c541706891543f2775c26e16d6c9366109b9d3d6
This commit is contained in:
Aleksandr Krasilnikov
2020-06-16 14:32:41 +03:00
committed by intellij-monorepo-bot
parent 5b63042f7f
commit 76c51918c3
5 changed files with 108 additions and 79 deletions
@@ -12,7 +12,6 @@ import circlet.ui.AccountsMenuListPopup
import circlet.vcs.*
import circlet.vcs.clone.*
import circlet.workspaces.*
import com.intellij.ide.*
import com.intellij.openapi.actionSystem.*
import com.intellij.openapi.project.*
import com.intellij.openapi.ui.popup.*
@@ -133,30 +132,28 @@ class CircletMainToolBarAction : DumbAwareAction() {
{ CircletCloneAction.runClone(project) },
showSeparatorAbove = true)
val projectContext = CircletProjectContext.getInstance(project)
val projectInfos = projectContext.projectDescriptions
if (projectInfos != null) {
val descriptions = projectInfos.second
val context = projectContext.context.value
if (!context.empty) {
val descriptions = context.reposInProject.keys
if (descriptions.size > 1) {
menuItems += AccountMenuItem.Group("Code Reviews", descriptions.map {
val reviewsUrl = Navigator.p.project(it.projectKey).reviews.absoluteHref(host)
browseAction("Open for ${it.project.name} project",reviewsUrl)
val reviewsUrl = Navigator.p.project(it.key).reviews.absoluteHref(host)
browseAction("Open for ${it.project.name} project", reviewsUrl)
}.toList())
menuItems += AccountMenuItem.Group("Checklists", descriptions.map {
val checklistsUrl = Navigator.p.project(it.projectKey).checklists().absoluteHref(host)
browseAction("Open for ${it.project.name} project",checklistsUrl)
val checklistsUrl = Navigator.p.project(it.key).checklists().absoluteHref(host)
browseAction("Open for ${it.project.name} project", checklistsUrl)
}.toList())
menuItems += AccountMenuItem.Group("Issues", descriptions.map {
val issuesUrl = Navigator.p.project(it.projectKey).issues().absoluteHref(host)
browseAction("Open for ${it.project.name} project",issuesUrl)
val issuesUrl = Navigator.p.project(it.key).issues().absoluteHref(host)
browseAction("Open for ${it.project.name} project", issuesUrl)
}.toList())
}
else {
val p = Navigator.p.project(descriptions.first().projectKey)
} else {
val p = Navigator.p.project(descriptions.first().key)
menuItems += browseAction("Code Reviews",p.reviews.absoluteHref(host))
menuItems += browseAction("Code Reviews", p.reviews.absoluteHref(host))
menuItems += browseAction("Checklists", p.checklists().absoluteHref(host))
menuItems += browseAction("Issues", p.issues().absoluteHref(host))
}
@@ -26,7 +26,7 @@ import git4idea.GitUtil
import git4idea.history.GitHistoryUtils
import git4idea.repo.GitRepository
import icons.SpaceIcons
import runtime.routing.Location
import runtime.routing.*
import com.intellij.openapi.util.Ref as Ref1
abstract class CircletOpenInBrowserActionGroup<T>(groupName: String) :
@@ -57,15 +57,15 @@ abstract class CircletOpenInBrowserActionGroup<T>(groupName: String) :
}
abstract class CircletOpenInBrowserAction(groupName: String) :
CircletOpenInBrowserActionGroup<Pair<CircletProjectDescription, String>>(groupName) {
CircletOpenInBrowserActionGroup<Pair<CircletProjectInfo, String>>(groupName) {
override fun update(e: AnActionEvent) {
CircletActionUtils.showIconInActionSearch(e)
val project = e.project
if (project != null) {
val projectContext = CircletProjectContext.getInstance(project)
val projectDescriptions = projectContext.projectDescriptions
if (projectDescriptions != null) {
val projectDescriptions = !projectContext.context.value.empty
if (projectDescriptions) {
e.presentation.isEnabled = true
return
@@ -74,19 +74,19 @@ abstract class CircletOpenInBrowserAction(groupName: String) :
e.presentation.isEnabled = false
}
override fun buildAction(it: Pair<CircletProjectDescription, String>): AnAction =
object : AnAction("Open for ${it.first.projectKey.key} project") {
override fun buildAction(it: Pair<CircletProjectInfo, String>): AnAction =
object : AnAction("Open for ${it.first.key.key} project") {
override fun actionPerformed(e: AnActionEvent) = BrowserUtil.browse(it.second)
}
companion object {
internal fun getProjectAwareUrls(endpoint: (ProjectLocation) -> Location, context: DataContext): List<Pair<CircletProjectDescription, String>>? {
internal fun getProjectAwareUrls(endpoint: (ProjectLocation) -> Location, context: DataContext): List<Pair<CircletProjectInfo, String>>? {
val project = context.getData(CommonDataKeys.PROJECT) ?: return null
val server = circletWorkspace.workspace.value?.client?.server?.removeSuffix("/") ?: return null
val description = CircletProjectContext.getInstance(project).projectDescriptions ?: return null
val description = CircletProjectContext.getInstance(project).context.value
return description.second.map {
val projectLocation = Navigator.p.project(it.projectKey)
return description.reposInProject.keys.map {
val projectLocation = Navigator.p.project(it.key)
val url = endpoint(projectLocation).absoluteHref(server)
it to url
@@ -96,19 +96,19 @@ abstract class CircletOpenInBrowserAction(groupName: String) :
}
class OpenReviews : CircletOpenInBrowserAction("Code reviews") {
override fun getData(dataContext: DataContext): List<Pair<CircletProjectDescription, String>>? {
override fun getData(dataContext: DataContext): List<Pair<CircletProjectInfo, String>>? {
return getProjectAwareUrls(ProjectLocation::reviews, dataContext)
}
}
class OpenChecklists : CircletOpenInBrowserAction("Checklists") {
override fun getData(dataContext: DataContext): List<Pair<CircletProjectDescription, String>>? {
override fun getData(dataContext: DataContext): List<Pair<CircletProjectInfo, String>>? {
return getProjectAwareUrls(ProjectLocation::checklists, dataContext)
}
}
class OpenIssues : CircletOpenInBrowserAction("Issues") {
override fun getData(dataContext: DataContext): List<Pair<CircletProjectDescription, String>>? {
override fun getData(dataContext: DataContext): List<Pair<CircletProjectInfo, String>>? {
return getProjectAwareUrls(ProjectLocation::issues, dataContext)
}
}
@@ -143,9 +143,9 @@ class CircletVcsOpenInBrowserActionGroup :
val change = changeListManager.getChange(virtualFile)
if (change != null && change.type == Change.Type.NEW) return null
val repoKeysInfo = findProjectInfo(gitRepository, project) ?: return null
val repoDescription = findProjectInfo(gitRepository, project) ?: return null
return repoKeysInfo.second.map { OpenData.File(project, server, it, repoKeysInfo.first, virtualFile, gitRepository) }
return repoDescription.projectInfos.map { OpenData.File(project, server, it, repoDescription.name, virtualFile, gitRepository) }
}
@@ -154,26 +154,26 @@ class CircletVcsOpenInBrowserActionGroup :
val fileRevision = dataContext.getData(VcsDataKeys.VCS_FILE_REVISION) ?: return null
if (fileRevision !is VcsFileRevisionEx) return null
val gitRepository = GitUtil.getRepositoryManager(project).getRepositoryForFileQuick(filePath) ?: return null
val repoKeysInfo = findProjectInfo(gitRepository, project) ?: return null
val repoDescription = findProjectInfo(gitRepository, project) ?: return null
return repoKeysInfo.second.map { OpenData.FileRevision(project, server, it, repoKeysInfo.first, fileRevision) }
return repoDescription.projectInfos.map { OpenData.FileRevision(project, server, it, repoDescription.name, fileRevision) }
}
private fun getDataFromLog(dataContext: DataContext, project: Project, server: String): List<OpenData>? {
val vcsLog = dataContext.getData(VcsLogDataKeys.VCS_LOG) ?: return null
val selectedCommit = vcsLog.selectedCommits.firstOrNull() ?: return null
val gitRepository = GitUtil.getRepositoryManager(project).getRepositoryForFileQuick(selectedCommit.root) ?: return null
val repoKeysInfo = findProjectInfo(gitRepository, project) ?: return null
val repoDescription = findProjectInfo(gitRepository, project) ?: return null
return repoKeysInfo.second.map { OpenData.Commit(project, server, it, repoKeysInfo.first, selectedCommit) }
return repoDescription.projectInfos.map { OpenData.Commit(project, server, it, repoDescription.name, selectedCommit) }
}
private fun findProjectInfo(gitRepository: GitRepository, project: Project): Pair<String, List<CircletProjectDescription>>? {
private fun findProjectInfo(gitRepository: GitRepository, project: Project): CircletRepoInfo? {
val circletContext = CircletProjectContext.getInstance(project)
return getRemoteUrls(gitRepository)
.mapNotNull { circletContext.findProjectInfo(it) }
.firstOrNull() ?: return null
.mapNotNull { circletContext.getRepoDescriptionByUrl(it) }
.firstOrNull()
}
private fun getRemoteUrls(gitRepository: GitRepository): List<String> {
@@ -181,7 +181,7 @@ class CircletVcsOpenInBrowserActionGroup :
}
companion object {
class OpenAction(private val data: OpenData) : DumbAwareAction("Open for ${data.description.project.name} project") {
class OpenAction(private val data: OpenData) : DumbAwareAction("Open for ${data.info.project.name} project") {
override fun actionPerformed(e: AnActionEvent) {
data.url?.let { BrowserUtil.browse(it) }
@@ -190,20 +190,20 @@ class CircletVcsOpenInBrowserActionGroup :
sealed class OpenData(val project: Project,
val server: String,
val description: CircletProjectDescription,
val info: CircletProjectInfo,
val repo: String
) {
abstract val url: String?
class Commit(project: Project,
server: String,
projectKey: CircletProjectDescription,
projectKey: CircletProjectInfo,
repo: String,
private val commit: CommitId) : OpenData(project, server, projectKey, repo) {
override val url: String?
get() {
return Navigator.p.project(description.projectKey)
return Navigator.p.project(info.key)
.commits(repo, "", commit.hash.asString())
.absoluteHref(server)
}
@@ -211,13 +211,13 @@ class CircletVcsOpenInBrowserActionGroup :
class FileRevision(project: Project,
server: String,
projectKey: CircletProjectDescription,
projectKey: CircletProjectInfo,
repo: String,
private val vcsFileRevisionEx: VcsFileRevisionEx) : OpenData(project, server, projectKey, repo) {
override val url: String?
get() {
return Navigator.p.project(description.projectKey)
return Navigator.p.project(info.key)
.revision(repo, vcsFileRevisionEx.revisionNumber.asString())
.absoluteHref(server)
}
@@ -225,7 +225,7 @@ class CircletVcsOpenInBrowserActionGroup :
class File(project: Project,
server: String,
projectKey: CircletProjectDescription,
projectKey: CircletProjectInfo,
repo: String,
private val virtualFile: VirtualFile, private val gitRepository: GitRepository) : OpenData(project, server, projectKey, repo) {
override val url: String?
@@ -233,7 +233,7 @@ class CircletVcsOpenInBrowserActionGroup :
val relativePath = VfsUtilCore.getRelativePath(virtualFile, gitRepository.root) ?: return null
val hash = getCurrentFileRevisionHash(project, virtualFile) ?: return null
return Navigator.p.project(description.projectKey)
return Navigator.p.project(info.key)
.fileAnnotate(repo, hash, relativePath)
.absoluteHref(server)
}
@@ -1,16 +1,22 @@
package circlet.vcs
import circlet.client.*
import circlet.client.api.*
import circlet.components.*
import circlet.platform.client.*
import circlet.workspaces.*
import com.intellij.openapi.*
import com.intellij.openapi.project.*
import git4idea.*
import git4idea.repo.*
import libraries.coroutines.extra.*
import runtime.async.*
import circlet.client.api.PR_Project
import circlet.client.api.ProjectKey
import circlet.client.api.Projects
import circlet.client.api.RepositoryService
import circlet.client.pr
import circlet.client.repoService
import circlet.components.circletWorkspace
import circlet.platform.client.ConnectionStatus
import circlet.platform.client.resolve
import circlet.workspaces.Workspace
import com.intellij.openapi.Disposable
import com.intellij.openapi.project.Project
import git4idea.GitUtil
import git4idea.repo.GitRepository
import git4idea.repo.GitRepositoryChangeListener
import libraries.coroutines.extra.LifetimeSource
import runtime.async.backoff
import runtime.reactive.*
class CircletProjectContext(project: Project) : Disposable {
@@ -18,15 +24,12 @@ class CircletProjectContext(project: Project) : Disposable {
private val remoteUrls: MutableProperty<Set<String>> = Property.createMutable(findRemoteUrls(project))
private val projectsInfo: Property<Map<String, Pair<String, List<CircletProjectDescription>>?>> = lifetime.mapInit(circletWorkspace.workspace, remoteUrls, emptyMap()) { ws, urls ->
ws ?: return@mapInit emptyMap<String, Pair<String, List<CircletProjectDescription>>?>()
val context: Property<Context> = lifetime.mapInit(circletWorkspace.workspace, remoteUrls, EMPTY) { ws, urls ->
ws ?: return@mapInit EMPTY
ws.client.connectionStatus.filter { it is ConnectionStatus.Connected }.awaitFirst(ws.lifetime)
reloadProjectKeys(ws, urls)
}
val projectDescriptions: Pair<String, List<CircletProjectDescription>>?
get() = projectsInfo.value.values.filterNotNull().firstOrNull()
init {
project.messageBus
.connect(this)
@@ -36,8 +39,8 @@ class CircletProjectContext(project: Project) : Disposable {
})
}
fun findProjectInfo(remoteUrl: String): Pair<String, List<CircletProjectDescription>>? {
return projectsInfo.value[remoteUrl]
fun getRepoDescriptionByUrl(remoteUrl: String): CircletRepoInfo? {
return context.value.repoByUrl[remoteUrl]
}
private fun findRemoteUrls(project: Project): Set<String> = GitUtil.getRepositoryManager(project).repositories
@@ -45,20 +48,33 @@ class CircletProjectContext(project: Project) : Disposable {
.flatMap { it.urls }
.toSet<String>()
private suspend fun reloadProjectKeys(ws: Workspace, urls: Set<String>): Map<String, Pair<String, List<CircletProjectDescription>>?> {
return urls.map { url ->
private suspend fun reloadProjectKeys(ws: Workspace, urls: Set<String>): Context {
val reposByUrl: Map<String, CircletRepoInfo?> = urls.map { url ->
backoff {
url to loadProjectKeysForUrl(ws, url)
}
}.toMap()
val reposInProject = HashMap<CircletProjectInfo, MutableSet<CircletRepoInfo>>()
for (repoDescription in reposByUrl.values.filterNotNull()) {
for (projectDescription in repoDescription.projectInfos) {
reposInProject.getOrPut(projectDescription, { HashSet() })
.add(repoDescription)
}
}
return Context(reposByUrl, reposInProject)
}
private suspend fun loadProjectKeysForUrl(ws: Workspace, url: String): Pair<String, List<CircletProjectDescription>>? {
private suspend fun loadProjectKeysForUrl(ws: Workspace, url: String): CircletRepoInfo? {
val repoService: RepositoryService = ws.client.repoService
val repoProjectKeys = repoService.findByRepositoryUrl(url) ?: return null
val (repoName, projectKeys) = repoService.findByRepositoryUrl(url) ?: return null
val projectService: Projects = ws.client.pr
val list = repoProjectKeys.second.map { CircletProjectDescription(it, projectService.getProjectByKey(it).resolve()) }.toList()
return repoProjectKeys.first to list
val projectDescriptions = projectKeys.map { CircletProjectInfo(it, projectService.getProjectByKey(it).resolve()) }.toSet()
return CircletRepoInfo(url,
repoName,
projectDescriptions)
}
override fun dispose() {
@@ -71,3 +87,27 @@ class CircletProjectContext(project: Project) : Disposable {
}
}
}
data class Context(
val repoByUrl: Map<String, CircletRepoInfo?>,
val reposInProject: Map<CircletProjectInfo, Set<CircletRepoInfo>>
) {
val empty: Boolean
get() {
return this == EMPTY
}
}
private val EMPTY: Context = Context(emptyMap(), emptyMap())
data class CircletRepoInfo(
val url: String,
val name: String,
val projectInfos: Set<CircletProjectInfo>
)
data class CircletProjectInfo(
val key: ProjectKey,
val project: PR_Project
)
@@ -1,8 +0,0 @@
package circlet.vcs
import circlet.client.api.*
data class CircletProjectDescription(
val projectKey: ProjectKey,
val project: PR_Project
)
@@ -44,8 +44,8 @@ class CircletShareProjectAction : DumbAwareAction() {
}
val context = CircletProjectContext.getInstance(project)
val descriptions = context.projectDescriptions
if (descriptions != null && descriptions.second.isNotEmpty()) {
val descriptions = context.context.value.empty
if (descriptions) {
e.presentation.isEnabledAndVisible = false
return
}