Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Resource associations visualization #243

Merged
merged 2 commits into from
Dec 2, 2024

Conversation

adityachoudhari26
Copy link
Contributor

@adityachoudhari26 adityachoudhari26 commented Dec 2, 2024

Summary by CodeRabbit

  • New Features

    • Enhanced edge creation logic for better relationship mapping in the visualization components.
    • Introduced new error handling in the VisualizePage to manage null relationships.
  • Bug Fixes

    • Improved robustness in resource relationship queries to prevent errors when resources are not found.
  • Refactor

    • Streamlined database queries and relationships handling by adopting new identifier structures across various components.
    • Removed the "Relationships" tab and associated components from the TargetDrawer and JobDrawer for a cleaner UI.

Copy link
Contributor

coderabbitai bot commented Dec 2, 2024

Walkthrough

This pull request implements significant changes to the handling of relationships and identifiers within the webservice and API components. The modifications primarily involve updating data access patterns to focus on relationships.nodes instead of relationships, enhancing the granularity of edge creation and representation. Additionally, identifier names have been standardized across various components, including the introduction of fromIdentifier and toIdentifier. Error handling improvements and adjustments to SQL schemas further refine how resource relationships are managed and queried.

Changes

File Change Summary
apps/webservice/src/app/[workspaceSlug]/(app)/(targets)/targets/[targetId]/visualize/edges.ts Updated edge creation functions to access relationships.nodes instead of relationships, and added logic for fromEdges and toEdges based on relationships.associations.
apps/webservice/src/app/[workspaceSlug]/(app)/(targets)/targets/[targetId]/visualize/nodes/EnvironmentNode.tsx Modified type definition for Environment to reference relationships.nodes instead of relationships.
apps/webservice/src/app/[workspaceSlug]/(app)/(targets)/targets/[targetId]/visualize/nodes/nodes.ts Changed data access in getResourceNodes, getProviderNodes, getEnvironmentNodes, and getDeploymentNodes to utilize relationships.nodes.
apps/webservice/src/app/[workspaceSlug]/(app)/(targets)/targets/[targetId]/visualize/page.tsx Added null check for relationships in VisualizePage to handle cases where the resource does not exist.
apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-drawer/RelationshipsDiagramDependencies.tsx Removed the entire component responsible for visualizing relationships in a diagram format.
apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-drawer/relationships/RelationshipsDiagram.tsx Removed the entire component responsible for rendering flow diagrams of target relationships.
apps/webservice/src/app/api/v1/relationship/resource-to-resource/route.ts Simplified POST request handler by removing isNull checks and using db.query.resource.findFirst for fetching resources.
packages/api/src/router/job.ts Enhanced job-related queries to include release information and updated identifier usage in reachable_relationships logic.
packages/api/src/router/resources.ts Updated SQL queries to replace source_id and target_id with fromIdentifier and toIdentifier, improving relationship handling.
packages/db/drizzle/0043_chemical_black_bird.sql Modified resource_relationship table structure, renaming columns and adding a new workspace_id column with constraints.
packages/db/drizzle/meta/_journal.json Added a new entry to the journal for the database schema changes.
packages/db/src/schema/resource.ts Updated resource_relationship table fields to reflect new identifiers and workspace context.
packages/job-dispatch/src/resource/delete.ts Changed deletion logic to use fromIdentifier and toIdentifier instead of sourceId and targetId.
apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-drawer/JobDrawer.tsx Removed the DependenciesDiagram component from the JobDrawer component.
apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-drawer/TargetDrawer.tsx Removed the "Relationships" tab and its associated content from the TargetDrawer component.
apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-drawer/relationships/RelationshipContent.tsx Removed the RelationshipsContent component that displayed the hierarchy of relationships.
packages/job-dispatch/src/policy-checker.ts Removed the isPassingReleaseDependencyPolicy check from the isPassingAllPolicies and isPassingAllPoliciesExceptNewerThanLastActive functions.

Possibly related PRs

Suggested reviewers

  • jsbroks

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 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 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.

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.

