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
1 change: 1 addition & 0 deletions web-server/libdefs/ambient.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ declare namespace NodeJS {
NEXT_PUBLIC_BUILD_TIME: string;
NEXT_PUBLIC_APP_ENVIRONMENT: 'production' | 'development';
INTERNAL_API_BASE_URL: string;
INTERNAL_SYNC_API_BASE_URL: string;
SECRET_PUBLIC_KEY: string;
SECRET_PRIVATE_KEY: string;
DB_HOST: string;
Expand Down
28 changes: 5 additions & 23 deletions web-server/pages/api/internal/[org_id]/git_provider_org.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { AxiosError } from 'axios';
import { getTeamRepos } from 'pages/api/resources/team_repos';
import * as yup from 'yup';

import { internal } from '@/api-helpers/axios';
Expand Down Expand Up @@ -42,8 +41,7 @@ endpoint.handle.GET(getSchema, async (req, res) => {
const response = await getRepos(
org_id,
provider as CodeSourceProvidersIntegration,
org_name,
team_id
org_name
);
return res.status(200).send(response);
});
Expand Down Expand Up @@ -72,25 +70,9 @@ export const getProviderOrgs = (
export const getRepos = async (
org_id: ID,
provider: CodeSourceProvidersIntegration,
org_name: string,
team_id?: ID
org_name: string
) => {
let [repos, teamRepos] = await Promise.all([
batchPaginatedListsRequest(
`/orgs/${org_id}/integrations/${provider}/${providerOrgBrandingMap[provider]}/${org_name}/repos`
).then((rs) => rs.map(getBaseRepoFromUnionRepo)),
getTeamRepos(team_id)
]);

const teamReposSet = new Set(
teamRepos.map((tr) => `${tr.org_name}/${tr.name}`)
);

if (team_id) {
repos = repos.filter((repo) =>
teamReposSet.has(`${repo.parent}/${repo.name}`)
);
}

return repos;
return await batchPaginatedListsRequest(
`/orgs/${org_id}/integrations/${provider}/${providerOrgBrandingMap[provider]}/${org_name}/repos`
).then((rs) => rs.map(getBaseRepoFromUnionRepo));
};
7 changes: 2 additions & 5 deletions web-server/pages/api/internal/[org_id]/sync_repos.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as yup from 'yup';

import { handleRequest } from '@/api-helpers/axios';
import { handleSyncServerRequest } from '@/api-helpers/axios';
import { Endpoint, nullSchema } from '@/api-helpers/global';

const pathSchema = yup.object().shape({
Expand All @@ -18,9 +18,6 @@ endpoint.handle.POST(nullSchema, async (req, res) => {
});

export const syncReposForOrg = (org_id: ID) =>
Promise.all([
handleRequest(`/orgs/${org_id}/sync_repos`, { method: 'POST' }),
handleRequest(`/orgs/${org_id}/sync_workflows`, { method: 'POST' })
]);
handleSyncServerRequest(`/orgs/${org_id}/sync`, { method: 'POST' });

export default endpoint.serve();
4 changes: 2 additions & 2 deletions web-server/pages/api/internal/team/[team_id]/get_incidents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ endpoint.handle.GET(getSchema, async (req, res) => {
await getAllTeamsReposProdBranchesForOrgAsMap(org_id);
const teamRepoFiltersMap =
repoFiltersFromTeamProdBranches(teamProdBranchesMap);
const pr_filter = await updatePrFilterParams(
const prFilter = await updatePrFilterParams(
team_id,
{},
{
Expand All @@ -63,7 +63,7 @@ endpoint.handle.GET(getSchema, async (req, res) => {
team_id,
from_date,
to_date,
pr_filter
pr_filter: prFilter.pr_filter
});

return res.send({
Expand Down
149 changes: 56 additions & 93 deletions web-server/pages/api/resources/orgs/[org_id]/teams/v2.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { groupBy, prop, mapObjIndexed, forEachObjIndexed } from 'ramda';
import * as yup from 'yup';

import { enableOrgReposIfNotEnabled } from '@/api/integrations/orgs';
import {
CodeSourceProvidersIntegration,
getProviderOrgs,
Expand All @@ -12,12 +11,13 @@ import {
updateOnBoardingState
} from '@/api/resources/orgs/[org_id]/onboarding';
import { getTeamRepos } from '@/api/resources/team_repos';
import { handleRequest } from '@/api-helpers/axios';
import { Endpoint } from '@/api-helpers/global';
import { Columns, Table } from '@/constants/db';
import { Row } from '@/constants/db';
import { Integration } from '@/constants/integrations';
import { getTeamV2Mock } from '@/mocks/teams';
import { BaseTeam } from '@/types/api/teams';
import { OnboardingStep, ReqOrgRepo } from '@/types/resources';
import { OnboardingStep, ReqRepoWithProvider } from '@/types/resources';
import { db, getFirstRow } from '@/utils/db';

const getSchema = yup.object().shape({
Expand Down Expand Up @@ -111,63 +111,66 @@ endpoint.handle.POST(postSchema, async (req, res) => {
}

const { org_repos, org_id, provider, name } = req.payload;
const orgReposList: ReqOrgRepo[] = [];
forEachObjIndexed(
(repos, org) => orgReposList.push({ org, repos }),
org_repos
);

const [team, repos, onboardingState] = await Promise.all([
const orgReposList: ReqRepoWithProvider[] = [];
forEachObjIndexed((repos, org) => {
repos.forEach((repo) => {
orgReposList.push({
...repo,
org,
provider
} as any as ReqRepoWithProvider);
});
}, org_repos);
const [team, onboardingState] = await Promise.all([
createTeam(org_id, name, []),
enableOrgReposIfNotEnabled(org_id, provider as Integration, orgReposList),
getOnBoardingState(org_id)
]);

const updatedOnboardingState = Array.from(
new Set(onboardingState.onboarding_state).add(OnboardingStep.TEAM_CREATED)
);
const [teamRepos] = await Promise.all([
addReposToTeam(
team.id,
repos.map((r) => r.id)
),
handleRequest<(Row<'TeamRepos'> & Row<'OrgRepo'>)[]>(
`/teams/${team.id}/repos`,
{
method: 'PUT',
data: {
repos: orgReposList
}
}
).then((repos) => repos.map((r) => ({ ...r, team_id: team.id }))),
updateOnBoardingState(org_id, updatedOnboardingState)
]);

res.send({ team, teamReposMap: groupBy(prop('team_id'), teamRepos), repos });
res.send({ team, teamReposMap: groupBy(prop('team_id'), teamRepos) });
});

endpoint.handle.PATCH(patchSchema, async (req, res) => {
if (req.meta?.features?.use_mock_data) {
return res.send(getTeamV2Mock);
}

const { org_id, id, name, org_repos, provider } = req.payload;
const orgReposList: ReqOrgRepo[] = [];
forEachObjIndexed(
(repos, org) => orgReposList.push({ org, repos }),
org_repos
);

const upsertPayload: Record<string, any> = {
id,
org_id,
updated_at: new Date(),
manager_id: null
};

if (name) {
upsertPayload.name = name;
}

const [team, repos] = await Promise.all([
const { id, name, org_repos, provider } = req.payload;
const orgReposList: ReqRepoWithProvider[] = [];
forEachObjIndexed((repos, org) => {
repos.forEach((repo) => {
orgReposList.push({
...repo,
org,
provider
} as any as ReqRepoWithProvider);
});
}, org_repos);

const [team, teamRepos] = await Promise.all([
updateTeam(id, name, []),
enableOrgReposIfNotEnabled(org_id, provider as Integration, orgReposList)
handleRequest<(Row<'TeamRepos'> & Row<'OrgRepo'>)[]>(`/teams/${id}/repos`, {
method: 'PUT',
data: {
repos: orgReposList
}
}).then((repos) => repos.map((r) => ({ ...r, team_id: id })))
]);
const teamRepos = await addReposToTeam(
id,
repos.map((r) => r.id)
);

res.send({ team, teamReposMap: groupBy(prop('team_id'), teamRepos) });
});

Expand All @@ -193,67 +196,27 @@ const updateTeam = async (
team_name: string,
member_ids: string[]
): Promise<BaseTeam> => {
return db('Team')
.insert({
id: team_id,
return handleRequest<BaseTeam>(`/team/${team_id}`, {
method: 'PATCH',
data: {
name: team_name,
member_ids: member_ids,
is_deleted: false,
updated_at: new Date()
})
.onConflict('id')
.merge()
.returning('*')
.then(getFirstRow);
member_ids
}
});
};

const createTeam = async (
org_id: ID,
team_name: string,
member_ids: string[]
): Promise<BaseTeam> => {
return db(Table.Team)
.insert({
org_id,
return handleRequest<BaseTeam>(`/org/${org_id}/team`, {
method: 'POST',
data: {
name: team_name,
member_ids: member_ids,
is_deleted: false
})
.returning('*')
.then(getFirstRow);
};

const addReposToTeam = async (team_id: ID, repo_ids: ID[]) => {
try {
await db(Table.TeamRepos)
.update({
[Columns[Table.TeamRepos].is_active]: false,
updated_at: new Date()
})
.where('team_id', team_id);
} catch (err) {}

try {
const payload = repo_ids.map((repo_id) => ({
[Columns[Table.TeamRepos].team_id]: team_id,
[Columns[Table.TeamRepos].org_repo_id]: repo_id,
[Columns[Table.TeamRepos].is_active]: true
}));
const data =
payload.length &&
(await db('TeamRepos')
.insert(
repo_ids.map((repo_id) => ({
[Columns[Table.TeamRepos].team_id]: team_id,
[Columns[Table.TeamRepos].org_repo_id]: repo_id,
[Columns[Table.TeamRepos].is_active]: true
}))
)
.onConflict(['team_id', 'org_repo_id'])
.merge()
.returning('*'));
return data;
} catch (err) {}
member_ids
}
});
};

export const getAllOrgRepos = async (
Expand Down
11 changes: 3 additions & 8 deletions web-server/pages/api/resources/team_repos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,9 @@ endpoint.handle.GET(getSchema, async (req, res) => {
});

export const getTeamRepos = (team_id: ID) =>
(team_id
? db('TeamRepos')
.select('*', 'OrgRepo.* as org_repo')
.leftJoin('OrgRepo', 'OrgRepo.id', 'TeamRepos.org_repo_id')
.where('TeamRepos.is_active', true)
.andWhere('TeamRepos.team_id', team_id)
.orderBy('name', 'asc')
: []) as any as Promise<(Row<'TeamRepos'> & Row<'OrgRepo'>)[]>;
handleRequest<(Row<'TeamRepos'> & Row<'OrgRepo'>)[]>(
`/teams/${team_id}/repos`
).then((repos) => repos.map((r) => ({ ...r, team_id: team_id })));

endpoint.handle.PATCH(patchSchema, async (req, res) => {
if (req.meta?.features?.use_mock_data) {
Expand Down
14 changes: 11 additions & 3 deletions web-server/pages/integrations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { FlexBox } from '@/components/FlexBox';
import { Line } from '@/components/Text';
import { Integration } from '@/constants/integrations';
import { ROUTES } from '@/constants/routes';
import { FetchState } from '@/constants/ui-states';
import { GithubIntegrationCard } from '@/content/Dashboards/IntegrationCards';
import { PageWrapper } from '@/content/PullRequests/PageWrapper';
import { useAuth } from '@/hooks/useAuth';
Expand All @@ -18,6 +19,10 @@ import { PageLayout } from '@/types/resources';
import { depFn } from '@/utils/fn';

function Integrations() {
const isLoading = useSelector(
(s) => s.team.requests?.teams === FetchState.REQUEST
);

return (
<>
<PageWrapper
Expand All @@ -29,6 +34,7 @@ function Integrations() {
hideAllSelectors
pageTitle="Integrations"
showEvenIfNoTeamSelected={true}
isLoading={isLoading}
>
<Content />
</PageWrapper>
Expand All @@ -51,6 +57,9 @@ const Content = () => {
const dispatch = useDispatch();
const loading = useBoolState(false);

const teamCount = teams.length;
const showCreationCTA = isLinked && !teamCount && !loading.value;

useEffect(() => {
if (!orgId) return;
depFn(loading.true);
Expand All @@ -62,14 +71,13 @@ const Content = () => {
).finally(loading.false);
}, [dispatch, loading.false, loading.true, orgId]);

const teamCount = teams.length;
return (
<FlexBox col gap2>
<FlexBox justifyBetween>
<Line white fontSize={'24px'}>
Integrate your Github to fetch DORA for your team
</Line>
{Boolean(teamCount) && (
{Boolean(teamCount) && Boolean(isLinked) && (
<Button href={ROUTES.DORA_METRICS.PATH} variant="contained">
<FlexBox centered fullWidth p={2 / 3}>
Continue to Dora {'->'}
Expand All @@ -82,7 +90,7 @@ const Content = () => {
<FlexBox>
<GithubIntegrationCard />
</FlexBox>
{isLinked && !teamCount && !loading && (
{showCreationCTA && (
<FlexBox mt={'56px'} col fit alignStart>
<Line fontSize={'24px'} semibold white>
Create team structure to see DORA
Expand Down
Loading