Skip to content

Comments

Fix outlook filters#976

Merged
elie222 merged 2 commits intomainfrom
fix/outlook-filters
Nov 16, 2025
Merged

Fix outlook filters#976
elie222 merged 2 commits intomainfrom
fix/outlook-filters

Conversation

@elie222
Copy link
Owner

@elie222 elie222 commented Nov 16, 2025

Summary by CodeRabbit

  • New Features

    • Improved Outlook filter behavior: label IDs are resolved to categories, enabling more reliable category assignment, folder moves, and mark-as-read actions.
    • Safer defaults and fallbacks: filters now default to marking messages read when no explicit actions are set.
  • Chores

    • Version bump to v2.19.7.

@vercel
Copy link

vercel bot commented Nov 16, 2025

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Preview Updated (UTC)
inbox-zero Ready Ready Preview Nov 16, 2025 9:54am

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 16, 2025

Walkthrough

Introduces a helper buildFilterActions to construct Outlook rule actions (moveToFolder, markAsRead, assignCategories) by resolving label IDs via getLabelById. Replaces inline action construction in createFilter, updates updateFilter to accept addLabelIds?, and bumps version to v2.19.7.

Changes

Cohort / File(s) Summary
Outlook filter refactoring
apps/web/utils/outlook/filter.ts
Adds internal buildFilterActions to build actions from addLabelIds/removeLabelIds, resolves label IDs with getLabelById (falls back to ID on error), uses helper in createFilter and updateFilter, and defaults to markAsRead when no actions present. updateFilter signature now accepts optional addLabelIds?: string[].
Version bump
version.txt
Version updated from v2.19.6 to v2.19.7.

Sequence Diagram(s)

sequenceDiagram
    participant Caller as createFilter / updateFilter
    participant Builder as buildFilterActions
    participant Labels as getLabelById
    note right of Caller `#D3E4CD`: Caller supplies addLabelIds/removeLabelIds

    Caller->>Builder: buildFilterActions(addLabelIds, removeLabelIds)
    activate Builder

    alt addLabelIds present
        loop for each addLabelId
            Builder->>Labels: getLabelById(id)
            Labels-->>Builder: categoryName or error/fallback id
        end
        Builder->>Builder: accumulate assignCategories
    end

    alt removeLabelIds present
        Builder->>Builder: set moveToFolder from removeLabelIds
    end

    alt no actions resolved
        Builder->>Builder: set markAsRead = true (fallback)
    end

    Builder-->>Caller: actions object (moveToFolder, markAsRead, assignCategories)
    deactivate Builder
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

  • Check label resolution error handling and fallback behavior in getLabelById calls.
  • Verify default markAsRead fallback aligns with Outlook API requirements.
  • Confirm updateFilter optional parameter changes don't break existing callers.

Possibly related PRs

Suggested reviewers

  • mosesjames7271-svg

Poem

🐰 I hop through code to tidy rules anew,
Labels turned to categories, clean and true.
Actions built from scattered bits,
A gentle fix in tiny wits.
Version bumped — a carrot for the crew. 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Fix outlook filters' is directly related to the main changes in the PR, which involve fixing and improving Outlook filter functionality by building filter actions via new helpers and adding label resolution support.
✨ 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 fix/outlook-filters

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
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
apps/web/utils/outlook/filter.ts (2)

243-268: Parallelize the API calls for better performance.

The function makes sequential API calls in a loop, which can be slow when resolving multiple label IDs. Consider using Promise.all() or Promise.allSettled() to parallelize these calls.

Apply this diff to parallelize the API calls:

 async function resolveCategoryNames(
   client: OutlookClient,
   labelIds: string[],
 ): Promise<string[]> {
-  const categoryNames: string[] = [];
-
-  for (const labelId of labelIds) {
-    try {
-      const category = await getLabelById({ client, id: labelId });
-      if (category?.displayName) {
-        categoryNames.push(category.displayName);
-      } else {
-        logger.warn("Category not found by ID, using ID as name", { labelId });
-        categoryNames.push(labelId);
-      }
-    } catch (error) {
-      logger.warn("Failed to resolve category ID, using ID as name", {
-        labelId,
-        error,
-      });
-      categoryNames.push(labelId);
-    }
-  }
-
-  return categoryNames;
+  const results = await Promise.allSettled(
+    labelIds.map((labelId) =>
+      getLabelById({ client, id: labelId }).then((category) => ({
+        labelId,
+        category,
+      }))
+    )
+  );
+
+  return results.map((result, index) => {
+    const labelId = labelIds[index];
+    if (result.status === "fulfilled" && result.value.category?.displayName) {
+      return result.value.category.displayName;
+    } else {
+      const error = result.status === "rejected" ? result.reason : undefined;
+      logger.warn(
+        result.status === "rejected"
+          ? "Failed to resolve category ID, using ID as name"
+          : "Category not found by ID, using ID as name",
+        { labelId, error }
+      );
+      return labelId;
+    }
+  });
 }

