Skip to content

Commit

Permalink
feat: impl'd describe for resources (#553)
Browse files Browse the repository at this point in the history
Signed-off-by: Andre Dietisheim <[email protected]>
  • Loading branch information
adietish committed Jul 17, 2024
1 parent 7d4d1b9 commit d0c9a23
Show file tree
Hide file tree
Showing 18 changed files with 1,076 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*******************************************************************************
* Copyright (c) 2020 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.actions

import com.intellij.openapi.actionSystem.AnActionEvent
import com.redhat.devtools.intellij.common.actions.StructureTreeAction
import com.redhat.devtools.intellij.kubernetes.editor.ResourceEditorFactory
import com.redhat.devtools.intellij.kubernetes.editor.describe.DescriptionViewerFactory
import com.redhat.devtools.intellij.kubernetes.model.Notification
import com.redhat.devtools.intellij.kubernetes.model.util.hasDeletionTimestamp
import io.fabric8.kubernetes.api.model.HasMetadata
import io.fabric8.kubernetes.api.model.Pod
import javax.swing.tree.TreePath

class DescribeResourceAction: StructureTreeAction() {

override fun actionPerformed(event: AnActionEvent?, path: TreePath?, selected: Any?) {
// not called
}

override fun actionPerformed(event: AnActionEvent?, path: Array<out TreePath>?, selected: Array<out Any>?) {
val descriptor = selected?.get(0)?.getDescriptor() ?: return
val project = descriptor.project ?: return
val toDescribe: HasMetadata = descriptor.element as? HasMetadata? ?: return
try {
DescriptionViewerFactory.instance.openEditor(toDescribe, project)
} catch (e: RuntimeException) {
Notification().error(
"Error opening editor ${toDescribe.metadata.name}",
"Could not open editor for ${toDescribe.kind} '${toDescribe.metadata.name}'."
)
}
}

override fun isVisible(selected: Array<out Any>?): Boolean {
return selected?.size == 1
&& isVisible(selected.firstOrNull())
}

override fun isVisible(selected: Any?): Boolean {
val element = selected?.getElement<HasMetadata>()
return element is Pod
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ abstract class ConsoleTab<T : ConsoleView, W : Any?>(
var i = 0
do {
val container = model.getElementAt(i).container
if (isRunning(getStatus(container, pod.status))) {
if (isRunning(container.getStatus(pod.status))) {
return i
}
} while (++i < model.size)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,8 @@ open class ResourceEditorTabTitleProvider(

private fun getTitleFor(info: KubernetesResourceInfo): String {
val name = info.name ?: TITLE_UNKNOWN_NAME
val namespace = info.namespace
return if (namespace == null) {
name
} else {
"$name@$namespace"
}
val namespace = info.namespace ?: return name
return "$name@$namespace"
}

/* for testing purposes */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*******************************************************************************
* Copyright (c) 2024 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.describe

abstract class DescriptionDocument : Paragraph("Document") {

abstract fun toText(): String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*******************************************************************************
* Copyright (c) 2024 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.describe

import java.time.DateTimeException
import java.time.Duration
import java.time.LocalDateTime
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter

object DescriptionConstants {
const val NONE = "<None>"
}

fun Paragraph.addValueOrNone(label: String, paragraphs: List<Paragraph>?): Paragraph {
if (paragraphs.isNullOrEmpty()) {
add(Paragraph(label, DescriptionConstants.NONE))
} else {
add(Paragraph(label, paragraphs))
}
return this
}

fun toRFC1123Date(dateTime: String?): String? {
if (dateTime == null) {
return null
}
return try {
val parsed = LocalDateTime.parse(dateTime, DateTimeFormatter.ISO_ZONED_DATE_TIME)
val zoned = parsed.atOffset(ZonedDateTime.now().offset)
DateTimeFormatter.RFC_1123_DATE_TIME.format(zoned)
} catch (e: DateTimeException) {
"Unrecognized Date: $dateTime"
}
}

/**
* Returns a human-readable form of the given date/time since the given date/time.
* Returns `null` if the given dateTime is not understood.
* The logic is copied from k8s.io/apimachinery/util/duration/ duration/HumanDuration.
*
* @see [k8s.io/apimachinery/util/duration/duration/HumanDuration](https://github.com/kubernetes/apimachinery/blob/d7e1c5311169d5ece2db0ae0118066859aa6f7d8/pkg/util/duration/duration.go#L48)
* @see
*/
fun toHumanReadableDurationSince(dateTime: String?, since: LocalDateTime): String? {
if (dateTime == null) {
return null
}
return try {
val parsed = LocalDateTime.parse(dateTime, DateTimeFormatter.ISO_ZONED_DATE_TIME)
val difference = if (since.isBefore(parsed)) {
Duration.between(since, parsed)
} else {
Duration.between(parsed, since)
}
val seconds = difference.toSeconds()
return if (seconds < 60 * 2) {
// < 2 minutes
"${seconds}s"
} else if (seconds < 60 * 10) {
// < 10 minutes
"${difference.toMinutesPart()}m${difference.toSecondsPart()}s"
} else if (seconds < 60 * 60 * 3) {
// < 3 hours
"${difference.toMinutes()}m"
} else if (seconds < 60 * 60 * 8) {
// < 8 hours
"${difference.toHoursPart()}h"
} else if (seconds < 60 * 60 * 48) {
// < 48 hours
"${difference.toHours()}h${difference.toMinutesPart()}m"
} else if (seconds < 60 * 60 * 24 * 8) {
// < 192 hours
if (difference.toHoursPart() == 0) {
"${difference.toDaysPart()}d"
} else {
"${difference.toDaysPart()}d${difference.toHoursPart()}h"
}
} else if (seconds < 60 * 60 * 24 * 365 * 2) {
// < 2 years
"${difference.toDaysPart()}d"
} else if (seconds < 60 * 60 * 24 * 365 * 8) {
// < 8 years
val years = difference.toDaysPart() / 365
"${years}y${difference.toDaysPart() % 365}d"
} else {
"${difference.toDaysPart() / 365}y"
}
} catch (e: DateTimeException) {
null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*******************************************************************************
* Copyright (c) 2024 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.describe

import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.editor.Document
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.editor.EditorFactory
import com.intellij.openapi.editor.EditorKind
import com.intellij.openapi.fileEditor.FileDocumentManager
import com.intellij.openapi.fileEditor.FileEditorManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.testFramework.LightVirtualFile
import com.redhat.devtools.intellij.kubernetes.telemetry.TelemetryService
import com.redhat.devtools.intellij.telemetry.core.service.TelemetryMessageBuilder
import io.fabric8.kubernetes.api.model.HasMetadata
import io.fabric8.kubernetes.api.model.Pod
import org.jetbrains.yaml.YAMLFileType


open class DescriptionViewerFactory protected constructor() {

companion object {
val instance = DescriptionViewerFactory()
private val KEY_RESOURCE = Key<HasMetadata>(HasMetadata::class.java.name)
}

fun openEditor(resource: HasMetadata, project: Project) {
val description = when(resource) {
is Pod -> describe(resource)
else -> null
} ?: return

openEditor(createYamlFile(description), project)
}

private fun describe(pod: Pod): String {
val document = YAMLDescriptionDocument()
PodDescription(pod).describeTo(document)
return document.toText()
}

private fun createYamlFile(content: String): VirtualFile {
val filename = System.currentTimeMillis().toString() + ".tmp"
val file = LightVirtualFile(filename, YAMLFileType.YML, content)
file.isWritable = false
return file
}

private fun openEditor(file: VirtualFile, project: Project) {
FileEditorManager.getInstance(project).openFile(file, true)
}

private fun createEditor(file: VirtualFile, project: Project): Editor? {
val document = ReadAction.compute<Document?, RuntimeException> {
FileDocumentManager.getInstance().getDocument(file)
} ?: return null
val editor = EditorFactory.getInstance().createEditor(
document,
project,
file,
true,
EditorKind.MAIN_EDITOR
)
val settings = editor.settings
settings.isLineNumbersShown = false
settings.isIndentGuidesShown = false
return editor
}

/** for testing purposes */
protected open fun runAsync(runnable: () -> Unit) {
ApplicationManager.getApplication().executeOnPooledThread(runnable)
}

/** for testing purposes */
protected open fun runInUI(runnable: () -> Unit) {
if (ApplicationManager.getApplication().isDispatchThread) {
runnable.invoke()
} else {
ApplicationManager.getApplication().invokeLater(runnable)
}
}

/* for testing purposes */
protected open fun getTelemetryMessageBuilder(): TelemetryMessageBuilder {
return TelemetryService.instance;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*******************************************************************************
* Copyright (c) 2024 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.describe

open class Paragraph(val title: String, val value: Any? = null) {

constructor(title: String, vararg children: Paragraph) :
this(title, children.toList())

constructor(title: String, children: List<Paragraph>) :
this(title, null) {
addAll(children)
}

val paragraphs = mutableListOf<Paragraph>()

fun add(title: String, value: String?): Paragraph {
if (value.isNullOrBlank()) {
return this
}
return add(Paragraph(title, value))
}

fun add(label: String, valueProvider: () -> String?): Paragraph {
return add(label, valueProvider.invoke())
}

fun add(label: String, value: Int?): Paragraph {
if (value == null) {
return this
}
return add(Paragraph(label, value))
}

fun add(label: String, value: Boolean?): Paragraph {
if (value == null) {
return this
}
return add(Paragraph(label, value))
}

fun add(title: String, paragraph: Paragraph): Paragraph {
return add(Paragraph(title, paragraph))
}

fun add(paragraph: Paragraph): Paragraph {
paragraphs.add(paragraph)
return this
}

fun addAll(title: String, paragraphs: List<Paragraph>): Paragraph {
add(Paragraph(title, paragraphs))
return this;
}

fun addAll(paragraphs: List<Paragraph>): Paragraph {
this.paragraphs.addAll(paragraphs)
return this
}

}
Loading

0 comments on commit d0c9a23

Please sign in to comment.