Skip to content

Commit

Permalink
chore: report default project not getting created in case of error (#246
Browse files Browse the repository at this point in the history
)

* chore: report default project not getting created in case of error

It also improves setup service and cleans workspace creation service
which had too many responsibilities

* chore: add tests to setup service

* chore: added tests to the project repository
  • Loading branch information
geclos authored Sep 21, 2024
1 parent b9693c5 commit 29c1a90
Show file tree
Hide file tree
Showing 14 changed files with 324 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,13 @@ export const runHandler = factory.createHandlers(
async (stream) => {
const { projectId, commitUuid } = c.req.param()
const { documentPath, parameters, source } = c.req.valid('json')

const workspace = c.get('workspace')

const { document, commit } = await getData({
workspace,
projectId: Number(projectId!),
commitUuid: commitUuid!,
documentPath: documentPath!,
})

const result = await runDocumentAtCommit({
workspace,
document,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ let merged: Commit
let document: DocumentVersion
let project: Project
let userData: User

describe('destroyDocumentAction', async () => {
beforeEach(async () => {
const {
Expand All @@ -44,6 +45,7 @@ describe('destroyDocumentAction', async () => {
doc1: helpers.createPrompt({ provider: 'openai', content: 'Doc 1' }),
},
})

const { commit } = await createDraft({ project: prj, user })
merged = cmt
userData = user
Expand Down
10 changes: 10 additions & 0 deletions apps/web/src/helpers/captureException.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import * as Sentry from '@sentry/nextjs'
import env from '$/env'

export const captureException = (error: Error) => {
if (env.NODE_ENV === 'production') {
Sentry.captureException(error)
} else {
console.error(error)
}
}
126 changes: 126 additions & 0 deletions apps/web/src/services/user/setupService.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import { Providers } from '@latitude-data/core/browser'
import { database } from '@latitude-data/core/client'
import { createProject, helpers } from '@latitude-data/core/factories'
import {
apiKeys,
commits,
documentVersions,
memberships,
projects,
providerApiKeys,
users,
workspaces,
} from '@latitude-data/core/schema'
import { env } from '@latitude-data/env'
import { eq } from 'drizzle-orm'
import { describe, expect, it, vi } from 'vitest'

import setupService from './setupService'

describe('setupService', () => {
it('should create all necessary entities when calling setup service', async () => {
const result = await setupService({
email: '[email protected]',
name: 'Test User',
companyName: 'Test Company',
})

expect(result.error).toBeUndefined()
expect(result.value).toBeDefined()
expect(result.value?.user).toBeDefined()
expect(result.value?.workspace).toBeDefined()

const { user, workspace } = result.value!

// Check user creation
const createdUser = await database.query.users.findFirst({
// @ts-expect-error - drizzle-orm types are not up to date
where: eq(users.id, user.id),
})
expect(createdUser).toBeDefined()
expect(createdUser?.email).toBe('[email protected]')
expect(createdUser?.name).toBe('Test User')

// Check workspace creation
const createdWorkspace = await database.query.workspaces.findFirst({
// @ts-expect-error - drizzle-orm types are not up to date
where: eq(workspaces.id, workspace.id),
})
expect(createdWorkspace).toBeDefined()
expect(createdWorkspace?.name).toBe('Test Company')

// Check membership creation
const createdMembership = await database.query.memberships.findFirst({
// @ts-expect-error - drizzle-orm types are not up to date
where: eq(memberships.userId, user.id),
})
expect(createdMembership).toBeDefined()
expect(createdMembership?.workspaceId).toBe(workspace.id)

// Check API key creation
const createdApiKey = await database.query.apiKeys.findFirst({
// @ts-expect-error - drizzle-orm types are not up to date
where: eq(apiKeys.workspaceId, workspace.id),
})
expect(createdApiKey).toBeDefined()

// Check provider API key creation
const createdProviderApiKey =
await database.query.providerApiKeys.findFirst({
// @ts-expect-error - drizzle-orm types are not up to date
where: eq(providerApiKeys.workspaceId, workspace.id),
})
expect(createdProviderApiKey).toBeDefined()
expect(createdProviderApiKey?.authorId).toBe(user.id)
})

it('should import the default project when calling setup service', async () => {
const prompt = helpers.createPrompt({
provider: 'Latitude',
model: 'gpt-4o',
})
const { project } = await createProject({
providers: [{ type: Providers.OpenAI, name: 'Latitude' }],
name: 'Default Project',
documents: {
foo: {
content: prompt,
},
},
})

vi.mocked(env).DEFAULT_PROJECT_ID = project.id

const result = await setupService({
email: '[email protected]',
name: 'Test User 2',
companyName: 'Test Company 2',
})

expect(result.error).toBeUndefined()
expect(result.value).toBeDefined()
expect(result.value?.user).toBeDefined()
expect(result.value?.workspace).toBeDefined()

const { workspace } = result.value!

// Check if the default project was imported
const importedProject = await database.query.projects.findFirst({
// @ts-expect-error - drizzle-orm types are not up to date
where: eq(projects.workspaceId, workspace.id),
})
expect(importedProject).toBeDefined()
expect(importedProject?.name).toBe('Default Project')

// Check if the documents were imported
const importedDocuments = await database
.select()
.from(documentVersions)
// @ts-expect-error - drizzle-orm types are not up to date
.innerJoin(commits, eq(commits.id, documentVersions.commitId))
// @ts-expect-error - drizzle-orm types are not up to date
.where(eq(commits.projectId, importedProject!.id))
expect(importedDocuments.length).toBe(1)
expect(importedDocuments[0]!.document_versions.content).toEqual(prompt)
})
})
43 changes: 30 additions & 13 deletions apps/web/src/services/user/setupService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ import { Result } from '@latitude-data/core/lib/Result'
import Transaction, {
PromisedResult,
} from '@latitude-data/core/lib/Transaction'
import { createApiKey } from '@latitude-data/core/services/apiKeys/create'
import { createMembership } from '@latitude-data/core/services/memberships/create'
import { importDefaultProject } from '@latitude-data/core/services/projects/import'
import { createProviderApiKey } from '@latitude-data/core/services/providerApiKeys/create'
import { createUser } from '@latitude-data/core/services/users/createUser'
import { createWorkspace } from '@latitude-data/core/services/workspaces/create'
import { env } from '@latitude-data/env'
import { captureException } from '$/helpers/captureException'

export default function setupService({
email,
Expand All @@ -27,28 +31,41 @@ export default function setupService({
if (userResult.error) return userResult

const user = userResult.value
const result = await createWorkspace(
const resultWorkspace = await createWorkspace(
{
name: companyName,
user,
},
tx,
)
if (resultWorkspace.error) return resultWorkspace
const workspace = resultWorkspace.value

if (result.error) return result

const workspace = result.value
const resultProviderApiKey = await createProviderApiKey(
{
workspace,
provider: Providers.OpenAI,
name: env.DEFAULT_PROVIDER_ID,
token: env.DEFAULT_PROVIDER_API_KEY,
authorId: user.id,
},
const resultImportingDefaultProject = await importDefaultProject(
{ workspace, user },
tx,
)
if (resultProviderApiKey.error) return resultProviderApiKey
if (resultImportingDefaultProject.error) {
captureException(resultImportingDefaultProject.error)
}

const results = await Promise.all([
createMembership({ confirmedAt: new Date(), user, workspace }, tx),
createApiKey({ workspace }, tx),
createProviderApiKey(
{
workspace,
provider: Providers.OpenAI,
name: env.DEFAULT_PROVIDER_ID,
token: env.DEFAULT_PROVIDER_API_KEY,
authorId: user.id,
},
tx,
),
])

const result = Result.findError(results)
if (result) return result

return Result.ok({
user,
Expand Down
25 changes: 25 additions & 0 deletions packages/core/src/events/handlers/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import {
ChainCallResponse,
Commit,
LogSources,
MagicLinkToken,
Membership,
Message,
Project,
ProviderLog,
User,
Workspace,
} from '../../browser'
import { PartialConfig } from '../../services/ai'
import { createEvaluationResultJob } from './createEvaluationResultJob'
Expand Down Expand Up @@ -91,6 +94,22 @@ export type AIProviderCallCompletedEvent = LatitudeEventGeneric<
}
>

export type WorkspaceCreatedEvent = LatitudeEventGeneric<
'workspaceCreated',
{
workspace: Workspace
user: User
}
>

export type ProjectCreated = LatitudeEventGeneric<
'projectCreated',
{
project: Project
commit: Commit
}
>

export type LatitudeEvent =
| MembershipCreatedEvent
| UserCreatedEvent
Expand All @@ -99,6 +118,8 @@ export type LatitudeEvent =
| DocumentRunEvent
| ProviderLogCreatedEvent
| AIProviderCallCompletedEvent
| WorkspaceCreatedEvent
| ProjectCreated

export interface IEventsHandlers {
magicLinkTokenCreated: EventHandler<MagicLinkTokenCreated>[]
Expand All @@ -108,6 +129,8 @@ export interface IEventsHandlers {
documentRun: EventHandler<DocumentRunEvent>[]
providerLogCreated: EventHandler<ProviderLogCreatedEvent>[]
aiProviderCallCompleted: EventHandler<AIProviderCallCompletedEvent>[]
workspaceCreated: EventHandler<WorkspaceCreatedEvent>[]
projectCreated: EventHandler<ProjectCreated>[]
}

export const EventHandlers: IEventsHandlers = {
Expand All @@ -118,4 +141,6 @@ export const EventHandlers: IEventsHandlers = {
documentRun: [createDocumentLogJob],
providerLogCreated: [],
aiProviderCallCompleted: [],
workspaceCreated: [],
projectCreated: [],
} as const
8 changes: 4 additions & 4 deletions packages/core/src/lib/Result.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ export class Result {
return result.ok
}

public static findError<V, E extends Error>(
results: TypedResult<V, E>[],
): TypedResult<V, E> | undefined {
return results.find((r) => !r.ok)
public static findError<E extends Error>(
results: TypedResult<any, E>[],
): ErrorResult<E> | undefined {
return results.find((r) => !r.ok) as ErrorResult<E> | undefined
}
}
Loading

0 comments on commit 29c1a90

Please sign in to comment.