Skip to content

Commit

Permalink
Added cycle issue finder
Browse files Browse the repository at this point in the history
  • Loading branch information
rbaul authored and rbaul committed Feb 6, 2024
1 parent 7e5cb33 commit f30b4c7
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface ProjectDto {
// connections?: Map<String, String[]>,
connections?: any,
dependencies?: any,
librariesCycle?: [string[]],
projectVersion: ProjectVersionLiteDto
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,22 @@
<div #visNetworkContainer class="vis-network-canvas"></div>
<p-toolbar class="network-toolbar" styleClass="vertical-align">
<div class="p-toolbar-group-start">
<p-button icon="pi pi-arrows-alt" class="mr-2" (click)="fit()" tooltipPosition="top" pTooltip="Fit to screen"></p-button>
<p-button icon="pi pi-search-plus" class="mr-2" (click)="zoomIn()" tooltipPosition="top" pTooltip="Zoom in"></p-button>
<p-button icon="pi pi-search-minus" class="mr-2" (click)="zoomOut()" tooltipPosition="top" pTooltip="Zoom out"></p-button>
<p-button icon="pi pi-window-maximize" class="mr-2" (click)="collapseAll()" tooltipPosition="top" pTooltip="Collapse all"></p-button>
<p-button icon="pi pi-window-minimize" class="mr-2" (click)="uncollapseAll()" tooltipPosition="top" pTooltip="Uncollapse all"></p-button>
<p-button icon="pi pi-share-alt" [disabled]="isNodeSelected()" class="mr-2" (click)="showDependenciesBySelectedNode()" tooltipPosition="top" pTooltip="Show dependencies"></p-button>
<p-button icon="pi pi-link" [disabled]="isNodeSelected()" class="mr-2" (click)="showNeighborsBySelectedNode()" tooltipPosition="top" pTooltip="Show neighbors"></p-button>
<p-button icon="pi pi-filter-slash" [disabled]="isFilterApplied()" class="mr-2" (click)="clearAppFilter()" tooltipPosition="top" pTooltip="Clear filters"></p-button>
<p-button icon="pi pi-arrows-alt" class="mr-2" (click)="fit()" tooltipPosition="top"
pTooltip="Fit to screen"></p-button>
<p-button icon="pi pi-search-plus" class="mr-2" (click)="zoomIn()" tooltipPosition="top"
pTooltip="Zoom in"></p-button>
<p-button icon="pi pi-search-minus" class="mr-2" (click)="zoomOut()" tooltipPosition="top"
pTooltip="Zoom out"></p-button>
<p-button icon="pi pi-window-maximize" class="mr-2" (click)="collapseAll()" tooltipPosition="top"
pTooltip="Collapse all"></p-button>
<p-button icon="pi pi-window-minimize" class="mr-2" (click)="uncollapseAll()" tooltipPosition="top"
pTooltip="Uncollapse all"></p-button>
<p-button icon="pi pi-share-alt" [disabled]="isNodeSelected()" class="mr-2"
(click)="showDependenciesBySelectedNode()" tooltipPosition="top" pTooltip="Show dependencies"></p-button>
<p-button icon="pi pi-link" [disabled]="isNodeSelected()" class="mr-2" (click)="showNeighborsBySelectedNode()"
tooltipPosition="top" pTooltip="Show neighbors"></p-button>
<p-button icon="pi pi-filter-slash" [disabled]="isFilterApplied()" class="mr-2" (click)="clearAppFilter()"
tooltipPosition="top" pTooltip="Clear filters"></p-button>


<p-multiSelect [options]="apps" [(ngModel)]="selectedApps" optionLabel="name" [showClear]="true"
Expand All @@ -38,10 +46,14 @@
(onChange)="onFilterOwners($event.value)">
</p-multiSelect>

<p-multiSelect [options]="typeOptions" [(ngModel)]="selectedTypes" [showClear]="true"
(onClear)="clearAppFilter()" placeholder="Select relevant types" display="chip"
(onChange)="onFilterTypes($event.value)">
<p-multiSelect [options]="typeOptions" [(ngModel)]="selectedTypes" [showClear]="true" (onClear)="clearAppFilter()"
placeholder="Select relevant types" display="chip" (onChange)="onFilterTypes($event.value)">
</p-multiSelect>


<p-dropdown [style]="{'width': '300px'}" [options]="cycleIssues" [(ngModel)]="selectedCycleIssue" [showClear]="true"
placeholder="Select a cycle issue" (onChange)="onCycleIssueSelect($event.value)"></p-dropdown>

</div>
</p-toolbar>

Expand All @@ -51,4 +63,4 @@ <h4>Custom Content</h4>
</ng-template>
</p-overlayPanel> -->

<!-- <p-contextMenu #contextMenu [model]="items" [target]="visNetworkContainer" [hidden]="isContextHidden()"></p-contextMenu> -->
<!-- <p-contextMenu #contextMenu [model]="items" [target]="visNetworkContainer" [hidden]="isContextHidden()"></p-contextMenu> -->
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { DataSet, IdType, Network } from 'vis-network/standalone';
import { ApplicationLiteDto, ApplicationType } from '../api/application-api.model';
import { COLLAPSED_NAME, GROUP_MARGIN, MOVE_TO_SCALE, NODE_HEIGHT, NODE_WIDTH, SCALE_FACTORY, topologyOptions } from './project-topology.const';


interface GroupCluster {
id?: string,
name?: string,
Expand Down Expand Up @@ -47,6 +48,7 @@ export enum TopologyType {
DropdownModule,
FormsModule,
ButtonModule,
DropdownModule
// ContextMenuModule
],
templateUrl: './project-topology.component.html',
Expand Down Expand Up @@ -86,6 +88,8 @@ export class ProjectTopologyComponent implements OnInit, AfterViewInit, OnChange
selectedApp?: ApplicationLiteDto;

selectedNodes: string[] = [];
cycleIssues?: [string[]];
selectedCycleIssue?: string[];

// items: MenuItem[] = [];

Expand Down Expand Up @@ -334,6 +338,8 @@ export class ProjectTopologyComponent implements OnInit, AfterViewInit, OnChange

this.moveToAppOptions = this.data.applications;

this.cycleIssues = this.data.librariesCycle;

const edges: any[] = [];

if (this.type === TopologyType.ALL || this.type === TopologyType.MICROSERVICES) {
Expand Down Expand Up @@ -504,6 +510,11 @@ export class ProjectTopologyComponent implements OnInit, AfterViewInit, OnChange
this.onFilterApps(applictionsToShow);
}

onCycleIssueSelect(cycleIssue: string[]): void {
const applictionsToShow = this.apps.filter((app) => cycleIssue.includes(app.name));
this.onFilterApps(applictionsToShow);
}

/**
* Move to specific node change
*/
Expand Down
7 changes: 6 additions & 1 deletion microservice-visualization/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.flywaydb:flyway-core'


// OpenApi
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.+'

Expand All @@ -39,6 +38,12 @@ dependencies {
// GitHub
implementation 'org.kohsuke:github-api:1.318'

// JGraphT
implementation 'org.jgrapht:jgrapht-core:1.5.2'

// Hibernate types
implementation 'io.hypersistence:hypersistence-utils-hibernate-63:3.7.1'


// WebApp
implementation project(':microservice-visualization-webapp')
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,15 @@
package com.github.rbaul.microservice_visualization.domain.model;

import jakarta.persistence.CascadeType;
import jakarta.persistence.ElementCollection;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import io.hypersistence.utils.hibernate.type.json.JsonType;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotEmpty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.annotations.Type;
import org.springframework.util.CollectionUtils;

import java.util.HashSet;
Expand Down Expand Up @@ -52,6 +46,12 @@ public class Project {
@ToString.Exclude
@ElementCollection
private Map<String, List<String>> dependencies;

@ToString.Exclude
@Type(JsonType.class)
@Column(columnDefinition = "json")
@Basic(fetch = FetchType.LAZY)
private Set<List<String>> librariesCycle;

@ToString.Exclude
@ElementCollection
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package com.github.rbaul.microservice_visualization.service;

import lombok.experimental.UtilityClass;
import lombok.extern.slf4j.Slf4j;
import org.jgrapht.Graph;
import org.jgrapht.GraphPath;
import org.jgrapht.alg.cycle.PatonCycleBase;
import org.jgrapht.graph.DefaultEdge;
import org.jgrapht.graph.SimpleGraph;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

@Slf4j
@UtilityClass
public class AlgorithmUtils {

// Function to find all simple cycles in an undirected graph using Paton's algorithm
public Set<List<String>> findSimpleCycles(Map<String, List<String>> connections) {
Graph<String, CustomEdge> graph = new SimpleGraph<>(CustomEdge.class);

// Vertex
connections.keySet().forEach(graph::addVertex);

// Edges
connections.forEach((vertex, cons) -> {
for (String con : cons) {
graph.addEdge(vertex, con, new CustomEdge());
}
});

return findSimpleCycles(graph);
}

// Function to find all simple cycles in an undirected graph using Paton's algorithm
private Set<List<String>> findSimpleCycles(Graph<String, CustomEdge> graph) {
PatonCycleBase<String, CustomEdge> cycleBase = new PatonCycleBase<>(graph);
return cycleBase.getCycleBasis().getCyclesAsGraphPaths().stream().map(GraphPath::getVertexList).collect(Collectors.toSet());
}

// Custom edge class (required by JGraphT for undirected graphs)
private static class CustomEdge extends DefaultEdge {
// You can add custom properties if needed
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import com.github.rbaul.microservice_visualization.config.MicroserviceVisualizationProperties;
import com.github.rbaul.microservice_visualization.domain.model.*;
import com.github.rbaul.microservice_visualization.service.AlgorithmUtils;
import com.github.rbaul.microservice_visualization.service.model.ApplicationDependency;
import com.github.rbaul.microservice_visualization.service.model.ProjectConfig;
import com.github.rbaul.microservice_visualization.utils.ConverterUtils;
Expand Down Expand Up @@ -125,6 +126,28 @@ protected Map<String, List<String>> createDependenciesMap(Project project) {
return appConnections;
}

protected Map<String, List<String>> createLibDependenciesMap(Project project) {
Map<String, List<String>> appConnections = new HashMap<>();
Set<String> appNames = project.getApplications().stream()
.filter(application -> application.getType() == ApplicationType.LIBRARY).map(Application::getName).collect(Collectors.toSet());

project.getApplications().forEach(application -> {
if (application.getType() == ApplicationType.LIBRARY) {
appConnections.put(application.getName(), new ArrayList<>());
if (application.getDependencies() != null) {
application.getDependencies().forEach(dep -> {
Dependency dependency = ConverterUtils.convertDependency(dep);
if (!application.getName().equals(dependency.name()) && appNames.contains(dependency.name())) {
appConnections.get(application.getName()).add(dependency.name());
}
});
}
}
});

return appConnections;
}

protected Set<String> createProjectRelevantTags(Project project) {
Set<String> tags = new HashSet<>();

Expand Down Expand Up @@ -214,6 +237,10 @@ protected void setApplicationToProject(Project project, ProjectConfig projectCon
project.setConnections(createTopology(project, projectConfig.getApplicationPostfix(), projectConfig.getApplicationApiPostfixes()));
// Dependencies
project.setDependencies(createDependenciesMap(project));

// Cycle dependencies
project.setLibrariesCycle(AlgorithmUtils.findSimpleCycles(createLibDependenciesMap(project)));

// Relevant Tags
project.setTags(createProjectRelevantTags(project));
if (!CollectionUtils.isEmpty(projectConfig.getGroups())) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public class ProjectDto {
private Map<String, List<String>> connections;

private Map<String, List<String>> dependencies;

private Set<List<String>> librariesCycle;

private List<ApplicationGroupDto> groups;

Expand Down

0 comments on commit f30b4c7

Please sign in to comment.