Skip to content

Commit ef1d0e5

Browse files
Project API (#354)
* added GET list projects * fix test * merge with master * added GET list project columns * added documentation * removed header and updated documentation * optimize imports * Update docs/docs/project.md Co-Authored-By: Ben Fradet <benjamin.fradet@gmail.com> * Update docs/docs/project.md Co-Authored-By: Ben Fradet <benjamin.fradet@gmail.com> * Update docs/docs/project.md Co-Authored-By: Ben Fradet <benjamin.fradet@gmail.com> * Update github4s/src/main/scala/github4s/interpreters/ProjectsInterpreter.scala Co-Authored-By: Ben Fradet <benjamin.fradet@gmail.com> * Update github4s/src/main/scala/github4s/algebras/Projects.scala Co-Authored-By: Ben Fradet <benjamin.fradet@gmail.com> * Address Ben F. and Eric B. PR comments * Address Eric B. PR comments * Address Rafa PR comments * fix bug travis Co-authored-by: Ben Fradet <benjamin.fradet@gmail.com>
1 parent 9f2e051 commit ef1d0e5

13 files changed

Lines changed: 473 additions & 6 deletions

File tree

docs/docs/project.md

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
---
2+
layout: docs
3+
title: Project API
4+
permalink: project
5+
---
6+
7+
# Project API
8+
9+
Note: The Projects API is currently available for developers to preview. During the preview period,
10+
the API may change without advance notice. Please see the blog post for full details. To access the
11+
API during the preview period, you must provide a custom media type in the `Accept` header:
12+
`application/vnd.github.inertia-preview+json`
13+
14+
Github4s supports the [Project API](https://developer.github.com/v3/projects/). As a result,
15+
with Github4s, you can interact with:
16+
17+
- [Project](#project)
18+
- [List projects](#list-project)
19+
- [Columns](#columns)
20+
- [List project columns](#list-projects-columns)
21+
22+
The following examples assume the following imports and token:
23+
24+
```scala mdoc:silent
25+
import github4s.Github
26+
import github4s.GithubIOSyntax._
27+
import cats.effect.IO
28+
import scala.concurrent.ExecutionContext.Implicits.global
29+
30+
implicit val IOContextShift = IO.contextShift(global)
31+
val accessToken = sys.env.get("GITHUB4S_ACCESS_TOKEN")
32+
```
33+
34+
They also make use of `cats.effect.IO`, but any type container `F` implementing `ConcurrentEffect` will do.
35+
36+
LiftIO syntax for `cats.Id` and `Future` are provided in `GithubIOSyntax`.
37+
38+
## Project
39+
40+
### List projects
41+
42+
You can list the project for a particular organization with `listProjects`; it takes as arguments:
43+
44+
- `org`: name of the organization for which we want to retrieve the projects.
45+
- `state`: filter projects returned by their state. Can be either `open`, `closed`, `all`. Default: `open`, optional
46+
- `pagination`: Limit and Offset for pagination, optional.
47+
- `header`: headers to include in the request, optional.
48+
49+
To list the projects for organization `47deg`:
50+
51+
```scala mdoc:compile-only
52+
val listProjects = Github[IO](accessToken).projects.listProjects(
53+
org = "47deg",
54+
headers = Map("Accept" -> "application/vnd.github.inertia-preview+json"))
55+
listProjects.unsafeRunSync() match {
56+
case Left(e) => println(s"Something went wrong: ${e.getMessage}")
57+
case Right(r) => println(r.result)
58+
}
59+
```
60+
61+
The `result` on the right is the corresponding [List[Project]][project-scala].
62+
63+
See [the API doc](https://developer.github.com/v3/projects/#list-organization-projects) for full reference.
64+
65+
[project-scala]: https://github.com/47deg/github4s/blob/master/github4s/src/main/scala/github4s/domain/Project.scala
66+
67+
### Columns
68+
69+
#### List project columns
70+
71+
You can list the columns for a particular project with `listColumns`; it takes as arguments:
72+
73+
- `project_id`: project id for which we want to retrieve the columns.
74+
- `pagination`: Limit and Offset for pagination, optional.
75+
- `header`: headers to include in the request, optional.
76+
77+
To list the columns for project_id `1910444`:
78+
79+
```scala mdoc:compile-only
80+
val listColumns = Github[IO](accessToken).projects.listColumns(
81+
project_id = 1910444,
82+
headers = Map("Accept" -> "application/vnd.github.inertia-preview+json"))
83+
listColumns.unsafeRunSync match {
84+
case Left(e) => println(s"Something went wrong: ${e.getMessage}")
85+
case Right(r) => println(r.result)
86+
}
87+
```
88+
89+
The `result` on the right is the corresponding [List[Column]][column-scala].
90+
91+
See [the API doc](https://developer.github.com/v3/projects/columns/#list-project-columns) for full reference.
92+
93+
[column-scala]: https://github.com/47deg/github4s/blob/master/github4s/src/main/scala/github4s/domain/Column.scala

docs/src/main/resources/microsite/data/menu.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,6 @@ options:
3131

3232
- title: Team API
3333
url: team
34+
35+
- title: Project API
36+
url: project

github4s/src/main/scala/github4s/Decoders.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,5 +276,8 @@ object Decoders {
276276
)
277277
)
278278

279-
implicit val decodeTeam: Decoder[Team] = deriveDecoder[Team]
279+
implicit val decodeTeam: Decoder[Team] = deriveDecoder[Team]
280+
implicit val decodeProject: Decoder[Project] = deriveDecoder[Project]
281+
implicit val decodeColumn: Decoder[Column] = deriveDecoder[Column]
282+
280283
}

github4s/src/main/scala/github4s/Github.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@ class Github[F[_]: ConcurrentEffect](accessToken: Option[String], timeout: Optio
4040
lazy val pullRequests: PullRequests[F] = module.pullRequests
4141
lazy val organizations: Organizations[F] = module.organizations
4242
lazy val teams: Teams[F] = module.teams
43+
lazy val projects: Projects[F] = module.projects
4344
}
4445

4546
object Github {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
* Copyright 2016-2020 47 Degrees, LLC. <http://www.47deg.com>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package github4s.algebras
18+
19+
import github4s.GithubResponses.GHResponse
20+
import github4s.domain._
21+
22+
trait Projects[F[_]] {
23+
24+
/**
25+
* List the projects belonging to a specific organization
26+
*
27+
* @param org Organization for which we want to retrieve the projects
28+
* @param state Filter projects returned by their state. Can be either `open`, `closed`, `all`.
29+
* Default: `open`
30+
* @param pagination Limit and Offset for pagination
31+
* @param headers Optional user headers to include in the request
32+
* @return GHResponse with the list of projects belonging to this organization
33+
*/
34+
def listProjects(
35+
org: String,
36+
state: Option[String] = None,
37+
pagination: Option[Pagination] = None,
38+
headers: Map[String, String] = Map()
39+
): F[GHResponse[List[Project]]]
40+
41+
/**
42+
* List the columns belonging to a specific project id
43+
*
44+
* @param project_id Project id for which we want to retrieve the columns
45+
* @param pagination Limit and Offset for pagination
46+
* @param headers Optional user headers to include in the request
47+
* @return GHResponse with the list of columns belonging to this project id
48+
*/
49+
def listColumns(
50+
project_id: Int,
51+
pagination: Option[Pagination] = None,
52+
headers: Map[String, String] = Map()
53+
): F[GHResponse[List[Column]]]
54+
55+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* Copyright 2016-2020 47 Degrees, LLC. <http://www.47deg.com>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package github4s.domain
18+
19+
final case class Project(
20+
owner_url: String,
21+
url: String,
22+
html_url: String,
23+
columns_url: String,
24+
id: Int,
25+
node_id: String,
26+
name: String,
27+
body: Option[String],
28+
number: Int,
29+
creator: Creator,
30+
created_at: String,
31+
updated_at: String,
32+
organization_permission: Option[String],
33+
`private`: Option[Boolean]
34+
)
35+
36+
final case class Creator(
37+
login: String,
38+
id: Int,
39+
node_id: String,
40+
avatar_url: String,
41+
gravatar_id: Option[String],
42+
url: String,
43+
html_url: String,
44+
followers_url: String,
45+
following_url: String,
46+
gists_url: String,
47+
starred_url: String,
48+
subscriptions_url: String,
49+
organizations_url: String,
50+
repos_url: String,
51+
events_url: String,
52+
received_events_url: String,
53+
`type`: String,
54+
site_admin: Boolean
55+
)
56+
57+
final case class Column(
58+
url: String,
59+
project_url: String,
60+
cards_url: String,
61+
id: Int,
62+
node_id: String,
63+
name: String,
64+
created_at: String,
65+
updated_at: String
66+
)
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2016-2020 47 Degrees, LLC. <http://www.47deg.com>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package github4s.interpreters
18+
19+
import cats.Applicative
20+
import github4s.GithubResponses.GHResponse
21+
import github4s.algebras.Projects
22+
import github4s.domain.{Column, Pagination, Project}
23+
import github4s.http.HttpClient
24+
import github4s.Decoders._
25+
26+
class ProjectsInterpreter[F[_]](
27+
implicit client: HttpClient[F],
28+
accessToken: Option[String]
29+
) extends Projects[F] {
30+
31+
override def listProjects(
32+
org: String,
33+
state: Option[String],
34+
pagination: Option[Pagination] = None,
35+
headers: Map[String, String] = Map()
36+
): F[GHResponse[List[Project]]] =
37+
client.get[List[Project]](
38+
accessToken,
39+
s"orgs/$org/projects",
40+
headers,
41+
state.fold(Map.empty[String, String])(s => Map("state" -> s)),
42+
pagination
43+
)
44+
45+
override def listColumns(
46+
project_id: Int,
47+
pagination: Option[Pagination],
48+
headers: Map[String, String]
49+
): F[GHResponse[List[Column]]] = client.get[List[Column]](
50+
accessToken,
51+
s"projects/$project_id/columns",
52+
headers,
53+
Map(),
54+
pagination
55+
)
56+
}

github4s/src/main/scala/github4s/modules/GithubAPIs.scala

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ package github4s.modules
1818

1919
import cats.effect.ConcurrentEffect
2020
import github4s.algebras._
21-
import github4s.interpreters._
2221
import github4s.http.HttpClient
22+
import github4s.interpreters._
2323

2424
import scala.concurrent.ExecutionContext
2525
import scala.concurrent.duration.Duration
@@ -35,6 +35,7 @@ sealed trait GithubAPIs[F[_]] {
3535
def pullRequests: PullRequests[F]
3636
def organizations: Organizations[F]
3737
def teams: Teams[F]
38+
def projects: Projects[F]
3839
}
3940

4041
class GithubAPIv3[F[_]: ConcurrentEffect](accessToken: Option[String] = None, timeout: Duration)(
@@ -54,5 +55,6 @@ class GithubAPIv3[F[_]: ConcurrentEffect](accessToken: Option[String] = None, ti
5455
override val pullRequests: PullRequests[F] = new PullRequestsInterpreter[F]
5556
override val organizations: Organizations[F] = new OrganizationsInterpreter[F]
5657
override val teams: Teams[F] = new TeamsInterpreter[F]
58+
override val projects: Projects[F] = new ProjectsInterpreter[F]
5759

5860
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/*
2+
* Copyright 2016-2020 47 Degrees, LLC. <http://www.47deg.com>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package github4s.integration
18+
19+
import cats.effect.IO
20+
import github4s.Github
21+
import github4s.domain.{Column, Project}
22+
import github4s.utils.{BaseIntegrationSpec, Integration}
23+
24+
trait GHProjectsSpec extends BaseIntegrationSpec {
25+
26+
"Project >> ListProjects" should "return the expected projects when a valid org is provided" taggedAs Integration in {
27+
val response =
28+
Github[IO](accessToken).projects
29+
.listProjects(validRepoOwner, headers = headerUserAgent ++ headerAccept)
30+
.unsafeRunSync()
31+
32+
testIsRight[List[Project]](response, { r =>
33+
r.result.nonEmpty shouldBe true
34+
r.statusCode shouldBe okStatusCode
35+
})
36+
}
37+
38+
it should "return error when an invalid org is passed" taggedAs Integration in {
39+
val response =
40+
Github[IO](accessToken).projects
41+
.listProjects(invalidRepoName, headers = headerUserAgent ++ headerAccept)
42+
.unsafeRunSync()
43+
44+
testIsLeft(response)
45+
}
46+
47+
"Project >> ListColumns" should "return the expected column when a valid project id is provided" taggedAs Integration in {
48+
val response =
49+
Github[IO](accessToken).projects
50+
.listColumns(validProjectId, headers = headerUserAgent ++ headerAccept)
51+
.unsafeRunSync()
52+
53+
testIsRight[List[Column]](response, { r =>
54+
r.result.nonEmpty shouldBe true
55+
r.statusCode shouldBe okStatusCode
56+
})
57+
}
58+
59+
it should "return error when an invalid project id is passed" taggedAs Integration in {
60+
val response =
61+
Github[IO](accessToken).projects
62+
.listColumns(invalidProjectId, headers = headerUserAgent ++ headerAccept)
63+
.unsafeRunSync()
64+
65+
testIsLeft(response)
66+
}
67+
68+
}

0 commit comments

Comments
 (0)