Skip to content

Commit 900db32

Browse files
authored
feat: first implementation of the evaluation job (CM-1167) (#4125)
Signed-off-by: Umberto Sgueglia <usgueglia@contractor.linuxfoundation.org>
1 parent 600ee6f commit 900db32

15 files changed

Lines changed: 402 additions & 0 deletions

File tree

pnpm-lock.yaml

Lines changed: 37 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
DOCKERFILE="./services/docker/Dockerfile.projects_evaluation_worker"
2+
CONTEXT="../"
3+
REPO="sjc.ocir.io/axbydjxa5zuh/projects-evaluation-worker"
4+
SERVICES="projects-evaluation-worker"
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
FROM node:20-alpine as builder
2+
3+
RUN apk add --no-cache python3 make g++
4+
5+
WORKDIR /usr/crowd/app
6+
RUN npm install -g corepack@latest && corepack enable pnpm && corepack prepare pnpm@9.15.0 --activate
7+
8+
COPY ./pnpm-workspace.yaml ./pnpm-lock.yaml ./
9+
RUN pnpm fetch
10+
11+
COPY ./services ./services
12+
RUN pnpm i --frozen-lockfile
13+
14+
FROM node:20-bookworm-slim as runner
15+
16+
WORKDIR /usr/crowd/app
17+
RUN npm install -g corepack@latest && corepack enable pnpm && corepack prepare pnpm@9.15.0 --activate && apt update && apt install -y ca-certificates --no-install-recommends && rm -rf /var/lib/apt/lists/*
18+
19+
COPY --from=builder /usr/crowd/app/node_modules ./node_modules
20+
COPY --from=builder /usr/crowd/app/services/base.tsconfig.json ./services/base.tsconfig.json
21+
COPY --from=builder /usr/crowd/app/services/libs ./services/libs
22+
COPY --from=builder /usr/crowd/app/services/archetypes/ ./services/archetypes
23+
COPY --from=builder /usr/crowd/app/services/apps/projects_evaluation_worker/ ./services/apps/projects_evaluation_worker
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
version: '3.1'
2+
3+
x-env-args: &env-args
4+
DOCKER_BUILDKIT: 1
5+
NODE_ENV: docker
6+
SERVICE: projects-evaluation-worker
7+
CROWD_TEMPORAL_TASKQUEUE: projects-evaluation
8+
SHELL: /bin/sh
9+
10+
services:
11+
projects-evaluation-worker:
12+
build:
13+
context: ../../
14+
dockerfile: ./scripts/services/docker/Dockerfile.projects_evaluation_worker
15+
command: 'pnpm run start'
16+
working_dir: /usr/crowd/app/services/apps/projects_evaluation_worker
17+
env_file:
18+
- ../../backend/.env.dist.local
19+
- ../../backend/.env.dist.composed
20+
- ../../backend/.env.override.local
21+
- ../../backend/.env.override.composed
22+
environment:
23+
<<: *env-args
24+
restart: always
25+
networks:
26+
- crowd-bridge
27+
28+
projects-evaluation-worker-dev:
29+
build:
30+
context: ../../
31+
dockerfile: ./scripts/services/docker/Dockerfile.projects_evaluation_worker
32+
command: 'pnpm run dev'
33+
working_dir: /usr/crowd/app/services/apps/projects_evaluation_worker
34+
env_file:
35+
- ../../backend/.env.dist.local
36+
- ../../backend/.env.dist.composed
37+
- ../../backend/.env.override.local
38+
- ../../backend/.env.override.composed
39+
environment:
40+
<<: *env-args
41+
hostname: projects-evaluation-worker
42+
networks:
43+
- crowd-bridge
44+
volumes:
45+
- ../../services/libs/audit-logs/src:/usr/crowd/app/services/libs/audit-logs/src
46+
- ../../services/libs/common/src:/usr/crowd/app/services/libs/common/src
47+
- ../../services/libs/common_services/src:/usr/crowd/app/services/libs/common_services/src
48+
- ../../services/libs/data-access-layer/src:/usr/crowd/app/services/libs/data-access-layer/src
49+
- ../../services/libs/database/src:/usr/crowd/app/services/libs/database/src
50+
- ../../services/libs/integrations/src:/usr/crowd/app/services/libs/integrations/src
51+
- ../../services/libs/logging/src:/usr/crowd/app/services/libs/logging/src
52+
- ../../services/libs/nango/src:/usr/crowd/app/services/libs/nango/src
53+
- ../../services/libs/opensearch/src:/usr/crowd/app/services/libs/opensearch/src
54+
- ../../services/libs/queue/src:/usr/crowd/app/services/libs/queue/src
55+
- ../../services/libs/redis/src:/usr/crowd/app/services/libs/redis/src
56+
- ../../services/libs/snowflake/src:/usr/crowd/app/services/libs/snowflake/src
57+
- ../../services/libs/telemetry/src:/usr/crowd/app/services/libs/telemetry/src
58+
- ../../services/libs/temporal/src:/usr/crowd/app/services/libs/temporal/src
59+
- ../../services/libs/types/src:/usr/crowd/app/services/libs/types/src
60+
- ../../services/apps/projects_evaluation_worker/src:/usr/crowd/app/services/apps/projects_evaluation_worker/src
61+
62+
networks:
63+
crowd-bridge:
64+
external: true
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "@crowd/projects-evaluation-worker",
3+
"private": true,
4+
"scripts": {
5+
"start": "CROWD_TEMPORAL_TASKQUEUE=projects-evaluation SERVICE=projects-evaluation-worker tsx src/main.ts",
6+
"start:debug:local": "set -a && . ../../../backend/.env.dist.local && . ../../../backend/.env.override.local && set +a && CROWD_TEMPORAL_TASKQUEUE=projects-evaluation SERVICE=projects-evaluation-worker tsx --inspect=0.0.0.0:9233 src/main.ts",
7+
"start:debug": "CROWD_TEMPORAL_TASKQUEUE=projects-evaluation SERVICE=projects-evaluation-worker LOG_LEVEL=trace tsx --inspect=0.0.0.0:9233 src/main.ts",
8+
"dev:local": "nodemon --watch src --watch ../../libs --ext ts --exec pnpm run start:debug:local",
9+
"dev": "nodemon --watch src --watch ../../libs --ext ts --exec pnpm run start:debug",
10+
"lint": "npx eslint --ext .ts src --max-warnings=0",
11+
"format": "npx prettier --write \"src/**/*.ts\"",
12+
"format-check": "npx prettier --check .",
13+
"tsc-check": "tsc --noEmit"
14+
},
15+
"dependencies": {
16+
"@crowd/archetype-standard": "workspace:*",
17+
"@crowd/archetype-worker": "workspace:*",
18+
"@crowd/data-access-layer": "workspace:*",
19+
"@crowd/logging": "workspace:*",
20+
"@crowd/temporal": "workspace:*",
21+
"@temporalio/client": "~1.11.8",
22+
"@temporalio/workflow": "~1.11.8",
23+
"tsx": "^4.7.1",
24+
"typescript": "^5.6.3"
25+
},
26+
"devDependencies": {
27+
"@types/node": "^20.8.2",
28+
"nodemon": "^3.0.1"
29+
}
30+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './activities/activities'
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import { findProjectCatalogPendingEvaluation, updateProjectCatalog } from '@crowd/data-access-layer'
2+
import { IDbProjectCatalog } from '@crowd/data-access-layer/src/project-catalog/types'
3+
import { pgpQx } from '@crowd/data-access-layer/src/queryExecutor'
4+
import { getServiceLogger } from '@crowd/logging'
5+
6+
import { evaluateProject } from '../evaluator/evaluator'
7+
import { svc } from '../main'
8+
9+
const log = getServiceLogger()
10+
11+
export async function fetchPendingProjects(batchSize: number): Promise<IDbProjectCatalog[]> {
12+
const qx = pgpQx(svc.postgres.reader.connection())
13+
14+
const projects = await findProjectCatalogPendingEvaluation(qx, { limit: batchSize })
15+
16+
log.info({ count: projects.length, batchSize }, 'Fetched projects pending evaluation.')
17+
18+
return projects
19+
}
20+
21+
export async function evaluateAndUpdateProject(project: IDbProjectCatalog): Promise<void> {
22+
const qx = pgpQx(svc.postgres.writer.connection())
23+
const startTime = Date.now()
24+
25+
log.info({ id: project.id, repoUrl: project.repoUrl }, 'Starting evaluation.')
26+
27+
const result = await evaluateProject({
28+
id: project.id,
29+
repoUrl: project.repoUrl,
30+
repoName: project.repoName,
31+
projectSlug: project.projectSlug,
32+
lfCriticalityScore: project.lfCriticalityScore,
33+
source: project.source,
34+
})
35+
36+
await updateProjectCatalog(qx, project.id, {
37+
action: result.outcome,
38+
evaluatedAt: new Date().toISOString(),
39+
})
40+
41+
const elapsedSeconds = ((Date.now() - startTime) / 1000).toFixed(1)
42+
43+
log.info(
44+
{
45+
id: project.id,
46+
repoUrl: project.repoUrl,
47+
outcome: result.outcome,
48+
reason: result.reason,
49+
elapsedSeconds,
50+
},
51+
'Evaluation complete.',
52+
)
53+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { IEvaluationInput, IEvaluationResult } from './types'
2+
3+
// TODO: Replace with the actual AI evaluation algorithm once the external repo is integrated.
4+
// The algorithm is described in the technical spec and currently takes ~30-40s per project
5+
// at ~$0.15/project. Reference: https://github.com/... (link TBD).
6+
export async function evaluateProject(input: IEvaluationInput): Promise<IEvaluationResult> {
7+
console.error(`evaluateProject is not implemented yet for repo: ${input.repoUrl}`)
8+
return { outcome: 'unsure', reason: 'evaluator not implemented' }
9+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { ProjectCatalogAction } from '@crowd/data-access-layer/src/project-catalog/types'
2+
3+
export interface IEvaluationInput {
4+
id: string
5+
repoUrl: string
6+
repoName: string
7+
projectSlug: string
8+
lfCriticalityScore: number | null
9+
source: string | null
10+
}
11+
12+
// Evaluation can only resolve to 'onboard' or 'unsure' — never back to 'evaluate' or 'auto'.
13+
export type EvaluationOutcome = Extract<ProjectCatalogAction, 'onboard' | 'unsure'>
14+
15+
export interface IEvaluationResult {
16+
outcome: EvaluationOutcome
17+
reason: string
18+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { Config } from '@crowd/archetype-standard'
2+
import { Options, ServiceWorker } from '@crowd/archetype-worker'
3+
4+
import { scheduleProjectsEvaluation } from './schedules/scheduleProjectsEvaluation'
5+
6+
const config: Config = {
7+
envvars: [],
8+
producer: {
9+
enabled: false,
10+
},
11+
temporal: {
12+
enabled: true,
13+
},
14+
redis: {
15+
enabled: false,
16+
},
17+
}
18+
19+
const options: Options = {
20+
postgres: {
21+
enabled: true,
22+
},
23+
opensearch: {
24+
enabled: false,
25+
},
26+
}
27+
28+
export const svc = new ServiceWorker(config, options)
29+
30+
setImmediate(async () => {
31+
await svc.init()
32+
33+
svc.log.info('Projects evaluation worker starting up.')
34+
35+
await scheduleProjectsEvaluation()
36+
37+
svc.log.info('Projects evaluation worker running — schedule registered, waiting for Temporal.')
38+
39+
await svc.start()
40+
})

0 commit comments

Comments
 (0)