Skip to content

Conversation

@harshtandiya
Copy link
Collaborator

@harshtandiya harshtandiya commented Dec 21, 2025

Summary by CodeRabbit

  • New Features

    • Team-scoped forms: view, create, and persist current team; new endpoints to fetch team and user data.
    • Frontend user store initializes at app startup.
  • Improvements

    • Forms now include a required "Linked Team" field; dashboard and creation flows accept team context.
    • UI tweaks: updated icons and logout menu; explicit theme preference (Light/Dark/Automatic).
  • Tests / Chores

    • Test setup enabled to provision roles/users for CI.

✏️ Tip: You can customize this high-level summary in your review settings.

* add user retrieval and user teams functionality in user.py
* enhance team retrieval with ordering in teams.py
* create user store in frontend for managing user data and theme preferences
* update theme utility to support setting specific themes
* introduce linked_team_id as a required link field in form.json
* update Form class in form.py to include linked_team_id attribute
* modify metadata timestamp for recent changes
@coderabbitai
Copy link

coderabbitai bot commented Dec 21, 2025

Walkthrough

Adds team-scoped forms: backend APIs and Pydantic schemas for user, teams, and team-forms; Form DocType and generator threaded with linked_team_id; FPTeam helpers and team utilities for current-team persistence; frontend Pinia user store, Dashboard refactor to team-scoped flows, theme API changes, and test/install setup.

Changes

Cohort / File(s) Summary
Backend API endpoints
forms_pro/api/team.py, forms_pro/api/user.py
New whitelisted endpoints: get_team_forms(team_id), get_user(), get_user_teams() with Pydantic response schemas and role extraction/validation.
Form DocType & types
forms_pro/forms_pro/doctype/form/form.json, forms_pro/forms_pro/doctype/form/form.py
Added linked_team_id Link field to Form DocType; type hints updated to include linked_team_id.
FPTeam helpers
forms_pro/forms_pro/doctype/fp_team/fp_team.py
Added team_members property and is_team_member(user: str) -> bool.
Team utilities
forms_pro/utils/teams.py
Added GetTeamFormsResponseSchema with validators; get_team_forms(team_id); set_current_team(team_name, user); enhanced get_user_teams() to compute/persist is_current.
Form generator (backend)
forms_pro/utils/form_generator.py
FormGenerator now requires team_id; create_form_with_doctype / create_form accept team_id; created Form assigned linked_team_id.
Role overrides
forms_pro/overrides/roles.py
Import set_current_team; create_default_team_for_user() now calls set_current_team(...) and has -> None annotation.
Hooks & install/test setup
forms_pro/hooks.py, forms_pro/install.py, forms_pro/utils/test_form_generator.py, forms_pro/forms_pro/doctype/form/test_form.py
Enabled before_tests hook; new install.py with before_tests, role assignment and test user creation; tests updated to obtain/passthrough team_id to FormGenerator and create team-linked Forms.
Frontend store & init
frontend/src/stores/user.ts, frontend/src/App.vue
New Pinia useUser store exposing initialize(), user, userTeams, currentTeam, setCurrentTeam, toggleThemePreference; App.vue calls userStore.initialize() on setup.
Dashboard & layout UI
frontend/src/pages/Dashboard.vue, frontend/src/layouts/BaseLayout.vue
Dashboard refactored to team-scoped listing (teamForms) via get_team_forms(team_id); creation handlers now pass team_id; BaseLayout imports icons and instantiates useUser.
Frontend utils & theme
frontend/src/utils/form_generator.ts, frontend/src/utils/theme.ts
createNewFormWithDoctype / createNewForm accept team_id; theme util replaced toggleTheme() with setTheme(theme: ThemePreferenceType) and added ThemePreferenceType type.

Sequence Diagram

