diff options
70 files changed, 739 insertions, 242 deletions
diff --git a/.gitlab/ci/global.gitlab-ci.yml b/.gitlab/ci/global.gitlab-ci.yml index 0c178f349a5..203f4826ae4 100644 --- a/.gitlab/ci/global.gitlab-ci.yml +++ b/.gitlab/ci/global.gitlab-ci.yml @@ -365,3 +365,8 @@ docker run --rm --privileged ${QEMU_IMAGE} --install all; fi - docker buildx create --use # creates and set's to active buildkit builder + +.use-kube-context: + before_script: + - export KUBE_CONTEXT="gitlab-org/gitlab:review-apps" + - kubectl config use-context ${KUBE_CONTEXT} diff --git a/.gitlab/ci/review-apps/main.gitlab-ci.yml b/.gitlab/ci/review-apps/main.gitlab-ci.yml index 27e2014baf1..cda13dd4be9 100644 --- a/.gitlab/ci/review-apps/main.gitlab-ci.yml +++ b/.gitlab/ci/review-apps/main.gitlab-ci.yml @@ -123,6 +123,7 @@ review-deploy: - echo "${CI_ENVIRONMENT_URL}" > environment_url.txt - echo "QA_GITLAB_URL=${CI_ENVIRONMENT_URL}" > environment.env - *base-before_script + - !reference [".use-kube-context", before_script] script: - run_timed_command "check_kube_domain" - run_timed_command "download_chart" @@ -156,6 +157,7 @@ review-deploy-sample-projects: - export GITLAB_WORKHORSE_VERSION=$(<GITLAB_WORKHORSE_VERSION) - echo "${CI_ENVIRONMENT_URL}" > environment_url.txt - *base-before_script + - !reference [".use-kube-context", before_script] script: - date - create_sample_projects @@ -173,6 +175,7 @@ review-deploy-sample-projects: before_script: - source ./scripts/utils.sh - source ./scripts/review_apps/review-apps.sh + - !reference [".use-kube-context", before_script] review-delete-deployment: extends: @@ -186,7 +189,7 @@ review-stop: extends: - .review-stop-base - .review:rules:review-stop - resource_group: review/${CI_COMMIT_REF_SLUG}${SCHEDULE_TYPE} # CI_ENVIRONMENT_SLUG is not available here and we want this to be the same as the environment + resource_group: review/${CI_COMMIT_REF_SLUG}${SCHEDULE_TYPE} # CI_ENVIRONMENT_SLUG is not available here and we want this to be the same as the environment stage: deploy needs: [] script: diff --git a/.gitlab/ci/review.gitlab-ci.yml b/.gitlab/ci/review.gitlab-ci.yml index be9ad5128f3..1fb8985aba1 100644 --- a/.gitlab/ci/review.gitlab-ci.yml +++ b/.gitlab/ci/review.gitlab-ci.yml @@ -7,14 +7,20 @@ review-cleanup: environment: name: review/regular-cleanup action: access + variables: + KUBE_NAMESPACE: "review-apps" # gcp_cleanup.sh requires passing KUBE_NAMESPACE variable before_script: - source scripts/utils.sh - source scripts/review_apps/gcp_cleanup.sh + - !reference [".use-kube-context", before_script] - install_gitlab_gem - setup_gcp_dependencies script: - - scripts/review_apps/automated_cleanup.rb - - gcp_cleanup + - exit_code_cmd_1=0; + - exit_code_cmd_2=0; + - scripts/review_apps/automated_cleanup.rb || exit_code_cmd_1=$? + - gcp_cleanup || exit_code_cmd_2=$? + - if [ $exit_code_cmd_1 -ne 0 ] || [ $exit_code_cmd_2 -ne 0 ]; then (scripts/slack review-apps-monitoring "☠️ \`${CI_JOB_NAME}\` failed! ☠️ See ${CI_JOB_URL}" warning "GitLab Bot" && exit 1); fi; .base-review-checks: extends: @@ -35,8 +41,10 @@ review-k8s-resources-count-checks: environment: name: review/k8s-resources-count-checks action: verify + before_script: + - !reference [".use-kube-context", before_script] script: - - scripts/review_apps/k8s-resources-count-checks.sh + - scripts/review_apps/k8s-resources-count-checks.sh || (scripts/slack review-apps-monitoring "☠️ \`${CI_JOB_NAME}\` failed! ☠️ See ${CI_JOB_URL}" warning "GitLab Bot" && exit 1); review-gcp-quotas-checks: extends: @@ -46,8 +54,10 @@ review-gcp-quotas-checks: environment: name: review/gcp-quotas-checks action: verify + before_script: + - !reference [".use-kube-context", before_script] script: - - ruby scripts/review_apps/gcp-quotas-checks.rb + - ruby scripts/review_apps/gcp-quotas-checks.rb || (scripts/slack review-apps-monitoring "☠️ \`${CI_JOB_NAME}\` failed! ☠️ See ${CI_JOB_URL}" warning "GitLab Bot" && exit 1); start-review-app-pipeline: extends: diff --git a/app/assets/javascripts/issues/list/components/issue_card_statistics.vue b/app/assets/javascripts/issues/list/components/issue_card_statistics.vue new file mode 100644 index 00000000000..2d00c3e549d --- /dev/null +++ b/app/assets/javascripts/issues/list/components/issue_card_statistics.vue @@ -0,0 +1,56 @@ +<script> +import { GlIcon, GlTooltipDirective } from '@gitlab/ui'; +import { i18n } from '../constants'; + +export default { + i18n, + components: { + GlIcon, + }, + directives: { + GlTooltip: GlTooltipDirective, + }, + props: { + issue: { + type: Object, + required: true, + }, + }, +}; +</script> + +<template> + <ul class="gl-display-contents"> + <li + v-if="issue.mergeRequestsCount" + v-gl-tooltip + class="gl-display-none gl-sm-display-block gl-mr-3" + :title="$options.i18n.relatedMergeRequests" + data-testid="merge-requests" + > + <gl-icon name="merge-request" /> + {{ issue.mergeRequestsCount }} + </li> + <li + v-if="issue.upvotes" + v-gl-tooltip + class="gl-display-none gl-sm-display-block gl-mr-3" + :title="$options.i18n.upvotes" + data-testid="issuable-upvotes" + > + <gl-icon name="thumb-up" /> + {{ issue.upvotes }} + </li> + <li + v-if="issue.downvotes" + v-gl-tooltip + class="gl-display-none gl-sm-display-block gl-mr-3" + :title="$options.i18n.downvotes" + data-testid="issuable-downvotes" + > + <gl-icon name="thumb-down" /> + {{ issue.downvotes }} + </li> + <slot></slot> + </ul> +</template> diff --git a/app/assets/javascripts/issues/list/components/issues_list_app.vue b/app/assets/javascripts/issues/list/components/issues_list_app.vue index 021e3c867b0..29faf6ed16c 100644 --- a/app/assets/javascripts/issues/list/components/issues_list_app.vue +++ b/app/assets/javascripts/issues/list/components/issues_list_app.vue @@ -1,13 +1,8 @@ <script> -import { - GlButton, - GlEmptyState, - GlFilteredSearchToken, - GlIcon, - GlTooltipDirective, -} from '@gitlab/ui'; +import { GlButton, GlEmptyState, GlFilteredSearchToken, GlTooltipDirective } from '@gitlab/ui'; import * as Sentry from '@sentry/browser'; import fuzzaldrinPlus from 'fuzzaldrin-plus'; +import IssueCardStatistics from 'ee_else_ce/issues/list/components/issue_card_statistics.vue'; import IssueCardTimeInfo from 'ee_else_ce/issues/list/components/issue_card_time_info.vue'; import getIssuesQuery from 'ee_else_ce/issues/list/queries/get_issues.query.graphql'; import getIssuesCountsQuery from 'ee_else_ce/issues/list/queries/get_issues_counts.query.graphql'; @@ -116,9 +111,9 @@ export default { EmptyStateSignedOut, GlButton, GlEmptyState, - GlIcon, IssuableByEmail, IssuableList, + IssueCardStatistics, IssueCardTimeInfo, NewIssueDropdown, }, @@ -854,37 +849,7 @@ export default { </template> <template #statistics="{ issuable = {} }"> - <li - v-if="issuable.mergeRequestsCount" - v-gl-tooltip - class="gl-display-none gl-sm-display-block" - :title="$options.i18n.relatedMergeRequests" - data-testid="merge-requests" - > - <gl-icon name="merge-request" /> - {{ issuable.mergeRequestsCount }} - </li> - <li - v-if="issuable.upvotes" - v-gl-tooltip - class="gl-display-none gl-sm-display-block" - :title="$options.i18n.upvotes" - data-testid="issuable-upvotes" - > - <gl-icon name="thumb-up" /> - {{ issuable.upvotes }} - </li> - <li - v-if="issuable.downvotes" - v-gl-tooltip - class="gl-display-none gl-sm-display-block" - :title="$options.i18n.downvotes" - data-testid="issuable-downvotes" - > - <gl-icon name="thumb-down" /> - {{ issuable.downvotes }} - </li> - <slot name="blocking-count" :issuable="issuable"></slot> + <issue-card-statistics :issue="issuable" /> </template> <template #empty-state> diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue index f71b1fbc539..79ea2624ec5 100644 --- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue +++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_author.vue @@ -1,8 +1,11 @@ <script> -import { GlTooltipDirective } from '@gitlab/ui'; +import { GlTooltipDirective, GlLink } from '@gitlab/ui'; export default { name: 'MrWidgetAuthor', + components: { + GlLink, + }, directives: { GlTooltip: GlTooltipDirective, }, @@ -28,13 +31,16 @@ export default { }; </script> <template> - <a + <gl-link v-gl-tooltip :href="authorUrl" :title="showAuthorName ? null : author.name" - class="author-link inline" + class="mr-widget-author" > - <img :src="avatarUrl" class="avatar avatar-inline s16" /> - <span v-if="showAuthorName" class="author">{{ author.name }}</span> - </a> + <img :src="avatarUrl" :alt="author.name" class="avatar avatar-inline s16" /><span + v-if="showAuthorName" + class="author" + >{{ author.name }}</span + > + </gl-link> </template> diff --git a/app/assets/stylesheets/page_bundles/merge_requests.scss b/app/assets/stylesheets/page_bundles/merge_requests.scss index b4ede515a7c..f36f6d652ba 100644 --- a/app/assets/stylesheets/page_bundles/merge_requests.scss +++ b/app/assets/stylesheets/page_bundles/merge_requests.scss @@ -1053,15 +1053,14 @@ $tabs-holder-z-index: 250; } } -.mr-ready-merge-related-links, -.mr-widget-merge-details { - a { - @include gl-text-decoration-underline; +.mr-ready-merge-related-links a, +.mr-widget-merge-details a, +.mr-widget-author { + @include gl-text-decoration-underline; - &:hover, - &:focus { - @include gl-text-decoration-none; - } + &:hover, + &:focus { + @include gl-text-decoration-none; } } diff --git a/app/controllers/concerns/web_hooks/hook_actions.rb b/app/controllers/concerns/web_hooks/hook_actions.rb index 75065ef9d24..093a7d943b2 100644 --- a/app/controllers/concerns/web_hooks/hook_actions.rb +++ b/app/controllers/concerns/web_hooks/hook_actions.rb @@ -66,9 +66,7 @@ module WebHooks end def hook_param_names - param_names = %i[enable_ssl_verification token url push_events_branch_filter] - param_names.push(:branch_filter_strategy) if Feature.enabled?(:enhanced_webhook_support_regex) - param_names + %i[enable_ssl_verification token url push_events_branch_filter branch_filter_strategy] end def destroy_hook(hook) diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index 90d5f945d78..39e8f6c500d 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -37,6 +37,6 @@ class Profiles::KeysController < Profiles::ApplicationController private def key_params - params.require(:key).permit(:title, :key, :expires_at) + params.require(:key).permit(:title, :key, :usage_type, :expires_at) end end diff --git a/app/graphql/types/subscription_type.rb b/app/graphql/types/subscription_type.rb index 9d5edec82b2..f7f26ba4c5a 100644 --- a/app/graphql/types/subscription_type.rb +++ b/app/graphql/types/subscription_type.rb @@ -34,6 +34,11 @@ module Types subscription: Subscriptions::IssuableUpdated, null: true, description: 'Triggered when the merge status of a merge request is updated.' + + field :merge_request_approval_state_updated, + subscription: Subscriptions::IssuableUpdated, + null: true, + description: 'Triggered when approval state of a merge request is updated.' end end diff --git a/app/helpers/profiles_helper.rb b/app/helpers/profiles_helper.rb index bfe39bbc211..979b979fba7 100644 --- a/app/helpers/profiles_helper.rb +++ b/app/helpers/profiles_helper.rb @@ -46,6 +46,14 @@ module ProfilesHelper end end + def ssh_key_usage_types + { + s_('SSHKey|Authentication & Signing') => 'auth_and_signing', + s_('SSHKey|Authentication') => 'auth', + s_('SSHKey|Signing') => 'signing' + } + end + # Overridden in EE::ProfilesHelper#ssh_key_expiration_tooltip def ssh_key_expiration_tooltip(key) return key.errors.full_messages.join(', ') if key.errors.full_messages.any? diff --git a/app/models/ci/pending_build.rb b/app/models/ci/pending_build.rb index 0fa6a234a3d..2b1eb67d4f2 100644 --- a/app/models/ci/pending_build.rb +++ b/app/models/ci/pending_build.rb @@ -3,11 +3,14 @@ module Ci class PendingBuild < Ci::ApplicationRecord include EachBatch + include Ci::Partitionable belongs_to :project belongs_to :build, class_name: 'Ci::Build' belongs_to :namespace, inverse_of: :pending_builds, class_name: 'Namespace' + partitionable scope: :build + validates :namespace, presence: true scope :ref_protected, -> { where(protected: true) } diff --git a/app/models/ci/running_build.rb b/app/models/ci/running_build.rb index ae38d54862d..62a428a0c1e 100644 --- a/app/models/ci/running_build.rb +++ b/app/models/ci/running_build.rb @@ -2,6 +2,10 @@ module Ci class RunningBuild < Ci::ApplicationRecord + include Ci::Partitionable + + partitionable scope: :build + belongs_to :project belongs_to :build, class_name: 'Ci::Build' belongs_to :runner, class_name: 'Ci::Runner' diff --git a/app/models/concerns/ci/partitionable.rb b/app/models/concerns/ci/partitionable.rb index 7d18ee137ea..4001ba7d0fe 100644 --- a/app/models/concerns/ci/partitionable.rb +++ b/app/models/concerns/ci/partitionable.rb @@ -30,6 +30,8 @@ module Ci Ci::BuildPendingState Ci::JobArtifact Ci::Pipeline + Ci::PendingBuild + Ci::RunningBuild Ci::PipelineVariable Ci::Stage Ci::UnitTestFailure diff --git a/app/models/group_deploy_key.rb b/app/models/group_deploy_key.rb index c65b00a6de0..9495df7ab6d 100644 --- a/app/models/group_deploy_key.rb +++ b/app/models/group_deploy_key.rb @@ -12,6 +12,11 @@ class GroupDeployKey < Key joins(:group_deploy_keys_groups).where(group_deploy_keys_groups: { group_id: group_ids }).uniq end + # Remove usage_type because it defined in Key class but doesn't have a column in group_deploy_keys table + def self.defined_enums + super.without('usage_type') + end + def type 'DeployKey' end diff --git a/app/models/hooks/active_hook_filter.rb b/app/models/hooks/active_hook_filter.rb index cdcfd3f3ff5..4599ebf8717 100644 --- a/app/models/hooks/active_hook_filter.rb +++ b/app/models/hooks/active_hook_filter.rb @@ -18,10 +18,6 @@ class ActiveHookFilter branch_name = Gitlab::Git.branch_name(data[:ref]) - if Feature.disabled?(:enhanced_webhook_support_regex) - return RefMatcher.new(@hook.push_events_branch_filter).matches?(branch_name) - end - case @hook.branch_filter_strategy when 'all_branches' true diff --git a/app/models/hooks/web_hook.rb b/app/models/hooks/web_hook.rb index 7b628de93c3..0c72158b407 100644 --- a/app/models/hooks/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -39,12 +39,9 @@ class WebHook < ApplicationRecord validates :token, format: { without: /\n/ } after_initialize :initialize_url_variables - before_validation :set_branch_filter_nil, \ - if: -> { branch_filter_strategy_all_branches? && enhanced_webhook_support_regex? } - validates :push_events_branch_filter, \ - untrusted_regexp: true, if: -> { branch_filter_strategy_regex? && enhanced_webhook_support_regex? } - validates :push_events_branch_filter, \ - "web_hooks/wildcard_branch_filter": true, if: -> { branch_filter_strategy_wildcard? } + before_validation :set_branch_filter_nil, if: :branch_filter_strategy_all_branches? + validates :push_events_branch_filter, untrusted_regexp: true, if: :branch_filter_strategy_regex? + validates :push_events_branch_filter, "web_hooks/wildcard_branch_filter": true, if: :branch_filter_strategy_wildcard? validates :url_variables, json_schema: { filename: 'web_hooks_url_variables' } validate :no_missing_url_variables @@ -239,10 +236,6 @@ class WebHook < ApplicationRecord errors.add(:url, "Invalid URL template. Missing keys: #{missing}") end - def enhanced_webhook_support_regex? - Feature.enabled?(:enhanced_webhook_support_regex) - end - def set_branch_filter_nil self.push_events_branch_filter = nil end diff --git a/app/models/key.rb b/app/models/key.rb index 78b0a38bcaa..35fc42a935f 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -32,6 +32,12 @@ class Key < ApplicationRecord delegate :name, :email, to: :user, prefix: true + enum usage_type: { + auth_and_signing: 0, + auth: 1, + signing: 2 + } + after_commit :add_to_authorized_keys, on: :create after_create :post_create_hook after_create :refresh_user_cache @@ -45,6 +51,8 @@ class Key < ApplicationRecord scope :preload_users, -> { preload(:user) } scope :for_user, -> (user) { where(user: user) } scope :order_last_used_at_desc, -> { reorder(arel_table[:last_used_at].desc.nulls_last) } + scope :auth, -> { where(usage_type: [:auth, :auth_and_signing]) } + scope :signing, -> { where(usage_type: [:signing, :auth_and_signing]) } # Date is set specifically in this scope to improve query time. scope :expired_today_and_not_notified, -> { where(["date(expires_at AT TIME ZONE 'UTC') = CURRENT_DATE AND expiry_notification_delivered_at IS NULL"]) } diff --git a/app/models/user.rb b/app/models/user.rb index b2235bff456..dae7ea75583 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -811,7 +811,7 @@ class User < ApplicationRecord # Returns a user for the given SSH key. def find_by_ssh_key_id(key_id) - find_by('EXISTS (?)', Key.select(1).where('keys.user_id = users.id').where(id: key_id)) + find_by('EXISTS (?)', Key.select(1).where('keys.user_id = users.id').auth.where(id: key_id)) end def find_by_full_path(path, follow_redirects: false) diff --git a/app/services/users/keys_count_service.rb b/app/services/users/keys_count_service.rb index f82d27eded9..378093f2e1b 100644 --- a/app/services/users/keys_count_service.rb +++ b/app/services/users/keys_count_service.rb @@ -11,7 +11,7 @@ module Users end def relation_for_count - user.keys + user.keys.auth end def raw? diff --git a/app/views/profiles/keys/_form.html.haml b/app/views/profiles/keys/_form.html.haml index e6d91543585..cf51d120edf 100644 --- a/app/views/profiles/keys/_form.html.haml +++ b/app/views/profiles/keys/_form.html.haml @@ -12,7 +12,12 @@ = f.label :title, s_('Profiles|Title'), class: 'label-bold' = f.text_field :title, class: "form-control gl-form-input input-lg", required: true, placeholder: s_('Profiles|Example: MacBook key'), data: { qa_selector: 'key_title_field' } %p.form-text.text-muted= s_('Profiles|Key titles are publicly visible.') - + - if Feature.enabled?(:ssh_key_usage_types, current_user) + .form-row + .col.form-group + = f.label :usage_type, s_('Profiles|Usage type') + .gl-md-form-input-lg + = f.select :usage_type, options_for_select(ssh_key_usage_types, :auth_and_signing), {}, { class: 'gl-form-select custom-select' } .form-row .col.form-group .js-access-tokens-expires-at{ data: {min_date: Date.tomorrow, max_date: max_date, default_date_offset: 365, description: ssh_key_expires_field_description } } diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml index de4a19bdad7..f0e4b143d0d 100644 --- a/app/views/profiles/keys/_key.html.haml +++ b/app/views/profiles/keys/_key.html.haml @@ -25,6 +25,10 @@ %span.expires.gl-mr-3 = key.expired? ? s_('Profiles|Expired:') : s_('Profiles|Expires:') = key.expires_at ? key.expires_at.to_date : _('Never') + - if Feature.enabled?(:ssh_key_usage_types, current_user) + %span.last-used-at.gl-mr-3 + = s_('Profiles|Usage type:') + = ssh_key_usage_types.invert[key.usage_type] %span.key-created-at.gl-display-flex.gl-align-items-center - if key.can_delete? .gl-ml-3 diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml index 04fa1d96204..1a8de16471f 100644 --- a/app/views/profiles/keys/_key_details.html.haml +++ b/app/views/profiles/keys/_key_details.html.haml @@ -9,6 +9,10 @@ %li %span.light= _('Title:') %strong= @key.title + - if Feature.enabled?(:ssh_key_usage_types, current_user) + %li + %span.light= s_('Profiles|Usage type:') + %strong= ssh_key_usage_types.invert[@key.usage_type] %li %span.light= _('Created on:') %strong= @key.created_at.to_s(:medium) diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml index 73ace033dc6..a749d1037a1 100644 --- a/app/views/shared/_file_highlight.html.haml +++ b/app/views/shared/_file_highlight.html.haml @@ -1,16 +1,28 @@ +-# We're not using `link_to` in the line loop because it is too slow once we get to thousands of lines. + +- offset = defined?(first_line_number) ? first_line_number : 1 +- highlight = defined?(highlight_line) && highlight_line ? highlight_line - offset : nil +- file_line_blame = Feature.enabled?(:file_line_blame) + +- if file_line_blame + - line_class = "js-line-links" + - blame_path = project_blame_path(@project, tree_join(@ref, blob.path)) +- else + - line_class = nil + - blame_path = nil + +- highlighted_blob = blob.present.highlight + #blob-content.file-content.code.js-syntax-highlight - - offset = defined?(first_line_number) ? first_line_number : 1 - - if Feature.enabled?(:file_line_blame) - - blame_path = project_blame_path(@project, tree_join(@ref, blob.path)) .line-numbers{ class: "gl-px-0!", data: { blame_path: blame_path } } - if blob.data.present? - - blob.data.each_line.each_with_index do |_, index| + - highlighted_blob.lines.count.times do |index| - i = index + offset - -# We're not using `link_to` because it is too slow once we get to thousands of lines. - %a.file-line-num.diff-line-num{ class: ("js-line-links" if Feature.enabled?(:file_line_blame)), href: "#L#{i}", id: "L#{i}", 'data-line-number' => i } + + %a.file-line-num.diff-line-num{ class: line_class, href: "#L#{i}", id: "L#{i}", 'data-line-number' => i } = i - - highlight = defined?(highlight_line) && highlight_line ? highlight_line - offset : nil + .blob-content{ data: { blob_id: blob.id, path: blob.path, highlight_line: highlight, qa_selector: 'file_content' } } %pre.code.highlight %code - = blob.present.highlight + = highlighted_blob diff --git a/app/views/shared/web_hooks/_form.html.haml b/app/views/shared/web_hooks/_form.html.haml index ecb736dac4f..12b105ae58c 100644 --- a/app/views/shared/web_hooks/_form.html.haml +++ b/app/views/shared/web_hooks/_form.html.haml @@ -19,16 +19,7 @@ = form.label :url, s_('Webhooks|Trigger'), class: 'label-bold' %ul.list-unstyled %li.gl-pb-5 - - if Feature.enabled?(:enhanced_webhook_support_regex) - - is_new_hook = hook.id.nil? - .js-vue-push-events{ data: { push_events: hook.push_events.to_s, strategy: hook.branch_filter_strategy, is_new_hook: is_new_hook.to_s, push_events_branch_filter: hook.push_events_branch_filter } } - - else - = form.gitlab_ui_checkbox_component :push_events, s_('Webhooks|Push events') - .gl-pl-6 - = form.text_field :push_events_branch_filter, class: 'form-control gl-form-input', - placeholder: 'Branch name or wildcard pattern to trigger on (leave blank for all)' - %p.form-text.text-muted.custom-control - = s_('Webhooks|Push to the repository.') + .js-vue-push-events{ data: { push_events: hook.push_events.to_s, strategy: hook.branch_filter_strategy, is_new_hook: hook.new_record?.to_s, push_events_branch_filter: hook.push_events_branch_filter } } %li.gl-pb-5 = form.gitlab_ui_checkbox_component :tag_push_events, s_('Webhooks|Tag push events'), diff --git a/config/feature_flags/development/enhanced_webhook_support_regex.yml b/config/feature_flags/development/enhanced_webhook_support_regex.yml deleted file mode 100644 index 2c0d2c82dbf..00000000000 --- a/config/feature_flags/development/enhanced_webhook_support_regex.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: enhanced_webhook_support_regex -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97235 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/375728 -milestone: '15.6' -type: development -group: group::integrations -default_enabled: false diff --git a/config/feature_flags/development/subgroups_approval_rules.yml b/config/feature_flags/development/ssh_key_usage_types.yml index e7935f5e5d2..fc7c719c13a 100644 --- a/config/feature_flags/development/subgroups_approval_rules.yml +++ b/config/feature_flags/development/ssh_key_usage_types.yml @@ -1,8 +1,8 @@ --- -name: subgroups_approval_rules -introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/91598 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/366741 -milestone: '15.2' +name: ssh_key_usage_types +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/104283 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/383046 +milestone: '15.7' type: development group: group::source code -default_enabled: true +default_enabled: false diff --git a/config/open_api.yml b/config/open_api.yml index e01b5265525..100bf4df67e 100644 --- a/config/open_api.yml +++ b/config/open_api.yml @@ -29,6 +29,8 @@ metadata: description: Operations related to clusters - name: container_registry description: Operations related to container registry + - name: container_registry_event + description: Operations related to container registry events - name: dashboard_annotations description: Operations related to dashboard annotations - name: dependency_proxy @@ -99,6 +101,8 @@ metadata: description: Operations related to project packages - name: protected environments description: Operations related to protected environments + - name: pypi_packages + description: Operations related to PyPI packages - name: release_links description: Operations related to release assets (links) - name: releases diff --git a/db/migrate/20221116161126_add_auth_signing_type_to_keys.rb b/db/migrate/20221116161126_add_auth_signing_type_to_keys.rb new file mode 100644 index 00000000000..795074fa0ca --- /dev/null +++ b/db/migrate/20221116161126_add_auth_signing_type_to_keys.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class AddAuthSigningTypeToKeys < Gitlab::Database::Migration[2.0] + def change + add_column :keys, :usage_type, :integer, limit: 2, null: false, default: 0 + end +end diff --git a/db/schema_migrations/20221116161126 b/db/schema_migrations/20221116161126 new file mode 100644 index 00000000000..5d65ed55915 --- /dev/null +++ b/db/schema_migrations/20221116161126 @@ -0,0 +1 @@ +93286f75aec167041985c2cde8ef1fc32447eae4f520c87131b89c28c402675c
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 69b25972812..ab7b632b04a 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -17126,7 +17126,8 @@ CREATE TABLE keys ( fingerprint_sha256 bytea, expires_at timestamp with time zone, expiry_notification_delivered_at timestamp with time zone, - before_expiry_notification_delivered_at timestamp with time zone + before_expiry_notification_delivered_at timestamp with time zone, + usage_type smallint DEFAULT 0 NOT NULL ); CREATE SEQUENCE keys_id_seq diff --git a/doc/api/product_analytics.md b/doc/api/product_analytics.md index e10327bc59b..b7401f83128 100644 --- a/doc/api/product_analytics.md +++ b/doc/api/product_analytics.md @@ -16,12 +16,13 @@ This feature is not ready for production use. NOTE: Make sure to define the `cube_api_base_url` and `cube_api_key` application settings first using [the API](settings.md). -## Send request to Cube +## Send query request to Cube Generate an access token that can be used to query the Cube API. For example: ```plaintext POST /projects/:id/product_analytics/request/load +POST /projects/:id/product_analytics/request/dry-run ``` | Attribute | Type | Required | Description | @@ -30,7 +31,7 @@ POST /projects/:id/product_analytics/request/load ### Request body -The body of the request should be a valid Cube query. +The body of the load request should be a valid Cube query. ```json { @@ -66,3 +67,15 @@ The body of the request should be a valid Cube query. "queryType": "multi" } ``` + +## Send meta request to Cube + +Returns Cube Meta data for the Analytics data. For example: + +```plaintext +GET /projects/:id/product_analytics/request/meta +``` + +| Attribute | Type | Required | Description | +| --------- |------------------| -------- |---------------------------------------------------------------| +| `id` | integer | yes | The ID of a project that the current user has read access to. | diff --git a/doc/integration/mattermost/index.md b/doc/integration/mattermost/index.md index 04b0157b737..df6130a7540 100644 --- a/doc/integration/mattermost/index.md +++ b/doc/integration/mattermost/index.md @@ -123,7 +123,7 @@ http://mattermost.example.com/signup/gitlab/complete http://mattermost.example.com/login/gitlab/complete ``` -Note that you do not need to select any options under **Scopes**. Choose **Save application**. +Make sure to select the **Trusted** and **Confidential** settings. Under **Scopes**, select `read_user`. Then, choose **Save application**. Once the application is created you are provided with an `Application ID` and `Secret`. One other piece of information needed is the URL of GitLab instance. Return to the server running GitLab Mattermost and edit the `/etc/gitlab/gitlab.rb` configuration file as follows using the values you received above: @@ -132,7 +132,7 @@ Return to the server running GitLab Mattermost and edit the `/etc/gitlab/gitlab. mattermost['gitlab_enable'] = true mattermost['gitlab_id'] = "12345656" mattermost['gitlab_secret'] = "123456789" -mattermost['gitlab_scope'] = "" +mattermost['gitlab_scope'] = "read_user" mattermost['gitlab_auth_endpoint'] = "http://gitlab.example.com/oauth/authorize" mattermost['gitlab_token_endpoint'] = "http://gitlab.example.com/oauth/token" mattermost['gitlab_user_api_endpoint'] = "http://gitlab.example.com/api/v4/user" diff --git a/lib/api/api.rb b/lib/api/api.rb index 10fdb8d7682..46d0aa3d19d 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -196,6 +196,7 @@ module API mount ::API::Clusters::Agents mount ::API::Commits mount ::API::CommitStatuses + mount ::API::ContainerRegistryEvent mount ::API::DependencyProxy mount ::API::DeployKeys mount ::API::DeployTokens @@ -252,6 +253,7 @@ module API mount ::API::ProjectTemplates mount ::API::ProtectedBranches mount ::API::ProtectedTags + mount ::API::PypiPackages mount ::API::Releases mount ::API::Release::Links mount ::API::RemoteMirrors @@ -289,7 +291,6 @@ module API mount ::API::ComposerPackages mount ::API::ConanInstancePackages mount ::API::ConanProjectPackages - mount ::API::ContainerRegistryEvent mount ::API::ContainerRepositories mount ::API::DebianGroupPackages mount ::API::DebianProjectPackages @@ -318,7 +319,6 @@ module API mount ::API::ProjectMilestones mount ::API::Projects mount ::API::ProtectedTags - mount ::API::PypiPackages mount ::API::ResourceLabelEvents mount ::API::ResourceStateEvents mount ::API::RpmProjectPackages diff --git a/lib/api/container_registry_event.rb b/lib/api/container_registry_event.rb index 9acf2fca1b3..9e59401ddf6 100644 --- a/lib/api/container_registry_event.rb +++ b/lib/api/container_registry_event.rb @@ -26,15 +26,21 @@ module API desc 'Receives notifications from the container registry when an operation occurs' do detail 'This feature was introduced in GitLab 12.10' consumes [:json, DOCKER_DISTRIBUTION_EVENTS_V1_JSON] + success code: 200, message: 'Success' + failure [ + { code: 401, message: 'Invalid Token' } + ] + tags %w[container_registry_event] end params do requires :events, type: Array, desc: 'Event notifications' do requires :action, type: String, desc: 'The action to perform, `push`, `delete`', values: %w[push delete].freeze optional :target, type: Hash, desc: 'The target of the action' do - optional :tag, type: String, desc: 'The target tag' - optional :repository, type: String, desc: 'The target repository' - optional :digest, type: String, desc: 'Unique identifier for target image manifest' + optional :tag, type: String, desc: 'The target tag', documentation: { example: 'latest' } + optional :repository, type: String, desc: 'The target repository', documentation: { example: 'group/p1' } + optional :digest, type: String, desc: 'Unique identifier for target image manifest', + documentation: { example: 'imagedigest' } end end end diff --git a/lib/api/internal/base.rb b/lib/api/internal/base.rb index c4464666020..dbd5c5f9db1 100644 --- a/lib/api/internal/base.rb +++ b/lib/api/internal/base.rb @@ -191,7 +191,7 @@ module API get '/authorized_keys', feature_category: :source_code_management, urgency: :high do fingerprint = Gitlab::InsecureKeyFingerprint.new(params.fetch(:key)).fingerprint_sha256 - key = Key.find_by_fingerprint_sha256(fingerprint) + key = Key.auth.find_by_fingerprint_sha256(fingerprint) not_found!('Key') if key.nil? present key, with: Entities::SSHKey end diff --git a/lib/api/pypi_packages.rb b/lib/api/pypi_packages.rb index 0707a0b0ec4..f9470ce1cb6 100644 --- a/lib/api/pypi_packages.rb +++ b/lib/api/pypi_packages.rb @@ -32,12 +32,12 @@ module API helpers do params :package_download do - requires :file_identifier, type: String, desc: 'The PyPi package file identifier', file_path: true - requires :sha256, type: String, desc: 'The PyPi package sha256 check sum' + requires :file_identifier, type: String, desc: 'The PyPi package file identifier', file_path: true, documentation: { example: 'my.pypi.package-0.0.1.tar.gz' } + requires :sha256, type: String, desc: 'The PyPi package sha256 check sum', documentation: { example: '5y57017232013c8ac80647f4ca153k3726f6cba62d055cd747844ed95b3c65ff' } end params :package_name do - requires :package_name, type: String, file_path: true, desc: 'The PyPi package name' + requires :package_name, type: String, file_path: true, desc: 'The PyPi package name', documentation: { example: 'my.pypi.package' } end def present_simple_index(group_or_project) @@ -102,7 +102,7 @@ module API end params do - requires :id, type: String, desc: 'The ID of a group' + requires :id, types: [Integer, String], desc: 'The ID or full path of the group.' end resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do after_validation do @@ -110,6 +110,16 @@ module API end namespace ':id/-/packages/pypi' do + desc 'Download a package file from a group' do + detail 'This feature was introduced in GitLab 13.12' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[pypi_packages] + end params do use :package_download end @@ -130,6 +140,13 @@ module API desc 'The PyPi Simple Group Index Endpoint' do detail 'This feature was introduced in GitLab 15.1' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[pypi_packages] end # An API entry point but returns an HTML file instead of JSON. @@ -141,6 +158,13 @@ module API desc 'The PyPi Simple Group Package Endpoint' do detail 'This feature was introduced in GitLab 12.10' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[pypi_packages] end params do @@ -164,6 +188,13 @@ module API namespace ':id/packages/pypi' do desc 'The PyPi package download endpoint' do detail 'This feature was introduced in GitLab 12.10' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[pypi_packages] end params do @@ -185,6 +216,13 @@ module API desc 'The PyPi Simple Project Index Endpoint' do detail 'This feature was introduced in GitLab 15.1' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[pypi_packages] end # An API entry point but returns an HTML file instead of JSON. @@ -196,6 +234,13 @@ module API desc 'The PyPi Simple Project Package Endpoint' do detail 'This feature was introduced in GitLab 12.10' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[pypi_packages] end params do @@ -211,15 +256,24 @@ module API desc 'The PyPi Package upload endpoint' do detail 'This feature was introduced in GitLab 12.10' + success code: 201 + failure [ + { code: 400, message: 'Bad Request' }, + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' }, + { code: 422, message: 'Unprocessable Entity' } + ] + tags %w[pypi_packages] end params do requires :content, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' } - requires :name, type: String - requires :version, type: String - optional :requires_python, type: String - optional :md5_digest, type: String - optional :sha256_digest, type: String, regexp: Gitlab::Regex.sha256_regex + requires :name, type: String, documentation: { example: 'my.pypi.package' } + requires :version, type: String, documentation: { example: '1.3.7' } + optional :requires_python, type: String, documentation: { example: '>=3.7' } + optional :md5_digest, type: String, documentation: { example: '900150983cd24fb0d6963f7d28e17f72' } + optional :sha256_digest, type: String, regexp: Gitlab::Regex.sha256_regex, documentation: { example: 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad' } end route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth @@ -243,6 +297,17 @@ module API forbidden! end + desc 'Authorize the PyPi package upload from workhorse' do + detail 'This feature was introduced in GitLab 12.10' + success code: 200 + failure [ + { code: 401, message: 'Unauthorized' }, + { code: 403, message: 'Forbidden' }, + { code: 404, message: 'Not Found' } + ] + tags %w[pypi_packages] + end + route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth post 'authorize' do project = project!(action: :read_project) diff --git a/lib/api/support/git_access_actor.rb b/lib/api/support/git_access_actor.rb index 16861a146ae..7a4e6f3e14c 100644 --- a/lib/api/support/git_access_actor.rb +++ b/lib/api/support/git_access_actor.rb @@ -16,7 +16,7 @@ module API def self.from_params(params) if params[:key_id] - new(key: Key.find_by_id(params[:key_id])) + new(key: Key.auth.find_by_id(params[:key_id])) elsif params[:user_id] new(user: UserFinder.new(params[:user_id]).find_by_id) elsif params[:username] diff --git a/lib/gitlab/ssh/signature.rb b/lib/gitlab/ssh/signature.rb index 3b4df9a8d0c..b1cad8d76c9 100644 --- a/lib/gitlab/ssh/signature.rb +++ b/lib/gitlab/ssh/signature.rb @@ -30,7 +30,7 @@ module Gitlab strong_memoize(:signed_by_key) do next unless key_fingerprint - Key.find_by_fingerprint_sha256(key_fingerprint) + Key.signing.find_by_fingerprint_sha256(key_fingerprint) end end diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index cf9876366aa..59c87c2b01b 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -81,7 +81,7 @@ namespace :gitlab do authorized_keys.clear - Key.find_in_batches(batch_size: 1000) do |keys| + Key.auth.find_in_batches(batch_size: 1000) do |keys| unless authorized_keys.batch_add_keys(keys) puts "Failed to add keys...".color(:red) exit 1 diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 0393bfbe671..0bfabf00780 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -31222,9 +31222,15 @@ msgstr "" msgid "Product analytics" msgstr "" +msgid "ProductAnalytics|Add to Dashboard" +msgstr "" + msgid "ProductAnalytics|Audience" msgstr "" +msgid "ProductAnalytics|New Analytics Widget Title" +msgstr "" + msgid "ProductAnalytics|There is no data for this type of chart currently. Please see the Setup tab if you have not configured the product analytics tool already." msgstr "" @@ -31591,6 +31597,12 @@ msgstr "" msgid "Profiles|Upload new avatar" msgstr "" +msgid "Profiles|Usage type" +msgstr "" + +msgid "Profiles|Usage type:" +msgstr "" + msgid "Profiles|Use a private email - %{email}" msgstr "" @@ -35812,6 +35824,15 @@ msgstr "" msgid "SSH public key" msgstr "" +msgid "SSHKey|Authentication" +msgstr "" + +msgid "SSHKey|Authentication & Signing" +msgstr "" + +msgid "SSHKey|Signing" +msgstr "" + msgid "SSL Verification:" msgstr "" @@ -45942,9 +45963,6 @@ msgstr "" msgid "Webhooks|Push events" msgstr "" -msgid "Webhooks|Push to the repository." -msgstr "" - msgid "Webhooks|Regex such as %{REGEX_CODE} is supported." msgstr "" diff --git a/package.json b/package.json index 37b400455c9..5d0ad0226ba 100644 --- a/package.json +++ b/package.json @@ -53,8 +53,8 @@ "@codesandbox/sandpack-client": "^1.2.2", "@gitlab/at.js": "1.5.7", "@gitlab/favicon-overlay": "2.0.0", - "@gitlab/svgs": "3.8.0", - "@gitlab/ui": "49.11.1", + "@gitlab/svgs": "3.11.0", + "@gitlab/ui": "49.11.2", "@gitlab/visual-review-tools": "1.7.3", "@gitlab/web-ide": "0.0.1-dev-20221114183058", "@rails/actioncable": "6.1.4-7", diff --git a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb index a7fe9c7630e..82df0b23312 100644 --- a/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb +++ b/qa/qa/specs/features/api/1_manage/migration/gitlab_migration_large_project_spec.rb @@ -225,8 +225,8 @@ module QA comment_diff = verify_comments(type, actual, expected) { - "missing_#{type}s": (expected.keys - actual.keys).map { |it| actual[it]&.slice(:title, :url) }.compact, - "extra_#{type}s": (actual.keys - expected.keys).map { |it| expected[it]&.slice(:title, :url) }.compact, + "missing_#{type}s": (expected.keys - actual.keys).map { |it| expected[it]&.slice(:title, :url) }.compact, + "extra_#{type}s": (actual.keys - expected.keys).map { |it| actual[it]&.slice(:title, :url) }.compact, "#{type}_comments": comment_diff } end diff --git a/qa/qa/tools/reliable_report.rb b/qa/qa/tools/reliable_report.rb index b3df6de3d54..08e87d994f8 100644 --- a/qa/qa/tools/reliable_report.rb +++ b/qa/qa/tools/reliable_report.rb @@ -341,7 +341,7 @@ module QA runs = records.count failed = records.count { |r| r.values["status"] == "failed" } - failure_rate = (failed.to_f / runs.to_f) * 100 + failure_rate = (failed.to_f / runs) * 100 result[stage][name] = { file: file, @@ -358,7 +358,7 @@ module QA # @return [String] def query(reliable) <<~QUERY - from(bucket: "#{Support::InfluxdbTools::INFLUX_TEST_METRICS_BUCKET}") + from(bucket: "#{Support::InfluxdbTools::INFLUX_MAIN_TEST_METRICS_BUCKET}") |> range(start: -#{range}d) |> filter(fn: (r) => r._measurement == "test-stats") |> filter(fn: (r) => r.run_type == "staging-full" or diff --git a/qa/spec/tools/reliable_report_spec.rb b/qa/spec/tools/reliable_report_spec.rb index f08af8a717a..c1ac5899279 100644 --- a/qa/spec/tools/reliable_report_spec.rb +++ b/qa/spec/tools/reliable_report_spec.rb @@ -56,7 +56,7 @@ describe QA::Tools::ReliableReport do def flux_query(reliable:) <<~QUERY - from(bucket: "e2e-test-stats") + from(bucket: "e2e-test-stats-main") |> range(start: -#{range}d) |> filter(fn: (r) => r._measurement == "test-stats") |> filter(fn: (r) => r.run_type == "staging-full" or diff --git a/spec/controllers/profiles/keys_controller_spec.rb b/spec/controllers/profiles/keys_controller_spec.rb index 63818337722..ed9022faf1b 100644 --- a/spec/controllers/profiles/keys_controller_spec.rb +++ b/spec/controllers/profiles/keys_controller_spec.rb @@ -14,13 +14,14 @@ RSpec.describe Profiles::KeysController do expires_at = 3.days.from_now expect do - post :create, params: { key: build(:key, expires_at: expires_at).attributes } + post :create, params: { key: build(:key, usage_type: :signing, expires_at: expires_at).attributes } end.to change { Key.count }.by(1) key = Key.last expect(key.expires_at).to be_like_time(expires_at) expect(key.fingerprint_md5).to be_present expect(key.fingerprint_sha256).to be_present + expect(key.usage_type).to eq('signing') end context 'with FIPS mode', :fips_mode do diff --git a/spec/controllers/projects/hooks_controller_spec.rb b/spec/controllers/projects/hooks_controller_spec.rb index 18f16937505..0045262a4af 100644 --- a/spec/controllers/projects/hooks_controller_spec.rb +++ b/spec/controllers/projects/hooks_controller_spec.rb @@ -154,28 +154,6 @@ RSpec.describe Projects::HooksController do expect(flash[:alert]).to be_blank end - it 'ignores branch_filter_strategy when flag is disabled' do - stub_feature_flags(enhanced_webhook_support_regex: false) - hook_params = { - url: 'http://example.com', - branch_filter_strategy: 'regex', - push_events: true - } - params = { namespace_id: project.namespace, project_id: project, hook: hook_params } - - expect { post :create, params: params }.to change(ProjectHook, :count).by(1) - - project_hook = ProjectHook.order_id_desc.take - - expect(project_hook).to have_attributes( - url: 'http://example.com', - branch_filter_strategy: 'wildcard' - ) - - expect(response).to have_gitlab_http_status(:found) - expect(flash[:alert]).to be_blank - end - it 'alerts the user if the new hook is invalid' do hook_params = { token: "TEST\nTOKEN", diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index b88d6b5fda4..eb9ec3e0483 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -7,6 +7,7 @@ FactoryBot.define do created_at { 'Di 29. Okt 09:50:00 CET 2013' } scheduling_type { 'stage' } pending + partition_id { pipeline.partition_id } options do { diff --git a/spec/factories/ci/pipelines.rb b/spec/factories/ci/pipelines.rb index 891628a0fc2..fea1d249e2b 100644 --- a/spec/factories/ci/pipelines.rb +++ b/spec/factories/ci/pipelines.rb @@ -8,7 +8,7 @@ FactoryBot.define do sha { 'b83d6e391c22777fca1ed3012fce84f633d7fed0' } status { 'pending' } add_attribute(:protected) { false } - partition_id { 100 } + partition_id { Ci::Pipeline.current_partition_value } project @@ -54,7 +54,6 @@ FactoryBot.define do end factory :ci_pipeline do - partition_id { 100 } transient { ci_ref_presence { true } } before(:create) do |pipeline, evaluator| diff --git a/spec/features/projects/settings/webhooks_settings_spec.rb b/spec/features/projects/settings/webhooks_settings_spec.rb index adbf2f6ee5c..0b6c4144340 100644 --- a/spec/features/projects/settings/webhooks_settings_spec.rb +++ b/spec/features/projects/settings/webhooks_settings_spec.rb @@ -48,47 +48,21 @@ RSpec.describe 'Projects > Settings > Webhook Settings' do expect(page).to have_content('Releases events') end - context 'when feature flag "enhanced_webhook_support_regex" is disabled' do - before do - stub_feature_flags(enhanced_webhook_support_regex: false) - end - - it 'create webhook', :js do - visit webhooks_path - - fill_in 'URL', with: url - check 'Tag push events' - fill_in 'hook_push_events_branch_filter', with: 'master' - check 'Enable SSL verification' - check 'Job events' - - click_button 'Add webhook' - - expect(page).to have_content(url) - expect(page).to have_content('SSL Verification: enabled') - expect(page).to have_content('Tag push events') - expect(page).to have_content('Job events') - expect(page).to have_content('Push events') - end - end - - context 'when feature flag "enhanced_webhook_support_regex" is enabled' do - it 'create webhook', :js do - visit webhooks_path + it 'create webhook', :js do + visit webhooks_path - fill_in 'URL', with: url - check 'Tag push events' - check 'Enable SSL verification' - check 'Job events' + fill_in 'URL', with: url + check 'Tag push events' + check 'Enable SSL verification' + check 'Job events' - click_button 'Add webhook' + click_button 'Add webhook' - expect(page).to have_content(url) - expect(page).to have_content('SSL Verification: enabled') - expect(page).to have_content('Tag push events') - expect(page).to have_content('Job events') - expect(page).to have_content('Push events') - end + expect(page).to have_content(url) + expect(page).to have_content('SSL Verification: enabled') + expect(page).to have_content('Tag push events') + expect(page).to have_content('Job events') + expect(page).to have_content('Push events') end it 'edit existing webhook', :js do diff --git a/spec/frontend/issues/list/components/issue_card_statistics_spec.js b/spec/frontend/issues/list/components/issue_card_statistics_spec.js new file mode 100644 index 00000000000..180d4ab7eb6 --- /dev/null +++ b/spec/frontend/issues/list/components/issue_card_statistics_spec.js @@ -0,0 +1,64 @@ +import { GlIcon } from '@gitlab/ui'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import IssueCardStatistics from '~/issues/list/components/issue_card_statistics.vue'; +import { i18n } from '~/issues/list/constants'; + +describe('IssueCardStatistics CE component', () => { + let wrapper; + + const findMergeRequests = () => wrapper.findByTestId('merge-requests'); + const findUpvotes = () => wrapper.findByTestId('issuable-upvotes'); + const findDownvotes = () => wrapper.findByTestId('issuable-downvotes'); + + const mountComponent = ({ mergeRequestsCount, upvotes, downvotes } = {}) => { + wrapper = shallowMountExtended(IssueCardStatistics, { + propsData: { + issue: { + mergeRequestsCount, + upvotes, + downvotes, + }, + }, + }); + }; + + describe('when issue attributes are undefined', () => { + it('does not render the attributes', () => { + mountComponent(); + + expect(findMergeRequests().exists()).toBe(false); + expect(findUpvotes().exists()).toBe(false); + expect(findDownvotes().exists()).toBe(false); + }); + }); + + describe('when issue attributes are defined', () => { + beforeEach(() => { + mountComponent({ mergeRequestsCount: 1, upvotes: 5, downvotes: 9 }); + }); + + it('renders merge requests', () => { + const mergeRequests = findMergeRequests(); + + expect(mergeRequests.text()).toBe('1'); + expect(mergeRequests.attributes('title')).toBe(i18n.relatedMergeRequests); + expect(mergeRequests.findComponent(GlIcon).props('name')).toBe('merge-request'); + }); + + it('renders upvotes', () => { + const upvotes = findUpvotes(); + + expect(upvotes.text()).toBe('5'); + expect(upvotes.attributes('title')).toBe(i18n.upvotes); + expect(upvotes.findComponent(GlIcon).props('name')).toBe('thumb-up'); + }); + + it('renders downvotes', () => { + const downvotes = findDownvotes(); + + expect(downvotes.text()).toBe('9'); + expect(downvotes.attributes('title')).toBe(i18n.downvotes); + expect(downvotes.findComponent(GlIcon).props('name')).toBe('thumb-down'); + }); + }); +}); diff --git a/spec/graphql/types/subscription_type_spec.rb b/spec/graphql/types/subscription_type_spec.rb index 04f0c72b06f..a57a8e751ac 100644 --- a/spec/graphql/types/subscription_type_spec.rb +++ b/spec/graphql/types/subscription_type_spec.rb @@ -14,6 +14,7 @@ RSpec.describe GitlabSchema.types['Subscription'] do issuable_milestone_updated merge_request_reviewers_updated merge_request_merge_status_updated + merge_request_approval_state_updated ] expect(described_class).to include_graphql_fields(*expected_fields) diff --git a/spec/lib/api/support/git_access_actor_spec.rb b/spec/lib/api/support/git_access_actor_spec.rb index e1c800d25a7..b3e8787583c 100644 --- a/spec/lib/api/support/git_access_actor_spec.rb +++ b/spec/lib/api/support/git_access_actor_spec.rb @@ -9,7 +9,8 @@ RSpec.describe API::Support::GitAccessActor do subject { described_class.new(user: user, key: key) } describe '.from_params' do - let(:key) { create(:key) } + let_it_be(:user) { create(:user) } + let_it_be(:key) { create(:key, user: user) } context 'with params that are valid' do it 'returns an instance of API::Support::GitAccessActor' do @@ -31,6 +32,42 @@ RSpec.describe API::Support::GitAccessActor do expect(described_class.from_params(identifier: "key-#{key.id}").user).to eq(key.user) end end + + context 'when passing a signing key' do + let_it_be(:key) { create(:key, usage_type: :signing, user: user) } + + it 'does not identify the user' do + actor = described_class.from_params({ identifier: "key-#{key.id}" }) + + expect(actor).to be_instance_of(described_class) + expect(actor.user).to be_nil + end + + it 'does not identify the key' do + actor = described_class.from_params({ key_id: key.id }) + + expect(actor).to be_instance_of(described_class) + expect(actor.key).to be_nil + end + end + + context 'when passing an auth-only key' do + let_it_be(:key) { create(:key, usage_type: :auth, user: user) } + + it 'identifies the user' do + actor = described_class.from_params({ identifier: "key-#{key.id}" }) + + expect(actor).to be_instance_of(described_class) + expect(actor.user).to eq(key.user) + end + + it 'identifies the key' do + actor = described_class.from_params({ key_id: key.id }) + + expect(actor).to be_instance_of(described_class) + expect(actor.key).to eq(key) + end + end end describe 'attributes' do diff --git a/spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb index 15df5b2f68c..74a68f28f3e 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/assign_partition_spec.rb @@ -10,13 +10,15 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::AssignPartition do Gitlab::Ci::Pipeline::Chain::Command.new(project: project, current_user: user) end - let(:pipeline) { build(:ci_pipeline, project: project) } + let(:pipeline) { build(:ci_pipeline, project: project, partition_id: nil) } let(:step) { described_class.new(pipeline, command) } let(:current_partition_id) { 123 } describe '#perform!' do + include Ci::PartitioningHelpers + before do - allow(Ci::Pipeline).to receive(:current_partition_value) { current_partition_id } + stub_current_partition_id(current_partition_id) end subject { step.perform! } diff --git a/spec/lib/gitlab/ssh/signature_spec.rb b/spec/lib/gitlab/ssh/signature_spec.rb index e8d366f0762..f3f1ba84f9e 100644 --- a/spec/lib/gitlab/ssh/signature_spec.rb +++ b/spec/lib/gitlab/ssh/signature_spec.rb @@ -7,7 +7,7 @@ RSpec.describe Gitlab::Ssh::Signature do let_it_be(:committer_email) { 'ssh-commit-test@example.com' } let_it_be(:public_key_text) { 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIJKOfqOH0fDde+Ua/1SObkXB1CEDF5M6UfARMpW3F87u' } let_it_be_with_reload(:user) { create(:user, email: committer_email) } - let_it_be_with_reload(:key) { create(:key, key: public_key_text, user: user) } + let_it_be_with_reload(:key) { create(:key, usage_type: :signing, key: public_key_text, user: user) } let(:signed_text) { 'This message was signed by an ssh key' } @@ -204,13 +204,25 @@ RSpec.describe Gitlab::Ssh::Signature do it_behaves_like 'unverified signature' end - context 'when key does not exist in GitLab' do - before do - key.delete + context 'when the signing key does not exist in GitLab' do + context 'when the key is not a signing one' do + before do + key.auth! + end + + it 'reports unknown_key status' do + expect(signature.verification_status).to eq(:unknown_key) + end end - it 'reports unknown_key status' do - expect(signature.verification_status).to eq(:unknown_key) + context 'when the key is removed' do + before do + key.delete + end + + it 'reports unknown_key status' do + expect(signature.verification_status).to eq(:unknown_key) + end end end diff --git a/spec/models/ci/pending_build_spec.rb b/spec/models/ci/pending_build_spec.rb index 4bb43233dbd..331522070df 100644 --- a/spec/models/ci/pending_build_spec.rb +++ b/spec/models/ci/pending_build_spec.rb @@ -196,6 +196,28 @@ RSpec.describe Ci::PendingBuild do end end + describe 'partitioning', :ci_partitionable do + include Ci::PartitioningHelpers + + before do + stub_current_partition_id + end + + let(:new_pipeline ) { create(:ci_pipeline, project: pipeline.project) } + let(:new_build) { create(:ci_build, pipeline: new_pipeline) } + + it 'assigns the same partition id as the one that build has', :aggregate_failures do + expect(new_build.partition_id).to eq ci_testing_partition_id + expect(new_build.partition_id).not_to eq pipeline.partition_id + + described_class.upsert_from_build!(build) + described_class.upsert_from_build!(new_build) + + expect(build.reload.queuing_entry.partition_id).to eq pipeline.partition_id + expect(new_build.reload.queuing_entry.partition_id).to eq ci_testing_partition_id + end + end + it_behaves_like 'cleanup by a loose foreign key' do let!(:parent) { create(:namespace) } let!(:model) { create(:ci_pending_build, namespace: parent) } diff --git a/spec/models/ci/running_build_spec.rb b/spec/models/ci/running_build_spec.rb index d2f74494308..84ef946980f 100644 --- a/spec/models/ci/running_build_spec.rb +++ b/spec/models/ci/running_build_spec.rb @@ -50,6 +50,28 @@ RSpec.describe Ci::RunningBuild do end end + describe 'partitioning', :ci_partitionable do + include Ci::PartitioningHelpers + + before do + stub_current_partition_id + end + + let(:new_pipeline ) { create(:ci_pipeline, project: pipeline.project) } + let(:new_build) { create(:ci_build, :running, pipeline: new_pipeline, runner: runner) } + + it 'assigns the same partition id as the one that build has', :aggregate_failures do + expect(new_build.partition_id).to eq ci_testing_partition_id + expect(new_build.partition_id).not_to eq pipeline.partition_id + + described_class.upsert_shared_runner_build!(build) + described_class.upsert_shared_runner_build!(new_build) + + expect(build.reload.runtime_metadata.partition_id).to eq pipeline.partition_id + expect(new_build.reload.runtime_metadata.partition_id).to eq ci_testing_partition_id + end + end + it_behaves_like 'cleanup by a loose foreign key' do let!(:parent) { create(:project) } let!(:model) { create(:ci_running_build, project: parent) } diff --git a/spec/models/group_deploy_key_spec.rb b/spec/models/group_deploy_key_spec.rb index dfb4fee593f..c1fd88ad748 100644 --- a/spec/models/group_deploy_key_spec.rb +++ b/spec/models/group_deploy_key_spec.rb @@ -30,6 +30,12 @@ RSpec.describe GroupDeployKey do end end + describe '.defined_enums' do + it 'excludes the inherited enum' do + expect(described_class.defined_enums).to eq({}) + end + end + describe '#can_be_edited_for' do let_it_be(:user) { create(:user) } diff --git a/spec/models/hooks/active_hook_filter_spec.rb b/spec/models/hooks/active_hook_filter_spec.rb index 47c0fbdb106..d2a8f89041e 100644 --- a/spec/models/hooks/active_hook_filter_spec.rb +++ b/spec/models/hooks/active_hook_filter_spec.rb @@ -85,23 +85,5 @@ RSpec.describe ActiveHookFilter do it { expect(filter.matches?(:push_hooks, { ref: 'refs/heads/feature1' })).to be true } end end - - context 'when feature flag is disabled' do - before do - stub_feature_flags(enhanced_webhook_support_regex: false) - end - - let(:hook) do - build( - :project_hook, - push_events: true, - push_events_branch_filter: '(master)', - branch_filter_strategy: 'regex' - ) - end - - it { expect(filter.matches?(:push_hooks, { ref: 'refs/heads/master' })).to be false } - it { expect(filter.matches?(:push_hooks, { ref: 'refs/heads/(master)' })).to be true } - end end end diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index b98c0e8eae0..92f4d6d8531 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -3,6 +3,8 @@ require 'spec_helper' RSpec.describe Key, :mailer do + it_behaves_like 'having unique enum values' + describe "Associations" do it { is_expected.to belong_to(:user) } end @@ -216,6 +218,20 @@ RSpec.describe Key, :mailer do end end end + + context 'usage type scopes' do + let_it_be(:auth_key) { create(:key, usage_type: :auth) } + let_it_be(:auth_and_signing_key) { create(:key, usage_type: :auth_and_signing) } + let_it_be(:signing_key) { create(:key, usage_type: :signing) } + + it 'auth scope returns auth and auth_and_signing keys' do + expect(described_class.auth).to match_array([auth_key, auth_and_signing_key]) + end + + it 'signing scope returns signing and auth_and_signing keys' do + expect(described_class.signing).to match_array([signing_key, auth_and_signing_key]) + end + end end context 'validation of uniqueness (based on fingerprint uniqueness)' do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index fdeb98f52ae..43541250904 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -3090,6 +3090,14 @@ RSpec.describe User do expect(described_class.find_by_ssh_key_id(-1)).to be_nil end end + + it 'does not return a signing-only key', :aggregate_failures do + signing_key = create(:key, usage_type: :signing, user: user) + auth_and_signing_key = create(:key, usage_type: :auth_and_signing, user: user) + + expect(described_class.find_by_ssh_key_id(signing_key.id)).to be_nil + expect(described_class.find_by_ssh_key_id(auth_and_signing_key.id)).to eq(user) + end end shared_examples "find user by login" do diff --git a/spec/requests/api/internal/base_spec.rb b/spec/requests/api/internal/base_spec.rb index 32cacfc713c..5ad56d43f88 100644 --- a/spec/requests/api/internal/base_spec.rb +++ b/spec/requests/api/internal/base_spec.rb @@ -325,6 +325,28 @@ RSpec.describe API::Internal::Base do expect(json_response['name']).to eq(user.name) end + context 'when signing key is passed' do + it 'does not authenticate user' do + key.signing! + + get(api("/internal/discover"), params: { key_id: key.id }, headers: gitlab_shell_internal_api_request_header) + + expect(json_response).to be_nil + end + end + + context 'when auth-only key is passed' do + it 'authenticates user' do + key.auth! + + get(api("/internal/discover"), params: { key_id: key.id }, headers: gitlab_shell_internal_api_request_header) + + expect(response).to have_gitlab_http_status(:ok) + + expect(json_response['name']).to eq(user.name) + end + end + it "finds a user by username" do get(api("/internal/discover"), params: { username: user.username }, headers: gitlab_shell_internal_api_request_header) @@ -360,6 +382,30 @@ RSpec.describe API::Internal::Base do expect(json_response['key'].split[1]).to eq(key.key.split[1]) end + context 'when signing key is passed' do + it 'does not return the key' do + key.signing! + + get(api('/internal/authorized_keys'), params: { key: key.key.split[1] }, headers: gitlab_shell_internal_api_request_header) + + expect(response).to have_gitlab_http_status(:not_found) + + expect(json_response['id']).to be_nil + end + end + + context 'when auth-only key is passed' do + it 'authenticates user' do + key.auth! + + get(api('/internal/authorized_keys'), params: { key: key.key.split[1] }, headers: gitlab_shell_internal_api_request_header) + + expect(response).to have_gitlab_http_status(:ok) + expect(json_response['id']).to eq(key.id) + expect(json_response['key'].split[1]).to eq(key.key.split[1]) + end + end + it 'exposes the comment of the key as a simple identifier of username + hostname' do get(api('/internal/authorized_keys'), params: { key: key.key.split[1] }, headers: gitlab_shell_internal_api_request_header) diff --git a/spec/services/users/keys_count_service_spec.rb b/spec/services/users/keys_count_service_spec.rb index aff267cce5e..607d2946b2c 100644 --- a/spec/services/users/keys_count_service_spec.rb +++ b/spec/services/users/keys_count_service_spec.rb @@ -17,6 +17,12 @@ RSpec.describe Users::KeysCountService, :use_clean_rails_memory_store_caching do it 'returns the number of SSH keys as an Integer' do expect(subject.count).to eq(1) end + + it 'does not count signing keys' do + create(:key, usage_type: :signing, user: user) + + expect(subject.count).to eq(1) + end end describe '#uncached_count' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e083bb06241..943105a57c4 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -457,6 +457,13 @@ RSpec.configure do |config| config.before(:each, :js) do allow_any_instance_of(VersionCheck).to receive(:response).and_return({ "severity" => "success" }) end + + # Add warning for example missing feature_category + config.before do |example| + if example.metadata[:feature_category].blank? && !ENV['CI'] + warn "Missing metadata feature_category: #{example.location} See https://docs.gitlab.com/ee/development/testing_guide/best_practices.html#feature-category-metadata" + end + end end ActiveRecord::Migration.maintain_test_schema! diff --git a/spec/support/helpers/ci/partitioning_helpers.rb b/spec/support/helpers/ci/partitioning_helpers.rb new file mode 100644 index 00000000000..110199a3147 --- /dev/null +++ b/spec/support/helpers/ci/partitioning_helpers.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +module Ci + module PartitioningHelpers + def stub_current_partition_id(id = Ci::PartitioningTesting::PartitionIdentifiers.ci_testing_partition_id) + allow(::Ci::Pipeline) + .to receive(:current_partition_value) + .and_return(id) + end + end +end diff --git a/spec/tasks/gitlab/shell_rake_spec.rb b/spec/tasks/gitlab/shell_rake_spec.rb index 52a9738fb51..195859eac70 100644 --- a/spec/tasks/gitlab/shell_rake_spec.rb +++ b/spec/tasks/gitlab/shell_rake_spec.rb @@ -22,4 +22,23 @@ RSpec.describe 'gitlab:shell rake tasks', :silence_stdout do run_rake_task('gitlab:shell:install') end end + + describe 'setup task' do + it 'writes authorized keys into the file' do + allow(Gitlab::CurrentSettings).to receive(:authorized_keys_enabled?).and_return(true) + stub_env('force', 'yes') + + auth_key = create(:key) + auth_and_signing_key = create(:key, usage_type: :auth_and_signing) + create(:key, usage_type: :signing) + + expect_next_instance_of(Gitlab::AuthorizedKeys) do |instance| + expect(instance).to receive(:batch_add_keys).once do |keys| + expect(keys).to match_array([auth_key, auth_and_signing_key]) + end + end + + run_rake_task('gitlab:shell:setup') + end + end end diff --git a/spec/views/profiles/keys/_form.html.haml_spec.rb b/spec/views/profiles/keys/_form.html.haml_spec.rb index 3c61afb21c5..2a1bb5334b6 100644 --- a/spec/views/profiles/keys/_form.html.haml_spec.rb +++ b/spec/views/profiles/keys/_form.html.haml_spec.rb @@ -32,6 +32,11 @@ RSpec.describe 'profiles/keys/_form.html.haml' do expect(rendered).to have_text('Key titles are publicly visible.') end + it 'has the usage type field', :aggregate_failures do + expect(page).to have_select _('Usage type'), + selected: 'Authentication & Signing', options: ['Authentication & Signing', 'Authentication', 'Signing'] + end + it 'has the expires at field', :aggregate_failures do expect(rendered).to have_field('Expiration date', type: 'text') expect(page.find_field('Expiration date')['min']).to eq(l(1.day.from_now, format: "%Y-%m-%d")) @@ -47,4 +52,17 @@ RSpec.describe 'profiles/keys/_form.html.haml' do expect(rendered).to have_button('Add key') end end + + context 'when ssh_key_usage_types is disabled' do + before do + stub_feature_flags(ssh_key_usage_types: false) + end + + it 'has the usage type field', :aggregate_failures do + render + + expect(rendered).not_to have_field('Usage type', type: 'text') + expect(rendered).not_to have_text('Authentication & Signing') + end + end end diff --git a/spec/views/profiles/keys/_key.html.haml_spec.rb b/spec/views/profiles/keys/_key.html.haml_spec.rb index 1040541332d..821e7ea794d 100644 --- a/spec/views/profiles/keys/_key.html.haml_spec.rb +++ b/spec/views/profiles/keys/_key.html.haml_spec.rb @@ -30,6 +30,38 @@ RSpec.describe 'profiles/keys/_key.html.haml' do expect(response).to render_template(partial: 'shared/ssh_keys/_key_delete') end + context 'displays the usage type' do + where(:usage_type, :usage_type_text) do + [ + [:auth, 'Authentication'], + [:auth_and_signing, 'Authentication & Signing'], + [:signing, 'Signing'] + ] + end + + with_them do + let(:key) { create(:key, user: user, usage_type: usage_type) } + + it 'renders usage type text' do + render + + expect(rendered).to have_text(usage_type_text) + end + + context 'when ssh_key_usage_types is disabled' do + before do + stub_feature_flags(ssh_key_usage_types: false) + end + + it 'does not render usage type text' do + render + + expect(rendered).not_to have_text(usage_type_text) + end + end + end + end + context 'when the key has not been used' do let_it_be(:key) do create(:personal_key, diff --git a/spec/views/profiles/keys/_key_details.html.haml_spec.rb b/spec/views/profiles/keys/_key_details.html.haml_spec.rb new file mode 100644 index 00000000000..acb22b5657e --- /dev/null +++ b/spec/views/profiles/keys/_key_details.html.haml_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'profiles/keys/_key_details.html.haml' do + let_it_be(:user) { create(:user) } + + before do + assign(:key, key) + allow(view).to receive(:is_admin).and_return(false) + end + + describe 'displays the usage type' do + where(:usage_type, :usage_type_text) do + [ + [:auth, 'Authentication'], + [:auth_and_signing, 'Authentication & Signing'], + [:signing, 'Signing'] + ] + end + + with_them do + let(:key) { create(:key, user: user, usage_type: usage_type) } + + it 'renders usage type text' do + render + + expect(rendered).to have_text(usage_type_text) + end + + context 'when ssh_key_usage_types is disabled' do + before do + stub_feature_flags(ssh_key_usage_types: false) + end + + it 'does not render usage type text' do + render + + expect(rendered).not_to have_text(usage_type_text) + end + end + end + end +end diff --git a/yarn.lock b/yarn.lock index e6e7d5cb9ca..9930c5671f7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1108,15 +1108,15 @@ stylelint-declaration-strict-value "1.8.0" stylelint-scss "4.2.0" -"@gitlab/svgs@3.8.0": - version "3.8.0" - resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.8.0.tgz#bc7fa51e345e26cff56fdff629ea439adfa1e0cb" - integrity sha512-DUWeG2Vx+1ntZ/1GT6S36ZOtXvM5Wm02MtDRrQS4GuOX4rkTeG9aoutSJuwQ2h9BNtxl0U/jkf5GVBxacj18XA== - -"@gitlab/ui@49.11.1": - version "49.11.1" - resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-49.11.1.tgz#ce18f23ac4f48159e8f57f8dedef2f05890a97f2" - integrity sha512-iFhhi03Vrz+wxxUzwVmaaP1s0qeJtACCEpj7xESDVbevjDEqf1muMz/PTH10NslrVbf1VchqNwSC+Ww6C7yAKQ== +"@gitlab/svgs@3.11.0": + version "3.11.0" + resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.11.0.tgz#91e8e25583cddef48c0c79175203e5b0a4eaa519" + integrity sha512-1cJu1WXPoOHfGgv5fT3nmA9cgAQ3U1Fm/oMSVYUgBxU35R0I8W704GMLsIZwBuQ/S/Ow7WLwIkoOhLb/spNKPg== + +"@gitlab/ui@49.11.2": + version "49.11.2" + resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-49.11.2.tgz#290bba7a3d4682365ad81747cf54a2f9927526c1" + integrity sha512-qu5qcl+4niYBCPIZS9ZU0i1h/IGL4ZOp4hDsEAIUFGJg9Sp0TBmwdjwKJQbvnexDS3xs1eSBzi+kQ57H+c9wQQ== dependencies: "@popperjs/core" "^2.11.2" bootstrap-vue "2.20.1" |