Skip to content
Snippets Groups Projects
Commit 5ae9a44a authored by Jacopo's avatar Jacopo
Browse files

Add project http fetch statistics API

The API get projects/:id/traffic/fetches allows user with write
access to the repository to get the number of clones for the
last 30 days.
parent 6fa88ed7
No related branches found
No related tags found
No related merge requests found
Showing
with 247 additions and 3 deletions
Loading
Loading
@@ -20,6 +20,8 @@ class Projects::GitHttpController < Projects::GitHttpClientController
 
# POST /foo/bar.git/git-upload-pack (git pull)
def git_upload_pack
enqueue_fetch_statistics_update
render_ok
end
 
Loading
Loading
@@ -67,6 +69,13 @@ class Projects::GitHttpController < Projects::GitHttpClientController
render plain: exception.message, status: :service_unavailable
end
 
def enqueue_fetch_statistics_update
return if wiki?
return unless project.daily_statistics_enabled?
ProjectDailyStatisticsWorker.perform_async(project.id)
end
def access
@access ||= access_klass.new(access_actor, project,
'http', authentication_abilities: authentication_abilities,
Loading
Loading
# frozen_string_literal: true
module Projects
class DailyStatisticsFinder
attr_reader :project
def initialize(project)
@project = project
end
def fetches
ProjectDailyStatistic.of_project(project)
.of_last_30_days
.sorted_by_date_desc
end
def total_fetch_count
fetches.sum_fetch_count
end
end
end
Loading
Loading
@@ -629,6 +629,10 @@ class Project < ActiveRecord::Base
auto_devops&.enabled.nil? && !(Gitlab::CurrentSettings.auto_devops_enabled? || Feature.enabled?(:force_autodevops_on_by_default, self))
end
 
def daily_statistics_enabled?
Feature.enabled?(:project_daily_statistics, self, default_enabled: true)
end
def empty_repo?
repository.empty?
end
Loading
Loading
# frozen_string_literal: true
class ProjectDailyStatistic < ActiveRecord::Base
belongs_to :project
scope :of_project, -> (project) { where(project: project) }
scope :of_last_30_days, -> { where('date >= ?', 29.days.ago.utc.to_date) }
scope :sorted_by_date_desc, -> { order(project_id: :desc, date: :desc) }
scope :sum_fetch_count, -> { sum(:fetch_count) }
end
Loading
Loading
@@ -278,6 +278,7 @@ class ProjectPolicy < BasePolicy
enable :admin_cluster
enable :create_environment_terminal
enable :destroy_release
enable :daily_statistics
end
 
rule { (mirror_available & can?(:admin_project)) | admin }.enable :admin_remote_mirror
Loading
Loading
# frozen_string_literal: true
module Projects
class FetchStatisticsIncrementService
attr_reader :project
def initialize(project)
@project = project
end
def execute
increment_fetch_count_sql = <<~SQL
INSERT INTO #{table_name} (project_id, date, fetch_count)
VALUES (#{project.id}, '#{Date.today}', 1)
SQL
increment_fetch_count_sql += if Gitlab::Database.postgresql?
"ON CONFLICT (project_id, date) DO UPDATE SET fetch_count = #{table_name}.fetch_count + 1"
else
"ON DUPLICATE KEY UPDATE fetch_count = #{table_name}.fetch_count + 1"
end
ActiveRecord::Base.connection.execute(increment_fetch_count_sql)
end
private
def table_name
ProjectDailyStatistic.table_name
end
end
end
Loading
Loading
@@ -146,3 +146,4 @@
- repository_cleanup
- delete_stored_files
- import_issues_csv
- project_daily_statistics
# frozen_string_literal: true
class ProjectDailyStatisticsWorker
include ApplicationWorker
def perform(project_id)
project = Project.find_by_id(project_id)
return unless project&.repository&.exists?
Projects::FetchStatisticsIncrementService.new(project).execute
end
end
---
title: Add project fetch statistics
merge_request: 23596
author: Jacopo Beschi @jacopo-beschi
type: added
Loading
Loading
@@ -85,5 +85,6 @@
- [repository_cleanup, 1]
- [delete_stored_files, 1]
- [remote_mirror_notification, 2]
- [project_daily_statistics, 1]
- [import_issues_csv, 2]
- [chat_notification, 2]
# frozen_string_literal: true
class CreateProjectDailyStatistics < ActiveRecord::Migration[5.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table :project_daily_statistics, id: :bigserial do |t|
t.integer :project_id, null: false
t.integer :fetch_count, null: false
t.date :date
t.index [:project_id, :date], unique: true, order: { date: :desc }
t.foreign_key :projects, on_delete: :cascade
end
end
end
Loading
Loading
@@ -1578,6 +1578,13 @@ ActiveRecord::Schema.define(version: 20190204115450) do
t.index ["project_id", "key"], name: "index_project_custom_attributes_on_project_id_and_key", unique: true, using: :btree
end
 
create_table "project_daily_statistics", id: :bigserial, force: :cascade do |t|
t.integer "project_id", null: false
t.integer "fetch_count", null: false
t.date "date"
t.index ["project_id", "date"], name: "index_project_daily_statistics_on_project_id_and_date", unique: true, order: { date: :desc }, using: :btree
end
create_table "project_deploy_tokens", force: :cascade do |t|
t.integer "project_id", null: false
t.integer "deploy_token_id", null: false
Loading
Loading
@@ -2461,6 +2468,7 @@ ActiveRecord::Schema.define(version: 20190204115450) do
add_foreign_key "project_auto_devops", "projects", on_delete: :cascade
add_foreign_key "project_ci_cd_settings", "projects", name: "fk_24c15d2f2e", on_delete: :cascade
add_foreign_key "project_custom_attributes", "projects", on_delete: :cascade
add_foreign_key "project_daily_statistics", "projects", on_delete: :cascade
add_foreign_key "project_deploy_tokens", "deploy_tokens", on_delete: :cascade
add_foreign_key "project_deploy_tokens", "projects", on_delete: :cascade
add_foreign_key "project_error_tracking_settings", "projects", on_delete: :cascade
Loading
Loading
# Project statistics API
Every API call to [project](../user/project/index.md) statistics must be authenticated.
## Get the statistics of the last 30 days
Retrieving the statistics requires write access to the repository.
Currently only HTTP fetches statistics are returned.
Fetches statistics includes both clones and pulls count and are HTTP only, SSH fetches are not included.
```
GET /projects/:id/statistics
```
| Attribute | Type | Required | Description |
| ---------- | ------ | -------- | ----------- |
| `id ` | integer / string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) |
Example response:
```json
{
"fetches": {
"total": 50,
"days": [
{
"count": 10,
"date": "2018-01-10"
},
{
"count": 10,
"date": "2018-01-09"
},
{
"count": 10,
"date": "2018-01-08"
},
{
"count": 10,
"date": "2018-01-07"
},
{
"count": 10,
"date": "2018-01-06"
}
]
}
}
```
Loading
Loading
@@ -111,6 +111,7 @@ The following table depicts the various user permission levels in a project.
| Force push to protected branches [^4] | | | | | |
| Remove protected branches [^4] | | | | | |
| View project Audit Events | | | | ✓ | ✓ |
| View project statistics | | | | ✓ | ✓ |
 
## Project features permissions
 
Loading
Loading
Loading
Loading
@@ -8,7 +8,7 @@ Your projects can be [available](../../public_access/public_access.md)
publicly, internally, or privately, at your choice. GitLab does not limit
the number of private projects you create.
 
## Project's features
## Project features
 
When you create a project in GitLab, you'll have access to a large number of
[features](https://about.gitlab.com/features/):
Loading
Loading
@@ -82,7 +82,7 @@ your code blocks, overriding GitLab's default choice of language.
the source, build output, and other metadata or artifacts
associated with a released version of your code.
 
### Project's integrations
### Project integrations
 
[Integrate your project](integrations/index.md) with Jira, Mattermost,
Kubernetes, Slack, and a lot more.
Loading
Loading
@@ -116,7 +116,7 @@ Read through the documentation on [project settings](settings/index.md).
- [Export a project from GitLab](settings/import_export.md#exporting-a-project-and-its-data)
- [Importing and exporting projects between GitLab instances](settings/import_export.md)
 
## Project's members
## Project members
 
Learn how to [add members to your projects](members/index.md).
 
Loading
Loading
@@ -170,3 +170,23 @@ password <personal_access_token>
 
To quickly access a project from the GitLab UI using the project ID,
visit the `/projects/:id` URL in your browser or other tool accessing the project.
## Project APIs
There are numerous [APIs](../../api/README.md) to use with your projects:
- [Badges](../../api/project_badges.md)
- [Clusters](../../api/project_clusters.md)
- [Discussions](../../api/discussions.md)
- [General](../../api/projects.md)
- [Import/export](../../api/project_import_export.md)
- [Issue Board](../../api/boards.md)
- [Labels](../../api/labels.md)
- [Markdown](../../api/markdown.md)
- [Merge Requests](../../api/merge_requests.md)
- [Milestones](../../api/milestones.md)
- [Services](../../api/services.md)
- [Snippets](../../api/project_snippets.md)
- [Templates](../../api/project_templates.md)
- [Traffic](../../api/project_statistics.md)
- [Variables](../../api/project_level_variables.md)
Loading
Loading
@@ -141,6 +141,7 @@ module API
mount ::API::Projects
mount ::API::ProjectSnapshots
mount ::API::ProjectSnippets
mount ::API::ProjectStatistics
mount ::API::ProjectTemplates
mount ::API::ProtectedBranches
mount ::API::ProtectedTags
Loading
Loading
Loading
Loading
@@ -300,6 +300,18 @@ module API
expose :build_artifacts_size, as: :job_artifacts_size
end
 
class ProjectDailyFetches < Grape::Entity
expose :fetch_count, as: :count
expose :date
end
class ProjectDailyStatistics < Grape::Entity
expose :fetches do
expose :total_fetch_count, as: :total
expose :fetches, as: :days, using: ProjectDailyFetches
end
end
class Member < Grape::Entity
expose :user, merge: true, using: UserBasic
expose :access_level
Loading
Loading
# frozen_string_literal: true
module API
class ProjectStatistics < Grape::API
before do
authenticate!
not_found! unless user_project.daily_statistics_enabled?
authorize! :daily_statistics, user_project
end
params do
requires :id, type: String, desc: 'The ID of a project'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Get the list of project fetch statistics for the last 30 days'
get ":id/statistics" do
statistic_finder = ::Projects::DailyStatisticsFinder.new(user_project)
present statistic_finder, with: Entities::ProjectDailyStatistics
end
end
end
end
# frozen_string_literal: true
FactoryBot.define do
factory :project_daily_statistic do
project
fetch_count 1
end
end
# frozen_string_literal: true
require 'spec_helper'
describe ProjectDailyStatistic do
it { is_expected.to belong_to(:project) }
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment