From 271e563cf65b75346f55cd496259a5673c7ff1ea Mon Sep 17 00:00:00 2001 From: GitLab Bot Date: Tue, 11 May 2021 00:10:50 +0000 Subject: [PATCH] Add latest changes from gitlab-org/gitlab@master --- .../components/sidebar/sidebar_status.vue | 3 +- app/controllers/concerns/boards_actions.rb | 6 +- app/models/board_group_recent_visit.rb | 20 ++---- app/models/board_project_recent_visit.rb | 20 ++---- app/models/concerns/board_recent_visit.rb | 34 ++++++++++ app/services/boards/visits/create_service.rb | 16 +++-- .../321625-epic_boards-redirect.yml | 5 ++ ...4929_add_epic_board_recent_visits_table.rb | 22 +++++++ ...0_add_index_to_epic_board_recent_visits.rb | 20 ++++++ db/schema_migrations/20260421104929 | 1 + db/schema_migrations/20260421104930 | 1 + db/structure.sql | 40 ++++++++++++ doc/administration/auth/README.md | 2 +- doc/administration/auth/atlassian.md | 2 +- doc/administration/auth/authentiq.md | 2 +- doc/administration/auth/cognito.md | 2 +- doc/administration/auth/crowd.md | 2 +- doc/administration/auth/jwt.md | 2 +- .../auth/ldap/ldap-troubleshooting.md | 2 +- doc/administration/auth/oidc.md | 2 +- doc/api/graphql/reference/index.md | 19 ++++++ .../application_security/api_fuzzing/index.md | 2 +- .../rebase_merge_request_spec.rb | 2 +- .../sidebar/alert_sidebar_status_spec.js | 6 +- spec/models/board_group_recent_visit_spec.rb | 60 ++--------------- .../models/board_project_recent_visit_spec.rb | 60 ++--------------- .../boards/visits/create_service_spec.rb | 43 +++--------- .../boards_recent_visit_shared_examples.rb | 65 +++++++++++++++++++ .../boards/create_service_shared_examples.rb | 25 +++++++ 29 files changed, 294 insertions(+), 192 deletions(-) create mode 100644 app/models/concerns/board_recent_visit.rb create mode 100644 changelogs/unreleased/321625-epic_boards-redirect.yml create mode 100644 db/migrate/20260421104929_add_epic_board_recent_visits_table.rb create mode 100644 db/migrate/20260421104930_add_index_to_epic_board_recent_visits.rb create mode 100644 db/schema_migrations/20260421104929 create mode 100644 db/schema_migrations/20260421104930 create mode 100644 spec/support/shared_examples/services/boards/boards_recent_visit_shared_examples.rb create mode 100644 spec/support/shared_examples/services/boards/create_service_shared_examples.rb diff --git a/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_status.vue b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_status.vue index 2a792dfa19a..feb023fb3d5 100644 --- a/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_status.vue +++ b/app/assets/javascripts/vue_shared/alert_details/components/sidebar/sidebar_status.vue @@ -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 {

(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 diff --git a/app/models/board_project_recent_visit.rb b/app/models/board_project_recent_visit.rb index 509c8f97b83..723afd6feab 100644 --- a/app/models/board_project_recent_visit.rb +++ b/app/models/board_project_recent_visit.rb @@ -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 diff --git a/app/models/concerns/board_recent_visit.rb b/app/models/concerns/board_recent_visit.rb new file mode 100644 index 00000000000..fd4d574ac58 --- /dev/null +++ b/app/models/concerns/board_recent_visit.rb @@ -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 diff --git a/app/services/boards/visits/create_service.rb b/app/services/boards/visits/create_service.rb index 428ed1a8bcc..4d659596803 100644 --- a/app/services/boards/visits/create_service.rb +++ b/app/services/boards/visits/create_service.rb @@ -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 diff --git a/changelogs/unreleased/321625-epic_boards-redirect.yml b/changelogs/unreleased/321625-epic_boards-redirect.yml new file mode 100644 index 00000000000..9f5600a1e12 --- /dev/null +++ b/changelogs/unreleased/321625-epic_boards-redirect.yml @@ -0,0 +1,5 @@ +--- +title: Redirect to the last visited epic board +merge_request: 60720 +author: +type: added diff --git a/db/migrate/20260421104929_add_epic_board_recent_visits_table.rb b/db/migrate/20260421104929_add_epic_board_recent_visits_table.rb new file mode 100644 index 00000000000..da69f14e34c --- /dev/null +++ b/db/migrate/20260421104929_add_epic_board_recent_visits_table.rb @@ -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 diff --git a/db/migrate/20260421104930_add_index_to_epic_board_recent_visits.rb b/db/migrate/20260421104930_add_index_to_epic_board_recent_visits.rb new file mode 100644 index 00000000000..1341886c50c --- /dev/null +++ b/db/migrate/20260421104930_add_index_to_epic_board_recent_visits.rb @@ -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 diff --git a/db/schema_migrations/20260421104929 b/db/schema_migrations/20260421104929 new file mode 100644 index 00000000000..58ae3012455 --- /dev/null +++ b/db/schema_migrations/20260421104929 @@ -0,0 +1 @@ +c92824844732aad7277fc8b50bac0bf6919ad0a8d72e73b4ec3b89eafc085b7d \ No newline at end of file diff --git a/db/schema_migrations/20260421104930 b/db/schema_migrations/20260421104930 new file mode 100644 index 00000000000..d32c8ad7c7a --- /dev/null +++ b/db/schema_migrations/20260421104930 @@ -0,0 +1 @@ +ae3c8336cb25efa7d23357a6777c0656dbe1a216efb5d4edcf923d1128f7e1e3 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 8f05390ae4a..8b7d6737916 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -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; diff --git a/doc/administration/auth/README.md b/doc/administration/auth/README.md index ef82c556468..a072cc73c43 100644 --- a/doc/administration/auth/README.md +++ b/doc/administration/auth/README.md @@ -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: diff --git a/doc/administration/auth/atlassian.md b/doc/administration/auth/atlassian.md index 9c5da558b0d..365236748b9 100644 --- a/doc/administration/auth/atlassian.md +++ b/doc/administration/auth/atlassian.md @@ -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. diff --git a/doc/administration/auth/authentiq.md b/doc/administration/auth/authentiq.md index dbc5e446287..2eab4555c85 100644 --- a/doc/administration/auth/authentiq.md +++ b/doc/administration/auth/authentiq.md @@ -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. diff --git a/doc/administration/auth/cognito.md b/doc/administration/auth/cognito.md index f264321ea6c..de5fa991abe 100644 --- a/doc/administration/auth/cognito.md +++ b/doc/administration/auth/cognito.md @@ -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. diff --git a/doc/administration/auth/crowd.md b/doc/administration/auth/crowd.md index 440d956fc8f..f83710ef4c7 100644 --- a/doc/administration/auth/crowd.md +++ b/doc/administration/auth/crowd.md @@ -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. diff --git a/doc/administration/auth/jwt.md b/doc/administration/auth/jwt.md index 59d00265a1b..b74b70ee8c0 100644 --- a/doc/administration/auth/jwt.md +++ b/doc/administration/auth/jwt.md @@ -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. diff --git a/doc/administration/auth/ldap/ldap-troubleshooting.md b/doc/administration/auth/ldap/ldap-troubleshooting.md index ecb521b0689..1e6684751ed 100644 --- a/doc/administration/auth/ldap/ldap-troubleshooting.md +++ b/doc/administration/auth/ldap/ldap-troubleshooting.md @@ -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 diff --git a/doc/administration/auth/oidc.md b/doc/administration/auth/oidc.md index 9e3f6cd51de..30ca7d94a1e 100644 --- a/doc/administration/auth/oidc.md +++ b/doc/administration/auth/oidc.md @@ -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. diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 8628f956b99..3626fe428b1 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -2563,6 +2563,25 @@ Input type: `IterationCadenceUpdateInput` | `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | | `iterationCadence` | [`IterationCadence`](#iterationcadence) | The updated iteration cadence. | +### `Mutation.iterationDelete` + +Input type: `IterationDeleteInput` + +#### Arguments + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| `id` | [`IterationID!`](#iterationid) | ID of the iteration. | + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | +| `errors` | [`[String!]!`](#string) | Errors encountered during execution of the mutation. | +| `group` | [`Group!`](#group) | Group the iteration belongs to. | + ### `Mutation.jiraImportStart` Input type: `JiraImportStartInput` diff --git a/doc/user/application_security/api_fuzzing/index.md b/doc/user/application_security/api_fuzzing/index.md index bc6ce132566..bb5ab2e4c79 100644 --- a/doc/user/application_security/api_fuzzing/index.md +++ b/doc/user/application_security/api_fuzzing/index.md @@ -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. | diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb index 34669eb3815..ca0eeb2403c 100644 --- a/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb +++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/rebase_merge_request_spec.rb @@ -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| diff --git a/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js index 1b069c1fe90..80eea6c19c2 100644 --- a/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js +++ b/spec/frontend/vue_shared/alert_details/sidebar/alert_sidebar_status_spec.js @@ -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'); diff --git a/spec/models/board_group_recent_visit_spec.rb b/spec/models/board_group_recent_visit_spec.rb index c6fbd263072..d2d287d8e24 100644 --- a/spec/models/board_group_recent_visit_spec.rb +++ b/spec/models/board_group_recent_visit_spec.rb @@ -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 diff --git a/spec/models/board_project_recent_visit_spec.rb b/spec/models/board_project_recent_visit_spec.rb index 145a4f5b1a7..262c3a8faaa 100644 --- a/spec/models/board_project_recent_visit_spec.rb +++ b/spec/models/board_project_recent_visit_spec.rb @@ -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 diff --git a/spec/services/boards/visits/create_service_spec.rb b/spec/services/boards/visits/create_service_spec.rb index 64faa2cf07b..8910345d170 100644 --- a/spec/services/boards/visits/create_service_spec.rb +++ b/spec/services/boards/visits/create_service_spec.rb @@ -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 diff --git a/spec/support/shared_examples/services/boards/boards_recent_visit_shared_examples.rb b/spec/support/shared_examples/services/boards/boards_recent_visit_shared_examples.rb new file mode 100644 index 00000000000..68ea460dabc --- /dev/null +++ b/spec/support/shared_examples/services/boards/boards_recent_visit_shared_examples.rb @@ -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 diff --git a/spec/support/shared_examples/services/boards/create_service_shared_examples.rb b/spec/support/shared_examples/services/boards/create_service_shared_examples.rb new file mode 100644 index 00000000000..63b5e3a5a84 --- /dev/null +++ b/spec/support/shared_examples/services/boards/create_service_shared_examples.rb @@ -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