Skip to content

Commit d5dc093

Browse files
committed
fix: dont trash the document when removing clutter (#818)
Signed-off-by: Andre Dietisheim <[email protected]>
1 parent c089bae commit d5dc093

File tree

15 files changed

+260
-27
lines changed

15 files changed

+260
-27
lines changed

src/main/.DS_Store

6 KB
Binary file not shown.

src/main/kotlin/.DS_Store

6 KB
Binary file not shown.

src/main/kotlin/com/.DS_Store

6 KB
Binary file not shown.

src/main/kotlin/com/redhat/.DS_Store

6 KB
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/editor/EditorResourceSerialization.kt

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@
1111
package com.redhat.devtools.intellij.kubernetes.editor
1212

1313
import com.intellij.json.JsonFileType
14-
import com.intellij.openapi.editor.Document
1514
import com.intellij.openapi.fileTypes.FileType
1615
import com.redhat.devtools.intellij.kubernetes.model.util.ResourceException
1716
import com.redhat.devtools.intellij.kubernetes.model.util.createResource
@@ -26,10 +25,13 @@ object EditorResourceSerialization {
2625
private const val RESOURCE_SEPARATOR_JSON = ",\n"
2726

2827
/**
29-
* Returns a [HasMetadata] for a given [Document] instance.
28+
* Returns a list of [HasMetadata] for a given yaml or json string.
3029
*
31-
* @param jsonYaml serialized resources
32-
* @return [HasMetadata] for the given editor and clients
30+
* @param jsonYaml string representing kubernetes resources
31+
* @param fileType yaml- or json file type (no other types are supported)
32+
* @return list of [HasMetadata] for the given yaml or json string
33+
*
34+
* @see isSupported
3335
*/
3436
fun deserialize(jsonYaml: String?, fileType: FileType?, currentNamespace: String?): List<HasMetadata> {
3537
return if (jsonYaml == null
@@ -64,25 +66,27 @@ object EditorResourceSerialization {
6466
return resource
6567
}
6668

67-
fun serialize(resources: Collection<HasMetadata>, fileType: FileType?): String? {
69+
fun serialize(resources: List<HasMetadata>, fileType: FileType?): String? {
6870
if (fileType == null) {
6971
return null
7072
}
71-
if (resources.size >=2 && fileType != YAMLFileType.YML) {
73+
if (resources.size >= 2
74+
&& fileType != YAMLFileType.YML) {
7275
throw UnsupportedOperationException(
7376
"${fileType.name} is not supported for multi-resource documents. Only ${YAMLFileType.YML.name} is.")
7477
}
7578
return resources
7679
.mapNotNull { resource -> serialize(resource, fileType) }
77-
.joinToString(RESOURCE_SEPARATOR_YAML)
80+
.joinToString("\n")
81+
7882
}
7983

8084
private fun serialize(resource: HasMetadata, fileType: FileType): String? {
8185
return when(fileType) {
8286
YAMLFileType.YML ->
8387
Serialization.asYaml(resource).trim()
8488
JsonFileType.INSTANCE ->
85-
Serialization.asYaml(resource).trim()
89+
Serialization.asJson(resource).trim()
8690
else -> null
8791
}
8892
}

src/main/kotlin/com/redhat/devtools/intellij/kubernetes/editor/ResourceEditor.kt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ open class ResourceEditor(
5151
// for mocking purposes
5252
private val createResources: (string: String?, fileType: FileType?, currentNamespace: String?) -> List<HasMetadata> =
5353
EditorResourceSerialization::deserialize,
54-
private val serialize: (resources: Collection<HasMetadata>, fileType: FileType?) -> String? =
54+
private val serialize: (resources: List<HasMetadata>, fileType: FileType?) -> String? =
5555
EditorResourceSerialization::serialize,
5656
// for mocking purposes
5757
private val createResourceFileForVirtual: (file: VirtualFile?) -> ResourceFile? =
@@ -165,7 +165,7 @@ open class ResourceEditor(
165165
}
166166
}
167167

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

271271
fun removeClutter() {
272-
val resources = editorResources.getAllResources().map { resource ->
272+
val resources = createResources(
273+
getDocument(editor),
274+
editor.file.fileType) // don't insert namespace if not present (no namespace param)
275+
val cleaned = resources.map { resource ->
273276
MetadataClutter.remove(resource.metadata)
274277
resource
275278
}
276279
runInUI {
277-
replaceDocument(resources)
280+
replaceDocument(cleaned)
278281
notifications.hideAll()
279282
}
280283
}
281284

282-
private fun createResources(document: Document?, fileType: FileType?, namespace: String?): List<HasMetadata> {
285+
private fun createResources(document: Document?, fileType: FileType?, namespace: String? = null): List<HasMetadata> {
283286
return createResources.invoke(
284287
document?.text,
285288
fileType,
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2025 Red Hat, Inc.
3+
* Distributed under license by Red Hat, Inc. All rights reserved.
4+
* This program is made available under the terms of the
5+
* Eclipse Public License v2.0 which accompanies this distribution,
6+
* and is available at http://www.eclipse.org/legal/epl-v20.html
7+
*
8+
* Contributors:
9+
* Red Hat, Inc. - initial API and implementation
10+
******************************************************************************/
11+
package com.redhat.devtools.intellij.kubernetes.editor
12+
13+
import com.intellij.json.JsonFileType
14+
import com.intellij.openapi.fileTypes.PlainTextFileType
15+
import com.redhat.devtools.intellij.kubernetes.model.mocks.ClientMocks.resource
16+
import com.redhat.devtools.intellij.kubernetes.model.util.ResourceException
17+
import io.fabric8.kubernetes.api.model.Pod
18+
import io.fabric8.kubernetes.client.utils.Serialization
19+
import org.assertj.core.api.Assertions.assertThat
20+
import org.assertj.core.api.Assertions.assertThatThrownBy
21+
import org.jetbrains.yaml.YAMLFileType
22+
import org.junit.Test
23+
24+
class EditorResourceSerializationTest {
25+
26+
@Test
27+
fun `#deserialize return empty list if it is given null yaml`() {
28+
// given
29+
// when
30+
val result = EditorResourceSerialization.deserialize(null, YAMLFileType.YML, "dagobah")
31+
// then
32+
assertThat(result).isEmpty()
33+
}
34+
35+
@Test
36+
fun `#deserialize returns a list of resources if given multi-resource YAML`() {
37+
// given
38+
val yaml = """
39+
apiVersion: v1
40+
kind: Pod
41+
metadata:
42+
name: yoda
43+
---
44+
apiVersion: v1
45+
kind: Service
46+
metadata:
47+
name: luke
48+
""".trimIndent()
49+
// when
50+
val result = EditorResourceSerialization.deserialize(yaml, YAMLFileType.YML, "dagobah")
51+
// then
52+
assertThat(result)
53+
.hasSize(2)
54+
.extracting("kind")
55+
.containsExactly("Pod", "Service")
56+
}
57+
58+
@Test
59+
fun `#deserialize throws if given multiple json resources`() {
60+
// given
61+
val json = """
62+
{"apiVersion": "v1", "kind": "Pod"}
63+
---
64+
{"apiVersion": "v1", "kind": "Service"}
65+
""".trimIndent()
66+
67+
assertThatThrownBy {
68+
// when
69+
EditorResourceSerialization.deserialize(json, JsonFileType.INSTANCE, "test")
70+
// then
71+
}.isInstanceOf(ResourceException::class.java)
72+
}
73+
74+
@Test
75+
fun `#deserialize returns a list with a single resource if given valid JSON with a single resource`() {
76+
// given
77+
val json = """
78+
{
79+
"apiVersion": "v1",
80+
"kind": "Pod",
81+
"metadata": {
82+
"name": "obiwan"
83+
}
84+
}
85+
""".trimIndent()
86+
// when
87+
val result = EditorResourceSerialization.deserialize(json, JsonFileType.INSTANCE, "test")
88+
// then
89+
assertThat(result)
90+
.singleElement()
91+
.extracting("kind")
92+
.isEqualTo("Pod")
93+
}
94+
95+
@Test
96+
fun `#deserialize sets the current namespace to the resulting resource if it has no namespace`() {
97+
// given
98+
val yaml = """
99+
apiVersion: v1
100+
kind: Pod
101+
metadata:
102+
name: has-no-namespace
103+
""".trimIndent()
104+
// when
105+
val result = EditorResourceSerialization.deserialize(yaml, YAMLFileType.YML, "namespace-that-should-be-set")
106+
// then
107+
assertThat(result)
108+
.first()
109+
.extracting { it.metadata.namespace }
110+
.isEqualTo("namespace-that-should-be-set")
111+
}
112+
113+
@Test
114+
fun `#deserialize does not change namespace in the resulting resource if it has a namespace`() {
115+
// given
116+
val yaml = """
117+
apiVersion: v1
118+
kind: Pod
119+
metadata:
120+
name: yoda
121+
namespace: has-a-namespace
122+
""".trimIndent()
123+
// when
124+
val result = EditorResourceSerialization.deserialize(yaml, YAMLFileType.YML, null) // no namespace provided
125+
// then
126+
assertThat(result)
127+
.first()
128+
.extracting { it.metadata.namespace }
129+
.isEqualTo("has-a-namespace")
130+
}
131+
132+
@Test
133+
fun `#deserialize throws if given invalid yaml`() {
134+
// given
135+
val invalidYaml = """
136+
apiVersion: v1
137+
kind: Pod
138+
metadata: invalid
139+
""".trimIndent()
140+
assertThatThrownBy {
141+
// when
142+
EditorResourceSerialization.deserialize(invalidYaml, YAMLFileType.YML, "dagobah")
143+
// then
144+
}.isInstanceOf(ResourceException::class.java)
145+
}
146+
147+
@Test
148+
fun `#serialize returns null if given null file type`() {
149+
// given
150+
val resource = resource<Pod>("darth vader")
151+
// when
152+
val result = EditorResourceSerialization.serialize(listOf(resource), null)
153+
// then
154+
assertThat(result)
155+
.isNull()
156+
}
157+
158+
@Test
159+
fun `#serialize throws if given multiple resources and non-YAML file type`() {
160+
// given
161+
val resources = listOf(
162+
resource<Pod>("darth vader"),
163+
resource<Pod>("emperor")
164+
)
165+
assertThatThrownBy {
166+
// when
167+
EditorResourceSerialization.serialize(resources, JsonFileType.INSTANCE)
168+
// then
169+
}.isInstanceOf(UnsupportedOperationException::class.java)
170+
}
171+
172+
@Test
173+
fun `#serialize returns correct YAML if given single resource and YAML file type`() {
174+
// given
175+
val resource = resource<Pod>("obiwan")
176+
val expected = Serialization.asYaml(resource).trim()
177+
// when
178+
val result = EditorResourceSerialization.serialize(listOf(resource), YAMLFileType.YML)
179+
//then
180+
assertThat(result)
181+
.isEqualTo(expected)
182+
}
183+
184+
@Test
185+
fun `#serialize returns multiple YAML resources joined with newline if given 2 resources and YAML file type`() {
186+
// given
187+
val resources = listOf(
188+
resource<Pod>("leia"),
189+
resource<Pod>("luke")
190+
)
191+
val expected = resources
192+
.joinToString("\n") {
193+
Serialization.asYaml(it).trim()
194+
}
195+
// when
196+
val result = EditorResourceSerialization.serialize(resources, YAMLFileType.YML)
197+
// then
198+
assertThat(result)
199+
.isEqualTo(expected)
200+
}
201+
202+
@Test
203+
fun `#serialize returns JSON if given JSON file type`() {
204+
// given
205+
val resource = resource<Pod>("obiwan")
206+
val expected = Serialization.asJson(resource).trim()
207+
// when
208+
val result = EditorResourceSerialization.serialize(listOf(resource), JsonFileType.INSTANCE)
209+
// then
210+
assertThat(result)
211+
.isEqualTo(expected)
212+
}
213+
214+
@Test
215+
fun `#serialize returns null if given unsupported file type`() {
216+
// given
217+
val resource = resource<Pod>("leia")
218+
// when
219+
val result = EditorResourceSerialization.serialize(listOf(resource), PlainTextFileType.INSTANCE)
220+
// then
221+
assertThat(result)
222+
.isEqualTo("")
223+
}
224+
225+
}

src/test/kotlin/com/redhat/devtools/intellij/kubernetes/editor/ResourceEditorTest.kt

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,10 +116,10 @@ class ResourceEditorTest {
116116
private val createResources: (string: String?, fileType: FileType?, currentNamespace: String?) -> List<HasMetadata> =
117117
mock<(string: String?, fileType: FileType?, currentNamespace: String?) -> List<HasMetadata>>()
118118
private val editorResources: EditorResources = mock()
119-
private val serialize: (resources: Collection<HasMetadata>, fileType: FileType?) -> String? =
120-
mock<(resources: Collection<HasMetadata>, fileType: FileType?) -> String?>().apply {
119+
private val serialize: (resources: List<HasMetadata>, fileType: FileType?) -> String? =
120+
mock<(resources: List<HasMetadata>, fileType: FileType?) -> String?>().apply {
121121
doAnswer { invocation ->
122-
val resources = invocation.getArgument<Collection<HasMetadata>>(0)
122+
val resources = invocation.getArgument<List<HasMetadata>>(0)
123123
EditorResourceSerialization.serialize(resources, YAMLFileType.YML)
124124
}.whenever(this).invoke(any(), any())
125125
}
@@ -546,6 +546,7 @@ class ResourceEditorTest {
546546
// then
547547
verify(notifications).hideAll()
548548
}
549+
549550
@Test
550551
fun `#isEditing should return true if there is an EditorResource with the given resource`() {
551552
// given
@@ -629,7 +630,7 @@ class ResourceEditorTest {
629630
resourceModel: IResourceModel,
630631
project: Project,
631632
createResources: (string: String?, fileType: FileType?, currentNamespace: String?) -> List<HasMetadata>,
632-
serialize: (resources: Collection<HasMetadata>, fileType: FileType?) -> String?,
633+
serialize: (resources: List<HasMetadata>, fileType: FileType?) -> String?,
633634
resourceFileForVirtual: (file: VirtualFile?) -> ResourceFile?,
634635
notifications: Notifications,
635636
documentProvider: (FileEditor) -> Document?,

src/test/kotlin/com/redhat/devtools/intellij/kubernetes/model/context/KubernetesContextTest.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ import com.redhat.devtools.intellij.kubernetes.model.mocks.ClientMocks.POD1
2525
import com.redhat.devtools.intellij.kubernetes.model.mocks.ClientMocks.POD2
2626
import com.redhat.devtools.intellij.kubernetes.model.mocks.ClientMocks.POD3
2727
import com.redhat.devtools.intellij.kubernetes.model.mocks.ClientMocks.client
28-
import com.redhat.devtools.intellij.kubernetes.model.mocks.ClientMocks.customResource
28+
import com.redhat.devtools.intellij.kubernetes.model.mocks.ClientMocks.genericKubernetesResource
2929
import com.redhat.devtools.intellij.kubernetes.model.mocks.ClientMocks.customResourceDefinition
3030
import com.redhat.devtools.intellij.kubernetes.model.mocks.ClientMocks.namedContext
3131
import com.redhat.devtools.intellij.kubernetes.model.mocks.ClientMocks.resource
@@ -113,11 +113,11 @@ class KubernetesContextTest {
113113
crdKind,
114114
listOf(namespacedDefinition, clusterwideDefinition))
115115

116-
private val namespacedCustomResource1 = customResource(
116+
private val namespacedCustomResource1 = genericKubernetesResource(
117117
"genericCustomResource1",
118118
"namespace1",
119119
namespacedDefinition)
120-
private val namespacedCustomResource2 = customResource(
120+
private val namespacedCustomResource2 = genericKubernetesResource(
121121
"genericCustomResource2",
122122
"namespace1",
123123
namespacedDefinition)

src/test/kotlin/com/redhat/devtools/intellij/kubernetes/model/mocks/ClientMocks.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ object ClientMocks {
338338
}
339339
}
340340

341-
fun customResource(
341+
fun genericKubernetesResource(
342342
name: String,
343343
namespace: String?,
344344
definition: CustomResourceDefinition,

0 commit comments

Comments
 (0)