Repository: appsmithorg/appsmith
Pull request #39517: fix: map widget marker onClick duplication
Author: login: jacquesikot
Labels: name: Bug
description: Something isn't working
name: Map Widget
name: Widgets Product
description: This label groups issues related to widgets
name: Needs Triaging
description: Needs attention from maintainers to triage
name: ok-to-test
description: Required label for CI
name: Widgets & Accelerators Pod
description: Issues related to widgets & Accelerators
name: Query Widgets & IDE Pod
description: All issues related to Query, JS, Eval, Widgets & IDE
Created: 2025-03-03T09:55:29Z | Merged: 2025-03-05T14:25:53Z

## Description
When clicking on a marker in the Map Widget, the onMarkerClick event was being triggered twice for a single click, causing duplicate actions and potentially unexpected behavior in applications.

```js
// In MapWidget/widget/index.tsx
onMarkerClick = (lat?: number, long?: number, title?: string) => {
  this.disableDrag(true);
  const selectedMarker = {
    lat: lat,
    long: long,
    title: title,
  };
  console.log("🚀 ~ MapWidget ~ title:", title); // This was logging twice

  this.props.updateWidgetMetaProperty("selectedMarker", selectedMarker, {
    triggerPropertyName: "onMarkerClick",
    dynamicString: this.props.onMarkerClick,
    event: {
      type: EventType.ON_MARKER_CLICK,
    },
  });
};
```

## Root Cause
This happens because:
1. The Marker component was adding duplicate event listeners to the Google Maps marker.
2. Two separate useEffect hooks were both adding "click" event listeners:
   - One during marker initialisation
   - Another in a separate useEffect that tracks the onClick prop

```js
// First listener in initialization useEffect
googleMapMarker.addListener("click", () => {
  if (onClick) onClick();
});

// Second listener in a separate useEffect
marker.addListener("click", () => {
  if (onClick) onClick();
});
```
3. When a marker was clicked, both event listeners fired, causing the onClick callback to execute twice.
4. This resulted in onMarkerClick being called twice for a single user interaction.

## Solution
Modify the `Marker.tsx` component to avoid adding duplicate event listeners:
1. Remove the click event listener from the initialisation useEffect
2. Add proper cleanup for event listeners using clearListeners and removeListener
3. Store the listener reference to properly remove it in the cleanup function
4. Apply the same pattern to the dragend event listener

```js
// track on onclick - with proper cleanup
useEffect(() => {
  if (!marker) return;

  // Clear existing click listeners before adding a new one
  google.maps.event.clearListeners(marker, "click");
  
  // Add the new click listener
  const clickListener = marker.addListener("click", () => {
    if (onClick) onClick();
  });

  // Return cleanup function
  return () => {
    google.maps.event.removeListener(clickListener);
  };
}, [marker, onClick]);
```

## Why this works
1. Properly cleans up existing listeners before adding new ones
2. Ensures only one click handler is active at any time
3. Prevents event handler duplication during component re-renders

## Testing
Added a Cypress test that verifies the marker click event triggers exactly once per click:
1. Creates a map widget with a marker
2. Uses a JS Object to track click count
3. Verifies the click count increments by exactly 1 for each click
4. Ensures no duplicate events are triggered


Fixes #39514 

## Automation

/ok-to-test tags="@tag.Widget, @tag.Sanity, @tag.Maps, @tag.Binding"

### :mag: Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/13673511245>
> Commit: 3c025ebd9dd85a3c409bc1582e13e324d8dbb2cd
> <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=13673511245&attempt=1" target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Widget, @tag.Sanity, @tag.Maps, @tag.Binding`
> Spec:
> <hr>Wed, 05 Mar 2025 11:49:24 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [x] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit

- **Bug Fixes**
  - Enhanced the behavior of map markers so they respond more reliably to user interactions like clicking and dragging, addressing issues that could affect overall stability.

- **Tests**
  - Added comprehensive tests to ensure marker functionality remains consistent and dependable during use.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

Linked issue #39514: [Bug]: Map widget is having performance issues with onMarkerClick event
### Is there an existing issue for this?

- [x] I have searched the existing issues

### Description

I am facing performance issues when using a Map widget with configured onMarkerClick.
So I configured the onMarkerClick to showAlert the title of the marker. I was able to notice that whenever I clicked the marker, two alerts were being thrown every time.

Also on using this functionality for a while, the page freezes and I get a popup stating that my App has hanged and I either need to wait or kill the Application.

Facing this issue in both Edit and View mode of the application

https://theappsmith.slack.com/archives/C02RCR2PURZ/p1740717067284009

### Steps To Reproduce

1. Drag and Drop a Map widget
2. Set some default markers in its setting
3. Configure onMarkerClick event to showAlert with the Marker's title
4. Now click on any Marker. You will see two alerts being thrown everytime
5. Keep clicking different markers, and after sometime the screen freezes and App starts to hang

### Public Sample App

_No response_

### Environment

Production

### Severity

Medium (Frustrating UX)

### Issue video log

_No response_

### Version

Cloud - 1.62

