Skip to content

Commit fe7408f

Browse files
Get all topics attached to a repo. (#8)
1 parent 1a7c8a5 commit fe7408f

8 files changed

Lines changed: 228 additions & 67 deletions

File tree

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,30 @@ You will need a Github access token with `repo` permissions. This is one way to
1010
```clojure
1111
(def token (System/getenv "GITHUB_ACCESS_TOKEN"))
1212
```
13+
### Core functions
14+
```clojure
15+
(require '[eamonnsullivan.github-api-lib.core :as core])
16+
17+
(core/get-repo-id token "https://github.com/eamonnsullivan/github-api-lib")
18+
;; "MDEwOlJlcG9zaXRvcnkzMDYxMjYwNDY="
19+
20+
(core/get-repo-topics token "eamonnsullivan/github-api-lib")
21+
;; ["clojure" "clojars" "github-graphql"]
22+
23+
;; make your own graphql query
24+
(def get-repo-id
25+
"query getRepoId ($owner: String!, $name: String!) {
26+
repository(owner:$owner, name:$name) {
27+
id
28+
}
29+
}
30+
")
31+
32+
(core/make-graphql-post
33+
token
34+
get-repo-id
35+
{:owner "eamonnsullivan" :name "github-api-lib"})
36+
```
1337
### Pull Requests
1438

1539
```clojure

pom.xml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
<modelVersion>4.0.0</modelVersion>
44
<groupId>eamonnsullivan</groupId>
55
<artifactId>github-api-lib</artifactId>
6-
<version>0.1.16</version>
6+
<version>0.1.17</version>
77
<name>github-api-lib</name>
88
<description>Library of Github API calls that I happen to need.</description>
99
<url>https://github.com/eamonnsullivan/github-api-lib</url>
@@ -15,7 +15,7 @@
1515
</licenses>
1616
<developers>
1717
<developer>
18-
<name>Eamonn</name>
18+
<name>Eamonn Sullivan</name>
1919
</developer>
2020
</developers>
2121
<scm>
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
query RepoTopicQuery ($repoId: ID!, $first: Int!, $after: String) {
2+
node(id: $repoId) {
3+
... on Repository {
4+
repositoryTopics(first: $first, after: $after) {
5+
nodes {
6+
topic {
7+
name
8+
}
9+
}
10+
pageInfo {
11+
hasNextPage
12+
endCursor
13+
}
14+
}
15+
}
16+
}
17+
}

src/eamonnsullivan/github_api_lib/core.clj

Lines changed: 76 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
[clojure.data.json :as json]
44
[clojure.java.io :as io]))
55

6+
(def ^:dynamic *default-page-size* 10)
7+
68
(def github-url "https://api.github.com/graphql")
79

810
(defn request-opts
@@ -96,26 +98,26 @@
9698
some? some?
9799
initk nil}}]
98100
(reify
99-
clojure.lang.Seqable
100-
(seq [_]
101-
((fn next [ret]
102-
(when (some? ret)
103-
(cons (vf ret)
104-
(when-some [k (kf ret)]
105-
(lazy-seq (next (step! k)))))))
106-
(step! initk)))
107-
clojure.lang.IReduceInit
108-
(reduce [_ rf init]
109-
(loop [acc init
110-
ret (step! initk)]
111-
(if (some? ret)
112-
(let [acc (rf acc (vf ret))]
113-
(if (reduced? acc)
114-
@acc
115-
(if-some [k (kf ret)]
116-
(recur acc (step! k))
117-
acc)))
118-
acc)))))
101+
clojure.lang.Seqable
102+
(seq [_]
103+
((fn next [ret]
104+
(when (some? ret)
105+
(cons (vf ret)
106+
(when-some [k (kf ret)]
107+
(lazy-seq (next (step! k)))))))
108+
(step! initk)))
109+
clojure.lang.IReduceInit
110+
(reduce [_ rf init]
111+
(loop [acc init
112+
ret (step! initk)]
113+
(if (some? ret)
114+
(let [acc (rf acc (vf ret))]
115+
(if (reduced? acc)
116+
@acc
117+
(if-some [k (kf ret)]
118+
(recur acc (step! k))
119+
acc)))
120+
acc)))))
119121

120122
(defn get-all-pages
121123
"Convenience function for getting all of the results from a paged search.
@@ -126,11 +128,57 @@
126128
127129
Returns a flattened, realised sequence with all of the result. Don't
128130
use this on an infinite or very big sequence."
129-
[getter results? valuesfn]
130-
(let [get-next (fn [ret] (if (-> ret :data :search :pageInfo :hasNextPage)
131-
(-> ret :data :search :pageInfo :endCursor)
132-
nil))]
133-
(vec (reduce
134-
(fn [acc page] (concat acc page))
135-
[]
136-
(iteration getter :vf valuesfn :kf get-next :some? results?)))))
131+
([getter results? valuesfn]
132+
(let [get-next (fn [ret] (if (-> ret :data :search :pageInfo :hasNextPage)
133+
(-> ret :data :search :pageInfo :endCursor)
134+
nil))]
135+
(get-all-pages getter results? valuesfn get-next)))
136+
([getter results? valuesfn get-nextfn]
137+
(vec (reduce
138+
(fn [acc page] (concat acc page))
139+
[]
140+
(iteration getter :vf valuesfn :kf get-nextfn :some? results?)))))
141+
142+
(defn get-repo-id
143+
"Get the unique ID value for a repository."
144+
([access-token url]
145+
(let [repo (parse-repo url)
146+
owner (:owner repo)
147+
name (:name repo)]
148+
(when repo
149+
(get-repo-id access-token owner name))))
150+
([access-token owner repo-name]
151+
(let [variables {:owner owner :name repo-name}]
152+
(-> (make-graphql-post
153+
access-token
154+
(get-graphql "get-repo-id-query")
155+
variables)
156+
:data
157+
:repository
158+
:id))))
159+
160+
(defn get-topics
161+
[page]
162+
(into [] (map #(str (-> % :topic :name))
163+
(-> page :data :node :repositoryTopics :nodes))))
164+
165+
(defn get-page-of-topics
166+
"Get a page of topics on a repo"
167+
[access-token repo-id page-size cursor]
168+
(-> (make-graphql-post
169+
access-token
170+
(get-graphql "repo-topic-query")
171+
{:repoId repo-id :first page-size :after cursor})))
172+
173+
(defn get-repo-topics
174+
"Get all of the topics attached to a repo."
175+
([access-token url]
176+
(get-repo-topics access-token url *default-page-size*))
177+
([access-token url page-size]
178+
(let [repo-id (get-repo-id access-token url)
179+
get-page (partial get-page-of-topics access-token repo-id page-size)
180+
results? (fn [page] (some? (get-topics page)))
181+
get-next (fn [ret] (if (-> ret :data :node :repositoryTopics :pageInfo :hasNextPage)
182+
(-> ret :data :node :repositoryTopics :pageInfo :endCursor)
183+
nil))]
184+
(get-all-pages get-page results? get-topics get-next))))

src/eamonnsullivan/github_api_lib/pull_requests.clj

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,6 @@
2626
body (json/read-str (:body response) :key-fn keyword)]
2727
(:node_id body)))
2828

29-
(defn get-repo-id
30-
"Get the unique ID value for a repository."
31-
([access-token url]
32-
(let [repo (core/parse-repo url)
33-
owner (:owner repo)
34-
name (:name repo)]
35-
(when repo
36-
(get-repo-id access-token owner name))))
37-
([access-token owner repo-name]
38-
(let [variables {:owner owner :name repo-name}]
39-
(-> (core/make-graphql-post
40-
access-token
41-
(core/get-graphql "get-repo-id-query")
42-
variables)
43-
:data
44-
:repository
45-
:id))))
46-
4729
(defn get-pull-request-id
4830
"Find the unique ID of a pull request on the repository at the
4931
provided url. Set must-be-open? to true to filter the pull requests
@@ -135,7 +117,7 @@
135117
base-branch :base
136118
merging-branch :branch
137119
draft :draft} (merge create-pr-defaults pull-request)
138-
repo-id (get-repo-id access-token owner repo-name)
120+
repo-id (core/get-repo-id access-token owner repo-name)
139121
variables {:repositoryId repo-id
140122
:title title
141123
:body body
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"data": {
3+
"node": {
4+
"repositoryTopics": {
5+
"nodes": [
6+
{
7+
"topic": {
8+
"name": "cps"
9+
}
10+
},
11+
{
12+
"topic": {
13+
"name": "cam"
14+
}
15+
},
16+
{
17+
"topic": {
18+
"name": "dpub"
19+
}
20+
},
21+
{
22+
"topic": {
23+
"name": "node"
24+
}
25+
},
26+
{
27+
"topic": {
28+
"name": "frontend"
29+
}
30+
},
31+
{
32+
"topic": {
33+
"name": "optimo"
34+
}
35+
}
36+
],
37+
"pageInfo": {
38+
"hasNextPage": false,
39+
"endCursor": "Y3Vyc29yOnYyOpHOARzyUg=="
40+
}
41+
}
42+
}
43+
}
44+
}

test/eamonnsullivan/github_api_lib/core_test.clj

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
[clj-http.client :as client]
44
[eamonnsullivan.github-api-lib.core :as sut]))
55

6+
(def repo-id-response-success (slurp "./test/eamonnsullivan/fixtures/repo-response-success.json"))
7+
(def repo-id-response-failure (slurp "./test/eamonnsullivan/fixtures/repo-response-failure.json"))
8+
(def repo-topic-response-success (slurp "./test/eamonnsullivan/fixtures/repo-topic-response.json"))
9+
610
(defn test-args-to-get
711
[url opts]
812
(is (= "access-token" (:username opts)))
@@ -144,3 +148,63 @@
144148
:kf #(if (-> % :data :search :pageInfo :hasNextPage)
145149
(-> % :data :search :pageInfo :endCursor)
146150
nil)))))))
151+
152+
(deftest test-get-repo-id
153+
(with-redefs [sut/http-post (fn [_ _ _] {:body repo-id-response-success})]
154+
(testing "parses id from successful response"
155+
(is (= "MDEwOlJlcG9zaXRvcnkyOTczNzY1NTc=" (sut/get-repo-id "secret-token" "owner" "repo-name"))))
156+
(testing "handles url instead of separate owner/repo-name"
157+
(is (= "MDEwOlJlcG9zaXRvcnkyOTczNzY1NTc=" (sut/get-repo-id "secret-token" "owner/repo-name")))
158+
(is (= "MDEwOlJlcG9zaXRvcnkyOTczNzY1NTc=" (sut/get-repo-id "secret-token" "https://github.com/owner/repo-name")))))
159+
(with-redefs [sut/http-post (fn [_ _ _] {:body repo-id-response-failure})]
160+
(testing "throws exception on error"
161+
(is (thrown-with-msg? RuntimeException
162+
#"Could not resolve to a Repository with the name 'eamonnsullivan/not-there'."
163+
(sut/get-repo-id "secret-token" "owner" "repo-name")))))
164+
(with-redefs [sut/parse-repo (fn [_] (throw (ex-info "error" {})))]
165+
(testing "passes on thrown exceptions from parse-repo"
166+
(is (thrown-with-msg? RuntimeException
167+
#"error"
168+
(sut/get-repo-id "secret-token" "whatever/whatever"))))))
169+
170+
(deftest test-get-page-of-topics
171+
(with-redefs [sut/http-post (fn [_ _ _] {:body repo-topic-response-success})]
172+
(testing "gets topics from a page"
173+
(is (= {:data
174+
{:node
175+
{:repositoryTopics
176+
{:nodes
177+
[{:topic {:name "cps"}}
178+
{:topic {:name "cam"}}
179+
{:topic {:name "dpub"}}
180+
{:topic {:name "node"}}
181+
{:topic {:name "frontend"}}
182+
{:topic {:name "optimo"}}],
183+
:pageInfo
184+
{:hasNextPage false, :endCursor "Y3Vyc29yOnYyOpHOARzyUg=="}}}}}
185+
(sut/get-page-of-topics "secret-token" "repo-id" 10 nil))))))
186+
187+
(defn fake-paging-response
188+
[_ _ _ cursor]
189+
(let [first-page {:data
190+
{:node
191+
{:repositoryTopics
192+
{:nodes
193+
[ {:topic {:name "tag1"}} {:topic {:name "tag2"}}]
194+
:pageInfo {:hasNextPage true, :endCursor "cursor"}}}}}
195+
last-page {:data
196+
{:node
197+
{:repositoryTopics
198+
{:nodes
199+
[ {:topic {:name "tag3"}} {:topic {:name "tag4"}}]
200+
:pageInfo {:hasNextPage false, :endCursor "cursor2"}}}}}]
201+
(if-not cursor
202+
first-page
203+
last-page)))
204+
205+
(deftest test-get-repo-topics
206+
(with-redefs [sut/get-repo-id (fn[_ _] "some-id")
207+
sut/get-page-of-topics fake-paging-response]
208+
(testing "follows pages"
209+
(is (= ["tag1" "tag2" "tag3" "tag4"]
210+
(sut/get-repo-topics "secret-token" "eamonnsullivan/github-api-lib"))))))

test/eamonnsullivan/github_api_lib/pull_requests_test.clj

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,24 +30,6 @@
3030
(fn [m]
3131
(= value (m key))))
3232

33-
(deftest test-get-repo-id
34-
(with-redefs [core/http-post (fn [_ _ _] {:body repo-id-response-success})]
35-
(testing "parses id from successful response"
36-
(is (= "MDEwOlJlcG9zaXRvcnkyOTczNzY1NTc=" (sut/get-repo-id "secret-token" "owner" "repo-name"))))
37-
(testing "handles url instead of separate owner/repo-name"
38-
(is (= "MDEwOlJlcG9zaXRvcnkyOTczNzY1NTc=" (sut/get-repo-id "secret-token" "owner/repo-name")))
39-
(is (= "MDEwOlJlcG9zaXRvcnkyOTczNzY1NTc=" (sut/get-repo-id "secret-token" "https://github.com/owner/repo-name")))))
40-
(with-redefs [core/http-post (fn [_ _ _] {:body repo-id-response-failure})]
41-
(testing "throws exception on error"
42-
(is (thrown-with-msg? RuntimeException
43-
#"Could not resolve to a Repository with the name 'eamonnsullivan/not-there'."
44-
(sut/get-repo-id "secret-token" "owner" "repo-name")))))
45-
(with-redefs [core/parse-repo (fn [_] (throw (ex-info "error" {})))]
46-
(testing "passes on thrown exceptions from parse-repo"
47-
(is (thrown-with-msg? RuntimeException
48-
#"error"
49-
(sut/get-repo-id "secret-token" "whatever/whatever"))))))
50-
5133
(defn make-fake-post
5234
[query-response mutation-response query-asserts mutation-asserts]
5335
(fn [_ payload _] (if (string/includes? payload "mutation")

0 commit comments

Comments
 (0)