sequenceDiagram
    participant App as App.vue
    participant Store as useUser (Pinia)
    participant Dashboard as Dashboard.vue
    participant API as Backend API
    participant DB as Database / DocType

    App->>Store: initialize()
    activate Store
    Store->>API: get_user()
    Store->>API: get_user_teams()
    API-->>Store: user + teams (with is_current)
    Store->>Store: set currentTeam
    deactivate Store

    Dashboard->>Store: read currentTeam
    Dashboard->>API: get_team_forms(team_id)
    API->>DB: query Forms by linked_team_id
    DB-->>API: forms list
    API-->>Dashboard: list[GetTeamFormsResponseSchema]
    Dashboard->>Dashboard: render teamForms

    Dashboard->>API: create_form_with_doctype(team_id, doctype,...)
    API->>DB: insert Form with linked_team_id
    DB-->>API: created form
    API-->>Dashboard: created form
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Pay attention to Pydantic validators and model_validate/model_dump usage in forms_pro/utils/teams.py.
  • Verify team_id propagation across frontend utils (frontend/src/utils/*), Dashboard handlers, and backend FormGenerator.
  • Inspect Pinia useUser.initialize() ordering and Dashboard mount/watch for race conditions when currentTeam is set.
  • Confirm set_current_team / get_user_default / set_user_default persistence correctness.
  • Review tests and before_tests setup in forms_pro/install.py for proper role/user creation and cleanup.

Poem

🐰 I hopped through code to link each team,

Forms tucked neatly in a shared stream,
Stores choose current, APIs reply,
Linked fields bloom and dashboards fly,
🥕 A little hop — scoped flows take wing!

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'feat: teams v1' is vague and generic, using non-descriptive terminology that doesn't convey meaningful information about the changeset's specific purpose or scope. Consider a more descriptive title that indicates the main feature being introduced, such as 'feat: add team management and form access control' or 'feat: introduce team-scoped forms with user team associations'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/teams

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@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: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/src/pages/Dashboard.vue (1)

12-17: Add missing doctypes resource definition.

The Combobox on line 14 references doctypes.data, but no doctypes resource is defined in the script. This will cause a runtime error when opening the "Create from Existing DocType" dialog. Add a resource definition using the backend API endpoint forms_pro.api.form.get_doctype_list:

const doctypes = createResource({
    url: "forms_pro.api.form.get_doctype_list",
});

onMounted(() => {
    teamForms.fetch();
    doctypes.fetch();
});

Also define selectedDoctype as a ref to store the selected value.

🧹 Nitpick comments (7)
frontend/src/utils/theme.ts (1)

3-8: Consider removing debug console.log statements.

The function contains two console.log statements that appear to be for debugging. Consider removing these before merging to production.

🔎 Proposed cleanup
 export default function setTheme(theme: ThemePreferenceType) {
-  console.log("setTheme", theme);
   const root = document.documentElement;
   root.setAttribute("data-theme", theme.toLowerCase());
-  console.log("root", root.getAttribute("data-theme"));
 }
frontend/src/App.vue (1)

4-7: Consider moving store initialization to a lifecycle hook.

Calling userStore.initialize() directly in the script setup top-level executes immediately during component creation. This could cause issues with SSR, race conditions with router initialization, or make error handling more difficult. Consider wrapping it in onMounted for more predictable initialization timing.

🔎 Proposed refactor
 <script setup lang="ts">
 import "vue-sonner/style.css";
 import { Toaster } from "vue-sonner";
 import { useUser } from "@/stores/user";
+import { onMounted } from "vue";
 
 const userStore = useUser();
-userStore.initialize();
+
+onMounted(() => {
+  userStore.initialize();
+});
 </script>
frontend/src/layouts/BaseLayout.vue (1)

28-29: Unused store instance.

userStore is declared but never used in this component. If the store is intended for the commented-out "Switch Teams" feature, consider removing it until the feature is implemented.

 import { useUser } from "@/stores/user";
-const userStore = useUser();
frontend/src/stores/user.ts (2)

50-62: Redundant await with .then() chaining.

Using both await and .then() is redundant. Consider using one or the other for cleaner async flow:

🔎 Suggested refactor
   async function toggleThemePreference(theme: ThemePreferenceType) {
     setTheme(theme);
 
     const switchTheme = createResource({
       url: "frappe.core.doctype.user.user.switch_theme",
       params: {
         theme,
       },
     });
-    await switchTheme.fetch().then(() => {
-      userResource.value?.reload();
-    });
+    await switchTheme.fetch();
+    userResource.reload();
   }

14-24: Consider reordering declarations for clarity.

The computed properties user and userTeams reference userResource and userTeamsResource before they are defined. While this works due to JavaScript's temporal behavior within the function scope, reordering the declarations improves readability:

+  const userResource = createResource({
+    url: "forms_pro.api.user.get_user",
+  });
+
+  const userTeamsResource = createResource({
+    url: "forms_pro.api.user.get_user_teams",
+  });
+
   const user = computed(() => userResource.data);
   const userTeams = computed<UserTeam[]>(() => userTeamsResource.data);
   const currentTeam = ref<UserTeam | null>(null);
-
-  const userResource = createResource({
-    url: "forms_pro.api.user.get_user",
-  });
-
-  const userTeamsResource = createResource({
-    url: "forms_pro.api.user.get_user_teams",
-  });
forms_pro/utils/teams.py (1)

90-116: Return type annotation mismatch.

The function signature declares list[GetTeamFormsResponseSchema], but line 115 returns a list of dictionaries via model_dump(). Consider correcting the type hint:

-def get_team_forms(team_id: str) -> list[GetTeamFormsResponseSchema]:
+def get_team_forms(team_id: str) -> list[dict]:

Alternatively, return the Pydantic models directly if the caller can handle them.

frontend/src/pages/Dashboard.vue (1)

109-127: Form creation may fail silently when no team is selected.

Both handleCreateDraftFormWithDoctype and handleCreateDraftForm pass user.currentTeam?.name which could be undefined. Consider disabling the create buttons or showing an error when no team is available:

 const handleCreateDraftForm = async () => {
+    if (!user.currentTeam?.name) {
+        // Handle missing team - show toast or error
+        return;
+    }
     const data = await createNewForm(user.currentTeam?.name);
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b8567ed and f4fe1e8.

📒 Files selected for processing (14)
  • forms_pro/api/team.py (1 hunks)
  • forms_pro/api/user.py (1 hunks)
  • forms_pro/forms_pro/doctype/form/form.json (2 hunks)
  • forms_pro/forms_pro/doctype/form/form.py (1 hunks)
  • forms_pro/forms_pro/doctype/fp_team/fp_team.py (1 hunks)
  • forms_pro/overrides/roles.py (3 hunks)
  • forms_pro/utils/form_generator.py (4 hunks)
  • forms_pro/utils/teams.py (2 hunks)
  • frontend/src/App.vue (1 hunks)
  • frontend/src/layouts/BaseLayout.vue (1 hunks)
  • frontend/src/pages/Dashboard.vue (5 hunks)
  • frontend/src/stores/user.ts (1 hunks)
  • frontend/src/utils/form_generator.ts (2 hunks)
  • frontend/src/utils/theme.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (4)
forms_pro/api/team.py (1)
forms_pro/utils/teams.py (2)
  • GetTeamFormsResponseSchema (10-52)
  • get_team_forms (90-116)
forms_pro/utils/teams.py (1)
forms_pro/api/team.py (1)
  • get_team_forms (8-19)
forms_pro/api/user.py (1)
forms_pro/utils/teams.py (1)
  • get_user_teams (55-83)
forms_pro/overrides/roles.py (1)
forms_pro/utils/teams.py (2)
  • get_user_teams (55-83)
  • set_current_team (86-87)
🪛 Ruff (0.14.8)
forms_pro/utils/teams.py

32-32: Avoid specifying long messages outside the exception class

(TRY003)


44-44: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Server
🔇 Additional comments (10)
forms_pro/forms_pro/doctype/fp_team/fp_team.py (2)

23-31: LGTM!

The team_members property implementation is clean and correctly handles the case when self.users is empty or None.


33-43: LGTM!

The is_team_member method correctly leverages the team_members property for membership checking.

forms_pro/api/user.py (2)

8-11: LGTM!

The GetUserTeamsResponseSchema is well-structured with clear field descriptions.


14-30: LGTM!

The GetUserResponseSchema correctly extracts role names from HasRole objects using a field validator, with proper handling of empty lists.

forms_pro/forms_pro/doctype/form/form.py (1)

24-24: LGTM!

The type annotation for linked_team_id correctly aligns with the new field added in form.json.

forms_pro/forms_pro/doctype/form/form.json (1)

112-118: LGTM!

The linked_team_id field configuration is correct, properly linking to the FP Team doctype with the required constraint.

frontend/src/utils/form_generator.ts (2)

3-19: LGTM!

The function correctly accepts and passes team_id to the backend API.


21-33: LGTM!

The function correctly accepts and passes team_id to the backend API.

forms_pro/overrides/roles.py (1)

30-43: LGTM!

Good addition to set the current team immediately after creating the default team. This ensures new users have a proper team context from the start.

frontend/src/pages/Dashboard.vue (1)

39-70: Good conditional UI based on user permissions.

The pattern of showing a Dropdown for desk users and a simple Button for others is a clean approach to differentiate capabilities based on user.has_desk_access.

Comment on lines +37 to +43
def __init__(
self,
team_id: str,
linked_doctype: str | None = None,
) -> None:
self.doctype = None
self.team_id = team_id
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing validation for team_id parameter.

The team_id parameter is required but there's no validation. If a caller passes None (e.g., when user.currentTeam?.name is undefined in the frontend), the form will be created with linked_team_id = None, which may cause issues when querying team forms later.

Consider adding validation:

🔎 Suggested fix
     def __init__(
         self,
         team_id: str,
         linked_doctype: str | None = None,
     ) -> None:
+        if not team_id:
+            frappe.throw("Team ID is required to create a form")
         self.doctype = None
         self.team_id = team_id
🤖 Prompt for AI Agents
In forms_pro/utils/form_generator.py around lines 37 to 43, the __init__ method
accepts a required team_id but performs no validation; add a guard to ensure
team_id is provided and is a non-empty string (e.g., if team_id is None or
team_id == "" then raise ValueError with a clear message), optionally
coerce/check type and document the expectation in the method docstring so
callers (and tests) must pass a valid team_id rather than None.

Comment on lines +90 to +113
def get_team_forms(team_id: str) -> list[GetTeamFormsResponseSchema]:
"""
Get the list of forms for a team
Args:
team_id: ID of the team
Returns:
list[GetTeamFormsResponseSchema] - List of forms for the team
"""
forms = frappe.get_all(
"Form",
filters={"linked_team_id": team_id},
fields=[
"name",
"title",
"description",
"route",
"is_published",
"creation",
"modified",
"linked_team_id",
],
)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Missing authorization check for team membership.

get_team_forms fetches forms for any team_id without verifying that the requesting user is a member of that team. This could allow users to view forms belonging to teams they're not part of.

Consider adding a membership check:

🔎 Suggested fix
 def get_team_forms(team_id: str) -> list[GetTeamFormsResponseSchema]:
     """
     Get the list of forms for a team
     ...
     """
+    # Verify user is a member of the team
+    is_member = frappe.db.exists(
+        "FP Team Member",
+        {"parent": team_id, "user": frappe.session.user}
+    )
+    if not is_member:
+        frappe.throw("You are not a member of this team", frappe.PermissionError)
+
     forms = frappe.get_all(
         "Form",
         filters={"linked_team_id": team_id},

Copy link

@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: 3

♻️ Duplicate comments (1)
forms_pro/utils/teams.py (1)

90-116: Authorization check still missing (previously flagged).

This function was previously flagged for lacking an authorization check to verify that the requesting user is a member of the team before returning its forms. This security issue remains unaddressed and allows users to view forms belonging to teams they're not part of.

🔎 Recommended fix to add membership verification
 def get_team_forms(team_id: str) -> list[GetTeamFormsResponseSchema]:
     """
     Get the list of forms for a team
 
     Args:
         team_id: ID of the team
 
     Returns:
         list[GetTeamFormsResponseSchema] - List of forms for the team
     """
+    # Verify user is a member of the team
+    is_member = frappe.db.exists(
+        "FP Team Member",
+        {"parent": team_id, "user": frappe.session.user}
+    )
+    if not is_member:
+        frappe.throw("You are not a member of this team", frappe.PermissionError)
+
     forms = frappe.get_all(
         "Form",
         filters={"linked_team_id": team_id},
🧹 Nitpick comments (1)
forms_pro/install.py (1)

18-30: Consider creating a default team for the test user.

The test user is created but no team is assigned. This will cause an IndexError in forms_pro/utils/test_form_generator.py line 15, where the test setup assumes the test user has at least one team.

Should create_test_user also create a default team for the test user? This would prevent the IndexError in test setup.

🔎 Suggested enhancement to create default team
 def create_test_user():
     test_user = "test_forms_pro_user@example.com"
 
     if frappe.db.exists("User", test_user):
         return
 
     user: User = frappe.new_doc("User")
     user.email = test_user
     user.first_name = "Test"
     user.last_name = "Forms Pro User"
     user.insert(ignore_permissions=True)
     user.append("roles", {"role": FORMS_PRO_ROLE})
     user.save(ignore_permissions=True)
+
+    # Create a default team for the test user
+    if not frappe.db.exists("FP Team", {"team_name": "Test Team"}):
+        team = frappe.new_doc("FP Team")
+        team.team_name = "Test Team"
+        team.append("members", {"user": test_user})
+        team.insert(ignore_permissions=True)
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f4fe1e8 and 6d02cc3.

📒 Files selected for processing (4)
  • forms_pro/hooks.py (1 hunks)
  • forms_pro/install.py (1 hunks)
  • forms_pro/utils/teams.py (2 hunks)
  • forms_pro/utils/test_form_generator.py (9 hunks)
✅ Files skipped from review due to trivial changes (1)
  • forms_pro/hooks.py
🧰 Additional context used
🧬 Code graph analysis (2)
forms_pro/utils/test_form_generator.py (2)
forms_pro/utils/teams.py (1)
  • get_user_teams (55-83)
forms_pro/utils/form_generator.py (1)
  • FormGenerator (36-109)
forms_pro/utils/teams.py (1)
forms_pro/api/team.py (1)
  • get_team_forms (8-19)
🪛 Ruff (0.14.8)
forms_pro/utils/teams.py

32-32: Avoid specifying long messages outside the exception class

(TRY003)


44-44: Avoid specifying long messages outside the exception class

(TRY003)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Server
🔇 Additional comments (6)
forms_pro/utils/teams.py (4)

1-8: LGTM!

The imports are appropriate and necessary for the team management functionality.


10-52: LGTM!

The Pydantic schema is well-structured with appropriate validators for type conversions. The field validators correctly handle datetime parsing, boolean conversion, and None string handling.


55-83: LGTM!

The IndexError issue from the previous review has been properly addressed with the len(teams) > 0 check on line 79. The function now safely handles cases where users have no teams.


86-88: LGTM!

Clean wrapper function that appropriately abstracts the user default persistence.

forms_pro/utils/test_form_generator.py (1)

21-159: LGTM!

All test methods have been consistently updated to pass team_id to the FormGenerator constructor. The changes properly align with the new team-scoped FormGenerator API while maintaining test coverage across all scenarios.

forms_pro/install.py (1)

1-10: LGTM!

The imports are appropriate, and the before_tests function provides a clean orchestration of test setup tasks.

Comment on lines +12 to +15
def give_admin_forms_pro_role():
admin = frappe.get_doc("User", "Administrator")
admin.append("roles", {"role": FORMS_PRO_ROLE})
admin.save()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Check for existing role before appending.

Line 14 appends the Forms Pro role without checking if it already exists. If before_tests is called multiple times, this will create duplicate role entries for the Administrator user.

🔎 Recommended fix to prevent duplicate roles
 def give_admin_forms_pro_role():
     admin = frappe.get_doc("User", "Administrator")
-    admin.append("roles", {"role": FORMS_PRO_ROLE})
+    if not any(r.role == FORMS_PRO_ROLE for r in admin.roles):
+        admin.append("roles", {"role": FORMS_PRO_ROLE})
     admin.save()
🤖 Prompt for AI Agents
In forms_pro/install.py around lines 12 to 15, the code appends the
FORMS_PRO_ROLE to the Administrator user unconditionally which can create
duplicate role entries; change it to first check whether the role already exists
on the admin document (e.g., inspect admin.get("roles") or admin.roles) and only
append and save if the role is not present, ensuring admin.save() is called only
when a new role was actually added.

Comment on lines +18 to +30
def create_test_user():
test_user = "test_forms_pro_user@example.com"

if frappe.db.exists("User", test_user):
return

user: User = frappe.new_doc("User")
user.email = test_user
user.first_name = "Test"
user.last_name = "Forms Pro User"
user.insert(ignore_permissions=True)
user.append("roles", {"role": FORMS_PRO_ROLE})
user.save(ignore_permissions=True)
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Check for existing role before appending.

Line 29 appends the Forms Pro role without checking if it already exists. While this is less critical than in give_admin_forms_pro_role due to the existence check on line 21, it could still cause issues if the user document is partially created in a previous failed run.

🔎 Recommended fix to prevent duplicate roles
     user: User = frappe.new_doc("User")
     user.email = test_user
     user.first_name = "Test"
     user.last_name = "Forms Pro User"
     user.insert(ignore_permissions=True)
-    user.append("roles", {"role": FORMS_PRO_ROLE})
+    if not any(r.role == FORMS_PRO_ROLE for r in user.roles):
+        user.append("roles", {"role": FORMS_PRO_ROLE})
     user.save(ignore_permissions=True)
🤖 Prompt for AI Agents
In forms_pro/install.py around lines 18 to 30, the code appends the
FORMS_PRO_ROLE to the new user unconditionally, which can create a duplicate
role entry if the user doc was partially created in a prior run; before
appending, check whether the user already has a role entry for FORMS_PRO_ROLE
(e.g., inspect user.get("roles") or query existing UserRole) and only append/add
the role when it's not present, then save the document as before.

Comment on lines +12 to +19
def setUp(self):
super().setUp()
self.test_user = "test_forms_pro_user@example.com"
self.test_team = get_user_teams(self.test_user)[0]["name"]

def tearDown(self):
frappe.set_user("Administrator")
super().tearDown()
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Potential IndexError in test setup.

Line 15 assumes the test user has at least one team. If get_user_teams(self.test_user) returns an empty list, accessing [0] will raise an IndexError and cause all tests to fail during setup.

🔎 Recommended fix to handle empty teams
 def setUp(self):
     super().setUp()
     self.test_user = "test_forms_pro_user@example.com"
-    self.test_team = get_user_teams(self.test_user)[0]["name"]
+    teams = get_user_teams(self.test_user)
+    if not teams:
+        # Create a test team if none exists
+        test_team = frappe.new_doc("FP Team")
+        test_team.team_name = "Test Team"
+        test_team.append("members", {"user": self.test_user})
+        test_team.insert(ignore_permissions=True)
+        teams = get_user_teams(self.test_user)
+    self.test_team = teams[0]["name"]

Alternatively, ensure that install.py:create_test_user creates a default team for the test user.

🤖 Prompt for AI Agents
In forms_pro/utils/test_form_generator.py around lines 12 to 19, the setUp
assumes get_user_teams(self.test_user)[0] exists which can raise IndexError if
the user has no teams; modify setUp to handle an empty list by checking the
result of get_user_teams(self.test_user) before indexing and either (a) create
or assign a default team for the test user (via the existing
install.py:create_test_user helper or a small helper that creates a team and
adds the user), or (b) fail the setup with a clear, actionable error message
when no teams are found; ensure teardown still resets the user.

Copy link

@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: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
frontend/src/pages/Dashboard.vue (2)

3-33: Missing resource declarations break DocType dialog.

Lines 13-14 and 24, 27 reference selectedDoctype and doctypes.data in the template and function handleCreateDraftFormWithDoctype, but neither variable is declared in the script section. This will cause runtime errors when the dialog is opened or the Create button is clicked—attempting to access doctypes.data will fail with undefined property access, and the v-model binding on selectedDoctype will not function.


109-127: Add frontend validation to prevent creating forms without an assigned team.

Both handleCreateDraftFormWithDoctype and handleCreateDraftForm pass user.currentTeam?.name to backend APIs, which could be undefined if no team is assigned. The backend has no validation to prevent forms from being created with linked_team_id = None. Either validate that currentTeam exists before calling these functions, or implement backend validation to reject requests with missing team_id.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6d02cc3 and 7a6a337.

📒 Files selected for processing (2)
  • frontend/src/pages/Dashboard.vue (5 hunks)
  • frontend/src/stores/user.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • frontend/src/stores/user.ts
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Server
🔇 Additional comments (2)
frontend/src/pages/Dashboard.vue (2)

129-138: Team forms resource configuration looks good.

The resource is properly configured with the team-scoped API endpoint and dynamic team_id parameter. The loading: true initial state and cache key are appropriate.


140-148: Watcher implementation properly addresses the race condition.

The watcher with immediate: true and conditional check correctly handles the team initialization race condition mentioned in the past review comment. It will:

  • Run immediately on mount if currentTeam is already set
  • Catch when currentTeam is set after mount (from undefined to defined)
  • Only fetch when a valid team is available

This resolves the previous concern about teamForms.fetch() being called with team_id: undefined.

Comment on lines +39 to +70
<template v-if="user.has_desk_access">
<Dropdown
class="w-fit"
:button="{
label: 'Create',
iconLeft: 'plus',
variant: 'solid',
}"
:options="[
{
label: 'Create New',
onClick: handleCreateDraftForm,
},
},
]"
/>
{
label: 'Create from Existing DocType',
onClick: () => {
showSelectDoctypeDialog = true;
},
},
]"
/>
</template>
<template v-else>
<Button
class="w-fit"
variant="solid"
iconLeft="plus"
@click="handleCreateDraftForm"
>
Create New
</Button>
</template>
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for has_desk_access in frontend
echo "=== Frontend usage of has_desk_access ==="
rg -n "has_desk_access" frontend/src || echo "No frontend matches"

# Search for has_desk_access in backend
echo "=== Backend usage of has_desk_access ==="
rg -n "has_desk_access" forms_pro/api || echo "No backend API matches"

# Broader search in backend
echo "=== All backend matches for has_desk_access ==="
rg -n "has_desk_access" . --type py || echo "No Python matches"

Repository: BuildWithHussain/forms_pro

Length of output: 557


🏁 Script executed:

#!/bin/bash
# Get full context around has_desk_access in user.py
echo "=== forms_pro/api/user.py context ==="
cat -n forms_pro/api/user.py | head -50

# Search for the user_doc class definition and has_desk_access method
echo ""
echo "=== Search for has_desk_access method definition ==="
rg -n "def has_desk_access" . --type py -B 2 -A 5

# Look for imports and base class
echo ""
echo "=== Check where user_doc comes from ==="
rg -n "user_doc" forms_pro/api/user.py -B 3 -A 1

Repository: BuildWithHussain/forms_pro

Length of output: 2126


🏁 Script executed:

#!/bin/bash
# Search for "desk_access" or "Desk" in the codebase to understand its purpose
echo "=== Search for desk_access or Desk references ==="
rg -n -i "desk" . --type py | head -20

echo ""
echo "=== Check if there's any documentation or comments about desk access ==="
rg -n -i "desk.*access|access.*desk" . -B 2 -A 2 | head -30

echo ""
echo "=== Look for DocType creation related code ==="
rg -n "DocType\|doctype" frontend/src/pages/Dashboard.vue -B 2 -A 2

echo ""
echo "=== Check handleCreateDraftForm implementation ==="
rg -n "handleCreateDraftForm" frontend/src -B 2 -A 10 | head -40

Repository: BuildWithHussain/forms_pro

Length of output: 5064


🏁 Script executed:

#!/bin/bash
# Get the full Dashboard.vue to understand the context better
echo "=== Full Dashboard.vue script section ==="
cat frontend/src/pages/Dashboard.vue | tail -100

echo ""
echo "=== Search for what handleCreateDraftForm does ==="
rg -n "const handleCreateDraftForm\|function handleCreateDraftForm" frontend/src -A 15

echo ""
echo "=== Check Frappe documentation or codebase for User.has_desk_access() ==="
echo "This is a Frappe built-in method, let's verify its purpose"
rg -n "has_desk_access" . --type py -B 5 -A 5

Repository: BuildWithHussain/forms_pro

Length of output: 4377


🏁 Script executed:

#!/bin/bash
# Look at the role.json fixture to understand desk_access better
echo "=== Role fixture with desk_access ==="
cat forms_pro/fixtures/role.json

echo ""
echo "=== Check if there are any role-based permissions related to form creation ==="
rg -n "create.*form\|form.*create" forms_pro/ --type py -i | head -20

Repository: BuildWithHussain/forms_pro

Length of output: 469


🏁 Script executed:

#!/bin/bash
# Check if there's backend enforcement of this permission when creating forms
echo "=== Check createNewForm backend implementation ==="
rg -n "createNewForm\|create_new_form" forms_pro/ --type py -A 10

echo ""
echo "=== Check createNewFormWithDoctype backend implementation ==="
rg -n "createNewFormWithDoctype\|create_new_form_with_doctype" forms_pro/ --type py -A 10

echo ""
echo "=== Check for permission checks in API endpoints ==="
rg -n "@frappe.whitelist\|@frappe.permission_required\|has_permission" forms_pro/api/ --type py -B 1 -A 3

Repository: BuildWithHussain/forms_pro

Length of output: 237


🏁 Script executed:

#!/bin/bash
# Search for form_generator file
echo "=== Find form_generator file ==="
find . -name "*form_generator*" -type f

echo ""
echo "=== Search for where form creation API is implemented ==="
rg -n "def.*form" forms_pro/api/ --type py | head -20

echo ""
echo "=== Check all files in forms_pro/api ==="
ls -la forms_pro/api/

echo ""
echo "=== Search for form creation in entire codebase ==="
rg -n "createNewForm" . -B 2 -A 5 | head -40

Repository: BuildWithHussain/forms_pro

Length of output: 3714


🏁 Script executed:

#!/bin/bash
# Check the form_generator.py file for permission checks
echo "=== forms_pro/utils/form_generator.py ==="
cat forms_pro/utils/form_generator.py

echo ""
echo "=== Check if create_form or create_form_with_doctype check has_desk_access ==="
rg -n "has_desk_access" forms_pro/utils/form_generator.py

Repository: BuildWithHussain/forms_pro

Length of output: 3723


Fix permission check mismatch in conditional rendering.

The frontend uses has_desk_access to gate the "Create from Existing DocType" feature, but the backend (create_form and create_form_with_doctype) enforces the FORMS_PRO_ROLE permission. These are semantically different:

  • has_desk_access = user can access Frappe Desk (admin interface)
  • FORMS_PRO_ROLE = user can create forms

Use FORMS_PRO_ROLE instead of has_desk_access on the frontend to match the backend permission model and ensure consistency.

🤖 Prompt for AI Agents
In frontend/src/pages/Dashboard.vue around lines 39 to 70, the template uses
user.has_desk_access to gate the "Create from Existing DocType" flow but the
backend requires the FORMS_PRO_ROLE permission; update the conditional to check
the actual forms permission instead of has_desk_access (e.g., replace
user.has_desk_access with a check for FORMS_PRO_ROLE via
user.roles.includes(FORMS_PRO_ROLE) or user.has_role('FORMS_PRO_ROLE'), and
import or reference the FORMS_PRO_ROLE constant if available) so the frontend
permission model matches the backend.

Copy link

@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: 1

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7a6a337 and 0f5c26a.

📒 Files selected for processing (1)
  • forms_pro/forms_pro/doctype/form/test_form.py (2 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Server
🔇 Additional comments (2)
forms_pro/forms_pro/doctype/form/test_form.py (2)

28-35: Addition of linked_team_id aligns with team-scoped forms.

The inclusion of linked_team_id in the Form document creation correctly implements the team-scoped forms feature described in the PR objectives. The test now properly exercises team-linked form creation.

Note: This depends on the proper setup of self.test_team in lines 23-25. Once the IndexError concern is addressed, this test will reliably validate team-scoped form behavior.


7-8: The get_user_teams utility is properly defined and available for import from forms_pro.utils.teams.

Comment on lines +23 to +25
self.test_user = "test_forms_pro_user@example.com"
self.test_team = get_user_teams(self.test_user)[0]["name"]

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -name "install.py" -o -name "*fixture*" -o -name "*setup*" | head -20

Repository: BuildWithHussain/forms_pro

Length of output: 113


🏁 Script executed:

rg -n "test_forms_pro_user@example.com" -A 5 -B 5

Repository: BuildWithHussain/forms_pro

Length of output: 2308


🏁 Script executed:

rg -n "def get_user_teams" -A 15

Repository: BuildWithHussain/forms_pro

Length of output: 1637


🏁 Script executed:

rg -n "def setUp|def setUpClass" -B 2 -A 10 forms_pro/forms_pro/doctype/form/test_form.py

Repository: BuildWithHussain/forms_pro

Length of output: 564


🏁 Script executed:

cat -n forms_pro/forms_pro/doctype/form/test_form.py | head -40

Repository: BuildWithHussain/forms_pro

Length of output: 1679


🏁 Script executed:

cat -n forms_pro/install.py

Repository: BuildWithHussain/forms_pro

Length of output: 1019


🏁 Script executed:

rg -n "FP Team" -B 3 -A 10 forms_pro/install.py

Repository: BuildWithHussain/forms_pro

Length of output: 52


🏁 Script executed:

cat -n forms_pro/utils/test_form_generator.py | head -30

Repository: BuildWithHussain/forms_pro

Length of output: 1331


🏁 Script executed:

rg -n "FP Team" forms_pro/ -A 5 | head -50

Repository: BuildWithHussain/forms_pro

Length of output: 3097


🏁 Script executed:

ls -la forms_pro/fixtures/

Repository: BuildWithHussain/forms_pro

Length of output: 239


🏁 Script executed:

cat forms_pro/fixtures/*.json 2>/dev/null | head -100

Repository: BuildWithHussain/forms_pro

Length of output: 352


🏁 Script executed:

cat -n forms_pro/overrides/roles.py

Repository: BuildWithHussain/forms_pro

Length of output: 1820


🏁 Script executed:

rg -n "def.*after_insert|def.*on_update" forms_pro/overrides/roles.py -B 2 -A 15

Repository: BuildWithHussain/forms_pro

Length of output: 52


Guard against IndexError when retrieving test team.

The test user is created with the "Forms Pro User" role during test setup, which should automatically create a default team through the handle_forms_pro_role_change hook in forms_pro/overrides/roles.py. However, accessing [0] on get_user_teams() without bounds checking creates an unnecessary risk—if team creation fails or is skipped, an IndexError will crash the test. Add defensive checks to ensure the user has at least one team before accessing it.

Suggested fix:

 self.test_user = "test_forms_pro_user@example.com"
-self.test_team = get_user_teams(self.test_user)[0]["name"]
+user_teams = get_user_teams(self.test_user)
+self.assertGreater(len(user_teams), 0, f"User {self.test_user} has no teams")
+self.test_team = user_teams[0]["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
self.test_user = "test_forms_pro_user@example.com"
self.test_team = get_user_teams(self.test_user)[0]["name"]
self.test_user = "test_forms_pro_user@example.com"
user_teams = get_user_teams(self.test_user)
self.assertGreater(len(user_teams), 0, f"User {self.test_user} has no teams")
self.test_team = user_teams[0]["name"]
🤖 Prompt for AI Agents
In forms_pro/forms_pro/doctype/form/test_form.py around lines 23 to 25, guard
against IndexError when retrieving the test user's team: call
get_user_teams(self.test_user) into a variable, check that the returned list is
non-empty before indexing, and if it is empty either (a) create or assign a
default team for the test user (e.g., invoke the team-creation path used by the
Forms Pro role hook) or (b) raise an explicit AssertionError with a clear
message so the test fails fast and clearly; then set self.test_team to the first
team's name only after this defensive check.

@harshtandiya harshtandiya merged commit 8f5269b into develop Dec 21, 2025
4 checks passed
@harshtandiya harshtandiya deleted the feat/teams branch December 21, 2025 13:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants