Skip to content

Commit f187039

Browse files
authored
Merge pull request #96 from lmnr-ai/dev
Dev
2 parents 08b8ecf + fceb4fe commit f187039

File tree

24 files changed

+556
-418
lines changed

24 files changed

+556
-418
lines changed

app-server/src/routes/datasets.rs

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -215,16 +215,6 @@ async fn update_datapoint_data(
215215
)
216216
.await?;
217217

218-
semantic_search
219-
.delete_embeddings(
220-
&project_id.to_string(),
221-
vec![HashMap::from([(
222-
"id".to_string(),
223-
datapoint_id.to_string(),
224-
)])],
225-
)
226-
.await?;
227-
228218
let dataset = db::datasets::get_dataset(&db.pool, project_id, dataset_id).await?;
229219
if dataset.indexed_on.is_some() {
230220
dataset

app-server/src/traces/consumer.rs

Lines changed: 44 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,32 @@ use crate::{
2929
};
3030

3131
pub async fn process_queue_spans(
32+
pipeline_runner: Arc<PipelineRunner>,
33+
db: Arc<DB>,
34+
cache: Arc<Cache>,
35+
semantic_search: Arc<SemanticSearch>,
36+
language_model_runner: Arc<LanguageModelRunner>,
37+
rabbitmq_connection: Arc<Connection>,
38+
clickhouse: clickhouse::Client,
39+
chunker_runner: Arc<chunk::runner::ChunkerRunner>,
40+
) {
41+
loop {
42+
inner_process_queue_spans(
43+
pipeline_runner.clone(),
44+
db.clone(),
45+
cache.clone(),
46+
semantic_search.clone(),
47+
language_model_runner.clone(),
48+
rabbitmq_connection.clone(),
49+
clickhouse.clone(),
50+
chunker_runner.clone(),
51+
)
52+
.await;
53+
log::warn!("Span listener exited. Creating a new RabbitMQ channel...");
54+
}
55+
}
56+
57+
async fn inner_process_queue_spans(
3258
pipeline_runner: Arc<PipelineRunner>,
3359
db: Arc<DB>,
3460
cache: Arc<Cache>,
@@ -171,7 +197,12 @@ pub async fn process_queue_spans(
171197
}
172198

173199
if let Err(e) = db::spans::record_span(&db.pool, &span).await {
174-
log::error!("Failed to record span [{}]: {:?}", span.span_id, e);
200+
log::error!(
201+
"Failed to record span. span_id [{}], project_id [{}]: {:?}",
202+
span.span_id,
203+
rabbitmq_span_message.project_id,
204+
e
205+
);
175206
} else {
176207
// ack the message as soon as the span is recorded
177208
let _ = delivery
@@ -184,7 +215,12 @@ pub async fn process_queue_spans(
184215
// TODO: Queue batches and send them every 1-2 seconds
185216
let insert_span_res = ch::spans::insert_span(clickhouse.clone(), &ch_span).await;
186217
if let Err(e) = insert_span_res {
187-
log::error!("Failed to insert span into Clickhouse: {:?}", e);
218+
log::error!(
219+
"Failed to insert span into Clickhouse. span_id [{}], project_id [{}]: {:?}",
220+
span.span_id,
221+
rabbitmq_span_message.project_id,
222+
e
223+
);
188224
}
189225

190226
let registered_label_classes = match get_registered_label_classes_for_path(
@@ -196,7 +232,11 @@ pub async fn process_queue_spans(
196232
{
197233
Ok(classes) => classes,
198234
Err(e) => {
199-
log::error!("Failed to get registered label classes: {:?}", e);
235+
log::error!(
236+
"Failed to get registered label classes. project_id [{}]: {:?}",
237+
rabbitmq_span_message.project_id,
238+
e
239+
);
200240
Vec::new() // Return an empty vector if there's an error
201241
}
202242
};
@@ -217,5 +257,5 @@ pub async fn process_queue_spans(
217257
}
218258
}
219259

220-
log::info!("Shutting down span listener");
260+
log::warn!("RabbitMQ closed connection. Shutting down span listener");
221261
}
Lines changed: 51 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1-
import { authOptions } from '@/lib/auth';
2-
import { fetcher } from '@/lib/utils';
3-
import { getServerSession } from 'next-auth';
1+
import { db } from '@/lib/db/drizzle';
2+
import { datasetDatapoints } from '@/lib/db/schema';
3+
import { and, eq } from 'drizzle-orm';
4+
import { z } from 'zod';
5+
import { isCurrentUserMemberOfProject } from '@/lib/db/utils';
46

57
export async function POST(
68
req: Request,
@@ -9,22 +11,56 @@ export async function POST(
911
}: { params: { projectId: string; datasetId: string; datapointId: string } }
1012
): Promise<Response> {
1113
const projectId = params.projectId;
14+
15+
if (!await isCurrentUserMemberOfProject(projectId)) {
16+
return new Response('Unauthorized', { status: 401 });
17+
}
18+
1219
const datasetId = params.datasetId;
1320
const datapointId = params.datapointId;
14-
const session = await getServerSession(authOptions);
15-
const user = session!.user;
1621

1722
const body = await req.json();
1823

19-
return await fetcher(
20-
`/projects/${projectId}/datasets/${datasetId}/datapoints/${datapointId}`,
21-
{
22-
method: 'POST',
23-
headers: {
24-
'Content-Type': 'application/json',
25-
Authorization: `Bearer ${user.apiKey}`
26-
},
27-
body: JSON.stringify(body)
24+
const schema = z.object({
25+
data: z.record(z.unknown()),
26+
target: z.record(z.unknown()).nullable(),
27+
metadata: z.record(z.unknown()).nullable(),
28+
});
29+
30+
const result = schema.safeParse(body);
31+
if (!result.success) {
32+
console.error('Invalid request body', result.error);
33+
return new Response('Invalid request body', { status: 400 });
34+
}
35+
36+
const { data, target, metadata } = result.data;
37+
38+
try {
39+
const updatedDatapoint = await db
40+
.update(datasetDatapoints)
41+
.set({
42+
data,
43+
target,
44+
metadata,
45+
})
46+
.where(
47+
and(
48+
eq(datasetDatapoints.id, datapointId),
49+
eq(datasetDatapoints.datasetId, datasetId)
50+
)
51+
)
52+
.returning();
53+
54+
if (updatedDatapoint.length === 0) {
55+
return new Response('Datapoint not found', { status: 404 });
2856
}
29-
);
57+
58+
return new Response(JSON.stringify(updatedDatapoint[0]), {
59+
status: 200,
60+
headers: { 'Content-Type': 'application/json' },
61+
});
62+
} catch (error) {
63+
console.error('Error updating datapoint:', error);
64+
return new Response('Internal Server Error', { status: 500 });
65+
}
3066
}

frontend/app/api/projects/[projectId]/datasets/[datasetId]/datapoints/route.ts

Lines changed: 28 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import { getServerSession } from 'next-auth';
22
import { authOptions } from '@/lib/auth';
33
import { fetcher } from '@/lib/utils';
44
import { NextRequest } from 'next/server';
5+
import { db } from '@/lib/db/drizzle';
6+
import { datasetDatapoints } from '@/lib/db/schema';
7+
import { and, inArray, eq } from 'drizzle-orm';
8+
import { isCurrentUserMemberOfProject } from '@/lib/db/utils';
59

610
export async function GET(
711
req: NextRequest,
@@ -53,20 +57,30 @@ export async function DELETE(
5357
): Promise<Response> {
5458
const projectId = params.projectId;
5559
const datasetId = params.datasetId;
56-
const session = await getServerSession(authOptions);
57-
const user = session!.user;
5860

59-
const body = await req.json();
61+
if (!(await isCurrentUserMemberOfProject(projectId))) {
62+
return new Response(JSON.stringify({ error: "User is not a member of the project" }), { status: 403 });
63+
}
6064

61-
return await fetcher(
62-
`/projects/${projectId}/datasets/${datasetId}/datapoints`,
63-
{
64-
method: 'DELETE',
65-
headers: {
66-
'Content-Type': 'application/json',
67-
Authorization: `Bearer ${user.apiKey}`
68-
},
69-
body: JSON.stringify(body)
70-
}
71-
);
65+
const { searchParams } = new URL(req.url);
66+
const datapointIds = searchParams.get('datapointIds')?.split(',');
67+
68+
if (!datapointIds) {
69+
return new Response('At least one Datapoint ID is required', { status: 400 });
70+
}
71+
72+
try {
73+
await db.delete(datasetDatapoints)
74+
.where(
75+
and(
76+
inArray(datasetDatapoints.id, datapointIds),
77+
eq(datasetDatapoints.datasetId, datasetId)
78+
)
79+
);
80+
81+
return new Response('datasetDatapoints deleted successfully', { status: 200 });
82+
} catch (error) {
83+
console.error('Error deleting datasetDatapoints:', error);
84+
return new Response('Error deleting datasetDatapoints', { status: 500 });
85+
}
7286
}
Lines changed: 45 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,52 @@
1-
import { getServerSession } from 'next-auth';
2-
import { authOptions } from '@/lib/auth';
3-
import { fetcher } from '@/lib/utils';
1+
import { db } from '@/lib/db/drizzle';
2+
import { evaluations } from '@/lib/db/schema';
3+
import { and, desc, eq, inArray } from 'drizzle-orm';
4+
import { isCurrentUserMemberOfProject } from '@/lib/db/utils';
45

56
export async function GET(
67
req: Request,
78
{ params }: { params: { projectId: string } }
89
): Promise<Response> {
910
const projectId = params.projectId;
10-
const session = await getServerSession(authOptions);
11-
const user = session!.user;
12-
13-
return await fetcher(`/projects/${projectId}/evaluations`, {
14-
method: 'GET',
15-
headers: {
16-
Authorization: `Bearer ${user.apiKey}`
17-
}
18-
});
11+
12+
if (!(await isCurrentUserMemberOfProject(projectId))) {
13+
return new Response(JSON.stringify({ error: "User is not a member of the project" }), { status: 403 });
14+
}
15+
16+
const result = await db.select().from(evaluations).where(eq(evaluations.projectId, projectId)).orderBy(desc(evaluations.createdAt));
17+
18+
return Response.json(result);
19+
}
20+
21+
export async function DELETE(
22+
req: Request,
23+
{ params }: { params: { projectId: string } }
24+
): Promise<Response> {
25+
const projectId = params.projectId;
26+
27+
if (!(await isCurrentUserMemberOfProject(projectId))) {
28+
return new Response(JSON.stringify({ error: "User is not a member of the project" }), { status: 403 });
29+
}
30+
31+
const { searchParams } = new URL(req.url);
32+
const evaluationIds = searchParams.get('evaluationIds')?.split(',');
33+
34+
if (!evaluationIds) {
35+
return new Response('At least one Evaluation ID is required', { status: 400 });
36+
}
37+
38+
try {
39+
await db.delete(evaluations)
40+
.where(
41+
and(
42+
inArray(evaluations.id, evaluationIds),
43+
eq(evaluations.projectId, projectId)
44+
)
45+
);
46+
47+
return new Response('Evaluations deleted successfully', { status: 200 });
48+
} catch (error) {
49+
console.error('Error deleting evaluations:', error);
50+
return new Response('Error deleting evaluations', { status: 500 });
51+
}
1952
}

frontend/app/error.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import Link from 'next/link';
44
import Image from 'next/image';
5-
import icon from '@/assets/logo/icon_light.svg';
5+
import icon from '@/assets/logo/icon.png';
66

77
export default function Error({
88
error,

frontend/app/globals.css

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@
3535
3636
--radius: 0.5rem; */
3737

38-
--background: 240 8% 8%;
39-
--foreground: 240 8% 88%;
38+
--background: 240 0% 6%;
39+
--foreground: 240 8% 95%;
4040

4141
--card: 240 8% 10%;
4242
--card-foreground: 240 8% 80%;
@@ -50,17 +50,17 @@
5050
--secondary: 232 9% 17%;
5151
--secondary-foreground: 240 8% 70%;
5252

53-
--muted: 232 9% 17%;
53+
--muted: 240 6% 16%;
5454
--muted-foreground: 215 20.2% 65.1%;
5555

5656
--accent: 232 9% 17%;
57-
--accent-foreground: 210 40% 98%;
57+
--accent-foreground: 210 100% 100%;
5858

5959
--destructive: 0 62.8% 30.6%;
6060
--destructive-foreground: 210 40% 98%;
6161

62-
--border: 240 6% 20%;
63-
--input: 240 6% 20%;
62+
--border: 240 6% 18%;
63+
--input: 240 6% 18%;
6464
--ring: 212 96% 78%;
6565

6666
--radius: 0.5rem;
Lines changed: 1 addition & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,5 @@
1-
import { authOptions } from '@/lib/auth';
2-
import { getServerSession } from 'next-auth';
3-
import { redirect } from 'next/navigation';
41
import { Metadata } from 'next';
52
import Evaluations from '@/components/evaluations/evaluations';
6-
import { fetcherJSON } from '@/lib/utils';
7-
import { Evaluation } from '@/lib/evaluation/types';
83

94
export const metadata: Metadata = {
105
title: 'Evaluations'
@@ -15,22 +10,5 @@ export default async function EvaluationsPage({
1510
}: {
1611
params: { projectId: string };
1712
}) {
18-
const session = await getServerSession(authOptions);
19-
if (!session) {
20-
redirect('/sign-in');
21-
}
22-
23-
const user = session.user;
24-
25-
const evaluations = (await fetcherJSON(
26-
`/projects/${params.projectId}/evaluations/`,
27-
{
28-
method: 'GET',
29-
headers: {
30-
Authorization: `Bearer ${user.apiKey}`
31-
}
32-
}
33-
)) as Evaluation[];
34-
35-
return <Evaluations evaluations={evaluations} />;
13+
return <Evaluations />;
3614
}

0 commit comments

Comments
 (0)