Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: bypass feature flag #2707

Closed
wants to merge 10 commits into from
Closed

feat: bypass feature flag #2707

wants to merge 10 commits into from

Conversation

ogzhanolguncu
Copy link
Contributor

@ogzhanolguncu ogzhanolguncu commented Dec 5, 2024

What does this PR do?

This PR allows us to bypass features and betaFeatures checks when developing locally. It also allows us to pick any feature flag with type safety.

Fixes #2704 (issue)

If there is not an issue for this, please create one first. This is used to tracking purposes and also helps use understand why this PR exists

Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • Chore (refactoring code, technical debt, workflow improvements)
  • Enhancement (small improvements)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How should this be tested?

  • Try to navigate to /logs page in local env. Normally this is not possible without manually updating the db.

Checklist

Required

  • Filled out the "How to test" section in this PR
  • Read Contributing Guide
  • Self-reviewed my own code
  • Commented on my code in hard-to-understand areas
  • Ran pnpm build
  • Ran pnpm fmt
  • Checked for warnings, there are none
  • Removed all console.logs
  • Merged the latest changes from main onto my branch with git pull origin main
  • My changes don't cause any responsiveness issues

Appreciated

  • [] If a UI change was made: Added a screen recording or screenshots to this PR
  • [] Updated the Unkey Docs if changes were necessary

Summary by CodeRabbit

Release Notes

  • New Features

    • Enhanced validation and updating mechanisms for the API settings page, ensuring accurate and current data display.
    • Introduced a centralized access control function, getFlag, improving clarity and maintainability across various components, including navigation and feature visibility.
    • Added default constants for rate limiting and audit log retention periods to streamline configuration.
  • Bug Fixes

    • Improved access checks for features like IP whitelisting and rate limit overrides, ensuring users see relevant options based on permissions.
  • Documentation

    • Updated type definitions and function signatures for better readability and understanding.

Copy link

changeset-bot bot commented Dec 5, 2024

⚠️ No Changeset found

Latest commit: 134ba3a

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Copy link

vercel bot commented Dec 5, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
dashboard ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 6, 2024 1:20pm
engineering ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 6, 2024 1:20pm
play ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 6, 2024 1:20pm
www ✅ Ready (Inspect) Visit Preview 💬 Add feedback Dec 6, 2024 1:20pm

Copy link
Contributor

coderabbitai bot commented Dec 5, 2024

📝 Walkthrough
📝 Walkthrough

Walkthrough

The pull request introduces significant updates across several components in the dashboard application, primarily focusing on enhancing feature access control through a new utility function, getFlag. This function centralizes the logic for checking access to various features based on the workspace context. Modifications include refining the retrieval and validation of workspaces and APIs, updating prop types for clarity, and ensuring that the display logic for navigation items and other components reflects the user's access rights accurately.

Changes

File Path Change Summary
apps/dashboard/app/(app)/apis/[apiId]/settings/page.tsx Refined workspace and API retrieval logic; added keyAuth size updating mechanism; updated export constant.
apps/dashboard/app/(app)/apis/[apiId]/settings/update-ip-whitelist.tsx Simplified workspace prop type; updated access control logic using getFlag.
apps/dashboard/app/(app)/audit/[bucket]/page.tsx Updated retention days logic to use getFlag; reformatted type definition for clarity.
apps/dashboard/app/(app)/identities/layout.tsx Replaced direct property check with getFlag for determining access to identities feature.
apps/dashboard/app/(app)/logs/page.tsx Modified access check for logs page to use getFlag.
apps/dashboard/app/(app)/ratelimits/[namespaceId]/overrides/page.tsx Updated logic for displaying ratelimit overrides using getFlag.
apps/dashboard/app/(app)/workspace-navigations.tsx Streamlined navigation visibility checks using getFlag.
apps/dashboard/lib/trpc/routers/api/updateIpWhitelist.ts Replaced direct check for IP whitelist access with getFlag.
apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts Updated maximum number of rate limit overrides logic to use getFlag.
apps/dashboard/lib/utils.ts Introduced getFlag function and new types for workspace features.

