Commit 21db9a55 authored by Felipe Artur's avatar Felipe Artur Committed by Sean McGivern

Track JIRA dev panel integration

parent defefe4f
......@@ -2215,6 +2215,14 @@ ActiveRecord::Schema.define(version: 20190124200344) do
t.string "encrypted_token_iv"
end
create_table "project_feature_usages", primary_key: "project_id", id: :integer, force: :cascade do |t|
t.datetime "jira_dvcs_cloud_last_sync_at"
t.datetime "jira_dvcs_server_last_sync_at"
t.index ["jira_dvcs_cloud_last_sync_at", "project_id"], name: "idx_proj_feat_usg_on_jira_dvcs_cloud_last_sync_at_and_proj_id", where: "(jira_dvcs_cloud_last_sync_at IS NOT NULL)", using: :btree
t.index ["jira_dvcs_server_last_sync_at", "project_id"], name: "idx_proj_feat_usg_on_jira_dvcs_server_last_sync_at_and_proj_id", where: "(jira_dvcs_server_last_sync_at IS NOT NULL)", using: :btree
t.index ["project_id"], name: "index_project_feature_usages_on_project_id", using: :btree
end
create_table "project_features", force: :cascade do |t|
t.integer "project_id", null: false
t.integer "merge_requests_access_level"
......@@ -3462,6 +3470,7 @@ ActiveRecord::Schema.define(version: 20190124200344) do
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
add_foreign_key "project_feature_usages", "projects", on_delete: :cascade
add_foreign_key "project_features", "projects", name: "fk_18513d9b92", on_delete: :cascade
add_foreign_key "project_group_links", "projects", name: "fk_daa8cee94c", on_delete: :cascade
add_foreign_key "project_import_data", "projects", name: "fk_ffb9ee3a10", on_delete: :cascade
......
......@@ -40,6 +40,7 @@ module EE
has_one :github_service
has_one :gitlab_slack_application_service
has_one :tracing_setting, class_name: 'ProjectTracingSetting'
has_one :feature_usage, class_name: 'ProjectFeatureUsage'
has_many :reviews, inverse_of: :project
has_many :approvers, as: :target, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
......@@ -99,6 +100,8 @@ module EE
:ever_updated_successfully?, :hard_failed?,
to: :import_state, prefix: :mirror, allow_nil: true
delegate :log_jira_dvcs_integration_usage, :jira_dvcs_server_last_sync_at, :jira_dvcs_cloud_last_sync_at, to: :feature_usage
validates :repository_size_limit,
numericality: { only_integer: true, greater_than_or_equal_to: 0, allow_nil: true }
......@@ -504,6 +507,10 @@ module EE
geo_primary_http_url_to_repo(self)
end
def feature_usage
super.presence || build_feature_usage
end
private
def set_override_pull_mirror_available
......
# frozen_string_literal: true
class ProjectFeatureUsage < ActiveRecord::Base
self.primary_key = :project_id
JIRA_DVCS_CLOUD_FIELD = 'jira_dvcs_cloud_last_sync_at'.freeze
JIRA_DVCS_SERVER_FIELD = 'jira_dvcs_server_last_sync_at'.freeze
belongs_to :project
validates :project, presence: true
scope :with_jira_dvcs_integration_enabled, -> (cloud: true) do
where.not(jira_dvcs_integration_field(cloud: cloud) => nil)
end
class << self
def jira_dvcs_integration_field(cloud: true)
cloud ? JIRA_DVCS_CLOUD_FIELD : JIRA_DVCS_SERVER_FIELD
end
end
def log_jira_dvcs_integration_usage(cloud: true)
transaction(requires_new: true) do
save unless persisted?
touch(self.class.jira_dvcs_integration_field(cloud: cloud))
end
rescue ActiveRecord::RecordNotUnique
reload
retry
end
end
---
title: Gather JIRA DVCS integration usage data
merge_request: 8949
author:
type: other
# frozen_string_literal: true
class CreateProjectFeatureUsage < ActiveRecord::Migration[5.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table :project_feature_usages, id: false, primary_key: :project_id do |t|
t.references :project,
foreign_key: { on_delete: :cascade },
null: false,
primary_key: true
t.timestamp :jira_dvcs_cloud_last_sync_at
t.timestamp :jira_dvcs_server_last_sync_at
t.index [:jira_dvcs_cloud_last_sync_at, :project_id], name: "idx_proj_feat_usg_on_jira_dvcs_cloud_last_sync_at_and_proj_id", where: "(jira_dvcs_cloud_last_sync_at IS NOT NULL)"
t.index [:jira_dvcs_server_last_sync_at, :project_id], name: "idx_proj_feat_usg_on_jira_dvcs_server_last_sync_at_and_proj_id", where: "(jira_dvcs_server_last_sync_at IS NOT NULL)"
end
end
end
......@@ -13,6 +13,11 @@ module API
NAMESPACE_ENDPOINT_REQUIREMENTS = { namespace: NO_SLASH_URL_PART_REGEX }.freeze
PROJECT_ENDPOINT_REQUIREMENTS = NAMESPACE_ENDPOINT_REQUIREMENTS.merge(project: NO_SLASH_URL_PART_REGEX).freeze
# Used to differentiate JIRA cloud requests from JIRA server requests
# JIRA cloud user agent format: JIRA DVCS Connector Vertigo/version
# JIRA server user agent format: JIRA DVCS Connector/version
JIRA_DVCS_CLOUD_USER_AGENT = 'JIRA DVCS Connector Vertigo'.freeze
include PaginationParams
before do
......@@ -30,6 +35,18 @@ module API
not_found! unless Gitlab::Jira::Middleware.jira_dvcs_connector?(request.env)
end
def update_project_feature_usage_for(project)
# Prevent errors on GitLab Geo not allowing
# UPDATE statements to happen in GET requests.
return if Gitlab::Database.read_only?
project.log_jira_dvcs_integration_usage(cloud: jira_cloud?)
end
def jira_cloud?
request.env['HTTP_USER_AGENT'].include?(JIRA_DVCS_CLOUD_USER_AGENT)
end
def find_project_with_access(params)
project = find_project!(
::Gitlab::Jira::Dvcs.restore_full_path(params.slice(:namespace, :project).symbolize_keys)
......@@ -162,6 +179,8 @@ module API
get ':namespace/:project/branches', requirements: PROJECT_ENDPOINT_REQUIREMENTS do
user_project = find_project_with_access(params)
update_project_feature_usage_for(user_project)
branches = ::Kaminari.paginate_array(user_project.repository.branches.sort_by(&:name))
present paginate(branches), with: ::API::Github::Entities::Branch, project: user_project
......
......@@ -113,6 +113,14 @@ module EE
usage_data
end
override :jira_usage
def jira_usage
super.merge(
projects_jira_dvcs_cloud_active: count(ProjectFeatureUsage.with_jira_dvcs_integration_enabled),
projects_jira_dvcs_server_active: count(ProjectFeatureUsage.with_jira_dvcs_integration_enabled(cloud: false))
)
end
def epics_deepest_relationship_level
{ epics_deepest_relationship_level: ::Epic.deepest_relationship_level }
end
......
......@@ -51,6 +51,7 @@ project:
- jenkins_service
- jenkins_deprecated_service
- index_status
- feature_usage
- approval_rules
- approvers
- pages_domains
......
......@@ -67,6 +67,8 @@ describe Gitlab::UsageData do
projects_with_prometheus_alerts
projects_with_packages
projects_with_tracing_enabled
projects_jira_dvcs_cloud_active
projects_jira_dvcs_server_active
))
expect(count_data[:projects_with_prometheus_alerts]).to eq(2)
......
# frozen_string_literal: true
require 'rails_helper'
describe ProjectFeatureUsage, type: :model do
describe '.jira_dvcs_integrations_enabled_count' do
it 'returns count of projects with JIRA DVCS cloud enabled' do
create(:project).feature_usage.log_jira_dvcs_integration_usage
create(:project).feature_usage.log_jira_dvcs_integration_usage
expect(described_class.with_jira_dvcs_integration_enabled.count).to eq(2)
end
it 'returns count of projects with JIRA DVCS server enabled' do
create(:project).feature_usage.log_jira_dvcs_integration_usage(cloud: false)
create(:project).feature_usage.log_jira_dvcs_integration_usage(cloud: false)
expect(described_class.with_jira_dvcs_integration_enabled(cloud: false).count).to eq(2)
end
end
describe '#log_jira_dvcs_integration_usage' do
let(:project) { create(:project) }
subject { project.feature_usage }
it 'logs JIRA DVCS cloud last sync' do
Timecop.freeze do
subject.log_jira_dvcs_integration_usage
expect(subject.jira_dvcs_server_last_sync_at).to be_nil
expect(subject.jira_dvcs_cloud_last_sync_at).to be_like_time(Time.now)
end
end
it 'logs JIRA DVCS server last sync' do
Timecop.freeze do
subject.log_jira_dvcs_integration_usage(cloud: false)
expect(subject.jira_dvcs_server_last_sync_at).to be_like_time(Time.now)
expect(subject.jira_dvcs_cloud_last_sync_at).to be_nil
end
end
context 'when log_jira_dvcs_integration_usage is called simultaneously for the same project' do
it 'logs the latest call' do
feature_usage = project.feature_usage
feature_usage.log_jira_dvcs_integration_usage
first_logged_at = feature_usage.jira_dvcs_cloud_last_sync_at
Timecop.freeze(1.hour.from_now) do
ProjectFeatureUsage.new(project_id: project.id).log_jira_dvcs_integration_usage
end
expect(feature_usage.reload.jira_dvcs_cloud_last_sync_at).to be > first_logged_at
end
end
end
end
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment