Commit 6c1cb0cb authored by Reuben Pereira's avatar Reuben Pereira Committed by Douglas Barbosa Alexandre
Browse files

Add ProjectMetricsDashboardSetting model and table

This new table will be used to store the external_dashboard_url which
allows users to add a link to their external dashboards (ex Grafana)
to the Metrics dashboard.
parent 4c24ed80e9e5
...@@ -56,6 +56,8 @@ def update_params ...@@ -56,6 +56,8 @@ def update_params
# overridden in EE # overridden in EE
def permitted_project_params def permitted_project_params
{ {
metrics_setting_attributes: [:external_dashboard_url],
error_tracking_setting_attributes: [ error_tracking_setting_attributes: [
:enabled, :enabled,
:api_host, :api_host,
......
...@@ -188,6 +188,7 @@ class Project < ApplicationRecord ...@@ -188,6 +188,7 @@ class Project < ApplicationRecord
has_one :import_export_upload, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent has_one :import_export_upload, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent
has_one :project_repository, inverse_of: :project has_one :project_repository, inverse_of: :project
has_one :error_tracking_setting, inverse_of: :project, class_name: 'ErrorTracking::ProjectErrorTrackingSetting' has_one :error_tracking_setting, inverse_of: :project, class_name: 'ErrorTracking::ProjectErrorTrackingSetting'
has_one :metrics_setting, inverse_of: :project, class_name: 'ProjectMetricsSetting'
# Merge Requests for target project should be removed with it # Merge Requests for target project should be removed with it
has_many :merge_requests, foreign_key: 'target_project_id', inverse_of: :target_project has_many :merge_requests, foreign_key: 'target_project_id', inverse_of: :target_project
...@@ -297,6 +298,7 @@ class Project < ApplicationRecord ...@@ -297,6 +298,7 @@ class Project < ApplicationRecord
reject_if: ->(attrs) { attrs[:id].blank? && attrs[:url].blank? } reject_if: ->(attrs) { attrs[:id].blank? && attrs[:url].blank? }
accepts_nested_attributes_for :error_tracking_setting, update_only: true accepts_nested_attributes_for :error_tracking_setting, update_only: true
accepts_nested_attributes_for :metrics_setting, update_only: true, allow_destroy: true
delegate :name, to: :owner, allow_nil: true, prefix: true delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true delegate :members, to: :team, prefix: true
......
# frozen_string_literal: true
class ProjectMetricsSetting < ApplicationRecord
belongs_to :project
validates :external_dashboard_url,
length: { maximum: 255 },
addressable_url: { enforce_sanitization: true, ascii_only: true }
end
...@@ -12,7 +12,16 @@ def execute ...@@ -12,7 +12,16 @@ def execute
private private
def project_update_params def project_update_params
error_tracking_params error_tracking_params.merge(metrics_setting_params)
end
def metrics_setting_params
attribs = params[:metrics_setting_attributes]
return {} unless attribs
destroy = attribs[:external_dashboard_url].blank?
{ metrics_setting_attributes: attribs.merge(_destroy: destroy) }
end end
def error_tracking_params def error_tracking_params
......
---
title: Add backend support for a External Dashboard URL setting
merge_request: 27550
author:
type: added
...@@ -14,6 +14,8 @@ en: ...@@ -14,6 +14,8 @@ en:
token: "Auth Token" token: "Auth Token"
project: "Project" project: "Project"
api_url: "Sentry API URL" api_url: "Sentry API URL"
project/metrics_setting:
external_dashboard_url: "External dashboard URL"
errors: errors:
messages: messages:
label_already_exists_at_group_level: "already exists at group level for %{group}. Please choose another one." label_already_exists_at_group_level: "already exists at group level for %{group}. Please choose another one."
......
# frozen_string_literal: true
class CreateProjectMetricsSettings < ActiveRecord::Migration[5.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table :project_metrics_settings, id: :int, primary_key: :project_id, default: nil do |t|
t.string :external_dashboard_url, null: false
t.foreign_key :projects, column: :project_id, on_delete: :cascade
end
end
end
...@@ -10,7 +10,7 @@ ...@@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20190408163745) do ActiveRecord::Schema.define(version: 20190422082247) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
...@@ -1681,6 +1681,10 @@ ...@@ -1681,6 +1681,10 @@
t.index ["project_id"], name: "index_project_import_data_on_project_id", using: :btree t.index ["project_id"], name: "index_project_import_data_on_project_id", using: :btree
end end
create_table "project_metrics_settings", primary_key: "project_id", id: :integer, default: nil, force: :cascade do |t|
t.string "external_dashboard_url", null: false
end
create_table "project_mirror_data", id: :serial, force: :cascade do |t| create_table "project_mirror_data", id: :serial, force: :cascade do |t|
t.integer "project_id", null: false t.integer "project_id", null: false
t.string "status" t.string "status"
...@@ -2529,6 +2533,7 @@ ...@@ -2529,6 +2533,7 @@
add_foreign_key "project_features", "projects", name: "fk_18513d9b92", 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_group_links", "projects", name: "fk_daa8cee94c", on_delete: :cascade
add_foreign_key "project_import_data", "projects", name: "fk_ffb9ee3a10", on_delete: :cascade add_foreign_key "project_import_data", "projects", name: "fk_ffb9ee3a10", on_delete: :cascade
add_foreign_key "project_metrics_settings", "projects", on_delete: :cascade
add_foreign_key "project_mirror_data", "projects", on_delete: :cascade add_foreign_key "project_mirror_data", "projects", on_delete: :cascade
add_foreign_key "project_repositories", "projects", on_delete: :cascade add_foreign_key "project_repositories", "projects", on_delete: :cascade
add_foreign_key "project_repositories", "shards", on_delete: :restrict add_foreign_key "project_repositories", "shards", on_delete: :restrict
......
...@@ -75,6 +75,7 @@ project_tree: ...@@ -75,6 +75,7 @@ project_tree:
- :project_badges - :project_badges
- :ci_cd_settings - :ci_cd_settings
- :error_tracking_setting - :error_tracking_setting
- :metrics_setting
# Only include the following attributes for the models specified. # Only include the following attributes for the models specified.
included_attributes: included_attributes:
......
...@@ -25,7 +25,8 @@ class RelationFactory ...@@ -25,7 +25,8 @@ class RelationFactory
metrics: 'MergeRequest::Metrics', metrics: 'MergeRequest::Metrics',
ci_cd_settings: 'ProjectCiCdSetting', ci_cd_settings: 'ProjectCiCdSetting',
error_tracking_setting: 'ErrorTracking::ProjectErrorTrackingSetting', error_tracking_setting: 'ErrorTracking::ProjectErrorTrackingSetting',
links: 'Releases::Link' }.freeze links: 'Releases::Link',
metrics_setting: 'ProjectMetricsSetting' }.freeze
USER_REFERENCES = %w[author_id assignee_id updated_by_id merged_by_id latest_closed_by_id user_id created_by_id last_edited_by_id merge_user_id resolved_by_id closed_by_id].freeze USER_REFERENCES = %w[author_id assignee_id updated_by_id merged_by_id latest_closed_by_id user_id created_by_id last_edited_by_id merge_user_id resolved_by_id closed_by_id].freeze
......
...@@ -166,6 +166,20 @@ def stub_operations_update_service_returning(return_value = {}) ...@@ -166,6 +166,20 @@ def stub_operations_update_service_returning(return_value = {})
end end
end end
context 'metrics dashboard setting' do
describe 'PATCH #update' do
let(:params) do
{
metrics_setting_attributes: {
external_dashboard_url: 'https://gitlab.com'
}
}
end
it_behaves_like 'PATCHable'
end
end
private private
def project_params(project, params = {}) def project_params(project, params = {})
......
# frozen_string_literal: true
FactoryBot.define do
factory :project_metrics_setting, class: ProjectMetricsSetting do
project
external_dashboard_url 'https://grafana.com'
end
end
...@@ -322,6 +322,7 @@ project: ...@@ -322,6 +322,7 @@ project:
- pool_repository - pool_repository
- kubernetes_namespaces - kubernetes_namespaces
- error_tracking_setting - error_tracking_setting
- metrics_setting
award_emoji: award_emoji:
- awardable - awardable
- user - user
...@@ -360,3 +361,5 @@ error_tracking_setting: ...@@ -360,3 +361,5 @@ error_tracking_setting:
- project - project
suggestions: suggestions:
- note - note
metrics_setting:
- project
...@@ -606,7 +606,6 @@ ResourceLabelEvent: ...@@ -606,7 +606,6 @@ ResourceLabelEvent:
- user_id - user_id
- created_at - created_at
ErrorTracking::ProjectErrorTrackingSetting: ErrorTracking::ProjectErrorTrackingSetting:
- id
- api_url - api_url
- project_id - project_id
- project_name - project_name
...@@ -626,3 +625,8 @@ MergeRequestAssignee: ...@@ -626,3 +625,8 @@ MergeRequestAssignee:
- id - id
- user_id - user_id
- merge_request_id - merge_request_id
ProjectMetricsSetting:
- project_id
- external_dashboard_url
- created_at
- updated_at
# frozen_string_literal: true
require 'spec_helper'
describe ProjectMetricsSetting do
describe 'Associations' do
it { is_expected.to belong_to(:project) }
end
describe 'Validations' do
context 'when external_dashboard_url is over 255 chars' do
before do
subject.external_dashboard_url = 'https://' + 'a' * 250
end
it 'fails validation' do
expect(subject).not_to be_valid
expect(subject.errors.messages[:external_dashboard_url])
.to include('is too long (maximum is 255 characters)')
end
end
context 'with unsafe url' do
before do
subject.external_dashboard_url = %{https://replaceme.com/'><script>alert(document.cookie)</script>}
end
it { is_expected.to be_invalid }
end
context 'non ascii chars in external_dashboard_url' do
before do
subject.external_dashboard_url = 'http://gitlab.com/api/0/projects/project1/something€'
end
it { is_expected.to be_invalid }
end
context 'internal url in external_dashboard_url' do
before do
subject.external_dashboard_url = 'http://192.168.1.1'
end
it { is_expected.to be_valid }
end
context 'external_dashboard_url is blank' do
before do
subject.external_dashboard_url = ''
end
it { is_expected.to be_invalid }
end
end
end
...@@ -11,6 +11,56 @@ ...@@ -11,6 +11,56 @@
subject { described_class.new(project, user, params) } subject { described_class.new(project, user, params) }
describe '#execute' do describe '#execute' do
context 'metrics dashboard setting' do
let(:params) do
{
metrics_setting_attributes: {
external_dashboard_url: 'http://gitlab.com'
}
}
end
context 'without existing metrics dashboard setting' do
it 'creates a setting' do
expect(result[:status]).to eq(:success)
expect(project.reload.metrics_setting.external_dashboard_url).to eq(
'http://gitlab.com'
)
end
end
context 'with existing metrics dashboard setting' do
before do
create(:project_metrics_setting, project: project)
end
it 'updates the settings' do
expect(result[:status]).to eq(:success)
expect(project.reload.metrics_setting.external_dashboard_url).to eq(
'http://gitlab.com'
)
end
context 'with blank external_dashboard_url in params' do
let(:params) do
{
metrics_setting_attributes: {
external_dashboard_url: ''
}
}
end
it 'destroys the metrics_setting entry in DB' do
expect(result[:status]).to eq(:success)
expect(project.reload.metrics_setting).to be_nil
end
end
end
end
context 'error tracking' do context 'error tracking' do
context 'with existing error tracking setting' do context 'with existing error tracking setting' do
let(:params) do let(:params) do
......
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