Skip to content

feat: submission list pages#59

Merged
harshtandiya merged 12 commits into
developfrom
feat/submission
Apr 7, 2026
Merged

feat: submission list pages#59
harshtandiya merged 12 commits into
developfrom
feat/submission

Conversation

@harshtandiya

@harshtandiya harshtandiya commented Apr 7, 2026

Copy link
Copy Markdown
Collaborator

Overview

Adds a Submissions section to the form management area, letting form owners browse all submissions, see metadata at a glance, and inspect individual responses field-by-field in a slide-over panel.

Changes

Backend (forms_pro/api/submission.py)

  • get_all_submissions(form_id) — new whitelisted endpoint that returns all Submitted entries for a form. Gated behind team write permission so only team members can access it.
  • get_submission_response(submission_id, doctype) — new whitelisted endpoint to fetch a single submission's full document, with the same team-based permission check.
  • Added owner field to UserSubmissionResponse so the submitter's identity is available in list and detail views.

Frontend

New reusable component — Drawer.vue

  • Slide-over panel that teleports to <body> with a backdrop.
  • Props: size (sm / md / lg / xl), position (right / left / top / bottom), title, actions.
  • Closes on Escape key or backdrop click; locks body scroll while open.
  • Smooth enter/leave transitions per position direction.

Submissions UI (components/form/submissions/)

  • SubmissionList.vueListView displaying all submissions with status badge, formatted timestamps, and owner avatar. Clicking a row opens the Drawer with full details.
  • SubmissionDetails.vue — shows submission metadata (ID, submitted on, last modified, submitted by) followed by every form field rendered through SubmissionFieldValue.
  • SubmissionFieldValue.vue — renders a field's value according to its type: Checkbox / Switch (read-only toggle), Rating, TextEditor (non-editable), Attach (clickable link), Textarea (pre-wrapped), date/time variants (formatted), and a plain text fallback.

Navigation & routing

  • Extracted sidebar config into useManageFormSidebarItems() composable (sidebarItems.ts) with reactive isActive driven by the current route name. Added a Submissions nav item alongside Overview.
  • ManageForm.vue simplified — sidebar config and breadcrumb logic moved out to the composable and individual page components respectively.
  • Overview.vue now owns its own breadcrumb and uses v-else-if for correct conditional rendering.
  • New Submissions.vue page component and a /manage/:id/submissions route registered in router.ts.

Summary by CodeRabbit

  • New Features
    • Added a submissions management interface to view all form submissions in a dedicated list view
    • Enabled viewing detailed submission information including metadata, timestamps, and field values
    • Introduced a drawer component for expanding and displaying submission details
    • Added "Submissions" navigation option in the form management sidebar with breadcrumb navigation support

- Introduced a new "Manage Form Submissions" page for viewing form submissions.
- Updated the router to include a route for submissions.
- Refactored sidebar items to include navigation to the new submissions page.
- Enhanced the overview page with breadcrumb navigation for better user context.
- Introduced a new Drawer component to provide a sliding panel interface.
- Implemented customizable properties for size, position, title, and actions.
- Added keyboard accessibility with Escape key support for closing the drawer.
- Included transition effects for smooth opening and closing animations.
- Added "xl" size option to the Drawer component for enhanced customization.
- Updated size classes for both horizontal and vertical orientations to include new "xl" dimensions.
- Introduced SubmissionDetails component to display detailed information about a specific submission.
- Created SubmissionFieldValue component to render individual field values with appropriate formatting based on field type.
- Enhanced SubmissionList component to include a drawer for viewing submission details upon row click.
- Updated API integration to fetch submission data securely based on user permissions.
- Added computed property to determine class names based on field type, improving layout for Switch and Checkbox types.
- Updated template structure to utilize dynamic classes for better styling and responsiveness.
@coderabbitai

coderabbitai Bot commented Apr 7, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@harshtandiya has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 6 minutes and 23 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 6 minutes and 23 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 01c591b4-9b76-4511-81d2-ff26ab252b5b

📥 Commits

Reviewing files that changed from the base of the PR and between 3e53eb3 and 3a06e3c.

📒 Files selected for processing (4)
  • forms_pro/api/submission.py
  • frontend/src/components/form/submissions/SubmissionFieldValue.vue
  • frontend/src/components/form/submissions/SubmissionList.vue
  • frontend/src/components/ui/Drawer.vue
📝 Walkthrough

Walkthrough

This PR adds a complete submission viewing system to the form management interface. It extends the backend with two new API endpoints (get_submission_response and get_all_submissions) that fetch submission data with permission validation, and implements a frontend submission management UI comprising a submissions list page, detail viewer, and reusable drawer and field-value display components. Additionally, sidebar navigation logic is extracted into a composable and the manage form layout is simplified.

Changes

Cohort / File(s) Summary
Backend Submission API
forms_pro/api/submission.py
Extended UserSubmissionResponse with owner field; added whitelisted endpoints get_submission_response() and get_all_submissions() with permission checks via frappe.has_permission against linked FP Team.
Reusable UI Components
frontend/src/components/ui/Drawer.vue, frontend/src/components/form/submissions/SubmissionFieldValue.vue
Added configurable slide-in Drawer component with overlay, escape-key close, and action slots; added read-only field renderer supporting date formatting, checkboxes, text editors, ratings, attachments, and fallback text display.
Submission Management UI
frontend/src/components/form/submissions/SubmissionDetails.vue, frontend/src/components/form/submissions/SubmissionList.vue, frontend/src/pages/manage/submissions/Submissions.vue
Added submission list view with paginated table (ID, Status, Timestamps, Owner), detail drawer with core metadata and dynamic field rendering, and submissions page wrapper with breadcrumbs.
Navigation & Layout Refactoring
frontend/src/pages/manage/sidebarItems.ts, frontend/src/pages/manage/ManageForm.vue, frontend/src/pages/manage/overview/Overview.vue, frontend/src/router.ts
Extracted sidebar section construction into useManageFormSidebarItems() composable; simplified ManageForm layout by removing hardcoded breadcrumbs/sidebar logic; added breadcrumbs to Overview page; added /manage/:id/submissions child route.

Sequence Diagram

