Skip to content

fix: Fixed issues and a working solution on unsubmitted forms#12

Open
Markez wants to merge 1 commit into
vula-africa:mainfrom
Markez:unsubmitted_forms_cleanup
Open

fix: Fixed issues and a working solution on unsubmitted forms#12
Markez wants to merge 1 commit into
vula-africa:mainfrom
Markez:unsubmitted_forms_cleanup

Conversation

@Markez
Copy link
Copy Markdown

@Markez Markez commented Sep 15, 2025

PR Description

This PR addresses critical issues in the unsubmitted_forms scheduled job to ensure safe and reliable cleanup of entities and tokens:

Fixes

  • Correct 7 day cutoff_date calculation
    • Replaced incorrect second-based math with millisecond precision (Date.now() - 7 * 24 * 60 * 60 * 1000).
    • Ensures forms older than 7 days are consistently targeted, even if the job skips a day.
  • Added “unsubmitted” filter
    • Tokens are only considered for cleanup if submittedAt = null.
    • Prevents accidental deletion of entities tied to submitted forms.
  • Scoped relationship deletion to entity
    • Replaced product_id-based query with entity_id scoping.
    • Ensures only relationships linked to the same entity are deleted, avoiding cross-entity data loss.
  • Guarded entity deletion with safety checks
    • Entities are only deleted if:
      • They have no submitted tokens, and
      • They have no non-new relationships.
    • Removed unsafe || "" fallback to avoid deleting with invalid IDs.

Improvements

  • Clearer transaction flow: always delete the expired token, then we can conditionally delete the entity and children.
  • More resilient against data inconsistencies by using deleteMany for children and scoping queries explicitly.
  • Improved readability with explicit constants (no_of_ms) and inline documentation.

Reviewer Notes

  • This PR focuses on correctness and data safety, not bulk performance optimizations.
  • Future work: Add batch deletes to handle large datasets more efficiently, and the job can safely be retried without errors.

Summary by CodeRabbit

  • Bug Fixes

    • Improved cleanup of unsubmitted forms older than 7 days with stricter safety checks to prevent accidental data deletion.
    • Removed orphaned draft tokens and applied transactional deletes to enhance data integrity.
    • Enhanced error handling for more reliable background processing.
  • Documentation

    • Added a “My Four Bulleted Retrospective” notes section to end-of-week issues documentation.
  • Chores

    • Updated ignore rules to exclude local IDE files from tests.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Sep 15, 2025

Walkthrough

Added .idea ignore rule in tests/.gitignore. Appended retrospective notes to tests/end_of_week_issues/README.md. Refactored tests/unsubmitted_forms/cleanup_unsubmitted_forms.ts: simplified age-based filtering, handled orphan tokens, introduced parallel counts to determine safe entity deletion, performed transactional deletes, and added error handling with job status updates.

Changes

