Skip to content

Commit

Permalink
Add support for the remove label endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
BenFradet committed Jun 18, 2018
1 parent f54cd6a commit bc3cd1a
Show file tree
Hide file tree
Showing 12 changed files with 206 additions and 0 deletions.
22 changes: 22 additions & 0 deletions docs/src/main/tut/issue.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,6 +300,28 @@ The `result` on the right is the corresponding assigned [List[Label]][issue-scal

See [the API doc](https://developer.github.com/v3/issues/labels/#add-labels-to-an-issue) for full reference.

### Remove label

You can remove a label from an issue with the following parameters:

- the repository coordinates (`owner` and `name` of the repository).
- `number`: The issue number.
- `label`: The label that requires removing.

To remove an existing label from an issue:

```tut:silent
val removedLabelList = Github(accessToken).issues.removeLabel("47deg", "github4s", 123, "bug")
removedLabelList.exec[cats.Id, HttpResponse[String]]() match {
case Left(e) => println(s"Something went wrong: ${e.getMessage}")
case Right(r) => println(r.result)
}
```

The `result` on the right is the corresponding removed [List[Label]][issue-scala]

See [the API doc](https://developer.github.com/v3/issues/labels/#remove-a-label-from-an-issue) for full reference.

As you can see, a few features of the issue endpoint are missing.

As a result, if you'd like to see a feature supported, feel free to create an issue and/or a pull request!
Expand Down
39 changes: 39 additions & 0 deletions github4s/jvm/src/test/scala/github4s/unit/ApiSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,45 @@ class ApiSpec
response should be('left)
}

"Issues >> RemoveLabel" should "return the removed issue labels when a valid issue number is provided" in {
val response =
issues.removeLabel(
accessToken,
headerUserAgent,
validRepoOwner,
validRepoName,
validIssueNumber,
validIssueLabel.head)
response should be('right)

response.toOption map { r
r.result.nonEmpty shouldBe true
r.statusCode shouldBe okStatusCode
}
}
it should "return an error if an invalid issue number is provided" in {
val response =
issues.removeLabel(
accessToken,
headerUserAgent,
validRepoOwner,
validRepoName,
invalidIssueNumber,
validIssueLabel.head)
response should be('left)
}
it should "return an error if no tokens are provided" in {
val response =
issues.removeLabel(
None,
headerUserAgent,
validRepoOwner,
validRepoName,
validIssueNumber,
validIssueLabel.head)
response should be('left)
}

"Issues >> AddLabels" should "return the assigned issue labels when a valid issue number is provided" in {
val response =
issues.addLabels(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -885,6 +885,32 @@ trait MockGithubApiServer extends MockServerService with FakeResponses with Test
.withHeader(not("Authorization")))
.respond(response.withStatusCode(notFoundStatusCode).withBody(notFoundResponse))

//Issues >> Remove label from an Issue
mockServer
.when(request
.withMethod("DELETE")
.withPath(
s"/repos/$validRepoOwner/$validRepoName/issues/$validIssueNumber/labels/${validIssueLabel.head}")
.withHeader("Authorization", tokenHeader))
.respond(response.withStatusCode(okStatusCode).withBody(listLabelsValidResponse))

mockServer
.when(
request
.withMethod("DELETE")
.withPath(
s"/repos/$validRepoOwner/$validRepoName/issues/$invalidIssueNumber/labels/${validIssueLabel.head}")
.withHeader("Authorization", tokenHeader))
.respond(response.withStatusCode(notFoundStatusCode).withBody(notFoundResponse))

mockServer
.when(request
.withMethod("DELETE")
.withPath(
s"/repos/$validRepoOwner/$validRepoName/issues/$validIssueNumber/labels/${validIssueLabel.head}")
.withHeader(not("Authorization")))
.respond(response.withStatusCode(notFoundStatusCode).withBody(notFoundResponse))

//Issues >> List labels for an Issue
mockServer
.when(
Expand Down
8 changes: 8 additions & 0 deletions github4s/shared/src/main/scala/github4s/GithubAPIs.scala
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,14 @@ class GHIssues(accessToken: Option[String] = None)(implicit O: IssueOps[GitHub4s
): GHIO[GHResponse[List[Label]]] =
O.addLabels(owner, repo, number, labels, accessToken)

def removeLabel(
owner: String,
repo: String,
number: Int,
label: String
): GHIO[GHResponse[List[Label]]] =
O.removeLabel(owner, repo, number, label, accessToken)

}

class GHActivities(accessToken: Option[String] = None)(implicit O: ActivityOps[GitHub4s]) {
Expand Down
10 changes: 10 additions & 0 deletions github4s/shared/src/main/scala/github4s/HttpClient.scala
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,16 @@ class HttpClient[C, M[_]](
httpRbImpl.runEmpty(
httpRequestBuilder(buildURL(method)).deleteMethod.withHeaders(headers).withAuth(accessToken))

def deleteWithResponse[A](
accessToken: Option[String] = None,
url: String,
headers: Map[String, String] = Map.empty
)(implicit D: Decoder[A]): M[GHResponse[A]] =
httpRbImpl.run[A](
httpRequestBuilder(buildURL(url)).deleteMethod
.withAuth(accessToken)
.withHeaders(headers))

private def buildURL(method: String) = urls.baseUrl + method

val defaultPage: Int = 1
Expand Down
23 changes: 23 additions & 0 deletions github4s/shared/src/main/scala/github4s/api/Issues.scala
Original file line number Diff line number Diff line change
Expand Up @@ -299,4 +299,27 @@ class Issues[C, M[_]](
headers,
labels.asJson.noSpaces)

/**
* Remove the specified label from an Issue
*
* @param accessToken to identify the authenticated user
* @param headers optional user headers to include in the request
* @param owner of the repo
* @param repo name of the repo
* @param number Issue number
* @param label the name of the label to remove from the issue
* @return a GHResponse with the list of labels removed from the Issue.
*/
def removeLabel(
accessToken: Option[String] = None,
headers: Map[String, String] = Map(),
owner: String,
repo: String,
number: Int,
label: String): M[GHResponse[List[Label]]] =
httpClient.deleteWithResponse[List[Label]](
accessToken,
s"repos/$owner/$repo/issues/$number/labels/$label",
headers)

}
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,14 @@ final case class AddLabels(
accessToken: Option[String] = None
) extends IssueOp[GHResponse[List[Label]]]

final case class RemoveLabel(
owner: String,
repo: String,
number: Int,
label: String,
accessToken: Option[String] = None
) extends IssueOp[GHResponse[List[Label]]]

/**
* Exposes Issue operations as a Free monadic algebra that may be combined with other Algebras via
* Coproduct
Expand Down Expand Up @@ -221,6 +229,15 @@ class IssueOps[F[_]](implicit I: InjectK[IssueOp, F]) {
): Free[F, GHResponse[List[Label]]] =
Free.inject[IssueOp, F](AddLabels(owner, repo, number, labels, accessToken))

def removeLabel(
owner: String,
repo: String,
number: Int,
label: String,
accessToken: Option[String] = None
): Free[F, GHResponse[List[Label]]] =
Free.inject[IssueOp, F](RemoveLabel(owner, repo, number, label, accessToken))

}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,8 @@ class Interpreters[M[_], C](
issues.listLabels(accessToken, headers, owner, repo, number)
case AddLabels(owner, repo, number, labels, accessToken)
issues.addLabels(accessToken, headers, owner, repo, number, labels)
case RemoveLabel(owner, repo, number, label, accessToken)
issues.removeLabel(accessToken, headers, owner, repo, number, label)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,17 @@ trait GHIssuesSpec[T] extends BaseIntegrationSpec[T] {
})
}

"Issues >> RemoveLabel" should "return a list of removed labels" in {
val response = Github(accessToken).issues
.removeLabel(validRepoOwner, validRepoName, validIssueNumber, validIssueLabel.head)
.execFuture[T](headerUserAgent)

testFutureIsRight[List[Label]](response, { r =>
r.result.nonEmpty shouldBe true
r.statusCode shouldBe okStatusCode
})
}

"Issues >> AddLabels" should "return a list of labels" in {
val response = Github(accessToken).issues
.addLabels(validRepoOwner, validRepoName, validIssueNumber, validIssueLabel)
Expand Down
13 changes: 13 additions & 0 deletions github4s/shared/src/test/scala/github4s/unit/GHIssuesSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -202,4 +202,17 @@ class GHIssuesSpec extends BaseSpec {
ghIssues.addLabels(validRepoOwner, validRepoName, validIssueNumber, validIssueLabel)
}

"Issues.RemoveLabel" should "call to IssuesOps with the right parameters" in {
val response: Free[GitHub4s, GHResponse[List[Label]]] =
Free.pure(Right(GHResult(List(label), okStatusCode, Map.empty)))

val commentOps = mock[IssueOpsTest]
(commentOps.removeLabel _)
.expects(validRepoOwner, validRepoName, validIssueNumber, validIssueLabel.head, sampleToken)
.returns(response)

val ghIssues = new GHIssues(sampleToken)(commentOps)
ghIssues.removeLabel(validRepoOwner, validRepoName, validIssueNumber, validIssueLabel.head)
}

}
21 changes: 21 additions & 0 deletions github4s/shared/src/test/scala/github4s/unit/IssuesSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -291,4 +291,25 @@ class IssuesSpec extends BaseSpec {
validIssueNumber,
validIssueLabel)
}

"Issues.RemoveLabel" should "call httpClient.delete with the right parameters" in {
val response: GHResponse[List[Label]] =
Right(GHResult(List(label), okStatusCode, Map.empty))

val httpClientMock = httpClientMockDeleteWithResponse[List[Label]](
url = s"repos/$validRepoOwner/$validRepoName/issues/$validIssueNumber/labels/${validIssueLabel.head}",
response = response
)

val issues = new Issues[String, Id] {
override val httpClient: HttpClient[String, Id] = httpClientMock
}
issues.removeLabel(
sampleToken,
headerUserAgent,
validRepoOwner,
validRepoName,
validIssueNumber,
validIssueLabel.head)
}
}
14 changes: 14 additions & 0 deletions github4s/shared/src/test/scala/github4s/utils/BaseSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,20 @@ trait BaseSpec extends FlatSpec with Matchers with TestData with IdInstances wit
httpClientMock
}

def httpClientMockDeleteWithResponse[T](
url: String,
response: GHResponse[T]): HttpClient[String, Id] = {
val httpClientMock = mock[HttpClientTest]
(httpClientMock
.deleteWithResponse[T](
_: Option[String],
_: String,
_: Map[String, String])(_: Decoder[T]))
.expects(sampleToken, url, headerUserAgent, *)
.returns(response)
httpClientMock
}

class GitDataOpsTest extends GitDataOps[GitHub4s]
class PullRequestOpsTest extends PullRequestOps[GitHub4s]
class RepositoryOpsTest extends RepositoryOps[GitHub4s]
Expand Down

0 comments on commit bc3cd1a

Please sign in to comment.