Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
cunla committed Apr 17, 2024
1 parent 091766e commit 65ee781
Show file tree
Hide file tree
Showing 18 changed files with 804 additions and 19 deletions.
17 changes: 10 additions & 7 deletions .github/ISSUE_TEMPLATE/bug_report.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,22 @@ title: ''
labels: ''
assignees: ''
---

**Describe the bug**
A clear and concise description of what the bug is.
### Describe the bug
A clear and concise description of what the bug is.
Add a screenshot if it is relevant.

**Describe the bug:**
<!-- A clear and concise description of what the bug is. -->

**Steps to reproduce:**
### Steps to reproduce

<!-- Steps to reproduce the issue. -->
### Expected behavior

**Expected behavior:**
<!-- A clear and concise description of what you expected to happen. -->

**Additional context:**
<!-- Add any other context about the problem here. -->
### Additional context

Plugin version:
IDE:
OS:
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.dsoftware.ghmanager

import com.intellij.ide.BrowserUtil
import com.intellij.ide.plugins.PluginManagerCore.getPlugin
import com.intellij.openapi.application.ApplicationInfo
import com.intellij.openapi.diagnostic.ErrorReportSubmitter
import com.intellij.openapi.diagnostic.IdeaLoggingEvent
import com.intellij.openapi.diagnostic.SubmittedReportInfo
import com.intellij.openapi.util.SystemInfo
import com.intellij.util.Consumer
import java.awt.Component
import java.net.URLEncoder
import java.nio.charset.StandardCharsets

