Skip to content

Commit

Permalink
Shows prompt to create Skaffold configurations if any YAML file in th…
Browse files Browse the repository at this point in the history
…e project becomes valid Skaffold file (GoogleContainerTools#126)

* Use VFS to track YAML file content and prompt to create Skaffold configs when new valid Skaffold YAML is created.

* added support for new files created /copied from outside of the IDE.
  • Loading branch information
ivanporty authored Jan 11, 2019
1 parent d629517 commit d99cc82
Show file tree
Hide file tree
Showing 2 changed files with 118 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import com.google.container.tools.skaffold.run.SkaffoldSingleRunConfigurationFac
import com.intellij.execution.RunManager
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.execution.configurations.ConfigurationTypeUtil
import com.intellij.execution.configurations.RunConfiguration
import com.intellij.notification.Notification
import com.intellij.notification.NotificationDisplayType
import com.intellij.notification.NotificationGroup
Expand All @@ -39,12 +38,19 @@ import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.DumbService
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileEvent
import com.intellij.openapi.vfs.VirtualFileListener
import com.intellij.openapi.vfs.VirtualFileManager
import org.jetbrains.yaml.YAMLFileType

/**
* Detects if project has Skaffold configuration but no Skaffold run targets configured. Uses
* [ProjectComponent] to watch project opening, and prompts to create dev/deploy configurations
* if they don't exist yet.
*
* Uses [VirtualFileManager] and [VirtualFileListener] to track new YAML files in already opened
* project to prompt creating new configurations when Skaffold YAML is created.
*
* @param project IDE Project for which this component is created.
*/
class SkaffoldConfigurationDetector(val project: Project) : ProjectComponent {
Expand All @@ -66,17 +72,48 @@ class SkaffoldConfigurationDetector(val project: Project) : ProjectComponent {
val skaffoldFiles: List<VirtualFile> =
SkaffoldFileService.instance.findSkaffoldFiles(project)
if (skaffoldFiles.isNotEmpty()) {
val skaffoldRunConfigList: List<RunConfiguration> =
getRunManager(project)
.allConfigurationsList.filter { it is AbstractSkaffoldRunConfiguration }
if (skaffoldRunConfigList.isEmpty()) {
if (!hasExistingSkaffoldConfigurations()) {
// existing Skaffold config files, but no skaffold configurations, prompt
showPromptForSkaffoldConfigurations(project, skaffoldFiles[0])
}
}
}

// add VFS listener to ensure we track YAML file changes when no Skaffold configurations
// exist yet to show prompt for configurations once Skaffold file is created.
addVirtualFileListener(object : VirtualFileListener {
override fun contentsChanged(event: VirtualFileEvent) {
checkForSkaffoldFile(event.file)
}

override fun fileCreated(event: VirtualFileEvent) {
checkForSkaffoldFile(event.file)
}

private fun checkForSkaffoldFile(file: VirtualFile) {
DumbService.getInstance(project).runWhenSmart {
if (file.fileType is YAMLFileType &&
SkaffoldFileService.instance.isSkaffoldFile(
file
) && !hasExistingSkaffoldConfigurations()
) {
// content changed to be a valid Skaffold file,
// and no Skaffold configurations exist, prompt
showPromptForSkaffoldConfigurations(project, file)
}
}
}
})
}

/**
* Checks if the project has existing Skaffold run configurations. Project is current, one
* per created component, passed a parameter.
*/
private fun hasExistingSkaffoldConfigurations() =
getRunManager(project)
.allConfigurationsList.filter { it is AbstractSkaffoldRunConfiguration }.isNotEmpty()

/**
* Prepares an IDE notification if no Skaffold configurations exist, adding actions to add
* both dev/deploy configuration, or only one of them.
Expand Down Expand Up @@ -169,4 +206,16 @@ class SkaffoldConfigurationDetector(val project: Project) : ProjectComponent {
@VisibleForTesting
fun getRunManager(project: Project): RunManager =
RunManager.getInstance(project)

/**
* Adds [VirtualFileListener] to [VirtualFileManager] to track project's file system changes
* and detect new file and content changes.
*/
@VisibleForTesting
fun addVirtualFileListener(listener: VirtualFileListener) {
VirtualFileManager.getInstance().addVirtualFileListener(
listener,
project /* project used as a disposable to remove listener when project closes */
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import com.intellij.execution.RunnerAndConfigurationSettings
import com.intellij.execution.configurations.ConfigurationFactory
import com.intellij.mock.MockVirtualFile
import com.intellij.notification.Notification
import com.intellij.openapi.vfs.VirtualFileEvent
import com.intellij.openapi.vfs.VirtualFileListener
import io.mockk.CapturingSlot
import io.mockk.every
import io.mockk.impl.annotations.MockK
Expand Down Expand Up @@ -60,6 +62,8 @@ class SkaffoldConfigurationDetectorTest {
@MockK
private lateinit var mockNotification: Notification

private val fileListenerCapture: CapturingSlot<VirtualFileListener> = slot()

@Before
fun setUp() {
skaffoldConfigurationDetector = spyk(
Expand All @@ -74,6 +78,10 @@ class SkaffoldConfigurationDetectorTest {
)
} answers { mockNotification }

every {
skaffoldConfigurationDetector.addVirtualFileListener(capture(fileListenerCapture))
} returns Unit

every { skaffoldConfigurationDetector.getRunManager(any()) } answers { mockRunManager }
every {
skaffoldConfigurationDetector.findConfigurationFactoryById(any())
Expand Down Expand Up @@ -114,6 +122,62 @@ class SkaffoldConfigurationDetectorTest {
verify(exactly = 0) { skaffoldConfigurationDetector.createNotification(any(), any()) }
}

@Test
@UiTest
fun `non-Skaffold yaml file modified in the project doesnt ask to add configs`() {
val nonSkaffoldFile = MockVirtualFile.file("pod.yaml")
every { mockSkaffoldFileService.isSkaffoldFile(nonSkaffoldFile) } returns false
every { mockSkaffoldFileService.findSkaffoldFiles(any()) } answers { listOf() }
val virtualFileEvent = VirtualFileEvent(null, nonSkaffoldFile, nonSkaffoldFile.name, null)

skaffoldConfigurationDetector.projectOpened()
fileListenerCapture.captured.contentsChanged(virtualFileEvent)

verify(exactly = 0) { skaffoldConfigurationDetector.createNotification(any(), any()) }
}

@Test
@UiTest
fun `Skaffold yaml file modified in the project prompts to add run configs`() {
val skaffoldFile = MockVirtualFile.file("skaffold.yaml")
every { mockSkaffoldFileService.isSkaffoldFile(skaffoldFile) } returns true
every { mockSkaffoldFileService.findSkaffoldFiles(any()) } answers { listOf() }
val virtualFileEvent = VirtualFileEvent(null, skaffoldFile, skaffoldFile.name, null)

skaffoldConfigurationDetector.projectOpened()
fileListenerCapture.captured.contentsChanged(virtualFileEvent)

verify(exactly = 1) { skaffoldConfigurationDetector.createNotification(any(), any()) }
}

@Test
@UiTest
fun `non-Skaffold yaml file copied or created in the project doesnt ask to add configs`() {
val nonSkaffoldFile = MockVirtualFile.file("pod.yaml")
every { mockSkaffoldFileService.isSkaffoldFile(nonSkaffoldFile) } returns false
every { mockSkaffoldFileService.findSkaffoldFiles(any()) } answers { listOf() }
val virtualFileEvent = VirtualFileEvent(null, nonSkaffoldFile, nonSkaffoldFile.name, null)

skaffoldConfigurationDetector.projectOpened()
fileListenerCapture.captured.fileCreated(virtualFileEvent)

verify(exactly = 0) { skaffoldConfigurationDetector.createNotification(any(), any()) }
}

@Test
@UiTest
fun `Skaffold yaml file copied or created in the project prompts to add run configs`() {
val skaffoldFile = MockVirtualFile.file("skaffold.yaml")
every { mockSkaffoldFileService.isSkaffoldFile(skaffoldFile) } returns true
every { mockSkaffoldFileService.findSkaffoldFiles(any()) } answers { listOf() }
val virtualFileEvent = VirtualFileEvent(null, skaffoldFile, skaffoldFile.name, null)

skaffoldConfigurationDetector.projectOpened()
fileListenerCapture.captured.fileCreated(virtualFileEvent)

verify(exactly = 1) { skaffoldConfigurationDetector.createNotification(any(), any()) }
}

@Test
fun `add dev config action creates and adds valid skaffold dev config`() {
val skaffoldFile = MockVirtualFile.file("skaffold.yaml")
Expand Down

0 comments on commit d99cc82

Please sign in to comment.