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
32 changes: 28 additions & 4 deletions apps/zbugs-swift/Sources/ZBugs/Queries.swift
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,13 @@ public struct IssueListV2Args: Codable, Sendable, Hashable {
}
}

public struct IssueListCountArgs: Codable, Sendable, Hashable {
public var listContext: ListContextParams
public init(listContext: ListContextParams) {
self.listContext = listContext
}
}

public struct ListIssueByIDArgs: Codable, Sendable, Hashable {
public var listContext: ListContextParams
public var idField: IDField
Expand Down Expand Up @@ -375,6 +382,13 @@ public enum Queries {
}

/// One issue, for the order to start a list at.
/// The number of issues in the list: the same filters and permissions as
/// `issueListV2`, reduced to a `count()`.
public static let issueListCount = defineQuery("issueListCount") {
(args: IssueListCountArgs, auth: AuthData?) in
filterListQuery(zql.issue, args.listContext, auth?.role).count()
}

public static let listIssueByID = defineQuery("listIssueByID") {
(args: ListIssueByIDArgs, auth: AuthData?) in
buildListQuery(ListQueryArgs(listContext: args.listContext, role: auth?.role))
Expand Down Expand Up @@ -421,7 +435,8 @@ public enum Queries {
/// Every query, for the server's registry and the drift test.
public static let all: [any NamedQuery<AuthData?>] = [
allLabels, allUsers, allProjects, user, crewUser, labels, labelsOrderByName, issuePreloadV2,
userPref, usersForProject, commentsPage, comment, issueDetail, issueListV2, listIssueByID,
userPref, usersForProject, commentsPage, comment, issueDetail, issueListV2, issueListCount,
listIssueByID,
emojiChange, prevNext, issueList, userPicker,
]
}
Expand Down Expand Up @@ -483,8 +498,6 @@ func buildListQuery(_ args: ListQueryArgs) -> Query<Issue, [Issue]> {
guard let listContext = args.listContext else {
return alwaysFalse(q)
}
let projectName = (listContext.projectName ?? zeroProjectName).lowercased()
q = q.whereExists(\.project, scalar: true) { $0.where(\.lowerCaseName, projectName) }

let dir: AST.Direction =
args.dir == .forward
Expand All @@ -502,6 +515,17 @@ func buildListQuery(_ args: ListQueryArgs) -> Query<Issue, [Issue]> {
q = q.limit(limit)
}

return filterListQuery(q, listContext, args.role)
}

/// Narrows `q` to the issues `listContext` selects that `role` may see
/// (`filterListQuery`): shared by the list and its count.
func filterListQuery(
_ q: Query<Issue, [Issue]>, _ listContext: ListContextParams, _ role: Role?
) -> Query<Issue, [Issue]> {
let projectName = (listContext.projectName ?? zeroProjectName).lowercased()
var q = q.whereExists(\.project, scalar: true) { $0.where(\.lowerCaseName, projectName) }

let text = truthy(listContext.textFilter).map { "%\(escapeLike($0))%" }
q = q.where { issue in
issue.and(
Expand Down Expand Up @@ -532,7 +556,7 @@ func buildListQuery(_ args: ListQueryArgs) -> Query<Issue, [Issue]> {
if let assignee = truthy(listContext.assignee) {
q = q.whereExists(\.assignee, scalar: true) { $0.where(\.login, assignee) }
}
return applyIssuePermissions(q, args.role)
return applyIssuePermissions(q, role)
}

extension Query {
Expand Down
52 changes: 52 additions & 0 deletions apps/zbugs-swift/Sources/ZBugsUI/IssueList.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,58 @@
}
}

/// The number of issues in the list, as zbugs' list page shows it: the
/// server's `issueListCount` once it has counted the list, in this session
/// or a previous one, and until then the list's running total. A text
/// search isn't counted: matching it checks every issue's comments, and it
/// changes with every keystroke.
struct IssueCount: View {
let filter: IssueFilter
let list: IssueVirtualizer?

var body: some View {
if filter.text.isEmpty {
SyncedIssueCount(filter: filter, list: list)
} else {
IssueCountLabel(synced: nil, list: list)
}
}
}

private struct SyncedIssueCount: View {
let list: IssueVirtualizer?
@ZeroQuery<Issue, Int, AuthData?> var count: Int

init(filter: IssueFilter, list: IssueVirtualizer?) {
self.list = list
_count = ZeroQuery(
Queries.issueListCount(IssueListCountArgs(listContext: filter.listContext)),
ttl: .defaultTTL)
}

var body: some View {
let counted = $count.resultType == .complete || $count.resultType == .cached
IssueCountLabel(synced: counted ? count : nil, list: list)
}
}

private struct IssueCountLabel: View {
let synced: Int?
let list: IssueVirtualizer?

var body: some View {
let s = list?.snapshot
let total = synced ?? s?.total
if let total {
Text(total.formatted()).accessibilityIdentifier("IssueCount")
} else if let s, s.complete || s.estimatedTotal > 0 {
// Rounded down to 50 above 50, as the list page does.
let e = s.estimatedTotal
Text("\((e < 50 ? e : e - e % 50).formatted())+").accessibilityIdentifier("IssueCount")
}
}
}

/// The issue list in SwiftUI.
struct IssueList: View {
let list: IssueVirtualizer
Expand Down
5 changes: 5 additions & 0 deletions apps/zbugs-swift/Sources/ZBugsUI/RootView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,11 @@
}

@ToolbarContentBuilder private var toolbar: some ToolbarContent {
ToolbarItem {
IssueCount(filter: filter, list: issues)
.foregroundStyle(.secondary)
.monospacedDigit()
}
ToolbarItem {
ConnectionBadge(connection: model.zero.connection)
}
Expand Down
4 changes: 1 addition & 3 deletions apps/zbugs-swift/Tests/ZBugsTests/DriftTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import ZeroTesting
.appendingPathComponent("drift-vectors.json"), encoding: .utf8)
let failures = try Drift.failures(
vectors: vectors, schema: .zbugs, queries: Queries.all, mutators: Mutators.all)
// Not ported yet: issueListCount is a count(), and the Swift client has no
// aggregates. Exact, so this fails once it is ported and must be removed.
#expect(failures == ["query issueListCount: no Swift definition"])
#expect(failures == [])
}
}
Loading
Loading