Code Monkey home page Code Monkey logo

module-graph's Introduction

Pre Merge Checks License Language

module graph icon

Module Graph Plugin

This plugin generates a Mermaid graph of your project's module relationships, so you can see how your modules interact at a glance.

A diagram about the current system is only useful if it's generated. If it is produced by hand it documents the author's belief, not the system. Still, important, but not an input for decision making. Development is primarily decision-making. Enable it through custom tools. source

You can read more about the background story of this plugin here.

Main Features ⭐

  • Automatically append/update the generated graph to your project's README file.
  • Enables you to focus and highlight specific nodes in the graph
  • Exclude specific configurations from the graph.
  • Built in themes and customization options.
  • The raw code block automatically renders as a graph on both GitHub, Gitlab, Jetbrains IDEs and VSCode .

Getting Started

You'll just need to add it to your project's root build.gradle or build.gradle.kts file.

build.gradle (Groovy DSL)

Using the plugins DSL

plugins {
    id "dev.iurysouza.modulegraph" version "0.10.0"
}
Using Legacy Plugin application
    buildscript {
    repositories {
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }
    dependencies {
        classpath "dev.iurysouza:modulegraph:0.10.0"
    }
}

apply plugin: "dev.iurysouza.modulegraph"

Configuring the plugin

import dev.iurysouza.modulegraph.LinkText
import dev.iurysouza.modulegraph.ModuleType
import dev.iurysouza.modulegraph.Orientation
import dev.iurysouza.modulegraph.Theme

moduleGraphConfig {
    readmePath = "./README.md"
    heading = "### Module Graph"
    // showFullPath = false // optional
    // orientation = Orientation.LEFT_TO_RIGHT // optional
    // linkText = LinkText.NONE // optional
    // excludedConfigurationsRegex = ".*test.*" // optional
    // excludedModulesRegex = ".*moduleName.*" // optional
    // focusedModulesRegex = ".*(projectName).*" // optional
    // rootModulesRegex = ".*moduleName.*" // optional
    // setStyleByModuleType = true // optional
    // theme = Theme.NEUTRAL // optional
    // Or you can fully customize it by using the BASE theme:
    // theme = new Theme.BASE(
    //     [
    //         "primaryTextColor": "#F6F8FAff", // All text colors
    //         "primaryColor": "#5a4f7c", // Node color
    //         "primaryBorderColor": "#5a4f7c", // Node border color
    //         "tertiaryColor": "#40375c", // Container box background
    //         "lineColor": "#f5a623",
    //         "fontSize": "12px"
    //     ],
    //     focusColor = "#F5A622" // Color of the focused nodes if any
    // )
    // theme.set(
    //     new Theme.BASE(
    //         themeVariables: [
    //             "primaryTextColor": "#F6F8FAff", // All text colors
    //             "primaryColor": "#5a4f7c", // Node color
    //             "primaryBorderColor": "#5a4f7c", // Node border color
    //             "tertiaryColor": "#40375c", // Container box background
    //             "lineColor": "#f5a623",
    //             "fontSize": "12px"
    //         ],
    //         focusColor: "#F5A622", // Color of the focused nodes if any
    //         moduleTypes: [
    //             new ModuleType.AndroidLibrary("#2C4162")
    //         ]
    //     )
    // )

    // You can add additional graphs.
    // A separate graph will be generated for each config below.
    // graph(
    //     "./README.md",
    //     "# Graph with root: gama",
    // ) {
    //     it.rootModulesRegex = ".*gama.*"
    // }
    // graph(
    //     "./SomeOtherReadme.md",
    //     "# Graph",
    // ) {
    //     it.rootModulesRegex = ".*zeta.*"
    // }

}

build.gradle.kts (Kotlin DSL)

Using the plugins DSL

plugins {
    id("dev.iurysouza.modulegraph") version "0.10.0"
}
Using Legacy Plugin application
    buildscript {
    repositories {
        maven {
            url = uri("https://plugins.gradle.org/m2/")
        }
    }
    dependencies {
        classpath("dev.iurysouza:modulegraph:0.10.0")
    }
}

apply(plugin = "dev.iurysouza:modulegraph")

Configuring the plugin

import dev.iurysouza.modulegraph.LinkText
import dev.iurysouza.modulegraph.ModuleType
import dev.iurysouza.modulegraph.Orientation
import dev.iurysouza.modulegraph.Theme

moduleGraphConfig {
    readmePath.set("./README.md")
    heading = "### Module Graph"
    // showFullPath.set(false) // optional
    // orientation.set(Orientation.LEFT_TO_RIGHT) //optional
    // linkText.set(LinkText.NONE) // optional
    // setStyleByModuleType.set(true) // optional
    // excludedConfigurationsRegex.set(".*test.*") // optional
    // excludedModulesRegex.set(".*moduleName.*") // optional
    // focusedModulesRegex.set(".*(projectName).*") // optional
    // rootModulesRegex.set(".*moduleName.*") // optional
    // theme.set(Theme.NEUTRAL) // optional
    // or you can fully customize it by using the BASE theme:
    // Theme.BASE(
    //     themeVariables = mapOf(
    //         "primaryTextColor" to "#F6F8FAff", // Text
    //         "primaryColor" to "#5a4f7c", // Node
    //         "primaryBorderColor" to "#5a4f7c", // Node border
    //         "tertiaryColor" to "#40375c", // Container box background
    //         "lineColor" to "#f5a623",
    //         "fontSize" to "12px",
    //     ),
    //     focusColor = "#F5A622",
    //     moduleTypes = listOf(
    //         ModuleType.AndroidLibrary("#2C4162"),
    //     )
    // ),
    // )

    // You can add additional graphs.
    // A separate graph will be generated for each config below.
    // graph(
    //     readmePath = "./README.md",
    //     heading = "# Graph with root: gama",
    // ) {
    //     rootModulesRegex = ".*gama.*"
    // }
    // graph(
    //     readmePath = "./SomeOtherReadme.md",
    //     heading = "# Graph",
    // ) {
    //     rootModulesRegex = ".*zeta.*"
    // }
}