internal class PluginErrorReportSubmitter : ErrorReportSubmitter() {
private val REPORT_URL =
"https://github.com/cunla/ghactions-manager/issues/new?assignees=&labels=&projects=&template=bug_report.md"

override fun getReportActionText(): String {
return "Report Issue on Plugin Issues Tracker"
}

override fun submit(
events: Array<IdeaLoggingEvent>,
additionalInfo: String?,
parentComponent: Component,
consumer: Consumer<in SubmittedReportInfo?>
): Boolean {
val event = events[0]
val throwableTitle = event.throwableText.lines()[0]

val sb = StringBuilder(REPORT_URL)

val titleEncoded = URLEncoder.encode(event.throwable?.message ?: throwableTitle, StandardCharsets.UTF_8)
sb.append("&title=${titleEncoded}")

val pluginVersion = getPlugin(pluginDescriptor.pluginId)?.version ?: "unknown"

val body = """
### Describe the bug
A clear and concise description of what the bug is.
Add a screenshot if it is relevant.
**Describe the bug:**
${additionalInfo ?: ""}
${event.message ?: ""}
#### Stack trace
{{{PLACEHOLDER}}}
### Steps to reproduce
<!-- Steps to reproduce the issue. -->
### Expected behavior
<!-- A clear and concise description of what you expected to happen. -->
### Additional context
Plugin version: $pluginVersion
IDE: ${ApplicationInfo.getInstance().fullApplicationName} (${ApplicationInfo.getInstance().build.asString()})
OS: ${SystemInfo.getOsNameAndVersion()}
""".trimIndent().replace("{{{PLACEHOLDER}}}", event.throwableText)
sb.append("&body=${URLEncoder.encode(body, StandardCharsets.UTF_8)}")
BrowserUtil.browse(sb.toString())

consumer.consume(SubmittedReportInfo(SubmittedReportInfo.SubmissionStatus.NEW_ISSUE))
return true
}

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.dsoftware.ghmanager.data

import com.dsoftware.ghmanager.ui.GhActionsMgrToolWindowContent
import com.dsoftware.ghmanager.ui.settings.GhActionsSettingsService
import com.intellij.openapi.Disposable
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
Expand All @@ -23,6 +24,8 @@ interface GhActionsService {
val gitHubAccounts: Set<GithubAccount>
val accountsState: StateFlow<Collection<GithubAccount>>
val toolWindowsJobMap: MutableMap<ToolWindow, Job>


fun guessAccountForRepository(repo: GHGitRepositoryMapping): GithubAccount? {
return gitHubAccounts.firstOrNull { it.server.equals(repo.repository.serverPath, true) }
}
Expand All @@ -46,8 +49,9 @@ interface GhActionsService {
}
}

open class GhActionsServiceImpl(project: Project, override val coroutineScope: CoroutineScope) : GhActionsService,
Disposable {
open class GhActionsServiceImpl(
project: Project, override val coroutineScope: CoroutineScope
) : GhActionsService, Disposable {
private val repositoriesManager = project.service<GHHostedRepositoriesManager>()
private val accountManager = service<GHAccountManager>()

Expand Down
196 changes: 196 additions & 0 deletions src/main/kotlin/com/dsoftware/ghmanager/psi/GitHubActionCache.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
package com.dsoftware.ghmanager.psi

import com.dsoftware.ghmanager.api.GhApiRequestExecutor
import com.dsoftware.ghmanager.data.GhActionsService
import com.dsoftware.ghmanager.psi.model.GitHubAction
import com.dsoftware.ghmanager.ui.ToolbarUtil
import com.dsoftware.ghmanager.ui.settings.GhActionsSettingsService
import com.fasterxml.jackson.databind.JsonNode
import com.intellij.collaboration.api.dto.GraphQLRequestDTO
import com.intellij.collaboration.api.dto.GraphQLResponseDTO
import com.intellij.openapi.components.PersistentStateComponent
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.State
import com.intellij.openapi.components.Storage
import com.intellij.openapi.components.service
import com.intellij.openapi.diagnostic.logger
import com.intellij.openapi.project.Project
import com.intellij.util.ResourceUtil
import com.intellij.util.ThrowableConvertor
import com.intellij.util.concurrency.annotations.RequiresEdt
import org.jetbrains.plugins.github.api.GithubApiContentHelper
import org.jetbrains.plugins.github.api.GithubApiRequest.Post
import org.jetbrains.plugins.github.api.GithubApiResponse
import org.jetbrains.plugins.github.api.data.graphql.GHGQLError
import org.jetbrains.plugins.github.exceptions.GithubAuthenticationException
import org.jetbrains.plugins.github.exceptions.GithubConfusingException
import org.jetbrains.plugins.github.exceptions.GithubJsonException
import org.jetbrains.plugins.github.util.GHCompatibilityUtil
import java.io.IOException
import java.util.concurrent.ScheduledFuture

@Service(Service.Level.PROJECT)
@State(name = "GitHubActionCache", storages = [Storage("githubActionCache.xml")])
class GitHubActionCache(private val project: Project) : PersistentStateComponent<GitHubActionCache.State?> {
private var state = State()
val actionsToResolve = mutableSetOf<String>()
private val task: ScheduledFuture<*>

private val ghActionsService = project.service<GhActionsService>()
private val settingsService = project.service<GhActionsSettingsService>()
private val serverPath: String
private var requestExecutor: GhApiRequestExecutor? = null

init {
this.serverPath = determineServerPath()
ghActionsService.gitHubAccounts.firstOrNull()?.let { account ->
val token = if (settingsService.state.useGitHubSettings) {
GHCompatibilityUtil.getOrRequestToken(account, project)
} else {
settingsService.state.apiToken
}
requestExecutor = if (token == null) null else GhApiRequestExecutor.create(token)
}
task = ToolbarUtil.executeTaskAtCustomFrequency(project, 5) {
actionsToResolve.removeAll(state.actions.keys)
actionsToResolve.forEach {
resolveGithubAction(it)
}
}
}

class State {
val actions = TimedCache()
}

override fun loadState(state: State) {
this.state = state
}

fun cleanup() {
state.actions.cleanup()
}

private fun determineServerPath(): String {
val mappings = ghActionsService.knownRepositoriesState.value
if (mappings.isEmpty()) {
LOG.info("No repository mappings, using default graphql url")
return "https://api.github.com/graphql"
} else {
val mapping = mappings.iterator().next()
return mapping.repository.serverPath.toGraphQLUrl()
}
}

fun getAction(fullActionName: String): GitHubAction? {
if (state.actions.containsKey(fullActionName)) {
return state.actions[fullActionName]
}
LOG.info("Action $fullActionName not found in cache, adding to resolve list")
actionsToResolve.add(fullActionName)
return null
}

@RequiresEdt
private fun resolveGithubAction(fullActionName: String) {
if (state.actions.containsKey(fullActionName)) {
return
}
val requestExecutor = this.requestExecutor
if (requestExecutor == null) {
LOG.warn("Failed to get latest version of action $fullActionName: no GitHub account found")
return
}
LOG.info("Resolving action $fullActionName")
val actionOrg = fullActionName.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0]
val actionName = fullActionName.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[1]
val query = ResourceUtil.getResource(
GhActionsService::class.java.classLoader,
"graphql/query",
"getLatestRelease.graphql"
)?.readText() ?: ""

val request = TraversedParsed(
serverPath,
query,
mapOf("owner" to actionOrg, "name" to actionName),
JsonNode::class.java,
"repository",
"latestRelease",
"tag",
"name"
)
try {
val response = requestExecutor.execute(request)
val version = response.toString().replace("\"", "")
state.actions[fullActionName] = GitHubAction(actionName, version)
} catch (e: IOException) {
LOG.warn("Failed to get latest version of action $fullActionName", e)
}
}


class TraversedParsed<out T : Any>(
url: String,
private val query: String,
private val variablesObject: Any,
private val clazz: Class<out T>,
private vararg val pathFromData: String
) : Post<T>(GithubApiContentHelper.JSON_MIME_TYPE, url) {

override val body: String
get() = GithubApiContentHelper.toJson(GraphQLRequestDTO(query, variablesObject), true)


protected fun throwException(errors: List<GHGQLError>): Nothing {
if (errors.any { it.type.equals("INSUFFICIENT_SCOPES", true) })
throw GithubAuthenticationException("Access token has not been granted the required scopes.")

if (errors.size == 1) throw GithubConfusingException(errors.single().toString())
throw GithubConfusingException(errors.toString())
}

override fun extractResult(response: GithubApiResponse): T {
return parseResponse(response, clazz, pathFromData)
?: throw GithubJsonException("Non-nullable entity is null or entity path is invalid")
}

private fun <T> parseResponse(
response: GithubApiResponse,
clazz: Class<T>,
pathFromData: Array<out String>
): T? {
val result: GraphQLResponseDTO<out JsonNode, GHGQLError> = parseGQLResponse(response, JsonNode::class.java)
val data = result.data
if (data != null && !data.isNull) {
var node: JsonNode = data
for (path in pathFromData) {
node = node[path] ?: break
}
if (!node.isNull) return GithubApiContentHelper.fromJson(node.toString(), clazz, true)
}
val errors = result.errors
if (errors == null) return null
else throwException(errors)
}

private fun <T> parseGQLResponse(
response: GithubApiResponse,
dataClass: Class<out T>
): GraphQLResponseDTO<out T, GHGQLError> {
return response.readBody(ThrowableConvertor {
@Suppress("UNCHECKED_CAST")
GithubApiContentHelper.readJsonObject(
it, GraphQLResponseDTO::class.java, dataClass, GHGQLError::class.java,
gqlNaming = true
) as GraphQLResponseDTO<T, GHGQLError>
})
}
}

override fun getState(): State = state

companion object {
private val LOG = logger<GitHubActionCache>()
}
}
Loading

0 comments on commit 65ee781

Please sign in to comment.