@@ -387,37 +394,37 @@ const releaseJobTriggerRouter = createTRPCRouter({
WITH RECURSIVE reachable_relationships(id, visited, tr_id, source_id, target_id, type) AS (
-- Base case: start with the given ID and no relationship
SELECT
${data.resource.id}::uuid AS id,
ARRAY[${data.resource.id}::uuid] AS visited,
${data.resource.identifier}::uuid AS identifier,
Copy link
Member

Choose a reason for hiding this comment

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

but we are doing a uuid cast?

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: 4

🧹 Outside diff range and nitpick comments (8)
apps/webservice/src/app/[workspaceSlug]/(app)/(targets)/targets/[targetId]/visualize/page.tsx (1)

12-12: Use strict equality comparison

While the null check is a good addition, consider using strict equality (===) instead of loose equality (==) for more precise type checking.

-  if (relationships == null) return notFound();
+  if (relationships === null) return notFound();
apps/webservice/src/app/[workspaceSlug]/(app)/(targets)/targets/[targetId]/visualize/nodes/nodes.ts (4)

Line range hint 9-9: Update type definition to reflect the new data structure

The type definition should be more specific about the structure containing nodes to ensure type safety and better documentation of the expected data shape.

-type Relationships = NonNullable<RouterOutputs["resource"]["relationships"]>;
+type Relationships = NonNullable<RouterOutputs["resource"]["relationships"]> & {
+  nodes: Array<NonNullable<RouterOutputs["resource"]["relationships"]["nodes"][number]>>;
+};

Line range hint 35-45: Consider using a more robust ID generation pattern

While the current implementation works, combining IDs with a hyphen could potentially lead to collisions if either ID contains hyphens themselves.

-            id: `${r.provider.id}-${r.id}`,
+            id: `provider:${r.provider.id}:resource:${r.id}`,

Line range hint 49-57: Consider optimizing nested array operations

The current implementation creates intermediate arrays through multiple flatMap operations. Consider using a more direct mapping approach.

-  relationships.nodes
-    .flatMap((r) => r.workspace.systems)
-    .flatMap((s) => s.environments.map((e) => ({ s, e })))
-    .map(({ s, e }) => ({
+  relationships.nodes.reduce((acc, r) => {
+    r.workspace.systems.forEach((s) => {
+      s.environments.forEach((e) => {
+        acc.push({
+          id: e.id,
+          type: NodeType.Environment,
+          data: { environment: e, label: `${s.name}/${e.name}` },
+          position: { x: 0, y: 0 },
+        });
+      });
+    });
+    return acc;
+  }, []);

Line range hint 60-75: Optimize performance and improve ID generation

The current implementation has two areas for improvement:

  1. Multiple nested flatMap operations create unnecessary intermediate arrays
  2. The ID generation pattern could be more robust
-  relationships.nodes.flatMap((r) =>
-    r.workspace.systems.flatMap((system) =>
-      system.environments.flatMap((environment) =>
-        system.deployments.map((deployment) => ({
+  relationships.nodes.reduce((acc, r) => {
+    r.workspace.systems.forEach((system) => {
+      system.environments.forEach((environment) => {
+        system.deployments.forEach((deployment) => {
+          acc.push({
-          id: `${environment.id}-${deployment.id}`,
+          id: `env:${environment.id}:deploy:${deployment.id}`,
           type: NodeType.Deployment,
           data: {
             deployment,
             environment,
             resource: r,
             label: deployment.name,
           },
           position: { x: 0, y: 0 },
-        })),
-      ),
-    ),
-  );
+          });
+        });
+      });
+    });
+    return acc;
+  }, []);
packages/db/src/schema/resource.ts (2)

Line range hint 351-357: Handle Possible Null Value for 'rel' to Prevent Runtime Errors

The variable rel may be undefined if takeFirst() returns no results. Accessing rel.system.workspaceId without checking could lead to a runtime error. Ensure that rel is not undefined before accessing its properties.

Add a null check for rel:

const rel = await ctx.db
  .select()
  .from(release)
  .innerJoin(deployment, eq(release.deploymentId, deployment.id))
  .innerJoin(system, eq(deployment.systemId, system.id))
  .then(takeFirst);

+ if (!rel) {
+   throw new Error("Related release not found.");
+ }

Line range hint 397-401: Avoid Casting 'identifier' from Text to UUID

The resource.identifier is of type text, but it's being cast to uuid using ::uuid. This can cause runtime errors if the identifier is not a valid UUID. Since identifiers are stored as text, casting them to uuid is unsafe.

Modify the code to avoid casting:

WITH RECURSIVE reachable_relationships(id, visited, tr_id, source_identifier, target_identifier, type) AS (
  -- Base case: start with the given ID and no relationship
  SELECT 
-     ${data.resource.identifier}::uuid AS identifier, 
-     ARRAY[${data.resource.identifier}::uuid] AS visited,
+     ${data.resource.identifier} AS identifier, 
+     ARRAY[${data.resource.identifier}] AS visited,
      NULL::uuid AS tr_id,
      NULL::uuid AS source_identifier,
      NULL::uuid AS target_identifier,
      NULL::resource_relationship_type AS type

Ensure subsequent code handles identifier as text.

apps/webservice/src/app/[workspaceSlug]/(app)/(targets)/targets/[targetId]/visualize/edges.ts (1)

67-71: Consider memoizing release job triggers

The nested flatMap operations to collect release job triggers are performed for each resource, which could impact performance with large datasets.

Consider extracting and memoizing the job triggers:

+ const getAllReleaseJobTriggers = (relationships: Relationships) =>
+   relationships.nodes
+     .flatMap((r) => r.workspace.systems)
+     .flatMap((s) => s.environments)
+     .flatMap((e) => e.latestActiveReleases)
+     .map((rel) => rel.releaseJobTrigger);

const createEdgesFromDeploymentsToResources = (relationships: Relationships) => {
+ const allReleaseJobTriggers = getAllReleaseJobTriggers(relationships);
  return relationships.nodes.map((resource) => {
    const { parent } = resource;
    if (parent == null) return null;

-   const allReleaseJobTriggers = relationships.nodes
-     .flatMap((r) => r.workspace.systems)
-     .flatMap((s) => s.environments)
-     .flatMap((e) => e.latestActiveReleases)
-     .map((rel) => rel.releaseJobTrigger);
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 21e76f9 and 4c1156b.

📒 Files selected for processing (13)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(targets)/targets/[targetId]/visualize/edges.ts (2 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(targets)/targets/[targetId]/visualize/nodes/EnvironmentNode.tsx (1 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(targets)/targets/[targetId]/visualize/nodes/nodes.ts (3 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/(targets)/targets/[targetId]/visualize/page.tsx (2 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-drawer/RelationshipsDiagramDependencies.tsx (4 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-drawer/relationships/RelationshipsDiagram.tsx (1 hunks)
  • apps/webservice/src/app/api/v1/relationship/resource-to-resource/route.ts (2 hunks)
  • packages/api/src/router/job.ts (3 hunks)
  • packages/api/src/router/resources.ts (3 hunks)
  • packages/db/drizzle/0043_chemical_black_bird.sql (1 hunks)
  • packages/db/drizzle/meta/_journal.json (1 hunks)
  • packages/db/src/schema/resource.ts (1 hunks)
  • packages/job-dispatch/src/resource/delete.ts (1 hunks)
🧰 Additional context used
📓 Learnings (2)
apps/webservice/src/app/[workspaceSlug]/(app)/(targets)/targets/[targetId]/visualize/nodes/EnvironmentNode.tsx (2)
Learnt from: adityachoudhari26
PR: ctrlplanedev/ctrlplane#183
File: apps/webservice/src/app/[workspaceSlug]/_components/environment-drawer/EnvironmentDropdownMenu.tsx:27-31
Timestamp: 2024-11-12T09:55:38.456Z
Learning: The `SCHEMA.Environment` type includes the `systemId` field.
Learnt from: adityachoudhari26
PR: ctrlplanedev/ctrlplane#188
File: apps/webservice/src/app/[workspaceSlug]/_components/environment-drawer/EnvironmentDrawer.tsx:139-154
Timestamp: 2024-11-12T09:55:38.456Z
Learning: In `EnvironmentDrawer.tsx`, for the `EnvironmentDrawer` component, when there are only a few `TabButton` instances (e.g., 3), it's acceptable to define them individually without extracting them into a configuration to reduce repetition.
apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-drawer/RelationshipsDiagramDependencies.tsx (1)
Learnt from: adityachoudhari26
PR: ctrlplanedev/ctrlplane#165
File: apps/webservice/src/app/[workspaceSlug]/_components/relationships/RelationshipsDiagramDependencies.tsx:185-189
Timestamp: 2024-11-12T09:55:38.456Z
Learning: In `RelationshipsDiagramDependencies.tsx`, when using React Flow's `BaseEdge`, the `markerStart` prop may intentionally use the `markerEnd` value to flip the arrow direction because 'source depends on target' logic is applied. These are intrinsic attributes of the React Flow `BaseEdge` component and cannot be renamed or changed.
🔇 Additional comments (22)
apps/webservice/src/app/[workspaceSlug]/(app)/(targets)/targets/[targetId]/visualize/page.tsx (1)

12-13: Verify relationships data structure

The AI summary indicates that the codebase is moving towards using relationships.nodes, but this component still passes the entire relationships object to the provider. Please verify if this is intentional or if it needs to be updated for consistency.

✅ Verification successful

Based on the shell script results, I can generate the final response as I have sufficient information to verify the concern. The results show that:

  1. The relationships object is consistently used across the codebase, with both direct usage and relationships.nodes access patterns.
  2. The visualization components (edges.ts and nodes.ts) internally handle the proper access to relationships.nodes when needed.
  3. The ResourceVisualizationDiagramProvider is part of a proper data flow where the parent component passes the complete relationships object, and child components extract the necessary data using relationships.nodes.

The current implementation is correct and consistent

The component correctly passes the entire relationships object to the provider. The child components (edges.ts and nodes.ts) properly access relationships.nodes internally when needed. This pattern maintains proper data encapsulation and allows child components to access other properties of the relationships object if needed.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check the usage pattern of relationships.nodes vs relationships
# across the codebase to ensure consistency

# Search for relationships.nodes pattern
echo "Checking for relationships.nodes usage:"
rg -A 2 "relationships\.nodes"

# Search for direct relationships usage
echo -e "\nChecking for direct relationships usage:"
rg -A 2 "relationships\s*[^.]"

# Check ResourceVisualizationDiagramProvider props
echo -e "\nChecking ResourceVisualizationDiagramProvider implementation:"
ast-grep --pattern 'export $_ function ResourceVisualizationDiagramProvider({ relationships }: { relationships: $_ }) {
  $$$
}'

Length of output: 24622

apps/webservice/src/app/[workspaceSlug]/(app)/(targets)/targets/[targetId]/visualize/nodes/nodes.ts (2)

27-32: LGTM! Clean and correct data access pattern

The function correctly accesses the new data structure while maintaining the same output format.


Line range hint 18-24: Verify node type components exist

Let's ensure all referenced node type components exist and are properly exported.

✅ Verification successful

All node type components are properly defined and mapped

The verification confirms:

  • All node components (ResourceNode, EnvironmentNode, ProviderNode, DeploymentNode) are properly exported and exist in their respective files
  • The NodeType enum values match exactly with the nodeTypes mapping
  • Each node type has a corresponding React component implementation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Verify that all node type components exist
for component in ResourceNode EnvironmentNode ProviderNode DeploymentNode; do
  echo "Checking for $component..."
  rg "export.*$component" -t tsx
done

Length of output: 838


Script:

#!/bin/bash
# Let's try with .ts and .tsx extensions explicitly using fd and rg
echo "Searching for node components..."
fd -e ts -e tsx | xargs rg "export.*\b(ResourceNode|EnvironmentNode|ProviderNode|DeploymentNode)\b"

# Also search for NodeType enum to verify all types are mapped
echo -e "\nSearching for NodeType enum..."
rg "enum\s+NodeType"

Length of output: 1445


Script:

#!/bin/bash
# Let's check the NodeType enum values to ensure all types are properly mapped
echo "Checking NodeType enum contents in the visualize/nodes/nodes.ts file..."
rg -A 10 "enum NodeType" "apps/webservice/src/app/[workspaceSlug]/(app)/(targets)/targets/[targetId]/visualize/nodes/nodes.ts"

Length of output: 565

packages/db/src/schema/resource.ts (1)

Line range hint 437-450: Confirm 'allIdentifiers' Correctly Compiles All Unique Identifiers

The allIdentifiers array is constructed by combining fromIdentifiers, toIdentifiers, and data.resource.identifier. Ensure that all these variables contain the expected identifier values and that duplicates are appropriately handled by _.uniq().

Run the following script to check for inconsistencies:

✅ Verification successful

Let me check the actual code implementation to verify the identifiers handling.


Let me check how these identifiers are populated before they're combined.


Let me check the relationships array construction to complete the verification.


The allIdentifiers Implementation is Correctly Handling Unique Identifiers

Based on the code analysis:

  1. fromIdentifiers and toIdentifiers are properly populated from database results, with values explicitly cast to strings
  2. The arrays are combined using spread operator and _.uniq() handles deduplication
  3. The implementation appears in both resources.ts and job.ts with consistent handling
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that 'fromIdentifiers', 'toIdentifiers', and 'data.resource.identifier' are correctly populated.

# Expected: The arrays contain valid identifiers without unexpected duplicates.

echo "fromIdentifiers: ${fromIdentifiers}"
echo "toIdentifiers: ${toIdentifiers}"
echo "data.resource.identifier: ${data.resource.identifier}"

# Check for duplicates
duplicates=$(echo "${allIdentifiers[@]}" | tr ' ' '\n' | sort | uniq -d)
if [ -n "$duplicates" ]; then
  echo "Duplicate identifiers found: $duplicates"
else
  echo "No duplicates in allIdentifiers."
fi

Length of output: 375


Script:

#!/bin/bash
# Search for the allIdentifiers implementation
ast-grep --pattern 'const allIdentifiers = $_'

Length of output: 681


Script:

#!/bin/bash
# Search for fromIdentifiers and toIdentifiers declarations
ast-grep --pattern 'const fromIdentifiers = $_'
ast-grep --pattern 'const toIdentifiers = $_'

# Also check the context around these usages
rg "fromIdentifiers|toIdentifiers" -A 3 -B 3

Length of output: 2088


Script:

#!/bin/bash
# Search for relationships array construction
rg "const relationships = " -A 5 -B 5

Length of output: 4557

packages/api/src/router/job.ts (2)

397-425: Review Recursive Query Logic for 'reachable_relationships'

The recursive CTE reachable_relationships includes changes to use identifiers instead of IDs. Ensure that the logic correctly identifies related resources using from_identifier and to_identifier.

Double-check the query to confirm that relationships are correctly traversed. If possible, add unit tests to validate the recursive logic.


437-450: Ensure Consistent Typing When Building 'relationships' and 'allIdentifiers'

When constructing the relationships array and the allIdentifiers list, ensure that the identifiers are of the correct type (string) and that there are no type mismatches or runtime errors.

Run the following script to validate the types:

✅ Verification successful

Let me gather more context about the type safety of these identifiers.


Let me gather more specific information about the type safety of these identifiers.


From the results, I can see that proper type safety is being maintained. The code explicitly converts the identifiers to strings using String() and has proper type assertions. Let me generate the final response.

Type Safety is Properly Maintained in Identifier Handling

The code already implements proper type safety:

  • Source and target identifiers are explicitly converted to strings using String()
  • The relationship type is properly type-asserted as "associated_with" | "depends_on"
  • The SQL query properly handles UUID types with explicit casting
  • All identifiers in the final array are guaranteed to be strings
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify that identifiers are strings and not undefined.

# Check for 'undefined' or incorrect types in relationships
grep -rnw 'packages/api/src/router/job.ts' -e 'relationships.map' -A 5

# Ensure allIdentifiers contains only unique, valid strings
echo "${allIdentifiers[@]}" | tr ' ' '\n' | awk '{ if ($0 == "") print "Empty identifier found"; else if ($0 ~ /undefined/) print "Undefined identifier found" }'

Length of output: 531


Script:

#!/bin/bash
# Check the type definitions and usage of source_identifier and target_identifier
ast-grep --pattern 'r.source_identifier'
ast-grep --pattern 'r.target_identifier'

# Check the type of data.resource.identifier
ast-grep --pattern 'data.resource.identifier'

# Look for type definitions related to these identifiers
rg "type.*Identifier" -A 2
rg "interface.*Resource" -A 5

Length of output: 65610


Script:

#!/bin/bash
# Check the context around the relationships mapping
rg -B 5 -A 5 "const relationships = " packages/api/src/router/job.ts

# Look for the type definition of the relationship object
rg -B 2 -A 2 "source_identifier|target_identifier" packages/api/src/router/job.ts

# Check how data.resource.identifier is typed
ast-grep --pattern 'interface Resource {' -A 10

Length of output: 2060

packages/job-dispatch/src/resource/delete.ts (1)

15-16: Update Conditions to Match Identifier Types

In the deleteObjectsAssociatedWithResource function, the conditions have been updated to use fromIdentifier and toIdentifier. Ensure that these fields correctly reference the resource's identifier and that the deletion logic accurately targets the associated relationships.

packages/db/drizzle/0043_chemical_black_bird.sql (1)

1-17: ⚠️ Potential issue

Ensure Data Integrity After Schema Changes

The SQL script alters the resource_relationship table by renaming columns and changing data types from uuid to text. This may lead to data inconsistencies if existing uuid values are not compatible with text fields. Additionally, the foreign key constraints on source_id and target_id have been dropped without adding new constraints for from_identifier and to_identifier.

Consider adding foreign key constraints to maintain referential integrity:

ALTER TABLE "resource_relationship"
ADD CONSTRAINT "resource_relationship_from_identifier_fkey"
FOREIGN KEY ("from_identifier") REFERENCES "resource" ("identifier") ON DELETE CASCADE;

ALTER TABLE "resource_relationship"
ADD CONSTRAINT "resource_relationship_to_identifier_fkey"
FOREIGN KEY ("to_identifier") REFERENCES "resource" ("identifier") ON DELETE CASCADE;

Ensure that data migration scripts handle the conversion of uuid values to text identifiers correctly.

apps/webservice/src/app/[workspaceSlug]/(app)/(targets)/targets/[targetId]/visualize/nodes/EnvironmentNode.tsx (1)

12-13: Update Type Definition for Environment

The type for Environment has been updated to reflect the structure change from relationships to relationships.nodes. Ensure that this change aligns with the data returned from the API and that all usages of Environment are updated accordingly.

apps/webservice/src/app/api/v1/relationship/resource-to-resource/route.ts (1)

46-47: LGTM: Improved response structure

Including the created relationship in the response is a good practice for REST APIs.

apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-drawer/relationships/RelationshipsDiagram.tsx (2)

47-47: LGTM: Consistent identifier usage

The changes correctly use resource identifiers for node IDs and orphan detection, aligning with the API changes.

Also applies to: 54-56


63-65: LGTM: Edge creation with identifiers

Edge creation properly uses the new identifier fields from relationships.

apps/webservice/src/app/[workspaceSlug]/(app)/(targets)/targets/[targetId]/visualize/edges.ts (2)

114-130: LGTM: Improved association edge creation

The new fromEdges and toEdges creation properly handles bidirectional associations, improving the visualization.


137-138: LGTM: Complete edge collection

All edge types are properly collected and filtered.

packages/db/drizzle/meta/_journal.json (1)

305-311: LGTM!

The new migration entry follows the established pattern and maintains consistency with existing entries.

apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-drawer/RelationshipsDiagramDependencies.tsx (3)

76-81: LGTM: Graph construction logic is correct.

The function correctly builds an undirected graph using the new identifier naming convention.


106-115: LGTM: Node initialization and orphan detection logic is accurate.

The orphan node detection logic correctly uses the new identifier naming convention.


122-124: LGTM: Edge handling is consistent with React Flow expectations.

The edge creation, reset, and highlighting logic correctly uses the new identifier naming convention while maintaining React Flow's expected behavior for edge markers.

Let's verify the edge creation consistency:

Also applies to: 142-144, 181-181, 186-188

✅ Verification successful

Edge handling is consistent across relationship diagrams

The verification confirms that the edge creation pattern is consistent across the codebase. Both RelationshipsDiagram.tsx and RelationshipsDiagramDependencies.tsx use the same pattern for:

  • Edge IDs: concatenating fromIdentifier-toIdentifier
  • Source/target properties: using fromIdentifier and toIdentifier respectively
  • Edge styling: applying consistent marker types and color schemes
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify edge creation pattern across the codebase
# Test: Search for edge creation patterns. Expect: Consistent usage of fromIdentifier/toIdentifier
rg -A 2 'id: `\${.*?}-\${.*?}`.*source:.*target:'

Length of output: 181


Script:

#!/bin/bash
# Search for edge creation patterns with source and target properties
rg -U 'id:.*source:.*target:' -A 3

# Also search for specific pattern of identifier concatenation
rg -U 'fromIdentifier.*toIdentifier' -A 3

Length of output: 3602

packages/api/src/router/resources.ts (4)

53-85: LGTM: Recursive CTE correctly handles identifier renaming.

The SQL query has been updated to use the new identifier naming convention while maintaining the same recursive traversal logic.


442-446: Improved error handling with resource existence check.

Good addition of the resource existence check before proceeding with relationship queries.


502-523: LGTM: Response structure maintains clear separation.

The response structure clearly separates nodes and associations, making it easier for clients to consume the data.


449-496: LGTM: Relationship queries are well-structured.

The separation into fromNodes and toNodes queries improves code clarity and maintainability.

Let's verify the relationship query patterns:

✅ Verification successful

Relationship queries are indeed well-structured and consistent

The verification confirms that the relationship queries maintain consistent patterns across the codebase:

  • In resources.ts, the queries properly handle both directions (from/to) with consistent field usage
  • The same pattern is followed in the job-dispatch module
  • All queries properly use workspaceId for scoping
  • The join conditions and where clauses are consistently structured
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify relationship query patterns
# Test: Search for relationship queries. Expect: Consistent usage of fromIdentifier/toIdentifier
ast-grep --pattern 'select()
  .from(schema.resourceRelationship)
  .where(
    and(
      eq(schema.resourceRelationship.$_, $_),
      $_
    )
  )'

Length of output: 152


Script:

#!/bin/bash
# Let's try a simpler pattern to find relationship queries
rg "from\(schema\.resourceRelationship\)" -A 10 -B 2

Length of output: 1795


Script:

#!/bin/bash
# Let's check for any other usages of resourceRelationship to ensure consistency
rg "resourceRelationship\." -A 2 -B 2

Length of output: 2035

Comment on lines +305 to +312
workspaceId: uuid("workspace_id")
.notNull()
.references(() => workspace.id, { onDelete: "cascade" }),
fromIdentifier: text("from_identifier").notNull(),
toIdentifier: text("to_identifier").notNull(),
type: resourceRelationshipType("type").notNull(),
},
(t) => ({ uniq: uniqueIndex().on(t.targetId, t.sourceId) }),
(t) => ({ uniq: uniqueIndex().on(t.toIdentifier, t.fromIdentifier) }),
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Add Missing Foreign Key Constraints for Identifiers

The fromIdentifier and toIdentifier fields are defined as text without foreign key constraints to the resource.identifier. This could lead to referential integrity issues if resources are deleted or identifiers change. Consider adding foreign key constraints to ensure data consistency.

Modify the table definition to include foreign key references:

export const resourceRelationship = pgTable(
  "resource_relationship",
  {
    id: uuid("id").primaryKey().defaultRandom(),
    workspaceId: uuid("workspace_id")
      .notNull()
      .references(() => workspace.id, { onDelete: "cascade" }),
    fromIdentifier: text("from_identifier")
      .notNull()
+     .references(() => resource.identifier, { onDelete: "cascade" }),
    toIdentifier: text("to_identifier")
      .notNull()
+     .references(() => resource.identifier, { onDelete: "cascade" }),
    type: resourceRelationshipType("type").notNull(),
  },
  (t) => ({ uniq: uniqueIndex().on(t.toIdentifier, t.fromIdentifier) }),
);

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 351 to 357
const rel = await ctx.db
.select()
.from(release)
.innerJoin(deployment, eq(release.deploymentId, deployment.id))
.innerJoin(system, eq(deployment.systemId, system.id))
.then(takeFirst);

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

⚠️ Potential issue

Initialize 'rel' Variable to Prevent Undefined Access

The variable rel may be undefined if the query does not return any results. Accessing rel.system.workspaceId later will cause a runtime error. Ensure that rel is properly initialized.

Add a check to handle the case when rel is undefined:

const rel = await ctx.db
  .select()
  .from(release)
  .innerJoin(deployment, eq(release.deploymentId, deployment.id))
  .innerJoin(system, eq(deployment.systemId, system.id))
  .then(takeFirst);

+ if (!rel) {
+   throw new Error("Release not found for the given job.");
+ }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
const rel = await ctx.db
.select()
.from(release)
.innerJoin(deployment, eq(release.deploymentId, deployment.id))
.innerJoin(system, eq(deployment.systemId, system.id))
.then(takeFirst);
const rel = await ctx.db
.select()
.from(release)
.innerJoin(deployment, eq(release.deploymentId, deployment.id))
.innerJoin(system, eq(deployment.systemId, system.id))
.then(takeFirst);
if (!rel) {
throw new Error("Release not found for the given job.");
}

Comment on lines +24 to +30
const inWorkspace = eq(SCHEMA.resource.workspaceId, body.workspaceId);
const fromResource = await db.query.resource.findFirst({
where: and(
inWorkspace,
eq(SCHEMA.resource.identifier, body.fromIdentifier),
),
});
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Verify resource existence before creating relationship

The current implementation doesn't validate if the resources actually exist before creating the relationship. If either resource is not found, it will still create the relationship using the provided identifiers.

Apply this diff to add proper validation:

      const fromResource = await db.query.resource.findFirst({
        where: and(
          inWorkspace,
          eq(SCHEMA.resource.identifier, body.fromIdentifier),
        ),
      });
+     if (!fromResource) {
+       return Response.json(
+         { error: "From resource not found" },
+         { status: 404 },
+       );
+     }

      const toResource = await db.query.resource.findFirst({
        where: and(
          inWorkspace,
          eq(SCHEMA.resource.identifier, body.toIdentifier),
        ),
      });
+     if (!toResource) {
+       return Response.json(
+         { error: "To resource not found" },
+         { status: 404 },
+       );
+     }

Also applies to: 32-37

Comment on lines +39 to +42
const relationship = await db.insert(SCHEMA.resourceRelationship).values({
...body,
fromIdentifier: fromResource?.identifier ?? body.fromIdentifier,
toIdentifier: toResource?.identifier ?? body.toIdentifier,
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use validated resource identifiers

Since we're fetching the resources first, we should use their validated identifiers instead of falling back to the request body.

Apply this diff:

      const relationship = await db.insert(SCHEMA.resourceRelationship).values({
        ...body,
-       fromIdentifier: fromResource?.identifier ?? body.fromIdentifier,
-       toIdentifier: toResource?.identifier ?? body.toIdentifier,
+       fromIdentifier: fromResource.identifier,
+       toIdentifier: toResource.identifier,
      });

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines 511 to 515
.filter((n) => n.node != null)
.map((n) => ({
...n.resource_relationship,
resource: n.node!,
})),
Copy link
Member

Choose a reason for hiding this comment

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

if you do isPresent check, typescript will know in the next map node is not null

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: 4

📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 4c1156b and 7a75a92.

📒 Files selected for processing (8)
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-drawer/JobDrawer.tsx (0 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-drawer/RelationshipsDiagramDependencies.tsx (0 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-drawer/TargetDrawer.tsx (0 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-drawer/relationships/RelationshipContent.tsx (0 hunks)
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-drawer/relationships/RelationshipsDiagram.tsx (0 hunks)
  • packages/api/src/router/job.ts (1 hunks)
  • packages/api/src/router/resources.ts (1 hunks)
  • packages/job-dispatch/src/policy-checker.ts (0 hunks)
💤 Files with no reviewable changes (6)
  • packages/job-dispatch/src/policy-checker.ts
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-drawer/TargetDrawer.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-drawer/RelationshipsDiagramDependencies.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-drawer/relationships/RelationshipContent.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/job-drawer/JobDrawer.tsx
  • apps/webservice/src/app/[workspaceSlug]/(app)/_components/target-drawer/relationships/RelationshipsDiagram.tsx
🔇 Additional comments (2)
packages/api/src/router/job.ts (1)

381-381: Initialize 'data' variable to prevent undefined access

The variable data might be undefined if the query does not return any results. Returning undefined may cause issues in the calling code. Ensure that data is properly initialized or handle the case when data is undefined.

Apply the following diff to handle the undefined case:

+ if (!data) {
+   throw new Error("No data found for the given job ID.");
+ }
  return data;
packages/api/src/router/resources.ts (1)

414-441: LGTM! Clear and type-safe results aggregation

The results aggregation is well-structured:

  • Proper Promise.all usage for parallel execution
  • Type-safe filtering of null nodes
  • Clear separation of nodes and associations

Comment on lines +360 to +363
const resource = await ctx.db.query.resource.findFirst({
where: eq(schema.resource.id, input),
});
if (resource == null) return null;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add isNotDeleted check for consistency

The resource existence check should include isNotDeleted to maintain consistency with other queries and prevent operating on soft-deleted resources.

 const resource = await ctx.db.query.resource.findFirst({
-  where: eq(schema.resource.id, input),
+  where: and(
+    eq(schema.resource.id, input),
+    isNotDeleted
+  ),
 });

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +390 to +413
const toNodesPromises = ctx.db
.select()
.from(schema.resourceRelationship)
.innerJoin(
schema.resource,
eq(
schema.resourceRelationship.toIdentifier,
schema.resource.identifier,
),
)
.where(
and(
eq(schema.resourceRelationship.workspaceId, resource.workspaceId),
eq(schema.resourceRelationship.fromIdentifier, resource.identifier),
),
)
.then((rows) =>
rows.map(async (row) => ({
...row,
node: await getNodeDataForResource(ctx.db, row.resource.id),
})),
)
.then((promises) => Promise.all(promises));

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Refactor duplicate code and optimize queries

This code block is nearly identical to the from nodes retrieval. Consider extracting a reusable function and applying the same query optimization.

+const getRelatedNodes = async (
+  db: Tx,
+  workspaceId: string,
+  fromIdentifier: string,
+  toIdentifier: string,
+) => {
+  return db
+    .select({
+      resource: schema.resource,
+      relationship: schema.resourceRelationship,
+      provider: schema.resourceProvider,
+      workspace: schema.workspace,
+    })
+    .from(schema.resourceRelationship)
+    // ... (rest of the optimized query)
+};

-const toNodesPromises = ctx.db
-  .select()
-  // ... (current implementation)
+const toNodesPromises = getRelatedNodes(
+  ctx.db,
+  resource.workspaceId,
+  resource.identifier,
+  resource.identifier,
+);

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +366 to +389
const fromNodesPromises = ctx.db
.select()
.from(schema.resourceRelationship)
.innerJoin(
schema.resource,
eq(
schema.resourceRelationship.fromIdentifier,
schema.resource.identifier,
),
)
.where(
and(
eq(schema.resourceRelationship.workspaceId, resource.workspaceId),
eq(schema.resourceRelationship.toIdentifier, resource.identifier),
),
)
.then((rows) =>
rows.map(async (row) => ({
...row,
node: await getNodeDataForResource(ctx.db, row.resource.id),
})),
)
.then((promises) => Promise.all(promises));

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optimize N+1 query pattern

The current implementation makes N+1 queries by calling getNodeDataForResource for each relationship. This could be optimized by fetching all resources in a single query.

 const fromNodesPromises = ctx.db
   .select({
+    resource: schema.resource,
+    relationship: schema.resourceRelationship,
+    provider: schema.resourceProvider,
+    workspace: schema.workspace,
   })
   .from(schema.resourceRelationship)
   .innerJoin(
     schema.resource,
     eq(
       schema.resourceRelationship.fromIdentifier,
       schema.resource.identifier,
     ),
   )
+  .leftJoin(
+    schema.resourceProvider,
+    eq(schema.resource.providerId, schema.resourceProvider.id),
+  )
+  .innerJoin(
+    schema.workspace,
+    eq(schema.resource.workspaceId, schema.workspace.id),
+  )
   .where(
     and(
       eq(schema.resourceRelationship.workspaceId, resource.workspaceId),
       eq(schema.resourceRelationship.toIdentifier, resource.identifier),
     ),
   )
-  .then((rows) =>
-    rows.map(async (row) => ({
-      ...row,
-      node: await getNodeDataForResource(ctx.db, row.resource.id),
-    })),
-  )
-  .then((promises) => Promise.all(promises));

Committable suggestion skipped: line range outside the PR's diff.

where: eq(schema.resource.id, input),
});
if (resource == null) return null;
const childrenNodes = await getNodesRecursively(ctx.db, input);
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Consider adding a depth limit to prevent stack overflow

The recursive implementation of getNodesRecursively could potentially cause stack overflow for deeply nested relationships. Consider adding a depth limit parameter.

-const getNodesRecursively = async (db: Tx, resourceId: string) => {
+const getNodesRecursively = async (
+  db: Tx,
+  resourceId: string,
+  maxDepth: number = 10
+) => {
   const baseNode = await getNodeDataForResource(db, resourceId);
-  return getNodesRecursivelyHelper(db, baseNode, []);
+  return getNodesRecursivelyHelper(db, baseNode, [], maxDepth);
 };

Committable suggestion skipped: line range outside the PR's diff.

@adityachoudhari26 adityachoudhari26 merged commit 35589c8 into main Dec 2, 2024
7 of 10 checks passed
@adityachoudhari26 adityachoudhari26 deleted the resource-associations branch December 2, 2024 06:12
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.

2 participants