-
Notifications
You must be signed in to change notification settings - Fork 3
Dashboard stats #59
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
Draft
pasibun
wants to merge
16
commits into
main
Choose a base branch
from
dashboard-stats
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Dashboard stats #59
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
14844ed
wip
dvh d69f346
Remove ListLintResults functionality from API and related tests
pasibun 3bd7fcf
Add statistics endpoints for API adoption and compliance tracking
pasibun 8945aa9
Update pkg/api_client/services/adoption_service.go
pasibun 1b7de02
Fix parseDateRange to set end date to the end of the day correctly
pasibun 9656aad
Add unit tests for adoption service and repository functionalities
pasibun 84d6902
Merge branch 'main' into dashboard-stats
pasibun 2564f42
Add security requirements to adoption summary and rules endpoints
pasibun f1e5014
Update pkg/api_client/repositories/adoption_repository.go
pasibun 43878c6
Add security definitions to API endpoints and improve date range vali…
pasibun 8310cec
Update pkg/api_client/repositories/adoption_repository.go
pasibun ceca733
Remove merge conflict markers from adoption_repository.go
pasibun 1591fe0
Remove ADR adoption plan documentation
pasibun 4b1577b
Enhance date parsing to support Dutch date format and improve error m…
pasibun f58fa54
Update adoption repository to use ruleset_version instead of adr_vers…
pasibun 329b4ea
Add adoption-related schemas to OpenAPI specification
pasibun File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| package handler | ||
|
|
||
| import ( | ||
| "github.com/developer-overheid-nl/don-api-register/pkg/api_client/helpers/util" | ||
| "github.com/developer-overheid-nl/don-api-register/pkg/api_client/models" | ||
| "github.com/developer-overheid-nl/don-api-register/pkg/api_client/services" | ||
| "github.com/gin-gonic/gin" | ||
| ) | ||
|
|
||
| type StatisticsController struct { | ||
| Service *services.AdoptionService | ||
| } | ||
|
|
||
| func NewStatisticsController(s *services.AdoptionService) *StatisticsController { | ||
| return &StatisticsController{Service: s} | ||
| } | ||
|
|
||
| func (c *StatisticsController) GetSummary(ctx *gin.Context, p *models.AdoptionBaseParams) (*models.AdoptionSummary, error) { | ||
| return c.Service.GetSummary(ctx.Request.Context(), p) | ||
| } | ||
|
|
||
| func (c *StatisticsController) GetRules(ctx *gin.Context, p *models.AdoptionRulesParams) (*models.AdoptionRules, error) { | ||
| return c.Service.GetRules(ctx.Request.Context(), p) | ||
| } | ||
|
|
||
| func (c *StatisticsController) GetTimeline(ctx *gin.Context, p *models.AdoptionTimelineParams) (*models.AdoptionTimeline, error) { | ||
| return c.Service.GetTimeline(ctx.Request.Context(), p) | ||
| } | ||
|
|
||
| func (c *StatisticsController) GetApis(ctx *gin.Context, p *models.AdoptionApisParams) (*models.AdoptionApis, error) { | ||
| result, pagination, err := c.Service.GetApis(ctx.Request.Context(), p) | ||
| if err != nil { | ||
| return nil, err | ||
| } | ||
| util.SetPaginationHeaders(ctx.Request, ctx.Header, *pagination) | ||
| return result, nil | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| package handler | ||
|
|
||
| import ( | ||
| "context" | ||
| "errors" | ||
| "net/http" | ||
| "net/http/httptest" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/developer-overheid-nl/don-api-register/pkg/api_client/models" | ||
| "github.com/developer-overheid-nl/don-api-register/pkg/api_client/repositories" | ||
| "github.com/developer-overheid-nl/don-api-register/pkg/api_client/services" | ||
| "github.com/gin-gonic/gin" | ||
| "github.com/lib/pq" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| type statisticsAdoptionRepoStub struct { | ||
| getSummaryFunc func(ctx context.Context, params repositories.AdoptionQueryParams) (repositories.SummaryResult, int, error) | ||
| getApisFunc func(ctx context.Context, params repositories.ApisQueryParams) ([]repositories.ApiRow, int, error) | ||
| } | ||
|
|
||
| func (s *statisticsAdoptionRepoStub) GetSummary(ctx context.Context, params repositories.AdoptionQueryParams) (repositories.SummaryResult, int, error) { | ||
| if s.getSummaryFunc != nil { | ||
| return s.getSummaryFunc(ctx, params) | ||
| } | ||
| return repositories.SummaryResult{}, 0, nil | ||
| } | ||
|
|
||
| func (s *statisticsAdoptionRepoStub) GetRules(ctx context.Context, params repositories.AdoptionQueryParams) ([]repositories.RuleRow, int, error) { | ||
| return nil, 0, nil | ||
| } | ||
|
|
||
| func (s *statisticsAdoptionRepoStub) GetTimeline(ctx context.Context, params repositories.TimelineQueryParams) ([]repositories.TimelineRow, error) { | ||
| return nil, nil | ||
| } | ||
|
|
||
| func (s *statisticsAdoptionRepoStub) GetApis(ctx context.Context, params repositories.ApisQueryParams) ([]repositories.ApiRow, int, error) { | ||
| if s.getApisFunc != nil { | ||
| return s.getApisFunc(ctx, params) | ||
| } | ||
| return nil, 0, nil | ||
| } | ||
|
|
||
| func TestStatisticsControllerGetSummary_Success(t *testing.T) { | ||
| gin.SetMode(gin.TestMode) | ||
| repo := &statisticsAdoptionRepoStub{ | ||
| getSummaryFunc: func(ctx context.Context, params repositories.AdoptionQueryParams) (repositories.SummaryResult, int, error) { | ||
| return repositories.SummaryResult{ | ||
| TotalApis: 5, | ||
| CompliantApis: 4, | ||
| AdoptionRate: 80, | ||
| }, 11, nil | ||
| }, | ||
| } | ||
| ctrl := NewStatisticsController(services.NewAdoptionService(repo)) | ||
|
|
||
| w := httptest.NewRecorder() | ||
| ctx, _ := gin.CreateTestContext(w) | ||
| ctx.Request = httptest.NewRequest(http.MethodGet, "/v1/statistics/summary", nil) | ||
|
|
||
| out, err := ctrl.GetSummary(ctx, &models.AdoptionBaseParams{ | ||
| AdrVersion: "ADR-2.0", | ||
| StartDate: "2026-01-01", | ||
| EndDate: "2026-01-31", | ||
| }) | ||
| require.NoError(t, err) | ||
| require.NotNil(t, out) | ||
| assert.Equal(t, 5, out.TotalApis) | ||
| assert.Equal(t, 4, out.CompliantApis) | ||
| assert.Equal(t, 80.0, out.OverallAdoptionRate) | ||
| assert.Equal(t, 11, out.TotalLintRuns) | ||
| } | ||
|
|
||
| func TestStatisticsControllerGetApis_SetsPaginationHeaders(t *testing.T) { | ||
| gin.SetMode(gin.TestMode) | ||
| repo := &statisticsAdoptionRepoStub{ | ||
| getApisFunc: func(ctx context.Context, params repositories.ApisQueryParams) ([]repositories.ApiRow, int, error) { | ||
| return []repositories.ApiRow{ | ||
| { | ||
| ApiId: "api-1", | ||
| ApiTitle: "API 1", | ||
| Organisation: "Org", | ||
| IsCompliant: true, | ||
| TotalFailures: 0, | ||
| TotalWarnings: 1, | ||
| ViolatedRules: pq.StringArray{}, | ||
| LastLintDate: time.Date(2026, 1, 10, 9, 0, 0, 0, time.UTC), | ||
| }, | ||
| }, 3, nil | ||
| }, | ||
| } | ||
| ctrl := NewStatisticsController(services.NewAdoptionService(repo)) | ||
|
|
||
| w := httptest.NewRecorder() | ||
| ctx, _ := gin.CreateTestContext(w) | ||
| req := httptest.NewRequest(http.MethodGet, "http://api.example.test/v1/statistics/apis?page=2&perPage=1", nil) | ||
| req.Host = "api.example.test" | ||
| ctx.Request = req | ||
|
|
||
| out, err := ctrl.GetApis(ctx, &models.AdoptionApisParams{ | ||
| AdoptionBaseParams: models.AdoptionBaseParams{AdrVersion: "ADR-2.0", StartDate: "2026-01-01", EndDate: "2026-01-31"}, | ||
| Page: 2, | ||
| PerPage: 1, | ||
| }) | ||
| require.NoError(t, err) | ||
| require.NotNil(t, out) | ||
| require.Len(t, out.Apis, 1) | ||
|
|
||
| assert.Equal(t, "3", w.Header().Get("Total-Count")) | ||
| assert.Equal(t, "3", w.Header().Get("Total-Pages")) | ||
| assert.Equal(t, "1", w.Header().Get("Per-Page")) | ||
| assert.Equal(t, "2", w.Header().Get("Current-Page")) | ||
| assert.Contains(t, w.Header().Get("Link"), `rel="prev"`) | ||
| assert.Contains(t, w.Header().Get("Link"), `rel="next"`) | ||
| assert.Contains(t, w.Header().Get("Link"), `page=2`) | ||
| } | ||
|
|
||
| func TestStatisticsControllerGetApis_PropagatesErrorWithoutHeaders(t *testing.T) { | ||
| gin.SetMode(gin.TestMode) | ||
| repo := &statisticsAdoptionRepoStub{ | ||
| getApisFunc: func(ctx context.Context, params repositories.ApisQueryParams) ([]repositories.ApiRow, int, error) { | ||
| return nil, 0, errors.New("query failed") | ||
| }, | ||
| } | ||
| ctrl := NewStatisticsController(services.NewAdoptionService(repo)) | ||
|
|
||
| w := httptest.NewRecorder() | ||
| ctx, _ := gin.CreateTestContext(w) | ||
| ctx.Request = httptest.NewRequest(http.MethodGet, "/v1/statistics/apis", nil) | ||
|
|
||
| out, err := ctrl.GetApis(ctx, &models.AdoptionApisParams{ | ||
| AdoptionBaseParams: models.AdoptionBaseParams{AdrVersion: "ADR-2.0", StartDate: "2026-01-01", EndDate: "2026-01-31"}, | ||
| }) | ||
|
|
||
| require.Error(t, err) | ||
| assert.Nil(t, out) | ||
| assert.Contains(t, err.Error(), "query failed") | ||
| assert.Empty(t, w.Header().Get("Total-Count")) | ||
| assert.Empty(t, w.Header().Get("Link")) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| package models | ||
|
|
||
| import "time" | ||
|
|
||
| type Period struct { | ||
| Start string `json:"start"` | ||
| End string `json:"end"` | ||
| } | ||
|
|
||
| // Summary endpoint | ||
|
|
||
| type AdoptionSummary struct { | ||
| AdrVersion string `json:"adrVersion"` | ||
| Period Period `json:"period"` | ||
| TotalApis int `json:"totalApis"` | ||
| CompliantApis int `json:"compliantApis"` | ||
| OverallAdoptionRate float64 `json:"overallAdoptionRate"` | ||
| TotalLintRuns int `json:"totalLintRuns"` | ||
| } | ||
|
|
||
| // Rules endpoint | ||
|
|
||
| type AdoptionRules struct { | ||
| AdrVersion string `json:"adrVersion"` | ||
| Period Period `json:"period"` | ||
| TotalApis int `json:"totalApis"` | ||
| Rules []RuleAdoption `json:"rules"` | ||
| } | ||
|
|
||
| type RuleAdoption struct { | ||
| Code string `json:"code"` | ||
| Severity string `json:"severity"` | ||
| ViolatingApis int `json:"violatingApis"` | ||
| CompliantApis int `json:"compliantApis"` | ||
| AdoptionRate float64 `json:"adoptionRate"` | ||
| } | ||
|
|
||
| // Timeline endpoint | ||
|
|
||
| type AdoptionTimeline struct { | ||
| AdrVersion string `json:"adrVersion"` | ||
| Granularity string `json:"granularity"` | ||
| Series []TimelineSeries `json:"series"` | ||
| } | ||
|
|
||
| type TimelineSeries struct { | ||
| Type string `json:"type"` | ||
| RuleCode string `json:"ruleCode,omitempty"` | ||
| DataPoints []TimelinePoint `json:"dataPoints"` | ||
| } | ||
|
|
||
| type TimelinePoint struct { | ||
| Period string `json:"period"` | ||
| TotalApis int `json:"totalApis"` | ||
| CompliantApis int `json:"compliantApis"` | ||
| AdoptionRate float64 `json:"adoptionRate"` | ||
| } | ||
|
|
||
| // APIs endpoint | ||
|
|
||
| type AdoptionApis struct { | ||
| AdrVersion string `json:"adrVersion"` | ||
| Period Period `json:"period"` | ||
| Apis []ApiAdoption `json:"apis"` | ||
| } | ||
|
|
||
| type ApiAdoption struct { | ||
| ApiId string `json:"apiId"` | ||
| ApiTitle string `json:"apiTitle"` | ||
| Organisation string `json:"organisation"` | ||
| IsCompliant bool `json:"isCompliant"` | ||
| TotalViolations int `json:"totalViolations"` | ||
| TotalWarnings int `json:"totalWarnings"` | ||
| ViolatedRules []string `json:"violatedRules"` | ||
| LastLintDate time.Time `json:"lastLintDate"` | ||
| } | ||
|
|
||
| // Query parameter structs | ||
|
|
||
| type AdoptionBaseParams struct { | ||
| AdrVersion string `query:"adrVersion" binding:"required"` | ||
| StartDate string `query:"startDate" binding:"required"` | ||
| EndDate string `query:"endDate" binding:"required"` | ||
| ApiIds *string `query:"apiIds"` | ||
| Organisation *string `query:"organisation"` | ||
| } | ||
|
|
||
| type AdoptionRulesParams struct { | ||
| AdoptionBaseParams | ||
| RuleCodes *string `query:"ruleCodes"` | ||
| Severity *string `query:"severity"` | ||
| } | ||
|
|
||
| type AdoptionTimelineParams struct { | ||
| AdoptionBaseParams | ||
| Granularity string `query:"granularity"` | ||
| RuleCodes *string `query:"ruleCodes"` | ||
| } | ||
|
|
||
| type AdoptionApisParams struct { | ||
| AdoptionBaseParams | ||
| Compliant *bool `query:"compliant"` | ||
| RuleCodes *string `query:"ruleCodes"` | ||
| Page int `query:"page"` | ||
| PerPage int `query:"perPage"` | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.