Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a '--user' option to extract the activity of an engineer that is not the current user #14

Merged
merged 6 commits into from
Mar 12, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
gpetiot marked this conversation as resolved.
Show resolved Hide resolved

### 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
Loading