Skip to content
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Rework the workspace panel
- include template name
- use a table to show the headers as well
- align the values in the table to columns
- the table is now providing a similar L&F as the one in the web app
  • Loading branch information
fioan89 committed Jul 15, 2022
commit d810bdb2bb253760fd6addd6b664aed3f388ef10
171 changes: 154 additions & 17 deletions src/main/kotlin/com/coder/gateway/views/steps/CoderWorkspacesStepView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,32 +7,59 @@ import com.coder.gateway.models.WorkspaceAgentModel
import com.coder.gateway.sdk.Arch
import com.coder.gateway.sdk.CoderRestClientService
import com.coder.gateway.sdk.OS
import com.coder.gateway.sdk.v2.models.ProvisionerJobStatus
import com.coder.gateway.sdk.v2.models.WorkspaceBuildTransition
import com.intellij.ide.IdeBundle
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.wm.impl.welcomeScreen.WelcomeScreenUIManager
import com.intellij.ui.CollectionListModel
import com.intellij.ui.components.JBList
import com.intellij.ui.dsl.builder.BottomGap
import com.intellij.ui.dsl.builder.TopGap
import com.intellij.ui.dsl.builder.panel
import com.intellij.ui.dsl.gridLayout.HorizontalAlign
import com.intellij.ui.dsl.gridLayout.VerticalAlign
import com.intellij.ui.table.TableView
import com.intellij.util.ui.ColumnInfo
import com.intellij.util.ui.JBFont
import com.intellij.util.ui.JBUI
import com.intellij.util.ui.ListTableModel
import com.intellij.util.ui.table.IconTableCellRenderer
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.awt.Color
import java.awt.Component
import java.awt.Dimension
import javax.swing.Icon
import javax.swing.JTable
import javax.swing.ListSelectionModel
import javax.swing.table.DefaultTableCellRenderer
import javax.swing.table.TableCellRenderer


class CoderWorkspacesStepView : CoderWorkspacesWizardStep, Disposable {
private val cs = CoroutineScope(Dispatchers.Main)

private val coderClient: CoderRestClientService = ApplicationManager.getApplication().getService(CoderRestClientService::class.java)
private var workspaces = CollectionListModel<WorkspaceAgentModel>()
private var workspacesView = JBList(workspaces)


private var listTableModelOfWorkspaces = ListTableModel<WorkspaceAgentModel>(WorkspaceIconColumnInfo(""), WorkspaceNameColumnInfo("Name"), WorkspaceTemplateNameColumnInfo("Template"), WorkspaceStatusColumnInfo("Status"))
private var tableOfWorkspaces = TableView(listTableModelOfWorkspaces).apply {
rowSelectionAllowed = true
columnSelectionAllowed = false
tableHeader.reorderingAllowed = false
showVerticalLines = false
intercellSpacing = Dimension(0, 0)
columnModel.getColumn(0).apply {
maxWidth = JBUI.scale(52)
minWidth = JBUI.scale(52)
}

setSelectionMode(ListSelectionModel.SINGLE_SELECTION)
}

private lateinit var wizard: CoderWorkspacesWizardModel

Expand All @@ -45,12 +72,7 @@ class CoderWorkspacesStepView : CoderWorkspacesWizardStep, Disposable {
}
}.bottomGap(BottomGap.MEDIUM)
row {
scrollCell(workspacesView).resizableColumn().horizontalAlign(HorizontalAlign.FILL).verticalAlign(VerticalAlign.FILL).applyToComponent {
border = JBUI.Borders.customLine(
WelcomeScreenUIManager.getSeparatorColor(),
1, 1, 1, 1
)
}
scrollCell(tableOfWorkspaces).resizableColumn().horizontalAlign(HorizontalAlign.FILL).verticalAlign(VerticalAlign.FILL)
cell()
}.topGap(TopGap.NONE).resizableRow()

