-
-
Notifications
You must be signed in to change notification settings - Fork 46
Set url on chart bubble #224
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0a87452
74f8b28
9526b05
603b3d5
2dc2c6d
48990a7
092a199
de6277e
db51c2e
09b494a
377b6af
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -77,7 +77,7 @@ public void execute( | |
| log.info("RefactorFirst JSON successfully generated at {}", targetFile.toAbsolutePath()); | ||
|
|
||
| // Copy Mustache template and viewer to .refactorfirst directory | ||
| copyViewerResources(dotRefactorFirstDir); | ||
| // copyViewerResources(dotRefactorFirstDir); | ||
| } | ||
|
|
||
| /** Copies the Mustache template and browser viewer into the report directory. */ | ||
|
|
@@ -254,13 +254,25 @@ public RefactorFirstReportDTO generateReportData( | |
| String startVertex = vertexes[0].trim(); | ||
| String endVertex = vertexes[1].trim(); | ||
|
|
||
| String sourcePath = | ||
| codebaseGraphDTO.getClassToSourceFilePathMapping().get(startVertex); | ||
| String targetPath = | ||
| codebaseGraphDTO.getClassToSourceFilePathMapping().get(endVertex); | ||
| String sourceUrl = (sourcePath != null && !sourcePath.isBlank()) ? repoUrl + sourcePath : ""; | ||
| String targetUrl = (targetPath != null && !targetPath.isBlank()) ? repoUrl + targetPath : ""; | ||
|
|
||
| // Build rendered label with links to class files | ||
| String renderedLabel = | ||
| buildClassLinkLabel(startVertex, endVertex, sourceUrl, targetUrl, classesToRemove, (int) | ||
| classGraph.getEdgeWeight(edgeInfo.getEdge())); | ||
|
|
||
| classRelList.add(ClassRelationshipDTO.builder() | ||
| .sourceClass(startVertex) | ||
| .targetClass(endVertex) | ||
| .sourceMarked(classesToRemove.contains(startVertex)) | ||
| .targetMarked(classesToRemove.contains(endVertex)) | ||
| .weight((int) classGraph.getEdgeWeight(edgeInfo.getEdge())) | ||
| .renderedLabel(renderPlainClassEdge(edgeInfo.getEdge())) | ||
| .renderedLabel(renderedLabel) | ||
| .priority(edgeInfo.getPriority()) | ||
| .cycleCount(edgeInfo.getCycleCount()) | ||
| .effortRank(edgeInfo.getEffortRank()) | ||
|
|
@@ -300,16 +312,81 @@ public RefactorFirstReportDTO generateReportData( | |
| String startVertex = vertexes[0].trim(); | ||
| String endVertex = vertexes[1].trim(); | ||
|
|
||
| // Extract package URLs from class relationships in the package edge | ||
| String sourceUrl = ""; | ||
| String targetUrl = ""; | ||
| Set<DefaultWeightedEdge> classRelationshipsInPackageRelationship = codebaseGraphDTO | ||
| .getClassRelationshipsInPackageRelationship() | ||
| .get(edgeInfo.getEdge()); | ||
| if (classRelationshipsInPackageRelationship != null) { | ||
| for (DefaultWeightedEdge classEdge : classRelationshipsInPackageRelationship) { | ||
| String[] classVertexes = extractVertexes(classEdge); | ||
| String classStart = classVertexes[0].trim(); | ||
| String classEnd = classVertexes[1].trim(); | ||
|
|
||
| // Get source file path for each class vertex | ||
| String classStartPath = codebaseGraphDTO | ||
| .getClassToSourceFilePathMapping() | ||
| .get(classStart); | ||
| String classEndPath = codebaseGraphDTO | ||
| .getClassToSourceFilePathMapping() | ||
| .get(classEnd); | ||
|
|
||
| // Derive package directory from source file path | ||
| if (classStartPath != null && !classStartPath.isBlank()) { | ||
| int lastSlash = classStartPath.lastIndexOf('/'); | ||
| if (lastSlash != -1) { | ||
| String packageDir = classStartPath.substring(0, lastSlash + 1); | ||
| // Check if this class belongs to the source package | ||
| if (isClassInPackage(classStart, startVertex, codebaseGraphDTO)) { | ||
| sourceUrl = repoUrl + packageDir; | ||
| } | ||
| // Check if this class belongs to the target package | ||
| if (isClassInPackage(classStart, endVertex, codebaseGraphDTO)) { | ||
| targetUrl = repoUrl + packageDir; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| if (classEndPath != null && !classEndPath.isBlank()) { | ||
| int lastSlash = classEndPath.lastIndexOf('/'); | ||
| if (lastSlash != -1) { | ||
| String packageDir = classEndPath.substring(0, lastSlash + 1); | ||
| // Check if this class belongs to the source package | ||
| if (isClassInPackage(classEnd, startVertex, codebaseGraphDTO)) { | ||
| sourceUrl = repoUrl + packageDir; | ||
| } | ||
| // Check if this class belongs to the target package | ||
| if (isClassInPackage(classEnd, endVertex, codebaseGraphDTO)) { | ||
| targetUrl = repoUrl + packageDir; | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Fallback to package name conversion if no class edges found | ||
| if (sourceUrl.isBlank()) { | ||
| sourceUrl = repoUrl + startVertex.replace('.', '/'); | ||
| } | ||
| if (targetUrl.isBlank()) { | ||
| targetUrl = repoUrl + endVertex.replace('.', '/'); | ||
| } | ||
|
|
||
| List<String> breakClassRels = | ||
| cells.length > 4 && !cells[4].isBlank() ? List.of(cells[4].split("<br>")) : List.of(); | ||
|
|
||
| // Build rendered label with links to package directories | ||
| String renderedLabel = | ||
| buildPackageLinkLabel(cells[0], startVertex, endVertex, sourceUrl, targetUrl, packagesToRemove); | ||
|
|
||
| packageRelList.add(PackageRelationshipDTO.builder() | ||
| .sourcePackage(startVertex) | ||
| .targetPackage(endVertex) | ||
| .sourceMarked(packagesToRemove.contains(startVertex)) | ||
| .targetMarked(packagesToRemove.contains(endVertex)) | ||
| .weight((int) packageGraph.getEdgeWeight(edgeInfo.getEdge())) | ||
| .renderedLabel(cells[0]) | ||
| .renderedLabel(renderedLabel) | ||
| .priority(edgeInfo.getPriority()) | ||
| .cycleCount(edgeInfo.getCycleCount()) | ||
| .effortRank(edgeInfo.getEffortRank()) | ||
|
|
@@ -358,7 +435,7 @@ public RefactorFirstReportDTO generateReportData( | |
| for (String vertex : largestCycle.getVertexSet()) { | ||
| String className; | ||
| if (classesToRemove.contains(vertex)) { | ||
| className = hyperlinkClass(vertex, repoUrl, codebaseGraphDTO) + "<strong>*</strong>"; | ||
| className = hyperlinkClass(vertex, repoUrl, codebaseGraphDTO) + "*"; | ||
| } else { | ||
| className = hyperlinkClass(vertex, repoUrl, codebaseGraphDTO); | ||
| } | ||
|
|
@@ -368,7 +445,7 @@ public RefactorFirstReportDTO generateReportData( | |
| if (edge.toString().startsWith("(" + vertex + " :")) { | ||
| if (classRelationshipsToRemove.contains(edge)) { | ||
| edges.append("<strong>"); | ||
| edges.append(renderClassEdge(edge) + "<strong>*</strong>"); | ||
| edges.append(renderClassEdge(edge) + "*"); | ||
| edges.append("</strong>"); | ||
| } else { | ||
| edges.append(renderClassEdge(edge)); | ||
|
|
@@ -426,17 +503,6 @@ public RefactorFirstReportDTO generateReportData( | |
| } | ||
| } | ||
|
|
||
| /** Renders a relationship label as plain text for Mustache's escaped interpolation. */ | ||
| private String renderPlainClassEdge(DefaultWeightedEdge edge) { | ||
| String[] vertexes = extractVertexes(edge); | ||
| String startVertex = vertexes[0].trim(); | ||
| String endVertex = vertexes[1].trim(); | ||
| String startMarker = classesToRemove.contains(startVertex) ? "*" : ""; | ||
| String endMarker = classesToRemove.contains(endVertex) ? "*" : ""; | ||
| return getClassName(startVertex) + startMarker + " → " + getClassName(endVertex) + endMarker + " : " | ||
| + (int) classGraph.getEdgeWeight(edge); | ||
| } | ||
|
|
||
| /** Converts ranked instances of one disharmony type into chart and table data. */ | ||
| private DisharmonySectionDTO buildDisharmonySection( | ||
| DisharmonySpec spec, boolean showDetails, List<RankedDisharmony> ranked, String repoUrl) { | ||
|
|
@@ -449,13 +515,15 @@ private DisharmonySectionDTO buildDisharmonySection( | |
| String label = rd.getFileName() != null | ||
| ? rd.getFileName() | ||
| : rd.getRawPriority().toString(); | ||
| String url = repoUrl + rd.getPath(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Bubble source links never open Clicking a bubble cannot open its new Learn moreThe JSON report contains a URL for each chart bubble, but the viewer maps bubbles to Chart.js points without registering a click handler. The tooltip only displays the label and ranking. A user can open a source link from the table, but clicking the corresponding bubble does nothing. Example: A bubble for Recommended fix: Update initBubbleChart to handle clicks on chart elements and open the clicked point's Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| bubbles.add(createBubble( | ||
| rd.getFileName(), | ||
| label, | ||
| rd.getEffortRank(), | ||
| rd.getChangePronenessRank(), | ||
| rd.getPriority(), | ||
| maxPriority)); | ||
| maxPriority, | ||
| url)); | ||
| } | ||
|
|
||
| DisharmonyChartDTO chartDTO = DisharmonyChartDTO.builder() | ||
|
|
@@ -615,7 +683,13 @@ private DisharmonySectionDTO buildDisharmonySection( | |
|
|
||
| /** Creates a chart bubble whose size and color reflect the finding priority. */ | ||
| public ChartJsBubbleDTO createBubble( | ||
| String id, String label, int effortRank, int changePronenessRank, int priority, int maxPriority) { | ||
| String id, | ||
| String label, | ||
| int effortRank, | ||
| int changePronenessRank, | ||
| int priority, | ||
| int maxPriority, | ||
| String url) { | ||
|
|
||
| int minRadius = 6; | ||
| int maxRadius = 24; | ||
|
|
@@ -655,6 +729,7 @@ public ChartJsBubbleDTO createBubble( | |
| .changePronenessRank(changePronenessRank) | ||
| .color(color) | ||
| .borderColor(borderColor) | ||
| .url(url) | ||
| .build(); | ||
| } | ||
|
|
||
|
|
@@ -667,4 +742,143 @@ private static String graphIdentifier(String value) { | |
| } | ||
| return "graph_" + sanitized + "_" + Integer.toUnsignedString(original.hashCode(), 36); | ||
| } | ||
|
|
||
| /** Checks if a class belongs to a given package by checking the fully qualified class name. */ | ||
| private boolean isClassInPackage(String className, String packageName, CodebaseGraphDTO codebaseGraphDTO) { | ||
| if (className == null || packageName == null) { | ||
| return false; | ||
| } | ||
| // Check if the class name starts with the package name followed by a dot | ||
| int lastDot = className.lastIndexOf('.'); | ||
| return lastDot > 0 && className.substring(0, lastDot).equals(packageName); | ||
| } | ||
|
|
||
| /** | ||
| * Builds a rendered label with HTML links to package directories. | ||
| * Transforms "org.hjug.graphbuilder.metrics → org.hjug.graphbuilder<strong>*</strong> : 1" | ||
| * into "<a href="...">org.hjug.graphbuilder.metrics</a> → <a href="...">org.hjug.graphbuilder</a>* : 1" | ||
| */ | ||
| private String buildPackageLinkLabel( | ||
| String originalLabel, | ||
| String startVertex, | ||
| String endVertex, | ||
| String sourceUrl, | ||
| String targetUrl, | ||
| Set<String> packagesToRemove) { | ||
| if (originalLabel == null || originalLabel.isBlank()) { | ||
| return originalLabel; | ||
| } | ||
|
|
||
| // The original label format is: "startPackage → endPackage : weight" | ||
| // We need to wrap the package names with links | ||
| String arrow = " → "; | ||
| String weightSeparator = " : "; | ||
|
|
||
| // Split the label into parts | ||
| String[] parts = originalLabel.split(weightSeparator); | ||
| if (parts.length < 2) { | ||
| return originalLabel; | ||
| } | ||
|
|
||
| String edgePart = parts[0]; | ||
| String weight = parts[1]; | ||
|
|
||
| // Split the edge part by arrow | ||
| String[] packages = edgePart.split(arrow, 2); | ||
| if (packages.length < 2) { | ||
| return originalLabel; | ||
| } | ||
|
|
||
| String startPackage = packages[0].trim(); | ||
| String endPackage = packages[1].trim(); | ||
|
|
||
| // Check if start package has the removal marker (escaped or unescaped) | ||
| boolean startMarked = | ||
| startPackage.contains("<strong>*</strong>") || startPackage.contains("<strong>*</strong>"); | ||
| if (startMarked) { | ||
| startPackage = startPackage.replace("<strong>*</strong>", "").replace("<strong>*</strong>", ""); | ||
| } | ||
|
|
||
| // Check if end package has the removal marker (escaped or unescaped) | ||
| boolean endMarked = | ||
| endPackage.contains("<strong>*</strong>") || endPackage.contains("<strong>*</strong>"); | ||
| if (endMarked) { | ||
| endPackage = endPackage.replace("<strong>*</strong>", "").replace("<strong>*</strong>", ""); | ||
| } | ||
|
|
||
| // Build the new label with links | ||
| StringBuilder newLabel = new StringBuilder(); | ||
| newLabel.append("<a href=\"") | ||
| .append(escapeHtmlAttribute(sourceUrl)) | ||
| .append("\" target=\"_blank\">") | ||
| .append(escapeHtmlLabel(startPackage)) | ||
| .append("</a>"); | ||
| if (startMarked) { | ||
| newLabel.append("*"); | ||
| } | ||
| newLabel.append(arrow); | ||
| newLabel.append("<a href=\"") | ||
| .append(escapeHtmlAttribute(targetUrl)) | ||
| .append("\" target=\"_blank\">") | ||
| .append(escapeHtmlLabel(endPackage)) | ||
| .append("</a>"); | ||
| if (endMarked) { | ||
| newLabel.append("*"); | ||
| } | ||
| newLabel.append(weightSeparator).append(weight); | ||
|
|
||
| return newLabel.toString(); | ||
| } | ||
|
|
||
| /** | ||
| * Builds a rendered label with HTML links to class files. | ||
| * Transforms "GraphMetricsCollector → DependencyCollector : 1" | ||
| * into "<a href="...">GraphMetricsCollector</a> → <a href="...">DependencyCollector</a>* : 1" | ||
| */ | ||
| private String buildClassLinkLabel( | ||
| String startVertex, | ||
| String endVertex, | ||
| String sourceUrl, | ||
| String targetUrl, | ||
| Set<String> classesToRemove, | ||
| int weight) { | ||
| // Extract simple class names for display | ||
| String startClass = getClassName(startVertex); | ||
| String endClass = getClassName(endVertex); | ||
|
|
||
| // Check if classes are marked for removal | ||
| boolean startMarked = classesToRemove.contains(startVertex); | ||
| boolean endMarked = classesToRemove.contains(endVertex); | ||
|
|
||
| // Build the new label with links | ||
| StringBuilder newLabel = new StringBuilder(); | ||
| if (sourceUrl != null && !sourceUrl.isBlank()) { | ||
| newLabel.append("<a href=\"") | ||
| .append(escapeHtmlAttribute(sourceUrl)) | ||
| .append("\" target=\"_blank\">") | ||
| .append(escapeHtmlLabel(startClass)) | ||
| .append("</a>"); | ||
| } else { | ||
| newLabel.append(escapeHtmlLabel(startClass)); | ||
| } | ||
| if (startMarked) { | ||
| newLabel.append("*"); | ||
| } | ||
| newLabel.append(" → "); | ||
| if (targetUrl != null && !targetUrl.isBlank()) { | ||
| newLabel.append("<a href=\"") | ||
| .append(escapeHtmlAttribute(targetUrl)) | ||
| .append("\" target=\"_blank\">") | ||
| .append(escapeHtmlLabel(endClass)) | ||
| .append("</a>"); | ||
| } else { | ||
| newLabel.append(escapeHtmlLabel(endClass)); | ||
| } | ||
| if (endMarked) { | ||
| newLabel.append("*"); | ||
| } | ||
| newLabel.append(" : ").append(weight); | ||
|
|
||
| return newLabel.toString(); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.