Skip to content
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
104 changes: 8 additions & 96 deletions web-server/pages/api/internal/[org_id]/git_provider_org.ts
Original file line number Diff line number Diff line change
@@ -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 =
Expand All @@ -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<Partial<Repo>[]> {
const token = await db('Integration')
const getGithubToken = async (org_id: ID) => {
return await db('Integration')
.select()
.where({
org_id,
Expand All @@ -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;
}
};
95 changes: 95 additions & 0 deletions web-server/pages/api/internal/[org_id]/utils.ts
Original file line number Diff line number Diff line change
@@ -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<BaseRepo[]> => {
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
);
};
11 changes: 7 additions & 4 deletions web-server/src/components/DateRangePicker/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
8 changes: 4 additions & 4 deletions web-server/src/slices/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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: {},
Expand Down