Expand All @@ -62,9 +84,6 @@ class CoderWorkspacesStepView : CoderWorkspacesWizardStep, Disposable {

override fun onInit(wizardModel: CoderWorkspacesWizardModel) {
wizard = wizardModel
workspaces.removeAll()
workspacesView.cellRenderer = WorkspaceCellRenderer()

cs.launch {
val workspaceList = withContext(Dispatchers.IO) {
try {
Expand All @@ -76,6 +95,7 @@ class CoderWorkspacesStepView : CoderWorkspacesWizardStep, Disposable {
val workspaceName = if (shouldContainAgentName) "${workspace.name}.${agent.name}" else workspace.name
WorkspaceAgentModel(
workspaceName,
workspace.templateName,
workspace.latestBuild.job.status,
workspace.latestBuild.workspaceTransition,
OS.from(agent.operatingSystem),
Expand All @@ -89,14 +109,14 @@ class CoderWorkspacesStepView : CoderWorkspacesWizardStep, Disposable {
emptyList()
}
}
workspaceList.forEach {
workspaces.add(it)
}

// if we just run the update on the main dispatcher, the code will block because it cant get some AWT locks
ApplicationManager.getApplication().invokeLater { listTableModelOfWorkspaces.updateItems(workspaceList) }
}
}

override fun onNext(wizardModel: CoderWorkspacesWizardModel): Boolean {
val workspace = workspacesView.selectedValue
val workspace = tableOfWorkspaces.selectedObject
if (workspace != null) {
wizardModel.selectedWorkspace = workspace
return true
Expand All @@ -108,6 +128,123 @@ class CoderWorkspacesStepView : CoderWorkspacesWizardStep, Disposable {
cs.cancel()
}

private class WorkspaceIconColumnInfo(columnName: String) : ColumnInfo<WorkspaceAgentModel, String>(columnName) {
override fun valueOf(workspace: WorkspaceAgentModel?): String? {
return workspace?.agentOS?.name
}

override fun getRenderer(item: WorkspaceAgentModel?): TableCellRenderer {
return object : IconTableCellRenderer<String>() {
override fun getText(): String {
return ""
}

override fun getIcon(value: String, table: JTable?, row: Int): Icon {
return when (OS.from(value)) {
OS.LINUX -> CoderIcons.LINUX
OS.WINDOWS -> CoderIcons.WINDOWS
OS.MAC -> CoderIcons.MACOS
else -> CoderIcons.UNKNOWN
}
}

override fun isCenterAlignment() = true
}
}
}

private class WorkspaceNameColumnInfo(columnName: String) : ColumnInfo<WorkspaceAgentModel, String>(columnName) {
override fun valueOf(workspace: WorkspaceAgentModel?): String? {
return workspace?.name
}

override fun getRenderer(item: WorkspaceAgentModel?): TableCellRenderer {
return object : DefaultTableCellRenderer() {
override fun getTableCellRendererComponent(table: JTable, value: Any, isSelected: Boolean, hasFocus: Boolean, row: Int, column: Int): Component {
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column)
if (value is String) {
text = value
}
font = JBFont.h3()
return this
}
}
}
}

private class WorkspaceTemplateNameColumnInfo(columnName: String) : ColumnInfo<WorkspaceAgentModel, String>(columnName) {
override fun valueOf(workspace: WorkspaceAgentModel?): String? {
return workspace?.templateName
}

override fun getRenderer(item: WorkspaceAgentModel?): TableCellRenderer {
return object : DefaultTableCellRenderer() {
override fun getTableCellRendererComponent(table: JTable, value: Any, isSelected: Boolean, hasFocus: Boolean, row: Int, column: Int): Component {
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column)
if (value is String) {
text = value
}
font = JBFont.h3()
return this
}
}
}
}

private class WorkspaceStatusColumnInfo(columnName: String) : ColumnInfo<WorkspaceAgentModel, String>(columnName) {
override fun valueOf(workspace: WorkspaceAgentModel?): String? {
return workspace?.statusLabel()
}

override fun getRenderer(item: WorkspaceAgentModel?): TableCellRenderer {
return object : DefaultTableCellRenderer() {
override fun getTableCellRendererComponent(table: JTable, value: Any, isSelected: Boolean, hasFocus: Boolean, row: Int, column: Int): Component {
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column)
if (value is String) {
text = value
}
font = JBFont.h3()
foreground = (table.model as ListTableModel<WorkspaceAgentModel>).getRowValue(row).statusColor()
return this
}
}
}

private fun WorkspaceAgentModel.statusLabel() = when (this.jobStatus) {
ProvisionerJobStatus.PENDING -> "◍ Queued"
ProvisionerJobStatus.RUNNING -> when (this.buildTransition) {
WorkspaceBuildTransition.START -> "⦿ Starting"
WorkspaceBuildTransition.STOP -> "◍ Stopping"
WorkspaceBuildTransition.DELETE -> "⦸ Deleting"
}

ProvisionerJobStatus.SUCCEEDED -> when (this.buildTransition) {
WorkspaceBuildTransition.START -> "⦿ Running"
WorkspaceBuildTransition.STOP -> "◍ Stopped"
WorkspaceBuildTransition.DELETE -> "⦸ Deleted"
}

ProvisionerJobStatus.CANCELING -> "◍ Canceling action"
ProvisionerJobStatus.CANCELED -> "◍ Canceled action"
ProvisionerJobStatus.FAILED -> "ⓧ Failed"
}

private fun WorkspaceAgentModel.statusColor() = when (this.jobStatus) {
ProvisionerJobStatus.SUCCEEDED -> if (this.buildTransition == WorkspaceBuildTransition.START) Color.GREEN else Color.RED
ProvisionerJobStatus.RUNNING -> when (this.buildTransition) {
WorkspaceBuildTransition.START, WorkspaceBuildTransition.STOP, WorkspaceBuildTransition.DELETE -> Color.GRAY
}

else -> Color.RED
}
}


private fun ListTableModel<WorkspaceAgentModel>.updateItems(workspaces: Collection<WorkspaceAgentModel>) {
while (this.rowCount > 0) this.removeRow(0)
this.addRows(workspaces)
}

companion object {
val logger = Logger.getInstance(CoderWorkspacesStepView::class.java.simpleName)
}
Expand Down