57-94: Consider using buildFilterActions for consistency.

The createAutoArchiveFilter function has hardcoded action construction that could be refactored to use the new buildFilterActions helper for consistency. While this function has a specific purpose, using the shared helper would reduce code duplication and improve maintainability.

You could refactor it like this:

export async function createAutoArchiveFilter({
  client,
  from,
  labelName,
}: {
  client: OutlookClient;
  from: string;
  labelName?: string;
}) {
  try {
    const actions = await buildFilterActions({
      client,
      addLabelIds: labelName ? [labelName] : undefined,
      removeLabelIds: ["INBOX", "UNREAD"],
      context: { from, labelName },
    });

    const rule: MessageRule = {
      displayName: `Auto-archive filter for ${from}`,
      sequence: 1,
      isEnabled: true,
      conditions: {
        senderContains: [from],
      },
      actions,
    };

    const response: MessageRule = await withOutlookRetry(() =>
      client.getClient().api("/me/mailFolders/inbox/messageRules").post(rule),
    );

    return { status: 201, data: response };
  } catch (error) {
    if (isAlreadyExistsError(error)) {
      logger.warn("Auto-archive filter already exists", { from });
      return { status: 200 };
    }
    throw error;
  }
}

Note: This assumes labelName is a label ID rather than a display name. If it's already a display name, you may need to adjust the approach.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b92aaae and 1dd7637.

📒 Files selected for processing (2)
  • apps/web/utils/outlook/filter.ts (5 hunks)
  • version.txt (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/utils/outlook/filter.ts (2)
apps/web/utils/outlook/client.ts (1)
  • OutlookClient (19-80)
apps/web/utils/outlook/label.ts (1)
  • getLabelById (68-78)
⏰ 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). (2)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: test
🔇 Additional comments (6)
version.txt (1)

1-1: LGTM: Version bump reflects the API changes.

The version bump from v2.19.6 to v2.19.7 is appropriate for the filter refactoring changes introduced in this PR.

apps/web/utils/outlook/filter.ts (5)

9-9: LGTM: Import added for label resolution.

The getLabelById import is correctly added to support the new category name resolution functionality.


23-41: LGTM: Refactored to use the new helper function.

The refactoring to use buildFilterActions improves code maintainability and consistency with the updateFilter function.


192-221: LGTM: Consistent refactoring with dynamic action construction.

The updateFilter function now accepts addLabelIds and uses buildFilterActions for dynamic action construction, maintaining consistency with createFilter.


274-320: LGTM: Well-structured helper with good documentation and fallback behavior.

The buildFilterActions function is well-implemented with:

  • Proper async handling for category name resolution
  • Clear handling of special labels ("INBOX" → archive, "UNREAD" → markAsRead)
  • Sensible fallback to markAsRead when no actions are specified
  • Good logging for debugging

300-306: Label identifiers are correct and consistent with codebase design.

The "INBOX" and "UNREAD" identifiers used in the filter code are properly defined in apps/web/utils/outlook/label.ts and align with the intentional design pattern. The codebase comment explicitly states these are mapped for Outlook "using same format as Gmail for consistency." The identifiers are used consistently throughout the Outlook integration code, including in related filter and stats logic.

Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

No issues found across 2 files

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

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

⚠️ Outside diff range comments (1)
apps/web/utils/outlook/filter.ts (1)

57-94: Refactor to use the new helper functions for consistency.

The createAutoArchiveFilter function was not updated to use the new buildFilterActions and resolveCategoryNames helpers. It still directly assigns labelName to assignCategories without validation, which could fail if labelName is not a valid category display name from masterCategories.

