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 Aug 9, 2024
1 parent c84158e commit 911769e
Show file tree
Hide file tree
Showing 48 changed files with 5,346 additions and 89 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*******************************************************************************
* 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.actions

import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.diagnostic.logger
import com.redhat.devtools.intellij.common.actions.StructureTreeAction
import com.redhat.devtools.intellij.kubernetes.editor.describe.DescriptionViewerFactory
import com.redhat.devtools.intellij.kubernetes.model.Notification
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) {
logger<DescribeResourceAction>().warn("Error opening editor ${toDescribe.metadata.name}", e)
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
@@ -0,0 +1,28 @@
/*******************************************************************************
* 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

import com.intellij.openapi.fileEditor.impl.EditorTabTitleProvider
import com.intellij.openapi.fileEditor.impl.UniqueNameEditorTabTitleProvider
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.redhat.devtools.intellij.kubernetes.editor.describe.DescriptionViewerTabTitleProvider

open class KubernetesEditorsTabTitleProvider(
private val fallback: EditorTabTitleProvider = UniqueNameEditorTabTitleProvider()
) : EditorTabTitleProvider {

override fun getEditorTabTitle(project: Project, file: VirtualFile): String? {
return ResourceEditorTabTitleProvider().getEditorTabTitle(project, file)
?: DescriptionViewerTabTitleProvider().getEditorTabTitle(project, file)
?: fallback.getEditorTabTitle(project, file)
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2021 Red Hat, Inc.
* 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,
Expand All @@ -11,54 +11,45 @@
package com.redhat.devtools.intellij.kubernetes.editor

import com.intellij.openapi.fileEditor.impl.EditorTabTitleProvider
import com.intellij.openapi.fileEditor.impl.UniqueNameEditorTabTitleProvider
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import com.redhat.devtools.intellij.common.validation.KubernetesResourceInfo
import com.redhat.devtools.intellij.kubernetes.editor.util.isKubernetesResource

open class ResourceEditorTabTitleProvider(
private val fallback: EditorTabTitleProvider = UniqueNameEditorTabTitleProvider()
) : EditorTabTitleProvider {

companion object {
const val TITLE_UNKNOWN_CLUSTERRESOURCE = "Unknown Cluster Resource"
const val TITLE_UNKNOWN_NAME = "unknown name"
}

override fun getEditorTabTitle(project: Project, file: VirtualFile): String? {
return if (isTemporary(file)) {
val resourceInfo = getKubernetesResourceInfo(file, project)
if (resourceInfo != null
&& isKubernetesResource(resourceInfo)
) {
getTitleFor(resourceInfo)
} else {
TITLE_UNKNOWN_CLUSTERRESOURCE
}
} else {
fallback.getEditorTabTitle(project, file)
}
}

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"
}
}

/* for testing purposes */
protected open fun getKubernetesResourceInfo(file: VirtualFile, project: Project): KubernetesResourceInfo? {
return com.redhat.devtools.intellij.kubernetes.editor.util.getKubernetesResourceInfo(file, project)
}

/* for testing purposes */
protected open fun isTemporary(file: VirtualFile): Boolean {
return ResourceFile.isTemporary(file)
}
open class ResourceEditorTabTitleProvider: EditorTabTitleProvider {
companion object {
const val TITLE_UNKNOWN_CLUSTERRESOURCE = "Unknown Cluster Resource"
const val TITLE_UNKNOWN_NAME = "unknown name"
}

override fun getEditorTabTitle(project: Project, file: VirtualFile): String? {
if (!isResourceFile(file)) {
return null
}

val resourceInfo = getKubernetesResourceInfo(file, project)
return if (resourceInfo != null
&& isKubernetesResource(resourceInfo)
) {
getTitleFor(resourceInfo)
} else {
TITLE_UNKNOWN_CLUSTERRESOURCE
}
}

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

protected open fun isResourceFile(file: VirtualFile): Boolean {
return ResourceFile.isResourceFile(file)
}

/* for testing purposes */
protected open fun getKubernetesResourceInfo(file: VirtualFile, project: Project): KubernetesResourceInfo? {
return com.redhat.devtools.intellij.kubernetes.editor.util.getKubernetesResourceInfo(file, project)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,10 @@ open class ResourceFile protected constructor(
&& virtualFile.path.startsWith(TEMP_FOLDER.toString())
}

fun isResourceFile(virtualFile: VirtualFile?): Boolean {
return isTemporary(virtualFile)
}

private fun isYamlOrJson(file: VirtualFile): Boolean {
if (true == file.extension?.isBlank()) {
return false
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*******************************************************************************
* 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.redhat.devtools.intellij.kubernetes.editor.describe.paragraphs.Chapter

abstract class Description: Chapter("Document") {

abstract fun toText(): String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*******************************************************************************
* 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.redhat.devtools.intellij.kubernetes.editor.describe.paragraphs.NamedSequence
import com.redhat.devtools.intellij.kubernetes.editor.describe.paragraphs.NamedValue
import com.redhat.devtools.intellij.kubernetes.editor.describe.paragraphs.Paragraph
import java.time.DateTimeException
import java.time.Duration
import java.time.LocalDateTime
import java.time.ZonedDateTime
import java.time.format.DateTimeFormatter

object DescriptionConstants {

object Labels {
const val NAME = "Name"
const val NAMESPACE = "Namespace"
}

object Values {
const val NONE = "<none>"
const val UNSET = "<unset>"
}
}

fun createValueOrSequence(title: String, items: List<String>?): Paragraph? {
return if (items.isNullOrEmpty()) {
null
} else if (items.size == 1) {
NamedValue(title, items.first())
} else {
NamedSequence(title, items)
}
}

fun createValues(map: Map<String, String>?): List<NamedValue> {
if (map.isNullOrEmpty()) {
return emptyList()
}
return map.entries.map { entry -> NamedValue(entry.key, entry.value) }
}

fun toString(items: List<String>?): String? {
return items?.joinToString("\n")
}

fun toRFC1123Date(dateTime: String?): String? {
if (dateTime == null) {
return null
}
val parsed = LocalDateTime.parse(dateTime, DateTimeFormatter.ISO_ZONED_DATE_TIME)
val zoned = parsed.atOffset(ZonedDateTime.now().offset)
return DateTimeFormatter.RFC_1123_DATE_TIME.format(zoned)
}

fun toRFC1123DateOrUnrecognized(dateTime: String?): String? {
return try {
toRFC1123Date(dateTime)
} 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 when {
seconds < 60 * 2 ->
// < 2 minutes
"${seconds}s"

seconds < 60 * 10 ->
// < 10 minutes
"${difference.toMinutesPart()}m${difference.toSecondsPart()}s"

seconds < 60 * 60 * 3 ->
// < 3 hours
"${difference.toMinutes()}m"

seconds < 60 * 60 * 8 ->
// < 8 hours
"${difference.toHoursPart()}h"

seconds < 60 * 60 * 48 ->
// < 48 hours
"${difference.toHours()}h${difference.toMinutesPart()}m"

seconds < 60 * 60 * 24 * 8 -> {
// < 192 hours
if (difference.toHoursPart() == 0) {
"${difference.toDaysPart()}d"
} else {
"${difference.toDaysPart()}d${difference.toHoursPart()}h"
}
}

seconds < 60 * 60 * 24 * 365 * 2 ->
// < 2 years
"${difference.toDaysPart()}d"

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
}
}
Loading

0 comments on commit 911769e

Please sign in to comment.