diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 5de63def412..21c95eb8520 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -8c3a416f8fbd8e1187e8d7869bd77f933e91be1b +3f702fcbdb3481eade8c18815a61f97d01886cef diff --git a/app/assets/images/learn_gitlab/code_owners_enabled.svg b/app/assets/images/learn_gitlab/code_owners_enabled.svg new file mode 100644 index 00000000000..019d74c64cc --- /dev/null +++ b/app/assets/images/learn_gitlab/code_owners_enabled.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/app/assets/images/learn_gitlab/git_write.svg b/app/assets/images/learn_gitlab/git_write.svg new file mode 100644 index 00000000000..ad87b3f3b12 --- /dev/null +++ b/app/assets/images/learn_gitlab/git_write.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/app/assets/images/learn_gitlab/merge_request_created.svg b/app/assets/images/learn_gitlab/merge_request_created.svg new file mode 100644 index 00000000000..b8137a60f06 --- /dev/null +++ b/app/assets/images/learn_gitlab/merge_request_created.svg @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/learn_gitlab/pipeline_created.svg b/app/assets/images/learn_gitlab/pipeline_created.svg new file mode 100644 index 00000000000..91c716be475 --- /dev/null +++ b/app/assets/images/learn_gitlab/pipeline_created.svg @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/learn_gitlab/required_mr_approvals_enabled.svg b/app/assets/images/learn_gitlab/required_mr_approvals_enabled.svg new file mode 100644 index 00000000000..027767368a6 --- /dev/null +++ b/app/assets/images/learn_gitlab/required_mr_approvals_enabled.svg @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/learn_gitlab/security_scan_enabled.svg b/app/assets/images/learn_gitlab/security_scan_enabled.svg new file mode 100644 index 00000000000..eea0693484c --- /dev/null +++ b/app/assets/images/learn_gitlab/security_scan_enabled.svg @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/assets/images/learn_gitlab/trial_started.svg b/app/assets/images/learn_gitlab/trial_started.svg new file mode 100644 index 00000000000..42d6fb6c013 --- /dev/null +++ b/app/assets/images/learn_gitlab/trial_started.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/app/assets/images/learn_gitlab/user_added.svg b/app/assets/images/learn_gitlab/user_added.svg new file mode 100644 index 00000000000..efbccff0bbb --- /dev/null +++ b/app/assets/images/learn_gitlab/user_added.svg @@ -0,0 +1,4 @@ + + + + diff --git a/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue b/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue index 22a767cfaae..af4e9acf4ba 100644 --- a/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue +++ b/app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue @@ -1,16 +1,7 @@ @@ -158,7 +89,7 @@ export default { - - - - - - {{ issueTransitionOption.label }} - - - - - - diff --git a/app/assets/javascripts/integrations/edit/index.js b/app/assets/javascripts/integrations/edit/index.js index 1ae353ab6e3..ab9bdd9ca2e 100644 --- a/app/assets/javascripts/integrations/edit/index.js +++ b/app/assets/javascripts/integrations/edit/index.js @@ -28,7 +28,6 @@ function parseDatasetToProps(data) { testPath, resetPath, vulnerabilitiesIssuetype, - jiraIssueTransitionId, ...booleanAttributes } = data; const { @@ -60,7 +59,6 @@ function parseDatasetToProps(data) { initialTriggerMergeRequest: mergeRequestEvents, initialEnableComments: enableComments, initialCommentDetail: commentDetail, - initialJiraIssueTransitionId: jiraIssueTransitionId, }, jiraIssuesProps: { showJiraIssuesIntegration, diff --git a/app/assets/javascripts/jobs/store/getters.js b/app/assets/javascripts/jobs/store/getters.js index 930a225857d..6cb96bee07d 100644 --- a/app/assets/javascripts/jobs/store/getters.js +++ b/app/assets/javascripts/jobs/store/getters.js @@ -1,7 +1,7 @@ import { isEmpty, isString } from 'lodash'; import { isScrolledToBottom } from '~/lib/utils/scroll_utils'; -export const headerTime = (state) => state.job.started ?? state.job.created_at; +export const headerTime = (state) => (state.job.started ? state.job.started : state.job.created_at); export const hasForwardDeploymentFailure = (state) => state?.job?.failure_reason === 'forward_deployment_failure'; diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 4cbe0a53307..f4b60fc0961 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -119,7 +119,7 @@ export default class MilestoneSelect { title: __('Any milestone'), }); } - if (showNo) { + if (showNo && term.trim() === '') { extraOptions.push({ id: -1, name: __('No milestone'), diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_a.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_a.vue index 0393793bfe1..32ca623ca45 100644 --- a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_a.vue +++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_a.vue @@ -1,11 +1,11 @@ diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_info_card.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_info_card.vue new file mode 100644 index 00000000000..3d2a8eed9d4 --- /dev/null +++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_info_card.vue @@ -0,0 +1,70 @@ + + diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js b/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js index 8606af29785..80f04b0cf44 100644 --- a/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js +++ b/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js @@ -1,12 +1,50 @@ import { s__ } from '~/locale'; -export const ACTION_TEXT = { - gitWrite: s__('LearnGitLab|Create a repository'), - userAdded: s__('LearnGitLab|Invite your colleagues'), - pipelineCreated: s__('LearnGitLab|Set-up CI/CD'), - trialStarted: s__('LearnGitLab|Start a free trial of GitLab Gold'), - codeOwnersEnabled: s__('LearnGitLab|Add code owners'), - requiredMrApprovalsEnabled: s__('LearnGitLab|Enable require merge approvals'), - mergeRequestCreated: s__('LearnGitLab|Submit a merge request (MR)'), - securityScanEnabled: s__('LearnGitLab|Run a Security scan using CI/CD'), +export const ACTION_LABELS = { + gitWrite: { + title: s__('LearnGitLab|Create or import a repository'), + actionLabel: s__('LearnGitLab|Create or import a repository'), + description: s__('LearnGitLab|Create or import your first repository into your new project.'), + }, + userAdded: { + title: s__('LearnGitLab|Invite your colleagues'), + actionLabel: s__('LearnGitLab|Invite your colleagues'), + description: s__( + 'LearnGitLab|GitLab works best as a team. Invite your colleague to enjoy all features.', + ), + }, + pipelineCreated: { + title: s__('LearnGitLab|Set up CI/CD'), + actionLabel: s__('LearnGitLab|Set-up CI/CD'), + description: s__('LearnGitLab|Save time by automating your integration and deployment tasks.'), + }, + trialStarted: { + title: s__('LearnGitLab|Start a free Ultimate trial'), + actionLabel: s__('LearnGitLab|Try GitLab Ultimate for free'), + description: s__('LearnGitLab|Try all GitLab features for 30 days, no credit card required.'), + }, + codeOwnersEnabled: { + title: s__('LearnGitLab|Add code owners'), + actionLabel: s__('LearnGitLab|Add code owners'), + description: s__( + 'LearnGitLab|Prevent unexpected changes to important assets by assigning ownership of files and paths.', + ), + trialRequired: true, + }, + requiredMrApprovalsEnabled: { + title: s__('LearnGitLab|Add merge request approval'), + actionLabel: s__('LearnGitLab|Enable require merge approvals'), + description: s__('LearnGitLab|Route code reviews to the right reviewers, every time.'), + trialRequired: true, + }, + mergeRequestCreated: { + title: s__('LearnGitLab|Submit a merge request'), + actionLabel: s__('LearnGitLab|Submit a merge request (MR)'), + description: s__('LearnGitLab|Review and edit proposed changes to source code.'), + }, + securityScanEnabled: { + title: s__('LearnGitLab|Run a security scan'), + actionLabel: s__('LearnGitLab|Run a Security scan'), + description: s__('LearnGitLab|Scan your code to uncover vulnerabilities before deploying.'), + }, }; diff --git a/app/assets/javascripts/pages/projects/milestones/new/index.js b/app/assets/javascripts/pages/projects/milestones/new/index.js index 9a4ebf9890d..364b0d95d9c 100644 --- a/app/assets/javascripts/pages/projects/milestones/new/index.js +++ b/app/assets/javascripts/pages/projects/milestones/new/index.js @@ -1,3 +1,3 @@ import initForm from '../../../../shared/milestones/form'; -document.addEventListener('DOMContentLoaded', () => initForm()); +initForm(); diff --git a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue index b4a818e2472..ae1c8f53604 100644 --- a/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue +++ b/app/assets/javascripts/pipeline_editor/pipeline_editor_app.vue @@ -186,11 +186,13 @@ export default { this.showSuccessAlert = false; }, reportFailure(type, reasons = []) { + window.scrollTo({ top: 0, behavior: 'smooth' }); this.showFailureAlert = true; this.failureType = type; this.failureReasons = reasons; }, reportSuccess(type) { + window.scrollTo({ top: 0, behavior: 'smooth' }); this.showSuccessAlert = true; this.successType = type; }, diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 4852700d878..6acb13476a8 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -430,8 +430,7 @@ } } - a[href*='/uploads/'], - a[href*='storage.googleapis.com/google-code-attachments/'] { + a.with-attachment-icon { &::before { margin-right: 4px; @@ -441,6 +440,11 @@ -webkit-font-smoothing: antialiased; content: '📎'; } + } + + a[href*='/uploads/'], + a[href*='storage.googleapis.com/google-code-attachments/'] { + @extend .with-attachment-icon; &.no-attachment-icon { &::before { diff --git a/app/assets/stylesheets/page_bundles/learn_gitlab.scss b/app/assets/stylesheets/page_bundles/learn_gitlab.scss new file mode 100644 index 00000000000..189aefb330b --- /dev/null +++ b/app/assets/stylesheets/page_bundles/learn_gitlab.scss @@ -0,0 +1,3 @@ +.learn-gitlab-info-card-content { + height: 200px; +} diff --git a/app/controllers/projects/ci/daily_build_group_report_results_controller.rb b/app/controllers/projects/ci/daily_build_group_report_results_controller.rb index aabcb74cefa..08ae9da9b41 100644 --- a/app/controllers/projects/ci/daily_build_group_report_results_controller.rb +++ b/app/controllers/projects/ci/daily_build_group_report_results_controller.rb @@ -3,7 +3,6 @@ class Projects::Ci::DailyBuildGroupReportResultsController < Projects::ApplicationController include Gitlab::Utils::StrongMemoize - MAX_ITEMS = 1000 REPORT_WINDOW = 90.days before_action :authorize_read_build_report_results! @@ -40,17 +39,13 @@ class Projects::Ci::DailyBuildGroupReportResultsController < Projects::Applicati end def report_results - if ::Gitlab::Ci::Features.use_coverage_data_new_finder?(project) - ::Ci::Testing::DailyBuildGroupReportResultsFinder.new( - params: new_finder_params, - current_user: current_user - ).execute - else - Ci::DailyBuildGroupReportResultsFinder.new(**finder_params).execute - end + ::Ci::DailyBuildGroupReportResultsFinder.new( + params: finder_params, + current_user: current_user + ).execute end - def new_finder_params + def finder_params { project: project, coverage: true, @@ -61,17 +56,6 @@ class Projects::Ci::DailyBuildGroupReportResultsController < Projects::Applicati } end - def finder_params - { - current_user: current_user, - project: project, - ref_path: params.require(:ref_path), - start_date: start_date, - end_date: end_date, - limit: MAX_ITEMS - } - end - def start_date strong_memoize(:start_date) do start_date = Date.parse(params.require(:start_date)) diff --git a/app/finders/ci/daily_build_group_report_results_finder.rb b/app/finders/ci/daily_build_group_report_results_finder.rb index ef97ccb4c0f..976f3a21b72 100644 --- a/app/finders/ci/daily_build_group_report_results_finder.rb +++ b/app/finders/ci/daily_build_group_report_results_finder.rb @@ -1,56 +1,86 @@ # frozen_string_literal: true +# DailyBuildGroupReportResultsFinder +# +# Used to filter DailyBuildGroupReportResults by set of params +# +# Arguments: +# current_user +# params: +# project: integer +# group: integer +# coverage: boolean +# ref_path: string +# start_date: date +# end_date: date +# sort: boolean +# limit: integer + module Ci class DailyBuildGroupReportResultsFinder include Gitlab::Allowable - def initialize(current_user:, project:, ref_path: nil, start_date:, end_date:, limit: nil) + MAX_ITEMS = 1_000 + + attr_reader :params, :current_user + + def initialize(params: {}, current_user: nil) + @params = params @current_user = current_user - @project = project - @ref_path = ref_path - @start_date = start_date - @end_date = end_date - @limit = limit end def execute - return none unless query_allowed? + return Ci::DailyBuildGroupReportResult.none unless query_allowed? - query + collection = Ci::DailyBuildGroupReportResult.by_projects(params[:project]) + collection = filter_report_results(collection) + collection end - protected - - attr_reader :current_user, :project, :ref_path, :start_date, :end_date, :limit - - def query - Ci::DailyBuildGroupReportResult.recent_results( - query_params, - limit: limit - ) - end + private def query_allowed? - can?(current_user, :read_build_report_results, project) + can?(current_user, :read_build_report_results, params[:project]) end - def query_params - params = { - project_id: project, - date: start_date..end_date - } + def filter_report_results(collection) + collection = by_coverage(collection) + collection = by_ref_path(collection) + collection = by_dates(collection) - if ref_path.present? - params[:ref_path] = ref_path - else - params[:default_branch] = true - end - - params + collection = sort(collection) + collection = limit_by(collection) + collection end - def none - Ci::DailyBuildGroupReportResult.none + def by_coverage(items) + params[:coverage].present? ? items.with_coverage : items + end + + def by_ref_path(items) + params[:ref_path].present? ? items.by_ref_path(params[:ref_path]) : items.with_default_branch + end + + def by_dates(items) + params[:start_date].present? && params[:end_date].present? ? items.by_dates(params[:start_date], params[:end_date]) : items + end + + def sort(items) + params[:sort].present? ? items.ordered_by_date_and_group_name : items + end + + # rubocop: disable CodeReuse/ActiveRecord + def limit_by(items) + items.limit(limit) + end + # rubocop: enable CodeReuse/ActiveRecord + + def limit + return MAX_ITEMS unless params[:limit].present? + + [params[:limit].to_i, MAX_ITEMS].min end end end + +Ci::DailyBuildGroupReportResultsFinder.prepend_if_ee('::EE::Ci::DailyBuildGroupReportResultsFinder') diff --git a/app/finders/ci/testing/daily_build_group_report_results_finder.rb b/app/finders/ci/testing/daily_build_group_report_results_finder.rb deleted file mode 100644 index 70d9e55dc47..00000000000 --- a/app/finders/ci/testing/daily_build_group_report_results_finder.rb +++ /dev/null @@ -1,88 +0,0 @@ -# frozen_string_literal: true - -# DailyBuildGroupReportResultsFinder -# -# Used to filter DailyBuildGroupReportResults by set of params -# -# Arguments: -# current_user -# params: -# project: integer -# group: integer -# coverage: boolean -# ref_path: string -# start_date: date -# end_date: date -# sort: boolean -# limit: integer - -module Ci - module Testing - class DailyBuildGroupReportResultsFinder - include Gitlab::Allowable - - MAX_ITEMS = 1_000 - - attr_reader :params, :current_user - - def initialize(params: {}, current_user: nil) - @params = params - @current_user = current_user - end - - def execute - return Ci::DailyBuildGroupReportResult.none unless query_allowed? - - collection = Ci::DailyBuildGroupReportResult.by_projects(params[:project]) - collection = filter_report_results(collection) - collection - end - - private - - def query_allowed? - can?(current_user, :read_build_report_results, params[:project]) - end - - def filter_report_results(collection) - collection = by_coverage(collection) - collection = by_ref_path(collection) - collection = by_dates(collection) - - collection = sort(collection) - collection = limit_by(collection) - collection - end - - def by_coverage(items) - params[:coverage].present? ? items.with_coverage : items - end - - def by_ref_path(items) - params[:ref_path].present? ? items.by_ref_path(params[:ref_path]) : items.with_default_branch - end - - def by_dates(items) - params[:start_date].present? && params[:end_date].present? ? items.by_dates(params[:start_date], params[:end_date]) : items - end - - def sort(items) - params[:sort].present? ? items.ordered_by_date_and_group_name : items - end - - # rubocop: disable CodeReuse/ActiveRecord - def limit_by(items) - items.limit(limit) - end - # rubocop: enable CodeReuse/ActiveRecord - - def limit - return MAX_ITEMS unless params[:limit].present? - - [params[:limit].to_i, MAX_ITEMS].min - end - end - end -end - -Ci::Testing::DailyBuildGroupReportResultsFinder.prepend_if_ee('::EE::Ci::Testing::DailyBuildGroupReportResultsFinder') diff --git a/app/finders/repositories/previous_tag_finder.rb b/app/finders/repositories/previous_tag_finder.rb index 150a6332c29..cf27132975d 100644 --- a/app/finders/repositories/previous_tag_finder.rb +++ b/app/finders/repositories/previous_tag_finder.rb @@ -16,12 +16,13 @@ module Repositories # This finder expects that all tags to consider meet the following # requirements: # - # * They start with the letter "v" - # * They use semantic versioning for the tag format + # * They start with the letter "v" followed by a version, or immediately start + # with a version + # * They use semantic versioning for the version format # # Tags not meeting these requirements are ignored. class PreviousTagFinder - TAG_REGEX = /\Av(?#{Gitlab::Regex.unbounded_semver_regex})\z/.freeze + TAG_REGEX = /\Av?(?#{Gitlab::Regex.unbounded_semver_regex})\z/.freeze def initialize(project) @project = project diff --git a/app/graphql/mutations/notes/create/diff_note.rb b/app/graphql/mutations/notes/create/diff_note.rb index 9b5f3092006..019e7cb8623 100644 --- a/app/graphql/mutations/notes/create/diff_note.rb +++ b/app/graphql/mutations/notes/create/diff_note.rb @@ -11,6 +11,22 @@ module Mutations required: true, description: copy_field_description(Types::Notes::NoteType, :position) + def ready?(**args) + # As both arguments are optional, validate here that one of the + # arguments are present. + # + # This may be able to be done using InputUnions in the future + # if this RFC is merged: + # https://github.com/graphql/graphql-spec/blob/master/rfcs/InputUnion.md + + if args[:position].to_hash.values_at(:old_line, :new_line).compact.blank? + raise Gitlab::Graphql::Errors::ArgumentError, + 'position oldLine or newLine arguments are required' + end + + super(**args) + end + private def create_note_params(noteable, args) diff --git a/app/graphql/types/notes/diff_position_input_type.rb b/app/graphql/types/notes/diff_position_input_type.rb index 02c91e173cb..b8716a30fc7 100644 --- a/app/graphql/types/notes/diff_position_input_type.rb +++ b/app/graphql/types/notes/diff_position_input_type.rb @@ -8,7 +8,7 @@ module Types argument :old_line, GraphQL::INT_TYPE, required: false, description: copy_field_description(Types::Notes::DiffPositionType, :old_line) - argument :new_line, GraphQL::INT_TYPE, required: true, + argument :new_line, GraphQL::INT_TYPE, required: false, description: copy_field_description(Types::Notes::DiffPositionType, :new_line) end # rubocop: enable Graphql/AuthorizeTypes diff --git a/app/helpers/learn_gitlab_helper.rb b/app/helpers/learn_gitlab_helper.rb index e72a9c83fc9..2fa2f582044 100644 --- a/app/helpers/learn_gitlab_helper.rb +++ b/app/helpers/learn_gitlab_helper.rb @@ -15,7 +15,8 @@ module LearnGitlabHelper [ action, url: url, - completed: attributes[OnboardingProgress.column_name(action)].present? + completed: attributes[OnboardingProgress.column_name(action)].present?, + svg: image_path("learn_gitlab/#{action}.svg") ] end.to_h end @@ -25,7 +26,7 @@ module LearnGitlabHelper ACTION_ISSUE_IDS = { git_write: 2, pipeline_created: 4, - merge_request_created: 6, + merge_request_created: 5, user_added: 7, trial_started: 13, required_mr_approvals_enabled: 15, diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb index 8fc63dcdb3a..14d20e7c622 100644 --- a/app/helpers/services_helper.rb +++ b/app/helpers/services_helper.rb @@ -86,7 +86,7 @@ module ServicesHelper end def integration_form_data(integration, group: nil) - form_data = { + { id: integration.id, show_active: integration.show_active_box?.to_s, activated: (integration.active || integration.new_record?).to_s, @@ -106,12 +106,6 @@ module ServicesHelper test_path: scoped_test_integration_path(integration), reset_path: scoped_reset_integration_path(integration, group: group) } - - if integration.is_a?(JiraService) - form_data[:jira_issue_transition_id] = integration.jira_issue_transition_id - end - - form_data end def trigger_events_for_service(integration) diff --git a/app/models/ci/daily_build_group_report_result.rb b/app/models/ci/daily_build_group_report_result.rb index 23c96e63724..81c4221aefa 100644 --- a/app/models/ci/daily_build_group_report_result.rb +++ b/app/models/ci/daily_build_group_report_result.rb @@ -30,10 +30,6 @@ module Ci upsert_all(data, unique_by: :index_daily_build_group_report_results_unique_columns) if data.any? end - def recent_results(attrs, limit: nil) - where(attrs).order(date: :desc, group_name: :asc).limit(limit) - end - def report_window(start_date) default_date = REPORT_WINDOW.ago.to_date date = Date.parse(start_date) rescue default_date diff --git a/app/models/pages/lookup_path.rb b/app/models/pages/lookup_path.rb index 46c347e5cf5..cbae0120006 100644 --- a/app/models/pages/lookup_path.rb +++ b/app/models/pages/lookup_path.rb @@ -43,8 +43,6 @@ module Pages def deployment strong_memoize(:deployment) do - next unless Feature.enabled?(:pages_serve_from_deployments, project, default_enabled: true) - project.pages_metadatum.pages_deployment end end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb index 12f7bac23e4..5857d86f921 100644 --- a/app/models/project_services/jira_service.rb +++ b/app/models/project_services/jira_service.rb @@ -124,11 +124,15 @@ class JiraService < IssueTrackerService end def fields + transition_id_help_path = help_page_path('user/project/integrations/jira', anchor: 'obtaining-a-transition-id') + transition_id_help_link_start = ''.html_safe % { transition_id_help_path: transition_id_help_path } + [ { type: 'text', name: 'url', title: s_('JiraService|Web URL'), placeholder: 'https://jira.example.com', required: true }, { type: 'text', name: 'api_url', title: s_('JiraService|Jira API URL'), placeholder: s_('JiraService|If different from Web URL') }, { type: 'text', name: 'username', title: s_('JiraService|Username or Email'), placeholder: s_('JiraService|Use a username for server version and an email for cloud version'), required: true }, - { type: 'password', name: 'password', title: s_('JiraService|Password or API token'), placeholder: s_('JiraService|Use a password for server version and an API token for cloud version'), required: true } + { type: 'password', name: 'password', title: s_('JiraService|Password or API token'), placeholder: s_('JiraService|Use a password for server version and an API token for cloud version'), required: true }, + { type: 'text', name: 'jira_issue_transition_id', title: s_('JiraService|Jira workflow transition IDs'), placeholder: s_('JiraService|For example, 12, 24'), help: s_('JiraService|Set transition IDs for Jira workflow transitions. %{link_start}Learn more%{link_end}'.html_safe % { link_start: transition_id_help_link_start, link_end: ''.html_safe }) } ] end @@ -155,19 +159,17 @@ class JiraService < IssueTrackerService # support any events. end - def find_issue(issue_key, rendered_fields: false, transitions: false) - expands = [] - expands << 'renderedFields' if rendered_fields - expands << 'transitions' if transitions - options = { expand: expands.join(',') } if expands.any? + def find_issue(issue_key, rendered_fields: false) + options = {} + options = options.merge(expand: 'renderedFields') if rendered_fields - jira_request { client.Issue.find(issue_key, options || {}) } + jira_request { client.Issue.find(issue_key, options) } end def close_issue(entity, external_issue, current_user) - issue = find_issue(external_issue.iid, transitions: automatic_issue_transitions?) + issue = find_issue(external_issue.iid) - return if issue.nil? || has_resolution?(issue) + return if issue.nil? || has_resolution?(issue) || !jira_issue_transition_id.present? commit_id = case entity when Commit then entity.id @@ -258,54 +260,26 @@ class JiraService < IssueTrackerService end end - def automatic_issue_transitions? - jira_issue_transition_id.blank? - end - # jira_issue_transition_id can have multiple values split by , or ; # the issue is transitioned at the order given by the user # if any transition fails it will log the error message and stop the transition sequence def transition_issue(issue) - return transition_issue_to_done(issue) if automatic_issue_transitions? - - jira_issue_transition_id.scan(Gitlab::Regex.jira_transition_id_regex).all? do |transition_id| - transition_issue_to_id(issue, transition_id) + jira_issue_transition_id.scan(Gitlab::Regex.jira_transition_id_regex).each do |transition_id| + issue.transitions.build.save!(transition: { id: transition_id }) + rescue => error + log_error( + "Issue transition failed", + error: { + exception_class: error.class.name, + exception_message: error.message, + exception_backtrace: Gitlab::BacktraceCleaner.clean_backtrace(error.backtrace) + }, + client_url: client_url + ) + return false end end - def transition_issue_to_id(issue, transition_id) - issue.transitions.build.save!( - transition: { id: transition_id } - ) - - true - rescue => error - log_error( - "Issue transition failed", - error: { - exception_class: error.class.name, - exception_message: error.message, - exception_backtrace: Gitlab::BacktraceCleaner.clean_backtrace(error.backtrace) - }, - client_url: client_url - ) - - false - end - - def transition_issue_to_done(issue) - transitions = issue.transitions rescue [] - - transition = transitions.find do |transition| - status = transition&.to&.statusCategory - status && status['key'] == 'done' - end - - return false unless transition - - transition_issue_to_id(issue, transition.id) - end - def log_usage(action, user) key = "i_ecosystem_jira_service_#{action}" diff --git a/app/services/ci/register_job_service.rb b/app/services/ci/register_job_service.rb index 59691fe4ef3..cd3db4a0205 100644 --- a/app/services/ci/register_job_service.rb +++ b/app/services/ci/register_job_service.rb @@ -6,15 +6,11 @@ module Ci class RegisterJobService attr_reader :runner - JOB_QUEUE_DURATION_SECONDS_BUCKETS = [1, 3, 10, 30, 60, 300, 900, 1800, 3600].freeze - JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET = 5.freeze - METRICS_SHARD_TAG_PREFIX = 'metrics_shard::' - DEFAULT_METRICS_SHARD = 'default' - Result = Struct.new(:build, :build_json, :valid?) def initialize(runner) @runner = runner + @metrics = ::Gitlab::Ci::Queue::Metrics.new(runner) end # rubocop: disable CodeReuse/ActiveRecord @@ -48,7 +44,7 @@ module Ci next unless result if result.valid? - register_success(result.build) + @metrics.register_success(result.build) return result else @@ -58,7 +54,7 @@ module Ci end end - register_failure + @metrics.register_failure Result.new(nil, nil, valid) end # rubocop: enable CodeReuse/ActiveRecord @@ -189,48 +185,6 @@ module Ci builds end - def register_failure - failed_attempt_counter.increment - attempt_counter.increment - end - - def register_success(job) - labels = { shared_runner: runner.instance_type?, - jobs_running_for_project: jobs_running_for_project(job), - shard: DEFAULT_METRICS_SHARD } - - if runner.instance_type? - shard = runner.tag_list.sort.find { |name| name.starts_with?(METRICS_SHARD_TAG_PREFIX) } - labels[:shard] = shard.gsub(METRICS_SHARD_TAG_PREFIX, '') if shard - end - - job_queue_duration_seconds.observe(labels, Time.current - job.queued_at) unless job.queued_at.nil? - attempt_counter.increment - end - - # rubocop: disable CodeReuse/ActiveRecord - def jobs_running_for_project(job) - return '+Inf' unless runner.instance_type? - - # excluding currently started job - running_jobs_count = job.project.builds.running.where(runner: Ci::Runner.instance_type) - .limit(JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET + 1).count - 1 - running_jobs_count < JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET ? running_jobs_count : "#{JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET}+" - end - # rubocop: enable CodeReuse/ActiveRecord - - def failed_attempt_counter - @failed_attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_failed_total, "Counts the times a runner tries to register a job") - end - - def attempt_counter - @attempt_counter ||= Gitlab::Metrics.counter(:job_register_attempts_total, "Counts the times a runner tries to register a job") - end - - def job_queue_duration_seconds - @job_queue_duration_seconds ||= Gitlab::Metrics.histogram(:job_queue_duration_seconds, 'Request handling execution time', {}, JOB_QUEUE_DURATION_SECONDS_BUCKETS) - end - def pre_assign_runner_checks { missing_dependency_failure: -> (build, _) { !build.has_valid_build_dependencies? }, diff --git a/app/services/concerns/alert_management/alert_processing.rb b/app/services/concerns/alert_management/alert_processing.rb index 9b15c5d7b4b..7b6f681fe3e 100644 --- a/app/services/concerns/alert_management/alert_processing.rb +++ b/app/services/concerns/alert_management/alert_processing.rb @@ -41,14 +41,21 @@ module AlertManagement end def process_resolved_alert - return unless auto_close_incident? - return close_issue(alert.issue) if alert.resolve(incoming_payload.ends_at) + SystemNoteService.log_resolving_alert(alert, alert_source) - logger.warn( - message: 'Unable to update AlertManagement::Alert status to resolved', - project_id: project.id, - alert_id: alert.id - ) + return unless auto_close_incident? + + if alert.resolve(incoming_payload.ends_at) + SystemNoteService.change_alert_status(alert, User.alert_bot) + + close_issue(alert.issue) + else + logger.warn( + message: 'Unable to update AlertManagement::Alert status to resolved', + project_id: project.id, + alert_id: alert.id + ) + end end def process_firing_alert diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb index 67d388dc8a3..01539d58545 100644 --- a/app/services/projects/update_pages_configuration_service.rb +++ b/app/services/projects/update_pages_configuration_service.rb @@ -11,6 +11,8 @@ module Projects end def execute + return success unless Feature.enabled?(:pages_update_legacy_storage, default_enabled: true) + # If the pages were never deployed, we can't write out the config, as the # directory would not exist. # https://gitlab.com/gitlab-org/gitlab/-/issues/235139 diff --git a/app/services/system_note_service.rb b/app/services/system_note_service.rb index 580acda747a..7b1234f802d 100644 --- a/app/services/system_note_service.rb +++ b/app/services/system_note_service.rb @@ -327,6 +327,10 @@ module SystemNoteService ::SystemNotes::IncidentService.new(noteable: incident, project: incident.project, author: author).change_incident_severity end + def log_resolving_alert(alert, monitoring_tool) + ::SystemNotes::AlertManagementService.new(noteable: alert, project: alert.project).log_resolving_alert(monitoring_tool) + end + private def merge_requests_service(noteable, project, author) diff --git a/app/services/system_notes/alert_management_service.rb b/app/services/system_notes/alert_management_service.rb index 376f2c1cfbf..27ddf2e36f1 100644 --- a/app/services/system_notes/alert_management_service.rb +++ b/app/services/system_notes/alert_management_service.rb @@ -62,5 +62,20 @@ module SystemNotes create_note(NoteSummary.new(noteable, project, author, body, action: 'status')) end + + # Called when an alert is resolved due to received resolving alert payload + # + # alert - AlertManagement::Alert object. + # + # Example Note text: + # + # "changed the status to Resolved by closing issue #17" + # + # Returns the created Note object + def log_resolving_alert(monitoring_tool) + body = "logged a resolving alert from **#{monitoring_tool}**" + + create_note(NoteSummary.new(noteable, project, User.alert_bot, body, action: 'new_alert_added')) + end end end diff --git a/app/views/projects/learn_gitlab/index.html.haml b/app/views/projects/learn_gitlab/index.html.haml index d5fdbc10eb4..94023b21aab 100644 --- a/app/views/projects/learn_gitlab/index.html.haml +++ b/app/views/projects/learn_gitlab/index.html.haml @@ -1,4 +1,5 @@ - breadcrumb_title _("Learn GitLab") - page_title _("Learn GitLab") +- add_page_specific_style 'page_bundles/learn_gitlab' #js-learn-gitlab-app{ data: { actions: onboarding_actions_data(@project).to_json } } diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index 4ff5ab515ec..e7926202579 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -9,7 +9,7 @@ %section.settings#js-general-pipeline-settings.no-animate{ class: ('expanded' if general_expanded), data: { qa_selector: 'general_pipelines_settings_content' } } .settings-header - %h4 + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only = _("General pipelines") %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' } = expanded ? _('Collapse') : _('Expand') @@ -20,7 +20,7 @@ %section.settings#autodevops-settings.no-animate{ class: ('expanded' if expanded), data: { qa_selector: 'autodevops_settings_content' } } .settings-header - %h4 + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only = s_('CICD|Auto DevOps') %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' } = expanded ? _('Collapse') : _('Expand') @@ -37,7 +37,7 @@ %section.settings.no-animate#js-runners-settings{ class: ('expanded' if expanded || params[:expand_runners]), data: { qa_selector: 'runners_settings_content' } } .settings-header - %h4 + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only = _("Runners") %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' } = expanded ? _('Collapse') : _('Expand') @@ -50,7 +50,7 @@ - if Gitlab::CurrentSettings.current_application_settings.keep_latest_artifact? %section.settings.no-animate#js-artifacts-settings{ class: ('expanded' if expanded) } .settings-header - %h4 + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only = _("Artifacts") %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' } = expanded ? _('Collapse') : _('Expand') @@ -67,7 +67,7 @@ %section.settings.no-animate#js-pipeline-triggers{ class: ('expanded' if expanded) } .settings-header - %h4 + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only = _("Pipeline triggers") %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' } = expanded ? _('Collapse') : _('Expand') @@ -80,7 +80,7 @@ - if settings_container_registry_expiration_policy_available?(@project) %section.settings.no-animate#js-registry-policies{ class: ('expanded' if expanded) } .settings-header - %h4 + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only = _("Clean up image tags") %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' } = expanded ? _('Collapse') : _('Expand') @@ -95,7 +95,7 @@ - if can?(current_user, :create_freeze_period, @project) %section.settings.no-animate#js-deploy-freeze-settings{ class: ('expanded' if expanded) } .settings-header - %h4 + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only = _("Deploy freezes") %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' } = expanded ? _('Collapse') : _('Expand') diff --git a/app/views/shared/form_elements/_apply_template_warning.html.haml b/app/views/shared/form_elements/_apply_template_warning.html.haml index 73be0c741dc..61c0e5c42f4 100644 --- a/app/views/shared/form_elements/_apply_template_warning.html.haml +++ b/app/views/shared/form_elements/_apply_template_warning.html.haml @@ -7,7 +7,7 @@ %p = _("Applying a template will replace the existing issue description. Any changes you have made will be lost.") - %button.js-override-template.btn.btn-warning.mr-2{ type: 'button' } + %button.js-override-template.btn.gl-button.btn-confirm.mr-2{ type: 'button' } = _("Apply template") - %button.js-close-btn.js-cancel-btn.btn.btn-inverted{ type: 'button' } + %button.js-close-btn.js-cancel-btn.btn.gl-button.btn-default{ type: 'button' } = _("Cancel") diff --git a/app/workers/pages_update_configuration_worker.rb b/app/workers/pages_update_configuration_worker.rb index 07238bae8c2..6e82e2099c7 100644 --- a/app/workers/pages_update_configuration_worker.rb +++ b/app/workers/pages_update_configuration_worker.rb @@ -6,6 +6,12 @@ class PagesUpdateConfigurationWorker idempotent! feature_category :pages + def self.perform_async(*args) + return unless Feature.enabled?(:pages_update_legacy_storage, default_enabled: true) + + super(*args) + end + def perform(project_id) project = Project.find_by_id(project_id) return unless project diff --git a/changelogs/unreleased/16119-jira-issue-transition-automatic.yml b/changelogs/unreleased/16119-jira-issue-transition-automatic.yml deleted file mode 100644 index 7d2a7a7b6c0..00000000000 --- a/changelogs/unreleased/16119-jira-issue-transition-automatic.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Support automatic transitions of Jira issues -merge_request: 53760 -author: -type: changed diff --git a/changelogs/unreleased/205484-01-project-settings-headers-cicd.yml b/changelogs/unreleased/205484-01-project-settings-headers-cicd.yml new file mode 100644 index 00000000000..9353ccfef31 --- /dev/null +++ b/changelogs/unreleased/205484-01-project-settings-headers-cicd.yml @@ -0,0 +1,5 @@ +--- +title: Project Settings CI/CD headers expand/collapse on click / tap +merge_request: 54114 +author: Daniel Schömer +type: changed diff --git a/changelogs/unreleased/262859-restrict-oncall-rotation-to-interval.yml b/changelogs/unreleased/262859-restrict-oncall-rotation-to-interval.yml new file mode 100644 index 00000000000..bb6ca6da372 --- /dev/null +++ b/changelogs/unreleased/262859-restrict-oncall-rotation-to-interval.yml @@ -0,0 +1,5 @@ +--- +title: Add active period columns to on-call rotations. +merge_request: 52998 +author: +type: added diff --git a/changelogs/unreleased/auto-scroll-pipeline-editor-on-commit.yml b/changelogs/unreleased/auto-scroll-pipeline-editor-on-commit.yml new file mode 100644 index 00000000000..af2a3ab9974 --- /dev/null +++ b/changelogs/unreleased/auto-scroll-pipeline-editor-on-commit.yml @@ -0,0 +1,5 @@ +--- +title: Auto-scroll to top of page upon committing in pipeline editor +merge_request: 54657 +author: +type: changed diff --git a/changelogs/unreleased/gl-button-apply-template.yml b/changelogs/unreleased/gl-button-apply-template.yml new file mode 100644 index 00000000000..cf03a388a69 --- /dev/null +++ b/changelogs/unreleased/gl-button-apply-template.yml @@ -0,0 +1,5 @@ +--- +title: Apply new GitLab UI for apply template button in new issue +merge_request: 54143 +author: Yogi (@yo) +type: changed diff --git a/changelogs/unreleased/mo-refactor-coverage-finder.yml b/changelogs/unreleased/mo-refactor-coverage-finder.yml new file mode 100644 index 00000000000..a064f148f6a --- /dev/null +++ b/changelogs/unreleased/mo-refactor-coverage-finder.yml @@ -0,0 +1,5 @@ +--- +title: Remove coverage_data_new_finder feature flag +merge_request: 54486 +author: +type: performance diff --git a/changelogs/unreleased/ph-282476-fixGraphQLDiffComments.yml b/changelogs/unreleased/ph-282476-fixGraphQLDiffComments.yml new file mode 100644 index 00000000000..c7b5acf018d --- /dev/null +++ b/changelogs/unreleased/ph-282476-fixGraphQLDiffComments.yml @@ -0,0 +1,5 @@ +--- +title: Fixed diff notes GraphQL mutation not allowing comments on deleted lines +merge_request: 54801 +author: +type: fixed diff --git a/changelogs/unreleased/relax-changelog-tag-restriction.yml b/changelogs/unreleased/relax-changelog-tag-restriction.yml new file mode 100644 index 00000000000..ef8e74dc797 --- /dev/null +++ b/changelogs/unreleased/relax-changelog-tag-restriction.yml @@ -0,0 +1,5 @@ +--- +title: Relax tag requirements when generating changelogs +merge_request: 54832 +author: +type: changed diff --git a/changelogs/unreleased/sy-system-note-for-resolving-alert.yml b/changelogs/unreleased/sy-system-note-for-resolving-alert.yml new file mode 100644 index 00000000000..04a1b3e6807 --- /dev/null +++ b/changelogs/unreleased/sy-system-note-for-resolving-alert.yml @@ -0,0 +1,5 @@ +--- +title: Create system note on alert when its auto-resolved via alert integration +merge_request: 54645 +author: +type: added diff --git a/config/application.rb b/config/application.rb index 7860c999b65..e9400fef186 100644 --- a/config/application.rb +++ b/config/application.rb @@ -196,6 +196,7 @@ module Gitlab config.assets.precompile << "page_bundles/iterations.css" config.assets.precompile << "page_bundles/jira_connect.css" config.assets.precompile << "page_bundles/jira_connect_users.css" + config.assets.precompile << "page_bundles/learn_gitlab.css" config.assets.precompile << "page_bundles/merge_conflicts.css" config.assets.precompile << "page_bundles/merge_requests.css" config.assets.precompile << "page_bundles/milestone.css" diff --git a/config/feature_flags/development/clear_status_with_quick_options.yml b/config/feature_flags/development/clear_status_with_quick_options.yml deleted file mode 100644 index 3a8af1569c2..00000000000 --- a/config/feature_flags/development/clear_status_with_quick_options.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: clear_status_with_quick_options -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53620 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/320777 -milestone: '13.9' -type: development -group: group::optimize -default_enabled: true diff --git a/config/feature_flags/development/coverage_data_new_finder.yml b/config/feature_flags/development/coverage_data_new_finder.yml deleted file mode 100644 index a7c283ce3db..00000000000 --- a/config/feature_flags/development/coverage_data_new_finder.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: coverage_data_new_finder -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/53670 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/301093 -milestone: '13.9' -type: development -group: group::testing -default_enabled: false diff --git a/config/feature_flags/development/pages_serve_from_deployments.yml b/config/feature_flags/development/pages_serve_from_deployments.yml deleted file mode 100644 index d13bf2dfaa5..00000000000 --- a/config/feature_flags/development/pages_serve_from_deployments.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: pages_serve_from_deployments -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/46320 -rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/production/-/issues/2932 -milestone: '13.6' -type: development -group: group::release -default_enabled: true diff --git a/data/whats_new/202102180001_13_09.yml b/data/whats_new/202102180001_13_09.yml index d2a48048d8c..314ffc8dba3 100644 --- a/data/whats_new/202102180001_13_09.yml +++ b/data/whats_new/202102180001_13_09.yml @@ -13,9 +13,7 @@ release: 13.9 - title: "GPU and smart scheduling support for GitLab Runner" body: | - Previously, if you wanted to reuse the same configuration in multiple jobs, you had two options: add YAML anchors, which don't work across different configuration files, or use `extends` to reuse an entire section. - - In this release, we've added a new YAML function called `!reference`, which lets you target the exact configuration you want to reuse as part of your CI/CD pipeline, even if it's in another file. + Specialized compute workloads like those used in machine learning can significantly benefit from access to GPUs. Developers can configure GitLab Runner to leverage GPUs in the Docker executor by forwarding the `--gpu` flag. You can also use this with recent support in [GitLab’s fork of Docker Machine](https://docs.gitlab.com/runner/executors/docker_machine.html#using-the-forked-version-of-docker-machine), which allows you to [accelerate workloads with attached GPUs](https://cloud.google.com/compute/docs/gpus/create-vm-with-gpus). Doing so can help control costs associated with potentially expensive machine configurations. stage: Verify self-managed: true gitlab-com: true diff --git a/db/migrate/20210201034649_add_active_periods_to_on_call_rotations.rb b/db/migrate/20210201034649_add_active_periods_to_on_call_rotations.rb new file mode 100644 index 00000000000..714187f60e0 --- /dev/null +++ b/db/migrate/20210201034649_add_active_periods_to_on_call_rotations.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddActivePeriodsToOnCallRotations < ActiveRecord::Migration[6.0] + DOWNTIME = false + + def change + add_column :incident_management_oncall_rotations, :active_period_start, :time, null: true + add_column :incident_management_oncall_rotations, :active_period_end, :time, null: true + end +end diff --git a/db/schema_migrations/20210201034649 b/db/schema_migrations/20210201034649 new file mode 100644 index 00000000000..5cc99ebc893 --- /dev/null +++ b/db/schema_migrations/20210201034649 @@ -0,0 +1 @@ +e5492820a8618d5599429ece04ea941e869c84c22d213d536644bcefc5775363 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 811f80ea9e9..87e6d3e6cbb 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -13217,6 +13217,8 @@ CREATE TABLE incident_management_oncall_rotations ( starts_at timestamp with time zone NOT NULL, name text NOT NULL, ends_at timestamp with time zone, + active_period_start time without time zone, + active_period_end time without time zone, CONSTRAINT check_5209fb5d02 CHECK ((char_length(name) <= 200)) ); diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index a13f6d19b97..df44baf3e71 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -2240,6 +2240,7 @@ Describes an incident management on-call rotation. | Field | Type | Description | | ----- | ---- | ----------- | +| `activePeriod` | OncallRotationActivePeriodType | Active period for the on-call rotation. | | `endsAt` | Time | End date and time of the on-call rotation. | | `id` | IncidentManagementOncallRotationID! | ID of the on-call rotation. | | `length` | Int | Length of the on-call schedule, in the units specified by lengthUnit. | @@ -2948,6 +2949,15 @@ The rotation participant and color palette. | `id` | IncidentManagementOncallParticipantID! | ID of the on-call participant. | | `user` | User! | The user who is participating. | +### OncallRotationActivePeriodType + +Active period time range for on-call rotation. + +| Field | Type | Description | +| ----- | ---- | ----------- | +| `endTime` | String | The end of the rotation active period. | +| `startTime` | String | The start of the rotation active period. | + ### OncallRotationCreatePayload Autogenerated return type of OncallRotationCreate. diff --git a/doc/api/repositories.md b/doc/api/repositories.md index 4c8d2c4b02c..06d39b515a0 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -285,8 +285,8 @@ Example response: Generate changelog data based on commits in a repository. -Given a version (using semantic versioning) and a range of commits, -GitLab generates a changelog for all commits that use a particular +Given a version (using [semantic versioning](https://semver.org/)) and a range +of commits, GitLab generates a changelog for all commits that use a particular [Git trailer](https://git-scm.com/docs/git-interpret-trailers). The output of this process is a new section in a changelog file in the Git @@ -312,15 +312,15 @@ Supported attributes: If the `from` attribute is unspecified, GitLab uses the Git tag of the last version that came before the version specified in the `version` attribute. For -this to work, your project must create Git tags for versions using the -following format: +this to work, your project must create Git tags for versions using one of the +following formats: -```plaintext -vX.Y.Z -``` +- `vX.Y.Z` +- `X.Y.Z` -Where `X.Y.Z` is a version that follows semantic versioning. For example, -consider a project with the following tags: +Where `X.Y.Z` is a version that follows [semantic +versioning](https://semver.org/). For example, consider a project with the +following tags: - v1.0.0 - v1.1.0 diff --git a/doc/api/services.md b/doc/api/services.md index 64daca1793a..765f459e704 100644 --- a/doc/api/services.md +++ b/doc/api/services.md @@ -814,7 +814,7 @@ Parameters: | `username` | string | yes | The username of the user created to be used with GitLab/Jira. | | `password` | string | yes | The password of the user created to be used with GitLab/Jira. | | `active` | boolean | no | Activates or deactivates the service. Defaults to false (deactivated). | -| `jira_issue_transition_id` | string | no | The ID of one or more transitions to move issues to a closed state. Read [custom issue transitions](../user/project/integrations/jira.md#custom-issue-transitions) for details. Defaults to a blank string, which enables [automatic issue transitions](../user/project/integrations/jira.md#automatic-issue-transitions). | +| `jira_issue_transition_id` | string | no | The ID of a transition that moves issues to a closed state. You can find this number under the Jira workflow administration (**Administration > Issues > Workflows**) by selecting **View** under **Operations** of the desired workflow of your project. The ID of each state can be found inside the parenthesis of each transition name under the transitions ID column. By default, this ID is set to `2`. | | `commit_events` | boolean | false | Enable notifications for commit events | | `merge_requests_events` | boolean | false | Enable notifications for merge request events | | `comment_on_event_enabled` | boolean | false | Enable comments inside Jira issues on each GitLab event (commit / merge request) | diff --git a/doc/development/directory_structure.md b/doc/development/directory_structure.md index c2329feb941..c019e5cd027 100644 --- a/doc/development/directory_structure.md +++ b/doc/development/directory_structure.md @@ -34,3 +34,61 @@ module MyDomain end end ``` + +### About namespace naming + +A good guideline for naming a top-level namespace (bounded context) is to use the related +feature category. For example, `Continuous Integration` feature category maps to `Ci::` namespace. + +Alternatively a new class could be added to `Projects::` or `Groups::` if it's either: + +- Strictly related to one of these domains. For example `Projects::Alias`. +- A new component that does not have yet a more specific domain. In this case, when + a more explicit domain does emerge we would need to move the class to a more specific + namespace. + +Do not use the [stage or group name](https://about.gitlab.com/handbook/product/categories/#devops-stages) +since a feature category could be reassigned to a different group in the future. + +```ruby +# bad +module Create + class Commit + end +end + +# good +module Repositories + class Commit + end +end +``` + +On the other hand, a feature category may sometimes be too granular. Features tend to be +treated differently according to Product and Marketing, while they may share a lot of +domain models and behavior under the hood. In this case, having too many bounded contexts +could make them shallow and more coupled with other contexts. + +Bounded contexts (or top-level namespaces) can be seen as macro-components in the overall app. +Good bounded contexts should be [deep](https://medium.com/@nakabonne/depth-of-module-f62dac3c2fdb) +so consider having nested namespaces to further break down complex parts of the domain. +E.g. `Ci::Config::`. + +For example, instead of having separate and granular bounded contexts like: `ContainerScanning::`, +`ContainerHostSecurity::`, `ContainerNetworkSecurity::`, we could have: + +```ruby +module ContainerSecurity + module HostSecurity + end + + module NetworkSecurity + end + + module Scanning + end +end +``` + +If classes that are defined into a namespace have a lot in common with classes in other namespaces, +chances are that these two namespaces are part of the same bounded context. diff --git a/doc/user/application_security/threat_monitoring/index.md b/doc/user/application_security/threat_monitoring/index.md index 4d149382907..747d31df356 100644 --- a/doc/user/application_security/threat_monitoring/index.md +++ b/doc/user/application_security/threat_monitoring/index.md @@ -223,4 +223,6 @@ checkbox **Hide dismissed alerts**. ![Policy Alert List](img/threat_monitoring_policy_alert_list_v13_9.png) +Clicking an alert's name takes the user to the [alert details page](../../../operations/incident_management/alerts.md#alert-details-page). + For information on work in progress for the alerts dashboard, see [this epic](https://gitlab.com/groups/gitlab-org/-/epics/5041). diff --git a/doc/user/packages/nuget_repository/index.md b/doc/user/packages/nuget_repository/index.md index 101bb810a0e..e1b61f28818 100644 --- a/doc/user/packages/nuget_repository/index.md +++ b/doc/user/packages/nuget_repository/index.md @@ -219,7 +219,7 @@ To use the [project-level](#use-the-gitlab-endpoint-for-nuget-packages) Package - + diff --git a/doc/user/project/integrations/jira.md b/doc/user/project/integrations/jira.md index 050f4faa5aa..0878e1c9386 100644 --- a/doc/user/project/integrations/jira.md +++ b/doc/user/project/integrations/jira.md @@ -106,6 +106,8 @@ To enable the Jira integration in a project: 1. To include a comment on the Jira issue when the above reference is made in GitLab, select **Enable comments**. + 1. Select the **Comment detail**: **Standard** or **All details**. + 1. Enter the further details on the page as described in the following table. | Field | Description | @@ -114,6 +116,7 @@ To enable the Jira integration in a project: | `Jira API URL` | The base URL to the Jira instance API. Web URL value is used if not set. For example, `https://jira-api.example.com`. Leave this field blank (or use the same value of `Web URL`) if using **Jira on Atlassian cloud**. | | `Username or Email` | Created in [configure Jira](#configure-jira) step. Use `username` for **Jira Server** or `email` for **Jira on Atlassian cloud**. | | `Password/API token` | Created in [configure Jira](#configure-jira) step. Use `password` for **Jira Server** or `API token` for **Jira on Atlassian cloud**. | + | `Jira workflow transition IDs` | Required for closing Jira issues via commits or merge requests. These are the IDs of transitions in Jira that move issues to a particular state. (See [Obtaining a transition ID](#obtaining-a-transition-id).) If you insert multiple transition IDs separated by `,` or `;`, the issue is moved to each state, one after another, using the given order. In GitLab 13.6 and earlier, field was called `Transition ID`. | 1. To enable users to view Jira issues inside the GitLab project, select **Enable Jira issues** and enter a Jira project key. **(PREMIUM)** @@ -135,19 +138,10 @@ To enable the Jira integration in a project: Your GitLab project can now interact with all Jira projects in your instance and the project now displays a Jira link that opens the Jira project. -#### Automatic issue transitions +#### Obtaining a transition ID -When you [close a Jira issues with a trigger word](../issues/managing_issues.md#closing-issues-automatically), -GitLab by default transitions the issue to the next available status with a category of "Done". - -#### Custom issue transitions - -For advanced workflows you can specify custom Jira transition IDs. If you insert multiple transition IDs separated by `,` or `;`, the issue is moved to each state, one after another, using the given order. - -To see the transition IDs on Jira Cloud, edit a workflow in the **Text** view. -The transition IDs display in the **Transitions** column. - -On Jira Server you can get the transition IDs in either of the following ways: +In the most recent Jira user interface, you can no longer see transition IDs in the workflow +administration UI. You can get the ID you need in either of the following ways: 1. By using the API, with a request like `https://yourcompany.atlassian.net/rest/api/2/issue/ISSUE-123/transitions` using an issue that is in the appropriate "open" state diff --git a/lib/api/users.rb b/lib/api/users.rb index f91e3c34ef2..3c7c9999731 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -1071,10 +1071,7 @@ module API put "status", feature_category: :users do forbidden! unless can?(current_user, :update_user_status, current_user) - update_params = declared_params - update_params.delete(:clear_status_after) if Feature.disabled?(:clear_status_with_quick_options, current_user, default_enabled: :yaml) - - if ::Users::SetStatusService.new(current_user, update_params).execute + if ::Users::SetStatusService.new(current_user, declared_params).execute present current_user.status, with: Entities::UserStatus else render_validation_error!(current_user.status) diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb index 366e57c6b32..06dddc74eba 100644 --- a/lib/banzai/filter/sanitization_filter.rb +++ b/lib/banzai/filter/sanitization_filter.rb @@ -64,3 +64,5 @@ module Banzai end end end + +Banzai::Filter::SanitizationFilter.prepend_if_ee('EE::Banzai::Filter::SanitizationFilter') diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb index ee495226b86..0c549a79393 100644 --- a/lib/gitlab/ci/features.rb +++ b/lib/gitlab/ci/features.rb @@ -71,10 +71,6 @@ module Gitlab def self.display_codequality_backend_comparison?(project) ::Feature.enabled?(:codequality_backend_comparison, project, default_enabled: :yaml) end - - def self.use_coverage_data_new_finder?(record) - ::Feature.enabled?(:coverage_data_new_finder, record, default_enabled: :yaml) - end end end end diff --git a/lib/gitlab/ci/queue/metrics.rb b/lib/gitlab/ci/queue/metrics.rb new file mode 100644 index 00000000000..441f1b1dca1 --- /dev/null +++ b/lib/gitlab/ci/queue/metrics.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + module Queue + class Metrics + extend Gitlab::Utils::StrongMemoize + + QUEUE_DURATION_SECONDS_BUCKETS = [1, 3, 10, 30, 60, 300, 900, 1800, 3600].freeze + METRICS_SHARD_TAG_PREFIX = 'metrics_shard::' + DEFAULT_METRICS_SHARD = 'default' + JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET = 5.freeze + + attr_reader :runner + + def initialize(runner) + @runner = runner + end + + def register_failure + self.class.failed_attempt_counter.increment + self.class.attempt_counter.increment + end + + def register_success(job) + labels = { shared_runner: runner.instance_type?, + jobs_running_for_project: jobs_running_for_project(job), + shard: DEFAULT_METRICS_SHARD } + + if runner.instance_type? + shard = runner.tag_list.sort.find { |name| name.starts_with?(METRICS_SHARD_TAG_PREFIX) } + labels[:shard] = shard.gsub(METRICS_SHARD_TAG_PREFIX, '') if shard + end + + self.class.job_queue_duration_seconds.observe(labels, Time.current - job.queued_at) unless job.queued_at.nil? + self.class.attempt_counter.increment + end + + # rubocop: disable CodeReuse/ActiveRecord + def jobs_running_for_project(job) + return '+Inf' unless runner.instance_type? + + # excluding currently started job + running_jobs_count = job.project.builds.running.where(runner: ::Ci::Runner.instance_type) + .limit(JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET + 1).count - 1 + running_jobs_count < JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET ? running_jobs_count : "#{JOBS_RUNNING_FOR_PROJECT_MAX_BUCKET}+" + end + # rubocop: enable CodeReuse/ActiveRecord + + def self.failed_attempt_counter + strong_memoize(:failed_attempt_counter) do + name = :job_register_attempts_failed_total + comment = 'Counts the times a runner tries to register a job' + + Gitlab::Metrics.counter(name, comment) + end + end + + def self.attempt_counter + strong_memoize(:attempt_counter) do + name = :job_register_attempts_total + comment = 'Counts the times a runner tries to register a job' + + Gitlab::Metrics.counter(name, comment) + end + end + + def self.job_queue_duration_seconds + strong_memoize(:job_queue_duration_seconds) do + name = :job_queue_duration_seconds + comment = 'Request handling execution time' + labels = {} + buckets = QUEUE_DURATION_SECONDS_BUCKETS + + Gitlab::Metrics.histogram(name, comment, labels, buckets) + end + end + end + end + end +end diff --git a/lib/gitlab/ci/variables/collection.rb b/lib/gitlab/ci/variables/collection.rb index 35ece398000..381a5cc41cb 100644 --- a/lib/gitlab/ci/variables/collection.rb +++ b/lib/gitlab/ci/variables/collection.rb @@ -10,13 +10,18 @@ module Gitlab def initialize(variables = [], errors = nil) @variables = [] + @variables_by_key = {} @errors = errors variables.each { |variable| self.append(variable) } end def append(resource) - tap { @variables.append(Collection::Item.fabricate(resource)) } + item = Collection::Item.fabricate(resource) + @variables.append(item) + @variables_by_key[item[:key]] = item + + self end def concat(resources) @@ -36,6 +41,14 @@ module Gitlab end end + def [](key) + @variables_by_key[key] + end + + def size + @variables.size + end + def to_runner_variables self.map(&:to_runner_variable) end diff --git a/lib/gitlab/ci/variables/collection/item.rb b/lib/gitlab/ci/variables/collection/item.rb index 84a9280e507..d32afee2e99 100644 --- a/lib/gitlab/ci/variables/collection/item.rb +++ b/lib/gitlab/ci/variables/collection/item.rb @@ -14,6 +14,10 @@ module Gitlab } end + def value + @variable.fetch(:value) + end + def [](key) @variable.fetch(key) end diff --git a/lib/gitlab/ci/variables/collection/sort.rb b/lib/gitlab/ci/variables/collection/sort.rb index 9f28b1bf504..40f129f62d1 100644 --- a/lib/gitlab/ci/variables/collection/sort.rb +++ b/lib/gitlab/ci/variables/collection/sort.rb @@ -52,7 +52,7 @@ module Gitlab end def tsort_each_child(variable, &block) - each_variable_reference(variable[:value], &block) + each_variable_reference(variable.value, &block) end def input_vars diff --git a/lib/gitlab/experimentation.rb b/lib/gitlab/experimentation.rb index 337c28f3179..a6168dc0d54 100644 --- a/lib/gitlab/experimentation.rb +++ b/lib/gitlab/experimentation.rb @@ -34,10 +34,6 @@ module Gitlab module Experimentation EXPERIMENTS = { - ci_notification_dot: { - tracking_category: 'Growth::Expansion::Experiment::CiNotificationDot', - use_backwards_compatible_subject_index: true - }, upgrade_link_in_user_menu_a: { tracking_category: 'Growth::Expansion::Experiment::UpgradeLinkInUserMenuA', use_backwards_compatible_subject_index: true diff --git a/locale/gitlab.pot b/locale/gitlab.pot index c79746a9785..e883e9e2d48 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -16921,9 +16921,6 @@ msgstr "" msgid "JiraService|An error occurred while fetching issue list" msgstr "" -msgid "JiraService|Automatically transitions Jira issues to the \"Done\" category. %{linkStart}Learn more%{linkEnd}" -msgstr "" - msgid "JiraService|Define the type of Jira issue to create from a vulnerability." msgstr "" @@ -16978,7 +16975,7 @@ msgstr "" msgid "JiraService|Jira project key" msgstr "" -msgid "JiraService|Move to Done" +msgid "JiraService|Jira workflow transition IDs" msgstr "" msgid "JiraService|Not all data may be displayed here. To view more details or make changes to this issue, go to %{linkStart}Jira%{linkEnd}." @@ -16999,7 +16996,7 @@ msgstr "" msgid "JiraService|Select issue type" msgstr "" -msgid "JiraService|Set a custom final state by using transition IDs. %{linkStart}Learn about transition IDs%{linkEnd}" +msgid "JiraService|Set transition IDs for Jira workflow transitions. %{link_start}Learn more%{link_end}" msgstr "" msgid "JiraService|Sign in to GitLab.com to get started." @@ -17011,18 +17008,12 @@ msgstr "" msgid "JiraService|This issue is synchronized with Jira" msgstr "" -msgid "JiraService|Transition Jira issues to their final state:" -msgstr "" - msgid "JiraService|Use a password for server version and an API token for cloud version" msgstr "" msgid "JiraService|Use a username for server version and an email for cloud version" msgstr "" -msgid "JiraService|Use custom transitions" -msgstr "" - msgid "JiraService|Username or Email" msgstr "" @@ -17520,6 +17511,9 @@ msgstr "" msgid "Learn GitLab - Ultimate trial" msgstr "" +msgid "Learn GitLab|Trial only" +msgstr "" + msgid "Learn how to %{link_start}contribute to the built-in templates%{link_end}" msgstr "" @@ -17580,30 +17574,96 @@ msgstr "" msgid "Learn more." msgstr "" +msgid "LearnGitLab|%{percentage}%{percentSymbol} completed" +msgstr "" + msgid "LearnGitLab|Add code owners" msgstr "" -msgid "LearnGitLab|Create a repository" +msgid "LearnGitLab|Add merge request approval" +msgstr "" + +msgid "LearnGitLab|Complete these tasks first so you can enjoy GitLab's features to their fullest:" +msgstr "" + +msgid "LearnGitLab|Create a workflow for your new workspace, and learn how GitLab features work together:" +msgstr "" + +msgid "LearnGitLab|Create or import a repository" +msgstr "" + +msgid "LearnGitLab|Create or import your first repository into your new project." +msgstr "" + +msgid "LearnGitLab|Deploy" msgstr "" msgid "LearnGitLab|Enable require merge approvals" msgstr "" +msgid "LearnGitLab|GitLab works best as a team. Invite your colleague to enjoy all features." +msgstr "" + msgid "LearnGitLab|Invite your colleagues" msgstr "" -msgid "LearnGitLab|Run a Security scan using CI/CD" +msgid "LearnGitLab|Learn GitLab" +msgstr "" + +msgid "LearnGitLab|Plan and execute" +msgstr "" + +msgid "LearnGitLab|Prevent unexpected changes to important assets by assigning ownership of files and paths." +msgstr "" + +msgid "LearnGitLab|Ready to get started with GitLab? Follow these steps to set up your workspace, plan and commit changes, and deploy your project." +msgstr "" + +msgid "LearnGitLab|Review and edit proposed changes to source code." +msgstr "" + +msgid "LearnGitLab|Route code reviews to the right reviewers, every time." +msgstr "" + +msgid "LearnGitLab|Run a Security scan" +msgstr "" + +msgid "LearnGitLab|Run a security scan" +msgstr "" + +msgid "LearnGitLab|Save time by automating your integration and deployment tasks." +msgstr "" + +msgid "LearnGitLab|Scan your code to uncover vulnerabilities before deploying." +msgstr "" + +msgid "LearnGitLab|Set up CI/CD" +msgstr "" + +msgid "LearnGitLab|Set up your workspace" msgstr "" msgid "LearnGitLab|Set-up CI/CD" msgstr "" -msgid "LearnGitLab|Start a free trial of GitLab Gold" +msgid "LearnGitLab|Start a free Ultimate trial" +msgstr "" + +msgid "LearnGitLab|Submit a merge request" msgstr "" msgid "LearnGitLab|Submit a merge request (MR)" msgstr "" +msgid "LearnGitLab|Try GitLab Ultimate for free" +msgstr "" + +msgid "LearnGitLab|Try all GitLab features for 30 days, no credit card required." +msgstr "" + +msgid "LearnGitLab|Use your new GitLab workflow to deploy your application, monitor its health, and keep it secure:" +msgstr "" + msgid "Leave" msgstr "" @@ -25503,6 +25563,9 @@ msgstr "" msgid "Restrict projects for this runner" msgstr "" +msgid "Restricted shift times are not available for hourly shifts" +msgstr "" + msgid "Restricts sign-ups for email addresses that match the given regex. See the %{supported_syntax_link_start}supported syntax%{supported_syntax_link_end} for more information." msgstr "" @@ -30588,6 +30651,9 @@ msgstr "" msgid "ThreatMonitoring|Environment" msgstr "" +msgid "ThreatMonitoring|Events" +msgstr "" + msgid "ThreatMonitoring|Hide dismissed alerts" msgstr "" @@ -35661,6 +35727,9 @@ msgstr "" msgid "must be greater than start date" msgstr "" +msgid "must be later than active period start" +msgstr "" + msgid "must contain only valid frameworks" msgstr "" diff --git a/qa/Gemfile b/qa/Gemfile index f55d27c159b..21ca945a500 100644 --- a/qa/Gemfile +++ b/qa/Gemfile @@ -21,6 +21,7 @@ gem 'rotp', '~> 3.1.0' gem 'timecop', '~> 0.9.1' gem 'parallel', '~> 1.19' gem 'rspec-parameterized', '~> 0.4.2' +gem 'github_api', '~> 0.18.2' group :development do gem 'pry-byebug', '~> 3.5.1', platform: :mri diff --git a/qa/Gemfile.lock b/qa/Gemfile.lock index 3b532d90526..4eeaca1f1a6 100644 --- a/qa/Gemfile.lock +++ b/qa/Gemfile.lock @@ -39,19 +39,31 @@ GEM adamantium (~> 0.2.0) equalizer (~> 0.0.9) concurrent-ruby (1.1.7) + descendants_tracker (0.0.4) + thread_safe (~> 0.3, >= 0.3.1) diff-lcs (1.3) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) equalizer (0.0.11) faker (1.9.3) i18n (>= 0.7) + faraday (0.17.3) + multipart-post (>= 1.2, < 3) + github_api (0.18.2) + addressable (~> 2.4) + descendants_tracker (~> 0.0.4) + faraday (~> 0.8) + hashie (~> 3.5, >= 3.5.2) + oauth2 (~> 1.0) gitlab-qa (4.0.0) + hashie (3.6.0) http-accept (1.7.0) http-cookie (1.0.3) domain_name (~> 0.5) i18n (1.8.5) concurrent-ruby (~> 1.0) ice_nine (0.11.2) + jwt (2.2.2) knapsack (1.17.1) rake launchy (2.4.3) @@ -65,10 +77,19 @@ GEM mini_mime (1.0.2) mini_portile2 (2.5.0) minitest (5.14.2) + multi_json (1.15.0) + multi_xml (0.6.0) + multipart-post (2.1.1) netrc (0.11.0) nokogiri (1.11.1) mini_portile2 (~> 2.5.0) racc (~> 1.4) + oauth2 (1.4.4) + faraday (>= 0.8, < 2.0) + jwt (>= 1.0, < 3.0) + multi_json (~> 1.3) + multi_xml (~> 0.5) + rack (>= 1.2, < 3) parallel (1.19.2) parallel_tests (2.29.0) parallel @@ -155,6 +176,7 @@ DEPENDENCIES capybara (~> 3.29.0) capybara-screenshot (~> 1.0.23) faker (~> 1.6, >= 1.6.6) + github_api (~> 0.18.2) gitlab-qa knapsack (~> 1.17) nokogiri (~> 1.11.1) diff --git a/qa/qa/page/project/import/github.rb b/qa/qa/page/project/import/github.rb index 6890c7de9f8..081d5303cbb 100644 --- a/qa/qa/page/project/import/github.rb +++ b/qa/qa/page/project/import/github.rb @@ -30,6 +30,7 @@ module QA set_path(full_path, name) import_project(full_path) wait_for_success + go_to_project(name) end private @@ -73,6 +74,13 @@ module QA page.has_content?('Done', wait: 1.0) end end + + def go_to_project(name) + Page::Main::Menu.perform(&:go_to_projects) + Page::Dashboard::Projects.perform do |dashboard| + dashboard.go_to_project(name) + end + end end end end diff --git a/qa/qa/page/project/settings/services/jira.rb b/qa/qa/page/project/settings/services/jira.rb index a9409d0223a..eaa3e90db78 100644 --- a/qa/qa/page/project/settings/services/jira.rb +++ b/qa/qa/page/project/settings/services/jira.rb @@ -10,12 +10,7 @@ module QA element :service_url_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern element :service_username_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern element :service_password_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern - end - - view 'app/assets/javascripts/integrations/edit/components/jira_trigger_fields.vue' do - element :service_issue_transition_mode_auto, ':data-qa-selector="`service_issue_transition_mode_${issueTransitionOption.value}`"' # rubocop:disable QA/ElementWithPattern - element :service_issue_transition_mode_custom, ':data-qa-selector="`service_issue_transition_mode_${issueTransitionOption.value}`"' # rubocop:disable QA/ElementWithPattern - element :service_jira_issue_transition_id_field + element :service_jira_issue_transition_id_field, ':data-qa-selector="`${fieldId}_field`"' # rubocop:disable QA/ElementWithPattern end view 'app/assets/javascripts/integrations/edit/components/integration_form.vue' do @@ -28,9 +23,7 @@ module QA set_jira_server_url(url) set_username(Runtime::Env.jira_admin_username) set_password(Runtime::Env.jira_admin_password) - - use_custom_transitions - set_transition_ids('11,21,31,41') + set_transaction_ids('11,21,31,41') click_save_changes_button wait_until(reload: false) do @@ -52,16 +45,8 @@ module QA fill_element(:service_password_field, password) end - def use_automatic_transitions - click_element :service_issue_transition_mode_auto - end - - def use_custom_transitions - click_element :service_issue_transition_mode_custom - end - - def set_transition_ids(transition_ids) - fill_element(:service_jira_issue_transition_id_field, transition_ids) + def set_transaction_ids(transaction_ids) + fill_element(:service_jira_issue_transition_id_field, transaction_ids) end def click_save_changes_button diff --git a/qa/qa/resource/project_imported_from_github.rb b/qa/qa/resource/project_imported_from_github.rb index 0b817b345fd..b06a7fe4e3d 100644 --- a/qa/qa/resource/project_imported_from_github.rb +++ b/qa/qa/resource/project_imported_from_github.rb @@ -12,7 +12,7 @@ module QA group.visit! Page::Group::Show.perform(&:go_to_new_project) - go_to_import_tab + go_to_import_page Page::Project::New.perform(&:click_github_link) Page::Project::Import::Github.perform do |import_page| @@ -21,7 +21,7 @@ module QA end end - def go_to_import_tab + def go_to_import_page Page::Project::New.perform(&:click_import_project) end end diff --git a/qa/qa/service/praefect_manager.rb b/qa/qa/service/praefect_manager.rb index ab4f28c292f..119013175c0 100644 --- a/qa/qa/service/praefect_manager.rb +++ b/qa/qa/service/praefect_manager.rb @@ -353,15 +353,47 @@ module QA Support::Waiter.wait_until(sleep_interval: 1) { replication_queue_incomplete_count == 0 && replicated?(project_id) } end + def replication_pending? + result = [] + shell sql_to_docker_exec_cmd( + <<~SQL + select job from replication_queue + where state = 'ready' + and job ->> 'change' = 'update' + and job ->> 'source_node_storage' = '#{current_primary_node}' + and job ->> 'target_node_storage' = '#{@primary_node}'; + SQL + ) do |line| + result << line + end + + # The result looks like: + # + # job + # ----------- + # {"change": "update", "params": null, "relative_path": "@hashed/4b/22/4b227777d4dd1fc61c6f884f48641d02b4d121d3fd328cb08b5531fcacdabf8a.git", "virtual_storage": "default", "source_node_storage": "gitaly3", "target_node_storage": "gitaly1"} + # (1 row) + # + # + # Therefore when replication is pending there is at least 1 row of data plus 4 rows of metadata/layout + + result.size >= 5 + end + private def current_primary_node - shell dataloss_command do |line| - QA::Runtime::Logger.debug(line.chomp) - - match = line.match(/Primary: (.*)/) - break match[1] if match + result = [] + shell sql_to_docker_exec_cmd("select node_name from shard_primaries where shard_name = '#{@virtual_storage}';") do |line| + result << line end + # The result looks like: + # node_name + # ----------- + # gitaly1 + # (1 row) + + result[2].strip end def dataloss_command diff --git a/qa/qa/specs/features/api/3_create/gitaly/backend_node_recovery_spec.rb b/qa/qa/specs/features/api/3_create/gitaly/backend_node_recovery_spec.rb index 89bf92cd3af..a240f72944f 100644 --- a/qa/qa/specs/features/api/3_create/gitaly/backend_node_recovery_spec.rb +++ b/qa/qa/specs/features/api/3_create/gitaly/backend_node_recovery_spec.rb @@ -3,7 +3,7 @@ module QA RSpec.describe 'Create' do context 'Gitaly' do - describe 'Backend node recovery', :orchestrated, :gitaly_cluster, :skip_live_env, quarantine: { issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/238186', type: :investigating } do + describe 'Backend node recovery', :orchestrated, :gitaly_cluster, :skip_live_env do let(:praefect_manager) { Service::PraefectManager.new } let(:project) do Resource::Project.fabricate! do |project| @@ -50,18 +50,17 @@ module QA push.file_content = 'new file' end + # Confirm that the commit is waiting to be replicated + expect(praefect_manager).to be_replication_pending + # Start the old primary node again praefect_manager.start_primary_node - praefect_manager.wait_for_health_check_current_primary_node + praefect_manager.wait_for_health_check_all_nodes - # Confirm dataloss (i.e., inconsistent nodes) - expect(praefect_manager.replicated?(project.id)).to be false - - # Reconcile nodes to recover from dataloss - praefect_manager.reconcile_nodes + # Wait for automatic replication praefect_manager.wait_for_replication(project.id) - # Confirm that both commits are available after reconciliation + # Confirm that both commits are available expect(project.commits.map { |commit| commit[:message].chomp }) .to include("Initial commit").and include("pushed after failover") end diff --git a/spec/controllers/projects/ci/daily_build_group_report_results_controller_spec.rb b/spec/controllers/projects/ci/daily_build_group_report_results_controller_spec.rb index 81318b49cd9..3c4376909f8 100644 --- a/spec/controllers/projects/ci/daily_build_group_report_results_controller_spec.rb +++ b/spec/controllers/projects/ci/daily_build_group_report_results_controller_spec.rb @@ -4,29 +4,25 @@ require 'spec_helper' RSpec.describe Projects::Ci::DailyBuildGroupReportResultsController do describe 'GET index' do - let(:project) { create(:project, :public, :repository) } - let(:ref_path) { 'refs/heads/master' } - let(:param_type) { 'coverage' } - let(:start_date) { '2019-12-10' } - let(:end_date) { '2020-03-09' } - let(:allowed_to_read) { true } - let(:user) { create(:user) } - let(:feature_enabled?) { true } + let_it_be(:project) { create(:project, :public, :repository) } + let_it_be(:ref_path) { 'refs/heads/master' } + let_it_be(:param_type) { 'coverage' } + let_it_be(:start_date) { '2019-12-10' } + let_it_be(:end_date) { '2020-03-09' } + let_it_be(:allowed_to_read) { true } + let_it_be(:user) { create(:user) } + let_it_be(:rspec_coverage_1) { create_daily_coverage('rspec', 79.0, '2020-03-09') } + let_it_be(:rspec_coverage_2) { create_daily_coverage('rspec', 77.0, '2020-03-08') } + let_it_be(:karma_coverage) { create_daily_coverage('karma', 81.0, '2019-12-10') } + let_it_be(:minitest_coverage) { create_daily_coverage('minitest', 67.0, '2019-12-09') } + let_it_be(:mocha_coverage) { create_daily_coverage('mocha', 71.0, '2019-12-09') } before do - create_daily_coverage('rspec', 79.0, '2020-03-09') - create_daily_coverage('rspec', 77.0, '2020-03-08') - create_daily_coverage('karma', 81.0, '2019-12-10') - create_daily_coverage('minitest', 67.0, '2019-12-09') - create_daily_coverage('mocha', 71.0, '2019-12-09') - sign_in(user) allow(Ability).to receive(:allowed?).and_call_original allow(Ability).to receive(:allowed?).with(user, :read_build_report_results, project).and_return(allowed_to_read) - stub_feature_flags(coverage_data_new_finder: feature_enabled?) - get :index, params: { namespace_id: project.namespace, project_id: project, @@ -140,33 +136,13 @@ RSpec.describe Projects::Ci::DailyBuildGroupReportResultsController do context 'when format is JSON' do let(:format) { :json } - context 'when coverage_data_new_finder flag is enabled' do - let(:feature_enabled?) { true } - - it_behaves_like 'JSON results' - end - - context 'when coverage_data_new_finder flag is disabled' do - let(:feature_enabled?) { false } - - it_behaves_like 'JSON results' - end + it_behaves_like 'JSON results' end context 'when format is CSV' do let(:format) { :csv } - context 'when coverage_data_new_finder flag is enabled' do - let(:feature_enabled?) { true } - - it_behaves_like 'CSV results' - end - - context 'when coverage_data_new_finder flag is disabled' do - let(:feature_enabled?) { false } - - it_behaves_like 'CSV results' - end + it_behaves_like 'CSV results' end end diff --git a/spec/features/projects/services/user_activates_jira_spec.rb b/spec/features/projects/services/user_activates_jira_spec.rb index 3b8032e1d0d..85afc54be48 100644 --- a/spec/features/projects/services/user_activates_jira_spec.rb +++ b/spec/features/projects/services/user_activates_jira_spec.rb @@ -6,14 +6,12 @@ RSpec.describe 'User activates Jira', :js do include_context 'project service activation' include_context 'project service Jira context' - before do - server_info = { key: 'value' }.to_json - stub_request(:get, test_url).to_return(body: server_info) - end - describe 'user tests Jira Service' do context 'when Jira connection test succeeds' do before do + server_info = { key: 'value' }.to_json + stub_request(:get, test_url).with(basic_auth: %w(username password)).to_return(body: server_info) + visit_project_integration('Jira') fill_form click_test_then_save_integration(expect_test_to_fail: false) @@ -83,40 +81,4 @@ RSpec.describe 'User activates Jira', :js do end end end - - describe 'issue transition settings' do - it 'shows validation errors' do - visit_project_integration('Jira') - - expect(page).to have_field('Move to Done', checked: true) - - fill_form - choose 'Use custom transitions' - click_save_integration - - within '[data-testid="issue-transition-settings"]' do - expect(page).to have_content('This field is required.') - end - - fill_in 'service[jira_issue_transition_id]', with: '1, 2, 3' - click_save_integration - - expect(page).to have_content('Jira settings saved and active.') - expect(project.reload.jira_service.jira_issue_transition_id).to eq('1, 2, 3') - end - - it 'clears the transition IDs when using automatic transitions' do - create(:jira_service, project: project, jira_issue_transition_id: '1, 2, 3') - visit_project_integration('Jira') - - expect(page).to have_field('Use custom transitions', checked: true) - expect(page).to have_field('service[jira_issue_transition_id]', with: '1, 2, 3') - - choose 'Move to Done' - click_save_integration - - expect(page).to have_content('Jira settings saved and active.') - expect(project.reload.jira_service.jira_issue_transition_id).to eq('') - end - end end diff --git a/spec/finders/ci/daily_build_group_report_results_finder_spec.rb b/spec/finders/ci/daily_build_group_report_results_finder_spec.rb index 2a6e44673e3..ecfa66b07dc 100644 --- a/spec/finders/ci/daily_build_group_report_results_finder_spec.rb +++ b/spec/finders/ci/daily_build_group_report_results_finder_spec.rb @@ -5,10 +5,14 @@ require 'spec_helper' RSpec.describe Ci::DailyBuildGroupReportResultsFinder do describe '#execute' do let_it_be(:project) { create(:project, :private) } - let_it_be(:current_user) { project.owner } + let(:user_without_permission) { create(:user) } + let_it_be(:user_with_permission) { project.owner } let_it_be(:ref_path) { 'refs/heads/master' } let(:limit) { nil } let_it_be(:default_branch) { false } + let(:start_date) { '2020-03-09' } + let(:end_date) { '2020-03-10' } + let(:sort) { true } let_it_be(:rspec_coverage_1) { create_daily_coverage('rspec', 79.0, '2020-03-09') } let_it_be(:karma_coverage_1) { create_daily_coverage('karma', 89.0, '2020-03-09') } @@ -17,24 +21,35 @@ RSpec.describe Ci::DailyBuildGroupReportResultsFinder do let_it_be(:rspec_coverage_3) { create_daily_coverage('rspec', 97.0, '2020-03-11') } let_it_be(:karma_coverage_3) { create_daily_coverage('karma', 99.0, '2020-03-11') } - let(:attributes) do + let(:finder) { described_class.new(params: params, current_user: current_user) } + + let(:params) do { - current_user: current_user, project: project, + coverage: true, ref_path: ref_path, - start_date: '2020-03-09', - end_date: '2020-03-10', - limit: limit + start_date: start_date, + end_date: end_date, + limit: limit, + sort: sort } end - subject(:coverages) do - described_class.new(**attributes).execute - end + subject(:coverages) { finder.execute } - context 'when ref_path is present' do - context 'when current user is allowed to read build report results' do - it 'returns all matching results within the given date range' do + context 'when params are provided' do + context 'when current user is not allowed to read data' do + let(:current_user) { user_without_permission } + + it 'returns an empty collection' do + expect(coverages).to be_empty + end + end + + context 'when current user is allowed to read data' do + let(:current_user) { user_with_permission } + + it 'returns matching coverages within the given date range' do expect(coverages).to match_array([ karma_coverage_2, rspec_coverage_2, @@ -43,10 +58,21 @@ RSpec.describe Ci::DailyBuildGroupReportResultsFinder do ]) end - context 'and limit is specified' do + context 'when ref_path is nil' do + let(:default_branch) { true } + let(:ref_path) { nil } + + it 'returns coverages for the default branch' do + rspec_coverage_4 = create_daily_coverage('rspec', 66.0, '2020-03-10') + + expect(coverages).to contain_exactly(rspec_coverage_4) + end + end + + context 'when limit is specified' do let(:limit) { 2 } - it 'returns limited number of matching results within the given date range' do + it 'returns limited number of matching coverages within the given date range' do expect(coverages).to match_array([ karma_coverage_2, rspec_coverage_2 @@ -54,28 +80,6 @@ RSpec.describe Ci::DailyBuildGroupReportResultsFinder do end end end - - context 'when current user is not allowed to read build report results' do - let(:current_user) { create(:user) } - - it 'returns an empty result' do - expect(coverages).to be_empty - end - end - end - - context 'when ref_path query parameter is not present' do - let(:ref_path) { nil } - - context 'when records with cover data from the default branch exist' do - let(:default_branch) { true } - - it 'returns records with default_branch:true, irrespective of ref_path' do - rspec_coverage_4 = create_daily_coverage('rspec', 66.0, '2020-03-10') - - expect(coverages).to contain_exactly(rspec_coverage_4) - end - end end end diff --git a/spec/finders/ci/testing/daily_build_group_report_results_finder_spec.rb b/spec/finders/ci/testing/daily_build_group_report_results_finder_spec.rb deleted file mode 100644 index a703f3b800c..00000000000 --- a/spec/finders/ci/testing/daily_build_group_report_results_finder_spec.rb +++ /dev/null @@ -1,99 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Ci::Testing::DailyBuildGroupReportResultsFinder do - describe '#execute' do - let_it_be(:project) { create(:project, :private) } - let(:user_without_permission) { create(:user) } - let_it_be(:user_with_permission) { project.owner } - let_it_be(:ref_path) { 'refs/heads/master' } - let(:limit) { nil } - let_it_be(:default_branch) { false } - let(:start_date) { '2020-03-09' } - let(:end_date) { '2020-03-10' } - let(:sort) { true } - - let_it_be(:rspec_coverage_1) { create_daily_coverage('rspec', 79.0, '2020-03-09') } - let_it_be(:karma_coverage_1) { create_daily_coverage('karma', 89.0, '2020-03-09') } - let_it_be(:rspec_coverage_2) { create_daily_coverage('rspec', 95.0, '2020-03-10') } - let_it_be(:karma_coverage_2) { create_daily_coverage('karma', 92.0, '2020-03-10') } - let_it_be(:rspec_coverage_3) { create_daily_coverage('rspec', 97.0, '2020-03-11') } - let_it_be(:karma_coverage_3) { create_daily_coverage('karma', 99.0, '2020-03-11') } - - let(:finder) { described_class.new(params: params, current_user: current_user) } - - let(:params) do - { - project: project, - coverage: true, - ref_path: ref_path, - start_date: start_date, - end_date: end_date, - limit: limit, - sort: sort - } - end - - subject(:coverages) { finder.execute } - - context 'when params are provided' do - context 'when current user is not allowed to read data' do - let(:current_user) { user_without_permission } - - it 'returns an empty collection' do - expect(coverages).to be_empty - end - end - - context 'when current user is allowed to read data' do - let(:current_user) { user_with_permission } - - it 'returns matching coverages within the given date range' do - expect(coverages).to match_array([ - karma_coverage_2, - rspec_coverage_2, - karma_coverage_1, - rspec_coverage_1 - ]) - end - - context 'when ref_path is nil' do - let(:default_branch) { true } - let(:ref_path) { nil } - - it 'returns coverages for the default branch' do - rspec_coverage_4 = create_daily_coverage('rspec', 66.0, '2020-03-10') - - expect(coverages).to contain_exactly(rspec_coverage_4) - end - end - - context 'when limit is specified' do - let(:limit) { 2 } - - it 'returns limited number of matching coverages within the given date range' do - expect(coverages).to match_array([ - karma_coverage_2, - rspec_coverage_2 - ]) - end - end - end - end - end - - private - - def create_daily_coverage(group_name, coverage, date) - create( - :ci_daily_build_group_report_result, - project: project, - ref_path: ref_path || 'feature-branch', - group_name: group_name, - data: { 'coverage' => coverage }, - date: date, - default_branch: default_branch - ) - end -end diff --git a/spec/finders/repositories/previous_tag_finder_spec.rb b/spec/finders/repositories/previous_tag_finder_spec.rb index 7cc33d11baf..e07eab7ec5f 100644 --- a/spec/finders/repositories/previous_tag_finder_spec.rb +++ b/spec/finders/repositories/previous_tag_finder_spec.rb @@ -12,16 +12,19 @@ RSpec.describe Repositories::PreviousTagFinder do tag1 = double(:tag1, name: 'v1.0.0') tag2 = double(:tag2, name: 'v1.1.0') tag3 = double(:tag3, name: 'v2.0.0') - tag4 = double(:tag4, name: '1.0.0') + tag4 = double(:tag4, name: '0.9.0') + tag5 = double(:tag4, name: 'v0.8.0-pre1') allow(project.repository) .to receive(:tags) - .and_return([tag1, tag3, tag2, tag4]) + .and_return([tag1, tag3, tag2, tag4, tag5]) expect(finder.execute('2.1.0')).to eq(tag3) expect(finder.execute('2.0.0')).to eq(tag2) expect(finder.execute('1.5.0')).to eq(tag2) expect(finder.execute('1.0.1')).to eq(tag1) + expect(finder.execute('1.0.0')).to eq(tag4) + expect(finder.execute('0.9.0')).to eq(tag5) end end diff --git a/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js b/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js index 6a8ab02a69a..c6e7ee44355 100644 --- a/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js +++ b/spec/frontend/integrations/edit/components/jira_trigger_fields_spec.js @@ -30,21 +30,14 @@ describe('JiraTriggerFields', () => { const findCommentSettings = () => wrapper.find('[data-testid="comment-settings"]'); const findCommentDetail = () => wrapper.find('[data-testid="comment-detail"]'); const findCommentSettingsCheckbox = () => findCommentSettings().find(GlFormCheckbox); - const findIssueTransitionSettings = () => - wrapper.find('[data-testid="issue-transition-settings"]'); - const findIssueTransitionModeRadios = () => - findIssueTransitionSettings().findAll('input[type="radio"]'); - const findIssueTransitionIdsField = () => - wrapper.find('input[type="text"][name="service[jira_issue_transition_id]"]'); describe('template', () => { describe('initialTriggerCommit and initialTriggerMergeRequest are false', () => { - it('does not show trigger settings', () => { + it('does not show comment settings', () => { createComponent(); expect(findCommentSettings().isVisible()).toBe(false); expect(findCommentDetail().isVisible()).toBe(false); - expect(findIssueTransitionSettings().isVisible()).toBe(false); }); }); @@ -55,10 +48,9 @@ describe('JiraTriggerFields', () => { }); }); - it('shows trigger settings', () => { + it('shows comment settings', () => { expect(findCommentSettings().isVisible()).toBe(true); expect(findCommentDetail().isVisible()).toBe(false); - expect(findIssueTransitionSettings().isVisible()).toBe(true); }); // As per https://vuejs.org/v2/guide/forms.html#Checkbox-1, @@ -81,14 +73,13 @@ describe('JiraTriggerFields', () => { }); describe('initialTriggerMergeRequest is true', () => { - it('shows trigger settings', () => { + it('shows comment settings', () => { createComponent({ initialTriggerMergeRequest: true, }); expect(findCommentSettings().isVisible()).toBe(true); expect(findCommentDetail().isVisible()).toBe(false); - expect(findIssueTransitionSettings().isVisible()).toBe(true); }); }); @@ -104,41 +95,7 @@ describe('JiraTriggerFields', () => { }); }); - describe('initialJiraIssueTransitionId is not set', () => { - it('uses automatic transitions', () => { - createComponent({ - initialTriggerCommit: true, - }); - - const [radio1, radio2] = findIssueTransitionModeRadios().wrappers; - expect(radio1.element.checked).toBe(true); - expect(radio2.element.checked).toBe(false); - - expect(findIssueTransitionIdsField().exists()).toBe(false); - }); - }); - - describe('initialJiraIssueTransitionId is set', () => { - it('uses custom transitions', () => { - createComponent({ - initialJiraIssueTransitionId: '1, 2, 3', - initialTriggerCommit: true, - }); - - const [radio1, radio2] = findIssueTransitionModeRadios().wrappers; - expect(radio1.element.checked).toBe(false); - expect(radio2.element.checked).toBe(true); - - const field = findIssueTransitionIdsField(); - expect(field.isVisible()).toBe(true); - expect(field.element).toMatchObject({ - type: 'text', - value: '1, 2, 3', - }); - }); - }); - - it('disables input fields if inheriting', () => { + it('disables checkboxes and radios if inheriting', () => { createComponent( { initialTriggerCommit: true, @@ -147,8 +104,12 @@ describe('JiraTriggerFields', () => { true, ); - wrapper.findAll('[type=text], [type=checkbox], [type=radio]').wrappers.forEach((input) => { - expect(input.attributes('disabled')).toBe('disabled'); + wrapper.findAll('[type=checkbox]').wrappers.forEach((checkbox) => { + expect(checkbox.attributes('disabled')).toBe('disabled'); + }); + + wrapper.findAll('[type=radio]').wrappers.forEach((radio) => { + expect(radio.attributes('disabled')).toBe('disabled'); }); }); }); diff --git a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_a_spec.js.snap b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_a_spec.js.snap index c9141d13a46..1c1327e7a4e 100644 --- a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_a_spec.js.snap +++ b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_a_spec.js.snap @@ -4,7 +4,7 @@ exports[`Learn GitLab Design A should render the loading state 1`] = `
  • - Create a repository + Create or import a repository
  • @@ -14,7 +14,11 @@ exports[`Learn GitLab Design A should render the loading state 1`] = `
  • - Set-up CI/CD + + Set up CI/CD +
  • @@ -22,7 +26,7 @@ exports[`Learn GitLab Design A should render the loading state 1`] = ` - Start a free trial of GitLab Gold + Start a free Ultimate trial
  • @@ -40,7 +44,7 @@ exports[`Learn GitLab Design A should render the loading state 1`] = ` - Enable require merge approvals + Add merge request approval @@ -49,7 +53,7 @@ exports[`Learn GitLab Design A should render the loading state 1`] = ` - Submit a merge request (MR) + Submit a merge request @@ -58,7 +62,7 @@ exports[`Learn GitLab Design A should render the loading state 1`] = ` - Run a Security scan using CI/CD + Run a security scan diff --git a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_b_spec.js.snap b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_b_spec.js.snap index 85e3b675e5b..dd899b93302 100644 --- a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_b_spec.js.snap +++ b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_b_spec.js.snap @@ -1,66 +1,519 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Learn GitLab Design B should render the loading state 1`] = ` -
      -
    • - - Create a repository - -
    • -
    • - - Invite your colleagues - -
    • -
    • - - Set-up CI/CD - -
    • -
    • - - +
      +
      +

      - Start a free trial of GitLab Gold - - -

    • -
    • - - + +

      - Add code owners - - -

    • -
    • - - + + + +
      +

      + 25% completed +

      + +
      +
      - Enable require merge approvals - - -
    • -
    • - - + + + + +

      + Set up your workspace +

      + +

      + Complete these tasks first so you can enjoy GitLab's features to their fullest: +

      + +
      +
      +
      - Submit a merge request (MR) - - -
    • -
    • - - + +
      +
      + +
      + +
      + + +
      + Invite your colleagues +
      + +

      + GitLab works best as a team. Invite your colleague to enjoy all features. +

      + + + Invite your colleagues + +
      +
      + + + + + +
      +
      - Run a Security scan using CI/CD - - -
    • -
    + + +
    +
    + +
    + +
    + + +
    + Create or import a repository +
    + +

    + Create or import your first repository into your new project. +

    + + + Create or import a repository + +
    +
    + + + + + +
    +
    + + +
    +
    + +
    + +
    + + +
    + Set up CI/CD +
    + +

    + Save time by automating your integration and deployment tasks. +

    + + + Set-up CI/CD + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + +
    + +
    + + +
    + Start a free Ultimate trial +
    + +

    + Try all GitLab features for 30 days, no credit card required. +

    + + + Try GitLab Ultimate for free + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + + Trial only + +
    + +
    + + +
    + Add code owners +
    + +

    + Prevent unexpected changes to important assets by assigning ownership of files and paths. +

    + + + Add code owners + +
    +
    + + +
    +
    + +
    +
    + + +
    +
    + + Trial only + +
    + +
    + + +
    + Add merge request approval +
    + +

    + Route code reviews to the right reviewers, every time. +

    + + + Enable require merge approvals + +
    +
    + + +
    +
    + + +

    + Plan and execute +

    + +

    + Create a workflow for your new workspace, and learn how GitLab features work together: +

    + +
    +
    +
    + + +
    +
    + +
    + +
    + + +
    + Submit a merge request +
    + +

    + Review and edit proposed changes to source code. +

    + + + Submit a merge request (MR) + +
    +
    + + +
    +
    +
    + +

    + Deploy +

    + +

    + Use your new GitLab workflow to deploy your application, monitor its health, and keep it secure: +

    + +
    +
    +
    + + +
    +
    + +
    + +
    + + +
    + Run a security scan +
    + +

    + Scan your code to uncover vulnerabilities before deploying. +

    + + + Run a Security scan + +
    +
    + + +
    +
    +
    + `; diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_a_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_a_spec.js index ddc5339e7e0..2154358de51 100644 --- a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_a_spec.js +++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_a_spec.js @@ -1,41 +1,6 @@ import { shallowMount } from '@vue/test-utils'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import LearnGitlabA from '~/pages/projects/learn_gitlab/components/learn_gitlab_a.vue'; - -const TEST_ACTIONS = { - gitWrite: { - url: 'http://example.com/', - completed: true, - }, - userAdded: { - url: 'http://example.com/', - completed: true, - }, - pipelineCreated: { - url: 'http://example.com/', - completed: true, - }, - trialStarted: { - url: 'http://example.com/', - completed: false, - }, - codeOwnersEnabled: { - url: 'http://example.com/', - completed: false, - }, - requiredMrApprovalsEnabled: { - url: 'http://example.com/', - completed: false, - }, - mergeRequestCreated: { - url: 'http://example.com/', - completed: false, - }, - securityScanEnabled: { - url: 'http://example.com/', - completed: false, - }, -}; +import { testActions } from './mock_data'; describe('Learn GitLab Design A', () => { let wrapper; @@ -46,13 +11,7 @@ describe('Learn GitLab Design A', () => { }); const createWrapper = () => { - wrapper = extendedWrapper( - shallowMount(LearnGitlabA, { - propsData: { - actions: TEST_ACTIONS, - }, - }), - ); + wrapper = shallowMount(LearnGitlabA, { propsData: { actions: testActions } }); }; it('should render the loading state', () => { diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_b_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_b_spec.js index be4f5768402..fbb989fae32 100644 --- a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_b_spec.js +++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_b_spec.js @@ -1,63 +1,38 @@ -import { shallowMount } from '@vue/test-utils'; -import { extendedWrapper } from 'helpers/vue_test_utils_helper'; -import LearnGitlabA from '~/pages/projects/learn_gitlab/components/learn_gitlab_a.vue'; - -const TEST_ACTIONS = { - gitWrite: { - url: 'http://example.com/', - completed: true, - }, - userAdded: { - url: 'http://example.com/', - completed: true, - }, - pipelineCreated: { - url: 'http://example.com/', - completed: true, - }, - trialStarted: { - url: 'http://example.com/', - completed: false, - }, - codeOwnersEnabled: { - url: 'http://example.com/', - completed: false, - }, - requiredMrApprovalsEnabled: { - url: 'http://example.com/', - completed: false, - }, - mergeRequestCreated: { - url: 'http://example.com/', - completed: false, - }, - securityScanEnabled: { - url: 'http://example.com/', - completed: false, - }, -}; +import { GlProgressBar } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import LearnGitlabB from '~/pages/projects/learn_gitlab/components/learn_gitlab_b.vue'; +import { testActions } from './mock_data'; describe('Learn GitLab Design B', () => { let wrapper; + const createWrapper = () => { + wrapper = mount(LearnGitlabB, { propsData: { actions: testActions } }); + }; + + beforeEach(() => { + createWrapper(); + }); + afterEach(() => { wrapper.destroy(); wrapper = null; }); - const createWrapper = () => { - wrapper = extendedWrapper( - shallowMount(LearnGitlabA, { - propsData: { - actions: TEST_ACTIONS, - }, - }), - ); - }; - - it('should render the loading state', () => { - createWrapper(); - + it('renders correctly', () => { expect(wrapper.element).toMatchSnapshot(); }); + + it('renders the progress percentage', () => { + const text = wrapper.find('[data-testid="completion-percentage"]').text(); + + expect(text).toEqual('25% completed'); + }); + + it('renders the progress bar with correct values', () => { + const progressBar = wrapper.find(GlProgressBar); + + expect(progressBar.attributes('value')).toBe('2'); + expect(progressBar.attributes('max')).toBe('8'); + }); }); diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_info_card_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_info_card_spec.js new file mode 100644 index 00000000000..ad4bc826a9d --- /dev/null +++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_info_card_spec.js @@ -0,0 +1,57 @@ +import { shallowMount } from '@vue/test-utils'; +import LearnGitlabInfoCard from '~/pages/projects/learn_gitlab/components/learn_gitlab_info_card.vue'; + +const defaultProps = { + title: 'Create Repository', + description: 'Some description', + actionLabel: 'Create Repository now', + url: 'https://example.com', + completed: false, + svg: 'https://example.com/illustration.svg', +}; + +describe('Learn GitLab Info Card', () => { + let wrapper; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const createWrapper = (props = {}) => { + wrapper = shallowMount(LearnGitlabInfoCard, { + propsData: { ...defaultProps, ...props }, + }); + }; + + it('renders no icon when not completed', () => { + createWrapper({ completed: false }); + + expect(wrapper.find('[data-testid="completed-icon"]').exists()).toBe(false); + }); + + it('renders the completion icon when completed', () => { + createWrapper({ completed: true }); + + expect(wrapper.find('[data-testid="completed-icon"]').exists()).toBe(true); + }); + + it('renders no trial only when it is not required', () => { + createWrapper(); + + expect(wrapper.find('[data-testid="trial-only"]').exists()).toBe(false); + }); + + it('renders trial only when trial is required', () => { + createWrapper({ trialRequired: true }); + + expect(wrapper.find('[data-testid="trial-only"]').exists()).toBe(true); + }); + + it('renders completion icon when completed a trial-only feature', () => { + createWrapper({ trialRequired: true, completed: true }); + + expect(wrapper.find('[data-testid="trial-only"]').exists()).toBe(false); + expect(wrapper.find('[data-testid="completed-icon"]').exists()).toBe(true); + }); +}); diff --git a/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js b/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js new file mode 100644 index 00000000000..caac667e2b1 --- /dev/null +++ b/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js @@ -0,0 +1,42 @@ +export const testActions = { + gitWrite: { + url: 'http://example.com/', + completed: true, + svg: 'http://example.com/images/illustration.svg', + }, + userAdded: { + url: 'http://example.com/', + completed: true, + svg: 'http://example.com/images/illustration.svg', + }, + pipelineCreated: { + url: 'http://example.com/', + completed: false, + svg: 'http://example.com/images/illustration.svg', + }, + trialStarted: { + url: 'http://example.com/', + completed: false, + svg: 'http://example.com/images/illustration.svg', + }, + codeOwnersEnabled: { + url: 'http://example.com/', + completed: false, + svg: 'http://example.com/images/illustration.svg', + }, + requiredMrApprovalsEnabled: { + url: 'http://example.com/', + completed: false, + svg: 'http://example.com/images/illustration.svg', + }, + mergeRequestCreated: { + url: 'http://example.com/', + completed: false, + svg: 'http://example.com/images/illustration.svg', + }, + securityScanEnabled: { + url: 'http://example.com/', + completed: false, + svg: 'http://example.com/images/illustration.svg', + }, +}; diff --git a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js index 46d0452f437..b44ba0e4543 100644 --- a/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js +++ b/spec/frontend/pipeline_editor/pipeline_editor_app_spec.js @@ -193,6 +193,7 @@ describe('Pipeline editor app component', () => { describe('and the commit mutation succeeds', () => { beforeEach(() => { + window.scrollTo = jest.fn(); createComponent(); findEditorHome().vm.$emit('commit', { type: COMMIT_SUCCESS }); @@ -201,11 +202,16 @@ describe('Pipeline editor app component', () => { it('shows a confirmation message', () => { expect(findAlert().text()).toBe(wrapper.vm.$options.successTexts[COMMIT_SUCCESS]); }); + + it('scrolls to the top of the page to bring attention to the confirmation message', () => { + expect(window.scrollTo).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' }); + }); }); describe('and the commit mutation fails', () => { const commitFailedReasons = ['Commit failed']; beforeEach(() => { + window.scrollTo = jest.fn(); createComponent(); findEditorHome().vm.$emit('showError', { @@ -219,11 +225,17 @@ describe('Pipeline editor app component', () => { `${updateFailureMessage} ${commitFailedReasons[0]}`, ); }); + + it('scrolls to the top of the page to bring attention to the error message', () => { + expect(window.scrollTo).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' }); + }); }); + describe('when an unknown error occurs', () => { const unknownReasons = ['Commit failed']; beforeEach(() => { + window.scrollTo = jest.fn(); createComponent(); findEditorHome().vm.$emit('showError', { @@ -237,6 +249,10 @@ describe('Pipeline editor app component', () => { `${updateFailureMessage} ${unknownReasons[0]}`, ); }); + + it('scrolls to the top of the page to bring attention to the error message', () => { + expect(window.scrollTo).toHaveBeenCalledWith({ top: 0, behavior: 'smooth' }); + }); }); }); }); diff --git a/spec/helpers/learn_gitlab_helper_spec.rb b/spec/helpers/learn_gitlab_helper_spec.rb index f789eb9d940..6cee8a9191c 100644 --- a/spec/helpers/learn_gitlab_helper_spec.rb +++ b/spec/helpers/learn_gitlab_helper_spec.rb @@ -41,11 +41,13 @@ RSpec.describe LearnGitlabHelper do it 'sets correct path and completion status' do expect(onboarding_actions_data[:git_write]).to eq({ url: project_issue_url(project, LearnGitlabHelper::ACTION_ISSUE_IDS[:git_write]), - completed: true + completed: true, + svg: helper.image_path("learn_gitlab/git_write.svg") }) expect(onboarding_actions_data[:pipeline_created]).to eq({ url: project_issue_url(project, LearnGitlabHelper::ACTION_ISSUE_IDS[:pipeline_created]), - completed: false + completed: false, + svg: helper.image_path("learn_gitlab/pipeline_created.svg") }) end end diff --git a/spec/helpers/services_helper_spec.rb b/spec/helpers/services_helper_spec.rb index 3b08393ebdf..1726a8362a7 100644 --- a/spec/helpers/services_helper_spec.rb +++ b/spec/helpers/services_helper_spec.rb @@ -27,26 +27,17 @@ RSpec.describe ServicesHelper do ] end - let(:jira_fields) { %i[jira_issue_transition_id] } - subject { helper.integration_form_data(integration) } context 'Slack service' do let(:integration) { build(:slack_service) } it { is_expected.to include(*fields) } - it { is_expected.not_to include(*jira_fields) } specify do expect(subject[:reset_path]).to eq(helper.scoped_reset_integration_path(integration)) end end - - context 'Jira service' do - let(:integration) { build(:jira_service) } - - it { is_expected.to include(*fields, *jira_fields) } - end end describe '#scoped_reset_integration_path' do diff --git a/spec/lib/gitlab/ci/variables/collection/item_spec.rb b/spec/lib/gitlab/ci/variables/collection/item_spec.rb index 2e43f22830a..42fbb88cceb 100644 --- a/spec/lib/gitlab/ci/variables/collection/item_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection/item_spec.rb @@ -32,6 +32,7 @@ RSpec.describe Gitlab::Ci::Variables::Collection::Item do it 'saves given value' do expect(subject[:key]).to eq variable_key expect(subject[:value]).to eq expected_value + expect(subject.value).to eq expected_value end end diff --git a/spec/lib/gitlab/ci/variables/collection_spec.rb b/spec/lib/gitlab/ci/variables/collection_spec.rb index 670d836b4e8..7a275b69fd8 100644 --- a/spec/lib/gitlab/ci/variables/collection_spec.rb +++ b/spec/lib/gitlab/ci/variables/collection_spec.rb @@ -98,6 +98,50 @@ RSpec.describe Gitlab::Ci::Variables::Collection do end end + describe '#[]' do + variable = { key: 'VAR', value: 'value', public: true, masked: false } + + collection = described_class.new([variable]) + + it 'returns nil for a non-existent variable name' do + expect(collection['UNKNOWN_VAR']).to be_nil + end + + it 'returns Item for an existent variable name' do + expect(collection['VAR']).to be_an_instance_of(Gitlab::Ci::Variables::Collection::Item) + expect(collection['VAR'].to_runner_variable).to eq(variable) + end + end + + describe '#size' do + it 'returns zero for empty collection' do + collection = described_class.new([]) + + expect(collection.size).to eq(0) + end + + it 'returns 2 for collection with 2 variables' do + collection = described_class.new( + [ + { key: 'VAR1', value: 'value', public: true, masked: false }, + { key: 'VAR2', value: 'value', public: true, masked: false } + ]) + + expect(collection.size).to eq(2) + end + + it 'returns 3 for collection with 2 duplicate variables' do + collection = described_class.new( + [ + { key: 'VAR1', value: 'value', public: true, masked: false }, + { key: 'VAR2', value: 'value', public: true, masked: false }, + { key: 'VAR1', value: 'value', public: true, masked: false } + ]) + + expect(collection.size).to eq(3) + end + end + describe '#to_runner_variables' do it 'creates an array of hashes in a runner-compatible format' do collection = described_class.new([{ key: 'TEST', value: '1' }]) diff --git a/spec/lib/gitlab/experimentation_spec.rb b/spec/lib/gitlab/experimentation_spec.rb index a4b046a9ed5..1cd9411f475 100644 --- a/spec/lib/gitlab/experimentation_spec.rb +++ b/spec/lib/gitlab/experimentation_spec.rb @@ -7,7 +7,6 @@ require 'spec_helper' RSpec.describe Gitlab::Experimentation::EXPERIMENTS do it 'temporarily ensures we know what experiments exist for backwards compatibility' do expected_experiment_keys = [ - :ci_notification_dot, :upgrade_link_in_user_menu_a, :invite_members_version_a, :invite_members_version_b, diff --git a/spec/models/pages/lookup_path_spec.rb b/spec/models/pages/lookup_path_spec.rb index 0a2b04f1a7c..9e65635da91 100644 --- a/spec/models/pages/lookup_path_spec.rb +++ b/spec/models/pages/lookup_path_spec.rb @@ -117,14 +117,6 @@ RSpec.describe Pages::LookupPath do end end - context 'when pages_serve_from_deployments feature flag is disabled' do - before do - stub_feature_flags(pages_serve_from_deployments: false) - end - - include_examples 'uses disk storage' - end - context 'when deployment were created during migration' do before do allow(deployment).to receive(:migrated?).and_return(true) diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb index ef0bc930152..3fc39fd3266 100644 --- a/spec/models/project_services/jira_service_spec.rb +++ b/spec/models/project_services/jira_service_spec.rb @@ -82,8 +82,11 @@ RSpec.describe JiraService do subject(:fields) { service.fields } - it 'returns custom fields' do - expect(fields.pluck(:name)).to eq(%w[url api_url username password]) + it 'includes transition help link' do + transition_id_field = fields.find { |field| field[:name] == 'jira_issue_transition_id' } + + expect(transition_id_field[:title]).to eq('Jira workflow transition IDs') + expect(transition_id_field[:help]).to include('/help/user/project/integrations/jira') end end @@ -457,10 +460,10 @@ RSpec.describe JiraService do end context 'with options' do - let(:issue_url) { "#{url}/rest/api/2/issue/#{issue_key}?expand=renderedFields,transitions" } + let(:issue_url) { "#{url}/rest/api/2/issue/#{issue_key}?expand=renderedFields" } it 'calls the Jira API with the options to get the issue' do - jira_service.find_issue(issue_key, rendered_fields: true, transitions: true) + jira_service.find_issue(issue_key, rendered_fields: true) expect(WebMock).to have_requested(:get, issue_url) end @@ -502,7 +505,7 @@ RSpec.describe JiraService do allow(closed_issue).to receive(:resolution).and_return(true) allow(JIRA::Resource::Issue).to receive(:find).and_return(open_issue, closed_issue) - allow_any_instance_of(JIRA::Resource::Issue).to receive(:key).and_return(issue_key) + allow_any_instance_of(JIRA::Resource::Issue).to receive(:key).and_return('JIRA-123') allow(JIRA::Resource::Remotelink).to receive(:all).and_return([]) WebMock.stub_request(:get, issue_url).with(basic_auth: %w(jira-username jira-password)) @@ -661,49 +664,6 @@ RSpec.describe JiraService do ).once end - context 'when using automatic issue transitions' do - let(:transitions) do - [ - { id: '1' }, - { id: '2', to: { statusCategory: { key: 'new' } } }, - { id: '3', to: { statusCategory: { key: 'done' } } }, - { id: '4', to: { statusCategory: { key: 'done' } } } - ] - end - - before do - allow(jira_service).to receive_messages(jira_issue_transition_id: '') - - close_issue - end - - it 'uses the next transition with a status category of done' do - expect(WebMock).to have_requested(:post, transitions_url).with( - body: /"id":"3"/ - ).once - end - - context 'when no done transition is available' do - let(:transitions) do - [ - { id: '1', to: { statusCategory: { key: 'new' } } } - ] - end - - it 'does not attempt to transition' do - expect(WebMock).not_to have_requested(:post, transitions_url) - end - end - - context 'when no valid transitions are returned' do - let(:transitions) { 'foo' } - - it 'does not attempt to transition' do - expect(WebMock).not_to have_requested(:post, transitions_url) - end - end - end - context 'when using multiple transition ids' do before do allow(jira_service).to receive_messages(jira_issue_transition_id: '1,2,3') diff --git a/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb b/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb index 7dd897f6466..b5aaf304812 100644 --- a/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb +++ b/spec/requests/api/graphql/mutations/notes/create/diff_note_spec.rb @@ -9,8 +9,9 @@ RSpec.describe 'Adding a DiffNote' do let(:noteable) { create(:merge_request, source_project: project, target_project: project) } let(:project) { create(:project, :repository) } let(:diff_refs) { noteable.diff_refs } - let(:mutation) do - variables = { + + let(:base_variables) do + { noteable_id: GitlabSchema.id_from_object(noteable).to_s, body: 'Body text', position: { @@ -18,16 +19,16 @@ RSpec.describe 'Adding a DiffNote' do old_path: 'files/ruby/popen.rb', new_path: 'files/ruby/popen2.rb' }, - new_line: 14, base_sha: diff_refs.base_sha, head_sha: diff_refs.head_sha, start_sha: diff_refs.start_sha } } - - graphql_mutation(:create_diff_note, variables) end + let(:variables) { base_variables.deep_merge({ position: { new_line: 14 } }) } + let(:mutation) { graphql_mutation(:create_diff_note, variables) } + def mutation_response graphql_mutation_response(:create_diff_note) end @@ -41,6 +42,18 @@ RSpec.describe 'Adding a DiffNote' do it_behaves_like 'a Note mutation that creates a Note' + context 'add comment to old line' do + let(:variables) { base_variables.deep_merge({ position: { old_line: 14 } }) } + + it_behaves_like 'a Note mutation that creates a Note' + end + + context 'add a comment with a position without lines' do + let(:variables) { base_variables } + + it_behaves_like 'a Note mutation that does not create a Note' + end + it_behaves_like 'a Note mutation when there are active record validation errors', model: DiffNote it_behaves_like 'a Note mutation when there are rate limit validation errors' diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index d70a8bd692d..44b7a38ed2b 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -3044,18 +3044,6 @@ RSpec.describe API::Users do expect(response).to have_gitlab_http_status(:bad_request) end - - context 'when the clear_status_with_quick_options feature flag is disabled' do - before do - stub_feature_flags(clear_status_with_quick_options: false) - end - - it 'does not persist clear_status_at' do - put api('/user/status', user), params: { emoji: 'smirk', message: 'hello world', clear_status_after: '3_hours' } - - expect(user.status.reload.clear_status_at).to be_nil - end - end end end diff --git a/spec/services/alert_management/process_prometheus_alert_service_spec.rb b/spec/services/alert_management/process_prometheus_alert_service_spec.rb index 288a33b71cd..9bd71ea6f64 100644 --- a/spec/services/alert_management/process_prometheus_alert_service_spec.rb +++ b/spec/services/alert_management/process_prometheus_alert_service_spec.rb @@ -11,6 +11,7 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do describe '#execute' do let(:service) { described_class.new(project, payload) } + let(:source) { 'Prometheus' } let(:auto_close_incident) { true } let(:create_issue) { true } let(:send_email) { true } @@ -31,7 +32,7 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do subject(:execute) { service.execute } context 'when alert payload is valid' do - let(:parsed_payload) { Gitlab::AlertManagement::Payload.parse(project, payload, monitoring_tool: 'Prometheus') } + let(:parsed_payload) { Gitlab::AlertManagement::Payload.parse(project, payload, monitoring_tool: source) } let(:fingerprint) { parsed_payload.gitlab_fingerprint } let(:payload) do { @@ -112,9 +113,7 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do it_behaves_like 'Alert Notification Service sends notification email' it_behaves_like 'processes incident issues' - it 'creates a system note corresponding to alert creation' do - expect { subject }.to change(Note, :count).by(1) - end + it_behaves_like 'creates single system note based on the source of the alert' context 'when auto-alert creation is disabled' do let(:create_issue) { false } @@ -158,17 +157,20 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do context 'when Prometheus alert status is resolved' do let(:status) { 'resolved' } - let!(:alert) { create(:alert_management_alert, project: project, fingerprint: fingerprint) } + let!(:alert) { create(:alert_management_alert, project: project, fingerprint: fingerprint, monitoring_tool: source) } context 'when auto_resolve_incident set to true' do context 'when status can be changed' do it_behaves_like 'Alert Notification Service sends notification email' it_behaves_like 'does not process incident issues' - it 'resolves an existing alert' do + it 'resolves an existing alert without error' do + expect(Gitlab::AppLogger).not_to receive(:warn) expect { execute }.to change { alert.reload.resolved? }.to(true) end + it_behaves_like 'creates status-change system note for an auto-resolved alert' + context 'existing issue' do let!(:alert) { create(:alert_management_alert, :with_issue, project: project, fingerprint: fingerprint) } @@ -215,6 +217,8 @@ RSpec.describe AlertManagement::ProcessPrometheusAlertService do it 'does not resolve an existing alert' do expect { execute }.not_to change { alert.reload.resolved? } end + + it_behaves_like 'creates single system note based on the source of the alert' end context 'when emails are disabled' do diff --git a/spec/services/ci/register_job_service_spec.rb b/spec/services/ci/register_job_service_spec.rb index 88770c8095b..d45d6120cf6 100644 --- a/spec/services/ci/register_job_service_spec.rb +++ b/spec/services/ci/register_job_service_spec.rb @@ -590,22 +590,14 @@ module Ci before do allow(Time).to receive(:now).and_return(current_time) - - # Stub defaults for any metrics other than the ones we're testing - allow(Gitlab::Metrics).to receive(:counter) - .with(any_args) - .and_return(Gitlab::Metrics::NullMetric.instance) - allow(Gitlab::Metrics).to receive(:histogram) - .with(any_args) - .and_return(Gitlab::Metrics::NullMetric.instance) - # Stub tested metrics - allow(Gitlab::Metrics).to receive(:counter) - .with(:job_register_attempts_total, anything) - .and_return(attempt_counter) - allow(Gitlab::Metrics).to receive(:histogram) - .with(:job_queue_duration_seconds, anything, anything, anything) - .and_return(job_queue_duration_seconds) + allow(Gitlab::Ci::Queue::Metrics) + .to receive(:attempt_counter) + .and_return(attempt_counter) + + allow(Gitlab::Ci::Queue::Metrics) + .to receive(:job_queue_duration_seconds) + .and_return(job_queue_duration_seconds) project.update!(shared_runners_enabled: true) pending_job.update!(created_at: current_time - 3600, queued_at: current_time - 1800) @@ -655,7 +647,7 @@ module Ci context 'when shared runner is used' do let(:runner) { create(:ci_runner, :instance, tag_list: %w(tag1 tag2)) } let(:expected_shared_runner) { true } - let(:expected_shard) { Ci::RegisterJobService::DEFAULT_METRICS_SHARD } + let(:expected_shard) { ::Gitlab::Ci::Queue::Metrics::DEFAULT_METRICS_SHARD } let(:expected_jobs_running_for_project_first_job) { 0 } let(:expected_jobs_running_for_project_third_job) { 2 } @@ -694,7 +686,7 @@ module Ci context 'when specific runner is used' do let(:runner) { create(:ci_runner, :project, projects: [project], tag_list: %w(tag1 metrics_shard::shard_tag tag2)) } let(:expected_shared_runner) { false } - let(:expected_shard) { Ci::RegisterJobService::DEFAULT_METRICS_SHARD } + let(:expected_shard) { ::Gitlab::Ci::Queue::Metrics::DEFAULT_METRICS_SHARD } let(:expected_jobs_running_for_project_first_job) { '+Inf' } let(:expected_jobs_running_for_project_third_job) { '+Inf' } diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb index 80de453cd8b..611f12c8146 100644 --- a/spec/services/merge_requests/merge_service_spec.rb +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -166,6 +166,20 @@ RSpec.describe MergeRequests::MergeService do service.execute(merge_request) end + context 'when jira_issue_transition_id is not present' do + before do + allow_any_instance_of(JIRA::Resource::Issue).to receive(:resolution).and_return(nil) + end + + it 'does not close issue' do + jira_tracker.update!(jira_issue_transition_id: nil) + + expect_any_instance_of(JiraService).not_to receive(:transition_issue) + + service.execute(merge_request) + end + end + context 'wrong issue markdown' do it 'does not close issues on Jira issue tracker' do jira_issue = ExternalIssue.new('#JIRA-123', project) diff --git a/spec/services/projects/alerting/notify_service_spec.rb b/spec/services/projects/alerting/notify_service_spec.rb index 4e366fce0d9..c272ce13132 100644 --- a/spec/services/projects/alerting/notify_service_spec.rb +++ b/spec/services/projects/alerting/notify_service_spec.rb @@ -119,6 +119,7 @@ RSpec.describe Projects::Alerting::NotifyService do end it_behaves_like 'does not an create alert management alert' + it_behaves_like 'creates single system note based on the source of the alert' context 'auto_close_enabled setting enabled' do let(:auto_close_enabled) { true } @@ -131,6 +132,8 @@ RSpec.describe Projects::Alerting::NotifyService do expect(alert.ended_at).to eql(ended_at) end + it_behaves_like 'creates status-change system note for an auto-resolved alert' + context 'related issue exists' do let(:alert) { create(:alert_management_alert, :with_issue, project: project, fingerprint: fingerprint_sha) } let(:issue) { alert.issue } @@ -209,10 +212,7 @@ RSpec.describe Projects::Alerting::NotifyService do ) end - it 'creates a system note corresponding to alert creation' do - expect { subject }.to change(Note, :count).by(1) - expect(Note.last.note).to include(source) - end + it_behaves_like 'creates single system note based on the source of the alert' end end diff --git a/spec/services/projects/update_pages_configuration_service_spec.rb b/spec/services/projects/update_pages_configuration_service_spec.rb index 294de813e02..9ef66a10f0d 100644 --- a/spec/services/projects/update_pages_configuration_service_spec.rb +++ b/spec/services/projects/update_pages_configuration_service_spec.rb @@ -26,11 +26,18 @@ RSpec.describe Projects::UpdatePagesConfigurationService do context 'when configuration changes' do it 'updates the config and reloads the daemon' do - allow(service).to receive(:update_file).and_call_original - expect(service).to receive(:update_file).with(file.path, an_instance_of(String)) .and_call_original - expect(service).to receive(:reload_daemon).and_call_original + allow(service).to receive(:update_file).with(File.join(::Settings.pages.path, '.update'), + an_instance_of(String)).and_call_original + + expect(subject).to include(status: :success) + end + + it "doesn't update configuration files if updates on legacy storage are disabled" do + stub_feature_flags(pages_update_legacy_storage: false) + + expect(service).not_to receive(:update_file) expect(subject).to include(status: :success) end @@ -42,8 +49,8 @@ RSpec.describe Projects::UpdatePagesConfigurationService do service.execute end - it 'does not update the .update file' do - expect(service).not_to receive(:reload_daemon) + it 'does not update anything' do + expect(service).not_to receive(:update_file) expect(subject).to include(status: :success) end diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index df4880dfa13..54cef164f1c 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -779,4 +779,17 @@ RSpec.describe SystemNoteService do described_class.change_incident_severity(incident, author) end end + + describe '.log_resolving_alert' do + let(:alert) { build(:alert_management_alert) } + let(:monitoring_tool) { 'Prometheus' } + + it 'calls AlertManagementService' do + expect_next_instance_of(SystemNotes::AlertManagementService) do |service| + expect(service).to receive(:log_resolving_alert).with(monitoring_tool) + end + + described_class.log_resolving_alert(alert, monitoring_tool) + end + end end diff --git a/spec/services/system_notes/alert_management_service_spec.rb b/spec/services/system_notes/alert_management_service_spec.rb index 4ebaa54534c..fc71799d8c5 100644 --- a/spec/services/system_notes/alert_management_service_spec.rb +++ b/spec/services/system_notes/alert_management_service_spec.rb @@ -59,4 +59,17 @@ RSpec.describe ::SystemNotes::AlertManagementService do expect(subject.note).to eq("changed the status to **Resolved** by closing issue #{issue.to_reference(project)}") end end + + describe '#log_resolving_alert' do + subject { described_class.new(noteable: noteable, project: project).log_resolving_alert('Some Service') } + + it_behaves_like 'a system note' do + let(:author) { User.alert_bot } + let(:action) { 'new_alert_added' } + end + + it 'has the appropriate message' do + expect(subject.note).to eq('logged a resolving alert from **Some Service**') + end + end end diff --git a/spec/support/shared_contexts/project_service_jira_context.rb b/spec/support/shared_contexts/project_service_jira_context.rb index 54bb9fd108e..8e01de70846 100644 --- a/spec/support/shared_contexts/project_service_jira_context.rb +++ b/spec/support/shared_contexts/project_service_jira_context.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true RSpec.shared_context 'project service Jira context' do - let(:url) { 'https://jira.example.com' } - let(:test_url) { 'https://jira.example.com/rest/api/2/serverInfo' } + let(:url) { 'http://jira.example.com' } + let(:test_url) { 'http://jira.example.com/rest/api/2/serverInfo' } def fill_form(disable: false) click_active_checkbox if disable @@ -10,5 +10,6 @@ RSpec.shared_context 'project service Jira context' do fill_in 'service_url', with: url fill_in 'service_username', with: 'username' fill_in 'service_password', with: 'password' + fill_in 'service_jira_issue_transition_id', with: '25' end end diff --git a/spec/support/shared_contexts/project_service_shared_context.rb b/spec/support/shared_contexts/project_service_shared_context.rb index a8e75c624e8..b4b9ab456e0 100644 --- a/spec/support/shared_contexts/project_service_shared_context.rb +++ b/spec/support/shared_contexts/project_service_shared_context.rb @@ -15,10 +15,7 @@ RSpec.shared_context 'project service activation' do def visit_project_integration(name) visit_project_integrations - - within('#content-body') do - click_link(name) - end + click_link(name) end def click_active_checkbox diff --git a/spec/support/shared_examples/alert_notification_service_shared_examples.rb b/spec/support/shared_examples/alert_notification_service_shared_examples.rb index 7bd6df8c608..fc935effe0e 100644 --- a/spec/support/shared_examples/alert_notification_service_shared_examples.rb +++ b/spec/support/shared_examples/alert_notification_service_shared_examples.rb @@ -27,3 +27,18 @@ RSpec.shared_examples 'Alert Notification Service sends no notifications' do |ht end end end + +RSpec.shared_examples 'creates status-change system note for an auto-resolved alert' do + it 'has 2 new system notes' do + expect { subject }.to change(Note, :count).by(2) + expect(Note.last.note).to include('Resolved') + end +end + +# Requires `source` to be defined +RSpec.shared_examples 'creates single system note based on the source of the alert' do + it 'has one new system note' do + expect { subject }.to change(Note, :count).by(1) + expect(Note.last.note).to include(source) + end +end diff --git a/spec/workers/pages_update_configuration_worker_spec.rb b/spec/workers/pages_update_configuration_worker_spec.rb index 87bbff1a28b..ff3727646c7 100644 --- a/spec/workers/pages_update_configuration_worker_spec.rb +++ b/spec/workers/pages_update_configuration_worker_spec.rb @@ -2,9 +2,9 @@ require "spec_helper" RSpec.describe PagesUpdateConfigurationWorker do - describe "#perform" do - let_it_be(:project) { create(:project) } + let_it_be(:project) { create(:project) } + describe "#perform" do it "does not break if the project doesn't exist" do expect { subject.perform(-1) }.not_to raise_error end @@ -42,4 +42,22 @@ RSpec.describe PagesUpdateConfigurationWorker do end end end + + describe '#perform_async' do + it "calls the correct service", :sidekiq_inline do + expect_next_instance_of(Projects::UpdatePagesConfigurationService, project) do |service| + expect(service).to receive(:execute).and_return(status: :success) + end + + described_class.perform_async(project.id) + end + + it "doesn't schedule a worker if updates on legacy storage are disabled", :sidekiq_inline do + stub_feature_flags(pages_update_legacy_storage: false) + + expect(Projects::UpdatePagesConfigurationService).not_to receive(:new) + + described_class.perform_async(project.id) + end + end end