Skip to content

Comments

#397: Highlight States#412

Closed
ethanluc7 wants to merge 33 commits intomainfrom
397-highlight-states
Closed

#397: Highlight States#412
ethanluc7 wants to merge 33 commits intomainfrom
397-highlight-states

Conversation

@ethanluc7
Copy link
Contributor

Related to issue: #397

Summary

Adds interactive highlight states to the Entity Relationship Diagram, allowing users to click on schema nodes to activate/deactivate edges and hover over rows and edges. Also includes a comprehensive set of dictionaries (single schema, linear chains, fan-out, cyclical, compound keys, etc.) for testing various ERD topologies.

Description of Changes

  • ActiveRelationshipContext.tsx (new) — React context and provider for tracking which relationships/edges are currently active or highlighted
    • EntityRelationshipDiagram.tsx — Integrated the active relationship provider and wired up click/hover interactions on nodes and edges
    • SchemaNode.tsx — Updated schema nodes to support active/inactive visual states and trigger edge highlighting on click
    • diagramUtils.ts — Added utility functions for building relationship maps, determining schema chains, and computing edge highlight states
    • schemaNodeStyles.ts — Added CSS styles for highlighted, dimmed, and hover states on schema nodes
    • OneCardinalityMarker.tsx — Updated cardinality marker to support active/inactive color states
    • DiagramSubtitle.tsx (new) — Component that renders the selected schema chain as a subtitle in the diagram modal
    • DiagramViewButton.tsx — Integrated the subtitle and relationship provider into the diagram modal flow
    • Modal.tsx — Extended to accept a ReactNode as the subtitle prop
    • index.ts — Updated exports to include new utilities and context
    • Storybook fixtures (new) — Added 11 JSON fixture dictionaries covering single schema, two isolated schemas, linear chains, fan-out, cyclical, compound keys, mixed relations, multi-FK, non-unique FK, and a simplified clinical example
    • EntityRelationshipDiagram.stories.tsx — Added stories for each new fixture

Readiness Checklist

  • Self Review
    • I have performed a self review of code
    • I have run the application locally and manually tested the feature
    • I have checked all updates to correct typos and misspellings
  • Formatting
    • Code follows the project style guide
    • Autmated code formatters (ie. Prettier) have been run
  • Local Testing
    • Successfully built all packages locally
    • Successfully ran all test suites, all unit and integration tests pass
  • Updated Tests
    • Unit and integration tests have been added that describe the bug that was fixed or the features that were added
  • Documentation
    • All new environment variables added to .env.schema file and documented in the README
    • All changes to server HTTP endpoints have open-api documentation
    • All new functions exported from their module have TSDoc comment documentation

Copy link
Contributor

@joneubank joneubank left a comment

Choose a reason for hiding this comment

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

PR has moved to #413, sending my review comments here to keep the record.

justify-content: space-between;
transition: background-color 0.2s;
position: relative;
background-color: ${isHighlighted ? theme.colors.secondary_1 : isEven ? '#e5edf3' : 'transparent'};
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider using css selectors for alternating colours instead of JS props:
https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Selectors/:nth-child

Comment on lines +25 to +29
type ActiveRelationshipState = {
edgeIds: Set<string>;
fieldKeys: Set<string>;
schemaChain: string[];
} | null;
Copy link
Contributor

Choose a reason for hiding this comment

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

Recommend defining the type without the union to null. That way we can use this type to represent known data, and we can always present union with null if the value could be missing.

Comment on lines +32 to +35
activeEdgeIds: Set<string> | null;
activeFieldKeys: Set<string> | null;
activeSchemaNames: Set<string> | null;
activeSchemaChain: string[] | null;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why union with null instead of just using optional fields?

TS (and the underlying JS) is built to use undefined for missing values with syntax like: activeEdgeIds?: Set<string>

One reason to define it as you have done is if you want to ensure that any variable ActiveRelationshipContextValue has the property declared (even if the value is null). The syntax I suggest above would allow a developer to omit the property when the value is not available.

Comment on lines +93 to +94
activeEdgeIds: activeState?.edgeIds ?? null,
activeFieldKeys: activeState?.fieldKeys ?? null,
Copy link
Contributor

Choose a reason for hiding this comment

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

Defining the type of activeEdgeIds as an optional field would simplify this assignment to not require the ?? null, without any loss of information.

const foreignFieldKeys: string[] = [];

foreignKey.mappings.forEach((mapping) => {
const edgeId = `${schema.name}-${mapping.local}-to-${foreignKey.schema}-${mapping.foreign}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

Hyphens are allowed characters in schema and field names. There are some very rare edge cases where this ID will not be unique. Lectern disallows . characters in field and schema names, perhaps you could use that as the separator in this ID.

Comment on lines +153 to +166
/**
* Traces the full FK chain from a clicked edge or field, following parent links upward
* and child links downward to collect all connected edges and field keys.
*
* When `mappingIndex` is provided (edge click), only the specific mapping's edge and
* field pair are collected, and chains are traced through individual fields.
* When `mappingIndex` is omitted (field click), all mappings of the FK are collected.
*
* @param {number} fkIndex — The index into fkRestrictions for the clicked FK
* @param {RelationshipMap} map — The FK adjacency graph
* @param {number} [mappingIndex] — Optional index of the specific mapping within the FK
* @returns {{ edgeIds: Set<string>, fieldKeys: Set<string>, schemaChain: string[] }} All edges and fields in the chain
*/
export function traceChain(
Copy link
Contributor

Choose a reason for hiding this comment

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

Great docs. Thanks.

}
};

const traceFieldDown = (localFieldKey: string) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

missing comments like trace field up has. Since these functions have side effects, let's clearly state what data they modify.

}
};

const clickedFk = map.fkRestrictions[fkIndex];
Copy link
Contributor

Choose a reason for hiding this comment

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

This function may be called by a click event handler, but itself has no knowledge or interest in the event that called it. Therefore, we don't know anything about clicks, and shouldn't have a variable with the out of context name clickedFk.

The source of this issue may be the argument name fkIndex. Perhaps making this name more clear like chainStartingIndex then this variable can be given a contextual name like chainStartingFK.

Feel free to use more clear language than I am proposing.

};

dictionary.schemas.forEach((schema) => {
if (!schema.restrictions?.foreignKey) return;

Choose a reason for hiding this comment

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

Minor but its safer to do the following instead of one-liners, this also applies to the other instances in the PR:

Suggested change
if (!schema.restrictions?.foreignKey) return;
if (!schema.restrictions?.foreignKey) {
return;
}

export function ActiveRelationshipProvider({ relationshipMap, children }: ActiveRelationshipProviderProps) {
const [activeState, setActiveState] = useState<ActiveRelationshipState>(null);

const activateRelationship = useCallback(

Choose a reason for hiding this comment

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

i appreciate the usage of useCallback here 🔥

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants