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 metrics CLI app 🚀 #6

Merged
merged 24 commits into from
Nov 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
f324696
feat: add helper for reading env vars
hackebrot Nov 8, 2023
7249f22
feat: add JSON and Plain export encoders
hackebrot Nov 8, 2023
3c64307
feat: add Writer and File exporters
hackebrot Nov 8, 2023
d8cb2e9
feat: query GitHub GraphQL API for PRs and Releases
hackebrot Nov 8, 2023
b57cf2d
feat: add factory for dependency injection
hackebrot Nov 8, 2023
18466af
fix: fix bug in env var helper
hackebrot Nov 8, 2023
09386e9
feat: update factory to check for GH API token
hackebrot Nov 8, 2023
a303903
feat: refactor CLI app and develop test framework
hackebrot Nov 8, 2023
616b609
test: implement fake GraphQL client for tests
hackebrot Nov 8, 2023
2b4cab5
feat: add capability to fetch GitHub PR data
hackebrot Nov 8, 2023
38e37a2
test: verify that GraphQL query variables are set correctly
hackebrot Nov 8, 2023
0849d3c
fix: update repo factory with CLI flags
hackebrot Nov 9, 2023
3480997
fix: fix pagination for Pull Requests
hackebrot Nov 9, 2023
543dd5b
feat: add capability to fetch GitHub Release data
hackebrot Nov 9, 2023
679552b
fix: bump cobra to v1.8.0 and EnableTraverseRunHooks
hackebrot Nov 9, 2023
9fe3dc2
feat: add capability to export to CSV
hackebrot Nov 9, 2023
a1c3496
test: add tests for CSV export
hackebrot Nov 10, 2023
b3b8605
refactor: add constructors for exporters
hackebrot Nov 10, 2023
853307a
feat: add file export feature to CLI app
hackebrot Nov 13, 2023
8e175aa
test: add more test cases for releases and prs
hackebrot Nov 13, 2023
a5a0f71
feat: add support for GitHub deployments
hackebrot Nov 14, 2023
5ffd4c3
test: use cmp.Diff for test failure message
hackebrot Nov 14, 2023
374ed45
fix: use root context in all subcommands
hackebrot Nov 15, 2023
be95695
feat: report full env var key in error
hackebrot Nov 23, 2023
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
63 changes: 63 additions & 0 deletions metrics/cmd/deployments.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package cmd

import (
"context"
"fmt"

"github.com/mozilla-services/rapid-release-model/metrics/internal/factory"
"github.com/mozilla-services/rapid-release-model/metrics/internal/github"
"github.com/spf13/cobra"
)

type DeploymentsOptions struct {
Limit int
Environments *[]string
}

func newDeploymentsCmd(f *factory.Factory) *cobra.Command {
opts := new(DeploymentsOptions)

cmd := &cobra.Command{
Use: "deployments",
Short: "Retrieve data about GitHub Deployments",
Long: "Retrieve data about GitHub Deployments",
PreRunE: func(cmd *cobra.Command, args []string) error {
if opts.Limit < 1 {
return fmt.Errorf("Limit cannot be smaller than 1.")
}
return nil
},
RunE: func(cmd *cobra.Command, args []string) error {
return runDeployments(cmd.Root().Context(), f, opts)
},
}
cmd.Flags().IntVarP(&opts.Limit, "limit", "l", 10, "limit for how many Deployments to fetch")

opts.Environments = cmd.Flags().StringArray("env", nil, "multiple use for Deployment environments")

return cmd
}

func runDeployments(ctx context.Context, f *factory.Factory, opts *DeploymentsOptions) error {
repo, err := f.NewGitHubRepo()
if err != nil {
return err
}

gqlClient, err := f.NewGitHubGraphQLClient()
if err != nil {
return err
}

deployments, err := github.QueryDeployments(gqlClient, repo, opts.Limit, opts.Environments)
if err != nil {
return err
}

exporter, err := f.NewExporter()
if err != nil {
return err
}

return exporter.Export(deployments)
Comment on lines +52 to +62

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we are expecting potentially to query and aggregate a lot of data, then we might want to consider writing to file or emitting to console as we paginate over the API so that we don't blow up the memory usage on our machines. Some other advantage for streaming (as opposed to batch exporting) is that if we get some data corruption, we can also more easily start from the last good entry (rather than redownloading everything again).

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea! 👍🏻

}
72 changes: 72 additions & 0 deletions metrics/cmd/deployments_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package cmd

