Skip to content

Commit

Permalink
Refactor/2318/migrate metric choosers (#2825)
Browse files Browse the repository at this point in the history
migrate edgeChooser.component, metricChooser.componen and codeMap.actions.service

ref #2318
  • Loading branch information
shaman-apprentice committed May 30, 2022
1 parent 82209aa commit a8b87a3
Show file tree
Hide file tree
Showing 90 changed files with 850 additions and 1,371 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/)

- Hide color metric range-slider in color metric options of ribbon bar in delta mode instead of disabling it [#2797](https://github.com/MaibornWolff/codecharta/pull/2797)
- Display max value of selected distribution metric in file extension bar [#2824](https://github.com/MaibornWolff/codecharta/pull/2824)
- Display max value of selected metric in all metric chooser of ribbon bar [#2825](https://github.com/MaibornWolff/codecharta/pull/2825)

### Fixed 🐞

Expand Down
12 changes: 6 additions & 6 deletions visualization/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,7 @@ import { BlacklistSearchPatternEffect } from "./codeCharta/ui/searchPanel/search
import { EdgeMetricToggleComponent } from "./codeCharta/ui/edgeSettingsPanel/edgeMetricToggle/edgeMetricToggle.component"
import { SearchPanelComponent } from "./codeCharta/ui/searchPanel/searchPanel.component"
import { SearchPanelModule } from "./codeCharta/ui/searchPanel/searchPanel.module"
import { MetricValueHoveredModule } from "./codeCharta/ui/metricChooserDeprecated/metricValueHovered/metricValueHovered.module"
import { UploadFilesButtonComponent } from "./codeCharta/ui/toolBar/uploadFilesButton/uploadFilesButton.component"
import { MetricTypeHoveredModule } from "./codeCharta/ui/metricChooserDeprecated/metricTypeHovered/metricTypeHovered.module"
import { SliderModule } from "./codeCharta/ui/slider/slider.module"
import { HeightSettingsPanelModule } from "./codeCharta/ui/ribbonBar/heightSettingsPanel/heightSettingsPanel.module"
import { MetricColorRangeSliderModule } from "./codeCharta/ui/colorSettingsPanel/metricColorRangeSlider/metricColorRangeSlider.module"
Expand All @@ -55,6 +53,8 @@ import { AreaSettingsPanelModule } from "./codeCharta/ui/ribbonBar/areaSettingsP
import { ResetDynamicMarginEffect } from "./codeCharta/state/effects/resetDynamicMargin/resetDynamicMargin.effect"
import { MetricChooserModule } from "./codeCharta/ui/metricChooser/metricChooser.module"
import { ResetChosenMetricsEffect } from "./codeCharta/state/effects/resetChosenMetrics/resetChosenMetrics.effect"
import { RibbonBarModule } from "./codeCharta/ui/ribbonBar/ribbonBar.module"
import { UpdateEdgePreviewsEffect } from "./codeCharta/state/effects/updateEdgePreviews/updateEdgePreviews.effect"

@NgModule({
imports: [
Expand All @@ -69,7 +69,8 @@ import { ResetChosenMetricsEffect } from "./codeCharta/state/effects/resetChosen
ResetColorRangeEffect,
SyncGlobalSettingsInLocalStorageEffect,
ResetDynamicMarginEffect,
ResetChosenMetricsEffect
ResetChosenMetricsEffect,
UpdateEdgePreviewsEffect
]),
SliderModule,
AttributeSideBarModule,
Expand All @@ -83,8 +84,6 @@ import { ResetChosenMetricsEffect } from "./codeCharta/state/effects/resetChosen
LoadingFileProgressSpinnerModule,
LoadingMapProgressSpinnerModule,
SearchPanelModule,
MetricTypeHoveredModule,
MetricValueHoveredModule,
CustomConfigsModule,
FilePanelModule,
HeightSettingsPanelModule,
Expand All @@ -93,7 +92,8 @@ import { ResetChosenMetricsEffect } from "./codeCharta/state/effects/resetChosen
GlobalConfigurationButtonModule,
DistributionMetricChooserModule,
AreaSettingsPanelModule,
MetricChooserModule
MetricChooserModule,
RibbonBarModule
],
providers: [
threeSceneServiceProvider,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { APP_INITIALIZER, Inject, InjectionToken, ModuleWithProviders, NgModule, Optional, SkipSelf } from "@angular/core"
import { APP_INITIALIZER, InjectionToken, ModuleWithProviders, NgModule } from "@angular/core"
import { Action } from "redux"
import { Observable, Subject } from "rxjs"

Expand All @@ -12,12 +12,6 @@ export const ActionsToken = new InjectionToken<Actions>("Actions")
export class EffectsModule {
static actions$ = new Subject<Action>()

constructor(@Optional() @SkipSelf() @Inject(EffectsModule) parentModule?: EffectsModule) {
if (parentModule) {
throw new Error("EffectsModule is already loaded. Import it in the AppModule only")
}
}

static forRoot(effects = []): ModuleWithProviders<EffectsModule> {
return {
ngModule: EffectsModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ import { getDefaultDistribution } from "./utils/getDefaultDistributionMetric"
export class ResetChosenMetricsEffect {
constructor(@Inject(Store) private store: Store) {}

resetChosenDistributionMetric$ = createEffect(() =>
this.store.select(nodeMetricDataSelector).pipe(
tap(nodeMetricData => {
if (isAnyMetricAvailable(nodeMetricData)) {
// when migrating area, height and color service, their resetting will be added here as well
this.store.dispatch(setDistributionMetric(getDefaultDistribution(nodeMetricData)))
}
})
)
resetChosenDistributionMetric$ = createEffect(
() =>
this.store.select(nodeMetricDataSelector).pipe(
tap(nodeMetricData => {
if (isAnyMetricAvailable(nodeMetricData)) {
// when migrating area, height and color service, their resetting will be added here as well
this.store.dispatch(setDistributionMetric(getDefaultDistribution(nodeMetricData)))
}
})
),
{ dispatch: false }
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { ApplicationInitStatus } from "@angular/core"
import { TestBed } from "@angular/core/testing"
import { EffectsModule } from "../../angular-redux/effects/effects.module"
import { setAmountOfEdgePreviews } from "../../store/appSettings/amountOfEdgePreviews/amountOfEdgePreviews.actions"
import { toggleEdgeMetricVisible } from "../../store/appSettings/isEdgeMetricVisible/isEdgeMetricVisible.actions"
import { setEdgeMetric } from "../../store/dynamicSettings/edgeMetric/edgeMetric.actions"
import { setEdges } from "../../store/fileSettings/edges/edges.actions"
import { Store } from "../../store/store"
import { UpdateEdgePreviewsEffect } from "./updateEdgePreviews.effect"

describe("updateEdgePreviewsEffect", () => {
let dispatchSpy

beforeEach(async () => {
Store["initialize"]()
TestBed.configureTestingModule({
imports: [EffectsModule.forRoot([UpdateEdgePreviewsEffect])]
})
await TestBed.inject(ApplicationInitStatus).donePromise

dispatchSpy = jest.spyOn(Store.store, "dispatch")
})

it("should ignore a not relevant action", () => {
Store.dispatch({ type: "whatever" })
expect(dispatchSpy).toHaveBeenCalledTimes(1)
})

it("should set isEdgeMetricVisible to true on edgeMetric change, if it was false", () => {
Store.dispatch(toggleEdgeMetricVisible()) // toggle first as it is true initially
Store.dispatch(setEdgeMetric("rloc"))
expect(dispatchSpy.mock.calls[2][0]).toEqual(toggleEdgeMetricVisible())
})

it("should not set isEdgeMetricVisible to false on edgeMetric change, if it was true", () => {
Store.dispatch(setEdgeMetric("rloc"))
expect(dispatchSpy).not.toHaveBeenCalledWith(toggleEdgeMetricVisible())
})

it("should update edges on amountOfEdgePreviewsSelector changed", () => {
Store.dispatch(setAmountOfEdgePreviews(0))
expect(dispatchSpy).toHaveBeenCalledWith(setEdges([]))
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { Inject, Injectable } from "@angular/core"
import { combineLatest, filter, map, withLatestFrom } from "rxjs"
import { createEffect } from "../../angular-redux/effects/createEffect"
import { Store } from "../../angular-redux/store"
import { amountOfEdgePreviewsSelector } from "../../store/appSettings/amountOfEdgePreviews/amountOfEdgePreviews.selector"
import { edgeHeightSelector } from "../../store/appSettings/edgeHeight/edgeHeight.selector"
import { toggleEdgeMetricVisible } from "../../store/appSettings/isEdgeMetricVisible/isEdgeMetricVisible.actions"
import { isEdgeMetricVisibleSelector } from "../../store/appSettings/isEdgeMetricVisible/isEdgeMetricVisible.selector"
import { showOnlyBuildingsWithEdgesSelector } from "../../store/appSettings/showOnlyBuildingsWithEdges/showOnlyBuildingsWithEdges.selector"
import { edgeMetricSelector } from "../../store/dynamicSettings/edgeMetric/edgeMetric.selector"
import { setEdges } from "../../store/fileSettings/edges/edges.actions"
import { edgesSelector } from "../../store/fileSettings/edges/edges.selector"
import { edgePreviewNodesSelector } from "./utils/edgePreviewNodes.selector"
import { setEdgeVisibility } from "./utils/setEdgeVisibility"

@Injectable()
export class UpdateEdgePreviewsEffect {
constructor(@Inject(Store) private store: Store) {}

resetIsEdgeMetricVisible$ = createEffect(() =>
this.store.select(edgeMetricSelector).pipe(
withLatestFrom(this.store.select(isEdgeMetricVisibleSelector)),
filter(([, isEdgeMetricVisible]) => !isEdgeMetricVisible),
map(() => toggleEdgeMetricVisible())
)
)

updateEdgePreviews$ = createEffect(() =>
combineLatest([
this.store.select(edgeMetricSelector),
this.store.select(amountOfEdgePreviewsSelector),
this.store.select(edgeHeightSelector),
this.store.select(showOnlyBuildingsWithEdgesSelector)
]).pipe(
withLatestFrom(this.store.select(edgePreviewNodesSelector), this.store.select(edgesSelector)),
map(([[edgeMetric], edgePreviewNodes, edges]) => {
setEdgeVisibility(edgePreviewNodes, edges, edgeMetric)
return setEdges(edges)
})
)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { EdgeMetricCount } from "../../../../codeCharta.model"
import { _getNodesWithHighestValue } from "./edgePreviewNodes.selector"

describe("edgePreviewNodesSelector", () => {
it("should return an empty list if there are no data for given metric", () => {
expect(_getNodesWithHighestValue(new Map(), "rloc", 10)).toEqual([])
})

it("should return a first x keys for given metric", () => {
const nodeEdgeMetricsOfRloc = new Map<string, EdgeMetricCount>([
["0", { incoming: 10, outgoing: 5 }],
["1", { incoming: 1, outgoing: 2 }]
])
expect(_getNodesWithHighestValue(new Map([["rloc", nodeEdgeMetricsOfRloc]]), "rloc", 1)).toEqual(["0"])
})

it("should return empty list if amountOfEdgePreviews is set to 0", () => {
const nodeEdgeMetricsOfRloc = new Map<string, EdgeMetricCount>([["0", { incoming: 10, outgoing: 5 }]])
expect(_getNodesWithHighestValue(new Map([["rloc", nodeEdgeMetricsOfRloc]]), "rloc", 0)).toEqual([])
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { createSelector } from "../../../angular-redux/createSelector"
import { edgeMetricMapSelector, NodeEdgeMetricsMap } from "../../../selectors/accumulatedData/metricData/edgeMetricData.selector"
import { amountOfEdgePreviewsSelector } from "../../../store/appSettings/amountOfEdgePreviews/amountOfEdgePreviews.selector"
import { edgeMetricSelector } from "../../../store/dynamicSettings/edgeMetric/edgeMetric.selector"

export const edgePreviewNodesSelector = createSelector(
[edgeMetricMapSelector, edgeMetricSelector, amountOfEdgePreviewsSelector],
(edgeMetricMap, edgeMetric, amountOfEdgePreviews) => new Set(_getNodesWithHighestValue(edgeMetricMap, edgeMetric, amountOfEdgePreviews))
)

export const _getNodesWithHighestValue = (edgeMetricMap: NodeEdgeMetricsMap, edgeMetric: string, amountOfEdgePreviews: number) => {
const keys: string[] = []

if (amountOfEdgePreviews === 0) {
return keys
}

const nodeEdgeMetrics = edgeMetricMap.get(edgeMetric)

if (nodeEdgeMetrics === undefined) {
return keys
}

// note that this depends on the fact, that edgeMetricMap is created by a list which is sorted by max value
for (const key of nodeEdgeMetrics.keys()) {
keys.push(key)
if (keys.length === amountOfEdgePreviews) {
break
}
}
return keys
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Edge, EdgeVisibility } from "../../../../codeCharta.model"
import { setEdgeVisibility } from "./setEdgeVisibility"

describe("setEdgeVisibility", () => {
const edgePreviewNodes = new Set(["aNode", "anotherNode"])

it("should set edge's visibility to 'none' if edge has no attributes for given metric", () => {
const edges: Edge[] = [{ attributes: {}, fromNodeName: "from", toNodeName: "to" }]
setEdgeVisibility(edgePreviewNodes, edges, "loc")
expect(edges[0].visible).toBe(EdgeVisibility.none)
})

it("should set edge's visibility to 'none' if edge has neither from nor to", () => {
const edges: Edge[] = [{ attributes: { loc: 12 }, fromNodeName: "from", toNodeName: "to" }]
setEdgeVisibility(edgePreviewNodes, edges, "loc")
expect(edges[0].visible).toBe(EdgeVisibility.none)
})

it("should set edge's visibility to 'both' if edge has from and to for given metric", () => {
const edges: Edge[] = [{ attributes: { loc: 12 }, fromNodeName: "aNode", toNodeName: "anotherNode" }]
setEdgeVisibility(edgePreviewNodes, edges, "loc")
expect(edges[0].visible).toBe(EdgeVisibility.both)
})

it("should set edge's visibility to 'from' if edge has only from for given metric", () => {
const edges: Edge[] = [{ attributes: { loc: 12 }, fromNodeName: "aNode", toNodeName: "to" }]
setEdgeVisibility(edgePreviewNodes, edges, "loc")
expect(edges[0].visible).toBe(EdgeVisibility.from)
})

it("should set edge's visibility to 'to' if edge has only to for given metric", () => {
const edges: Edge[] = [{ attributes: { loc: 12 }, fromNodeName: "from", toNodeName: "aNode" }]
setEdgeVisibility(edgePreviewNodes, edges, "loc")
expect(edges[0].visible).toBe(EdgeVisibility.to)
})
})
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Edge, EdgeVisibility } from "../../../../codeCharta.model"

export const setEdgeVisibility = (edgePreviewNodes: Set<string>, edges: Edge[], edgeMetric: string) => {
for (const edge of edges) {
edge.visible = EdgeVisibility.none

if (edge.attributes[edgeMetric] !== undefined) {
const hasFromNodeEdgePreview = edgePreviewNodes.has(edge.fromNodeName)
const hasToNodeEdgePreview = edgePreviewNodes.has(edge.toNodeName)

if (hasFromNodeEdgePreview && hasToNodeEdgePreview) {
edge.visible = EdgeVisibility.both
} else if (hasFromNodeEdgePreview) {
edge.visible = EdgeVisibility.from
} else if (hasToNodeEdgePreview) {
edge.visible = EdgeVisibility.to
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,29 +12,29 @@ describe("edgeMetricDataSelector", () => {
})

it("should create correct edge Metrics", () => {
const result = calculateEdgeMetricData(fileStates, [])
const { sortedEdgeMetricList } = calculateEdgeMetricData(fileStates, [])

expect(result.map(x => x.name)).toContain("pairingRate")
expect(result.map(x => x.name)).toContain("otherMetric")
expect(sortedEdgeMetricList.map(x => x.name)).toContain("pairingRate")
expect(sortedEdgeMetricList.map(x => x.name)).toContain("otherMetric")
})

it("should calculate correct maximum value for edge Metrics", () => {
const result = calculateEdgeMetricData(fileStates, [])
const { sortedEdgeMetricList } = calculateEdgeMetricData(fileStates, [])

expect(result.find(x => x.name === "pairingRate").maxValue).toEqual(2)
expect(result.find(x => x.name === "otherMetric").maxValue).toEqual(1)
expect(sortedEdgeMetricList.find(x => x.name === "pairingRate").maxValue).toEqual(2)
expect(sortedEdgeMetricList.find(x => x.name === "otherMetric").maxValue).toEqual(1)
})

it("should sort the metrics after calculating them", () => {
const result = calculateEdgeMetricData(fileStates, [])
const { sortedEdgeMetricList } = calculateEdgeMetricData(fileStates, [])

expect(result).toHaveLength(3)
expect(result[0].name).toBe("avgCommits")
expect(result[1].name).toBe("otherMetric")
expect(result[2].name).toBe("pairingRate")
expect(sortedEdgeMetricList).toHaveLength(3)
expect(sortedEdgeMetricList[0].name).toBe("avgCommits")
expect(sortedEdgeMetricList[1].name).toBe("otherMetric")
expect(sortedEdgeMetricList[2].name).toBe("pairingRate")
})

it("metrics Map should contain correct entries", () => {
it("should sync with deprecated public exposed metrics Map", () => {
calculateEdgeMetricData(fileStates, [])

const pairingRateMapKeys = [...nodeEdgeMetricsMap.get("pairingRate").keys()]
Expand All @@ -43,4 +43,14 @@ describe("edgeMetricDataSelector", () => {
expect(pairingRateMapKeys[1]).toEqual("/root/Parent Leaf/small leaf")
expect(pairingRateMapKeys[2]).toEqual("/root/Parent Leaf/other small leaf")
})

it("should provide nodeEdgeMetricsMap", () => {
const { nodeEdgeMetricsMap } = calculateEdgeMetricData(fileStates, [])

const pairingRateMapKeys = [...nodeEdgeMetricsMap.get("pairingRate").keys()]

expect(pairingRateMapKeys[0]).toEqual("/root/big leaf")
expect(pairingRateMapKeys[1]).toEqual("/root/Parent Leaf/small leaf")
expect(pairingRateMapKeys[2]).toEqual("/root/Parent Leaf/other small leaf")
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,20 @@ import { sortByMetricName } from "./sortByMetricName"

export type EdgeMetricCountMap = Map<string, EdgeMetricCount>
export type NodeEdgeMetricsMap = Map<string, EdgeMetricCountMap>
// Required for performance improvements
// TODO move this as soon as edgeMetricData.service is deleted, to prevent random access / non unidirectional data flow
/** @deprecated use edgeMetricMapSelector instead to ensure unidirectional data flow and prevent random manipulation */
export let nodeEdgeMetricsMap: NodeEdgeMetricsMap = new Map()

export const edgeMetricDataSelector = createSelector([visibleFileStatesSelector, blacklistSelector], calculateEdgeMetricData)
const edgeMetricDataAndMapSelector = createSelector([visibleFileStatesSelector, blacklistSelector], calculateEdgeMetricData)

export const edgeMetricDataSelector = createSelector(
[edgeMetricDataAndMapSelector],
edgeMetricDataAndMap => edgeMetricDataAndMap.sortedEdgeMetricList
)

export const edgeMetricMapSelector = createSelector(
[edgeMetricDataAndMapSelector],
edgeMetricDataAndMap => edgeMetricDataAndMap.nodeEdgeMetricsMap
)

export function calculateEdgeMetricData(visibleFileStates: FileState[], blacklist: BlacklistItem[]) {
nodeEdgeMetricsMap = new Map()
Expand All @@ -38,7 +47,7 @@ export function calculateEdgeMetricData(visibleFileStates: FileState[], blacklis
}
const newEdgeMetricData = getMetricDataFromMap()
sortByMetricName(newEdgeMetricData)
return newEdgeMetricData
return { sortedEdgeMetricList: newEdgeMetricData, nodeEdgeMetricsMap }
}

function bothNodesAssociatedAreVisible(edge: Edge, filePaths: Set<string>, blacklist: BlacklistItem[]) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { createSelector } from "../../../angular-redux/createSelector"
import { appSettingsSelector } from "../appSettings.selector"

export const amountOfEdgePreviewsSelector = createSelector([appSettingsSelector], appSettings => appSettings.amountOfEdgePreviews)

0 comments on commit a8b87a3

Please sign in to comment.