diff --git a/web-server/pages/api/internal/[org_id]/git_provider_org.ts b/web-server/pages/api/internal/[org_id]/git_provider_org.ts index d0837a055..928f85f05 100644 --- a/web-server/pages/api/internal/[org_id]/git_provider_org.ts +++ b/web-server/pages/api/internal/[org_id]/git_provider_org.ts @@ -1,15 +1,9 @@ -import { AxiosError } from 'axios'; import * as yup from 'yup'; -import { internal } from '@/api-helpers/axios'; +import { searchGithubRepos } from '@/api/internal/[org_id]/utils'; import { Endpoint } from '@/api-helpers/global'; -import { Errors, ResponseError } from '@/constants/error'; import { Integration } from '@/constants/integrations'; -import { LoadedOrg, Repo } from '@/types/github'; -import { BaseRepo } from '@/types/resources'; import { dec } from '@/utils/auth-supplementary'; -import { getBaseRepoFromUnionRepo } from '@/utils/code'; -import { homogenize } from '@/utils/datatype'; import { db, getFirstRow } from '@/utils/db'; export type CodeSourceProvidersIntegration = @@ -30,57 +24,17 @@ const endpoint = new Endpoint(pathSchema); endpoint.handle.GET(getSchema, async (req, res) => { const { org_id, search_text } = req.payload; - let count = 0; - const repos = await getRepos(org_id, search_text); - const searchResults = [] as BaseRepo[]; - for (let raw_repo of repos) { - const repo = getBaseRepoFromUnionRepo(raw_repo); - if (count >= 5) break; - if (!search_text) { - count++; - searchResults.push(repo); - continue; - } - const repoName = homogenize(`${repo.parent}/${repo.name}`); - const searchText = homogenize(search_text); - const matchesSearch = repoName.includes(searchText); - if (matchesSearch) { - count++; - searchResults.push(repo); - } - } - return res.status(200).send(searchResults); + const token = await getGithubToken(org_id); + const repos = await searchGithubRepos(token, search_text); + + return res.status(200).send(repos); }); export default endpoint.serve(); -const providerOrgBrandingMap = { - bitbucket: 'workspaces', - github: 'orgs', - gitlab: 'groups' -}; - -const THRESHOLD = 300; - -export const getProviderOrgs = ( - org_id: ID, - provider: CodeSourceProvidersIntegration -) => - internal - .get<{ orgs: LoadedOrg[] }>( - `/orgs/${org_id}/integrations/${provider}/${providerOrgBrandingMap[provider]}` - ) - .catch((e: AxiosError) => { - if (e.response.status !== 404) throw e; - throw new ResponseError(Errors.INTEGRATION_NOT_FOUND); - }); - -async function getRepos( - org_id: ID, - searchQuery?: string -): Promise[]> { - const token = await db('Integration') +const getGithubToken = async (org_id: ID) => { + return await db('Integration') .select() .where({ org_id, @@ -89,46 +43,4 @@ async function getRepos( .returning('*') .then(getFirstRow) .then((r) => dec(r.access_token_enc_chunks)); - - const baseUrl = 'https://api.github.com/user/repos'; - const params: URLSearchParams = new URLSearchParams(); - params.set('access_token', token); - - if (searchQuery) { - params.set('q', searchQuery); - } - - let allRepos: any[] = []; - let url = `${baseUrl}?${params.toString()}`; - let response: Response; - - do { - if (allRepos.length >= THRESHOLD) { - break; - } - response = await fetch(url, { - headers: { - Authorization: `token ${token}` - } - }); - - if (!response.ok) { - throw new Error(`Failed to fetch repos: ${response.statusText}`); - } - - const data = (await response.json()) as any[]; - allRepos = allRepos.concat(data); - - const nextLink = response.headers.get('Link'); - if (nextLink) { - const nextUrl = nextLink - .split(',') - .find((link) => link.includes('rel="next"')); - url = nextUrl ? nextUrl.trim().split(';')[0].slice(1, -1) : ''; - } else { - url = ''; - } - } while (url); - - return allRepos; -} +}; diff --git a/web-server/pages/api/internal/[org_id]/utils.ts b/web-server/pages/api/internal/[org_id]/utils.ts new file mode 100644 index 000000000..599831231 --- /dev/null +++ b/web-server/pages/api/internal/[org_id]/utils.ts @@ -0,0 +1,95 @@ +import { BaseRepo } from '@/types/resources'; + +const GITHUB_API_URL = 'https://api.github.com/graphql'; + +type GithubRepo = { + name: string; + url: string; + defaultBranchRef?: { + name: string; + }; + databaseId: string; + description?: string; + primaryLanguage?: { + name: string; + }; + owner: { + login: string; + }; +}; + +type RepoReponse = { + data: { + search: { + edges: { + node: GithubRepo; + }[]; + }; + }; + message?: string; +}; + +export const searchGithubRepos = async ( + pat: string, + searchString: string +): Promise => { + const query = ` + query($queryString: String!) { + search(type: REPOSITORY, query: $queryString, first: 50) { + edges { + node { + ... on Repository { + name + url + defaultBranchRef { + name + } + databaseId + description + primaryLanguage { + name + } + + owner { + login + } + } + } + } + } + } + `; + + const queryString = `${searchString} in:name`; + + const response = await fetch(GITHUB_API_URL, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${pat}` + }, + body: JSON.stringify({ query, variables: { queryString } }) + }); + + const responseBody = (await response.json()) as RepoReponse; + + if (!response.ok) { + throw new Error(`GitHub API error: ${responseBody.message}`); + } + + const repositories = responseBody.data.search.edges.map((edge) => edge.node); + + return repositories.map( + (repo) => + ({ + id: repo.databaseId, + name: repo.name, + desc: repo.description, + slug: repo.name, + parent: repo.owner.login, + web_url: repo.url, + language: repo.primaryLanguage?.name, + branch: repo.defaultBranchRef?.name + }) as BaseRepo + ); +}; diff --git a/web-server/src/components/DateRangePicker/utils.ts b/web-server/src/components/DateRangePicker/utils.ts index 76bc387b5..90b962ae4 100644 --- a/web-server/src/components/DateRangePicker/utils.ts +++ b/web-server/src/components/DateRangePicker/utils.ts @@ -187,9 +187,12 @@ export const presetOptions: { } ]; -export const defaultRange = - process.env.NEXT_PUBLIC_APP_ENVIRONMENT === 'development' - ? DateRangeLogic.oneWeek() - : DateRangeLogic.oneMonth(); +export const defaultDate = { + preset: 'twoWeeks', + range: DateRangeLogic.twoWeeks() +} as { + preset: QuickRangeOptions; + range: DateRange; +}; export const DATE_RANGE_MAX_DIFF = 95; diff --git a/web-server/src/slices/app.ts b/web-server/src/slices/app.ts index 25960db50..7f789f269 100644 --- a/web-server/src/slices/app.ts +++ b/web-server/src/slices/app.ts @@ -4,8 +4,8 @@ import { head, uniq } from 'ramda'; import { handleApi } from '@/api-helpers/axios-api-instance'; import { - defaultRange, - QuickRangeOptions + QuickRangeOptions, + defaultDate } from '@/components/DateRangePicker/utils'; import { Team } from '@/types/api/teams'; import { StateFetchConfig } from '@/types/redux'; @@ -72,10 +72,10 @@ const initialState: State = { errors: {}, singleTeam: [], allTeams: [], - dateRange: defaultRange.map((date) => + dateRange: defaultDate.range.map((date) => date.toISOString() ) as SerializableDateRange, - dateMode: 'oneMonth', + dateMode: defaultDate.preset, branchMode: ActiveBranchMode.ALL, branchNames: '', teamsProdBranchMap: {},