summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab/ci/notify.gitlab-ci.yml2
-rw-r--r--.gitlab/ci/rules.gitlab-ci.yml1
-rw-r--r--.gitlab/issue_templates/Security developer workflow.md2
-rw-r--r--app/assets/javascripts/boards/components/modal/empty_state.vue23
-rw-r--r--app/assets/javascripts/groups/components/invite_members_banner.vue34
-rw-r--r--app/assets/javascripts/groups/init_invite_members_banner.js3
-rw-r--r--app/assets/javascripts/notes/components/note_actions.vue61
-rw-r--r--app/assets/javascripts/notes/components/noteable_note.vue4
-rw-r--r--app/assets/javascripts/vue_shared/components/markdown/header.vue6
-rw-r--r--app/controllers/concerns/renders_notes.rb10
-rw-r--r--app/helpers/issuables_helper.rb6
-rw-r--r--app/helpers/notes_helper.rb4
-rw-r--r--app/models/group.rb13
-rw-r--r--app/models/merge_request.rb2
-rw-r--r--app/models/note.rb39
-rw-r--r--app/models/project_team.rb34
-rw-r--r--app/serializers/note_entity.rb4
-rw-r--r--app/serializers/project_note_entity.rb8
-rw-r--r--app/services/projects/create_service.rb9
-rw-r--r--app/views/groups/show.html.haml1
-rw-r--r--app/views/projects/notes/_actions.html.haml13
-rw-r--r--app/views/shared/blob/_markdown_buttons.html.haml6
-rw-r--r--changelogs/unreleased/238165-fix_project_create_authorization_for_admin.yml5
-rw-r--r--changelogs/unreleased/display-contributor-and-author-badges-on-notes.yml5
-rw-r--r--changelogs/unreleased/nfriend-only-apply-command-markdown-shortcuts-on-mac.yml5
-rw-r--r--config/feature_flags/development/show_author_on_note.yml7
-rw-r--r--config/feature_flags/development/show_contributor_on_note.yml7
-rw-r--r--doc/README.md4
-rw-r--r--doc/api/api_resources.md2
-rw-r--r--doc/api/epics.md6
-rw-r--r--doc/api/issues.md6
-rw-r--r--doc/api/merge_requests.md6
-rw-r--r--doc/api/todos.md22
-rw-r--r--doc/api/v3_to_v4.md4
-rw-r--r--doc/ci/variables/README.md1
-rw-r--r--doc/development/api_styleguide.md4
-rw-r--r--doc/development/documentation/feature_flags.md2
-rw-r--r--doc/development/documentation/styleguide.md1
-rw-r--r--doc/development/testing_guide/best_practices.md4
-rw-r--r--doc/operations/incident_management/alert_details.md18
-rw-r--r--doc/university/README.md2
-rw-r--r--doc/user/admin_area/settings/external_authorization.md10
-rw-r--r--doc/user/application_security/threat_monitoring/index.md49
-rw-r--r--doc/user/group/saml_sso/index.md2
-rw-r--r--doc/user/index.md6
-rw-r--r--doc/user/profile/notifications.md2
-rw-r--r--doc/user/profile/preferences.md2
-rw-r--r--doc/user/project/issues/due_dates.md4
-rw-r--r--doc/user/project/issues/issue_data_and_actions.md2
-rw-r--r--doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md7
-rw-r--r--doc/user/todos.md170
-rw-r--r--lib/api/sidekiq_metrics.rb2
-rw-r--r--locale/gitlab.pot15
-rw-r--r--spec/features/markdown/keyboard_shortcuts_spec.rb38
-rw-r--r--spec/fixtures/api/schemas/entities/discussion.json3
-rw-r--r--spec/frontend/groups/components/invite_members_banner_spec.js46
-rw-r--r--spec/frontend/notes/components/note_actions_spec.js34
-rw-r--r--spec/frontend/shortcuts_spec.js6
-rw-r--r--spec/models/group_spec.rb13
-rw-r--r--spec/models/note_spec.rb66
-rw-r--r--spec/models/project_team_spec.rb95
-rw-r--r--spec/requests/api/projects_spec.rb19
62 files changed, 739 insertions, 248 deletions
diff --git a/.gitlab/ci/notify.gitlab-ci.yml b/.gitlab/ci/notify.gitlab-ci.yml
index fcdd5ee97d2..6dcf19da942 100644
--- a/.gitlab/ci/notify.gitlab-ci.yml
+++ b/.gitlab/ci/notify.gitlab-ci.yml
@@ -10,7 +10,7 @@ notify-update-gitaly:
extends:
- .notify-slack
rules:
- - if: '$CI_MERGE_REQUEST_IID && $CI_COMMIT_BRANCH == $GITALY_UPDATE_BRANCH'
+ - if: '$CI_MERGE_REQUEST_SOURCE_BRANCH_NAME == $GITALY_UPDATE_BRANCH'
when: on_failure
allow_failure: true
variables:
diff --git a/.gitlab/ci/rules.gitlab-ci.yml b/.gitlab/ci/rules.gitlab-ci.yml
index f6e606e0534..a8e0e1ccaaa 100644
--- a/.gitlab/ci/rules.gitlab-ci.yml
+++ b/.gitlab/ci/rules.gitlab-ci.yml
@@ -128,6 +128,7 @@
- "{,ee/}spec/**/*.rb"
- ".gitlab-ci.yml"
- ".gitlab/ci/**/*"
+ - "*_VERSION"
.db-patterns: &db-patterns
- "{,ee/}{,spec/}{db,migrations}/**/*"
diff --git a/.gitlab/issue_templates/Security developer workflow.md b/.gitlab/issue_templates/Security developer workflow.md
index d21da6a161b..840ef4c6337 100644
--- a/.gitlab/issue_templates/Security developer workflow.md
+++ b/.gitlab/issue_templates/Security developer workflow.md
@@ -27,7 +27,7 @@ After your merge request has been approved according to our [approval guidelines
* At this point, it might be easy to squash the commits from the MR into one
* You can use the script `bin/secpick` instead of the following steps, to help you cherry-picking. See the [secpick documentation]
- [ ] Create each MR targeting the stable branch `X-Y-stable`, using the [Security Release merge request template].
- * Every merge request will have its own set of TODOs, so make sure to complete those.
+ * Every merge request will have its own set of to-dos, so make sure to complete those.
- [ ] On the "Related merge requests" section, ensure that `4` merge requests are associated: The one targeting `master` and the `3` backports.
- [ ] If this issue requires less than `4` merge requests, post a message on the Security Release Tracking Issue and ping the Release Managers.
diff --git a/app/assets/javascripts/boards/components/modal/empty_state.vue b/app/assets/javascripts/boards/components/modal/empty_state.vue
index 8d80319634d..cd4512f320f 100644
--- a/app/assets/javascripts/boards/components/modal/empty_state.vue
+++ b/app/assets/javascripts/boards/components/modal/empty_state.vue
@@ -1,10 +1,14 @@
<script>
/* eslint-disable vue/no-v-html */
+import { GlButton } from '@gitlab/ui';
import { __, sprintf } from '~/locale';
import ModalStore from '../../stores/modal_store';
import modalMixin from '../../mixins/modal_mixins';
export default {
+ components: {
+ GlButton,
+ },
mixins: [modalMixin],
props: {
newIssuePath: {
@@ -54,17 +58,22 @@ export default {
<div class="text-content">
<h4>{{ contents.title }}</h4>
<p v-html="contents.content"></p>
- <a v-if="activeTab === 'all'" :href="newIssuePath" class="btn btn-success btn-inverted">{{
- __('New issue')
- }}</a>
- <button
+ <gl-button
+ v-if="activeTab === 'all'"
+ :href="newIssuePath"
+ category="secondary"
+ variant="success"
+ >
+ {{ __('New issue') }}
+ </gl-button>
+ <gl-button
v-if="activeTab === 'selected'"
- class="btn btn-default"
- type="button"
+ category="primary"
+ variant="default"
@click="changeTab('all')"
>
{{ __('Open issues') }}
- </button>
+ </gl-button>
</div>
</div>
</div>
diff --git a/app/assets/javascripts/groups/components/invite_members_banner.vue b/app/assets/javascripts/groups/components/invite_members_banner.vue
index a2d18229c8b..da7adab1d86 100644
--- a/app/assets/javascripts/groups/components/invite_members_banner.vue
+++ b/app/assets/javascripts/groups/components/invite_members_banner.vue
@@ -2,21 +2,50 @@
import { GlBanner } from '@gitlab/ui';
import { s__ } from '~/locale';
import { parseBoolean, setCookie, getCookie } from '~/lib/utils/common_utils';
+import Tracking from '~/tracking';
+
+const trackingMixin = Tracking.mixin();
export default {
components: {
GlBanner,
},
- inject: ['svgPath', 'inviteMembersPath', 'isDismissedKey'],
+ mixins: [trackingMixin],
+ inject: ['svgPath', 'inviteMembersPath', 'isDismissedKey', 'trackLabel'],
data() {
return {
isDismissed: parseBoolean(getCookie(this.isDismissedKey)),
+ tracking: {
+ label: this.trackLabel,
+ },
};
},
+ created() {
+ this.$nextTick(() => {
+ this.addTrackingAttributesToButton();
+ });
+ },
+ mounted() {
+ this.trackOnShow();
+ },
methods: {
handleClose() {
setCookie(this.isDismissedKey, true);
this.isDismissed = true;
+ this.track(this.$options.dismissEvent);
+ },
+ trackOnShow() {
+ if (!this.isDismissed) this.track(this.$options.displayEvent);
+ },
+ addTrackingAttributesToButton() {
+ if (this.$refs.banner === undefined) return;
+
+ const button = this.$refs.banner.$el.querySelector(`[href='${this.inviteMembersPath}']`);
+
+ if (button) {
+ button.setAttribute('data-track-event', this.$options.buttonClickEvent);
+ button.setAttribute('data-track-label', this.trackLabel);
+ }
},
},
i18n: {
@@ -26,6 +55,9 @@ export default {
),
button_text: s__('InviteMembersBanner|Invite your colleagues'),
},
+ displayEvent: 'invite_members_banner_displayed',
+ buttonClickEvent: 'invite_members_banner_button_clicked',
+ dismissEvent: 'invite_members_banner_dismissed',
};
</script>
diff --git a/app/assets/javascripts/groups/init_invite_members_banner.js b/app/assets/javascripts/groups/init_invite_members_banner.js
index 9117337895f..c7967827917 100644
--- a/app/assets/javascripts/groups/init_invite_members_banner.js
+++ b/app/assets/javascripts/groups/init_invite_members_banner.js
@@ -8,7 +8,7 @@ export default function initInviteMembersBanner() {
return false;
}
- const { svgPath, inviteMembersPath, isDismissedKey } = el.dataset;
+ const { svgPath, inviteMembersPath, isDismissedKey, trackLabel } = el.dataset;
return new Vue({
el,
@@ -16,6 +16,7 @@ export default function initInviteMembersBanner() {
svgPath,
inviteMembersPath,
isDismissedKey,
+ trackLabel,
},
render: createElement => createElement(InviteMembersBanner),
});
diff --git a/app/assets/javascripts/notes/components/note_actions.vue b/app/assets/javascripts/notes/components/note_actions.vue
index 643145651e5..a8057276f1a 100644
--- a/app/assets/javascripts/notes/components/note_actions.vue
+++ b/app/assets/javascripts/notes/components/note_actions.vue
@@ -1,12 +1,13 @@
<script>
import { mapGetters } from 'vuex';
import { GlLoadingIcon, GlTooltipDirective, GlIcon } from '@gitlab/ui';
-import { __ } from '~/locale';
+import { __, sprintf } from '~/locale';
import resolvedStatusMixin from '~/batch_comments/mixins/resolved_status';
import ReplyButton from './note_actions/reply_button.vue';
import eventHub from '~/sidebar/event_hub';
import Api from '~/api';
import { deprecatedCreateFlash as flash } from '~/flash';
+import { splitCamelCase } from '../../lib/utils/text_utility';
export default {
name: 'NoteActions',
@@ -47,6 +48,26 @@ export default {
required: false,
default: null,
},
+ isAuthor: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ isContributor: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ noteableType: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ projectName: {
+ type: String,
+ required: false,
+ default: '',
+ },
showReply: {
type: Boolean,
required: true,
@@ -121,6 +142,9 @@ export default {
targetType() {
return this.getNoteableData.targetType;
},
+ noteableDisplayName() {
+ return splitCamelCase(this.noteableType).toLowerCase();
+ },
assignees() {
return this.getNoteableData.assignees || [];
},
@@ -130,6 +154,22 @@ export default {
canAssign() {
return this.getNoteableData.current_user?.can_update && this.isIssue;
},
+ displayAuthorBadgeText() {
+ return sprintf(__('This user is the author of this %{noteable}.'), {
+ noteable: this.noteableDisplayName,
+ });
+ },
+ displayMemberBadgeText() {
+ return sprintf(__('This user is a %{access} of the %{name} project.'), {
+ access: this.accessLevel.toLowerCase(),
+ name: this.projectName,
+ });
+ },
+ displayContributorBadgeText() {
+ return sprintf(__('This user has previously committed to the %{name} project.'), {
+ name: this.projectName,
+ });
+ },
},
methods: {
onEdit() {
@@ -175,7 +215,24 @@ export default {
<template>
<div class="note-actions">
- <span v-if="accessLevel" class="note-role user-access-role">{{ accessLevel }}</span>
+ <span
+ v-if="isAuthor"
+ class="note-role user-access-role has-tooltip d-none d-md-inline-block"
+ :title="displayAuthorBadgeText"
+ >{{ __('Author') }}</span
+ >
+ <span
+ v-if="accessLevel"
+ class="note-role user-access-role has-tooltip"
+ :title="displayMemberBadgeText"
+ >{{ accessLevel }}</span
+ >
+ <span
+ v-else-if="isContributor"
+ class="note-role user-access-role has-tooltip"
+ :title="displayContributorBadgeText"
+ >{{ __('Contributor') }}</span
+ >
<div v-if="canResolve" class="note-actions-item">
<button
ref="resolveButton"
diff --git a/app/assets/javascripts/notes/components/noteable_note.vue b/app/assets/javascripts/notes/components/noteable_note.vue
index 180d12c9e21..4f45fcb0062 100644
--- a/app/assets/javascripts/notes/components/noteable_note.vue
+++ b/app/assets/javascripts/notes/components/noteable_note.vue
@@ -389,6 +389,10 @@ export default {
:note-id="note.id"
:note-url="note.noteable_note_url"
:access-level="note.human_access"
+ :is-contributor="note.is_contributor"
+ :is-author="note.is_noteable_author"
+ :project-name="note.project_name"
+ :noteable-type="note.noteable_type"
:show-reply="showReplyButton"
:can-edit="note.current_user.can_edit"
:can-award-emoji="note.current_user.can_award_emoji"
diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue
index a768bc617c3..d0a0560846a 100644
--- a/app/assets/javascripts/vue_shared/components/markdown/header.vue
+++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue
@@ -143,7 +143,7 @@ export default {
:button-title="
sprintf(s__('MarkdownEditor|Add bold text (%{modifierKey}B)'), { modifierKey })
"
- :shortcuts="['command+b', 'ctrl+b']"
+ shortcuts="mod+b"
icon="bold"
/>
<toolbar-button
@@ -151,7 +151,7 @@ export default {
:button-title="
sprintf(s__('MarkdownEditor|Add italic text (%{modifierKey}I)'), { modifierKey })
"
- :shortcuts="['command+i', 'ctrl+i']"
+ shortcuts="mod+i"
icon="italic"
/>
<toolbar-button
@@ -207,7 +207,7 @@ export default {
:button-title="
sprintf(s__('MarkdownEditor|Add a link (%{modifierKey}K)'), { modifierKey })
"
- :shortcuts="['command+k', 'ctrl+k']"
+ shortcuts="mod+k"
icon="link"
/>
</div>
diff --git a/app/controllers/concerns/renders_notes.rb b/app/controllers/concerns/renders_notes.rb
index 18015b1de88..f8e3717acee 100644
--- a/app/controllers/concerns/renders_notes.rb
+++ b/app/controllers/concerns/renders_notes.rb
@@ -5,7 +5,6 @@ module RendersNotes
def prepare_notes_for_rendering(notes, noteable = nil)
preload_noteable_for_regular_notes(notes)
preload_max_access_for_authors(notes, @project)
- preload_first_time_contribution_for_authors(noteable, notes)
preload_author_status(notes)
Notes::RenderService.new(current_user).execute(notes)
@@ -19,7 +18,8 @@ module RendersNotes
return unless project
user_ids = notes.map(&:author_id)
- project.team.max_member_access_for_user_ids(user_ids)
+ access = project.team.max_member_access_for_user_ids(user_ids).select { |k, v| v == Gitlab::Access::NO_ACCESS }.keys
+ project.team.contribution_check_for_user_ids(access)
end
# rubocop: disable CodeReuse/ActiveRecord
@@ -28,12 +28,6 @@ module RendersNotes
end
# rubocop: enable CodeReuse/ActiveRecord
- def preload_first_time_contribution_for_authors(noteable, notes)
- return unless noteable.is_a?(Issuable) && noteable.first_contribution?
-
- notes.each {|n| n.specialize_for_first_contribution!(noteable)}
- end
-
# rubocop: disable CodeReuse/ActiveRecord
def preload_author_status(notes)
ActiveRecord::Associations::Preloader.new.preload(notes, { author: :status })
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 54fa3ca57b8..b255597b18d 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -205,6 +205,12 @@ module IssuablesHelper
author_output
end
+ if access = project.team.human_max_access(issuable.author_id)
+ output << content_tag(:span, access, class: "user-access-role has-tooltip d-none d-xl-inline-block gl-ml-3 ", title: _("This user is a %{access} of the %{name} project.") % { access: access.downcase, name: project.name })
+ elsif project.team.contributor?(issuable.author_id)
+ output << content_tag(:span, _("Contributor"), class: "user-access-role has-tooltip d-none d-xl-inline-block gl-ml-3", title: _("This user has previously committed to the %{name} project.") % { name: project.name })
+ end
+
output << content_tag(:span, (sprite_icon('first-contribution', css_class: 'gl-icon gl-vertical-align-middle') if issuable.first_contribution?), class: 'has-tooltip gl-ml-2', title: _('1st contribution!'))
output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "d-none d-sm-none d-md-inline-block gl-ml-3")
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index 8397c348790..c02adfcf4c6 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -85,6 +85,10 @@ module NotesHelper
note.project.team.max_member_access(note.author_id)
end
+ def note_human_max_access(note)
+ note.project.team.human_max_access(note.author_id)
+ end
+
def discussion_path(discussion)
if discussion.for_merge_request?
return unless discussion.diff_discussion?
diff --git a/app/models/group.rb b/app/models/group.rb
index 01438ebb3a8..c0f145997cc 100644
--- a/app/models/group.rb
+++ b/app/models/group.rb
@@ -410,10 +410,17 @@ class Group < Namespace
.where(namespaces: { id: self_and_descendants.select(:id) })
end
- def max_member_access_for_user(user)
+ # Return the highest access level for a user
+ #
+ # A special case is handled here when the user is a GitLab admin
+ # which implies it has "OWNER" access everywhere, but should not
+ # officially appear as a member of a group unless specifically added to it
+ #
+ # @param user [User]
+ # @param only_concrete_membership [Bool] whether require admin concrete membership status
+ def max_member_access_for_user(user, only_concrete_membership: false)
return GroupMember::NO_ACCESS unless user
-
- return GroupMember::OWNER if user.admin?
+ return GroupMember::OWNER if user.admin? && !only_concrete_membership
max_member_access = members_with_parents.where(user_id: user)
.reorder(access_level: :desc)
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index a2cdd733e73..6da32f5cb62 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -1603,7 +1603,7 @@ class MergeRequest < ApplicationRecord
def first_contribution?
return false if project.team.max_member_access(author_id) > Gitlab::Access::GUEST
- project.merge_requests.merged.where(author_id: author_id).empty?
+ !project.merge_requests.merged.exists?(author_id: author_id)
end
# TODO: remove once production database rename completes
diff --git a/app/models/note.rb b/app/models/note.rb
index 7330b97bd76..812d77d5f86 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -20,20 +20,6 @@ class Note < ApplicationRecord
include ThrottledTouch
include FromUnion
- module SpecialRole
- FIRST_TIME_CONTRIBUTOR = :first_time_contributor
-
- class << self
- def values
- constants.map {|const| self.const_get(const, false)}
- end
-
- def value?(val)
- values.include?(val)
- end
- end
- end
-
cache_markdown_field :note, pipeline: :note, issuable_state_filter_enabled: true
redact_field :note
@@ -60,9 +46,6 @@ class Note < ApplicationRecord
# Attribute used to store the attributes that have been changed by quick actions.
attr_accessor :commands_changes
- # A special role that may be displayed on issuable's discussions
- attr_reader :special_role
-
default_value_for :system, false
attr_mentionable :note, pipeline: :note
@@ -220,10 +203,6 @@ class Note < ApplicationRecord
.where(noteable_type: type, noteable_id: ids)
end
- def has_special_role?(role, note)
- note.special_role == role
- end
-
def search(query)
fuzzy_search(query, [:note])
end
@@ -342,20 +321,20 @@ class Note < ApplicationRecord
noteable.author_id == user.id
end
- def special_role=(role)
- raise "Role is undefined, #{role} not found in #{SpecialRole.values}" unless SpecialRole.value?(role)
+ def contributor?
+ return false unless ::Feature.enabled?(:show_contributor_on_note, project)
- @special_role = role
+ project&.team&.contributor?(self.author_id)
end
- def has_special_role?(role)
- self.class.has_special_role?(role, self)
- end
+ def noteable_author?(noteable)
+ return false unless ::Feature.enabled?(:show_author_on_note, project)
- def specialize_for_first_contribution!(noteable)
- return unless noteable.author_id == self.author_id
+ noteable.author == self.author
+ end
- self.special_role = Note::SpecialRole::FIRST_TIME_CONTRIBUTOR
+ def project_name
+ project&.name
end
def confidential?(include_noteable: false)
diff --git a/app/models/project_team.rb b/app/models/project_team.rb
index 072d281e5f8..5b7eded00cd 100644
--- a/app/models/project_team.rb
+++ b/app/models/project_team.rb
@@ -178,6 +178,40 @@ class ProjectTeam
max_member_access_for_user_ids([user_id])[user_id]
end
+ def contribution_check_for_user_ids(user_ids)
+ user_ids = user_ids.uniq
+ key = "contribution_check_for_users:#{project.id}"
+
+ Gitlab::SafeRequestStore[key] ||= {}
+ contributors = Gitlab::SafeRequestStore[key] || {}
+
+ user_ids -= contributors.keys
+
+ return contributors if user_ids.empty?
+
+ resource_contributors = project.merge_requests
+ .merged
+ .where(author_id: user_ids, target_branch: project.default_branch.to_s)
+ .pluck(:author_id)
+ .product([true]).to_h
+
+ contributors.merge!(resource_contributors)
+
+ missing_resource_ids = user_ids - resource_contributors.keys
+
+ missing_resource_ids.each do |resource_id|
+ contributors[resource_id] = false
+ end
+
+ contributors
+ end
+
+ def contributor?(user_id)
+ return false if max_member_access(user_id) >= Gitlab::Access::GUEST
+
+ contribution_check_for_user_ids([user_id])[user_id]
+ end
+
private
def fetch_members(level = nil)
diff --git a/app/serializers/note_entity.rb b/app/serializers/note_entity.rb
index c49dec2a93c..ef305195e22 100644
--- a/app/serializers/note_entity.rb
+++ b/app/serializers/note_entity.rb
@@ -46,6 +46,10 @@ class NoteEntity < API::Entities::Note
SystemNoteHelper.system_note_icon_name(note)
end
+ expose :is_noteable_author do |note|
+ note.noteable_author?(request.noteable)
+ end
+
expose :discussion_id do |note|
note.discussion_id(request.noteable)
end
diff --git a/app/serializers/project_note_entity.rb b/app/serializers/project_note_entity.rb
index f6cdea1d8b5..3cd34f6af0d 100644
--- a/app/serializers/project_note_entity.rb
+++ b/app/serializers/project_note_entity.rb
@@ -5,6 +5,14 @@ class ProjectNoteEntity < NoteEntity
note.project.team.human_max_access(note.author_id)
end
+ expose :is_contributor, if: -> (note, _) { note.project.present? } do |note|
+ note.contributor?
+ end
+
+ expose :project_name, if: -> (note, _) { note.project.present? } do |note|
+ note.project.name
+ end
+
expose :toggle_award_path, if: -> (note, _) { note.emoji_awardable? } do |note|
toggle_award_emoji_project_note_path(note.project, note.id)
end
diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb
index 33ed1151407..68b40fdd8f1 100644
--- a/app/services/projects/create_service.rb
+++ b/app/services/projects/create_service.rb
@@ -114,8 +114,13 @@ module Projects
# completes), and any other affected users in the background
def setup_authorizations
if @project.group
- current_user.project_authorizations.create!(project: @project,
- access_level: @project.group.max_member_access_for_user(current_user))
+ group_access_level = @project.group.max_member_access_for_user(current_user,
+ only_concrete_membership: true)
+
+ if group_access_level > GroupMember::NO_ACCESS
+ current_user.project_authorizations.create!(project: @project,
+ access_level: group_access_level)
+ end
if Feature.enabled?(:specialized_project_authorization_workers)
AuthorizedProjectUpdate::ProjectCreateWorker.perform_async(@project.id)
diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml
index b0055ec81db..ec4ab603d22 100644
--- a/app/views/groups/show.html.haml
+++ b/app/views/groups/show.html.haml
@@ -7,6 +7,7 @@
.container-fluid.container-limited{ class: "gl-pb-2! gl-pt-6! #{@content_class}" }
.js-group-invite-members-banner{ data: { svg_path: image_path('illustrations/merge_requests.svg'),
is_dismissed_key: "invite_#{@group.id}_#{current_user.id}",
+ track_label: 'invite_members_banner',
invite_members_path: group_group_members_path(@group) } }
= content_for :meta_tags do
diff --git a/app/views/projects/notes/_actions.html.haml b/app/views/projects/notes/_actions.html.haml
index 66721a28e62..058366eb75d 100644
--- a/app/views/projects/notes/_actions.html.haml
+++ b/app/views/projects/notes/_actions.html.haml
@@ -1,9 +1,10 @@
-- access = note_max_access_for_user(note)
-- if note.has_special_role?(Note::SpecialRole::FIRST_TIME_CONTRIBUTOR)
- %span.note-role.note-role-special.has-tooltip{ title: _("This is the author's first Merge Request to this project.") }
- = sprite_icon('first-contribution', css_class: 'gl-icon gl-vertical-align-top')
-- if access.nonzero?
- %span.note-role.user-access-role= Gitlab::Access.human_access(access)
+- access = note_human_max_access(note)
+- if note.noteable_author?(@noteable)
+ %span{ class: 'note-role user-access-role has-tooltip d-none d-md-inline-block', title: _("This user is the author of this %{noteable}.") % { noteable: @noteable.human_class_name } }= _("Author")
+- if access
+ %span{ class: 'note-role user-access-role has-tooltip', title: _("This user is a %{access} of the %{name} project.") % { access: access.downcase, name: note.project_name } }= access
+- elsif note.contributor?
+ %span{ class: 'note-role user-access-role has-tooltip', title: _("This user has previously committed to the %{name} project.") % { name: note.project_name } }= _("Contributor")
- if note.resolvable?
- can_resolve = can?(current_user, :resolve_note, note)
diff --git a/app/views/shared/blob/_markdown_buttons.html.haml b/app/views/shared/blob/_markdown_buttons.html.haml
index 7fc4bcdff38..085206714c6 100644
--- a/app/views/shared/blob/_markdown_buttons.html.haml
+++ b/app/views/shared/blob/_markdown_buttons.html.haml
@@ -2,18 +2,18 @@
.md-header-toolbar.active
= markdown_toolbar_button({ icon: "bold",
- data: { "md-tag" => "**", "md-shortcuts": '["command+b","ctrl+b"]' },
+ data: { "md-tag" => "**", "md-shortcuts": '["mod+b"]' },
title: sprintf(s_("MarkdownEditor|Add bold text (%{modifier_key}B)") % { modifier_key: modifier_key }) })
= markdown_toolbar_button({ icon: "italic",
- data: { "md-tag" => "_", "md-shortcuts": '["command+i","ctrl+i"]' },
+ data: { "md-tag" => "_", "md-shortcuts": '["mod+i"]' },
title: sprintf(s_("MarkdownEditor|Add italic text (%{modifier_key}I)") % { modifier_key: modifier_key }) })
= markdown_toolbar_button({ icon: "quote", data: { "md-tag" => "> ", "md-prepend" => true }, title: _("Insert a quote") })
= markdown_toolbar_button({ icon: "code", data: { "md-tag" => "`", "md-block" => "```" }, title: _("Insert code") })
= markdown_toolbar_button({ icon: "link",
- data: { "md-tag" => "[{text}](url)", "md-select" => "url", "md-shortcuts": '["command+k","ctrl+k"]' },
+ data: { "md-tag" => "[{text}](url)", "md-select" => "url", "md-shortcuts": '["mod+k"]' },
title: sprintf(s_("MarkdownEditor|Add a link (%{modifier_key}K)") % { modifier_key: modifier_key }) })
= markdown_toolbar_button({ icon: "list-bulleted", data: { "md-tag" => "- ", "md-prepend" => true }, title: _("Add a bullet list") })
diff --git a/changelogs/unreleased/238165-fix_project_create_authorization_for_admin.yml b/changelogs/unreleased/238165-fix_project_create_authorization_for_admin.yml
new file mode 100644
index 00000000000..96761d940e6
--- /dev/null
+++ b/changelogs/unreleased/238165-fix_project_create_authorization_for_admin.yml
@@ -0,0 +1,5 @@
+---
+title: Do not add admins as owners to project authorizations during project creation
+merge_request: 42335
+author:
+type: fixed
diff --git a/changelogs/unreleased/display-contributor-and-author-badges-on-notes.yml b/changelogs/unreleased/display-contributor-and-author-badges-on-notes.yml
new file mode 100644
index 00000000000..0832052dd17
--- /dev/null
+++ b/changelogs/unreleased/display-contributor-and-author-badges-on-notes.yml
@@ -0,0 +1,5 @@
+---
+title: Display Contributor and Author badges on notes
+merge_request: 40198
+author: Mycroft Kang @TaehyeokKang
+type: added
diff --git a/changelogs/unreleased/nfriend-only-apply-command-markdown-shortcuts-on-mac.yml b/changelogs/unreleased/nfriend-only-apply-command-markdown-shortcuts-on-mac.yml
new file mode 100644
index 00000000000..4a30d8d6da9
--- /dev/null
+++ b/changelogs/unreleased/nfriend-only-apply-command-markdown-shortcuts-on-mac.yml
@@ -0,0 +1,5 @@
+---
+title: Stop applying Ctrl keyboard shortcuts inside Markdown editors on Mac
+merge_request: 42239
+author:
+type: fixed
diff --git a/config/feature_flags/development/show_author_on_note.yml b/config/feature_flags/development/show_author_on_note.yml
new file mode 100644
index 00000000000..1f67392a306
--- /dev/null
+++ b/config/feature_flags/development/show_author_on_note.yml
@@ -0,0 +1,7 @@
+---
+name: show_author_on_note
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40198
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/250282
+group: group::project management
+type: development
+default_enabled: false \ No newline at end of file
diff --git a/config/feature_flags/development/show_contributor_on_note.yml b/config/feature_flags/development/show_contributor_on_note.yml
new file mode 100644
index 00000000000..89533037244
--- /dev/null
+++ b/config/feature_flags/development/show_contributor_on_note.yml
@@ -0,0 +1,7 @@
+---
+name: show_contributor_on_note
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40198
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/249179
+group: group::project management
+type: development
+default_enabled: false \ No newline at end of file
diff --git a/doc/README.md b/doc/README.md
index 1038517a5cc..efae2cdd3ff 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -126,7 +126,7 @@ The following documentation relates to the DevOps **Plan** stage:
| [Roadmap](user/group/roadmap/index.md) **(ULTIMATE)** | Visualize epic timelines. |
| [Service Desk](user/project/service_desk.md) | A simple way to allow people to create issues in your GitLab instance without needing their own user account. |
| [Time Tracking](user/project/time_tracking.md) | Track time spent on issues and merge requests. |
-| [Todos](user/todos.md) | Keep track of work requiring attention with a chronological list displayed on a simple dashboard. |
+| [To-Do List](user/todos.md) | Keep track of work requiring attention with a chronological list displayed on a simple dashboard. |
<div align="right">
<a type="button" class="btn btn-default" href="#overview">
@@ -159,7 +159,7 @@ The following documentation relates to the DevOps **Create** stage:
| [Issue Analytics](user/group/issues_analytics/index.md) **(PREMIUM)** | Check how many issues were created per month. |
| [Merge Request Analytics](user/analytics/merge_request_analytics.md) **(PREMIUM)** | Check your throughput productivity - how many merge requests were merged per month. |
| [Projects](user/project/index.md), including [project access](public_access/public_access.md)<br/>and [settings](user/project/settings/index.md) | Host source code, and control your project's visibility and set configuration. |
-| [Search through GitLab](user/search/index.md) | Search for issues, merge requests, projects, groups, and todos. |
+| [Search through GitLab](user/search/index.md) | Search for issues, merge requests, projects, groups, and to-dos. |
| [Snippets](user/snippets.md) | Snippets allow you to create little bits of code. |
| [Web IDE](user/project/web_ide/index.md) | Edit files within GitLab's user interface. |
| [Static Site Editor](user/project/static_site_editor/index.md) | Edit content on static websites. |
diff --git a/doc/api/api_resources.md b/doc/api/api_resources.md
index c44ec8e35f6..9fa9f25ab9f 100644
--- a/doc/api/api_resources.md
+++ b/doc/api/api_resources.md
@@ -151,7 +151,7 @@ The following API resources are available outside of project and group contexts
| [Sidekiq metrics](sidekiq_metrics.md) | `/sidekiq` |
| [Suggestions](suggestions.md) | `/suggestions` |
| [System hooks](system_hooks.md) | `/hooks` |
-| [Todos](todos.md) | `/todos` |
+| [To-dos](todos.md) | `/todos` |
| [Users](users.md) | `/users` |
| [Validate `.gitlab-ci.yml` file](lint.md) | `/lint` |
| [Version](version.md) | `/version` |
diff --git a/doc/api/epics.md b/doc/api/epics.md
index c3ba42c6efd..91ea92c8589 100644
--- a/doc/api/epics.md
+++ b/doc/api/epics.md
@@ -422,10 +422,10 @@ DELETE /groups/:id/epics/:epic_iid
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/groups/1/epics/5"
```
-## Create a todo
+## Create a to-do
-Manually creates a todo for the current user on an epic. If
-there already exists a todo for the user on that epic, status code `304` is
+Manually creates a to-do for the current user on an epic. If
+there already exists a to-do for the user on that epic, status code `304` is
returned.
```plaintext
diff --git a/doc/api/issues.md b/doc/api/issues.md
index df9eaab7962..d8249869cab 100644
--- a/doc/api/issues.md
+++ b/doc/api/issues.md
@@ -1496,10 +1496,10 @@ Example response:
}
```
-## Create a todo
+## Create a to-do
-Manually creates a todo for the current user on an issue. If
-there already exists a todo for the user on that issue, status code `304` is
+Manually creates a to-do for the current user on an issue. If
+there already exists a to-do for the user on that issue, status code `304` is
returned.
```plaintext
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 4798145e837..faefc445210 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -2085,10 +2085,10 @@ the `approvals_before_merge` parameter:
}
```
-## Create a todo
+## Create a to-do
-Manually creates a todo for the current user on a merge request.
-If there already exists a todo for the user on that merge request,
+Manually creates a to-do for the current user on a merge request.
+If there already exists a to-do for the user on that merge request,
status code `304` is returned.
```plaintext
diff --git a/doc/api/todos.md b/doc/api/todos.md
index 54dd59af9a8..ebe10ecbd49 100644
--- a/doc/api/todos.md
+++ b/doc/api/todos.md
@@ -4,13 +4,13 @@ group: Project Management
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
-# Todos API
+# To-dos API
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/3188) in GitLab 8.10.
-## Get a list of todos
+## Get a list of to-dos
-Returns a list of todos. When no filter is applied, it returns all pending todos
+Returns a list of to-dos. When no filter is applied, it returns all pending to-dos
for the current user. Different filters allow the user to precise the request.
```plaintext
@@ -25,8 +25,8 @@ Parameters:
| `author_id` | integer | no | The ID of an author |
| `project_id` | integer | no | The ID of a project |
| `group_id` | integer | no | The ID of a group |
-| `state` | string | no | The state of the todo. Can be either `pending` or `done` |
-| `type` | string | no | The type of a todo. Can be either `Issue`, `MergeRequest`, `DesignManagement::Design` or `AlertManagement::Alert` |
+| `state` | string | no | The state of the to-do. Can be either `pending` or `done` |
+| `type` | string | no | The type of a to-do. Can be either `Issue`, `MergeRequest`, `DesignManagement::Design` or `AlertManagement::Alert` |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/todos"
@@ -187,10 +187,10 @@ Example Response:
]
```
-## Mark a todo as done
+## Mark a to-do as done
-Marks a single pending todo given by its ID for the current user as done. The
-todo marked as done is returned in the response.
+Marks a single pending to-do given by its ID for the current user as done. The
+to-do marked as done is returned in the response.
```plaintext
POST /todos/:id/mark_as_done
@@ -200,7 +200,7 @@ Parameters:
| Attribute | Type | Required | Description |
| --------- | ---- | -------- | ----------- |
-| `id` | integer | yes | The ID of a todo |
+| `id` | integer | yes | The ID of a to-do |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/todos/130/mark_as_done"
@@ -285,9 +285,9 @@ Example Response:
}
```
-## Mark all todos as done
+## Mark all to-dos as done
-Marks all pending todos for the current user as done. It returns the HTTP status code `204` with an empty response.
+Marks all pending to-dos for the current user as done. It returns the HTTP status code `204` with an empty response.
```plaintext
POST /todos/mark_as_done
diff --git a/doc/api/v3_to_v4.md b/doc/api/v3_to_v4.md
index 4571d4d8304..4139438bea0 100644
--- a/doc/api/v3_to_v4.md
+++ b/doc/api/v3_to_v4.md
@@ -74,8 +74,8 @@ Below are the changes made between V3 and V4.
- `POST /projects/:id/trigger/builds` to `POST /projects/:id/trigger/pipeline`
- Require description when creating a new trigger `POST /projects/:id/triggers`
- Simplify project payload exposed on Environment endpoints [!9675](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/9675)
-- API uses merge request `IID`s (internal ID, as in the web UI) rather than `ID`s. This affects the merge requests, award emoji, todos, and time tracking APIs. [!9530](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/9530)
-- API uses issue `IID`s (internal ID, as in the web UI) rather than `ID`s. This affects the issues, award emoji, todos, and time tracking APIs. [!9530](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/9530)
+- API uses merge request `IID`s (internal ID, as in the web UI) rather than `ID`s. This affects the merge requests, award emoji, to-dos, and time tracking APIs. [!9530](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/9530)
+- API uses issue `IID`s (internal ID, as in the web UI) rather than `ID`s. This affects the issues, award emoji, to-dos, and time tracking APIs. [!9530](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/9530)
- Change initial page from `0` to `1` on `GET /projects/:id/repository/commits` (like on the rest of the API) [!9679](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/9679)
- Return correct `Link` header data for `GET /projects/:id/repository/commits` [!9679](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/9679)
- Update endpoints for repository files [!9637](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/9637)
diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md
index 1f64f5b62d8..1a982fa4e19 100644
--- a/doc/ci/variables/README.md
+++ b/doc/ci/variables/README.md
@@ -547,6 +547,7 @@ The order of precedence for variables is (from highest to lowest):
and [manual pipeline run variables](#override-a-variable-by-manually-running-a-pipeline).
1. Project-level [variables](#custom-environment-variables) or [protected variables](#protect-a-custom-variable).
1. Group-level [variables](#group-level-environment-variables) or [protected variables](#protect-a-custom-variable).
+1. Instance-level [variables](#instance-level-cicd-environment-variables) or [protected variables](#protect-a-custom-variable).
1. [Inherited environment variables](#inherit-environment-variables).
1. YAML-defined [job-level variables](../yaml/README.md#variables).
1. YAML-defined [global variables](../yaml/README.md#variables).
diff --git a/doc/development/api_styleguide.md b/doc/development/api_styleguide.md
index d87f3ceac30..400752c69e9 100644
--- a/doc/development/api_styleguide.md
+++ b/doc/development/api_styleguide.md
@@ -175,7 +175,7 @@ guide on how you can add a new custom validator.
validates the parameter value for different cases. Mainly, it checks whether a
path is relative and does it contain `../../` relative traversal using
`File::Separator` or not, and whether the path is absolute, for example
- `/etc/passwd/`. By default, absolute paths are not allowed. However, you can optionally pass in an allowlist for allowed absolute paths in the following way:
+ `/etc/passwd/`. By default, absolute paths are not allowed. However, you can optionally pass in an allowlist for allowed absolute paths in the following way:
`requires :file_path, type: String, file_path: { allowlist: ['/foo/bar/', '/home/foo/', '/app/home'] }`
- `Git SHA`:
@@ -249,7 +249,7 @@ most basic entity, with successive entities building upon that scope.
The `with_api_entity_associations` scope will also [automatically preload
data](https://gitlab.com/gitlab-org/gitlab/blob/19f74903240e209736c7668132e6a5a735954e7c/app%2Fmodels%2Ftodo.rb#L34)
-for `Todo` _targets_ when returned in the Todos API.
+for `Todo` _targets_ when returned in the [to-dos API](../api/todos.md).
For more context and discussion about preloading see
[this merge request](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/25711)
diff --git a/doc/development/documentation/feature_flags.md b/doc/development/documentation/feature_flags.md
index 392f478273e..a4a6ee2fa0f 100644
--- a/doc/development/documentation/feature_flags.md
+++ b/doc/development/documentation/feature_flags.md
@@ -119,7 +119,7 @@ use:
# Feature Name
> - [Introduced](link-to-issue) in GitLab 12.0.
-> - It was deployed [deployed behind a feature flag](<replace with path to>/user/feature_flags.md), disabled by default.
+> - It was [deployed behind a feature flag](<replace with path to>/user/feature_flags.md), disabled by default.
> - [Became enabled by default](link-to-issue) on GitLab 12.1.
> - It's enabled on GitLab.com.
> - It's recommended for production use.
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index c3033f28928..09bb6b9da6a 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -369,6 +369,7 @@ create an issue or an MR to propose a change to the user interface text.
- milestones
- reorder issues
- runner, runners, shared runners
+ - a to-do, to-dos
- *Some features are capitalized*, typically nouns naming GitLab-specific
capabilities or tools. For example:
- GitLab CI/CD
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index c7853be4932..6ef9be381b4 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -448,7 +448,9 @@ so we need to set some guidelines for their use going forward:
- `let!` variables should be used only in case if strict evaluation with defined
order is required, otherwise `let` will suffice. Remember that `let` is lazy and won't
be evaluated until it is referenced.
-- Use named `subject(:name)` over un-named `subject` in examples, as this gives the subject a contextual name.
+- Avoid referencing `subject` in examples. Use a named subject `subject(:name)`, or a `let` variable instead, so
+ the variable has a contextual name.
+- If the `subject` is never referenced inside examples, then it's acceptable to define the `subject` without a name.
### Common test setup
diff --git a/doc/operations/incident_management/alert_details.md b/doc/operations/incident_management/alert_details.md
index 16a11f3863a..860e6d32ae4 100644
--- a/doc/operations/incident_management/alert_details.md
+++ b/doc/operations/incident_management/alert_details.md
@@ -30,12 +30,12 @@ The **Overview** tab provides basic information about the alert:
![Alert Full Details](./img/alert_detail_full_v13_1.png)
-### Update an Alert's status
+### Update an alert's status
The Alert detail view enables you to update the Alert Status.
See [Create and manage alerts in GitLab](./alerts.md) for more details.
-### Create an Issue from an Alert
+### Create an issue from an alert
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217745) in GitLab 13.1.
@@ -47,7 +47,7 @@ alert by clicking the **View Issue** button.
Closing a GitLab issue associated with an alert changes the alert's status to Resolved.
See [Create and manage alerts in GitLab](alerts.md) for more details about alert statuses.
-### Update an Alert's assignee
+### Update an alert's assignee
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab 13.1.
@@ -73,7 +73,7 @@ GitLab currently only supports a single assignee per alert.
**{angle-double-right}** **Expand sidebar** to expand it.
1. In the right sidebar, locate the **Assignee** and click **Edit**. From the
dropdown menu, select each user you want to assign to the alert. GitLab creates
- a [To-Do list item](../../user/todos.md) for each user.
+ a [to-do list item](../../user/todos.md) for each user.
![Alert Details View Assignee(s)](./img/alert_todo_assignees_v13_1.png)
@@ -96,12 +96,12 @@ The following actions will result in a system note:
![Alert Details View System Notes](./img/alert_detail_system_notes_v13_1.png)
-### Create a To-Do from an Alert
+### Create a to-do from an alert
> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3066) in GitLab 13.1.
You can manually create [To-Do list items](../../user/todos.md) for yourself from the
-Alert details screen, and view them later on your **To-Do List**. To add a To-Do:
+Alert details screen, and view them later on your **To-Do List**. To add a to-do:
1. To display the list of current alerts, click
**{cloud-gear}** **Operations > Alerts**.
@@ -110,11 +110,11 @@ Alert details screen, and view them later on your **To-Do List**. To add a To-Do
![Alert Details Add A To Do](./img/alert_detail_add_todo_v13_1.png)
-Click the **To-Do** **{todo-done}** in the navigation bar to view your current To-Do list.
+Click the **To-Do** **{todo-done}** in the navigation bar to view your current to-do list.
![Alert Details Added to Do](./img/alert_detail_added_todo_v13_1.png)
-### View an Alert's metrics data
+### View an alert's metrics data
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217768) in GitLab 13.2.
@@ -154,7 +154,7 @@ unassign their account from the alert when their role is complete.
The alert status can be updated on the [Alert list](./alerts.md) to
reflect if the alert has been resolved.
-## View an Alert's logs
+## View an alert's logs
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/217768) in GitLab 13.3.
diff --git a/doc/university/README.md b/doc/university/README.md
index 2a9111276d3..d029c91a19f 100644
--- a/doc/university/README.md
+++ b/doc/university/README.md
@@ -47,7 +47,7 @@ The GitLab University curriculum is composed of GitLab videos, screencasts, pres
1. [Repositories, Projects and Groups - Video](https://www.youtube.com/watch?v=4TWfh1aKHHw&index=1&list=PLFGfElNsQthbQu_IWlNOxul0TbS_2JH-e)
1. [Creating a Project in GitLab - Video](https://www.youtube.com/watch?v=7p0hrpNaJ14)
1. [How to Create Files and Directories](https://about.gitlab.com/blog/2016/02/10/feature-highlight-create-files-and-directories-from-files-page/)
-1. [GitLab Todos](https://about.gitlab.com/blog/2016/03/02/gitlab-todos-feature-highlight/)
+1. [GitLab To-Do List](https://about.gitlab.com/blog/2016/03/02/gitlab-todos-feature-highlight/)
1. [GitLab's Work in Progress (WIP) Flag](https://about.gitlab.com/blog/2016/01/08/feature-highlight-wip/)
### 1.5. Migrating from other Source Control
diff --git a/doc/user/admin_area/settings/external_authorization.md b/doc/user/admin_area/settings/external_authorization.md
index c5c5f08aea1..0b250e07412 100644
--- a/doc/user/admin_area/settings/external_authorization.md
+++ b/doc/user/admin_area/settings/external_authorization.md
@@ -22,11 +22,11 @@ known response, the result is cached for 6 hours.
If the external authorization is enabled, GitLab will further block pages and
functionality that render cross-project data. That includes:
-- most pages under Dashboard (Activity, Milestones, Snippets, Assigned merge
- requests, Assigned issues, Todos)
-- under a specific group (Activity, Contribution analytics, Issues, Issue boards,
- Labels, Milestones, Merge requests)
-- Global and Group search will be disabled
+- Most pages under Dashboard (Activity, Milestones, Snippets, Assigned merge
+ requests, Assigned issues, To-Do List).
+- Under a specific group (Activity, Contribution analytics, Issues, Issue boards,
+ Labels, Milestones, Merge requests).
+- Global and Group search will be disabled.
This is to prevent performing to many requests at once to the external
authorization service.
diff --git a/doc/user/application_security/threat_monitoring/index.md b/doc/user/application_security/threat_monitoring/index.md
index 0fdd244a8f6..5414800b290 100644
--- a/doc/user/application_security/threat_monitoring/index.md
+++ b/doc/user/application_security/threat_monitoring/index.md
@@ -88,8 +88,9 @@ investigate it for potential threats by
The **Threat Monitoring** page's **Policy** tab displays deployed
network policies for all available environments. You can check a
-network policy's `yaml` manifest and toggle the policy's enforcement
-status. This section has the following prerequisites:
+network policy's `yaml` manifest, toggle the policy's enforcement
+status, and create and edit deployed policies. This section has the
+following prerequisites:
- Your project contains at least one [environment](../../../ci/environments/index.md)
- You've [installed Cilium](../../clusters/applications.md#install-cilium-using-gitlab-cicd)
@@ -124,3 +125,47 @@ Disabled network policies have the
`podSelector` block. This narrows the scope of such a policy and as a
result it doesn't affect any pods. The policy itself is still deployed
to the corresponding deployment namespace.
+
+### Container Network Policy editor
+
+> [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/3403) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 13.4.
+
+The policy editor allows you to create, edit, and delete policies. To
+create a new policy click the **New policy** button located in the
+**Policy** tab's header. To edit an existing policy, click**Edit
+policy** in the selected policy drawer.
+
+NOTE: **Note:**
+The policy editor only supports the
+[CiliumNetworkPolicy](https://docs.cilium.io/en/v1.8/policy/)specification. Regular
+Kubernetes
+[NetworkPolicy](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/#networkpolicy-v1-networking-k8s-io)
+resources aren't supported.
+
+The policy editor has two modes:
+
+- The visual _Rule_ mode allows you to construct and preview policy
+ rules using rule blocks and related controls.
+- YAML mode allows you to enter a policy definition in `.yaml` format
+ and is aimed at expert users and cases that the Rule mode doesn't
+ support.
+
+You can use both modes interchangeably and switch between them at any
+time. If a YAML resource is incorrect, Rule mode is automatically
+disabled. You must use YAML mode to fix your policy before Rule mode
+is available again.
+
+Rule mode supports the following rule types:
+
+- [Labels](https://docs.cilium.io/en/v1.8/policy/language/#labels-based).
+- [Entities](https://docs.cilium.io/en/v1.8/policy/language/#entities-based).
+- [IP/CIDR](https://docs.cilium.io/en/v1.8/policy/language/#ip-cidr-based). Only
+ the `toCIDR` block without `except` is supported.
+- [DNS](https://docs.cilium.io/en/v1.8/policy/language/#dns-based).
+- [Level 4](https://docs.cilium.io/en/v1.8/policy/language/#layer-4-examples)
+ can be added to all other rules.
+
+Once your policy is complete, save it by pressing the **Save policy**
+button at the bottom of the editor. Existing policies can also be
+removed from the editor interface by clicking the **Delete policy**
+button at the bottom of the editor.
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index 8be8474fcd0..57b9cc92c51 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -361,7 +361,7 @@ Here are possible causes and solutions:
Getting both of these errors at the same time suggests the NameID capitalization provided by the Identity Provider didn't exactly match the previous value for that user.
-This can be prevented by configuring the [NameID](#nameid) to return a consistent value. Fixing this for an individual user involves [unlinking SAML in the GitLab account](#unlinking-accounts), although this will cause group membership and Todos to be lost.
+This can be prevented by configuring the [NameID](#nameid) to return a consistent value. Fixing this for an individual user involves [unlinking SAML in the GitLab account](#unlinking-accounts), although this will cause group membership and to-dos to be lost.
### Message: "Request to link SAML account must be authorized"
diff --git a/doc/user/index.md b/doc/user/index.md
index 7627c8e67f8..ce8713591ab 100644
--- a/doc/user/index.md
+++ b/doc/user/index.md
@@ -134,10 +134,10 @@ the best of GitLab Flavored Markdown in your threads, comments,
issues and merge requests descriptions, and everywhere else GFM is
supported.
-## Todos
+## To-Do List
-Never forget to reply to your collaborators. [GitLab Todos](todos.md)
-are a tool for working faster and more effectively with your team,
+Never forget to reply to your collaborators. [GitLab To-Do List](todos.md)
+is a tool for working faster and more effectively with your team,
by listing all user or group mentions, as well as issues and merge
requests you're assigned to.
diff --git a/doc/user/profile/notifications.md b/doc/user/profile/notifications.md
index 6d29374596c..73a83b08a23 100644
--- a/doc/user/profile/notifications.md
+++ b/doc/user/profile/notifications.md
@@ -144,7 +144,7 @@ Users will be notified of the following events:
| New email added | User | Security email, always sent. |
| Email changed | User | Security email, always sent. |
| Password changed | User | Security email, always sent when user changes their own password |
-| Password changed by administrator | User | Security email, always sent when an adminstrator changes the password of another user |
+| Password changed by administrator | User | Security email, always sent when an administrator changes the password of another user |
| Two-factor authentication disabled | User | Security email, always sent. |
| New user created | User | Sent on user creation, except for OmniAuth (LDAP)|
| User added to project | User | Sent when user is added to project |
diff --git a/doc/user/profile/preferences.md b/doc/user/profile/preferences.md
index b94ae958d3b..03bb1bf677e 100644
--- a/doc/user/profile/preferences.md
+++ b/doc/user/profile/preferences.md
@@ -114,7 +114,7 @@ You have 8 options here that you can use for your default dashboard view:
- Your projects' activity
- Starred projects' activity
- Your groups
-- Your [Todos](../todos.md)
+- Your [to-dos](../todos.md)
- Assigned Issues
- Assigned Merge Requests
- Operations Dashboard **(PREMIUM)**
diff --git a/doc/user/project/issues/due_dates.md b/doc/user/project/issues/due_dates.md
index 56fb4ca5cc7..55b45bf9d3d 100644
--- a/doc/user/project/issues/due_dates.md
+++ b/doc/user/project/issues/due_dates.md
@@ -44,9 +44,9 @@ the icon and the date colored red. You can sort issues by those that are
![Issues with due dates in the issues index page](img/due_dates_issues_index_page.png)
-Due dates also appear in your [todos list](../../todos.md).
+Due dates also appear in your [to-do list](../../todos.md).
-![Issues with due dates in the todos](img/due_dates_todos.png)
+![Issues with due dates in the to-dos](img/due_dates_todos.png)
The day before an open issue is due, an email will be sent to all participants
of the issue. Like the due date, the "day before the due date" is determined by the
diff --git a/doc/user/project/issues/issue_data_and_actions.md b/doc/user/project/issues/issue_data_and_actions.md
index 818bf375169..5356e6aeb40 100644
--- a/doc/user/project/issues/issue_data_and_actions.md
+++ b/doc/user/project/issues/issue_data_and_actions.md
@@ -196,7 +196,7 @@ allowing many formatting options.
### Mentions
You can mention a user or a group present in your GitLab instance with `@username` or
-`@groupname` and they will be notified via todos and email, unless they have disabled
+`@groupname` and they will be notified via to-dos and email, unless they have disabled
all notifications in their profile settings. This is controlled in the
[notification settings](../../profile/notifications.md).
diff --git a/doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md b/doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md
index 6d181bcc822..403067ba0cd 100644
--- a/doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md
+++ b/doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md
@@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: index, reference
---
-# Reviewing and managing merge requests
+# Reviewing and managing merge requests **(CORE)**
Merge requests are the primary method of making changes to files in a GitLab project.
Changes are proposed by [creating and submitting a merge request](creating_merge_requests.md),
@@ -203,6 +203,11 @@ If there's an [environment](../../../ci/environments/index.md) and the applicati
successfully deployed to it, the deployed environment and the link to the
Review App will be shown as well.
+NOTE: **Note:**
+When the default branch (for example, `main`) is red due to a failed CI pipeline, the `merge` button
+When the pipeline fails in a merge request but it can be merged nonetheless,
+the **Merge** button will be colored in red.
+
### Post-merge pipeline status
When a merge request is merged, you can see the post-merge pipeline status of
diff --git a/doc/user/todos.md b/doc/user/todos.md
index 02fef07a89a..1fca3c0ab64 100644
--- a/doc/user/todos.md
+++ b/doc/user/todos.md
@@ -5,67 +5,75 @@ group: Project Management
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#designated-technical-writers
---
-# GitLab To-Do List
+# GitLab To-Do List **(CORE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/2817) in GitLab 8.5.
-When you log into GitLab, you normally want to see where you should spend your
-time, take some action, or know what you need to keep an eye on without
-a huge pile of e-mail notifications. GitLab is where you do your work,
-so being able to get started quickly is important.
+When you sign in to GitLab, you normally want to determine where you should
+spend your time. This can include taking an action, or keeping track of things
+(without having to read lots of email notifications). Because GitLab is where you
+do your work, being able to get started quickly is important.
-Your To-Do List offers a chronological list of items that are waiting for your input, all
-in a simple dashboard.
+Your *To-Do List* offers a chronological list of items waiting for your input
+(known as *to-dos*) in a single dashboard.
-![To Do screenshot showing a list of items to check on](img/todos_index.png)
+The To-Do List supports tracking [actions](#what-triggers-a-to-do) related to
+the following:
-You can quickly access your To-Do List by clicking the checkmark icon next to the
-search bar in the top navigation. If the count is:
+- Issues
+- Merge Requests
+- Epics **(ULTIMATE)**
-- Less than 100, the number in blue is the number of To-Do items.
-- 100 or more, the number displays as 99+. The exact number displays
- on the To-Do List.
-you still have open. Otherwise, the number displays as 99+. The exact number
-displays on the To-Do List.
+![to-do screenshot showing a list of items to check on](img/todos_index.png)
+
+You can access your To-Do List by clicking the **{task-done}** To-Do List icon
+next to the search bar in the top navigation. If the to-do item count is:
+
+- *Less than 100*, the number in blue is the number of to-do items.
+- *100 or more*, the number displays as 99+. The exact number displays in the
+ To-Do List.
![To Do icon](img/todos_icon.png)
-## What triggers a To Do
+## What triggers a to-do
-A To Do appears on your To-Do List when:
+A to-do item appears on your To-Do List when:
-- An issue or merge request is assigned to you
-- You are `@mentioned` in the description or comment of an:
- - Issue
- - Merge Request
- - Epic **(ULTIMATE)**
+- An issue or merge request is assigned to you.
+- You're `@mentioned` in the description or comment of an issue or merge request
+ (or epic **(ULTIMATE)**).
- You are `@mentioned` in a comment on a:
- Commit
- Design
-- The CI/CD pipeline for your merge request failed
-- An open merge request becomes unmergeable due to conflict, and one of the following is true:
- - You are the author
- - You are the user that set it to automatically merge once the pipeline succeeds
-- [Since GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/12136), a merge request
- is removed from a [merge train](../ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md)
- and you are the user that added it. **(PREMIUM)**
-
-When multiple trigger actions occur for the same user on the same object (for example, an issue)
-only the first is displayed as a single to-do on their To-Do List.
-
-To-do triggers are not affected by [GitLab Notification Email settings](profile/notifications.md).
+- The CI/CD pipeline for your merge request failed.
+- An open merge request becomes unmergeable due to conflict, and one of the
+ following is true:
+ - You're the author.
+ - You're the user that set the merge request to automatically merge after a
+ pipeline succeeds.
+- [Since GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/issues/12136), a
+ merge request is removed from a
+ [merge train](../ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md),
+ and you're the user that added it. **(PREMIUM)**
+
+When several trigger actions occur for the same user on the same object (for
+example, an issue), GitLab displays only the first action as a single to-do
+item.
+
+To-do triggers aren't affected by [GitLab notification email settings](profile/notifications.md).
NOTE: **Note:**
-When a user no longer has access to a resource related to a To Do (like an issue, merge request,
-project, or group) the related To-Do items are deleted within the next hour for security reasons.
-The delete is delayed to prevent data loss, in case the user's access was revoked by mistake.
+When a user no longer has access to a resource related to a to-do (such as an
+issue, merge request, project, or group), for security reasons GitLab deletes
+any related to-do items within the next hour. Deletion is delayed to prevent
+data loss, in the case where a user's access is accidentally revoked.
-### Directly addressing a To Do
+### Directly addressing a to-do
> [Introduced](https://gitlab.com/gitlab-org/gitlab-foss/-/merge_requests/7926) in GitLab 9.0.
-If you are mentioned at the start of a line, the To Do you receive will be listed
-as 'directly addressed'. For example, in this comment:
+If you're mentioned at the start of a line, the to-do you receive will be listed
+as *directly addressed*. For example, in the following comment:
```markdown
@alice What do you think? cc: @bob
@@ -79,81 +87,71 @@ as 'directly addressed'. For example, in this comment:
@erin @frank thank you!
```
-The people receiving directly addressed To-Do items are `@alice`, `@erin`, and
-`@frank`. Directly addressed To-Do items only differ from mentions in their type
+The people receiving directly addressed to-do items are `@alice`, `@erin`, and
+`@frank`. Directly addressed to-do items only differ from mentions in their type
for filtering purposes; otherwise, they appear as normal.
-### Manually creating a To Do
+### Manually creating a to-do
-You can also add the following to your To-Do List by clicking the **Add a To Do** button on an:
-
-- Issue
-- Merge Request
-- Epic **(ULTIMATE)**
+You can add an issue or merge request (or epic **(ULTIMATE)**) to your
+To-Do List by clicking its **Add a To Do** button.
![Adding a To Do from the issuable sidebar](img/todos_add_todo_sidebar.png)
-## Marking a To Do as done
-
-Any action to the following will mark the corresponding To Do as done:
+## Marking a to-do as done
-- Issue
-- Merge Request
-- Epic **(ULTIMATE)**
+Any action to an issue or merge request (or epic **(ULTIMATE)**) will mark its
+corresponding to-do as done.
-Actions that dismiss To-Do items include:
+Actions that dismiss to-do items include:
- Changing the assignee
- Changing the milestone
- Adding/removing a label
- Commenting on the issue
-Your To-Do List is personal, and items are only marked as done if the action comes from
-you. If you close the issue or merge request, your To Do is automatically
-marked as done.
-
-To prevent other users from closing issues without you being notified, if someone else closes, merges, or takes action on the any of the following, your To Do will remain pending:
-
-- Issue
-- Merge request
-- Epic **(ULTIMATE)**
+Your To-Do List is personal, and items are only marked as done if you take
+action. If you close the issue or merge request, your to-do is marked as done.
-There is just one To Do for each of these, so mentioning a user a hundred times in an issue will only trigger one To Do.
+To prevent other users from closing issues without you being notified, if
+someone else closes, merges, or takes action on an issue or merge request (or
+epic **(ULTIMATE)**), your to-do will remain pending.
-If no action is needed, you can manually mark the To Do as done by clicking the
-corresponding **Done** button, and it will disappear from your To-Do List.
+There's just one to-do for each of these, so mentioning a user many times in an
+issue will only trigger one to-do item.
-![A To Do in the To-Do List](img/todos_todo_list_item.png)
+If no action is needed, you can manually mark the to-do as done by clicking its
+corresponding **Done** button to have GitLab remove the item from your
+To-Do List.
-You can also mark a To Do as done by clicking the **Mark as done** button in the sidebar of the following:
+![A to-do in the To-Do List](img/todos_todo_list_item.png)
-- Issue
-- Merge Request
-- Epic **(ULTIMATE)**
+You can also mark a to-do as done by clicking the **Mark as done** button in the
+sidebar of an issue or merge request (or epic **(ULTIMATE)**).
![Mark as done from the issuable sidebar](img/todos_mark_done_sidebar.png)
-You can mark all your To-Do items as done at once by clicking the **Mark all as
-done** button.
+You can mark all your to-do items as done at once by clicking the
+**Mark all as done** button.
## Filtering your To-Do List
-There are four kinds of filters you can use on your To-Do List.
+You can use the following types of filters with your To-Do List:
-| Filter | Description |
-| ------- | ----------- |
-| Project | Filter by project |
-| Group | Filter by group |
-| Author | Filter by the author that triggered the To Do |
-| Type | Filter by issue, merge request, design, or epic **(ULTIMATE)** |
-| Action | Filter by the action that triggered the To Do |
+| Filter | Description |
+| ------- | ---------------------------------------------------------------- |
+| Project | Filter by project. |
+| Group | Filter by group. |
+| Author | Filter by the author that triggered the To Do. |
+| Type | Filter by issue, merge request, design, or epic. **(ULTIMATE)** |
+| Action | Filter by the action that triggered the To Do. |
-You can also filter by more than one of these at the same time. The possible Actions are
-[described above](#what-triggers-a-to-do) and include:
+You can also filter by more than one of these at the same time. The previously
+described [triggering actions](#what-triggers-a-to-do) include:
-- Any Action
+- Any action
- Assigned
- Mentioned
- Added
- Pipelines
-- Directly Addressed
+- Directly addressed
diff --git a/lib/api/sidekiq_metrics.rb b/lib/api/sidekiq_metrics.rb
index de1373144e3..77f2b1e871e 100644
--- a/lib/api/sidekiq_metrics.rb
+++ b/lib/api/sidekiq_metrics.rb
@@ -17,7 +17,7 @@ module API
end
def process_metrics
- Sidekiq::ProcessSet.new.map do |process|
+ Sidekiq::ProcessSet.new(false).map do |process|
{
hostname: process['hostname'],
pid: process['pid'],
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 337dfe3d4d3..9082f355494 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -7005,6 +7005,9 @@ msgstr ""
msgid "Contributions per group member"
msgstr ""
+msgid "Contributor"
+msgstr ""
+
msgid "Contributors"
msgstr ""
@@ -25843,9 +25846,6 @@ msgstr ""
msgid "This is a security log of important events involving your account."
msgstr ""
-msgid "This is the author's first Merge Request to this project."
-msgstr ""
-
msgid "This is the highest peak of users on your installation since the license started."
msgstr ""
@@ -26083,6 +26083,15 @@ msgstr ""
msgid "This user has no identities"
msgstr ""
+msgid "This user has previously committed to the %{name} project."
+msgstr ""
+
+msgid "This user is a %{access} of the %{name} project."
+msgstr ""
+
+msgid "This user is the author of this %{noteable}."
+msgstr ""
+
msgid "This user will be the author of all events in the activity feed that are the result of an update, like new branches being created or new commits being pushed to existing branches."
msgstr ""
diff --git a/spec/features/markdown/keyboard_shortcuts_spec.rb b/spec/features/markdown/keyboard_shortcuts_spec.rb
index ff028a0281f..81b1928658c 100644
--- a/spec/features/markdown/keyboard_shortcuts_spec.rb
+++ b/spec/features/markdown/keyboard_shortcuts_spec.rb
@@ -6,6 +6,10 @@ RSpec.describe 'Markdown keyboard shortcuts', :js do
let_it_be(:project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
+ let(:is_mac) { page.evaluate_script('navigator.platform').include?('Mac') }
+ let(:modifier_key) { is_mac ? :command : :control }
+ let(:other_modifier_key) { is_mac ? :control : :command }
+
before do
project.add_developer(user)
@@ -16,7 +20,7 @@ RSpec.describe 'Markdown keyboard shortcuts', :js do
wait_for_requests
end
- shared_examples 'keyboard shortcuts for modifier key' do
+ shared_examples 'keyboard shortcuts' do
it 'bolds text when <modifier>+B is pressed' do
type_and_select('bold')
@@ -57,17 +61,29 @@ RSpec.describe 'Markdown keyboard shortcuts', :js do
end
end
- shared_examples 'keyboard shortcuts for implementation' do
- context 'Ctrl key' do
- let(:modifier_key) { :control }
+ shared_examples 'no side effects' do
+ it 'does not bold text when <other modifier>+B is pressed' do
+ type_and_select('bold')
+
+ markdown_field.send_keys([@other_modifier_key, 'b'])
+
+ expect(markdown_field.value).not_to eq('**bold**')
+ end
+
+ it 'does not italicize text when <other modifier>+I is pressed' do
+ type_and_select('italic')
+
+ markdown_field.send_keys([@other_modifier_key, 'i'])
- it_behaves_like 'keyboard shortcuts for modifier key'
+ expect(markdown_field.value).not_to eq('_italic_')
end
- context '⌘ key' do
- let(:modifier_key) { :command }
+ it 'does not link text when <other modifier>+K is pressed' do
+ type_and_select('link')
+
+ markdown_field.send_keys([@other_modifier_key, 'k'])
- it_behaves_like 'keyboard shortcuts for modifier key'
+ expect(markdown_field.value).not_to eq('[link](url)')
end
end
@@ -76,7 +92,8 @@ RSpec.describe 'Markdown keyboard shortcuts', :js do
let(:markdown_field) { find_field('Release notes') }
let(:non_markdown_field) { find_field('Release title') }
- it_behaves_like 'keyboard shortcuts for implementation'
+ it_behaves_like 'keyboard shortcuts'
+ it_behaves_like 'no side effects'
end
context 'Haml markdown editor' do
@@ -84,7 +101,8 @@ RSpec.describe 'Markdown keyboard shortcuts', :js do
let(:markdown_field) { find_field('Description') }
let(:non_markdown_field) { find_field('Title') }
- it_behaves_like 'keyboard shortcuts for implementation'
+ it_behaves_like 'keyboard shortcuts'
+ it_behaves_like 'no side effects'
end
def type_and_select(text)
diff --git a/spec/fixtures/api/schemas/entities/discussion.json b/spec/fixtures/api/schemas/entities/discussion.json
index 21d8efe0b2b..2afabcc9195 100644
--- a/spec/fixtures/api/schemas/entities/discussion.json
+++ b/spec/fixtures/api/schemas/entities/discussion.json
@@ -60,6 +60,9 @@
"resolve_with_issue_path": { "type": "string" },
"cached_markdown_version": { "type": "integer" },
"human_access": { "type": ["string", "null"] },
+ "is_noteable_author": { "type": "boolean" },
+ "is_contributor": { "type": "boolean" },
+ "project_name": { "type": "string" },
"toggle_award_path": { "type": "string" },
"path": { "type": "string" },
"commands_changes": { "type": "object", "additionalProperties": true },
diff --git a/spec/frontend/groups/components/invite_members_banner_spec.js b/spec/frontend/groups/components/invite_members_banner_spec.js
index e2e7af624f8..95003b211fd 100644
--- a/spec/frontend/groups/components/invite_members_banner_spec.js
+++ b/spec/frontend/groups/components/invite_members_banner_spec.js
@@ -1,7 +1,8 @@
import { shallowMount } from '@vue/test-utils';
import { GlBanner } from '@gitlab/ui';
-import InviteMembersBanner from '~/groups/components/invite_members_banner.vue';
+import { mockTracking, unmockTracking } from 'helpers/tracking_helper';
import { setCookie, parseBoolean } from '~/lib/utils/common_utils';
+import InviteMembersBanner from '~/groups/components/invite_members_banner.vue';
jest.mock('~/lib/utils/common_utils');
@@ -12,6 +13,7 @@ const body =
const svgPath = '/illustrations/background';
const inviteMembersPath = 'groups/members';
const buttonText = 'Invite your colleagues';
+const trackLabel = 'invite_members_banner';
const createComponent = (stubs = {}) => {
return shallowMount(InviteMembersBanner, {
@@ -19,6 +21,7 @@ const createComponent = (stubs = {}) => {
svgPath,
inviteMembersPath,
isDismissedKey,
+ trackLabel,
},
stubs,
});
@@ -26,10 +29,51 @@ const createComponent = (stubs = {}) => {
describe('InviteMembersBanner', () => {
let wrapper;
+ let trackingSpy;
+
+ beforeEach(() => {
+ document.body.dataset.page = 'any:page';
+ trackingSpy = mockTracking('_category_', undefined, jest.spyOn);
+ });
afterEach(() => {
wrapper.destroy();
wrapper = null;
+ unmockTracking();
+ });
+
+ describe('tracking', () => {
+ beforeEach(() => {
+ wrapper = createComponent({ GlBanner });
+ });
+
+ const trackCategory = undefined;
+ const displayEvent = 'invite_members_banner_displayed';
+ const buttonClickEvent = 'invite_members_banner_button_clicked';
+ const dismissEvent = 'invite_members_banner_dismissed';
+
+ it('sends the displayEvent when the banner is displayed', () => {
+ expect(trackingSpy).toHaveBeenCalledWith(trackCategory, displayEvent, {
+ label: trackLabel,
+ });
+ });
+
+ it('sets the button attributes for the buttonClickEvent', () => {
+ const button = wrapper.find(`[href='${wrapper.vm.inviteMembersPath}']`);
+
+ expect(button.attributes()).toMatchObject({
+ 'data-track-event': buttonClickEvent,
+ 'data-track-label': trackLabel,
+ });
+ });
+
+ it('sends the dismissEvent when the banner is dismissed', () => {
+ wrapper.find(GlBanner).vm.$emit('close');
+
+ expect(trackingSpy).toHaveBeenCalledWith(trackCategory, dismissEvent, {
+ label: trackLabel,
+ });
+ });
});
describe('rendering', () => {
diff --git a/spec/frontend/notes/components/note_actions_spec.js b/spec/frontend/notes/components/note_actions_spec.js
index 97d1752726b..a79c3bbacb7 100644
--- a/spec/frontend/notes/components/note_actions_spec.js
+++ b/spec/frontend/notes/components/note_actions_spec.js
@@ -35,8 +35,12 @@ describe('noteActions', () => {
canEdit: true,
canAwardEmoji: true,
canReportAsAbuse: true,
+ isAuthor: true,
+ isContributor: false,
+ noteableType: 'MergeRequest',
noteId: '539',
noteUrl: `${TEST_HOST}/group/project/-/merge_requests/1#note_1`,
+ projectName: 'project',
reportAbusePath: `${TEST_HOST}/abuse_reports/new?ref_url=http%3A%2F%2Flocalhost%3A3000%2Fgitlab-org%2Fgitlab-ce%2Fissues%2F7%23note_539&user_id=26`,
showReply: false,
};
@@ -60,15 +64,43 @@ describe('noteActions', () => {
wrapper = shallowMountNoteActions(props);
});
+ it('should render noteable author badge', () => {
+ expect(
+ wrapper
+ .findAll('.note-role')
+ .at(0)
+ .text()
+ .trim(),
+ ).toEqual('Author');
+ });
+
it('should render access level badge', () => {
expect(
wrapper
- .find('.note-role')
+ .findAll('.note-role')
+ .at(1)
.text()
.trim(),
).toEqual(props.accessLevel);
});
+ it('should render contributor badge', () => {
+ wrapper.setProps({
+ accessLevel: null,
+ isContributor: true,
+ });
+
+ return wrapper.vm.$nextTick().then(() => {
+ expect(
+ wrapper
+ .findAll('.note-role')
+ .at(1)
+ .text()
+ .trim(),
+ ).toBe('Contributor');
+ });
+ });
+
it('should render emoji link', () => {
expect(wrapper.find('.js-add-award').exists()).toBe(true);
expect(wrapper.find('.js-add-award').attributes('data-position')).toBe('right');
diff --git a/spec/frontend/shortcuts_spec.js b/spec/frontend/shortcuts_spec.js
index 21258e09356..538b3afa50f 100644
--- a/spec/frontend/shortcuts_spec.js
+++ b/spec/frontend/shortcuts_spec.js
@@ -63,9 +63,9 @@ describe('Shortcuts', () => {
// Get all shortcuts specified with md-shortcuts attributes in the fixture.
// `shortcuts` will look something like this:
// [
- // [ 'command+b', 'ctrl+b' ],
- // [ 'command+i', 'ctrl+i' ],
- // [ 'command+k', 'ctrl+k' ]
+ // [ 'mod+b' ],
+ // [ 'mod+i' ],
+ // [ 'mod+k' ]
// ]
shortcuts = $('.edit-note .js-md')
.map(function getShortcutsFromToolbarBtn() {
diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb
index 3a3b5e4bdf6..15972f66fd6 100644
--- a/spec/models/group_spec.rb
+++ b/spec/models/group_spec.rb
@@ -653,6 +653,19 @@ RSpec.describe Group do
expect(shared_group.max_member_access_for_user(user)).to eq(Gitlab::Access::MAINTAINER)
end
end
+
+ context 'evaluating admin access level' do
+ let_it_be(:admin) { create(:admin) }
+
+ it 'returns OWNER by default' do
+ expect(group.max_member_access_for_user(admin)).to eq(Gitlab::Access::OWNER)
+ end
+
+ it 'returns NO_ACCESS when only concrete membership should be considered' do
+ expect(group.max_member_access_for_user(admin, only_concrete_membership: true))
+ .to eq(Gitlab::Access::NO_ACCESS)
+ end
+ end
end
describe '#members_with_parents' do
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 3b453a11090..a3417ee5fc7 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -286,6 +286,56 @@ RSpec.describe Note do
end
end
+ describe "noteable_author?" do
+ let(:user1) { create(:user) }
+ let(:user2) { create(:user) }
+ let(:project) { create(:project, :public, :repository) }
+
+ context 'when note is on commit' do
+ let(:noteable) { create(:commit, project: project, author: user1) }
+
+ context 'if user is the noteable author' do
+ let(:note) { create(:discussion_note_on_commit, commit_id: noteable.id, project: project, author: user1) }
+ let(:diff_note) { create(:diff_note_on_commit, commit_id: noteable.id, project: project, author: user1) }
+
+ it 'returns true' do
+ expect(note.noteable_author?(noteable)).to be true
+ expect(diff_note.noteable_author?(noteable)).to be true
+ end
+ end
+
+ context 'if user is not the noteable author' do
+ let(:note) { create(:discussion_note_on_commit, commit_id: noteable.id, project: project, author: user2) }
+ let(:diff_note) { create(:diff_note_on_commit, commit_id: noteable.id, project: project, author: user2) }
+
+ it 'returns false' do
+ expect(note.noteable_author?(noteable)).to be false
+ expect(diff_note.noteable_author?(noteable)).to be false
+ end
+ end
+ end
+
+ context 'when note is on issue' do
+ let(:noteable) { create(:issue, project: project, author: user1) }
+
+ context 'if user is the noteable author' do
+ let(:note) { create(:note, noteable: noteable, author: user1, project: project) }
+
+ it 'returns true' do
+ expect(note.noteable_author?(noteable)).to be true
+ end
+ end
+
+ context 'if user is not the noteable author' do
+ let(:note) { create(:note, noteable: noteable, author: user2, project: project) }
+
+ it 'returns false' do
+ expect(note.noteable_author?(noteable)).to be false
+ end
+ end
+ end
+ end
+
describe "edited?" do
let(:note) { build(:note, updated_by_id: nil, created_at: Time.current, updated_at: Time.current + 5.hours) }
@@ -1228,22 +1278,6 @@ RSpec.describe Note do
end
end
- describe '#special_role=' do
- let(:role) { Note::SpecialRole::FIRST_TIME_CONTRIBUTOR }
-
- it 'assigns role' do
- subject.special_role = role
-
- expect(subject.special_role).to eq(role)
- end
-
- it 'does not assign unknown role' do
- expect { subject.special_role = :bogus }.to raise_error(/Role is undefined/)
-
- expect(subject.special_role).to be_nil
- end
- end
-
describe '#parent' do
it 'returns project for project notes' do
project = create(:project)
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index 34ec856459c..bbc056889d6 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -3,6 +3,8 @@
require "spec_helper"
RSpec.describe ProjectTeam do
+ include ProjectForksHelper
+
let(:maintainer) { create(:user) }
let(:reporter) { create(:user) }
let(:guest) { create(:user) }
@@ -237,6 +239,35 @@ RSpec.describe ProjectTeam do
end
end
+ describe '#contributor?' do
+ let(:project) { create(:project, :public, :repository) }
+
+ context 'when user is a member of project' do
+ before do
+ project.add_maintainer(maintainer)
+ project.add_reporter(reporter)
+ project.add_guest(guest)
+ end
+
+ it { expect(project.team.contributor?(maintainer.id)).to be false }
+ it { expect(project.team.contributor?(reporter.id)).to be false }
+ it { expect(project.team.contributor?(guest.id)).to be false }
+ end
+
+ context 'when user has at least one merge request merged into default_branch' do
+ let(:contributor) { create(:user) }
+ let(:user_without_access) { create(:user) }
+ let(:first_fork_project) { fork_project(project, contributor, repository: true) }
+
+ before do
+ create(:merge_request, :merged, author: contributor, target_project: project, source_project: first_fork_project, target_branch: project.default_branch.to_s)
+ end
+
+ it { expect(project.team.contributor?(contributor.id)).to be true }
+ it { expect(project.team.contributor?(user_without_access.id)).to be false }
+ end
+ end
+
describe '#max_member_access' do
let(:requester) { create(:user) }
@@ -366,6 +397,66 @@ RSpec.describe ProjectTeam do
end
end
+ describe '#contribution_check_for_user_ids', :request_store do
+ let(:project) { create(:project, :public, :repository) }
+ let(:contributor) { create(:user) }
+ let(:second_contributor) { create(:user) }
+ let(:user_without_access) { create(:user) }
+ let(:first_fork_project) { fork_project(project, contributor, repository: true) }
+ let(:second_fork_project) { fork_project(project, second_contributor, repository: true) }
+
+ let(:users) do
+ [contributor, second_contributor, user_without_access].map(&:id)
+ end
+
+ let(:expected) do
+ {
+ contributor.id => true,
+ second_contributor.id => true,
+ user_without_access.id => false
+ }
+ end
+
+ before do
+ create(:merge_request, :merged, author: contributor, target_project: project, source_project: first_fork_project, target_branch: project.default_branch.to_s)
+ create(:merge_request, :merged, author: second_contributor, target_project: project, source_project: second_fork_project, target_branch: project.default_branch.to_s)
+ end
+
+ def contributors(users)
+ project.team.contribution_check_for_user_ids(users)
+ end
+
+ it 'does not perform extra queries when asked for users who have already been found' do
+ contributors(users)
+
+ expect { contributors([contributor.id]) }.not_to exceed_query_limit(0)
+
+ expect(contributors([contributor.id])).to eq(expected)
+ end
+
+ it 'only requests the extra users when uncached users are passed' do
+ new_contributor = create(:user)
+ new_fork_project = fork_project(project, new_contributor, repository: true)
+ second_new_user = create(:user)
+ all_users = users + [new_contributor.id, second_new_user.id]
+ create(:merge_request, :merged, author: new_contributor, target_project: project, source_project: new_fork_project, target_branch: project.default_branch.to_s)
+
+ expected_all = expected.merge(new_contributor.id => true,
+ second_new_user.id => false)
+
+ contributors(users)
+
+ queries = ActiveRecord::QueryRecorder.new { contributors(all_users) }
+
+ expect(queries.count).to eq(1)
+ expect(contributors([new_contributor.id])).to eq(expected_all)
+ end
+
+ it 'returns correct contributors' do
+ expect(contributors(users)).to eq(expected)
+ end
+ end
+
shared_examples 'max member access for users' do
let(:project) { create(:project) }
let(:group) { create(:group) }
@@ -438,9 +529,9 @@ RSpec.describe ProjectTeam do
it 'does not perform extra queries when asked for users who have already been found' do
access_levels(users)
- expect { access_levels(users) }.not_to exceed_query_limit(0)
+ expect { access_levels([maintainer.id]) }.not_to exceed_query_limit(0)
- expect(access_levels(users)).to eq(expected)
+ expect(access_levels([maintainer.id])).to eq(expected)
end
it 'only requests the extra users when uncached users are passed' do
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index b4705c4cc55..831b0d6e678 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -490,6 +490,25 @@ RSpec.describe API::Projects do
expect(json_response.first['name']).to eq(project4.name)
expect(json_response.first['owner']['username']).to eq(user4.username)
end
+
+ context 'when admin creates a project' do
+ before do
+ group = create(:group)
+ project_create_opts = {
+ name: 'GitLab',
+ namespace_id: group.id
+ }
+
+ Projects::CreateService.new(admin, project_create_opts).execute
+ end
+
+ it 'does not list as owned project for admin' do
+ get api('/projects', admin), params: { owned: true }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response).to be_empty
+ end
+ end
end
context 'and with starred=true' do