feat(pull_requests): make it possible to create a PR from a branchin same repository or from forks
@@ -1,5 +1,5 @@
{
- "_generatedAtUnix": 1665177333861,
+ "_generatedAtUnix": 1665327556839,
"_hashAlgorithm": "sha1",
"_version": 2,
"islands": {
@@ -15,6 +15,12 @@
"pathBundle": "./public/.islands/InstantRouterIndicator.bundle.js",
"pathSourceMap": "./public/.islands/InstantRouterIndicator.bundle.js.map"
},
+ "PullRequestSourceSelect": {
+ "hash": "0bf2070d8f31e61476388b8b753d1979ccda9f5d",
+ "pathSource": "./app/islands/PullRequestSourceSelect.tsx",
+ "pathBundle": "./public/.islands/PullRequestSourceSelect.bundle.js",
+ "pathSourceMap": "./public/.islands/PullRequestSourceSelect.bundle.js.map"
+ },
"RepositoriesList": {
"hash": "123782350476918ef2f540ddaca91ef9c82bcc8f",
"pathSource": "./app/islands/RepositoriesList.tsx",
@@ -58,7 +64,7 @@
"pathSourceMap": "./public/.islands/RepositoryInitialSetup.bundle.js.map"
},
"RepositoryPullRequestCreateForm": {
- "hash": "a0a6064032fc7cc6034090bb18cdf52b6de02d16",
+ "hash": "da23a4714b31aff0f9a61eaf5635080ce8755859",
"pathSource": "./app/islands/RepositoryPullRequestCreateForm.tsx",
"pathBundle": "./public/.islands/RepositoryPullRequestCreateForm.bundle.js",
"pathSourceMap": "./public/.islands/RepositoryPullRequestCreateForm.bundle.js.map"
@@ -120,7 +126,7 @@
"pathSource": "./app/views/repository/RepositoryForkView.tsx"
},
"RepositoryPullRequestCreateView": {
- "hash": "84f98ab3cd89b3a3842a5e0118a08ae141179a73",
+ "hash": "c35bc6337734776b13969010051805f4dc4c4d3a",
"pathSource": "./app/views/repository/RepositoryPullRequestCreateView.tsx"
},
"RepositoryPullRequestsView": {
@@ -1,14 +1,18 @@
// 1st-party
import { ReqHandler } from "@ethicdevs/react-monolith";
// app
+import type { RepositoryWithParentAndForkedFromRepos } from "../../types";
+import { Const } from "../../const";
import { AppRoute, AppRoutesParams } from "../../routes";
// app services
import { makeOrganizationService } from "../../services/organization";
import { makeRepositoryService } from "../../services/repository";
+import { makeUsersService } from "../../services/user";
// app islands
import {
PullRequestFormState,
RepositoryPullRequestCreateFormVariant,
+ RepositoryPullRequestTarget,
} from "../../islands/RepositoryPullRequestCreateForm";
// app views
import RepositoryPullRequestCreateView, {
@@ -19,6 +23,14 @@ const getRepositoryPullRequestCreateView: ReqHandler = async (
request,
reply
) => {
+ if (
+ request.session.data.authenticated === false ||
+ request.session.data.curr_user_uid == null
+ ) {
+ reply.redirect(302, request.namedViewsPathMap[AppRoute.AUTH_LOGIN]);
+ return reply;
+ }
+
const { from_branch } =
request.query as AppRoutesParams[AppRoute.REPOSITORY_PULL_REQUEST_CREATE]["querystring"];
const { orgSlug, repoSlug } =
@@ -26,6 +38,8 @@ const getRepositoryPullRequestCreateView: ReqHandler = async (
const orgService = makeOrganizationService({ request });
const repoService = makeRepositoryService({ request });
+ const usersService = makeUsersService({ request });
+ usersService;
const parentOrg = await orgService.getOrganizationBySlug(orgSlug);
const repo = await repoService.getRepository(orgSlug, repoSlug);
@@ -35,26 +49,138 @@ const getRepositoryPullRequestCreateView: ReqHandler = async (
}
const branches = await repoService.getRepositoryBranches(repo);
+ const currentUserForks = await repoService.getCurrentUserRepositoryForks(
+ repo
+ );
- const variant: RepositoryPullRequestCreateFormVariant = {
- state: PullRequestFormState.CONFIGURE,
- data: {
- source: {
- branches,
- parentOrg,
- repo,
- isFork: repo.forkedFromRepoId != null,
- },
- initialTarget:
- from_branch != null
- ? {
- parentOrg,
- repo,
- branch: from_branch,
- }
- : undefined,
+ const repos = await currentUserForks.reduce(
+ async (accP, r) => {
+ let acc = await accP;
+ acc = {
+ ...acc,
+ [r.slug]: {
+ repo: r as any,
+ branches: await repoService.getRepositoryBranches(r),
+ },
+ };
+ return acc;
},
- };
+ Promise.resolve({}) as Promise<{
+ [repoSlug: string]: {
+ repo: RepositoryWithParentAndForkedFromRepos;
+ branches: string[];
+ };
+ }>
+ );
+
+ // Inject PR target's repository first.
+ // const [personalOrg] = await usersService.getUserOrganizations(
+ // request.session.data.curr_user_uid,
+ // true // personalOnly
+ // );
+
+ // const personalRepos = {
+ // [repo.slug]: { repo: repo as any, branches },
+ // };
+
+ let variant: RepositoryPullRequestCreateFormVariant;
+ if (from_branch == null) {
+ variant = {
+ state: PullRequestFormState.CONFIGURE,
+ data: {
+ sources: {
+ // In case user has forks on its own, add its personal org (where forks **should** be)
+ // ...(currentUserForks.length >= 1 && {
+ // [personalOrg.slug]: {
+ // org: personalOrg,
+ // repos: personalRepos,
+ // },
+ // }),
+ // target repo's source
+ [parentOrg.slug]: {
+ org: parentOrg,
+ repos: {
+ [repo.slug]: { repo: repo as any, branches },
+ ...repos,
+ },
+ },
+ },
+ target: {
+ branch: from_branch || Const.PRIMARY_BRANCH_REF,
+ parentOrg,
+ repo,
+ },
+ },
+ };
+ } else {
+ let target: RepositoryPullRequestTarget = {
+ branch: from_branch,
+ parentOrg,
+ repo,
+ };
+
+ const orgRepoBranchRegExp = /^([\w\-_\.]+)\/([\w\-_\.]+)@(.*)/i;
+ const matches = from_branch.match(orgRepoBranchRegExp);
+
+ if (matches != null && Array.isArray(matches)) {
+ const [_, targetOrgSlug, targetRepoSlug, targetFromBranch] = matches;
+ const targetParentOrg = await orgService.getOrganizationBySlug(
+ targetOrgSlug
+ );
+ const targetRepo = await repoService.getRepository(
+ targetOrgSlug,
+ targetRepoSlug
+ );
+
+ if (targetParentOrg == null || targetRepo == null) {
+ return reply.status(404).callNotFound();
+ }
+
+ const fileDiffs = await repoService.getRepositoryRefDiff(
+ repo,
+ Const.PRIMARY_BRANCH_REF,
+ targetFromBranch
+ );
+
+ target = {
+ branch: from_branch,
+ parentOrg: targetParentOrg,
+ repo: targetRepo,
+ };
+
+ variant = {
+ state: PullRequestFormState.COMPARE,
+ data: {
+ source: {
+ branch: Const.PRIMARY_BRANCH_REF,
+ parentOrg,
+ repo,
+ },
+ target,
+ fileDiffs,
+ },
+ };
+ } else {
+ const fileDiffs = await repoService.getRepositoryRefDiff(
+ repo,
+ Const.PRIMARY_BRANCH_REF,
+ from_branch
+ );
+
+ variant = {
+ state: PullRequestFormState.COMPARE,
+ data: {
+ source: {
+ branch: Const.PRIMARY_BRANCH_REF,
+ parentOrg,
+ repo,
+ },
+ target,
+ fileDiffs,
+ },
+ };
+ }
+ }
const reqHandler = reply.makeRequestHandler(request, reply);
return reqHandler<RepositoryPullRequestCreateViewProps>(
@@ -9,7 +9,9 @@ import {
} from "../../islands/RepositoryPullRequestCreateForm";
// app services
import { makeOrganizationService } from "../../services/organization";
+import { makePullRequestService } from "../../services/pullRequest";
import { makeRepositoryService } from "../../services/repository";
+import { makeUsersService } from "../../services/user";
// app views
import RepositoryPullRequestCreateView, {
RepositoryPullRequestCreateViewProps,
@@ -19,9 +21,19 @@ const postRepositoryPullRequestCreateAction: ReqHandler = async (
request,
reply
) => {
+ if (
+ request.session.data.authenticated === false ||
+ request.session.data.curr_user_uid == null
+ ) {
+ reply.redirect(302, request.namedViewsPathMap[AppRoute.AUTH_LOGIN]);
+ return reply;
+ }
+
const { orgSlug, repoSlug } =
request.params as AppRoutesParams[AppRoute.REPOSITORY_PULL_REQUEST_CREATE_ACTION]["params"];
const {
+ description,
+ summary,
state_from: fromState,
state_dest: desiredState,
source_parent_org_slug: sourceParentOrgSlug,
@@ -34,6 +46,17 @@ const postRepositoryPullRequestCreateAction: ReqHandler = async (
const orgService = makeOrganizationService({ request });
const repoService = makeRepositoryService({ request });
+ const pullsService = makePullRequestService({ request });
+ const usersService = makeUsersService({ request });
+
+ const currentUser = await usersService.getUserById(
+ request.session.data.curr_user_uid
+ );
+
+ if (currentUser == null) {
+ reply.redirect(302, request.namedViewsPathMap[AppRoute.AUTH_LOGIN]);
+ return reply;
+ }
const parentOrg = await orgService.getOrganizationBySlug(orgSlug);
const repo = await repoService.getRepository(orgSlug, repoSlug);
@@ -107,10 +130,40 @@ const postRepositoryPullRequestCreateAction: ReqHandler = async (
},
};
} else if (desiredState === PullRequestFormState.DETAILS) {
+ // source
+ const sourceRepo = await repoService.getRepository(
+ sourceParentOrgSlug,
+ sourceRepoSlug
+ );
+ // target
+ const targetRepo = await repoService.getRepository(
+ targetParentOrgSlug,
+ targetRepoSlug
+ );
+
+ if (sourceRepo == null || targetRepo == null) {
+ return reply.status(404).callNotFound();
+ }
+
+ const pullRequest = await pullsService.createPullRequest({
+ summary,
+ textMd: description,
+ author: currentUser,
+ source: {
+ repository: targetRepo,
+ fromBranch: targetRepoDestBranch,
+ },
+ target: {
+ repository: sourceRepo,
+ destBranch: sourceRepoFromBranch,
+ },
+ });
+
variant = {
state: desiredState,
data: {
canCurrentUserSubmitPullRequest: false,
+ pullRequest,
},
};
}
@@ -0,0 +1,224 @@
+// 1st-party
+import type { ReactIsland } from "@ethicdevs/react-monolith";
+// 3rd-party
+import React, { useCallback, useEffect, useMemo, useState } from "react";
+// generated via script[generate:prisma]
+import { Organization } from "@prisma/client";
+// app
+import { RepositoryWithParentAndForkedFromRepos } from "../types";
+
+export interface PullRequestSourceSelectionChangeEvent {
+ data: {
+ selectedOrg: {
+ org: Organization;
+ repos: {
+ [repoSlug: string]: {
+ repo: RepositoryWithParentAndForkedFromRepos;
+ branches: string[];
+ };
+ };
+ };
+ selectedRepo: {
+ repo: RepositoryWithParentAndForkedFromRepos;
+ branches: string[];
+ };
+ selectedBranch: string;
+ };
+}
+
+export interface PullRequestSourceSelectProps {
+ defaultSource: {
+ org: Organization;
+ repo: RepositoryWithParentAndForkedFromRepos;
+ branch: string;
+ };
+ sources: {
+ [orgSlug: string]: {
+ org: Organization;
+ repos: {
+ [repoSlug: string]: {
+ repo: RepositoryWithParentAndForkedFromRepos;
+ branches: string[];
+ };
+ };
+ };
+ };
+ namePrefix: "source" | "target";
+ onSelectionChange?: (
+ ev: PullRequestSourceSelectionChangeEvent
+ ) => void | Promise<void>;
+}
+
+const PullRequestSourceSelect: ReactIsland<PullRequestSourceSelectProps> = ({
+ defaultSource,
+ sources,
+ namePrefix,
+ onSelectionChange,
+}) => {
+ const [selectedOrgSlug, setSelectedOrgSlug] = useState<string>(
+ defaultSource.org.slug
+ );
+ const [selectedRepoSlug, setSelectedRepoSlug] = useState<string>(
+ defaultSource.repo.slug
+ );
+ const [selectedBranch, setSelectedBranch] = useState<string>(
+ defaultSource.branch
+ );
+
+ const allOrganizations = useMemo(() => {
+ const orgsBySlug = Object.entries(sources);
+ return orgsBySlug.reduce((acc, [orgSlug, { org }]) => {
+ acc = [
+ ...acc,
+ {
+ key: orgSlug,
+ value: org.displayName || org.slug,
+ },
+ ];
+ return acc;
+ }, [] as { key: string; value: string }[]);
+ }, [sources]);
+
+ const allReposBySelectedOrgSlug = useMemo(() => {
+ const orgsBySlug = Object.entries(sources);
+ const org = orgsBySlug.find(([_, o]) => o.org.slug === selectedOrgSlug);
+ if (org == null) return [];
+ const [_selectedOrgSlug, { repos: orgRepositories }] = org;
+ const reposBySlug = Object.entries(orgRepositories);
+ return reposBySlug.reduce((acc, [repoSlug, { repo }]) => {
+ acc = [
+ ...acc,
+ {
+ key: repoSlug,
+ value: repo.displayName || repo.slug,
+ },
+ ];
+ return acc;
+ }, [] as { key: string; value: string }[]);
+ }, [sources, selectedOrgSlug]);
+
+ const allBranchesBySelectedOrgAndRepoSlugs = useMemo(() => {
+ const orgsBySlug = Object.entries(sources);
+ const org = orgsBySlug.find(([_, o]) => o.org.slug === selectedOrgSlug);
+ if (org == null) return [];
+ const [_selectedOrgSlug, { repos: orgRepositories }] = org;
+ const reposBySlug = Object.entries(orgRepositories);
+ const repo = reposBySlug.find(([_, r]) => r.repo.slug === selectedRepoSlug);
+ if (repo == null) return [];
+ const [_selectedRepoSlug, { branches: repoBranches }] = repo;
+ return repoBranches.map((branch) => ({
+ key: branch,
+ value: branch,
+ })) as { key: string; value: string }[];
+ }, [sources, selectedOrgSlug, selectedRepoSlug]);
+
+ const selectedOrg = useMemo(() => {
+ const orgsBySlug = Object.entries(sources);
+ const org = orgsBySlug.find(([_, o]) => o.org.slug === selectedOrgSlug);
+ return org != null ? org[1] : null;
+ }, [sources, selectedOrgSlug]);
+
+ const selectedRepo = useMemo(() => {
+ const orgsBySlug = Object.entries(sources);
+ const parentOrg = orgsBySlug.find(
+ ([_, o]) => o.org.slug === selectedOrgSlug
+ );
+ if (parentOrg == null) return null;
+ const reposBySlug = Object.entries(parentOrg[1].repos);
+ const repo = reposBySlug.find(([_, r]) => r.repo.slug === selectedRepoSlug);
+ return repo != null ? repo[1] : null;
+ }, [sources, selectedOrgSlug, selectedRepoSlug]);
+
+ const onOrgSlugChange = useCallback(
+ (ev: React.ChangeEvent<HTMLSelectElement>) => {
+ if (ev == null) return;
+ if (ev.target == null) return;
+ if (ev.target.value == null) return;
+ setSelectedOrgSlug(ev.target.value);
+ },
+ [setSelectedOrgSlug]
+ );
+
+ const onRepoSlugChange = useCallback(
+ (ev: React.ChangeEvent<HTMLSelectElement>) => {
+ if (ev == null) return;
+ if (ev.target == null) return;
+ if (ev.target.value == null) return;
+ setSelectedRepoSlug(ev.target.value);
+ },
+ [setSelectedRepoSlug]
+ );
+
+ const onBranchChange = useCallback(
+ (ev: React.ChangeEvent<HTMLSelectElement>) => {
+ if (ev == null) return;
+ if (ev.target == null) return;
+ if (ev.target.value == null) return;
+ setSelectedBranch(ev.target.value);
+ },
+ [setSelectedBranch]
+ );
+
+ const keyValueToOptionMapFn = useMemo(
+ () =>
+ ({ key, value }: { key: string; value: string }) =>
+ (
+ <option key={key} value={key}>
+ {value}
+ </option>
+ ),
+ []
+ );
+
+ useEffect(() => {
+ if (selectedOrg != null && selectedRepo != null && selectedBranch != null) {
+ if (
+ onSelectionChange != null &&
+ typeof onSelectionChange === "function" &&
+ selectedOrg != null &&
+ selectedRepo != null
+ ) {
+ onSelectionChange({
+ data: {
+ selectedOrg,
+ selectedRepo,
+ selectedBranch,
+ },
+ });
+ }
+ }
+ }, [onSelectionChange, selectedOrg, selectedRepo, selectedBranch]);
+
+ return (
+ <div>
+ <select
+ name={`${namePrefix}_parent_org_slug`}
+ value={selectedOrgSlug}
+ onChange={onOrgSlugChange}
+ >
+ {allOrganizations.map(keyValueToOptionMapFn)}
+ </select>
+ {" / "}
+ <select
+ name={`${namePrefix}_repository_slug`}
+ value={selectedRepoSlug}
+ onChange={onRepoSlugChange}
+ >
+ {allReposBySelectedOrgSlug.map(keyValueToOptionMapFn)}
+ </select>
+ {" / "}
+ <select
+ name={`${namePrefix}_repository_${
+ namePrefix === "source" ? "from" : "dest"
+ }_branch`}
+ value={selectedBranch}
+ onChange={onBranchChange}
+ >
+ {allBranchesBySelectedOrgAndRepoSlugs.map(keyValueToOptionMapFn)}
+ </select>
+ </div>
+ );
+};
+
+PullRequestSourceSelect.displayName = "PullRequestSourceSelect";
+export default PullRequestSourceSelect;
@@ -1,19 +1,23 @@
// 1st-party
import type { ReactIsland } from "@ethicdevs/react-monolith";
// 3rd-party
-import React from "react";
+import React, { useCallback } from "react";
// generated via script[generate:prisma]
-import type { Organization, Repository } from "@prisma/client";
+import type { Organization, PullRequest, Repository } from "@prisma/client";
// app
import type {
AppThemeScheme,
RepositoryFileDiff,
- RepositoryForkedFromRepoMeta,
+ RepositoryWithParentAndForkedFromRepos,
} from "../types";
+import { Card } from "../components/Card.styled";
import { Grid } from "../components/Grid";
import { IslandWrapper } from "../components/IslandWrapper.styled";
// app islands
import RepositoryFilesDiffsList from "./RepositoryFilesDiffsList";
+import PullRequestSourceSelect, {
+ PullRequestSourceSelectionChangeEvent,
+} from "./PullRequestSourceSelect";
export enum PullRequestFormState {
CONFIGURE = "configure",
@@ -35,14 +39,18 @@ export type RepositoryPullRequestCreateFormPropsByState<
S extends PullRequestFormState = PullRequestFormState
> = S extends PullRequestFormState.CONFIGURE
? {
- source: {
- branches: string[];
- parentOrg: Organization;
- repo: Repository;
- isFork: boolean;
- forkedFromRepoMetas?: RepositoryForkedFromRepoMeta;
+ sources: {
+ [orgSlug: string]: {
+ org: Organization;
+ repos: {
+ [repoSlug: string]: {
+ repo: RepositoryWithParentAndForkedFromRepos;
+ branches: string[];
+ };
+ };
+ };
};
- initialTarget?: RepositoryPullRequestTarget;
+ target: RepositoryPullRequestTarget;
}
: S extends PullRequestFormState.COMPARE
? {
@@ -53,6 +61,7 @@ export type RepositoryPullRequestCreateFormPropsByState<
: S extends PullRequestFormState.DETAILS
? {
canCurrentUserSubmitPullRequest: boolean;
+ pullRequest: PullRequest;
}
: S extends PullRequestFormState.ERROR
? {
@@ -80,6 +89,8 @@ export type RepositoryPullRequestCreateFormVariant =
export interface RepositoryPullRequestCreateFormProps
extends RepositoryPullRequestCreateFormPropsCommon {
+ parentOrgSlug: string;
+ repoSlug: string;
themeScheme: AppThemeScheme;
variant: RepositoryPullRequestCreateFormVariant;
}
@@ -126,21 +137,94 @@ const isErrorStateData = (
typeof i !== "undefined" && i != null && s === PullRequestFormState.ERROR;
const RepositoryPullRequestCreateForm: ReactIsland<RepositoryPullRequestCreateFormProps> =
- ({ themeScheme, variant: { state, data } }) => {
+ ({ parentOrgSlug, repoSlug, themeScheme, variant: { state, data } }) => {
+ /*const [selectedSourceOrgSlug, setSelectedSourceOrgSlug] =
+ useState<null | string>(null);*/
+
+ const onSourceSelectionChange = useCallback(
+ (ev: PullRequestSourceSelectionChangeEvent) => {
+ console.warn(
+ `Selected: ${ev.data.selectedOrg.org.slug} / ${ev.data.selectedRepo.repo.slug} / ${ev.data.selectedBranch}`
+ );
+ },
+ []
+ );
+
// PullRequestFormState.CONFIGURE
if (isConfigureState(state) && isConfigureStateData(state, data)) {
return (
<Grid.Col fluid nowrap>
- <h1>Configure input and output (org, repo, branch)</h1>
<form
method={"POST"}
- action={`/${data.source.parentOrg.slug}/${data.source.repo.slug}/pulls/new`}
+ action={`/${parentOrgSlug}/${repoSlug}/pulls/new`}
+ style={{ width: "100%" }}
+ >
+ <Card themeScheme={themeScheme}>
+ <input type={"hidden"} name={"state_from"} value={state} />
+ <input
+ type={"hidden"}
+ name={"state_dest"}
+ value={PullRequestFormState.COMPARE}
+ />
+ <Grid.Row fluid alignItems={"center"}>
+ <div data-islandid={`${PullRequestSourceSelect.name}$$0`}>
+ <PullRequestSourceSelect
+ namePrefix={"source"}
+ defaultSource={{
+ org: Object.values(data.sources)[0].org,
+ repo: Object.values(
+ Object.values(data.sources)[0].repos
+ )[0].repo,
+ branch: Object.values(
+ Object.values(data.sources)[0].repos
+ )[0].branches[0],
+ }}
+ sources={data.sources}
+ onSelectionChange={onSourceSelectionChange}
+ />
+ </div>
+ <div style={{ margin: "0 8px" }}>
+ <span>vs.</span>
+ </div>
+ <div data-islandid={`${PullRequestSourceSelect.name}$$1`}>
+ <PullRequestSourceSelect
+ namePrefix={"target"}
+ defaultSource={{
+ org: Object.values(data.sources)[0].org,
+ repo: Object.values(
+ Object.values(data.sources)[0].repos
+ )[0].repo,
+ branch: Object.values(
+ Object.values(data.sources)[0].repos
+ )[0].branches[0],
+ }}
+ sources={data.sources}
+ onSelectionChange={onSourceSelectionChange}
+ />
+ </div>
+ <button type={"submit"} style={{ marginLeft: 8 }}>
+ Compare
+ </button>
+ </Grid.Row>
+ </Card>
+ </form>
+ </Grid.Col>
+ );
+ }
+ // PullRequestFormState.COMPARE
+ if (isCompareState(state) && isCompareStateData(state, data)) {
+ return (
+ <Grid.Col fluid nowrap>
+ <form
+ method={"POST"}
+ action={`/${parentOrgSlug}/${repoSlug}/pulls/new`}
+ style={{ width: "100%" }}
>
<input type={"hidden"} name={"state_from"} value={state} />
<input
type={"hidden"}
name={"state_dest"}
- value={PullRequestFormState.COMPARE}
+ value={PullRequestFormState.DETAILS}
/>
<input
type={"hidden"}
@@ -154,124 +238,67 @@ const RepositoryPullRequestCreateForm: ReactIsland<RepositoryPullRequestCreateFo
/>
<input
type={"hidden"}
- name={"target_parent_org_slug"}
- value={
- data.initialTarget?.parentOrg.slug || data.source.parentOrg.slug
- }
+ name={"source_repository_from_branch"}
+ value={data.source.branch}
/>
<input
type={"hidden"}
- name={"target_repository_slug"}
- value={
- data.initialTarget?.parentOrg.slug || data.source.repo.slug
- }
- />
-
- <div>
- <span>Source:</span>
- </div>
- <input
- disabled
- type={"text"}
- name={"source_parent_org_display_name"}
- value={
- data.source.parentOrg.displayName || data.source.parentOrg.slug
- }
- />
- {" / "}
- <input
- disabled
- type={"text"}
- name={"source_repository_display_name"}
- value={data.source.repo.displayName || data.source.repo.slug}
- />
- {" / "}
- <select name={"source_repository_from_branch"}>
- {data.source.branches.map((branch, idx) => (
- <option key={[branch, idx].join(":")} value={branch}>
- {branch}
- </option>
- ))}
- </select>
-
- {/* VS. */}
-
- <div>
- <span>Target:</span>
- </div>
-
- <input
- disabled
- type={"text"}
- name={"target_parent_org_display_name"}
- value={
- data.initialTarget?.parentOrg.displayName ||
- data.initialTarget?.parentOrg.slug ||
- data.source.parentOrg.displayName ||
- data.source.parentOrg.slug
- }
+ name={"target_parent_org_slug"}
+ value={data.target.parentOrg.slug}
/>
- {" / "}
<input
- disabled
- type={"text"}
- name={"target_repository_display_name"}
- value={
- data.initialTarget?.repo.displayName ||
- data.initialTarget?.repo.slug ||
- data.source.repo.displayName ||
- data.source.repo.slug
- }
+ type={"hidden"}
+ name={"target_repository_slug"}
+ value={data.target.repo.slug}
/>
- {" / "}
- <select
- name={"target_repository_dest_branch"}
- defaultValue={
- data.initialTarget != null
- ? data.initialTarget.branch
- : data.source.branches.length >= 1
- ? data.source.branches[0]
- : undefined
- }
- >
- {data.initialTarget != null && (
- <option
- key={data.initialTarget.branch}
- value={data.initialTarget.branch}
- >
- {data.initialTarget.branch}
- </option>
- )}
- {data.source.branches.map((branch, idx) => (
- <option key={[branch, idx].join(":")} value={branch}>
- {branch}
- </option>
- ))}
- </select>
-
- <button type={"submit"}>Go to COMPARE step</button>
- </form>
- </Grid.Col>
- );
- }
- // PullRequestFormState.COMPARE
- if (isCompareState(state) && isCompareStateData(state, data)) {
- return (
- <Grid.Col fluid nowrap>
- <h1>Compare input and output (org, repo, branch)</h1>
- <form
- method={"POST"}
- action={`/${data.source.parentOrg.slug}/${data.source.repo.slug}/pulls/new`}
- >
- <input type={"hidden"} name={"state_from"} value={state} />
<input
type={"hidden"}
- name={"state_dest"}
- value={PullRequestFormState.DETAILS}
+ name={"target_repository_dest_branch"}
+ value={data.target.branch}
/>
- <button type={"submit"}>Go to DETAILS step</button>
+ <Grid.Row fluid>
+ <Grid.Col fluid>
+ <Card themeScheme={themeScheme} style={{ width: "100%" }}>
+ <Grid.Col fluid>
+ <label htmlFor={"summary"}>Summary</label>
+ <input
+ type={"text"}
+ name={"summary"}
+ style={{ width: "100%" }}
+ />
+ </Grid.Col>
+ <Grid.Col fluid style={{ marginTop: 8 }}>
+ <label htmlFor={"description"}>Description</label>
+ <textarea
+ name={"description"}
+ style={{
+ width: "100%",
+ maxWidth: "100%",
+ minWidth: "100%",
+ minHeight: 180,
+ }}
+ ></textarea>
+ </Grid.Col>
+ <Grid.Col
+ fluid
+ style={{ marginTop: 8 }}
+ alignItems={"flex-end"}
+ >
+ <button type={"submit"}>Create Pull Request</button>
+ </Grid.Col>
+ </Card>
+ </Grid.Col>
+ <Grid.Col flex={0.3} style={{ marginLeft: 16 }}>
+ <Card themeScheme={themeScheme} style={{ width: "100%" }}>
+ <span>fuck the police</span>
+ </Card>
+ </Grid.Col>
+ </Grid.Row>
</form>
- <IslandWrapper data-islandid={`${RepositoryFilesDiffsList.name}$$0`}>
+ <IslandWrapper
+ data-islandid={`${RepositoryFilesDiffsList.name}$$0`}
+ style={{ marginTop: 16 }}
+ >
<RepositoryFilesDiffsList
filesDiffs={data.fileDiffs}
themeScheme={themeScheme}
@@ -287,7 +314,6 @@ const RepositoryPullRequestCreateForm: ReactIsland<RepositoryPullRequestCreateFo
if (isDetailsState(state) && isDetailsStateData(state, data)) {
return (
<Grid.Col fluid nowrap>
- <h1>Add details about the Pull Request:</h1>
<form
method={"POST"}
// action={`/${data.source.parentOrg.slug}/${data.source.repo.slug}/pulls/new`}
@@ -0,0 +1,77 @@
+// 1st-party
+import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
+// generated via script[generate:prisma]
+import { Repository } from "@prisma/client";
+// app
+import { RepositoryWithParentAndForkedFromRepos } from "../../types";
+// service
+import type { RepositoryServiceDeps } from "./types";
+
+const makeGetCurrentUserRepositoryForks: ServiceMethodFactory<
+ RepositoryServiceDeps,
+ [Repository],
+ Promise<RepositoryWithParentAndForkedFromRepos[]>
+> = ({ request }) => {
+ return async (repository) => {
+ if (request.session.data.curr_user_uid == null) {
+ return [];
+ }
+
+ const currentUserForks = await request.prisma.repository.findMany({
+ include: {
+ organization: true,
+ forkedFromRepo: {
+ select: {
+ id: true,
+ slug: true,
+ displayName: true,
+ organization: {
+ select: {
+ id: true,
+ slug: true,
+ displayName: true,
+ },
+ },
+ },
+ },
+ forks: {
+ select: {
+ _count: true,
+ },
+ },
+ },
+ where: {
+ isFork: true,
+ forkedFromRepo: {
+ id: {
+ equals: repository.id,
+ },
+ },
+ organization: {
+ OR: [
+ {
+ owner: {
+ id: {
+ equals: request.session.data.curr_user_uid,
+ },
+ },
+ },
+ {
+ memberships: {
+ some: {
+ userId: {
+ equals: request.session.data.curr_user_uid,
+ },
+ },
+ },
+ },
+ ],
+ },
+ },
+ });
+
+ return currentUserForks;
+ };
+};
+
+export default makeGetCurrentUserRepositoryForks;
@@ -51,10 +51,13 @@ const makeGetRepositoryBranches: ServiceMethodFactory<
});
});
- return gitBranchResult
+ const branches = gitBranchResult
.split("\n")
.map((branch) => branch.trim().replace(/^\* /i, ""))
.filter((x) => x != null && x.trim() !== "");
+
+ console.log("branches:", parentOrg.slug, repo.slug, branches);
+ return branches;
} catch (_) {
return [];
}
@@ -6,6 +6,7 @@ import type { RepositoryServiceAPI, RepositoryServiceDeps } from "./types";
import { default as makeCanUserAccessRepository } from "./canUserAccessRepository";
import { default as makeCreateRepository } from "./createRepository";
import { default as makeForkRepository } from "./forkRepository";
+import { default as makeGetCurrentUserRepositoryForks } from "./getCurrentUserRepositoryForks";
import { default as makeGetRepository } from "./getRepository";
import { default as makeGetRepositoryBranches } from "./getRepositoryBranches";
import { default as makeGetRepositoryCommitLog } from "./getRepositoryCommitLog";
@@ -28,6 +29,7 @@ export const makeRepositoryService = makeService<
canUserAccessRepository: makeCanUserAccessRepository,
createRepository: makeCreateRepository,
forkRepository: makeForkRepository,
+ getCurrentUserRepositoryForks: makeGetCurrentUserRepositoryForks,
getRepository: makeGetRepository,
getRepositoryBranches: makeGetRepositoryBranches,
getRepositoryCommitLog: makeGetRepositoryCommitLog,
@@ -15,6 +15,7 @@ import type {
RepositoryLog,
RepositoryObject,
RepositoryWithForkedFromRepo,
+ RepositoryWithParentAndForkedFromRepos,
} from "../../types";
export interface CreateRepositoryDTO {
@@ -54,6 +55,9 @@ export interface RepositoryServiceAPI extends ServiceApiContract {
orgSlug: string,
repoSlug: string
): Promise<RepositoryWithForkedFromRepo | null>;
+ getCurrentUserRepositoryForks(
+ repository: Repository
+ ): Promise<RepositoryWithParentAndForkedFromRepos[]>;
getRepositoryBranches(repository: Repository): Promise<string[]>;
getRepositoryCommitLog(
repository: Repository,
@@ -1,30 +1,52 @@
// 1st-party
import type { ServiceMethodFactory } from "@ethicdevs/react-monolith";
// generated via script[generate:prisma]
-import { Organization } from "@prisma/client";
+import { Organization, OrganizationKind } from "@prisma/client";
// app
import type { UsersServiceDeps } from "./types";
const getUserOrganizations: ServiceMethodFactory<
UsersServiceDeps,
- [string],
+ [string, undefined | boolean],
Promise<Organization[]>
> = ({ request }) => {
- return async (userId) => {
+ return async (userId, personalOnly = false) => {
const userOrgs = await request.prisma.organization.findMany({
where: {
- OR: [
- {
- ownerId: userId,
+ AND: [
+ personalOnly === true && {
+ kind: OrganizationKind.PERSONAL,
},
{
- memberships: {
- some: {
- userId,
+ OR: [
+ {
+ ownerId: userId,
+ },
+ {
+ memberships: {
+ some: {
+ userId,
+ },
+ },
},
- },
+ ],
},
- ],
+ ].filter(
+ (
+ x
+ ): x is
+ | { kind: "PERSONAL"; OR?: undefined }
+ | {
+ OR: (
+ | { ownerId: string; memberships?: undefined }
+ | {
+ memberships: { some: { userId: string } };
+ ownerId?: undefined;
+ }
+ )[];
+ kind?: undefined;
+ } => x != null && x !== false
+ ),
},
orderBy: {
kind: "asc", // private first then company
@@ -18,7 +18,10 @@ export interface UsersServiceAPI extends ServiceApiContract {
getUserOrganizationMemberships(
userId: string
): Promise<OrganizationMembership[]>;
- getUserOrganizations(userId: string): Promise<Organization[]>;
+ getUserOrganizations(
+ userId: string,
+ personalOnly?: boolean
+ ): Promise<Organization[]>;
getUserRepositories(
user: User,
where?: Prisma.RepositoryWhereInput
@@ -1,4 +1,10 @@
-import type { Prisma, GlobalRole, Repository } from "@prisma/client";
+// generated via script[generate:prisma]
+import type {
+ Prisma,
+ GlobalRole,
+ Repository,
+ Organization,
+} from "@prisma/client";
export type AppThemeScheme = "light" | "dark";
export type WithThemeSchemeProp = {
@@ -175,3 +181,8 @@ export type RepositoryWithForkedFromRepo = Repository & {
_count: Prisma.RepositoryCountOutputType;
}[];
};
+
+export type RepositoryWithParentAndForkedFromRepos =
+ RepositoryWithForkedFromRepo & {
+ organization: Organization;
+ };
@@ -37,8 +37,11 @@ const RepositoryPullRequestCreateView: ReactView<RepositoryPullRequestCreateView
</IslandWrapper>
<IslandWrapper
data-islandid={`${RepositoryPullRequestCreateForm.name}$$0`}
+ style={{ marginTop: 24 }}
>
<RepositoryPullRequestCreateForm
+ parentOrgSlug={parentOrg.slug}
+ repoSlug={repo.slug}
themeScheme={commonProps.themeScheme}
variant={variant}
/>