import (
"path/filepath"
"testing"

"github.com/mozilla-services/rapid-release-model/metrics/internal/config"
"github.com/mozilla-services/rapid-release-model/metrics/internal/github"
"github.com/mozilla-services/rapid-release-model/metrics/internal/test"
"github.com/shurcooL/githubv4"
)

func TestDeployments(t *testing.T) {
repo := &github.Repo{Owner: "hackebrot", Name: "turtle"}

env := map[string]string{
config.EnvKey("GITHUB", "REPO_OWNER"): "",
config.EnvKey("GITHUB", "REPO_NAME"): "",
}

tempDir := t.TempDir()

tests := []test.TestCase{{
Name: "deployments__repo_owner__required",
Args: []string{"github", "-n", repo.Name, "deployments"},
ErrContains: "Repo.Owner and Repo.Name are required. Set env vars or pass flags",
Env: env,
}, {
Name: "deployments__repo_name__required",
Args: []string{"github", "-o", repo.Owner, "deployments"},
ErrContains: "Repo.Owner and Repo.Name are required. Set env vars or pass flags",
Env: env,
}, {
Name: "deployments__default",
Args: []string{"github", "-o", repo.Owner, "-n", repo.Name, "deployments"},
WantFixture: test.NewFixture("deployments", "want__default.json"),
Env: env,
}, {
Name: "deployments__limit",
Args: []string{"github", "-o", repo.Owner, "-n", repo.Name, "deployments", "-l", "2"},
WantFixture: test.NewFixture("deployments", "want__limit.json"),
Env: env,
}, {
Name: "deployments__json",
Args: []string{"github", "-o", repo.Owner, "-n", repo.Name, "deployments", "-e", "json"},
WantFixture: test.NewFixture("deployments", "want__default.json"),
Env: env,
}, {
Name: "deployments__csv",
Args: []string{"github", "-o", repo.Owner, "-n", repo.Name, "deployments", "-e", "csv"},
WantFixture: test.NewFixture("deployments", "want__default.csv"),
Env: env,
}, {
Name: "deployments__filename",
Args: []string{"github", "-o", repo.Owner, "-n", repo.Name, "deployments", "-f", filepath.Join(tempDir, "r.json")},
WantFixture: test.NewFixture("deployments", "want__default.json"),
WantFile: filepath.Join(tempDir, "r.json"),
Env: env,
}, {
Name: "deployments__env__single",
Args: []string{"github", "-o", repo.Owner, "-n", repo.Name, "deployments", "--env", "prod"},
WantVariables: map[string]interface{}{"environments": []githubv4.String{githubv4.String("prod")}},
Env: env,
}, {
Name: "deployments__env__multiple",
Args: []string{"github", "-o", repo.Owner, "-n", repo.Name, "deployments", "--env", "prod", "--env", "hello"},
WantVariables: map[string]interface{}{"environments": []githubv4.String{githubv4.String("prod"), githubv4.String("hello")}},
Env: env,
}}

test.RunTests(t, newRootCmd, tests)
}
52 changes: 52 additions & 0 deletions metrics/cmd/fixtures/deployments/query.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
{
"Repository": {
"Name": "turtle",
"Owner": {
"Login": "hackebrot"
},
"Deployments": {
"PageInfo": {
"HasNextPage": true,
"EndCursor": "abc123"
},
"Nodes": [
{
"Description": "Deployment03",
"CreatedAt": "2022-05-02T20:25:05Z",
"UpdatedAt": "2022-05-02T20:25:05Z",
"OriginalEnvironment": "prod",
"LatestEnvironment": "prod",
"Task": "deploy",
"State": "ACTIVE",
"Commit": {
"AbbreviatedOid": "1abc111"
}
},
{
"Description": "Deployment03",
"CreatedAt": "2022-05-01T20:20:05Z",
"UpdatedAt": "2022-05-01T20:20:05Z",
"OriginalEnvironment": "stage",
"LatestEnvironment": "stage",
"Task": "deploy",
"State": "ACTIVE",
"Commit": {
"AbbreviatedOid": "1abc111"
}
},
{
"Description": "Deployment02",
"CreatedAt": "2022-04-01T20:25:05Z",
"UpdatedAt": "2022-04-01T20:25:05Z",
"OriginalEnvironment": "stage",
"LatestEnvironment": "stage",
"Task": "deploy",
"State": "INACTIVE",
"Commit": {
"AbbreviatedOid": "2abc111"
}
}
]
}
}
}
28 changes: 28 additions & 0 deletions metrics/cmd/fixtures/deployments/query_abc123.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"Repository": {
"Name": "turtle",
"Owner": {
"Login": "hackebrot"
},
"Deployments": {
"PageInfo": {
"HasNextPage": false,
"EndCursor": "abc456"
},
"Nodes": [
{
"Description": "Deployment01",
"CreatedAt": "2022-02-01T20:25:05Z",
"UpdatedAt": "2022-02-01T20:25:05Z",
"OriginalEnvironment": "hello",
"LatestEnvironment": "hello",
"Task": "deploy",
"State": "ACTIVE",
"Commit": {
"AbbreviatedOid": "3abc111"
}
}
]
}
}
}
5 changes: 5 additions & 0 deletions metrics/cmd/fixtures/deployments/want__default.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
description,createdAt,updatedAt,originalEnvironment,latestEnvironment,task,state,commitOid
Deployment03,2022-05-02T20:25:05Z,2022-05-02T20:25:05Z,prod,prod,deploy,ACTIVE,1abc111
Deployment03,2022-05-01T20:20:05Z,2022-05-01T20:20:05Z,stage,stage,deploy,ACTIVE,1abc111
Deployment02,2022-04-01T20:25:05Z,2022-04-01T20:25:05Z,stage,stage,deploy,INACTIVE,2abc111
Deployment01,2022-02-01T20:25:05Z,2022-02-01T20:25:05Z,hello,hello,deploy,ACTIVE,3abc111
50 changes: 50 additions & 0 deletions metrics/cmd/fixtures/deployments/want__default.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
[
{
"Description": "Deployment03",
"CreatedAt": "2022-05-02T20:25:05Z",
"UpdatedAt": "2022-05-02T20:25:05Z",
"OriginalEnvironment": "prod",
"LatestEnvironment": "prod",
"Task": "deploy",
"State": "ACTIVE",
"Commit": {
"AbbreviatedOid": "1abc111"
}
},
{
"Description": "Deployment03",
"CreatedAt": "2022-05-01T20:20:05Z",
"UpdatedAt": "2022-05-01T20:20:05Z",
"OriginalEnvironment": "stage",
"LatestEnvironment": "stage",
"Task": "deploy",
"State": "ACTIVE",
"Commit": {
"AbbreviatedOid": "1abc111"
}
},
{
"Description": "Deployment02",
"CreatedAt": "2022-04-01T20:25:05Z",
"UpdatedAt": "2022-04-01T20:25:05Z",
"OriginalEnvironment": "stage",
"LatestEnvironment": "stage",
"Task": "deploy",
"State": "INACTIVE",
"Commit": {
"AbbreviatedOid": "2abc111"
}
},
{
"Description": "Deployment01",
"CreatedAt": "2022-02-01T20:25:05Z",
"UpdatedAt": "2022-02-01T20:25:05Z",
"OriginalEnvironment": "hello",
"LatestEnvironment": "hello",
"Task": "deploy",
"State": "ACTIVE",
"Commit": {
"AbbreviatedOid": "3abc111"
}
}
]
26 changes: 26 additions & 0 deletions metrics/cmd/fixtures/deployments/want__limit.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[
{
"Description": "Deployment03",
"CreatedAt": "2022-05-02T20:25:05Z",
"UpdatedAt": "2022-05-02T20:25:05Z",
"OriginalEnvironment": "prod",
"LatestEnvironment": "prod",
"Task": "deploy",
"State": "ACTIVE",
"Commit": {
"AbbreviatedOid": "1abc111"
}
},
{
"Description": "Deployment03",
"CreatedAt": "2022-05-01T20:20:05Z",
"UpdatedAt": "2022-05-01T20:20:05Z",
"OriginalEnvironment": "stage",
"LatestEnvironment": "stage",
"Task": "deploy",
"State": "ACTIVE",
"Commit": {
"AbbreviatedOid": "1abc111"
}
}
]
43 changes: 43 additions & 0 deletions metrics/cmd/fixtures/prs/query.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
{
"Repository": {
"Name": "turtle",
"Owner": {
"Login": "hackebrot"
},
"PullRequests": {
"PageInfo": {
"HasNextPage": true,
"EndCursor": "abc"
},
"Nodes": [
{
"ID": "PULLREQUEST1",
"Number": 1,
"Title": "Set up CI/CD workflow 📦",
"CreatedAt": "2023-09-08T16:33:20Z",
"UpdatedAt": "2023-09-10T07:24:20Z",
"ClosedAt": "2023-09-10T07:24:17Z",
"MergedAt": "2023-09-10T07:24:16Z"
},
{
"ID": "PULLREQUEST2",
"Number": 2,
"Title": "Refactor test framework 🤖",
"CreatedAt": "2023-09-08T09:18:42Z",
"UpdatedAt": "2023-09-08T09:40:23Z",
"ClosedAt": "2023-09-08T09:40:20Z",
"MergedAt": "2023-09-08T09:40:19Z"
},
{
"ID": "PULLREQUEST3",
"Number": 3,
"Title": "Fetch deployment metrics 🚀",
"CreatedAt": "2023-10-08T08:09:38Z",
"UpdatedAt": "2023-10-08T08:58:13Z",
"ClosedAt": "2023-11-08T08:58:10Z",
"MergedAt": "2023-11-08T08:58:10Z"
}
]
}
}
}
25 changes: 25 additions & 0 deletions metrics/cmd/fixtures/prs/query_abc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"Repository": {
"Name": "turtle",
"Owner": {
"Login": "hackebrot"
},
"PullRequests": {
"PageInfo": {
"HasNextPage": false,
"EndCursor": "hello"
},
"Nodes": [
{
"ID": "PULLREQUEST4",
"Number": 4,
"Title": "Updating Docker image 📦",
"CreatedAt": "2023-12-08T16:33:20Z",
"UpdatedAt": "2023-12-10T07:24:20Z",
"ClosedAt": "2023-12-10T07:24:17Z",
"MergedAt": "2023-12-10T07:24:16Z"
}
]
}
}
}
5 changes: 5 additions & 0 deletions metrics/cmd/fixtures/prs/want__default.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
id,number,title,createdAt,updatedAt,closedAt,mergedAt
PULLREQUEST1,1,Set up CI/CD workflow 📦,2023-09-08T16:33:20Z,2023-09-10T07:24:20Z,2023-09-10T07:24:17Z,2023-09-10T07:24:16Z
PULLREQUEST2,2,Refactor test framework 🤖,2023-09-08T09:18:42Z,2023-09-08T09:40:23Z,2023-09-08T09:40:20Z,2023-09-08T09:40:19Z
PULLREQUEST3,3,Fetch deployment metrics 🚀,2023-10-08T08:09:38Z,2023-10-08T08:58:13Z,2023-11-08T08:58:10Z,2023-11-08T08:58:10Z
PULLREQUEST4,4,Updating Docker image 📦,2023-12-08T16:33:20Z,2023-12-10T07:24:20Z,2023-12-10T07:24:17Z,2023-12-10T07:24:16Z
Loading