Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename files #231

Merged
merged 1 commit into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions apps/web/src/actions/documents/renamePathsAction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use server'

import { CommitsRepository } from '@latitude-data/core/repositories'
import { renameDocumentPaths } from '@latitude-data/core/services/documents/renameDocumentPaths'
import { z } from 'zod'

import { withProject } from '../procedures'

export const renameDocumentPathsAction = withProject
.createServerAction()
.input(
z.object({
commitUuid: z.string(),
oldPath: z.string(),
newPath: z.string(),
}),
{ type: 'json' },
)
.handler(async ({ input, ctx }) => {
const commitsScope = new CommitsRepository(ctx.project.workspaceId)
const commit = await commitsScope
.getCommitByUuid({ uuid: input.commitUuid, projectId: ctx.project.id })
.then((r) => r.unwrap())

const result = await renameDocumentPaths({
commit,
oldPath: input.oldPath,
newPath: input.newPath,
})

return result.unwrap()
})
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default function ClientFilesTree({
const { commit, isHead } = useCurrentCommit()
const isMerged = !!commit.mergedAt
const { project } = useCurrentProject()
const documentPath = currentDocument?.path
const documentUuid = currentDocument?.documentUuid
const navigateToDocument = useCallback(
(documentUuid: string) => {
const documentDetails = ROUTES.projects
Expand All @@ -42,16 +42,22 @@ export default function ClientFilesTree({
[project.id, commit.uuid, isHead],
)

const { createFile, destroyFile, destroyFolder, isDestroying, data } =
useDocumentVersions(
{ commitUuid: commit.uuid, projectId: project.id },
{
fallbackData: serverDocuments,
onSuccessCreate: (document) => {
navigateToDocument(document.documentUuid)
},
const {
createFile,
destroyFile,
destroyFolder,
renamePaths,
isDestroying,
data,
} = useDocumentVersions(
{ commitUuid: commit.uuid, projectId: project.id },
{
fallbackData: serverDocuments,
onSuccessCreate: (document) => {
navigateToDocument(document.documentUuid)
},
)
},
)
const onMergeCommitClick = useCallback(() => {
setWarningOpen(true)
}, [setWarningOpen])
Expand All @@ -61,10 +67,11 @@ export default function ClientFilesTree({
<FilesTree
isMerged={isMerged}
documents={data}
currentPath={documentPath}
currentUuid={documentUuid}
navigateToDocument={navigateToDocument}
onMergeCommitClick={onMergeCommitClick}
createFile={createFile}
renamePaths={renamePaths}
destroyFile={destroyFile}
destroyFolder={destroyFolder}
isDestroying={isDestroying}
Expand Down
38 changes: 38 additions & 0 deletions apps/web/src/stores/documentVersions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { useToast } from '@latitude-data/web-ui'
import { createDocumentVersionAction } from '$/actions/documents/create'
import { destroyDocumentAction } from '$/actions/documents/destroyDocumentAction'
import { destroyFolderAction } from '$/actions/documents/destroyFolderAction'
import { renameDocumentPathsAction } from '$/actions/documents/renamePathsAction'
import { updateDocumentContentAction } from '$/actions/documents/updateContent'
import useLatitudeAction from '$/hooks/useLatitudeAction'
import { ROUTES } from '$/services/routes'
Expand Down Expand Up @@ -34,6 +35,9 @@ export default function useDocumentVersions(
},
},
)
const { execute: executeRenamePaths } = useServerAction(
renameDocumentPathsAction,
)
const { execute: executeDestroyDocument, isPending: isDestroyingFile } =
useServerAction(destroyDocumentAction)
const { execute: executeDestroyFolder, isPending: isDestroyingFolder } =
Expand Down Expand Up @@ -103,6 +107,39 @@ export default function useDocumentVersions(
[executeCreateDocument, mutate, data, commitUuid],
)

const renamePaths = useCallback(
async ({ oldPath, newPath }: { oldPath: string; newPath: string }) => {
if (!projectId) return

const [updatedDocuments, error] = await executeRenamePaths({
oldPath,
newPath,
projectId,
commitUuid,
})

if (updatedDocuments) {
mutate(
data.map((d) => {
const updatedDocument = updatedDocuments.find(
(ud) => ud.documentUuid === d.documentUuid,
)
return updatedDocument ? updatedDocument : d
}),
)
}

if (error) {
toast({
title: 'Error renaming paths',
description: error.formErrors?.[0] || error.message,
variant: 'destructive',
})
}
},
[executeRenamePaths, mutate, data, commitUuid],
)

const destroyFile = useCallback(
async (documentUuid: string) => {
if (!projectId) return
Expand Down Expand Up @@ -188,6 +225,7 @@ export default function useDocumentVersions(
isLoading: isLoading,
error: swrError,
createFile,
renamePaths,
destroyFile,
destroyFolder,
updateContent,
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@
"tc": "turbo tc",
"prettier": "prettier --write \"**/*.{ts,tsx,md}\"",
"prettier:check": "prettier --check \"**/*.{ts,tsx,md}\"",
"test": "turbo test"
"test": "turbo test",
"catchup": "pnpm i && pnpm build --filter=\"./packages/**/*\" && pnpm --filter \"@latitude-data/core\" db:migrate"
},
"devDependencies": {
"@babel/parser": "^7.25.4",
Expand Down
1 change: 1 addition & 0 deletions packages/core/src/services/documents/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './destroyDocument'
export * from './destroyFolder'
export * from './recomputeChanges'
export * from './getResolvedContent'
export * from './renameDocumentPaths'
80 changes: 80 additions & 0 deletions packages/core/src/services/documents/renameDocumentPaths.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { describe, expect, it } from 'vitest'

import { Providers } from '../../constants'
import { BadRequestError } from '../../lib'
import { DocumentVersionsRepository } from '../../repositories'
import * as factories from '../../tests/factories'
import { renameDocumentPaths } from './renameDocumentPaths'

describe('renameDocumentPaths', () => {
it('renames a single document path', async () => {
const { project, user } = await factories.createProject({
providers: [{ type: Providers.OpenAI, name: 'openai' }],
documents: {
'a/b': factories.helpers.createPrompt({ provider: 'openai' }),
'a/b/c': factories.helpers.createPrompt({ provider: 'openai' }),
},
})
const { commit: draft } = await factories.createDraft({ project, user })

const result = await renameDocumentPaths({
commit: draft,
oldPath: 'a/b', // a/b/c should not be affected, since I'm only renaming a file
newPath: 'new/path',
})

expect(result.ok).toBeTruthy()

const docsScope = new DocumentVersionsRepository(project.workspaceId)
const docs = await docsScope
.getDocumentsAtCommit(draft)
.then((r) => r.unwrap())
const paths = docs.map((d) => d.path).sort()
expect(paths).toEqual(['a/b/c', 'new/path'])
})

it('renames a folder path', async () => {
const { project, user } = await factories.createProject({
providers: [{ type: Providers.OpenAI, name: 'openai' }],
documents: {
'a/b/c': factories.helpers.createPrompt({ provider: 'openai' }),
'a/b/c/d': factories.helpers.createPrompt({ provider: 'openai' }),
'not/affected': factories.helpers.createPrompt({ provider: 'openai' }),
},
})
const { commit: draft } = await factories.createDraft({ project, user })

const result = await renameDocumentPaths({
commit: draft,
oldPath: 'a/b/',
newPath: 'newpath/',
})

expect(result.ok).toBeTruthy()

const docsScope = new DocumentVersionsRepository(project.workspaceId)
const docs = await docsScope
.getDocumentsAtCommit(draft)
.then((r) => r.unwrap())
const paths = docs.map((d) => d.path).sort()
expect(paths).toEqual(['newpath/c', 'newpath/c/d', 'not/affected'])
})

it('fails when trying to rename a folder as a document', async () => {
const { project, user } = await factories.createProject({
providers: [{ type: Providers.OpenAI, name: 'openai' }],
documents: {
'a/b': factories.helpers.createPrompt({ provider: 'openai' }),
},
})
const { commit: draft } = await factories.createDraft({ project, user })

const result = await renameDocumentPaths({
commit: draft,
oldPath: 'a/',
newPath: 'new/path',
})

expect(result.error).toBeInstanceOf(BadRequestError)
})
})
64 changes: 64 additions & 0 deletions packages/core/src/services/documents/renameDocumentPaths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import type { Commit, DocumentVersion } from '../../browser'
import { database } from '../../client'
import { findWorkspaceFromCommit } from '../../data-access'
import { Result, Transaction, TypedResult } from '../../lib'
import { BadRequestError } from '../../lib/errors'
import { DocumentVersionsRepository } from '../../repositories'
import { updateDocument } from './update'

export async function renameDocumentPaths(
{
commit,
oldPath,
newPath,
}: {
commit: Commit
oldPath: string
newPath: string
},
db = database,
): Promise<TypedResult<DocumentVersion[], Error>> {
return await Transaction.call(async (tx) => {
if (commit.mergedAt !== null) {
return Result.error(new BadRequestError('Cannot modify a merged commit'))
}

if (oldPath.endsWith('/') !== newPath.endsWith('/')) {
return Result.error(
new BadRequestError(
'Trying to rename a folder as a document or vice versa',
),
)
}

const workspace = await findWorkspaceFromCommit(commit, tx)
csansoon marked this conversation as resolved.
Show resolved Hide resolved
const docsScope = new DocumentVersionsRepository(workspace!.id, tx)

const currentDocs = await docsScope
.getDocumentsAtCommit(commit)
.then((r) => r.unwrap())

const docsToUpdate = currentDocs.filter((d) =>
oldPath.endsWith('/') ? d.path.startsWith(oldPath) : d.path === oldPath,
)

const updatedDocs = await Promise.all(
docsToUpdate.map(async (document) => {
const updatedPath = newPath + document.path.slice(oldPath.length)
// A simple replace would also replace other instances of oldPath in the path
// For example, relpacing "a" to "b" in "a/name" would result in "b/nbme"
const updatedDoc = await updateDocument(
{
commit,
document,
path: updatedPath,
},
tx,
)
return updatedDoc.unwrap()
}),
)

return Result.ok(updatedDocs)
}, db)
}
4 changes: 4 additions & 0 deletions packages/env/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ if (environment !== 'production') {
FILES_STORAGE_PATH,
DEFAULT_PROJECT_ID: '1',
DEFAULT_PROVIDER_API_KEY: 'd32da7c2-94fd-49c3-8dca-b57a5c3bbe27',
NEXT_PUBLIC_POSTHOG_KEY: '',
NEXT_PUBLIC_POSTHOG_HOST: '',
},
{ path: pathToEnv },
)
Expand Down Expand Up @@ -80,6 +82,8 @@ export const env = createEnv({
SENTRY_DSN: z.string().optional(),
SENTRY_ORG: z.string().optional(),
SENTRY_PROJECT: z.string().optional(),
NEXT_PUBLIC_POSTHOG_KEY: z.string(),
NEXT_PUBLIC_POSTHOG_HOST: z.string(),
},
runtimeEnv: {
...process.env,
Expand Down
2 changes: 2 additions & 0 deletions packages/web-ui/src/ds/atoms/Icons/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import {
LoaderCircle,
Lock,
Moon,
Pencil,
RefreshCcw,
SquareDot,
SquareMinus,
Expand Down Expand Up @@ -63,6 +64,7 @@ const Icons = {
sun: Sun,
eye: Eye,
externalLink: ExternalLink,
pencil: Pencil,
refresh: RefreshCcw,
}

Expand Down
Loading
Loading