Skip to content
This repository has been archived by the owner on Sep 20, 2023. It is now read-only.

Commit

Permalink
feat: add repository labels pagination
Browse files Browse the repository at this point in the history
fix #2902
  • Loading branch information
qkdreyer committed Mar 6, 2020
1 parent 18fef6c commit b9579e2
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 21 deletions.
62 changes: 62 additions & 0 deletions Classes/Labels/GitHubClient+RepositoryLabels.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
//
// GitHubClient+RepositoryLabels.swift
// Freetime
//
// Created by Quentin Dreyer on 05/03/2020.
// Copyright © 2020 Ryan Nystrom. All rights reserved.
//

import GitHubAPI
import Apollo

private extension FetchRepositoryLabelsQuery.Data {

func labels() -> [RepositoryLabel] {
var labels: [RepositoryLabel] = []
repository?.labels.map { nodes in
nodes.nodes.map { node in
labels += node.compactMap {
guard let label = $0 else { return nil }
return RepositoryLabel(color: label.color, name: label.name)
}
}
}
return labels
}

func nextPageToken() -> String? {
guard repository?.labels?.pageInfo.hasNextPage == true else { return nil }
return repository?.labels?.pageInfo.endCursor
}

}

extension GithubClient {

struct RepositoryLabelsPayload {
let labels: [RepositoryLabel]
let nextPage: String?
}

func fetchRepositoryLabels(owner: String,
repo: String,
nextPage: String?,
completion: @escaping (Result<RepositoryLabelsPayload>) -> Void
) {
let query = FetchRepositoryLabelsQuery(owner: owner, repo: repo, after: nextPage)
client.query(query, result: { $0 }, completion: { result in

switch result {
case .failure(let error):
completion(.error(error))

case .success(let data):
let payload = RepositoryLabelsPayload(
labels: data.labels(),
nextPage: data.nextPageToken()
)
completion(.success(payload))
}
})
}
}
28 changes: 15 additions & 13 deletions Classes/Labels/LabelsViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ LabelSectionControllerDelegate {

private let selectedLabels: Set<RepositoryLabel>
private var labels = [RepositoryLabel]()
private let owner: String
private let repo: String
private let client: GithubClient
private let request: RepositoryLabelsQuery

init(
selected: [RepositoryLabel],
Expand All @@ -27,7 +28,8 @@ LabelSectionControllerDelegate {
) {
self.selectedLabels = Set(selected)
self.client = client
self.request = RepositoryLabelsQuery(owner: owner, repo: repo)
self.owner = owner
self.repo = repo
super.init(emptyErrorMessage: NSLocalizedString("No labels found", comment: ""))
preferredContentSize = Styles.Sizes.contextMenuSize
title = Constants.Strings.labels
Expand Down Expand Up @@ -87,20 +89,20 @@ LabelSectionControllerDelegate {
// MARK: Overrides

override func fetch(page: String?) {
client.client.query(request, result: { data in
data.repository?.labels?.nodes
}, completion: { [weak self] result in
client.fetchRepositoryLabels(
owner: owner,
repo: repo,
nextPage: page as String?
) { [weak self] result in
guard let strongSelf = self else { return }
switch result {
case .success(let nodes):
self?.labels = nodes.compactMap {
guard let node = $0 else { return nil }
return RepositoryLabel(color: node.color, name: node.name)
}.sorted { $0.name < $1.name }
self?.update(animated: true)
case .failure(let error):
case .success(let payload):
self?.labels = payload.labels.sorted { $0.name < $1.name }
strongSelf.update(page: payload.nextPage, animated: true)
case .error(let error):
Squawk.show(error: error)
}
})
}
}

// MARK: BaseListViewControllerDataSource
Expand Down
4 changes: 4 additions & 0 deletions Freetime.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,7 @@
29FE635F21AE2E2F00A07A86 /* RepositoryLoadingViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FE635E21AE2E2F00A07A86 /* RepositoryLoadingViewController.swift */; };
29FE636121AE2E7900A07A86 /* RepositoryErrorViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FE636021AE2E7900A07A86 /* RepositoryErrorViewController.swift */; };
29FF85A51EE1EA7A007B8762 /* ReactionContent+ReactionType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 29FF85A41EE1EA7A007B8762 /* ReactionContent+ReactionType.swift */; };
2CDD97C22411B61C0016D5CF /* GitHubClient+RepositoryLabels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CDD97C12411B61C0016D5CF /* GitHubClient+RepositoryLabels.swift */; };
3E79A2FF1F8A7DA700E1126B /* ShortcutHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3E79A2FE1F8A7DA700E1126B /* ShortcutHandler.swift */; };
4920F1A81F72E27200131E9D /* UIViewController+UserActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4920F1A71F72E27200131E9D /* UIViewController+UserActivity.swift */; };
49AF91B1204B416500DFF325 /* MergeTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49AF91B0204B416500DFF325 /* MergeTests.swift */; };
Expand Down Expand Up @@ -1060,6 +1061,7 @@
29FE635E21AE2E2F00A07A86 /* RepositoryLoadingViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoryLoadingViewController.swift; sourceTree = "<group>"; };
29FE636021AE2E7900A07A86 /* RepositoryErrorViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepositoryErrorViewController.swift; sourceTree = "<group>"; };
29FF85A41EE1EA7A007B8762 /* ReactionContent+ReactionType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "ReactionContent+ReactionType.swift"; sourceTree = "<group>"; };
2CDD97C12411B61C0016D5CF /* GitHubClient+RepositoryLabels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GitHubClient+RepositoryLabels.swift"; sourceTree = "<group>"; };
36115D494E8C3B4F39AC8CD9 /* Pods-Freetime.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Freetime.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Freetime/Pods-Freetime.debug.xcconfig"; sourceTree = "<group>"; };
3E106824819769E0A6665A79 /* Pods-FreetimeWatch.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FreetimeWatch.release.xcconfig"; path = "Pods/Target Support Files/Pods-FreetimeWatch/Pods-FreetimeWatch.release.xcconfig"; sourceTree = "<group>"; };
3E79A2FE1F8A7DA700E1126B /* ShortcutHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutHandler.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2154,6 +2156,7 @@
2924C18A20D5B3A100FCFCFF /* LabelMenuCell.swift */,
29C8F9B4208C081D0075931C /* LabelSectionController.swift */,
2924C18C20D5B3DD00FCFCFF /* LabelsViewController.swift */,
2CDD97C12411B61C0016D5CF /* GitHubClient+RepositoryLabels.swift */,
);
path = Labels;
sourceTree = "<group>";
Expand Down Expand Up @@ -3263,6 +3266,7 @@
031E0241220B433C00A329F1 /* UIImage+Color.swift in Sources */,
29999734203135E100995FFD /* IssueMergeContextCell.swift in Sources */,
29EDFE821F661562005BCCEB /* RepositoryReadmeModel.swift in Sources */,
2CDD97C22411B61C0016D5CF /* GitHubClient+RepositoryLabels.swift in Sources */,
29EDFE841F661776005BCCEB /* RepositoryReadmeSectionController.swift in Sources */,
29136BDF200A7A75007317BE /* UIScrollView+LeftRightSafeInset.swift in Sources */,
298C7E2621D7F56600DD2A60 /* SettingsAccountCell.swift in Sources */,
Expand Down
72 changes: 67 additions & 5 deletions gql/API.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1389,20 +1389,22 @@ public final class AddReactionMutation: GraphQLMutation {
}
}

