From 6c737c4cd7b0a6bab3e60cf5c342a78b0b55dd58 Mon Sep 17 00:00:00 2001 From: liangfung Date: Wed, 18 Dec 2024 16:46:29 +0700 Subject: [PATCH] update: test --- Untitled-1.ipynb | 34 +++++++ ee/tabby-ui/components/chat/chat.tsx | 10 +- ee/tabby-ui/lib/utils/repository.ts | 24 +++-- ee/tabby-ui/package.json | 6 +- ee/tabby-ui/test/index.test.ts | 102 +++++++++++++++++++ ee/tabby-ui/tsconfig.json | 6 +- pnpm-lock.yaml | 142 ++++++++++++++++++++++++++- 7 files changed, 305 insertions(+), 19 deletions(-) create mode 100644 Untitled-1.ipynb create mode 100644 ee/tabby-ui/test/index.test.ts diff --git a/Untitled-1.ipynb b/Untitled-1.ipynb new file mode 100644 index 000000000000..75cdb1bf14ea --- /dev/null +++ b/Untitled-1.ipynb @@ -0,0 +1,34 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def fibonacci_memoization(n, memo={}):\n", + " if n <= 0:\n", + " raise ValueError(\"Input should be a positive integer.\")\n", + " if n in memo:\n", + " return memo[n]\n", + " if n == 1:\n", + " return 0\n", + " elif n == 2:\n", + " return 1\n", + " else:\n", + " memo[n] = fibonacci_memoization(n - 1, memo) + fibonacci_memoization(n - 2, memo)\n", + " return memo[n]\n", + "\n", + "# Example usage:\n", + "print(fibonacci_memoization(10))" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/ee/tabby-ui/components/chat/chat.tsx b/ee/tabby-ui/components/chat/chat.tsx index 89b583c4c1ad..61321f8df3eb 100644 --- a/ee/tabby-ui/components/chat/chat.tsx +++ b/ee/tabby-ui/components/chat/chat.tsx @@ -34,7 +34,7 @@ import { UserMessage, UserMessageWithOptionalId } from '@/lib/types/chat' -import { cn, findClosestRepositoryMatch, nanoid } from '@/lib/utils' +import { cn, findClosestGitRepository, nanoid } from '@/lib/utils' import { ChatPanel, ChatPanelRef } from './chat-panel' import { ChatScrollAnchor } from './chat-scroll-anchor' @@ -539,10 +539,10 @@ function ChatRenderer( // get default repo if (workspaceGitRepositories?.length && repos?.length) { const defaultGitUrl = workspaceGitRepositories[0].url - const targetGirUrl = findClosestRepositoryMatch( - defaultGitUrl, - repos.map(x => x.gitUrl) - ) + const targetGirUrl = findClosestGitRepository( + repos.map(x => ({ url: x.gitUrl })), + defaultGitUrl + )?.url if (targetGirUrl) { const repo = repos.find(x => x.gitUrl === targetGirUrl) if (repo) { diff --git a/ee/tabby-ui/lib/utils/repository.ts b/ee/tabby-ui/lib/utils/repository.ts index dfd582878c4c..bbb7c205d23f 100644 --- a/ee/tabby-ui/lib/utils/repository.ts +++ b/ee/tabby-ui/lib/utils/repository.ts @@ -1,9 +1,19 @@ -import { go } from 'fuzzysort' +import gitUrlParse from 'git-url-parse' +import type { GitRepository } from 'tabby-chat-panel' -export const findClosestRepositoryMatch = ( - target: string, - gitUrls: string[] -) => { - const results = go(target.replace(/\.git$/, ''), gitUrls) - return results.length > 0 ? results[0].target : null +export function findClosestGitRepository( + repositories: GitRepository[], + gitUrl: string +): GitRepository | undefined { + const gitSearch = gitUrlParse(gitUrl) + if (!gitSearch) { + return undefined + } + + const repos = repositories.filter(repo => { + const search = gitUrlParse(repo.url) + return search.name === gitSearch.name + }) + // If there're multiple matches, we pick the one with highest alphabetical order + return repos.sort((a, b) => b.url.localeCompare(a.url))[0] } diff --git a/ee/tabby-ui/package.json b/ee/tabby-ui/package.json index f5d8d8bc420e..f44474941979 100644 --- a/ee/tabby-ui/package.json +++ b/ee/tabby-ui/package.json @@ -11,6 +11,7 @@ "start": "next start", "lint": "next lint && pnpm format:check", "lint:fix": "next lint --fix && pnpm format:write", + "test": "vitest run", "preview": "next build && next start", "type-check": "tsc --noEmit", "format:write": "prettier --write \"{app,lib,components}/**/*.{ts,tsx,mdx}\" --cache", @@ -77,6 +78,7 @@ "focus-trap-react": "^10.1.1", "framer-motion": "^11.11.7", "fuzzysort": "^3.0.2", + "git-url-parse": "^16.0.0", "graphql": "^16.8.1", "graphql-ws": "^5.16.0", "humanize-duration": "^3.31.0", @@ -134,6 +136,7 @@ "@tailwindcss/typography": "^0.5.9", "@types/color": "^3.0.6", "@types/dompurify": "^3.0.5", + "@types/git-url-parse": "^9.0.3", "@types/he": "^1.2.3", "@types/humanize-duration": "^3.27.4", "@types/lodash-es": "^4.17.10", @@ -162,6 +165,7 @@ "tailwind-merge": "^1.12.0", "tailwindcss": "^3.3.1", "tailwindcss-animate": "^1.0.5", - "typescript": "^5.1.3" + "typescript": "^5.1.3", + "vitest": "^1.5.2" } } diff --git a/ee/tabby-ui/test/index.test.ts b/ee/tabby-ui/test/index.test.ts new file mode 100644 index 000000000000..eaf552c92786 --- /dev/null +++ b/ee/tabby-ui/test/index.test.ts @@ -0,0 +1,102 @@ +import { describe, expect, it } from 'vitest' +import { findClosestGitRepository } from '../lib/utils/repository' +import type { GitRepository } from 'tabby-chat-panel' + +describe('findClosestGitRepository', () => { + it('should match .git suffix', () => { + const repositories: GitRepository[] = [ + { url: 'https://github.com/example/test' }, + ] + const result = findClosestGitRepository(repositories, 'https://github.com/example/test.git') + expect(result).toEqual(repositories[0]) + }) + + it('should match auth in URL', () => { + const repositories: GitRepository[] = [ + { url: 'https://github.com/example/test' }, + ] + const result = findClosestGitRepository(repositories, 'https://creds@github.com/example/test') + expect(result).toEqual(repositories[0]) + }) + + it('should not match different names', () => { + const repositories: GitRepository[] = [ + { url: 'https://github.com/example/anoth-repo' }, + ] + const result = findClosestGitRepository(repositories, 'https://github.com/example/another-repo') + expect(result).toBeUndefined() + }) + + it('should not match repositories with a common prefix', () => { + const repositories: GitRepository[] = [ + { url: 'https://github.com/TabbyML/registry-tabby' }, + ] + const result = findClosestGitRepository(repositories, 'https://github.com/TabbyML/tabby') + expect(result).toBeUndefined() + }) + + it('should not match entirely different repository names', () => { + const repositories: GitRepository[] = [ + { url: 'https://github.com/TabbyML/uptime' }, + ] + const result = findClosestGitRepository(repositories, 'https://github.com/TabbyML/tabby') + expect(result).toBeUndefined() + }) + + it('should not match URL without repository name', () => { + const repositories: GitRepository[] = [ + { url: 'https://github.com/TabbyML/tabby' }, + ] + const result = findClosestGitRepository(repositories, 'https://github.com') + expect(result).toBeUndefined() + }) + + it('should not match different host', () => { + const repositories: GitRepository[] = [ + { url: 'https://github.com/TabbyML/tabby' }, + ] + const result = findClosestGitRepository(repositories, 'https://bitbucket.com/TabbyML/tabby') + expect(result).toBeUndefined() + }) + + it('should not match multiple close matches', () => { + const repositories: GitRepository[] = [ + { url: 'https://bitbucket.com/CrabbyML/crabby' }, + { url: 'https://gitlab.com/TabbyML/registry-tabby' }, + ] + const result = findClosestGitRepository(repositories, 'git@github.com:TabbyML/tabby') + expect(result).toBeUndefined() + }) + + it('should match different protocol and suffix', () => { + const repositories: GitRepository[] = [ + { url: 'https://github.com/TabbyML/tabby' }, + ] + const result = findClosestGitRepository(repositories, 'git@github.com:TabbyML/tabby.git') + expect(result).toEqual(repositories[0]) + }) + + it('should match different protocol', () => { + const repositories: GitRepository[] = [ + { url: 'https://github.com/TabbyML/tabby' }, + ] + const result = findClosestGitRepository(repositories, 'git@github.com:TabbyML/tabby') + expect(result).toEqual(repositories[0]) + }) + + it('should match URL without organization', () => { + const repositories: GitRepository[] = [ + { url: 'https://custom-git.com/TabbyML/tabby' }, + ] + const result = findClosestGitRepository(repositories, 'https://custom-git.com/tabby') + expect(result).toEqual(repositories[0]) + }) + + it('should match local URL', () => { + const repositories: GitRepository[] = [ + { url: 'file:///home/TabbyML/tabby' }, + ] + const result = findClosestGitRepository(repositories, 'git@github.com:TabbyML/tabby.git') + expect(result).toEqual(repositories[0]) + }) +}) diff --git a/ee/tabby-ui/tsconfig.json b/ee/tabby-ui/tsconfig.json index a625d1aef0af..a62cc00a466e 100644 --- a/ee/tabby-ui/tsconfig.json +++ b/ee/tabby-ui/tsconfig.json @@ -29,7 +29,9 @@ "next-auth.d.ts", "**/*.ts", "**/*.tsx", - ".next/types/**/*.ts" -, "lib/patch-fetch.js" ], + ".next/types/**/*.ts", + "lib/patch-fetch.js", + "test" + ], "exclude": ["node_modules"] } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 94d67021249f..22d6692e4b94 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -224,6 +224,9 @@ importers: clients/tabby-chat-panel: dependencies: + git-url-parse: + specifier: ^16.0.0 + version: 16.0.0 react: specifier: 18.2.0 version: 18.2.0 @@ -237,6 +240,9 @@ importers: '@antfu/ni': specifier: ^0.21.12 version: 0.21.12 + '@types/git-url-parse': + specifier: ^9.0.3 + version: 9.0.3 '@types/node': specifier: ^20.12.7 version: 20.12.12 @@ -360,10 +366,10 @@ importers: version: 5.2.0 esbuild-plugin-copy: specifier: ^2.1.1 - version: 2.1.1(esbuild@0.19.12) + version: 2.1.1(esbuild@0.20.2) esbuild-plugin-polyfill-node: specifier: ^0.3.0 - version: 0.3.0(esbuild@0.19.12) + version: 0.3.0(esbuild@0.20.2) eslint: specifier: ^8.55.0 version: 8.57.0 @@ -601,6 +607,9 @@ importers: fuzzysort: specifier: ^3.0.2 version: 3.0.2 + git-url-parse: + specifier: ^16.0.0 + version: 16.0.0 graphql: specifier: ^16.8.1 version: 16.8.1 @@ -767,6 +776,9 @@ importers: '@types/dompurify': specifier: ^3.0.5 version: 3.0.5 + '@types/git-url-parse': + specifier: ^9.0.3 + version: 9.0.3 '@types/he': specifier: ^1.2.3 version: 1.2.3 @@ -854,6 +866,9 @@ importers: typescript: specifier: ^5.1.3 version: 5.2.2 + vitest: + specifier: ^1.5.2 + version: 1.6.0(@types/node@17.0.45)(terser@5.31.0) packages: @@ -3944,6 +3959,9 @@ packages: '@types/fs-extra@11.0.4': resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + '@types/git-url-parse@9.0.3': + resolution: {integrity: sha512-Wrb8zeghhpKbYuqAOg203g+9YSNlrZWNZYvwxJuDF4dTmerijqpnGbI79yCuPtHSXHPEwv1pAFUB4zsSqn82Og==} + '@types/glob@7.2.0': resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} @@ -4022,6 +4040,9 @@ packages: '@types/object-hash@3.0.6': resolution: {integrity: sha512-fOBV8C1FIu2ELinoILQ+ApxcUKz4ngq+IWUYrxSGjXzzjUALijilampwkMgEtJ+h2njAW3pi853QpzNVCHB73w==} + '@types/parse-path@7.0.3': + resolution: {integrity: sha512-LriObC2+KYZD3FzCrgWGv/qufdUy4eXrxcLgQMfYXgPbLIecKIsVBaQgUPmxSSLcjmYbDTQbMgr6qr6l/eb7Bg==} + '@types/parse5@6.0.3': resolution: {integrity: sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==} @@ -6513,6 +6534,12 @@ packages: resolution: {integrity: sha512-8EHPljDvs7qKykr6uw8b+lqLiUc/vUg+KVTI0uND4s63TdsZM2Xus3mflvF0DDG9SiM4RlCkFGL+7aAjRmV7KA==} hasBin: true + git-up@8.0.0: + resolution: {integrity: sha512-uBI8Zdt1OZlrYfGcSVroLJKgyNNXlgusYFzHk614lTasz35yg2PVpL1RMy0LOO2dcvF9msYW3pRfUSmafZNrjg==} + + git-url-parse@16.0.0: + resolution: {integrity: sha512-Y8iAF0AmCaqXc6a5GYgPQW9ESbncNLOL+CeQAJRhmWUOmnPkKpBYeWYp4mFd3LA5j53CdGDdslzX12yEBVHQQg==} + github-from-package@0.0.0: resolution: {integrity: sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==} @@ -7104,6 +7131,9 @@ packages: resolution: {integrity: sha512-nA2hv5XIhLR3uVzDDfCIknerhx8XUKnstuOERPNNIinXG7v9u+ohXF67vxm4TPTEPU6lm61ZkwP3c9PCB97rhg==} engines: {node: '>= 0.4'} + is-ssh@1.4.0: + resolution: {integrity: sha512-x7+VxdxOdlV3CYpjvRLBv5Lo9OJerlYanjwFrPR9fuGPjCiNiCzFgAWpiLAohSbsnH4ZAys3SBh+hq5rJosxUQ==} + is-stream@2.0.1: resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} engines: {node: '>=8'} @@ -8324,9 +8354,16 @@ packages: resolution: {integrity: sha512-1Y1A//QUXEZK7YKz+rD9WydcE1+EuPr6ZBgKecAB8tmoW6UFv0NREVJe1p+jRxtThkcbbKkfwIbWJe/IeE6m2Q==} engines: {node: '>=0.10.0'} + parse-path@7.0.0: + resolution: {integrity: sha512-Euf9GG8WT9CdqwuWJGdf3RkUcTBArppHABkO7Lm8IzRQp0e2r/kkFnmhu4TSK30Wcu5rVAZLmfPKSBBi9tWFog==} + parse-semver@1.1.1: resolution: {integrity: sha512-Eg1OuNntBMH0ojvEKSrvDSnwLmvVuUOSdylH/pSCPNMIspLlweJyIWXCE+k/5hm3cj/EBUYwmWkjhBALNP4LXQ==} + parse-url@9.2.0: + resolution: {integrity: sha512-bCgsFI+GeGWPAvAiUv63ZorMeif3/U0zaXABGJbOWt5OH2KCaPHF6S+0ok4aqM9RuIPGyZdx9tR9l13PsW4AYQ==} + engines: {node: '>=14.13.0'} + parse5-htmlparser2-tree-adapter@7.0.0: resolution: {integrity: sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==} @@ -8866,6 +8903,9 @@ packages: proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} + protocols@2.0.1: + resolution: {integrity: sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==} + ps-tree@1.2.0: resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} engines: {node: '>= 0.10'} @@ -14351,6 +14391,8 @@ snapshots: '@types/jsonfile': 6.1.4 '@types/node': 20.12.12 + '@types/git-url-parse@9.0.3': {} + '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 @@ -14425,6 +14467,8 @@ snapshots: '@types/object-hash@3.0.6': {} + '@types/parse-path@7.0.3': {} + '@types/parse5@6.0.3': {} '@types/prismjs@1.26.4': {} @@ -16722,11 +16766,11 @@ snapshots: is-date-object: 1.0.5 is-symbol: 1.0.4 - esbuild-plugin-copy@2.1.1(esbuild@0.19.12): + esbuild-plugin-copy@2.1.1(esbuild@0.20.2): dependencies: chalk: 4.1.2 chokidar: 3.6.0 - esbuild: 0.19.12 + esbuild: 0.20.2 fs-extra: 10.1.0 globby: 11.1.0 @@ -16736,6 +16780,12 @@ snapshots: esbuild: 0.19.12 import-meta-resolve: 3.1.1 + esbuild-plugin-polyfill-node@0.3.0(esbuild@0.20.2): + dependencies: + '@jspm/core': 2.0.1 + esbuild: 0.20.2 + import-meta-resolve: 3.1.1 + esbuild@0.19.11: optionalDependencies: '@esbuild/aix-ppc64': 0.19.11 @@ -17755,6 +17805,15 @@ snapshots: pathe: 1.1.2 tar: 6.2.1 + git-up@8.0.0: + dependencies: + is-ssh: 1.4.0 + parse-url: 9.2.0 + + git-url-parse@16.0.0: + dependencies: + git-up: 8.0.0 + github-from-package@0.0.0: optional: true @@ -18432,6 +18491,10 @@ snapshots: dependencies: call-bind: 1.0.7 + is-ssh@1.4.0: + dependencies: + protocols: 2.0.1 + is-stream@2.0.1: {} is-stream@3.0.0: {} @@ -19956,10 +20019,19 @@ snapshots: parse-passwd@1.0.0: {} + parse-path@7.0.0: + dependencies: + protocols: 2.0.1 + parse-semver@1.1.1: dependencies: semver: 5.7.2 + parse-url@9.2.0: + dependencies: + '@types/parse-path': 7.0.3 + parse-path: 7.0.0 + parse5-htmlparser2-tree-adapter@7.0.0: dependencies: domhandler: 5.0.3 @@ -20533,6 +20605,8 @@ snapshots: proto-list@1.2.4: {} + protocols@2.0.1: {} + ps-tree@1.2.0: dependencies: event-stream: 3.3.4 @@ -22466,6 +22540,23 @@ snapshots: d3-time: 3.1.0 d3-timer: 3.0.1 + vite-node@1.6.0(@types/node@17.0.45)(terser@5.31.0): + dependencies: + cac: 6.7.14 + debug: 4.3.4(supports-color@9.4.0) + pathe: 1.1.2 + picocolors: 1.0.1 + vite: 5.2.11(@types/node@17.0.45)(terser@5.31.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + vite-node@1.6.0(@types/node@20.12.12)(terser@5.31.0): dependencies: cac: 6.7.14 @@ -22483,6 +22574,16 @@ snapshots: - supports-color - terser + vite@5.2.11(@types/node@17.0.45)(terser@5.31.0): + dependencies: + esbuild: 0.20.2 + postcss: 8.4.38 + rollup: 4.27.4 + optionalDependencies: + '@types/node': 17.0.45 + fsevents: 2.3.3 + terser: 5.31.0 + vite@5.2.11(@types/node@20.12.12)(terser@5.31.0): dependencies: esbuild: 0.20.2 @@ -22493,6 +22594,39 @@ snapshots: fsevents: 2.3.3 terser: 5.31.0 + vitest@1.6.0(@types/node@17.0.45)(terser@5.31.0): + dependencies: + '@vitest/expect': 1.6.0 + '@vitest/runner': 1.6.0 + '@vitest/snapshot': 1.6.0 + '@vitest/spy': 1.6.0 + '@vitest/utils': 1.6.0 + acorn-walk: 8.3.2 + chai: 4.4.1 + debug: 4.3.4(supports-color@9.4.0) + execa: 8.0.1 + local-pkg: 0.5.0 + magic-string: 0.30.10 + pathe: 1.1.2 + picocolors: 1.0.1 + std-env: 3.7.0 + strip-literal: 2.1.0 + tinybench: 2.8.0 + tinypool: 0.8.4 + vite: 5.2.11(@types/node@17.0.45)(terser@5.31.0) + vite-node: 1.6.0(@types/node@17.0.45)(terser@5.31.0) + why-is-node-running: 2.2.2 + optionalDependencies: + '@types/node': 17.0.45 + transitivePeerDependencies: + - less + - lightningcss + - sass + - stylus + - sugarss + - supports-color + - terser + vitest@1.6.0(@types/node@20.12.12)(terser@5.31.0): dependencies: '@vitest/expect': 1.6.0