Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2022-02-22 12:18:00 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2022-02-22 12:18:00 +0300
commit932d504aaadc03b978eccad962a12be93f84be47 (patch)
treeda6fba1cb0b34d8a5c8ecd57f85b23fc3ee9ed63
parentc123291db91a8c54865779ee29ddebaad159d147 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.rubocop_todo/graphql/field_definitions.yml5
-rw-r--r--.rubocop_todo/graphql/ordered_arguments.yml4
-rw-r--r--app/assets/javascripts/issues/show/components/title.vue4
-rw-r--r--app/assets/javascripts/security_configuration/components/training_provider_list.vue16
-rw-r--r--app/assets/javascripts/security_configuration/constants.js3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue71
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue95
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/extensions/issues.js10
-rw-r--r--app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue7
-rw-r--r--app/graphql/types/commit_action_type.rb16
-rw-r--r--app/graphql/types/diff_paths_input_type.rb4
-rw-r--r--app/graphql/types/issues/negated_issue_filter_input_type.rb24
-rw-r--r--app/graphql/types/jira_users_mapping_input_type.rb8
-rw-r--r--app/views/shared/issue_type/_details_content.html.haml2
-rw-r--r--db/fixtures/development/33_triage_ops.rb139
-rw-r--r--doc/api/topics.md25
-rw-r--r--doc/development/contributing/merge_request_workflow.md2
-rw-r--r--doc/development/feature_flags/index.md5
-rw-r--r--doc/development/new_fe_guide/modules/widget_extensions.md1
-rw-r--r--doc/user/application_security/secret_detection/index.md8
-rw-r--r--doc/user/usage_quotas.md2
-rw-r--r--lib/api/topics.rb14
-rw-r--r--spec/features/incidents/incident_details_spec.rb4
-rw-r--r--spec/features/issues/incident_issue_spec.rb2
-rw-r--r--spec/features/issues/issue_detail_spec.rb4
-rw-r--r--spec/features/issues/spam_issues_spec.rb10
-rw-r--r--spec/features/labels_hierarchy_spec.rb2
-rw-r--r--spec/frontend/security_configuration/components/training_provider_list_spec.js20
-rw-r--r--spec/frontend/vue_mr_widget/components/extensions/child_content_spec.js40
-rw-r--r--spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js6
-rw-r--r--spec/requests/api/topics_spec.rb39
-rw-r--r--vendor/assets/javascripts/vue-virtual-scroller/src/components/RecycleScroller.vue1
32 files changed, 468 insertions, 125 deletions
diff --git a/.rubocop_todo/graphql/field_definitions.yml b/.rubocop_todo/graphql/field_definitions.yml
index e1245a53409..0e2399ba243 100644
--- a/.rubocop_todo/graphql/field_definitions.yml
+++ b/.rubocop_todo/graphql/field_definitions.yml
@@ -1,9 +1,4 @@
---
GraphQL/FieldDefinitions:
Exclude:
- - ee/app/graphql/types/ci/code_quality_degradation_type.rb
- - ee/app/graphql/types/epic_type.rb
- - ee/app/graphql/types/group_release_stats_type.rb
- - ee/app/graphql/types/iteration_type.rb
- - ee/app/graphql/types/requirements_management/requirement_type.rb
- ee/app/graphql/types/vulnerability_type.rb
diff --git a/.rubocop_todo/graphql/ordered_arguments.yml b/.rubocop_todo/graphql/ordered_arguments.yml
index def1b0085e4..4b9cb0bdebd 100644
--- a/.rubocop_todo/graphql/ordered_arguments.yml
+++ b/.rubocop_todo/graphql/ordered_arguments.yml
@@ -4,10 +4,6 @@ GraphQL/OrderedArguments:
- app/graphql/resolvers/base_issues_resolver.rb
- app/graphql/resolvers/design_management/designs_resolver.rb
- app/graphql/resolvers/design_management/version/design_at_version_resolver.rb
- - app/graphql/types/commit_action_type.rb
- - app/graphql/types/diff_paths_input_type.rb
- - app/graphql/types/issues/negated_issue_filter_input_type.rb
- - app/graphql/types/jira_users_mapping_input_type.rb
- app/graphql/types/notes/diff_image_position_input_type.rb
- app/graphql/types/notes/diff_position_base_input_type.rb
- app/graphql/types/notes/diff_position_input_type.rb
diff --git a/app/assets/javascripts/issues/show/components/title.vue b/app/assets/javascripts/issues/show/components/title.vue
index 5e92211685a..1982147e454 100644
--- a/app/assets/javascripts/issues/show/components/title.vue
+++ b/app/assets/javascripts/issues/show/components/title.vue
@@ -68,7 +68,7 @@ export default {
<template>
<div class="title-container">
- <h2
+ <h1
v-safe-html="titleHtml"
:class="{
'issue-realtime-pre-pulse': preAnimation,
@@ -76,7 +76,7 @@ export default {
}"
class="title qa-title"
dir="auto"
- ></h2>
+ ></h1>
<gl-button
v-if="showInlineEditButton && canUpdate"
v-gl-tooltip.bottom
diff --git a/app/assets/javascripts/security_configuration/components/training_provider_list.vue b/app/assets/javascripts/security_configuration/components/training_provider_list.vue
index 539e2bff17c..dd470562f2d 100644
--- a/app/assets/javascripts/security_configuration/components/training_provider_list.vue
+++ b/app/assets/javascripts/security_configuration/components/training_provider_list.vue
@@ -6,6 +6,8 @@ import { __ } from '~/locale';
import {
TRACK_TOGGLE_TRAINING_PROVIDER_ACTION,
TRACK_TOGGLE_TRAINING_PROVIDER_LABEL,
+ TRACK_PROVIDER_LEARN_MORE_CLICK_ACTION,
+ TRACK_PROVIDER_LEARN_MORE_CLICK_LABEL,
} from '~/security_configuration/constants';
import dismissUserCalloutMutation from '~/graphql_shared/mutations/dismiss_user_callout.mutation.graphql';
import securityTrainingProvidersQuery from '../graphql/security_training_providers.query.graphql';
@@ -137,6 +139,12 @@ export default {
},
});
},
+ trackProviderLearnMoreClick(providerId) {
+ this.track(TRACK_PROVIDER_LEARN_MORE_CLICK_ACTION, {
+ label: TRACK_PROVIDER_LEARN_MORE_CLICK_LABEL,
+ property: providerId,
+ });
+ },
},
i18n,
};
@@ -172,7 +180,13 @@ export default {
<h3 class="gl-font-lg gl-m-0 gl-mb-2">{{ provider.name }}</h3>
<p>
{{ provider.description }}
- <gl-link :href="provider.url" target="_blank">{{ __('Learn more.') }}</gl-link>
+ <gl-link
+ :href="provider.url"
+ target="_blank"
+ @click="trackProviderLearnMoreClick(provider.id)"
+ >
+ {{ __('Learn more.') }}
+ </gl-link>
</p>
</div>
</div>
diff --git a/app/assets/javascripts/security_configuration/constants.js b/app/assets/javascripts/security_configuration/constants.js
index dc76436e91d..86cc3a9c2f9 100644
--- a/app/assets/javascripts/security_configuration/constants.js
+++ b/app/assets/javascripts/security_configuration/constants.js
@@ -1,2 +1,5 @@
export const TRACK_TOGGLE_TRAINING_PROVIDER_ACTION = 'toggle_security_training_provider';
export const TRACK_TOGGLE_TRAINING_PROVIDER_LABEL = 'update_security_training_provider';
+
+export const TRACK_PROVIDER_LEARN_MORE_CLICK_ACTION = 'click_link';
+export const TRACK_PROVIDER_LEARN_MORE_CLICK_LABEL = 'security_training_provider';
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
index a25b4ab54e5..4f27a3baca9 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/base.vue
@@ -2,8 +2,6 @@
import {
GlButton,
GlLoadingIcon,
- GlLink,
- GlBadge,
GlSafeHtmlDirective,
GlTooltipDirective,
GlIntersectionObserver,
@@ -17,6 +15,7 @@ import Poll from '~/lib/utils/poll';
import { EXTENSION_ICON_CLASS, EXTENSION_ICONS } from '../../constants';
import StatusIcon from './status_icon.vue';
import Actions from './actions.vue';
+import ChildContent from './child_content.vue';
import { generateText } from './utils';
export const LOADING_STATES = {
@@ -30,12 +29,11 @@ export default {
components: {
GlButton,
GlLoadingIcon,
- GlLink,
- GlBadge,
GlIntersectionObserver,
SmartVirtualList,
StatusIcon,
Actions,
+ ChildContent,
},
directives: {
SafeHtml: GlSafeHtmlDirective,
@@ -196,9 +194,6 @@ export default {
Sentry.captureException(e);
});
},
- isArray(arr) {
- return Array.isArray(arr);
- },
appear(index) {
if (index === this.fullData.length - 1) {
this.showFade = false;
@@ -299,60 +294,14 @@ export default {
class="gl-py-3 gl-pl-7"
data-testid="extension-list-item"
>
- <div class="gl-w-full">
- <div v-if="data.header" class="gl-mb-2">
- <template v-if="isArray(data.header)">
- <component
- :is="headerI === 0 ? 'strong' : 'span'"
- v-for="(header, headerI) in data.header"
- :key="headerI"
- v-safe-html="generateText(header)"
- class="gl-display-block"
- />
- </template>
- <strong v-else v-safe-html="generateText(data.header)"></strong>
- </div>
- <div class="gl-display-flex">
- <status-icon
- v-if="data.icon"
- :icon-name="data.icon.name"
- :size="12"
- class="gl-pl-0"
- />
- <gl-intersection-observer
- :options="{ rootMargin: '100px', thresholds: 0.1 }"
- class="gl-w-full"
- @appear="appear(index)"
- @disappear="disappear(index)"
- >
- <div class="gl-flex-wrap gl-display-flex gl-w-full">
- <div class="gl-mr-4 gl-display-flex gl-align-items-center">
- <p v-safe-html="generateText(data.text)" class="gl-m-0"></p>
- </div>
- <div v-if="data.link">
- <gl-link :href="data.link.href">{{ data.link.text }}</gl-link>
- </div>
- <div v-if="data.supportingText">
- <p v-safe-html="generateText(data.supportingText)" class="gl-m-0"></p>
- </div>
- <gl-badge v-if="data.badge" :variant="data.badge.variant || 'info'">
- {{ data.badge.text }}
- </gl-badge>
-
- <actions
- :widget="$options.label || $options.name"
- :tertiary-buttons="data.actions"
- class="gl-ml-auto"
- />
- </div>
- <p
- v-if="data.subtext"
- v-safe-html="generateText(data.subtext)"
- class="gl-m-0 gl-font-sm"
- ></p>
- </gl-intersection-observer>
- </div>
- </div>
+ <gl-intersection-observer
+ :options="{ rootMargin: '100px', thresholds: 0.1 }"
+ class="gl-w-full"
+ @appear="appear(index)"
+ @disappear="disappear(index)"
+ >
+ <child-content :data="data" :widget-label="widgetLabel" :level="2" />
+ </gl-intersection-observer>
</li>
</smart-virtual-list>
<div
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue b/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue
new file mode 100644
index 00000000000..5f42c6c7acb
--- /dev/null
+++ b/app/assets/javascripts/vue_merge_request_widget/components/extensions/child_content.vue
@@ -0,0 +1,95 @@
+<script>
+import { GlBadge, GlLink, GlSafeHtmlDirective } from '@gitlab/ui';
+import StatusIcon from './status_icon.vue';
+import Actions from './actions.vue';
+import { generateText } from './utils';
+
+export default {
+ name: 'ChildContent',
+ components: {
+ GlBadge,
+ GlLink,
+ StatusIcon,
+ Actions,
+ },
+ directives: {
+ SafeHtml: GlSafeHtmlDirective,
+ },
+ props: {
+ data: {
+ type: Object,
+ required: true,
+ },
+ widgetLabel: {
+ type: String,
+ required: true,
+ },
+ level: {
+ type: Number,
+ required: true,
+ },
+ },
+ methods: {
+ isArray(arr) {
+ return Array.isArray(arr);
+ },
+ generateText,
+ },
+};
+</script>
+
+<template>
+ <div :class="{ 'gl-pl-6': level === 3 }" class="gl-w-full">
+ <div v-if="data.header" class="gl-mb-2">
+ <template v-if="isArray(data.header)">
+ <component
+ :is="headerI === 0 ? 'strong' : 'span'"
+ v-for="(header, headerI) in data.header"
+ :key="headerI"
+ v-safe-html="generateText(header)"
+ class="gl-display-block"
+ />
+ </template>
+ <strong v-else v-safe-html="generateText(data.header)"></strong>
+ </div>
+ <div class="gl-display-flex">
+ <status-icon v-if="data.icon" :icon-name="data.icon.name" :size="12" class="gl-pl-0" />
+ <div class="gl-w-full">
+ <div class="gl-flex-wrap gl-display-flex gl-w-full">
+ <div class="gl-mr-4 gl-display-flex gl-align-items-center">
+ <p v-safe-html="generateText(data.text)" class="gl-m-0"></p>
+ </div>
+ <div v-if="data.link">
+ <gl-link :href="data.link.href">{{ data.link.text }}</gl-link>
+ </div>
+ <div v-if="data.supportingText">
+ <p v-safe-html="generateText(data.supportingText)" class="gl-m-0"></p>
+ </div>
+ <gl-badge v-if="data.badge" :variant="data.badge.variant || 'info'">
+ {{ data.badge.text }}
+ </gl-badge>
+ <actions :widget="widgetLabel" :tertiary-buttons="data.actions" class="gl-ml-auto" />
+ </div>
+ <p
+ v-if="data.subtext"
+ v-safe-html="generateText(data.subtext)"
+ class="gl-m-0 gl-font-sm"
+ ></p>
+ </div>
+ </div>
+ <template v-if="data.children && level === 2">
+ <ul class="gl-m-0 gl-p-0 gl-list-style-none">
+ <li>
+ <child-content
+ v-for="childData in data.children"
+ :key="childData.id"
+ :data="childData"
+ :widget-label="widgetLabel"
+ :level="3"
+ data-testid="child-content"
+ />
+ </li>
+ </ul>
+ </template>
+ </div>
+</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js b/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js
index 4aeebf095c4..e52f2c2c666 100644
--- a/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js
+++ b/app/assets/javascripts/vue_merge_request_widget/extensions/issues.js
@@ -88,6 +88,16 @@ export default {
// text: 'Link text', // Required: Text to be used inside the link
// },
actions: [{ text: 'Full report', href: 'https://gitlab.com', target: '_blank' }],
+ children: [
+ {
+ id: `child-${issue.id}`,
+ header: 'New',
+ text: '%{critical_start}1 Critical%{critical_end}',
+ icon: {
+ name: EXTENSION_ICONS.error,
+ },
+ },
+ ],
}));
});
},
diff --git a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue
index b96ce0c43f7..45941174a62 100644
--- a/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue
+++ b/app/assets/javascripts/vue_shared/issuable/show/components/issuable_title.vue
@@ -58,7 +58,12 @@ export default {
<template>
<div>
<div class="title-container">
- <h2 v-safe-html="issuable.titleHtml || issuable.title" class="title qa-title" dir="auto"></h2>
+ <h1
+ v-safe-html="issuable.titleHtml || issuable.title"
+ class="title qa-title"
+ dir="auto"
+ data-testid="title"
+ ></h1>
<gl-button
v-if="enableEdit"
v-gl-tooltip.bottom
diff --git a/app/graphql/types/commit_action_type.rb b/app/graphql/types/commit_action_type.rb
index 6f6d6a418dc..1aa3a4e7ee1 100644
--- a/app/graphql/types/commit_action_type.rb
+++ b/app/graphql/types/commit_action_type.rb
@@ -4,17 +4,17 @@ module Types
class CommitActionType < BaseInputObject
argument :action, type: Types::CommitActionModeEnum, required: true,
description: 'Action to perform: create, delete, move, update, or chmod.'
- argument :file_path, type: GraphQL::Types::String, required: true,
- description: 'Full path to the file.'
argument :content, type: GraphQL::Types::String, required: false,
description: 'Content of the file.'
- argument :previous_path, type: GraphQL::Types::String, required: false,
- description: 'Original full path to the file being moved.'
- argument :last_commit_id, type: GraphQL::Types::String, required: false,
- description: 'Last known file commit ID.'
- argument :execute_filemode, type: GraphQL::Types::Boolean, required: false,
- description: 'Enables/disables the execute flag on the file.'
argument :encoding, type: Types::CommitEncodingEnum, required: false,
description: 'Encoding of the file. Default is text.'
+ argument :execute_filemode, type: GraphQL::Types::Boolean, required: false,
+ description: 'Enables/disables the execute flag on the file.'
+ argument :file_path, type: GraphQL::Types::String, required: true,
+ description: 'Full path to the file.'
+ argument :last_commit_id, type: GraphQL::Types::String, required: false,
+ description: 'Last known file commit ID.'
+ argument :previous_path, type: GraphQL::Types::String, required: false,
+ description: 'Original full path to the file being moved.'
end
end
diff --git a/app/graphql/types/diff_paths_input_type.rb b/app/graphql/types/diff_paths_input_type.rb
index cdcff1a7e34..c5c75105fda 100644
--- a/app/graphql/types/diff_paths_input_type.rb
+++ b/app/graphql/types/diff_paths_input_type.rb
@@ -2,9 +2,9 @@
module Types
class DiffPathsInputType < BaseInputObject
- argument :old_path, GraphQL::Types::String, required: false,
- description: 'Path of the file on the start SHA.'
argument :new_path, GraphQL::Types::String, required: false,
description: 'Path of the file on the HEAD SHA.'
+ argument :old_path, GraphQL::Types::String, required: false,
+ description: 'Path of the file on the start SHA.'
end
end
diff --git a/app/graphql/types/issues/negated_issue_filter_input_type.rb b/app/graphql/types/issues/negated_issue_filter_input_type.rb
index 73e090a4802..fc39efd2493 100644
--- a/app/graphql/types/issues/negated_issue_filter_input_type.rb
+++ b/app/graphql/types/issues/negated_issue_filter_input_type.rb
@@ -5,6 +5,15 @@ module Types
class NegatedIssueFilterInputType < BaseInputObject
graphql_name 'NegatedIssueFilterInput'
+ argument :assignee_id, GraphQL::Types::String,
+ required: false,
+ description: 'ID of a user not assigned to the issues.'
+ argument :assignee_usernames, [GraphQL::Types::String],
+ required: false,
+ description: 'Usernames of users not assigned to the issue.'
+ argument :author_username, GraphQL::Types::String,
+ required: false,
+ description: "Username of a user who didn't author the issue."
argument :iids, [GraphQL::Types::String],
required: false,
description: 'List of IIDs of issues to exclude. For example, `[1, 2]`.'
@@ -14,24 +23,15 @@ module Types
argument :milestone_title, [GraphQL::Types::String],
required: false,
description: 'Milestone not applied to this issue.'
- argument :release_tag, [GraphQL::Types::String],
- required: false,
- description: "Release tag not associated with the issue's milestone. Ignored when parent is a group."
- argument :author_username, GraphQL::Types::String,
- required: false,
- description: "Username of a user who didn't author the issue."
- argument :assignee_usernames, [GraphQL::Types::String],
- required: false,
- description: 'Usernames of users not assigned to the issue.'
- argument :assignee_id, GraphQL::Types::String,
- required: false,
- description: 'ID of a user not assigned to the issues.'
argument :milestone_wildcard_id, ::Types::NegatedMilestoneWildcardIdEnum,
required: false,
description: 'Filter by negated milestone wildcard values.'
argument :my_reaction_emoji, GraphQL::Types::String,
required: false,
description: 'Filter by reaction emoji applied by the current user.'
+ argument :release_tag, [GraphQL::Types::String],
+ required: false,
+ description: "Release tag not associated with the issue's milestone. Ignored when parent is a group."
argument :types, [Types::IssueTypeEnum],
as: :issue_types,
description: 'Filters out issues by the given issue types.',
diff --git a/app/graphql/types/jira_users_mapping_input_type.rb b/app/graphql/types/jira_users_mapping_input_type.rb
index 37fd05370c0..4df2e27b45a 100644
--- a/app/graphql/types/jira_users_mapping_input_type.rb
+++ b/app/graphql/types/jira_users_mapping_input_type.rb
@@ -4,13 +4,13 @@ module Types
class JiraUsersMappingInputType < BaseInputObject
graphql_name 'JiraUsersMappingInputType'
- argument :jira_account_id,
- GraphQL::Types::String,
- required: true,
- description: 'Jira account ID of the user.'
argument :gitlab_id,
GraphQL::Types::Int,
required: false,
description: 'ID of the GitLab user.'
+ argument :jira_account_id,
+ GraphQL::Types::String,
+ required: true,
+ description: 'Jira account ID of the user.'
end
end
diff --git a/app/views/shared/issue_type/_details_content.html.haml b/app/views/shared/issue_type/_details_content.html.haml
index e5197acf06f..1babc6885c2 100644
--- a/app/views/shared/issue_type/_details_content.html.haml
+++ b/app/views/shared/issue_type/_details_content.html.haml
@@ -5,7 +5,7 @@
.detail-page-description.content-block
#js-issuable-app{ data: { initial: issuable_initial_data(issuable).to_json, full_path: @project.full_path } }
.title-container
- %h2.title= markdown_field(issuable, :title)
+ %h1.title= markdown_field(issuable, :title)
- if issuable.description.present?
.description
.md= markdown_field(issuable, :description)
diff --git a/db/fixtures/development/33_triage_ops.rb b/db/fixtures/development/33_triage_ops.rb
new file mode 100644
index 00000000000..38e0d4a6371
--- /dev/null
+++ b/db/fixtures/development/33_triage_ops.rb
@@ -0,0 +1,139 @@
+# frozen_string_literal: true
+
+require './spec/support/sidekiq_middleware'
+require './spec/support/helpers/test_env'
+
+class Gitlab::Seeder::TriageOps
+ WEBHOOK_URL = 'http://0.0.0.0:8080'
+ WEBHOOK_TOKEN = "triage-ops-webhook-token"
+
+ def seed!
+ puts "Updating settings to allow web hooks to localhost"
+ ApplicationSetting.current_without_cache.update!(allow_local_requests_from_web_hooks_and_services: true)
+
+ Sidekiq::Testing.inline! do
+ puts "Ensuring required groups"
+ ensure_group('gitlab-com')
+ ensure_group('gitlab-jh/jh-team')
+ ensure_group('gitlab-org')
+ ensure_group('gitlab-org/gitlab-core-team/community-members')
+ ensure_group('gitlab-org/security')
+ puts "Ensuring required projects"
+ ensure_project('gitlab-org/gitlab')
+ ensure_project('gitlab-org/security/gitlab')
+ puts "Ensuring required bot user"
+ ensure_bot_user
+ puts "Setting up webhooks for #{WEBHOOK_URL}"
+ ensure_webhook_for('gitlab-com')
+ ensure_webhook_for('gitlab-org')
+ end
+ end
+
+ private
+
+ def ensure_bot_user
+ bot = User.find_by_username('triagebot')
+ bot ||= User.create!(
+ username: 'triagebot',
+ name: 'Triage Bot',
+ email: 'triagebot@example.com',
+ confirmed_at: DateTime.now,
+ password: SecureRandom.hex.slice(0, 16)
+ )
+
+ ensure_group('gitlab-org').add_maintainer(bot)
+ ensure_group('gitlab-com').add_maintainer(bot)
+
+ params = {
+ scopes: ['api'],
+ name: "API Token #{Time.zone.now}"
+ }
+ response = PersonalAccessTokens::CreateService.new(current_user: bot, target_user: bot, params: params).execute
+
+ unless response.success?
+ raise "Can't create Triage Bot access token: #{response.message}"
+ end
+
+ puts "Bot with API_TOKEN=#{response[:personal_access_token].token} is present now."
+
+ bot
+ end
+
+ def ensure_webhook_for(group_path)
+ group = Group.find_by_full_path(group_path)
+
+ hook_params = {
+ enable_ssl_verification: false,
+ token: WEBHOOK_TOKEN,
+ url: WEBHOOK_URL
+ }
+ # Subscribe the hook to all possible events.
+ all_group_hook_events = GroupHook.triggers.values
+ all_group_hook_events.each { |value| hook_params[value] = true }
+
+ group.hooks.delete_all
+
+ hook = group.hooks.new(hook_params)
+ hook.save!
+
+ puts "Hook token '#{hook.token}' for '#{group_path}' group is present now."
+ end
+
+ def ensure_group(full_path)
+ group = Group.find_by_full_path(full_path)
+
+ return group if group
+
+ parent_path = full_path.split('/')[0..-2].join('/')
+ parent = ensure_group(parent_path) if parent_path.present?
+
+ group_path = full_path.split('/').last
+
+ group = Group.new(
+ name: group_path.titleize,
+ path: group_path,
+ parent_id: parent&.id
+ )
+ group.description = FFaker::Lorem.sentence
+ group.save!
+
+ group.add_owner(User.first)
+ group.create_namespace_settings
+
+ group
+ end
+
+ def ensure_project(project_fullpath)
+ project = Project.find_by_full_path(project_fullpath)
+
+ return project if project
+
+ group_path = project_fullpath.split('/')[0..-2].join('/')
+ project_path = project_fullpath.split('/').last
+
+ group = ensure_group(group_path)
+
+ params = {
+ namespace_id: group.id,
+ name: project_path.titleize,
+ path: project_path,
+ description: FFaker::Lorem.sentence,
+ visibility_level: Gitlab::VisibilityLevel::PRIVATE,
+ skip_disk_validation: true
+ }
+
+ project = ::Projects::CreateService.new(User.first, params).execute
+
+ raise "Can't create project '#{project_fullpath}' : #{project.errors.full_messages}" unless project.persisted?
+
+ project
+ end
+end
+
+if ENV['SEED_TRIAGE_OPS']
+ Gitlab::Seeder.quiet do
+ Gitlab::Seeder::TriageOps.new.seed!
+ end
+else
+ puts "Skipped. Use the `SEED_TRIAGE_OPS` environment variable to enable seeding data for triage ops project."
+end
diff --git a/doc/api/topics.md b/doc/api/topics.md
index 538b9af9374..1246e36f2cb 100644
--- a/doc/api/topics.md
+++ b/doc/api/topics.md
@@ -203,3 +203,28 @@ curl --request PUT \
--header "PRIVATE-TOKEN: <your_access_token>" \
"https://gitlab.example.com/api/v4/topics/1"
```
+
+## Delete a project topic
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/80725) in GitLab 14.9.
+
+You must be an administrator to delete a project.
+When you delete a project topic, you also delete the topic assignment for projects.
+
+```plaintext
+DELETE /topics/:id
+```
+
+Supported attributes:
+
+| Attribute | Type | Required | Description |
+| ------------- | ------- | ---------------------- | ------------------- |
+| `id` | integer | **{check-circle}** Yes | ID of project topic |
+
+Example request:
+
+```shell
+curl --request DELETE \
+ --header "PRIVATE-TOKEN: <your_access_token>" \
+ "https://gitlab.example.com/api/v4/topics/1"
+```
diff --git a/doc/development/contributing/merge_request_workflow.md b/doc/development/contributing/merge_request_workflow.md
index 47c89f41656..6798d02f046 100644
--- a/doc/development/contributing/merge_request_workflow.md
+++ b/doc/development/contributing/merge_request_workflow.md
@@ -264,6 +264,8 @@ requirements.
1. Peer member testing is optional but recommended when the risk of a change is high. This includes when the changes are [far-reaching](https://about.gitlab.com/handbook/engineering/development/#reducing-the-impact-of-far-reaching-work) or are for [components critical for security](../code_review.md#security).
1. Regressions and bugs are covered with tests that reduce the risk of the issue happening
again.
+1. Code affected by a feature flag is covered by [automated tests with the feature flag enabled and disabled](../feature_flags/index.md#feature-flags-in-tests), or both
+ states are tested as part of peer member testing or as part of the rollout plan.
1. [Performance guidelines](../merge_request_performance_guidelines.md) have been followed.
1. [Secure coding guidelines](https://gitlab.com/gitlab-com/gl-security/security-guidelines) have been followed.
1. [Application and rate limit guidelines](../merge_request_application_and_rate_limit_guidelines.md) have been followed.
diff --git a/doc/development/feature_flags/index.md b/doc/development/feature_flags/index.md
index af402713f6e..4b417b26381 100644
--- a/doc/development/feature_flags/index.md
+++ b/doc/development/feature_flags/index.md
@@ -530,8 +530,9 @@ Feature.remove(:feature_flag_name)
## Feature flags in tests
Introducing a feature flag into the codebase creates an additional code path that should be tested.
-It is strongly advised to test all code affected by a feature flag, both when **enabled** and **disabled**
-to ensure the feature works properly.
+It is strongly advised to include automated tests for all code affected by a feature flag, both when **enabled** and **disabled**
+to ensure the feature works properly. If automated tests are not included for both states, the functionality associated
+with the untested code path should be manually tested before deployment to production.
When using the testing environment, all feature flags are enabled by default.
diff --git a/doc/development/new_fe_guide/modules/widget_extensions.md b/doc/development/new_fe_guide/modules/widget_extensions.md
index 37712cb2cec..d3cd839464d 100644
--- a/doc/development/new_fe_guide/modules/widget_extensions.md
+++ b/doc/development/new_fe_guide/modules/widget_extensions.md
@@ -128,6 +128,7 @@ mentioned below:
variant: '', // Optional: GitLab UI badge variant, defaults to info
},
actions: [], // Optional: Action button for row
+ children: [], // Optional: Child content to render, structure matches the same structure
}
```
diff --git a/doc/user/application_security/secret_detection/index.md b/doc/user/application_security/secret_detection/index.md
index 2ce2d59898f..336d1027656 100644
--- a/doc/user/application_security/secret_detection/index.md
+++ b/doc/user/application_security/secret_detection/index.md
@@ -328,14 +328,6 @@ as part of your normal job definition.
A new configuration variable ([`SECRET_DETECTION_HISTORIC_SCAN`](#available-cicd-variables))
can be set to change the behavior of the GitLab Secret Detection scan to run on the entire Git history of a repository.
-We have created a [short video walkthrough](https://youtu.be/wDtc_K00Y0A) showcasing how you can perform a full history secret detection scan.
-<div class="video-fallback">
- See the video: <a href="https://www.youtube.com/watch?v=wDtc_K00Y0A">Walkthrough of historical secret detection scan</a>.
-</div>
-<figure class="video-container">
- <iframe src="https://www.youtube.com/embed/wDtc_K00Y0A" frameborder="0" allowfullscreen="true"> </iframe>
-</figure>
-
## Running Secret Detection in an offline environment
For self-managed GitLab instances in an environment with limited, restricted, or intermittent access
diff --git a/doc/user/usage_quotas.md b/doc/user/usage_quotas.md
index 7fe889b9953..67f7f7f3db5 100644
--- a/doc/user/usage_quotas.md
+++ b/doc/user/usage_quotas.md
@@ -11,7 +11,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - Moved to GitLab Free.
NOTE:
-Free tier namespaces on GitLab SaaS have a 5GB storage limit. To learn more, visit our [pricing page](https://about.gitlab.com/pricing/).
+Free tier namespaces on GitLab SaaS have a 5GB storage limit. This limit is not visible on the storage quota page nor currently enforced for users who exceed the limit. To learn more, visit our [pricing page](https://about.gitlab.com/pricing/).
A project's repository has a free storage quota of 10 GB. When a project's repository reaches
the quota it is locked. You cannot push changes to a locked project. To monitor the size of each
diff --git a/lib/api/topics.rb b/lib/api/topics.rb
index b9c2bcc2da8..e4a1fa2367e 100644
--- a/lib/api/topics.rb
+++ b/lib/api/topics.rb
@@ -77,5 +77,19 @@ module API
render_validation_error!(topic)
end
end
+
+ desc 'Delete a topic' do
+ detail 'This feature was introduced in GitLab 14.9.'
+ end
+ params do
+ requires :id, type: Integer, desc: 'ID of project topic'
+ end
+ delete 'topics/:id' do
+ authenticated_as_admin!
+
+ topic = ::Projects::Topic.find(params[:id])
+
+ destroy_conditionally!(topic)
+ end
end
end
diff --git a/spec/features/incidents/incident_details_spec.rb b/spec/features/incidents/incident_details_spec.rb
index b704a0515c8..dbcd524cad0 100644
--- a/spec/features/incidents/incident_details_spec.rb
+++ b/spec/features/incidents/incident_details_spec.rb
@@ -21,7 +21,7 @@ RSpec.describe 'Incident details', :js do
context 'when a developer+ displays the incident' do
it 'shows the incident' do
page.within('.issuable-details') do
- expect(find('h2')).to have_content(incident.title)
+ expect(find('h1')).to have_content(incident.title)
end
end
@@ -33,7 +33,7 @@ RSpec.describe 'Incident details', :js do
page.within('.issuable-details') do
incident_tabs = find('[data-testid="incident-tabs"]')
- expect(find('h2')).to have_content(incident.title)
+ expect(find('h1')).to have_content(incident.title)
expect(incident_tabs).to have_content('Summary')
expect(incident_tabs).to have_content(incident.description)
end
diff --git a/spec/features/issues/incident_issue_spec.rb b/spec/features/issues/incident_issue_spec.rb
index 3033a138551..2956ddede2e 100644
--- a/spec/features/issues/incident_issue_spec.rb
+++ b/spec/features/issues/incident_issue_spec.rb
@@ -38,7 +38,7 @@ RSpec.describe 'Incident Detail', :js do
incident_tabs = find('[data-testid="incident-tabs"]')
aggregate_failures 'shows title and Summary tab' do
- expect(find('h2')).to have_content(incident.title)
+ expect(find('h1')).to have_content(incident.title)
expect(incident_tabs).to have_content('Summary')
expect(incident_tabs).to have_content(incident.description)
end
diff --git a/spec/features/issues/issue_detail_spec.rb b/spec/features/issues/issue_detail_spec.rb
index b37c8e9d1cf..88709d66887 100644
--- a/spec/features/issues/issue_detail_spec.rb
+++ b/spec/features/issues/issue_detail_spec.rb
@@ -17,7 +17,7 @@ RSpec.describe 'Issue Detail', :js do
it 'shows the issue' do
page.within('.issuable-details') do
- expect(find('h2')).to have_content(issue.title)
+ expect(find('h1')).to have_content(issue.title)
end
end
end
@@ -85,7 +85,7 @@ RSpec.describe 'Issue Detail', :js do
it 'shows the issue' do
page.within('.issuable-details') do
- expect(find('h2')).to have_content(issue.reload.title)
+ expect(find('h1')).to have_content(issue.reload.title)
end
end
end
diff --git a/spec/features/issues/spam_issues_spec.rb b/spec/features/issues/spam_issues_spec.rb
index 70d7deadec3..de9888295b9 100644
--- a/spec/features/issues/spam_issues_spec.rb
+++ b/spec/features/issues/spam_issues_spec.rb
@@ -66,7 +66,7 @@ RSpec.describe 'New issue', :js do
it 'allows issue creation' do
click_button 'Create issue'
- expect(page.find('.issue-details h2.title')).to have_content('issue title')
+ expect(page.find('.issue-details h1.title')).to have_content('issue title')
expect(page.find('.issue-details .description')).to have_content('issue description')
end
@@ -111,7 +111,7 @@ RSpec.describe 'New issue', :js do
click_button 'Create issue'
- expect(page.find('.issue-details h2.title')).to have_content('issue title')
+ expect(page.find('.issue-details h1.title')).to have_content('issue title')
expect(page.find('.issue-details .description')).to have_content('issue description')
end
end
@@ -126,7 +126,7 @@ RSpec.describe 'New issue', :js do
click_button 'Create issue'
expect(page).not_to have_css('.recaptcha')
- expect(page.find('.issue-details h2.title')).to have_content('issue title')
+ expect(page.find('.issue-details h1.title')).to have_content('issue title')
expect(page.find('.issue-details .description')).to have_content('issue description')
end
@@ -152,7 +152,7 @@ RSpec.describe 'New issue', :js do
click_button 'Create issue'
expect(page).not_to have_css('.recaptcha')
- expect(page.find('.issue-details h2.title')).to have_content('issue title')
+ expect(page.find('.issue-details h1.title')).to have_content('issue title')
expect(page.find('.issue-details .description')).to have_content('issue description')
end
@@ -181,7 +181,7 @@ RSpec.describe 'New issue', :js do
click_button 'Create issue'
- expect(page.find('.issue-details h2.title')).to have_content('issue title')
+ expect(page.find('.issue-details h1.title')).to have_content('issue title')
expect(page.find('.issue-details .description')).to have_content('issue description')
end
end
diff --git a/spec/features/labels_hierarchy_spec.rb b/spec/features/labels_hierarchy_spec.rb
index a064eef5cc8..479199b72b7 100644
--- a/spec/features/labels_hierarchy_spec.rb
+++ b/spec/features/labels_hierarchy_spec.rb
@@ -160,7 +160,7 @@ RSpec.describe 'Labels Hierarchy', :js do
find('.btn-confirm').click
- expect(page.find('.issue-details h2.title')).to have_content('new created issue')
+ expect(page.find('.issue-details h1.title')).to have_content('new created issue')
expect(page).to have_selector('span.gl-label-text', text: grandparent_group_label.title)
expect(page).to have_selector('span.gl-label-text', text: parent_group_label.title)
expect(page).to have_selector('span.gl-label-text', text: project_label_1.title)
diff --git a/spec/frontend/security_configuration/components/training_provider_list_spec.js b/spec/frontend/security_configuration/components/training_provider_list_spec.js
index 18c9ada6bde..4d92f0cca22 100644
--- a/spec/frontend/security_configuration/components/training_provider_list_spec.js
+++ b/spec/frontend/security_configuration/components/training_provider_list_spec.js
@@ -8,6 +8,8 @@ import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import {
TRACK_TOGGLE_TRAINING_PROVIDER_ACTION,
TRACK_TOGGLE_TRAINING_PROVIDER_LABEL,
+ TRACK_PROVIDER_LEARN_MORE_CLICK_ACTION,
+ TRACK_PROVIDER_LEARN_MORE_CLICK_LABEL,
} from '~/security_configuration/constants';
import TrainingProviderList from '~/security_configuration/components/training_provider_list.vue';
import securityTrainingProvidersQuery from '~/security_configuration/graphql/security_training_providers.query.graphql';
@@ -244,6 +246,24 @@ describe('TrainingProviderList component', () => {
},
});
});
+
+ it(`tracks when a provider's "Learn more" link is clicked`, () => {
+ const firstProviderLink = findLinks().at(0);
+ const [{ id: firstProviderId }] = securityTrainingProviders;
+
+ expect(trackingSpy).not.toHaveBeenCalled();
+
+ firstProviderLink.vm.$emit('click');
+
+ expect(trackingSpy).toHaveBeenCalledWith(
+ undefined,
+ TRACK_PROVIDER_LEARN_MORE_CLICK_ACTION,
+ {
+ label: TRACK_PROVIDER_LEARN_MORE_CLICK_LABEL,
+ property: firstProviderId,
+ },
+ );
+ });
});
});
diff --git a/spec/frontend/vue_mr_widget/components/extensions/child_content_spec.js b/spec/frontend/vue_mr_widget/components/extensions/child_content_spec.js
new file mode 100644
index 00000000000..198a4c2823a
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/components/extensions/child_content_spec.js
@@ -0,0 +1,40 @@
+import { shallowMount } from '@vue/test-utils';
+import ChildContent from '~/vue_merge_request_widget/components/extensions/child_content.vue';
+
+let wrapper;
+const mockData = () => ({
+ header: 'Test header',
+ text: 'Test content',
+ icon: {
+ name: 'error',
+ },
+});
+
+function factory(propsData) {
+ wrapper = shallowMount(ChildContent, {
+ propsData: {
+ ...propsData,
+ widgetLabel: 'Test',
+ },
+ });
+}
+
+describe('MR widget extension child content', () => {
+ afterEach(() => {
+ wrapper.destroy();
+ wrapper = null;
+ });
+
+ it('renders child components', () => {
+ factory({
+ data: {
+ ...mockData(),
+ children: [mockData()],
+ },
+ level: 2,
+ });
+
+ expect(wrapper.find('[data-testid="child-content"]').exists()).toBe(true);
+ expect(wrapper.find('[data-testid="child-content"]').props('level')).toBe(3);
+ });
+});
diff --git a/spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js b/spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js
index 93de6dbe306..11e3302d409 100644
--- a/spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js
+++ b/spec/frontend/vue_shared/issuable/show/components/issuable_title_spec.js
@@ -66,10 +66,12 @@ describe('IssuableTitle', () => {
});
await nextTick();
- const titleEl = wrapperWithTitle.find('h2');
+ const titleEl = wrapperWithTitle.find('[data-testid="title"]');
expect(titleEl.exists()).toBe(true);
- expect(titleEl.html()).toBe('<h2 dir="auto" class="title qa-title"><b>Sample</b> title</h2>');
+ expect(titleEl.html()).toBe(
+ '<h1 dir="auto" data-testid="title" class="title qa-title"><b>Sample</b> title</h1>',
+ );
wrapperWithTitle.destroy();
});
diff --git a/spec/requests/api/topics_spec.rb b/spec/requests/api/topics_spec.rb
index fd9f306128f..5c17ca9581e 100644
--- a/spec/requests/api/topics_spec.rb
+++ b/spec/requests/api/topics_spec.rb
@@ -255,4 +255,43 @@ RSpec.describe API::Topics do
end
end
end
+
+ describe 'DELETE /topics', :aggregate_failures do
+ context 'as administrator' do
+ it 'deletes a topic' do
+ delete api("/topics/#{topic_3.id}", admin), params: { name: 'my-topic' }
+
+ expect(response).to have_gitlab_http_status(:no_content)
+ end
+
+ it 'returns 404 for non existing id' do
+ delete api("/topics/#{non_existing_record_id}", admin), params: { name: 'my-topic' }
+
+ expect(response).to have_gitlab_http_status(:not_found)
+ end
+
+ it 'returns 400 for invalid `id` parameter' do
+ delete api('/topics/invalid', admin), params: { name: 'my-topic' }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['error']).to eql('id is invalid')
+ end
+ end
+
+ context 'as normal user' do
+ it 'returns 403 Forbidden' do
+ delete api("/topics/#{topic_3.id}", user), params: { name: 'my-topic' }
+
+ expect(response).to have_gitlab_http_status(:forbidden)
+ end
+ end
+
+ context 'as anonymous' do
+ it 'returns 401 Unauthorized' do
+ delete api("/topics/#{topic_3.id}"), params: { name: 'my-topic' }
+
+ expect(response).to have_gitlab_http_status(:unauthorized)
+ end
+ end
+ end
end
diff --git a/vendor/assets/javascripts/vue-virtual-scroller/src/components/RecycleScroller.vue b/vendor/assets/javascripts/vue-virtual-scroller/src/components/RecycleScroller.vue
index 3ce35999d8a..d18c5f3ee97 100644
--- a/vendor/assets/javascripts/vue-virtual-scroller/src/components/RecycleScroller.vue
+++ b/vendor/assets/javascripts/vue-virtual-scroller/src/components/RecycleScroller.vue
@@ -59,6 +59,7 @@
<script>
import { ResizeObserver } from 'vue-resize'
+import 'vue-resize/dist/vue-resize.css'
import { ObserveVisibility } from 'vue-observe-visibility'
import ScrollParent from 'scrollparent'
import config from '../config'