diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-15 18:08:10 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-03-15 18:08:10 +0000 |
commit | 7f08e6916d8259a8ed1549cb54460f0b746d9d8b (patch) | |
tree | 40853e994af97de42bd68076bd0bffa6be5c2814 | |
parent | cb7f766437db0c483adf3c128e35c64237a2ef53 (diff) | |
download | gitlab-ce-7f08e6916d8259a8ed1549cb54460f0b746d9d8b.tar.gz |
Add latest changes from gitlab-org/gitlab@master
88 files changed, 1159 insertions, 622 deletions
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 804e2152697..200d09d4331 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -d0d5fa790767c12eeadb40a1ecfbc00fde2a4768 +4ef97df05e54269d90fdbd4d2f59fcc29b1afcdf @@ -153,7 +153,7 @@ gem 'html-pipeline', '~> 2.13.2' gem 'deckar01-task_list', '2.3.1' gem 'gitlab-markup', '~> 1.8.0' gem 'github-markup', '~> 1.7.0', require: 'github/markup' -gem 'commonmarker', '~> 0.23.2' +gem 'commonmarker', '~> 0.23.4' gem 'kramdown', '~> 2.3.1' gem 'RedCloth', '~> 4.3.2' gem 'rdoc', '~> 6.3.2' @@ -405,7 +405,7 @@ group :development, :test, :danger do end group :development, :test, :coverage do - gem 'simplecov', '~> 0.18.5', require: false + gem 'simplecov', '~> 0.21', require: false gem 'simplecov-lcov', '~> 0.8.0', require: false gem 'simplecov-cobertura', '~> 1.3.1', require: false gem 'undercover', '~> 0.4.4', require: false diff --git a/Gemfile.lock b/Gemfile.lock index d2e2d1e6438..00d16ddbfd5 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -202,7 +202,7 @@ GEM open4 (~> 1.3) coderay (1.1.3) colored2 (3.1.2) - commonmarker (0.23.2) + commonmarker (0.23.4) concurrent-ruby (1.1.9) connection_pool (2.2.5) contracts (0.11.0) @@ -281,7 +281,7 @@ GEM diffy (3.3.0) discordrb-webhooks (3.4.2) rest-client (>= 2.0.0) - docile (1.3.2) + docile (1.4.0) domain_name (0.5.20190701) unf (>= 0.0.5, < 1.0.0) doorkeeper (5.5.0.rc2) @@ -1207,13 +1207,15 @@ GEM jwt (>= 1.5, < 3.0) multi_json (~> 1.10) simple_po_parser (1.1.2) - simplecov (0.18.5) + simplecov (0.21.2) docile (~> 1.1) simplecov-html (~> 0.11) + simplecov_json_formatter (~> 0.1) simplecov-cobertura (1.3.1) simplecov (~> 0.8) simplecov-html (0.12.3) simplecov-lcov (0.8.0) + simplecov_json_formatter (0.1.4) sixarm_ruby_unaccent (1.2.0) slack-messenger (2.3.4) snowplow-tracker (0.6.1) @@ -1438,7 +1440,7 @@ DEPENDENCIES capybara-screenshot (~> 1.0.22) carrierwave (~> 1.3) charlock_holmes (~> 0.7.7) - commonmarker (~> 0.23.2) + commonmarker (~> 0.23.4) concurrent-ruby (~> 1.1) connection_pool (~> 2.0) countries (~> 3.0) @@ -1648,7 +1650,7 @@ DEPENDENCIES sidekiq-cron (~> 1.2) sigdump (~> 0.2.4) simple_po_parser (~> 1.1.2) - simplecov (~> 0.18.5) + simplecov (~> 0.21) simplecov-cobertura (~> 1.3.1) simplecov-lcov (~> 0.8.0) slack-messenger (~> 2.3.4) diff --git a/app/assets/javascripts/environments/components/new_environment_folder.vue b/app/assets/javascripts/environments/components/new_environment_folder.vue index 30a178db5cc..d5c6d26cfd0 100644 --- a/app/assets/javascripts/environments/components/new_environment_folder.vue +++ b/app/assets/javascripts/environments/components/new_environment_folder.vue @@ -3,6 +3,7 @@ import { GlButton, GlCollapse, GlIcon, GlBadge, GlLink } from '@gitlab/ui'; import { __, s__ } from '~/locale'; import pollIntervalQuery from '../graphql/queries/poll_interval.query.graphql'; import folderQuery from '../graphql/queries/folder.query.graphql'; +import { ENVIRONMENT_COUNT_BY_SCOPE } from '../constants'; import EnvironmentItem from './new_environment_item.vue'; export default { @@ -56,7 +57,8 @@ export default { return this.visible ? this.$options.i18n.collapse : this.$options.i18n.expand; }, count() { - return this.folder?.[`${this.scope}Count`] ?? 0; + const count = ENVIRONMENT_COUNT_BY_SCOPE[this.scope]; + return this.folder?.[count] ?? 0; }, folderClass() { return { 'gl-font-weight-bold': this.visible }; diff --git a/app/assets/javascripts/environments/components/new_environments_app.vue b/app/assets/javascripts/environments/components/new_environments_app.vue index 8e6457ed918..087e9cd2fcd 100644 --- a/app/assets/javascripts/environments/components/new_environments_app.vue +++ b/app/assets/javascripts/environments/components/new_environments_app.vue @@ -9,6 +9,7 @@ import environmentToDeleteQuery from '../graphql/queries/environment_to_delete.q import environmentToRollbackQuery from '../graphql/queries/environment_to_rollback.query.graphql'; import environmentToStopQuery from '../graphql/queries/environment_to_stop.query.graphql'; import environmentToChangeCanaryQuery from '../graphql/queries/environment_to_change_canary.query.graphql'; +import { ENVIRONMENTS_SCOPE } from '../constants'; import EnvironmentFolder from './new_environment_folder.vue'; import EnableReviewAppModal from './enable_review_app_modal.vue'; import StopEnvironmentModal from './stop_environment_modal.vue'; @@ -82,12 +83,14 @@ export default { }, modalId: 'enable-review-app-info', data() { - const { page = '1', scope = 'available' } = queryToObject(window.location.search); + const { page = '1', scope } = queryToObject(window.location.search); return { interval: undefined, isReviewAppModalVisible: false, page: parseInt(page, 10), - scope, + scope: Object.values(ENVIRONMENTS_SCOPE).includes(scope) + ? scope + : ENVIRONMENTS_SCOPE.AVAILABLE, environmentToDelete: {}, environmentToRollback: {}, environmentToStop: {}, @@ -188,6 +191,7 @@ export default { }); }, }, + ENVIRONMENTS_SCOPE, }; </script> <template> @@ -209,7 +213,10 @@ export default { query-param-name="scope" @primary="showReviewAppModal" > - <gl-tab query-param-value="available" @click="setScope('available')"> + <gl-tab + :query-param-value="$options.ENVIRONMENTS_SCOPE.AVAILABLE" + @click="setScope($options.ENVIRONMENTS_SCOPE.AVAILABLE)" + > <template #title> <span>{{ $options.i18n.available }}</span> <gl-badge size="sm" class="gl-tab-counter-badge"> @@ -217,7 +224,10 @@ export default { </gl-badge> </template> </gl-tab> - <gl-tab query-param-value="stopped" @click="setScope('stopped')"> + <gl-tab + :query-param-value="$options.ENVIRONMENTS_SCOPE.STOPPED" + @click="setScope($options.ENVIRONMENTS_SCOPE.STOPPED)" + > <template #title> <span>{{ $options.i18n.stopped }}</span> <gl-badge size="sm" class="gl-tab-counter-badge"> diff --git a/app/assets/javascripts/environments/constants.js b/app/assets/javascripts/environments/constants.js index 6d427bef4e6..942491039d6 100644 --- a/app/assets/javascripts/environments/constants.js +++ b/app/assets/javascripts/environments/constants.js @@ -38,3 +38,13 @@ export const CANARY_STATUS = { }; export const CANARY_UPDATE_MODAL = 'confirm-canary-change'; + +export const ENVIRONMENTS_SCOPE = { + AVAILABLE: 'available', + STOPPED: 'stopped', +}; + +export const ENVIRONMENT_COUNT_BY_SCOPE = { + [ENVIRONMENTS_SCOPE.AVAILABLE]: 'availableCount', + [ENVIRONMENTS_SCOPE.STOPPED]: 'stoppedCount', +}; diff --git a/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue b/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue index 633dee75237..ca60f876c6f 100644 --- a/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue +++ b/app/assets/javascripts/members/components/filter_sort/members_filtered_search_bar.vue @@ -1,5 +1,4 @@ <script> -import { GlFilteredSearchToken } from '@gitlab/ui'; import { mapState } from 'vuex'; import { getParameterByName, @@ -7,46 +6,24 @@ import { queryToObject, redirectTo, } from '~/lib/utils/url_utility'; -import { s__ } from '~/locale'; import { SEARCH_TOKEN_TYPE, SORT_QUERY_PARAM_NAME, ACTIVE_TAB_QUERY_PARAM_NAME, -} from '~/members/constants'; -import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants'; + AVAILABLE_FILTERED_SEARCH_TOKENS, +} from 'ee_else_ce/members/constants'; import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; export default { name: 'MembersFilteredSearchBar', components: { FilteredSearchBar }, - availableTokens: [ - { - type: 'two_factor', - icon: 'lock', - title: s__('Members|2FA'), - token: GlFilteredSearchToken, - unique: true, - operators: OPERATOR_IS_ONLY, - options: [ - { value: 'enabled', title: s__('Members|Enabled') }, - { value: 'disabled', title: s__('Members|Disabled') }, - ], - requiredPermissions: 'canManageMembers', - }, - { - type: 'with_inherited_permissions', - icon: 'group', - title: s__('Members|Membership'), - token: GlFilteredSearchToken, - unique: true, - operators: OPERATOR_IS_ONLY, - options: [ - { value: 'exclude', title: s__('Members|Direct') }, - { value: 'only', title: s__('Members|Inherited') }, - ], - }, - ], - inject: ['namespace', 'sourceId', 'canManageMembers'], + availableTokens: AVAILABLE_FILTERED_SEARCH_TOKENS, + inject: { + namespace: {}, + sourceId: {}, + canManageMembers: {}, + canFilterByEnterprise: { default: false }, + }, data() { return { initialFilterValue: [], diff --git a/app/assets/javascripts/members/constants.js b/app/assets/javascripts/members/constants.js index 273f1acebc7..49ce00a1689 100644 --- a/app/assets/javascripts/members/constants.js +++ b/app/assets/javascripts/members/constants.js @@ -1,4 +1,7 @@ -import { __ } from '~/locale'; +import { GlFilteredSearchToken } from '@gitlab/ui'; + +import { __, s__ } from '~/locale'; +import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants'; export const FIELD_KEY_ACCOUNT = 'account'; export const FIELD_KEY_SOURCE = 'source'; @@ -82,6 +85,38 @@ export const DEFAULT_SORT = { sortDesc: false, }; +export const FILTERED_SEARCH_TOKEN_TWO_FACTOR = { + type: 'two_factor', + icon: 'lock', + title: s__('Members|2FA'), + token: GlFilteredSearchToken, + unique: true, + operators: OPERATOR_IS_ONLY, + options: [ + { value: 'enabled', title: s__('Members|Enabled') }, + { value: 'disabled', title: s__('Members|Disabled') }, + ], + requiredPermissions: 'canManageMembers', +}; + +export const FILTERED_SEARCH_TOKEN_WITH_INHERITED_PERMISSIONS = { + type: 'with_inherited_permissions', + icon: 'group', + title: s__('Members|Membership'), + token: GlFilteredSearchToken, + unique: true, + operators: OPERATOR_IS_ONLY, + options: [ + { value: 'exclude', title: s__('Members|Direct') }, + { value: 'only', title: s__('Members|Inherited') }, + ], +}; + +export const AVAILABLE_FILTERED_SEARCH_TOKENS = [ + FILTERED_SEARCH_TOKEN_TWO_FACTOR, + FILTERED_SEARCH_TOKEN_WITH_INHERITED_PERMISSIONS, +]; + export const AVATAR_SIZE = 48; export const MEMBER_TYPES = { diff --git a/app/assets/javascripts/members/index.js b/app/assets/javascripts/members/index.js index 510e89240f4..0df876cabd7 100644 --- a/app/assets/javascripts/members/index.js +++ b/app/assets/javascripts/members/index.js @@ -18,6 +18,7 @@ export const initMembersApp = (el, options) => { sourceId, canManageMembers, canExportMembers, + canFilterByEnterprise, exportCsvPath, ...vuexStoreAttributes } = parseDataAttributes(el); @@ -60,6 +61,7 @@ export const initMembersApp = (el, options) => { currentUserId: gon.current_user_id || null, sourceId, canManageMembers, + canFilterByEnterprise, canExportMembers, exportCsvPath, }, diff --git a/app/assets/javascripts/pages/groups/group_members/index.js b/app/assets/javascripts/pages/groups/group_members/index.js index 36f37dab40d..be12ca6b015 100644 --- a/app/assets/javascripts/pages/groups/group_members/index.js +++ b/app/assets/javascripts/pages/groups/group_members/index.js @@ -21,7 +21,7 @@ initMembersApp(document.querySelector('.js-group-members-list-app'), { requestFormatter: groupMemberRequestFormatter, filteredSearchBar: { show: true, - tokens: ['two_factor', 'with_inherited_permissions'], + tokens: ['two_factor', 'with_inherited_permissions', 'enterprise'], searchParam: 'search', placeholder: s__('Members|Filter members'), recentSearchesStorageKey: 'group_members', diff --git a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue index d0ec02bbd0c..573f996a254 100644 --- a/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue +++ b/app/assets/javascripts/pages/projects/learn_gitlab/components/learn_gitlab_section_link.vue @@ -32,7 +32,7 @@ export default { ); }, openInNewTab() { - return ACTION_LABELS[this.action]?.openInNewTab === true; + return ACTION_LABELS[this.action]?.openInNewTab === true || this.value.openInNewTab === true; }, }, methods: { @@ -65,8 +65,6 @@ export default { data-testid="uncompleted-learn-gitlab-link" data-track-action="click_link" :data-track-label="$options.i18n.ACTION_LABELS[action].title" - data-track-property="Growth::Conversion::Experiment::LearnGitLab" - data-track-experiment="change_continuous_onboarding_link_urls" > {{ $options.i18n.ACTION_LABELS[action].title }} </gl-link> 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 880cf699e5e..1887c48dd1b 100644 --- a/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js +++ b/app/assets/javascripts/pages/projects/learn_gitlab/constants/index.js @@ -62,7 +62,6 @@ export const ACTION_LABELS = { description: s__('LearnGitLab|Scan your code to uncover vulnerabilities before deploying.'), section: 'deploy', position: 1, - openInNewTab: true, }, issueCreated: { title: s__('LearnGitLab|Create an issue'), diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 1e51bf3d974..1caf02937d5 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -439,6 +439,12 @@ .na { color: inherit; } + + // Rouge `Comment` token (quoted text in email body) + .c { + color: $gl-grayish-blue; + font-style: italic; + } } } } diff --git a/app/assets/stylesheets/notify.scss b/app/assets/stylesheets/notify.scss index ca8c358d97f..2d501781119 100644 --- a/app/assets/stylesheets/notify.scss +++ b/app/assets/stylesheets/notify.scss @@ -1,24 +1,4 @@ -@import 'framework/mixins'; -@import 'framework/variables'; - -img { - max-width: 100%; - height: auto; -} - -p.details { - font-style: italic; - color: $gray-500; -} - -.footer > p { - font-size: small; - color: $gray-500; -} - -pre.commit-message { - white-space: pre-wrap; -} +@import 'notify_base'; .gl-label-scoped { border: 2px solid currentColor; @@ -52,6 +32,4 @@ pre.commit-message { border: 1px solid $gray-100; border-radius: $border-radius-small; } - - @include email-code-block; } diff --git a/app/assets/stylesheets/notify_base.scss b/app/assets/stylesheets/notify_base.scss new file mode 100644 index 00000000000..8c6f9a27077 --- /dev/null +++ b/app/assets/stylesheets/notify_base.scss @@ -0,0 +1,25 @@ +@import 'framework/mixins'; +@import 'framework/variables'; + +img { + max-width: 100%; + height: auto; +} + +p.details { + font-style: italic; + color: $gray-500; +} + +.footer > p { + font-size: small; + color: $gray-500; +} + +pre.commit-message { + white-space: pre-wrap; +} + +.content { + @include email-code-block; +} diff --git a/app/assets/stylesheets/notify_enhanced.scss b/app/assets/stylesheets/notify_enhanced.scss new file mode 100644 index 00000000000..5df5a8592bf --- /dev/null +++ b/app/assets/stylesheets/notify_enhanced.scss @@ -0,0 +1,68 @@ +// Import a subset of the GitLab UI framework: +// keep parts that affect elements that can appear in emails; +// omit Bootstrap Reboot since it adds unnecessary styles to every element. +@import 'notify_base'; +@import 'bootstrap/scss/functions'; +@import 'bootstrap/scss/variables'; +@import 'bootstrap/scss/mixins'; +@import 'bootstrap/scss/code'; +@import '@gitlab/ui/src/scss/variables'; +@import '@gitlab/ui/src/scss/utility-mixins/index'; +@import '@gitlab/ui/src/scss/components'; +@import 'bootstrap_migration'; +@import 'framework/common'; +@import 'framework/gfm'; +@import 'framework/kbd'; +@import 'framework/tables'; +@import 'framework/typography'; +@import 'framework/emojis'; + +body { + font-family: $regular-font; + font-size: inherit; +} + +a { + text-decoration: none; +} + +.content { + .md { + padding: 1rem 0; + } + + hr { + border: 1px solid #e1e1e1; + } + + blockquote { + border-top-width: 0; + border-bottom-width: 0; + border-right-width: 0; + + &:dir(rtl) { + border-left-width: 0; + border-right-width: inherit; + } + } + + table { + border-collapse: collapse; + } + + .diff-table.code, + table.code { + width: auto; + + td { + padding: inherit; + + pre { + background-color: inherit; + margin: 0; + padding: 0; + border: inherit; + } + } + } +} diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb index 84ebdcd9364..eabc048e341 100644 --- a/app/controllers/projects/environments_controller.rb +++ b/app/controllers/projects/environments_controller.rb @@ -29,13 +29,14 @@ class Projects::EnvironmentsController < Projects::ApplicationController feature_category :continuous_delivery def index - @environments = project.environments - .with_state(params[:scope] || :available) @project = ProjectPresenter.new(project, current_user: current_user) respond_to do |format| format.html format.json do + @environments = project.environments + .with_state(params[:scope] || :available) + Gitlab::PollingInterval.set_header(response, interval: 3_000) environments_count_by_state = project.environments.count_by_state @@ -52,14 +53,15 @@ class Projects::EnvironmentsController < Projects::ApplicationController # Returns all environments for a given folder # rubocop: disable CodeReuse/ActiveRecord def folder - folder_environments = project.environments.where(environment_type: params[:id]) - @environments = folder_environments.with_state(params[:scope] || :available) - .order(:name) @folder = params[:id] respond_to do |format| format.html format.json do + folder_environments = project.environments.where(environment_type: params[:id]) + @environments = folder_environments.with_state(params[:scope] || :available) + .order(:name) + render json: { environments: serialize_environments(request, response), available_count: folder_environments.available.count, diff --git a/app/controllers/projects/redirect_controller.rb b/app/controllers/projects/redirect_controller.rb new file mode 100644 index 00000000000..6bcbe87ee42 --- /dev/null +++ b/app/controllers/projects/redirect_controller.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +# Projects::RedirectController is used to resolve the route projects/:id. +# It's helpful for this to be in its own controller so that the +# ProjectsController can assume that :namespace_id exists +class Projects::RedirectController < ::ApplicationController + skip_before_action :authenticate_user! + + feature_category :projects + + def redirect_from_id + project = Project.find(params[:id]) + + if can?(current_user, :read_project, project) + redirect_to project + else + render_404 + end + end +end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 7b45bed426b..9f971800ff5 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -17,10 +17,10 @@ class ProjectsController < Projects::ApplicationController around_action :allow_gitaly_ref_name_caching, only: [:index, :show] before_action :disable_query_limiting, only: [:show, :create] - before_action :authenticate_user!, except: [:index, :show, :activity, :refs, :resolve, :unfoldered_environment_names] + before_action :authenticate_user!, except: [:index, :show, :activity, :refs, :unfoldered_environment_names] before_action :redirect_git_extension, only: [:show] - before_action :project, except: [:index, :new, :create, :resolve] - before_action :repository, except: [:index, :new, :create, :resolve] + before_action :project, except: [:index, :new, :create] + before_action :repository, except: [:index, :new, :create] before_action :verify_git_import_enabled, only: [:create] before_action :project_export_enabled, only: [:export, :download_export, :remove_export, :generate_new_export] before_action :present_project, only: [:edit] @@ -48,7 +48,7 @@ class ProjectsController < Projects::ApplicationController feature_category :projects, [ :index, :show, :new, :create, :edit, :update, :transfer, - :destroy, :resolve, :archive, :unarchive, :toggle_star, :activity + :destroy, :archive, :unarchive, :toggle_star, :activity ] feature_category :source_code_management, [:remove_fork, :housekeeping, :refs] @@ -324,16 +324,6 @@ class ProjectsController < Projects::ApplicationController end # rubocop: enable CodeReuse/ActiveRecord - def resolve - @project = Project.find(params[:id]) - - if can?(current_user, :read_project, @project) - redirect_to @project - else - render_404 - end - end - def unfoldered_environment_names respond_to do |format| format.json do diff --git a/app/graphql/resolvers/concerns/group_issuable_resolver.rb b/app/graphql/resolvers/concerns/group_issuable_resolver.rb index 542ff5374ff..92d22409ff2 100644 --- a/app/graphql/resolvers/concerns/group_issuable_resolver.rb +++ b/app/graphql/resolvers/concerns/group_issuable_resolver.rb @@ -3,12 +3,21 @@ module GroupIssuableResolver extend ActiveSupport::Concern - class_methods do - def include_subgroups(name_of_things) - argument :include_subgroups, GraphQL::Types::Boolean, - required: false, - default_value: false, - description: "Include #{name_of_things} belonging to subgroups" - end + included do + argument :include_subgroups, GraphQL::Types::Boolean, + required: false, + default_value: false, + description: "Include #{issuable_collection_name} belonging to subgroups" + + argument :include_archived, GraphQL::Types::Boolean, + required: false, + default_value: false, + description: "Return #{issuable_collection_name} from archived projects" + end + + def resolve(**args) + args[:non_archived] = !args.delete(:include_archived) + + super end end diff --git a/app/graphql/resolvers/group_issues_resolver.rb b/app/graphql/resolvers/group_issues_resolver.rb index 28f9266974f..05c5e803539 100644 --- a/app/graphql/resolvers/group_issues_resolver.rb +++ b/app/graphql/resolvers/group_issues_resolver.rb @@ -3,9 +3,11 @@ module Resolvers class GroupIssuesResolver < BaseIssuesResolver - include GroupIssuableResolver + def self.issuable_collection_name + 'issues' + end - include_subgroups 'issues' + include GroupIssuableResolver def ready?(**args) if args.dig(:not, :release_tag).present? diff --git a/app/graphql/resolvers/group_merge_requests_resolver.rb b/app/graphql/resolvers/group_merge_requests_resolver.rb index 34a4c67bc56..da1b6169c07 100644 --- a/app/graphql/resolvers/group_merge_requests_resolver.rb +++ b/app/graphql/resolvers/group_merge_requests_resolver.rb @@ -2,13 +2,16 @@ module Resolvers class GroupMergeRequestsResolver < MergeRequestsResolver + def self.issuable_collection_name + 'merge requests' + end + include GroupIssuableResolver alias_method :group, :object type Types::MergeRequestType.connection_type, null: true - include_subgroups 'merge requests' accept_assignee accept_author diff --git a/app/helpers/learn_gitlab_helper.rb b/app/helpers/learn_gitlab_helper.rb index 7dfd9ed47e3..60f3b12d736 100644 --- a/app/helpers/learn_gitlab_helper.rb +++ b/app/helpers/learn_gitlab_helper.rb @@ -1,6 +1,10 @@ # frozen_string_literal: true module LearnGitlabHelper + IMAGE_PATH_PLAN = "learn_gitlab/section_plan.svg" + IMAGE_PATH_DEPLOY = "learn_gitlab/section_deploy.svg" + IMAGE_PATH_WORKSPACE = "learn_gitlab/section_workspace.svg" + def learn_gitlab_enabled?(project) return false unless current_user @@ -25,19 +29,7 @@ module LearnGitlabHelper def onboarding_actions_data(project) attributes = onboarding_progress(project).attributes.symbolize_keys - urls_to_use = nil - - experiment( - :change_continuous_onboarding_link_urls, - namespace: project.namespace, - actor: current_user, - sticky_to: project.namespace - ) do |e| - e.control { urls_to_use = action_urls } - e.candidate { urls_to_use = new_action_urls(project) } - end - - urls_to_use.to_h do |action, url| + action_urls(project).to_h do |action, url| [ action, url: url, @@ -50,13 +42,13 @@ module LearnGitlabHelper def onboarding_sections_data { workspace: { - svg: image_path("learn_gitlab/section_workspace.svg") + svg: image_path(IMAGE_PATH_WORKSPACE) }, plan: { - svg: image_path("learn_gitlab/section_plan.svg") + svg: image_path(IMAGE_PATH_PLAN) }, deploy: { - svg: image_path("learn_gitlab/section_deploy.svg") + svg: image_path(IMAGE_PATH_DEPLOY) } } end @@ -65,22 +57,20 @@ module LearnGitlabHelper { name: project.name } end - def action_urls - LearnGitlab::Onboarding::ACTION_ISSUE_IDS.transform_values { |id| project_issue_url(learn_gitlab_project, id) } - .merge(LearnGitlab::Onboarding::ACTION_DOC_URLS) - end - - def new_action_urls(project) - action_urls.merge( + def action_urls(project) + action_issue_urls.merge( issue_created: project_issues_path(project), git_write: project_path(project), - pipeline_created: project_pipelines_path(project), merge_request_created: project_merge_requests_path(project), user_added: project_members_url(project), security_scan_enabled: project_security_configuration_path(project) ) end + def action_issue_urls + LearnGitlab::Onboarding::ACTION_ISSUE_IDS.transform_values { |id| project_issue_url(learn_gitlab_project, id) } + end + def learn_gitlab_project @learn_gitlab_project ||= LearnGitlab::Project.new(current_user).project end diff --git a/app/models/project.rb b/app/models/project.rb index e55395b32e7..3778db48ff2 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -3046,10 +3046,6 @@ class Project < ApplicationRecord Projects::SyncEvent.enqueue_worker end end - - def allow_serialization?(options = nil) - Feature.disabled?(:block_project_serialization, self, default_enabled: :yaml) || super - end end Project.prepend_mod_with('Project') diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index e922b505be8..3b979f69cac 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -3,7 +3,10 @@ %meta{ content: "text/html; charset=utf-8", "http-equiv" => "Content-Type" } %title GitLab - = stylesheet_link_tag 'notify' + - if Feature.enabled?(:enhanced_notify_css) + = stylesheet_link_tag 'notify_enhanced' + - else + = stylesheet_link_tag 'notify' = yield :head %body .content diff --git a/app/views/layouts/service_desk.html.haml b/app/views/layouts/service_desk.html.haml index 26d15a74403..a838ba91d26 100644 --- a/app/views/layouts/service_desk.html.haml +++ b/app/views/layouts/service_desk.html.haml @@ -5,7 +5,10 @@ %title GitLab -# haml-lint:enable NoPlainNodes - = stylesheet_link_tag 'notify' + - if Feature.enabled?(:enhanced_notify_css) + = stylesheet_link_tag 'notify_enhanced' + - else + = stylesheet_link_tag 'notify' = yield :head %body .content diff --git a/app/views/notify/_note_email.html.haml b/app/views/notify/_note_email.html.haml index ad0c873bf56..55984472047 100644 --- a/app/views/notify/_note_email.html.haml +++ b/app/views/notify/_note_email.html.haml @@ -25,11 +25,11 @@ = content_for :head do = stylesheet_link_tag 'mailers/highlighted_diff_email' - %table + %table.code = render partial: "projects/diffs/email_line", collection: discussion.truncated_diff_lines(diff_limit: diff_limit), as: :line, locals: { diff_file: discussion.diff_file } -%div{ style: note_style } +.md{ style: note_style } = markdown(note.note, pipeline: :email, author: note.author, current_user: @recipient, issuable_reference_expansion_enabled: true) diff --git a/app/views/notify/issue_due_email.html.haml b/app/views/notify/issue_due_email.html.haml index c9cd9c32b54..e512d7732e2 100644 --- a/app/views/notify/issue_due_email.html.haml +++ b/app/views/notify/issue_due_email.html.haml @@ -8,5 +8,5 @@ This issue is due on: #{@issue.due_date.to_s(:medium)} - if @issue.description - %div - = markdown(@issue.description, pipeline: :email, author: @issue.author, current_user: @recipient, issuable_reference_expansion_enabled: true) + .md + = markdown(@issue.description, pipeline: :email, author: @issue.author, current_user: @recipient, issuable_reference_expansion_enabled: true) diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml index 439604a950a..592b3f453af 100644 --- a/app/views/notify/new_issue_email.html.haml +++ b/app/views/notify/new_issue_email.html.haml @@ -7,5 +7,5 @@ = assignees_label(@issue) - if @issue.description - %div - = markdown(@issue.description, pipeline: :email, author: @issue.author, current_user: @recipient, issuable_reference_expansion_enabled: true) + .md + = markdown(@issue.description, pipeline: :email, author: @issue.author, current_user: @recipient, issuable_reference_expansion_enabled: true) diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml index 54fb6573c26..f67ac5f8fb2 100644 --- a/app/views/notify/new_merge_request_email.html.haml +++ b/app/views/notify/new_merge_request_email.html.haml @@ -15,5 +15,5 @@ = render_if_exists 'notify/merge_request_approvers', presenter: @mr_presenter - if @merge_request.description - %div + .md = markdown(@merge_request.description, pipeline: :email, author: @merge_request.author, current_user: @recipient, issuable_reference_expansion_enabled: true) diff --git a/app/views/notify/new_release_email.html.haml b/app/views/notify/new_release_email.html.haml index 1cd3a2340c6..09c0e7a8abd 100644 --- a/app/views/notify/new_release_email.html.haml +++ b/app/views/notify/new_release_email.html.haml @@ -1,7 +1,7 @@ - release_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: @target_url } - description_details = { tag: @release.tag, name: @project.name, release_link_start: release_link_start, release_link_end: '</a>'.html_safe } -%div{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif;" } +.md{ style: "font-family: 'Helvetica Neue',Helvetica,Arial,sans-serif;" } %p = _("A new Release %{tag} for %{name} was published. Visit the %{release_link_start}Releases page%{release_link_end} to read more about it.").html_safe % description_details diff --git a/app/views/notify/service_desk_new_note_email.html.haml b/app/views/notify/service_desk_new_note_email.html.haml index 186bdf133e3..0c16cf3315f 100644 --- a/app/views/notify/service_desk_new_note_email.html.haml +++ b/app/views/notify/service_desk_new_note_email.html.haml @@ -1,5 +1,5 @@ - if Gitlab::CurrentSettings.email_author_in_body %div = _("%{author_link} wrote:").html_safe % { author_link: link_to(@note.author_name, user_url(@note.author)) } -%div +.md = markdown(@note.note, pipeline: :email, author: @note.author, issuable_reference_expansion_enabled: true) diff --git a/config/application.rb b/config/application.rb index 8d795e6bc4e..e66f64ce791 100644 --- a/config/application.rb +++ b/config/application.rb @@ -247,6 +247,7 @@ module Gitlab config.assets.precompile << "mailer.css" config.assets.precompile << "mailer_client_specific.css" config.assets.precompile << "notify.css" + config.assets.precompile << "notify_enhanced.css" config.assets.precompile << "mailers/*.css" config.assets.precompile << "page_bundles/_mixins_and_variables_and_functions.css" config.assets.precompile << "page_bundles/admin/application_settings_metrics_and_profiling.css" diff --git a/config/feature_flags/development/block_project_serialization.yml b/config/feature_flags/development/enhanced_notify_css.yml index c4ef7145fcb..e47db3ba435 100644 --- a/config/feature_flags/development/block_project_serialization.yml +++ b/config/feature_flags/development/enhanced_notify_css.yml @@ -1,8 +1,8 @@ --- -name: block_project_serialization -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/81900 +name: enhanced_notify_css +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/78604 rollout_issue_url: -milestone: '14.9' +milestone: '14.8' type: development -group: group::workspace +group: group::project management default_enabled: false diff --git a/config/feature_flags/experiment/change_continuous_onboarding_link_urls.yml b/config/feature_flags/experiment/change_continuous_onboarding_link_urls.yml deleted file mode 100644 index e65d7cd8d94..00000000000 --- a/config/feature_flags/experiment/change_continuous_onboarding_link_urls.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: change_continuous_onboarding_link_urls -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71408 -rollout_issue_url: -milestone: '14.5' -type: experiment -group: group::conversion -default_enabled: false diff --git a/config/routes.rb b/config/routes.rb index 910ddb2e571..4811e59d3e6 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -269,7 +269,7 @@ Rails.application.routes.draw do resources :projects, only: [:index, :new, :create] - get '/projects/:id' => 'projects#resolve' + get '/projects/:id' => 'projects/redirect#redirect_from_id' draw :git_http draw :api diff --git a/doc/.vale/gitlab/HeadingContent.yml b/doc/.vale/gitlab/HeadingContent.yml new file mode 100644 index 00000000000..a8dc596f2a2 --- /dev/null +++ b/doc/.vale/gitlab/HeadingContent.yml @@ -0,0 +1,18 @@ +--- +# Error: gitlab.HeadingContent +# +# Checks for generic, unhelpful subheadings. +# +# For a list of all options, see https://errata-ai.gitbook.io/vale/getting-started/styles +extends: existence +message: 'Rename the subheading "%s", or re-purpose the content elsewhere.' +level: warning +scope: heading +link: https://docs.gitlab.com/ee/development/documentation/styleguide/#headings-1 +ignorecase: false +tokens: + - How it works + - Limitations + - Overview + - Use cases? + - Important notes? diff --git a/doc/administration/geo/index.md b/doc/administration/geo/index.md index c0a5b2e0ff0..1b80e91c9c4 100644 --- a/doc/administration/geo/index.md +++ b/doc/administration/geo/index.md @@ -200,7 +200,7 @@ This list of limitations only reflects the latest version of GitLab. If you are - Pushing directly to a **secondary** site redirects (for HTTP) or proxies (for SSH) the request to the **primary** site instead of [handling it directly](https://gitlab.com/gitlab-org/gitlab/-/issues/1381), except when using Git over HTTP with credentials embedded in the URI. For example, `https://user:password@secondary.tld`. - The **primary** site has to be online for OAuth login to happen. Existing sessions and Git are not affected. Support for the **secondary** site to use an OAuth provider independent from the primary is [being planned](https://gitlab.com/gitlab-org/gitlab/-/issues/208465). -- The installation takes multiple manual steps that together can take about an hour depending on circumstances. We are working on improving this experience. See [Omnibus GitLab issue #2978](https://gitlab.com/gitlab-org/omnibus-gitlab/-/issues/2978) for details. +- The installation takes multiple manual steps that together can take about an hour depending on circumstances. Consider using [the GitLab Environment Toolkit](https://gitlab.com/gitlab-org/gitlab-environment-toolkit) to deploy and operate production GitLab instances based on our [Reference Architectures](../reference_architectures/index.md), including automation of common daily tasks. We are planning to [improve Geo's installation even further](https://gitlab.com/groups/gitlab-org/-/epics/1465). - Real-time updates of issues/merge requests (for example, via long polling) doesn't work on the **secondary** site. - GitLab Runners cannot register with a **secondary** site. Support for this is [planned for the future](https://gitlab.com/gitlab-org/gitlab/-/issues/3294). - [Selective synchronization](replication/configuration.md#selective-synchronization) only limits what repositories and files are replicated. The entire PostgreSQL data is still replicated. Selective synchronization is not built to accommodate compliance / export control use cases. diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 3c7be07227d..dab2609bfbd 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -11473,6 +11473,7 @@ four standard [pagination arguments](#connection-pagination-arguments): | <a id="groupissuesepicid"></a>`epicId` | [`String`](#string) | ID of an epic associated with the issues, "none" and "any" values are supported. | | <a id="groupissuesiid"></a>`iid` | [`String`](#string) | IID of the issue. For example, "1". | | <a id="groupissuesiids"></a>`iids` | [`[String!]`](#string) | List of IIDs of issues. For example, `["1", "2"]`. | +| <a id="groupissuesincludearchived"></a>`includeArchived` | [`Boolean`](#boolean) | Return issues from archived projects. | | <a id="groupissuesincludesubepics"></a>`includeSubepics` | [`Boolean`](#boolean) | Whether to include subepics when filtering issues by epicId. | | <a id="groupissuesincludesubgroups"></a>`includeSubgroups` | [`Boolean`](#boolean) | Include issues belonging to subgroups. | | <a id="groupissuesiterationid"></a>`iterationId` | [`[ID]`](#id) | List of iteration Global IDs applied to the issue. | @@ -11606,6 +11607,7 @@ four standard [pagination arguments](#connection-pagination-arguments): | <a id="groupmergerequestscreatedbefore"></a>`createdBefore` | [`Time`](#time) | Merge requests created before this timestamp. | | <a id="groupmergerequestsdraft"></a>`draft` | [`Boolean`](#boolean) | Limit result to draft merge requests. | | <a id="groupmergerequestsiids"></a>`iids` | [`[String!]`](#string) | Array of IIDs of merge requests, for example `[1, 2]`. | +| <a id="groupmergerequestsincludearchived"></a>`includeArchived` | [`Boolean`](#boolean) | Return merge requests from archived projects. | | <a id="groupmergerequestsincludesubgroups"></a>`includeSubgroups` | [`Boolean`](#boolean) | Include merge requests belonging to subgroups. | | <a id="groupmergerequestslabels"></a>`labels` | [`[String!]`](#string) | Array of label names. All resolved merge requests will have all of these labels. | | <a id="groupmergerequestsmergedafter"></a>`mergedAfter` | [`Time`](#time) | Merge requests merged after this date. | diff --git a/doc/api/group_labels.md b/doc/api/group_labels.md index 96b8a162e34..e0f1b451231 100644 --- a/doc/api/group_labels.md +++ b/doc/api/group_labels.md @@ -8,7 +8,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w > [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/21368) in GitLab 11.8. -This API supports managing [group labels](../user/project/labels.md#project-labels-and-group-labels). +This API supports managing [group labels](../user/project/labels.md#types-of-labels). It allows users to list, create, update, and delete group labels. Furthermore, users can subscribe to and unsubscribe from group labels. diff --git a/doc/api/issues.md b/doc/api/issues.md index 5801f072062..ef0727e1c13 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -210,6 +210,28 @@ Issues created by users on GitLab Premium or higher include the `epic` property: } ``` +Issues created by users on GitLab Premium or higher include the `iteration` property: + +```json +{ + "iteration": { + "id":90, + "iid":4, + "sequence":2, + "group_id":162, + "title":null, + "description":null, + "state":2, + "created_at":"2022-03-14T05:21:11.929Z", + "updated_at":"2022-03-14T05:21:11.929Z", + "start_date":"2022-03-08", + "due_date":"2022-03-14", + "web_url":"http://gitlab.com/groups/my-group/-/iterations/90" + } + ... +} +``` + Issues created by users on GitLab Ultimate include the `health_status` property: ```json diff --git a/doc/development/documentation/index.md b/doc/development/documentation/index.md index 5cf7bb74549..66d6beb821f 100644 --- a/doc/development/documentation/index.md +++ b/doc/development/documentation/index.md @@ -7,7 +7,7 @@ description: Learn how to contribute to GitLab Documentation. # GitLab Documentation guidelines -The GitLab documentation is [intended as the single source of truth (SSOT)](https://about.gitlab.com/handbook/documentation/) for information about how to configure, use, and troubleshoot GitLab. The documentation contains use cases and usage instructions for every GitLab feature, organized by product area and subject. This includes topics and workflows that span multiple GitLab features, and the use of GitLab with other applications. +The GitLab documentation is [intended as the single source of truth (SSOT)](https://about.gitlab.com/handbook/documentation/) for information about how to configure, use, and troubleshoot GitLab. The documentation contains use cases and usage instructions for every GitLab feature, organized by product area and subject. This includes topics and workflows that span multiple GitLab features and the use of GitLab with other applications. In addition to this page, the following resources can help you craft and contribute to documentation: @@ -55,9 +55,9 @@ docs-only merge requests using the following guide: [Contributions to GitLab docs](workflow.md) are welcome from the entire GitLab community. -To ensure that GitLab docs are current, there are special processes and responsibilities for all [feature changes](workflow.md), that is development work that impacts the appearance, usage, or administration of a feature. +To ensure that the GitLab docs are current, there are special processes and responsibilities for all [feature changes](workflow.md), that is development work that impacts the appearance, usage, or administration of a feature. -However, anyone can contribute [documentation improvements](workflow.md) that are not associated with a feature change. For example, adding a new doc on how to accomplish a use case that's already possible with GitLab or with third-party tools and GitLab. +However, anyone can contribute [documentation improvements](workflow.md) that are not associated with a feature change. For example, adding a new document on how to accomplish a use case that's already possible with GitLab or with third-party tools and GitLab. ## Markdown and styles @@ -87,8 +87,8 @@ belongs to, as well as an information block as described below: - `group`: The [Group](https://about.gitlab.com/company/team/structure/#product-groups) to which the majority of the page's content belongs. - `info`: The following line, which provides direction to contributors regarding - how to contact the Technical Writer associated with the page's Stage and - Group: + how to contact the Technical Writer associated with the page's stage and + group: ```plaintext To determine the technical writer assigned to the Stage/Group @@ -116,7 +116,7 @@ The following metadata should be added when a page is moved to another location: location to which visitors should be redirected for a moved page. [Learn more](redirects.md). - `disqus_identifier`: Identifier for Disqus commenting system. Used to keep - comments with a page that's been moved to a new URL. + comments with a page that has been moved to a new URL. [Learn more](redirects.md#redirections-for-pages-with-disqus-comments). ### Comments metadata @@ -192,8 +192,8 @@ For example: 1. The change shows up in the 14.5 self-managed release, due to missing the release cutoff for 14.4. -The exact cutoff date for each release is flexible, and can be earlier or later -than expected due to holidays, weekends, or other events. In general, MRs merged +The exact cutoff date for each release is flexible, and can be sooner or later +than expected due to holidays, weekends or other events. In general, MRs merged by the 17th should be present in the release on the 22nd, though it is not guaranteed. If it is important that a documentation update is present in that month's release, merge it as early as possible. @@ -209,7 +209,7 @@ with the following conventions: - It's relative to the `doc/` directory in the GitLab repository. - It omits the `.md` extension. -- It doesn't end with a slash (`/`). +- It doesn't end with a forward slash (`/`). The help text follows the [Pajamas guidelines](https://design.gitlab.com/usability/helping-users/#formatting-help-content). @@ -316,7 +316,7 @@ process. This is configured in the `Dangerfile` in the GitLab repository under ## Automatic screenshot generator -You can now set up an automatic screenshot generator to take and compress screenshots, with the +You can now set up an automatic screenshot generator to take and compress screenshots with the help of a configuration file known as **screenshot generator**. ### Use the tool diff --git a/doc/development/service_ping/index.md b/doc/development/service_ping/index.md index ee3782dd5a9..e06ee648d34 100644 --- a/doc/development/service_ping/index.md +++ b/doc/development/service_ping/index.md @@ -68,7 +68,10 @@ Because of these limitations we recommend you: > Introduced in GitLab 14.1. -In GitLab versions 14.1 and later, free self-managed users running [GitLab EE](../ee_features.md) can receive paid features by registering with GitLab and sending us activity data through Service Ping. Features introduced here do not remove the feature from its paid tier. Users can continue to access the features in a paid tier without sharing usage data. +In GitLab versions 14.1 and later, GitLab Free customers with a self-managed instance running +[GitLab EE](../ee_features.md) can receive paid features by registering with GitLab and sending us +activity data through Service Ping. Features introduced here do not remove the feature from its paid +tier. Users can continue to access the features in a paid tier without sharing usage data. #### Features available in 14.1 and later diff --git a/doc/user/application_security/cluster_image_scanning/index.md b/doc/user/application_security/cluster_image_scanning/index.md index 8bf0efd5261..293645b8de6 100644 --- a/doc/user/application_security/cluster_image_scanning/index.md +++ b/doc/user/application_security/cluster_image_scanning/index.md @@ -46,7 +46,7 @@ To enable cluster image scanning in your pipeline, you need the following: - [GitLab Runner](https://docs.gitlab.com/runner/) with the [`docker`](https://docs.gitlab.com/runner/executors/docker.html) or [`kubernetes`](https://docs.gitlab.com/runner/install/kubernetes.html) - executor. + executor on Linux/amd64. - Docker `18.09.03` or later installed on the same computer as the runner. If you're using the shared runners on GitLab.com, then this is already the case. - [Starboard Operator](https://aquasecurity.github.io/starboard/v0.10.3/operator/installation/kubectl/) diff --git a/doc/user/application_security/container_scanning/index.md b/doc/user/application_security/container_scanning/index.md index 7dcbaf9e649..f2d6cef669d 100644 --- a/doc/user/application_security/container_scanning/index.md +++ b/doc/user/application_security/container_scanning/index.md @@ -50,7 +50,7 @@ To enable container scanning in your pipeline, you need the following: - Container Scanning runs in the `test` stage, which is available by default. If you redefine the stages in the `.gitlab-ci.yml` file, the `test` stage is required. - [GitLab Runner](https://docs.gitlab.com/runner/) with the [`docker`](https://docs.gitlab.com/runner/executors/docker.html) - or [`kubernetes`](https://docs.gitlab.com/runner/install/kubernetes.html) executor. + or [`kubernetes`](https://docs.gitlab.com/runner/install/kubernetes.html) executor on Linux/amd64. - Docker `18.09.03` or higher installed on the same computer as the runner. If you're using the shared runners on GitLab.com, then this is already the case. - An image matching the [supported distributions](#supported-distributions). diff --git a/doc/user/group/index.md b/doc/user/group/index.md index 86c33933144..4b9ff7f64e8 100644 --- a/doc/user/group/index.md +++ b/doc/user/group/index.md @@ -172,6 +172,7 @@ Filter a group to find members. By default, all members in the group and subgrou - To view members in the group only, select **Membership = Direct**. - To view members of the group and its subgroups, select **Membership = Inherited**. - To view members with two-factor authentication enabled or disabled, select **2FA = Enabled** or **Disabled**. + - [In GitLab 14.0 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/349887), to view GitLab users created by [SAML SSO](saml_sso/index.md) or [SCIM provisioning](saml_sso/scim_setup.md) select **Enterprise = true**. ### Search a group diff --git a/doc/user/project/img/labels_sort_label_priority.png b/doc/user/project/img/labels_sort_label_priority.png Binary files differdeleted file mode 100644 index faf629ac61d..00000000000 --- a/doc/user/project/img/labels_sort_label_priority.png +++ /dev/null diff --git a/doc/user/project/img/labels_sort_priority.png b/doc/user/project/img/labels_sort_priority.png Binary files differdeleted file mode 100644 index a6b5fca26f4..00000000000 --- a/doc/user/project/img/labels_sort_priority.png +++ /dev/null diff --git a/doc/user/project/img/labels_subscriptions_v13_5.png b/doc/user/project/img/labels_subscriptions_v13_5.png Binary files differdeleted file mode 100644 index a2a4e9e50cc..00000000000 --- a/doc/user/project/img/labels_subscriptions_v13_5.png +++ /dev/null diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index 428cb4a7f17..d731ceb5aca 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -536,7 +536,7 @@ changing a label. A typical workflow of using an issue board would be: -1. You have [created](labels.md#label-management) and [prioritized](labels.md#label-priority) +1. You [create](labels.md#create-a-label) and [prioritize](labels.md#set-label-priority) labels to categorize your issues. 1. You have a bunch of issues (ideally labeled). 1. You visit the issue board and start [creating lists](#create-a-new-list) to diff --git a/doc/user/project/issues/sorting_issue_lists.md b/doc/user/project/issues/sorting_issue_lists.md index 329f65bfacd..9311ef590df 100644 --- a/doc/user/project/issues/sorting_issue_lists.md +++ b/doc/user/project/issues/sorting_issue_lists.md @@ -49,7 +49,7 @@ Ties are broken arbitrarily. Only the highest prioritized label is checked, and labels with a lower priority are ignored. For more information, see [issue 14523](https://gitlab.com/gitlab-org/gitlab/-/issues/14523). -To learn more about priority labels, read the [Labels](../labels.md#label-priority) documentation. +To learn how to change label priority, see [Label priority](../labels.md#set-label-priority). ## Sorting by last updated @@ -98,7 +98,9 @@ When you sort by **Priority**, the issue order changes to sort in this order: 1. Issues with a higher priority label. 1. Issues without a prioritized label. -To learn more about priority, read the [Labels](../labels.md#label-priority) documentation. +Ties are broken arbitrarily. + +To learn how to change label priority, see [Label priority](../labels.md#set-label-priority). ## Sorting by title diff --git a/doc/user/project/labels.md b/doc/user/project/labels.md index 45ed5960626..18197cd860f 100644 --- a/doc/user/project/labels.md +++ b/doc/user/project/labels.md @@ -6,10 +6,10 @@ info: To determine the technical writer assigned to the Stage/Group associated w # Labels **(FREE)** -As your count of issues, merge requests, and epics grows in GitLab, it's more and more challenging +As your count of issues, merge requests, and epics grows in GitLab, it gets more challenging to keep track of those items. Especially as your organization grows from just a few people to -hundreds or thousands. This is where labels come in. They help you organize and tag your work -so you can track and find the work items you're interested in. +hundreds or thousands. With labels, you can organize and tag your work, and track the work items +you're interested in. Labels are a key part of [issue boards](issue_board.md). With labels you can: @@ -19,132 +19,257 @@ Labels are a key part of [issue boards](issue_board.md). With labels you can: - [Search lists of issues, merge requests, and epics](../search/index.md#search-issues-and-merge-requests), as well as [issue boards](../search/index.md#issue-boards). -## Project labels and group labels +## Types of labels -There are two types of labels in GitLab: +You can use two types of labels in GitLab: - **Project labels** can be assigned to issues and merge requests in that project only. -- **Group labels** can be assigned to issues and merge requests in any project in - the selected group or its subgroups. - - They can also be assigned to [epics](../group/epics/index.md) in the selected group or its subgroups. +- **Group labels** can be assigned to issues, merge requests, and [epics](../group/epics/index.md) + in any project in the selected group or its subgroups. ## Assign and unassign labels > Unassigning labels with the **X** button [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216881) in GitLab 13.5. -Every issue, merge request, and epic can be assigned any number of labels. The labels are -managed in the right sidebar, where you can assign or unassign labels as needed. +You can assign labels to any issue, merge request, or epic. To assign or unassign a label: -1. In the **Labels** section of the sidebar, click **Edit**. +1. In the **Labels** section of the sidebar, select **Edit**. 1. In the **Assign labels** list, search for labels by typing their names. You can search repeatedly to add more labels. The selected labels are marked with a checkmark. -1. Click the labels you want to assign or unassign. +1. Select the labels you want to assign or unassign. 1. To apply your changes to labels, select **X** next to **Assign labels** or select any area outside the label section. -Alternatively, to unassign a label, click the **X** on the label you want to unassign. +Alternatively, to unassign a label, select the **X** on the label you want to unassign. -You can also assign a label with the `/label` [quick action](quick_actions.md), -remove labels with `/unlabel`, and reassign labels (remove all and assign new ones) with `/relabel`. +You can also assign and unassign labels with [quick actions](quick_actions.md): -## Label management +- Assign labels with `/label`. +- Remove labels with `/unlabel`. +- Remove all labels and assign new ones with `/relabel`. -Users with a [permission level](../permissions.md) of Reporter or higher are able to create -and edit labels. +## View available labels -### Project labels +### View project labels -> Showing all inherited labels [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241990) in GitLab 13.5. +> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/241990) in GitLab 13.5: the label list in a project also shows all inherited labels. -To view a project's available labels, in the project, go to **Project information > Labels**. -Its list of labels includes both the labels defined at the project level, and -all labels defined by its ancestor groups. For each label, you can see the -project or group path from where it was created. You can filter the list by -entering a search query in the **Filter** field, and then clicking its search -icon (**{search}**). +To view the **project's labels**: -To create a new project label: +1. On the top bar, select **Menu > Projects** and find your project. +1. On the left sidebar, select **Project information > Labels**. -1. In your project, go to **Project information > Labels**. -1. Select the **New label** button. +Or: + +1. View an issue or merge request. +1. On the right sidebar, in the **Labels** section, select **Edit**. +1. Select **Manage project labels**. + +The list of labels includes both the labels created in the project and +all labels created in the project's ancestor groups. For each label, you can see the +project or group path where it was created. + +### View group labels + +To view the **group's labels**: + +1. On the top bar, select **Menu > Groups** and find your group. +1. On the left sidebar, select **Group information > Labels**. + +Or: + +1. View an epic. +1. On the right sidebar, in the **Labels** section, select **Edit**. +1. Select **Manage group labels**. + +The list includes all labels created only in the group. It does not list any labels created in +the group's projects. + +## Create a label + +Prerequisites: + +- You must have at least the Reporter role for the project or group. + +### Create a project label + +To create a project label: + +1. On the top bar, select **Menu > Projects** and find your project. +1. On the left sidebar, select **Project information > Labels**. +1. Select **New label**. +1. In the **Title** field, enter a short, descriptive name for the label. You + can also use this field to create [scoped, mutually exclusive labels](#scoped-labels). +1. Optional. In the **Description** field, enter additional + information about how and when to use this label. +1. Optional. Select a color by selecting from the available colors, or enter a hex color value for + a specific color in the **Background color** field. +1. Select **Create label**. + +### Create a project label from an issue or merge request + +You can also create a new project label from an issue or merge request. +Labels you create this way belong to the same project as the issue or merge request. + +Prerequisites: + +- You must have at least the Reporter role for the project. + +To do so: + +1. View an issue or merge request. +1. On the right sidebar, in the **Labels** section, select **Edit**. +1. Select **Create project label**. +1. Fill in the name field. You can't specify a description if creating a label this way. + You can add a description later by [editing the label](#edit-a-label). +1. Optional. Select a color by selecting from the available colors, or enter a hex color value for + a specific color. +1. Select **Create**. + +### Create a group label + +To create a group label: + +1. On the top bar, select **Menu > Groups** and find your group. +1. On the left sidebar, select **Group information > Labels**. +1. Select **New label**. 1. In the **Title** field, enter a short, descriptive name for the label. You can also use this field to create [scoped, mutually exclusive labels](#scoped-labels). -1. Optional. In the **Description** field, you can enter additional +1. Optional. In the **Description** field, enter additional information about how and when to use this label. -1. Optional. Select a background color for the label by selecting one of the - available colors, or by entering a hex color value in the **Background color** - field. +1. Optional. Select a color by selecting from the available colors, or enter a hex color value for + a specific color in the **Background color** field. 1. Select **Create label**. -You can also create a new project label from within an issue or merge request. In the -label section of the right sidebar of an issue or a merge request: +### Create a group label from an epic **(PREMIUM)** + +You can also create a new group label from an epic. +Labels you create this way belong to the same group as the epic. + +Prerequisites: + +- You must have at least the Reporter role for the group. -1. Click **Edit**. -1. Click **Create project label**. - - Fill in the name field. Note that you can't specify a description if creating a label - this way. You can add a description later by editing the label (see below). - - Optional. Select a color by clicking on the available colors, or input a hex - color value for a specific color. -1. Click **Create**. +To do so: -To edit a label after you create it, select (**{pencil}**). +1. View an epic. +1. On the right sidebar, in the **Labels** section, select **Edit**. +1. Select **Create group label**. +1. Fill in the name field. You can't specify a description if creating a label this way. + You can add a description later by [editing the label](#edit-a-label). +1. Optional. Select a color by selecting from the available colors,enter input a hex color value + for a specific color. +1. Select **Create**. -To delete a project label, select (**{ellipsis_v}**) next to the **Subscribe** button -and select **Delete** or select **Delete** when you edit a label. +## Edit a label + +Prerequisites: + +- You must have at least the Reporter role for the project or group. + +### Edit a project label + +To edit a **project** label: + +1. On the top bar, select **Menu > Projects** and find your project. +1. On the left sidebar, select **Project information > Labels**. +1. Next to the label you want to edit, select **Edit** (**{pencil}**). + +### Edit a group label + +To edit a **group** label: + +1. On the top bar, select **Menu > Groups** and find your group. +1. On the left sidebar, select **Group information > Labels**. +1. Next to the label you want to edit, select **Edit** (**{pencil}**). + +## Delete a label WARNING: -If you delete a label, it is permanently deleted. All references to the label are removed from the system and you cannot undo the deletion. +If you delete a label, it is permanently deleted. All references to the label are removed from the +system and you cannot undo the deletion. + +Prerequisites: -#### Promote a project label to a group label +- You must have at least the Reporter role for the project. + +### Delete a project label + +To delete a **project** label: + +1. On the top bar, select **Menu > Projects** and find your project. +1. On the left sidebar, select **Project information > Labels**. +1. Either: + + - Next to the **Subscribe** button, select (**{ellipsis_v}**). + - Next to the label you want to edit, select **Edit** (**{pencil}**). + +1. Select **Delete**. + +### Delete a group label + +To delete a **group** label: + +1. On the top bar, select **Menu > Groups** and find your group. +1. On the left sidebar, select **Group information > Labels**. +1. Either: + + - Next to the **Subscribe** button, select (**{ellipsis_v}**). + - Next to the label you want to edit, select **Edit** (**{pencil}**). + +1. Select **Delete**. + +## Promote a project label to a group label > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/231472) in GitLab 13.6: promoting a project label keeps that label's ID and changes it into a group label. Previously, promoting a project label created a new group label with a new ID and deleted the old label. -If you previously created a project label and now want to make it available for other -projects within the same group, you can promote it to a group label. +You might want to make a project label available for other +projects in the same group. Then, you can promote the label to a group label. If other projects in the same group have a label with the same title, they are all merged with the new group label. If a group label with the same title exists, it is also merged. -All issues, merge requests, issue board lists, issue board filters, and label subscriptions -with the old labels are assigned to the new group label. +WARNING: +Promoting a label is a permanent action and cannot be reversed. -The new group label has the same ID as the previous project label. +Prerequisites: -WARNING: -Promoting a label is a permanent action, and cannot be reversed. +- You must have at least the Reporter role for the project. +- You must have at least the Reporter role for the project's parent group. To promote a project label to a group label: -1. Navigate to **Project information > Labels** in the project. -1. Click on the three dots (**{ellipsis_v}**) next to the **Subscribe** button and +1. On the top bar, select **Menu > Projects** and find your project. +1. On the left sidebar, select **Project information > Labels**. +1. Next to the **Subscribe** button, select the three dots (**{ellipsis_v}**) and select **Promote to group label**. -### Group labels +All issues, merge requests, issue board lists, issue board filters, and label subscriptions +with the old labels are assigned to the new group label. + +The new group label has the same ID as the previous project label. -To view the group labels list, navigate to the group and click **Group information > Labels**. -The list includes all labels that are defined at the group level only. It does not -list any labels that are defined in projects. You can filter the list by entering -a search query at the top and clicking search (**{search}**). +## Generate default project labels -To create a **group label**, navigate to **Group information > Labels** in the group and -follow the same process as [creating a project label](#project-labels). +If a project or its parent group has no labels, you can generate a default set of project +labels from the label list page. -#### Create group labels from epics **(ULTIMATE)** +Prerequisites: -You can create group labels from the epic sidebar. The labels you create -belong to the immediate group to which the epic belongs. The process is the same as -creating a [project label from an issue or merge request](#project-labels). +- You must have at least the Reporter role for the project. +- The project must have no labels present. -### Generate default labels +To add the default labels to the project: -If a project or group has no labels, you can generate a default set of project or group -labels from the label list page. The page shows a **Generate a default set of labels** -button if the list is empty. Select the button to add the following default labels -to the project: +1. On the top bar, select **Menu > Projects** and find your project. +1. On the left sidebar, select **Project information > Labels**. +1. Select **Generate a default set of labels**. + +The following labels are created: - `bug` - `confirmed` @@ -157,131 +282,143 @@ to the project: ## Scoped labels **(PREMIUM)** -Scoped labels allow teams to use the label feature to annotate issues, merge requests -and epics with mutually exclusive labels. This can enable more complicated workflows -by preventing certain labels from being used together. - -A label is scoped when it uses a special double-colon (`::`) syntax in the label's -title, for example: +Teams can use scoped labels to annotate issues, merge requests, and epics with mutually exclusive +labels. By preventing certain labels from being used together, you can create more complex workflows. ![Scoped labels](img/labels_key_value_v13_5.png) -An issue, merge request or epic cannot have two scoped labels, of the form `key::value`, -with the same `key`. Adding a new label with the same `key`, but a different `value` -causes the previous `key` label to be replaced with the new label. +A scoped label uses a double-colon (`::`) syntax in its title, for example: `workflow::in-review`. -For example: +An issue, merge request, or epic cannot have two scoped labels, of the form `key::value`, +with the same `key`. If you add a new label with the same `key` but a different `value`, +the previous `key` label is replaced with the new label. -1. An issue is identified as being low priority, and a `priority::low` project - label is added to it. -1. After more review the issue priority is increased, and a `priority::high` label is - added. -1. GitLab automatically removes the `priority::low` label, as an issue should not - have two priority labels at the same time. +<i class="fa fa-youtube-play youtube" aria-hidden="true"></i> +For a video overview, see [Scoped Labels Speed Run](https://www.youtube.com/watch?v=ebyCiKMFODg). ### Filter by scoped labels > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/12285) in GitLab 14.4. -To filter issue, merge request, or epic lists for ones with labels that belong to a given scope, enter +To filter issue, merge request, or epic lists by a given scope, enter `<scope>::*` in the searched label name. For example, filtering by the `platform::*` label returns issues that have `platform::iOS`, `platform::Android`, or `platform::Linux` labels. NOTE: -This is not available on the [issues or merge requests dashboard pages](../search/index.md#search-issues-and-merge-requests). +Filtering by scoped labels not available on the [issues or merge requests dashboard pages](../search/index.md#search-issues-and-merge-requests). + +### Scoped labels examples + +**Example 1.** Updating issue priority: + +1. You decide that an issue is of low priority, and assign it the `priority::low` label. +1. After more review, you realize the issue's priority is higher increased, and you assign it the + `priority::high` label. +1. Because an issue shouldn't have two priority labels at the same time, GitLab removes the + `priority::low` label. + +**Example 2.** You want a custom field in issues to track the operating system platform +that your features target, where each issue should only target one platform. + +You create three labels: + +- `platform::iOS` +- `platform::Android` +- `platform::Linux` + +If you assign any of these labels to an issue automatically removes any other existing label that +starts with `platform::`. + +**Example 3.** You can use scoped labels to represent the workflow states of your teams. -### Workflows with scoped labels +Suppose you have the following labels: -Suppose you wanted a custom field in issues to track the operating system platform -that your features target, where each issue should only target one platform. You -would then create three labels `platform::iOS`, `platform::Android`, `platform::Linux`. -Applying any one of these labels on a given issue would automatically remove any other -existing label that starts with `platform::`. +- `workflow::development` +- `workflow::review` +- `workflow::deployed` -The same pattern could be applied to represent the workflow states of your teams. -Suppose you have the labels `workflow::development`, `workflow::review`, and -`workflow::deployed`. If an issue already has the label `workflow::development` -applied, and a developer wanted to advance the issue to `workflow::review`, they -would simply apply that label, and the `workflow::development` label would -automatically be removed. This behavior already exists when you move issues -across label lists in an [issue board](issue_board.md#create-workflows), but -now, team members who may not be working in an issue board directly would still -be able to advance workflow states consistently in issues themselves. +If an issue already has the label `workflow::development` and a developer wants to show that the +issue is now under review, they assign the `workflow::review`, and the `workflow::development` label +is removed. -This functionality is demonstrated in a video regarding -[using scoped labels for custom fields and workflows](https://www.youtube.com/watch?v=4BCBby6du3c). +The same happens when you move issues across label lists in an +[issue board](issue_board.md#create-workflows). With scoped labels, team members not working in an +issue board can also advance workflow states consistently in issues themselves. -### Scoped labels with nested scopes +For a video explanation, see: + +<div class="video-fallback"> + See the video: <a href="https://www.youtube.com/watch?v=4BCBby6du3c">Use scoped labels for custom fields and custom workflows</a>. +</div> +<figure class="video-container"> + <iframe src="https://www.youtube.com/embed/4BCBby6du3c" frameborder="0" allowfullscreen="true"> </iframe> +</figure> + +### Nested scopes You can create a label with a nested scope by using multiple double colons `::` when creating it. In this case, everything before the last `::` is the scope. -For example, `workflow::backend::review` and `workflow::backend::development` are valid -scoped labels, but they **can't** exist on the same issue at the same time, as they -both share the same scope, `workflow::backend`. +For example, if your project has these labels: -Additionally, `workflow::backend::review` and `workflow::frontend::review` are valid -scoped labels, and they **can** exist on the same issue at the same time, as they -both have different scopes, `workflow::frontend` and `workflow::backend`. +- `workflow::backend::review` +- `workflow::backend::development` +- `workflow::frontend::review` -## Subscribing to labels +An issue **can't** have both `workflow::backend::review` and `workflow::backend::development` +labels at the same time, because they both share the same scope: `workflow::backend`. -From the project label list page and the group label list page, you can click **Subscribe** -to the right of any label to enable [notifications](../profile/notifications.md) for that -label. You are notified whenever the label is assigned to an epic, -issue, or merge request. +On the other hand, an issue **can** have both `workflow::backend::review` and `workflow::frontend::review` +labels at the same time, because they both have different scopes: `workflow::frontend` and `workflow::backend`. -If you are subscribing to a group label from within a project, you can select to subscribe -to label notifications for the project only, or the whole group. +## Receive notifications when a label is used -![Labels subscriptions](img/labels_subscriptions_v13_5.png) +You can subscribe to a label to [receive notifications](../profile/notifications.md) whenever the +label is assigned to an issue, merge request, or epic. -## Label priority +To subscribe to a label: -Labels can have relative priorities, which are used in the **Label priority** and -**Priority** sort orders of issues and merge request list pages. Prioritization -for both group and project labels happens at the project level, and cannot be done -from the group label list. +1. [View the label list page.](#view-available-labels) +1. To the right of any label, select **Subscribe**. +1. Optional. If you are subscribing to a group label from a project, select either: + - **Subscribe at project level** to be notified about events in this project. + - **Subscribe at group level**: to be notified about events in the whole group. -NOTE: -Priority sorting is based on the highest priority label only. [This discussion](https://gitlab.com/gitlab-org/gitlab/-/issues/14523) considers changing this. +## Set label priority -From the project label list page, star a label to indicate that it has a priority. - -![Labels prioritized](img/labels_prioritized_v13_5.png) +Labels can have relative priorities, which are used when you sort issue and merge request lists +by [label priority](issues/sorting_issue_lists.md#sorting-by-label-priority) and [priority](issues/sorting_issue_lists.md#sorting-by-priority). -Drag starred labels up and down the list to change their priority, where higher in the list -means higher priority. +When prioritizing labels, you must do it from a project. +It's not possible to do it from the group label list. -![Drag to change label priority](img/labels_drag_priority_v12_1.gif) +NOTE: +Priority sorting is based on the highest priority label only. +[This discussion](https://gitlab.com/gitlab-org/gitlab/-/issues/14523) considers changing this. -On the merge request and issue list pages (for both groups and projects) you -can sort by `Label priority` or `Priority`. +Prerequisites: -If you sort by `Label priority`, GitLab uses this sort comparison order: +- You must have at least the Reporter role for the project. -1. Items with a higher priority label. -1. Items without a prioritized label. +To prioritize a label: -Ties are broken arbitrarily. Note that only the highest prioritized label is checked, -and labels with a lower priority are ignored. See this [related issue](https://gitlab.com/gitlab-org/gitlab/-/issues/14523) -for more information. +1. On the top bar, select **Menu > Projects** and find your project. +1. On the left sidebar, select **Project information > Labels**. +1. Next to a label you want to prioritize, select the star (**{star-o}**). -![Labels sort label priority](img/labels_sort_label_priority.png) +![Labels prioritized](img/labels_prioritized_v13_5.png) -If you sort by `Priority`, GitLab uses this sort comparison order: +This label now appears at the top of the label list, under **Prioritized Labels**. -1. Items with milestones that have due dates, where the soonest assigned [milestone](milestones/index.md) - is listed first. -1. Items with milestones with no due dates. -1. Items with a higher priority label. -1. Items without a prioritized label. +To change the relative priority of these labels, drag them up and down the list. +The labels higher in the list get higher priority. -Ties are broken arbitrarily. +![Drag to change label priority](img/labels_drag_priority_v12_1.gif) -![Labels sort priority](img/labels_sort_priority.png) +To learn what happens when you sort by priority or label priority, see +[Sorting and ordering issue lists](issues/sorting_issue_lists.md). ## Troubleshooting diff --git a/doc/user/search/index.md b/doc/user/search/index.md index c2e572a52e8..44327af380e 100644 --- a/doc/user/search/index.md +++ b/doc/user/search/index.md @@ -194,7 +194,7 @@ Some filters can be added multiple times. These include but are not limited to a You can search your [To-Do List](../todos.md) by "to do" and "done". You can filter to-do items per project, author, type, and action. -Also, you can sort them by [**Label priority**](../../user/project/labels.md#label-priority), +Also, you can sort them by [**Label priority**](../../user/project/labels.md#set-label-priority), **Last created**, and **Oldest created**. ## Projects diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index cefa1c34ac3..a19fcd6fede 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -76,9 +76,9 @@ module Backup run_create_task(task_name) end - write_info + write_backup_information - if ENV['SKIP'] && ENV['SKIP'].include?('tar') + if skipped?('tar') upload else pack @@ -96,6 +96,7 @@ module Backup def run_create_task(task_name) definition = @definitions[task_name] + build_backup_information puts_time "Dumping #{definition.task.human_name} ... ".color(:blue) unless definition.task.enabled @@ -103,7 +104,7 @@ module Backup return end - if ENV["SKIP"] && ENV["SKIP"].include?(task_name) + if skipped?(task_name) puts_time "[SKIPPED]".color(:cyan) return end @@ -118,10 +119,11 @@ module Backup def restore cleanup_required = unpack + read_backup_information verify_backup_version @definitions.keys.each do |task_name| - run_restore_task(task_name) unless skipped?(task_name) + run_restore_task(task_name) if !skipped?(task_name) && enabled_task?(task_name) end Rake::Task['gitlab:shell:setup'].invoke @@ -141,6 +143,7 @@ module Backup def run_restore_task(task_name) definition = @definitions[task_name] + read_backup_information puts_time "Restoring #{definition.task.human_name} ... ".color(:blue) unless definition.task.enabled @@ -171,7 +174,11 @@ module Backup private - def write_info + def read_backup_information + @backup_information ||= YAML.load_file(File.join(backup_path, MANIFEST_NAME)) + end + + def write_backup_information # Make sure there is a connection ActiveRecord::Base.connection.reconnect! @@ -182,6 +189,23 @@ module Backup end end + def build_backup_information + @backup_information ||= { + db_version: ActiveRecord::Migrator.current_version.to_s, + backup_created_at: Time.now, + gitlab_version: Gitlab::VERSION, + tar_version: tar_version, + installation_type: Gitlab::INSTALLATION_TYPE, + skipped: ENV["SKIP"] + } + end + + def backup_information + raise Backup::Error, "#{MANIFEST_NAME} not yet loaded" unless @backup_information + + @backup_information + end + def pack Dir.chdir(backup_path) do # create archive @@ -287,15 +311,15 @@ module Backup def verify_backup_version Dir.chdir(backup_path) do # restoring mismatching backups can lead to unexpected problems - if settings[:gitlab_version] != Gitlab::VERSION + if backup_information[:gitlab_version] != Gitlab::VERSION progress.puts(<<~HEREDOC.color(:red)) GitLab version mismatch: Your current GitLab version (#{Gitlab::VERSION}) differs from the GitLab version in the backup! Please switch to the following version and try again: - version: #{settings[:gitlab_version]} + version: #{backup_information[:gitlab_version]} HEREDOC progress.puts - progress.puts "Hint: git checkout v#{settings[:gitlab_version]}" + progress.puts "Hint: git checkout v#{backup_information[:gitlab_version]}" exit 1 end end @@ -351,7 +375,7 @@ module Backup end def skipped?(item) - settings[:skipped] && settings[:skipped].include?(item) || !enabled_task?(item) + backup_information[:skipped] && backup_information[:skipped].include?(item) end def enabled_task?(task_name) @@ -411,15 +435,11 @@ module Backup def backup_contents [MANIFEST_NAME] + @definitions.reject do |name, definition| - skipped?(name) || + skipped?(name) || !enabled_task?(name) || (definition.destination_optional && !File.exist?(File.join(backup_path, definition.destination_path))) end.values.map(&:destination_path) end - def settings - @settings ||= YAML.load_file(MANIFEST_NAME) - end - def tar_file @tar_file ||= if ENV['BACKUP'].present? File.basename(ENV['BACKUP']) + FILE_NAME_SUFFIX @@ -428,17 +448,6 @@ module Backup end end - def backup_information - @backup_information ||= { - db_version: ActiveRecord::Migrator.current_version.to_s, - backup_created_at: Time.now, - gitlab_version: Gitlab::VERSION, - tar_version: tar_version, - installation_type: Gitlab::INSTALLATION_TYPE, - skipped: ENV["SKIP"] - } - end - def create_attributes attrs = { key: remote_target, diff --git a/lib/gitlab/ci/config/entry/reports.rb b/lib/gitlab/ci/config/entry/reports.rb index e45dbfa243f..f8fce1abc06 100644 --- a/lib/gitlab/ci/config/entry/reports.rb +++ b/lib/gitlab/ci/config/entry/reports.rb @@ -8,6 +8,7 @@ module Gitlab # Entry that represents a configuration of job artifacts. # class Reports < ::Gitlab::Config::Entry::Node + include ::Gitlab::Config::Entry::Configurable include ::Gitlab::Config::Entry::Validatable include ::Gitlab::Config::Entry::Attributable @@ -15,10 +16,13 @@ module Gitlab %i[junit codequality sast secret_detection dependency_scanning container_scanning dast performance browser_performance load_performance license_scanning metrics lsif dotenv cobertura terraform accessibility cluster_applications - requirements coverage_fuzzing api_fuzzing cluster_image_scanning].freeze + requirements coverage_fuzzing api_fuzzing cluster_image_scanning + coverage_report].freeze attributes ALLOWED_KEYS + entry :coverage_report, Reports::CoverageReport, description: 'Coverage report configuration.' + validations do validates :config, type: Hash validates :config, allowed_keys: ALLOWED_KEYS @@ -47,10 +51,18 @@ module Gitlab validates :cluster_applications, array_of_strings_or_string: true # DEPRECATED: https://gitlab.com/gitlab-org/gitlab/-/issues/333441 validates :requirements, array_of_strings_or_string: true end + + validates :config, mutually_exclusive_keys: [:coverage_report, :cobertura] end def value - @config.transform_values { |v| Array(v) } + @config.transform_values do |value| + if value.is_a?(Hash) + value + else + Array(value) + end + end end end end diff --git a/lib/gitlab/ci/config/entry/reports/coverage_report.rb b/lib/gitlab/ci/config/entry/reports/coverage_report.rb new file mode 100644 index 00000000000..98119c7fd53 --- /dev/null +++ b/lib/gitlab/ci/config/entry/reports/coverage_report.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Gitlab + module Ci + class Config + module Entry + class Reports + class CoverageReport < ::Gitlab::Config::Entry::Node + include ::Gitlab::Config::Entry::Validatable + include ::Gitlab::Config::Entry::Attributable + + ALLOWED_KEYS = %i[coverage_format path].freeze + SUPPORTED_COVERAGE = %w[cobertura].freeze + + attributes ALLOWED_KEYS + + validations do + validates :config, type: Hash + validates :config, allowed_keys: ALLOWED_KEYS + + with_options(presence: true) do + validates :coverage_format, inclusion: { in: SUPPORTED_COVERAGE, message: "must be one of supported formats: #{SUPPORTED_COVERAGE.join(', ')}." } + validates :path, type: String + end + end + end + end + end + end + end +end diff --git a/lib/gitlab/config/entry/validators.rb b/lib/gitlab/config/entry/validators.rb index 6ebcc476e4b..cc24ae837f3 100644 --- a/lib/gitlab/config/entry/validators.rb +++ b/lib/gitlab/config/entry/validators.rb @@ -39,6 +39,17 @@ module Gitlab end end + class MutuallyExclusiveKeysValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + mutually_exclusive_keys = value.try(:keys).to_a & options[:in] + + if mutually_exclusive_keys.length > 1 + record.errors.add(attribute, "please use only one the following keys: " + + mutually_exclusive_keys.join(', ')) + end + end + end + class AllowedValuesValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) unless options[:in].include?(value.to_s) diff --git a/lib/learn_gitlab/onboarding.rb b/lib/learn_gitlab/onboarding.rb index 38ffa9eb2e6..42415aacbee 100644 --- a/lib/learn_gitlab/onboarding.rb +++ b/lib/learn_gitlab/onboarding.rb @@ -5,19 +5,19 @@ module LearnGitlab include Gitlab::Utils::StrongMemoize ACTION_ISSUE_IDS = { - issue_created: 4, - git_write: 6, pipeline_created: 7, - merge_request_created: 9, - user_added: 8, trial_started: 2, required_mr_approvals_enabled: 11, code_owners_enabled: 10 }.freeze - ACTION_DOC_URLS = { - security_scan_enabled: 'https://docs.gitlab.com/ee/user/application_security/security_dashboard/#gitlab-security-dashboard-security-center-and-vulnerability-reports' - }.freeze + ACTION_PATHS = [ + :issue_created, + :git_write, + :merge_request_created, + :user_added, + :security_scan_enabled + ].freeze def initialize(namespace) @namespace = namespace @@ -49,7 +49,7 @@ module LearnGitlab end def tracked_actions - ACTION_ISSUE_IDS.keys + ACTION_DOC_URLS.keys + ACTION_ISSUE_IDS.keys + ACTION_PATHS end attr_reader :namespace diff --git a/scripts/merge-simplecov b/scripts/merge-simplecov index b74a416a6e0..32a0cd86f82 100755 --- a/scripts/merge-simplecov +++ b/scripts/merge-simplecov @@ -5,26 +5,4 @@ require_relative '../spec/simplecov_env' SimpleCovEnv.configure_profile SimpleCovEnv.configure_formatter -module SimpleCov - module ResultMerger - class << self - def resultset_files - Dir.glob(File.join(SimpleCov.coverage_path, '*', '.resultset.json')) - end - - def resultset_hashes - resultset_files.map do |path| - JSON.parse(File.read(path)) - rescue StandardError - {} - end - end - - def resultset - resultset_hashes.reduce({}, :merge) - end - end - end -end - -SimpleCov::ResultMerger.merged_result.format! +SimpleCov.collate Dir.glob(File.join(SimpleCov.coverage_path, '*', '.resultset.json')) diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index f21e00b5522..cf8f02ea17e 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -1602,59 +1602,6 @@ RSpec.describe ProjectsController do end end - describe 'GET resolve' do - shared_examples 'resolvable endpoint' do - it 'redirects to the project page' do - get :resolve, params: { id: project.id } - - expect(response).to have_gitlab_http_status(:found) - expect(response).to redirect_to(project_path(project)) - end - end - - context 'with an authenticated user' do - before do - sign_in(user) - end - - context 'when user has access to the project' do - before do - project.add_developer(user) - end - - it_behaves_like 'resolvable endpoint' - end - - context 'when user has no access to the project' do - it 'gives 404 for existing project' do - get :resolve, params: { id: project.id } - - expect(response).to have_gitlab_http_status(:not_found) - end - end - - it 'gives 404 for non-existing project' do - get :resolve, params: { id: '0' } - - expect(response).to have_gitlab_http_status(:not_found) - end - end - - context 'non authenticated user' do - context 'with a public project' do - let(:project) { public_project } - - it_behaves_like 'resolvable endpoint' - end - - it 'gives 404 for private project' do - get :resolve, params: { id: project.id } - - expect(response).to have_gitlab_http_status(:not_found) - end - end - end - it 'updates Service Desk attributes' do project.add_maintainer(user) sign_in(user) diff --git a/spec/frontend/environments/new_environments_app_spec.js b/spec/frontend/environments/new_environments_app_spec.js index d903e1e2d56..cb9535da363 100644 --- a/spec/frontend/environments/new_environments_app_spec.js +++ b/spec/frontend/environments/new_environments_app_spec.js @@ -70,8 +70,9 @@ describe('~/environments/components/new_environments_app.vue', () => { previousPage: 1, __typename: 'LocalPageInfo', }, + location = '?scope=available&page=2', }) => { - setWindowLocation('?scope=available&page=2'); + setWindowLocation(location); environmentAppMock.mockReturnValue(environmentsApp); environmentFolderMock.mockReturnValue(folder); paginationMock.mockReturnValue(pageInfo); @@ -98,6 +99,21 @@ describe('~/environments/components/new_environments_app.vue', () => { wrapper.destroy(); }); + it('should request available environments if the scope is invalid', async () => { + await createWrapperWithMocked({ + environmentsApp: resolvedEnvironmentsApp, + folder: resolvedFolder, + location: '?scope=bad&page=2', + }); + + expect(environmentAppMock).toHaveBeenCalledWith( + expect.anything(), + expect.objectContaining({ scope: 'available', page: 2 }), + expect.anything(), + expect.anything(), + ); + }); + it('should show all the folders that are fetched', async () => { await createWrapperWithMocked({ environmentsApp: resolvedEnvironmentsApp, diff --git a/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js b/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js index ee2fbbe57b9..b692eea4aa5 100644 --- a/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js +++ b/spec/frontend/members/components/filter_sort/members_filtered_search_bar_spec.js @@ -1,12 +1,14 @@ -import { GlFilteredSearchToken } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import Vue from 'vue'; import Vuex from 'vuex'; import setWindowLocation from 'helpers/set_window_location_helper'; import { redirectTo } from '~/lib/utils/url_utility'; import MembersFilteredSearchBar from '~/members/components/filter_sort/members_filtered_search_bar.vue'; -import { MEMBER_TYPES } from '~/members/constants'; -import { OPERATOR_IS_ONLY } from '~/vue_shared/components/filtered_search_bar/constants'; +import { + MEMBER_TYPES, + FILTERED_SEARCH_TOKEN_TWO_FACTOR, + FILTERED_SEARCH_TOKEN_WITH_INHERITED_PERMISSIONS, +} from '~/members/constants'; import FilteredSearchBar from '~/vue_shared/components/filtered_search_bar/filtered_search_bar_root.vue'; jest.mock('~/lib/utils/url_utility', () => { @@ -32,7 +34,7 @@ describe('MembersFilteredSearchBar', () => { state: { filteredSearchBar: { show: true, - tokens: ['two_factor'], + tokens: [FILTERED_SEARCH_TOKEN_TWO_FACTOR.type], searchParam: 'search', placeholder: 'Filter members', recentSearchesStorageKey: 'group_members', @@ -70,21 +72,7 @@ describe('MembersFilteredSearchBar', () => { it('includes tokens set in `filteredSearchBar.tokens`', () => { createComponent(); - expect(findFilteredSearchBar().props('tokens')).toEqual([ - { - type: 'two_factor', - icon: 'lock', - title: '2FA', - token: GlFilteredSearchToken, - unique: true, - operators: OPERATOR_IS_ONLY, - options: [ - { value: 'enabled', title: 'Enabled' }, - { value: 'disabled', title: 'Disabled' }, - ], - requiredPermissions: 'canManageMembers', - }, - ]); + expect(findFilteredSearchBar().props('tokens')).toEqual([FILTERED_SEARCH_TOKEN_TWO_FACTOR]); }); describe('when `canManageMembers` is false', () => { @@ -93,7 +81,10 @@ describe('MembersFilteredSearchBar', () => { state: { filteredSearchBar: { show: true, - tokens: ['two_factor', 'with_inherited_permissions'], + tokens: [ + FILTERED_SEARCH_TOKEN_TWO_FACTOR.type, + FILTERED_SEARCH_TOKEN_WITH_INHERITED_PERMISSIONS.type, + ], searchParam: 'search', placeholder: 'Filter members', recentSearchesStorageKey: 'group_members', @@ -105,18 +96,7 @@ describe('MembersFilteredSearchBar', () => { }); expect(findFilteredSearchBar().props('tokens')).toEqual([ - { - type: 'with_inherited_permissions', - icon: 'group', - title: 'Membership', - token: GlFilteredSearchToken, - unique: true, - operators: OPERATOR_IS_ONLY, - options: [ - { value: 'exclude', title: 'Direct' }, - { value: 'only', title: 'Inherited' }, - ], - }, + FILTERED_SEARCH_TOKEN_WITH_INHERITED_PERMISSIONS, ]); }); }); @@ -134,7 +114,7 @@ describe('MembersFilteredSearchBar', () => { expect(findFilteredSearchBar().props('initialFilterValue')).toEqual([ { - type: 'two_factor', + type: FILTERED_SEARCH_TOKEN_TWO_FACTOR.type, value: { data: 'enabled', operator: '=', @@ -183,7 +163,7 @@ describe('MembersFilteredSearchBar', () => { createComponent(); findFilteredSearchBar().vm.$emit('onFilter', [ - { type: 'two_factor', value: { data: 'enabled', operator: '=' } }, + { type: FILTERED_SEARCH_TOKEN_TWO_FACTOR.type, value: { data: 'enabled', operator: '=' } }, ]); expect(redirectTo).toHaveBeenCalledWith('https://localhost/?two_factor=enabled'); @@ -193,7 +173,7 @@ describe('MembersFilteredSearchBar', () => { createComponent(); findFilteredSearchBar().vm.$emit('onFilter', [ - { type: 'two_factor', value: { data: 'enabled', operator: '=' } }, + { type: FILTERED_SEARCH_TOKEN_TWO_FACTOR.type, value: { data: 'enabled', operator: '=' } }, { type: 'filtered-search-term', value: { data: 'foobar' } }, ]); @@ -206,7 +186,7 @@ describe('MembersFilteredSearchBar', () => { createComponent(); findFilteredSearchBar().vm.$emit('onFilter', [ - { type: 'two_factor', value: { data: 'enabled', operator: '=' } }, + { type: FILTERED_SEARCH_TOKEN_TWO_FACTOR.type, value: { data: 'enabled', operator: '=' } }, { type: 'filtered-search-term', value: { data: 'foo bar baz' } }, ]); @@ -221,7 +201,7 @@ describe('MembersFilteredSearchBar', () => { createComponent(); findFilteredSearchBar().vm.$emit('onFilter', [ - { type: 'two_factor', value: { data: 'enabled', operator: '=' } }, + { type: FILTERED_SEARCH_TOKEN_TWO_FACTOR.type, value: { data: 'enabled', operator: '=' } }, { type: 'filtered-search-term', value: { data: 'foobar' } }, ]); diff --git a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap index 86ccaa43786..62cf769cffd 100644 --- a/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap +++ b/spec/frontend/pages/projects/learn_gitlab/components/__snapshots__/learn_gitlab_spec.js.snap @@ -137,9 +137,7 @@ exports[`Learn GitLab renders correctly 1`] = ` class="gl-link" data-testid="uncompleted-learn-gitlab-link" data-track-action="click_link" - data-track-experiment="change_continuous_onboarding_link_urls" data-track-label="Set up CI/CD" - data-track-property="Growth::Conversion::Experiment::LearnGitLab" href="http://example.com/" target="_self" > @@ -157,9 +155,7 @@ exports[`Learn GitLab renders correctly 1`] = ` class="gl-link" data-testid="uncompleted-learn-gitlab-link" data-track-action="click_link" - data-track-experiment="change_continuous_onboarding_link_urls" data-track-label="Start a free Ultimate trial" - data-track-property="Growth::Conversion::Experiment::LearnGitLab" href="http://example.com/" target="_self" > @@ -177,9 +173,7 @@ exports[`Learn GitLab renders correctly 1`] = ` class="gl-link" data-testid="uncompleted-learn-gitlab-link" data-track-action="click_link" - data-track-experiment="change_continuous_onboarding_link_urls" data-track-label="Add code owners" - data-track-property="Growth::Conversion::Experiment::LearnGitLab" href="http://example.com/" target="_self" > @@ -204,9 +198,7 @@ exports[`Learn GitLab renders correctly 1`] = ` class="gl-link" data-testid="uncompleted-learn-gitlab-link" data-track-action="click_link" - data-track-experiment="change_continuous_onboarding_link_urls" data-track-label="Add merge request approval" - data-track-property="Growth::Conversion::Experiment::LearnGitLab" href="http://example.com/" target="_self" > @@ -267,9 +259,7 @@ exports[`Learn GitLab renders correctly 1`] = ` class="gl-link" data-testid="uncompleted-learn-gitlab-link" data-track-action="click_link" - data-track-experiment="change_continuous_onboarding_link_urls" data-track-label="Create an issue" - data-track-property="Growth::Conversion::Experiment::LearnGitLab" href="http://example.com/" target="_self" > @@ -287,9 +277,7 @@ exports[`Learn GitLab renders correctly 1`] = ` class="gl-link" data-testid="uncompleted-learn-gitlab-link" data-track-action="click_link" - data-track-experiment="change_continuous_onboarding_link_urls" data-track-label="Submit a merge request" - data-track-property="Growth::Conversion::Experiment::LearnGitLab" href="http://example.com/" target="_self" > @@ -343,9 +331,7 @@ exports[`Learn GitLab renders correctly 1`] = ` class="gl-link" data-testid="uncompleted-learn-gitlab-link" data-track-action="click_link" - data-track-experiment="change_continuous_onboarding_link_urls" data-track-label="Run a Security scan using CI/CD" - data-track-property="Growth::Conversion::Experiment::LearnGitLab" href="https://docs.gitlab.com/ee/foobar/" rel="noopener noreferrer" target="_blank" diff --git a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js index 3b113f4dcd7..e21371123e8 100644 --- a/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js +++ b/spec/frontend/pages/projects/learn_gitlab/components/learn_gitlab_section_link_spec.js @@ -12,8 +12,9 @@ const defaultProps = { completed: false, }; -const docLinkProps = { +const openInNewTabProps = { url: 'https://docs.gitlab.com/ee/user/application_security/security_dashboard/', + openInNewTab: true, }; describe('Learn GitLab Section Link', () => { @@ -59,9 +60,9 @@ describe('Learn GitLab Section Link', () => { expect(wrapper.find('[data-testid="trial-only"]').exists()).toBe(true); }); - describe('doc links', () => { + describe('links marked with openInNewTab', () => { beforeEach(() => { - createWrapper('securityScanEnabled', docLinkProps); + createWrapper('securityScanEnabled', openInNewTabProps); }); it('renders links with blank target', () => { @@ -78,7 +79,6 @@ describe('Learn GitLab Section Link', () => { expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_link', { label: 'Run a Security scan using CI/CD', - property: 'Growth::Conversion::Experiment::LearnGitLab', }); unmockTracking(); diff --git a/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js b/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js index b21965e8f48..5dc64097d81 100644 --- a/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js +++ b/spec/frontend/pages/projects/learn_gitlab/components/mock_data.js @@ -38,6 +38,7 @@ export const testActions = { url: 'https://docs.gitlab.com/ee/foobar/', completed: false, svg: 'http://example.com/images/illustration.svg', + openInNewTab: true, }, issueCreated: { url: 'http://example.com/', diff --git a/spec/graphql/resolvers/base_resolver_spec.rb b/spec/graphql/resolvers/base_resolver_spec.rb index d77a0b6242e..39b00c14161 100644 --- a/spec/graphql/resolvers/base_resolver_spec.rb +++ b/spec/graphql/resolvers/base_resolver_spec.rb @@ -239,16 +239,16 @@ RSpec.describe Resolvers::BaseResolver do it 'increases complexity based on arguments' do field = Types::BaseField.new(name: 'test', type: GraphQL::Types::String.connection_type, resolver_class: described_class, null: false, max_page_size: 1) - expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 3 - expect(field.to_graphql.complexity.call({}, { search: 'foo' }, 1)).to eq 7 + expect(field.complexity.call({}, { sort: 'foo' }, 1)).to eq 3 + expect(field.complexity.call({}, { search: 'foo' }, 1)).to eq 7 end it 'does not increase complexity when filtering by iids' do field = Types::BaseField.new(name: 'test', type: GraphQL::Types::String.connection_type, resolver_class: described_class, null: false, max_page_size: 100) - expect(field.to_graphql.complexity.call({}, { sort: 'foo' }, 1)).to eq 6 - expect(field.to_graphql.complexity.call({}, { sort: 'foo', iid: 1 }, 1)).to eq 3 - expect(field.to_graphql.complexity.call({}, { sort: 'foo', iids: [1, 2, 3] }, 1)).to eq 3 + expect(field.complexity.call({}, { sort: 'foo' }, 1)).to eq 6 + expect(field.complexity.call({}, { sort: 'foo', iid: 1 }, 1)).to eq 3 + expect(field.complexity.call({}, { sort: 'foo', iids: [1, 2, 3] }, 1)).to eq 3 end end diff --git a/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb b/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb index 38f5a644985..4c4aa4f53e1 100644 --- a/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb +++ b/spec/graphql/resolvers/concerns/resolves_pipelines_spec.rb @@ -106,9 +106,9 @@ RSpec.describe ResolvesPipelines do it 'increases field complexity based on arguments' do field = Types::BaseField.new(name: 'test', type: GraphQL::Types::String, resolver_class: resolver, null: false, max_page_size: 1) - expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 2 - expect(field.to_graphql.complexity.call({}, { sha: 'foo' }, 1)).to eq 4 - expect(field.to_graphql.complexity.call({}, { sha: 'ref' }, 1)).to eq 4 + expect(field.complexity.call({}, {}, 1)).to eq 2 + expect(field.complexity.call({}, { sha: 'foo' }, 1)).to eq 4 + expect(field.complexity.call({}, { sha: 'ref' }, 1)).to eq 4 end def resolve_pipelines(args = {}, context = { current_user: current_user }) diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb index dc717b113c1..326c105a358 100644 --- a/spec/graphql/resolvers/issues_resolver_spec.rb +++ b/spec/graphql/resolvers/issues_resolver_spec.rb @@ -618,8 +618,8 @@ RSpec.describe Resolvers::IssuesResolver do it 'increases field complexity based on arguments' do field = Types::BaseField.new(name: 'test', type: GraphQL::Types::String.connection_type, resolver_class: described_class, null: false, max_page_size: 100) - expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 4 - expect(field.to_graphql.complexity.call({}, { labelName: 'foo' }, 1)).to eq 8 + expect(field.complexity.call({}, {}, 1)).to eq 4 + expect(field.complexity.call({}, { labelName: 'foo' }, 1)).to eq 8 end def create_issue_with_severity(project, severity:) diff --git a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb index b1f50a4a4a5..eb4d0ab6f37 100644 --- a/spec/graphql/resolvers/namespace_projects_resolver_spec.rb +++ b/spec/graphql/resolvers/namespace_projects_resolver_spec.rb @@ -147,8 +147,8 @@ RSpec.describe Resolvers::NamespaceProjectsResolver do it 'has an high complexity regardless of arguments' do field = Types::BaseField.new(name: 'test', type: GraphQL::Types::String.connection_type, resolver_class: described_class, null: false, max_page_size: 100) - expect(field.to_graphql.complexity.call({}, {}, 1)).to eq 24 - expect(field.to_graphql.complexity.call({}, { include_subgroups: true }, 1)).to eq 24 + expect(field.complexity.call({}, {}, 1)).to eq 24 + expect(field.complexity.call({}, { include_subgroups: true }, 1)).to eq 24 end def resolve_projects(args = { include_subgroups: false, sort: nil, search: nil, ids: nil }, context = { current_user: current_user }) diff --git a/spec/graphql/resolvers/project_resolver_spec.rb b/spec/graphql/resolvers/project_resolver_spec.rb index cd3fdc788e6..dec9d4701e1 100644 --- a/spec/graphql/resolvers/project_resolver_spec.rb +++ b/spec/graphql/resolvers/project_resolver_spec.rb @@ -36,8 +36,8 @@ RSpec.describe Resolvers::ProjectResolver do field1 = Types::BaseField.new(name: 'test', type: GraphQL::Types::String, resolver_class: described_class, null: false, max_page_size: 100) field2 = Types::BaseField.new(name: 'test', type: GraphQL::Types::String, resolver_class: described_class, null: false, max_page_size: 1) - expect(field1.to_graphql.complexity.call({}, {}, 1)).to eq 2 - expect(field2.to_graphql.complexity.call({}, {}, 1)).to eq 2 + expect(field1.complexity.call({}, {}, 1)).to eq 2 + expect(field2.complexity.call({}, {}, 1)).to eq 2 end def resolve_project(full_path) diff --git a/spec/graphql/types/base_enum_spec.rb b/spec/graphql/types/base_enum_spec.rb index 64524f3ffcd..65a345052c7 100644 --- a/spec/graphql/types/base_enum_spec.rb +++ b/spec/graphql/types/base_enum_spec.rb @@ -136,7 +136,7 @@ RSpec.describe Types::BaseEnum do value 'TEST_VALUE', **args end - enum.to_graphql.values['TEST_VALUE'] + enum.values['TEST_VALUE'] end end end diff --git a/spec/graphql/types/base_field_spec.rb b/spec/graphql/types/base_field_spec.rb index 31d07f701e8..9d02f061435 100644 --- a/spec/graphql/types/base_field_spec.rb +++ b/spec/graphql/types/base_field_spec.rb @@ -19,7 +19,7 @@ RSpec.describe Types::BaseField do it 'defaults to 1' do field = described_class.new(name: 'test', type: GraphQL::Types::String, null: true) - expect(field.to_graphql.complexity).to eq 1 + expect(field.complexity).to eq 1 end describe '#base_complexity' do @@ -43,7 +43,7 @@ RSpec.describe Types::BaseField do it 'has specified value' do field = described_class.new(name: 'test', type: GraphQL::Types::String, null: true, complexity: 12) - expect(field.to_graphql.complexity).to eq 12 + expect(field.complexity).to eq 12 end context 'when field has a resolver' do @@ -51,7 +51,7 @@ RSpec.describe Types::BaseField do let(:field) { described_class.new(name: 'test', type: GraphQL::Types::String.connection_type, resolver_class: resolver, complexity: 2, max_page_size: 100, null: true) } it 'uses this complexity' do - expect(field.to_graphql.complexity).to eq 2 + expect(field.complexity).to eq 2 end end @@ -59,13 +59,13 @@ RSpec.describe Types::BaseField do let(:field) { described_class.new(name: 'test', type: GraphQL::Types::String.connection_type, resolver_class: resolver, max_page_size: 100, null: true) } it 'sets complexity depending on arguments for resolvers' do - expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 4 - expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 3 + expect(field.complexity.call({}, {}, 2)).to eq 4 + expect(field.complexity.call({}, { first: 50 }, 2)).to eq 3 end it 'sets complexity depending on number load limits for resolvers' do - expect(field.to_graphql.complexity.call({}, { first: 1 }, 2)).to eq 2 - expect(field.to_graphql.complexity.call({}, { first: 1, foo: true }, 2)).to eq 4 + expect(field.complexity.call({}, { first: 1 }, 2)).to eq 2 + expect(field.complexity.call({}, { first: 1, foo: true }, 2)).to eq 4 end end @@ -73,8 +73,8 @@ RSpec.describe Types::BaseField do it 'sets complexity as normal' do field = described_class.new(name: 'test', type: GraphQL::Types::String, resolver_class: resolver, max_page_size: 100, null: true) - expect(field.to_graphql.complexity.call({}, {}, 2)).to eq 2 - expect(field.to_graphql.complexity.call({}, { first: 50 }, 2)).to eq 2 + expect(field.complexity.call({}, {}, 2)).to eq 2 + expect(field.complexity.call({}, { first: 50 }, 2)).to eq 2 end end end @@ -84,9 +84,9 @@ RSpec.describe Types::BaseField do it 'adds 1 if true' do with_gitaly_field = described_class.new(name: 'test', type: GraphQL::Types::String, resolver_class: resolver, null: true, calls_gitaly: true) without_gitaly_field = described_class.new(name: 'test', type: GraphQL::Types::String, resolver_class: resolver, null: true) - base_result = without_gitaly_field.to_graphql.complexity.call({}, {}, 2) + base_result = without_gitaly_field.complexity.call({}, {}, 2) - expect(with_gitaly_field.to_graphql.complexity.call({}, {}, 2)).to eq base_result + 1 + expect(with_gitaly_field.complexity.call({}, {}, 2)).to eq base_result + 1 end end @@ -94,7 +94,7 @@ RSpec.describe Types::BaseField do it 'adds 1 if true' do field = described_class.new(name: 'test', type: GraphQL::Types::String, null: true, calls_gitaly: true) - expect(field.to_graphql.complexity).to eq 2 + expect(field.complexity).to eq 2 end end @@ -108,14 +108,14 @@ RSpec.describe Types::BaseField do it 'has complexity set to that constant' do field = described_class.new(name: 'test', type: GraphQL::Types::String, null: true, complexity: 12) - expect(field.to_graphql.complexity).to eq 12 + expect(field.complexity).to eq 12 end it 'does not raise an error even with Gitaly calls' do allow(Gitlab::GitalyClient).to receive(:get_request_count).and_return([0, 1]) field = described_class.new(name: 'test', type: GraphQL::Types::String, null: true, complexity: 12) - expect(field.to_graphql.complexity).to eq 12 + expect(field.complexity).to eq 12 end end end diff --git a/spec/graphql/types/global_id_type_spec.rb b/spec/graphql/types/global_id_type_spec.rb index f9391efdf08..8df92c818fc 100644 --- a/spec/graphql/types/global_id_type_spec.rb +++ b/spec/graphql/types/global_id_type_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Types::GlobalIDType do let(:gid) { project.to_global_id } it 'is has the correct name' do - expect(described_class.to_graphql.name).to eq('GlobalID') + expect(described_class.graphql_name).to eq('GlobalID') end describe '.coerce_result' do @@ -63,7 +63,7 @@ RSpec.describe Types::GlobalIDType do let(:type) { ::Types::GlobalIDType[::Project] } it 'is has the correct name' do - expect(type.to_graphql.name).to eq('ProjectID') + expect(type.graphql_name).to eq('ProjectID') end context 'the GID is appropriate' do @@ -126,7 +126,7 @@ RSpec.describe Types::GlobalIDType do let(:deprecating_gid) { Gitlab::GlobalId.build(model_name: 'Issue', id: issue.id) } it 'appends the description with a deprecation notice for the old Global ID' do - expect(type.to_graphql.description).to include('The older format `"gid://gitlab/OldIssue/1"` was deprecated in 10.0') + expect(type.description).to include('The older format `"gid://gitlab/OldIssue/1"` was deprecated in 10.0') end describe 'coercing input against the type (parsing the Global ID string when supplied as an argument)' do @@ -242,7 +242,7 @@ RSpec.describe Types::GlobalIDType do let(:type) { ::Types::GlobalIDType[::Ci::Build] } it 'is has a valid GraphQL identifier for a name' do - expect(type.to_graphql.name).to eq('CiBuildID') + expect(type.graphql_name).to eq('CiBuildID') end end diff --git a/spec/helpers/learn_gitlab_helper_spec.rb b/spec/helpers/learn_gitlab_helper_spec.rb index ffc2bb31b8f..9fce7495b5a 100644 --- a/spec/helpers/learn_gitlab_helper_spec.rb +++ b/spec/helpers/learn_gitlab_helper_spec.rb @@ -97,29 +97,29 @@ RSpec.describe LearnGitlabHelper do trial_started: a_hash_including( url: a_string_matching(%r{/learn_gitlab/-/issues/2\z}) ), - issue_created: a_hash_including( - url: a_string_matching(%r{/learn_gitlab/-/issues/4\z}) - ), - git_write: a_hash_including( - url: a_string_matching(%r{/learn_gitlab/-/issues/6\z}) - ), pipeline_created: a_hash_including( url: a_string_matching(%r{/learn_gitlab/-/issues/7\z}) ), - user_added: a_hash_including( - url: a_string_matching(%r{/learn_gitlab/-/issues/8\z}) - ), - merge_request_created: a_hash_including( - url: a_string_matching(%r{/learn_gitlab/-/issues/9\z}) - ), code_owners_enabled: a_hash_including( url: a_string_matching(%r{/learn_gitlab/-/issues/10\z}) ), required_mr_approvals_enabled: a_hash_including( url: a_string_matching(%r{/learn_gitlab/-/issues/11\z}) ), + issue_created: a_hash_including( + url: a_string_matching(%r{/learn_gitlab/-/issues\z}) + ), + git_write: a_hash_including( + url: a_string_matching(%r{/learn_gitlab\z}) + ), + user_added: a_hash_including( + url: a_string_matching(%r{/learn_gitlab/-/project_members\z}) + ), + merge_request_created: a_hash_including( + url: a_string_matching(%r{/learn_gitlab/-/merge_requests\z}) + ), security_scan_enabled: a_hash_including( - url: a_string_matching(%r{docs\.gitlab\.com/ee/user/application_security/security_dashboard/#gitlab-security-dashboard-security-center-and-vulnerability-reports\z}) + url: a_string_matching(%r{/learn_gitlab/-/security/configuration\z}) ) }) end @@ -137,58 +137,5 @@ RSpec.describe LearnGitlabHelper do security_scan_enabled: a_hash_including(completed: false) }) end - - context 'when in the new action URLs experiment' do - before do - stub_experiments(change_continuous_onboarding_link_urls: :candidate) - end - - it_behaves_like 'has all data' - - it 'sets mostly new paths' do - expect(onboarding_actions_data).to match({ - trial_started: a_hash_including( - url: a_string_matching(%r{/learn_gitlab/-/issues/2\z}) - ), - issue_created: a_hash_including( - url: a_string_matching(%r{/learn_gitlab/-/issues\z}) - ), - git_write: a_hash_including( - url: a_string_matching(%r{/learn_gitlab\z}) - ), - pipeline_created: a_hash_including( - url: a_string_matching(%r{/learn_gitlab/-/pipelines\z}) - ), - user_added: a_hash_including( - url: a_string_matching(%r{/learn_gitlab/-/project_members\z}) - ), - merge_request_created: a_hash_including( - url: a_string_matching(%r{/learn_gitlab/-/merge_requests\z}) - ), - code_owners_enabled: a_hash_including( - url: a_string_matching(%r{/learn_gitlab/-/issues/10\z}) - ), - required_mr_approvals_enabled: a_hash_including( - url: a_string_matching(%r{/learn_gitlab/-/issues/11\z}) - ), - security_scan_enabled: a_hash_including( - url: a_string_matching(%r{/learn_gitlab/-/security/configuration\z}) - ) - }) - end - - it 'calls experiment with expected context & options' do - allow(helper).to receive(:current_user).and_return(user) - - expect(helper).to receive(:experiment).with( - :change_continuous_onboarding_link_urls, - namespace: namespace, - actor: user, - sticky_to: namespace - ) - - learn_gitlab_data - end - end end end diff --git a/spec/lib/backup/manager_spec.rb b/spec/lib/backup/manager_spec.rb index bf1a039f354..9cf78a11bc7 100644 --- a/spec/lib/backup/manager_spec.rb +++ b/spec/lib/backup/manager_spec.rb @@ -71,7 +71,7 @@ RSpec.describe Backup::Manager do end before do - allow(YAML).to receive(:load_file).with('backup_information.yml') + allow(YAML).to receive(:load_file).with(File.join(Gitlab.config.backup.path, 'backup_information.yml')) .and_return(backup_information) end @@ -171,7 +171,8 @@ RSpec.describe Backup::Manager do before do allow(ActiveRecord::Base.connection).to receive(:reconnect!) allow(Kernel).to receive(:system).and_return(true) - allow(YAML).to receive(:load_file).and_return(backup_information) + allow(YAML).to receive(:load_file).with(File.join(Gitlab.config.backup.path, 'backup_information.yml')) + .and_return(backup_information) allow(subject).to receive(:backup_information).and_return(backup_information) allow(task1).to receive(:dump).with(File.join(Gitlab.config.backup.path, 'task1.tar.gz')) @@ -571,7 +572,8 @@ RSpec.describe Backup::Manager do allow(task1).to receive(:restore).with(File.join(Gitlab.config.backup.path, 'task1.tar.gz')) allow(task2).to receive(:restore).with(File.join(Gitlab.config.backup.path, 'task2.tar.gz')) - allow(YAML).to receive(:load_file).and_return(backup_information) + allow(YAML).to receive(:load_file).with(File.join(Gitlab.config.backup.path, 'backup_information.yml')) + .and_return(backup_information) allow(Rake::Task['gitlab:shell:setup']).to receive(:invoke) allow(Rake::Task['cache:clear']).to receive(:invoke) end diff --git a/spec/lib/gitlab/ci/config/entry/reports/coverage_report_spec.rb b/spec/lib/gitlab/ci/config/entry/reports/coverage_report_spec.rb new file mode 100644 index 00000000000..588f53150ff --- /dev/null +++ b/spec/lib/gitlab/ci/config/entry/reports/coverage_report_spec.rb @@ -0,0 +1,57 @@ +# frozen_string_literal: true + +require 'fast_spec_helper' + +RSpec.describe Gitlab::Ci::Config::Entry::Reports::CoverageReport do + let(:entry) { described_class.new(config) } + + describe 'validations' do + context 'when it is valid' do + let(:config) { { coverage_format: 'cobertura', path: 'cobertura-coverage.xml' } } + + it { expect(entry).to be_valid } + + it { expect(entry.value).to eq(config) } + end + + context 'with unsupported coverage format' do + let(:config) { { coverage_format: 'jacoco', path: 'jacoco.xml' } } + + it { expect(entry).not_to be_valid } + + it { expect(entry.errors).to include /format must be one of supported formats/ } + end + + context 'without coverage format' do + let(:config) { { path: 'cobertura-coverage.xml' } } + + it { expect(entry).not_to be_valid } + + it { expect(entry.errors).to include /format can't be blank/ } + end + + context 'without path' do + let(:config) { { coverage_format: 'cobertura' } } + + it { expect(entry).not_to be_valid } + + it { expect(entry.errors).to include /path can't be blank/ } + end + + context 'with invalid path' do + let(:config) { { coverage_format: 'cobertura', path: 123 } } + + it { expect(entry).not_to be_valid } + + it { expect(entry.errors).to include /path should be a string/ } + end + + context 'with unknown keys' do + let(:config) { { coverage_format: 'cobertura', path: 'cobertura-coverage.xml', foo: :bar } } + + it { expect(entry).not_to be_valid } + + it { expect(entry.errors).to include /contains unknown keys/ } + end + end +end diff --git a/spec/lib/gitlab/ci/config/entry/reports_spec.rb b/spec/lib/gitlab/ci/config/entry/reports_spec.rb index 12b8960eb32..061d8f34c8d 100644 --- a/spec/lib/gitlab/ci/config/entry/reports_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/reports_spec.rb @@ -6,12 +6,8 @@ RSpec.describe Gitlab::Ci::Config::Entry::Reports do let(:entry) { described_class.new(config) } describe 'validates ALLOWED_KEYS' do - let(:artifact_file_types) { Ci::JobArtifact.file_types } - - described_class::ALLOWED_KEYS.each do |keyword, _| - it "expects #{keyword} to be an artifact file_type" do - expect(artifact_file_types).to include(keyword) - end + it "expects ALLOWED_KEYS to be an artifact file_type or coverage_report" do + expect(Ci::JobArtifact.file_types.keys.map(&:to_sym) + [:coverage_report]).to include(*described_class::ALLOWED_KEYS) end end @@ -68,6 +64,45 @@ RSpec.describe Gitlab::Ci::Config::Entry::Reports do it_behaves_like 'a valid entry', params[:keyword], params[:file] end end + + context 'when coverage_report is specified' do + let(:coverage_format) { :cobertura } + let(:filename) { 'cobertura-coverage.xml' } + let(:coverage_report) { { path: filename, coverage_format: coverage_format } } + let(:config) { { coverage_report: coverage_report } } + + it 'is valid' do + expect(entry).to be_valid + end + + it 'returns artifacts configuration' do + expect(entry.value).to eq(config) + end + + context 'and another report is specified' do + let(:config) { { coverage_report: coverage_report, dast: 'gl-dast-report.json' } } + + it 'is valid' do + expect(entry).to be_valid + end + + it 'returns artifacts configuration' do + expect(entry.value).to eq({ coverage_report: coverage_report, dast: ['gl-dast-report.json'] }) + end + end + + context 'and a direct coverage report format is specified' do + let(:config) { { coverage_report: coverage_report, cobertura: 'cobertura-coverage.xml' } } + + it 'is not valid' do + expect(entry).not_to be_valid + end + + it 'reports error' do + expect(entry.errors).to include /please use only one the following keys: coverage_report, cobertura/ + end + end + end end context 'when entry value is not correct' do diff --git a/spec/lib/gitlab/config/entry/validators_spec.rb b/spec/lib/gitlab/config/entry/validators_spec.rb new file mode 100644 index 00000000000..cbc09aac586 --- /dev/null +++ b/spec/lib/gitlab/config/entry/validators_spec.rb @@ -0,0 +1,43 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::Config::Entry::Validators do + let(:klass) do + Class.new do + include ActiveModel::Validations + include Gitlab::Config::Entry::Validators + end + end + + let(:instance) { klass.new } + + describe described_class::MutuallyExclusiveKeysValidator do + using RSpec::Parameterized::TableSyntax + + before do + klass.instance_eval do + validates :config, mutually_exclusive_keys: [:foo, :bar] + end + + allow(instance).to receive(:config).and_return(config) + end + + where(:context, :config, :valid_result) do + 'with mutually exclusive keys' | { foo: 1, bar: 2 } | false + 'without mutually exclusive keys' | { foo: 1 } | true + 'without mutually exclusive keys' | { bar: 1 } | true + 'with other keys' | { foo: 1, baz: 2 } | true + end + + with_them do + it 'validates the instance' do + expect(instance.valid?).to be(valid_result) + + unless valid_result + expect(instance.errors.messages_for(:config)).to include /please use only one the following keys: foo, bar/ + end + end + end + end +end diff --git a/spec/lib/gitlab/graphql/markdown_field_spec.rb b/spec/lib/gitlab/graphql/markdown_field_spec.rb index c2253811e91..ed3f19d8cf2 100644 --- a/spec/lib/gitlab/graphql/markdown_field_spec.rb +++ b/spec/lib/gitlab/graphql/markdown_field_spec.rb @@ -11,7 +11,7 @@ RSpec.describe Gitlab::Graphql::MarkdownField do expect(field.name).to eq('testHtml') expect(field.description).to eq('The GitLab Flavored Markdown rendering of `hello`') expect(field.type).to eq(GraphQL::Types::String) - expect(field.to_graphql.complexity).to eq(5) + expect(field.complexity).to eq(5) end context 'developer warnings' do @@ -43,7 +43,7 @@ RSpec.describe Gitlab::Graphql::MarkdownField do let(:field) { type_class.fields['noteHtml'] } it 'renders markdown from the same property as the field name without the `_html` suffix' do - expect(field.to_graphql.resolve(type_instance, {}, context)).to eq(expected_markdown) + expect(field.resolve(type_instance, {}, context)).to eq(expected_markdown) end context 'when a `method` argument is passed' do @@ -51,7 +51,7 @@ RSpec.describe Gitlab::Graphql::MarkdownField do let(:field) { type_class.fields['testHtml'] } it 'renders markdown from a specific property' do - expect(field.to_graphql.resolve(type_instance, {}, context)).to eq(expected_markdown) + expect(field.resolve(type_instance, {}, context)).to eq(expected_markdown) end end @@ -62,21 +62,21 @@ RSpec.describe Gitlab::Graphql::MarkdownField do let(:note) { build(:note, note: "Referencing #{issue.to_reference(full: true)}") } it 'renders markdown correctly' do - expect(field.to_graphql.resolve(type_instance, {}, context)).to include(issue_path(issue)) + expect(field.resolve(type_instance, {}, context)).to include(issue_path(issue)) end context 'when the issue is not publicly accessible' do let_it_be(:project) { create(:project, :private) } it 'hides the references from users that are not allowed to see the reference' do - expect(field.to_graphql.resolve(type_instance, {}, context)).not_to include(issue_path(issue)) + expect(field.resolve(type_instance, {}, context)).not_to include(issue_path(issue)) end it 'shows the reference to users that are allowed to see it' do context = GraphQL::Query::Context.new(query: query, values: { current_user: project.first_owner }, object: nil) type_instance = type_class.authorized_new(note, context) - expect(field.to_graphql.resolve(type_instance, {}, context)).to include(issue_path(issue)) + expect(field.resolve(type_instance, {}, context)).to include(issue_path(issue)) end end end diff --git a/spec/lib/gitlab/graphql/mount_mutation_spec.rb b/spec/lib/gitlab/graphql/mount_mutation_spec.rb index fe25e923506..09fd9eac714 100644 --- a/spec/lib/gitlab/graphql/mount_mutation_spec.rb +++ b/spec/lib/gitlab/graphql/mount_mutation_spec.rb @@ -17,7 +17,7 @@ RSpec.describe Gitlab::Graphql::MountMutation do f.mount_mutation(mutation) end - mutation_type.get_field('testMutation').to_graphql + mutation_type.get_field('testMutation') end it 'mounts a mutation' do @@ -31,7 +31,7 @@ RSpec.describe Gitlab::Graphql::MountMutation do f.mount_aliased_mutation('MyAlias', mutation) end - mutation_type.get_field('myAlias').to_graphql + mutation_type.get_field('myAlias') end it 'mounts a mutation' do @@ -43,11 +43,11 @@ RSpec.describe Gitlab::Graphql::MountMutation do end it 'has a correct type' do - expect(field.type.name).to eq('MyAliasPayload') + expect(field.type.to_type_signature).to eq('MyAliasPayload') end it 'has a correct input argument' do - expect(field.arguments['input'].type.unwrap.name).to eq('MyAliasInput') + expect(field.arguments['input'].type.unwrap.to_type_signature).to eq('MyAliasInput') end end diff --git a/spec/lib/learn_gitlab/onboarding_spec.rb b/spec/lib/learn_gitlab/onboarding_spec.rb index 6b4be65f3b2..8c7284ed7f5 100644 --- a/spec/lib/learn_gitlab/onboarding_spec.rb +++ b/spec/lib/learn_gitlab/onboarding_spec.rb @@ -9,7 +9,7 @@ RSpec.describe LearnGitlab::Onboarding do let(:namespace) { build(:namespace) } let_it_be(:tracked_action_columns) do - tracked_actions = described_class::ACTION_ISSUE_IDS.keys + described_class::ACTION_DOC_URLS.keys + tracked_actions = described_class::ACTION_ISSUE_IDS.keys + described_class::ACTION_PATHS tracked_actions.map { |key| OnboardingProgress.column_name(key) } end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index d58b91e675a..f3c4f5a4f6f 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -8026,14 +8026,6 @@ RSpec.describe Project, factory_default: :keep do let(:object) { build(:project) } it_behaves_like 'blocks unsafe serialization' - - context 'when feature flag block_project_serialization is disabled' do - before do - stub_feature_flags(block_project_serialization: false) - end - - it_behaves_like 'allows unsafe serialization' - end end private diff --git a/spec/requests/api/graphql/group/issues_spec.rb b/spec/requests/api/graphql/group/issues_spec.rb index 332bf242e9c..26338f46611 100644 --- a/spec/requests/api/graphql/group/issues_spec.rb +++ b/spec/requests/api/graphql/group/issues_spec.rb @@ -44,6 +44,31 @@ RSpec.describe 'getting an issue list for a group' do end end + context 'when there are archived projects' do + let_it_be(:archived_project) { create(:project, :archived, group: group1) } + let_it_be(:archived_issue) { create(:issue, project: archived_project) } + + before_all do + group1.add_developer(current_user) + end + + it 'excludes issues from archived projects by default' do + post_graphql(query, current_user: current_user) + + expect(issues_ids).to contain_exactly(issue1_gid, issue2_gid) + end + + context 'when include_archived is true' do + let(:issue_filter_params) { { include_archived: true } } + + it 'includes issues from archived projects' do + post_graphql(query, current_user: current_user) + + expect(issues_ids).to contain_exactly(issue1_gid, issue2_gid, archived_issue.to_global_id.to_s) + end + end + end + context 'when there is a confidential issue' do let_it_be(:confidential_issue1) { create(:issue, :confidential, project: project1) } let_it_be(:confidential_issue2) { create(:issue, :confidential, project: project2) } diff --git a/spec/requests/api/graphql/group/merge_requests_spec.rb b/spec/requests/api/graphql/group/merge_requests_spec.rb index e9a5e558b1d..c0faff11c8d 100644 --- a/spec/requests/api/graphql/group/merge_requests_spec.rb +++ b/spec/requests/api/graphql/group/merge_requests_spec.rb @@ -16,6 +16,9 @@ RSpec.describe 'Query.group.mergeRequests' do let_it_be(:project_x) { create(:project, :repository) } let_it_be(:user) { create(:user, developer_projects: [project_x]) } + let_it_be(:archived_project) { create(:project, :archived, :repository, group: group) } + let_it_be(:archived_mr) { create(:merge_request, source_project: archived_project) } + let_it_be(:mr_attrs) do { target_branch: 'master' } end @@ -119,4 +122,22 @@ RSpec.describe 'Query.group.mergeRequests' do expect(mrs_data).to match_array(expected_mrs(mrs_a + mrs_b + mrs_c)) end end + + describe 'passing include_archived: true' do + let(:query) do + <<~GQL + query($path: ID!) { + group(fullPath: $path) { + mergeRequests(includeArchived: true) { nodes { id } } + } + } + GQL + end + + it 'can find all merge requests in the group, including from archived projects' do + post_graphql(query, current_user: user, variables: { path: group.full_path }) + + expect(mrs_data).to match_array(expected_mrs(mrs_a + mrs_b + [archived_mr])) + end + end end diff --git a/spec/requests/projects/redirect_controller_spec.rb b/spec/requests/projects/redirect_controller_spec.rb new file mode 100644 index 00000000000..3bbca3ca32b --- /dev/null +++ b/spec/requests/projects/redirect_controller_spec.rb @@ -0,0 +1,66 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe "Projects::RedirectController requests" do + using RSpec::Parameterized::TableSyntax + + let_it_be(:private_project) { create(:project, :private) } + let_it_be(:public_project) { create(:project, :public) } + let_it_be(:user) { create(:user) } + + before_all do + private_project.add_developer(user) + end + + describe 'GET redirect_from_id' do + where(:authenticated, :project, :is_found) do + true | ref(:private_project) | true + false | ref(:private_project) | false + true | ref(:public_project) | true + false | ref(:public_project) | true + true | build(:project, id: 0) | false + end + + with_them do + before do + sign_in(user) if authenticated + + get "/projects/#{project.id}" + end + + if params[:is_found] + it 'redirects to the project page' do + expect(response).to have_gitlab_http_status(:found) + expect(response).to redirect_to(project_path(project)) + end + else + it 'gives 404' do + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + end + + # This is a regression test for https://gitlab.com/gitlab-org/gitlab/-/issues/351058 + context 'with sourcegraph enabled' do + let_it_be(:sourcegraph_url) { 'https://sourcegraph.test' } + + before do + allow(Gitlab::CurrentSettings).to receive(:sourcegraph_url).and_return(sourcegraph_url) + allow(Gitlab::CurrentSettings).to receive(:sourcegraph_enabled).and_return(true) + + sign_in(user) + end + + context 'with projects/:id route' do + subject { get "/projects/#{public_project.id}" } + + it 'redirects successfully' do + subject + + expect(response).to redirect_to(project_path(public_project)) + end + end + end +end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 3a935ddbdd8..65772895826 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -70,9 +70,11 @@ RSpec.describe 'project routing' do route_to('projects#preview_markdown', namespace_id: 'gitlab', id: 'gitlabhq') ) end + end - it 'to #resolve' do - expect(get('/projects/1')).to route_to('projects#resolve', id: '1') + describe Projects::RedirectController, 'routing' do + it 'to #redirect_from_id' do + expect(get('/projects/1')).to route_to('projects/redirect#redirect_from_id', id: '1') end end diff --git a/spec/services/ci/create_pipeline_service/artifacts_spec.rb b/spec/services/ci/create_pipeline_service/artifacts_spec.rb new file mode 100644 index 00000000000..1ec30d68666 --- /dev/null +++ b/spec/services/ci/create_pipeline_service/artifacts_spec.rb @@ -0,0 +1,67 @@ +# frozen_string_literal: true +require 'spec_helper' + +RSpec.describe Ci::CreatePipelineService do + let_it_be(:project) { create(:project, :repository) } + let_it_be(:user) { project.first_owner } + + let(:ref) { 'refs/heads/master' } + let(:source) { :push } + + let(:service) { described_class.new(project, user, { ref: ref }) } + let(:pipeline) { service.execute(source).payload } + + describe 'artifacts:' do + before do + stub_ci_pipeline_yaml_file(config) + allow_next_instance_of(Ci::BuildScheduleWorker) do |instance| + allow(instance).to receive(:perform).and_return(true) + end + end + + describe 'reports:' do + context 'with valid config' do + let(:config) do + <<~YAML + test-job: + script: "echo 'hello world' > cobertura.xml" + artifacts: + reports: + coverage_report: + coverage_format: 'cobertura' + path: 'cobertura.xml' + + dependency-scanning-job: + script: "echo 'hello world' > gl-dependency-scanning-report.json" + artifacts: + reports: + dependency_scanning: 'gl-dependency-scanning-report.json' + YAML + end + + it 'creates pipeline with builds' do + expect(pipeline).to be_persisted + expect(pipeline).not_to have_yaml_errors + expect(pipeline.builds.pluck(:name)).to contain_exactly('test-job', 'dependency-scanning-job') + end + end + + context 'with invalid config' do + let(:config) do + <<~YAML + test-job: + script: "echo 'hello world' > cobertura.xml" + artifacts: + reports: + foo: 'bar' + YAML + end + + it 'creates pipeline with yaml errors' do + expect(pipeline).to be_persisted + expect(pipeline).to have_yaml_errors + end + end + end + end +end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index b895e9ba67e..399b2b4be2d 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -1072,6 +1072,7 @@ RSpec.describe NotificationService, :mailer do end before do + project.reload add_user_subscriptions(issue) reset_delivered_emails! update_custom_notification(:new_issue, @u_guest_custom, resource: project) |