Usage

Make sure you have a heading in your README with the same format as the one you set in the configuration, if not, the plugin will append it with the graph to the end of the file.

After that, just run the following command:

./gradlew createModuleGraph

Now, just look for the generated graph in your project's README file.

Configuration Docs

Each Graph has the following configuration parameters.

Required settings:

  • readmePath: The path of the file where the dependency graph will be added.
  • heading: The heading where the dependency graph will be added.

Optional settings:

  • setStyleByModuleType: Whether to style the modules based on their type (KotlinMultiplatform, Android Library, etc). Default is false. Read more.
  • focusedModulesRegex: The regex to match nodes in the graph (project names) that should be focused. By default, no nodes are focused. If set, the matching nodes will be highlighted and only related nodes will be shown. The color can be customized via the focusColor property from Theme.BASE. Read more.
  • showFullPath: Whether to show the full path of the modules in the graph. Default is false. This removes subgraphs.
  • theme: The mermaid theme to be used for styling the graph. Default is NEUTRAL.
    • Further customization is possible by setting the themeVariables property on the BASE theme. Check the mermaid docs for more info.
  • orientation: The orientation that the flowchart will have. Default is LEFT_TO_RIGHT.
  • linkText: Whether to add information as text on links in graph. Available values:
    • NONE: No text added. (Default.)
    • CONFIGURATION: The name of the configuration which the dependency belongs to (e.g. " implementation", "compileOnly", "jsMain").
  • excludedConfigurationsRegex:
    • Regex matching the configurations which should be ignored. e.g. "implementation", "testImplementation".
  • excludedModulesRegex:
    • Regex matching the modules which should be ignored.
  • rootModules:
    • Regex matching the modules that should be used as root modules. If this value is supplied, the generated graph will only include dependencies (direct and transitive) of root modules. In other words, the graph will only include modules that can be reached from a root module.

Multiple graphs

You can apply configuration options directly in the root of the moduleGraphConfig block like so:

moduleGraphConfig {
    readmePath.set("${rootDir}/README.md")
    heading.set("### Module Graph")
    showFullPath.set(false)
}

When you do this, you are configuring the 'Primary Graph'. This is useful if you only need one graph to be generated.

But sometimes you want multiple graphs to be generated. To achieve this you can add additional graph configs using graph. Each additional graph has exactly the same configuration parameters as the primary graph:

moduleGraphConfig {
    graph(
        readmePath = "${rootDir}/README.md",
        heading = "### Module Graph",
    ) {
        showFullPath = false
    }
}

Note that graph requires the required parameters to be provided in the function call, while the optional parameters can be provided in the configuration block.

You can add as many graph calls as you like: each one will generate a separate graph:

moduleGraphConfig {
    graph(
        readmePath = "${rootDir}/README.md",
        heading = "### Module Graph",
    ) {
        showFullPath = false
    }
    graph(
        readmePath = "${rootDir}/README.md",
        heading = "### Another Module Graph",
    ) {
        showFullPath = true
    }
}

For this plugin to work, you need to configure at least one graph. This can be via the Primary Graph, or via a graph call.

If using only graph calls, then the Primary Graph doesn't need to be setup at all! You can see this in the samples above.

Show me that graph!

This is an example of using the plugin on an Android project with a multimodule setup. Here, the following configuration was used:

moduleGraphConfig {
    readmePath.set("${rootDir}/README.md")
    heading.set("### Module Graph")
    theme.set(
        Theme.BASE(
            mapOf(
                "primaryTextColor" to "#fff",
                "primaryColor" to "#5a4f7c",
                "primaryBorderColor" to "#5a4f7c",
                "lineColor" to "#f5a623",
                "tertiaryColor" to "#40375c",
                "fontSize" to "12px",
            ),
            focusColor = "#FA8140"
        ),
    )
}

And we got this graph:

%%{
  init: {
    'theme': 'base',
	'themeVariables': {"primaryTextColor":"#fff","primaryColor":"#5a4f7c","primaryBorderColor":"#5a4f7c","lineColor":"#f5a623","tertiaryColor":"#40375c","fontSize":"12px"}
  }
}%%
graph LR
    subgraph app
        main
        playground
    end
    subgraph core
        common
        design-system
        footballinfo
        reddit
        webview-to-native-player
    end
    subgraph features
        match-day
        match-thread
    end
    footballinfo --> common
    match-day --> common
    match-day --> footballinfo
    match-day --> design-system
    match-day --> reddit
    match-thread --> webview-to-native-player
    match-thread --> common
    match-thread --> footballinfo
    match-thread --> design-system
    match-thread --> reddit
    playground --> webview-to-native-player
    playground --> match-thread
    playground --> match-day
    playground --> design-system
    reddit --> common
    webview-to-native-player --> common
    main --> match-thread
    main --> match-day
    main --> design-system
    main --> common
