| author | |
| committer | |
| log | cb395ee13b5b5ddd4352e7c3d4216181501cc2e5 |
| tree | df70cd8a05e11a7cbb18a74a9fc1b9d51df03c92 |
| parent | e093789dd2340d8a41ee7ac12341fb011f9ff86c |
| signature | Signed by PGP key B5690EEEBB952194 |
4 files changed, 67 insertions(+), 5 deletions(-)
apps/mobile/src/features/pull-requests/PullRequestsScreen.tsx+10-3| ... | @@ -34,6 +34,7 @@ import { | ... | @@ -34,6 +34,7 @@ import { |
| 34 | scopedPullRequestListKey as pullRequestKey, | 34 | scopedPullRequestListKey as pullRequestKey, |
| 35 | type EnvironmentPullRequestListEntry, | 35 | type EnvironmentPullRequestListEntry, |
| 36 | PR_LIST_PAGE_SIZE, | 36 | PR_LIST_PAGE_SIZE, |
| 37 | withoutScratchProjectScope, | ||
| 37 | } from "./pullRequestList"; | 38 | } from "./pullRequestList"; |
| 38 | import { useDebouncedValue } from "../../state/queries"; | 39 | import { useDebouncedValue } from "../../state/queries"; |
| 39 | import { useWorkspaceTabs } from "../workspace/WorkspaceTabsContext"; | 40 | import { useWorkspaceTabs } from "../workspace/WorkspaceTabsContext"; |
| ... | @@ -102,7 +103,12 @@ export function PullRequestsScreen( | ... | @@ -102,7 +103,12 @@ export function PullRequestsScreen( |
| 102 | }, | 103 | }, |
| 103 | [navigation, selectAndroidWorkspace], | 104 | [navigation, selectAndroidWorkspace], |
| 104 | ); | 105 | ); |
| 105 | const controls = listControls.controlsFor(environmentId); | 106 | const allProjects = useProjects(); |
| 107 | const savedControls = listControls.controlsFor(environmentId); | ||
| 108 | const controls = useMemo( | ||
| 109 | () => withoutScratchProjectScope(savedControls, allProjects), | ||
| 110 | [allProjects, savedControls], | ||
| 111 | ); | ||
| 106 | const targets = useMemo( | 112 | const targets = useMemo( |
| 107 | () => | 113 | () => |
| 108 | environments | 114 | environments |
| ... | @@ -175,8 +181,9 @@ export function PullRequestsScreen( | ... | @@ -175,8 +181,9 @@ export function PullRequestsScreen( |
| 175 | ].sort(), | 181 | ].sort(), |
| 176 | })); | 182 | })); |
| 177 | } | 183 | } |
| 178 | const projects = useProjects().filter( | 184 | const projects = allProjects.filter( |
| 179 | (project) => environmentId === null || project.environmentId === environmentId, | 185 | (project) => |
| 186 | !project.isScratch && (environmentId === null || project.environmentId === environmentId), | ||
| 180 | ); | 187 | ); |
| 181 | const renderPullRequest = useCallback( | 188 | const renderPullRequest = useCallback( |
| 182 | ({ item }: { readonly item: EnvironmentPullRequestListEntry }) => ( | 189 | ({ item }: { readonly item: EnvironmentPullRequestListEntry }) => ( |
apps/mobile/src/features/pull-requests/pullRequestList.test.ts+37| ... | @@ -18,6 +18,7 @@ import { | ... | @@ -18,6 +18,7 @@ import { |
| 18 | pullRequestLabelColor, | 18 | pullRequestLabelColor, |
| 19 | scopedPullRequestListKey, | 19 | scopedPullRequestListKey, |
| 20 | pullRequestListRowsAreEqual, | 20 | pullRequestListRowsAreEqual, |
| 21 | withoutScratchProjectScope, | ||
| 21 | } from "./pullRequestList"; | 22 | } from "./pullRequestList"; |
| 22 | 23 | ||
| 23 | function entry(number: number, patch: Partial<PullRequestListEntry> = {}): PullRequestListEntry { | 24 | function entry(number: number, patch: Partial<PullRequestListEntry> = {}): PullRequestListEntry { |
| ... | @@ -373,3 +374,39 @@ describe("mobile PR scan and filters", () => { | ... | @@ -373,3 +374,39 @@ describe("mobile PR scan and filters", () => { |
| 373 | expect(pullRequestLabelColor("not-a-color")).toBeNull(); | 374 | expect(pullRequestLabelColor("not-a-color")).toBeNull(); |
| 374 | }); | 375 | }); |
| 375 | }); | 376 | }); |
| 377 | |||
| 378 | describe("withoutScratchProjectScope", () => { | ||
| 379 | const projects = [ | ||
| 380 | { id: "repo", environmentId: "env-a" }, | ||
| 381 | { id: "scratch", environmentId: "env-a", isScratch: true as const }, | ||
| 382 | ]; | ||
| 383 | |||
| 384 | it("reads a scope saved on the Scratch folder as all projects", () => { | ||
| 385 | const controls = withoutScratchProjectScope( | ||
| 386 | { ...DEFAULT_PR_LIST_CONTROLS, projectId: "scratch", projectEnvironmentId: "env-a" }, | ||
| 387 | projects, | ||
| 388 | ); | ||
| 389 | expect(controls.projectId).toBeUndefined(); | ||
| 390 | expect(controls.projectEnvironmentId).toBeUndefined(); | ||
| 391 | }); | ||
| 392 | |||
| 393 | it("keeps a repository scope, and one whose projects have not arrived yet", () => { | ||
| 394 | const repository = { | ||
| 395 | ...DEFAULT_PR_LIST_CONTROLS, | ||
| 396 | projectId: "repo", | ||
| 397 | projectEnvironmentId: "env-a", | ||
| 398 | }; | ||
| 399 | expect(withoutScratchProjectScope(repository, projects)).toBe(repository); | ||
| 400 | const pending = { ...repository, projectId: "scratch" }; | ||
| 401 | expect(withoutScratchProjectScope(pending, [])).toBe(pending); | ||
| 402 | }); | ||
| 403 | |||
| 404 | it("keeps another server's project that shares the Scratch folder's id", () => { | ||
| 405 | const elsewhere = { | ||
| 406 | ...DEFAULT_PR_LIST_CONTROLS, | ||
| 407 | projectId: "scratch", | ||
| 408 | projectEnvironmentId: "env-b", | ||
| 409 | }; | ||
| 410 | expect(withoutScratchProjectScope(elsewhere, projects)).toBe(elsewhere); | ||
| 411 | }); | ||
| 412 | }); |
apps/mobile/src/features/pull-requests/pullRequestList.ts+15| ... | @@ -15,6 +15,21 @@ export const DEFAULT_PR_LIST_CONTROLS: PullRequestListControls = { | ... | @@ -15,6 +15,21 @@ export const DEFAULT_PR_LIST_CONTROLS: PullRequestListControls = { |
| 15 | sort: "updated", | 15 | sort: "updated", |
| 16 | }; | 16 | }; |
| 17 | 17 | ||
| 18 | /** A scope saved on the Scratch folder is no longer offered, so it reads as "All projects". */ | ||
| 19 | export function withoutScratchProjectScope( | ||
| 20 | controls: PullRequestListControls, | ||
| 21 | projects: readonly { id: string; environmentId: string; isScratch?: true }[], | ||
| 22 | ): PullRequestListControls { | ||
| 23 | return projects.some( | ||
| 24 | (project) => | ||
| 25 | project.isScratch && | ||
| 26 | project.id === controls.projectId && | ||
| 27 | project.environmentId === controls.projectEnvironmentId, | ||
| 28 | ) | ||
| 29 | ? { ...controls, projectId: undefined, projectEnvironmentId: undefined } | ||
| 30 | : controls; | ||
| 31 | } | ||
| 32 | |||
| 18 | export function pullRequestListKey( | 33 | export function pullRequestListKey( |
| 19 | item: Pick<PullRequestListEntry, "host" | "repository" | "number">, | 34 | item: Pick<PullRequestListEntry, "host" | "repository" | "number">, |
| 20 | ) { | 35 | ) { |
apps/web/src/routes/_chat.pull-requests.tsx+5-2| ... | @@ -411,9 +411,12 @@ function PullRequestsRouteView() { | ... | @@ -411,9 +411,12 @@ function PullRequestsRouteView() { |
| 411 | // the one wrong answer the empty state can give. | 411 | // the one wrong answer the empty state can give. |
| 412 | const projectsKnown = useAllEnvironmentShellsBootstrapped(); | 412 | const projectsKnown = useAllEnvironmentShellsBootstrapped(); |
| 413 | // Only the projects the page can actually read: one on an environment that cannot list pull | 413 | // Only the projects the page can actually read: one on an environment that cannot list pull |
| 414 | // requests could neither be listed nor acted on. | 414 | // requests could neither be listed nor acted on, and the Scratch folder is never a repository. |
| 415 | const projects = useMemo( | 415 | const projects = useMemo( |
| 416 | () => allProjects.filter((project) => environmentIds.includes(project.environmentId)), | 416 | () => |
| 417 | allProjects.filter( | ||
| 418 | (project) => !project.isScratch && environmentIds.includes(project.environmentId), | ||
| 419 | ), | ||
| 417 | [allProjects, environmentIds], | 420 | [allProjects, environmentIds], |
| 418 | ); | 421 | ); |
| 419 | const environmentLabels = useMemo( | 422 | const environmentLabels = useMemo( |