Skip to content

Commit

Permalink
fix: dont trash the document when removing clutter (#818)
Browse files Browse the repository at this point in the history
Signed-off-by: Andre Dietisheim <[email protected]>
  • Loading branch information
adietish committed Jan 28, 2025
1 parent c089bae commit 92f4e7e
Show file tree
Hide file tree
Showing 7 changed files with 314 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
package com.redhat.devtools.intellij.kubernetes.editor

import com.intellij.json.JsonFileType
import com.intellij.openapi.editor.Document
import com.intellij.openapi.fileTypes.FileType
import com.redhat.devtools.intellij.kubernetes.model.util.ResourceException
import com.redhat.devtools.intellij.kubernetes.model.util.createResource
Expand All @@ -26,10 +25,20 @@ object EditorResourceSerialization {
private const val RESOURCE_SEPARATOR_JSON = ",\n"

/**
* Returns a [HasMetadata] for a given [Document] instance.
* Returns a list of [HasMetadata] for a given yaml or json string.
* Several resources are only supported for yaml file type. Trying to deserialize ex. json that would result
* in several resources throws a [ResourceException].
*
* @param jsonYaml serialized resources
* @return [HasMetadata] for the given editor and clients
* @param jsonYaml string representing kubernetes resources
* @param fileType yaml- or json file type (no other types are supported)
* @param currentNamespace the namespace to set to the resulting resources if they have none
* @return list of [HasMetadata] for the given yaml or json string
* @throws ResourceException if several resources are to be deserialized to a non-yaml filetype
*
* @see isSupported
* @see RESOURCE_SEPARATOR_YAML
* @see YAMLFileType.YML
* @see JsonFileType.INSTANCE
*/
fun deserialize(jsonYaml: String?, fileType: FileType?, currentNamespace: String?): List<HasMetadata> {
return if (jsonYaml == null
Expand Down Expand Up @@ -64,25 +73,27 @@ object EditorResourceSerialization {
return resource
}

fun serialize(resources: Collection<HasMetadata>, fileType: FileType?): String? {
fun serialize(resources: List<HasMetadata>, fileType: FileType?): String? {
if (fileType == null) {
return null
}
if (resources.size >=2 && fileType != YAMLFileType.YML) {
if (resources.size >= 2
&& fileType != YAMLFileType.YML) {
throw UnsupportedOperationException(
"${fileType.name} is not supported for multi-resource documents. Only ${YAMLFileType.YML.name} is.")
}
return resources
.mapNotNull { resource -> serialize(resource, fileType) }
.joinToString(RESOURCE_SEPARATOR_YAML)
.joinToString("\n")

}

private fun serialize(resource: HasMetadata, fileType: FileType): String? {
return when(fileType) {
YAMLFileType.YML ->
Serialization.asYaml(resource).trim()
JsonFileType.INSTANCE ->
Serialization.asYaml(resource).trim()
Serialization.asJson(resource).trim()
else -> null
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ open class ResourceEditor(
// for mocking purposes
private val createResources: (string: String?, fileType: FileType?, currentNamespace: String?) -> List<HasMetadata> =
EditorResourceSerialization::deserialize,
private val serialize: (resources: Collection<HasMetadata>, fileType: FileType?) -> String? =
private val serialize: (resources: List<HasMetadata>, fileType: FileType?) -> String? =
EditorResourceSerialization::serialize,
// for mocking purposes
private val createResourceFileForVirtual: (file: VirtualFile?) -> ResourceFile? =
Expand Down Expand Up @@ -165,7 +165,7 @@ open class ResourceEditor(
}
}

private fun replaceDocument(resources: Collection<HasMetadata>): Boolean {
private fun replaceDocument(resources: List<HasMetadata>): Boolean {
val manager = getPsiDocumentManager.invoke(project)
val document = getDocument.invoke(editor) ?: return false
val jsonYaml = serialize.invoke(resources, getFileType(document, manager)) ?: return false
Expand Down Expand Up @@ -269,17 +269,20 @@ open class ResourceEditor(
}

fun removeClutter() {
val resources = editorResources.getAllResources().map { resource ->
val resources = createResources(
getDocument(editor),
editor.file.fileType) // don't insert namespace if not present (no namespace param)
val cleaned = resources.map { resource ->
MetadataClutter.remove(resource.metadata)
resource
}
runInUI {
replaceDocument(resources)
replaceDocument(cleaned)
notifications.hideAll()
}
}

private fun createResources(document: Document?, fileType: FileType?, namespace: String?): List<HasMetadata> {
private fun createResources(document: Document?, fileType: FileType?, namespace: String? = null): List<HasMetadata> {
return createResources.invoke(
document?.text,
fileType,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,272 @@
/*******************************************************************************
* Copyright (c) 2025 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution,
* and is available at http://www.eclipse.org/legal/epl-v20.html
*
* Contributors:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package com.redhat.devtools.intellij.kubernetes.editor

import com.intellij.json.JsonFileType
import com.intellij.openapi.fileTypes.PlainTextFileType
import com.redhat.devtools.intellij.kubernetes.model.mocks.ClientMocks.resource
import com.redhat.devtools.intellij.kubernetes.model.util.ResourceException
import io.fabric8.kubernetes.api.model.Pod
import io.fabric8.kubernetes.client.utils.Serialization
import org.assertj.core.api.Assertions.assertThat
import org.assertj.core.api.Assertions.assertThatThrownBy
import org.jetbrains.yaml.YAMLFileType
import org.junit.Test

class EditorResourceSerializationTest {

@Test
fun `#deserialize returns empty list if it is given null yaml`() {
// given
// when
val deserialized = EditorResourceSerialization.deserialize(null, YAMLFileType.YML, "dagobah")
// then
assertThat(deserialized)
.isEmpty()
}

@Test
fun `#deserialize returns a list of resources if given multi-resource YAML`() {
// given
val yaml = """
apiVersion: v1
kind: Pod
metadata:
name: yoda
---
apiVersion: v1
kind: Service
metadata:
name: luke
""".trimIndent()
// when
val deserialized = EditorResourceSerialization.deserialize(yaml, YAMLFileType.YML, "dagobah")
// then
assertThat(deserialized)
.hasSize(2)
.extracting("kind")
.containsExactly("Pod", "Service")
}

@Test
fun `#deserialize throws if given multiple json resources`() {
// given
val json = """
{"apiVersion": "v1", "kind": "Pod"}
---
{"apiVersion": "v1", "kind": "Service"}
""".trimIndent()

assertThatThrownBy {
// when
EditorResourceSerialization.deserialize(json, JsonFileType.INSTANCE, null)
// then
}.isInstanceOf(ResourceException::class.java)
}

@Test
fun `#deserialize returns a list with a single resource if given valid JSON with a single resource`() {
// given
val json = """
{
"apiVersion": "v1",
"kind": "Pod",
"metadata": {
"name": "obiwan"
}
}
""".trimIndent()
// when
val deserialized = EditorResourceSerialization.deserialize(json, JsonFileType.INSTANCE, null)
// then
assertThat(deserialized)
.singleElement()
.extracting("kind")
.isEqualTo("Pod")
}

@Test
fun `#deserialize sets the current namespace to the resulting resource if it has no namespace`() {
// given
val yaml = """
apiVersion: v1
kind: Pod
metadata:
name: has-no-namespace
""".trimIndent()
// when
val deserialized = EditorResourceSerialization.deserialize(yaml, YAMLFileType.YML, "namespace-that-should-be-set")
// then
assertThat(deserialized)
.first()
.extracting { it.metadata.namespace }
.isEqualTo("namespace-that-should-be-set")
}

@Test
fun `#deserialize sets the current namespace only to the resources that have no namespace`() {
// given
val yaml = """
apiVersion: v1
kind: Pod
metadata:
name: has-no-namespace
---
apiVersion: v1
kind: Pod
metadata:
name: has-a-namespace
namespace: alderaan
""".trimIndent()
// when
val deserialized = EditorResourceSerialization.deserialize(
yaml, YAMLFileType.YML, "namespace-that-should-be-set"
)
// then
assertThat(deserialized)
.satisfiesExactly(
{ assertThat(it.metadata.namespace).isEqualTo("namespace-that-should-be-set") }, // namespace set
{ assertThat(it.metadata.namespace).isEqualTo("alderaan") } // no namespace set
)
}

@Test
fun `#deserialize does not change namespace in the resulting resource if it has a namespace`() {
// given
val yaml = """
apiVersion: v1
kind: Pod
metadata:
name: yoda
namespace: has-a-namespace
""".trimIndent()
// when
val deserialized = EditorResourceSerialization.deserialize(yaml, YAMLFileType.YML, "should-not-override-existing")
// then
assertThat(deserialized)
.first()
.extracting { it.metadata.namespace }
.isEqualTo("has-a-namespace")
}

@Test
fun `#deserialize does not change namespace in the resulting resource if it has a namespace and no given namespace`() {
// given
val yaml = """
apiVersion: v1
kind: Pod
metadata:
name: yoda
namespace: has-a-namespace
""".trimIndent()
// when
val deserialized = EditorResourceSerialization.deserialize(yaml, YAMLFileType.YML, null) // no namespace provided, keep whatever exists
// then
assertThat(deserialized)
.first()
.extracting { it.metadata.namespace }
.isEqualTo("has-a-namespace")
}

@Test
fun `#deserialize throws if given invalid yaml`() {
// given
val invalidYaml = """
apiVersion: v1
kind: Pod
metadata: invalid
""".trimIndent()
assertThatThrownBy {
// when
EditorResourceSerialization.deserialize(invalidYaml, YAMLFileType.YML, "dagobah")
// then
}.isInstanceOf(ResourceException::class.java)
}

@Test
fun `#serialize returns null if given null file type`() {
// given
val resource = resource<Pod>("darth vader")
// when
val serialized = EditorResourceSerialization.serialize(listOf(resource), null)
// then
assertThat(serialized)
.isNull()
}

@Test
fun `#serialize throws if given multiple resources and non-YAML file type`() {
// given
val resources = listOf(
resource<Pod>("darth vader"),
resource<Pod>("emperor")
)
assertThatThrownBy {
// when
EditorResourceSerialization.serialize(resources, JsonFileType.INSTANCE)
// then
}.isInstanceOf(UnsupportedOperationException::class.java)
}

@Test
fun `#serialize returns correct YAML if given single resource and YAML file type`() {
// given
val resource = resource<Pod>("obiwan")
val expected = Serialization.asYaml(resource).trim()
// when
val serialized = EditorResourceSerialization.serialize(listOf(resource), YAMLFileType.YML)
//then
assertThat(serialized)
.isEqualTo(expected)
}

@Test
fun `#serialize returns multiple YAML resources joined with newline if given 2 resources and YAML file type`() {
// given
val resources = listOf(
resource<Pod>("leia"),
resource<Pod>("luke")
)
val expected = resources
.joinToString("\n") {
Serialization.asYaml(it).trim()
}
// when
val serialized = EditorResourceSerialization.serialize(resources, YAMLFileType.YML)
// then
assertThat(serialized)
.isEqualTo(expected)
}

@Test
fun `#serialize returns JSON if given JSON file type`() {
// given
val resource = resource<Pod>("obiwan")
val expected = Serialization.asJson(resource).trim()
// when
val serialized = EditorResourceSerialization.serialize(listOf(resource), JsonFileType.INSTANCE)
// then
assertThat(serialized)
.isEqualTo(expected)
}

@Test
fun `#serialize returns null if given unsupported file type`() {
// given
val resource = resource<Pod>("leia")
// when
val serialized = EditorResourceSerialization.serialize(listOf(resource), PlainTextFileType.INSTANCE)
// then
assertThat(serialized)
.isEqualTo("")
}

}
Loading

0 comments on commit 92f4e7e

Please sign in to comment.