Diff:
diff --git a/app/client/cypress/e2e/Regression/ClientSide/Widgets/Others/MapWidget_Spec.ts b/app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapWidget_Spec.ts
similarity index 100%
rename from app/client/cypress/e2e/Regression/ClientSide/Widgets/Others/MapWidget_Spec.ts
rename to app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapWidget_Spec.ts
diff --git a/app/client/cypress/e2e/Regression/ClientSide/Widgets/Others/MapWidget_loading_Spec.ts b/app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapWidget_loading_Spec.ts
similarity index 100%
rename from app/client/cypress/e2e/Regression/ClientSide/Widgets/Others/MapWidget_loading_Spec.ts
rename to app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapWidget_loading_Spec.ts
diff --git a/app/client/src/widgets/MapWidget/component/MapComponent.test.tsx b/app/client/src/widgets/MapWidget/component/MapComponent.test.tsx
new file mode 100644
index 000000000000..998a4d382df8
--- /dev/null
+++ b/app/client/src/widgets/MapWidget/component/MapComponent.test.tsx
@@ -0,0 +1,121 @@
+import React from "react";
+import { render } from "@testing-library/react";
+import Marker from "../component/Marker";
+
+// Mock the google maps API
+const mockAddListener = jest.fn().mockImplementation((event, callback) => {
+  // Store the callback to simulate click events
+  if (event === "click") {
+    (
+      mockAddListener as unknown as {
+        clickCallback: (...args: unknown[]) => void;
+      }
+    ).clickCallback = callback;
+  }
+
+  return "listener-id"; // Return a mock listener ID
+});
+
+const mockRemoveListener = jest.fn();
+const mockSetMap = jest.fn();
+const mockSetIcon = jest.fn();
+const mockSetPosition = jest.fn();
+const mockSetTitle = jest.fn();
+
+// Add type declaration for the global google object
+declare global {
+  interface Window {
+    google: unknown;
+  }
+}
+
+// Mock the google object
+(global as unknown as { google: unknown }).google = {
+  maps: {
+    Marker: jest.fn().mockImplementation(() => ({
+      setMap: mockSetMap,
+      setIcon: mockSetIcon,
+      setPosition: mockSetPosition,
+      setTitle: mockSetTitle,
+      addListener: mockAddListener,
+    })),
+    Point: jest.fn().mockImplementation((x, y) => ({ x, y })),
+    event: {
+      clearListeners: jest.fn(),
+      removeListener: mockRemoveListener,
+    },
+  },
+};
+
+describe("Map Widget - Marker Component", () => {
+  beforeEach(() => {
+    jest.clearAllMocks();
+  });
+
+  it("should trigger onClick callback only once per click", () => {
+    const onClickMock = jest.fn();
+
+    // Render the marker component with onClick handler
+    render(
+      <Marker
+        onClick={onClickMock}
+        position={{ lat: 37.7749, lng: -122.4194 }}
+        title="Test Marker"
+      />,
+    );
+
+    // Simulate a marker click by directly calling the stored callback
+    (
+      mockAddListener as unknown as {
+        clickCallback: (...args: unknown[]) => void;
+      }
+    ).clickCallback();
+
+    // Verify onClick was called exactly once
+    expect(onClickMock).toHaveBeenCalledTimes(1);
+
+    // Simulate another click
+    (
+      mockAddListener as unknown as {
+        clickCallback: (...args: unknown[]) => void;
+      }
+    ).clickCallback();
+
+    // Verify onClick was called exactly twice (once per click)
+    expect(onClickMock).toHaveBeenCalledTimes(2);
+  });
+
+  it("should clear previous click listeners when onClick prop changes", () => {
+    const { rerender } = render(
+      <Marker
+        onClick={jest.fn()}
+        position={{ lat: 37.7749, lng: -122.4194 }}
+        title="Test Marker"
+      />,
+    );
+
+    // Verify clearListeners was called during initial render
+    expect(google.maps.event.clearListeners).toHaveBeenCalledWith(
+      expect.anything(),
+      "click",
+    );
+
+    // Reset the mock to check if it's called again
+    (google.maps.event.clearListeners as jest.Mock).mockClear();
+
+    // Rerender with a different onClick handler
+    rerender(
+      <Marker
+        onClick={jest.fn()}
+        position={{ lat: 37.7749, lng: -122.4194 }}
+        title="Test Marker"
+      />,
+    );
+
+    // Verify clearListeners was called again when onClick changed
+    expect(google.maps.event.clearListeners).toHaveBeenCalledWith(
+      expect.anything(),
+      "click",
+    );
+  });
+});
diff --git a/app/client/src/widgets/MapWidget/component/Marker.tsx b/app/client/src/widgets/MapWidget/component/Marker.tsx
index 7701ded5be28..ef9bec51e479 100644
--- a/app/client/src/widgets/MapWidget/component/Marker.tsx
+++ b/app/client/src/widgets/MapWidget/component/Marker.tsx
@@ -31,10 +31,6 @@ const Marker: React.FC<MarkerProps> = (options) => {
         title,
       });
 
-      googleMapMarker.addListener("click", () => {
-        if (onClick) onClick();
-      });
-
       setMarker(googleMapMarker);
     }
 
@@ -79,19 +75,32 @@ const Marker: React.FC<MarkerProps> = (options) => {
   useEffect(() => {
     if (!marker) return;
 
-    marker.addListener("click", () => {
+    google.maps.event.clearListeners(marker, "click");
+    const clickListener = marker.addListener("click", () => {
       if (onClick) onClick();
     });
+
+    return () => {
+      google.maps.event.removeListener(clickListener);
+    };
   }, [marker, onClick]);
 
   // add dragend event on marker
   useEffect(() => {
-    if (!marker) return;
+    if (!marker || !onDragEnd) return;
 
-    marker.addListener("dragend", (e: google.maps.MapMouseEvent) => {
-      if (onDragEnd) onDragEnd(e);
-    });
-  }, [marker, options.onDragEnd]);
+    google.maps.event.clearListeners(marker, "dragend");
+    const dragEndListener = marker.addListener(
+      "dragend",
+      (e: google.maps.MapMouseEvent) => {
+        if (onDragEnd) onDragEnd(e);
+      },
+    );
+
+    return () => {
+      google.maps.event.removeListener(dragEndListener);
+    };
+  }, [marker, onDragEnd]);
 
   return null;
 };


