diff options
author | Kamil Trzciński <ayufan@ayufan.eu> | 2018-02-05 14:24:43 +0000 |
---|---|---|
committer | Kamil Trzciński <ayufan@ayufan.eu> | 2018-02-05 14:24:43 +0000 |
commit | 9edd95658308f3e0e8e6df6eb829555a3cb418d1 (patch) | |
tree | 9b5d96752c3eb767c603bdf12f381abc9604d0dd | |
parent | bedfc7b103c25cd6b9aada350142549160e41a5e (diff) | |
parent | f24705212791072a93edd4f7b1c530507b6a2c49 (diff) | |
download | gitlab-ce-9edd95658308f3e0e8e6df6eb829555a3cb418d1.tar.gz |
Merge branch 'persistent-callouts' into 'master'
Add backend for persistently dismissible callouts
See merge request gitlab-org/gitlab-ce!16735
-rw-r--r-- | app/controllers/user_callouts_controller.rb | 23 | ||||
-rw-r--r-- | app/helpers/user_callouts_helper.rb | 14 | ||||
-rw-r--r-- | app/models/user.rb | 1 | ||||
-rw-r--r-- | app/models/user_callout.rb | 13 | ||||
-rw-r--r-- | changelogs/unreleased/persistent-callouts.yml | 5 | ||||
-rw-r--r-- | config/routes.rb | 3 | ||||
-rw-r--r-- | db/migrate/20180125214301_create_user_callouts.rb | 16 | ||||
-rw-r--r-- | db/schema.rb | 9 | ||||
-rw-r--r-- | spec/controllers/user_callouts_controller_spec.rb | 49 | ||||
-rw-r--r-- | spec/factories/user_callouts.rb | 7 | ||||
-rw-r--r-- | spec/helpers/user_callouts_helper_spec.rb | 47 | ||||
-rw-r--r-- | spec/models/user_callout_spec.rb | 16 |
12 files changed, 203 insertions, 0 deletions
diff --git a/app/controllers/user_callouts_controller.rb b/app/controllers/user_callouts_controller.rb new file mode 100644 index 00000000000..18cde4a7b1a --- /dev/null +++ b/app/controllers/user_callouts_controller.rb @@ -0,0 +1,23 @@ +class UserCalloutsController < ApplicationController + def create + if ensure_callout.persisted? + respond_to do |format| + format.json { head :ok } + end + else + respond_to do |format| + format.json { head :bad_request } + end + end + end + + private + + def ensure_callout + current_user.callouts.find_or_create_by(feature_name: UserCallout.feature_names[feature_name]) + end + + def feature_name + params.require(:feature_name) + end +end diff --git a/app/helpers/user_callouts_helper.rb b/app/helpers/user_callouts_helper.rb new file mode 100644 index 00000000000..6368e248c6e --- /dev/null +++ b/app/helpers/user_callouts_helper.rb @@ -0,0 +1,14 @@ +module UserCalloutsHelper + GKE_CLUSTER_INTEGRATION = 'gke_cluster_integration'.freeze + + def show_gke_cluster_integration_callout?(project) + can?(current_user, :create_cluster, project) && + !user_dismissed?(GKE_CLUSTER_INTEGRATION) + end + + private + + def user_dismissed?(feature_name) + current_user&.callouts&.find_by(feature_name: feature_name) + end +end diff --git a/app/models/user.rb b/app/models/user.rb index cad118f5502..a57ba3740c9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -135,6 +135,7 @@ class User < ActiveRecord::Base has_many :assigned_merge_requests, dependent: :nullify, foreign_key: :assignee_id, class_name: "MergeRequest" # rubocop:disable Cop/ActiveRecordDependent has_many :custom_attributes, class_name: 'UserCustomAttribute' + has_many :callouts, class_name: 'UserCallout' has_many :uploads, as: :model, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent # diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb new file mode 100644 index 00000000000..e4b69382626 --- /dev/null +++ b/app/models/user_callout.rb @@ -0,0 +1,13 @@ +class UserCallout < ActiveRecord::Base + belongs_to :user + + enum feature_name: { + gke_cluster_integration: 1 + } + + validates :user, presence: true + validates :feature_name, + presence: true, + uniqueness: { scope: :user_id }, + inclusion: { in: UserCallout.feature_names.keys } +end diff --git a/changelogs/unreleased/persistent-callouts.yml b/changelogs/unreleased/persistent-callouts.yml new file mode 100644 index 00000000000..ca949a3b96c --- /dev/null +++ b/changelogs/unreleased/persistent-callouts.yml @@ -0,0 +1,5 @@ +--- +title: Add backend for persistently dismissably callouts +merge_request: +author: +type: added diff --git a/config/routes.rb b/config/routes.rb index f162043dd5e..e72ea1881cd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -60,6 +60,9 @@ Rails.application.routes.draw do resources :issues, module: :boards, only: [:index, :update] end + + # UserCallouts + resources :user_callouts, only: [:create] end # Koding route diff --git a/db/migrate/20180125214301_create_user_callouts.rb b/db/migrate/20180125214301_create_user_callouts.rb new file mode 100644 index 00000000000..856eff36ae0 --- /dev/null +++ b/db/migrate/20180125214301_create_user_callouts.rb @@ -0,0 +1,16 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class CreateUserCallouts < ActiveRecord::Migration + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def change + create_table :user_callouts do |t| + t.integer :feature_name, null: false + t.references :user, index: true, foreign_key: { on_delete: :cascade }, null: false + end + + add_index :user_callouts, [:user_id, :feature_name], unique: true + end +end diff --git a/db/schema.rb b/db/schema.rb index c701a5f1e17..14024164359 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1769,6 +1769,14 @@ ActiveRecord::Schema.define(version: 20180202111106) do add_index "user_agent_details", ["subject_id", "subject_type"], name: "index_user_agent_details_on_subject_id_and_subject_type", using: :btree + create_table "user_callouts", force: :cascade do |t| + t.integer "feature_name", null: false + t.integer "user_id", null: false + end + + add_index "user_callouts", ["user_id", "feature_name"], name: "index_user_callouts_on_user_id_and_feature_name", unique: true, using: :btree + add_index "user_callouts", ["user_id"], name: "index_user_callouts_on_user_id", using: :btree + create_table "user_custom_attributes", force: :cascade do |t| t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "updated_at", null: false @@ -2040,6 +2048,7 @@ ActiveRecord::Schema.define(version: 20180202111106) do add_foreign_key "todos", "projects", name: "fk_45054f9c45", on_delete: :cascade add_foreign_key "trending_projects", "projects", on_delete: :cascade add_foreign_key "u2f_registrations", "users" + add_foreign_key "user_callouts", "users", on_delete: :cascade add_foreign_key "user_custom_attributes", "users", on_delete: :cascade add_foreign_key "user_synced_attributes_metadata", "users", on_delete: :cascade add_foreign_key "users_star_projects", "projects", name: "fk_22cd27ddfc", on_delete: :cascade diff --git a/spec/controllers/user_callouts_controller_spec.rb b/spec/controllers/user_callouts_controller_spec.rb new file mode 100644 index 00000000000..48e2ff75cac --- /dev/null +++ b/spec/controllers/user_callouts_controller_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe UserCalloutsController do + let(:user) { create(:user) } + + before do + sign_in(user) + end + + describe "POST #create" do + subject { post :create, feature_name: feature_name, format: :json } + + context 'with valid feature name' do + let(:feature_name) { UserCallout.feature_names.keys.first } + + context 'when callout entry does not exist' do + it 'should create a callout entry with dismissed state' do + expect { subject }.to change { UserCallout.count }.by(1) + end + + it 'should return success' do + subject + + expect(response).to have_gitlab_http_status(:ok) + end + end + + context 'when callout entry already exists' do + let!(:callout) { create(:user_callout, feature_name: UserCallout.feature_names.keys.first, user: user) } + + it 'should return success' do + subject + + expect(response).to have_gitlab_http_status(:ok) + end + end + end + + context 'with invalid feature name' do + let(:feature_name) { 'bogus_feature_name' } + + it 'should return bad request' do + subject + + expect(response).to have_gitlab_http_status(:bad_request) + end + end + end +end diff --git a/spec/factories/user_callouts.rb b/spec/factories/user_callouts.rb new file mode 100644 index 00000000000..528e442c14b --- /dev/null +++ b/spec/factories/user_callouts.rb @@ -0,0 +1,7 @@ +FactoryBot.define do + factory :user_callout do + feature_name :gke_cluster_integration + + user + end +end diff --git a/spec/helpers/user_callouts_helper_spec.rb b/spec/helpers/user_callouts_helper_spec.rb new file mode 100644 index 00000000000..27455705d23 --- /dev/null +++ b/spec/helpers/user_callouts_helper_spec.rb @@ -0,0 +1,47 @@ +require "spec_helper" + +describe UserCalloutsHelper do + let(:user) { create(:user) } + + before do + allow(helper).to receive(:current_user).and_return(user) + end + + describe '.show_gke_cluster_integration_callout?' do + let(:project) { create(:project) } + + subject { helper.show_gke_cluster_integration_callout?(project) } + + context 'when user can create a cluster' do + before do + allow(helper).to receive(:can?).with(anything, :create_cluster, anything) + .and_return(true) + end + + context 'when user has not dismissed' do + before do + allow(helper).to receive(:user_dismissed?).and_return(false) + end + + it { is_expected.to be true } + end + + context 'when user dismissed' do + before do + allow(helper).to receive(:user_dismissed?).and_return(true) + end + + it { is_expected.to be false } + end + end + + context 'when user can not create a cluster' do + before do + allow(helper).to receive(:can?).with(anything, :create_cluster, anything) + .and_return(false) + end + + it { is_expected.to be false } + end + end +end diff --git a/spec/models/user_callout_spec.rb b/spec/models/user_callout_spec.rb new file mode 100644 index 00000000000..64ba17c81fe --- /dev/null +++ b/spec/models/user_callout_spec.rb @@ -0,0 +1,16 @@ +require 'rails_helper' + +describe UserCallout do + let!(:callout) { create(:user_callout) } + + describe 'relationships' do + it { is_expected.to belong_to(:user) } + end + + describe 'validations' do + it { is_expected.to validate_presence_of(:user) } + + it { is_expected.to validate_presence_of(:feature_name) } + it { is_expected.to validate_uniqueness_of(:feature_name).scoped_to(:user_id).ignoring_case_sensitivity } + end +end |