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
233 changes: 126 additions & 107 deletions src/Share/Postgres/Queries.hs
Original file line number Diff line number Diff line change
Expand Up @@ -707,59 +707,69 @@ listBranchesByProject ::
BranchKindFilter ->
ProjectId ->
-- | (branch, contributorHandle)
PG.Transaction e [(Branch CausalId, Maybe UserHandle)]
PG.Transaction e (Paged (UTCTime, BranchId) (Branch CausalId, Maybe UserHandle))
listBranchesByProject limit mayCursor mayBranchNamePrefix mayContributorQuery kind projectId = do
let kindFilter = case kind of
AllBranchKinds -> ""
OnlyContributorBranches -> "AND b.contributor_id IS NOT NULL"
OnlyCoreBranches -> "AND b.contributor_id IS NULL"
let contributorFilter = case mayContributorQuery of
Nothing -> mempty
-- Allow null contributor here for the case where we're listing 'all' branch kinds.
Just (Left contributorId) -> [PG.sql| AND (b.contributor_id IS NULL OR (b.contributor_id = #{contributorId})) |]
Just (Right (Query contributorHandlePrefix)) -> [PG.sql| AND (contributor.handle IS NULL OR starts_with(contributor.handle, #{contributorHandlePrefix})) |]
let branchNameFilter = case mayBranchNamePrefix of
Nothing -> mempty
Just (Query branchNamePrefix) -> [PG.sql| AND starts_with(b.name, #{branchNamePrefix}) |]
let cursorFilter = case mayCursor of
Nothing -> mempty
Just (Cursor (beforeTime, branchId) Previous) -> [PG.sql| AND (b.updated_at, b.id) < (#{beforeTime}, #{branchId})|]
Just (Cursor (afterTime, branchId) Next) -> [PG.sql| AND (b.updated_at, b.id) > (#{afterTime}, #{branchId})|]
let sql =
intercalateMap
"\n"
id
[ ( [PG.sql|
SELECT
b.id,
b.project_id,
b.name,
b.contributor_id,
b.causal_id,
b.merge_target_branch_id,
b.created_at,
b.updated_at,
b.creator_id,
contributor.handle
FROM project_branches b
LEFT JOIN users AS contributor ON contributor.id = b.contributor_id
WHERE
b.deleted_at IS NULL
AND b.project_id = #{projectId}
results <- query limit (mkCursorFilter mayCursor)
let paged@(Paged {prevCursor, nextCursor}) =
results
& pagedOn (\(Branch {updatedAt, branchId}, _) -> (updatedAt, branchId))
hasPrevPage <- not . null <$> query 1 (mkCursorFilter prevCursor)
hasNextPage <- not . null <$> query 1 (mkCursorFilter nextCursor)
pure $ guardPaged hasPrevPage hasNextPage paged
where
mkCursorFilter cursor = case cursor of
Nothing -> mempty
Just (Cursor (afterTime, branchId) Previous) -> [PG.sql| AND (b.updated_at, b.id) > (#{afterTime}, #{branchId})|]
Just (Cursor (beforeTime, branchId) Next) -> [PG.sql| AND (b.updated_at, b.id) < (#{beforeTime}, #{branchId})|]
kindFilter = case kind of
AllBranchKinds -> ""
OnlyContributorBranches -> "AND b.contributor_id IS NOT NULL"
OnlyCoreBranches -> "AND b.contributor_id IS NULL"
contributorFilter = case mayContributorQuery of
Nothing -> mempty
-- Allow null contributor here for the case where we're listing 'all' branch kinds.
Just (Left contributorId) -> [PG.sql| AND (b.contributor_id IS NULL OR (b.contributor_id = #{contributorId})) |]
Just (Right (Query contributorHandlePrefix)) -> [PG.sql| AND (contributor.handle IS NULL OR starts_with(contributor.handle, #{contributorHandlePrefix})) |]
branchNameFilter = case mayBranchNamePrefix of
Nothing -> mempty
Just (Query branchNamePrefix) -> [PG.sql| AND starts_with(b.name, #{branchNamePrefix}) |]
query :: Limit -> PG.Sql -> PG.Transaction e [(Branch CausalId, Maybe UserHandle)]
query limit cursorFilter = do
let sql =
intercalateMap
"\n"
id
[ ( [PG.sql|
SELECT
b.id,
b.project_id,
b.name,
b.contributor_id,
b.causal_id,
b.merge_target_branch_id,
b.created_at,
b.updated_at,
b.creator_id,
contributor.handle
FROM project_branches b
LEFT JOIN users AS contributor ON contributor.id = b.contributor_id
WHERE
b.deleted_at IS NULL
AND b.project_id = #{projectId}
|]
),
kindFilter,
contributorFilter,
branchNameFilter,
cursorFilter,
( [PG.sql|
ORDER BY b.updated_at DESC, b.id DESC
LIMIT #{limit}
|]
),
kindFilter,
contributorFilter,
branchNameFilter,
cursorFilter,
( [PG.sql|
ORDER BY b.updated_at DESC, b.id DESC
LIMIT #{limit}
|]
)
]
PG.queryListRows sql
<&> fmap (\(branch PG.:. PG.Only contributorHandle) -> (branch, contributorHandle))
)
]
PG.queryListRows sql
<&> fmap (\(branch PG.:. PG.Only contributorHandle) -> (branch, contributorHandle))

-- | List all BranchHashes which are reachable within a given user's codebase.
accessibleCausalsForUser ::
Expand Down Expand Up @@ -817,63 +827,72 @@ listContributorBranchesOfUserAccessibleToCaller ::
Maybe (Cursor (UTCTime, BranchId)) ->
Maybe Query ->
Maybe ProjectId ->
-- | (branch, project, projectOwnerHandle)
PG.Transaction e [(Branch CausalId, Project, ProjectOwner)]
PG.Transaction e (Paged (UTCTime, BranchId) (Branch CausalId, Project, ProjectOwner))
listContributorBranchesOfUserAccessibleToCaller contributorUserId mayCallerUserId limit mayCursor mayBranchNamePrefix mayProjectId = do
let branchNameFilter = case mayBranchNamePrefix of
Nothing -> mempty
Just (Query branchNamePrefix) -> [PG.sql| AND starts_with(b.name, #{branchNamePrefix}) |]
let cursorFilter = case mayCursor of
Nothing -> mempty
Just (Cursor (beforeTime, branchId) Previous) -> [PG.sql| AND (b.updated_at, b.id) < (#{beforeTime}, #{branchId}) |]
Just (Cursor (afterTime, branchId) Next) -> [PG.sql| AND (b.updated_at, b.id) > (#{afterTime}, #{branchId}) |]
let projectFilter = case mayProjectId of
Nothing -> mempty
Just projId -> [PG.sql| AND b.project_id = #{projId} |]
let sql =
intercalateMap
"\n"
id
[ [PG.sql|
SELECT
b.id,
b.project_id,
b.name,
b.contributor_id,
b.causal_id,
b.merge_target_branch_id,
b.created_at,
b.updated_at,
b.creator_id,
project.id,
project.owner_user_id,
project.slug,
project.summary,
project.tags,
project.private,
project.created_at,
project.updated_at,
project_owner.handle,
project_owner.name,
EXISTS (SELECT FROM org_members WHERE org_members.organization_user_id = project.owner_user_id)
FROM project_branches b
JOIN projects project ON project.id = b.project_id
JOIN users AS project_owner ON project_owner.id = project.owner_user_id
WHERE
b.deleted_at IS NULL
AND b.contributor_id = #{contributorUserId}
AND user_has_project_permission(#{mayCallerUserId}, b.project_id, #{ProjectView})
|],
branchNameFilter,
cursorFilter,
projectFilter,
[PG.sql|
ORDER BY b.updated_at DESC, b.id DESC
LIMIT #{limit}
|]
]
PG.queryListRows sql
<&> fmap (\(branch PG.:. project PG.:. projectOwner) -> (branch, project, projectOwner))
results <- query limit (mkCursorFilter mayCursor)
let paged@(Paged {prevCursor, nextCursor}) =
results
& pagedOn (\(Branch {updatedAt, branchId}, _, _) -> (updatedAt, branchId))
hasPrevPage <- not . null <$> query 1 (mkCursorFilter prevCursor)
hasNextPage <- not . null <$> query 1 (mkCursorFilter nextCursor)
pure $ guardPaged hasPrevPage hasNextPage paged
where
branchNameFilter = case mayBranchNamePrefix of
Nothing -> mempty
Just (Query branchNamePrefix) -> [PG.sql| AND starts_with(b.name, #{branchNamePrefix}) |]
mkCursorFilter cursor = case cursor of
Nothing -> mempty
Just (Cursor (afterTime, branchId) Previous) -> [PG.sql| AND (b.updated_at, b.id) > (#{afterTime}, #{branchId}) |]
Just (Cursor (beforeTime, branchId) Next) -> [PG.sql| AND (b.updated_at, b.id) < (#{beforeTime}, #{branchId}) |]
projectFilter = case mayProjectId of
Nothing -> mempty
Just projId -> [PG.sql| AND b.project_id = #{projId} |]
query :: Limit -> PG.Sql -> PG.Transaction e [(Branch CausalId, Project, ProjectOwner)]
query limit cursorFilter = do
let sql =
intercalateMap
"\n"
id
[ [PG.sql|
SELECT
b.id,
b.project_id,
b.name,
b.contributor_id,
b.causal_id,
b.merge_target_branch_id,
b.created_at,
b.updated_at,
b.creator_id,
project.id,
project.owner_user_id,
project.slug,
project.summary,
project.tags,
project.private,
project.created_at,
project.updated_at,
project_owner.handle,
project_owner.name,
EXISTS (SELECT FROM org_members WHERE org_members.organization_user_id = project.owner_user_id)
FROM project_branches b
JOIN projects project ON project.id = b.project_id
JOIN users AS project_owner ON project_owner.id = project.owner_user_id
WHERE
b.deleted_at IS NULL
AND b.contributor_id = #{contributorUserId}
AND user_has_project_permission(#{mayCallerUserId}, b.project_id, #{ProjectView})
|],
branchNameFilter,
cursorFilter,
projectFilter,
[PG.sql|
ORDER BY b.updated_at DESC, b.id DESC
LIMIT #{limit}
|]
]
PG.queryListRows sql
<&> fmap (\(branch PG.:. project PG.:. projectOwner) -> (branch, project, projectOwner))

-- | Returns Project Owner information, including whether that user is an organization or not.
projectOwnerByProjectId :: ProjectId -> PG.Transaction e (Maybe ProjectOwner)
Expand Down
14 changes: 5 additions & 9 deletions src/Share/Web/Share/Branches/Impl.hs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import Share.Web.Share.Branches.API qualified as API
import Share.Web.Share.Branches.Types (BranchKindFilter (..), ShareBranch (..))
import Share.Web.Share.Branches.Types qualified as API
import Share.Web.Share.CodeBrowsing.API qualified as API
import Share.Web.Share.Contributions.Types
import Share.Web.Share.DisplayInfo.Types
import Share.Web.Share.Projects.Types (projectToAPI)
import Share.Web.Share.Types
import U.Codebase.HashTags (CausalHash)
Expand Down Expand Up @@ -535,7 +537,7 @@ listBranchesByProjectEndpoint (AuthN.MaybeAuthedUserID callerUserId) userHandle
(mayNamePrefix, mayContributorFilter) <- computeSearchFilters
branches <- PG.runTransaction do
branches <- Q.listBranchesByProject limit mayCursor mayNamePrefix mayContributorFilter (fromMaybe defaultKindFilter mayKindFilter) projectId
branchesWithContributions <-
branchesWithContributions :: (Paged (UTCTime, BranchId) (Branch CausalId, [ShareContribution UserDisplayInfo], Maybe UserHandle)) <-
branches
& fmap (\(branch@(Branch {branchId}), contributorHandle) -> (branch, branchId, contributorHandle))
& ContributionsQ.shareContributionsByBranchOf (traversed . _2)
Expand All @@ -549,10 +551,7 @@ listBranchesByProjectEndpoint (AuthN.MaybeAuthedUserID callerUserId) userHandle
let branchShortHand = BranchShortHand {branchName, contributorHandle}
in API.branchToShareBranch branchShortHand branch shareProject contributions
)
branches
& pagedOn (\(Branch {updatedAt, branchId}, _, _) -> (updatedAt, branchId))
& (\p -> p {items = shareBranches})
& pure
pure shareBranches
where
userIdForHandle handle = do
fmap user_id <$> PG.runTransaction (UserQ.userByHandle handle)
Expand Down Expand Up @@ -628,10 +627,7 @@ listBranchesByUserEndpoint (AuthN.MaybeAuthedUserID callerUserId) contributorHan
shareProject = projectToAPI projectOwnerHandle project
in API.branchToShareBranch branchShortHand branch shareProject contributions
)
expandedBranches
& pagedOn ((\(Branch {updatedAt, branchId}, _contr, _proj, _projOwner) -> (updatedAt, branchId)))
& (\p -> p {items = shareBranches})
& pure
pure shareBranches
where
defaultLimit = Limit 20
limit = fromMaybe defaultLimit mayLimit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@
"updatedAt": "<TIMESTAMP>"
}
],
"nextCursor": "<CURSOR>",
"prevCursor": "<CURSOR>"
"nextCursor": null,
"prevCursor": null
},
"status": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@
"updatedAt": "<TIMESTAMP>"
}
],
"nextCursor": "<CURSOR>",
"prevCursor": "<CURSOR>"
"nextCursor": null,
"prevCursor": null
},
"status": [
{
Expand Down
4 changes: 2 additions & 2 deletions transcripts/share-apis/branches/branch-list-by-user-self.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,8 @@
"updatedAt": "<TIMESTAMP>"
}
],
"nextCursor": "<CURSOR>",
"prevCursor": "<CURSOR>"
"nextCursor": null,
"prevCursor": null
},
"status": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@
"updatedAt": "<TIMESTAMP>"
}
],
"nextCursor": "<CURSOR>",
"prevCursor": "<CURSOR>"
"nextCursor": null,
"prevCursor": null
},
"status": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,8 +101,8 @@
"updatedAt": "<TIMESTAMP>"
}
],
"nextCursor": "<CURSOR>",
"prevCursor": "<CURSOR>"
"nextCursor": null,
"prevCursor": null
},
"status": [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@
"updatedAt": "<TIMESTAMP>"
}
],
"nextCursor": "<CURSOR>",
"prevCursor": "<CURSOR>"
"nextCursor": null,
"prevCursor": null
},
"status": [
{
Expand Down
4 changes: 2 additions & 2 deletions transcripts/share-apis/branches/branch-list-core-only.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@
"updatedAt": "<TIMESTAMP>"
}
],
"nextCursor": "<CURSOR>",
"prevCursor": "<CURSOR>"
"nextCursor": null,
"prevCursor": null
},
"status": [
{
Expand Down
4 changes: 2 additions & 2 deletions transcripts/share-apis/branches/branch-list-name-filter.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
"updatedAt": "<TIMESTAMP>"
}
],
"nextCursor": "<CURSOR>",
"prevCursor": "<CURSOR>"
"nextCursor": null,
"prevCursor": null
},
"status": [
{
Expand Down
4 changes: 2 additions & 2 deletions transcripts/share-apis/branches/branch-list-name-prefix.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,8 @@
"updatedAt": "<TIMESTAMP>"
}
],
"nextCursor": "<CURSOR>",
"prevCursor": "<CURSOR>"
"nextCursor": null,
"prevCursor": null
},
"status": [
{
Expand Down
Loading
Loading