public final class RepositoryLabelsQuery: GraphQLQuery {
public final class FetchRepositoryLabelsQuery: GraphQLQuery {
public let operationDefinition =
"query RepositoryLabels($owner: String!, $repo: String!) {\n repository(owner: $owner, name: $repo) {\n __typename\n labels(first: 100) {\n __typename\n nodes {\n __typename\n name\n color\n }\n }\n }\n}"
"query fetchRepositoryLabels($owner: String!, $repo: String!, $after: String) {\n repository(owner: $owner, name: $repo) {\n __typename\n labels(first: 100, after: $after) {\n __typename\n nodes {\n __typename\n name\n color\n }\n pageInfo {\n __typename\n hasNextPage\n endCursor\n }\n }\n }\n}"

public var owner: String
public var repo: String
public var after: String?

public init(owner: String, repo: String) {
public init(owner: String, repo: String, after: String? = nil) {
self.owner = owner
self.repo = repo
self.after = after
}

public var variables: GraphQLMap? {
return ["owner": owner, "repo": repo]
return ["owner": owner, "repo": repo, "after": after]
}

public struct Data: GraphQLSelectionSet {
Expand Down Expand Up @@ -1437,7 +1439,7 @@ public final class RepositoryLabelsQuery: GraphQLQuery {

public static let selections: [GraphQLSelection] = [
GraphQLField("__typename", type: .nonNull(.scalar(String.self))),
GraphQLField("labels", arguments: ["first": 100], type: .object(Label.selections)),
GraphQLField("labels", arguments: ["first": 100, "after": GraphQLVariable("after")], type: .object(Label.selections)),
]

public private(set) var resultMap: ResultMap
Expand Down Expand Up @@ -1475,6 +1477,7 @@ public final class RepositoryLabelsQuery: GraphQLQuery {
public static let selections: [GraphQLSelection] = [
GraphQLField("__typename", type: .nonNull(.scalar(String.self))),
GraphQLField("nodes", type: .list(.object(Node.selections))),
GraphQLField("pageInfo", type: .nonNull(.object(PageInfo.selections))),
]

public private(set) var resultMap: ResultMap
Expand Down Expand Up @@ -1506,6 +1509,16 @@ public final class RepositoryLabelsQuery: GraphQLQuery {
}
}

/// Information to aid in pagination.
public var pageInfo: PageInfo {
get {
return PageInfo(unsafeResultMap: resultMap["pageInfo"]! as! ResultMap)
}
set {
resultMap.updateValue(newValue.resultMap, forKey: "pageInfo")
}
}

public struct Node: GraphQLSelectionSet {
public static let possibleTypes = ["Label"]

Expand Down Expand Up @@ -1555,6 +1568,55 @@ public final class RepositoryLabelsQuery: GraphQLQuery {
}
}
}

public struct PageInfo: GraphQLSelectionSet {
public static let possibleTypes = ["PageInfo"]

public static let selections: [GraphQLSelection] = [
GraphQLField("__typename", type: .nonNull(.scalar(String.self))),
GraphQLField("hasNextPage", type: .nonNull(.scalar(Bool.self))),
GraphQLField("endCursor", type: .scalar(String.self)),
]

public private(set) var resultMap: ResultMap

public init(unsafeResultMap: ResultMap) {
self.resultMap = unsafeResultMap
}

public init(hasNextPage: Bool, endCursor: String? = nil) {
self.init(unsafeResultMap: ["__typename": "PageInfo", "hasNextPage": hasNextPage, "endCursor": endCursor])
}

public var __typename: String {
get {
return resultMap["__typename"]! as! String
}
set {
resultMap.updateValue(newValue, forKey: "__typename")
}
}

/// When paginating forwards, are there more items?
public var hasNextPage: Bool {
get {
return resultMap["hasNextPage"]! as! Bool
}
set {
resultMap.updateValue(newValue, forKey: "hasNextPage")
}
}

/// When paginating forwards, the cursor to continue.
public var endCursor: String? {
get {
return resultMap["endCursor"] as? String
}
set {
resultMap.updateValue(newValue, forKey: "endCursor")
}
}
}
}
}
}
Expand Down
10 changes: 7 additions & 3 deletions gql/RepositoryLabels.graphql
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
query RepositoryLabels($owner: String!, $repo: String!) {
query fetchRepositoryLabels($owner: String!, $repo: String!, $after: String) {
repository(owner: $owner, name: $repo) {
labels(first:100) {
labels(first:100, after: $after) {
nodes {
name
color
}
}
pageInfo {
hasNextPage
endCursor
}
}
}
}

0 comments on commit b9579e2

Please sign in to comment.