sequenceDiagram
    participant User
    participant SubmissionList
    participant SubmissionDetails
    participant API as forms_pro API
    participant Frappe as Frappe/Doctype

    User->>SubmissionList: Click submission row
    SubmissionList->>SubmissionList: Update selectedSubmission & toggle drawer
    SubmissionList->>SubmissionDetails: Render with submissionId + doctype
    SubmissionDetails->>API: get_submission_response(id, doctype)
    API->>Frappe: Lookup fp_linked_form on doctype
    Frappe-->>API: Parent form reference
    API->>Frappe: Check frappe.has_permission against FP Team
    alt Permission granted
        Frappe-->>API: ✓ Allowed
        API->>Frappe: Fetch submission document
        Frappe-->>API: Submission dict
        API-->>SubmissionDetails: Submission data (with owner)
        SubmissionDetails->>SubmissionDetails: Render metadata grid + field values
        SubmissionDetails-->>User: Display submission details
    else Permission denied
        Frappe-->>API: ✗ Denied
        API-->>SubmissionDetails: PermissionError
        SubmissionDetails-->>User: Toast error
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~28 minutes

Possibly related PRs

  • PR #10: Directly related as main extension of the same submission API surface—adds owner field to UserSubmissionResponse and new endpoints building on earlier user-submission models from PR #10.
  • PR #5: Backend submission APIs rely on Form.linked_team_id and FP Team permission utilities introduced in the teams v1 PR.

Poem

🐰 A drawer slides in with a gentle glide,
Submissions dance on lists applied,
Fields unfold their values true,
Breadcrumbs light the path for you!
Permissions checked, the data sings—
What joy new features bring! 🎪

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: submission list pages' directly and accurately summarizes the primary changes—adding new submission list UI pages and related functionality throughout the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 80.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/submission

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.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (6)
frontend/src/components/form/submissions/SubmissionDetails.vue (1)

33-41: Consider watching both submissionId and doctype props.

Currently only submissionId triggers a re-fetch. If doctype could change (e.g., when navigating between different forms quickly), the resource parameters would be stale. Though this may be unlikely in practice given the current navigation flow, watching both would be more defensive.