Apply this refactor to align with the approach used in createFilter and updateFilter:

 export async function createAutoArchiveFilter({
   client,
   from,
   labelName,
 }: {
   client: OutlookClient;
   from: string;
   labelName?: string;
 }) {
   try {
-    // For Outlook, we'll create a rule that moves messages to archive
+    const actions = await buildFilterActions({
+      client,
+      addLabelIds: labelName ? [labelName] : undefined,
+      removeLabelIds: ["INBOX", "UNREAD"],
+      context: { from, autoArchive: true },
+    });
+
     const rule: MessageRule = {
       displayName: `Auto-archive filter for ${from}`,
       sequence: 1,
       isEnabled: true,
       conditions: {
         senderContains: [from],
       },
-      actions: {
-        moveToFolder: "archive",
-        markAsRead: true,
-        ...(labelName && { assignCategories: [labelName] }),
-      },
+      actions,
     };

     const response: MessageRule = await withOutlookRetry(() =>
       client.getClient().api("/me/mailFolders/inbox/messageRules").post(rule),
     );

     return { status: 201, data: response };
   } catch (error) {
     if (isAlreadyExistsError(error)) {
       logger.warn("Auto-archive filter already exists", { from });
       return { status: 200 };
     }
     throw error;
   }
 }

Note: If labelName is expected to be a category ID rather than a display name, this refactor ensures proper resolution. If it's already a display name, you'll need to adjust the approach or add a parameter to distinguish between IDs and names.

🧹 Nitpick comments (1)
apps/web/utils/outlook/filter.ts (1)

243-266: Good fix for the previous review concern, but consider handling partial failures more explicitly.

The code correctly addresses the past review comment by not falling back to raw labelId values as category names. However, when category resolution fails for some or all IDs, they're silently skipped (with error logs). This could lead to filters that don't behave as users expect.

Consider one of these approaches:

  1. Return resolution metadata to let callers know about failures:
-): Promise<string[]> {
+): Promise<{ categoryNames: string[]; failedIds: string[] }> {
   const categoryNames: string[] = [];
+  const failedIds: string[] = [];

   for (const labelId of labelIds) {
     try {
       const category = await getLabelById({ client, id: labelId });
       if (category?.displayName) {
         categoryNames.push(category.displayName);
       } else {
         logger.error("Category not found by ID", { labelId });
+        failedIds.push(labelId);
       }
     } catch (error) {
       logger.error("Failed to resolve category ID", {
         labelId,
         error,
       });
+      failedIds.push(labelId);
     }
   }

-  return categoryNames;
+  return { categoryNames, failedIds };
 }
  1. Throw an error if all resolutions fail, ensuring at least some categories are resolved when the user explicitly provides them.

Based on learnings

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1dd7637 and 8faa620.

📒 Files selected for processing (1)
  • apps/web/utils/outlook/filter.ts (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
apps/web/utils/outlook/filter.ts (2)
apps/web/utils/outlook/client.ts (1)
  • OutlookClient (19-80)
apps/web/utils/outlook/label.ts (1)
  • getLabelById (68-78)
⏰ 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). (2)
  • GitHub Check: cubic · AI code reviewer
  • GitHub Check: test
🔇 Additional comments (4)
apps/web/utils/outlook/filter.ts (4)

9-9: LGTM!

The import is necessary for the new category resolution functionality.


23-31: LGTM!

Good refactoring to extract action building logic into a reusable helper. This improves maintainability and reduces code duplication.


196-211: LGTM!

Consistent refactoring that mirrors the createFilter changes, properly adding support for addLabelIds parameter.


268-318: No issues found — implementation correctly handles Microsoft Graph API requirements.

The web search confirms that the Microsoft Graph messageRule API requires at least one action and rejects empty actions objects with HTTP 400. Your code's fallback to markAsRead when no actions are generated is necessary to satisfy this API requirement. The implementation is correct: it prevents API failures, includes appropriate warning logging when the fallback is triggered, and ensures the service will always receive a valid request. No changes needed.

@elie222 elie222 merged commit 1b8c1ac into main Nov 16, 2025
15 checks passed
@elie222 elie222 deleted the fix/outlook-filters branch December 18, 2025 23:06
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