Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2021-05-11 00:10:50 +00:00
parent c57962295c
commit 271e563cf6
29 changed files with 294 additions and 192 deletions

View File

@ -52,7 +52,7 @@ export default {
},
toggleFormDropdown() {
this.isDropdownShowing = !this.isDropdownShowing;
const { dropdown } = this.$children[2].$refs.dropdown.$refs;
const { dropdown } = this.$refs.status.$refs.dropdown.$refs;
if (dropdown && this.isDropdownShowing) {
dropdown.show();
}
@ -102,6 +102,7 @@ export default {
</p>
<alert-status
ref="status"
:alert="alert"
:project-path="projectPath"
:is-dropdown-showing="isDropdownShowing"

View File

@ -19,7 +19,7 @@ module BoardsActions
def show
# Add / update the board in the recent visits table
Boards::Visits::CreateService.new(parent, current_user).execute(board) if request.format.html?
board_visit_service.new(parent, current_user).execute(board) if request.format.html?
respond_with_board
end
@ -52,6 +52,10 @@ module BoardsActions
board_klass.to_type
end
def board_visit_service
Boards::Visits::CreateService
end
def serializer
BoardSerializer.new(current_user: current_user)
end

View File

@ -2,27 +2,19 @@
# Tracks which boards in a specific group a user has visited
class BoardGroupRecentVisit < ApplicationRecord
include BoardRecentVisit
belongs_to :user
belongs_to :group
belongs_to :board
validates :user, presence: true
validates :user, presence: true
validates :group, presence: true
validates :board, presence: true
scope :by_user_group, -> (user, group) { where(user: user, group: group) }
scope :by_user_parent, -> (user, group) { where(user: user, group: group) }
def self.visited!(user, board)
visit = find_or_create_by(user: user, group: board.group, board: board)
visit.touch if visit.updated_at < Time.current
rescue ActiveRecord::RecordNotUnique
retry
end
def self.latest(user, group, count: nil)
visits = by_user_group(user, group).order(updated_at: :desc)
visits = visits.preload(:board) if count && count > 1
visits.first(count)
def self.board_parent_relation
:group
end
end

View File

@ -2,27 +2,19 @@
# Tracks which boards in a specific project a user has visited
class BoardProjectRecentVisit < ApplicationRecord
include BoardRecentVisit
belongs_to :user
belongs_to :project
belongs_to :board
validates :user, presence: true
validates :user, presence: true
validates :project, presence: true
validates :board, presence: true
scope :by_user_project, -> (user, project) { where(user: user, project: project) }
scope :by_user_parent, -> (user, project) { where(user: user, project: project) }
def self.visited!(user, board)
visit = find_or_create_by(user: user, project: board.project, board: board)
visit.touch if visit.updated_at < Time.current
rescue ActiveRecord::RecordNotUnique
retry
end
def self.latest(user, project, count: nil)
visits = by_user_project(user, project).order(updated_at: :desc)
visits = visits.preload(:board) if count && count > 1
visits.first(count)
def self.board_parent_relation
:project
end
end

View File

@ -0,0 +1,34 @@
# frozen_string_literal: true
module BoardRecentVisit
extend ActiveSupport::Concern
class_methods do
def visited!(user, board)
find_or_create_by(
"user" => user,
board_parent_relation => board.resource_parent,
board_relation => board
).tap do |visit|
visit.touch
end
rescue ActiveRecord::RecordNotUnique
retry
end
def latest(user, parent, count: nil)
visits = by_user_parent(user, parent).order(updated_at: :desc)
visits = visits.preload(board_relation)
visits.first(count)
end
def board_relation
:board
end
def board_parent_relation
raise NotImplementedError
end
end
end

View File

@ -5,13 +5,17 @@ module Boards
class CreateService < Boards::BaseService
def execute(board)
return unless current_user && Gitlab::Database.read_write?
return unless board.is_a?(Board) # other board types do not support board visits yet
return unless board
if parent.is_a?(Group)
BoardGroupRecentVisit.visited!(current_user, board)
else
BoardProjectRecentVisit.visited!(current_user, board)
end
model.visited!(current_user, board)
end
private
def model
return BoardGroupRecentVisit if parent.is_a?(Group)
BoardProjectRecentVisit
end
end
end

View File

@ -0,0 +1,5 @@
---
title: Redirect to the last visited epic board
merge_request: 60720
author:
type: added

View File

@ -0,0 +1,22 @@
# frozen_string_literal: true
class AddEpicBoardRecentVisitsTable < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
def up
with_lock_retries do
create_table :boards_epic_board_recent_visits do |t|
t.references :user, index: true, null: false, foreign_key: { on_delete: :cascade }
t.references :epic_board, index: true, foreign_key: { to_table: :boards_epic_boards, on_delete: :cascade }, null: false
t.references :group, index: true, foreign_key: { to_table: :namespaces, on_delete: :cascade }, null: false
t.timestamps_with_timezone null: false
end
end
end
def down
with_lock_retries do
drop_table :boards_epic_board_recent_visits
end
end
end

View File

@ -0,0 +1,20 @@
# frozen_string_literal: true
class AddIndexToEpicBoardRecentVisits < ActiveRecord::Migration[6.0]
include Gitlab::Database::MigrationHelpers
INDEX_NAME = 'index_epic_board_recent_visits_on_user_group_and_board'
disable_ddl_transaction!
def up
add_concurrent_index :boards_epic_board_recent_visits,
[:user_id, :group_id, :epic_board_id],
name: INDEX_NAME,
unique: true
end
def down
remove_concurrent_index_by_name :boards_epic_board_recent_visits, INDEX_NAME
end
end

View File

@ -0,0 +1 @@
c92824844732aad7277fc8b50bac0bf6919ad0a8d72e73b4ec3b89eafc085b7d

View File

@ -0,0 +1 @@
ae3c8336cb25efa7d23357a6777c0656dbe1a216efb5d4edcf923d1128f7e1e3

View File

@ -10057,6 +10057,24 @@ CREATE SEQUENCE boards_epic_board_positions_id_seq
ALTER SEQUENCE boards_epic_board_positions_id_seq OWNED BY boards_epic_board_positions.id;
CREATE TABLE boards_epic_board_recent_visits (
id bigint NOT NULL,
user_id bigint NOT NULL,
epic_board_id bigint NOT NULL,
group_id bigint NOT NULL,
created_at timestamp with time zone NOT NULL,
updated_at timestamp with time zone NOT NULL
);
CREATE SEQUENCE boards_epic_board_recent_visits_id_seq
START WITH 1
INCREMENT BY 1
NO MINVALUE
NO MAXVALUE
CACHE 1;
ALTER SEQUENCE boards_epic_board_recent_visits_id_seq OWNED BY boards_epic_board_recent_visits.id;
CREATE TABLE boards_epic_boards (
id bigint NOT NULL,
hide_backlog_list boolean DEFAULT false NOT NULL,
@ -19353,6 +19371,8 @@ ALTER TABLE ONLY boards_epic_board_labels ALTER COLUMN id SET DEFAULT nextval('b
ALTER TABLE ONLY boards_epic_board_positions ALTER COLUMN id SET DEFAULT nextval('boards_epic_board_positions_id_seq'::regclass);
ALTER TABLE ONLY boards_epic_board_recent_visits ALTER COLUMN id SET DEFAULT nextval('boards_epic_board_recent_visits_id_seq'::regclass);
ALTER TABLE ONLY boards_epic_boards ALTER COLUMN id SET DEFAULT nextval('boards_epic_boards_id_seq'::regclass);
ALTER TABLE ONLY boards_epic_list_user_preferences ALTER COLUMN id SET DEFAULT nextval('boards_epic_list_user_preferences_id_seq'::regclass);
@ -20468,6 +20488,9 @@ ALTER TABLE ONLY boards_epic_board_labels
ALTER TABLE ONLY boards_epic_board_positions
ADD CONSTRAINT boards_epic_board_positions_pkey PRIMARY KEY (id);
ALTER TABLE ONLY boards_epic_board_recent_visits
ADD CONSTRAINT boards_epic_board_recent_visits_pkey PRIMARY KEY (id);
ALTER TABLE ONLY boards_epic_boards
ADD CONSTRAINT boards_epic_boards_pkey PRIMARY KEY (id);
@ -22293,6 +22316,12 @@ CREATE INDEX index_boards_epic_board_positions_on_epic_id ON boards_epic_board_p
CREATE INDEX index_boards_epic_board_positions_on_scoped_relative_position ON boards_epic_board_positions USING btree (epic_board_id, epic_id, relative_position);
CREATE INDEX index_boards_epic_board_recent_visits_on_epic_board_id ON boards_epic_board_recent_visits USING btree (epic_board_id);
CREATE INDEX index_boards_epic_board_recent_visits_on_group_id ON boards_epic_board_recent_visits USING btree (group_id);
CREATE INDEX index_boards_epic_board_recent_visits_on_user_id ON boards_epic_board_recent_visits USING btree (user_id);
CREATE INDEX index_boards_epic_boards_on_group_id ON boards_epic_boards USING btree (group_id);
CREATE INDEX index_boards_epic_list_user_preferences_on_epic_list_id ON boards_epic_list_user_preferences USING btree (epic_list_id);
@ -22857,6 +22886,8 @@ CREATE INDEX index_environments_on_state_and_auto_stop_at ON environments USING
CREATE UNIQUE INDEX index_epic_board_list_preferences_on_user_and_list ON boards_epic_list_user_preferences USING btree (user_id, epic_list_id);
CREATE UNIQUE INDEX index_epic_board_recent_visits_on_user_group_and_board ON boards_epic_board_recent_visits USING btree (user_id, group_id, epic_board_id);
CREATE INDEX index_epic_issues_on_epic_id ON epic_issues USING btree (epic_id);
CREATE INDEX index_epic_issues_on_epic_id_and_issue_id ON epic_issues USING btree (epic_id, issue_id);
@ -26626,6 +26657,9 @@ ALTER TABLE ONLY packages_rubygems_metadata
ALTER TABLE ONLY packages_pypi_metadata
ADD CONSTRAINT fk_rails_9698717cdd FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE;
ALTER TABLE ONLY boards_epic_board_recent_visits
ADD CONSTRAINT fk_rails_96c2c18642 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
ALTER TABLE ONLY packages_dependency_links
ADD CONSTRAINT fk_rails_96ef1c00d3 FOREIGN KEY (package_id) REFERENCES packages_packages(id) ON DELETE CASCADE;
@ -26890,6 +26924,9 @@ ALTER TABLE ONLY pages_deployments
ALTER TABLE ONLY merge_request_user_mentions
ADD CONSTRAINT fk_rails_c440b9ea31 FOREIGN KEY (note_id) REFERENCES notes(id) ON DELETE CASCADE;
ALTER TABLE ONLY boards_epic_board_recent_visits
ADD CONSTRAINT fk_rails_c4dcba4a3e FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY ci_job_artifacts
ADD CONSTRAINT fk_rails_c5137cb2c1 FOREIGN KEY (job_id) REFERENCES ci_builds(id) ON DELETE CASCADE;
@ -27067,6 +27104,9 @@ ALTER TABLE ONLY draft_notes
ALTER TABLE ONLY namespace_package_settings
ADD CONSTRAINT fk_rails_e773444769 FOREIGN KEY (namespace_id) REFERENCES namespaces(id) ON DELETE CASCADE;
ALTER TABLE ONLY boards_epic_board_recent_visits
ADD CONSTRAINT fk_rails_e77911cf03 FOREIGN KEY (epic_board_id) REFERENCES boards_epic_boards(id) ON DELETE CASCADE;
ALTER TABLE ONLY dast_site_tokens
ADD CONSTRAINT fk_rails_e84f721a8e FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;

View File

@ -6,7 +6,7 @@ group: Access
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# GitLab authentication and authorization
# GitLab authentication and authorization **(FREE SELF)**
GitLab integrates with the following external authentication and authorization
providers:

View File

@ -5,7 +5,7 @@ group: Access
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Atlassian OmniAuth Provider
# Atlassian OmniAuth Provider **(FREE SELF)**
To enable the Atlassian OmniAuth provider for passwordless authentication you must register an application with Atlassian.

View File

@ -5,7 +5,7 @@ group: Access
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Authentiq OmniAuth Provider
# Authentiq OmniAuth Provider **(FREE SELF)**
To enable the Authentiq OmniAuth provider for passwordless authentication you must register an application with Authentiq.

View File

@ -5,7 +5,7 @@ group: Access
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Amazon Web Services Cognito
# Amazon Web Services Cognito **(FREE SELF)**
Amazon Cognito lets you add user sign-up, sign-in, and access control to your GitLab instance.
The following documentation enables Cognito as an OAuth2 provider.

View File

@ -5,7 +5,7 @@ group: Access
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Atlassian Crowd OmniAuth Provider
# Atlassian Crowd OmniAuth Provider **(FREE SELF)**
Authenticate to GitLab using the Atlassian Crowd OmniAuth provider. Enabling
this provider also allows Crowd authentication for Git-over-https requests.

View File

@ -5,7 +5,7 @@ group: Access
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# JWT OmniAuth provider
# JWT OmniAuth provider **(FREE SELF)**
To enable the JWT OmniAuth provider, you must register your application with JWT.
JWT will provide you with a secret key for you to use.

View File

@ -5,7 +5,7 @@ group: Access
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# LDAP Troubleshooting for Administrators
# LDAP Troubleshooting for Administrators **(FREE SELF)**
## Common Problems & Workflows

View File

@ -5,7 +5,7 @@ group: Access
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# OpenID Connect OmniAuth provider
# OpenID Connect OmniAuth provider **(FREE SELF)**
GitLab can use [OpenID Connect](https://openid.net/specs/openid-connect-core-1_0.html) as an OmniAuth provider.

View File

@ -2563,6 +2563,25 @@ Input type: `IterationCadenceUpdateInput`
| <a id="mutationiterationcadenceupdateerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationiterationcadenceupdateiterationcadence"></a>`iterationCadence` | [`IterationCadence`](#iterationcadence) | The updated iteration cadence. |
### `Mutation.iterationDelete`
Input type: `IterationDeleteInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationiterationdeleteclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationiterationdeleteid"></a>`id` | [`IterationID!`](#iterationid) | ID of the iteration. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationiterationdeleteclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationiterationdeleteerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. |
| <a id="mutationiterationdeletegroup"></a>`group` | [`Group!`](#group) | Group the iteration belongs to. |
### `Mutation.jiraImportStart`
Input type: `JiraImportStartInput`

View File

@ -588,7 +588,7 @@ NOTE:
| `SECURE_ANALYZERS_PREFIX` | Specify the Docker registry base address from which to download the analyzer. |
| `FUZZAPI_VERSION` | Specify API Fuzzing container version. Defaults to `latest`. |
| `FUZZAPI_TARGET_URL` | Base URL of API testing target. |
|[`FUZZAPI_CONFIG`](#configuration-files) | API Fuzzing configuration file. Defaults to `.gitlab-apifuzzer.yml`. |
|[`FUZZAPI_CONFIG`](#configuration-files) | [Deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/276395) in GitLab 13.12, replaced with default `.gitlab/gitlab-api-fuzzing-config.yml`. API Fuzzing configuration file. |
|[`FUZZAPI_PROFILE`](#configuration-files) | Configuration profile to use during testing. Defaults to `Quick`. |
|[`FUZZAPI_OPENAPI`](#openapi-specification) | OpenAPI specification file or URL. |
|[`FUZZAPI_HAR`](#http-archive-har) | HTTP Archive (HAR) file. |

View File

@ -3,7 +3,7 @@
module QA
RSpec.describe 'Create', quarantine: { only: { subdomain: :staging }, issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/323990', type: :flaky } do
describe 'Merge request rebasing' do
it 'user rebases source branch of merge request', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1274' do
it 'user rebases source branch of merge request', testcase: 'https://gitlab.com/gitlab-org/quality/testcases/-/issues/1800' do
Flow::Login.sign_in
project = Resource::Project.fabricate_via_api! do |project|

View File

@ -10,9 +10,9 @@ const mockAlert = mockAlerts[0];
describe('Alert Details Sidebar Status', () => {
let wrapper;
const findStatusDropdown = () => wrapper.find(GlDropdown);
const findStatusDropdownItem = () => wrapper.find(GlDropdownItem);
const findStatusLoadingIcon = () => wrapper.find(GlLoadingIcon);
const findStatusDropdown = () => wrapper.findComponent(GlDropdown);
const findStatusDropdownItem = () => wrapper.findComponent(GlDropdownItem);
const findStatusLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findStatusDropdownHeader = () => wrapper.findByTestId('dropdown-header');
const findAlertStatus = () => wrapper.findComponent(AlertStatus);
const findStatus = () => wrapper.findByTestId('status');

View File

@ -3,9 +3,8 @@
require 'spec_helper'
RSpec.describe BoardGroupRecentVisit do
let(:user) { create(:user) }
let(:group) { create(:group) }
let(:board) { create(:board, group: group) }
let_it_be(:board_parent) { create(:group) }
let_it_be(:board) { create(:board, group: board_parent) }
describe 'relationships' do
it { is_expected.to belong_to(:user) }
@ -19,56 +18,9 @@ RSpec.describe BoardGroupRecentVisit do
it { is_expected.to validate_presence_of(:board) }
end
describe '#visited' do
it 'creates a visit if one does not exists' do
expect { described_class.visited!(user, board) }.to change(described_class, :count).by(1)
end
shared_examples 'was visited previously' do
let!(:visit) { create :board_group_recent_visit, group: board.group, board: board, user: user, updated_at: 7.days.ago }
it 'updates the timestamp' do
freeze_time do
described_class.visited!(user, board)
expect(described_class.count).to eq 1
expect(described_class.first.updated_at).to be_like_time(Time.zone.now)
end
end
end
it_behaves_like 'was visited previously'
context 'when we try to create a visit that is not unique' do
before do
expect(described_class).to receive(:find_or_create_by).and_raise(ActiveRecord::RecordNotUnique, 'record not unique')
expect(described_class).to receive(:find_or_create_by).and_return(visit)
end
it_behaves_like 'was visited previously'
end
end
describe '#latest' do
def create_visit(time)
create :board_group_recent_visit, group: group, user: user, updated_at: time
end
it 'returns the most recent visited' do
create_visit(7.days.ago)
create_visit(5.days.ago)
recent = create_visit(1.day.ago)
expect(described_class.latest(user, group)).to eq recent
end
it 'returns last 3 visited boards' do
create_visit(7.days.ago)
visit1 = create_visit(3.days.ago)
visit2 = create_visit(2.days.ago)
visit3 = create_visit(5.days.ago)
expect(described_class.latest(user, group, count: 3)).to eq([visit2, visit1, visit3])
end
it_behaves_like 'boards recent visit' do
let_it_be(:board_relation) { :board }
let_it_be(:board_parent_relation) { :group }
let_it_be(:visit_relation) { :board_group_recent_visit }
end
end

View File

@ -3,9 +3,8 @@
require 'spec_helper'
RSpec.describe BoardProjectRecentVisit do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:board) { create(:board, project: project) }
let_it_be(:board_parent) { create(:project) }
let_it_be(:board) { create(:board, project: board_parent) }
describe 'relationships' do
it { is_expected.to belong_to(:user) }
@ -19,56 +18,9 @@ RSpec.describe BoardProjectRecentVisit do
it { is_expected.to validate_presence_of(:board) }
end
describe '#visited' do
it 'creates a visit if one does not exists' do
expect { described_class.visited!(user, board) }.to change(described_class, :count).by(1)
end
shared_examples 'was visited previously' do
let!(:visit) { create :board_project_recent_visit, project: board.project, board: board, user: user, updated_at: 7.days.ago }
it 'updates the timestamp' do
freeze_time do
described_class.visited!(user, board)
expect(described_class.count).to eq 1
expect(described_class.first.updated_at).to be_like_time(Time.zone.now)
end
end
end
it_behaves_like 'was visited previously'
context 'when we try to create a visit that is not unique' do
before do
expect(described_class).to receive(:find_or_create_by).and_raise(ActiveRecord::RecordNotUnique, 'record not unique')
expect(described_class).to receive(:find_or_create_by).and_return(visit)
end
it_behaves_like 'was visited previously'
end
end
describe '#latest' do
def create_visit(time)
create :board_project_recent_visit, project: project, user: user, updated_at: time
end
it 'returns the most recent visited' do
create_visit(7.days.ago)
create_visit(5.days.ago)
recent = create_visit(1.day.ago)
expect(described_class.latest(user, project)).to eq recent
end
it 'returns last 3 visited boards' do
create_visit(7.days.ago)
visit1 = create_visit(3.days.ago)
visit2 = create_visit(2.days.ago)
visit3 = create_visit(5.days.ago)
expect(described_class.latest(user, project, count: 3)).to eq([visit2, visit1, visit3])
end
it_behaves_like 'boards recent visit' do
let_it_be(:board_relation) { :board }
let_it_be(:board_parent_relation) { :project }
let_it_be(:visit_relation) { :board_project_recent_visit }
end
end

View File

@ -7,47 +7,20 @@ RSpec.describe Boards::Visits::CreateService do
let(:user) { create(:user) }
context 'when a project board' do
let(:project) { create(:project) }
let(:project_board) { create(:board, project: project) }
let_it_be(:project) { create(:project) }
let_it_be(:board) { create(:board, project: project) }
subject(:service) { described_class.new(project_board.resource_parent, user) }
let_it_be(:model) { BoardProjectRecentVisit }
it 'returns nil when there is no user' do
service.current_user = nil
expect(service.execute(project_board)).to eq nil
end
it 'returns nil when database is read-only' do
allow(Gitlab::Database).to receive(:read_only?) { true }
expect(service.execute(project_board)).to eq nil
end
it 'records the visit' do
expect(BoardProjectRecentVisit).to receive(:visited!).once
service.execute(project_board)
end
it_behaves_like 'boards recent visit create service'
end
context 'when a group board' do
let(:group) { create(:group) }
let(:group_board) { create(:board, group: group) }
let_it_be(:group) { create(:group) }
let_it_be(:board) { create(:board, group: group) }
let_it_be(:model) { BoardGroupRecentVisit }
subject(:service) { described_class.new(group_board.resource_parent, user) }
it 'returns nil when there is no user' do
service.current_user = nil
expect(service.execute(group_board)).to eq nil
end
it 'records the visit' do
expect(BoardGroupRecentVisit).to receive(:visited!).once
service.execute(group_board)
end
it_behaves_like 'boards recent visit create service'
end
end
end

View File

@ -0,0 +1,65 @@
# frozen_string_literal: true
RSpec.shared_examples 'boards recent visit' do
let_it_be(:user) { create(:user) }
describe '#visited' do
it 'creates a visit if one does not exists' do
expect { described_class.visited!(user, board) }.to change(described_class, :count).by(1)
end
shared_examples 'was visited previously' do
let_it_be(:visit) do
create(visit_relation,
board_parent_relation => board_parent,
board_relation => board,
user: user,
updated_at: 7.days.ago
)
end
it 'updates the timestamp' do
freeze_time do
described_class.visited!(user, board)
expect(described_class.count).to eq 1
expect(described_class.first.updated_at).to be_like_time(Time.zone.now)
end
end
end
it_behaves_like 'was visited previously'
context 'when we try to create a visit that is not unique' do
before do
expect(described_class).to receive(:find_or_create_by).and_raise(ActiveRecord::RecordNotUnique, 'record not unique')
expect(described_class).to receive(:find_or_create_by).and_return(visit)
end
it_behaves_like 'was visited previously'
end
end
describe '#latest' do
def create_visit(time)
create(visit_relation, board_parent_relation => board_parent, user: user, updated_at: time)
end
it 'returns the most recent visited' do
create_visit(7.days.ago)
create_visit(5.days.ago)
recent = create_visit(1.day.ago)
expect(described_class.latest(user, board_parent)).to eq recent
end
it 'returns last 3 visited boards' do
create_visit(7.days.ago)
visit1 = create_visit(3.days.ago)
visit2 = create_visit(2.days.ago)
visit3 = create_visit(5.days.ago)
expect(described_class.latest(user, board_parent, count: 3)).to eq([visit2, visit1, visit3])
end
end
end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
RSpec.shared_examples 'boards recent visit create service' do
let_it_be(:user) { create(:user) }
subject(:service) { described_class.new(board.resource_parent, user) }
it 'returns nil when there is no user' do
service.current_user = nil
expect(service.execute(board)).to be_nil
end
it 'returns nil when database is read only' do
allow(Gitlab::Database).to receive(:read_only?) { true }
expect(service.execute(board)).to be_nil
end
it 'records the visit' do
expect(model).to receive(:visited!).once
service.execute(board)
end
end