Loading

Too much information? We can fix that.

Focusing on specific nodes

If you want to focus on specific nodes in the graph, you can use the focusedModulesRegex property in the configuration.

moduleGraphConfig {
    //... keep previous configs
    focusedModulesRegex.set(".*(reddit).*")
}

By doing this, the plugin will highlight the nodes that match the pattern, and will only show the other nodes that are connected to them. It will generate the following graph:

%%{
  init: {
    'theme': 'base',
	'themeVariables': {"primaryTextColor":"#fff","primaryColor":"#5a4f7c","primaryBorderColor":"#5a4f7c","lineColor":"#f5a623","tertiaryColor":"#40375c","fontSize":"12px"}
  }
}%%
graph LR
    subgraph core
        common
        reddit
    end
    subgraph features
        match-day
        match-thread
    end
    match-day --> reddit
    match-thread --> reddit
    reddit --> common
    classDef focus fill:#E04380,stroke:#fff,stroke-width:2px,color:#fff;
    class reddit focus
Loading

Since it's just a regex pattern, you can, for example, match multiple nodes by using the | operator, or you can come up with whatever cryptic regex patterns you want if you're into that kind of thing.

When was the last time Regex made you happy? =)

// This matches module names that contain "reddit" or "match-day"
focusedModulesRegex.set(".*(reddit|match-day).*")
%%{
  init: {
    'theme': 'base',
	'themeVariables': {"primaryTextColor":"#fff","primaryColor":"#5a4f7c","primaryBorderColor":"#5a4f7c","lineColor":"#f5a623","tertiaryColor":"#40375c","fontSize":"12px"}
  }
}%%
graph LR
    subgraph app
        main
        playground
    end
    subgraph core
        common
        design-system
        footballinfo
        reddit
    end
    subgraph features
        match-day
        match-thread
    end
    match-day --> common
    match-day --> footballinfo
    match-day --> design-system
    match-day --> reddit
    match-thread --> reddit
    playground --> match-day
    reddit --> common
    main --> match-day
    classDef focus fill:#E04380,stroke:#fff,stroke-width:2px,color:#fff;
    class match-day focus
    class reddit focus
Loading

Module type based styling

This feature enables detecting and rendering modules based on their type, eg.: kotlin, java, android-library, kotlin-multiplatform, etc

Getting Started

Just toggle this option on:

moduleGraphConfig {
    //..
    setStyleByModuleType.set(true)
}

That's it. Just run the task and you'll get a graph identifying modules by their type.

Batteries included

We have default styling for these module types:

  • Android Application
  • React Native
  • Kotlin Multiplatform
  • Android Library
  • Kotlin
  • Java Library
  • Java

These supported plugins are pre-configured with a default color pattern, but can be customized further if needed. You can also add you own module type.

Customization

The supported plugins already have a default color pattern , but you can also customize them via the Theme.BASE object.

Additionally, you can detect and customize styling for other plugins by providing a CustomPlugin with an id and its color. The ID will be used to match a Gradle plugin applied to that module and will have higher precedence than all the others. For example, if you have a plugin defined with the app.compose id, you can pass it as Custom("app.compose", "#0E0E0E") and the graph will be generated accordingly. eg.:

import dev.iurysouza.modulegraph.ModuleType.*
import dev.iurysouza.modulegraph.Theme

moduleGraphConfig {
    theme.set(
        Theme.BASE(
            moduleTypes = listOf(
                Custom(id = "app.compose", color = "#0E0E0E"),
                AndroidApp("#3CD483"),
                AndroidLibrary("#292B2B"),
            ),
        ),
    )
}

Below is an example of how the module graph would show up:

%%{
  init: {
    'theme': 'base',
    'themeVariables': {"lineColor":"#676767"},
  }
}%%
graph LR
    subgraph app
        playground
        main
    end
    subgraph core
        webview-to-native-player
        common
        footballinfo
        design-system
        reddit
    end
    subgraph features
        match-thread
        match-day
    end
    match-thread --> webview-to-native-player
    match-thread --> common
    match-thread --> footballinfo
    match-thread --> design-system
    match-thread --> reddit
    match-day --> common
    match-day --> footballinfo
    match-day --> design-system
    match-day --> reddit
    playground --> webview-to-native-player
    playground --> match-thread
    playground --> design-system
    playground --> match-day
    main --> match-thread
    main --> match-day
    main --> design-system
    main --> common
    reddit --> common
    webview-to-native-player --> common
    footballinfo --> common
    classDef android_library fill:#292B2B,stroke:#fff,stroke-width:2px,color:#fff;
    classDef app_compose fill:#82AAFF,stroke:#fff,stroke-width:2px,color:#fff;
    classDef android_application fill:#3CD483,stroke:#fff,stroke-width: 2px,color:#fff;
    class match-thread app_compose
    class webview-to-native-player android_library
    class common android_library
    class footballinfo android_library
    class design-system app_compose
    class reddit android_library
    class match-day app_compose
    class playground android_application
    class main android_application

Loading

Note

Modules can only have one type. So we're using a hardcoded precedence order for identifying them.

