Skip to content

Commit

Permalink
Add container and component tabs on code view
Browse files Browse the repository at this point in the history
  • Loading branch information
LunarN0va committed May 13, 2024
1 parent 1a5203e commit 2f902f7
Show file tree
Hide file tree
Showing 22 changed files with 306 additions and 93 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,13 @@ val SoftwareSystem.includedProperties
val Container.hasComponents
get() = this.components.isNotEmpty()

fun SoftwareSystem.firstContainerName(generatorContext: GeneratorContext) = containers
.firstOrNull { container ->
fun SoftwareSystem.firstContainer(generatorContext: GeneratorContext) = containers
.sortedBy { it.name }.firstOrNull { container ->
generatorContext.workspace.hasComponentDiagrams(container) or
generatorContext.workspace.hasImageViews(container.id) }
?.name?.normalize()

fun SoftwareSystem.firstContainerName(generatorContext: GeneratorContext) = firstContainer(generatorContext)
?.name?.normalize()

fun SoftwareSystem.hasDecisions() = documentation.decisions.isNotEmpty()

Expand All @@ -39,6 +41,11 @@ fun SoftwareSystem.hasDocumentationSections() = documentation.sections.size >= 2

fun SoftwareSystem.hasContainerDocumentationSections() = containers.any { it.hasSections() }

fun Container.firstComponentName(generatorContext: GeneratorContext) = components
.sortedBy { it.name }.firstOrNull {
component -> generatorContext.workspace.hasImageViews(component.id) }
?.name?.normalize()

fun Container.hasDecisions() = documentation.decisions.isNotEmpty()

fun Container.hasSections() = documentation.sections.isNotEmpty()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ private class WriterWithElementLinks(
val path = when (element) {
is SoftwareSystem -> "/${element.name?.normalize()}/${page?.let { page } ?: "container"}/".asUrlToDirectory(url)
is Container -> "/${element.parent?.name?.normalize()}/${page?.let { page } ?: "component"}/${element.name?.normalize()}".asUrlToDirectory(url)
is Component -> "/${element.parent?.parent?.name?.normalize()}/${page?.let { page } ?: "code"}/".asUrlToDirectory(url)
is Component -> "/${element.parent?.parent?.name?.normalize()}/${page?.let { page } ?: "code"}/${element.container?.name?.normalize()}/${element.name?.normalize()}".asUrlToDirectory(url)
else -> throw IllegalStateException("Not supported element")
}
return "$TEMP_URI$path"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import com.structurizr.Workspace
import com.structurizr.util.WorkspaceUtils
import kotlinx.html.*
import kotlinx.html.stream.appendHTML
import nl.avisi.structurizr.site.generatr.hasComponentDiagrams
import nl.avisi.structurizr.site.generatr.hasImageViews
import nl.avisi.structurizr.site.generatr.includedSoftwareSystems
import nl.avisi.structurizr.site.generatr.site.model.*
import nl.avisi.structurizr.site.generatr.site.views.*
Expand Down Expand Up @@ -144,7 +146,6 @@ private fun generateHtmlFiles(context: GeneratorContext, branchDir: File) {
add { writeHtmlFile(branchDir, SoftwareSystemHomePageViewModel(context, it)) }
add { writeHtmlFile(branchDir, SoftwareSystemContextPageViewModel(context, it)) }
add { writeHtmlFile(branchDir, SoftwareSystemContainerPageViewModel(context, it)) }
add { writeHtmlFile(branchDir, SoftwareSystemCodePageViewModel(context, it)) }
add { writeHtmlFile(branchDir, SoftwareSystemDynamicPageViewModel(context, it)) }
add { writeHtmlFile(branchDir, SoftwareSystemDeploymentPageViewModel(context, it)) }
add { writeHtmlFile(branchDir, SoftwareSystemDependenciesPageViewModel(context, it)) }
Expand Down Expand Up @@ -180,6 +181,18 @@ private fun generateHtmlFiles(context: GeneratorContext, branchDir: File) {
.forEach { container ->
add { writeHtmlFile(branchDir, SoftwareSystemContainerComponentsPageViewModel(context, container)) } }

it.containers
.filter { container ->
( context.workspace.hasComponentDiagrams(container) or
context.workspace.hasImageViews(container.id)) }
.forEach { container ->
container.components.filter { component ->
context.workspace.hasImageViews(component.id) }
.forEach { component ->
add { writeHtmlFile(branchDir, SoftwareSystemContainerComponentCodePageViewModel(context, container, component)) }
}
}

it.documentation.sections.filter { section -> section.order != 1 }.forEach { section ->
add { writeHtmlFile(branchDir, SoftwareSystemSectionPageViewModel(context, it, section)) }
}
Expand All @@ -205,7 +218,7 @@ private fun writeHtmlFile(exportDir: File, viewModel: PageViewModel) {
is SoftwareSystemContainerSectionPageViewModel -> softwareSystemContainerSectionPage(viewModel)
is SoftwareSystemContainerSectionsPageViewModel -> softwareSystemContainerSectionsPage(viewModel)
is SoftwareSystemContainerComponentsPageViewModel -> softwareSystemContainerComponentsPage(viewModel)
is SoftwareSystemCodePageViewModel -> softwareSystemCodePage(viewModel)
is SoftwareSystemContainerComponentCodePageViewModel -> softwareSystemContainerComponentCodePage(viewModel)
is SoftwareSystemDynamicPageViewModel -> softwareSystemDynamicPage(viewModel)
is SoftwareSystemDeploymentPageViewModel -> softwareSystemDeploymentPage(viewModel)
is SoftwareSystemDependenciesPageViewModel -> softwareSystemDependenciesPage(viewModel)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package nl.avisi.structurizr.site.generatr.site.model

data class ComponentTabViewModel(val pageViewModel: PageViewModel, val title: String, val url: String) {
val link = LinkViewModel(pageViewModel, title, url)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package nl.avisi.structurizr.site.generatr.site.model

import com.structurizr.model.Container
import nl.avisi.structurizr.site.generatr.hasImageViews
import nl.avisi.structurizr.site.generatr.site.GeneratorContext

fun SoftwareSystemPageViewModel.createComponentsTabViewModel(
generatorContext: GeneratorContext,
container: Container
) = buildList {
container
.components
.sortedBy { it.name }
.filter { component ->
generatorContext.workspace.hasImageViews(component.id) }
.map {
ComponentTabViewModel(
this@createComponentsTabViewModel,
it.name,
SoftwareSystemContainerComponentCodePageViewModel.url(it.container, it)
)
}
.forEach { add(it) }
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package nl.avisi.structurizr.site.generatr.site.model

data class ContainerTabViewModel(val pageViewModel: SoftwareSystemPageViewModel, val title: String, val url: String) {
val link = LinkViewModel(pageViewModel, title, url)
data class ContainerTabViewModel(val pageViewModel: SoftwareSystemPageViewModel, val title: String, val url: String, val match: Match = Match.EXACT) {
val link = LinkViewModel(pageViewModel, title, url, match)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package nl.avisi.structurizr.site.generatr.site.model

import com.structurizr.model.SoftwareSystem
import nl.avisi.structurizr.site.generatr.hasComponentDiagrams
import nl.avisi.structurizr.site.generatr.hasImageViews
import nl.avisi.structurizr.site.generatr.site.GeneratorContext

fun SoftwareSystemPageViewModel.createContainersCodeTabViewModel(
generatorContext: GeneratorContext,
softwareSystem: SoftwareSystem,
) = buildList {
softwareSystem
.containers
.sortedBy { it.name }
.filter { container ->
(generatorContext.workspace.hasComponentDiagrams(container) or
generatorContext.workspace.hasImageViews(container.id)) and
container.components.any { component -> generatorContext.workspace.hasImageViews(component.id) }
}
.map { container ->
ContainerTabViewModel(
this@createContainersCodeTabViewModel,
container.name,
SoftwareSystemContainerComponentCodePageViewModel.url(container, container.components.sortedBy { it.name }.firstOrNull { component -> generatorContext.workspace.hasImageViews(component.id) }),
Match.SIBLING
)
}
.forEach { add(it) }
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,19 @@ import nl.avisi.structurizr.site.generatr.hasComponentDiagrams
import nl.avisi.structurizr.site.generatr.hasImageViews
import nl.avisi.structurizr.site.generatr.site.GeneratorContext

fun SoftwareSystemPageViewModel.createContainersTabViewModel(
fun SoftwareSystemPageViewModel.createContainersComponentTabViewModel(
generatorContext: GeneratorContext,
softwareSystem: SoftwareSystem,
) = buildList {
softwareSystem
softwareSystem
.containers
.sortedBy { it.name }
.filter { container ->
generatorContext.workspace.hasComponentDiagrams(container) or
generatorContext.workspace.hasImageViews(container.id) }
.map {
ContainerTabViewModel(
this@createContainersTabViewModel,
this@createContainersComponentTabViewModel,
it.name,
SoftwareSystemContainerComponentsPageViewModel.url(it)
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,17 @@ data class LinkViewModel(
Match.EXACT -> isHrefOfContainingPage
Match.CHILD -> isChildHrefOfContainingPage
Match.SIBLING -> isSiblingHrefOfContainingPage
Match.SIBLING_CHILD -> isSiblingChildHrefOfContainingPage
}

private val isHrefOfContainingPage get() = href == pageViewModel.url
private val isChildHrefOfContainingPage get() = pageViewModel.url == href || pageViewModel.url.startsWith("$href/")
private val isSiblingHrefOfContainingPage get() = pageViewModel.url.trimEnd('/').dropLastWhile { it != '/' } == href.trimEnd('/').dropLastWhile { it != '/' }
private val isSiblingChildHrefOfContainingPage get() = pageViewModel.url.trimEnd('/').dropLastWhile { it != '/' }.trimEnd('/').dropLastWhile { it != '/' } == href.trimEnd('/').dropLastWhile { it != '/' }.trimEnd('/').dropLastWhile { it != '/' }
}
enum class Match {
EXACT,
CHILD,
SIBLING
SIBLING,
SIBLING_CHILD
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package nl.avisi.structurizr.site.generatr.site.model

import com.structurizr.model.Container
import com.structurizr.model.Component
import nl.avisi.structurizr.site.generatr.normalize
import nl.avisi.structurizr.site.generatr.site.GeneratorContext

class SoftwareSystemContainerComponentCodePageViewModel(generatorContext: GeneratorContext, container: Container, component: Component) :
SoftwareSystemPageViewModel(generatorContext, container.softwareSystem, Tab.CODE) {
override val url = url(container, component)
val images = generatorContext.workspace.views.imageViews
.filter { it.elementId in component.id }
.sortedBy { it.key }
.map { ImageViewViewModel(it) }

val visible = images.isNotEmpty()
val containerTabs = createContainersCodeTabViewModel(generatorContext, container.softwareSystem)
val componentTabs = createComponentsTabViewModel(generatorContext, container)

companion object {
fun url(container: Container, component: Component?) =
url(container.softwareSystem, Tab.CODE) +
"/${container.name.normalize()}" +
"/${component?.name?.normalize()}"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class SoftwareSystemContainerComponentsPageViewModel(generatorContext: Generator
.map { ImageViewViewModel(it) }

val visible = diagrams.isNotEmpty() or images.isNotEmpty()
val containerTabs = createContainersTabViewModel(generatorContext, container.softwareSystem)
val containerTabs = createContainersComponentTabViewModel(generatorContext, container.softwareSystem)
companion object {
fun url(container: Container) = "${url(container.softwareSystem, Tab.COMPONENT)}/${container.name.normalize()}"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,12 @@ open class SoftwareSystemPageViewModel(
"${url(softwareSystem, tab)}/${softwareSystem.firstContainerName(generatorContext)}",
match
)
Tab.CODE -> LinkViewModel(
this@SoftwareSystemPageViewModel,
title,
"${url(softwareSystem, tab)}/${softwareSystem.firstContainerName(generatorContext)}/${softwareSystem.firstContainer(generatorContext)?.firstComponentName(generatorContext)}",
match
)
else -> LinkViewModel(this@SoftwareSystemPageViewModel, title, url(softwareSystem, tab), match)
}

Expand Down Expand Up @@ -59,7 +65,7 @@ open class SoftwareSystemPageViewModel(
TabViewModel(Tab.SYSTEM_CONTEXT),
TabViewModel(Tab.CONTAINER),
TabViewModel(Tab.COMPONENT, Match.SIBLING),
TabViewModel(Tab.CODE),
TabViewModel(Tab.CODE, Match.SIBLING_CHILD),
TabViewModel(Tab.DYNAMIC),
TabViewModel(Tab.DEPLOYMENT),
TabViewModel(Tab.DEPENDENCIES),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package nl.avisi.structurizr.site.generatr.site.views

import kotlinx.html.HTML
import kotlinx.html.div
import kotlinx.html.li
import kotlinx.html.ul
import nl.avisi.structurizr.site.generatr.site.model.SoftwareSystemContainerComponentCodePageViewModel

fun HTML.softwareSystemContainerComponentCodePage(viewModel: SoftwareSystemContainerComponentCodePageViewModel) {
if (viewModel.visible) {
softwareSystemPage(viewModel) {
div(classes = "tabs") {
ul(classes = "m-0 is-flex-wrap-wrap is-flex-shrink-1 is-flex-grow-0") {
viewModel.containerTabs
.forEach {
li(classes = if (it.link.active) "is-active" else null) {
link(it.link)
}
}
}
}
div(classes = "tabs tabs-code") {
ul(classes = "m-0 is-flex-wrap-wrap is-flex-shrink-1 is-flex-grow-0") {
viewModel.componentTabs
.forEach {
li(classes = if (it.link.active) "is-active" else null) {
link(it.link)
}
}
}
}
viewModel.images.forEach { image(it) }
}
} else
redirectUpPage()
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ fun HTML.softwareSystemContainerComponentsPage(viewModel: SoftwareSystemContaine
if (viewModel.visible) {
softwareSystemPage(viewModel) {
div(classes = "tabs") {
ul(classes = "m-0 is-flex-wrap-wrap is flex-shrink-1 is flex-grow-0") {
ul(classes = "m-0 is-flex-wrap-wrap is-flex-shrink-1 is-flex-grow-0") {
viewModel.containerTabs
.forEach {
li(classes = if (it.link.active) "is-active" else null) {
Expand Down

This file was deleted.

4 changes: 4 additions & 0 deletions src/main/resources/assets/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,7 @@ svg a:hover {
.modal-image {
object-fit: contain;
}

.tabs-code {
font-size: 0.75rem;
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ class PlantUmlExporterTest {
assertThat(diagram.definition.withoutC4HeaderAndFooter()).isEqualTo(
"""
Container_Boundary("System1.Container1_boundary", "Container 1", ${'$'}tags="") {
Component(System1.Container1.Component1, "Component 1", ${'$'}techn="", ${'$'}descr="", ${'$'}tags="", ${'$'}link="../system-1/code/")
Component(System1.Container1.Component1, "Component 1", ${'$'}techn="", ${'$'}descr="", ${'$'}tags="", ${'$'}link="../system-1/code/container-1/component-1/")
Component(System1.Container1.Component2, "Component 2", ${'$'}techn="", ${'$'}descr="", ${'$'}tags="", ${'$'}link="")
Component(System1.Container1.Component3, "Component 3", ${'$'}techn="", ${'$'}descr="", ${'$'}tags="", ${'$'}link="")
}
Expand All @@ -191,7 +191,7 @@ class PlantUmlExporterTest {
assertThat(diagram.definition.withoutStructurizrHeaderAndFooter()).isEqualTo(
"""
rectangle "Container 1\n<size:10>[Container]</size>" <<System1.Container1>> {
rectangle "==Component 1\n<size:10>[Component]</size>" <<System1.Container1.Component1>> as System1.Container1.Component1 [[../system-1/code/]]
rectangle "==Component 1\n<size:10>[Component]</size>" <<System1.Container1.Component1>> as System1.Container1.Component1 [[../system-1/code/container-1/component-1/]]
rectangle "==Component 2\n<size:10>[Component]</size>" <<System1.Container1.Component2>> as System1.Container1.Component2
rectangle "==Component 3\n<size:10>[Component]</size>" <<System1.Container1.Component3>> as System1.Container1.Component3
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,4 +78,28 @@ class LinkViewModelTest : ViewModelTest() {
assertThat(expectInactiveSiblingMatch.active).isFalse()
assertThat(expectActiveSiblingMatch.active).isTrue()
}

@ParameterizedTest
@ValueSource(strings = ["/some-page/sibling/child/", "/some-page/other-sibling/other-child/"])
fun `sibling child links are active when two url paths back matches`(pageHref: String) {
val pageViewModel = pageViewModel(pageHref)
val viewModel = LinkViewModel(pageViewModel, "Some page", "/some-page/another-sibling/another-child", Match.SIBLING_CHILD)
assertThat(viewModel.active).isTrue()
}

@ParameterizedTest
@ValueSource(strings = ["/some-page/sibling/child/", "/some-page/other-sibling/other-child/"])
fun `sibling child links are not active when two url paths back doesnt match`(pageHref: String) {
val pageViewModel = pageViewModel(pageHref)
val viewModel = LinkViewModel(pageViewModel, "Some other page", "/some-other-page/not-a-sibling/child/", Match.SIBLING_CHILD)
assertThat(viewModel.active).isFalse()
}

@Test
fun `sibling child links are only active when two url paths back matches with two url paths back from href`() {
val expectInactiveSiblingChildMatch = LinkViewModel(pageViewModel("/page/one/description"), "Some page", "/some-page/", Match.SIBLING_CHILD)
val expectActiveSiblingChildMatch = LinkViewModel(pageViewModel("/page/one/description"), "Some page", "/page/two/title-screen", Match.SIBLING_CHILD)
assertThat(expectInactiveSiblingChildMatch.active).isFalse()
assertThat(expectActiveSiblingChildMatch.active).isTrue()
}
}
Loading

0 comments on commit 2f902f7

Please sign in to comment.