Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ export function PullRequestsScreen(
const { actions, select, busyKey } = usePullRequestListActions(
connectedTargets.map((target) => target.environmentId),
result.updateEntry,
result.data.hostActions,
);
const sidebar = props.sidebar === true || primaryColumn !== null;
const selectedKey = props.selectedPullRequestKey ?? primaryColumn?.selectedPullRequestKey;
Expand Down
42 changes: 42 additions & 0 deletions apps/mobile/src/features/pull-requests/pullRequestList.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
pullRequestLabelColors,
pullRequestListHead,
pullRequestListInput,
pullRequestListRowActions,
scopedPullRequestListKey,
shownPullRequestListPages,
pullRequestListRowsAreEqual,
Expand Down Expand Up @@ -241,6 +242,47 @@ describe("all-environment PR list", () => {
).map((row) => row.environmentId),
).toEqual([mbp]);
});
it("offers only the row actions each environment's host reports", () => {
const bitbucket = {
host: "Bitbucket.org",
kind: SourceControlProviderKind.make("bitbucket"),
searchesOnHost: false,
projectCount: 1,
configured: true,
detail: null,
};
const row = (environmentId: EnvironmentId, patch: Partial<PullRequestListEntry>) => ({
...entry(1, {
provider: SourceControlProviderKind.make("bitbucket"),
host: "bitbucket.org",
...patch,
}),
environmentId,
});
const { hostActions } = mergeEnvironmentPullRequestPages([
{
environmentId: cups,
pages: pullRequestListHead(null, {
...page([]),
providers: [{ ...bitbucket, actions: ["merge", "close"] }],
}),
},
{
environmentId: mbp,
pages: pullRequestListHead(null, { ...page([]), providers: [bitbucket] }),
},
]);
expect(pullRequestListRowActions(row(cups, {}), hostActions)).toEqual(["close"]);
expect(pullRequestListRowActions(row(cups, { isDraft: true }), hostActions)).toEqual(["close"]);
expect(pullRequestListRowActions(row(cups, { state: "closed" }), hostActions)).toEqual([]);
expect(pullRequestListRowActions(row(mbp, {}), hostActions)).toEqual([]);
expect(
pullRequestListRowActions(
{ ...entry(1, { state: "closed" }), environmentId: mbp },
hostActions,
),
).toEqual(["reopen"]);
});
});