Reviews:
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (2)</summary><blockquote>

<details>
<summary>app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapMarkerClickEvent_spec.ts (2)</summary><blockquote>

`75-76`: **Use data-* attributes instead of aria-label selector**

According to your coding guidelines, selectors should use data-* attributes instead of other attributes.

```diff
- agHelper.GetNClick('div[aria-label="Test Marker"]');
+ // Use data-* attribute selectors when possible
+ agHelper.GetNClick('[data-testid="map-marker"]');
```

Consider working with the component developers to add appropriate data-testid attributes to map markers.

---

`27-43`: **Consider optimizing JSObject creation**

The JS object implementation is functional but could be improved to use proper state management:

```diff
 export default {
   clickCount: 0,
   incrementClickCount: () => {
-    this.clickCount += 1;
-    return this.clickCount;
+    return ++this.clickCount;
   },
   resetClickCount: () => {
     this.clickCount = 0;
+    return this.clickCount;
   }
 }
```

This ensures consistent state behavior and provides return values for all methods.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 9ef4c2d182d0fdc519ea699896730f649b49f5db and d2fe1218e28d9018f0a776d5eb025db45a09b8b6.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapMarkerClickEvent_spec.ts` (1 hunks)
* `app/client/src/widgets/MapWidget/component/Marker.tsx` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`app/client/cypress/**/**.*`: Review the following e2e test ...</summary>

> `app/client/cypress/**/**.*`: Review the following e2e test code written using the Cypress test library. Ensure that:
> - Follow best practices for Cypress code and e2e automation. 
> - Avoid using cy.wait in code.
> - Avoid using cy.pause in code.
> - Avoid using agHelper.sleep().
> - Use locator variables for locators and do not use plain strings.
> - Use data-* attributes for selectors.
> - Avoid Xpaths, Attributes and CSS path.
> - Avoid selectors like .btn.submit or button[type=submit].
> - Perform logins via API with LoginFromAPI.
> - Perform logout via API with LogOutviaAPI.
> - Perform signup via API with SignupFromAPI.
> - Avoid using it.only.
> - Avoid using after and aftereach in test cases.
> - Use multiple assertions for expect statements.
> - Avoid using strings for assertions.
> - Do not use duplicate filenames even with different paths.
> - Avoid using agHelper.Sleep, this.Sleep in any file in code.
> 

- `app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapMarkerClickEvent_spec.ts`

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (6)</summary>

* GitHub Check: build
* GitHub Check: perform-test / client-build / client-build
* GitHub Check: client-lint / client-lint
* GitHub Check: client-check-cyclic-deps / check-cyclic-dependencies
* GitHub Check: client-build / client-build
* GitHub Check: client-unit-tests / client-unit-tests

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapMarkerClickEvent_spec.ts (2)</summary>

`16-85`: **Test correctly verifies marker click fires only once**

The test implementation effectively validates that the map marker click event triggers only once per click, which directly addresses the issue fixed in this PR.

---

`81-82`: **Use data-* attributes instead of aria-label selector**

Same issue with the second click action.

</details>
<details>
<summary>app/client/src/widgets/MapWidget/component/Marker.tsx (3)</summary>

`78-85`: **Properly fixes marker click event duplication**

The implementation correctly addresses the duplicate click event issue by:
1. Clearing existing listeners before adding new ones
2. Storing the listener reference for proper cleanup
3. Implementing cleanup function to remove listeners when component unmounts

This change effectively resolves the problem where the `onMarkerClick` event was being triggered twice.

---

`92-102`: **Improved dragend event handling with proper cleanup**

The implementation now correctly:
1. Checks if onDragEnd handler exists before setting up listeners
2. Clears existing listeners before adding new ones
3. Properly removes listeners on cleanup

This approach prevents memory leaks and ensures consistent behavior.

---

`90-91`: **Early return optimization for dragend listener**

Good optimization to return early when marker or onDragEnd is not available, preventing unnecessary code execution.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T09:59:28Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapMarkerClickEvent_spec.ts (1)</summary><blockquote>

`24-45`: **Consider using class properties instead of `this` in a JS object.**

The JS object used to track clicks might encounter issues with the `this` context in the exported methods. In JavaScript, the value of `this` depends on how the function is called rather than where it's defined.


```diff
export default {
  clickCount: 0,
  incrementClickCount: () => {
-    this.clickCount += 1;
-    return this.clickCount;
+    return ++JSObject1.clickCount;
  },
  resetClickCount: () => {
-    this.clickCount = 0;
+    JSObject1.clickCount = 0;
  }
}
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 9ef4c2d182d0fdc519ea699896730f649b49f5db and d2fe1218e28d9018f0a776d5eb025db45a09b8b6.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapMarkerClickEvent_spec.ts` (1 hunks)
* `app/client/src/widgets/MapWidget/component/Marker.tsx` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`app/client/cypress/**/**.*`: Review the following e2e test ...</summary>

> `app/client/cypress/**/**.*`: Review the following e2e test code written using the Cypress test library. Ensure that:
> - Follow best practices for Cypress code and e2e automation. 
> - Avoid using cy.wait in code.
> - Avoid using cy.pause in code.
> - Avoid using agHelper.sleep().
> - Use locator variables for locators and do not use plain strings.
> - Use data-* attributes for selectors.
> - Avoid Xpaths, Attributes and CSS path.
> - Avoid selectors like .btn.submit or button[type=submit].
> - Perform logins via API with LoginFromAPI.
> - Perform logout via API with LogOutviaAPI.
> - Perform signup via API with SignupFromAPI.
> - Avoid using it.only.
> - Avoid using after and aftereach in test cases.
> - Use multiple assertions for expect statements.
> - Avoid using strings for assertions.
> - Do not use duplicate filenames even with different paths.
> - Avoid using agHelper.Sleep, this.Sleep in any file in code.
> 

- `app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapMarkerClickEvent_spec.ts`

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (5)</summary>

* GitHub Check: perform-test / client-build / client-build
* GitHub Check: client-lint / client-lint
* GitHub Check: client-check-cyclic-deps / check-cyclic-dependencies
* GitHub Check: client-build / client-build
* GitHub Check: client-unit-tests / client-unit-tests

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapMarkerClickEvent_spec.ts (4)</summary>

`16-19`: **Looks like a well-structured test suite.**

The test is correctly tagged with "@tag.Widget" and "@tag.Map" following the tagging convention.

---

`20-23`: **The test case description is clear and focuses on the specific issue being fixed.**

The test correctly verifies that the marker click event triggers only once, which directly addresses the duplicate event listener issue mentioned in the PR objectives.

---

`81-82`: **Same selector issue as before, but good verification logic.**

The test correctly verifies that clicking the marker a second time properly increments the counter, confirming that the event handler continues to work as expected.


Same selector concern as mentioned earlier.

---

`75-76`: <details>
<summary>❓ Verification inconclusive</summary>

**Use data-* attributes for selectors instead of attribute selectors.**

According to the provided coding guidelines, you should use data-* attributes for selectors instead of attribute selectors like `div[aria-label="Test Marker"]`.


If the Map widget doesn't expose data attributes for markers, consider adding them to make testing more robust. For now, this selector might be the only option available.

---

🏁 Script executed:

```shell
#!/bin/bash
# Check if there are any data attributes available for map markers
rg -A 5 -B 5 "data-" app/client/src/widgets/MapWidget/component/Marker.tsx
```

Length of output: 74

---



</details>

**Switch to data-* Attribute Selectors for Map Markers**

It appears that the Marker component in `app/client/src/widgets/MapWidget/component/Marker.tsx` does not currently expose any `data-*` attributes, which means the test is forced to use the fragile `aria-label` selector (`div[aria-label="Test Marker"]`) at lines 75–76. According to our coding guidelines, please consider updating the Map widget component to include a unique `data-*` attribute (for example, `data-marker="test-marker"`) for more robust and maintainable tests. If adding such an attribute isn’t feasible at the moment, document this as a temporary workaround and plan to revisit it later.

File: `app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapMarkerClickEvent_spec.ts` (Lines: 75-76)

</details>
<details>
<summary>app/client/src/widgets/MapWidget/component/Marker.tsx (1)</summary>

`90-103`: **Good improvement to dragend event listener management.**

The changes correctly:
1. Add an early return if marker or onDragEnd aren't defined
2. Clear existing dragend listeners before adding a new one
3. Store the listener in a variable for proper cleanup
4. Include a cleanup function to remove the listener on unmount

This follows the same pattern as the click event fix, ensuring consistent event handler management throughout the component.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T10:00:37Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapMarkerClickEvent_spec.ts (1)</summary><blockquote>

`75-75`: **Avoid using attribute selectors according to coding guidelines.**

The selector `div[aria-label="Test Marker"]` doesn't follow the guideline of using data-* attributes for selectors. This makes tests more brittle if the aria-label changes.


```diff
- agHelper.GetNClick('div[aria-label="Test Marker"]');
+ // Add a data-cy attribute to the marker in the component and use:
+ agHelper.GetNClick('[data-cy="map-marker"]');
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 9ef4c2d182d0fdc519ea699896730f649b49f5db and d2fe1218e28d9018f0a776d5eb025db45a09b8b6.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapMarkerClickEvent_spec.ts` (1 hunks)
* `app/client/src/widgets/MapWidget/component/Marker.tsx` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`app/client/cypress/**/**.*`: Review the following e2e test ...</summary>

> `app/client/cypress/**/**.*`: Review the following e2e test code written using the Cypress test library. Ensure that:
> - Follow best practices for Cypress code and e2e automation. 
> - Avoid using cy.wait in code.
> - Avoid using cy.pause in code.
> - Avoid using agHelper.sleep().
> - Use locator variables for locators and do not use plain strings.
> - Use data-* attributes for selectors.
> - Avoid Xpaths, Attributes and CSS path.
> - Avoid selectors like .btn.submit or button[type=submit].
> - Perform logins via API with LoginFromAPI.
> - Perform logout via API with LogOutviaAPI.
> - Perform signup via API with SignupFromAPI.
> - Avoid using it.only.
> - Avoid using after and aftereach in test cases.
> - Use multiple assertions for expect statements.
> - Avoid using strings for assertions.
> - Do not use duplicate filenames even with different paths.
> - Avoid using agHelper.Sleep, this.Sleep in any file in code.
> 

- `app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapMarkerClickEvent_spec.ts`

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (5)</summary>

* GitHub Check: perform-test / client-build / client-build
* GitHub Check: client-lint / client-lint
* GitHub Check: client-check-cyclic-deps / check-cyclic-dependencies
* GitHub Check: client-build / client-build
* GitHub Check: client-unit-tests / client-unit-tests

</details>

<details>
<summary>🔇 Additional comments (7)</summary><blockquote>

<details>
<summary>app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapMarkerClickEvent_spec.ts (3)</summary>

`81-81`: **Avoid using attribute selectors according to coding guidelines.**

The selector `div[aria-label="Test Marker"]` should be replaced with a data-* attribute selector per coding guidelines.

---

`16-19`: **Test suite structure looks good.**

The test suite is properly structured with clear tags for categorization.

---

`77-82`: **Good verification pattern for click event.**

The test properly verifies that clicks are counted exactly once by checking the incremented value after each interaction.

</details>
<details>
<summary>app/client/src/widgets/MapWidget/component/Marker.tsx (4)</summary>

`78-85`: **Good fix for the duplicate click event listener issue.**

Clearing existing listeners before adding new ones prevents the duplication issue. The cleanup function ensures proper resource management by removing listeners when the component unmounts or dependencies change.

---

`90-103`: **Improved event listener management for dragend events.**

The conditional check prevents adding unnecessary listeners, and the cleanup function ensures proper resource management. This follows the same pattern as the click listener and fixes potential memory leaks.

---

`90-90`: **Improved conditional guard for dragend event.**

Adding the `!onDragEnd` check prevents attaching listeners when there's no handler, which is a performance optimization.

---

`92-98`: **Performance optimization and bug fix.**

Clearing existing event listeners before adding new ones prevents memory leaks and ensures consistent behavior during component updates.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T10:00:45Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 1**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapMarkerClickEvent_spec.ts (1)</summary><blockquote>

`75-76`: **Use data-* attributes for selectors instead of aria-label.**

Per the coding guidelines, avoid using attribute selectors like `div[aria-label="Test Marker"]` and prefer data-* attributes.


Consider working with the development team to add a data-cy attribute to markers for more reliable test selection.

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between 9ef4c2d182d0fdc519ea699896730f649b49f5db and d2fe1218e28d9018f0a776d5eb025db45a09b8b6.

</details>

<details>
<summary>📒 Files selected for processing (2)</summary>

* `app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapMarkerClickEvent_spec.ts` (1 hunks)
* `app/client/src/widgets/MapWidget/component/Marker.tsx` (1 hunks)

</details>

<details>
<summary>🧰 Additional context used</summary>

<details>
<summary>📓 Path-based instructions (1)</summary>

<details>
<summary>`app/client/cypress/**/**.*`: Review the following e2e test ...</summary>

> `app/client/cypress/**/**.*`: Review the following e2e test code written using the Cypress test library. Ensure that:
> - Follow best practices for Cypress code and e2e automation. 
> - Avoid using cy.wait in code.
> - Avoid using cy.pause in code.
> - Avoid using agHelper.sleep().
> - Use locator variables for locators and do not use plain strings.
> - Use data-* attributes for selectors.
> - Avoid Xpaths, Attributes and CSS path.
> - Avoid selectors like .btn.submit or button[type=submit].
> - Perform logins via API with LoginFromAPI.
> - Perform logout via API with LogOutviaAPI.
> - Perform signup via API with SignupFromAPI.
> - Avoid using it.only.
> - Avoid using after and aftereach in test cases.
> - Use multiple assertions for expect statements.
> - Avoid using strings for assertions.
> - Do not use duplicate filenames even with different paths.
> - Avoid using agHelper.Sleep, this.Sleep in any file in code.
> 

- `app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapMarkerClickEvent_spec.ts`

</details>

</details>

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (5)</summary>

* GitHub Check: perform-test / client-build / client-build
* GitHub Check: client-lint / client-lint
* GitHub Check: client-check-cyclic-deps / check-cyclic-dependencies
* GitHub Check: client-build / client-build
* GitHub Check: client-unit-tests / client-unit-tests

</details>

<details>
<summary>🔇 Additional comments (6)</summary><blockquote>

<details>
<summary>app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapMarkerClickEvent_spec.ts (4)</summary>

`16-20`: **Test implementation aligns with PR objectives.**

The test structure directly addresses the issue described in the PR objectives where `onMarkerClick` was being triggered twice.

---

`71-72`: **LGTM! Text widget configuration is correct.**

Properly binds the text widget to display the click counter value.

---

`77-79`: **Effective validation approach.**

The assertion correctly verifies that the click event fires exactly once, which is the core issue being fixed.

---

`80-82`: **Double-verification is a good practice.**

Testing a second click confirms not only that duplicate events are prevented but also that subsequent clicks still function correctly.

</details>
<details>
<summary>app/client/src/widgets/MapWidget/component/Marker.tsx (2)</summary>

`78-86`: **Key fix for the duplicate click event issue.**

This implementation properly solves the issue described in the PR by:
1. Clearing any existing listeners before adding a new one
2. Adding proper cleanup to prevent memory leaks
3. Ensuring only one click handler is active at any time

This directly addresses the root cause where multiple event listeners were being attached to the marker.

---

`89-103`: **Consistent approach for dragend event listener management.**

The implementation for the dragend event follows the same pattern as the click event, showing consistency in approach:
1. Early return if dependencies are not available
2. Clears existing listeners before adding new ones
3. Properly cleans up listeners on unmount or dependency change

This ensures dragend events also won't have duplicate handlers.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T10:02:44Z
user: login: rahulbarwal
body: It seems we are adding listeners to markers and removing them from Google Maps. Just validate the correctness of it. 
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-03T10:24:05Z
user: login: coderabbitai[bot]
body: **Actionable comments posted: 0**

<details>
<summary>🧹 Nitpick comments (1)</summary><blockquote>

<details>
<summary>app/client/src/widgets/MapWidget/component/MapComponent.test.tsx (1)</summary><blockquote>

`50-121`: **Consider adding tests for dragend event and component unmount.**

The PR mentioned both click and dragend events were affected by the duplication issue. Adding tests for dragend events and verifying proper cleanup on component unmount would provide more comprehensive coverage.

```diff
+ it("should handle dragend events correctly", () => {
+   const onDragEndMock = jest.fn();
+   
+   render(
+     <Marker
+       onDragEnd={onDragEndMock}
+       position={{ lat: 37.7749, lng: -122.4194 }}
+       title="Test Marker"
+       draggable={true}
+     />,
+   );
+   
+   // Simulate dragend by calling the stored callback
+   // You'd need to add dragend callback storage in the mockAddListener
+   
+   expect(onDragEndMock).toHaveBeenCalledTimes(1);
+ });
+
+ it("should clean up listeners on unmount", () => {
+   const { unmount } = render(
+     <Marker
+       onClick={jest.fn()}
+       position={{ lat: 37.7749, lng: -122.4194 }}
+       title="Test Marker"
+     />,
+   );
+   
+   unmount();
+   
+   // Verify cleanup occurred
+   expect(mockRemoveListener).toHaveBeenCalled();
+ });
```

</blockquote></details>

</blockquote></details>

<details>
<summary>📜 Review details</summary>

**Configuration used: .coderabbit.yaml**
**Review profile: CHILL**
**Plan: Pro**

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between d2fe1218e28d9018f0a776d5eb025db45a09b8b6 and 3c025ebd9dd85a3c409bc1582e13e324d8dbb2cd.

</details>

<details>
<summary>📒 Files selected for processing (1)</summary>

* `app/client/src/widgets/MapWidget/component/MapComponent.test.tsx` (1 hunks)

</details>

<details>
<summary>⏰ Context from checks skipped due to timeout of 90000ms (8)</summary>

* GitHub Check: perform-test / client-build / client-build
* GitHub Check: perform-test / rts-build / build
* GitHub Check: perform-test / server-build / server-unit-tests
* GitHub Check: client-lint / client-lint
* GitHub Check: client-check-cyclic-deps / check-cyclic-dependencies
* GitHub Check: client-build / client-build
* GitHub Check: client-unit-tests / client-unit-tests
* GitHub Check: client-prettier / prettier-check

</details>

<details>
<summary>🔇 Additional comments (5)</summary><blockquote>

<details>
<summary>app/client/src/widgets/MapWidget/component/MapComponent.test.tsx (5)</summary>

`1-4`: **Appropriate import structure.**

The file correctly imports React and necessary testing utilities.

---

`5-48`: **Well-structured Google Maps API mocking.**

The mock implementation properly simulates the Google Maps API functionality needed for testing marker click events. The approach of storing the click callback for later simulation is effective.

---

`50-54`: **Good test setup with proper cleanup.**

The beforeEach hook ensures tests are isolated from each other by clearing all mocks between test runs.

---

`55-86`: **Well-designed test for click event behavior.**

This test case correctly validates that click events trigger exactly once per click, addressing the core issue from the PR description.

---

`88-120`: **Proper testing of listener cleanup.**

This test effectively verifies that previous click listeners are cleared when the onClick prop changes, which is essential for preventing duplicate event handling.

</details>

</blockquote></details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T10:12:22Z
user: login: rahulbarwal
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T14:19:03Z
user: login: jacquesikot
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-05T14:25:40Z

Review comments:
diff_hunk: @@ -79,19 +75,32 @@ const Marker: React.FC<MarkerProps> = (options) => {
   useEffect(() => {
     if (!marker) return;
 
-    marker.addListener("click", () => {
+    google.maps.event.clearListeners(marker, "click");
+    const clickListener = marker.addListener("click", () => {
       if (onClick) onClick();
     });
+
+    return () => {
+      google.maps.event.removeListener(clickListener);
+    };
path: app/client/src/widgets/MapWidget/component/Marker.tsx
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Excellent fix for the duplicate click event issue.**

The implementation now properly clears existing listeners before adding a new one and includes a cleanup function to remove the listener on unmount. This addresses the core issue where `onMarkerClick` was being triggered twice due to duplicate event listeners.

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-03T10:00:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,85 @@
+/// <reference types="Cypress" />
+import {
+  agHelper,
+  entityExplorer,
+  draggableWidgets,
+  jsEditor,
+  propPane,
+  locators,
+} from "../../../../../support/Objects/ObjectsCore";
+import EditorNavigation, {
+  EntityType,
+  PageLeftPane,
+  PagePaneSegment,
+} from "../../../../../support/Pages/EditorNavigation";
+
+describe(
+  "Map Widget - Marker Click Event",
+  { tags: ["@tag.Widget", "@tag.Map"] },
+  function () {
+    it("1. Verify map marker click event triggers only once", () => {
+      // Add a map widget
+      entityExplorer.DragDropWidgetNVerify(draggableWidgets.MAP);
+
+      // Add a JS Object to track marker clicks
+      PageLeftPane.switchSegment(PagePaneSegment.JS);
+      jsEditor.CreateJSObject(
+        `export default {
+        clickCount: 0,
+        incrementClickCount: () => {
+          this.clickCount += 1;
+          return this.clickCount;
+        },
+        resetClickCount: () => {
+          this.clickCount = 0;
+        }
+      }`,
path: app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapMarkerClickEvent_spec.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**JS Object using `this` within arrow functions may cause unexpected behavior.**

Arrow functions don't bind their own `this` context, which means references to `this.clickCount` won't work as expected. This will likely cause the click tracking functionality to fail.


```diff
export default {
  clickCount: 0,
  incrementClickCount: () => {
-    this.clickCount += 1;
-    return this.clickCount;
+    MarkerTracker.clickCount += 1;
+    return MarkerTracker.clickCount;
  },
  resetClickCount: () => {
-    this.clickCount = 0;
+    MarkerTracker.clickCount = 0;
  }
}
```


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

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-03T10:00:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,85 @@
+/// <reference types="Cypress" />
+import {
+  agHelper,
+  entityExplorer,
+  draggableWidgets,
+  jsEditor,
+  propPane,
+  locators,
+} from "../../../../../support/Objects/ObjectsCore";
+import EditorNavigation, {
+  EntityType,
+  PageLeftPane,
+  PagePaneSegment,
+} from "../../../../../support/Pages/EditorNavigation";
+
+describe(
+  "Map Widget - Marker Click Event",
+  { tags: ["@tag.Widget", "@tag.Map"] },
+  function () {
+    it("1. Verify map marker click event triggers only once", () => {
+      // Add a map widget
+      entityExplorer.DragDropWidgetNVerify(draggableWidgets.MAP);
+
+      // Add a JS Object to track marker clicks
+      PageLeftPane.switchSegment(PagePaneSegment.JS);
+      jsEditor.CreateJSObject(
+        `export default {
+        clickCount: 0,
+        incrementClickCount: () => {
+          this.clickCount += 1;
+          return this.clickCount;
+        },
+        resetClickCount: () => {
+          this.clickCount = 0;
+        }
+      }`,
+        {
+          paste: true,
+          completeReplace: true,
+          toRun: false,
+          shouldCreateNewJSObj: true,
+        },
+      );
+      jsEditor.RenameJSObjFromExplorer("JSObject1", "MarkerTracker");
+
path: app/client/cypress/e2e/Regression/ClientSide/Widgets/Map/MapMarkerClickEvent_spec.ts
user: login: coderabbitai[bot]
body: _⚠️ Potential issue_

**Fix the JS object implementation to properly track state.**

The current implementation uses `this.clickCount` which won't work as expected in this context. When using object methods with arrow functions, `this` will not refer to the object itself.


```diff
export default {
  clickCount: 0,
  incrementClickCount: () => {
-    this.clickCount += 1;
-    return this.clickCount;
+    // Use direct object reference instead of this
+    JSObject.clickCount += 1;
+    return JSObject.clickCount;
  },
  resetClickCount: () => {
-    this.clickCount = 0;
+    JSObject.clickCount = 0;
  }
}
```


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

<!-- This is an auto-generated comment by CodeRabbit -->
created_at: 2025-03-03T10:02:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -79,19 +75,32 @@ const Marker: React.FC<MarkerProps> = (options) => {
   useEffect(() => {
     if (!marker) return;
 
-    marker.addListener("click", () => {
+    google.maps.event.clearListeners(marker, "click");
+    const clickListener = marker.addListener("click", () => {
       if (onClick) onClick();
     });
+
+    return () => {
+      google.maps.event.removeListener(clickListener);
+    };
   }, [marker, onClick]);
 
   // add dragend event on marker
   useEffect(() => {
-    if (!marker) return;
+    if (!marker || !onDragEnd) return;
 
-    marker.addListener("dragend", (e: google.maps.MapMouseEvent) => {
-      if (onDragEnd) onDragEnd(e);
-    });
-  }, [marker, options.onDragEnd]);
+    google.maps.event.clearListeners(marker, "dragend");
+    const dragEndListener = marker.addListener(
+      "dragend",
+      (e: google.maps.MapMouseEvent) => {
+        if (onDragEnd) onDragEnd(e);
+      },
+    );
+
+    return () => {
+      google.maps.event.removeListener(dragEndListener);
path: app/client/src/widgets/MapWidget/component/Marker.tsx
user: login: rahulbarwal
body: In this effect we are adding drag and listener to a marker but when the component is dismounted we are removing the listener from the maps object. Is this correct? Shouldn't we be keeping track of all the markers and all the listeners and clearing those listeners as soon as the marker itself gets removed? 
created_at: 2025-03-03T10:22:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -79,19 +75,32 @@ const Marker: React.FC<MarkerProps> = (options) => {
   useEffect(() => {
     if (!marker) return;
 
-    marker.addListener("click", () => {
+    google.maps.event.clearListeners(marker, "click");
+    const clickListener = marker.addListener("click", () => {
       if (onClick) onClick();
     });
+
path: app/client/src/widgets/MapWidget/component/Marker.tsx
user: login: rahulbarwal
body: Similar question as above.
created_at: 2025-03-03T10:23:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -79,19 +75,32 @@ const Marker: React.FC<MarkerProps> = (options) => {
   useEffect(() => {
     if (!marker) return;
 
-    marker.addListener("click", () => {
+    google.maps.event.clearListeners(marker, "click");
+    const clickListener = marker.addListener("click", () => {
       if (onClick) onClick();
     });
+
+    return () => {
+      google.maps.event.removeListener(clickListener);
+    };
   }, [marker, onClick]);
 
   // add dragend event on marker
   useEffect(() => {
-    if (!marker) return;
+    if (!marker || !onDragEnd) return;
 
-    marker.addListener("dragend", (e: google.maps.MapMouseEvent) => {
-      if (onDragEnd) onDragEnd(e);
-    });
-  }, [marker, options.onDragEnd]);
+    google.maps.event.clearListeners(marker, "dragend");
+    const dragEndListener = marker.addListener(
+      "dragend",
+      (e: google.maps.MapMouseEvent) => {
+        if (onDragEnd) onDragEnd(e);
+      },
+    );
+
+    return () => {
+      google.maps.event.removeListener(dragEndListener);
path: app/client/src/widgets/MapWidget/component/Marker.tsx
user: login: jacquesikot
body: Not necessarily. The approach ensures that only a single listener for each event is mounted in a single time, and all listeners are named and cleaned up appropriately. 
created_at: 2025-03-05T14:25:40Z
author_association: CONTRIBUTOR

PR comments:
user: login: jacquesikot
created_at: 2025-03-03T09:56:54Z
body: /build-deploy-preview skip-tests=true
author_association: CONTRIBUTOR
user: login: coderabbitai[bot]
created_at: 2025-03-03T09:57:10Z
body: <!-- This is an auto-generated comment: summarize by coderabbit.ai -->
<!-- walkthrough_start -->

## Walkthrough
The changes update the Marker component by refactoring the event listener management. The new implementation uses a `useEffect` hook to clear any existing click and dragend listeners before adding new ones, ensuring cleanup on component unmount or dependency change to prevent memory leaks. Additionally, a new test file verifies the Marker’s behavior using a mocked Google Maps API.

## Changes

| File(s)                                                  | Change Summary                                                                                                                      |
|----------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------|
| `app/.../MapWidget/component/Marker.tsx`                 | Refactored event listener management in the Marker component: clears and re-adds click and dragend listeners within `useEffect`  |
| `app/.../MapWidget/component/MapComponent.test.tsx`      | Added tests for the Marker component using a mocked Google Maps API to verify proper listener cleanup and callback execution     |

## Sequence Diagram(s)
```mermaid
sequenceDiagram
  participant C as Marker Component
  participant U as useEffect Hook
  participant M as Marker Instance
  participant G as google.maps.event

  C->>U: Component mounts/updates
  U->>G: Clear "click" listeners on M
  U->>G: Add new "click" listener to M
  U->>G: Clear "dragend" listeners on M
  U->>G: Add new "dragend" listener to M
  Note over U: Listener setup complete
  C->>U: Component unmounts/dependencies change
  U->>G: Remove registered "click" and "dragend" listeners
