Skip to content

Commit

Permalink
Add a '--user' option to extract the activity of an engineer that is …
Browse files Browse the repository at this point in the history
…not the current user
  • Loading branch information
gpetiot committed Mar 11, 2024
1 parent 06b13cf commit cbb6733
Show file tree
Hide file tree
Showing 5 changed files with 117 additions and 58 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
### Added

- Add the `--version` command-line option (#13, @gpetiot)
- Add a `--user` option to extract the activity of an engineer that is not the current user (#<PR_NUMBER>, @gpetiot)

### Changed

Expand Down
20 changes: 12 additions & 8 deletions bin/main.ml
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ let mtime path =

let get_token () = Token.load (home / ".github" / "github-activity-token")

let show ~from json =
let contribs = Contributions.of_json ~from json in
let show ~from ~user json =
let contribs = Contributions.of_json ~from ~user json in
if Contributions.is_empty contribs then
Fmt.epr "(no activity found since %s)@." from
else Fmt.pr "@[<v>%a@]@." Contributions.pp contribs
Expand Down Expand Up @@ -68,35 +68,39 @@ let period =
in
Term.(const f $ from $ to_ $ last_week)

let user =
let doc = Arg.info ~doc:"User name" [ "user" ] in
Arg.(value & opt (some string) None & doc)

let version =
match Build_info.V1.version () with
| None -> "dev"
| Some v -> Build_info.V1.Version.to_string v

let info = Cmd.info "get-activity" ~version

let run period : unit =
let run period user : unit =
match mode with
| `Normal ->
Period.with_period period ~last_fetch_file ~f:(fun period ->
(* Fmt.pr "period: %a@." Fmt.(pair string string) period; *)
let* token = get_token () in
let request = Contributions.request ~period ~token in
let request = Contributions.request ~period ~user ~token in
let* contributions = Graphql.exec request in
show ~from:(fst period) contributions)
show ~from:(fst period) ~user contributions)
| `Save ->
Period.with_period period ~last_fetch_file ~f:(fun period ->
let* token = get_token () in
let request = Contributions.request ~period ~token in
let request = Contributions.request ~period ~user ~token in
let* contributions = Graphql.exec request in
Yojson.Safe.to_file "activity.json" contributions)
| `Load ->
(* When testing formatting changes, it is quicker to fetch the data once and then load it again for each test: *)
let from =
mtime last_fetch_file |> Option.value ~default:0.0 |> Period.to_8601
in
show ~from @@ Yojson.Safe.from_file "activity.json"
show ~from ~user @@ Yojson.Safe.from_file "activity.json"

let term = Term.(const run $ period)
let term = Term.(const run $ period $ user)
let cmd = Cmd.v info term
let () = Stdlib.exit @@ Cmd.eval cmd
29 changes: 22 additions & 7 deletions lib/contributions.ml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ module Json = Yojson.Safe

let ( / ) a b = Json.Util.member b a

let query =
{|query($from: DateTime!, $to: DateTime!) {
viewer {
let query user =
Format.sprintf
{|query($from: DateTime!, $to: DateTime!) {
%s {
login
contributionsCollection(from: $from, to: $to) {
issueContributions(first: 100) {
Expand Down Expand Up @@ -54,9 +55,13 @@ let query =
}
}
}|}
(match user with
| Some u -> Format.sprintf "user(login: %S)" u
| None -> "viewer")

let request ~period:(start, finish) ~token =
let request ~period:(start, finish) ~user ~token =
let variables = [ ("from", `String start); ("to", `String finish) ] in
let query = query user in
Graphql.request ~token ~variables ~query ()

module Datetime = struct
Expand Down Expand Up @@ -137,9 +142,19 @@ let read_repos json =
repo;
}

let of_json ~from json =
let username = json / "data" / "viewer" / "login" |> Json.Util.to_string in
let contribs = json / "data" / "viewer" / "contributionsCollection" in
let of_json ~from ~user json =
let username, contribs =
match user with
| Some username ->
let contribs = json / "data" / "user" / "contributionsCollection" in
(username, contribs)
| None ->
let username =
json / "data" / "viewer" / "login" |> Json.Util.to_string
in
let contribs = json / "data" / "viewer" / "contributionsCollection" in
(username, contribs)
in
let items =
read_issues (contribs / "issueContributions")
@ read_prs (contribs / "pullRequestContributions")
Expand Down
8 changes: 6 additions & 2 deletions lib/contributions.mli
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,13 @@ module Repo_map : Map.S with type key = string

type t = { username : string; activity : item list Repo_map.t }

val request : period:string * string -> token:Token.t -> Graphql.request
val request :
period:string * string ->
user:string option ->
token:Token.t ->
Graphql.request

val of_json : from:string -> Yojson.Safe.t -> t
val of_json : from:string -> user:string option -> Yojson.Safe.t -> t
(** We pass [from] again here so we can filter out anything that GitHub included by accident. *)

val is_empty : t -> bool
Expand Down
117 changes: 76 additions & 41 deletions test/lib/test_contributions.ml
Original file line number Diff line number Diff line change
Expand Up @@ -83,29 +83,10 @@ module Testable = struct
let contributions = Contributions.testable
end

let test_request =
let make_test name ~period ~token ~expected =
let name = Printf.sprintf "request: %s" name in
let test_fun () =
let actual = Contributions.request ~period ~token in
Alcotest.(check Alcotest_ext.request) name expected actual
in
(name, `Quick, test_fun)
in
[
make_test "no token" ~token:"" ~period:("", "")
~expected:
{
meth = `POST;
url = "https://api.github.com/graphql";
headers = [ ("Authorization", "bearer ") ];
body =
`Assoc
[
( "query",
`String
{|query($from: DateTime!, $to: DateTime!) {
viewer {
let request ~user =
Format.sprintf
{|query($from: DateTime!, $to: DateTime!) {
%s {
login
contributionsCollection(from: $from, to: $to) {
issueContributions(first: 100) {
Expand Down Expand Up @@ -155,19 +136,61 @@ let test_request =
}
}
}|}
);
( "variables",
`Assoc [ ("from", `String ""); ("to", `String "") ] );
];
};
(match user with
| Some u -> Format.sprintf "user(login: %S)" u
| None -> "viewer")

let test_request =
let make_test name ~period ~user ~token ~expected =
let name = Printf.sprintf "request: %s" name in
let test_fun () =
let actual = Contributions.request ~period ~user ~token in
Alcotest.(check Alcotest_ext.request) name expected actual
in
(name, `Quick, test_fun)
in
[
(let user = None in
make_test "no token" ~user ~token:"" ~period:("", "")
~expected:
{
meth = `POST;
url = "https://api.github.com/graphql";
headers = [ ("Authorization", "bearer ") ];
body =
`Assoc
[
("query", `String (request ~user));
( "variables",
`Assoc [ ("from", `String ""); ("to", `String "") ] );
];
});
(let user = Some "me" in
make_test "no token" ~user ~token:"" ~period:("", "")
~expected:
{
meth = `POST;
url = "https://api.github.com/graphql";
headers = [ ("Authorization", "bearer ") ];
body =
`Assoc
[
("query", `String (request ~user));
( "variables",
`Assoc [ ("from", `String ""); ("to", `String "") ] );
];
});
]

let activity_example =
{|
let or_viewer = function Some u -> u | None -> "gpetiot"

let activity_example ~user =
Format.sprintf
{|
{
"data": {
"viewer": {
"login": "gpetiot",
%S: {
"login": %S,
"contributionsCollection": {
"issueContributions": {
"nodes": [
Expand Down Expand Up @@ -282,13 +305,16 @@ let activity_example =
}
}
|}
(match user with Some _ -> "user" | None -> "viewer")
(user |> or_viewer)

let activity_example_json = Yojson.Safe.from_string activity_example
let activity_example_json ~user =
Yojson.Safe.from_string (activity_example ~user)

let contributions_example =
let contributions_example ~user =
let open Contributions in
{
username = "gpetiot";
username = user |> or_viewer;
activity =
Repo_map.empty
|> Repo_map.add "gpetiot/config.ml"
Expand Down Expand Up @@ -379,17 +405,23 @@ let contributions_example =
}

let test_of_json =
let make_test name ~from json ~expected =
let make_test name ~from ~user json ~expected =
let name = Printf.sprintf "of_json: %s" name in
let test_fun () =
let actual = Contributions.of_json ~from json in
let actual = Contributions.of_json ~from ~user json in
Alcotest.(check Testable.contributions) name expected actual
in
(name, `Quick, test_fun)
in
[
make_test "no token" ~from:"" activity_example_json
~expected:contributions_example;
(let user = None in
make_test "no token" ~from:"" ~user
(activity_example_json ~user)
~expected:(contributions_example ~user));
(let user = Some "gpetiot" in
make_test "no token" ~from:"" ~user
(activity_example_json ~user)
~expected:(contributions_example ~user));
]

let test_is_empty =
Expand All @@ -406,7 +438,9 @@ let test_is_empty =
~input:
{ Contributions.username = ""; activity = Contributions.Repo_map.empty }
~expected:true;
make_test "not empty" ~input:contributions_example ~expected:false;
make_test "not empty"
~input:(contributions_example ~user:None)
~expected:false;
]

let test_pp =
Expand All @@ -423,7 +457,8 @@ let test_pp =
~input:
{ Contributions.username = ""; activity = Contributions.Repo_map.empty }
~expected:"(no activity)";
make_test "not empty" ~input:contributions_example
make_test "not empty"
~input:(contributions_example ~user:None)
~expected:
"### gpetiot/config.ml\n\
Created repository \
Expand Down

0 comments on commit cbb6733

Please sign in to comment.