+
+
import { GlSearchBoxByType } from '@gitlab/ui';
-import { uniq } from 'lodash';
-import { EXCLUDED_NODES, HIDE_CLASS, HIGHLIGHT_CLASS, TYPING_DELAY } from '../constants';
+import { uniq, escapeRegExp } from 'lodash';
+import {
+ EXCLUDED_NODES,
+ HIDE_CLASS,
+ HIGHLIGHT_CLASS,
+ NONE_PADDING_CLASS,
+ TYPING_DELAY,
+} from '../constants';
const origExpansions = new Map();
@@ -37,9 +43,13 @@ const resetSections = ({ sectionSelector }) => {
};
const clearHighlights = () => {
- document
- .querySelectorAll(`.${HIGHLIGHT_CLASS}`)
- .forEach((element) => element.classList.remove(HIGHLIGHT_CLASS));
+ document.querySelectorAll(`.${HIGHLIGHT_CLASS}`).forEach((element) => {
+ const { parentNode } = element;
+ const textNode = document.createTextNode(element.textContent);
+ parentNode.replaceChild(textNode, element);
+
+ parentNode.normalize();
+ });
};
const hideSectionsExcept = (sectionSelector, visibleSections) => {
@@ -50,17 +60,41 @@ const hideSectionsExcept = (sectionSelector, visibleSections) => {
});
};
-const highlightElements = (elements = []) => {
- elements.forEach((element) => element.classList.add(HIGHLIGHT_CLASS));
+const transformMatchElement = (element, searchTerm) => {
+ const textStr = element.textContent;
+ const escapedSearchTerm = new RegExp(`(${escapeRegExp(searchTerm)})`, 'gi');
+
+ const textList = textStr.split(escapedSearchTerm);
+ const replaceFragment = document.createDocumentFragment();
+ textList.forEach((text) => {
+ let addElement = document.createTextNode(text);
+ if (escapedSearchTerm.test(text)) {
+ addElement = document.createElement('mark');
+ addElement.className = `${HIGHLIGHT_CLASS} ${NONE_PADDING_CLASS}`;
+ addElement.textContent = text;
+ escapedSearchTerm.lastIndex = 0;
+ }
+ replaceFragment.appendChild(addElement);
+ });
+
+ return replaceFragment;
};
-const displayResults = ({ sectionSelector, expandSection }, matches) => {
+const highlightElements = (elements = [], searchTerm) => {
+ elements.forEach((element) => {
+ const replaceFragment = transformMatchElement(element, searchTerm);
+ element.innerHTML = '';
+ element.appendChild(replaceFragment);
+ });
+};
+
+const displayResults = ({ sectionSelector, expandSection, searchTerm }, matches) => {
const elements = matches.map((match) => match.parentElement);
const sections = uniq(elements.map((element) => findSettingsSection(sectionSelector, element)));
hideSectionsExcept(sectionSelector, sections);
sections.forEach(expandSection);
- highlightElements(elements);
+ highlightElements(elements, searchTerm);
};
const clearResults = (params) => {
@@ -116,21 +150,21 @@ export default {
},
methods: {
search(value) {
+ this.searchTerm = value;
const displayOptions = {
sectionSelector: this.sectionSelector,
expandSection: this.expandSection,
collapseSection: this.collapseSection,
isExpanded: this.isExpandedFn,
+ searchTerm: this.searchTerm,
};
- this.searchTerm = value;
-
clearResults(displayOptions);
if (value.length) {
saveExpansionState(document.querySelectorAll(this.sectionSelector), displayOptions);
- displayResults(displayOptions, search(this.searchRoot, value));
+ displayResults(displayOptions, search(this.searchRoot, this.searchTerm));
} else {
restoreExpansionState(displayOptions);
}
diff --git a/app/assets/javascripts/search_settings/constants.js b/app/assets/javascripts/search_settings/constants.js
index 9452d149122..a49351dc7b0 100644
--- a/app/assets/javascripts/search_settings/constants.js
+++ b/app/assets/javascripts/search_settings/constants.js
@@ -7,5 +7,8 @@ export const HIDE_CLASS = 'gl-display-none';
// used to highlight the text that matches the * search term
export const HIGHLIGHT_CLASS = 'gl-bg-orange-100';
+// used to remove padding for text that matches the * search term
+export const NONE_PADDING_CLASS = 'gl-p-0';
+
// How many seconds to wait until the user * stops typing
export const TYPING_DELAY = 400;
diff --git a/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue b/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue
index af426584f4f..d5647619ea3 100644
--- a/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue
+++ b/app/assets/javascripts/sidebar/components/labels/sidebar_labels.vue
@@ -36,6 +36,7 @@ export default {
'allowLabelEdit',
'allowScopedLabels',
'iid',
+ 'fullPath',
'initiallySelectedLabels',
'issuableType',
'labelsFetchPath',
@@ -145,6 +146,8 @@ export default {
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue
index 0f660c92e7c..a2ed08e6b28 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_create_view.vue
@@ -19,16 +19,20 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
- inject: {
- fullPath: {
- default: '',
- },
- },
props: {
issuableType: {
type: String,
required: true,
},
+ fullPath: {
+ type: String,
+ required: true,
+ },
+ attrWorkspacePath: {
+ type: String,
+ required: false,
+ default: undefined,
+ },
},
data() {
return {
@@ -46,11 +50,19 @@ export default {
return Object.keys(colorsMap).map((color) => ({ [color]: colorsMap[color] }));
},
mutationVariables() {
- return this.issuableType === IssuableType.Epic
+ if (this.issuableType === IssuableType.Epic) {
+ return {
+ title: this.labelTitle,
+ color: this.selectedColor,
+ groupPath: this.fullPath,
+ };
+ }
+
+ return this.attrWorkspacePath !== undefined
? {
title: this.labelTitle,
color: this.selectedColor,
- groupPath: this.fullPath,
+ groupPath: this.attrWorkspacePath,
}
: {
title: this.labelTitle,
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue
index 2215f2cba91..e6a25362ff0 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/dropdown_contents_labels_view.vue
@@ -24,7 +24,6 @@ export default {
GlIntersectionObserver,
LabelItem,
},
- inject: ['fullPath'],
model: {
prop: 'localSelectedLabels',
},
@@ -45,6 +44,10 @@ export default {
type: Array,
required: true,
},
+ fullPath: {
+ type: String,
+ required: true,
+ },
},
data() {
return {
@@ -84,7 +87,7 @@ export default {
return this.$apollo.queries.labels.loading;
},
localSelectedLabelsIds() {
- return this.localSelectedLabels.map((label) => label.id);
+ return this.localSelectedLabels.map((label) => getIdFromGraphQLId(label.id));
},
visibleLabels() {
if (this.searchKey) {
@@ -130,7 +133,9 @@ export default {
updateSelectedLabels(label) {
let labels;
if (this.isLabelSelected(label)) {
- labels = this.localSelectedLabels.filter(({ id }) => id !== getIdFromGraphQLId(label.id));
+ labels = this.localSelectedLabels.filter(
+ ({ id }) => id !== getIdFromGraphQLId(label.id) && id !== label.id,
+ );
} else {
labels = [
...this.localSelectedLabels,
diff --git a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue
index e4ec7909bdd..6bd43da2203 100644
--- a/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue
+++ b/app/assets/javascripts/vue_shared/components/sidebar/labels_select_widget/labels_select_root.vue
@@ -21,15 +21,20 @@ export default {
SidebarEditableItem,
},
inject: {
- iid: {
- default: '',
- },
allowLabelEdit: {
default: false,
},
- fullPath: {},
},
props: {
+ iid: {
+ type: String,
+ required: false,
+ default: '',
+ },
+ fullPath: {
+ type: String,
+ required: true,
+ },
allowLabelRemove: {
type: Boolean,
required: false,
@@ -99,6 +104,11 @@ export default {
type: String,
required: true,
},
+ attrWorkspacePath: {
+ type: String,
+ required: false,
+ default: undefined,
+ },
},
data() {
return {
@@ -206,6 +216,8 @@ export default {
:variant="variant"
:issuable-type="issuableType"
:is-visible="edit"
+ :full-path="fullPath"
+ :attr-workspace-path="attrWorkspacePath"
@setLabels="handleDropdownClose"
@closeDropdown="collapseEditableItem"
/>
@@ -224,6 +236,7 @@ export default {
:selected-labels="selectedLabels"
:variant="variant"
:issuable-type="issuableType"
+ :full-path="fullPath"
@setLabels="handleDropdownClose"
/>
diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss
index 6296b023e90..a2d822bb5de 100644
--- a/app/assets/stylesheets/pages/issuable.scss
+++ b/app/assets/stylesheets/pages/issuable.scss
@@ -877,18 +877,6 @@
vertical-align: sub;
}
-.suggestion-confidential {
- color: $orange-500;
-}
-
-.suggestion-state-open {
- color: $green-500;
-}
-
-.suggestion-state-closed {
- color: $blue-500;
-}
-
.suggestion-help-hover {
cursor: help;
}
diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb
index 60708c13b85..e8e6a7e5c1a 100644
--- a/app/controllers/groups/boards_controller.rb
+++ b/app/controllers/groups/boards_controller.rb
@@ -11,6 +11,7 @@ class Groups::BoardsController < Groups::ApplicationController
push_frontend_feature_flag(:board_multi_select, group, default_enabled: :yaml)
push_frontend_feature_flag(:swimlanes_buffered_rendering, group, default_enabled: :yaml)
push_frontend_feature_flag(:iteration_cadences, group, default_enabled: :yaml)
+ push_frontend_feature_flag(:labels_widget, group, default_enabled: :yaml)
end
feature_category :boards
diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb
index 558a4bbfd45..6e59f159636 100644
--- a/app/controllers/groups/group_members_controller.rb
+++ b/app/controllers/groups/group_members_controller.rb
@@ -54,7 +54,7 @@ class Groups::GroupMembersController < Groups::ApplicationController
end
def invited_members
- group_members.invite
+ group_members.invite.with_invited_user_state
end
def non_invited_members
diff --git a/app/controllers/import/bulk_imports_controller.rb b/app/controllers/import/bulk_imports_controller.rb
index 2dca9385da6..bec26cb547d 100644
--- a/app/controllers/import/bulk_imports_controller.rb
+++ b/app/controllers/import/bulk_imports_controller.rb
@@ -22,13 +22,16 @@ class Import::BulkImportsController < ApplicationController
def status
respond_to do |format|
format.json do
- data = importable_data
+ data = ::BulkImports::GetImportableDataService.new(params, query_params, credentials).execute
pagination_headers.each do |header|
- response.set_header(header, data.headers[header])
+ response.set_header(header, data[:response].headers[header])
end
- render json: { importable_data: serialized_data(data.parsed_response) }
+ json_response = { importable_data: serialized_data(data[:response].parsed_response) }
+ json_response[:version_validation] = data[:version_validation]
+
+ render json: json_response
end
format.html do
@source_url = session[url_key]
@@ -66,10 +69,6 @@ class Import::BulkImportsController < ApplicationController
@serializer ||= BaseSerializer.new(current_user: current_user)
end
- def importable_data
- client.get('groups', query_params)
- end
-
# Default query string params used to fetch groups from GitLab source instance
#
# top_level_only: fetch only top level groups (subgroups are fetched during import itself)
@@ -85,15 +84,6 @@ class Import::BulkImportsController < ApplicationController
query_params
end
- def client
- @client ||= BulkImports::Clients::HTTP.new(
- url: session[url_key],
- token: session[access_token_key],
- per_page: params[:per_page],
- page: params[:page]
- )
- end
-
def configure_params
params.permit(access_token_key, url_key)
end
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index 316582f3994..834e4baa7dd 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -11,6 +11,7 @@ class Projects::BoardsController < Projects::ApplicationController
push_frontend_feature_flag(:issue_boards_filtered_search, project, default_enabled: :yaml)
push_frontend_feature_flag(:board_multi_select, project, default_enabled: :yaml)
push_frontend_feature_flag(:iteration_cadences, project&.group, default_enabled: :yaml)
+ push_frontend_feature_flag(:labels_widget, project, default_enabled: :yaml)
end
feature_category :boards
diff --git a/app/controllers/projects/ci/pipeline_editor_controller.rb b/app/controllers/projects/ci/pipeline_editor_controller.rb
index 953b9d83a66..e925bbea5e9 100644
--- a/app/controllers/projects/ci/pipeline_editor_controller.rb
+++ b/app/controllers/projects/ci/pipeline_editor_controller.rb
@@ -4,6 +4,7 @@ class Projects::Ci::PipelineEditorController < Projects::ApplicationController
before_action :check_can_collaborate!
before_action do
push_frontend_feature_flag(:pipeline_editor_drawer, @project, default_enabled: :yaml)
+ push_frontend_feature_flag(:pipeline_editor_mini_graph, @project, default_enabled: :yaml)
push_frontend_feature_flag(:schema_linting, @project, default_enabled: :yaml)
end
diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb
index cce2497ae25..e8074f7d793 100644
--- a/app/controllers/projects/project_members_controller.rb
+++ b/app/controllers/projects/project_members_controller.rb
@@ -58,7 +58,7 @@ class Projects::ProjectMembersController < Projects::ApplicationController
end
def invited_members
- members.invite
+ members.invite.with_invited_user_state
end
def non_invited_members
diff --git a/app/controllers/repositories/git_http_controller.rb b/app/controllers/repositories/git_http_controller.rb
index e51bfe6a37e..c3c6a51239d 100644
--- a/app/controllers/repositories/git_http_controller.rb
+++ b/app/controllers/repositories/git_http_controller.rb
@@ -11,6 +11,9 @@ module Repositories
rescue_from Gitlab::GitAccess::NotFoundError, with: :render_404_with_exception
rescue_from Gitlab::GitAccessProject::CreationError, with: :render_422_with_exception
rescue_from Gitlab::GitAccess::TimeoutError, with: :render_503_with_exception
+ rescue_from GRPC::Unavailable do |e|
+ render_503_with_exception(e, message: 'The git server, Gitaly, is not available at this time. Please contact your administrator.')
+ end
# GET /foo/bar.git/info/refs?service=git-upload-pack (git pull)
# GET /foo/bar.git/info/refs?service=git-receive-pack (git push)
@@ -71,8 +74,8 @@ module Repositories
render plain: exception.message, status: :unprocessable_entity
end
- def render_503_with_exception(exception)
- render plain: exception.message, status: :service_unavailable
+ def render_503_with_exception(exception, message: nil)
+ render plain: message || exception.message, status: :service_unavailable
end
def update_fetch_statistics
diff --git a/app/graphql/types/packages/nuget/metadatum_type.rb b/app/graphql/types/packages/nuget/metadatum_type.rb
index ed9d97724af..b58fd954a74 100644
--- a/app/graphql/types/packages/nuget/metadatum_type.rb
+++ b/app/graphql/types/packages/nuget/metadatum_type.rb
@@ -10,9 +10,9 @@ module Types
authorize :read_package
field :id, ::Types::GlobalIDType[::Packages::Nuget::Metadatum], null: false, description: 'ID of the metadatum.'
- field :license_url, GraphQL::Types::String, null: false, description: 'License URL of the Nuget package.'
- field :project_url, GraphQL::Types::String, null: false, description: 'Project URL of the Nuget package.'
- field :icon_url, GraphQL::Types::String, null: false, description: 'Icon URL of the Nuget package.'
+ field :license_url, GraphQL::Types::String, null: true, description: 'License URL of the Nuget package.'
+ field :project_url, GraphQL::Types::String, null: true, description: 'Project URL of the Nuget package.'
+ field :icon_url, GraphQL::Types::String, null: true, description: 'Icon URL of the Nuget package.'
end
end
end
diff --git a/app/models/bulk_import.rb b/app/models/bulk_import.rb
index dee55675304..818ae04ba29 100644
--- a/app/models/bulk_import.rb
+++ b/app/models/bulk_import.rb
@@ -4,7 +4,8 @@
# projects to a GitLab instance. It associates the import with the responsible
# user.
class BulkImport < ApplicationRecord
- MINIMUM_GITLAB_MAJOR_VERSION = 14
+ MIN_MAJOR_VERSION = 14
+ MIN_MINOR_VERSION_FOR_PROJECT = 4
belongs_to :user, optional: false
@@ -34,6 +35,14 @@ class BulkImport < ApplicationRecord
end
end
+ def source_version_info
+ Gitlab::VersionInfo.parse(source_version)
+ end
+
+ def self.min_gl_version_for_project_migration
+ Gitlab::VersionInfo.new(MIN_MAJOR_VERSION, MIN_MINOR_VERSION_FOR_PROJECT)
+ end
+
def self.all_human_statuses
state_machine.states.map(&:human_name)
end
diff --git a/app/models/bulk_imports/entity.rb b/app/models/bulk_imports/entity.rb
index d2b9e1f567a..ecac4ab95f4 100644
--- a/app/models/bulk_imports/entity.rb
+++ b/app/models/bulk_imports/entity.rb
@@ -83,9 +83,9 @@ class BulkImports::Entity < ApplicationRecord
def pipelines
@pipelines ||= case source_type
when 'group_entity'
- BulkImports::Groups::Stage.pipelines
+ BulkImports::Groups::Stage.new(bulk_import).pipelines
when 'project_entity'
- BulkImports::Projects::Stage.pipelines
+ BulkImports::Projects::Stage.new(bulk_import).pipelines
end
end
diff --git a/app/models/member.rb b/app/models/member.rb
index 45af34a3e6b..7e0b4705217 100644
--- a/app/models/member.rb
+++ b/app/models/member.rb
@@ -50,6 +50,11 @@ class Member < ApplicationRecord
},
if: :project_bot?
+ scope :with_invited_user_state, -> do
+ joins('LEFT JOIN users as invited_user ON invited_user.email = members.invite_email')
+ .select('members.*', 'invited_user.state as invited_user_state')
+ end
+
scope :in_hierarchy, ->(source) do
groups = source.root_ancestor.self_and_descendants
group_members = Member.default_scoped.where(source: groups)
diff --git a/app/models/packages/helm/file_metadatum.rb b/app/models/packages/helm/file_metadatum.rb
index 1771003d1f9..dfa4ab6df82 100644
--- a/app/models/packages/helm/file_metadatum.rb
+++ b/app/models/packages/helm/file_metadatum.rb
@@ -12,7 +12,7 @@ module Packages
validates :channel,
presence: true,
- length: { maximum: 63 },
+ length: { maximum: 255 },
format: { with: Gitlab::Regex.helm_channel_regex }
validates :metadata,
diff --git a/app/serializers/member_entity.rb b/app/serializers/member_entity.rb
index 5100a41638e..d7221109ecb 100644
--- a/app/serializers/member_entity.rb
+++ b/app/serializers/member_entity.rb
@@ -44,6 +44,8 @@ class MemberEntity < Grape::Entity
MemberUserEntity.represent(member.user, source: options[:source])
end
+ expose :state
+
expose :invite, if: -> (member) { member.invite? } do
expose :email do |member|
member.invite_email
@@ -56,6 +58,10 @@ class MemberEntity < Grape::Entity
expose :can_resend do |member|
member.can_resend_invite?
end
+
+ expose :user_state do |member|
+ member.respond_to?(:invited_user_state) ? member.invited_user_state : ""
+ end
end
end
diff --git a/app/services/bulk_imports/create_service.rb b/app/services/bulk_imports/create_service.rb
index 1cea7632aa1..c1becbb5609 100644
--- a/app/services/bulk_imports/create_service.rb
+++ b/app/services/bulk_imports/create_service.rb
@@ -52,7 +52,11 @@ module BulkImports
def create_bulk_import
BulkImport.transaction do
- bulk_import = BulkImport.create!(user: current_user, source_type: 'gitlab')
+ bulk_import = BulkImport.create!(
+ user: current_user,
+ source_type: 'gitlab',
+ source_version: client.instance_version
+ )
bulk_import.create_configuration!(credentials.slice(:url, :access_token))
params.each do |entity|
@@ -68,5 +72,12 @@ module BulkImports
bulk_import
end
end
+
+ def client
+ @client ||= BulkImports::Clients::HTTP.new(
+ url: @credentials[:url],
+ token: @credentials[:access_token]
+ )
+ end
end
end
diff --git a/app/services/bulk_imports/get_importable_data_service.rb b/app/services/bulk_imports/get_importable_data_service.rb
new file mode 100644
index 00000000000..07e0b3976a1
--- /dev/null
+++ b/app/services/bulk_imports/get_importable_data_service.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+
+module BulkImports
+ class GetImportableDataService
+ def initialize(params, query_params, credentials)
+ @params = params
+ @query_params = query_params
+ @credentials = credentials
+ end
+
+ def execute
+ {
+ version_validation: version_validation,
+ response: importables
+ }
+ end
+
+ private
+
+ def importables
+ client.get('groups', @query_params)
+ end
+
+ def version_validation
+ {
+ features: {
+ project_migration: {
+ available: client.compatible_for_project_migration?,
+ min_version: BulkImport.min_gl_version_for_project_migration.to_s
+ },
+ source_instance_version: client.instance_version.to_s
+ }
+ }
+ end
+
+ def client
+ @client ||= BulkImports::Clients::HTTP.new(
+ url: @credentials[:url],
+ token: @credentials[:access_token],
+ per_page: @params[:per_page],
+ page: @params[:page]
+ )
+ end
+ end
+end
diff --git a/app/views/admin/users/_access_levels.html.haml b/app/views/admin/users/_access_levels.html.haml
index aeb274fe2cb..6a5f07dd2db 100644
--- a/app/views/admin/users/_access_levels.html.haml
+++ b/app/views/admin/users/_access_levels.html.haml
@@ -19,22 +19,20 @@
.col-sm-10
- editing_current_user = (current_user == @user)
- = f.radio_button :access_level, :regular, disabled: editing_current_user
- = f.label :access_level_regular, class: 'font-weight-bold' do
- = s_('AdminUsers|Regular')
- %p.light
- = s_('AdminUsers|Regular users have access to their groups and projects')
+ = f.gitlab_ui_radio_component :access_level, :regular,
+ s_('AdminUsers|Regular'),
+ radio_options: { disabled: editing_current_user },
+ help_text: s_('AdminUsers|Regular users have access to their groups and projects.')
= render_if_exists 'admin/users/auditor_access_level_radio', f: f, disabled: editing_current_user
- = f.radio_button :access_level, :admin, disabled: editing_current_user
- = f.label :access_level_admin, class: 'font-weight-bold' do
- = s_('AdminUsers|Admin')
- %p.light
- = s_('AdminUsers|Administrators have access to all groups, projects and users and can manage all features in this installation')
- - if editing_current_user
- %p.light
- = s_('AdminUsers|You cannot remove your own admin rights.')
+ - help_text = s_('AdminUsers|Administrators have access to all groups, projects and users and can manage all features in this installation.')
+ - help_text += ' ' + s_('AdminUsers|You cannot remove your own admin rights.') if editing_current_user
+ = f.gitlab_ui_radio_component :access_level, :admin,
+ s_('AdminUsers|Admin'),
+ radio_options: { disabled: editing_current_user },
+ help_text: help_text
+
.form-group.row
.col-sm-2.col-form-label.gl-pt-0
diff --git a/app/views/admin/users/_form.html.haml b/app/views/admin/users/_form.html.haml
index 9d62c19e2fc..96fd0f5aac1 100644
--- a/app/views/admin/users/_form.html.haml
+++ b/app/views/admin/users/_form.html.haml
@@ -1,5 +1,5 @@
.user_new
- = form_for [:admin, @user], html: { class: 'fieldset-form' } do |f|
+ = gitlab_ui_form_for [:admin, @user], html: { class: 'fieldset-form' } do |f|
= form_errors(@user)
%fieldset
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index ec2904245d3..dff1b5e3d04 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -17,6 +17,7 @@
= render_two_factor_auth_recovery_settings_check
= render_if_exists "layouts/header/ee_subscribable_banner"
= render_if_exists "shared/namespace_storage_limit_alert"
+ = render_if_exists "shared/namespace_user_cap_reached_alert"
= render_if_exists "shared/new_user_signups_cap_reached_alert"
= yield :page_level_alert
= yield :customize_homepage_banner
diff --git a/app/views/projects/ci/pipeline_editor/show.html.haml b/app/views/projects/ci/pipeline_editor/show.html.haml
index 674765e9f89..ce6f7553ab4 100644
--- a/app/views/projects/ci/pipeline_editor/show.html.haml
+++ b/app/views/projects/ci/pipeline_editor/show.html.haml
@@ -1,3 +1,5 @@
+- add_page_specific_style 'page_bundles/pipelines'
+
- page_title s_('Pipelines|Pipeline Editor')
- content_for :prefetch_asset_tags do
- webpack_preload_asset_tag('monaco')
diff --git a/config/feature_flags/development/pipeline_editor_mini_graph.yml b/config/feature_flags/development/pipeline_editor_mini_graph.yml
new file mode 100644
index 00000000000..6f31cb18d82
--- /dev/null
+++ b/config/feature_flags/development/pipeline_editor_mini_graph.yml
@@ -0,0 +1,8 @@
+---
+name: pipeline_editor_mini_graph
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/71622
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/342217
+milestone: '14.4'
+type: development
+group: group::pipeline authoring
+default_enabled: false
diff --git a/danger/specs/Dangerfile b/danger/specs/Dangerfile
index 7cc68407170..117eaf61062 100644
--- a/danger/specs/Dangerfile
+++ b/danger/specs/Dangerfile
@@ -20,7 +20,7 @@ Please make sure the spec files pass in AS-IF-FOSS mode either:
1. Locally with `FOSS_ONLY=1 bin/rspec -- %
s`.
1. In the MR pipeline by verifying that the `rspec foss-impact` job has passed.
-1. In the MR pipelines by including `RUN AS-IF-FOSS` in the MR title (you can do it with the ``/title %s [RUN AS-IF-FOSS]`` quick action) and start a new MR pipeline.
+1. In the MR pipelines by setting the ~"pipeline:run-as-if-foss" label on the MR (you can do it with the `/label ~"pipeline:run-as-if-foss"` quick action) and start a new MR pipeline.
MSG
@@ -46,7 +46,7 @@ end
# The only changes outside `ee/` are in `spec/`
if has_ee_app_changes && has_spec_changes && !(has_app_changes || has_ee_spec_changes)
- warn format(EE_CHANGE_WITH_FOSS_SPEC_CHANGE_MESSAGE, spec_files: spec_changes.join(" "), mr_title: gitlab.mr_json['title']), sticky: false
+ warn format(EE_CHANGE_WITH_FOSS_SPEC_CHANGE_MESSAGE, spec_files: spec_changes.join(" ")), sticky: false
end
# Forbidding a new file addition under `/spec/controllers` or `/ee/spec/controllers`
diff --git a/data/deprecations/templates/_deprecation_template.md.erb b/data/deprecations/templates/_deprecation_template.md.erb
index 5721badff36..a037151c6ac 100644
--- a/data/deprecations/templates/_deprecation_template.md.erb
+++ b/data/deprecations/templates/_deprecation_template.md.erb
@@ -6,6 +6,14 @@ info: "See the Technical Writers assigned to Development Guidelines: https://abo
# Deprecated feature removal schedule
+DISCLAIMER:
+This page contains information related to upcoming products, features, and functionality.
+It is important to note that the information presented is for informational purposes only.
+Please do not rely on this information for purchasing or planning purposes.
+As with all projects, the items mentioned on this page are subject to change or delay.
+The development, release, and timing of any products, features, or functionality remain at the
+sole discretion of GitLab Inc.
+
+
diff --git a/doc/development/testing_guide/index.md b/doc/development/testing_guide/index.md
index 015d8a92a4d..2e00a00c454 100644
--- a/doc/development/testing_guide/index.md
+++ b/doc/development/testing_guide/index.md
@@ -48,7 +48,7 @@ testing promises, stubbing etc.
What are flaky tests, the different kind of flaky tests we encountered, and what
we do about them.
-## [GitLab tests in the Continuous Integration (CI) context](ci.md)
+## [GitLab pipelines](../pipelines.md)
How GitLab test suite is run in the CI context: setup, caches, artifacts,
parallelization, monitoring.
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index 21ab73faa0d..3e92998e9a7 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -197,15 +197,13 @@ For example configurations, see the [notes on specific providers](#providers).
| Field | Supported keys |
|-----------------|----------------|
| Email (required)| `email`, `mail` |
-| Username | `username`, `nickname` |
| Full Name | `name` |
| First Name | `first_name`, `firstname`, `firstName` |
| Last Name | `last_name`, `lastname`, `lastName` |
-If a username is not specified, the email address is used to generate the GitLab username.
-
-See [`attribute_statements`](#attribute_statements) for examples on how the
-assertions are configured.
+See [`attribute_statements`](#attribute_statements) for examples on how custom
+assertions are configured. This section also describes how to configure custom
+username attributes.
Please refer to [the OmniAuth SAML gem](https://github.com/omniauth/omniauth-saml/blob/master/lib/omniauth/strategies/saml.rb)
for a full list of supported assertions.
@@ -444,7 +442,7 @@ SAML users has an administrator role.
You may also bypass the auto sign-in feature by browsing to
`https://gitlab.example.com/users/sign_in?auto_sign_in=false`.
-### `attribute_statements`
+### `attribute_statements` **(FREE SELF)**
NOTE:
This setting should be used only to map attributes that are part of the OmniAuth
@@ -476,11 +474,10 @@ args: {
#### Set a username
-By default, the email in the SAML response is used to automatically generate the
-user's GitLab username.
+By default, the local part of the email address in the SAML response is used to
+generate the user's GitLab username.
-If you'd like to set another attribute as the username, assign it to the `nickname` OmniAuth `info`
-hash attribute, and add the following setting to your configuration file:
+Configure `nickname` in `attribute_statements` to specify one or more attributes that contain a user's desired username:
```yaml
args: {
diff --git a/doc/topics/autodevops/customize.md b/doc/topics/autodevops/customize.md
index ecd4aaa7f0b..c01ed4a49d0 100644
--- a/doc/topics/autodevops/customize.md
+++ b/doc/topics/autodevops/customize.md
@@ -58,7 +58,7 @@ If your goal is to use only a single custom buildpack, you should provide the pr
## Custom `Dockerfile`
-> Support for `DOCKERFILE_PATH` was [added in GitLab 13.2](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35662)
+> Support for `DOCKERFILE_PATH` was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/35662) in GitLab 13.2
If your project has a `Dockerfile` in the root of the project repository, Auto DevOps
builds a Docker image based on the Dockerfile, rather than using buildpacks.
diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md
index e232af05d50..9340f89c502 100644
--- a/doc/topics/autodevops/index.md
+++ b/doc/topics/autodevops/index.md
@@ -131,7 +131,7 @@ following levels:
| Instance type | [Project](#at-the-project-level) | [Group](#at-the-group-level) | [Instance](#at-the-instance-level) (Admin Area) |
|---------------------|------------------------|------------------------|------------------------|
-| GitLab SaaS | **{check-circle}** Yes | **{dotted-circle}** No | **{dotted-circle}** No |
+| GitLab SaaS | **{check-circle}** Yes | **{check-circle}** Yes | **{dotted-circle}** No |
| GitLab self-managed | **{check-circle}** Yes | **{check-circle}** Yes | **{check-circle}** Yes |
Before enabling Auto DevOps, consider [preparing it for deployment](requirements.md). If you don't, Auto DevOps can build and test your app,
diff --git a/doc/topics/autodevops/quick_start_guide.md b/doc/topics/autodevops/quick_start_guide.md
index 4ee412f5c65..c84b5e4d9c7 100644
--- a/doc/topics/autodevops/quick_start_guide.md
+++ b/doc/topics/autodevops/quick_start_guide.md
@@ -199,13 +199,13 @@ The jobs are separated into stages:
vulnerabilities and is allowed to fail ([Auto Container Scanning](stages.md#auto-container-scanning))
- The `dependency_scanning` job checks if the application has any dependencies
susceptible to vulnerabilities and is allowed to fail
- ([Auto Dependency Scanning](stages.md#auto-dependency-scanning)) **(ULTIMATE)**
+ ([Auto Dependency Scanning](stages.md#auto-dependency-scanning))
- Jobs suffixed with `-sast` run static analysis on the current code to check for potential
- security issues, and are allowed to fail ([Auto SAST](stages.md#auto-sast)) **(ULTIMATE)**
- - The `secret-detection` job checks for leaked secrets and is allowed to fail ([Auto Secret Detection](stages.md#auto-secret-detection)) **(ULTIMATE)**
+ security issues, and are allowed to fail ([Auto SAST](stages.md#auto-sast))
+ - The `secret-detection` job checks for leaked secrets and is allowed to fail ([Auto Secret Detection](stages.md#auto-secret-detection))
- The `license_scanning` job searches the application's dependencies to determine each of their
licenses and is allowed to fail
- ([Auto License Compliance](stages.md#auto-license-compliance)) **(ULTIMATE)**
+ ([Auto License Compliance](stages.md#auto-license-compliance))
- **Review** - Pipelines on the default branch include this stage with a `dast_environment_deploy` job.
To learn more, see [Dynamic Application Security Testing (DAST)](../../user/application_security/dast/index.md).
@@ -214,7 +214,7 @@ The jobs are separated into stages:
Kubernetes ([Auto Deploy](stages.md#auto-deploy)).
- **Performance** - Performance tests are run on the deployed application
- ([Auto Browser Performance Testing](stages.md#auto-browser-performance-testing)). **(PREMIUM)**
+ ([Auto Browser Performance Testing](stages.md#auto-browser-performance-testing)).
- **Cleanup** - Pipelines on the default branch include this stage with a `stop_dast_environment` job.
@@ -323,7 +323,7 @@ and customized to fit your workflow. Here are some helpful resources for further
1. [Auto DevOps](index.md)
1. [Multiple Kubernetes clusters](multiple_clusters_auto_devops.md)
-1. [Incremental rollout to production](customize.md#incremental-rollout-to-production) **(PREMIUM)**
+1. [Incremental rollout to production](customize.md#incremental-rollout-to-production)
1. [Disable jobs you don't need with CI/CD variables](customize.md#cicd-variables)
1. [Use your own buildpacks to build your application](customize.md#custom-buildpacks)
1. [Prometheus monitoring](../../user/project/integrations/prometheus.md)
diff --git a/doc/topics/autodevops/stages.md b/doc/topics/autodevops/stages.md
index 9e6f3103664..ead2e957684 100644
--- a/doc/topics/autodevops/stages.md
+++ b/doc/topics/autodevops/stages.md
@@ -35,7 +35,7 @@ your own `Dockerfile`, you must either:
### Auto Build using Cloud Native Buildpacks
-> - Introduced in [GitLab 12.10](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28165).
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28165) in GitLab 12.10.
> - Auto Build using Cloud Native Buildpacks by default was [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/63351) in GitLab 14.0.
Auto Build builds an application using a project's `Dockerfile` if present. If no
@@ -147,7 +147,7 @@ might want to use a [custom buildpack](customize.md#custom-buildpacks).
## Auto Code Quality
-> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/212499) to GitLab Free in 13.2.
+> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/212499) from GitLab Starter to GitLab Free in 13.2.
Auto Code Quality uses the
[Code Quality image](https://gitlab.com/gitlab-org/ci-cd/codequality) to run
@@ -174,8 +174,8 @@ see the documentation.
## Auto Secret Detection
-> - Introduced in GitLab Ultimate 13.1.
-> - [Select functionality made available in all tiers](../../user/application_security/secret_detection/#making-secret-detection-available-to-all-gitlab-tiers) in 13.3
+> - Introduced in GitLab 13.1.
+> - Select functionality [made available](../../user/application_security/secret_detection/#making-secret-detection-available-to-all-gitlab-tiers) in all tiers in GitLab 13.3
Secret Detection uses the
[Secret Detection Docker image](https://gitlab.com/gitlab-org/security-products/analyzers/secrets) to run Secret Detection on the current code, and checks for leaked secrets. Auto Secret Detection requires [GitLab Runner](https://docs.gitlab.com/runner/) 11.5 or above.
@@ -202,7 +202,7 @@ see the documentation.
## Auto License Compliance **(ULTIMATE)**
-> Introduced in GitLab Ultimate 11.0.
+> Introduced in GitLab 11.0.
License Compliance uses the
[License Compliance Docker image](https://gitlab.com/gitlab-org/security-products/analyzers/license-finder)
@@ -310,7 +310,7 @@ You can disable DAST:
## Auto Browser Performance Testing **(PREMIUM)**
-> Introduced in [GitLab Premium](https://about.gitlab.com/pricing/) 10.4.
+> Introduced in GitLab 10.4.
Auto [Browser Performance Testing](../../user/project/merge_requests/browser_performance_testing.md)
measures the browser performance of a web page with the
@@ -331,7 +331,7 @@ Any browser performance differences between the source and target branches are a
## Auto Load Performance Testing **(PREMIUM)**
-> Introduced in [GitLab Premium](https://about.gitlab.com/pricing/) 13.2.
+> Introduced in GitLab 13.2.
Auto [Load Performance Testing](../../user/project/merge_requests/load_performance_testing.md)
measures the server performance of an application with the
@@ -348,7 +348,7 @@ Any load performance test result differences between the source and target branc
## Auto Deploy
-[Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216008) in GitLab 13.6, you have the choice to deploy to [Amazon Elastic Compute Cloud (Amazon EC2)](https://aws.amazon.com/ec2/) in addition to a Kubernetes cluster.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216008) in GitLab 13.6, you have the choice to deploy to [Amazon Elastic Compute Cloud (Amazon EC2)](https://aws.amazon.com/ec2/) in addition to a Kubernetes cluster.
Auto Deploy is an optional step for Auto DevOps. If the [requirements](requirements.md) are not met, the job is skipped.
diff --git a/doc/topics/autodevops/upgrading_auto_deploy_dependencies.md b/doc/topics/autodevops/upgrading_auto_deploy_dependencies.md
index 7ddcdcbacb5..8c460247734 100644
--- a/doc/topics/autodevops/upgrading_auto_deploy_dependencies.md
+++ b/doc/topics/autodevops/upgrading_auto_deploy_dependencies.md
@@ -5,7 +5,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
type: reference
---
-# Upgrading deployments for newer Auto Deploy dependencies
+# Upgrading deployments for newer Auto Deploy dependencies **(FREE)**
[Auto Deploy](stages.md#auto-deploy) is a feature that deploys your application to a Kubernetes cluster.
It consists of several dependencies:
diff --git a/doc/update/deprecations.md b/doc/update/deprecations.md
index c1bf316a4ae..e2af4f453c0 100644
--- a/doc/update/deprecations.md
+++ b/doc/update/deprecations.md
@@ -6,6 +6,14 @@ info: "See the Technical Writers assigned to Development Guidelines: https://abo
# Deprecated feature removal schedule
+DISCLAIMER:
+This page contains information related to upcoming products, features, and functionality.
+It is important to note that the information presented is for informational purposes only.
+Please do not rely on this information for purchasing or planning purposes.
+As with all projects, the items mentioned on this page are subject to change or delay.
+The development, release, and timing of any products, features, or functionality remain at the
+sole discretion of GitLab Inc.
+