Skip to content

Commit 9c31921

Browse files
andriidudarclaude
andauthored
[OPIK-5165] [FE] feat: v2 alerts page — project-scoped API integration (#5913)
* [OPIK-5165] [FE] feat: v2 alerts page — project-scoped API integration - Create useProjectAlertsList hook for GET /projects/{projectId}/alerts - Add project_id to Alert type - Switch AlertsPage to project-scoped list endpoint - Pass project_id in alert create/update payload - Remove ProjectsSelectBox from EventTriggers (project is always active project) - Remove scope:project trigger config building (BE auto-creates from project_id) - Remove useProjectsSelectData from AddEditAlertPage - Scope feedback score names dropdown to alert's project via entityIds - Add project-alerts cache invalidation to all alert mutation hooks Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(ExperimentsTab): pass projectId to experiment hooks in PromptPage Resolve TODOs left from OPIK-4969 — now that OPIK-4968 landed, pass activeProjectId to useExperimentsFeedbackScores and useGroupedExperimentsList in the prompt detail ExperimentsTab. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent fefa6e6 commit 9c31921

File tree

14 files changed

+99
-117
lines changed

14 files changed

+99
-117
lines changed

apps/opik-frontend/src/api/alerts/useAlertCreateMutation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ const useAlertCreateMutation = () => {
3434
});
3535
},
3636
onSettled: () => {
37+
queryClient.invalidateQueries({ queryKey: ["project-alerts"] });
3738
return queryClient.invalidateQueries({
3839
queryKey: [ALERTS_KEY],
3940
});

apps/opik-frontend/src/api/alerts/useAlertUpdateMutation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ const useAlertUpdateMutation = () => {
3737
});
3838
},
3939
onSettled: () => {
40+
queryClient.invalidateQueries({ queryKey: ["project-alerts"] });
4041
return queryClient.invalidateQueries({
4142
queryKey: [ALERTS_KEY],
4243
});

apps/opik-frontend/src/api/alerts/useAlertsBatchDeleteMutation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const useAlertsBatchDeleteMutation = () => {
3232
});
3333
},
3434
onSettled: () => {
35+
queryClient.invalidateQueries({ queryKey: ["project-alerts"] });
3536
return queryClient.invalidateQueries({
3637
queryKey: [ALERTS_KEY],
3738
});
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { QueryFunctionContext, useQuery } from "@tanstack/react-query";
2+
import api, { PROJECTS_REST_ENDPOINT, QueryConfig } from "@/api/api";
3+
import { AlertsListResponse } from "@/types/alerts";
4+
import { Sorting } from "@/types/sorting";
5+
import { Filter } from "@/types/filters";
6+
import { processSorting } from "@/lib/sorting";
7+
import { generateSearchByFieldFilters, processFilters } from "@/lib/filters";
8+
9+
type UseProjectAlertsListParams = {
10+
projectId: string;
11+
search?: string;
12+
filters?: Filter[];
13+
sorting?: Sorting;
14+
page: number;
15+
size: number;
16+
};
17+
18+
const getProjectAlertsList = async (
19+
{ signal }: QueryFunctionContext,
20+
{
21+
projectId,
22+
search,
23+
filters,
24+
sorting,
25+
size,
26+
page,
27+
}: UseProjectAlertsListParams,
28+
) => {
29+
const { data } = await api.get<AlertsListResponse>(
30+
`${PROJECTS_REST_ENDPOINT}${projectId}/alerts`,
31+
{
32+
signal,
33+
params: {
34+
...processFilters(
35+
filters,
36+
generateSearchByFieldFilters("name", search),
37+
),
38+
...processSorting(sorting),
39+
size,
40+
page,
41+
},
42+
},
43+
);
44+
45+
return data;
46+
};
47+
48+
export default function useProjectAlertsList(
49+
params: UseProjectAlertsListParams,
50+
options?: QueryConfig<AlertsListResponse>,
51+
) {
52+
return useQuery({
53+
queryKey: ["project-alerts", params],
54+
queryFn: (context) => getProjectAlertsList(context, params),
55+
...options,
56+
});
57+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ export interface AlertMetadata {
6565
export interface Alert {
6666
id?: string;
6767
name: string;
68+
project_id?: string;
6869
enabled: boolean;
6970
alert_type?: ALERT_TYPE;
7071
metadata?: AlertMetadata;

apps/opik-frontend/src/v2/layout/SideBar/ProjectSelector/ProjectSelector.tsx

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,6 @@ const ProjectSelector: React.FC = () => {
6868

6969
const handleSelect = useCallback(
7070
(projectId: string) => {
71-
setActiveProject(workspaceName, projectId);
7271
setOpen(false);
7372
setSearch("");
7473
navigate({
Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import React, { useEffect, useMemo } from "react";
1+
import React, { useEffect } from "react";
22
import { useParams } from "@tanstack/react-router";
33
import Loader from "@/shared/Loader/Loader";
44
import useAlertById from "@/api/alerts/useAlertById";
55
import useBreadcrumbsStore from "@/store/BreadcrumbsStore";
66
import AlertForm from "./AlertForm";
7-
import { useProjectsSelectData } from "@/v2/pages-shared/automations/ProjectsSelectBox";
87

98
const AddEditAlertPage: React.FunctionComponent = () => {
109
const { alertId } = useParams({ strict: false });
@@ -17,23 +16,17 @@ const AddEditAlertPage: React.FunctionComponent = () => {
1716
{ enabled: isEdit },
1817
);
1918

20-
const { projects, isLoading } = useProjectsSelectData({});
21-
2219
useEffect(() => {
2320
if (isEdit && alert?.name) {
2421
setBreadcrumbParam("alertId", alertId!, alert.name);
2522
}
2623
}, [isEdit, alertId, alert?.name, setBreadcrumbParam]);
2724

28-
const projectIds = useMemo(() => projects.map((p) => p.id), [projects]);
29-
30-
if ((isEdit && isPending) || isLoading) {
25+
if (isEdit && isPending) {
3126
return <Loader />;
3227
}
3328

34-
return (
35-
<AlertForm alert={isEdit ? alert : undefined} projectsIds={projectIds} />
36-
);
29+
return <AlertForm alert={isEdit ? alert : undefined} />;
3730
};
3831

3932
export default AddEditAlertPage;

apps/opik-frontend/src/v2/pages/AlertsPage/AddEditAlertPage/AlertForm.tsx

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,9 @@ import {
3131

3232
type AlertFormProps = {
3333
alert?: Alert;
34-
projectsIds: string[];
3534
};
3635

37-
const AlertForm: React.FunctionComponent<AlertFormProps> = ({
38-
alert,
39-
projectsIds,
40-
}) => {
36+
const AlertForm: React.FunctionComponent<AlertFormProps> = ({ alert }) => {
4137
const navigate = useNavigate();
4238
const workspaceName = useAppStore((state) => state.activeWorkspaceName);
4339
const activeProjectId = useActiveProjectId();
@@ -65,7 +61,7 @@ const AlertForm: React.FunctionComponent<AlertFormProps> = ({
6561
value,
6662
}))
6763
: [],
68-
triggers: alertTriggersToFormTriggers(alert?.triggers ?? [], projectsIds),
64+
triggers: alertTriggersToFormTriggers(alert?.triggers ?? []),
6965
},
7066
});
7167

@@ -76,6 +72,7 @@ const AlertForm: React.FunctionComponent<AlertFormProps> = ({
7672
name: formData.name.trim(),
7773
enabled: formData.enabled,
7874
alert_type: formData.alertType,
75+
project_id: activeProjectId ?? undefined,
7976
metadata: {
8077
...alert?.metadata,
8178
base_url: buildFullBaseUrl(),
@@ -99,9 +96,9 @@ const AlertForm: React.FunctionComponent<AlertFormProps> = ({
9996
)
10097
: undefined,
10198
},
102-
triggers: formTriggersToAlertTriggers(formData.triggers, projectsIds),
99+
triggers: formTriggersToAlertTriggers(formData.triggers),
103100
};
104-
}, [form, projectsIds, alert?.metadata]);
101+
}, [form, activeProjectId, alert?.metadata]);
105102

106103
const handleNavigateBack = useCallback(() => {
107104
navigate({
@@ -239,7 +236,10 @@ const AlertForm: React.FunctionComponent<AlertFormProps> = ({
239236

240237
<WebhookSettings form={form} />
241238

242-
<EventTriggers form={form} projectsIds={projectsIds} />
239+
<EventTriggers
240+
form={form}
241+
projectId={alert?.project_id || activeProjectId!}
242+
/>
243243

244244
<Separator className="lg:hidden" />
245245

apps/opik-frontend/src/v2/pages/AlertsPage/AddEditAlertPage/EventTriggers.tsx

Lines changed: 3 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import { Popover, PopoverContent, PopoverTrigger } from "@/ui/popover";
1919
import { Separator } from "@/ui/separator";
2020
import { Input } from "@/ui/input";
2121
import SelectBox from "@/shared/SelectBox/SelectBox";
22-
import ProjectsSelectBox from "@/v2/pages-shared/automations/ProjectsSelectBox";
2322
import { DropdownOption } from "@/types/shared";
2423
import { AlertFormType } from "./schema";
2524
import { TRIGGER_CONFIG } from "./helpers";
@@ -33,7 +32,7 @@ import FeedbackScoreConditions, {
3332

3433
type EventTriggersProps = {
3534
form: UseFormReturn<AlertFormType>;
36-
projectsIds: string[];
35+
projectId: string;
3736
};
3837

3938
const WINDOW_OPTIONS: DropdownOption<string>[] = [
@@ -77,7 +76,7 @@ function getThresholdPlaceholder(eventType: ALERT_EVENT_TYPE): string {
7776

7877
const EventTriggers: React.FunctionComponent<EventTriggersProps> = ({
7978
form,
80-
projectsIds,
79+
projectId,
8180
}) => {
8281
const triggersError = form.formState.errors.triggers;
8382
const isGuardrailsEnabled = useIsFeatureEnabled(
@@ -103,7 +102,6 @@ const EventTriggers: React.FunctionComponent<EventTriggersProps> = ({
103102

104103
append({
105104
eventType,
106-
projectIds: projectsIds,
107105
...(isFeedbackScoreTrigger
108106
? {
109107
conditions: [DEFAULT_FEEDBACK_SCORE_CONDITION],
@@ -202,6 +200,7 @@ const EventTriggers: React.FunctionComponent<EventTriggersProps> = ({
202200
form={form}
203201
triggerIndex={index}
204202
eventType={eventType}
203+
projectId={projectId}
205204
/>
206205
);
207206
};
@@ -333,27 +332,6 @@ const EventTriggers: React.FunctionComponent<EventTriggersProps> = ({
333332
</Label>
334333
<Description>{config.description}</Description>
335334
</div>
336-
337-
{config.hasScope && (
338-
<FormField
339-
control={form.control}
340-
name={
341-
`triggers.${index}.projectIds` as Path<AlertFormType>
342-
}
343-
render={({ field }) => (
344-
<FormItem className="justify-center">
345-
<ProjectsSelectBox
346-
value={field.value as string[]}
347-
onValueChange={field.onChange}
348-
multiselect={true}
349-
className="h-8 w-40"
350-
showSelectAll={true}
351-
minWidth={204}
352-
/>
353-
</FormItem>
354-
)}
355-
/>
356-
)}
357335
</div>
358336
{isThresholdTrigger &&
359337
renderThresholdConfig(index, field.eventType)}

apps/opik-frontend/src/v2/pages/AlertsPage/AddEditAlertPage/FeedbackScoreConditions.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ type FeedbackScoreConditionsProps = {
2020
form: UseFormReturn<AlertFormType>;
2121
triggerIndex: number;
2222
eventType: ALERT_EVENT_TYPE;
23+
projectId: string;
2324
};
2425

2526
export const DEFAULT_FEEDBACK_SCORE_CONDITION: FeedbackScoreConditionType = {
@@ -51,6 +52,7 @@ const FeedbackScoreConditions: React.FC<FeedbackScoreConditionsProps> = ({
5152
form,
5253
triggerIndex,
5354
eventType,
55+
projectId,
5456
}) => {
5557
const conditionsFieldArray = useFieldArray({
5658
control: form.control,
@@ -141,6 +143,7 @@ const FeedbackScoreConditions: React.FC<FeedbackScoreConditionsProps> = ({
141143
value={field.value as string}
142144
onChange={field.onChange}
143145
scoreSource={scoreSource}
146+
entityIds={[projectId]}
144147
multiselect={false}
145148
className={cn("h-8 rounded-r-none", {
146149
"border-destructive": Boolean(

0 commit comments

Comments
 (0)