```

## Suggested labels
`QA`, `High`, `Production`, `Community Reported`, `List Widget`

## Suggested reviewers
- rahulbarwal
- ApekshaBhosale
- sagar-qa007

## Poem
> In the realm of code so bright,  
> Listeners now dance with proper might.  
> Clicks and drags are gracefully managed,  
> Cleanup functions neatly bandaged.  
> Testers cheer as bugs take flight!  
> 🚀✨

<!-- walkthrough_end -->
<!-- internal state start -->


<!-- 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= -->

<!-- internal state end -->
<!-- finishing_touch_checkbox_start -->

<details>
<summary>✨ Finishing Touches</summary>

- [ ] <!-- {"checkboxId": "7962f53c-55bc-4827-bfbf-6a18da830691"} --> 📝 Generate Docstrings

</details>

<!-- finishing_touch_checkbox_end -->
<!-- tips_start -->

---

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

<details>
<summary>❤️ Share</summary>

- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)
- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)
- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)
- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)

</details>

<details>
<summary>🪧 Tips</summary>

### Chat

There are 3 ways to chat with [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=appsmithorg/appsmith&utm_content=39517):

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

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

### CodeRabbit Commands (Invoked using PR comments)

- `@coderabbitai pause` to pause the reviews on a PR.
- `@coderabbitai resume` to resume the paused reviews.
- `@coderabbitai review` to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
- `@coderabbitai full review` to do a full review from scratch and review all the files again.
- `@coderabbitai summary` to regenerate the summary of the PR.
- `@coderabbitai generate docstrings` to [generate docstrings](https://docs.coderabbit.ai/finishing-touches/docstrings) for this PR.
- `@coderabbitai resolve` resolve all the CodeRabbit review comments.
- `@coderabbitai configuration` to show the current CodeRabbit configuration for the repository.
- `@coderabbitai help` to get help.

### Other keywords and placeholders

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

### Documentation and Community

- Visit our [Documentation](https://docs.coderabbit.ai) for detailed information on how to use CodeRabbit.
- Join our [Discord Community](http://discord.gg/coderabbit) to get help, request features, and share feedback.
- Follow us on [X/Twitter](https://twitter.com/coderabbitai) for updates and announcements.

</details>

<!-- tips_end -->
author_association: CONTRIBUTOR
user: login: github-actions[bot]
created_at: 2025-03-03T09:57:15Z
body: Deploying Your Preview: <https://github.com/appsmithorg/appsmith/actions/runs/13628093818>.
Workflow: `On demand build Docker image and deploy preview`.
skip-tests: `true`.
env: ``.
PR: 39517.
recreate: .
author_association: NONE
user: login: github-actions[bot]
created_at: 2025-03-03T10:07:33Z
body: Deploy-Preview-URL: https://ce-39517.dp.appsmith.com
author_association: NONE