Precedence

The system determines the module type based on the hierarchy of applied plugins. For instance:

  • A module with both React Native and Android Library will be identified as React Native.
  • A module with both Android Library and Kotlin will be identified as Android Library.

Contributing 🀝

Feel free to open an issue or submit a pull request for any bugs/improvements.

License πŸ“„

This project is licensed under the MIT License - see the License file for details.

Buy Me a Coffee

If you found this project useful or want to support the development, consider buying me a coffee! Any donations are greatly appreciated and help to support the development. Relevant xkcd.

Buy Me A Pingado

module-graph's People

Contributors

es0329 avatar hisakaz0 avatar iurysza avatar lukeneedham avatar omuomugin avatar stylianosgakis avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

module-graph's Issues

Refact: implement Intermediate graph model

⚠️ Is your feature request related to a problem? Please describe

Entropy's kicked in, and our little plugin, which started off as a basic script, has grown some new features which makes it harder to keep developing while only relying on Gradle Functional/integration tests.

πŸ’‘ Describe the solution you'd like

To improve the code quality I think it we could introduce an intermediate graph representation, which could be mapped to mermaid syntax by a separate component.

This would enable us to have a more testable and easier to reason about architecture.

🀚 Do you want to develop this feature yourself?

  • Yes
  • No

create graph for kotlin multiplatform modules relations

⚠️ Is your feature request related to a problem? Please describe

when I use your module graph in my modular multiplatform app, I can't see my sub-module (common, android, ios) and their relations with other sub-modules and just see parent module relations.

πŸ’‘ Describe the solution you'd like

I get this graph :

%%{
  init: {
    'theme': 'neutral'
  }
}%%

graph TB
  :composeApp --> :x
  :composeApp --> :y

Loading

but I want to create graph like this :

%%{
  init: {
    'theme': 'neutral'
  }
}%%


graph TB
  :android --> :x:android
  :common --> :x:common
  :ios --> :x:ios
  :ios --> :common
  :android --> :common
  subgraph composeApp
   :common 
   :android
   :ios
  end
  :x:android --> :x:common
  :x:ios --> :x:common
  subgraph module x
   :x:android
   :x:common 
   :x:ios
  end
  :y:android --> :y:common
  :y:ios --> :y:common
  :common --> :y:common
  subgraph module y
   :y:android
   :y:common 
   :y:ios
  end 

Loading

🀚 Do you want to develop this feature yourself?

  • Yes
  • No

Groovy DSL: Cannot set theme option

πŸ› Describe the bug

Attempting to configure the theme option in Groovy DSL results in an error, whereas setting other options like orientation or linkText does not cause any issues.

⚠️ Current behavior

Build fails with error message: Cannot set the value of extension 'moduleGraphConfig' property 'theme' of type dev.iurysouza.modulegraph.Theme using an instance of type java.lang.Class.

βœ… Expected behavior

theme can be configured.

πŸ’£ Steps to reproduce

import dev.iurysouza.modulegraph.Theme

moduleGraphConfig {
    readmePath = "./README.md"
    heading = '### Dependency Diagram'
    theme = Theme.DEFAULT
}

Add different color for different kinds of modules

πŸ’‘ Describe the solution you'd like

I think a really cool feature would be to make a visual distinction between Android apps, Android libs, Kotlin/Jvm modules (maybe others related with KMP? πŸ€” I feel these ones would be a good start though).

🀚 Do you want to develop this feature yourself?

I might look into it if I have time, but right now it's impossible. Between my own library, work, family, I'm totally booked 😬

Tried adding to my project but did not work.

I was trying to add this plugin to my project but it seems to be no luck. The task completes successfully (no error) but nothing is added to the Readme file.

Here's what I've done,

  1. Added the plugin (legacy approach) Groovy DSL.
  2. Configured the plugin,
 moduleGraphConfig {
      readmePath = '$projectDir/test.md' // created this new file under root, seems like an output file parameter.
      heading = '### Dependency Diagram'
  }
  1. Ran the gradle command as mentioned ./gradlew createModuleGraph. The task completes successfully but produces no output i.e no change to test.md file.

Also, I was looking at the source code & it seems there is an additional configuration property outputFile but when I set it, it says no such property exists. Seems like the latest version is not published on the gradle plugins portal.

Consider logging the error more verbosely if the "heading" is set wrong

I was trying this out, and I misunderstood what "Heading" was from the ModuleGraphExtension, and thought maybe it'd be a title for the mermaid graph.
So I added a random name in there. This meant that inside appendMermaidGraphToReadme it was not finding the heading so it was hitting this line.
Running it by default, means that I do not get the debug errors, and I feel like this has a justified reason to be more than just logger.debug, maybe it should be an error instead.

What do you think?

p.s. I did figure out how it works afterwards, just thought the error would help with a good explanation of how the heading property as misused and how to use it properly

Could not get unknown property 'Orientation'

πŸ› Describe the bug

Not able to define all properties listed on the documentation.

⚠️ Current behavior

Whenever I sync my project with your plugin applied or execute the gradle task createModuleGraph i get the error:

Build file 'myproject/build.gradle' line: 148

A problem occurred evaluating root project 'myproject'.
> Could not get unknown property 'Orientation' for extension 'moduleGraphConfig' of type dev.iurysouza.modulegraph.gradle.ModuleGraphExtension.