describe("mobile PR scan and filters", () => {
Expand Down
33 changes: 32 additions & 1 deletion apps/mobile/src/features/pull-requests/pullRequestList.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import { pullRequestFilterProjects } from "@t3tools/client-runtime/pull-request-project-filter";
import { parsePullRequestQuery } from "@t3tools/client-runtime/pull-request-query";
import { sourceControlClients } from "@t3tools/client-runtime/source-control-clients";
import {
ProjectId,
resolvePullRequestAuthorFilter,
type EnvironmentId,
type PullRequestAction,
type PullRequestDiffStat,
type PullRequestListEntry,
type PullRequestListInput,
Expand Down Expand Up @@ -213,25 +215,54 @@ export function mergeEnvironmentPullRequestPages(
) {
const viewers: Record<string, string> = {};
const localSearchHosts = new Set<string>();
const hostActions = new Map<string, ReadonlySet<PullRequestAction>>();
const entries: EnvironmentPullRequestListEntry[] = [];
const errors = [];
for (const { environmentId, pages } of sources) {
entries.push(...pages.entries.map((entry) => ({ ...entry, environmentId })));
for (const [host, viewer] of Object.entries(pages.head.viewers))
viewers[`${environmentId} ${host}`] = viewer;
for (const provider of pages.head.providers)
for (const provider of pages.head.providers) {
if (!provider.searchesOnHost) localSearchHosts.add(`${environmentId} ${provider.host}`);
if (provider.actions !== undefined)
hostActions.set(
`${environmentId} ${provider.host.toLowerCase()}`,
new Set(provider.actions),
);
}
errors.push(...pages.head.errors.map((error) => ({ ...error, environmentId })));
}
return {
entries,
viewers,
localSearchHosts,
hostActions,
errors,
continued: sources.some(({ pages }) => pages.continued),
};
}

export type PullRequestListRowAction = "ready" | "draft" | "close" | "reopen";

export function pullRequestListRowActions(
entry: Pick<
EnvironmentPullRequestListEntry,
"environmentId" | "host" | "provider" | "state" | "isDraft"
>,
hostActions: ReadonlyMap<string, ReadonlySet<PullRequestAction>>,
): readonly PullRequestListRowAction[] {
const forState: readonly PullRequestListRowAction[] =
entry.state === "open"
? [entry.isDraft ? "ready" : "draft", "close"]
: entry.state === "closed"
? ["reopen"]
: [];
const supported =
hostActions.get(`${entry.environmentId} ${entry.host.toLowerCase()}`) ??
sourceControlClients.find(entry.provider)?.changeRequestActionsWhenUnreported;
return forState.filter((action) => supported?.has(action) === true);
}

export function mergePullRequestListEntries(
previous: readonly PullRequestListEntry[],
incoming: readonly PullRequestListEntry[],
Expand Down
44 changes: 23 additions & 21 deletions apps/mobile/src/features/pull-requests/usePullRequestListActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,30 @@ import {
pullRequestActionLabel,
} from "./pullRequestActions";
import { sourceControlClients } from "@t3tools/client-runtime/source-control-clients";
import { scopedPullRequestListKey, type EnvironmentPullRequestListEntry } from "./pullRequestList";
import {
pullRequestListRowActions,
scopedPullRequestListKey,
type EnvironmentPullRequestListEntry,
type PullRequestListRowAction,
} from "./pullRequestList";
import { copyPullRequestLink, sharePullRequestLink } from "./pullRequestLink";
import { pullRequestTarget } from "./usePullRequestDiff";
import { useSelectWorkspaceDetail } from "../workspace/useSelectWorkspaceDetail";

const ROW_ACTION_IMAGES = {
ready: ["pencil", "edit"],
draft: ["pencil", "edit"],
close: ["xmark", "close"],
reopen: ["arrow.clockwise", "refresh"],
} as const satisfies Record<PullRequestListRowAction, readonly [string, string]>;

export function usePullRequestListActions(
environmentIds: readonly EnvironmentId[],
onChanged: (
entry: EnvironmentPullRequestListEntry,
patch: Partial<EnvironmentPullRequestListEntry>,
) => void,
hostActions: ReadonlyMap<string, ReadonlySet<PullRequestAction>>,
) {
const registry = use(RegistryContext);
const navigation = useNavigation();
Expand Down Expand Up @@ -166,27 +179,16 @@ export function usePullRequestListActions(
title: "Request review",
image: Platform.OS === "ios" ? "person.badge.plus" : "person_add",
},
{
id: entry.isDraft ? "ready" : "draft",
title: entry.isDraft ? "Mark ready for review" : "Convert to draft",
image: Platform.OS === "ios" ? "pencil" : "edit",
},
{
id: "close",
title: pullRequestActionLabel("close", terminology),
image: Platform.OS === "ios" ? "xmark" : "close",
attributes: { destructive: true },
},
]
: writableEnvironments.has(entry.environmentId) && entry.state === "closed"
? [
{
id: "reopen",
title: pullRequestActionLabel("reopen", terminology),
image: Platform.OS === "ios" ? "arrow.clockwise" : "refresh",
},
]
: []),
: []),
...(writableEnvironments.has(entry.environmentId)
? pullRequestListRowActions(entry, hostActions).map((action) => ({
id: action,
title: pullRequestActionLabel(action, terminology),
image: ROW_ACTION_IMAGES[action][Platform.OS === "ios" ? 0 : 1],
...(action === "close" ? { attributes: { destructive: true } } : {}),
}))
: []),
{
id: "share",
title: "Share…",
Expand Down
Loading