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

feat: documentation lineage propagation #1498

Merged
merged 43 commits into from
Nov 22, 2024

Conversation

AdiGajbhiye
Copy link
Collaborator

@AdiGajbhiye AdiGajbhiye commented Nov 7, 2024

Overview

https://www.loom.com/share/b289e2b70e674eb39ff34341520d9ee5

Problem

Describe the problem you are solving. Mention the ticket/issue if applicable.

Solution

Describe the implemented solution. Add external references if needed.

Screenshot/Demo

A picture is worth a thousand words. Please highlight the changes if applicable.

How to test

  • Steps to be followed to verify the solution or code changes
  • Mention if there is any settings configuration added/changed/deleted

Checklist

  • I have run this code and it appears to resolve the stated issue
  • README.md updated and added information about my change

Important

Introduces documentation lineage propagation with DbtLineageService and UI updates for column-level lineage handling.

  • Behavior:
    • Adds event_type to DBTColumnLineageRequest in altimate.ts.
    • Introduces DbtLineageService to handle column-level lineage and documentation propagation.
    • Implements DocumentationPropagationButton in DocGeneratorInput.tsx for UI interaction.
  • Services:
    • New DbtLineageService class for managing lineage operations, including getConnectedColumns() and handleColumnLineage().
    • Updates DbtTestService to support fetching tests for models and columns.
  • UI Components:
    • Adds DocumentationPropagationButton in DocGeneratorInput.tsx for propagating documentation.
    • Introduces new styles in styles.module.scss for UI consistency.
  • Misc:
    • Adds PropagateIcon to icons/index.tsx for UI representation.
    • Refactors NewLineagePanel and DocsEditViewPanel to integrate with DbtLineageService.

This description was created by Ellipsis for cc9f90a. It will automatically update as commits are pushed.


Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced DbtLineageService for managing and retrieving column-level lineage information.
    • Added DocumentationPropagationButton for propagating documentation across database columns.
    • New methods in DbtTestService for retrieving tests based on the current model context.
    • Enhanced DocsEditViewPanel with new commands for managing column lineage.
    • Added a new property event_type to improve column lineage request handling.
    • New icon component PropagateIcon for improved visual representation.
  • Improvements

    • Updated error handling across various components for better user feedback.
    • Streamlined message handling in DocsEditViewPanel.
    • Enhanced progress tracking and cancellation management in DbtLineageService.
    • Improved the logic for managing connected tables and columns in DbtLineageService.
  • Styles

    • Added new styles for documentation editor components to improve UI presentation.
    • Introduced new CSS classes for better layout and design in documentation propagation.

Copy link
Contributor

coderabbitai bot commented Nov 7, 2024

Walkthrough

This pull request introduces several significant changes across multiple files, primarily focusing on enhancing data lineage functionality. Key updates include the addition of a new event_type property in the DBTColumnLineageRequest interface and the introduction of the DbtLineageService class, which centralizes lineage management tasks. Other modifications include new methods in the DbtTestService and updates to the DocsEditViewPanel and NewLineagePanel classes to integrate lineage services. Additionally, new components and styles for documentation propagation are added, enhancing the overall structure and user interaction capabilities.

Changes

File Change Summary
src/altimate.ts Added event_type: string; to DBTColumnLineageRequest interface.
src/services/dbtLineageService.ts Added DbtLineageService class, CllEvents enum, and multiple methods for managing column lineage.
src/services/dbtTestService.ts Added methods getTestsForCurrentModel and getTestsForModel to retrieve test configurations.
src/webview_provider/docsEditPanel.ts Updated constructor to include DbtLineageService, added new methods for handling lineage requests and improved error handling.
src/webview_provider/newLineagePanel.ts Refactored to utilize DbtLineageService, removed cancellation token logic, and streamlined command handling.
webview_panels/src/assets/icons/index.tsx Added export for PropagateIcon.
webview_panels/src/modules/documentationEditor/components/docGenerator/DocGeneratorInput.tsx Imported DocumentationPropagationButton and modified onChange function for action dispatching.
webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx Introduced DocumentationPropagationButton component with state management for documentation propagation.
webview_panels/src/modules/documentationEditor/components/documentationPropagation/styles.module.scss Added new CSS classes .itemCard and .itemRow for styling.

Possibly related PRs

Suggested reviewers

  • mdesmet

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.

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.

Copy link
Contributor

sweep-ai bot commented Nov 7, 2024

Hey @AdiGajbhiye, here is an example of how you can ask me to improve this pull request:

@Sweep Add unit tests for the `LineageService.getConnectedTables()` method to verify:
1. Correct table retrieval for different node types (source, model, metric, exposure)
2. Handling of edge cases like empty or undefined graph meta maps
3. Sorting of returned tables

📖 For more information on how to use Sweep, please read our documentation.

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

❌ Changes requested. Reviewed everything up to 470a5f0 in 1 minute and 39 seconds

More details
  • Looked at 1708 lines of code in 9 files
  • Skipped 1 files when reviewing.
  • Skipped posting 1 drafted comments based on config settings.
1. src/services/dbtLineageService.ts:12
  • Draft comment:
    Avoid using abbreviations like 'env'. Use 'environment variables' for clarity.
  • Reason this comment was not posted:
    Marked as duplicate.

Workflow ID: wflow_ObVZ9kZhp8hJOifz


Want Ellipsis to fix these issues? Tag @ellipsis-dev in a comment. You can customize Ellipsis with 👍 / 👎 feedback, review rules, user-specific overrides, quiet mode, and more.

src/services/dbtLineageService.ts Outdated Show resolved Hide resolved
import {
CancellationToken,
CancellationTokenSource,
env,
Copy link
Contributor

Choose a reason for hiding this comment

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

Avoid using abbreviations like 'env'. Use 'environment variables' for clarity.

Suggested change
env,
environmentVariables,

@@ -913,6 +985,60 @@ export class DocsEditViewPanel implements WebviewViewProvider {
);
}

private async handleSyncRequestFromWebview(
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 duplicates existing functionality in altimateWebviewProvider.ts. Consider refactoring to use the existing function.

Copy link
Collaborator

Choose a reason for hiding this comment

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

valid comment

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

🧹 Outside diff range and nitpick comments (7)
webview_panels/src/modules/documentationEditor/components/docGenerator/DocGeneratorInput.tsx (1)

148-148: Consider enhancing error handling and user feedback

While the DocumentationPropagationButton integration looks good, consider these improvements:

  1. Add error boundaries to handle potential failures gracefully
  2. Include loading states for better user feedback
  3. Consider adding user confirmation before propagation to prevent accidental updates

Example implementation:

<ErrorBoundary fallback={<ErrorMessage />}>
  <DocumentationPropagationButton 
    type={type} 
    name={entity.name}
    onBeforePropagation={() => confirm('Are you sure you want to propagate this documentation?')}
    onError={(error) => handleError(error)}
  />
</ErrorBoundary>
src/services/dbtTestService.ts (2)

259-267: Add error handling and documentation for getTestsForCurrentModel.

The method could be improved in several ways:

  1. Add JSDoc documentation explaining the method's purpose and return type.
  2. Add error handling for invalid file paths.
  3. Consider supporting other dbt model file extensions besides .sql.

Here's a suggested improvement:

+  /**
+   * Retrieves tests for the currently active model in the editor.
+   * @returns {Promise<TestMetaData[] | undefined>} Array of test metadata for the current model, or undefined if no model is found.
+   * @throws {Error} If the current file is not a valid dbt model.
+   */
   public async getTestsForCurrentModel() {
     const eventResult = this.queryManifestService.getEventByCurrentProject();
     if (!eventResult?.event || !eventResult?.currentDocument) {
       return undefined;
     }
 
     const project = this.queryManifestService.getProject();
     if (!project) {
       return undefined;
     }
 
     const { currentDocument } = eventResult;
+    const supportedExtensions = ['.sql', '.py', '.yml'];  // Add other supported extensions
+    const fileExt = path.extname(currentDocument.uri.fsPath);
+    if (!supportedExtensions.includes(fileExt)) {
+      throw new Error(`Invalid dbt model file type: ${fileExt}`);
+    }
     const modelName = path.basename(currentDocument.uri.fsPath, fileExt);
     return this.getTestsForModel(modelName);
   }

Line range hint 284-324: Improve readability and type safety of test metadata processing.

The implementation could be enhanced with better type safety and readability:

  1. Extract the "tests" constant to avoid magic strings.
  2. Add explicit type for the filter callback.
  3. Break down the complex map-filter chain for better readability.

Here's a suggested improvement:

+    const GRAPH_META_TESTS_KEY = 'tests';
+    type TestMetaDataWithKey = TestMetaData & { key: string };
+
     this.dbtTerminal.debug(
       "dbtTests",
       "getting tests by modelName:",
       false,
       modelName,
     );
     const _node = nodeMetaMap.lookupByBaseName(modelName);
     if (!_node) {
       this.dbtTerminal.debug("no node for tableName:", modelName);
       return;
     }
     const key = _node.uniqueId;
-    return (graphMetaMap["tests"].get(key)?.nodes || [])
+    return (graphMetaMap[GRAPH_META_TESTS_KEY].get(key)?.nodes || [])
       .map((n) => {
         const testKey = n.label.split(".")[0];
         const testData = testMetaMap.get(testKey);
 
         if (!testData) {
           return null;
         }
 
         // For singular tests, attached_node will be undefined
         if (!testData.attached_node) {
           return { ...testData, key: testKey };
         }
 
         // dbt sends tests (ex: relationships) to both source and connected models
         // do not send the test which has different model in attached_node
         if (testData.attached_node !== key) {
           return null;
         }
 
         const {
           depends_on: { macros },
           test_metadata,
         } = testData;
 
         const macroFilepath = this.getMacroFilePath(
           macros,
           projectName,
           macroMetaMap,
           test_metadata?.name,
         );
 
         return {
           ...testData,
           path: macroFilepath || testData.path,
           key: testKey,
         };
-      })
-      .filter((t) => Boolean(t));
+      })
+      .filter((t): t is TestMetaDataWithKey => Boolean(t));
src/altimate.ts (1)

Line range hint 743-748: Consider adding input validation for event_type.

The getColumnLevelLineage method accepts the request without validating the event_type. Consider adding validation or using an enum to restrict the possible values.