if i comment the orientation it generate correctly.

βœ… Expected behavior

I am able to define all the properties available.

πŸ’£ Steps to reproduce

  1. Add maven to root build.gradle.
 maven {
            url = uri("https://plugins.gradle.org/m2/")
        }
  1. Apply the plugin
plugins {
    id "dev.iurysouza.modulegraph" version "0.5.0"
}
  1. Configure the plugin on root build.gradle
moduleGraphConfig {
    readmePath.set("./README.md")
    heading.set("### Dependency Diagram")
    showFullPath.set(false) // optional
    orientation.set(Orientation.TOP_TO_BOTTOM) //optional
    //linkText.set(LinkText.NONE) // optional
    //theme.set(Theme.NEUTRAL) // optional
    // or you can fully customize it by using the BASE theme:
    // theme.set(Theme.BASE(
    //      mapOf(
    //          "primaryTextColor" to "#fff",
    //          "primaryColor" to "#5a4f7c",
    //          "primaryBorderColor" to "#5a4f7c",
    //          "lineColor" to "#f5a623",
    //          "tertiaryColor" to "#40375c",
    //          "fontSize" to "11px"
    //      )
    //   )
    // )
}
  1. Run the gradle task createModuleGraph or sync the project

Implement a way to customize mermaid's themeVariables.

πŸ’‘ Describe the solution you'd like

Implement a way to customize mermaid's theme. According to the mermaid's docs:

Customizing Themes with themeVariables

To make a custom theme, modify themeVariables via init.
You will need to use the base theme as it is the only modifiable theme.

So we can solve this exposing an API that looks like this:

moduleGraphConfig {
    theme.set(
        Base(themeVariables =  // Optional
            mapOf(
                "primaryColor" to "#BB2528",
                "primaryTextColor" to "#fff",
                "primaryBorderColor" to "#7C0000",
                "lineColor" to "#F8B229",
                "secondaryColor" to "#006100",
                "tertiaryColor" to "#fff"
            )
        )
    )
}

🀚 Do you want to develop this feature yourself?

  • Yes
  • No

Node focus

What

Enable users to set a regex pattern to focus on one or more nodes.

Why

I've discussed similar ideas/solutions/issues here and here, and even started working on it on this WIP branch.
Finally, last month I had another conversation on Twitter that motivated me to work on topic again.

I think the overarching problem is making these graphs actually usable and useful for bigger projects. So I think this solution is a good starting point for that. I hope to use it as the basis for other features like different node colors based on the plugin they're using.

πŸ’‘ Describe the solution you'd like

Add a new property ο»ΏfocusedNodesPattern which represents a Pattern (Regular Expression) matching specific nodes (project names) in the graph that are to be highlighted and focused on. By default, no nodes will be focused.

What does focus mean?

When focusedNodesPattern is set the graph will show:

  • Highlighted nodes: nodes which name's match the regular expression. These will be rendered with a different color.
  • Adjacent nodes: nodes that have connections with highlighted nodes.

Customisation: Users should be able to change the focus node color, and a default color should be provided. Customisation should be optional.

🀚 Do you want to develop this feature yourself?

  • Yes
  • No

Syntax error even with correct syntax

πŸ› Describe the bug

Mermaid can fail to render a graph even with correct syntax.

Already reported here: Syntax error even with correct syntax
This is a bug on the Mermaid renderer. Keep reading if you're looking for workarounds.

⚠️ Current behavior

It's not clear when, as I only saw this issue in one scenario, but some graphs can fail to render.

βœ… Expected behavior

Graph should render normally.

Workaround alternatives

  • Change the renderer:
    If you get this issue you can manually change the renderer by adding this to the init block and it will solve the issue. Although, changing the render will change the look of the graph.
%%{
  init: {
    "flowchart": { "defaultRenderer": "elk"}
  }
}%%
  • Use the showFullPath option:
    Set showFullPath property to true:
moduleGraphConfig {
    //...
    showFullPath.set(true)
}

πŸ’£ Steps to reproduce

Apply the plugin on TIVI and use the focusedModules property like so:

Module Graph plugin options:

moduleGraphConfig {
  readmePath.set("./docs/module-graph.md")
  heading.set("### Module Graph")
  focusedModulesRegex.set(".*(data).*")
}

Generated graph:

%%{
  init: {
    'theme': 'neutral'
  }
}%%

