Skip to content

Commit

Permalink
Merge pull request #356 from jp7677/group-separator
Browse files Browse the repository at this point in the history
Identify external software systems by custom tag
  • Loading branch information
jp7677 authored Nov 4, 2023
2 parents 6041dd6 + 9d29ce9 commit 5ec27c7
Show file tree
Hide file tree
Showing 16 changed files with 246 additions and 85 deletions.
9 changes: 9 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,12 @@ indent_style = space
[Dockerfile]
# indent_size = 4
indent_style = space

[*.{kt,kts}]
max_line_length = 160
ij_kotlin_allow_trailing_comma_on_call_site = false
ij_kotlin_allow_trailing_comma = false

ktlint_standard_no-wildcard-imports = disabled
ktlint_standard_argument-list-wrapping = disabled
ktlint_standard_multiline_if_else = disabled
10 changes: 6 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ is generated from the example workspace in this repository.

- Generate a static HTML site, based on a Structurizr DSL workspace.
- Generates diagrams in SVG, PNG and PlantUML format, which can be viewed and downloaded from the generated site.
- Easy browsing through the site by clicking on software system and container elements in the diagrams.
- Easy browsing through the site by clicking on software system and container elements in the diagrams. Note that
external software systems are excluded from the menu. A software system is considered external when it lives outside
the (deprecated) enterprise boundary or when it contains a specific tag, see [Customizing the generated website](#customizing-the-generated-website).
- Start a development server which generates a site, serves it and updates the site automatically whenever a file that's
part of the Structurizr workspace changes.
- Include documentation (in Markdown or AsciiDoc format) in the generated site. Both workspace level documentation and software
Expand Down Expand Up @@ -264,11 +266,11 @@ architecture model:
| `generatr.search.language` | Indexing/stemming language for the search index. See [Lunr language support](https://github.com/olivernn/lunr-languages) | `en` | `nl` |
| `generatr.markdown.flexmark.extensions` | Additional extensions to the markdown generator to add new markdown capabilities. [More Details](https://avisi-cloud.github.io/structurizr-site-generatr/main/extended-markdown-features/) | Tables | `Tables,Admonition` |
| `generatr.svglink.target` | Specifies the link target for element links in the exported svg | `_top` | `_self` |
| `generatr.site.nestGroups` | Will show software systems in the left side navigator in collapsable groups | `false` | `true` |

| `generatr.site.externalTag` | Software systems containing this tag will be considered external | | |
| `generatr.site.nestGroups` | Will show software systems in the left side navigator in collapsable ~~~~groups | `false` | `true` |

See the included example for usage of some those properties in the
[C4 architecture model example](https://github.com/avisi-cloud/structurizr-site-generatr/blob/main/docs/example/workspace.dsl#L159).
[C4 architecture model example](https://github.com/avisi-cloud/structurizr-site-generatr/blob/main/docs/example/workspace.dsl#L163).

## Contributing

Expand Down
9 changes: 8 additions & 1 deletion docs/example/workspace.dsl
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@ workspace "Big Bank plc" "This is an example workspace to illustrate the key fea
!adrs workspace-adrs

model {
properties {
"structurizr.groupSeparator" "/"
}

customer = person "Personal Banking Customer" "A customer of the bank, with personal bank accounts." "Customer"

acquirer = softwaresystem "Acquirer" "Facilitates PIN transactions for merchants." "External System"

enterprise "Big Bank plc" {
group "Big Bank plc" {
supportStaff = person "Customer Service Staff" "Customer service staff within the bank." "Bank Staff" {
properties {
"Location" "Customer Services"
Expand Down Expand Up @@ -177,6 +181,9 @@ workspace "Big Bank plc" "This is an example workspace to illustrate the key fea
// * it's not possible to use "GitLab" and "ResizableImage" extensions together
// default behaviour, if no generatr.markdown.flexmark.extensions property is specified, is to load the Tables extension only
"generatr.markdown.flexmark.extensions" "Abbreviation,Admonition,AnchorLink,Attributes,Autolink,Definition,Emoji,Footnotes,GfmTaskList,GitLab,MediaTags,Tables,TableOfContents,Typographic"

"generatr.site.externalTag" "External System"
"generatr.site.nestGroups" "false"
}

systemlandscape "SystemLandscape" {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
package nl.avisi.structurizr.site.generatr

import com.structurizr.Workspace
import com.structurizr.model.Container
import com.structurizr.model.Location
import com.structurizr.model.Model
import com.structurizr.model.SoftwareSystem
import com.structurizr.view.ViewSet

val Model.includedSoftwareSystems: List<SoftwareSystem>
get() = softwareSystems.filter { it.includedSoftwareSystem }

val SoftwareSystem.includedSoftwareSystem
get() = this.location != Location.External

val Container.hasComponents
get() = this.components.isNotEmpty()
val Workspace.includedSoftwareSystems: List<SoftwareSystem>
get() = model.softwareSystems.filter {
val externalTag = views.configuration.properties.getOrDefault("generatr.site.externalTag", null)
it.location != Location.External && if (externalTag != null) !it.tags.contains(externalTag) else true
}

val SoftwareSystem.hasContainers
get() = this.containers.isNotEmpty()

val SoftwareSystem.includedProperties
get() = this.properties.filterNot { (name, _) -> name == "structurizr.dsl.identifier" }

val Container.hasComponents
get() = this.components.isNotEmpty()

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

fun SoftwareSystem.hasContainerDecisions() = containers.any { it.hasDecisions() }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package nl.avisi.structurizr.site.generatr.site

import com.structurizr.Workspace
import com.structurizr.export.Diagram
import com.structurizr.export.IndentingWriter
import com.structurizr.export.plantuml.C4PlantUMLExporter
Expand All @@ -10,8 +11,9 @@ import com.structurizr.view.*
import nl.avisi.structurizr.site.generatr.*

class C4PlantUmlExporterWithElementLinks(
private val workspace: Workspace,
private val url: String
): C4PlantUMLExporter() {
) : C4PlantUMLExporter() {
companion object {
const val TEMP_URI = "https://will-be-changed-to-relative/"

Expand Down Expand Up @@ -48,15 +50,15 @@ class C4PlantUmlExporterWithElementLinks(
}

private fun needsLinkToSoftwareSystem(element: Element?, view: ModelView?) =
element is SoftwareSystem && element.includedSoftwareSystem && element != view?.softwareSystem
element is SoftwareSystem && workspace.includedSoftwareSystems.contains(element) && element != view?.softwareSystem

private fun getUrlToSoftwareSystem(element: Element?): String {
val path = "/${element?.name?.normalize()}/context/".asUrlToDirectory(url)
return "$TEMP_URI$path"
}

private fun needsLinkToContainerViews(element: Element?, view: ModelView?) =
element is SoftwareSystem && element.includedSoftwareSystem && element == view?.softwareSystem && element.hasContainers
element is SoftwareSystem && workspace.includedSoftwareSystems.contains(element) && element == view?.softwareSystem && element.hasContainers

private fun getUrlToContainerViews(element: Element?): String {
val path = "/${element?.name?.normalize()}/container/".asUrlToDirectory(url)
Expand Down Expand Up @@ -88,5 +90,4 @@ class C4PlantUmlExporterWithElementLinks(
.split(System.lineSeparator())
.forEach { line -> writer?.writeLine(line) }
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import com.structurizr.view.View
import net.sourceforge.plantuml.FileFormat
import net.sourceforge.plantuml.FileFormatOption
import net.sourceforge.plantuml.SourceStringReader
import nl.avisi.structurizr.site.generatr.includedSoftwareSystems
import nl.avisi.structurizr.site.generatr.site.C4PlantUmlExporterWithElementLinks.Companion.export
import java.io.ByteArrayOutputStream
import java.io.File
Expand Down Expand Up @@ -83,7 +84,7 @@ private fun saveAsPng(diagram: Diagram, pngDir: File) {
}

private fun generatePlantUMLDiagramWithElementLinks(workspace: Workspace, view: View, url: String): Diagram {
val plantUMLExporter = C4PlantUmlExporterWithElementLinks(url)
val plantUMLExporter = C4PlantUmlExporterWithElementLinks(workspace, url)

if (workspace.views.configuration.properties.containsKey("generatr.svglink.target")) {
plantUMLExporter.addSkinParam(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ private fun generateHtmlFiles(context: GeneratorContext, branchDir: File) {
add { writeHtmlFile(branchDir, WorkspaceDecisionPageViewModel(context, it)) }
}

context.workspace.model.includedSoftwareSystems.forEach {
context.workspace.includedSoftwareSystems.forEach {
add { writeHtmlFile(branchDir, SoftwareSystemHomePageViewModel(context, it)) }
add { writeHtmlFile(branchDir, SoftwareSystemContextPageViewModel(context, it)) }
add { writeHtmlFile(branchDir, SoftwareSystemContainerPageViewModel(context, it)) }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package nl.avisi.structurizr.site.generatr.site.model

import nl.avisi.structurizr.site.generatr.includedSoftwareSystems
import nl.avisi.structurizr.site.generatr.site.GeneratorContext

class MenuViewModel(generatorContext: GeneratorContext, private val pageViewModel: PageViewModel) {
Expand All @@ -19,17 +18,16 @@ class MenuViewModel(generatorContext: GeneratorContext, private val pageViewMode
.forEach { yield(createMenuItem(it.contentTitle(), WorkspaceDocumentationSectionPageViewModel.url(it))) }
}.toList()

val softwareSystemItems = generatorContext.workspace.model.includedSoftwareSystems
val softwareSystemItems = pageViewModel.includedSoftwareSystems
.sortedBy { it.name.lowercase() }
.map {
createMenuItem(it.name, SoftwareSystemPageViewModel.url(it, SoftwareSystemPageViewModel.Tab.HOME), false)
}

private val groupSeparator = generatorContext.workspace.model.properties["structurizr.groupSeparator"]
private val groupSeparator = generatorContext.workspace.model.properties["structurizr.groupSeparator"] ?: "/"

private val softwareSystemPaths = generatorContext.workspace.model.includedSoftwareSystems
.filter { it.group != null }
.map { it.group + groupSeparator + it.name }
private val softwareSystemPaths = pageViewModel.includedSoftwareSystems
.map { "${it.group ?: ""}$groupSeparator${it.name}" }
.sortedBy { it.lowercase() }

private fun createMenuItem(title: String, href: String, exact: Boolean = true) =
Expand All @@ -39,8 +37,6 @@ class MenuViewModel(generatorContext: GeneratorContext, private val pageViewMode
data class MutableMenuNode(val name: String, val children: MutableList<MutableMenuNode>) {
fun toMenuNode(): MenuNodeViewModel = MenuNodeViewModel(name, children.map { it.toMenuNode() })
}
if (groupSeparator == null)
throw IllegalStateException("Property structurizr.groupSeparator not defined for model") // This is also validated earlier by structurizr when parsing the model

val rootNode = MutableMenuNode("", mutableListOf())

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package nl.avisi.structurizr.site.generatr.site.model

import nl.avisi.structurizr.site.generatr.includedSoftwareSystems
import nl.avisi.structurizr.site.generatr.site.GeneratorContext

abstract class PageViewModel(protected val generatorContext: GeneratorContext) {
Expand All @@ -19,6 +20,7 @@ abstract class PageViewModel(protected val generatorContext: GeneratorContext) {
val flexmarkConfig by lazy { buildFlexmarkConfig(generatorContext) }
val includeAdmonition = flexmarkConfig.selectedExtensionMap.containsKey("Admonition")
val includeKatex = flexmarkConfig.selectedExtensionMap.containsKey("GitLab")
val includedSoftwareSystems = generatorContext.workspace.includedSoftwareSystems
val configuration = generatorContext.workspace.views.configuration.properties
val includeTreeview = configuration.getOrDefault("generatr.site.nestGroups", "false").toBoolean()

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package nl.avisi.structurizr.site.generatr.site.model

import nl.avisi.structurizr.site.generatr.includedSoftwareSystem
import nl.avisi.structurizr.site.generatr.site.GeneratorContext
import nl.avisi.structurizr.site.generatr.site.model.indexing.home
import nl.avisi.structurizr.site.generatr.site.model.indexing.softwareSystemComponents
Expand All @@ -25,8 +24,7 @@ class SearchViewModel(generatorContext: GeneratorContext) : PageViewModel(genera
addAll(workspaceDecisions(generatorContext.workspace.documentation, this@SearchViewModel))
addAll(workspaceSections(generatorContext.workspace.documentation, this@SearchViewModel))
addAll(
generatorContext.workspace.model.softwareSystems
.filter { it.includedSoftwareSystem }
includedSoftwareSystems
.flatMap {
buildList {
add(softwareSystemHome(it, this@SearchViewModel))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import com.structurizr.model.SoftwareSystem
fun TableViewModel.TableViewInitializerContext.softwareSystemCell(
pageViewModel: PageViewModel,
system: SoftwareSystem
) = if (system.location == Location.External)
headerCell("${system.name} (External)", greyText = true)
else
) = if (pageViewModel.includedSoftwareSystems.contains(system))
headerCellWithLink(
pageViewModel,
system.name,
SoftwareSystemPageViewModel.url(system, SoftwareSystemPageViewModel.Tab.HOME)
)
else
headerCell("${system.name} (External)", greyText = true)
Loading

0 comments on commit 5ec27c7

Please sign in to comment.