Skip to content

Commit

Permalink
feat: add detail view to hotspot treemap
Browse files Browse the repository at this point in the history
  • Loading branch information
manfredsteyer committed Oct 13, 2024
1 parent bfc6cd6 commit 0292893
Show file tree
Hide file tree
Showing 10 changed files with 296 additions and 186 deletions.
7 changes: 6 additions & 1 deletion apps/backend/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@
"options": {
"buildTarget": "backend:build",
"runBuildTargetDependencies": false,
"args": ["--path", ".", "--open", "false"]
"args": [
"--path",
"/Users/manfredsteyer/projects/public/standalone-example-cli",
"--open",
"false"
]
},
"configurations": {
"development": {
Expand Down
40 changes: 15 additions & 25 deletions apps/frontend/src/app/features/hotspot/hotspot-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,9 @@ import { ChartEvent, InteractionItem } from 'chart.js';
import { AggregatedHotspot } from '../../model/hotspot-result';
import { TreeMapChartConfig } from '../../ui/treemap/treemap.component';

type HotspotDataSet = {
tree: AggregatedHotspot[];
export type ScoreType = 'hotspot' | 'warning' | 'fine';
export type AggregatedHotspotWithType = AggregatedHotspot & {
type: ScoreType;
};

export function toTreeMapConfig(
Expand All @@ -19,16 +20,6 @@ export function toTreeMapConfig(
]);

const options = {
onClick: (_event: ChartEvent, elements: InteractionItem[]) => {
if (elements.length > 0) {
const element = elements[elements.length - 1];
const dataIndex = element.index;
const dataset = config.data.datasets[0] as unknown as HotspotDataSet;
const tree = dataset.tree;
const item = tree[dataIndex];
console.log('item', item);
}
},
onHover: (event: ChartEvent, elements: InteractionItem[]) => {
const chartElement = event.native?.target as HTMLCanvasElement;
if (elements.length >= 2) {
Expand Down Expand Up @@ -66,24 +57,12 @@ export function toTreeMapConfig(
spacing: 1,
borderWidth: 0.5,
borderColor: '#EFEFEF',
// backgroundColor: 'rgba(220,230,220,0.3)',
backgroundColor: (ctx) => {
if (typeof ctx.raw?.l !== 'undefined' && ctx.raw?.l < 2) {
return '#EFEFEF';
}

switch (ctx.raw?.g) {
case 'hotspot':
return '#E74C3C';
case 'warning':
return '#F1C40F';
case 'fine':
return '#2ECC71';
}

return 'gray';
return getScoreTypeColor(ctx.raw?.g as ScoreType);
},
// hoverBackgroundColor: 'rgba(220,230,220,0.5)',
captions: {
align: 'center',
display: true,
Expand All @@ -109,3 +88,14 @@ export function toTreeMapConfig(

return config;
}

export function getScoreTypeColor(scoreType: ScoreType) {
switch (scoreType) {
case 'hotspot':
return '#E74C3C';
case 'warning':
return '#F1C40F';
case 'fine':
return '#2ECC71';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.title {
margin-left: 15px;
margin-top: 10px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<h2 class="title mat-dialog-title">
<div
[ngStyle]="{ 'border-bottom-color': color() }"
style="border-bottom: 4px solid; display: inline-block"
>
{{ module() }}
</div>
</h2>

<div class="detail" style="height: 640px">
<div class="p10">
@if (loadingHotspots()) { Determining Hotspots ...
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
} @else if (hotspotResult().hotspots.length > 0) {
<div xclass="mat-elevation-z8">
<table mat-table [dataSource]="detailDataSource">
<ng-container matColumnDef="fileName">
<th mat-header-cell *matHeaderCellDef>Module</th>
<td mat-cell *matCellDef="let element">{{ element.fileName }}</td>
</ng-container>

<ng-container matColumnDef="commits">
<th mat-header-cell *matHeaderCellDef class="metric-cell">Commits</th>
<td mat-cell *matCellDef="let element" class="metric-cell">
{{ element.commits }}
</td>
</ng-container>

<ng-container matColumnDef="complexity">
<th mat-header-cell *matHeaderCellDef class="metric-cell">
Complexity
</th>
<td mat-cell *matCellDef="let element" class="metric-cell">
{{ element.complexity }}
</td>
</ng-container>

<ng-container matColumnDef="score">
<th mat-header-cell *matHeaderCellDef class="metric-cell">Score</th>
<td mat-cell *matCellDef="let element" class="metric-cell">
{{ element.score }}
</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="detailColumns"></tr>
<tr mat-row *matRowDef="let row; columns: detailColumns"></tr>
</table>
</div>
}
</div>
</div>

<mat-paginator [pageSize]="10" showFirstLastButtons></mat-paginator>
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { CommonModule } from '@angular/common';
import {
Component,
computed,
effect,
inject,
untracked,
viewChild,
} from '@angular/core';
import { MatDialogModule } from '@angular/material/dialog';
import { MatPaginator } from '@angular/material/paginator';
import { MatProgressBar } from '@angular/material/progress-bar';
import { MatSortModule } from '@angular/material/sort';
import { MatTableDataSource, MatTableModule } from '@angular/material/table';

import { FlatHotspot } from '../../../model/hotspot-result';
import { getScoreTypeColor } from '../hotspot-adapter';
import { HotspotStore } from '../hotspot.store';

@Component({
selector: 'app-hotspot-detail',
standalone: true,
imports: [
CommonModule,
MatTableModule,
MatSortModule,
MatPaginator,
MatProgressBar,
MatDialogModule,
],
templateUrl: './hotspot-detail.component.html',
styleUrl: './hotspot-detail.component.css',
})
export class HotspotDetailComponent {
private hotspotStore = inject(HotspotStore);

module = this.hotspotStore.filter.module;
color = computed(() => getScoreTypeColor(this.hotspotStore.scoreType()));

paginator = viewChild(MatPaginator);
detailDataSource = new MatTableDataSource<FlatHotspot>();
detailColumns = ['fileName', 'commits', 'complexity', 'score'];

loadingAggregated = this.hotspotStore.loadingAggregated;
loadingHotspots = this.hotspotStore.loadingHotspots;

aggregatedResult = this.hotspotStore.aggregatedResult;
hotspotResult = this.hotspotStore.hotspotResult;

formattedHotspots = computed(() =>
formatHotspots(
this.hotspotResult().hotspots,
untracked(() => this.hotspotStore.filter.module())
)
);

constructor() {
effect(() => {
const hotspots = this.formattedHotspots();
this.detailDataSource.data = hotspots;
});

effect(() => {
const paginator = this.paginator();
if (paginator) {
this.detailDataSource.paginator = paginator;
}
});
}
}

function formatHotspots(
hotspot: FlatHotspot[],
selectedModule: string
): FlatHotspot[] {
return hotspot.map((hs) => ({
...hs,
fileName: trimSegments(hs.fileName, selectedModule),
}));
}

function trimSegments(fileName: string, prefix: string): string {
if (fileName.startsWith(prefix)) {
return fileName.substring(prefix.length + 1);
}
return fileName;
}
86 changes: 4 additions & 82 deletions apps/frontend/src/app/features/hotspot/hotspot.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,85 +35,7 @@
</app-limits>
</div>

<div class="aggregated">
<div class="p10">
@if (loadingAggregated() || (loadingAggregated() && selectedModule())) {
Determining Hotspots ...
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
} @else {
<table
mat-table
[dataSource]="formattedAggregated()"
class="mat-elevation-z8"
>
<ng-container matColumnDef="module">
<th mat-header-cell *matHeaderCellDef>Module</th>
<td mat-cell *matCellDef="let element">{{ element.module }}</td>
</ng-container>

<ng-container matColumnDef="count">
<th mat-header-cell *matHeaderCellDef>Count</th>
<td mat-cell *matCellDef="let element">{{ element.count }}</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="columnsToDisplay"></tr>
<tr
mat-row
*matRowDef="let row; columns: columnsToDisplay; let i = index"
(click)="selectRow(row, i)"
[class.selected]="isSelected(i)"
></tr>
</table>
}
</div>
</div>

<div class="detail">
<div class="p10">
@if (loadingHotspots()) { Determining Hotspots ...
<mat-progress-bar mode="indeterminate"></mat-progress-bar>
} @else if (hotspotResult().hotspots.length > 0) {
<div class="mat-elevation-z8">
<table mat-table [dataSource]="detailDataSource">
<ng-container matColumnDef="fileName">
<th mat-header-cell *matHeaderCellDef>Module</th>
<td mat-cell *matCellDef="let element">{{ element.fileName }}</td>
</ng-container>

<ng-container matColumnDef="commits">
<th mat-header-cell *matHeaderCellDef class="metric-cell">Commits</th>
<td mat-cell *matCellDef="let element" class="metric-cell">
{{ element.commits }}
</td>
</ng-container>

<ng-container matColumnDef="complexity">
<th mat-header-cell *matHeaderCellDef class="metric-cell">
Complexity
</th>
<td mat-cell *matCellDef="let element" class="metric-cell">
{{ element.complexity }}
</td>
</ng-container>

<ng-container matColumnDef="score">
<th mat-header-cell *matHeaderCellDef class="metric-cell">Score</th>
<td mat-cell *matCellDef="let element" class="metric-cell">
{{ element.score }}
</td>
</ng-container>

<tr mat-header-row *matHeaderRowDef="detailColumns"></tr>
<tr mat-row *matRowDef="let row; columns: detailColumns"></tr>
</table>
<mat-paginator
[pageSizeOptions]="[5, 10, 15, 20, 25]"
[pageSize]="10"
showFirstLastButtons
></mat-paginator>
</div>
}
</div>
</div>

<app-treemap [chartConfig]="treeMapConfig()"></app-treemap>
<app-treemap
[chartConfig]="treeMapConfig()"
(elementSelected)="selectModule($event)"
></app-treemap>
Loading

0 comments on commit 0292893

Please sign in to comment.