Skip to content
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

Refactor/2318/migrate attribute type selector #2519

Merged
merged 11 commits into from
Nov 22, 2021
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/)
### Chore 👨‍💻 👩‍💻

- Refactor where metric data are calculated ([#2514](https://github.com/MaibornWolff/codecharta/pull/2514)).
- Migrate `attribute-type-selector-component` to Angular ([#2519](https://github.com/MaibornWolff/codecharta/pull/2519)).

### Changed

Expand Down
12 changes: 10 additions & 2 deletions visualization/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,19 @@ import { MapTreeViewComponent } from "./codeCharta/ui/mapTreeView/mapTreeView.co
import { MatchingFilesCounterComponent } from "./codeCharta/ui/matchingFilesCounter/matchingFilesCounter.component"
import { MatchingFilesCounterModule } from "./codeCharta/ui/matchingFilesCounter/matchingFilesCounter.module"
import { NodePathComponent } from "./codeCharta/ui/attributeSideBar/nodePath/nodePath.component"
import { AttributeTypeSelectorModule } from "./codeCharta/ui/attributeTypeSelector/attributeTypeSelector.module"
import { AttributeTypeSelectorComponent } from "./codeCharta/ui/attributeTypeSelector/attributeTypeSelector.component"

@NgModule({
imports: [BrowserModule, UpgradeModule, MaterialModule, MapTreeViewModule, MatchingFilesCounterModule],
imports: [BrowserModule, UpgradeModule, MaterialModule, MapTreeViewModule, MatchingFilesCounterModule, AttributeTypeSelectorModule],
declarations: [MetricDeltaSelectedComponent, NodePathComponent],
entryComponents: [MapTreeViewComponent, MetricDeltaSelectedComponent, MatchingFilesCounterComponent, NodePathComponent]
entryComponents: [
MapTreeViewComponent,
MetricDeltaSelectedComponent,
MatchingFilesCounterComponent,
NodePathComponent,
AttributeTypeSelectorComponent
]
})
export class AppModule {
constructor(@Inject(UpgradeModule) private upgrade: UpgradeModule) {}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { createSelector } from "../angular-redux/store"
import { attributeTypesSelector } from "../store/fileSettings/attributeTypes/attributeTypes.selector"
import { CcState } from "../store/store"
import { AttributeTypeValue } from "../../codeCharta.model"

export type GetAttributeTypeOfNodesByMetric = (metricName: string) => AttributeTypeValue

export const getAttributeTypeOfNodesByMetricSelector: (state: CcState) => GetAttributeTypeOfNodesByMetric = createSelector(
[attributeTypesSelector],
attributeTypes => (metricName: string) => attributeTypes.nodes[metricName]
)
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,12 @@ <h2 class="node-name-h2">
<td>
<i class="fa fa-arrows-alt"></i>
<div class="metric-value">
<attribute-type-selector-component
<cc-attribute-type-selector
class="attribute-type-select"
type="nodes"
metric="{{::$ctrl._viewModel.primaryMetricKeys.node.area}}"
ng-if="!$ctrl._viewModel.node.isLeaf"
>
</attribute-type-selector-component>
</cc-attribute-type-selector>
<span>
{{ $ctrl._viewModel.node.attributes[$ctrl._viewModel.primaryMetricKeys.node.area] | number: 0 }}
</span>
Expand All @@ -43,13 +42,12 @@ <h2 class="node-name-h2">
<td>
<i class="fa fa-arrows-v"></i>
<div class="metric-value">
<attribute-type-selector-component
<cc-attribute-type-selector
class="attribute-type-select"
type="nodes"
metric="{{::$ctrl._viewModel.primaryMetricKeys.node.height}}"
ng-if="!$ctrl._viewModel.node.isLeaf"
>
</attribute-type-selector-component>
</cc-attribute-type-selector>
<span>
{{ $ctrl._viewModel.node.attributes[$ctrl._viewModel.primaryMetricKeys.node.height] | number: 0 }}
</span>
Expand All @@ -65,13 +63,12 @@ <h2 class="node-name-h2">
<td>
<i class="fa fa-paint-brush"></i>
<div class="metric-value">
<attribute-type-selector-component
<cc-attribute-type-selector
class="attribute-type-select"
type="nodes"
metric="{{::$ctrl._viewModel.primaryMetricKeys.node.color}}"
ng-if="!$ctrl._viewModel.node.isLeaf"
>
</attribute-type-selector-component>
</cc-attribute-type-selector>
<span>
{{ $ctrl._viewModel.node.attributes[$ctrl._viewModel.primaryMetricKeys.node.color] | number: 0 }}
</span>
Expand Down Expand Up @@ -106,13 +103,12 @@ <h2 class="node-name-h2">
<tr ng-repeat="attributeKey in $ctrl._viewModel.secondaryMetrics track by $index" class="metric-row">
<td class="metric-summary">
<span class="metric-type">
<attribute-type-selector-component
<cc-attribute-type-selector
class="attribute-type-select"
type="nodes"
metric="{{::attributeKey.name}}"
ng-if="!$ctrl._viewModel.node.isLeaf"
>
</attribute-type-selector-component>
</cc-attribute-type-selector>
</span>
<span class="metric-value">{{ $ctrl._viewModel.node.attributes[attributeKey.name] | number: 0 }}</span>
</td>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { AttributeTypeValue } from "../../codeCharta.model"
import { AggregationSymbolPipe } from "./aggregationSymbol.pipe"

describe("aggregationSymbolPipe", () => {
it("should map `AttributeTypeValue.relative` to x͂", () => {
const getAttributeTypeOfNodesByMetricSelector = () => AttributeTypeValue.relative
expect(new AggregationSymbolPipe().transform("some-metric-name", getAttributeTypeOfNodesByMetricSelector)).toBe("x͂")
})

it("should map `AttributeTypeValue.absolute` to Σ", () => {
const getAttributeTypeOfNodesByMetricSelector = () => AttributeTypeValue.absolute
expect(new AggregationSymbolPipe().transform("some-metric-name", getAttributeTypeOfNodesByMetricSelector)).toBe("Σ")
})

it("should default to Σ", () => {
const getAttributeTypeOfNodesByMetricSelector = () => "" as AttributeTypeValue
expect(new AggregationSymbolPipe().transform("some-metric-name", getAttributeTypeOfNodesByMetricSelector)).toBe("Σ")
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Pipe, PipeTransform } from "@angular/core"

import { AttributeTypeValue } from "../../codeCharta.model"
import { GetAttributeTypeOfNodesByMetric } from "../../state/selectors/getAttributeTypeOfNodesByMetric.selector"

@Pipe({ name: "aggregationSymbolPipe" })
export class AggregationSymbolPipe implements PipeTransform {
transform(metricName: string, getAttributeTypeOfNodesByMetricSelector: GetAttributeTypeOfNodesByMetric): string {
const type = getAttributeTypeOfNodesByMetricSelector(metricName)
switch (type) {
case AttributeTypeValue.relative:
return "x͂"
case AttributeTypeValue.absolute:
default:
return "Σ"
}
}
}
Original file line number Diff line number Diff line change
@@ -1,21 +1,12 @@
<md-menu>
<div class="small-action-button transparent">
<md-button class="md-fab" ng-click="$mdMenu.open($event)" title="Select attribute type">
<span class="symbol">{{ $ctrl._viewModel.aggregationSymbol }}</span>
</md-button>
</div>
<md-menu-content width="2">
<md-menu-item style="min-height: 20px; height: 20px">
<md-button style="min-height: 20px; height: 20px; line-height: 20px; font-size: 9pt; text-align: center" ng-disabled="true"
>Set global aggregation for {{ $ctrl.metric }}</md-button
>
</md-menu-item>
<md-divider></md-divider>
<md-menu-item>
<md-button ng-click="$ctrl.setToAbsolute($ctrl.metric, $ctrl.type)">Σ Sum</md-button>
</md-menu-item>
<md-menu-item>
<md-button ng-click="$ctrl.setToRelative($ctrl.metric, $ctrl.type)">x͂ Median</md-button>
</md-menu-item>
</md-menu-content>
</md-menu>
<div class="small-action-button transparent">
<button mat-button [matMenuTriggerFor]="menu" title="Select attribute type" class="menu-button">
<span class="symbol">{{ metric | aggregationSymbolPipe: (getAttributeTypeOfNodesByMetric$ | async) }}</span>
</button>
</div>

<mat-menu #menu>
<div class="menu-header" mat-menu-item disabled>Set global aggregation for rloc</div>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personal preference, but i think a span or any other generic text element would be cleaner than just writing the text straight into a div, since we could in theory have spans standardized across the project but not generic text in divs.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The advantage of this standard isn't clear to me. Anyway, lets not spend time in something which we might do, while "just" migrating to Angular, which is already a very very huge task :P

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Text just in a div has no semantic meaning for text, its used for layout, while span is used to style texts. If that makes more sense

<mat-divider></mat-divider>
<button mat-menu-item (click)="setToAbsolute()">Σ Sum</button>
<button mat-menu-item (click)="setToRelative()">x͂ Median</button>
</mat-menu>
Original file line number Diff line number Diff line change
@@ -1,33 +1,22 @@
attribute-type-selector-component {
& .small-action-button {
background-color: white;
border-radius: 16px;
display: inline-block;
cc-attribute-type-selector {
display: inline-block;

.menu-button {
height: 24px;
width: 24px;
vertical-align: top;

.md-fab {
min-height: 0;
width: 100%;
height: 100%;
margin: 0;
text-transform: unset !important;
min-width: 0 !important;
shaman-apprentice marked this conversation as resolved.
Show resolved Hide resolved
background-color: white;
border-radius: 16px;
padding: 0;

Christian-Eberhard marked this conversation as resolved.
Show resolved Hide resolved
.symbol {
position: absolute;
top: -16px;
left: 8px;
}
&:hover {
background-color: rgb(200, 200, 200);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We used #c8c8c8 before, for most hovers. But this is a personal preference to use hex codes over rgb

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

&.transparent .md-fab {
background-color: rgba(230, 230, 230, 0.96);
box-shadow: none;

&:hover {
background-color: rgb(200, 200, 200) !important;
}
.symbol {
position: absolute;
top: -6px;
left: 8px;
}
}
}
Original file line number Diff line number Diff line change
@@ -1,93 +1,53 @@
import "./attributeTypeSelector.module"
import { AttributeTypeSelectorController } from "./attributeTypeSelector.component"
import { instantiateModule, getService } from "../../../../mocks/ng.mockhelper"
import { StoreService } from "../../state/store.service"
import { setAttributeTypes } from "../../state/store/fileSettings/attributeTypes/attributeTypes.actions"
import { AttributeTypeValue } from "../../codeCharta.model"
import { NodeMetricDataService } from "../../state/store/metricData/nodeMetricData/nodeMetricData.service"
import { EdgeMetricDataService } from "../../state/store/metricData/edgeMetricData/edgeMetricData.service"
import { IRootScopeService } from "angular"
import { TestBed } from "@angular/core/testing"
import { render, screen, fireEvent, waitForElementToBeRemoved } from "@testing-library/angular"

describe("AttributeTypeSelectorController", () => {
let attributeTypeSelectorController: AttributeTypeSelectorController
let $rootScope: IRootScopeService
let storeService: StoreService
let nodeMetricDataService: NodeMetricDataService
let edgeMetricDataService: EdgeMetricDataService
import { AttributeTypeSelectorModule } from "./attributeTypeSelector.module"
import { Store } from "../../state/store/store"
import { AttributeTypeSelectorComponent } from "./attributeTypeSelector.component"
import { AttributeTypeValue } from "../../codeCharta.model"
import { setAttributeTypes } from "../../state/store/fileSettings/attributeTypes/attributeTypes.actions"

describe("attributeTypeSelector", () => {
beforeEach(() => {
restartSystem()
rebuildController()
})

function restartSystem() {
instantiateModule("app.codeCharta.ui.attributeTypeSelector")
$rootScope = getService<IRootScopeService>("$rootScope")
storeService = getService<StoreService>("storeService")
nodeMetricDataService = getService<NodeMetricDataService>("nodeMetricDataService")
edgeMetricDataService = getService<EdgeMetricDataService>("edgeMetricDataService")
}

function rebuildController() {
attributeTypeSelectorController = new AttributeTypeSelectorController(
$rootScope,
storeService,
nodeMetricDataService,
edgeMetricDataService
)
}

describe("setToAbsolute", () => {
it("should update attributeType to absolute", () => {
attributeTypeSelectorController.setToAbsolute("bar", "nodes")

expect(storeService.getState().fileSettings.attributeTypes.nodes["bar"]).toEqual(AttributeTypeValue.absolute)
TestBed.configureTestingModule({
imports: [AttributeTypeSelectorModule]
})
})

describe("setToRelative", () => {
it("should set attributeType to relative", () => {
attributeTypeSelectorController.setToRelative("foo", "edges")

expect(storeService.getState().fileSettings.attributeTypes.edges["foo"]).toEqual(AttributeTypeValue.relative)
})
Store["initialize"]()
Store.store.dispatch(
setAttributeTypes({
nodes: { rloc: AttributeTypeValue.absolute }
})
)
})

describe("setAggregationSymbol", () => {
beforeEach(() => {
storeService.dispatch(
setAttributeTypes({
nodes: { rloc: AttributeTypeValue.absolute },
edges: { pairingRate: AttributeTypeValue.relative }
})
)
})

it("should set aggregationSymbol to absolute", () => {
attributeTypeSelectorController["metric"] = "rloc"
attributeTypeSelectorController["type"] = "nodes"

attributeTypeSelectorController.$onInit()

expect(attributeTypeSelectorController["_viewModel"].aggregationSymbol).toEqual("Σ")
it("should update to median", async () => {
await render(AttributeTypeSelectorComponent, {
componentProperties: { metric: "rloc" },
excludeComponentDeclaration: true
})

it("should set aggregationSymbol to relative", () => {
attributeTypeSelectorController["metric"] = "pairingRate"
attributeTypeSelectorController["type"] = "edges"
const initialDisplayedElement = await screen.findByText("Σ")
expect(initialDisplayedElement).toBeTruthy()

attributeTypeSelectorController.$onInit()
fireEvent.click(initialDisplayedElement)
const medianMenuItem = await screen.findByText("x͂ Median")
expect(medianMenuItem).toBeTruthy()

expect(attributeTypeSelectorController["_viewModel"].aggregationSymbol).toEqual("x͂")
fireEvent.click(medianMenuItem)
await waitForElementToBeRemoved(() => {
const medianMenuItem = screen.queryByText("x͂ Median")
return medianMenuItem
})
const updatedDisplayedElement = await screen.findByText("x͂")
expect(updatedDisplayedElement).toBeTruthy()
})

it("should set aggregationSymbol to absolute if attributeType is not available", () => {
attributeTypeSelectorController["metric"] = "foobar"
attributeTypeSelectorController["type"] = "nodes"

attributeTypeSelectorController.$onInit()

expect(attributeTypeSelectorController["_viewModel"].aggregationSymbol).toEqual("Σ")
it("should set aggregation symbol to absolute if attributeType is not available", async () => {
await render(AttributeTypeSelectorComponent, {
componentProperties: { metric: "non-existing" },
excludeComponentDeclaration: true
})
expect(await screen.findByText("Σ")).toBeTruthy()
})
})