+export enum DBTColumnLineageEventType {
+  DOCUMENTATION = "documentation",
+  // Add other valid event types
+}

 export interface DBTColumnLineageRequest {
   model_dialect: string;
   targets: { uniqueId: string; column_name: string }[];
   model_info: ModelInfo[];
   upstream_expansion: boolean;
   upstream_models: string[];
   selected_column: { model_node?: ModelNode; column: string };
   session_id: string;
   show_indirect_edges: boolean;
-  event_type: string;
+  event_type: DBTColumnLineageEventType;
 }
src/webview_provider/newLineagePanel.ts (2)

Line range hint 118-122: Add Error Handling for 'getUpstreamTables' Method

The call to this.dbtLineageService.getUpstreamTables(params) lacks error handling. If an exception occurs within this method, it could lead to unhandled exceptions and potentially crash the extension. Consider wrapping this call in a try-catch block to handle possible errors gracefully.

Apply this diff to add error handling:

+ try {
    const body = this.dbtLineageService.getUpstreamTables(params);
    this._panel?.webview.postMessage({
      command: "response",
      args: { id, syncRequestId, body, status: true },
    });
+ } catch (error) {
+   window.showErrorMessage(
+     extendErrorWithSupportLinks(
+       "Unable to get upstream tables: " + (error as Error).message,
+     ),
+   );
+   this._panel?.webview.postMessage({
+     command: "response",
+     args: { id, syncRequestId, error, status: false },
+   });
+   this.telemetry.sendTelemetryError("getUpstreamTablesError", {
+     params,
+   });
+ }

Line range hint 127-131: Add Error Handling for 'getDownstreamTables' Method

Similar to the previous issue, the call to this.dbtLineageService.getDownstreamTables(params) does not include error handling. If an exception occurs, it could result in unhandled exceptions and potentially crash the extension. It's advisable to wrap this call in a try-catch block to manage errors effectively.

Apply this diff to add error handling:

+ try {
    const body = this.dbtLineageService.getDownstreamTables(params);
    this._panel?.webview.postMessage({
      command: "response",
      args: { id, syncRequestId, body, status: true },
    });
+ } catch (error) {
+   window.showErrorMessage(
+     extendErrorWithSupportLinks(
+       "Unable to get downstream tables: " + (error as Error).message,
+     ),
+   );
+   this._panel?.webview.postMessage({
+     command: "response",
+     args: { id, syncRequestId, error, status: false },
+   });
+   this.telemetry.sendTelemetryError("getDownstreamTablesError", {
+     params,
+   });
+ }
src/services/dbtLineageService.ts (1)

483-485: Enhance error handling for undefined error messages

The error handling assumes that error is an instance of Error with a message property. If error is of a different type, this could result in an undefined message being displayed. Consider checking if error has a message property or using a default message.

Apply this diff to improve error handling:

  window.showErrorMessage(
    extendErrorWithSupportLinks(
-     "Could not generate column level lineage: " +
-       (error as Error).message,
+     "Could not generate column level lineage: " +
+       (error instanceof Error && error.message ? error.message : String(error)),
    ),
  );
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 839a6e8 and 0333496.