Cohort / File(s) Summary
Ignore rules
tests/.gitignore
Added .idea/* to ignore IDE project files within tests.
Docs update
tests/end_of_week_issues/README.md
Appended a “My Four Bulleted Retrospective” section with three bullets on post-deployment issues, rollback strategy, and monitoring/alerts.
Unsubmitted forms cleanup refactor
tests/unsubmitted_forms/cleanup_unsubmitted_forms.ts
Replaced date-window logic with 7-day cutoff; deleted orphan tokens immediately; computed submitted-token and non-new relationship counts in parallel; determined safe entity deletion; always deleted token; conditionally deleted related data and entity within a transaction; added error logging, job failure status, and rethrow.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Job as Cleanup Job
  participant DB as Database
  participant Tx as Transaction

  Job->>DB: Query tokens where createdAt < (now - 7 days) AND submittedAt IS NULL
  loop For each token
    alt token has no entityId
      Job->>DB: Delete token
    else token has entityId
      par Count operations
        Job->>DB: Count submitted tokens for entity
        Job->>DB: Count relationships with status != "new" for entity
      end
      Job->>Job: safeToDeleteEntity = (submittedCount == 0 && nonNewRelCount == 0)
      Job->>DB: Delete token
      alt safeToDeleteEntity
        Job->>Tx: Begin transaction
        Tx->>DB: Delete "new" relationships for entity
        Tx->>DB: Delete corpus items for entity
        Tx->>DB: Delete entity
        Tx-->>Job: Commit
      else Not safe
        Note right of Job: Keep entity and non-new relationships
      end
    end
  end

  rect rgb(255,240,240)
    Note over Job: Error path
    Job-->>Job: Log error
    Job->>DB: Update job status = "failed"
    Job-->>Job: Rethrow
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

I tidied tests with a hop and a cheer,
Ignored IDE crumbs that gathered near.
Jotted lessons from the week’s long run,
Then swept old forms—one by one.
Count, decide, and transact—no fear!
A safe little warren of cleaner code here. 🐇✨

Pre-merge checks and finishing touches

✅ 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 references the PR's primary change—fixes and a working solution for unsubmitted forms—so it aligns with the changeset's main objective of improving the unsubmitted_forms cleanup job and is not misleading; it is clear enough for a reviewer to understand the primary intent. The wording is slightly verbose and redundant ("Fixed issues and a working solution") but still acceptable.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


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.

Copy link
Copy Markdown

@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

🧹 Nitpick comments (8)
tests/.gitignore (1)

1-1: Scope ignore to repo root or use directory form.

Placing .idea/* under tests/ won’t ignore IDE files created at the repo root. Prefer adding .idea/ to the root .gitignore, or if you intend to keep it scoped here, use .idea/ (trailing slash) for clarity.

tests/end_of_week_issues/README.md (1)

28-32: Fix typos and align heading with bullet count.

Minor language fixes; also add a 4th bullet to match the “Four Bulleted” heading.

-   My Four Bulleted Retrospective
-   - An issue was resported following a deployment that happened for v1.3 and seems testing was improper
-   - A need to work on a deployment strategy that is behind an automated rolback configurations in the event of a issues
-   - A need to discuss and work on monitory, alerts and notifications to pick up on issues before clients do 
+   My Four‑Bullet Retrospective
+   - An issue was reported after the v1.3 deployment; pre‑release testing was insufficient.
+   - Adopt an automated rollback strategy (one‑click/auto rollback on health/SLO breach).
+   - Improve monitoring, alerting, and notifications to detect issues before clients do.
+   - Introduce canary releases/feature flags to limit blast radius during rollouts.
tests/unsubmitted_forms/cleanup_unsubmitted_forms.ts (6)

31-31: Name constants clearly.

Use an uppercase constant; this improves readability and avoids “no_of_ms” ambiguity.

-const no_of_ms = 24 * 60 * 60 * 1000;
+const DAY_MS = 24 * 60 * 60 * 1000;

36-43: Consistent naming for dates.

CamelCase for variables; also adjust usage below.

-const cutoff_date = new Date(Date.now() - 7 * no_of_ms);
+const cutoffDate = new Date(Date.now() - 7 * DAY_MS);
...
-        createdAt: { lt: cutoff_date },
+        createdAt: { lt: cutoffDate },

40-45: Potential large result set; consider pagination and index.

findMany without take/orderBy can load too many rows. Use paginated batches (take, cursor, orderBy: { createdAt: 'asc' }) and ensure an index on (submittedAt, createdAt) (or at least createdAt) to keep the daily job predictable.


75-100: Re-check invariants inside the transaction to close race windows.

Between the pre-check and the transaction, another process could submit a token or promote a relationship out of “new.” Validate again inside tx before cascading deletes.

-        if (safeToDeleteEntity) {
+        if (safeToDeleteEntity) {
+          // Re-check inside the transaction to avoid races
+          const [submittedNow, otherUnsubmittedNow, nonNewRelNow] = await Promise.all([
+            tx.publicFormsTokens.count({ where: { entityId, submittedAt: { not: null } } }),
+            tx.publicFormsTokens.count({ where: { entityId, submittedAt: null } }),
+            tx.relationship.count({ where: { entity_id: entityId, NOT: { status: "new" } } }),
+          ]);
+          if (submittedNow > 0 || otherUnsubmittedNow > 0 || nonNewRelNow > 0) {
+            return; // abort entity/children delete; token delete already done
+          }
           // remove new relationships tied to this entity
           await tx.relationship.deleteMany({
             where: {
               entity_id: entityId,
               status: "new",
             },
           });
 
           // Delete all corpus items associated with the entity
           await tx.new_corpus.deleteMany({
             where: { entity_id: entityId },
           });
 
           // Delete the entity (company)
           await tx.entity.delete({
             where: { id: entityId },
           });
         }

104-108: Prefer structured logs and avoid leaking sensitive values.

Log with context (job id, counts), but don’t include raw tokens or PII. Consider using your logger with severity and event codes; keep console.error for tests only.


40-101: Optional: per-record error isolation and batching.

A single failure currently aborts the whole run. Wrap the per-token body in its own try/catch and keep tallies so the job can complete best‑effort, then report summary stats. Also consider deleting expired orphan tokens in bulk via one deleteMany before the loop.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

Cache: Disabled due to data retention organization setting

Knowledge base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 1dd21f7 and 54f9395.

📒 Files selected for processing (3)
  • tests/.gitignore (1 hunks)
  • tests/end_of_week_issues/README.md (1 hunks)
  • tests/unsubmitted_forms/cleanup_unsubmitted_forms.ts (1 hunks)
🔇 Additional comments (1)
tests/unsubmitted_forms/cleanup_unsubmitted_forms.ts (1)

49-52: Good: orphan-token fast path.

Early delete for tokens without entityId keeps the loop simple and prevents unnecessary lookups.

Comment on lines +57 to +73
const [submittedTokenCount, nonNewRelCount] = await Promise.all([
prisma.publicFormsTokens.count({
where: {
entityId,
submittedAt: { not: null },
},
}),
prisma.relationship.count({
where: {
entity_id: entityId,
NOT: { status: "new" }, // anything other than 'new' blocks deletion
},
}),
]);

const safeToDeleteEntity = submittedTokenCount === 0 && nonNewRelCount === 0;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue

Blocker: entity delete can violate FK if other unsubmitted tokens exist.

You only check for submitted tokens and non‑new relationships. If the entity has another unsubmitted token, deleting the entity will fail and abort the whole job. Include a count of other unsubmitted tokens for the entity (excluding the current token) in the safety gate.

-      const [submittedTokenCount, nonNewRelCount] = await Promise.all([
+      const [submittedTokenCount, nonNewRelCount, otherUnsubmittedTokenCount] = await Promise.all([
         prisma.publicFormsTokens.count({
           where: {
             entityId,
             submittedAt: { not: null },
           },
         }),
         prisma.relationship.count({
           where: {
             entity_id: entityId,
             NOT: { status: "new" }, // anything other than 'new' blocks deletion
           },
         }),
+        prisma.publicFormsTokens.count({
+          where: {
+            entityId,
+            submittedAt: null,
+            token: { not: token.token }, // exclude the token we're processing
+          },
+        }),
       ]);
 
-      const safeToDeleteEntity = submittedTokenCount === 0 && nonNewRelCount === 0;
+      const safeToDeleteEntity =
+        submittedTokenCount === 0 &&
+        nonNewRelCount === 0 &&
+        otherUnsubmittedTokenCount === 0;
📝 Committable suggestion

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

Suggested change
const [submittedTokenCount, nonNewRelCount] = await Promise.all([
prisma.publicFormsTokens.count({
where: {
entityId,
submittedAt: { not: null },
},
}),
prisma.relationship.count({
where: {
entity_id: entityId,
NOT: { status: "new" }, // anything other than 'new' blocks deletion
},
}),
]);
const safeToDeleteEntity = submittedTokenCount === 0 && nonNewRelCount === 0;
const [submittedTokenCount, nonNewRelCount, otherUnsubmittedTokenCount] = await Promise.all([
prisma.publicFormsTokens.count({
where: {
entityId,
submittedAt: { not: null },
},
}),
prisma.relationship.count({
where: {
entity_id: entityId,
NOT: { status: "new" }, // anything other than 'new' blocks deletion
},
}),
prisma.publicFormsTokens.count({
where: {
entityId,
submittedAt: null,
token: { not: token.token }, // exclude the token we're processing
},
}),
]);
const safeToDeleteEntity =
submittedTokenCount === 0 &&
nonNewRelCount === 0 &&
otherUnsubmittedTokenCount === 0;
🤖 Prompt for AI Agents
In tests/unsubmitted_forms/cleanup_unsubmitted_forms.ts around lines 57 to 73,
the safety check only counts submitted tokens and non‑new relationships but
misses other unsubmitted tokens for the same entity, which can cause FK delete
failures; add a third Promise.count that counts publicFormsTokens with the same
entityId, submittedAt: null, and id !== currentTokenId (exclude the current
token being deleted), include that count in the Promise.all result, and only
mark safeToDeleteEntity true when submittedTokenCount === 0, nonNewRelCount ===
0, and otherUnsubmittedCount === 0; update the safeToDeleteEntity condition
accordingly.

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