Assessment against linked issues

Objective Addressed Explanation
Add a way to check feature flags with some type-safety (#2704)

Possibly related PRs

  • refactor: query audit logs from planetscale #2181: The changes in this PR involve modifications to the AuditPage component, specifically in how audit logs are retrieved and structured, which relates to the main PR's focus on enhancing the validation and updating mechanisms for API settings.
  • feat: icons #2700: This PR introduces a new Icon component and related changes, which may not directly relate to the main PR but indicates ongoing enhancements in the UI components that could be relevant in the context of the dashboard's settings page.
  • chore: remove eventrouter and tinybird references #2705: This PR focuses on removing references to the event router and Tinybird, which aligns with the main PR's goal of refining the API settings and validation logic, as both aim to streamline the application's functionality and reduce complexity.

Suggested labels

🕹️ oss.gg, :joystick: 300 points, hacktoberfest

Suggested reviewers

  • mcstepp
  • perkinsjr
  • MichaelUnkey
  • chronark

📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ccc2d6d and 134ba3a.

📒 Files selected for processing (2)
  • apps/dashboard/app/(app)/apis/[apiId]/settings/page.tsx (1 hunks)
  • apps/dashboard/app/(app)/audit/[bucket]/page.tsx (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/dashboard/app/(app)/apis/[apiId]/settings/page.tsx
  • apps/dashboard/app/(app)/audit/[bucket]/page.tsx

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

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

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

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

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

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added the Feature New feature or request label Dec 5, 2024
Copy link
Contributor

github-actions bot commented Dec 5, 2024

Thank you for following the naming conventions for pull request titles! 🙏

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

🧹 Outside diff range and nitpick comments (9)
apps/dashboard/lib/utils.ts (2)

34-56: Documentation is comprehensive but needs minor improvements

The documentation is thorough but could be enhanced:

  • Return type in documentation mentions "undefined" but code returns "null"
  • Example code shows undefined check but should show null check

Apply these documentation improvements:

 * Checks if a workspace has access to a specific feature or beta feature
-* Returns the feature value if access is granted, undefined otherwise
+* Returns the feature value if access is granted, null otherwise
 * Note: Always returns true in development environment
 *
 * @param flagName - The name of the feature to check
 * @param workspace - The workspace to check access for
-* @returns The feature value (boolean | number | string) if access granted, undefined otherwise
+* @returns The feature value (boolean | number | string) if access granted, null otherwise
 *
 * @example
 * ```typescript
 * // Check if workspace has access to logs page
 * if (!hasWorkspaceAccess("logsPage", workspace)) {
 *   return notFound();
 * }
 *
 * // Check if workspace has access to a feature with numeric value
 * const userLimit = hasWorkspaceAccess("userLimit", workspace);
-* if (userLimit === undefined) {
+* if (userLimit === null) {
 *   return notFound();
 * }
 * ```

57-82: Consider security implications of development bypass

The implementation looks solid but has some security considerations:

  1. Development bypass returns true for all features
  2. No logging of bypassed checks in development

Consider these improvements:

  1. Add environment variable to explicitly enable bypass mode
  2. Add debug logging in development mode
  3. Consider allowing selective feature bypass instead of all-or-nothing

Example implementation:

const FEATURE_BYPASS_ENABLED = process.env.FEATURE_BYPASS_ENABLED === 'true';
const BYPASS_FEATURES = process.env.BYPASS_FEATURES?.split(',') || [];

export function hasWorkspaceAccess<T extends keyof ConfigObject>(
  flagName: T,
  workspace: Partial<WorkspaceFeatures>,
): FlagValue<T> | null {
  if (process.env.NODE_ENV === "development" && FEATURE_BYPASS_ENABLED) {
    if (BYPASS_FEATURES.length === 0 || BYPASS_FEATURES.includes(flagName)) {
      console.debug(`[Feature Bypass] Enabling feature: ${flagName}`);
      return true as FlagValue<T>;
    }
  }
  // ... rest of the implementation
}
apps/dashboard/app/(app)/logs/page.tsx (2)

32-34: Consider enhancing error handling and user feedback

The implementation correctly uses hasWorkspaceAccess but could benefit from better error handling:

  1. Consider showing a more user-friendly message instead of 404
  2. Add logging for debugging purposes in development

Consider this enhancement:

   if (!hasWorkspaceAccess("logsPage", workspace)) {
+    if (process.env.NODE_ENV === "development") {
+      console.debug("[Logs Page] Access denied: logsPage feature not enabled");
+    }
+    return (
+      <div className="text-center p-4">
+        <h2>Logs Page Access Restricted</h2>
+        <p>Please contact your administrator to enable this feature.</p>
+      </div>
+    );
-    return notFound();
   }

Line range hint 44-46: Enhance error handling for ClickHouse errors

The error handling for ClickHouse could be more robust:

   if (logs.err) {
-    throw new Error(`Something went wrong when fetching logs from ClickHouse: ${logs.err.message}`);
+    console.error("[ClickHouse] Error fetching logs:", logs.err);
+    return (
+      <div className="text-center p-4">
+        <h2>Unable to Load Logs</h2>
+        <p>There was an error loading the logs. Please try again later.</p>
+        {process.env.NODE_ENV === "development" && (
+          <pre className="mt-4 p-2 bg-gray-100 rounded">
+            {logs.err.message}
+          </pre>
+        )}
+      </div>
+    );
   }
apps/dashboard/app/(app)/ratelimits/[namespaceId]/overrides/page.tsx (1)

59-61: Consider extracting the default ratelimit override limit as a constant

The hardcoded value of 5 should be extracted into a named constant to improve maintainability and document its purpose.

+const DEFAULT_RATELIMIT_OVERRIDE_LIMIT = 5;
+
 {Intl.NumberFormat().format(
-  hasWorkspaceAccess("ratelimitOverrides", namespace.workspace) ?? 5,
+  hasWorkspaceAccess("ratelimitOverrides", namespace.workspace) ?? DEFAULT_RATELIMIT_OVERRIDE_LIMIT,
 )}
apps/dashboard/app/(app)/apis/[apiId]/settings/page.tsx (1)

Line range hint 51-64: Consider moving database operations to a service layer

The keyAuth size update logic should be moved to a dedicated service layer for better separation of concerns and maintainability. Additionally, consider:

  1. Extracting the cache duration (60_000ms) as a named constant
  2. Adding a lock mechanism to prevent race conditions during concurrent updates

Example service layer implementation:

// services/keyAuth.ts
const KEY_AUTH_SIZE_CACHE_DURATION = 60_000; // 1 minute

export class KeyAuthService {
  static async updateSizeIfStale(keyAuth: KeyAuth): Promise<void> {
    if (keyAuth.sizeLastUpdatedAt >= Date.now() - KEY_AUTH_SIZE_CACHE_DURATION) {
      return;
    }

    // TODO: Implement distributed locking mechanism
    const res = await db
      .select({ count: sql<string>`count(*)` })
      .from(schema.keys)
      .where(and(
        eq(schema.keys.keyAuthId, keyAuth.id),
        isNull(schema.keys.deletedAt)
      ));

    await db
      .update(schema.keyAuth)
      .set({
        sizeApprox: Number.parseInt(res?.at(0)?.count ?? "0"),
        sizeLastUpdatedAt: Date.now(),
      })
      .where(eq(schema.keyAuth.id, keyAuth.id));
  }
}
apps/dashboard/lib/trpc/routers/api/updateIpWhitelist.ts (1)

Line range hint 62-68: Extract support email into configuration

The support email address is hardcoded in error messages. Consider extracting it into a configuration value for easier maintenance.

+const SUPPORT_EMAIL = process.env.SUPPORT_EMAIL ?? "[email protected]";
+
 if (!hasWorkspaceAccess("ipWhitelist", api.workspace)) {
   throw new TRPCError({
     code: "FORBIDDEN",
     message:
-      "IP Whitelisting is only available for enterprise plans. Please contact [email protected].",
+      `IP Whitelisting is only available for enterprise plans. Please contact ${SUPPORT_EMAIL}.`,
   });
 }
apps/dashboard/app/(app)/workspace-navigations.tsx (1)

93-93: Consider memoizing feature access checks

The implementation looks good, but there are multiple calls to hasWorkspaceAccess for different features. Consider memoizing these checks to optimize performance.

Consider creating a memoized helper:

const getFeatureAccess = (workspace: Workspace) => {
  const memo = new Map<string, boolean>();
  return (feature: string) => {
    if (!memo.has(feature)) {
      memo.set(feature, hasWorkspaceAccess(feature, workspace));
    }
    return memo.get(feature)!;
  };
};

Then use it in createWorkspaceNavigation:

const checkAccess = getFeatureAccess(workspace);
// ...
hidden: !checkAccess("webhooks"),
// ...

Also applies to: 100-100, 108-108, 122-122

apps/dashboard/app/(app)/audit/[bucket]/page.tsx (1)

94-94: Consider refactoring the retention days logic for better readability

The current nested nullish coalescing with ternary operator makes the logic hard to follow. Consider breaking it down into more explicit conditions.

Here's a suggested refactor:

-    hasWorkspaceAccess("auditLogRetentionDays", workspace) ?? workspace.plan === "free" ? 30 : 90;
+    const customRetentionDays = hasWorkspaceAccess("auditLogRetentionDays", workspace);
+    if (customRetentionDays !== null) {
+      return customRetentionDays;
+    }
+    return workspace.plan === "free" ? 30 : 90;

This makes it clearer that we:

  1. First check for custom retention days via feature flag
  2. Fall back to plan-based retention days if no custom value is set
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 9a140d7 and a3fcd86.

📒 Files selected for processing (10)
  • apps/dashboard/app/(app)/apis/[apiId]/settings/page.tsx (2 hunks)
  • apps/dashboard/app/(app)/apis/[apiId]/settings/update-ip-whitelist.tsx (3 hunks)
  • apps/dashboard/app/(app)/audit/[bucket]/page.tsx (3 hunks)
  • apps/dashboard/app/(app)/identities/layout.tsx (2 hunks)
  • apps/dashboard/app/(app)/logs/page.tsx (2 hunks)
  • apps/dashboard/app/(app)/ratelimits/[namespaceId]/overrides/page.tsx (2 hunks)
  • apps/dashboard/app/(app)/workspace-navigations.tsx (5 hunks)
  • apps/dashboard/lib/trpc/routers/api/updateIpWhitelist.ts (2 hunks)
  • apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts (2 hunks)
  • apps/dashboard/lib/utils.ts (2 hunks)
🔇 Additional comments (8)
apps/dashboard/lib/utils.ts (1)

28-32: LGTM: Well-structured type definitions

The type definitions are well thought out:

  • WorkspaceFeatures correctly picks only relevant fields
  • ConfigObject union type ensures type safety across features
  • FlagValue generic type provides precise typing for feature values
apps/dashboard/app/(app)/identities/layout.tsx (1)

Line range hint 25-29: LGTM: Clean implementation of feature check

The implementation correctly uses hasWorkspaceAccess and gracefully handles the case when access is not granted by showing the OptIn component.

However, consider adding error handling for the case when workspace is undefined:

+  if (!workspace) {
+    return redirect("/auth/sign-in");
+  }
+
   if (!hasWorkspaceAccess("identities", workspace)) {
     children = (
       <OptIn title="Identities" description="Identities are in beta" feature="identities" />
     );
   }
apps/dashboard/lib/trpc/routers/api/updateIpWhitelist.ts (1)

7-7: LGTM: hasWorkspaceAccess integration

The integration of hasWorkspaceAccess for feature flag checking aligns well with the PR objectives and provides a cleaner way to handle feature access control.

apps/dashboard/app/(app)/apis/[apiId]/settings/update-ip-whitelist.tsx (1)

32-32: Props and feature check implementation looks good!

The changes improve type safety and consistency:

  1. Using the full Workspace type instead of partial type
  2. Simplified component signature without unnecessary React.FC
  3. Consistent feature checking with hasWorkspaceAccess

Also applies to: 41-41, 43-43

apps/dashboard/app/(app)/workspace-navigations.tsx (1)

47-47: Type improvements look good!

The simplified type definitions and component signatures improve code clarity:

  1. Removed unnecessary React.FC
  2. Simplified Workspace type in createWorkspaceNavigation

Also applies to: 59-59

apps/dashboard/app/(app)/audit/[bucket]/page.tsx (3)

8-8: LGTM: Import statement aligns with feature flag implementation

The import of hasWorkspaceAccess is correctly placed and follows the project's import organization pattern.


37-39: LGTM: Type definition formatting improves readability

The reformatted type definition enhances code clarity while maintaining the same functionality.


94-94: Verify consistent feature flag access pattern

Let's verify that other instances of feature access have been updated to use the new hasWorkspaceAccess utility.

✅ Verification successful

Feature flag access pattern is consistently implemented

The codebase shows consistent usage of the hasWorkspaceAccess utility across all feature flag checks. No direct access to workspace.features was found, indicating proper implementation of the feature flag pattern.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for direct feature access patterns that might need updating
rg -g '!{*.test.*,*.spec.*}' -A 2 'workspace\.features\.' 

# Search for existing hasWorkspaceAccess usage for consistency
rg -g '!{*.test.*,*.spec.*}' -A 2 'hasWorkspaceAccess\('

Length of output: 3543

apps/dashboard/lib/utils.ts Outdated Show resolved Hide resolved
apps/dashboard/lib/utils.ts Show resolved Hide resolved
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

🧹 Outside diff range and nitpick comments (4)
apps/dashboard/lib/utils.ts (3)

61-63: Consider using a type-safe development mode bypass

The current type assertion true as FlagValue<T> might hide type mismatches when the actual feature value is expected to be a number or string.

Consider this safer alternative:

-  return true as FlagValue<T>;
+  return (typeof workspace?.features?.[flagName] === 'number' ? 1 : true) as FlagValue<T>;

34-56: Enhance documentation with return type examples

The documentation is comprehensive but could be more explicit about return types for different feature types.

Add examples for numeric and string feature flags:

 * @example
 * ```typescript
 * // Check if workspace has access to logs page
 * if (!flag("logsPage", workspace)) {
 *   return notFound();
 * }
 *
 * // Check if workspace has access to a feature with numeric value
 * const userLimit = flag("userLimit", workspace);
 * if (userLimit === undefined) {
 *   return notFound();
 * }
+* 
+* // Example with string feature value
+* const tier = flag("subscriptionTier", workspace);
+* if (tier === "premium") {
+*   // Enable premium features
+* }
 * ```

69-79: Improve type safety of feature value casting

The type assertions when returning feature values could potentially hide type mismatches.

Consider adding runtime type checking:

-  return betaFeature as FlagValue<T>;
+  const expectedType = typeof workspace.features?.[flagName];
+  if (expectedType !== typeof betaFeature) {
+    console.warn(`Type mismatch for feature ${String(flagName)}: expected ${expectedType}, got ${typeof betaFeature}`);
+  }
+  return betaFeature as FlagValue<T>;
apps/dashboard/app/(app)/workspace-navigations.tsx (1)

59-59: Consider adding type safety for segments parameter

While the workspace type has been improved, the segments parameter could benefit from stronger typing.

Consider adding a union type for valid segments:

-  segments: string[],
+  segments: Array<'apis' | 'ratelimits' | 'authorization' | 'audit' | 'verifications' | 'logs' | 'success' | 'semantic-cache' | 'identities' | 'settings'>,
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between a3fcd86 and c39ebae.

📒 Files selected for processing (9)
  • apps/dashboard/app/(app)/apis/[apiId]/settings/update-ip-whitelist.tsx (3 hunks)
  • apps/dashboard/app/(app)/audit/[bucket]/page.tsx (3 hunks)
  • apps/dashboard/app/(app)/identities/layout.tsx (2 hunks)
  • apps/dashboard/app/(app)/logs/page.tsx (2 hunks)
  • apps/dashboard/app/(app)/ratelimits/[namespaceId]/overrides/page.tsx (3 hunks)
  • apps/dashboard/app/(app)/workspace-navigations.tsx (5 hunks)
  • apps/dashboard/lib/trpc/routers/api/updateIpWhitelist.ts (2 hunks)
  • apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts (2 hunks)
  • apps/dashboard/lib/utils.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (6)
  • apps/dashboard/app/(app)/identities/layout.tsx
  • apps/dashboard/app/(app)/logs/page.tsx
  • apps/dashboard/app/(app)/ratelimits/[namespaceId]/overrides/page.tsx
  • apps/dashboard/lib/trpc/routers/api/updateIpWhitelist.ts
  • apps/dashboard/app/(app)/audit/[bucket]/page.tsx
  • apps/dashboard/app/(app)/apis/[apiId]/settings/update-ip-whitelist.tsx
🧰 Additional context used
📓 Learnings (1)
apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts (1)
Learnt from: ogzhanolguncu
PR: unkeyed/unkey#2707
File: apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts:63-63
Timestamp: 2024-12-05T13:27:55.555Z
Learning: In `apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts`, when determining the maximum number of rate limit overrides (`max`), the intentional use of `const max = hasWorkspaceAccess("ratelimitOverrides", namespace.workspace) || 5;` allows `max` to fall back to `5` when `hasWorkspaceAccess` returns `0` or `false`. This fallback behavior is expected and intended in the codebase.
🔇 Additional comments (3)
apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts (1)

63-63: LGTM! Maintains existing fallback behavior

The change correctly uses the new flag function while preserving the intended fallback behavior where max defaults to 5 when the feature flag returns 0 or false.

apps/dashboard/app/(app)/workspace-navigations.tsx (2)

Line range hint 47-56: LGTM! Modern type definition

The Tag component's type definition has been improved to use a more modern and explicit approach.


Line range hint 93-122: LGTM! Consistent feature flag usage

The implementation consistently uses the new flag function for checking feature availability across all navigation items.

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

🧹 Outside diff range and nitpick comments (2)
apps/dashboard/lib/utils.ts (2)

28-32: Consider more domain-specific type names

While the types are well-structured, consider renaming them to be more domain-specific:

  • WorkspaceFeaturesWorkspaceFeatureConfig
  • ConfigObjectFeatureFlagConfig
  • FlagValueFeatureFlagValue

This would make their purpose more immediately clear to other developers.


34-56: Fix return type documentation

The documentation states "undefined otherwise" but the function returns null. Update the documentation to match the implementation:

- * Returns the feature value if access is granted, undefined otherwise
+ * Returns the feature value if access is granted, null otherwise

Also in the example:

- * if (userLimit === undefined) {
+ * if (userLimit === null) {
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c39ebae and c55257a.

📒 Files selected for processing (9)
  • apps/dashboard/app/(app)/apis/[apiId]/settings/update-ip-whitelist.tsx (3 hunks)
  • apps/dashboard/app/(app)/audit/[bucket]/page.tsx (3 hunks)
  • apps/dashboard/app/(app)/identities/layout.tsx (2 hunks)
  • apps/dashboard/app/(app)/logs/page.tsx (2 hunks)
  • apps/dashboard/app/(app)/ratelimits/[namespaceId]/overrides/page.tsx (3 hunks)
  • apps/dashboard/app/(app)/workspace-navigations.tsx (5 hunks)
  • apps/dashboard/lib/trpc/routers/api/updateIpWhitelist.ts (2 hunks)
  • apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts (2 hunks)
  • apps/dashboard/lib/utils.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (8)
  • apps/dashboard/app/(app)/identities/layout.tsx
  • apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts
  • apps/dashboard/app/(app)/logs/page.tsx
  • apps/dashboard/lib/trpc/routers/api/updateIpWhitelist.ts
  • apps/dashboard/app/(app)/audit/[bucket]/page.tsx
  • apps/dashboard/app/(app)/ratelimits/[namespaceId]/overrides/page.tsx
  • apps/dashboard/app/(app)/apis/[apiId]/settings/update-ip-whitelist.tsx
  • apps/dashboard/app/(app)/workspace-navigations.tsx
🔇 Additional comments (2)
apps/dashboard/lib/utils.ts (2)

4-5: LGTM!

The import is necessary and correctly structured.


57-82: Rename function to match documentation

The function is named getFlag but the documentation examples use flag. Consider renaming for consistency:

-export function getFlag<T extends keyof ConfigObject>(
+export function flag<T extends keyof ConfigObject>(

This matches the examples and provides a more concise API.

apps/dashboard/lib/utils.ts Outdated Show resolved Hide resolved
apps/dashboard/lib/utils.ts Outdated Show resolved Hide resolved
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: 2

🧹 Outside diff range and nitpick comments (1)
apps/dashboard/lib/utils.ts (1)

34-68: Add @throws to JSDoc documentation.

The documentation is comprehensive but should document the error case when no workspace is found.

Add the following to the JSDoc:

 * @returns The feature value (boolean | number | string) based on environment and settings
+* @throws {Error} When no workspace is found in the database
 *
 * @example
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between c55257a and ccc2d6d.

📒 Files selected for processing (10)
  • apps/dashboard/app/(app)/apis/[apiId]/settings/update-ip-whitelist.tsx (3 hunks)
  • apps/dashboard/app/(app)/audit/[bucket]/page.tsx (4 hunks)
  • apps/dashboard/app/(app)/identities/layout.tsx (2 hunks)
  • apps/dashboard/app/(app)/logs/page.tsx (2 hunks)
  • apps/dashboard/app/(app)/ratelimits/[namespaceId]/overrides/page.tsx (2 hunks)
  • apps/dashboard/app/(app)/workspace-navigations.tsx (5 hunks)
  • apps/dashboard/lib/constants.ts (1 hunks)
  • apps/dashboard/lib/trpc/routers/api/updateIpWhitelist.ts (2 hunks)
  • apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts (2 hunks)
  • apps/dashboard/lib/utils.ts (2 hunks)
✅ Files skipped from review due to trivial changes (1)
  • apps/dashboard/lib/constants.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • apps/dashboard/app/(app)/ratelimits/[namespaceId]/overrides/page.tsx
  • apps/dashboard/app/(app)/logs/page.tsx
  • apps/dashboard/app/(app)/identities/layout.tsx
  • apps/dashboard/app/(app)/apis/[apiId]/settings/update-ip-whitelist.tsx
  • apps/dashboard/lib/trpc/routers/api/updateIpWhitelist.ts
  • apps/dashboard/lib/trpc/routers/ratelimit/createOverride.ts
🔇 Additional comments (5)
apps/dashboard/lib/utils.ts (2)

28-32: LGTM! Well-structured type definitions.

The type definitions provide strong type safety through:

  • Proper use of TypeScript's utility types (Pick, NonNullable)
  • Clear separation of concerns between workspace features and configuration

90-98: ⚠️ Potential issue

Add runtime type validation for feature values.

The type assertions (as FlagValue<TFlagName>) could be unsafe if the feature values don't match the expected types.

Consider adding runtime type validation:

+function validateFeatureValue<T>(value: unknown, flagName: string): value is T {
+  // Add type-specific validation based on flagName
+  return true;
+}

 const betaFeature = workspace.betaFeatures?.[flagName as keyof Workspace["betaFeatures"]];
-if (betaFeature !== undefined) {
+if (betaFeature !== undefined && validateFeatureValue<FlagValue<TFlagName>>(betaFeature, flagName)) {
   return betaFeature as FlagValue<TFlagName>;
 }

 const feature = workspace.features?.[flagName as keyof Workspace["features"]];
-if (feature !== undefined) {
+if (feature !== undefined && validateFeatureValue<FlagValue<TFlagName>>(feature, flagName)) {
   return feature as FlagValue<TFlagName>;
 }

Likely invalid or redundant comment.

apps/dashboard/app/(app)/workspace-navigations.tsx (3)

16-16: LGTM: Import of getFlag utility

The addition of the getFlag utility import aligns with the PR objectives to implement type-safe feature flag checks.


Line range hint 47-56: LGTM: Improved type safety in Tag component

The change from React.FC to inline type definition is a good practice as it:

  • Makes the component props more explicit
  • Removes the implicit children prop that wasn't being used

59-59: Verify workspace type change impact

The simplified type Workspace & { llmGateways: { id: string }[] } might expose more workspace fields than necessary. Consider using a more specific type that only includes the required fields.

✅ Verification successful

Type change is safe to use

Based on the codebase analysis, the simplified type Workspace & { llmGateways: { id: string }[] } is appropriate for this context because:

  1. The type is used specifically for navigation creation where only feature flags and llmGateways are accessed
  2. The intersection type maintains type safety by explicitly declaring the additional llmGateways requirement
  3. The usage in the function shows controlled access to workspace properties through the getFlag utility
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for Workspace type definition to understand exposed fields
ast-grep --pattern 'type Workspace = {
  $$$
}'

Length of output: 49


Script:

#!/bin/bash
# Search for Workspace type/interface definition with different patterns
ast-grep --pattern 'interface Workspace {
  $$$
}'

# Also search for potential type imports
rg -l "Workspace.*from.*@" 

# Search for Workspace type usage patterns
rg "type.*Workspace" -A 2

Length of output: 4019


Script:

#!/bin/bash
# Let's check the actual Workspace type definition in internal/db
cat internal/db/src/types.ts

# Let's also check the schema definition
cat internal/db/src/schema.ts

# Check the actual usage context
cat apps/dashboard/app/\(app\)/workspace-navigations.tsx

Length of output: 6809


Script:

#!/bin/bash
# Let's check the schema definition in a different location since it wasn't found
fd schema.ts

# And then read the content of any relevant schema files
cat internal/db/schema.ts

Length of output: 298

apps/dashboard/app/(app)/audit/[bucket]/page.tsx Outdated Show resolved Hide resolved
Copy link
Collaborator

@chronark chronark left a comment

Choose a reason for hiding this comment

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

most of the flags are inverted now, which actually breaks our behavior, I didn't comment on all of them

@@ -21,7 +22,7 @@ export default async function AuthorizationLayout({
return redirect("/auth/sign-in");
}

if (!workspace.betaFeatures.identities) {
if (getFlag(workspace, "identities", { prodFallback: true, devFallback: false })) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

you flipped it around, so when they had it set to true in our database, we would actually deny them access

Suggested change
if (getFlag(workspace, "identities", { prodFallback: true, devFallback: false })) {
if (!getFlag(workspace, "identities", { prodFallback: false, devFallback: true })) {

@@ -28,7 +29,7 @@ export default async function Page({
return <div>Workspace with tenantId: {tenantId} not found</div>;
}

if (!workspace.betaFeatures.logsPage) {
if (getFlag(workspace, "logsPage", { devFallback: false, prodFallback: true })) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
if (getFlag(workspace, "logsPage", { devFallback: false, prodFallback: true })) {
if (!getFlag(workspace, "logsPage", { devFallback: true, prodFallback: false })) {

@@ -91,22 +90,31 @@ export const createWorkspaceNavigation = (
href: "/monitors/verifications",
label: "Monitors",
active: segments.at(0) === "verifications",
hidden: !workspace.features.webhooks,
hidden: getFlag(workspace, "webhooks", {
Copy link
Collaborator

Choose a reason for hiding this comment

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

that's reversed too
it's too confusing this way I think
in the db I'd set it to { "webhooks": true } if I want to give someone access, but now that would revoke access...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Feature New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add a way to check feature flags with some type-safety
2 participants