Skip to content

Commit 71c4772

Browse files
authored
[IJ plugin] Add a Download Schema action (#5143)
1 parent 78d7528 commit 71c4772

File tree

9 files changed

+245
-27
lines changed

9 files changed

+245
-27
lines changed

docs/source/advanced/plugin-configuration.mdx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ apollo {
203203
This will create a task named `download<ServiceName>ApolloSchemaFromRegistry` (`downloadServiceApolloSchemaFromRegistry`
204204
by default).
205205

206+
With the [Android Studio plugin](../testing/android-studio-plugin), you can also go to <kbd>Tools</kbd> | <kbd>Apollo</kbd> | <kbd>Download schema</kbd> which acts as a shortcut to these tasks.
207+
206208
## All options
207209

208210
Below is a summary of the Gradle options in a single code block. For more details, take a look at the [ApolloExtension KDoc](https://www.apollographql.com/docs/kotlin/kdoc/apollo-gradle-plugin-external/com.apollographql.apollo3.gradle.api/-apollo-extension/index.html)
@@ -348,4 +350,4 @@ apollo {
348350
// Link sqlite for Kotlin native projects
349351
linkSqlite.set(true)
350352
}
351-
```
353+
```

docs/source/testing/android-studio-plugin.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ In the <kbd>Refactor</kbd> | <kbd>Apollo</kbd> menu, you can find helpers to mig
5858
Note: while these helpers will automatically update your code when possible, there are some cases where this isn't possible and manual changes are required.
5959
Please refer to the migration guides ([3.x](../migration/3.0/), [4.x](../migration/4.0/)) when upgrading.
6060

61+
### Download schema
62+
63+
Download the latest version of your schema(s) by going to <kbd>Tools</kbd> | <kbd>Apollo</kbd> | <kbd>Download schema</kbd>.
64+
65+
This uses the Introspection endpoint or the Registry that you can [configure](../advanced/plugin-configuration#downloading-a-schema) on your Apollo service.
66+
6167
### Open in Apollo Sandbox
6268

6369
You can open a GraphQL file in [Apollo Sandbox](https://studio.apollographql.com/sandbox/explorer) with right click | <kbd>Open in</kbd> | <kbd>Apollo Sandbox</kbd>.

intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/codegen/ApolloCodegenService.kt

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package com.apollographql.ijplugin.codegen
33
import com.apollographql.ijplugin.gradle.CODEGEN_GRADLE_TASK_NAME
44
import com.apollographql.ijplugin.gradle.GradleExecutionHelperCompat
55
import com.apollographql.ijplugin.gradle.GradleHasSyncedListener
6+
import com.apollographql.ijplugin.gradle.SimpleProgressListener
67
import com.apollographql.ijplugin.gradle.getGradleRootPath
78
import com.apollographql.ijplugin.project.ApolloProjectListener
89
import com.apollographql.ijplugin.project.ApolloProjectService
@@ -37,11 +38,6 @@ import com.intellij.openapi.util.CheckedDisposable
3738
import com.intellij.openapi.vfs.VfsUtil
3839
import org.gradle.tooling.CancellationTokenSource
3940
import org.gradle.tooling.GradleConnector
40-
import org.gradle.tooling.events.FailureResult
41-
import org.gradle.tooling.events.FinishEvent
42-
import org.gradle.tooling.events.ProgressListener
43-
import org.gradle.tooling.events.StartEvent
44-
import org.gradle.tooling.events.SuccessResult
4541
import org.jetbrains.kotlin.idea.configuration.GRADLE_SYSTEM_ID
4642
import org.jetbrains.plugins.gradle.settings.GradleExecutionSettings
4743
import org.jetbrains.plugins.gradle.util.GradleConstants
@@ -197,27 +193,13 @@ class ApolloCodegenService(
197193
.forTasks(CODEGEN_GRADLE_TASK_NAME)
198194
.withCancellationToken(gradleCodegenCancellation!!.token())
199195
.addArguments("--continuous")
200-
.addProgressListener(ProgressListener { event ->
201-
when {
202-
event is StartEvent && event.descriptor.name == "Run build" -> {
203-
logd("Gradle build started")
204-
}
205-
206-
event is FinishEvent && event.descriptor.name == "Run build" -> {
207-
when (val result = event.result) {
208-
is FailureResult -> {
209-
logd("Gradle build failed: ${result.failures.map { it.message }}")
210-
}
211-
212-
is SuccessResult -> {
213-
logd("Gradle build success, marking generated source roots as dirty")
214-
// Mark the generated sources dirty so the files are visible to the IDE
215-
val generatedSourceRoots = modules.flatMap { it.apolloGeneratedSourcesRoots() }
216-
logd("Mark dirty $generatedSourceRoots")
217-
VfsUtil.markDirtyAndRefresh(true, true, true, *generatedSourceRoots.toTypedArray())
218-
}
219-
}
220-
}
196+
.addProgressListener(object : SimpleProgressListener() {
197+
override fun onSuccess() {
198+
logd("Gradle build success, marking generated source roots as dirty")
199+
// Mark the generated sources dirty so the files are visible to the IDE
200+
val generatedSourceRoots = modules.flatMap { it.apolloGeneratedSourcesRoots() }
201+
logd("Mark dirty $generatedSourceRoots")
202+
VfsUtil.markDirtyAndRefresh(true, true, true, *generatedSourceRoots.toTypedArray())
221203
}
222204
})
223205
.run()
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package com.apollographql.ijplugin.gradle
2+
3+
import com.apollographql.ijplugin.ApolloBundle
4+
import com.apollographql.ijplugin.project.apolloProjectService
5+
import com.apollographql.ijplugin.util.logd
6+
import com.apollographql.ijplugin.util.logw
7+
import com.apollographql.ijplugin.util.showNotification
8+
import com.intellij.ide.BrowserUtil
9+
import com.intellij.notification.NotificationType
10+
import com.intellij.openapi.actionSystem.ActionUpdateThread
11+
import com.intellij.openapi.actionSystem.AnAction
12+
import com.intellij.openapi.actionSystem.AnActionEvent
13+
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskId
14+
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskNotificationListenerAdapter
15+
import com.intellij.openapi.externalSystem.model.task.ExternalSystemTaskType
16+
import com.intellij.openapi.externalSystem.util.ExternalSystemApiUtil
17+
import com.intellij.openapi.progress.ProgressIndicator
18+
import com.intellij.openapi.progress.Task
19+
import com.intellij.openapi.project.Project
20+
import org.gradle.tooling.Failure
21+
import org.gradle.tooling.model.GradleProject
22+
import org.jetbrains.kotlin.idea.configuration.GRADLE_SYSTEM_ID
23+
import org.jetbrains.plugins.gradle.settings.GradleExecutionSettings
24+
import org.jetbrains.plugins.gradle.util.GradleConstants
25+
26+
class DownloadSchemaAction : AnAction() {
27+
override fun actionPerformed(e: AnActionEvent) {
28+
logd()
29+
val project = e.project ?: return
30+
DownloadSchemaTask(project).queue()
31+
}
32+
33+
override fun update(e: AnActionEvent) {
34+
e.presentation.isEnabled = e.project?.apolloProjectService?.apolloVersion?.isAtLeastV3 == true && e.project?.getGradleRootPath() != null
35+
}
36+
37+
override fun getActionUpdateThread() = ActionUpdateThread.BGT
38+
}
39+
40+
private val DOWNLOAD_SCHEMA_TASK_REGEX = Regex("download.+ApolloSchemaFrom(Introspection|Registry)")
41+
private const val SCHEMA_CONFIGURATION_DOC_URL = "https://www.apollographql.com/docs/kotlin/advanced/plugin-configuration#downloading-a-schema"
42+
43+
private class DownloadSchemaTask(project: Project) : Task.Backgroundable(
44+
project,
45+
ApolloBundle.message("action.DownloadSchemaAction.progress"),
46+
false,
47+
) {
48+
override fun run(indicator: ProgressIndicator) {
49+
val rootProjectPath = project.getGradleRootPath() ?: return
50+
val gradleExecutionHelper = GradleExecutionHelperCompat()
51+
val executionSettings = ExternalSystemApiUtil.getExecutionSettings<GradleExecutionSettings>(project, rootProjectPath, GradleConstants.SYSTEM_ID)
52+
val rootGradleProject = gradleExecutionHelper.execute(rootProjectPath, executionSettings) { connection ->
53+
logd("Fetch Gradle project model")
54+
return@execute try {
55+
val id = ExternalSystemTaskId.create(GRADLE_SYSTEM_ID, ExternalSystemTaskType.RESOLVE_PROJECT, project)
56+
gradleExecutionHelper.getModelBuilder(GradleProject::class.java, connection, id, executionSettings, ExternalSystemTaskNotificationListenerAdapter.NULL_OBJECT)
57+
.get()
58+
} catch (t: Throwable) {
59+
logw(t, "Couldn't fetch Gradle project model")
60+
null
61+
}
62+
} ?: return
63+
64+
val allDownloadSchemaTasks: List<String> = (rootGradleProject.children + rootGradleProject)
65+
.flatMap { gradleProject -> gradleProject.tasks.filter { task -> task.name.matches(DOWNLOAD_SCHEMA_TASK_REGEX) } }
66+
.map { it.name }
67+
.distinct()
68+
logd("allDownloadSchemaTasks=$allDownloadSchemaTasks")
69+
if (allDownloadSchemaTasks.isEmpty()) {
70+
showNotification(
71+
project = project,
72+
title = ApolloBundle.message("action.DownloadSchemaAction.noTasksFound.title"),
73+
content = ApolloBundle.message("action.DownloadSchemaAction.noTasksFound.content"),
74+
type = NotificationType.WARNING,
75+
object : AnAction(ApolloBundle.message("action.DownloadSchemaAction.openDocumentation")) {
76+
override fun actionPerformed(e: AnActionEvent) {
77+
BrowserUtil.browse(SCHEMA_CONFIGURATION_DOC_URL, project)
78+
}
79+
}
80+
)
81+
return
82+
}
83+
84+
gradleExecutionHelper.execute(rootProjectPath, executionSettings) { connection ->
85+
try {
86+
val id = ExternalSystemTaskId.create(GRADLE_SYSTEM_ID, ExternalSystemTaskType.EXECUTE_TASK, project)
87+
gradleExecutionHelper.getBuildLauncher(connection, id, allDownloadSchemaTasks, executionSettings, ExternalSystemTaskNotificationListenerAdapter.NULL_OBJECT)
88+
.forTasks(*allDownloadSchemaTasks.toTypedArray())
89+
.addProgressListener(object : SimpleProgressListener() {
90+
override fun onFailure(failures: List<Failure>) {
91+
super.onFailure(failures)
92+
showNotification(
93+
project = project,
94+
title = ApolloBundle.message("action.DownloadSchemaAction.buildFail.title"),
95+
content = ApolloBundle.message("action.DownloadSchemaAction.buildFail.content", failures.firstOrNull()?.message
96+
?: "(no message)", allDownloadSchemaTasks.joinToString(" ")),
97+
type = NotificationType.WARNING
98+
)
99+
}
100+
101+
override fun onSuccess() {
102+
super.onSuccess()
103+
val schemas = if (allDownloadSchemaTasks.size > 1) ApolloBundle.message("action.DownloadSchemaAction.schema.plural") else ApolloBundle.message("action.DownloadSchemaAction.schema.singular")
104+
showNotification(
105+
project = project,
106+
content = ApolloBundle.message("action.DownloadSchemaAction.buildSuccess.content", schemas),
107+
type = NotificationType.INFORMATION
108+
)
109+
}
110+
})
111+
.run()
112+
logd("Gradle execution finished")
113+
} catch (t: Throwable) {
114+
logd(t, "Gradle execution failed")
115+
}
116+
}
117+
}
118+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.apollographql.ijplugin.gradle
2+
3+
import com.apollographql.ijplugin.util.logd
4+
import org.gradle.tooling.Failure
5+
import org.gradle.tooling.events.FailureResult
6+
import org.gradle.tooling.events.FinishEvent
7+
import org.gradle.tooling.events.ProgressEvent
8+
import org.gradle.tooling.events.ProgressListener
9+
import org.gradle.tooling.events.StartEvent
10+
import org.gradle.tooling.events.SuccessResult
11+
12+
open class SimpleProgressListener : ProgressListener {
13+
override fun statusChanged(event: ProgressEvent) {
14+
when {
15+
event is StartEvent && event.descriptor.name == "Run build" -> onStart()
16+
17+
event is FinishEvent && event.descriptor.name == "Run build" -> {
18+
when (val result = event.result) {
19+
is FailureResult -> onFailure(result.failures)
20+
21+
is SuccessResult -> onSuccess()
22+
}
23+
}
24+
}
25+
}
26+
27+
open fun onStart() {
28+
logd("Gradle build started")
29+
}
30+
31+
open fun onFailure(failures: List<Failure>) {
32+
logd("Gradle build failed: ${failures.map { it.message }}")
33+
}
34+
35+
open fun onSuccess() {
36+
logd("Gradle build success")
37+
}
38+
}

intellij-plugin/src/main/kotlin/com/apollographql/ijplugin/studio/fieldinsights/RefreshFieldInsightsAction.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.apollographql.ijplugin.studio.fieldinsights
22

33
import com.apollographql.ijplugin.ApolloBundle
4+
import com.apollographql.ijplugin.project.apolloProjectService
45
import com.apollographql.ijplugin.settings.SettingsConfigurable
56
import com.apollographql.ijplugin.settings.settingsState
67
import com.apollographql.ijplugin.util.logd
@@ -34,5 +35,9 @@ class RefreshFieldInsightsAction : AnAction() {
3435
project.fieldInsightsService.fetchLatencies()
3536
}
3637

38+
override fun update(e: AnActionEvent) {
39+
e.presentation.isEnabled = e.project?.apolloProjectService?.apolloVersion?.isAtLeastV3 == true
40+
}
41+
3742
override fun getActionUpdateThread() = ActionUpdateThread.BGT
3843
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.apollographql.ijplugin.util
2+
3+
import com.intellij.notification.Notification
4+
import com.intellij.notification.NotificationGroupManager
5+
import com.intellij.notification.NotificationType
6+
import com.intellij.openapi.actionSystem.AnAction
7+
import com.intellij.openapi.project.Project
8+
import com.intellij.openapi.util.NlsContexts.NotificationContent
9+
import com.intellij.openapi.util.NlsContexts.NotificationTitle
10+
11+
@Suppress("UnstableApiUsage")
12+
fun createNotification(
13+
@NotificationTitle title: String = "",
14+
@NotificationContent content: String,
15+
type: NotificationType,
16+
vararg actions: AnAction,
17+
): Notification = NotificationGroupManager.getInstance()
18+
.getNotificationGroup("Apollo")
19+
.createNotification(title, content, type)
20+
.apply {
21+
for (action in actions) {
22+
addAction(action)
23+
}
24+
}
25+
26+
@Suppress("UnstableApiUsage")
27+
fun showNotification(
28+
project: Project,
29+
@NotificationTitle title: String = "",
30+
@NotificationContent content: String,
31+
type: NotificationType,
32+
vararg actions: AnAction,
33+
) = createNotification(
34+
title = title,
35+
content = content,
36+
type = type,
37+
actions = actions,
38+
).notify(project)

intellij-plugin/src/main/resources/META-INF/plugin.xml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,13 @@
146146
serviceImplementation="com.apollographql.ijplugin.studio.fieldinsights.FieldInsightsServiceImpl"
147147
/>
148148

149+
<!-- Notifications -->
150+
<notificationGroup
151+
id="Apollo"
152+
displayType="BALLOON"
153+
key="notification.group.apollo"
154+
/>
155+
149156
</extensions>
150157

151158
<extensions defaultExtensionNs="com.intellij.lang.jsgraphql">
@@ -236,6 +243,14 @@
236243
>
237244
<add-to-group group-id="ApolloToolsActionGroup" />
238245
</action>
246+
247+
<!-- Tools / Apollo / Download schema -->
248+
<action
249+
id="DownloadSchemaAction"
250+
class="com.apollographql.ijplugin.gradle.DownloadSchemaAction"
251+
>
252+
<add-to-group group-id="ApolloToolsActionGroup" />
253+
</action>
239254
</actions>
240255

241256
</idea-plugin>

intellij-plugin/src/main/resources/messages/ApolloBundle.properties

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,18 @@ action.RefreshFieldInsightsAction.mustConfigureDialog.title=Apollo GraphOS Not C
4646
action.RefreshFieldInsightsAction.mustConfigureDialog.ok=Open Settings
4747
action.RefreshFieldInsightsAction.mustConfigureDialog.cancel=Cancel
4848

49+
action.DownloadSchemaAction.text=Download Schema
50+
action.DownloadSchemaAction.description=Download the latest schema(s) from introspection or registry as configured in the Apollo Gradle configuration
51+
action.DownloadSchemaAction.progress=Downloading schema
52+
action.DownloadSchemaAction.noTasksFound.title=Could not download schema
53+
action.DownloadSchemaAction.noTasksFound.content=No download schema tasks were found. Please configure an <code>introspection</code> or <code>registry</code> in the Apollo Gradle configuration.
54+
action.DownloadSchemaAction.openDocumentation=Open Documentation
55+
action.DownloadSchemaAction.buildFail.title=Could not download schema
56+
action.DownloadSchemaAction.buildFail.content=Gradle task execution failed: ''{0}''.<br>Try on the command line with <code>./gradlew {1}</code> for more information.
57+
action.DownloadSchemaAction.buildSuccess.content={0} downloaded successfully
58+
action.DownloadSchemaAction.schema.singular=Schema
59+
action.DownloadSchemaAction.schema.plural=Schemas
60+
4961
ApolloMigrationRefactoringProcessor.codeReferences=Items to be migrated
5062

5163
ApolloV2ToV3MigrationProcessor.title=Migrate to Apollo Kotlin 3
@@ -109,3 +121,5 @@ inspection.unusedField.quickFix=Delete field
109121
inspection.apollo4Available.displayName=Apollo Kotlin 4 is available
110122
inspection.apollo4Available.reportText=Apollo Kotlin 4 is available
111123
inspection.apollo4Available.quickFix=Migrate to Apollo Kotlin 4
124+
125+
notification.group.apollo=Apollo

0 commit comments

Comments
 (0)