graph LR
  subgraph :api
    :api:trakt["trakt"]
    :api:tmdb["tmdb"]
  end
  subgraph :common
    :common:imageloading["imageloading"]
  end
  subgraph :common:ui
    :common:ui:compose["compose"]
  end
  subgraph :common:ui:resources
    :common:ui:resources:strings["strings"]
  end
  subgraph :core
    :core:base["base"]
    :core:analytics["analytics"]
    :core:preferences["preferences"]
  end
  subgraph :core:logging
    :core:logging:api["api"]
  end
  subgraph :data
    :data:traktauth["traktauth"]
    :data:models["models"]
    :data:db-sqldelight["db-sqldelight"]
    :data:db["db"]
    :data:showimages["showimages"]
    :data:legacy["legacy"]
    :data:test["test"]
    :data:followedshows["followedshows"]
    :data:episodes["episodes"]
    :data:shows["shows"]
    :data:trendingshows["trendingshows"]
    :data:licenses["licenses"]
    :data:search["search"]
    :data:watchedshows["watchedshows"]
    :data:recommendedshows["recommendedshows"]
    :data:popularshows["popularshows"]
    :data:relatedshows["relatedshows"]
    :data:traktusers["traktusers"]
  end
  subgraph :shared
    :shared:common["common"]
    :shared:prod["prod"]
    :shared:qa["qa"]
  end
  subgraph :ui
    :ui:root["root"]
    :ui:account["account"]
    :ui:licenses["licenses"]
  end
  :ui:root --> :data:traktauth
  :common:ui:compose --> :data:models
  :data:db-sqldelight --> :data:db
  :data:db-sqldelight --> :core:base
  :data:showimages --> :data:models
  :data:showimages --> :data:db
  :data:showimages --> :data:legacy
  :data:showimages --> :api:trakt
  :data:showimages --> :api:tmdb
  :data:test --> :core:analytics
  :data:test --> :core:logging:api
  :data:test --> :data:db-sqldelight
  :data:test --> :data:followedshows
  :data:test --> :data:episodes
  :data:test --> :data:showimages
  :data:test --> :data:shows
  :data:test --> :data:legacy
  :data:trendingshows --> :data:models
  :data:trendingshows --> :data:db
  :data:trendingshows --> :data:legacy
  :data:trendingshows --> :api:trakt
  :data:trendingshows --> :api:tmdb
  :shared:common --> :data:db-sqldelight
  :shared:common --> :data:licenses
  :data:followedshows --> :data:models
  :data:followedshows --> :data:traktauth
  :data:followedshows --> :api:trakt
  :data:followedshows --> :api:tmdb
  :data:followedshows --> :data:db
  :data:followedshows --> :data:legacy
  :data:shows --> :data:models
  :data:shows --> :data:db
  :data:shows --> :data:legacy
  :data:shows --> :api:trakt
  :data:shows --> :api:tmdb
  :shared:prod --> :data:traktauth
  :data:search --> :data:models
  :data:search --> :data:db
  :data:search --> :data:legacy
  :data:search --> :api:trakt
  :data:search --> :api:tmdb
  :ui:account --> :data:traktauth
  :data:licenses --> :core:base
  :data:licenses --> :core:logging:api
  :data:watchedshows --> :data:models
  :data:watchedshows --> :data:db
  :data:watchedshows --> :data:legacy
  :data:watchedshows --> :api:trakt
  :data:watchedshows --> :api:tmdb
  :ui:licenses --> :data:licenses
  :data:legacy --> :core:base
  :data:legacy --> :api:trakt
  :data:legacy --> :api:tmdb
  :data:legacy --> :core:logging:api
  :data:legacy --> :data:models
  :data:legacy --> :data:db
  :common:ui:resources:strings --> :data:models
  :common:imageloading --> :data:models
  :common:imageloading --> :data:episodes
  :common:imageloading --> :data:showimages
  :data:models --> :core:base
  :data:recommendedshows --> :data:models
  :data:recommendedshows --> :data:db
  :data:recommendedshows --> :data:legacy
  :data:recommendedshows --> :api:trakt
  :data:recommendedshows --> :api:tmdb
  :data:db --> :data:models
  :data:db --> :core:base
  :data:traktauth --> :core:base
  :data:traktauth --> :core:logging:api
  :data:episodes --> :core:preferences
  :data:episodes --> :data:models
  :data:episodes --> :data:traktauth
  :data:episodes --> :api:trakt
  :data:episodes --> :api:tmdb
  :data:episodes --> :data:db
  :data:episodes --> :data:legacy
  :shared:qa --> :data:traktauth
  :domain --> :data:models
  :domain --> :data:legacy
  :domain --> :data:episodes
  :domain --> :data:followedshows
  :domain --> :data:popularshows
  :domain --> :data:recommendedshows
  :domain --> :data:relatedshows
  :domain --> :data:search
  :domain --> :data:showimages
  :domain --> :data:shows
  :domain --> :data:traktauth
  :domain --> :data:traktusers
  :domain --> :data:trendingshows
  :domain --> :data:watchedshows
  :domain --> :data:licenses
  :domain --> :data:db
  :data:relatedshows --> :data:models
  :data:relatedshows --> :data:db
  :data:relatedshows --> :data:legacy
  :data:relatedshows --> :api:trakt
  :data:relatedshows --> :api:tmdb
  :api:trakt --> :data:traktauth
  :data:traktusers --> :data:models
  :data:traktusers --> :data:db
  :data:traktusers --> :data:legacy
  :data:traktusers --> :api:trakt
  :data:traktusers --> :api:tmdb
  :data:popularshows --> :data:models
  :data:popularshows --> :data:db
  :data:popularshows --> :data:legacy
  :data:popularshows --> :api:trakt
  :data:popularshows --> :api:tmdb

classDef focus fill:#769566,stroke:#fff,stroke-width:2px,color:#fff;
class :data:traktauth focus
class :data:models focus
class :data:db-sqldelight focus
class :data:db focus
class :data:showimages focus
class :data:legacy focus
class :data:test focus
class :data:followedshows focus
class :data:episodes focus
class :data:shows focus
class :data:trendingshows focus
class :data:licenses focus
class :data:search focus
class :data:watchedshows focus
class :data:recommendedshows focus
class :data:popularshows focus
class :data:relatedshows focus
class :data:traktusers focus
Loading

