Commit 396bea61 authored by Jan Provaznik's avatar Jan Provaznik

Merge branch 'issue_218028_be' into 'master'

Persist user preferences for boards

See merge request gitlab-org/gitlab!33892
parents 1a9ccb74 3df75648
# frozen_string_literal: true
class CreateBoardUserPreferences < ActiveRecord::Migration[6.0]
DOWNTIME = false
def up
create_table :board_user_preferences do |t|
t.bigint :user_id, null: false, index: true
t.bigint :board_id, null: false, index: true
t.boolean :hide_labels
t.timestamps_with_timezone null: false
end
add_index :board_user_preferences, [:user_id, :board_id], unique: true
end
def down
drop_table :board_user_preferences
end
end
# frozen_string_literal: true
class AddUsersForeignKeyToBoardUserPreferences < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
with_lock_retries do
add_foreign_key :board_user_preferences, :users, column: :user_id, on_delete: :cascade # rubocop:disable Migration/AddConcurrentForeignKey
end
end
def down
with_lock_retries do
remove_foreign_key :board_user_preferences, column: :user_id
end
end
end
# frozen_string_literal: true
class AddBoardsForeignKeyToBoardUserPreferences < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def up
with_lock_retries do
add_foreign_key :board_user_preferences, :boards, column: :board_id, on_delete: :cascade # rubocop:disable Migration/AddConcurrentForeignKey
end
end
def down
with_lock_retries do
remove_foreign_key :board_user_preferences, column: :board_id
end
end
end
...@@ -804,6 +804,24 @@ CREATE SEQUENCE public.board_project_recent_visits_id_seq ...@@ -804,6 +804,24 @@ CREATE SEQUENCE public.board_project_recent_visits_id_seq
ALTER SEQUENCE public.board_project_recent_visits_id_seq OWNED BY public.board_project_recent_visits.id; ALTER SEQUENCE public.board_project_recent_visits_id_seq OWNED BY public.board_project_recent_visits.id;
CREATE TABLE public.board_user_preferences (
id bigint NOT NULL,
user_id bigint NOT NULL,
board_id bigint NOT NULL,
hide_labels boolean,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);
CREATE SEQUENCE public.board_user_preferences_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE public.board_user_preferences_id_seq OWNED BY public.board_user_preferences.id;
CREATE TABLE public.boards ( CREATE TABLE public.boards (
id integer NOT NULL, id integer NOT NULL,
project_id integer, project_id integer,
...@@ -7435,6 +7453,8 @@ ALTER TABLE ONLY public.board_labels ALTER COLUMN id SET DEFAULT nextval('public ...@@ -7435,6 +7453,8 @@ ALTER TABLE ONLY public.board_labels ALTER COLUMN id SET DEFAULT nextval('public
ALTER TABLE ONLY public.board_project_recent_visits ALTER COLUMN id SET DEFAULT nextval('public.board_project_recent_visits_id_seq'::regclass); ALTER TABLE ONLY public.board_project_recent_visits ALTER COLUMN id SET DEFAULT nextval('public.board_project_recent_visits_id_seq'::regclass);
ALTER TABLE ONLY public.board_user_preferences ALTER COLUMN id SET DEFAULT nextval('public.board_user_preferences_id_seq'::regclass);
ALTER TABLE ONLY public.boards ALTER COLUMN id SET DEFAULT nextval('public.boards_id_seq'::regclass); ALTER TABLE ONLY public.boards ALTER COLUMN id SET DEFAULT nextval('public.boards_id_seq'::regclass);
ALTER TABLE ONLY public.broadcast_messages ALTER COLUMN id SET DEFAULT nextval('public.broadcast_messages_id_seq'::regclass); ALTER TABLE ONLY public.broadcast_messages ALTER COLUMN id SET DEFAULT nextval('public.broadcast_messages_id_seq'::regclass);
...@@ -8109,6 +8129,9 @@ ALTER TABLE ONLY public.board_labels ...@@ -8109,6 +8129,9 @@ ALTER TABLE ONLY public.board_labels
ALTER TABLE ONLY public.board_project_recent_visits ALTER TABLE ONLY public.board_project_recent_visits
ADD CONSTRAINT board_project_recent_visits_pkey PRIMARY KEY (id); ADD CONSTRAINT board_project_recent_visits_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.board_user_preferences
ADD CONSTRAINT board_user_preferences_pkey PRIMARY KEY (id);
ALTER TABLE ONLY public.boards ALTER TABLE ONLY public.boards
ADD CONSTRAINT boards_pkey PRIMARY KEY (id); ADD CONSTRAINT boards_pkey PRIMARY KEY (id);
...@@ -9299,6 +9322,12 @@ CREATE INDEX index_board_project_recent_visits_on_user_id ON public.board_projec ...@@ -9299,6 +9322,12 @@ CREATE INDEX index_board_project_recent_visits_on_user_id ON public.board_projec
CREATE UNIQUE INDEX index_board_project_recent_visits_on_user_project_and_board ON public.board_project_recent_visits USING btree (user_id, project_id, board_id); CREATE UNIQUE INDEX index_board_project_recent_visits_on_user_project_and_board ON public.board_project_recent_visits USING btree (user_id, project_id, board_id);
CREATE INDEX index_board_user_preferences_on_board_id ON public.board_user_preferences USING btree (board_id);
CREATE INDEX index_board_user_preferences_on_user_id ON public.board_user_preferences USING btree (user_id);
CREATE UNIQUE INDEX index_board_user_preferences_on_user_id_and_board_id ON public.board_user_preferences USING btree (user_id, board_id);
CREATE INDEX index_boards_on_group_id ON public.boards USING btree (group_id); CREATE INDEX index_boards_on_group_id ON public.boards USING btree (group_id);
CREATE INDEX index_boards_on_milestone_id ON public.boards USING btree (milestone_id); CREATE INDEX index_boards_on_milestone_id ON public.boards USING btree (milestone_id);
...@@ -12335,6 +12364,9 @@ ALTER TABLE ONLY public.snippet_repositories ...@@ -12335,6 +12364,9 @@ ALTER TABLE ONLY public.snippet_repositories
ALTER TABLE ONLY public.gpg_key_subkeys ALTER TABLE ONLY public.gpg_key_subkeys
ADD CONSTRAINT fk_rails_8b2c90b046 FOREIGN KEY (gpg_key_id) REFERENCES public.gpg_keys(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_8b2c90b046 FOREIGN KEY (gpg_key_id) REFERENCES public.gpg_keys(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.board_user_preferences
ADD CONSTRAINT fk_rails_8b3b23ce82 FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.allowed_email_domains ALTER TABLE ONLY public.allowed_email_domains
ADD CONSTRAINT fk_rails_8b5da859f9 FOREIGN KEY (group_id) REFERENCES public.namespaces(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_8b5da859f9 FOREIGN KEY (group_id) REFERENCES public.namespaces(id) ON DELETE CASCADE;
...@@ -12674,6 +12706,9 @@ ALTER TABLE ONLY public.dependency_proxy_blobs ...@@ -12674,6 +12706,9 @@ ALTER TABLE ONLY public.dependency_proxy_blobs
ALTER TABLE ONLY public.issues_prometheus_alert_events ALTER TABLE ONLY public.issues_prometheus_alert_events
ADD CONSTRAINT fk_rails_db5b756534 FOREIGN KEY (issue_id) REFERENCES public.issues(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_db5b756534 FOREIGN KEY (issue_id) REFERENCES public.issues(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.board_user_preferences
ADD CONSTRAINT fk_rails_dbebdaa8fe FOREIGN KEY (board_id) REFERENCES public.boards(id) ON DELETE CASCADE;
ALTER TABLE ONLY public.vulnerability_occurrence_pipelines ALTER TABLE ONLY public.vulnerability_occurrence_pipelines
ADD CONSTRAINT fk_rails_dc3ae04693 FOREIGN KEY (occurrence_id) REFERENCES public.vulnerability_occurrences(id) ON DELETE CASCADE; ADD CONSTRAINT fk_rails_dc3ae04693 FOREIGN KEY (occurrence_id) REFERENCES public.vulnerability_occurrences(id) ON DELETE CASCADE;
...@@ -13848,6 +13883,9 @@ COPY "schema_migrations" (version) FROM STDIN; ...@@ -13848,6 +13883,9 @@ COPY "schema_migrations" (version) FROM STDIN;
20200602013901 20200602013901
20200603073101 20200603073101
20200604143628 20200604143628
20200604145731
20200604174544
20200604174558
20200608075553 20200608075553
20200609002841 20200609002841
\. \.
......
# frozen_string_literal: true
class BoardUserPreference < ApplicationRecord
belongs_to :user, inverse_of: :board_preferences
belongs_to :board, inverse_of: :user_preferences
validates :user, presence: true
validates :board, presence: true
validates :user_id, uniqueness: { scope: :board_id, message: "should have only one board preference per user" }
end
...@@ -12,6 +12,7 @@ module EE ...@@ -12,6 +12,7 @@ module EE
belongs_to :milestone belongs_to :milestone
has_many :board_labels has_many :board_labels
has_many :user_preferences, class_name: 'BoardUserPreference', inverse_of: :board
# These can safely be changed to has_many when we support # These can safely be changed to has_many when we support
# multiple assignees on the board configuration. # multiple assignees on the board configuration.
......
...@@ -58,6 +58,8 @@ module EE ...@@ -58,6 +58,8 @@ module EE
has_many :smartcard_identities has_many :smartcard_identities
has_many :scim_identities has_many :scim_identities
has_many :board_preferences, class_name: 'BoardUserPreference', inverse_of: :user
belongs_to :managing_group, class_name: 'Group', optional: true, inverse_of: :managed_users belongs_to :managing_group, class_name: 'Group', optional: true, inverse_of: :managed_users
scope :not_managed, ->(group: nil) { scope :not_managed, ->(group: nil) {
......
# frozen_string_literal: true
module Boards
module UserPreferences
class UpdateService
attr_accessor :user, :params
def initialize(user, params = {})
@user = user
@params = params
end
def execute(board)
return false unless user
preference = board.user_preferences.safe_find_or_create_by!(user: user, board: board)
preference.update(sanitized_params)
end
private
def sanitized_params
params.slice(:hide_labels)
end
end
end
end
---
title: Persist user preferences for boards
merge_request: 33892
author:
type: added
# frozen_string_literal: true
FactoryBot.define do
factory :board_user_preference do
user
board
end
end
...@@ -13,6 +13,7 @@ RSpec.describe Board do ...@@ -13,6 +13,7 @@ RSpec.describe Board do
it { is_expected.to have_one(:assignee).through(:board_assignee) } it { is_expected.to have_one(:assignee).through(:board_assignee) }
it { is_expected.to have_many(:board_labels) } it { is_expected.to have_many(:board_labels) }
it { is_expected.to have_many(:labels).through(:board_labels) } it { is_expected.to have_many(:labels).through(:board_labels) }
it { is_expected.to have_many(:user_preferences) }
end end
describe 'validations' do describe 'validations' do
......
# frozen_string_literal: true
require 'spec_helper'
describe BoardUserPreference do
before do
create(:board_user_preference)
end
describe 'relationships' do
it { is_expected.to belong_to(:board) }
it { is_expected.to belong_to(:user) }
it do
is_expected.to validate_uniqueness_of(:user_id).scoped_to(:board_id)
.with_message("should have only one board preference per user")
end
end
end
...@@ -25,6 +25,7 @@ RSpec.describe User do ...@@ -25,6 +25,7 @@ RSpec.describe User do
it { is_expected.to have_many(:path_locks).dependent(:destroy) } it { is_expected.to have_many(:path_locks).dependent(:destroy) }
it { is_expected.to have_many(:users_security_dashboard_projects) } it { is_expected.to have_many(:users_security_dashboard_projects) }
it { is_expected.to have_many(:security_dashboard_projects) } it { is_expected.to have_many(:security_dashboard_projects) }
it { is_expected.to have_many(:board_preferences) }
end end
describe 'nested attributes' do describe 'nested attributes' do
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Boards::UserPreferences::UpdateService, services: true do
describe '#execute' do
let_it_be(:user) { create(:user) }
let_it_be(:board) { create(:board) }
context 'when user is not present' do
it 'does not create user preference record' do
service = described_class.new(nil, hide_labels: true)
result = service.execute(board)
expect(result).to eq(false)
expect(board.user_preferences).to be_empty
end
end
context 'when user is present' do
context 'when there is no user preference' do
it 'creates user preference' do
result = described_class.new(user, hide_labels: true).execute(board)
preferences = board.user_preferences.find_by(user: user)
expect(preferences.hide_labels).to eq(true)
expect(result).to eq(true)
end
end
context 'when there is an user preference' do
let_it_be(:user_preference) { create(:board_user_preference, user: user, hide_labels: true) }
let_it_be(:board) { user_preference.board }
it 'does not duplicate user preference' do
result = described_class.new(user, hide_labels: false).execute(board)
preferences = board.user_preferences.where(user: user)
expect(preferences.count).to eq(1)
expect(preferences.first.hide_labels).to eq(false)
expect(result).to eq(true)
end
it 'does not update user_id' do
expect { described_class.new(user, user: create(:user)).execute(board) }
.not_to change { user_preference.reload.user_id }
end
it 'does not update board_id' do
expect { described_class.new(user, board: create(:board)).execute(board) }
.not_to change { user_preference.reload.board_id }
end
end
end
end
end
...@@ -589,6 +589,7 @@ boards: ...@@ -589,6 +589,7 @@ boards:
- board_assignee - board_assignee
- assignee - assignee
- labels - labels
- user_preferences
lists: lists:
- user - user
- milestone - milestone
......
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