Skip to content

Conversation

trangdoan982
Copy link
Collaborator

@trangdoan982 trangdoan982 commented Mar 5, 2025

https://linear.app/discourse-graphs/issue/ENG-42/create-setting-to-define-a-relationship

Node types

Screenshot 2025-03-06 at 17 05 01 - valid format: no name or format duplications, has to have {content} in the format, no [[ or ]] in format - can't delete if there's a Discourse Relation using this Node Type

Relation Type

image - check for duplication - can't delete if there's a Discourse Relation using this Relation Type

Discourse relation definition

Screenshot 2025-03-06 at 17 05 44 - because we use id, it's always synced with Node types and Relation Types - check for duplicate

Confirm before delete:
image
Screenshot 2025-03-17 at 13 38 41
Screenshot 2025-03-17 at 13 38 52

Copy link

vercel bot commented Mar 5, 2025

@trangdoan982 is attempting to deploy a commit to the Discourse Graphs Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Contributor

coderabbitai bot commented Mar 5, 2025

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

📝 Walkthrough

Walkthrough

This update introduces several new React components and utility functions to manage node types, relationships, and relationship types. A new context provider has been added to share the plugin instance, and the Settings component now uses a tabbed interface to render different settings views. Enhancements include unique ID generation, validation utilities, updated type definitions, and refined CSS for relationship nodes and visualizations.

Changes

File(s) Changed Summary
apps/obsidian/src/components/NodeTypeSettings.tsx
apps/obsidian/src/components/RelationshipSettings.tsx
apps/obsidian/src/components/RelationshipTypeSettings.tsx
apps/obsidian/src/components/PluginContext.tsx
apps/obsidian/src/components/Settings.tsx
Added new components for managing node types, discourse relations, and relationship types; created a plugin context with a custom hook; refactored the Settings component to use a tabbed interface and a context-driven design.
apps/obsidian/src/index.ts
apps/obsidian/src/types.ts
Expanded default settings by adding discourseRelations and relationTypes; updated DiscourseNode with an id and introduced new types: DiscourseRelation and DiscourseRelationType into the Settings structure.
apps/obsidian/src/utils/generateUid.ts
apps/obsidian/src/utils/validateNodeType.ts
Introduced a utility function for generating unique IDs and new validation functions for checking node format and name uniqueness.
apps/obsidian/styles.css Removed the obsolete .modal-content class and added new CSS classes for styling relationship nodes and discourse relation visualizations, including hover and transition effects.

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant S as Settings (Tabbed Interface)
    participant P as PluginProvider
    participant C as Child Component (Node/Relation Settings)

    U->>S: Select a settings tab (e.g., "Node Types")
    S->>P: Retrieve plugin context
    S->>C: Render the corresponding settings component
    C->>C: Process user input & perform validation
    C->>P: Save changes via plugin settings
    P-->>C: Acknowledge update
Loading

Possibly related PRs

Suggested reviewers

  • mdroidian

Poem

I’m a rabbit coding swift and neat,
Hopping through components with nimble feet.
Tabs and contexts now dance in a row,
With validations and UIDs making code glow.
Cheers and carrots for a job well done! 🐇🌟


🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@trangdoan982 trangdoan982 marked this pull request as draft March 5, 2025 18:47
@trangdoan982 trangdoan982 changed the base branch from main to posthog-user-permission March 6, 2025 21:55
@trangdoan982 trangdoan982 changed the base branch from posthog-user-permission to main March 6, 2025 21:55
@trangdoan982 trangdoan982 marked this pull request as ready for review March 6, 2025 22:04
@trangdoan982
Copy link
Collaborator Author

@coderabbitai review

Copy link
Contributor

coderabbitai bot commented Mar 6, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (13)
apps/obsidian/src/utils/generateUid.ts (1)

1-5: Simple and effective unique ID generation

The function provides a good approach for generating unique identifiers by combining a prefix, timestamp, and random string.

For non-critical identification purposes, this implementation is adequate, but consider the following enhancements:

