fix: resolve entitlement check on ai bridge settings/view (#22385)

Resolves cases where the user is entitled to AI Governance but we don't
show them the page because its not enabled. If for some reason the user
doesn't have AI Bridge enabled anymore but still wants to access the old
logs page they now can.

Furthermore, we link to the docs regardless of if they have AI Bridge
enabled, this is inline with our other settings pages.
This commit is contained in:
Jake Howell
2026-02-28 03:25:04 +11:00
committed by GitHub
parent 6f3385d5e4
commit bcb5b43aa7
6 changed files with 39 additions and 34 deletions
@@ -3,7 +3,7 @@ import { useFilter } from "components/Filter/Filter";
import { useUserFilterMenu } from "components/Filter/UserFilter";
import { useAuthenticated } from "hooks";
import { usePaginatedQuery } from "hooks/usePaginatedQuery";
import { useFeatureVisibility } from "modules/dashboard/useFeatureVisibility";
import { useDashboard } from "modules/dashboard/useDashboard";
import { RequirePermission } from "modules/permissions/RequirePermission";
import type { FC } from "react";
import { useSearchParams } from "react-router";
@@ -13,14 +13,16 @@ import { useProviderFilterMenu } from "./RequestLogsFilter/ProviderFilter";
import { RequestLogsPageView } from "./RequestLogsPageView";
const RequestLogsPage: FC = () => {
const feats = useFeatureVisibility();
const { permissions } = useAuthenticated();
const { entitlements } = useDashboard();
// Users are allowed to view their own request logs via the API,
// but this page is only visible if the feature is enabled and the user
// has the `viewAnyAIBridgeInterception` permission.
// (as its defined in the Admin settings dropdown).
const isEntitled = Boolean(feats.aibridge);
const isEntitled =
entitlements.features.aibridge.entitlement === "entitled" ||
entitlements.features.aibridge.entitlement === "grace_period";
const hasPermission = permissions.viewAnyAIBridgeInterception;
const canViewRequestLogs = isEntitled && hasPermission;
@@ -68,7 +70,7 @@ const RequestLogsPage: FC = () => {
<RequestLogsPageView
isLoading={interceptionsQuery.isLoading}
isRequestLogsVisible={isEntitled}
isRequestLogsEntitled={isEntitled}
interceptions={interceptionsQuery.data?.results}
interceptionsQuery={interceptionsQuery}
filterProps={{
@@ -47,13 +47,13 @@ type Story = StoryObj<typeof RequestLogsPageView>;
export const Paywall: Story = {
args: {
isRequestLogsVisible: false,
isRequestLogsEntitled: false,
},
};
export const Loaded: Story = {
args: {
isRequestLogsVisible: true,
isRequestLogsEntitled: true,
interceptions,
filterProps: {
...defaultFilterProps,
@@ -64,7 +64,7 @@ export const Loaded: Story = {
export const Empty: Story = {
args: {
isRequestLogsVisible: true,
isRequestLogsEntitled: true,
interceptions: [],
filterProps: {
...defaultFilterProps,
@@ -76,7 +76,7 @@ export const Empty: Story = {
export const Loading: Story = {
args: {
isLoading: true,
isRequestLogsVisible: true,
isRequestLogsEntitled: true,
interceptions: [],
filterProps: {
...defaultFilterProps,
@@ -19,7 +19,7 @@ import { RequestLogsRow } from "./RequestLogsRow/RequestLogsRow";
interface RequestLogsPageViewProps {
isLoading: boolean;
isRequestLogsVisible: boolean;
isRequestLogsEntitled: boolean;
interceptions?: readonly AIBridgeInterception[];
interceptionsQuery: PaginationResult;
filterProps: ComponentProps<typeof RequestLogsFilter>;
@@ -27,12 +27,12 @@ interface RequestLogsPageViewProps {
export const RequestLogsPageView: FC<RequestLogsPageViewProps> = ({
isLoading,
isRequestLogsVisible,
isRequestLogsEntitled,
interceptions,
interceptionsQuery,
filterProps,
}) => {
if (!isRequestLogsVisible) {
if (!isRequestLogsEntitled) {
return <PaywallAIGovernance />;
}
@@ -14,7 +14,10 @@ const AIGovernanceSettingsPage: FC = () => {
<AIGovernanceSettingsPageView
options={deploymentConfig.options}
featureAIBridgeEnabled={entitlements.features.aibridge.enabled}
featureAIBridgeEntitled={
entitlements.features.aibridge.entitlement === "entitled" ||
entitlements.features.aibridge.entitlement === "grace_period"
}
/>
</>
);
@@ -23,7 +23,7 @@ const meta: Meta<typeof AIGovernanceSettingsPageView> = {
hidden: false,
},
],
featureAIBridgeEnabled: true,
featureAIBridgeEntitled: true,
},
};
@@ -34,7 +34,7 @@ export const Page: Story = {};
export const Paywall: Story = {
args: {
featureAIBridgeEnabled: false,
featureAIBridgeEntitled: false,
options: [],
},
};
@@ -14,42 +14,42 @@ import OptionsTable from "../OptionsTable";
type AIGovernanceSettingsPageViewProps = {
options: SerpentOption[];
featureAIBridgeEnabled: boolean;
featureAIBridgeEntitled: boolean;
};
export const AIGovernanceSettingsPageView: FC<
AIGovernanceSettingsPageViewProps
> = ({ options, featureAIBridgeEnabled }) => {
> = ({ options, featureAIBridgeEntitled }) => {
return (
<Stack direction="column" spacing={6}>
<SettingsHeader>
<SettingsHeaderTitle>AI Governance</SettingsHeaderTitle>
</SettingsHeader>
{featureAIBridgeEnabled ? (
<div>
<SettingsHeader
actions={
<SettingsHeaderDocsLink href={docs("/ai-coder/ai-bridge")} />
}
>
<SettingsHeaderTitle hierarchy="secondary" level="h2">
AI Bridge
</SettingsHeaderTitle>
<SettingsHeaderDescription>
Monitor and manage AI requests across your deployment.
</SettingsHeaderDescription>
</SettingsHeader>
<div>
<SettingsHeader
actions={
<SettingsHeaderDocsLink href={docs("/ai-coder/ai-bridge")} />
}
>
<SettingsHeaderTitle hierarchy="secondary" level="h2">
AI Bridge
</SettingsHeaderTitle>
<SettingsHeaderDescription>
Monitor and manage AI requests across your deployment.
</SettingsHeaderDescription>
</SettingsHeader>
{featureAIBridgeEntitled ? (
<OptionsTable
options={options
.filter((o) => deploymentGroupHasParent(o.group, "AI Bridge"))
.filter((o) => !o.annotations?.secret === true)}
/>
</div>
) : (
<PaywallAIGovernance />
)}
) : (
<PaywallAIGovernance />
)}
</div>
</Stack>
);
};