From c7eb7778099913bf12d0a3d7ae2422eda743ebc0 Mon Sep 17 00:00:00 2001 From: PianoRollRepresentation Date: Fri, 18 Jun 2021 12:13:11 +0200 Subject: [PATCH 01/11] Frontend add searched property to QueryResult and adapt styling accordingly --- frontend/src/stores/colors/EntityStyleProviderImpl.ts | 6 +++++- frontend/src/visualization/filtering/Filter.tsx | 2 ++ shared/src/queries/QueryEdgeResult.ts | 5 +++++ shared/src/queries/QueryNodeResult.ts | 5 +++++ 4 files changed, 17 insertions(+), 1 deletion(-) diff --git a/frontend/src/stores/colors/EntityStyleProviderImpl.ts b/frontend/src/stores/colors/EntityStyleProviderImpl.ts index 9f96f8e4..8d2172da 100644 --- a/frontend/src/stores/colors/EntityStyleProviderImpl.ts +++ b/frontend/src/stores/colors/EntityStyleProviderImpl.ts @@ -36,7 +36,7 @@ export class EntityStyleProviderImpl implements EntityStyleProvider { color: common.black, text: { color: common.black }, stroke: { - width: 1, + width: this.isSearched(entity) ? 5 : 1, dashes: false, color: common.black, }, @@ -121,6 +121,10 @@ export class EntityStyleProviderImpl implements EntityStyleProvider { private isVirtual(entity: QueryEntityResult): boolean { return entity.virtual === true; } + + private isSearched(entity: QueryEntityResult): boolean { + return entity.searched === true; + } } export default EntityStyleProviderImpl; diff --git a/frontend/src/visualization/filtering/Filter.tsx b/frontend/src/visualization/filtering/Filter.tsx index aa73ee43..de12878d 100644 --- a/frontend/src/visualization/filtering/Filter.tsx +++ b/frontend/src/visualization/filtering/Filter.tsx @@ -148,6 +148,7 @@ const Filter = (): JSX.Element => { id: -1, types: [type.name], virtual: true, + searched: false, } as QueryNodeResult).color, type.name, 'node' @@ -166,6 +167,7 @@ const Filter = (): JSX.Element => { from: -1, to: -1, virtual: true, + searched: false, } as QueryEdgeResult).color, type.name, 'edge' diff --git a/shared/src/queries/QueryEdgeResult.ts b/shared/src/queries/QueryEdgeResult.ts index 4df97b69..02959bb1 100644 --- a/shared/src/queries/QueryEdgeResult.ts +++ b/shared/src/queries/QueryEdgeResult.ts @@ -20,4 +20,9 @@ export interface QueryEdgeResult extends EdgeDescriptor { * A boolean that specified whether the edge is part of the path that shall be highlighted. */ isPath?: boolean; + + /** + * Specifies whether this node was searched for. + */ + searched?: boolean; } diff --git a/shared/src/queries/QueryNodeResult.ts b/shared/src/queries/QueryNodeResult.ts index 3b3963d8..fe4b7b77 100644 --- a/shared/src/queries/QueryNodeResult.ts +++ b/shared/src/queries/QueryNodeResult.ts @@ -20,4 +20,9 @@ export default interface QueryNodeResult extends NodeDescriptor { * A boolean that specified whether the node is part of the path that shall be highlighted. */ isPath?: boolean; + + /** + * Specifies whether this node was searched for. + */ + searched?: boolean; } From 8763298a769d629a2b2799dca02c4626029abab0 Mon Sep 17 00:00:00 2001 From: PianoRollRepresentation Date: Fri, 18 Jun 2021 16:37:04 +0200 Subject: [PATCH 02/11] Frontend add key to SelectedSearchResult to defer types --- frontend/src/search/Searchbar.tsx | 7 ++++++- frontend/src/stores/SearchSelectionStore.ts | 17 ++++++++++------- .../stores/colors/EntityStyleProviderImpl.ts | 6 +++--- shared/src/queries/QueryEdgeResult.ts | 4 ++-- shared/src/queries/QueryNodeResult.ts | 4 ++-- 5 files changed, 23 insertions(+), 15 deletions(-) diff --git a/frontend/src/search/Searchbar.tsx b/frontend/src/search/Searchbar.tsx index 0eae909e..271f5fbd 100644 --- a/frontend/src/search/Searchbar.tsx +++ b/frontend/src/search/Searchbar.tsx @@ -163,7 +163,12 @@ export default function Searchbar(): JSX.Element { key={element.key} button component="a" - onClick={() => onCardSelected(element.entity)} + onClick={() => + onCardSelected({ + key: result.key, + content: element.entity, + }) + } > {element.element} diff --git a/frontend/src/stores/SearchSelectionStore.ts b/frontend/src/stores/SearchSelectionStore.ts index fd685226..c04f2ff6 100644 --- a/frontend/src/stores/SearchSelectionStore.ts +++ b/frontend/src/stores/SearchSelectionStore.ts @@ -2,12 +2,15 @@ import SimpleStore from './SimpleStore'; import { EdgeDescriptor, NodeDescriptor } from '../shared/entities'; import { EdgeTypeDescriptor, NodeTypeDescriptor } from '../shared/schema'; -export type SelectedSearchResult = - | EdgeDescriptor - | EdgeTypeDescriptor - | NodeDescriptor - | NodeTypeDescriptor - | undefined; +export type SelectedSearchResult = { + key?: string | number; + content: + | EdgeDescriptor + | EdgeTypeDescriptor + | NodeDescriptor + | NodeTypeDescriptor + | undefined; +}; /** * Contains the selected search result (if any). @@ -15,6 +18,6 @@ export type SelectedSearchResult = */ export default class SearchSelectionStore extends SimpleStore { protected getInitialValue(): SelectedSearchResult { - return undefined; + return { content: undefined }; } } diff --git a/frontend/src/stores/colors/EntityStyleProviderImpl.ts b/frontend/src/stores/colors/EntityStyleProviderImpl.ts index 8d2172da..bb5454bd 100644 --- a/frontend/src/stores/colors/EntityStyleProviderImpl.ts +++ b/frontend/src/stores/colors/EntityStyleProviderImpl.ts @@ -36,7 +36,7 @@ export class EntityStyleProviderImpl implements EntityStyleProvider { color: common.black, text: { color: common.black }, stroke: { - width: this.isSearched(entity) ? 5 : 1, + width: this.isSelected(entity) ? 5 : 1, dashes: false, color: common.black, }, @@ -122,8 +122,8 @@ export class EntityStyleProviderImpl implements EntityStyleProvider { return entity.virtual === true; } - private isSearched(entity: QueryEntityResult): boolean { - return entity.searched === true; + private isSelected(entity: QueryEntityResult): boolean { + return entity.selected === true; } } diff --git a/shared/src/queries/QueryEdgeResult.ts b/shared/src/queries/QueryEdgeResult.ts index 02959bb1..36184e17 100644 --- a/shared/src/queries/QueryEdgeResult.ts +++ b/shared/src/queries/QueryEdgeResult.ts @@ -22,7 +22,7 @@ export interface QueryEdgeResult extends EdgeDescriptor { isPath?: boolean; /** - * Specifies whether this node was searched for. + * Specifies whether this node was selected after search. */ - searched?: boolean; + selected?: boolean; } diff --git a/shared/src/queries/QueryNodeResult.ts b/shared/src/queries/QueryNodeResult.ts index fe4b7b77..4610f378 100644 --- a/shared/src/queries/QueryNodeResult.ts +++ b/shared/src/queries/QueryNodeResult.ts @@ -22,7 +22,7 @@ export default interface QueryNodeResult extends NodeDescriptor { isPath?: boolean; /** - * Specifies whether this node was searched for. + * Specifies whether this node was selected after search. */ - searched?: boolean; + selected?: boolean; } From 5eba513cf1444d0260c2ebf61cbebebeb5663d49 Mon Sep 17 00:00:00 2001 From: PianoRollRepresentation Date: Sat, 19 Jun 2021 11:35:03 +0200 Subject: [PATCH 03/11] Frontend add identifier to Descriptors --- frontend/src/search/Searchbar.tsx | 7 +------ shared/src/schema/EdgeTypeDescriptor.ts | 7 ++++++- shared/src/schema/NodeTypeDescriptor.ts | 7 ++++++- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/frontend/src/search/Searchbar.tsx b/frontend/src/search/Searchbar.tsx index 271f5fbd..0eae909e 100644 --- a/frontend/src/search/Searchbar.tsx +++ b/frontend/src/search/Searchbar.tsx @@ -163,12 +163,7 @@ export default function Searchbar(): JSX.Element { key={element.key} button component="a" - onClick={() => - onCardSelected({ - key: result.key, - content: element.entity, - }) - } + onClick={() => onCardSelected(element.entity)} > {element.element} diff --git a/shared/src/schema/EdgeTypeDescriptor.ts b/shared/src/schema/EdgeTypeDescriptor.ts index a0a860da..98a2dea6 100644 --- a/shared/src/schema/EdgeTypeDescriptor.ts +++ b/shared/src/schema/EdgeTypeDescriptor.ts @@ -4,4 +4,9 @@ import { EntityTypeDescriptor } from './EntityTypeDescriptor'; * A descriptor of an edge type. */ // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface EdgeTypeDescriptor extends EntityTypeDescriptor {} +export interface EdgeTypeDescriptor extends EntityTypeDescriptor { + /** + * Used to identify if an object of this type corresponds to an EdgeTypeDescriptor + */ + edge: undefined +} diff --git a/shared/src/schema/NodeTypeDescriptor.ts b/shared/src/schema/NodeTypeDescriptor.ts index 9f6444f1..16056db9 100644 --- a/shared/src/schema/NodeTypeDescriptor.ts +++ b/shared/src/schema/NodeTypeDescriptor.ts @@ -4,4 +4,9 @@ import { EntityTypeDescriptor } from './EntityTypeDescriptor'; * A descriptor of a node type. */ // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface NodeTypeDescriptor extends EntityTypeDescriptor {} +export interface NodeTypeDescriptor extends EntityTypeDescriptor { + /** + * Used to identify if an object of this type corresponds to an NodeTypeDescriptor + */ + node: undefined +} From 7be67bf063962e741b02ffac3825ed138f82fd31 Mon Sep 17 00:00:00 2001 From: PianoRollRepresentation Date: Sat, 19 Jun 2021 11:49:45 +0200 Subject: [PATCH 04/11] Frontend add listening support from EntityStyleStore.ts to SearchSelectionStore.ts --- frontend/src/stores/SearchSelectionStore.ts | 33 +++++-- .../stores/colors/EntityStyleProviderImpl.ts | 38 +++++++- .../src/stores/colors/EntityStyleStore.ts | 92 ++++++++++++++++++- 3 files changed, 147 insertions(+), 16 deletions(-) diff --git a/frontend/src/stores/SearchSelectionStore.ts b/frontend/src/stores/SearchSelectionStore.ts index c04f2ff6..13320796 100644 --- a/frontend/src/stores/SearchSelectionStore.ts +++ b/frontend/src/stores/SearchSelectionStore.ts @@ -2,15 +2,28 @@ import SimpleStore from './SimpleStore'; import { EdgeDescriptor, NodeDescriptor } from '../shared/entities'; import { EdgeTypeDescriptor, NodeTypeDescriptor } from '../shared/schema'; -export type SelectedSearchResult = { - key?: string | number; - content: - | EdgeDescriptor - | EdgeTypeDescriptor - | NodeDescriptor - | NodeTypeDescriptor - | undefined; -}; +export type SelectedSearchResult = + | EdgeDescriptor + | EdgeTypeDescriptor + | NodeDescriptor + | NodeTypeDescriptor + | undefined; + +export const isEdgeDescriptor = ( + e: SelectedSearchResult +): e is EdgeDescriptor => (e === undefined ? false : 'type' in e); + +export const isNodeDescriptor = ( + e: SelectedSearchResult +): e is NodeDescriptor => (e === undefined ? false : 'types' in e); + +export const isEdgeTypeDescriptor = ( + e: SelectedSearchResult +): e is EdgeTypeDescriptor => (e === undefined ? false : 'edge' in e); + +export const isNodeTypeDescriptor = ( + e: SelectedSearchResult +): e is NodeTypeDescriptor => (e === undefined ? false : 'node' in e); /** * Contains the selected search result (if any). @@ -18,6 +31,6 @@ export type SelectedSearchResult = { */ export default class SearchSelectionStore extends SimpleStore { protected getInitialValue(): SelectedSearchResult { - return { content: undefined }; + return undefined; } } diff --git a/frontend/src/stores/colors/EntityStyleProviderImpl.ts b/frontend/src/stores/colors/EntityStyleProviderImpl.ts index bb5454bd..0cc40ae1 100644 --- a/frontend/src/stores/colors/EntityStyleProviderImpl.ts +++ b/frontend/src/stores/colors/EntityStyleProviderImpl.ts @@ -7,7 +7,7 @@ import { EdgeDescriptor, NodeDescriptor } from '../../shared/entities'; import { ArgumentError } from '../../shared/errors'; import { QueryNodeResult, QueryEdgeResult } from '../../shared/queries'; import getTextColor from './getTextColor'; -import EntityStyleStore from './EntityStyleStore'; +import EntityStyleStore, { SelectionInfo } from './EntityStyleStore'; type EntityStyleIntersection = NodeStyle & EdgeStyle; type QueryEntityResult = QueryNodeResult | QueryEdgeResult; @@ -36,7 +36,12 @@ export class EntityStyleProviderImpl implements EntityStyleProvider { color: common.black, text: { color: common.black }, stroke: { - width: this.isSelected(entity) ? 5 : 1, + width: this.isSelected( + entity, + this.entityStyleStore.getEntitySelection() + ) + ? 5 + : 1, dashes: false, color: common.black, }, @@ -122,8 +127,33 @@ export class EntityStyleProviderImpl implements EntityStyleProvider { return entity.virtual === true; } - private isSelected(entity: QueryEntityResult): boolean { - return entity.selected === true; + /** + * Returns true if entity corresponds to the entity that is selected via search. + * @param entity - is compared with the selection + * @param selection - represents the entity that is selected via search + * @private + */ + private isSelected( + entity: QueryEntityResult, + selection: SelectionInfo + ): boolean { + if (selection === undefined) { + return false; + } + + if (this.getTypeOfEntity(entity).includes(selection.kind)) { + if ('id' in selection) { + // selection is single entity + return entity.id === selection.id; + } + if ('type' in selection) { + // selection is type -> multiple entites + return isNodeDescriptor(entity) + ? selection.type === entity.types[0] + : selection.type === entity.type; + } + } + return false; } } diff --git a/frontend/src/stores/colors/EntityStyleStore.ts b/frontend/src/stores/colors/EntityStyleStore.ts index 2721ee85..d3542a2b 100644 --- a/frontend/src/stores/colors/EntityStyleStore.ts +++ b/frontend/src/stores/colors/EntityStyleStore.ts @@ -1,8 +1,19 @@ import 'reflect-metadata'; -import { injectable } from 'inversify'; -import { BehaviorSubject, Observable } from 'rxjs'; +import { inject, injectable } from 'inversify'; +import { BehaviorSubject, Observable, Subscription } from 'rxjs'; import { EntityStyleProvider } from './EntityStyleProvider'; import { EntityStyleProviderImpl } from './EntityStyleProviderImpl'; +import SearchSelectionStore, { + isEdgeDescriptor, + isEdgeTypeDescriptor, + isNodeDescriptor, + isNodeTypeDescriptor, + SelectedSearchResult, +} from '../SearchSelectionStore'; + +export type SelectionInfo = + | { kind: 'NODE' | 'EDGE'; id: number } + | { kind: 'NODE' | 'EDGE'; type: string }; /** * A simple store contains a single state. @@ -11,6 +22,11 @@ import { EntityStyleProviderImpl } from './EntityStyleProviderImpl'; */ @injectable() export class EntityStyleStore { + private searchSelectionStoreSubscription?: Subscription; + + @inject(SearchSelectionStore) + private readonly searchSelectionStore!: SearchSelectionStore; + /** * Contains the state. * Returns the current state immediately after subscribing. @@ -27,11 +43,18 @@ export class EntityStyleStore { protected getEntityColorizer(): EntityStyleProvider { return new EntityStyleProviderImpl(this); } + /** + * Updates the current filter by replacing it completely. + */ + public setState(newState: EntityStyleProvider): void { + this.storeSubject.next(newState); + } /** * Returns an observable that outputs the stored value. */ public getState(): Observable { + this.ensureInit(); return this.storeSubject.pipe(); } @@ -42,6 +65,13 @@ export class EntityStyleStore { return this.storeSubject.value; } + protected ensureInit(): void { + if (this.searchSelectionStoreSubscription == null) { + this.searchSelectionStoreSubscription = + this.subscribeToSearchSelectionStore(); + } + } + private readonly greyScaleEdges = new BehaviorSubject(false); /** @@ -60,6 +90,64 @@ export class EntityStyleStore { this.greyScaleEdges.next(greyScale); this.storeSubject.next(this.getEntityColorizer()); } + + private entitySelection!: SelectionInfo; + + /** + * An adapted {@link SelectedSearchResult} that is used in the {@link EntityStyleProvider}. + */ + public getEntitySelection(): SelectionInfo { + return this.entitySelection; + } + + /** + * Updates the store using a {@link SelectedSearchResult} + * @param selectionResult - the {@link SelectedSearchResult} that is used for updating + * @private + */ + private update(selectionResult: SelectedSearchResult) { + const entitySelection = this.convertToSelectionInfo(selectionResult); + + if (entitySelection !== undefined) { + this.entitySelection = entitySelection; + } + + this.setState(this.getValue()); + } + + /** + * Converts a {@link SelectedSearchResult} to a {@link SelectionInfo} + * @param selectionResult - {@link SelectedSearchResult} that is converted + * @private + */ + private convertToSelectionInfo( + selectionResult: SelectedSearchResult + ): SelectionInfo | undefined { + let entitySelection: SelectionInfo | undefined; + if (isNodeDescriptor(selectionResult)) { + entitySelection = { kind: 'NODE', id: selectionResult.id }; + } else if (isEdgeDescriptor(selectionResult)) { + entitySelection = { kind: 'EDGE', id: selectionResult.id }; + } else if (isNodeTypeDescriptor(selectionResult)) { + entitySelection = { kind: 'NODE', type: selectionResult.name }; + } else if (isEdgeTypeDescriptor(selectionResult)) { + entitySelection = { kind: 'EDGE', type: selectionResult.name }; + } + + return entitySelection; + } + + /** + * Subscribes to the state of the {@link searchSelectionStore} so that the state + * of this store is updated when the {@link searchSelectionStore} updates. + * @returns subscription of the {@link searchSelectionStore} state + * @private + */ + private subscribeToSearchSelectionStore(): Subscription { + return this.searchSelectionStore.getState().subscribe({ + next: (selectionResult) => this.update(selectionResult), + }); + } } export default EntityStyleStore; From 965fedfe32189b7f507b0183dadc28d15484d7ff Mon Sep 17 00:00:00 2001 From: PianoRollRepresentation Date: Sat, 19 Jun 2021 11:53:52 +0200 Subject: [PATCH 05/11] Frontend remove unnecessary selected attribute in QueryEdgeResult.ts and QueryNodeResult.ts --- frontend/src/visualization/filtering/Filter.tsx | 2 -- shared/src/queries/QueryEdgeResult.ts | 5 ----- shared/src/queries/QueryNodeResult.ts | 5 ----- shared/src/schema/EdgeTypeDescriptor.ts | 2 +- shared/src/schema/NodeTypeDescriptor.ts | 2 +- 5 files changed, 2 insertions(+), 14 deletions(-) diff --git a/frontend/src/visualization/filtering/Filter.tsx b/frontend/src/visualization/filtering/Filter.tsx index de12878d..aa73ee43 100644 --- a/frontend/src/visualization/filtering/Filter.tsx +++ b/frontend/src/visualization/filtering/Filter.tsx @@ -148,7 +148,6 @@ const Filter = (): JSX.Element => { id: -1, types: [type.name], virtual: true, - searched: false, } as QueryNodeResult).color, type.name, 'node' @@ -167,7 +166,6 @@ const Filter = (): JSX.Element => { from: -1, to: -1, virtual: true, - searched: false, } as QueryEdgeResult).color, type.name, 'edge' diff --git a/shared/src/queries/QueryEdgeResult.ts b/shared/src/queries/QueryEdgeResult.ts index 36184e17..4df97b69 100644 --- a/shared/src/queries/QueryEdgeResult.ts +++ b/shared/src/queries/QueryEdgeResult.ts @@ -20,9 +20,4 @@ export interface QueryEdgeResult extends EdgeDescriptor { * A boolean that specified whether the edge is part of the path that shall be highlighted. */ isPath?: boolean; - - /** - * Specifies whether this node was selected after search. - */ - selected?: boolean; } diff --git a/shared/src/queries/QueryNodeResult.ts b/shared/src/queries/QueryNodeResult.ts index 4610f378..3b3963d8 100644 --- a/shared/src/queries/QueryNodeResult.ts +++ b/shared/src/queries/QueryNodeResult.ts @@ -20,9 +20,4 @@ export default interface QueryNodeResult extends NodeDescriptor { * A boolean that specified whether the node is part of the path that shall be highlighted. */ isPath?: boolean; - - /** - * Specifies whether this node was selected after search. - */ - selected?: boolean; } diff --git a/shared/src/schema/EdgeTypeDescriptor.ts b/shared/src/schema/EdgeTypeDescriptor.ts index 98a2dea6..7384c1bd 100644 --- a/shared/src/schema/EdgeTypeDescriptor.ts +++ b/shared/src/schema/EdgeTypeDescriptor.ts @@ -8,5 +8,5 @@ export interface EdgeTypeDescriptor extends EntityTypeDescriptor { /** * Used to identify if an object of this type corresponds to an EdgeTypeDescriptor */ - edge: undefined + edge: undefined; } diff --git a/shared/src/schema/NodeTypeDescriptor.ts b/shared/src/schema/NodeTypeDescriptor.ts index 16056db9..19ec2c62 100644 --- a/shared/src/schema/NodeTypeDescriptor.ts +++ b/shared/src/schema/NodeTypeDescriptor.ts @@ -8,5 +8,5 @@ export interface NodeTypeDescriptor extends EntityTypeDescriptor { /** * Used to identify if an object of this type corresponds to an NodeTypeDescriptor */ - node: undefined + node: undefined; } From 9b3daa4c43451a7d297f329de2998cf929199728 Mon Sep 17 00:00:00 2001 From: PianoRollRepresentation Date: Sun, 20 Jun 2021 10:14:11 +0200 Subject: [PATCH 06/11] Shared delete identifier in NodeTypeDescriptor.ts and EdgeTypeDescriptor.ts --- shared/src/schema/EdgeTypeDescriptor.ts | 7 +------ shared/src/schema/NodeTypeDescriptor.ts | 7 +------ 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/shared/src/schema/EdgeTypeDescriptor.ts b/shared/src/schema/EdgeTypeDescriptor.ts index 7384c1bd..a0a860da 100644 --- a/shared/src/schema/EdgeTypeDescriptor.ts +++ b/shared/src/schema/EdgeTypeDescriptor.ts @@ -4,9 +4,4 @@ import { EntityTypeDescriptor } from './EntityTypeDescriptor'; * A descriptor of an edge type. */ // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface EdgeTypeDescriptor extends EntityTypeDescriptor { - /** - * Used to identify if an object of this type corresponds to an EdgeTypeDescriptor - */ - edge: undefined; -} +export interface EdgeTypeDescriptor extends EntityTypeDescriptor {} diff --git a/shared/src/schema/NodeTypeDescriptor.ts b/shared/src/schema/NodeTypeDescriptor.ts index 19ec2c62..9f6444f1 100644 --- a/shared/src/schema/NodeTypeDescriptor.ts +++ b/shared/src/schema/NodeTypeDescriptor.ts @@ -4,9 +4,4 @@ import { EntityTypeDescriptor } from './EntityTypeDescriptor'; * A descriptor of a node type. */ // eslint-disable-next-line @typescript-eslint/no-empty-interface -export interface NodeTypeDescriptor extends EntityTypeDescriptor { - /** - * Used to identify if an object of this type corresponds to an NodeTypeDescriptor - */ - node: undefined; -} +export interface NodeTypeDescriptor extends EntityTypeDescriptor {} From 08e6c756b0382ba4666e7a064b9346ea18b1a8ec Mon Sep 17 00:00:00 2001 From: PianoRollRepresentation Date: Sun, 20 Jun 2021 10:15:42 +0200 Subject: [PATCH 07/11] Frontend add identifier to SelectedSearchResult --- frontend/src/search/Searchbar.tsx | 20 +++++++++++++- frontend/src/stores/SearchSelectionStore.ts | 27 ++++++++++--------- .../src/stores/colors/EntityStyleStore.ts | 13 ++++++--- 3 files changed, 44 insertions(+), 16 deletions(-) diff --git a/frontend/src/search/Searchbar.tsx b/frontend/src/search/Searchbar.tsx index 0eae909e..04a92261 100644 --- a/frontend/src/search/Searchbar.tsx +++ b/frontend/src/search/Searchbar.tsx @@ -29,6 +29,16 @@ import SearchSelectionStore, { SelectedSearchResult, } from '../stores/SearchSelectionStore'; +export type EntityIdentifier = 'Nodes' | 'Edges' | 'Node Types' | 'Edge Types'; + +function isEntityIdentifier( + header: string | number +): header is EntityIdentifier { + return ['Nodes', 'Edges', 'Node Types', 'Edge Types'].some( + (e) => e === header + ); +} + export default function Searchbar(): JSX.Element { const searchService = useService(SearchService); const searchSelectionStore = useService(SearchSelectionStore); @@ -163,7 +173,15 @@ export default function Searchbar(): JSX.Element { key={element.key} button component="a" - onClick={() => onCardSelected(element.entity)} + onClick={() => + onCardSelected( + isEntityIdentifier(result.header) + ? Object.assign(element.entity, { + kind: result.header, + }) + : undefined + ) + } > {element.element} diff --git a/frontend/src/stores/SearchSelectionStore.ts b/frontend/src/stores/SearchSelectionStore.ts index 13320796..ab8195ca 100644 --- a/frontend/src/stores/SearchSelectionStore.ts +++ b/frontend/src/stores/SearchSelectionStore.ts @@ -1,29 +1,32 @@ import SimpleStore from './SimpleStore'; import { EdgeDescriptor, NodeDescriptor } from '../shared/entities'; import { EdgeTypeDescriptor, NodeTypeDescriptor } from '../shared/schema'; +import { EntityIdentifier } from '../search/Searchbar'; export type SelectedSearchResult = - | EdgeDescriptor - | EdgeTypeDescriptor - | NodeDescriptor - | NodeTypeDescriptor + | (( + | EdgeDescriptor + | EdgeTypeDescriptor + | NodeDescriptor + | NodeTypeDescriptor + ) & { kind: EntityIdentifier }) | undefined; export const isEdgeDescriptor = ( e: SelectedSearchResult -): e is EdgeDescriptor => (e === undefined ? false : 'type' in e); +): e is EdgeDescriptor & { kind: EntityIdentifier } => + e === undefined ? false : e.kind === 'Edges'; export const isNodeDescriptor = ( e: SelectedSearchResult -): e is NodeDescriptor => (e === undefined ? false : 'types' in e); +): e is NodeDescriptor & { kind: EntityIdentifier } => + e === undefined ? false : e.kind === 'Nodes'; -export const isEdgeTypeDescriptor = ( - e: SelectedSearchResult -): e is EdgeTypeDescriptor => (e === undefined ? false : 'edge' in e); +export const isEdgeTypeDescriptor = (e: SelectedSearchResult): boolean => + e === undefined ? false : e.kind === 'Edge Types'; -export const isNodeTypeDescriptor = ( - e: SelectedSearchResult -): e is NodeTypeDescriptor => (e === undefined ? false : 'node' in e); +export const isNodeTypeDescriptor = (e: SelectedSearchResult): boolean => + e === undefined ? false : e.kind === 'Node Types'; /** * Contains the selected search result (if any). diff --git a/frontend/src/stores/colors/EntityStyleStore.ts b/frontend/src/stores/colors/EntityStyleStore.ts index d3542a2b..257cfcea 100644 --- a/frontend/src/stores/colors/EntityStyleStore.ts +++ b/frontend/src/stores/colors/EntityStyleStore.ts @@ -10,6 +10,7 @@ import SearchSelectionStore, { isNodeTypeDescriptor, SelectedSearchResult, } from '../SearchSelectionStore'; +import { EdgeTypeDescriptor, NodeTypeDescriptor } from '../../shared/schema'; export type SelectionInfo = | { kind: 'NODE' | 'EDGE'; id: number } @@ -128,10 +129,16 @@ export class EntityStyleStore { entitySelection = { kind: 'NODE', id: selectionResult.id }; } else if (isEdgeDescriptor(selectionResult)) { entitySelection = { kind: 'EDGE', id: selectionResult.id }; - } else if (isNodeTypeDescriptor(selectionResult)) { - entitySelection = { kind: 'NODE', type: selectionResult.name }; } else if (isEdgeTypeDescriptor(selectionResult)) { - entitySelection = { kind: 'EDGE', type: selectionResult.name }; + entitySelection = { + kind: 'EDGE', + type: (selectionResult as EdgeTypeDescriptor).name, + }; + } else if (isNodeTypeDescriptor(selectionResult)) { + entitySelection = { + kind: 'NODE', + type: (selectionResult as NodeTypeDescriptor).name, + }; } return entitySelection; From 17fd0bc9fe3d6bbabf447ff7ca737113d22d1ed5 Mon Sep 17 00:00:00 2001 From: Johannes Jablonski Date: Sun, 20 Jun 2021 14:59:55 +0200 Subject: [PATCH 08/11] adds discriminated union types to selected search results --- frontend/src/search/SearchEntryConverter.tsx | 8 +- frontend/src/search/SearchResultList.ts | 9 +- frontend/src/search/Searchbar.tsx | 20 +--- frontend/src/stores/SearchSelectionStore.ts | 48 +++++----- .../stores/colors/EntityStyleProviderImpl.ts | 51 ++++++----- .../src/stores/colors/EntityStyleStore.ts | 91 ++++++++----------- 6 files changed, 97 insertions(+), 130 deletions(-) diff --git a/frontend/src/search/SearchEntryConverter.tsx b/frontend/src/search/SearchEntryConverter.tsx index 7947023b..85eabe82 100644 --- a/frontend/src/search/SearchEntryConverter.tsx +++ b/frontend/src/search/SearchEntryConverter.tsx @@ -50,7 +50,7 @@ export default function convertSearchResultToSearchResultList( {formatEntry(n)} ), - entity: n, + entity: { ...n, interfaceType: 'NodeDescriptor' as const }, })), }, { @@ -67,7 +67,7 @@ export default function convertSearchResultToSearchResultList(  {formatEntry(e)} ), - entity: e, + entity: { ...e, interfaceType: 'EdgeDescriptor' as const }, })), }, { @@ -81,7 +81,7 @@ export default function convertSearchResultToSearchResultList( color={styleProvider.getStyle({ id: -1, types: [t.name] }).color} /> ), - entity: t, + entity: { ...t, interfaceType: 'NodeTypeDescriptor' as const }, })), }, { @@ -98,7 +98,7 @@ export default function convertSearchResultToSearchResultList( } /> ), - entity: t, + entity: { ...t, interfaceType: 'EdgeTypeDescriptor' as const }, })), }, ] diff --git a/frontend/src/search/SearchResultList.ts b/frontend/src/search/SearchResultList.ts index d78a4f27..dd9df1bd 100644 --- a/frontend/src/search/SearchResultList.ts +++ b/frontend/src/search/SearchResultList.ts @@ -1,5 +1,4 @@ -import { EdgeDescriptor, NodeDescriptor } from '../shared/entities'; -import { EdgeTypeDescriptor, NodeTypeDescriptor } from '../shared/schema'; +import { SelectedSearchResult } from '../stores/SearchSelectionStore'; export default interface SearchResultList { key: number | string; @@ -7,10 +6,6 @@ export default interface SearchResultList { elements: { key: number | string; element: JSX.Element; - entity: - | EdgeDescriptor - | EdgeTypeDescriptor - | NodeDescriptor - | NodeTypeDescriptor; + entity: SelectedSearchResult; }[]; } diff --git a/frontend/src/search/Searchbar.tsx b/frontend/src/search/Searchbar.tsx index 04a92261..0eae909e 100644 --- a/frontend/src/search/Searchbar.tsx +++ b/frontend/src/search/Searchbar.tsx @@ -29,16 +29,6 @@ import SearchSelectionStore, { SelectedSearchResult, } from '../stores/SearchSelectionStore'; -export type EntityIdentifier = 'Nodes' | 'Edges' | 'Node Types' | 'Edge Types'; - -function isEntityIdentifier( - header: string | number -): header is EntityIdentifier { - return ['Nodes', 'Edges', 'Node Types', 'Edge Types'].some( - (e) => e === header - ); -} - export default function Searchbar(): JSX.Element { const searchService = useService(SearchService); const searchSelectionStore = useService(SearchSelectionStore); @@ -173,15 +163,7 @@ export default function Searchbar(): JSX.Element { key={element.key} button component="a" - onClick={() => - onCardSelected( - isEntityIdentifier(result.header) - ? Object.assign(element.entity, { - kind: result.header, - }) - : undefined - ) - } + onClick={() => onCardSelected(element.entity)} > {element.element} diff --git a/frontend/src/stores/SearchSelectionStore.ts b/frontend/src/stores/SearchSelectionStore.ts index ab8195ca..752d6795 100644 --- a/frontend/src/stores/SearchSelectionStore.ts +++ b/frontend/src/stores/SearchSelectionStore.ts @@ -1,39 +1,35 @@ import SimpleStore from './SimpleStore'; import { EdgeDescriptor, NodeDescriptor } from '../shared/entities'; import { EdgeTypeDescriptor, NodeTypeDescriptor } from '../shared/schema'; -import { EntityIdentifier } from '../search/Searchbar'; -export type SelectedSearchResult = - | (( - | EdgeDescriptor - | EdgeTypeDescriptor - | NodeDescriptor - | NodeTypeDescriptor - ) & { kind: EntityIdentifier }) - | undefined; - -export const isEdgeDescriptor = ( - e: SelectedSearchResult -): e is EdgeDescriptor & { kind: EntityIdentifier } => - e === undefined ? false : e.kind === 'Edges'; - -export const isNodeDescriptor = ( - e: SelectedSearchResult -): e is NodeDescriptor & { kind: EntityIdentifier } => - e === undefined ? false : e.kind === 'Nodes'; - -export const isEdgeTypeDescriptor = (e: SelectedSearchResult): boolean => - e === undefined ? false : e.kind === 'Edge Types'; +// enables discriminated union types https://www.typescriptlang.org/docs/handbook/unions-and-intersections.html#discriminating-unions +interface TypedEdgeDescriptor extends EdgeDescriptor { + interfaceType: 'EdgeDescriptor'; +} +interface TypedEdgeTypeDescriptor extends EdgeTypeDescriptor { + interfaceType: 'EdgeTypeDescriptor'; +} +interface TypedNodeDescriptor extends NodeDescriptor { + interfaceType: 'NodeDescriptor'; +} +interface TypedNodeTypeDescriptor extends NodeTypeDescriptor { + interfaceType: 'NodeTypeDescriptor'; +} -export const isNodeTypeDescriptor = (e: SelectedSearchResult): boolean => - e === undefined ? false : e.kind === 'Node Types'; +export type SelectedSearchResult = + | TypedEdgeDescriptor + | TypedEdgeTypeDescriptor + | TypedNodeDescriptor + | TypedNodeTypeDescriptor; /** * Contains the selected search result (if any). * It's the search result that was clicked on in the search bar. */ -export default class SearchSelectionStore extends SimpleStore { - protected getInitialValue(): SelectedSearchResult { +export default class SearchSelectionStore extends SimpleStore< + SelectedSearchResult | undefined +> { + protected getInitialValue(): SelectedSearchResult | undefined { return undefined; } } diff --git a/frontend/src/stores/colors/EntityStyleProviderImpl.ts b/frontend/src/stores/colors/EntityStyleProviderImpl.ts index 0cc40ae1..1f7989d2 100644 --- a/frontend/src/stores/colors/EntityStyleProviderImpl.ts +++ b/frontend/src/stores/colors/EntityStyleProviderImpl.ts @@ -1,13 +1,13 @@ import 'reflect-metadata'; import { common } from '@material-ui/core/colors'; -import { NodeStyle, EdgeStyle } from './EntityStyle'; +import { EdgeStyle, NodeStyle } from './EntityStyle'; import { EntityStyleProvider } from './EntityStyleProvider'; import getNthColor from './getNthColor'; import { EdgeDescriptor, NodeDescriptor } from '../../shared/entities'; import { ArgumentError } from '../../shared/errors'; -import { QueryNodeResult, QueryEdgeResult } from '../../shared/queries'; +import { QueryEdgeResult, QueryNodeResult } from '../../shared/queries'; import getTextColor from './getTextColor'; -import EntityStyleStore, { SelectionInfo } from './EntityStyleStore'; +import EntityStyleStore from './EntityStyleStore'; type EntityStyleIntersection = NodeStyle & EdgeStyle; type QueryEntityResult = QueryNodeResult | QueryEdgeResult; @@ -36,12 +36,7 @@ export class EntityStyleProviderImpl implements EntityStyleProvider { color: common.black, text: { color: common.black }, stroke: { - width: this.isSelected( - entity, - this.entityStyleStore.getEntitySelection() - ) - ? 5 - : 1, + width: this.isSelected(entity) ? 5 : 1, dashes: false, color: common.black, }, @@ -130,29 +125,39 @@ export class EntityStyleProviderImpl implements EntityStyleProvider { /** * Returns true if entity corresponds to the entity that is selected via search. * @param entity - is compared with the selection - * @param selection - represents the entity that is selected via search * @private */ - private isSelected( - entity: QueryEntityResult, - selection: SelectionInfo - ): boolean { + private isSelected(entity: QueryEntityResult): boolean { + const selection = this.entityStyleStore.getEntitySelectionInfo(); if (selection === undefined) { return false; } - if (this.getTypeOfEntity(entity).includes(selection.kind)) { - if ('id' in selection) { - // selection is single entity - return entity.id === selection.id; + // check entity type + if ( + !( + (selection.kind === 'EDGE' && isEdgeDescriptor(entity)) || + (selection.kind === 'NODE' && isNodeDescriptor(entity)) + ) + ) { + return false; + } + + if ('id' in selection) { + // single entity selected => check if entity is that entity + return entity.id === selection.id; + } + + if ('type' in selection) { + // type selected => check if entity is of that type + if (isNodeDescriptor(entity)) { + return entity.types.some((type) => type === selection.type); } - if ('type' in selection) { - // selection is type -> multiple entites - return isNodeDescriptor(entity) - ? selection.type === entity.types[0] - : selection.type === entity.type; + if (isEdgeDescriptor(entity)) { + return entity.type === selection.type; } } + return false; } } diff --git a/frontend/src/stores/colors/EntityStyleStore.ts b/frontend/src/stores/colors/EntityStyleStore.ts index 257cfcea..1258d5d8 100644 --- a/frontend/src/stores/colors/EntityStyleStore.ts +++ b/frontend/src/stores/colors/EntityStyleStore.ts @@ -4,18 +4,38 @@ import { BehaviorSubject, Observable, Subscription } from 'rxjs'; import { EntityStyleProvider } from './EntityStyleProvider'; import { EntityStyleProviderImpl } from './EntityStyleProviderImpl'; import SearchSelectionStore, { - isEdgeDescriptor, - isEdgeTypeDescriptor, - isNodeDescriptor, - isNodeTypeDescriptor, SelectedSearchResult, } from '../SearchSelectionStore'; -import { EdgeTypeDescriptor, NodeTypeDescriptor } from '../../shared/schema'; +import { ArgumentError } from '../../shared/errors'; export type SelectionInfo = | { kind: 'NODE' | 'EDGE'; id: number } | { kind: 'NODE' | 'EDGE'; type: string }; +/** + * Converts a {@link SelectedSearchResult} to a {@link SelectionInfo} + * @param selectionResult - {@link SelectedSearchResult} that is converted + * @private + */ +function createSelectionInfo( + selectionResult?: SelectedSearchResult +): SelectionInfo | undefined { + if (selectionResult === undefined) return undefined; + + switch (selectionResult.interfaceType) { + case 'EdgeDescriptor': + return { kind: 'EDGE', id: selectionResult.id }; + case 'NodeDescriptor': + return { kind: 'NODE', id: selectionResult.id }; + case 'EdgeTypeDescriptor': + return { kind: 'EDGE', type: selectionResult.name }; + case 'NodeTypeDescriptor': + return { kind: 'NODE', type: selectionResult.name }; + default: + throw new ArgumentError(`Unknown selection`); + } +} + /** * A simple store contains a single state. * The current state can be retrieved with {@link getValue} (just once) @@ -37,6 +57,16 @@ export class EntityStyleStore { this.getEntityColorizer() ); + /** + * Contains information about the current selected entity. + * @private + */ + private entitySelectionInfo?: SelectionInfo; + + public getEntitySelectionInfo(): SelectionInfo | undefined { + return this.entitySelectionInfo; + } + /** * Returns the entity colorizing function. * @protected @@ -63,6 +93,7 @@ export class EntityStyleStore { * Returns the current value of the stored value. */ public getValue(): EntityStyleProvider { + this.ensureInit(); return this.storeSubject.value; } @@ -92,56 +123,14 @@ export class EntityStyleStore { this.storeSubject.next(this.getEntityColorizer()); } - private entitySelection!: SelectionInfo; - - /** - * An adapted {@link SelectedSearchResult} that is used in the {@link EntityStyleProvider}. - */ - public getEntitySelection(): SelectionInfo { - return this.entitySelection; - } - /** * Updates the store using a {@link SelectedSearchResult} * @param selectionResult - the {@link SelectedSearchResult} that is used for updating * @private */ - private update(selectionResult: SelectedSearchResult) { - const entitySelection = this.convertToSelectionInfo(selectionResult); - - if (entitySelection !== undefined) { - this.entitySelection = entitySelection; - } - - this.setState(this.getValue()); - } - - /** - * Converts a {@link SelectedSearchResult} to a {@link SelectionInfo} - * @param selectionResult - {@link SelectedSearchResult} that is converted - * @private - */ - private convertToSelectionInfo( - selectionResult: SelectedSearchResult - ): SelectionInfo | undefined { - let entitySelection: SelectionInfo | undefined; - if (isNodeDescriptor(selectionResult)) { - entitySelection = { kind: 'NODE', id: selectionResult.id }; - } else if (isEdgeDescriptor(selectionResult)) { - entitySelection = { kind: 'EDGE', id: selectionResult.id }; - } else if (isEdgeTypeDescriptor(selectionResult)) { - entitySelection = { - kind: 'EDGE', - type: (selectionResult as EdgeTypeDescriptor).name, - }; - } else if (isNodeTypeDescriptor(selectionResult)) { - entitySelection = { - kind: 'NODE', - type: (selectionResult as NodeTypeDescriptor).name, - }; - } - - return entitySelection; + private nextSelectionResult(selectionResult?: SelectedSearchResult) { + this.entitySelectionInfo = createSelectionInfo(selectionResult); + this.storeSubject.next(this.getEntityColorizer()); } /** @@ -152,7 +141,7 @@ export class EntityStyleStore { */ private subscribeToSearchSelectionStore(): Subscription { return this.searchSelectionStore.getState().subscribe({ - next: (selectionResult) => this.update(selectionResult), + next: (selectionResult) => this.nextSelectionResult(selectionResult), }); } } From 90434009dae08680392402e8882be4abbfb677b1 Mon Sep 17 00:00:00 2001 From: PianoRollRepresentation Date: Mon, 21 Jun 2021 14:27:05 +0200 Subject: [PATCH 09/11] Frontend adjust EntityStyleStore.ts to pass unit tests --- .../cypress/unit/stores/colors/EdgeColorStore.test.ts | 5 ++++- .../cypress/unit/stores/colors/EntityColorStore.test.ts | 9 ++++++--- .../cypress/unit/stores/colors/NodeColorStore.test.ts | 5 ++++- frontend/src/configureServices.ts | 9 ++++++++- frontend/src/stores/colors/EntityStyleStore.ts | 6 ++++-- 5 files changed, 26 insertions(+), 8 deletions(-) diff --git a/frontend/cypress/unit/stores/colors/EdgeColorStore.test.ts b/frontend/cypress/unit/stores/colors/EdgeColorStore.test.ts index 085d39c6..4f5e7e35 100644 --- a/frontend/cypress/unit/stores/colors/EdgeColorStore.test.ts +++ b/frontend/cypress/unit/stores/colors/EdgeColorStore.test.ts @@ -1,8 +1,10 @@ import { EdgeDescriptor } from '../../../../src/shared/entities'; import { EntityStyleStore } from '../../../../src/stores/colors'; +import SearchSelectionStore from '../../../../src/stores/SearchSelectionStore'; describe('EdgeColorStore', () => { let entityStyleStore: EntityStyleStore; + let searchSelectionStore: SearchSelectionStore; const dummies: EdgeDescriptor[] = [ { id: 1, type: 'HELLO', from: 1, to: 1 }, { id: 2, type: 'WORLD', from: 2, to: 2 }, @@ -11,7 +13,8 @@ describe('EdgeColorStore', () => { ]; beforeEach(() => { - entityStyleStore = new EntityStyleStore(); + searchSelectionStore = new SearchSelectionStore(); + entityStyleStore = new EntityStyleStore(searchSelectionStore); }); it('should return colors for types', () => { diff --git a/frontend/cypress/unit/stores/colors/EntityColorStore.test.ts b/frontend/cypress/unit/stores/colors/EntityColorStore.test.ts index deab4c6f..a25bc090 100644 --- a/frontend/cypress/unit/stores/colors/EntityColorStore.test.ts +++ b/frontend/cypress/unit/stores/colors/EntityColorStore.test.ts @@ -3,20 +3,23 @@ import { NodeDescriptor, } from '../../../../src/shared/entities'; import { EntityStyleStore } from '../../../../src/stores/colors'; +import SearchSelectionStore from '../../../../src/stores/SearchSelectionStore'; describe('EntityColorStore', () => { - let entityColorStore: EntityStyleStore; + let entityStyleStore: EntityStyleStore; + let searchSelectionStore: SearchSelectionStore; const dummies: (EdgeDescriptor | NodeDescriptor)[] = [ { id: 1, type: 'HELLO', from: 1, to: 1 }, { id: 1, types: ['HELLO'] }, ]; beforeEach(() => { - entityColorStore = new EntityStyleStore(); + searchSelectionStore = new SearchSelectionStore(); + entityStyleStore = new EntityStyleStore(searchSelectionStore); }); it('should return different colors for different entity types', () => { - const styleProvider = entityColorStore.getValue(); + const styleProvider = entityStyleStore.getValue(); const color1 = styleProvider.getStyle(dummies[0] as EdgeDescriptor); const color2 = styleProvider.getStyle(dummies[1] as NodeDescriptor); diff --git a/frontend/cypress/unit/stores/colors/NodeColorStore.test.ts b/frontend/cypress/unit/stores/colors/NodeColorStore.test.ts index addf4b67..d173ab55 100644 --- a/frontend/cypress/unit/stores/colors/NodeColorStore.test.ts +++ b/frontend/cypress/unit/stores/colors/NodeColorStore.test.ts @@ -1,8 +1,10 @@ import { NodeDescriptor } from '../../../../src/shared/entities'; import { EntityStyleStore } from '../../../../src/stores/colors'; +import SearchSelectionStore from '../../../../src/stores/SearchSelectionStore'; describe('NodeColorStore', () => { let entityStyleStore: EntityStyleStore; + let searchSelectionStore: SearchSelectionStore; const dummies: NodeDescriptor[] = [ { id: 1, types: ['HELLO'] }, { id: 2, types: ['WORLD'] }, @@ -10,7 +12,8 @@ describe('NodeColorStore', () => { ]; beforeEach(() => { - entityStyleStore = new EntityStyleStore(); + searchSelectionStore = new SearchSelectionStore(); + entityStyleStore = new EntityStyleStore(searchSelectionStore); }); it('should return colors for types', () => { diff --git a/frontend/src/configureServices.ts b/frontend/src/configureServices.ts index 0018c3c5..4f31c570 100644 --- a/frontend/src/configureServices.ts +++ b/frontend/src/configureServices.ts @@ -64,7 +64,14 @@ export default function configureServices(container: Container): void { container.bind(FilterStateStore).to(FilterStateStore).inSingletonScope(); container.bind(FilterQueryStore).to(FilterQueryStore).inSingletonScope(); container.bind(QueryResultStore).to(QueryResultStore).inSingletonScope(); - container.bind(EntityStyleStore).to(EntityStyleStore).inSingletonScope(); + container + .bind(EntityStyleStore) + .toDynamicValue( + (context) => + new EntityStyleStore(context.container.get(SearchSelectionStore)) + ) + .inSingletonScope(); + container.bind(ShortestPathStateStore).toSelf().inSingletonScope(); container.bind(ExplorationStore).toSelf().inSingletonScope(); container.bind(SearchSelectionStore).toSelf().inSingletonScope(); diff --git a/frontend/src/stores/colors/EntityStyleStore.ts b/frontend/src/stores/colors/EntityStyleStore.ts index 1258d5d8..c3ac4a4d 100644 --- a/frontend/src/stores/colors/EntityStyleStore.ts +++ b/frontend/src/stores/colors/EntityStyleStore.ts @@ -45,8 +45,10 @@ function createSelectionInfo( export class EntityStyleStore { private searchSelectionStoreSubscription?: Subscription; - @inject(SearchSelectionStore) - private readonly searchSelectionStore!: SearchSelectionStore; + constructor( + @inject(SearchSelectionStore) + private readonly searchSelectionStore: SearchSelectionStore + ) {} /** * Contains the state. From 4acacdc1bc87c567be78f98f7caf291c2be99657 Mon Sep 17 00:00:00 2001 From: PianoRollRepresentation Date: Mon, 21 Jun 2021 18:53:50 +0200 Subject: [PATCH 10/11] Frontend add tests for EntityStyleStore --- .../stores/colors/EntityColorStore.test.ts | 28 ----- .../stores/colors/EntityStyleStore.test.ts | 101 ++++++++++++++++++ 2 files changed, 101 insertions(+), 28 deletions(-) delete mode 100644 frontend/cypress/unit/stores/colors/EntityColorStore.test.ts create mode 100644 frontend/cypress/unit/stores/colors/EntityStyleStore.test.ts diff --git a/frontend/cypress/unit/stores/colors/EntityColorStore.test.ts b/frontend/cypress/unit/stores/colors/EntityColorStore.test.ts deleted file mode 100644 index a25bc090..00000000 --- a/frontend/cypress/unit/stores/colors/EntityColorStore.test.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { - EdgeDescriptor, - NodeDescriptor, -} from '../../../../src/shared/entities'; -import { EntityStyleStore } from '../../../../src/stores/colors'; -import SearchSelectionStore from '../../../../src/stores/SearchSelectionStore'; - -describe('EntityColorStore', () => { - let entityStyleStore: EntityStyleStore; - let searchSelectionStore: SearchSelectionStore; - const dummies: (EdgeDescriptor | NodeDescriptor)[] = [ - { id: 1, type: 'HELLO', from: 1, to: 1 }, - { id: 1, types: ['HELLO'] }, - ]; - - beforeEach(() => { - searchSelectionStore = new SearchSelectionStore(); - entityStyleStore = new EntityStyleStore(searchSelectionStore); - }); - - it('should return different colors for different entity types', () => { - const styleProvider = entityStyleStore.getValue(); - const color1 = styleProvider.getStyle(dummies[0] as EdgeDescriptor); - const color2 = styleProvider.getStyle(dummies[1] as NodeDescriptor); - - expect(color1).not.to.be.deep.eq(color2); - }); -}); diff --git a/frontend/cypress/unit/stores/colors/EntityStyleStore.test.ts b/frontend/cypress/unit/stores/colors/EntityStyleStore.test.ts new file mode 100644 index 00000000..ce775b13 --- /dev/null +++ b/frontend/cypress/unit/stores/colors/EntityStyleStore.test.ts @@ -0,0 +1,101 @@ +import { + EdgeDescriptor, + NodeDescriptor, +} from '../../../../src/shared/entities'; +import { EntityStyleStore } from '../../../../src/stores/colors'; +import SearchSelectionStore, { + SelectedSearchResult, +} from '../../../../src/stores/SearchSelectionStore'; + +describe('EntityStyleStore', () => { + let entityStyleStore: EntityStyleStore; + let searchSelectionStore: SearchSelectionStore; + const entityDummies: (EdgeDescriptor | NodeDescriptor)[] = [ + { id: 1, type: 'EDGE', from: 1, to: 1 }, + { id: 2, type: 'EDGE', from: 2, to: 2 }, + { id: 1, types: ['NODE'] }, + { id: 2, types: ['NODE'] }, + ]; + + const searchSelectionDummies: SelectedSearchResult[] = [ + { id: 1, type: 'EDGE', from: 1, to: 1, interfaceType: 'EdgeDescriptor' }, + { id: 2, type: 'EDGE', from: 2, to: 2, interfaceType: 'EdgeDescriptor' }, + { id: 1, types: ['NODE'], interfaceType: 'NodeDescriptor' }, + { id: 2, types: ['NODE'], interfaceType: 'NodeDescriptor' }, + { name: 'EDGE', interfaceType: 'EdgeTypeDescriptor' }, + { name: 'NODE', interfaceType: 'NodeTypeDescriptor' }, + ]; + + beforeEach(() => { + searchSelectionStore = new SearchSelectionStore(); + entityStyleStore = new EntityStyleStore(searchSelectionStore); + }); + + it('should return different colors for different entity types', () => { + const styleProvider = entityStyleStore.getValue(); + const color1 = styleProvider.getStyle(entityDummies[0] as EdgeDescriptor); + const color2 = styleProvider.getStyle(entityDummies[2] as NodeDescriptor); + + expect(color1).not.to.be.deep.eq(color2); + }); + + context('change stroke width', () => { + it('should change stroke width for a single edge search selection update', () => { + searchSelectionStore.setState(searchSelectionDummies[0]); + + const styleProvider = entityStyleStore.getValue(); + + const edgeStyle = styleProvider.getStyle( + entityDummies[0] as EdgeDescriptor + ); + + expect(edgeStyle.stroke.width).to.be.eq(5); + }); + + it('should change stroke width for a single node search selection update', () => { + searchSelectionStore.setState(searchSelectionDummies[2]); + + const styleProvider = entityStyleStore.getValue(); + + const nodeStyle = styleProvider.getStyle( + entityDummies[2] as NodeDescriptor + ); + + expect(nodeStyle.stroke.width).to.be.eq(5); + }); + + it('should change stroke width for an edge type search selection update', () => { + searchSelectionStore.setState(searchSelectionDummies[4]); + + const styleProvider = entityStyleStore.getValue(); + + const edgeStyle1 = styleProvider.getStyle( + entityDummies[0] as NodeDescriptor + ); + + const edgeStyle2 = styleProvider.getStyle( + entityDummies[1] as NodeDescriptor + ); + + expect(edgeStyle1.stroke.width).to.be.eq(5); + expect(edgeStyle2.stroke.width).to.be.eq(5); + }); + + it('should change stroke width for a node type search selection update', () => { + searchSelectionStore.setState(searchSelectionDummies[5]); + + const styleProvider = entityStyleStore.getValue(); + + const nodeStyle1 = styleProvider.getStyle( + entityDummies[2] as NodeDescriptor + ); + + const nodeStyle2 = styleProvider.getStyle( + entityDummies[3] as NodeDescriptor + ); + + expect(nodeStyle1.stroke.width).to.be.eq(5); + expect(nodeStyle2.stroke.width).to.be.eq(5); + }); + }); +}); From 39a7755cb8ec18a78d3c6b868a72e09e55a74b15 Mon Sep 17 00:00:00 2001 From: PianoRollRepresentation Date: Mon, 21 Jun 2021 19:10:24 +0200 Subject: [PATCH 11/11] Frontend remove untestable code from coverage --- .../cypress/unit/stores/colors/EntityStyleStore.test.ts | 2 +- frontend/src/stores/colors/EntityStyle.ts | 1 + frontend/src/stores/colors/EntityStyleProvider.ts | 1 + frontend/src/stores/colors/EntityStyleStore.ts | 9 +++------ 4 files changed, 6 insertions(+), 7 deletions(-) diff --git a/frontend/cypress/unit/stores/colors/EntityStyleStore.test.ts b/frontend/cypress/unit/stores/colors/EntityStyleStore.test.ts index ce775b13..1174c945 100644 --- a/frontend/cypress/unit/stores/colors/EntityStyleStore.test.ts +++ b/frontend/cypress/unit/stores/colors/EntityStyleStore.test.ts @@ -39,7 +39,7 @@ describe('EntityStyleStore', () => { expect(color1).not.to.be.deep.eq(color2); }); - context('change stroke width', () => { + describe('change stroke width', () => { it('should change stroke width for a single edge search selection update', () => { searchSelectionStore.setState(searchSelectionDummies[0]); diff --git a/frontend/src/stores/colors/EntityStyle.ts b/frontend/src/stores/colors/EntityStyle.ts index 8f9b0c70..2b6d4456 100644 --- a/frontend/src/stores/colors/EntityStyle.ts +++ b/frontend/src/stores/colors/EntityStyle.ts @@ -1,3 +1,4 @@ +/* istanbul ignore file */ /** * Described the stroke style of an entity. */ diff --git a/frontend/src/stores/colors/EntityStyleProvider.ts b/frontend/src/stores/colors/EntityStyleProvider.ts index 7141e617..e2fab897 100644 --- a/frontend/src/stores/colors/EntityStyleProvider.ts +++ b/frontend/src/stores/colors/EntityStyleProvider.ts @@ -1,3 +1,4 @@ +/* istanbul ignore file */ import { NodeStyle, EdgeStyle } from './EntityStyle'; import { EdgeDescriptor, NodeDescriptor } from '../../shared/entities'; diff --git a/frontend/src/stores/colors/EntityStyleStore.ts b/frontend/src/stores/colors/EntityStyleStore.ts index c3ac4a4d..018d3933 100644 --- a/frontend/src/stores/colors/EntityStyleStore.ts +++ b/frontend/src/stores/colors/EntityStyleStore.ts @@ -31,6 +31,7 @@ function createSelectionInfo( return { kind: 'EDGE', type: selectionResult.name }; case 'NodeTypeDescriptor': return { kind: 'NODE', type: selectionResult.name }; + /* istanbul ignore next */ default: throw new ArgumentError(`Unknown selection`); } @@ -76,13 +77,8 @@ export class EntityStyleStore { protected getEntityColorizer(): EntityStyleProvider { return new EntityStyleProviderImpl(this); } - /** - * Updates the current filter by replacing it completely. - */ - public setState(newState: EntityStyleProvider): void { - this.storeSubject.next(newState); - } + /* istanbul ignore next */ /** * Returns an observable that outputs the stored value. */ @@ -108,6 +104,7 @@ export class EntityStyleStore { private readonly greyScaleEdges = new BehaviorSubject(false); + /* istanbul ignore next */ /** * Getter for greyScaleEdges property * @returns An Observable holding a boolean determining whether edges should be in greyscale or colored