⛔ Files ignored due to path filters (1)
  • webview_panels/src/assets/icons/propagate.svg is excluded by !**/*.svg
📒 Files selected for processing (9)
  • src/altimate.ts (1 hunks)
  • src/services/dbtLineageService.ts (1 hunks)
  • src/services/dbtTestService.ts (2 hunks)
  • src/webview_provider/docsEditPanel.ts (7 hunks)
  • src/webview_provider/newLineagePanel.ts (8 hunks)
  • webview_panels/src/assets/icons/index.tsx (1 hunks)
  • webview_panels/src/modules/documentationEditor/components/docGenerator/DocGeneratorInput.tsx (5 hunks)
  • webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx (1 hunks)
  • webview_panels/src/modules/documentationEditor/components/documentationPropagation/styles.module.scss (1 hunks)
✅ Files skipped from review due to trivial changes (1)
  • webview_panels/src/modules/documentationEditor/components/documentationPropagation/styles.module.scss
🧰 Additional context used
🪛 Biome
webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx

[error] 171-171: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

🔇 Additional comments (9)
webview_panels/src/modules/documentationEditor/components/docGenerator/DocGeneratorInput.tsx (3)

26-26: LGTM: Clean import addition

The import follows React conventions and aligns with the feature's purpose.


126-126: Minor formatting changes

These changes only affect the closing parentheses alignment.

Also applies to: 136-136


26-26: Verify documentation propagation integration

Let's verify the complete integration of the documentation propagation feature.

Also applies to: 148-148

✅ Verification successful

Based on the shell script output, I can see the complete implementation of the documentation propagation feature. Let me analyze the key findings:

  1. The DocumentationPropagationButton component is properly integrated:

    • It's imported and used in DocGeneratorInput.tsx
    • Has proper event handling with propagateDocumentation function
    • Uses proper state management through props (type and name)
    • Has proper UI elements with button and icon
  2. The propagation event handling is properly implemented:

    • Uses proper event handling with onClick
    • Has proper event emission to backend via documentation_propagation event type
  3. The component is properly typed and follows the codebase patterns:

    • Uses TypeScript
    • Follows the component structure pattern
    • Has proper prop types

Documentation propagation integration is properly implemented

The feature is correctly integrated with proper event handling, state management and type definitions. No issues found.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify proper integration of documentation propagation feature

# Test 1: Check for proper type definitions
echo "Checking for DocumentationPropagationButton type definitions..."
rg -t typescript "interface.*DocumentationPropagationButton" 

# Test 2: Verify state management integration
echo "Checking for documentation propagation state management..."
rg -t typescript "useDocumentationContext.*propagation"

# Test 3: Look for event handlers
echo "Checking for propagation event handlers..."
rg -t typescript "onPropagate|handlePropagate"

Length of output: 633


Script:

#!/bin/bash
# Let's try without typescript file type flag and search for the component and its integration

# Check for DocumentationPropagationButton component definition and usage
echo "Checking DocumentationPropagationButton component..."
rg "DocumentationPropagationButton"

# Check for propagation related state and handlers
echo "Checking propagation related code..."
rg "propagation|propagate" -i

# Look for the actual component file
echo "Finding component file..."
fd "DocumentationPropagation"

Length of output: 278290

webview_panels/src/assets/icons/index.tsx (1)

30-30: LGTM! Verify SVG file existence.

The new icon export follows the established pattern and naming convention.

Let's verify that the SVG file exists:

✅ Verification successful

SVG file exists and follows the established pattern

The propagate.svg file is present in the expected location at webview_panels/src/assets/icons/propagate.svg, confirming that the icon export is properly configured.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check if the propagate.svg file exists in the icons directory
fd --type f "propagate.svg" webview_panels/src/assets/icons/

Length of output: 106

src/altimate.ts (2)

Line range hint 743-748: Implementation looks good!

The integration of the new event_type property is clean and doesn't impact the existing error handling or request processing logic.


66-66: Verify the impact of adding required property.

The addition of event_type as a required property in DBTColumnLineageRequest interface could be a breaking change. Existing code using this interface will need to be updated to provide this property.

webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx (1)

1-270: Overall implementation is solid and aligns with the requirements

The DocumentationPropagationButton component is well-structured and integrates seamlessly with the existing documentation editor. The use of React hooks (useState, useEffect, useRef) is appropriate, and asynchronous operations are handled correctly with executeRequestInAsync and executeRequestInSync. The component effectively manages state and provides a good user experience by allowing users to propagate documentation to downstream models.

🧰 Tools
🪛 Biome

[error] 171-171: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

src/services/dbtLineageService.ts (1)

466-466: Typo in property name 'confindence'

There's a typo in the return object: confindence should be confidence. This could lead to issues when accessing this property elsewhere in the code.

src/webview_provider/docsEditPanel.ts (1)

1023-1040: Utility method 'sendResponseToWebview' is well-implemented

The sendResponseToWebview method provides a clear and consistent way to send responses back to the webview. The implementation is clean and follows best practices.

Comment on lines +269 to +274
public async getTestsForModel(modelName: string) {
const eventResult = this.queryManifestService.getEventByCurrentProject();
if (!eventResult?.event || !eventResult?.currentDocument) {
return undefined;
}

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

Remove unnecessary check and add documentation for getTestsForModel.

The method has a few issues that should be addressed:

  1. Missing JSDoc documentation.
  2. Unnecessary check for currentDocument since this method doesn't use it.
  3. Missing return type annotation.

Here's the suggested improvement:

+  /**
+   * Retrieves tests associated with a specific dbt model.
+   * @param modelName - The name of the dbt model without file extension
+   * @returns {Promise<TestMetaData[] | undefined>} Array of test metadata for the model, or undefined if no model is found
+   */
-  public async getTestsForModel(modelName: string) {
+  public async getTestsForModel(modelName: string): Promise<TestMetaData[] | undefined> {
     const eventResult = this.queryManifestService.getEventByCurrentProject();
-    if (!eventResult?.event || !eventResult?.currentDocument) {
+    if (!eventResult?.event) {
       return undefined;
     }
📝 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
public async getTestsForModel(modelName: string) {
const eventResult = this.queryManifestService.getEventByCurrentProject();
if (!eventResult?.event || !eventResult?.currentDocument) {
return undefined;
}
/**
* Retrieves tests associated with a specific dbt model.
* @param modelName - The name of the dbt model without file extension
* @returns {Promise<TestMetaData[] | undefined>} Array of test metadata for the model, or undefined if no model is found
*/
public async getTestsForModel(modelName: string): Promise<TestMetaData[] | undefined> {
const eventResult = this.queryManifestService.getEventByCurrentProject();
if (!eventResult?.event) {
return undefined;
}

Comment on lines +167 to +177
const setAllColumnsValue = (value: boolean) => {
setSelectedColumns(
allColumns.reduce(
(acc, curr) => ({
...acc,
[curr.model + "/" + curr.column]: value,
}),
{},
),
);
};
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 setAllColumnsValue to improve performance

Using the spread operator (...acc) on accumulators within a reduce function can lead to O(n²) time complexity due to the creation of a new object at each iteration. This can cause performance issues with larger datasets.

Apply the following refactor to enhance performance:

-    setSelectedColumns(
-      allColumns.reduce(
-        (acc, curr) => ({
-          ...acc,
-          [curr.model + "/" + curr.column]: value,
-        }),
-        {},
-      ),
-    );
+    const newSelectedColumns: Record<string, boolean> = {};
+    for (const curr of allColumns) {
+      newSelectedColumns[curr.model + "/" + curr.column] = value;
+    }
+    setSelectedColumns(newSelectedColumns);
📝 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 setAllColumnsValue = (value: boolean) => {
setSelectedColumns(
allColumns.reduce(
(acc, curr) => ({
...acc,
[curr.model + "/" + curr.column]: value,
}),
{},
),
);
};
const setAllColumnsValue = (value: boolean) => {
const newSelectedColumns: Record<string, boolean> = {};
for (const curr of allColumns) {
newSelectedColumns[curr.model + "/" + curr.column] = value;
}
setSelectedColumns(newSelectedColumns);
};
🧰 Tools
🪛 Biome

[error] 171-171: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

Comment on lines +494 to +495
const url = window.activeTextEditor!.document.uri.path;
const node = this.dbtLineageService.createTable(event.event, url, key);
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

Prevent Potential Null Reference Error with 'window.activeTextEditor'

The use of window.activeTextEditor! assumes that there is always an active text editor. However, window.activeTextEditor can be undefined, which may lead to a runtime error if no editor is open. To prevent this, add a null check before accessing window.activeTextEditor.

Apply this diff to include a null check:

+ if (!window.activeTextEditor) {
+   return {
+     aiEnabled,
+     missingLineageMessage: this.getMissingLineageMessage(),
+   };
+ }
  const url = window.activeTextEditor.document.uri.path;
  const node = this.dbtLineageService.createTable(event.event, url, key);
  return { node, aiEnabled };

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

return g.get(key)?.nodes.length || 0;
}

private cancellationTokenSource: CancellationTokenSource | undefined;
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

Ensure 'cancellationTokenSource' is initialized before use

In the getConnectedColumns method, this.cancellationTokenSource is asserted as non-null using the ! operator. If cancellationTokenSource is undefined, this will cause a runtime error. Ensure that cancellationTokenSource is properly initialized before using it, or add necessary null checks.

Apply this diff to handle the potential undefined value:

  await project.getNodesWithDBColumns(
    event,
    modelsToFetch,
-   this.cancellationTokenSource!.token,
+   this.cancellationTokenSource?.token || new CancellationTokenSource().token,
  );

Alternatively, ensure that this.cancellationTokenSource is always initialized before calling getConnectedColumns.

Also applies to: 318-318

if (this.cancellationTokenSource?.token.isCancellationRequested) {
return { column_lineage: [] };
}
const sessionId = `${env.sessionId}-${selectedColumn.table}-${selectedColumn.name}`;
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

Sanitize 'sessionId' components to handle special characters

When constructing sessionId with selectedColumn.table and selectedColumn.name, special characters in these values may lead to issues. To prevent potential errors or security vulnerabilities, encode these components.

Apply this diff to encode the variables:

-     const sessionId = `${env.sessionId}-${selectedColumn.table}-${selectedColumn.name}`;
+     const sessionId = `${env.sessionId}-${encodeURIComponent(selectedColumn.table)}-${encodeURIComponent(selectedColumn.name)}`;
📝 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 sessionId = `${env.sessionId}-${selectedColumn.table}-${selectedColumn.name}`;
const sessionId = `${env.sessionId}-${encodeURIComponent(selectedColumn.table)}-${encodeURIComponent(selectedColumn.name)}`;

nodeType: string;
materialization?: string;
description?: string;
tests: any[];
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

Define a specific type for the 'tests' property

The 'tests' property in the Table type is currently typed as any[]. Using any reduces type safety and can lead to runtime errors. Consider defining a specific interface or type for tests to enhance code reliability.

Apply this diff to define a specific type for tests:

-   tests: any[];
+   tests: TestMetadata[];

Ensure that TestMetadata is defined appropriately to reflect the structure of test objects.

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

Comment on lines 703 to 704
if (!t[0].startsWith("model")) {
return;
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 null check for 't[0]' before using 'startsWith'

Ensure that t[0] is defined before calling t[0].startsWith("model") to prevent potential runtime errors if t[0] is undefined.

Apply this diff to fix the issue:

-                    if (!t[0].startsWith("model")) {
+                    if (!t[0] || !t[0].startsWith("model")) {
📝 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
if (!t[0].startsWith("model")) {
return;
if (!t[0] || !t[0].startsWith("model")) {
return;

Comment on lines 671 to 679
const _tables = targets
.map(
(t) =>
this.dbtLineageService.getDownstreamTables({ table: t[0] })
?.tables,
)
.filter((t) => Boolean(t))
.flat() as Table[];
const tables = _tables.map((t) => t?.table);
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

Ensure asynchronous functions are properly awaited

The method getDownstreamTables might be asynchronous. If it returns a Promise, you need to await its result. Currently, the code does not await the result, which could lead to unexpected behavior or runtime errors.

Apply this diff to fix the issue:

-                const _tables = targets
-                  .map(
-                    (t) =>
-                      this.dbtLineageService.getDownstreamTables({ table: t[0] })
-                        ?.tables,
-                  )
-                  .filter((t) => Boolean(t))
-                  .flat() as Table[];
+                const _tablesNested = await Promise.all(
+                  targets.map(async (t) => {
+                    const result = await this.dbtLineageService.getDownstreamTables({ table: t[0] });
+                    return result?.tables;
+                  }),
+                );
+                const _tables = _tablesNested.filter((t) => Boolean(t)).flat() as Table[];
📝 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 _tables = targets
.map(
(t) =>
this.dbtLineageService.getDownstreamTables({ table: t[0] })
?.tables,
)
.filter((t) => Boolean(t))
.flat() as Table[];
const tables = _tables.map((t) => t?.table);
const _tablesNested = await Promise.all(
targets.map(async (t) => {
const result = await this.dbtLineageService.getDownstreamTables({ table: t[0] });
return result?.tables;
}),
);
const _tables = _tablesNested.filter((t) => Boolean(t)).flat() as Table[];
const tables = _tables.map((t) => t?.table);

Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
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

🧹 Outside diff range and nitpick comments (3)
src/services/dbtLineageService.ts (3)

73-111: Consider enhancing token disposal

While the implementation handles cancellation states well, consider adding a try-finally block to ensure token disposal in all cases, including unexpected errors.

 async handleColumnLineage(
   { event }: { event: CllEvents },
   onCancel: () => void,
 ) {
+  try {
     if (event === CllEvents.START) {
       window.withProgress(
         {
           title: "Retrieving column level lineage",
           location: ProgressLocation.Notification,
           cancellable: true,
         },
         async (_, token) => {
           await new Promise<void>((resolve) => {
             this.cancellationTokenSource = new DerivedCancellationTokenSource(
               token,
             );
             this.cllProgressResolve = resolve;
             token.onCancellationRequested(() => {
               onCancel();
             });
           });
         },
       );
       return;
     }
     // ... rest of the implementation
+  } finally {
+    if (event !== CllEvents.START) {
+      this.cancellationTokenSource?.dispose();
+    }
+  }
 }

262-491: Consider decomposing this large method

The getConnectedColumns method is quite complex and handles multiple responsibilities. Consider breaking it down into smaller, focused methods:

  1. Model preparation
  2. SQL compilation
  3. API request handling
  4. Error handling

Example refactor for the model preparation part:

+ private prepareModels(
+   currAnd1HopTables: string[],
+   targets: [string, string][],
+   upstreamExpansion: boolean,
+   event: ManifestCacheProjectAddedEvent
+ ) {
+   const modelInfos: ModelInfo[] = [];
+   let upstream_models: string[] = [];
+   let auxiliaryTables: string[] = [];
+   let sqlTables: string[] = [];
+   
+   currAnd1HopTables = Array.from(new Set(currAnd1HopTables));
+   const currTables = new Set(targets.map((t) => t[0]));
+   
+   if (upstreamExpansion) {
+     const hop1Tables = currAnd1HopTables.filter((t) => !currTables.has(t));
+     upstream_models = [...hop1Tables];
+     sqlTables = [...hop1Tables];
+     auxiliaryTables = DBTProject.getNonEphemeralParents(event, hop1Tables);
+   } else {
+     auxiliaryTables = DBTProject.getNonEphemeralParents(
+       event,
+       Array.from(currTables),
+     );
+     sqlTables = Array.from(currTables);
+   }
+   
+   return {
+     modelInfos,
+     upstream_models,
+     auxiliaryTables,
+     sqlTables,
+     currAnd1HopTables: Array.from(new Set(currAnd1HopTables))
+   };
+ }

374-384: Enhance error message with actionable information

The error message for materialization failure could be more helpful by including steps to resolve the issue.

 if (relationsWithoutColumns.length !== 0) {
   window.showErrorMessage(
     extendErrorWithSupportLinks(
-      "Failed to fetch columns for " +
-        relationsWithoutColumns.join(", ") +
-        ". Probably the dbt models are not yet materialized.",
+      `Failed to fetch columns for ${relationsWithoutColumns.join(", ")}. ` +
+      "These models need to be materialized first. Run 'dbt run' to materialize the models."
     ),
   );
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 0333496 and bf4570b.

📒 Files selected for processing (1)
  • src/services/dbtLineageService.ts (1 hunks)
🔇 Additional comments (1)
src/services/dbtLineageService.ts (1)

53-60: Well-implemented token management!

The DerivedCancellationTokenSource class effectively manages linked cancellation tokens, ensuring proper cleanup and cancellation propagation.

@@ -535,7 +543,7 @@ export class DocsEditViewPanel implements WebviewViewProvider {
return undefined;
}

const { command, syncRequestId, args } = message;
const { command, syncRequestId, ...params } = message;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This might break other commands handled here. Make sure all the commands in this handler are tested from webview panel

}
}

private sendResponseToWebview({
Copy link
Collaborator

Choose a reason for hiding this comment

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

this is also available in altimateWebview provider

@@ -168,7 +124,7 @@ export class NewLineagePanel
}

if (command === "downstreamTables") {
const body = await this.getDownstreamTables(params);
const body = this.dbtLineageService.getDownstreamTables(params);
Copy link
Collaborator

Choose a reason for hiding this comment

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

no await needed?

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

❌ Changes requested. Incremental review on 84cb9ef in 51 seconds

More details
  • Looked at 498 lines of code in 1 files
  • Skipped 0 files when reviewing.
  • Skipped posting 1 drafted comments based on config settings.
1. src/webview_provider/docsEditPanel.ts:744
  • Draft comment:
    The saveDocumentation function is duplicated. Consider removing the duplicate to avoid redundancy and potential maintenance issues.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable:
    The comment seems to be incorrect as there is no evidence of duplication in the provided code. The function was refactored into a separate method, which is a common practice to improve code organization and readability.
    I might be missing some context from other parts of the codebase that are not included in the diff. However, based on the provided information, there is no duplication.
    The task is to review the diff provided, and based on that, there is no duplication of the saveDocumentation function.
    The comment about the saveDocumentation function being duplicated is incorrect based on the provided diff. It should be removed.

Workflow ID: wflow_E5k4DkzGROblD48L


Want Ellipsis to fix these issues? Tag @ellipsis-dev in a comment. You can customize Ellipsis with 👍 / 👎 feedback, review rules, user-specific overrides, quiet mode, and more.

@@ -913,6 +741,288 @@ export class DocsEditViewPanel implements WebviewViewProvider {
);
}

private async saveDocumentation(message: any, syncRequestId: string) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Please provide a return type for the saveDocumentation function to improve code readability and maintainability.

Suggested change
private async saveDocumentation(message: any, syncRequestId: string) {
private async saveDocumentation(message: any, syncRequestId: string): Promise<void> {

}
}

private async handleSyncRequestFromWebview(
Copy link
Contributor

Choose a reason for hiding this comment

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

Please provide a return type for the handleSyncRequestFromWebview function to improve code readability and maintainability.

Suggested change
private async handleSyncRequestFromWebview(
private async handleSyncRequestFromWebview(syncRequestId: string | undefined, callback: () => any, command: string, showErrorNotification?: boolean): Promise<void> {

}
// check if file exists, if not create an empty file
if (!existsSync(patchPath)) {
writeFileSync(patchPath, "");

Check failure

Code scanning / CodeQL

Potential file system race condition High

The file may have changed since it
was checked
.
}
// Force reload from manifest after manifest refresh
this.loadedFromManifest = false;
writeFileSync(patchPath, stringify(parsedDocFile, { lineWidth: 0 }));

Check failure

Code scanning / CodeQL

Potential file system race condition High

The file may have changed since it
was checked
.
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: 2

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between bf4570b and 84cb9ef.

📒 Files selected for processing (1)
  • src/webview_provider/docsEditPanel.ts (7 hunks)
🧰 Additional context used
🪛 GitHub Check: CodeQL
src/webview_provider/docsEditPanel.ts

[failure] 792-792: Potential file system race condition
The file may have changed since it was checked.


[failure] 924-924: Potential file system race condition
The file may have changed since it was checked.

🪛 Biome
src/webview_provider/docsEditPanel.ts

[error] 761-764: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 771-773: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

🔇 Additional comments (3)
src/webview_provider/docsEditPanel.ts (3)

23-23: LGTM: New imports and constructor changes

The new imports and constructor dependency injection are well-structured and align with the new lineage functionality being added.

Also applies to: 35-35, 53-58, 121-121


660-668: LGTM: Column lineage base handler

The handler is well-implemented with proper error handling through the callback.


972-1024: LGTM: Well-structured utility methods

The new utility methods handleSyncRequestFromWebview and sendResponseToWebview are well-implemented with:

  • Proper error handling
  • Type safety
  • Consistent response format

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

👍 Looks good to me! Incremental review on 42eb8bc in 49 seconds

More details
  • Looked at 75 lines of code in 2 files
  • Skipped 0 files when reviewing.
  • Skipped posting 5 drafted comments based on config settings.
1. webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx:11
  • Draft comment:
    Remove unused import panelLogger to clean up the code.
  • Reason this comment was not posted:
    Confidence changes required: 10%
    The code in DocumentationPropagation.tsx has a redundant import statement for panelLogger which is not used anywhere in the file. Removing unused imports is a good practice to keep the code clean and efficient.
2. src/webview_provider/docsEditPanel.ts:741
  • Draft comment:
    Consider adding error handling within window.withProgress to manage any errors during the bulk save operation.
  • Reason this comment was not posted:
    Confidence changes required: 50%
    The saveDocumentationBulk function in docsEditPanel.ts uses window.withProgress to show progress while saving documentation. However, it does not handle any errors that might occur during the save operation. Adding error handling would improve robustness.
3. webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx:157
  • Draft comment:
    Consider using ?? instead of || for nullish coalescing to handle null or undefined values more accurately.
  • Reason this comment was not posted:
    Confidence changes required: 30%
    The patchPath assignment in DocumentationPropagation.tsx uses a fallback mechanism with ||. In TypeScript, using ?? (nullish coalescing) is preferred for handling null or undefined values, as it avoids false positives with falsy values like 0 or ''.
4. src/webview_provider/docsEditPanel.ts:734
  • Draft comment:
    Consider using early returns in the saveDocumentationBulk function to reduce nesting and improve readability.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable:
    The 'saveDocumentationBulk' function is a new addition, and the comment is about improving readability by using early returns. The function currently uses a loop to iterate over 'message.models' and calls 'saveDocumentation' for each item. There is no obvious nesting that could be reduced by early returns, as the function is straightforward.
    I might be missing some subtlety in how early returns could be applied here, but the function seems simple enough that early returns wouldn't significantly improve readability.
    Given the simplicity of the function, early returns might not provide a noticeable improvement in readability. The function's current structure is clear and direct.
    The comment about using early returns in the 'saveDocumentationBulk' function is not strongly justified given the function's simplicity. The comment should be deleted.
5. webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx:149
  • Draft comment:
    Use semantic versioning (semver) for version comparisons instead of direct array index comparisons for better accuracy and readability.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable:
    The comment seems to be misplaced as there is no evidence of version comparison or array index usage for versioning in the provided code. The code is focused on propagating documentation and handling related data structures.
    I might be missing some context about where versioning could be relevant, but based on the provided code, there is no indication of version handling that would require semantic versioning.
    Given the code provided, there is no indication of version handling, so the comment about semantic versioning seems irrelevant.
    The comment about using semantic versioning is not relevant to the changes made in the diff and should be deleted.

Workflow ID: wflow_LZV4eGj5tmoQrvl0


You can customize Ellipsis with 👍 / 👎 feedback, review rules, user-specific overrides, quiet mode, and more.

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

🧹 Outside diff range and nitpick comments (4)
webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx (4)

41-76: Consider consolidating state management

The component uses multiple separate useState hooks that could be consolidated into a single state object for better maintainability.

Consider refactoring the state management like this:

-  const [allColumns, setAllColumns] = useState<DocsItem[]>([]);
-  const [currColumns, setCurrColumns] = useState<DocsItem[]>([]);
-  const [isLoading, setIsLoading] = useState(false);
-  const [tableMetadata, setTableMetadata] = useState<TableMetadata[]>([]);
-  const [testsMetadata, setTestsMetadata] = useState<Record<string, unknown>>({});
-  const [selectedColumns, setSelectedColumns] = useState<Record<string, boolean>>({});
+  const [state, setState] = useState({
+    allColumns: [] as DocsItem[],
+    currColumns: [] as DocsItem[],
+    isLoading: false,
+    tableMetadata: [] as TableMetadata[],
+    testsMetadata: {} as Record<string, unknown>,
+    selectedColumns: {} as Record<string, boolean>
+  });

77-86: Extract magic number into a named constant

The iteration limit of 3 should be extracted into a named constant for better maintainability.

+const MAX_DOWNSTREAM_LEVELS = 3;
 const loadMoreDownstreamModels = async () => {
   executeRequestInAsync("columnLineageBase", { event: "start" });
   setIsLoading(true);
   let i = 0;
   const iAllColumns = [...allColumns];
   let iCurrColumns = currColumns;
-  while (i++ < 3) {
+  while (i++ < MAX_DOWNSTREAM_LEVELS) {

99-100: Remove commented code

Remove the commented line as it's not providing any value and could cause confusion.

   if (item.type === "indirect") continue;
-  // if (item.viewsType !== "Unchanged") continue;

156-158: Use nullish coalescing consistently

The eslint comment suggests nullish coalescing is preferred, but logical OR is used instead.

-        // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
-        patchPath: node?.patchPath || defaultPatchPath,
+        patchPath: node?.patchPath ?? defaultPatchPath,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 84cb9ef and 42eb8bc.

📒 Files selected for processing (2)
  • src/webview_provider/docsEditPanel.ts (6 hunks)
  • webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx (1 hunks)
🧰 Additional context used
🪛 GitHub Check: CodeQL
src/webview_provider/docsEditPanel.ts

[failure] 810-810: Potential file system race condition
The file may have changed since it was checked.


[failure] 942-942: Potential file system race condition
The file may have changed since it was checked.

🪛 Biome
src/webview_provider/docsEditPanel.ts

[error] 779-782: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 789-791: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx

[error] 170-170: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

🔇 Additional comments (6)
webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx (3)

13-39: LGTM: Well-structured interfaces

The interfaces are well-defined with appropriate types and clear structure.


166-176: ** Performance optimization needed in setAllColumnsValue**

This comment is skipped as it's already covered in past review comments and the static analysis hints correctly identified the performance issue with spread operator in reducers.

🧰 Tools
🪛 Biome

[error] 170-170: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)


178-269: LGTM: Well-structured UI implementation

The UI implementation is well-organized with proper conditional rendering and state management for buttons and form elements.

src/webview_provider/docsEditPanel.ts (3)

23-23: LGTM: Clean dependency injection and import organization

The new imports and constructor injection of DbtLineageService are well-organized and follow good practices.

Also applies to: 35-35, 53-58, 121-121


660-668: LGTM: Clean implementation of columnLineageBase handler

The handler properly delegates to the lineage service and handles cancellation.


990-1042: LGTM: Well-implemented utility methods

The new utility methods handleSyncRequestFromWebview and sendResponseToWebview are well-structured with proper error handling and type safety.

Comment on lines +762 to +988
);

if (existingColumn) {
// ignore tests, data_tests from existing column, as it will be recreated in `getTestDataByColumn`
const { tests, data_tests, ...rest } = existingColumn.toJSON();
this.setOrDeleteInParsedDocument(
existingColumn,
"description",
column.description?.trim(),
);
this.setOrDeleteInParsedDocument(
existingColumn,
"data_type",
(rest.data_type || column.type)?.toLowerCase(),
);
const allTests = this.getTestDataByColumn(
message,
column.name,
project,
existingColumn.toJSON(),
);
this.setOrDeleteInParsedDocument(
existingColumn,
"tests",
allTests?.tests,
);
this.setOrDeleteInParsedDocument(
existingColumn,
"data_tests",
allTests?.data_tests,
);
} else {
const name = getColumnNameByCase(
column.name,
projectByFilePath.getAdapterType(),
);
model.addIn(["columns"], {
name,
description: column.description?.trim() || undefined,
data_type: column.type?.toLowerCase(),
...this.getTestDataByColumn(message, column.name, project),
...(isQuotedIdentifier(
column.name,
projectByFilePath.getAdapterType(),
)
? { quote: true }
: undefined),
});
}
});
}
// Force reload from manifest after manifest refresh
this.loadedFromManifest = false;
writeFileSync(patchPath, stringify(parsedDocFile, { lineWidth: 0 }));
this.documentation = (
await this.docGenService.getDocumentationForCurrentActiveFile()
).documentation;
const tests = await this.dbtTestService.getTestsForCurrentModel();
if (syncRequestId) {
this._panel!.webview.postMessage({
command: "response",
args: {
syncRequestId,
body: {
saved: true,
tests,
},
status: true,
},
});
}
} catch (error) {
this.transmitError();
this.telemetry.sendTelemetryError(
TelemetryEvents["DocumentationEditor/SaveError"],
error,
);
window.showErrorMessage(
`Could not save documentation to ${patchPath}: ${error}`,
);
this.terminal.error(
"saveDocumentationError",
`Could not save documentation to ${patchPath}`,
error,
false,
);
if (syncRequestId) {
this._panel!.webview.postMessage({
command: "response",
args: {
syncRequestId,
body: {
saved: false,
},
status: true,
},
});
}
}
}
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

Address file system race conditions and improve code organization

The saveDocumentation method has several issues:

  1. Potential file system race conditions when checking existence and writing files

  2. Switch statement declarations that could leak

  3. Method is quite long and could be broken down

  4. Fix file system race conditions:

+ import { writeFileSync, readFileSync, renameSync } from 'fs';
+ import { tmpdir } from 'os';
+ import { randomBytes } from 'crypto';

  private async saveDocumentation(message: any, syncRequestId: string) {
    let patchPath = message.patchPath;
    try {
      // ... existing code ...

-     if (!existsSync(patchPath)) {
-       writeFileSync(patchPath, "");
+     // Create a temporary file
+     const tmpPath = path.join(tmpdir(), randomBytes(16).toString('hex'));
+     if (!existsSync(patchPath)) {
+       writeFileSync(tmpPath, "");
+       renameSync(tmpPath, patchPath);
      }

      // ... existing code ...

-     writeFileSync(patchPath, stringify(parsedDocFile, { lineWidth: 0 }));
+     writeFileSync(tmpPath, stringify(parsedDocFile, { lineWidth: 0 }));
+     renameSync(tmpPath, patchPath);
  1. Fix switch statement declarations:
  switch (message.dialogType) {
    case "Existing file": {
+     let openDialog;
      openDialog = await window.showOpenDialog({
        filters: { Yaml: ["yml"] },
        canSelectMany: false,
      });
      if (openDialog === undefined || openDialog.length === 0) {
        return;
      }
      patchPath = openDialog[0].fsPath;
      break;
    }
    case "New file": {
+     let saveDialog;
      saveDialog = await window.showSaveDialog({
        filters: { Yaml: ["yml"] },
      });
      if (!saveDialog) {
        return;
      }
      patchPath = saveDialog.fsPath;
      break;
    }
  }
  1. Consider extracting the following methods to improve readability:
  • handleFileSelection(dialogType: string): Promise<string>
  • updateModelDocumentation(model: YAMLMap, message: any)
  • updateColumnDocumentation(column: YAMLMap, message: any)

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

🧰 Tools
🪛 GitHub Check: CodeQL

[failure] 810-810: Potential file system race condition
The file may have changed since it was checked.


[failure] 942-942: Potential file system race condition
The file may have changed since it was checked.

🪛 Biome

[error] 779-782: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 789-791: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

Comment on lines 669 to 723
case "getDownstreamColumns": {
const targets = params.targets as [string, string][];
const _tables = targets
.map(
(t) =>
this.dbtLineageService.getDownstreamTables({ table: t[0] })
?.tables,
)
.filter((t) => Boolean(t))
.flat() as Table[];
const tables = _tables.map((t) => t?.table);
if (tables.length === 0) {
this.handleSyncRequestFromWebview(
syncRequestId,
() => ({}),
command,
);
return;
}
const selectedColumn = {
table: params.model as string,
name: params.column as string,
};
const currAnd1HopTables = [...tables, ...targets.map((t) => t[0])];
const columns = await this.dbtLineageService.getConnectedColumns({
targets,
currAnd1HopTables,
selectedColumn,
upstreamExpansion: false,
showIndirectEdges: false,
eventType: "documentation_propagation",
});
const testsResult = await Promise.all(
targets.map(async (t) => {
if (!t[0].startsWith("model")) {
return;
}
const splits = t[0].split(".");
const modelName = splits[splits.length - 1];
return await this.dbtTestService.getTestsForModel(modelName);
}),
);
const tests: Record<string, unknown> = {};
targets.forEach((t, i) => {
tests[t[0]] = testsResult[i];
});
this.handleSyncRequestFromWebview(
syncRequestId,
() => {
return { ...columns, tables: _tables, tests };
},
command,
);
break;
}
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

Fix async operations and add error handling in getDownstreamColumns

The current implementation has several issues that need to be addressed:

  1. Async operations aren't properly awaited
  2. Missing null check before using startsWith
  3. No error handling for the entire operation

Apply this diff to fix the issues:

  case "getDownstreamColumns": {
+   try {
      const targets = params.targets as [string, string][];
-     const _tables = targets
-       .map(
-         (t) =>
-           this.dbtLineageService.getDownstreamTables({ table: t[0] })
-             ?.tables,
-       )
-       .filter((t) => Boolean(t))
-       .flat() as Table[];
+     const _tablesNested = await Promise.all(
+       targets.map(async (t) => {
+         const result = await this.dbtLineageService.getDownstreamTables({ table: t[0] });
+         return result?.tables;
+       })
+     );
+     const _tables = _tablesNested.filter((t) => Boolean(t)).flat() as Table[];
      const tables = _tables.map((t) => t?.table);
      if (tables.length === 0) {
        this.handleSyncRequestFromWebview(
          syncRequestId,
          () => ({}),
          command,
        );
        return;
      }
      const selectedColumn = {
        table: params.model as string,
        name: params.column as string,
      };
      const currAnd1HopTables = [...tables, ...targets.map((t) => t[0])];
      const columns = await this.dbtLineageService.getConnectedColumns({
        targets,
        currAnd1HopTables,
        selectedColumn,
        upstreamExpansion: false,
        showIndirectEdges: false,
        eventType: "documentation_propagation",
      });
      const testsResult = await Promise.all(
        targets.map(async (t) => {
-         if (!t[0].startsWith("model")) {
+         if (!t[0] || !t[0].startsWith("model")) {
            return;
          }
          const splits = t[0].split(".");
          const modelName = splits[splits.length - 1];
          return await this.dbtTestService.getTestsForModel(modelName);
        }),
      );
      const tests: Record<string, unknown> = {};
      targets.forEach((t, i) => {
        tests[t[0]] = testsResult[i];
      });
      this.handleSyncRequestFromWebview(
        syncRequestId,
        () => {
          return { ...columns, tables: _tables, tests };
        },
        command,
      );
+   } catch (error) {
+     this.handleSyncRequestFromWebview(
+       syncRequestId,
+       () => {
+         throw error;
+       },
+       command,
+       true
+     );
+   }
    break;
  }
📝 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
case "getDownstreamColumns": {
const targets = params.targets as [string, string][];
const _tables = targets
.map(
(t) =>
this.dbtLineageService.getDownstreamTables({ table: t[0] })
?.tables,
)
.filter((t) => Boolean(t))
.flat() as Table[];
const tables = _tables.map((t) => t?.table);
if (tables.length === 0) {
this.handleSyncRequestFromWebview(
syncRequestId,
() => ({}),
command,
);
return;
}
const selectedColumn = {
table: params.model as string,
name: params.column as string,
};
const currAnd1HopTables = [...tables, ...targets.map((t) => t[0])];
const columns = await this.dbtLineageService.getConnectedColumns({
targets,
currAnd1HopTables,
selectedColumn,
upstreamExpansion: false,
showIndirectEdges: false,
eventType: "documentation_propagation",
});
const testsResult = await Promise.all(
targets.map(async (t) => {
if (!t[0].startsWith("model")) {
return;
}
const splits = t[0].split(".");
const modelName = splits[splits.length - 1];
return await this.dbtTestService.getTestsForModel(modelName);
}),
);
const tests: Record<string, unknown> = {};
targets.forEach((t, i) => {
tests[t[0]] = testsResult[i];
});
this.handleSyncRequestFromWebview(
syncRequestId,
() => {
return { ...columns, tables: _tables, tests };
},
command,
);
break;
}
case "getDownstreamColumns": {
try {
const targets = params.targets as [string, string][];
const _tablesNested = await Promise.all(
targets.map(async (t) => {
const result = await this.dbtLineageService.getDownstreamTables({ table: t[0] });
return result?.tables;
})
);
const _tables = _tablesNested.filter((t) => Boolean(t)).flat() as Table[];
const tables = _tables.map((t) => t?.table);
if (tables.length === 0) {
this.handleSyncRequestFromWebview(
syncRequestId,
() => ({}),
command,
);
return;
}
const selectedColumn = {
table: params.model as string,
name: params.column as string,
};
const currAnd1HopTables = [...tables, ...targets.map((t) => t[0])];
const columns = await this.dbtLineageService.getConnectedColumns({
targets,
currAnd1HopTables,
selectedColumn,
upstreamExpansion: false,
showIndirectEdges: false,
eventType: "documentation_propagation",
});
const testsResult = await Promise.all(
targets.map(async (t) => {
if (!t[0] || !t[0].startsWith("model")) {
return;
}
const splits = t[0].split(".");
const modelName = splits[splits.length - 1];
return await this.dbtTestService.getTestsForModel(modelName);
}),
);
const tests: Record<string, unknown> = {};
targets.forEach((t, i) => {
tests[t[0]] = testsResult[i];
});
this.handleSyncRequestFromWebview(
syncRequestId,
() => {
return { ...columns, tables: _tables, tests };
},
command,
);
} catch (error) {
this.handleSyncRequestFromWebview(
syncRequestId,
() => {
throw error;
},
command,
true
);
}
break;
}

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

❌ Changes requested. Incremental review on 773d0b9 in 30 seconds

More details
  • Looked at 13 lines of code in 1 files
  • Skipped 0 files when reviewing.
  • Skipped posting 0 drafted comments based on config settings.

Workflow ID: wflow_yyFUkAfUp7X5fHtE


Want Ellipsis to fix these issues? Tag @ellipsis-dev in a comment. You can customize Ellipsis with 👍 / 👎 feedback, review rules, user-specific overrides, quiet mode, and more.

if (item.type === "indirect") continue;
if (item.viewsType !== "Unchanged") continue;
if (
iCurrColumns.find(
Copy link
Contributor

Choose a reason for hiding this comment

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

Consider optimizing the search for columns in iCurrColumns by using a Set or Map for faster lookup instead of using find in a loop. This will improve performance, especially with larger datasets.

iCurrColumns = [];
break;
}
const newColumns: DocsItem[] = [];
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 early returns in loadMoreDownstreamModels to reduce nesting and improve readability. This applies to other parts of the function as well.

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 (3)
webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx (2)

41-70: Consider using useReducer for better state management

The component currently manages multiple related state variables. Consider refactoring to use useReducer to centralize state management and make state updates more predictable.

Example implementation:

interface State {
  allColumns: DocsItem[];
  currColumns: DocsItem[];
  isLoading: boolean;
  tableMetadata: TableMetadata[];
  testsMetadata: Record<string, unknown>;
  selectedColumns: Record<string, boolean>;
}

type Action = 
  | { type: 'SET_ALL_COLUMNS', payload: DocsItem[] }
  | { type: 'SET_CURR_COLUMNS', payload: DocsItem[] }
  | { type: 'SET_LOADING', payload: boolean }
  // ... other actions

const reducer = (state: State, action: Action): State => {
  switch (action.type) {
    case 'SET_ALL_COLUMNS':
      return { ...state, allColumns: action.payload };
    // ... other cases
  }
};

186-271: Enhance accessibility and loading state feedback

Consider the following improvements:

  1. Add aria-labels to buttons and inputs
  2. Show loading spinner during operations
  3. Add error messages for failed operations

Example improvements:

 <Button
   color="primary"
   outline
   onClick={loadMoreDownstreamModels}
   disabled={isLoading}
+  aria-label="Load more downstream models"
 >
-  Load 3 more downstream levels
+  {isLoading ? 'Loading...' : 'Load 3 more downstream levels'}
 </Button>
src/webview_provider/docsEditPanel.ts (1)

762-762: Add return type for better type safety

The function should explicitly declare its return type.

- private async saveDocumentation(message: any, syncRequestId: string) {
+ private async saveDocumentation(message: any, syncRequestId: string): Promise<void> {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 42eb8bc and 773d0b9.

📒 Files selected for processing (2)
  • src/webview_provider/docsEditPanel.ts (6 hunks)
  • webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx (1 hunks)
🧰 Additional context used
🪛 GitHub Check: CodeQL
src/webview_provider/docsEditPanel.ts

[failure] 810-810: Potential file system race condition
The file may have changed since it was checked.


[failure] 942-942: Potential file system race condition
The file may have changed since it was checked.

🪛 Biome
src/webview_provider/docsEditPanel.ts

[error] 779-782: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 789-791: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx

[error] 174-174: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

🔇 Additional comments (6)
webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx (3)

13-39: LGTM! Well-structured interfaces

The interfaces are well-defined with appropriate types and clear structure.


140-168: Add validation and error handling to propagateDocumentation

The function should validate inputs and handle potential errors during the bulk save operation.

Consider adding:

  1. Input validation before processing
  2. Try-catch block around the save operation
  3. User feedback for success/failure cases

170-180: Skip: Performance issue already flagged

This issue was already identified in a previous review comment and the static analysis hints.

🧰 Tools
🪛 Biome

[error] 174-174: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

src/webview_provider/docsEditPanel.ts (3)

23-23: LGTM: Clean dependency injection and imports

The new imports and constructor parameter for DbtLineageService are well-organized and follow dependency injection patterns.

Also applies to: 35-35, 53-58, 121-121


660-668: LGTM: Clean implementation of column lineage base handler

The handler properly integrates with the lineage service and includes appropriate event handling.


990-1042: LGTM: Well-structured utility methods

The new utility methods are well-implemented with:

  • Proper error handling including PythonException
  • Consistent response formatting
  • Clear separation of concerns

Comment on lines 77 to 138
const loadMoreDownstreamModels = async () => {
executeRequestInAsync("columnLineageBase", { event: "start" });
setIsLoading(true);
let i = 0;
const iAllColumns = [...allColumns];
let iCurrColumns = currColumns;
while (i++ < 3) {
if (iCurrColumns.length === 0) {
break;
}
const result = (await executeRequestInSync("getDownstreamColumns", {
targets: iCurrColumns.map((c) => [c.model, c.column]),
model: currentDocsData?.uniqueId,
column: name,
})) as DownstreamColumns;
if (!result.column_lineage) {
break;
}
setTableMetadata((prev) => [...prev, ...result.tables]);
setTestsMetadata((prev) => ({ ...prev, ...result.tests }));
if (result.column_lineage.length === 0) {
iCurrColumns = [];
break;
}
const newColumns: DocsItem[] = [];
for (const item of result.column_lineage) {
if (item.type === "indirect") continue;
if (item.viewsType !== "Unchanged") continue;
if (
iCurrColumns.find(
(c) => c.model === item.target[0] && c.column === item.target[1],
)
) {
newColumns.push({
model: item.source[0],
column: item.source[1],
description:
result.tables.find((t) => t.table === item.source[0])?.columns[
item.source[1]
]?.description ?? "",
});
}
}
iCurrColumns = newColumns;
iAllColumns.push(...newColumns);
}
executeRequestInAsync("columnLineageBase", { event: "end" });
const finalAllColumns: DocsItem[] = [];
for (const c of iAllColumns) {
if (
finalAllColumns.find(
(_c) => _c.model === c.model && _c.column === c.column,
)
) {
continue;
}
finalAllColumns.push(c);
}
setIsLoading(false);
setAllColumns(finalAllColumns);
setCurrColumns(iCurrColumns);
};
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

Add error handling and simplify loadMoreDownstreamModels function

The function could benefit from:

  1. Proper error handling for failed requests
  2. Breaking down complex logic into smaller functions
  3. Adding loading state error handling

Consider refactoring like this:

const loadMoreDownstreamModels = async () => {
  try {
    executeRequestInAsync("columnLineageBase", { event: "start" });
    setIsLoading(true);
    
    const newColumns = await loadNextThreeLevels();
    const finalColumns = removeDuplicateColumns(newColumns);
    
    setAllColumns(finalColumns);
    setCurrColumns(newColumns.currColumns);
  } catch (error) {
    console.error('Failed to load downstream models:', error);
    // Handle error appropriately
  } finally {
    setIsLoading(false);
    executeRequestInAsync("columnLineageBase", { event: "end" });
  }
};

Comment on lines 669 to 723
case "getDownstreamColumns": {
const targets = params.targets as [string, string][];
const testsResult = await Promise.all(
targets.map(async (t) => {
if (!t[0].startsWith("model")) {
return;
}
const splits = t[0].split(".");
const modelName = splits[splits.length - 1];
return await this.dbtTestService.getTestsForModel(modelName);
}),
);
const tests: Record<string, unknown> = {};
targets.forEach((t, i) => {
tests[t[0]] = testsResult[i];
});
const _tables = targets
.map(
(t) =>
this.dbtLineageService.getDownstreamTables({ table: t[0] })
?.tables,
)
.filter((t) => Boolean(t))
.flat() as Table[];
const tables = _tables.map((t) => t?.table);
if (tables.length === 0) {
this.handleSyncRequestFromWebview(
syncRequestId,
() => ({ column_lineage: [], tables: [], tests }),
command,
);
return;
}
const selectedColumn = {
table: params.model as string,
name: params.column as string,
};
const currAnd1HopTables = [...tables, ...targets.map((t) => t[0])];
const columns = await this.dbtLineageService.getConnectedColumns({
targets,
currAnd1HopTables,
selectedColumn,
upstreamExpansion: false,
showIndirectEdges: false,
eventType: "documentation_propagation",
});
this.handleSyncRequestFromWebview(
syncRequestId,
() => {
return { ...columns, tables: _tables, tests };
},
command,
);
break;
}
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

Fix async operations and add safety checks

The getDownstreamColumns handler needs several improvements:

  1. Async operations aren't properly awaited
  2. Missing null check for array access
  3. The code could be more maintainable with intermediate variables

Apply this diff to fix the issues:

case "getDownstreamColumns": {
  const targets = params.targets as [string, string][];
  const testsResult = await Promise.all(
    targets.map(async (t) => {
-     if (!t[0].startsWith("model")) {
+     if (!t[0] || !t[0].startsWith("model")) {
        return;
      }
      const splits = t[0].split(".");
      const modelName = splits[splits.length - 1];
      return await this.dbtTestService.getTestsForModel(modelName);
    }),
  );
  const tests: Record<string, unknown> = {};
  targets.forEach((t, i) => {
    tests[t[0]] = testsResult[i];
  });
- const _tables = targets
-   .map(
-     (t) =>
-       this.dbtLineageService.getDownstreamTables({ table: t[0] })
-         ?.tables,
-   )
-   .filter((t) => Boolean(t))
-   .flat() as Table[];
+ const _tablesNested = await Promise.all(
+   targets.map(async (t) => {
+     const result = await this.dbtLineageService.getDownstreamTables({ table: t[0] });
+     return result?.tables;
+   })
+ );
+ const _tables = _tablesNested.filter((t) => Boolean(t)).flat() as Table[];
📝 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
case "getDownstreamColumns": {
const targets = params.targets as [string, string][];
const testsResult = await Promise.all(
targets.map(async (t) => {
if (!t[0].startsWith("model")) {
return;
}
const splits = t[0].split(".");
const modelName = splits[splits.length - 1];
return await this.dbtTestService.getTestsForModel(modelName);
}),
);
const tests: Record<string, unknown> = {};
targets.forEach((t, i) => {
tests[t[0]] = testsResult[i];
});
const _tables = targets
.map(
(t) =>
this.dbtLineageService.getDownstreamTables({ table: t[0] })
?.tables,
)
.filter((t) => Boolean(t))
.flat() as Table[];
const tables = _tables.map((t) => t?.table);
if (tables.length === 0) {
this.handleSyncRequestFromWebview(
syncRequestId,
() => ({ column_lineage: [], tables: [], tests }),
command,
);
return;
}
const selectedColumn = {
table: params.model as string,
name: params.column as string,
};
const currAnd1HopTables = [...tables, ...targets.map((t) => t[0])];
const columns = await this.dbtLineageService.getConnectedColumns({
targets,
currAnd1HopTables,
selectedColumn,
upstreamExpansion: false,
showIndirectEdges: false,
eventType: "documentation_propagation",
});
this.handleSyncRequestFromWebview(
syncRequestId,
() => {
return { ...columns, tables: _tables, tests };
},
command,
);
break;
}
case "getDownstreamColumns": {
const targets = params.targets as [string, string][];
const testsResult = await Promise.all(
targets.map(async (t) => {
if (!t[0] || !t[0].startsWith("model")) {
return;
}
const splits = t[0].split(".");
const modelName = splits[splits.length - 1];
return await this.dbtTestService.getTestsForModel(modelName);
}),
);
const tests: Record<string, unknown> = {};
targets.forEach((t, i) => {
tests[t[0]] = testsResult[i];
});
const _tablesNested = await Promise.all(
targets.map(async (t) => {
const result = await this.dbtLineageService.getDownstreamTables({ table: t[0] });
return result?.tables;
})
);
const _tables = _tablesNested.filter((t) => Boolean(t)).flat() as Table[];
const tables = _tables.map((t) => t?.table);
if (tables.length === 0) {
this.handleSyncRequestFromWebview(
syncRequestId,
() => ({ column_lineage: [], tables: [], tests }),
command,
);
return;
}
const selectedColumn = {
table: params.model as string,
name: params.column as string,
};
const currAnd1HopTables = [...tables, ...targets.map((t) => t[0])];
const columns = await this.dbtLineageService.getConnectedColumns({
targets,
currAnd1HopTables,
selectedColumn,
upstreamExpansion: false,
showIndirectEdges: false,
eventType: "documentation_propagation",
});
this.handleSyncRequestFromWebview(
syncRequestId,
() => {
return { ...columns, tables: _tables, tests };
},
command,
);
break;
}

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

👍 Looks good to me! Incremental review on b944892 in 30 seconds

More details
  • Looked at 13 lines of code in 1 files
  • Skipped 0 files when reviewing.
  • Skipped posting 2 drafted comments based on config settings.
1. webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx:104
  • Draft comment:
    Ensure that filtering out 'Transformation' types aligns with the intended logic. Double-check if this change from 'Unchanged' to 'Transformation' is correct.
  • Reason this comment was not posted:
    Comment did not seem useful.
2. webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx:101
  • Draft comment:
    Use early returns in loadMoreDownstreamModels to reduce nesting and improve readability. This applies to the entire function.
  • Reason this comment was not posted:
    Comment was not on a valid diff hunk.

Workflow ID: wflow_sNJr33vS0qvawQeH


You can customize Ellipsis with 👍 / 👎 feedback, review rules, user-specific overrides, quiet mode, and more.

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

🧹 Outside diff range and nitpick comments (2)
webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx (2)

125-134: Optimize duplicate checking in finalAllColumns construction

In the loop constructing finalAllColumns, using .find inside a loop can lead to O(n²) time complexity due to repeated linear searches. To improve performance, consider using a Set or a Map to track duplicates more efficiently.

Apply this diff to enhance performance:

-    const finalAllColumns: DocsItem[] = [];
-    for (const c of iAllColumns) {
-      if (
-        finalAllColumns.find(
-          (_c) => _c.model === c.model && _c.column === c.column,
-        )
-      ) {
-        continue;
-      }
-      finalAllColumns.push(c);
-    }
+    const seenKeys = new Set<string>();
+    const finalAllColumns: DocsItem[] = [];
+    for (const c of iAllColumns) {
+      const key = c.model + "/" + c.column;
+      if (seenKeys.has(key)) {
+        continue;
+      }
+      seenKeys.add(key);
+      finalAllColumns.push(c);
+    }

263-265: Optimize selection count calculation

Calculating the number of selected columns using Object.values(selectedColumns).filter((v) => Boolean(v)).length on every render can be inefficient for large datasets. Consider maintaining a separate count state or using a memoized selector to improve performance.

Here's how you might implement a selection count state:

+  const [selectedCount, setSelectedCount] = useState(0);

+  useEffect(() => {
+    setSelectedCount(
+      Object.values(selectedColumns).filter((v) => Boolean(v)).length
+    );
+  }, [selectedColumns]);

   <Button
     color="primary"
     disabled={
-      Object.values(selectedColumns).filter((v) => Boolean(v)).length === 0
+      selectedCount === 0
     }
     onClick={() => propagateDocumentation()}
   >
     Propagate documentation to selected models
   </Button>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 773d0b9 and b944892.

📒 Files selected for processing (1)
  • webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx (1 hunks)
🧰 Additional context used
🪛 Biome
webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx

[error] 174-174: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

🔇 Additional comments (1)
webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx (1)

226-231: Avoid using spread operator when updating state in setSelectedColumns

Using the spread operator in state updates can be inefficient for large objects. Since selectedColumns is an object, consider using functional updates or alternative methods to update state without unnecessarily copying the entire object.

However, in this context, the impact might be minimal. If performance becomes a concern with large datasets, refactoring might be needed.

Comment on lines +160 to +161
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
patchPath: node?.patchPath || defaultPatchPath,
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 nullish coalescing operator instead of logical OR

The use of || can lead to unintended behavior when node?.patchPath is a falsy value like an empty string. Replace node?.patchPath || defaultPatchPath with node?.patchPath ?? defaultPatchPath to ensure that defaultPatchPath is used only when node?.patchPath is null or undefined.

Apply this diff to follow best practices:

-        // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
-        patchPath: node?.patchPath || defaultPatchPath,
+        patchPath: node?.patchPath ?? defaultPatchPath,

Also, remove the ESLint disable comment since it is no longer necessary.

📝 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
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
patchPath: node?.patchPath || defaultPatchPath,
patchPath: node?.patchPath ?? defaultPatchPath,

Comment on lines +140 to +168
const propagateDocumentation = async () => {
const defaultPackageName = tableMetadata.filter((t) => t.packageName)[0]
?.packageName;
const defaultPatchPath = defaultPackageName
? defaultPackageName + "://models/schema.yml"
: "";

const req = [];

for (const item of allColumns) {
const key = item.model + "/" + item.column;
if (!selectedColumns[key]) continue;
const splits = item.model.split(".");
const modelName = splits[splits.length - 1];
const node = tableMetadata.find((t) => t.table === item.model);
req.push({
name: modelName,
description: node?.description,
columns: [{ name: item.column, description: currColumnDescription }],
dialogType: "Existing file",
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
patchPath: node?.patchPath || defaultPatchPath,
filePath: node?.url,
updatedTests: testsMetadata[item.model],
});
}

await executeRequestInSync("saveDocumentationBulk", { models: req });
};
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 error handling in propagateDocumentation function

The function propagateDocumentation lacks error handling for the asynchronous operation. If executeRequestInSync("saveDocumentationBulk", { models: req }) fails, it could lead to unhandled promise rejections and impact user experience. Implement error handling to manage potential failures gracefully.

Apply this diff to include error handling:

-    await executeRequestInSync("saveDocumentationBulk", { models: req });
+    try {
+      await executeRequestInSync("saveDocumentationBulk", { models: req });
+    } catch (error) {
+      console.error('Failed to propagate documentation:', error);
+      // Optionally, provide user feedback or retry logic here
+    }
📝 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 propagateDocumentation = async () => {
const defaultPackageName = tableMetadata.filter((t) => t.packageName)[0]
?.packageName;
const defaultPatchPath = defaultPackageName
? defaultPackageName + "://models/schema.yml"
: "";
const req = [];
for (const item of allColumns) {
const key = item.model + "/" + item.column;
if (!selectedColumns[key]) continue;
const splits = item.model.split(".");
const modelName = splits[splits.length - 1];
const node = tableMetadata.find((t) => t.table === item.model);
req.push({
name: modelName,
description: node?.description,
columns: [{ name: item.column, description: currColumnDescription }],
dialogType: "Existing file",
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
patchPath: node?.patchPath || defaultPatchPath,
filePath: node?.url,
updatedTests: testsMetadata[item.model],
});
}
await executeRequestInSync("saveDocumentationBulk", { models: req });
};
const propagateDocumentation = async () => {
const defaultPackageName = tableMetadata.filter((t) => t.packageName)[0]
?.packageName;
const defaultPatchPath = defaultPackageName
? defaultPackageName + "://models/schema.yml"
: "";
const req = [];
for (const item of allColumns) {
const key = item.model + "/" + item.column;
if (!selectedColumns[key]) continue;
const splits = item.model.split(".");
const modelName = splits[splits.length - 1];
const node = tableMetadata.find((t) => t.table === item.model);
req.push({
name: modelName,
description: node?.description,
columns: [{ name: item.column, description: currColumnDescription }],
dialogType: "Existing file",
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
patchPath: node?.patchPath || defaultPatchPath,
filePath: node?.url,
updatedTests: testsMetadata[item.model],
});
}
try {
await executeRequestInSync("saveDocumentationBulk", { models: req });
} catch (error) {
console.error('Failed to propagate documentation:', error);
// Optionally, provide user feedback or retry logic here
}
};

Copy link
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

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

❌ Changes requested. Incremental review on cc9f90a in 1 minute and 1 seconds

More details
  • Looked at 48 lines of code in 2 files
  • Skipped 0 files when reviewing.
  • Skipped posting 2 drafted comments based on config settings.
1. src/webview_provider/docsEditPanel.ts:688
  • Draft comment:
    The method getUpstreamTables should be getDownstreamTables to correctly fetch downstream tables.
                  this.dbtLineageService.getDownstreamTables({ table: t[0] })
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable:
    The change from getDownstreamTables to getUpstreamTables seems intentional, especially since upstreamExpansion was also set to true. This suggests the code is now intended to handle upstream tables. The comment does not provide strong evidence that the change is incorrect, and the context supports the change.
    I might be missing some specific business logic that requires downstream tables instead of upstream. However, the change in the code suggests a clear intention to switch to upstream tables.
    The change in the code, including setting upstreamExpansion to true, strongly indicates that the intention is to work with upstream tables. Without additional context suggesting otherwise, the comment seems incorrect.
    The comment should be deleted as the change to getUpstreamTables appears intentional and aligns with other changes in the code.
2. src/webview_provider/docsEditPanel.ts:685
  • Draft comment:
    Use semantic versioning (semver) for version comparisons instead of direct array index comparisons. This ensures more accurate and reliable version checks. This issue is also present in other parts of the code.
  • Reason this comment was not posted:
    Comment was not on a valid diff hunk.

Workflow ID: wflow_89ZEWB7PBOdO5j0Q


Want Ellipsis to fix these issues? Tag @ellipsis-dev in a comment. You can customize Ellipsis with 👍 / 👎 feedback, review rules, user-specific overrides, quiet mode, and more.

if (item.viewsType === "Transformation") continue;
if (
iCurrColumns.find(
(c) => c.model === item.source[0] && c.column === item.source[1],
Copy link
Contributor

Choose a reason for hiding this comment

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

The condition should check against item.target instead of item.source to correctly identify current columns.

Suggested change
(c) => c.model === item.source[0] && c.column === item.source[1],
(c) => c.model === item.target[0] && c.column === item.target[1],

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 UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between b944892 and cc9f90a.

📒 Files selected for processing (2)
  • src/webview_provider/docsEditPanel.ts (6 hunks)
  • webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx (1 hunks)
🧰 Additional context used
🪛 Biome
src/webview_provider/docsEditPanel.ts

[error] 779-782: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 789-791: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

webview_panels/src/modules/documentationEditor/components/documentationPropagation/DocumentationPropagation.tsx

[error] 174-174: Avoid the use of spread (...) syntax on accumulators.

Spread syntax should be avoided on accumulators (like those in .reduce) because it causes a time complexity of O(n^2).
Consider methods such as .splice or .push instead.

(lint/performance/noAccumulatingSpread)

🔇 Additional comments (3)
src/webview_provider/docsEditPanel.ts (3)

23-23: LGTM: Clean dependency injection and error handling setup.

The new imports and constructor changes follow good practices for dependency injection and error handling.

Also applies to: 35-35, 53-58, 121-121


660-668: LGTM: Clean implementation of column lineage base handler.

The handler properly delegates to the lineage service and handles cancellation.


990-1042: LGTM: Well-structured utility methods with proper error handling.

The new utility methods are well-implemented with:

  • Proper error handling for different error types
  • Clear separation of concerns
  • Type-safe message handling

Comment on lines +809 to +811
if (!existsSync(patchPath)) {
writeFileSync(patchPath, "");
}
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

Address potential file system race conditions.

The current implementation has potential race conditions when checking file existence and writing files.

Apply this diff to fix the issues:

+ import { writeFileSync, readFileSync, renameSync } from 'fs';
+ import { tmpdir } from 'os';
+ import { randomBytes } from 'crypto';

- if (!existsSync(patchPath)) {
-   writeFileSync(patchPath, "");
- }
+ const tmpPath = path.join(tmpdir(), randomBytes(16).toString('hex'));
+ writeFileSync(tmpPath, "");
+ renameSync(tmpPath, patchPath);

- writeFileSync(patchPath, stringify(parsedDocFile, { lineWidth: 0 }));
+ const tmpOutputPath = path.join(tmpdir(), randomBytes(16).toString('hex'));
+ writeFileSync(tmpOutputPath, stringify(parsedDocFile, { lineWidth: 0 }));
+ renameSync(tmpOutputPath, patchPath);

Also applies to: 941-942

Comment on lines +777 to +800
switch (message.dialogType) {
case "Existing file":
const openDialog = await window.showOpenDialog({
filters: { Yaml: ["yml"] },
canSelectMany: false,
});
if (openDialog === undefined || openDialog.length === 0) {
return;
}
patchPath = openDialog[0].fsPath;
break;
case "New file":
const saveDialog = await window.showSaveDialog({
filters: { Yaml: ["yml"] },
});
if (!saveDialog) {
return;
}
this.telemetry.sendTelemetryEvent(
TelemetryEvents["DocumentationEditor/SaveNewFilePathSelect"],
);
patchPath = saveDialog.fsPath;
break;
}
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

Fix switch statement variable declarations.

The switch statement has variable declarations that could leak between cases.

Apply this diff to fix the issue:

switch (message.dialogType) {
  case "Existing file": {
+   let openDialog;
    openDialog = await window.showOpenDialog({
      filters: { Yaml: ["yml"] },
      canSelectMany: false,
    });
    if (openDialog === undefined || openDialog.length === 0) {
      return;
    }
    patchPath = openDialog[0].fsPath;
    break;
  }
  case "New file": {
+   let saveDialog;
    saveDialog = await window.showSaveDialog({
      filters: { Yaml: ["yml"] },
    });
    if (!saveDialog) {
      return;
    }
    patchPath = saveDialog.fsPath;
    break;
  }
}
📝 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
switch (message.dialogType) {
case "Existing file":
const openDialog = await window.showOpenDialog({
filters: { Yaml: ["yml"] },
canSelectMany: false,
});
if (openDialog === undefined || openDialog.length === 0) {
return;
}
patchPath = openDialog[0].fsPath;
break;
case "New file":
const saveDialog = await window.showSaveDialog({
filters: { Yaml: ["yml"] },
});
if (!saveDialog) {
return;
}
this.telemetry.sendTelemetryEvent(
TelemetryEvents["DocumentationEditor/SaveNewFilePathSelect"],
);
patchPath = saveDialog.fsPath;
break;
}
switch (message.dialogType) {
case "Existing file": {
let openDialog;
openDialog = await window.showOpenDialog({
filters: { Yaml: ["yml"] },
canSelectMany: false,
});
if (openDialog === undefined || openDialog.length === 0) {
return;
}
patchPath = openDialog[0].fsPath;
break;
}
case "New file": {
let saveDialog;
saveDialog = await window.showSaveDialog({
filters: { Yaml: ["yml"] },
});
if (!saveDialog) {
return;
}
this.telemetry.sendTelemetryEvent(
TelemetryEvents["DocumentationEditor/SaveNewFilePathSelect"],
);
patchPath = saveDialog.fsPath;
break;
}
}
🧰 Tools
🪛 Biome

[error] 779-782: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 789-791: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Unsafe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)

Comment on lines +669 to +723
case "getDownstreamColumns": {
const targets = params.targets as [string, string][];
const testsResult = await Promise.all(
targets.map(async (t) => {
if (!t[0].startsWith("model")) {
return;
}
const splits = t[0].split(".");
const modelName = splits[splits.length - 1];
return await this.dbtTestService.getTestsForModel(modelName);
}),
);
const tests: Record<string, unknown> = {};
targets.forEach((t, i) => {
tests[t[0]] = testsResult[i];
});
const _tables = targets
.map(
(t) =>
this.dbtLineageService.getUpstreamTables({ table: t[0] })
?.tables,
)
.filter((t) => Boolean(t))
.flat() as Table[];
const tables = _tables.map((t) => t?.table);
if (tables.length === 0) {
this.handleSyncRequestFromWebview(
syncRequestId,
() => ({ column_lineage: [], tables: [], tests }),
command,
);
return;
}
const selectedColumn = {
table: params.model as string,
name: params.column as string,
};
const currAnd1HopTables = [...tables, ...targets.map((t) => t[0])];
const columns = await this.dbtLineageService.getConnectedColumns({
targets,
currAnd1HopTables,
selectedColumn,
upstreamExpansion: true,
showIndirectEdges: false,
eventType: "documentation_propagation",
});
this.handleSyncRequestFromWebview(
syncRequestId,
() => {
return { ...columns, tables: _tables, tests };
},
command,
);
break;
}
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

Fix async operations and add safety checks.

The getDownstreamColumns handler needs several improvements:

  1. Missing null check before using startsWith
  2. Async operations in getDownstreamTables aren't properly awaited
  3. No error handling for the entire operation

Apply this diff to fix the issues:

case "getDownstreamColumns": {
  try {
    const targets = params.targets as [string, string][];
    const testsResult = await Promise.all(
      targets.map(async (t) => {
-       if (!t[0].startsWith("model")) {
+       if (!t[0] || !t[0].startsWith("model")) {
          return;
        }
        const splits = t[0].split(".");
        const modelName = splits[splits.length - 1];
        return await this.dbtTestService.getTestsForModel(modelName);
      }),
    );
    const tests: Record<string, unknown> = {};
    targets.forEach((t, i) => {
      tests[t[0]] = testsResult[i];
    });
-   const _tables = targets
-     .map(
-       (t) =>
-         this.dbtLineageService.getUpstreamTables({ table: t[0] })
-           ?.tables,
-     )
-     .filter((t) => Boolean(t))
-     .flat() as Table[];
+   const _tablesNested = await Promise.all(
+     targets.map(async (t) => {
+       const result = await this.dbtLineageService.getUpstreamTables({ table: t[0] });
+       return result?.tables;
+     })
+   );
+   const _tables = _tablesNested.filter((t) => Boolean(t)).flat() as Table[];
    // ... rest of the code ...
  } catch (error) {
    this.handleSyncRequestFromWebview(
      syncRequestId,
      () => {
        throw error;
      },
      command,
      true
    );
  }
}

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

Comment on lines +125 to +135
for (const c of iAllColumns) {
if (
finalAllColumns.find(
(_c) => _c.model === c.model && _c.column === c.column,
)
) {
continue;
}
finalAllColumns.push(c);
}
setIsLoading(false);
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 removal to improve performance

Using find within a loop to check for duplicates results in O(n²) time complexity. This can negatively impact performance with larger datasets.

Apply the following refactor to enhance performance:

- const finalAllColumns: DocsItem[] = [];
- for (const c of iAllColumns) {
-   if (
-     finalAllColumns.find(
-       (_c) => _c.model === c.model && _c.column === c.column,
-     )
-   ) {
-     continue;
-   }
-   finalAllColumns.push(c);
- }
+ const seenKeys = new Set<string>();
+ const finalAllColumns: DocsItem[] = [];
+ for (const c of iAllColumns) {
+   const key = `${c.model}/${c.column}`;
+   if (!seenKeys.has(key)) {
+     seenKeys.add(key);
+     finalAllColumns.push(c);
+   }
+ }
📝 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
for (const c of iAllColumns) {
if (
finalAllColumns.find(
(_c) => _c.model === c.model && _c.column === c.column,
)
) {
continue;
}
finalAllColumns.push(c);
}
setIsLoading(false);
const seenKeys = new Set<string>();
const finalAllColumns: DocsItem[] = [];
for (const c of iAllColumns) {
const key = `${c.model}/${c.column}`;
if (!seenKeys.has(key)) {
seenKeys.add(key);
finalAllColumns.push(c);
}
}
setIsLoading(false);

@mdesmet mdesmet merged commit a9d0046 into master Nov 22, 2024
9 checks passed
@mdesmet mdesmet deleted the feat-documentation-lineage-propagation branch November 22, 2024 04: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.

3 participants