Badge for deep dependency graph.

⚠️ Is your feature request related to a problem? Please describe

Not a problem at all.
Its like a Badge to indicate how healthier is the dependency graph

πŸ’‘ Describe the solution you'd like

It would be nice if we could define a field to define how healthier is the dependency graph, let's say

healthDeepGraph = 4

if( deepGraph <= healthDeepGraph) Green badge
if( deeGraph > healthDeepGraph) Yellow badge
if( deepGraph > 2*healthDeepGraph) Red badge

🀚 Do you want to develop this feature yourself?

  • Yes
  • No (probably no)

Missing relationships & failure to ignore a module

πŸ› Describe the bug

Hello,

first of all, thank you for this plugin, seems very usable in the early state it's in. This is definitly something that has been missing in the ecosystem for a while.

I've tried it out in a project that we're using as an example for clean architecture, thus it has a lot of modules.

But between the outgoing infra modules (sqldb, events-out) it seems to be missing the relations to the domain module (as they implement domain interfaces).

Also, I am able to ignore the vocabulary module (to which a lot of dependencies point at to make use of typed id classes etc), but I am unable to ignore the main partition module that points towards every module to bring it together to make it runnable.

⚠️ Current behavior

  • missing following relationships between modules:
  :code:infrastructure:outgoing:sqldb --> :code:application:domain
  :code:infrastructure:outgoing:events-out --> :code:application:domain
  • has these relationships that I would not expect because of ignoring the main module, and the subgraph
  subgraph :code
    :code:main["main"]
  end

  :code:main --> :code:application:usecases-api
  :code:main --> :code:application:usecases
  :code:main --> :code:application:policy-api
  :code:main --> :code:application:policy
  :code:main --> :code:infrastructure:incoming:commandbus
  :code:main --> :code:infrastructure:incoming:events-in
  :code:main --> :code:infrastructure:incoming:rest
  :code:main --> :code:infrastructure:incoming:validation
  :code:main --> :code:infrastructure:outgoing:events-out
  :code:main --> :code:infrastructure:outgoing:sqldb

βœ… Expected behavior

This is the output that I would have expected from the plugin with the current config:

%%{
  init: {
    'theme': 'neutral'
  }
}%%

graph LR
  subgraph :code:application
    :code:application:usecases["usecases"]
    :code:application:usecases-api["usecases-api"]
    :code:application:domain-events["domain-events"]
    :code:application:policy-api["policy-api"]
    :code:application:policy["policy"]
    :code:application:domain["domain"]
  end
  subgraph :code:infrastructure:incoming
    :code:infrastructure:incoming:commandbus["commandbus"]
    :code:infrastructure:incoming:events-in["events-in"]
    :code:infrastructure:incoming:rest["rest"]
    :code:infrastructure:incoming:validation["validation"]
  end
  subgraph :code:infrastructure:outgoing
    :code:infrastructure:outgoing:events-out["events-out"]
    :code:infrastructure:outgoing:sqldb["sqldb"]
  end
  :code:application:usecases --> :code:application:usecases-api
  :code:application:usecases --> :code:application:domain-events
  :code:infrastructure:incoming:commandbus --> :code:application:usecases-api
  :code:infrastructure:incoming:events-in --> :code:application:domain-events
  :code:infrastructure:incoming:events-in --> :code:application:policy-api
  :code:application:policy --> :code:application:policy-api
  :code:application:policy --> :code:application:usecases-api
  :code:application:policy --> :code:application:domain-events
  :code:application:domain --> :code:application:domain-events
  :code:application:policy-api --> :code:application:domain-events
  :code:infrastructure:incoming:rest --> :code:application:usecases-api
  :code:infrastructure:incoming:validation --> :code:application:usecases-api
  :code:infrastructure:outgoing:sqldb --> :code:application:domain
  :code:infrastructure:outgoing:events-out --> :code:application:domain
Loading

πŸ’£ Steps to reproduce

Project can be found here:
https://gitlab.rotate-it.be/tripled/triple-todo/-/tree/main

Generated the graph using: ./gradlew createModuleGraph

πŸ“· Screenshots

expected:
image

vs
current:
image

Ignore certain modules

πŸ’‘ Describe the solution you'd like

It would be nice to be able to ignore certain modules.
My use case for this is that we have a "test-fixtures" module which is depended on from almost all other modules and also depends on a lot of modules. It is only used for testing, so I don't really care about it tbh, and it makes the graph super uggly and unnecessarily complex.

I guess another way of achieving it is if we could choose certain buildTypes (or similar) to ignore. For example I can say I want to ignore all dependenceis like testImplementation, androidTestImplementation, debugImplementation etc. Then we could also not show modules that after these rules are not depended on by any other maybe πŸ€”

🀚 Do you want to develop this feature yourself?

Same as the other feature request, I could take a look if I have some time, but right now it's really hard.

Thank you for doing this plugin btw, it's really a good source for documentation of the project that is in a good format for VCS (mermaid) and is always up to date! πŸ‘