-const generateUid = (prefix = "dg") => {
+/**
+ * Generates a unique identifier with an optional prefix
+ * @param prefix - The prefix to prepend to the ID (defaults to "dg")
+ * @returns A string in the format `${prefix}_${timestamp}_${randomString}`
+ */
+const generateUid = (prefix = "dg"): string => {
   return `${prefix}_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
 };
apps/obsidian/src/components/PluginContext.tsx (1)

16-26: Well-typed provider component

The PluginProvider component is properly typed and implements a clean pattern for providing the plugin instance to child components.

Consider adding a memo wrapper if this provider is used in performance-sensitive areas:

-export const PluginProvider = ({
+export const PluginProvider = React.memo(({
   plugin,
   children,
 }: {
   plugin: DiscourseGraphPlugin;
   children: ReactNode;
-}) => {
+}) => {
   return (
     <PluginContext.Provider value={plugin}>{children}</PluginContext.Provider>
   );
-};
+});
apps/obsidian/styles.css (1)

12-18: Smooth transition effects for relationship visualization

The transition and hover effects provide good visual feedback to users when interacting with relationship elements.

Consider specifying which properties should transition instead of using all for better performance:

.discourse-relations .relationship-visualization {
-  transition: all 0.2s ease;
+  transition: background 0.2s ease, box-shadow 0.2s ease;
}
apps/obsidian/src/components/NodeTypeSettings.tsx (2)

7-10: Inconsistent import paths.

There's an inconsistency in the import paths. Line 9 uses the alias ~/utils/generateUid while other utility imports use relative paths.

-import generateUid from "~/utils/generateUid";
+import generateUid from "../utils/generateUid";

33-61: Refactor duplicate validation logic.

The validation logic for format and name fields contains significant duplication. Consider extracting this into a reusable function.

  if (field === "format") {
-    const { isValid, error } = validateNodeFormat(value, updatedNodeTypes);
-    if (!isValid) {
-      setFormatErrors((prev) => ({
-        ...prev,
-        [index]: error || "Invalid format",
-      }));
-    } else {
-      setFormatErrors((prev) => {
-        const newErrors = { ...prev };
-        delete newErrors[index];
-        return newErrors;
-      });
-    }
+    updateErrors(index, validateNodeFormat(value, updatedNodeTypes));
  } else if (field === "name") {
-    const nameValidation = validateNodeName(value, updatedNodeTypes);
-    if (!nameValidation.isValid) {
-      setFormatErrors((prev) => ({
-        ...prev,
-        [index]: nameValidation.error || "Invalid name",
-      }));
-    } else {
-      setFormatErrors((prev) => {
-        const newErrors = { ...prev };
-        delete newErrors[index];
-        return newErrors;
-      });
-    }
+    updateErrors(index, validateNodeName(value, updatedNodeTypes));
  }

+ // Add this helper function at the top of your component
+ const updateErrors = (index: number, validation: { isValid: boolean; error?: string }) => {
+   if (!validation.isValid) {
+     setFormatErrors((prev) => ({
+       ...prev,
+       [index]: validation.error || "Invalid input",
+     }));
+   } else {
+     setFormatErrors((prev) => {
+       const newErrors = { ...prev };
+       delete newErrors[index];
+       return newErrors;
+     });
+   }
+ };
apps/obsidian/src/components/RelationshipTypeSettings.tsx (2)

45-62: Unnecessary reload of settings after deletion.

After deleting a relationship type and saving settings, you're also loading settings. This is unnecessary since you've just saved them and could cause additional flicker or delay.

  const handleDeleteRelationType = async (index: number): Promise<void> => {
    const isUsed = plugin.settings.discourseRelations?.some(
      (rel) => rel.relationshipTypeId === relationTypes[index]?.id,
    );

    if (isUsed) {
      new Notice(
        "Cannot delete this relation type as it is used in one or more relations.",
      );
      return;
    }

    const updatedRelationTypes = relationTypes.filter((_, i) => i !== index);
    setRelationTypes(updatedRelationTypes);
    plugin.settings.relationTypes = updatedRelationTypes;
    await plugin.saveSettings();
-   await plugin.loadSettings();
  };

64-82: Add validation for complement field uniqueness.

While labels are checked for uniqueness, the complement field isn't. This could allow duplicates that might cause confusion.

  const handleSave = async (): Promise<void> => {
    for (const relType of relationTypes) {
      if (!relType.id || !relType.label || !relType.complement) {
        new Notice("All fields are required for relation types.");
        return;
      }
    }

    const labels = relationTypes.map((rt) => rt.label);
    if (new Set(labels).size !== labels.length) {
      new Notice("Relation type labels must be unique.");
      return;
    }
    
+   const complements = relationTypes.map((rt) => rt.complement);
+   if (new Set(complements).size !== complements.length) {
+     new Notice("Relation type complements must be unique.");
+     return;
+   }

    plugin.settings.relationTypes = relationTypes;
    await plugin.saveSettings();
    setHasUnsavedChanges(false);
    new Notice("Relation types saved.");
  };
apps/obsidian/src/components/RelationshipSettings.tsx (3)

1-1: Unused import.

The useEffect hook is imported but not used in this component.

-import { useState, useEffect } from "react";
+import { useState } from "react";

60-66: Inconsistent save behavior across components.

Like in NodeTypeSettings, the delete operation immediately saves changes to settings, while other operations require explicit saving.

  const handleDeleteRelation = async (index: number): Promise<void> => {
    const updatedRelations = discourseRelations.filter((_, i) => i !== index);
    setDiscourseRelations(updatedRelations);
-   plugin.settings.discourseRelations = updatedRelations;
-   await plugin.saveSettings();
-   new Notice("Relation deleted");
+   setHasUnsavedChanges(true);
+   new Notice("Relation will be deleted when you save changes");
  };

17-25: Consider caching lookup functions.

The findNodeById and findRelationTypeById functions might be called multiple times during rendering. Consider memoizing these functions with useMemo or caching their results.

+import { useState, useMemo } from "react";

  const RelationshipSettings = () => {
    const plugin = usePlugin();
    // ...
    
-   const findNodeById = (id: string): DiscourseNode | undefined => {
-     return plugin.settings.nodeTypes.find((node) => node.id === id);
-   };
+   const nodeMap = useMemo(() => {
+     const map = new Map<string, DiscourseNode>();
+     plugin.settings.nodeTypes.forEach(node => map.set(node.id, node));
+     return map;
+   }, [plugin.settings.nodeTypes]);
+   
+   const findNodeById = (id: string): DiscourseNode | undefined => {
+     return nodeMap.get(id);
+   };
    
    // Similar for findRelationTypeById
apps/obsidian/src/utils/validateNodeType.ts (1)

72-100: Add unit tests for each validation scenario.

Currently, there's no evidence of test coverage for these validation functions. Implementing tests that cover all the edge cases (e.g., empty format, invalid format without {content}, duplicate names, etc.) will help ensure code stability and prevent regressions.

apps/obsidian/src/components/Settings.tsx (2)

2-2: Remove unused import.

Notice is not used in this file. Eliminating unused imports tidies up the code and avoids confusion:

- import { App, PluginSettingTab, Notice } from "obsidian";
+ import { App, PluginSettingTab } from "obsidian";

16-84: Extract inline styling to improve maintainability.

Repeated inline styling for tabs can be moved into a dedicated CSS/SASS/SCSS file. This approach centralizes style definitions, making them easier to maintain and update as the codebase grows.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 322006b and 3c8bdcd.

📒 Files selected for processing (10)
  • apps/obsidian/src/components/NodeTypeSettings.tsx (1 hunks)
  • apps/obsidian/src/components/PluginContext.tsx (1 hunks)
  • apps/obsidian/src/components/RelationshipSettings.tsx (1 hunks)
  • apps/obsidian/src/components/RelationshipTypeSettings.tsx (1 hunks)
  • apps/obsidian/src/components/Settings.tsx (2 hunks)
  • apps/obsidian/src/index.ts (1 hunks)
  • apps/obsidian/src/types.ts (1 hunks)
  • apps/obsidian/src/utils/generateUid.ts (1 hunks)
  • apps/obsidian/src/utils/validateNodeType.ts (1 hunks)
  • apps/obsidian/styles.css (1 hunks)
🔇 Additional comments (12)
apps/obsidian/src/index.ts (1)

8-9: Settings structure extended appropriately

The addition of discourseRelations and relationTypes arrays to the default settings properly aligns with the PR's objective to define relationships.

apps/obsidian/src/components/PluginContext.tsx (2)

1-6: Well-structured React context implementation

The implementation of PluginContext follows React best practices for creating a typed context.


8-14: Good error handling in custom hook

The usePlugin hook correctly throws a descriptive error when used outside of its provider context, which will help developers identify integration issues.

apps/obsidian/styles.css (1)

1-10: Good styling for relationship nodes

The styling for relationship nodes uses appropriate CSS variables for theme compatibility and provides a clean, consistent appearance.

apps/obsidian/src/types.ts (4)

1-7: Good addition of id field to DiscourseNode type.

Adding an id field to the DiscourseNode type is a good practice as it provides a stable reference for relationships.


9-13: Well-structured relationship type definition.

The DiscourseRelationType structure with label and complement fields provides a clear way to express bidirectional relationships (e.g., "supports" and "is supported by").


15-19: Clean graph-based relation structure.

The DiscourseRelation type properly implements a graph-like structure with source, destination, and relationship type references using IDs, which is an appropriate design for modeling relationships.


21-25: Consistent Settings type extension.

The Settings type has been appropriately extended to include the new relationship-related arrays, maintaining type safety throughout the application.

apps/obsidian/src/components/NodeTypeSettings.tsx (1)

81-105: Inconsistent save behavior between delete and other operations.

The delete operation immediately saves changes to settings, while other operations require clicking "Save Changes". This inconsistency might confuse users.

Consider either:

  1. Making the delete operation update only the local state without saving, requiring "Save Changes" like other operations, or
  2. Adding a confirmation dialog before deleting since it's immediately saved
  const handleDeleteNodeType = async (index: number): Promise<void> => {
    const nodeId = nodeTypes[index]?.id;
    const isUsed = plugin.settings.discourseRelations?.some(
      (rel) => rel.sourceId === nodeId || rel.destinationId === nodeId,
    );

    if (isUsed) {
      new Notice(
        "Cannot delete this node type as it is used in one or more relations.",
      );
      return;
    }

    const updatedNodeTypes = nodeTypes.filter((_, i) => i !== index);
    setNodeTypes(updatedNodeTypes);
+   setHasUnsavedChanges(true);
-   plugin.settings.nodeTypes = updatedNodeTypes;
-   await plugin.saveSettings();
    if (formatErrors[index]) {
      setFormatErrors((prev) => {
        const newErrors = { ...prev };
        delete newErrors[index];
        return newErrors;
      });
    }
  };
apps/obsidian/src/components/RelationshipSettings.tsx (1)

192-269: Good relationship visualization.

The relationship visualization provides an excellent user experience by showing how node types are connected. This makes it easier for users to understand the relationships they're creating.

apps/obsidian/src/utils/validateNodeType.ts (1)

31-34: Ensure uniqueness logic reflects intended behavior.

validateFormatUniqueness() checks if any two nodes in nodeTypes share the same format. If you want to allow a node to preserve its existing format without triggering a duplicate error (e.g., when editing a single node), consider tailoring the uniqueness check to exclude the current node under edit. Otherwise, multiple edits to a single node or comparing across large sets might result in false-positive duplicates.

apps/obsidian/src/components/Settings.tsx (1)

111-113: Well-structured context integration.

Wrapping the Settings component in PluginProvider cleanly separates plugin logic from the UI. This promotes modularity and makes the component more testable.

@trangdoan982 trangdoan982 requested a review from mdroidian March 6, 2025 22:17
@trangdoan982 trangdoan982 force-pushed the trang/relationship-type-def branch from 824dead to c42d587 Compare March 6, 2025 22:19
Copy link
Contributor

@mdroidian mdroidian left a comment

Choose a reason for hiding this comment

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

🔥 Good stuff, mostly just nits.

};
const Settings = () => {
const plugin = usePlugin();
const [activeTab, setActiveTab] = useState("nodeTypes");
Copy link
Contributor

Choose a reason for hiding this comment

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

nit - we can address this later
since we are using the unsaved changes pattern, we should probably also warn before tab switch if unsaved changes

Copy link

vercel bot commented Mar 13, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
discourse-graph 🔄 Building (Inspect) Visit Preview 💬 Add feedback Mar 13, 2025 9:31pm

@trangdoan982 trangdoan982 requested a review from mdroidian March 17, 2025 17:40
@mdroidian mdroidian merged commit 750974c into DiscourseGraphs:main Mar 21, 2025
1 of 2 checks passed
@github-project-automation github-project-automation bot moved this to Done in General Mar 21, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

2 participants