Commit 3df75648 authored by Felipe Artur's avatar Felipe Artur Committed by Jan Provaznik

Persist user preferences for boards

Create table and model methods to persist
board preferences for users.
parent e5d7d61b
# 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
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 (
id integer NOT NULL,
project_id integer,
......@@ -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_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.broadcast_messages ALTER COLUMN id SET DEFAULT nextval('public.broadcast_messages_id_seq'::regclass);
......@@ -8109,6 +8129,9 @@ ALTER TABLE ONLY public.board_labels
ALTER TABLE ONLY public.board_project_recent_visits
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
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
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_milestone_id ON public.boards USING btree (milestone_id);
......@@ -12335,6 +12364,9 @@ ALTER TABLE ONLY public.snippet_repositories
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;
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
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
ALTER TABLE ONLY public.issues_prometheus_alert_events
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
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;
20200602013901
20200603073101
20200604143628
20200604145731
20200604174544
20200604174558
20200608075553
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
belongs_to :milestone
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
# multiple assignees on the board configuration.
......
......@@ -58,6 +58,8 @@ module EE
has_many :smartcard_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
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
it { is_expected.to have_one(:assignee).through(:board_assignee) }
it { is_expected.to have_many(:board_labels) }
it { is_expected.to have_many(:labels).through(:board_labels) }
it { is_expected.to have_many(:user_preferences) }
end
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
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(:security_dashboard_projects) }
it { is_expected.to have_many(:board_preferences) }
end
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:
- board_assignee
- assignee
- labels
- user_preferences
lists:
- user
- 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