Suggested improvement
 watch(
-    () => props.submissionId,
-    (newId) => {
-        if (newId) {
+    () => [props.submissionId, props.doctype],
+    ([newId, newDoctype]) => {
+        if (newId && newDoctype) {
             submissionResource.fetch();
         }
     },
     { immediate: true }
 );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/form/submissions/SubmissionDetails.vue` around lines
33 - 41, The watch only observes props.submissionId so
submissionResource.fetch() can run with stale params if props.doctype changes;
update the watcher to observe both props.submissionId and props.doctype (e.g.,
watch a tuple/computed of [props.submissionId, props.doctype] or pass an array
of sources) and call submissionResource.fetch() when either changes, preserving
the { immediate: true } option so the resource is fetched initially; target the
existing watch call and submissionResource.fetch invocation.
frontend/src/pages/manage/overview/Overview.vue (1)

14-17: Breadcrumb "Manage Form" link points to itself.

The first breadcrumb item links to /manage/${currentFormId}/overview, which is the current page. Typically, the first breadcrumb in a hierarchy should link to a parent or higher-level page (e.g., the dashboard or forms list).

Consider linking to a higher-level page
 const breadcrumbItems = computed(() => [
-    { label: "Manage Form", to: `/manage/${manageFormStore.currentFormId}/overview` },
+    { label: "Forms", to: "/" },
+    { label: manageFormStore.formData?.title || "Form" },
     { label: "Overview" },
 ]);

Alternatively, if the current behavior is intentional, the breadcrumb could simply omit the link for the current page:

 const breadcrumbItems = computed(() => [
-    { label: "Manage Form", to: `/manage/${manageFormStore.currentFormId}/overview` },
-    { label: "Overview" },
+    { label: "Forms", to: "/" },
+    { label: manageFormStore.formData?.title || "Form", to: `/manage/${manageFormStore.currentFormId}/overview` },
 ]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/pages/manage/overview/Overview.vue` around lines 14 - 17,
breadcrumbItems currently sets the first item to link to the current overview
page (breadcrumbItems / manageFormStore.currentFormId), which is wrong; update
the first item's to to point to the parent/higher-level page (e.g., the forms
list or dashboard path such as "/manage" or "/manage/forms") so it navigates up
the hierarchy, or if the intent was to show the current page then remove the to
property from the first item so it renders as plain text; change the
breadcrumbItems definition in Overview.vue accordingly (refer to breadcrumbItems
and manageFormStore.currentFormId).
frontend/src/components/form/submissions/SubmissionList.vue (2)

20-20: isLoading state is unused.

The isLoading ref is declared and managed but never consumed in the template. Consider either removing it if unnecessary, or passing it to ListView to display a loading indicator.

♻️ Option 1: Use loading prop in ListView (if supported)
     <ListView
         :columns="columns"
         :rows="allSubmissions"
+        :loading="isLoading"
         :options="{
♻️ Option 2: Remove unused state
 const manageFormStore = useManageForm();
-const isLoading = ref(false);

 const allSubmissionsResource = createResource({
     url: "forms_pro.api.submission.get_all_submissions",
     makeParams() {
         return {
             form_id: manageFormStore.currentFormId,
         };
     },
-    onSuccess() {
-        isLoading.value = false;
-    },
     onError(error: Error) {
         toast.error("Failed to fetch submissions", {
             description: error.message,
         });
     },
 });
 const allSubmissions = computed(() => allSubmissionsResource.data ?? []);

 onMounted(() => {
-    isLoading.value = true;
     allSubmissionsResource.fetch();
 });

Also applies to: 74-86

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/form/submissions/SubmissionList.vue` at line 20, The
isLoading ref declared in SubmissionList.vue (isLoading) is never used in the
template or passed to child components; either remove the unused ref or wire it
into the ListView so the UI can show loading state—specifically, remove the
declaration and any set/reset logic around isLoading if you opt to delete it, or
pass isLoading as a prop (e.g., :loading="isLoading" or the prop name ListView
expects) and ensure updates to isLoading in the fetch/submission methods (see
the state and the fetch logic around the block referenced at lines 74-86)
correctly wrap async calls.

112-118: Drawer may appear empty if row clicked before form data loads.

The condition manageFormStore.formData?.linked_doctype guards against rendering SubmissionDetails before the async form data is available. However, if a user clicks a row before formData loads, they'll see an empty drawer. Consider adding a loading indicator inside the drawer for better UX.

♻️ Suggested improvement
     <Drawer v-model="drawerOpen" size="lg" title="Submission Details">
         <SubmissionDetails
             v-if="selectedSubmission && manageFormStore.formData?.linked_doctype"
             :submissionId="selectedSubmission.name"
             :doctype="manageFormStore.formData.linked_doctype"
         />
+        <div v-else-if="selectedSubmission" class="text-center text-ink-gray-6">
+            Loading...
+        </div>
     </Drawer>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/form/submissions/SubmissionList.vue` around lines 112
- 118, The Drawer currently only renders SubmissionDetails when
selectedSubmission and manageFormStore.formData?.linked_doctype exist, causing
an empty drawer if a row is clicked before formData loads; update the Drawer to
show a loading state when drawerOpen is true but manageFormStore.formData or
linked_doctype is not yet available (e.g., render a spinner or "Loading..."
placeholder inside the Drawer when drawerOpen &&
!manageFormStore.formData?.linked_doctype), keeping the existing conditional
rendering for SubmissionDetails (referencing Drawer, drawerOpen,
SubmissionDetails, selectedSubmission, and
manageFormStore.formData?.linked_doctype).
frontend/src/components/ui/Drawer.vue (2)

94-99: Consider adding aria-labelledby when title is present.

The dialog has role="dialog" and aria-modal="true" but lacks aria-labelledby to associate it with the title. This helps screen readers announce the dialog's purpose.

♿ Proposed accessibility improvement

Add an id to the title element and reference it:

+const titleId = computed(() => (props.title || slots.title ? 'drawer-title' : undefined));
+
 const panelClasses = computed(() => {
             <div
                 v-if="open"
                 role="dialog"
                 aria-modal="true"
+                :aria-labelledby="titleId"
                 :class="panelClasses"
                 class="fixed z-50 flex flex-col bg-surface-modal shadow-2xl"
             >
                 <!-- Title -->
                 <div
                     class="flex items-center justify-between shrink-0 px-5 py-3.5"
                     :class="{
                         'border-b border-surface-gray-2': title || $slots.title,
                     }"
                 >
                     <slot name="title">
                         <h3
                             v-if="title"
+                            id="drawer-title"
                             class="text-lg font-semibold text-ink-gray-9 truncate pr-2"
                         >
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/ui/Drawer.vue` around lines 94 - 99, The dialog
element rendered when open (the div using v-if="open", role="dialog",
aria-modal="true" and :class="panelClasses") should include an aria-labelledby
attribute when a title exists: add a stable id to the title element (the heading
inside this Drawer component) and set aria-labelledby on the dialog div to that
id so assistive tech can announce the dialog title; update the template to
conditionally add aria-labelledby referencing that title id and ensure the title
element (e.g., the component's <h2> or similar) has the matching id.

83-91: Consider adding focus trap for modal accessibility.

Modal dialogs should trap keyboard focus to prevent users from tabbing to background content. The overlay click-to-close and Escape key handling are good, but focus can still escape via Tab navigation.

You could use @vueuse/integrations/useFocusTrap or implement manual focus management. This can be deferred to a follow-up PR if accessibility isn't immediately critical.

Also applies to: 93-123

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/ui/Drawer.vue` around lines 83 - 91, The Drawer.vue
overlay allows closing but does not trap keyboard focus, so add focus-trap logic
when the drawer opens and remove it when it closes: import and use
`@vueuse/integrations/useFocusTrap` (or implement manual focus management) inside
the Drawer component and apply it to the drawer root element referenced by a ref
(e.g., the element used with v-if="open"); initialize the trap when open becomes
true (or in the open watcher/mounted logic) and deactivate it on close/unmount
(and when Escape/overlay click calls the close method) so Tab/Shift+Tab cannot
move focus outside the Drawer.vue modal while open.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@forms_pro/api/submission.py`:
- Around line 115-143: In get_submission_response, validate that the incoming
doctype matches the form's configured linked_doctype before loading the
submission or checking team permissions: fetch the Form record via linked_form
and compare its linked_doctype to the doctype parameter, and if they differ
raise an error (e.g., frappe.PermissionError or DoesNotExistError) to block
mismatched access; perform this check after computing linked_form and before
using linked_team_id or calling frappe.get_doc so the function
(get_submission_response, linked_form, linked_team_id, doctype) cannot be abused
by passing an arbitrary doctype.
- Around line 179-185: The code calls frappe.db.get_value("Form", form_id,
"linked_team_id") into linked_team without checking that the Form exists, so
subsequent frappe.has_permission(doc=linked_team) may be run with
linked_team==None; add an explicit existence/validation check after the
get_value (e.g., if linked_team is None or the Form lookup returned no row) and
raise a clear error via frappe.throw (or a NotFound/ValidationError) before
calling frappe.has_permission; reference the linked_team variable and the
frappe.db.get_value / frappe.has_permission usage so you update that exact
block.

In `@frontend/src/components/form/submissions/SubmissionFieldValue.vue`:
- Around line 96-101: The Textarea branch in the SubmissionFieldValue.vue
component renders an empty span when value is null/undefined; update the
v-else-if branch for fieldtype === FormFieldTypes.Textarea to use the same
null-coalescing placeholder as the default case (use value ?? "–") so the
textarea field displays "–" when value is null/undefined while preserving the
existing classes (text-sm text-ink-gray-7 whitespace-pre-wrap) and binding.

In `@frontend/src/components/form/submissions/SubmissionList.vue`:
- Around line 29-36: The onError handler currently logs the failure via
toast.error but never resets the loading flag; update the onError callback in
SubmissionList.vue (the onError function that receives error: Error) to set
isLoading.value = false (same as onSuccess) so the loading state is cleared on
failures (or alternatively move isLoading.value = false into a shared
finally/cleanup path used by both onSuccess and onError).

---

Nitpick comments:
In `@frontend/src/components/form/submissions/SubmissionDetails.vue`:
- Around line 33-41: The watch only observes props.submissionId so
submissionResource.fetch() can run with stale params if props.doctype changes;
update the watcher to observe both props.submissionId and props.doctype (e.g.,
watch a tuple/computed of [props.submissionId, props.doctype] or pass an array
of sources) and call submissionResource.fetch() when either changes, preserving
the { immediate: true } option so the resource is fetched initially; target the
existing watch call and submissionResource.fetch invocation.

In `@frontend/src/components/form/submissions/SubmissionList.vue`:
- Line 20: The isLoading ref declared in SubmissionList.vue (isLoading) is never
used in the template or passed to child components; either remove the unused ref
or wire it into the ListView so the UI can show loading state—specifically,
remove the declaration and any set/reset logic around isLoading if you opt to
delete it, or pass isLoading as a prop (e.g., :loading="isLoading" or the prop
name ListView expects) and ensure updates to isLoading in the fetch/submission
methods (see the state and the fetch logic around the block referenced at lines
74-86) correctly wrap async calls.
- Around line 112-118: The Drawer currently only renders SubmissionDetails when
selectedSubmission and manageFormStore.formData?.linked_doctype exist, causing
an empty drawer if a row is clicked before formData loads; update the Drawer to
show a loading state when drawerOpen is true but manageFormStore.formData or
linked_doctype is not yet available (e.g., render a spinner or "Loading..."
placeholder inside the Drawer when drawerOpen &&
!manageFormStore.formData?.linked_doctype), keeping the existing conditional
rendering for SubmissionDetails (referencing Drawer, drawerOpen,
SubmissionDetails, selectedSubmission, and
manageFormStore.formData?.linked_doctype).

In `@frontend/src/components/ui/Drawer.vue`:
- Around line 94-99: The dialog element rendered when open (the div using
v-if="open", role="dialog", aria-modal="true" and :class="panelClasses") should
include an aria-labelledby attribute when a title exists: add a stable id to the
title element (the heading inside this Drawer component) and set aria-labelledby
on the dialog div to that id so assistive tech can announce the dialog title;
update the template to conditionally add aria-labelledby referencing that title
id and ensure the title element (e.g., the component's <h2> or similar) has the
matching id.
- Around line 83-91: The Drawer.vue overlay allows closing but does not trap
keyboard focus, so add focus-trap logic when the drawer opens and remove it when
it closes: import and use `@vueuse/integrations/useFocusTrap` (or implement manual
focus management) inside the Drawer component and apply it to the drawer root
element referenced by a ref (e.g., the element used with v-if="open");
initialize the trap when open becomes true (or in the open watcher/mounted
logic) and deactivate it on close/unmount (and when Escape/overlay click calls
the close method) so Tab/Shift+Tab cannot move focus outside the Drawer.vue
modal while open.

In `@frontend/src/pages/manage/overview/Overview.vue`:
- Around line 14-17: breadcrumbItems currently sets the first item to link to
the current overview page (breadcrumbItems / manageFormStore.currentFormId),
which is wrong; update the first item's to to point to the parent/higher-level
page (e.g., the forms list or dashboard path such as "/manage" or
"/manage/forms") so it navigates up the hierarchy, or if the intent was to show
the current page then remove the to property from the first item so it renders
as plain text; change the breadcrumbItems definition in Overview.vue accordingly
(refer to breadcrumbItems and manageFormStore.currentFormId).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 0e738b48-7f49-4a67-9966-1b8ef079aaeb

📥 Commits

Reviewing files that changed from the base of the PR and between 75b26c6 and 3e53eb3.

📒 Files selected for processing (10)
  • forms_pro/api/submission.py
  • frontend/src/components/form/submissions/SubmissionDetails.vue
  • frontend/src/components/form/submissions/SubmissionFieldValue.vue
  • frontend/src/components/form/submissions/SubmissionList.vue
  • frontend/src/components/ui/Drawer.vue
  • frontend/src/pages/manage/ManageForm.vue
  • frontend/src/pages/manage/overview/Overview.vue
  • frontend/src/pages/manage/sidebarItems.ts
  • frontend/src/pages/manage/submissions/Submissions.vue
  • frontend/src/router.ts

Comment thread forms_pro/api/submission.py
Comment thread forms_pro/api/submission.py
Comment thread frontend/src/components/form/submissions/SubmissionFieldValue.vue
Comment thread frontend/src/components/form/submissions/SubmissionList.vue Outdated
harshtandiya and others added 6 commits April 7, 2026 17:23
A client could pass an arbitrary doctype to access submissions from a
different form. Now we fetch both linked_team_id and linked_doctype from
the Form doc and reject requests where the supplied doctype doesn't match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
If form_id is invalid, frappe.db.get_value returns None and calling
has_permission(doc=None) has undefined behaviour. Explicitly throw
DoesNotExistError before the permission check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…Value

Textarea was rendering blank when value is null/undefined, inconsistent
with the default case which uses value ?? "–".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
isLoading was declared and set but never bound in the template, so the
loading state had no effect and was also never cleared on fetch error.
Removed it entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
If a row is clicked before formData loads, the drawer was empty. Now
shows a Loading... placeholder until linked_doctype is available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds id="drawer-title" to the title heading and wires aria-labelledby
on the dialog div so assistive tech can announce the drawer title.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@harshtandiya harshtandiya merged commit 13fbb6a into develop Apr 7, 2026
6 checks passed
@harshtandiya harshtandiya deleted the feat/submission branch April 7, 2026 11:57
harshtandiya added a commit that referenced this pull request Apr 19, 2026
* feat: add submissions page and update sidebar navigation

- Introduced a new "Manage Form Submissions" page for viewing form submissions.
- Updated the router to include a route for submissions.
- Refactored sidebar items to include navigation to the new submissions page.
- Enhanced the overview page with breadcrumb navigation for better user context.

* feat: submissions page v1

* feat: add Drawer component for UI enhancement

- Introduced a new Drawer component to provide a sliding panel interface.
- Implemented customizable properties for size, position, title, and actions.
- Added keyboard accessibility with Escape key support for closing the drawer.
- Included transition effects for smooth opening and closing animations.

* feat: extend Drawer component with additional size options

- Added "xl" size option to the Drawer component for enhanced customization.
- Updated size classes for both horizontal and vertical orientations to include new "xl" dimensions.

* feat: add submission details view and field value component

- Introduced SubmissionDetails component to display detailed information about a specific submission.
- Created SubmissionFieldValue component to render individual field values with appropriate formatting based on field type.
- Enhanced SubmissionList component to include a drawer for viewing submission details upon row click.
- Updated API integration to fetch submission data securely based on user permissions.

* feat: enhance SubmissionFieldValue component with dynamic class handling

- Added computed property to determine class names based on field type, improving layout for Switch and Checkbox types.
- Updated template structure to utilize dynamic classes for better styling and responsiveness.

* fix: validate doctype against linked_doctype in get_submission_response

A client could pass an arbitrary doctype to access submissions from a
different form. Now we fetch both linked_team_id and linked_doctype from
the Form doc and reject requests where the supplied doctype doesn't match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: throw when form not found in get_all_submissions

If form_id is invalid, frappe.db.get_value returns None and calling
has_permission(doc=None) has undefined behaviour. Explicitly throw
DoesNotExistError before the permission check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: show dash placeholder for null textarea value in SubmissionFieldValue

Textarea was rendering blank when value is null/undefined, inconsistent
with the default case which uses value ?? "–".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: remove unused isLoading ref in SubmissionList

isLoading was declared and set but never bound in the template, so the
loading state had no effect and was also never cleared on fetch error.
Removed it entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: show loading state in drawer when formData not yet available

If a row is clicked before formData loads, the drawer was empty. Now
shows a Loading... placeholder until linked_doctype is available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add aria-labelledby to Drawer dialog element

Adds id="drawer-title" to the title heading and wires aria-labelledby
on the dialog div so assistive tech can announce the drawer title.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
harshtandiya added a commit that referenced this pull request Apr 19, 2026
* fix: route handling for /form

* feat: child table support v1 (#42)

* chore: bump frappe-ui to `0.1.262`

* feat: add 'Table' option to fieldtype selection

- Updated form_field.json and form_field.py to include 'Table' in the list of selectable field types for enhanced functionality.
- Adjusted the modified timestamp in form_field.json to reflect recent changes.

* feat: implement Table field component and enhance field options handling

- Added a new Table component for rendering tabular data input.
- Updated RenderField and FieldRenderer components to support dynamic options for Select and Link fields.
- Introduced useFieldOptions composable for improved option management.
- Enhanced form_fields utility to include Table as a selectable field type.
- Updated auto-imports and TypeScript definitions to accommodate new functionality.

* feat: enhance Table component fieldtype mapping

- Integrated utility function to map fieldtype for Table component, ensuring proper handling of field types.
- Defaulted unmapped fieldtypes to "Data" for improved consistency in table rendering.

* fix: update TableField component to use the correct Table component

- Replaced ListView with Table in the TableField definition to ensure proper rendering of tabular data.
- Adjusted imports in form_fields utility to reflect the change in component usage.

* refactor: remove onMounted hook from useFieldOptions

- Eliminated the onMounted lifecycle hook from the useFieldOptions composable to streamline the loading process of options.
- Adjusted imports to reflect the removal of the unused onMounted function.

* fix: child table fields fetch

* refactor: update TextEditor component bindings

- Changed `:model-value` to `:content` for TextEditor components across multiple files to ensure consistency in prop usage.
- Added an empty declaration block in auto-imports.d.ts for improved TypeScript support.

* feat: team management pages (#46)

* chore: bump lucide-icons

* feat: restructure home dashboard and sidebar

- Introduced a new home dashboard layout with a dedicated Dashboard.vue component.
- Migrated existing dashboard functionality from the previous Dashboard.vue to the new home structure.
- Added sidebar items management through a new sidebarItems.ts file for better organization.
- Updated TeamSwitcher component to conditionally render based on the current team.
- Removed unused code and components to streamline the application.

* refactor: enhance layout structure in BaseLayout and Dashboard components

- Wrapped the slot in BaseLayout with a div for improved layout consistency.
- Removed unnecessary padding class from the Dashboard component for a cleaner design.
- Ensured consistent formatting in watch options for better readability.

* fix: manageform page layout

* feat: team invitations via User Invitations

* feat: implement team member removal and permission toggling

- Added functionality to remove members from a team and toggle their edit permissions.
- Introduced `remove_member_from_team` and `toggle_can_edit_team` API endpoints.
- Created `RemoveMemberDialog` component for user confirmation before removal.
- Updated `TeamMemberList` to include removal actions and permission toggling.
- Enhanced `FPTeam` model to manage team member permissions effectively.

* feat: add team details update functionality

- Implemented a new API endpoint `save` to update team fields, allowing modifications to `team_name` and `logo`.
- Created `ManageTeamHeader` component for editing team name and logo upload functionality.
- Integrated the new save functionality into the team store for seamless updates.
- Updated `ManageTeam` page to include the new header component for enhanced team management.

* refactor: clean up imports in team store

- Removed unused `call` import from the team store file to streamline dependencies.

* chore: better code

- Added checks to prevent duplicate team member invitations and ensure team owners cannot have their permissions toggled.
- Updated `FPTeam` model to prevent removal of the team owner from the team.
- Modified `RemoveMemberDialog` and `TeamMemberList` components to improve member removal functionality and user experience.

* fix: tests

* chore: better code

* fix: team avatar uploader  (#47)

* feat: Image uploader component with crop

* feat: integrate ImageUploader for team logo management

- Replaced the existing logo upload button with an ImageUploader component for enhanced functionality.
- Added error handling and upload progress display within the ImageUploader.
- Updated the button label dynamically based on the upload state and existing logo presence.

* feat: enhance TeamSwitcher with search functionality

- Added a search input to the TeamSwitcher component, allowing users to filter teams by name.
- Updated the team options computation to include search query handling.
- Integrated a new search icon and improved layout for better user experience.
- Refactored dropdown item templates to accommodate the search input and maintain consistent styling.

* refactor: update CreateTeamDialog to use ImageUploader for logo uploads

- Replaced FileUploader with ImageUploader component for improved logo management.
- Updated type handling for uploaded files to align with new component.
- Enhanced error handling and upload progress display in the logo upload section.
- Adjusted button label to reflect upload status dynamically.

* refactor: update BaseLayout styling and icon usage

- Modified the layout classes for improved design consistency and responsiveness.
- Changed the logout button icon from a string to a component reference for better integration with the icon library.

* feat: add Breadcrumbs component to Dashboard

- Integrated Breadcrumbs component into the Dashboard for improved navigation.
- Defined breadcrumb items to enhance user context within the application.

* feat: submission list pages (#59)

* feat: add submissions page and update sidebar navigation

- Introduced a new "Manage Form Submissions" page for viewing form submissions.
- Updated the router to include a route for submissions.
- Refactored sidebar items to include navigation to the new submissions page.
- Enhanced the overview page with breadcrumb navigation for better user context.

* feat: submissions page v1

* feat: add Drawer component for UI enhancement

- Introduced a new Drawer component to provide a sliding panel interface.
- Implemented customizable properties for size, position, title, and actions.
- Added keyboard accessibility with Escape key support for closing the drawer.
- Included transition effects for smooth opening and closing animations.

* feat: extend Drawer component with additional size options

- Added "xl" size option to the Drawer component for enhanced customization.
- Updated size classes for both horizontal and vertical orientations to include new "xl" dimensions.

* feat: add submission details view and field value component

- Introduced SubmissionDetails component to display detailed information about a specific submission.
- Created SubmissionFieldValue component to render individual field values with appropriate formatting based on field type.
- Enhanced SubmissionList component to include a drawer for viewing submission details upon row click.
- Updated API integration to fetch submission data securely based on user permissions.

* feat: enhance SubmissionFieldValue component with dynamic class handling

- Added computed property to determine class names based on field type, improving layout for Switch and Checkbox types.
- Updated template structure to utilize dynamic classes for better styling and responsiveness.

* fix: validate doctype against linked_doctype in get_submission_response

A client could pass an arbitrary doctype to access submissions from a
different form. Now we fetch both linked_team_id and linked_doctype from
the Form doc and reject requests where the supplied doctype doesn't match.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: throw when form not found in get_all_submissions

If form_id is invalid, frappe.db.get_value returns None and calling
has_permission(doc=None) has undefined behaviour. Explicitly throw
DoesNotExistError before the permission check.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: show dash placeholder for null textarea value in SubmissionFieldValue

Textarea was rendering blank when value is null/undefined, inconsistent
with the default case which uses value ?? "–".

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: remove unused isLoading ref in SubmissionList

isLoading was declared and set but never bound in the template, so the
loading state had no effect and was also never cleared on fetch error.
Removed it entirely.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: show loading state in drawer when formData not yet available

If a row is clicked before formData loads, the drawer was empty. Now
shows a Loading... placeholder until linked_doctype is available.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: add aria-labelledby to Drawer dialog element

Adds id="drawer-title" to the title heading and wires aria-labelledby
on the dialog div so assistive tech can announce the drawer title.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: form sharing methods (#58)

* fix: form sharing methods

- Introduced `add_form_access` and `set_form_permission` functions to manage user permissions on forms.
- Updated the frontend to utilize the new API endpoints for adding access and setting permissions.
- Enhanced permission validation to ensure only authorized users can share forms.

* fix: validate permission_to allowlist and add docstrings to sharing API

- Validate `permission_to` against an explicit allowlist in `set_form_permission`
  to prevent unexpected kwargs from being forwarded to `add_docshare`
- Use `int(bool(value))` for safe coercion of the permission value
- Expand docstrings on `add_form_access` and `set_form_permission` with
  full Args/Raises sections and inline comments

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: hygine (#60)

* chore: update gitignore and pre-commit config for current project structure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: replace license.txt with LICENSE

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: add community health files and GitHub templates

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: update README with contributing and security references

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: update Python version references to 3.14 across all configs

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix: set ruff target-version to py313 (py314 not yet supported)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor: move vulnerable dependency check to a separate workflow

The vulnerable dependency check has been extracted from the linter workflow into its own dedicated workflow file. This change improves organization and allows for more focused execution of dependency checks during pull requests and manual triggers.

* chore: update dependabot configuration to use npm instead of yarn

* chore(deps): bump actions/cache from 4 to 5 (#61)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](actions/cache@v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump actions/setup-node from 3 to 6 (#62)

Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 6.
- [Release notes](https://github.com/actions/setup-node/releases)
- [Commits](actions/setup-node@v3...v6)

---
updated-dependencies:
- dependency-name: actions/setup-node
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump actions/setup-python from 4 to 6 (#63)

Bumps [actions/setup-python](https://github.com/actions/setup-python) from 4 to 6.
- [Release notes](https://github.com/actions/setup-python/releases)
- [Commits](actions/setup-python@v4...v6)

---
updated-dependencies:
- dependency-name: actions/setup-python
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump pre-commit/action from 3.0.0 to 3.0.1 (#65)

Bumps [pre-commit/action](https://github.com/pre-commit/action) from 3.0.0 to 3.0.1.
- [Release notes](https://github.com/pre-commit/action/releases)
- [Commits](pre-commit/action@v3.0.0...v3.0.1)

---
updated-dependencies:
- dependency-name: pre-commit/action
  dependency-version: 3.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump actions/checkout from 3 to 6 (#66)

Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](actions/checkout@v3...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps-dev): bump jsdom from 25.0.1 to 29.0.2 in /frontend (#67)

Bumps [jsdom](https://github.com/jsdom/jsdom) from 25.0.1 to 29.0.2.
- [Release notes](https://github.com/jsdom/jsdom/releases)
- [Commits](jsdom/jsdom@v25.0.1...v29.0.2)

---
updated-dependencies:
- dependency-name: jsdom
  dependency-version: 29.0.2
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump vue from 3.5.21 to 3.5.32 in /frontend (#68)

Bumps [vue](https://github.com/vuejs/core) from 3.5.21 to 3.5.32.
- [Release notes](https://github.com/vuejs/core/releases)
- [Changelog](https://github.com/vuejs/core/blob/main/CHANGELOG.md)
- [Commits](vuejs/core@v3.5.21...v3.5.32)

---
updated-dependencies:
- dependency-name: vue
  dependency-version: 3.5.32
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump zod from 4.1.12 to 4.3.6 in /frontend (#69)

Bumps [zod](https://github.com/colinhacks/zod) from 4.1.12 to 4.3.6.
- [Release notes](https://github.com/colinhacks/zod/releases)
- [Commits](colinhacks/zod@v4.1.12...v4.3.6)

---
updated-dependencies:
- dependency-name: zod
  dependency-version: 4.3.6
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): bump socket.io-client from 4.8.1 to 4.8.3 in /frontend (#70)

Bumps [socket.io-client](https://github.com/socketio/socket.io) from 4.8.1 to 4.8.3.
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/main/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/socket.io-client@4.8.1...socket.io-client@4.8.3)

---
updated-dependencies:
- dependency-name: socket.io-client
  dependency-version: 4.8.3
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps-dev): bump postcss from 8.5.6 to 8.5.8 in /frontend (#71)

Bumps [postcss](https://github.com/postcss/postcss) from 8.5.6 to 8.5.8.
- [Release notes](https://github.com/postcss/postcss/releases)
- [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md)
- [Commits](postcss/postcss@8.5.6...8.5.8)

---
updated-dependencies:
- dependency-name: postcss
  dependency-version: 8.5.8
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps): update frappe-ui to 0.1.272

* chore(deps): update vite to version 5.4.21 and adjust auto-imports and Vite config

* refactor(tests): streamline email handling in TestTeamInvitations

Removed the use of patching for the sendmail function and replaced it with a flag to mute emails during tests. This change simplifies the setup and teardown process while ensuring that email notifications are suppressed during test execution. Additionally, cleaned up the tearDown method to delete the Email Queue after tests.

* refactor(tests): enhance email handling in TestTeamInvitations

Reintroduced patching for the sendmail function in the TestTeamInvitations class to ensure email notifications are suppressed during tests. Updated the setUp and tearDown methods to manage the patching process effectively, ensuring compatibility with different Frappe versions. Removed the deletion of the Email Queue in tearDown as it is no longer necessary.

* feat: test framework with test factory (#72)

* feat(tests): add UserFactory and FPTeamFactory via frappe_factory_bot

Introduces a factories package under forms_pro/tests/factories/ with:
- UserFactory: generates random User docs; with_forms_pro_role trait
  includes the role in the initial doc dict so on_update fires once
  with the role already set
- FPTeamFactory: generates FP Team docs with a random team name default

Both factories implement __del_override__ as a safety-net cleanup guard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(tests): use UserFactory in test_roles

Replaces manual frappe.get_doc + Faker boilerplate with UserFactory.create().
Role assignment is kept as a post-insert step since the test specifically
exercises the on_update hook that fires when a role is added after creation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* refactor(tests): use factories in TestTeamInvitations

Replaces _create_user() and _create_team() helpers and the Faker/string
tracking lists with UserFactory and FPTeamFactory. Key changes:
- _user(*traits) and _team(owner) thin wrappers call the factories and
  track Document objects instead of email/name strings
- Helper signatures updated to accept Document args throughout
- UserFactory.build().email used where only a random email is needed
  (no DB insert required)
- All 11 tests pass unchanged in behaviour

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(hooks): add required_apps for frappe_factory_bot

Introduces a new required_apps list in hooks.py to include the frappe_factory_bot repository, enabling its integration into the application. This change enhances the app's capabilities by allowing the use of factory methods for testing and development.

* ci: fetch frappe_factory_bot before installing forms_pro

bench get-app with a local path skips required_apps resolution, so
frappe_factory_bot was never fetched. bench install-app then failed
with ModuleNotFoundError when Frappe tried to satisfy the required_apps
entry. Explicitly get the app before installing forms_pro.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* docs: update README with local test instructions and installation steps for frappe_factory_bot

Added a new section to the README detailing how to run tests locally using frappe_factory_bot. Included installation instructions and commands for running all tests as well as specific test modules.

* chore: remove del_override

* refactor: tests

* chore: refactor test_fp_team

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore(deps): update devDependencies for Vite and Vitest (#73)

Updated the versions of @vitejs/plugin-vue, @vitest/coverage-v8, vite, and vitest in package.json to their latest compatible releases. Removed deprecated dependencies from yarn.lock and updated @bcoe/v8-coverage to version 1.0.2.

* feat(e2e): Playwright E2E test suite (#57) (#79)

* feat(e2e): Phase 1 — Playwright foundation

Install @playwright/test, add test:e2e scripts, create playwright.config.ts
with Chromium project, global-setup for Frappe session auth, and a smoke
spec that asserts an authenticated user can reach the forms dashboard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(e2e): Phase 2 — data-testid attributes, POMs, and test data fixture

Add data-testid to 7 Vue components (form-card, btn-new-form, field-type-*,
btn-save-form, btn-publish, btn-submit-form, submission-success,
btn-send-invite, input-invite-email). Restructure InviteMemberDialog to use
#actions slot so the send button is directly targetable. Create Page Object
Models (dashboard, form-builder, submission, team) and a Playwright fixture
with API helpers for form create/publish/teardown.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(e2e): Phase 2+3 — semantic selectors and form-creation spec (5 tests)

Remove all data-testid attributes from Vue components (reverted to original).
Rewrite POMs with semantic selectors: getByRole/getByLabel/getByText, scoped
to pre-existing data-form-builder-component attributes. Add form-creation.spec.ts
covering dashboard listing, builder load, field canvas, publish, and unpublish.
Tests avoid triggering Frappe validation by never saving a field with an empty
label; publish/unpublish uses setValue({is_published}) which is always valid.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* feat(e2e): Phase 3+4 — remaining specs, flake fixes, and CI workflow

Specs:
- form-submission.spec.ts — guest context submission + success message
- submission-view.spec.ts — empty state and post-submission row visibility
- team-invite.spec.ts — invite dialog open/fill/send flow

Fixture:
- submitForm fixture for creating a guest submission via API

Reliability fixes:
- smoke.spec.ts: reload on Frappe 500 under parallel startup load
- TeamPage.goto(): waitForLoadState("networkidle") so Vue API calls
  complete before assertions run
- TeamPage.openInviteDialog(): wait for email input to be visible (not
  just the dialog shell) before returning
- playwright.config.ts: retries=1 everywhere; github reporter added

CI:
- .github/workflows/ui-tests.yml: full Playwright E2E workflow running
  against a real Frappe bench on every PR and push to develop

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci(ui-tests): log bench serve output on startup timeout

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci(ui-tests): fix server startup — switch to bench start, add hostname, fix action versions

- Replace bench serve (silent crash) with bench start + Procfile (disable watch/schedule)
- Add forms.test to /etc/hosts and set host_name config; rename site test_site → forms.test
- Swap npx wait-on for a curl loop (no install overhead, same pattern as buzz CI)
- Fix non-existent action versions: checkout@v6→@v4, setup-python@v6→@v5, setup-node@v6→@v4
- Add timeout-minutes: 60, fix concurrency group for push events, add workflow_dispatch
- Add Playwright browser cache step and bench logs dump on failure

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci(ui-tests): setup E2E test data — give Admin Forms Pro role and default team

`before_tests()` already added the role to Administrator, but the
`on_update` hook that creates the default team checks `has_value_changed`
on a freshly-fetched doc (which is always False), so no team was ever
created. `_ensure_admin_has_default_team()` closes that gap explicitly.

A new "Setup E2E test data" CI step calls `bench execute
forms_pro.install.before_tests` before the server starts, so the
test user has a team available when fixtures call `get_user_teams`.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(form-generator): replace clear_cache() with targeted doctype cache clear

frappe.clear_cache() flushes Redis entirely, including session and CSRF
token data. In the E2E test suite this causes all POST API calls after
the first form creation to fail with CSRFTokenError, because the token
captured during global-setup is invalidated server-side.

Replace with frappe.clear_document_cache("DocType", name) which only
evicts the specific newly-created DocType from the document cache —
sufficient for the new schema to be reloaded on next access, and does
not touch sessions or CSRF tokens.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci(ui-tests): set ignore_csrf=1 for test site

Frappe's DocType.on_update() calls frappe.clear_cache() internally
whenever a DocType is saved. This flushes session/CSRF data from Redis,
invalidating the CSRF token captured in Playwright's storageState.json.
All POST API calls after the first form creation then fail with
CSRFTokenError.

ignore_csrf=1 is the standard Frappe configuration for CI/test sites
and skips CSRF validation entirely, which is correct here.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci(ui-tests): mute emails so invite test does not need an email account

Sets mute_emails=1 in site config so Frappe returns a dummy outgoing
account instead of throwing OutgoingEmailError when no real account is
configured, allowing the team-invite E2E test to succeed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* ci(ui-tests): run E2E tests as the shared Forms Pro test user

- Switch global-setup login from Administrator to FORMS_PRO_TEST_USER
  (test_forms_pro_user@example.com / testforms123) so E2E tests run
  under a least-privileged Forms Pro User, matching real-user conditions
- Fix create_test_user() to strip the System User role Frappe auto-assigns
  on insert, leaving only the Forms Pro User role
- Set a known password via update_password() so Playwright can log in
- Add frontend/e2e/tsconfig.json with @types/node so process.env
  references in global-setup.ts type-check correctly
- Install @types/node dev dependency

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>

* chore: update .gitignore to include Playwright report and skills-lock.json

* feat(multiselect): add Multiselect field with registry-driven builder extras (#85)

* feat(form-field): add Multiselect fieldtype to doctype and backend mapping

* fix(submission): serialize list values to JSON string before DocType insert

* feat(multiselect): wire up Multiselect field across the frontend

* docs: add add-field skill and register it in CLAUDE.md

* chore: move skills to .claude/skills, gitignore .agents, document npx skills workflow

* docs: add userinterface-wiki skill reference to CLAUDE.md

* feat(fieldTypes): add optional builderExtras component slot to FieldTypeDefinition

* refactor(fields): move Multiselect into multiselect/ subfolder, add MultiselectBuilderExtras

* feat(fieldTypes): update Multiselect import path and register MultiselectBuilderExtras

* refactor(builder): replace hardcoded Multiselect extras with registry-driven builderExtras

* feat(multiselect): enhance option addition with error handling and unique validation

* feat(multiselect): change layout to description-first (label → desc → checkboxes)

* refactor(builder): wrap template in single root div so builderExtras stacks below field

* test(e2e): add Playwright spec for Multiselect field builder and submission flow

* test(e2e): wait for canvas render and set label before adding options

* test(e2e): fix strict mode violation on No options defined locator

* test(e2e): wait for Add Option button to confirm field rendered on canvas

* test(e2e): ensure Add Option button is visible before adding options

* fix(e2e): ensure form is saved before publishing to enable Publish button

* fix(e2e): set form title and wait on Save button state

Multiselect spec clicked Save while form title was blank — Frappe
rejected the save with MandatoryError, Save button never hid, and the
publish helper timed out. Fill a title before save so the request
succeeds, and in the helper wait for the Save button to disappear (same
render cycle as Publish appearing) instead of polling for Publish.

* fix(ci): resolve all CI failures on backport branch

- Node 18 → 20 in ci.yml and typecheck.yml (@vitejs/plugin-vue@6 requires ≥20.19)
- nosemgrep comment on allow_guest=True in get_doctype_fields (intentional for public form rendering)
- Migrate form_fields.ts to re-export FIELD_TYPE_DEFINITIONS, fixing Multiselect missing from sidebar
- Add Multiselect to FormFieldTypes enum; fix TS2367 comparison in SubmissionFieldValue.vue

* fix(tests): use FrappeTestCase for version-15 compatibility

IntegrationTestCase was introduced in Frappe v16; version-15 exposes
FrappeTestCase from frappe.tests.utils.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
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.

1 participant