Automatically create README.md (if it doesn't exist)

⚠️ Is your feature request related to a problem? Please describe

It would be great to set this up in a new project with single module and in these projects, task fails with README.md (No such file or directory).

πŸ’‘ Describe the solution you'd like

When the task createModuleGraph is run, create README.md automatically if it doesn't exist.

🀚 Do you want to develop this feature yourself?

  • Yes
  • No

Use regex to exclude modules from graph

πŸ’‘ Describe the solution you'd like

Allows the user to set up regex patterns to exclude specific modules during graph generation.

🀚 Do you want to develop this feature yourself?

  • Yes
  • No

Empty Graph is getting generated

πŸ› Describe the bug

Empty Graph is getting generated each time.

My Modified README file

Dependency Diagram

```mermaid
%%{
  init: {
    'theme': 'neutral'
  }
}%%

graph LR



## Gradle Wrapper

distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-all.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists



## πŸ“± Tech info
 - Android Studio Electric Eel | 2022.1.1 Patch 2
 - Mac OS: 13.3 (22E252)

Support applying this plugin in the root build.gradle file

So I'll start off by saying that I am not sure if this is a bug and setting this plugin in the root build.gradle doesn't work or if my setup is somehow unconventional and brings this problem, but let me explain.

In the line dependencies.forEach { (source, targets) ->, for all the other modules it works perfectly fine. But one of those is also the root module, where source is ":", just a string with a colon.
This results in when these lines are hit, and we try to do last() but the list after trying to split on ":" is empty, so it crashes.

One thing I did locally to make this work was replacing

targets.forEach { target ->
    val sourceName = source.split(":").last { it.isNotBlank() }
    val targetName = target.split(":").last { it.isNotBlank() }

with

targets.forEach targetsForEach@{ target ->
  val sourceName = source.split(":").lastOrNull() { it.isNotBlank() } ?: return@targetsForEach
  val targetName = target.split(":").lastOrNull() { it.isNotBlank() } ?: return@targetsForEach

And this just works, since it ignores that module. Could even replace that section with something like this

dependencies.forEach { (source, targets) ->
  if (source == ":") return@forEach
  targets.forEach { target ->
    val sourceName = source.split(":").last { it.isNotBlank() }
    val targetName = target.split(":").last { it.isNotBlank() }
    if (sourceName != targetName) {
      arrows += "  $sourceName --> $targetName\n"
    }
  }
}

and it also works fine that way too.

Is it possible to print the longest graph path with this plugin?

⚠️ Is your feature request related to a problem? Please describe

Is it possible to print the longest graph path with this plugin?

πŸ’‘ Describe the solution you'd like

I want to find and highlight the longest path of graph dependency?

🀚 Do you want to develop this feature yourself?

  • Yes
  • No

Can't generate module graph

Using the latest version of plugin in a kotlin multiplatform multi module project and createModuleGraph task fails with the following error.

> Task :createModuleGraph FAILED
List contains no element matching the predicate.
java.util.NoSuchElementException: List contains no element matching the predicate.
        at dev.iurysouza.modulegraph.graph.ExtKt.getProjectName(Ext.kt:16)
        at dev.iurysouza.modulegraph.graph.DigraphBuilder.buildModel(DigraphBuilder.kt:46)
        at dev.iurysouza.modulegraph.graph.DigraphBuilder.build(DigraphBuilder.kt:19)
        at dev.iurysouza.modulegraph.Mermaid.generateGraph(Mermaid.kt:13)
        at dev.iurysouza.modulegraph.gradle.CreateModuleGraphTask.execute(CreateModuleGraphTask.kt:91)
        at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
        at java.base/java.lang.reflect.Method.invoke(Method.java:580)

`maxTextSize` config example

⚠️ Is your feature request related to a problem? Please describe

Getting "Maximum text size in diagram exceeded" error in large project, and I can't figure out how to change the maxTextSize property in config.

πŸ’‘ Describe the solution you'd like

An example how to change 'maxTextSize'

🀚 Do you want to develop this feature yourself?

  • Yes
  • No

Dependency edges are duplicated.

πŸ› Describe the bug

When createModuleGraph task is executed in this project, there are duplicated dependency edges.

Maybe when a gradle module has multi sourceSets, this problem occurred .

⚠️ Current behavior

A dependency(for examplealpha --> beta) edges are duplicated.

βœ… Expected behavior

A dependency represents only one edge.

πŸ’£ Steps to reproduce

  1. clone this repository https://github.com/hisakaz0/conference-app-2023/
  2. switch branch to mod-graph.
  3. ./gradlew createModuleGraph

πŸ“· Screenshots

πŸ“± Tech info

  • Device:
  • OS: macOS 13.5.1 (22G90)
  • Library/App version: 0.4.0

Add more options for the way the mermaid graph is generated

I personally enjoy when a tree like this looks vertical instead of horizontal, so you get a view like this for example (taken from here) instead of horizontal, which I personally have a bit of a harder time reading.

So adding such an option and others in the future would be a great addition to give more customisation options. Not sure how many exist for mermaid diagrams, as this is the first time I ever hear about them, but I'm sure there's a lot of room for creativity here :D

Empty generated graph

After applying the plugin (version 0.4.0) to our project, the createModuleGraph task runs successfully, but the created graph is empty. I have seen #10 and in our case ./gradlew projects gives output of 100+ projects.

This is currently created graph:

### Dependency Diagram

```mermaid
%%{
  init: {
    'theme': 'dark'
  }
}%%

graph TB


```

I tried looking into the issue a bit and it seems that the parseProjectStructure() method is called before the projects are configured, so sourceProject.configurations is empty at the time.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.