Skip to content

Commit e1a7fd7

Browse files
anastasiapyzhikthiagohora
authored andcommitted
[OPIK-4721] Use playground permission (#5922)
* [OPIK-4721] add new permission * [OPIK-4721] add page guards * [OPIK-4721] hide playground in sidebar menu * [OPIK-4721] hide playground CTAs * [OPIK-4721] guard confirmation dialog
1 parent 098a3bc commit e1a7fd7

18 files changed

Lines changed: 216 additions & 87 deletions

File tree

apps/opik-frontend/src/plugins/comet/PermissionsProvider.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ const PermissionsProvider: React.FC<{ children: ReactNode }> = ({
3333
canUpdateAlerts,
3434
canAnnotateTraceSpanThread,
3535
canTagTrace,
36+
canUsePlayground,
3637
isPending,
3738
} = useUserPermission();
3839

@@ -65,6 +66,7 @@ const PermissionsProvider: React.FC<{ children: ReactNode }> = ({
6566
canUpdateAlerts,
6667
canAnnotateTraceSpanThread,
6768
canTagTrace,
69+
canUsePlayground,
6870
},
6971
isPending,
7072
}),
@@ -95,6 +97,7 @@ const PermissionsProvider: React.FC<{ children: ReactNode }> = ({
9597
canUpdateAlerts,
9698
canAnnotateTraceSpanThread,
9799
canTagTrace,
100+
canUsePlayground,
98101
isPending,
99102
],
100103
);

apps/opik-frontend/src/plugins/comet/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ export enum ManagementPermissionsNames {
8787
COMMENT_WRITE = "comment_write",
8888
ONLINE_EVALUATION_RULE_UPDATE = "online_evaluation_rule_update",
8989
ALERT_UPDATE = "alert_update",
90+
PLAYGROUND_USE = "playground_use",
9091
}
9192

9293
export interface UserPermission {

apps/opik-frontend/src/plugins/comet/useUserPermission.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,11 @@ const useUserPermission = (config?: { enabled?: boolean }) => {
228228
[checkNullablePermission],
229229
);
230230

231+
const canUsePlayground = useMemo(
232+
() => checkNullablePermission(ManagementPermissionsNames.PLAYGROUND_USE),
233+
[checkNullablePermission],
234+
);
235+
231236
return {
232237
canInviteMembers,
233238
isWorkspaceOwner,
@@ -257,6 +262,7 @@ const useUserPermission = (config?: { enabled?: boolean }) => {
257262
canUpdateAlerts,
258263
canAnnotateTraceSpanThread,
259264
canTagTrace,
265+
canUsePlayground,
260266
isPending: isEnabled && isPending,
261267
};
262268
};

apps/opik-frontend/src/types/permissions.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export interface Permissions {
2525
canUpdateAlerts: boolean;
2626
canAnnotateTraceSpanThread: boolean;
2727
canTagTrace: boolean;
28+
canUsePlayground: boolean;
2829
}
2930

3031
export interface PermissionsContextValue {
@@ -60,6 +61,7 @@ export const DEFAULT_PERMISSIONS: PermissionsContextValue = {
6061
canUpdateAlerts: true,
6162
canAnnotateTraceSpanThread: true,
6263
canTagTrace: true,
64+
canUsePlayground: true,
6365
},
6466
isPending: false,
6567
};
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { usePermissions } from "@/contexts/PermissionsContext";
2+
import NoAccessPageGuard from "@/v1/layout/NoAccessPageGuard/NoAccessPageGuard";
3+
4+
const PlaygroundPageGuard = () => {
5+
const {
6+
permissions: { canUsePlayground },
7+
} = usePermissions();
8+
9+
return (
10+
<NoAccessPageGuard
11+
canViewPage={canUsePlayground}
12+
message="You don't have permissions to use playground in this workspace."
13+
/>
14+
);
15+
};
16+
17+
export default PlaygroundPageGuard;

apps/opik-frontend/src/v1/layout/SideBar/SideBarMenuItems.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,19 @@ interface SideBarMenuItemsProps {
2727
const SideBarMenuItems: React.FC<SideBarMenuItemsProps> = ({ expanded }) => {
2828
const { activeWorkspaceName: workspaceName } = useAppStore();
2929
const {
30-
permissions: { canViewExperiments, canViewDashboards, canViewDatasets },
30+
permissions: {
31+
canViewExperiments,
32+
canViewDashboards,
33+
canViewDatasets,
34+
canUsePlayground,
35+
},
3136
} = usePermissions();
3237

3338
const menuItems = getMenuItems({
3439
canViewExperiments,
3540
canViewDashboards,
3641
canViewDatasets,
42+
canUsePlayground,
3743
});
3844

3945
const { data: projectData } = useProjectsList(

apps/opik-frontend/src/v1/layout/SideBar/helpers/getMenuItems.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,12 @@ const getMenuItems = ({
2121
canViewExperiments,
2222
canViewDashboards,
2323
canViewDatasets,
24+
canUsePlayground,
2425
}: {
2526
canViewExperiments: boolean;
2627
canViewDashboards: boolean;
2728
canViewDatasets: boolean;
29+
canUsePlayground: boolean;
2830
}): MenuItemGroup[] => {
2931
return [
3032
{
@@ -115,13 +117,17 @@ const getMenuItems = ({
115117
label: "Prompt library",
116118
count: "prompts",
117119
},
118-
{
119-
id: "playground",
120-
path: "/$workspaceName/playground",
121-
type: MENU_ITEM_TYPE.router,
122-
icon: Blocks,
123-
label: "Playground",
124-
},
120+
...(canUsePlayground
121+
? [
122+
{
123+
id: "playground",
124+
path: "/$workspaceName/playground",
125+
type: MENU_ITEM_TYPE.router as const,
126+
icon: Blocks,
127+
label: "Playground",
128+
},
129+
]
130+
: []),
125131
],
126132
},
127133
{

apps/opik-frontend/src/v1/pages-shared/traces/TraceDetailsPanel/TraceDataViewer/PromptsTab.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import get from "lodash/get";
1212
import { FileTerminal, GitCommitVertical } from "lucide-react";
1313
import useAppStore from "@/store/AppStore";
1414
import { useIsFeatureEnabled } from "@/contexts/feature-toggles-provider";
15+
import { usePermissions } from "@/contexts/PermissionsContext";
1516
import { FeatureToggleKeys } from "@/types/feature-toggles";
1617
import TryInPlaygroundButton from "@/v1/pages/PromptPage/TryInPlaygroundButton";
1718
import PromptContentView, {
@@ -71,6 +72,10 @@ const PromptsTab: React.FunctionComponent<PromptsTabProps> = ({
7172
data,
7273
search,
7374
}) => {
75+
const {
76+
permissions: { canUsePlayground },
77+
} = usePermissions();
78+
7479
const rawPrompts = get(
7580
data.metadata as Record<string, unknown>,
7681
"opik_prompts",
@@ -129,10 +134,12 @@ const PromptsTab: React.FunctionComponent<PromptsTabProps> = ({
129134
search={search}
130135
templateStructure={rawPrompts[index]?.template_structure}
131136
playgroundButton={
132-
<TryInPlaygroundButton
133-
prompt={promptInfo}
134-
ButtonComponent={CustomUseInPlaygroundButton}
135-
/>
137+
canUsePlayground ? (
138+
<TryInPlaygroundButton
139+
prompt={promptInfo}
140+
ButtonComponent={CustomUseInPlaygroundButton}
141+
/>
142+
) : null
136143
}
137144
/>
138145
</AccordionContent>

apps/opik-frontend/src/v1/pages/EvaluationSuiteItemsPage/UseEvaluationSuiteDropdown.tsx

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,11 @@ function UseEvaluationSuiteDropdown({
3333
const [openConfirmDialog, setOpenConfirmDialog] = useState(false);
3434

3535
const {
36-
permissions: { canViewExperiments, canCreateExperiments },
36+
permissions: { canViewExperiments, canCreateExperiments, canUsePlayground },
3737
} = usePermissions();
3838

39+
const hasAnyAction = canUsePlayground || canCreateExperiments;
40+
3941
const { loadPlayground, isPlaygroundEmpty, isPendingProviderKeys } =
4042
useLoadPlayground();
4143

@@ -55,6 +57,8 @@ function UseEvaluationSuiteDropdown({
5557
}
5658
};
5759

60+
if (!hasAnyAction) return null;
61+
5862
return (
5963
<>
6064
{canViewExperiments && (
@@ -65,19 +69,21 @@ function UseEvaluationSuiteDropdown({
6569
datasetName={datasetName}
6670
/>
6771
)}
68-
<ConfirmDialog
69-
key={`confirm-dialog-${resetKeyRef.current}`}
70-
open={openConfirmDialog}
71-
setOpen={setOpenConfirmDialog}
72-
onConfirm={handleLoadPlayground}
73-
title={`Load ${
74-
isEvalSuite ? "evaluation suite" : "dataset"
75-
} into playground`}
76-
description={`Loading this ${
77-
isEvalSuite ? "evaluation suite" : "dataset"
78-
} into the Playground will replace any unsaved changes. This action cannot be undone.`}
79-
confirmText={`Load ${isEvalSuite ? "evaluation suite" : "dataset"}`}
80-
/>
72+
{canUsePlayground && (
73+
<ConfirmDialog
74+
key={`confirm-dialog-${resetKeyRef.current}`}
75+
open={openConfirmDialog}
76+
setOpen={setOpenConfirmDialog}
77+
onConfirm={handleLoadPlayground}
78+
title={`Load ${
79+
isEvalSuite ? "evaluation suite" : "dataset"
80+
} into playground`}
81+
description={`Loading this ${
82+
isEvalSuite ? "evaluation suite" : "dataset"
83+
} into the Playground will replace any unsaved changes. This action cannot be undone.`}
84+
confirmText={`Load ${isEvalSuite ? "evaluation suite" : "dataset"}`}
85+
/>
86+
)}
8187
<DropdownMenu>
8288
<DropdownMenuTrigger asChild>
8389
<Button variant="outline" size="sm" disabled={disabled}>
@@ -86,20 +92,22 @@ function UseEvaluationSuiteDropdown({
8692
</Button>
8793
</DropdownMenuTrigger>
8894
<DropdownMenuContent align="end" className="w-80">
89-
<DropdownMenuItem
90-
onClick={handleOpenPlaygroundClick}
91-
disabled={disabled || isPendingProviderKeys}
92-
>
93-
<Blocks className="mr-2 mt-0.5 size-4 shrink-0 self-start" />
94-
<div className="comet-body-s flex flex-col">
95-
<span>Open in Playground</span>
96-
<span className="text-light-slate">
97-
Test prompts over your{" "}
98-
{isEvalSuite ? "evaluation suite" : "dataset"} and run
99-
evaluations interactively
100-
</span>
101-
</div>
102-
</DropdownMenuItem>
95+
{canUsePlayground && (
96+
<DropdownMenuItem
97+
onClick={handleOpenPlaygroundClick}
98+
disabled={disabled || isPendingProviderKeys}
99+
>
100+
<Blocks className="mr-2 mt-0.5 size-4 shrink-0 self-start" />
101+
<div className="comet-body-s flex flex-col">
102+
<span>Open in Playground</span>
103+
<span className="text-light-slate">
104+
Test prompts over your{" "}
105+
{isEvalSuite ? "evaluation suite" : "dataset"} and run
106+
evaluations interactively
107+
</span>
108+
</div>
109+
</DropdownMenuItem>
110+
)}
103111
{canCreateExperiments && (
104112
<DropdownMenuItem
105113
onClick={() => {

apps/opik-frontend/src/v1/pages/PromptPage/PromptTab/PromptTab.tsx

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,12 +26,17 @@ import ChatPromptView from "./ChatPromptView";
2626
import TextPromptView from "./TextPromptView";
2727
import TagListRenderer from "@/shared/TagListRenderer/TagListRenderer";
2828
import usePromptVersionsUpdateMutation from "@/api/prompts/usePromptVersionsUpdateMutation";
29+
import { usePermissions } from "@/contexts/PermissionsContext";
2930

3031
interface PromptTabInterface {
3132
prompt?: PromptWithLatestVersion;
3233
}
3334

3435
const PromptTab = ({ prompt }: PromptTabInterface) => {
36+
const {
37+
permissions: { canUsePlayground },
38+
} = usePermissions();
39+
3540
const [openUseThisPrompt, setOpenUseThisPrompt] = useState(false);
3641
const [openEditPrompt, setOpenEditPrompt] = useState(false);
3742
const [versionToRestore, setVersionToRestore] =
@@ -120,8 +125,13 @@ const PromptTab = ({ prompt }: PromptTabInterface) => {
120125
<Info className="mr-1.5 size-3.5" />
121126
Use this prompt
122127
</Button>
123-
<TryInPlaygroundButton prompt={prompt} activeVersion={activeVersion} />
124-
{!isChatPrompt && (
128+
{canUsePlayground && (
129+
<TryInPlaygroundButton
130+
prompt={prompt}
131+
activeVersion={activeVersion}
132+
/>
133+
)}
134+
{canUsePlayground && !isChatPrompt && (
125135
<ImproveInPlaygroundButton
126136
prompt={prompt}
127137
activeVersion={activeVersion}

0 commit comments

Comments
 (0)