diff --git a/app/assets/stylesheets/startup/startup-signin.scss b/app/assets/stylesheets/startup/startup-signin.scss
index b36b6827643..b3afb746ba7 100644
--- a/app/assets/stylesheets/startup/startup-signin.scss
+++ b/app/assets/stylesheets/startup/startup-signin.scss
@@ -11,6 +11,9 @@ html {
font-family: sans-serif;
line-height: 1.15;
}
+header {
+ display: block;
+}
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
@@ -28,7 +31,8 @@ hr {
height: 0;
overflow: visible;
}
-h1 {
+h1,
+h3 {
margin-top: 0;
margin-bottom: 0.25rem;
}
@@ -49,26 +53,49 @@ img {
vertical-align: middle;
border-style: none;
}
+svg {
+ overflow: hidden;
+ vertical-align: middle;
+}
label {
display: inline-block;
margin-bottom: 0.5rem;
}
-input {
+button {
+ border-radius: 0;
+}
+input,
+button {
margin: 0;
font-family: inherit;
font-size: inherit;
line-height: inherit;
}
+button,
input {
overflow: visible;
}
+button {
+ text-transform: none;
+}
+[role="button"] {
+ cursor: pointer;
+}
+button:not(:disabled),
+[type="button"]:not(:disabled),
[type="submit"]:not(:disabled) {
cursor: pointer;
}
+button::-moz-focus-inner,
+[type="button"]::-moz-focus-inner,
[type="submit"]::-moz-focus-inner {
padding: 0;
border-style: none;
}
+input[type="checkbox"] {
+ box-sizing: border-box;
+ padding: 0;
+}
fieldset {
min-width: 0;
padding: 0;
@@ -78,7 +105,8 @@ fieldset {
[hidden] {
display: none !important;
}
-h1 {
+h1,
+h3 {
margin-bottom: 0.25rem;
font-weight: 600;
line-height: 1.2;
@@ -87,6 +115,9 @@ h1 {
h1 {
font-size: 2.1875rem;
}
+h3 {
+ font-size: 1.53125rem;
+}
hr {
margin-top: 0.5rem;
margin-bottom: 0.5rem;
@@ -120,23 +151,42 @@ hr {
max-width: 1140px;
}
}
-.col-sm-12,
-.col {
+.row {
+ display: flex;
+ flex-wrap: wrap;
+ margin-right: -15px;
+ margin-left: -15px;
+}
+.col-md-6,
+.col-sm-12 {
position: relative;
width: 100%;
padding-right: 15px;
padding-left: 15px;
}
-.col {
- flex-basis: 0;
- flex-grow: 1;
- max-width: 100%;
+.order-1 {
+ order: 1;
+}
+.order-12 {
+ order: 12;
}
@media (min-width: 576px) {
.col-sm-12 {
flex: 0 0 100%;
max-width: 100%;
}
+ .order-sm-1 {
+ order: 1;
+ }
+ .order-sm-12 {
+ order: 12;
+ }
+}
+@media (min-width: 768px) {
+ .col-md-6 {
+ flex: 0 0 50%;
+ max-width: 50%;
+ }
}
.form-control {
display: block;
@@ -169,16 +219,6 @@ hr {
.form-group {
margin-bottom: 1rem;
}
-.form-row {
- display: flex;
- flex-wrap: wrap;
- margin-right: -5px;
- margin-left: -5px;
-}
-.form-row > .col {
- padding-right: 5px;
- padding-left: 5px;
-}
.btn {
display: inline-block;
font-weight: 400;
@@ -204,6 +244,137 @@ hr {
fieldset:disabled a.btn {
pointer-events: none;
}
+.btn-block {
+ display: block;
+ width: 100%;
+}
+.btn-block + .btn-block {
+ margin-top: 0.5rem;
+}
+input.btn-block[type="submit"],
+input.btn-block[type="button"] {
+ width: 100%;
+}
+.custom-control {
+ position: relative;
+ z-index: 1;
+ display: block;
+ min-height: 1.5rem;
+ padding-left: 1.5rem;
+ color-adjust: exact;
+}
+.custom-control-input {
+ position: absolute;
+ left: 0;
+ z-index: -1;
+ width: 1rem;
+ height: 1.25rem;
+ opacity: 0;
+}
+.custom-control-input:checked ~ .custom-control-label::before {
+ color: #fff;
+ border-color: #007bff;
+ background-color: #007bff;
+}
+.custom-control-input:not(:disabled):active ~ .custom-control-label::before {
+ color: #fff;
+ background-color: #b3d7ff;
+ border-color: #b3d7ff;
+}
+.custom-control-input:disabled ~ .custom-control-label {
+ color: #5e5e5e;
+}
+.custom-control-input:disabled ~ .custom-control-label::before {
+ background-color: #fafafa;
+}
+.custom-control-label {
+ position: relative;
+ margin-bottom: 0;
+ vertical-align: top;
+}
+.custom-control-label::before {
+ position: absolute;
+ top: 0.25rem;
+ left: -1.5rem;
+ display: block;
+ width: 1rem;
+ height: 1rem;
+ pointer-events: none;
+ content: "";
+ background-color: #fff;
+ border: #666 solid 1px;
+}
+.custom-control-label::after {
+ position: absolute;
+ top: 0.25rem;
+ left: -1.5rem;
+ display: block;
+ width: 1rem;
+ height: 1rem;
+ content: "";
+ background: no-repeat 50% / 50% 50%;
+}
+.custom-checkbox .custom-control-label::before {
+ border-radius: 0.25rem;
+}
+.custom-checkbox .custom-control-input:checked ~ .custom-control-label::after {
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='8' height='8' viewBox='0 0 8 8'%3e%3cpath fill='%23fff' d='M6.564.75l-3.59 3.612-1.538-1.55L0 4.26l2.974 2.99L8 2.193z'/%3e%3c/svg%3e");
+}
+.custom-checkbox
+ .custom-control-input:indeterminate
+ ~ .custom-control-label::before {
+ border-color: #007bff;
+ background-color: #007bff;
+}
+.custom-checkbox
+ .custom-control-input:indeterminate
+ ~ .custom-control-label::after {
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' width='4' height='4' viewBox='0 0 4 4'%3e%3cpath stroke='%23fff' d='M0 2h4'/%3e%3c/svg%3e");
+}
+.custom-checkbox
+ .custom-control-input:disabled:checked
+ ~ .custom-control-label::before {
+ background-color: rgba(0, 123, 255, 0.5);
+}
+.custom-checkbox
+ .custom-control-input:disabled:indeterminate
+ ~ .custom-control-label::before {
+ background-color: rgba(0, 123, 255, 0.5);
+}
+@media (prefers-reduced-motion: reduce) {
+}
+.tab-content > .tab-pane {
+ display: none;
+}
+.tab-content > .active {
+ display: block;
+}
+.navbar {
+ position: relative;
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ padding: 0.25rem 0.5rem;
+}
+.navbar .container {
+ display: flex;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+}
+.clearfix::after {
+ display: block;
+ clear: both;
+ content: "";
+}
+.fixed-top {
+ position: fixed;
+ top: 0;
+ right: 0;
+ left: 0;
+ z-index: 1030;
+}
.mt-3 {
margin-top: 1rem !important;
}
@@ -213,8 +384,8 @@ fieldset:disabled a.btn {
.text-nowrap {
white-space: nowrap !important;
}
-.text-center {
- text-align: center !important;
+.font-weight-normal {
+ font-weight: 400 !important;
}
.gl-form-input,
.gl-form-input.form-control {
@@ -251,13 +422,103 @@ fieldset:disabled a.btn {
.gl-form-input.form-control::placeholder {
color: #868686;
}
+.gl-form-checkbox {
+ font-size: 0.875rem;
+ line-height: 1rem;
+ color: #303030;
+}
+.gl-form-checkbox .custom-control-input:disabled,
+.gl-form-checkbox .custom-control-input:disabled ~ .custom-control-label {
+ cursor: not-allowed;
+ color: #868686;
+}
+.gl-form-checkbox.custom-control .custom-control-input ~ .custom-control-label {
+ cursor: pointer;
+}
+.gl-form-checkbox.custom-control
+ .custom-control-input
+ ~ .custom-control-label::before,
+.gl-form-checkbox.custom-control
+ .custom-control-input
+ ~ .custom-control-label::after {
+ top: 0;
+}
+.gl-form-checkbox.custom-control
+ .custom-control-input
+ ~ .custom-control-label::before {
+ background-color: #fff;
+ border-color: #868686;
+}
+.gl-form-checkbox.custom-control
+ .custom-control-input:checked
+ ~ .custom-control-label::before {
+ background-color: #1f75cb;
+ border-color: #1f75cb;
+}
+.gl-form-checkbox.custom-control
+ .custom-control-input[type="checkbox"]:checked
+ ~ .custom-control-label::after,
+.gl-form-checkbox.custom-control
+ .custom-control-input[type="checkbox"]:indeterminate
+ ~ .custom-control-label::after {
+ background: none;
+ background-color: #fff;
+ mask-repeat: no-repeat;
+ mask-position: center center;
+}
+.gl-form-checkbox.custom-control
+ .custom-control-input[type="checkbox"]:checked
+ ~ .custom-control-label::after {
+ mask-image: url('data:image/svg+xml,%3Csvg width="8" height="7" viewBox="0 0 8 7" fill="none" xmlns="http://www.w3.org/2000/svg"%3E%3Cpath d="M1 3.05299L2.99123 5L7 1" stroke="white" stroke-width="2"/%3E%3C/svg%3E%0A');
+}
+.gl-form-checkbox.custom-control
+ .custom-control-input[type="checkbox"]:indeterminate
+ ~ .custom-control-label::after {
+ mask-image: url('data:image/svg+xml,%3Csvg width="8" height="2" viewBox="0 0 8 2" fill="none" xmlns="http://www.w3.org/2000/svg"%3E%3Cpath d="M0 1L8 1" stroke="white" stroke-width="2"/%3E%3C/svg%3E%0A');
+}
+.gl-form-checkbox.custom-control.custom-checkbox
+ .custom-control-input:indeterminate
+ ~ .custom-control-label::before {
+ background-color: #1f75cb;
+ border-color: #1f75cb;
+}
+.gl-form-checkbox.custom-control
+ .custom-control-input:disabled
+ ~ .custom-control-label {
+ cursor: not-allowed;
+}
+.gl-form-checkbox.custom-control
+ .custom-control-input:disabled
+ ~ .custom-control-label::before {
+ background-color: #f0f0f0;
+ border-color: #dbdbdb;
+ pointer-events: auto;
+}
+.gl-form-checkbox.custom-control
+ .custom-control-input:checked:disabled
+ ~ .custom-control-label::before,
+.gl-form-checkbox.custom-control
+ .custom-control-input:indeterminate:disabled
+ ~ .custom-control-label::before {
+ background-color: #dbdbdb;
+ border-color: #dbdbdb;
+}
+.gl-form-checkbox.custom-control
+ .custom-control-input:checked:disabled
+ ~ .custom-control-label::after,
+.gl-form-checkbox.custom-control
+ .custom-control-input:indeterminate:disabled
+ ~ .custom-control-label::after {
+ background-color: #5e5e5e;
+}
.gl-button {
display: inline-flex;
}
.gl-button:not(.btn-link):active {
text-decoration: none;
}
-.gl-button.gl-button {
+.gl-button.gl-button,
+.gl-button.gl-button.btn-block {
border-width: 0;
padding-top: 0.5rem;
padding-bottom: 0.5rem;
@@ -273,7 +534,8 @@ fieldset:disabled a.btn {
font-size: 0.875rem;
border-radius: 0.25rem;
}
-.gl-button.gl-button .gl-button-text {
+.gl-button.gl-button .gl-button-text,
+.gl-button.gl-button.btn-block .gl-button-text {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
@@ -282,29 +544,39 @@ fieldset:disabled a.btn {
margin-top: -1px;
margin-bottom: -1px;
}
-.gl-button.gl-button .gl-button-icon {
+.gl-button.gl-button .gl-button-icon,
+.gl-button.gl-button.btn-block .gl-button-icon {
height: 1rem;
width: 1rem;
flex-shrink: 0;
margin-right: 0.25rem;
top: auto;
}
-.gl-button.gl-button.btn-default {
+.gl-button.gl-button.btn-default,
+.gl-button.gl-button.btn-block.btn-default {
background-color: #fff;
}
-.gl-button.gl-button.btn-default:active {
+.gl-button.gl-button.btn-default:active,
+.gl-button.gl-button.btn-default.active,
+.gl-button.gl-button.btn-block.btn-default:active,
+.gl-button.gl-button.btn-block.btn-default.active {
box-shadow: inset 0 0 0 1px #5e5e5e, 0 0 0 1px #fff, 0 0 0 3px #428fdc;
outline: none;
background-color: #dbdbdb;
}
-.gl-button.gl-button.btn-confirm {
+.gl-button.gl-button.btn-confirm,
+.gl-button.gl-button.btn-block.btn-confirm {
color: #fff;
}
-.gl-button.gl-button.btn-confirm {
+.gl-button.gl-button.btn-confirm,
+.gl-button.gl-button.btn-block.btn-confirm {
background-color: #1f75cb;
box-shadow: inset 0 0 0 1px #1068bf;
}
-.gl-button.gl-button.btn-confirm:active {
+.gl-button.gl-button.btn-confirm:active,
+.gl-button.gl-button.btn-confirm.active,
+.gl-button.gl-button.btn-block.btn-confirm:active,
+.gl-button.gl-button.btn-block.btn-confirm.active {
box-shadow: inset 0 0 0 1px #033464, 0 0 0 1px #fff, 0 0 0 3px #428fdc;
outline: none;
background-color: #0b5cad;
@@ -312,10 +584,14 @@ fieldset:disabled a.btn {
body {
font-size: 0.875rem;
}
-[type="submit"] {
+button,
+html [type="button"],
+[type="submit"],
+[role="button"] {
cursor: pointer;
}
-h1 {
+h1,
+h3 {
margin-top: 20px;
margin-bottom: 10px;
}
@@ -325,6 +601,9 @@ a {
hr {
overflow: hidden;
}
+svg {
+ vertical-align: baseline;
+}
.form-control {
font-size: 0.875rem;
}
@@ -332,9 +611,6 @@ hr {
display: none !important;
visibility: hidden !important;
}
-.hide {
- display: none;
-}
html {
overflow-y: scroll;
}
@@ -372,13 +648,34 @@ body.navless {
background-color: #f0f0f0;
box-shadow: none;
}
-.btn:active {
+.btn:active,
+.btn.active {
background-color: #eaeaea;
border-color: #e3e3e3;
color: #303030;
}
-.light {
- color: #303030;
+.btn svg {
+ height: 15px;
+ width: 15px;
+}
+.btn svg:not(:last-child) {
+ margin-right: 5px;
+}
+.btn-block {
+ width: 100%;
+ margin: 0;
+ margin-bottom: 1rem;
+}
+.btn-block.btn {
+ padding: 6px 0;
+}
+.tab-content {
+ overflow: visible;
+}
+@media (max-width: 767.98px) {
+ .tab-content {
+ isolation: isolate;
+ }
}
hr {
margin: 1.5rem 0;
@@ -416,6 +713,9 @@ input {
label {
font-weight: 600;
}
+label.custom-control-label {
+ font-weight: 400;
+}
label.label-bold {
font-weight: 600;
}
@@ -429,8 +729,25 @@ label.label-bold {
.gl-show-field-errors .form-control:not(textarea) {
height: 34px;
}
-.gl-show-field-errors .gl-field-hint {
- color: #303030;
+.navbar-empty {
+ justify-content: center;
+ height: var(--header-height, 48px);
+ background: #fff;
+ border-bottom: 1px solid #dbdbdb;
+}
+.navbar-empty .tanuki-logo,
+.navbar-empty .brand-header-logo {
+ max-height: 100%;
+}
+.tanuki-logo .tanuki {
+ fill: #e24329;
+}
+.tanuki-logo .left-cheek,
+.tanuki-logo .right-cheek {
+ fill: #fc6d26;
+}
+.tanuki-logo .chin {
+ fill: #fca326;
}
input::-moz-placeholder {
color: #868686;
@@ -442,6 +759,9 @@ input::-ms-input-placeholder {
input:-ms-input-placeholder {
color: #868686;
}
+svg {
+ fill: currentColor;
+}
.login-page .container {
max-width: 960px;
}
@@ -649,14 +969,17 @@ input:-ms-input-placeholder {
}
}
-.gl-text-green-600 {
- color: #217645;
+.gl-display-flex {
+ display: flex;
}
-.gl-text-red-500 {
- color: #dd2b0e;
+.gl-display-inline-block {
+ display: inline-block;
}
-.gl-display-block {
- display: block;
+.gl-flex-wrap {
+ flex-wrap: wrap;
+}
+.gl-float-right {
+ float: right;
}
.gl-w-10 {
width: 3.5rem;
@@ -675,14 +998,18 @@ input:-ms-input-placeholder {
width: 100%;
}
}
-.gl-p-4 {
- padding: 0.75rem;
+.gl-p-5 {
+ padding: 1rem;
+}
+.gl-px-5 {
+ padding-left: 1rem;
+ padding-right: 1rem;
}
.gl-pt-5 {
padding-top: 1rem;
}
-.gl-mt-2 {
- margin-top: 0.25rem;
+.gl-mt-3 {
+ margin-top: 0.5rem;
}
.gl-mt-5 {
margin-top: 1rem;
@@ -702,15 +1029,17 @@ input:-ms-input-placeholder {
.gl-mb-3 {
margin-bottom: 0.5rem;
}
-.gl-mb-5 {
- margin-bottom: 1rem;
-}
.gl-ml-auto {
margin-left: auto;
}
.gl-ml-2 {
margin-left: 0.25rem;
}
+@media (min-width: 576px) {
+ .gl-sm-mt-0 {
+ margin-top: 0;
+ }
+}
.gl-text-center {
text-align: center;
}
@@ -720,6 +1049,9 @@ input:-ms-input-placeholder {
.gl-font-weight-normal {
font-weight: 400;
}
+.gl-font-weight-bold {
+ font-weight: 600;
+}
@import "startup/cloaking";
@include cloak-startup-scss(none);
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index 7ef9fd9daed..311c83ab9b4 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -6,6 +6,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
# See https://gitlab.com/gitlab-org/gitlab/-/issues/226002 for more details.
include MetricsDashboard
+ include ProductAnalyticsTracking
layout 'project'
@@ -26,6 +27,18 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :expire_etag_cache, only: [:index], unless: -> { request.format.json? }
after_action :expire_etag_cache, only: [:cancel_auto_stop]
+ track_event :index,
+ :folder,
+ :show,
+ :new,
+ :edit,
+ :create,
+ :update,
+ :stop,
+ :cancel_auto_stop,
+ :terminal,
+ name: 'users_visiting_environments_pages'
+
feature_category :continuous_delivery
urgency :low
diff --git a/app/helpers/nav/top_nav_helper.rb b/app/helpers/nav/top_nav_helper.rb
index efec6f2d0d8..c4135dc086e 100644
--- a/app/helpers/nav/top_nav_helper.rb
+++ b/app/helpers/nav/top_nav_helper.rb
@@ -48,6 +48,13 @@ module Nav
private
+ def top_nav_localized_headers
+ {
+ explore: s_('TopNav|Explore'),
+ switch_to: s_('TopNav|Switch to')
+ }.freeze
+ end
+
def build_base_view_model(builder:, project:, group:)
if current_user
build_view_model(builder: builder, project: project, group: group)
@@ -60,6 +67,7 @@ module Nav
# These come from `app/views/layouts/nav/_explore.html.ham`
if explore_nav_link?(:projects)
builder.add_primary_menu_item_with_shortcut(
+ header: top_nav_localized_headers[:explore],
href: explore_root_path,
active: nav == 'project' || active_nav_link?(path: %w[dashboard#show root#show projects#trending projects#starred projects#index]),
**projects_menu_item_attrs
@@ -68,6 +76,7 @@ module Nav
if explore_nav_link?(:groups)
builder.add_primary_menu_item_with_shortcut(
+ header: top_nav_localized_headers[:explore],
href: explore_groups_path,
active: nav == 'group' || active_nav_link?(controller: [:groups, 'groups/milestones', 'groups/group_members']),
**groups_menu_item_attrs
@@ -76,6 +85,7 @@ module Nav
if explore_nav_link?(:snippets)
builder.add_primary_menu_item_with_shortcut(
+ header: top_nav_localized_headers[:explore],
active: active_nav_link?(controller: :snippets),
href: explore_snippets_path,
**snippets_menu_item_attrs
@@ -89,6 +99,7 @@ module Nav
current_item = project ? current_project(project: project) : {}
builder.add_primary_menu_item_with_shortcut(
+ header: top_nav_localized_headers[:switch_to],
active: nav == 'project' || active_nav_link?(path: %w[root#index projects#trending projects#starred dashboard/projects#index]),
css_class: 'qa-projects-dropdown',
data: { track_label: "projects_dropdown", track_action: "click_dropdown" },
@@ -103,6 +114,7 @@ module Nav
current_item = group ? current_group(group: group) : {}
builder.add_primary_menu_item_with_shortcut(
+ header: top_nav_localized_headers[:switch_to],
active: nav == 'group' || active_nav_link?(path: %w[dashboard/groups explore/groups]),
css_class: 'qa-groups-dropdown',
data: { track_label: "groups_dropdown", track_action: "click_dropdown" },
@@ -116,6 +128,7 @@ module Nav
if dashboard_nav_link?(:milestones)
builder.add_primary_menu_item_with_shortcut(
id: 'milestones',
+ header: top_nav_localized_headers[:explore],
title: _('Milestones'),
href: dashboard_milestones_path,
active: active_nav_link?(controller: 'dashboard/milestones'),
@@ -127,6 +140,7 @@ module Nav
if dashboard_nav_link?(:snippets)
builder.add_primary_menu_item_with_shortcut(
+ header: top_nav_localized_headers[:explore],
active: active_nav_link?(controller: 'dashboard/snippets'),
data: { qa_selector: 'snippets_link', **menu_data_tracking_attrs('snippets') },
href: dashboard_snippets_path,
@@ -137,6 +151,7 @@ module Nav
if dashboard_nav_link?(:activity)
builder.add_primary_menu_item_with_shortcut(
id: 'activity',
+ header: top_nav_localized_headers[:explore],
title: _('Activity'),
href: activity_dashboard_path,
active: active_nav_link?(path: 'dashboard#activity'),
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index 9a2a60fd7ad..2a04cc35ece 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -10,11 +10,7 @@ class ApplicationSetting < ApplicationRecord
ignore_columns %i[elasticsearch_shards elasticsearch_replicas], remove_with: '14.4', remove_after: '2021-09-22'
ignore_columns %i[static_objects_external_storage_auth_token], remove_with: '14.9', remove_after: '2022-03-22'
- ignore_column %i[max_package_files_for_package_destruction], remove_with: '14.9', remove_after: '2022-03-22'
ignore_column :user_email_lookup_limit, remove_with: '15.0', remove_after: '2022-04-18'
- ignore_column :pseudonymizer_enabled, remove_with: '15.1', remove_after: '2022-06-22'
- ignore_column :enforce_ssh_key_expiration, remove_with: '15.2', remove_after: '2022-07-22'
- ignore_column :enforce_pat_expiration, remove_with: '15.2', remove_after: '2022-07-22'
INSTANCE_REVIEW_MIN_USERS = 50
GRAFANA_URL_ERROR_MESSAGE = 'Please check your Grafana URL setting in ' \
diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb
index 4c0fbcfe330..ef01ef4697f 100644
--- a/app/models/ci/pipeline.rb
+++ b/app/models/ci/pipeline.rb
@@ -1073,7 +1073,7 @@ module Ci
latest_test_report_builds.failed.limit(limit)
end
- def has_reports?(reports_scope)
+ def complete_and_has_reports?(reports_scope)
if Feature.enabled?(:mr_show_reports_immediately, project, type: :development)
latest_report_builds(reports_scope).exists?
else
@@ -1090,7 +1090,7 @@ module Ci
end
def can_generate_codequality_reports?
- has_reports?(Ci::JobArtifact.of_report_type(:codequality))
+ complete_and_has_reports?(Ci::JobArtifact.of_report_type(:codequality))
end
def test_report_summary
@@ -1313,7 +1313,7 @@ module Ci
def has_test_reports?
strong_memoize(:has_test_reports) do
- has_reports?(::Ci::JobArtifact.of_report_type(:test))
+ complete_and_has_reports?(::Ci::JobArtifact.of_report_type(:test))
end
end
diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb
index 3cbc4c50444..60a9eade5a5 100644
--- a/app/models/ci/runner.rb
+++ b/app/models/ci/runner.rb
@@ -8,15 +8,12 @@ module Ci
include ChronicDurationAttribute
include FromUnion
include TokenAuthenticatable
- include IgnorableColumns
include FeatureGate
include Gitlab::Utils::StrongMemoize
include TaggableQueries
include Presentable
include EachBatch
- ignore_column :semver, remove_with: '15.4', remove_after: '2022-08-22'
-
add_authentication_token_field :token, encrypted: :optional, expires_at: :compute_token_expiration, expiration_enforced?: :token_expiration_enforced?
enum access_level: {
diff --git a/app/models/clusters/applications/ingress.rb b/app/models/clusters/applications/ingress.rb
index 3a8c314efe4..27550616002 100644
--- a/app/models/clusters/applications/ingress.rb
+++ b/app/models/clusters/applications/ingress.rb
@@ -16,14 +16,10 @@ module Clusters
include ::Clusters::Concerns::ApplicationData
include AfterCommitQueue
include UsageStatistics
- include IgnorableColumns
default_value_for :ingress_type, :nginx
default_value_for :version, VERSION
- ignore_column :modsecurity_enabled, remove_with: '14.2', remove_after: '2021-07-22'
- ignore_column :modsecurity_mode, remove_with: '14.2', remove_after: '2021-07-22'
-
enum ingress_type: {
nginx: 1
}
diff --git a/app/models/concerns/ci/metadatable.rb b/app/models/concerns/ci/metadatable.rb
index 8c3a05c23f0..71b26b70bbf 100644
--- a/app/models/concerns/ci/metadatable.rb
+++ b/app/models/concerns/ci/metadatable.rb
@@ -34,7 +34,7 @@ module Ci
end
def ensure_metadata
- metadata || build_metadata(project: project)
+ metadata || build_metadata(project: project, partition_id: partition_id)
end
def degenerated?
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index d155a295481..3a438b2cf63 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -1566,7 +1566,7 @@ class MergeRequest < ApplicationRecord
end
def has_test_reports?
- actual_head_pipeline&.has_reports?(Ci::JobArtifact.of_report_type(:test))
+ actual_head_pipeline&.complete_and_has_reports?(Ci::JobArtifact.of_report_type(:test))
end
def predefined_variables
@@ -1596,7 +1596,7 @@ class MergeRequest < ApplicationRecord
end
def has_accessibility_reports?
- actual_head_pipeline.present? && actual_head_pipeline.has_reports?(Ci::JobArtifact.of_report_type(:accessibility))
+ actual_head_pipeline.present? && actual_head_pipeline.complete_and_has_reports?(Ci::JobArtifact.of_report_type(:accessibility))
end
def has_coverage_reports?
@@ -1604,7 +1604,7 @@ class MergeRequest < ApplicationRecord
end
def has_terraform_reports?
- actual_head_pipeline&.has_reports?(Ci::JobArtifact.of_report_type(:terraform))
+ actual_head_pipeline&.complete_and_has_reports?(Ci::JobArtifact.of_report_type(:terraform))
end
def compare_accessibility_reports
@@ -1644,7 +1644,7 @@ class MergeRequest < ApplicationRecord
end
def has_codequality_reports?
- actual_head_pipeline&.has_reports?(Ci::JobArtifact.of_report_type(:codequality))
+ actual_head_pipeline&.complete_and_has_reports?(Ci::JobArtifact.of_report_type(:codequality))
end
def compare_codequality_reports
@@ -1694,11 +1694,11 @@ class MergeRequest < ApplicationRecord
end
def has_sast_reports?
- !!actual_head_pipeline&.has_reports?(::Ci::JobArtifact.of_report_type(:sast))
+ !!actual_head_pipeline&.complete_and_has_reports?(::Ci::JobArtifact.of_report_type(:sast))
end
def has_secret_detection_reports?
- !!actual_head_pipeline&.has_reports?(::Ci::JobArtifact.of_report_type(:secret_detection))
+ !!actual_head_pipeline&.complete_and_has_reports?(::Ci::JobArtifact.of_report_type(:secret_detection))
end
def compare_sast_reports(current_user)
diff --git a/app/models/project.rb b/app/models/project.rb
index 0be24252fa8..5130a476332 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -51,8 +51,6 @@ class Project < ApplicationRecord
BoardLimitExceeded = Class.new(StandardError)
ExportLimitExceeded = Class.new(StandardError)
- ignore_columns :mirror_last_update_at, :mirror_last_successful_update_at, remove_after: '2021-09-22', remove_with: '14.4'
- ignore_columns :pull_mirror_branch_prefix, remove_after: '2021-09-22', remove_with: '14.4'
ignore_columns :build_coverage_regex, remove_after: '2022-10-22', remove_with: '15.5'
STATISTICS_ATTRIBUTE = 'repositories_count'
diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb
index 02f25a82307..af175b8da1c 100644
--- a/app/services/ci/create_pipeline_service.rb
+++ b/app/services/ci/create_pipeline_service.rb
@@ -23,6 +23,7 @@ module Ci
Gitlab::Ci::Pipeline::Chain::RemoveUnwantedChatJobs,
Gitlab::Ci::Pipeline::Chain::SeedBlock,
Gitlab::Ci::Pipeline::Chain::EvaluateWorkflowRules,
+ Gitlab::Ci::Pipeline::Chain::AssignPartition,
Gitlab::Ci::Pipeline::Chain::Seed,
Gitlab::Ci::Pipeline::Chain::Limit::Size,
Gitlab::Ci::Pipeline::Chain::Limit::Deployments,
diff --git a/app/services/ci/job_artifacts/track_artifact_report_service.rb b/app/services/ci/job_artifacts/track_artifact_report_service.rb
index 349050c173e..d553d0b4e6f 100644
--- a/app/services/ci/job_artifacts/track_artifact_report_service.rb
+++ b/app/services/ci/job_artifacts/track_artifact_report_service.rb
@@ -10,7 +10,7 @@ module Ci
def execute(pipeline)
REPORT_TRACKED.each do |report|
- if pipeline.has_reports?(Ci::JobArtifact.of_report_type(report))
+ if pipeline.complete_and_has_reports?(Ci::JobArtifact.of_report_type(report))
track_usage_event(event_name(report), [pipeline.id, pipeline.user_id].join(VALUES_DELIMITER))
end
end
diff --git a/app/services/ci/pipelines/add_job_service.rb b/app/services/ci/pipelines/add_job_service.rb
index fc852bc3edd..dfbb37cf0dc 100644
--- a/app/services/ci/pipelines/add_job_service.rb
+++ b/app/services/ci/pipelines/add_job_service.rb
@@ -39,11 +39,13 @@ module Ci
job.pipeline = pipeline
job.project = pipeline.project
job.ref = pipeline.ref
+ job.partition_id = pipeline.partition_id
# update metadata since it might have been lazily initialised before this call
# metadata is present on `Ci::Processable`
if job.respond_to?(:metadata) && job.metadata
job.metadata.project = pipeline.project
+ job.metadata.partition_id = pipeline.partition_id
end
end
end
diff --git a/app/services/issues/export_csv_service.rb b/app/services/issues/export_csv_service.rb
index 6209127bd86..46e4b865dc3 100644
--- a/app/services/issues/export_csv_service.rb
+++ b/app/services/issues/export_csv_service.rb
@@ -5,20 +5,20 @@ module Issues
include Gitlab::Routing.url_helpers
include GitlabRoutingHelper
- def initialize(issuables_relation, project)
- super
+ def initialize(issuables_relation, project, user = nil)
+ super(issuables_relation, project)
@labels = @issuables.labels_hash.transform_values { |labels| labels.sort.join(',').presence }
end
- def email(user)
- Notify.issues_csv_email(user, project, csv_data, csv_builder.status).deliver_now
+ def email(mail_to_user)
+ Notify.issues_csv_email(mail_to_user, project, csv_data, csv_builder.status).deliver_now
end
private
def associations_to_preload
- %i(author assignees timelogs milestone project)
+ [:author, :assignees, :timelogs, :milestone, { project: { namespace: :route } }]
end
def header_to_value_hash
diff --git a/app/views/projects/pipelines/_with_tabs.html.haml b/app/views/projects/pipelines/_with_tabs.html.haml
index 78e404d15c6..e83547fd8f8 100644
--- a/app/views/projects/pipelines/_with_tabs.html.haml
+++ b/app/views/projects/pipelines/_with_tabs.html.haml
@@ -42,7 +42,7 @@
#js-pipeline-tests-detail{ data: { summary_endpoint: summary_project_pipeline_tests_path(@project, @pipeline, format: :json),
suite_endpoint: project_pipeline_test_path(@project, @pipeline, suite_name: 'suite', format: :json),
blob_path: project_blob_path(@project, @pipeline.sha),
- has_test_report: @pipeline.has_reports?(Ci::JobArtifact.of_report_type(:test)).to_s,
+ has_test_report: @pipeline.complete_and_has_reports?(Ci::JobArtifact.of_report_type(:test)).to_s,
empty_state_image_path: image_path('illustrations/empty-state/empty-test-cases-lg.svg'),
artifacts_expired_image_path: image_path('illustrations/pipeline.svg') } }
= render_if_exists "projects/pipelines/tabs_content", pipeline: @pipeline, project: @project
diff --git a/app/views/shared/issuable/form/_merge_params.html.haml b/app/views/shared/issuable/form/_merge_params.html.haml
index e28c5b2af03..09086d3aa82 100644
--- a/app/views/shared/issuable/form/_merge_params.html.haml
+++ b/app/views/shared/issuable/form/_merge_params.html.haml
@@ -11,7 +11,7 @@
- if issuable.can_remove_source_branch?(current_user)
.form-check.gl-mb-3
= hidden_field_tag 'merge_request[force_remove_source_branch]', '0', id: nil
- = check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch?, class: 'form-check-input'
+ = check_box_tag 'merge_request[force_remove_source_branch]', '1', issuable.force_remove_source_branch?, class: 'form-check-input js-form-update'
= label_tag 'merge_request[force_remove_source_branch]', class: 'form-check-label' do
= _("Delete source branch when merge request is accepted.")
- if !project.squash_never?
@@ -21,7 +21,7 @@
= check_box_tag 'merge_request[squash]', '1', project.squash_enabled_by_default?, class: 'form-check-input', disabled: 'true'
- else
= hidden_field_tag 'merge_request[squash]', '0', id: nil
- = check_box_tag 'merge_request[squash]', '1', issuable_squash_option?(issuable, project), class: 'form-check-input'
+ = check_box_tag 'merge_request[squash]', '1', issuable_squash_option?(issuable, project), class: 'form-check-input js-form-update'
= label_tag 'merge_request[squash]', class: 'form-check-label' do
= _("Squash commits when merge request is accepted.")
= link_to sprite_icon('question-o'), help_page_path('user/project/merge_requests/squash_and_merge'), target: '_blank', rel: 'noopener noreferrer'
diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb
index 29df6da6ef1..262ef1dea21 100644
--- a/config/initializers/sidekiq.rb
+++ b/config/initializers/sidekiq.rb
@@ -127,3 +127,4 @@ end
Sidekiq::Scheduled::Poller.prepend Gitlab::Patch::SidekiqPoller
Sidekiq::Cron::Poller.prepend Gitlab::Patch::SidekiqPoller
+Sidekiq::Cron::Poller.prepend Gitlab::Patch::SidekiqCronPoller
diff --git a/config/initializers/wikicloth_redos_patch.rb b/config/initializers/wikicloth_redos_patch.rb
index 4ff545dd6f7..95901378891 100644
--- a/config/initializers/wikicloth_redos_patch.rb
+++ b/config/initializers/wikicloth_redos_patch.rb
@@ -1,3 +1,27 @@
+# This file contains code based on the wikicloth project:
+# https://github.com/nricciar/wikicloth
+#
+# Copyright (c) 2009 The wikicloth authors.
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
# frozen_string_literal: true
require 'wikicloth'
@@ -20,7 +44,10 @@ require 'digest/sha2'
# - https://gitlab.com/gitlab-org/gitlab/-/issues/361266
# Guard to ensure we remember to delete this patch if they ever release a new version of wikicloth
-raise 'New version of WikiCloth detected, please remove this patch' unless Gem::Version.new(WikiCloth::VERSION) == Gem::Version.new('0.8.1')
+unless Gem::Version.new(WikiCloth::VERSION) == Gem::Version.new('0.8.1')
+ raise 'New version of WikiCloth detected, please either update the version for this check, ' \
+ 'or remove this patch if no longer needed'
+end
# rubocop:disable Style/ClassAndModuleChildren
# rubocop:disable Layout/SpaceAroundEqualsInParameterDefault
@@ -43,6 +70,12 @@ raise 'New version of WikiCloth detected, please remove this patch' unless Gem::
# rubocop:disable Style/RegexpLiteralMixedPreserve
# rubocop:disable Style/RedundantRegexpCharacterClass
# rubocop:disable Performance/StringInclude
+# rubocop:disable Layout/LineLength
+# rubocop:disable Style/RedundantSelf
+# rubocop:disable Style/SymbolProc
+# rubocop:disable Layout/SpaceInsideParens
+# rubocop:disable Style/GuardClause
+# rubocop:disable Style/RedundantRegexpEscape
module WikiCloth
class WikiCloth
def render(opt={})
@@ -218,3 +251,9 @@ end
# rubocop:enable Style/RegexpLiteralMixedPreserve
# rubocop:enable Style/RedundantRegexpCharacterClass
# rubocop:enable Performance/StringInclude
+# rubocop:enable Layout/LineLength
+# rubocop:enable Style/RedundantSelf
+# rubocop:enable Style/SymbolProc
+# rubocop:enable Layout/SpaceInsideParens
+# rubocop:enable Style/GuardClause
+# rubocop:enable Style/RedundantRegexpEscape
diff --git a/config/initializers/wikicloth_ruby_3_patch.rb b/config/initializers/wikicloth_ruby_3_patch.rb
new file mode 100644
index 00000000000..d80383a125d
--- /dev/null
+++ b/config/initializers/wikicloth_ruby_3_patch.rb
@@ -0,0 +1,272 @@
+# This file contains code based on the wikicloth project:
+# https://github.com/nricciar/wikicloth
+#
+# Copyright (c) 2009 The wikicloth authors.
+#
+# Permission is hereby granted, free of charge, to any person obtaining
+# a copy of this software and associated documentation files (the
+# "Software"), to deal in the Software without restriction, including
+# without limitation the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the Software, and to
+# permit persons to whom the Software is furnished to do so, subject to
+# the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+#
+# frozen_string_literal: true
+
+require 'wikicloth'
+require 'wikicloth/wiki_buffer/var'
+
+# Adds patch for changes in this PRs:
+#
+# https://github.com/nricciar/wikicloth/pull/110
+#
+# The maintainers are not releasing new versions, so we
+# need to patch it here.
+#
+# If they ever do release a version, then we can remove this file.
+#
+# See:
+# - https://gitlab.com/gitlab-org/gitlab/-/issues/372400
+
+# Guard to ensure we remember to delete this patch if they ever release a new version of wikicloth
+unless Gem::Version.new(WikiCloth::VERSION) == Gem::Version.new('0.8.1')
+ raise 'New version of WikiCloth detected, please either update the version for this check, ' \
+ 'or remove this patch if no longer needed'
+end
+
+# rubocop:disable Style/ClassAndModuleChildren
+# rubocop:disable Style/HashSyntax
+# rubocop:disable Layout/SpaceAfterComma
+# rubocop:disable Style/RescueStandardError
+# rubocop:disable Metrics/AbcSize
+# rubocop:disable Metrics/CyclomaticComplexity
+# rubocop:disable Metrics/PerceivedComplexity
+# rubocop:disable Cop/LineBreakAroundConditionalBlock
+# rubocop:disable Layout/EmptyLineAfterGuardClause
+# rubocop:disable Performance/ReverseEach
+# rubocop:disable Style/PerlBackrefs
+# rubocop:disable Style/RedundantRegexpCharacterClass
+# rubocop:disable Performance/StringInclude
+# rubocop:disable Style/IfUnlessModifier
+# rubocop:disable Layout/LineLength
+# rubocop:disable Lint/DeprecatedClassMethods
+# rubocop:disable Lint/UselessAssignment
+# rubocop:disable Lint/RedundantStringCoercion
+# rubocop:disable Style/StringLiteralsInInterpolation
+# rubocop:disable Lint/UriEscapeUnescape
+# rubocop:disable Style/For
+# rubocop:disable Style/SlicingWithRange
+# rubocop:disable Style/GuardClause
+# rubocop:disable Style/ZeroLengthPredicate
+# rubocop:disable Cop/LineBreakAfterGuardClauses
+# rubocop:disable Layout/MultilineHashBraceLayout
+module WikiCloth
+ class WikiCloth
+ class MathExtension < Extension
+ #
+ #
+ element 'math', :skip_html => true, :run_globals => false do |buffer|
+ blahtex_path = @options[:blahtex_path] || '/usr/bin/blahtex'
+ blahtex_png_path = @options[:blahtex_png_path] || '/tmp'
+ blahtex_options = @options[:blahtex_options] || '--texvc-compatible-commands --mathml-version-1-fonts --disallow-plane-1 --spacing strict'
+
+ if File.exists?(blahtex_path) && @options[:math_formatter] != :google
+ begin
+ # pass tex markup to blahtex
+ response = IO.popen("#{blahtex_path} #{blahtex_options} --png --mathml --png-directory #{blahtex_png_path}","w+") do |pipe|
+ pipe.write(buffer.element_content)
+ pipe.close_write
+ pipe.gets
+ end
+
+ xml_response = REXML::Document.new(response).root
+
+ if @options[:blahtex_html_prefix]
+ # render as embedded image
+ file_md5 = xml_response.elements["png/md5"].text
+ ""
+ else
+ # render as mathml
+ html = xml_response.elements["mathml/markup"].text
+ ""
+ end
+ rescue => err
+ # blahtex error
+ "#{I18n.t("unable to parse mathml", :error => err)}"
+ end
+ else
+ # if blahtex does not exist fallback to google charts api
+ # This is the patched line from:
+ # https://github.com/nricciar/wikicloth/pull/110/files#diff-f0cb4c400957bbdcc4c97d69d2aa7f48d8ba56c5943e484863f620605d7d17d4R37
+ encoded_string = URI.encode_www_form_component(buffer.element_content)
+ ""
+ end
+ end
+ end
+
+ class WikiBuffer::Var < WikiBuffer
+ def default_functions(name,params)
+ case name
+ when "#if"
+ params.first.blank? ? params[2] : params[1]
+ when "#switch"
+ match = params.first
+ default = nil
+ for p in params[1..-1]
+ temp = p.split("=")
+ if p !~ /=/ && temp.length == 1 && p == params.last
+ return p
+ elsif temp.instance_of?(Array) && temp.length > 0
+ test = temp.first.strip
+ default = temp[1..-1].join("=").strip if test == "#default"
+ return temp[1..-1].join("=").strip if test == match || (test == "none" && match.blank?)
+ end
+ end
+ default.nil? ? "" : default
+ when "#expr"
+ begin
+ ExpressionParser::Parser.new.parse(params.first)
+ rescue RuntimeError
+ I18n.t('expression error', :error => $!)
+ end
+ when "#ifexpr"
+ val = false
+ begin
+ val = ExpressionParser::Parser.new.parse(params.first)
+ rescue RuntimeError
+ end
+ if val
+ params[1]
+ else
+ params[2]
+ end
+ when "#ifeq"
+ if params[0] =~ /^[0-9A-Fa-f]+$/ && params[1] =~ /^[0-9A-Fa-f]+$/
+ params[0].to_i == params[1].to_i ? params[2] : params[3]
+ else
+ params[0] == params[1] ? params[2] : params[3]
+ end
+ when "#len"
+ params.first.length
+ when "#sub"
+ params.first[params[1].to_i,params[2].to_i]
+ when "#pad"
+ case params[3]
+ when "right"
+ params[0].ljust(params[1].to_i,params[2])
+ when "center"
+ params[0].center(params[1].to_i,params[2])
+ else
+ params[0].rjust(params[1].to_i,params[2])
+ end
+ when "#iferror"
+ params.first =~ /error/ ? params[1] : params[2]
+ when "#capture"
+ @options[:params][params.first] = params[1]
+ ""
+ when "urlencode"
+ # This is the patched line from:
+ # https://github.com/nricciar/wikicloth/pull/110/files#diff-f262faf4fadb222cca87185be0fb65b3f49659abc840794cc83a736d41310fb1R170
+ URI.encode_www_form_component(params.first)
+ when "lc"
+ params.first.downcase
+ when "uc"
+ params.first.upcase
+ when "ucfirst"
+ params.first.capitalize
+ when "lcfirst"
+ params.first[0,1].downcase + params.first[1..-1]
+ when "anchorencode"
+ params.first.gsub(/\s+/,'_')
+ when "plural"
+ begin
+ expr_value = ExpressionParser::Parser.new.parse(params.first)
+ expr_value.to_i == 1 ? params[1] : params[2]
+ rescue RuntimeError
+ I18n.t('expression error', :error => $!)
+ end
+ when "ns"
+ values = {
+ "" => "", "0" => "",
+ "1" => localise_ns("Talk"), "talk" => localise_ns("Talk"),
+ "6" => localise_ns("File"), "file" => localise_ns("File"), "image" => localise_ns("File"),
+ "10" => localise_ns("Template"), "template" => localise_ns("Template"),
+ "14" => localise_ns("Category"), "category" => localise_ns("Category"),
+ "-1" => localise_ns("Special"), "special" => localise_ns("Special"),
+ "12" => localise_ns("Help"), "help" => localise_ns("Help"),
+ "-2" => localise_ns("Media"), "media" => localise_ns("Media") }
+
+ values[localise_ns(params.first,:en).gsub(/\s+/,'_').downcase]
+ when "#language"
+ WikiNamespaces.language_name(params.first)
+ when "#tag"
+ return "" if params.empty?
+ elem = Builder::XmlMarkup.new
+ return elem.tag!(params.first) if params.length == 1
+ return elem.tag!(params.first) { |e| e << params.last } if params.length == 2
+ tag_attrs = {}
+ params[1..-2].each do |attr|
+ tag_attrs[$1] = $2 if attr =~ /^\s*([\w]+)\s*=\s*"(.*)"\s*$/
+ end
+ elem.tag!(params.first,tag_attrs) { |e| e << params.last }
+ when "debug"
+ ret = nil
+ case params.first
+ when "param"
+ @options[:buffer].buffers.reverse.each do |b|
+ if b.instance_of?(WikiBuffer::HTMLElement) && b.element_name == "template"
+ ret = b.get_param(params[1])
+ end
+ end
+ ret
+ when "buffer"
+ ret = ""
+ buffer = @options[:buffer].buffers
+ buffer.each do |b|
+ ret += " --- #{b.class}"
+ ret += b.instance_of?(WikiBuffer::HTMLElement) ? " -- #{b.element_name}\n" : " -- #{b.data}\n"
+ end
+ "#{ret}
"
+ end
+ end
+ end
+ end
+ end
+end
+# rubocop:enable Style/ClassAndModuleChildren
+# rubocop:enable Style/HashSyntax
+# rubocop:enable Layout/SpaceAfterComma
+# rubocop:enable Style/RescueStandardError
+# rubocop:enable Metrics/AbcSize
+# rubocop:enable Metrics/CyclomaticComplexity
+# rubocop:enable Metrics/PerceivedComplexity
+# rubocop:enable Cop/LineBreakAroundConditionalBlock
+# rubocop:enable Layout/EmptyLineAfterGuardClause
+# rubocop:enable Performance/ReverseEach
+# rubocop:enable Style/PerlBackrefs
+# rubocop:enable Style/RedundantRegexpCharacterClass
+# rubocop:enable Performance/StringInclude
+# rubocop:enable Style/IfUnlessModifier
+# rubocop:enable Layout/LineLength
+# rubocop:enable Lint/DeprecatedClassMethods
+# rubocop:enable Lint/UselessAssignment
+# rubocop:enable Lint/RedundantStringCoercion
+# rubocop:enable Style/StringLiteralsInInterpolation
+# rubocop:enable Lint/UriEscapeUnescape
+# rubocop:enable Style/For
+# rubocop:enable Style/SlicingWithRange
+# rubocop:enable Style/GuardClause
+# rubocop:enable Style/ZeroLengthPredicate
+# rubocop:enable Cop/LineBreakAfterGuardClauses
+# rubocop:enable Layout/MultilineHashBraceLayout
diff --git a/config/metrics/counts_28d/20210216174910_analytics_unique_visits_for_any_target_monthly.yml b/config/metrics/counts_28d/20210216174910_analytics_unique_visits_for_any_target_monthly.yml
index e766c555d3a..fbf4be136cb 100644
--- a/config/metrics/counts_28d/20210216174910_analytics_unique_visits_for_any_target_monthly.yml
+++ b/config/metrics/counts_28d/20210216174910_analytics_unique_visits_for_any_target_monthly.yml
@@ -48,7 +48,6 @@ tier:
- premium
- ultimate
performance_indicator_type:
-- smau
- gmau
- paid_gmau
milestone: "<13.9"
diff --git a/config/metrics/counts_28d/20220905210112_users_visiting_environments_pages_monthly.yml b/config/metrics/counts_28d/20220905210112_users_visiting_environments_pages_monthly.yml
new file mode 100644
index 00000000000..3eac5ca1c01
--- /dev/null
+++ b/config/metrics/counts_28d/20220905210112_users_visiting_environments_pages_monthly.yml
@@ -0,0 +1,26 @@
+---
+key_path: redis_hll_counters.environments.users_visiting_environments_pages_monthly
+description: Monthly count of unique users visiting environments pages
+product_section: ops
+product_stage: release
+product_group: release
+product_category: environment_management
+value_type: number
+status: active
+milestone: "15.4"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/97063
+time_frame: 28d
+data_source: redis_hll
+data_category: optional
+instrumentation_class: RedisHLLMetric
+performance_indicator_type: []
+options:
+ events:
+ - users_visiting_environments_pages
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
diff --git a/config/metrics/counts_all/20220825232556_count_user_auth.yml b/config/metrics/counts_all/20220825232556_count_user_auth.yml
index 623e0dbb7a4..c849cf82047 100644
--- a/config/metrics/counts_all/20220825232556_count_user_auth.yml
+++ b/config/metrics/counts_all/20220825232556_count_user_auth.yml
@@ -13,7 +13,8 @@ time_frame: all
data_source: database
instrumentation_class: CountUserAuthMetric
data_category: optional
-performance_indicator_type: []
+performance_indicator_type:
+- smau
distribution:
- ce
- ee
diff --git a/db/migrate/20220906093857_add_column_branch_filter_strategy_to_web_hooks.rb b/db/migrate/20220906093857_add_column_branch_filter_strategy_to_web_hooks.rb
new file mode 100644
index 00000000000..739bedda9e0
--- /dev/null
+++ b/db/migrate/20220906093857_add_column_branch_filter_strategy_to_web_hooks.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+class AddColumnBranchFilterStrategyToWebHooks < Gitlab::Database::Migration[2.0]
+ def change
+ add_column :web_hooks, :branch_filter_strategy, :integer, null: false, default: 0, limit: 2
+ end
+end
diff --git a/db/schema_migrations/20220906093857 b/db/schema_migrations/20220906093857
new file mode 100644
index 00000000000..a5bc36ab6ef
--- /dev/null
+++ b/db/schema_migrations/20220906093857
@@ -0,0 +1 @@
+394f346e3a93f8a6b74fd0461eb59f569c6a18f90ae653c330a38e3a3706b5f6
\ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 8c2f64638f4..aa1548927a3 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -22922,7 +22922,8 @@ CREATE TABLE web_hooks (
disabled_until timestamp with time zone,
encrypted_url_variables bytea,
encrypted_url_variables_iv bytea,
- integration_id integer
+ integration_id integer,
+ branch_filter_strategy smallint DEFAULT 0 NOT NULL
);
CREATE SEQUENCE web_hooks_id_seq
diff --git a/doc/raketasks/backup_gitlab.md b/doc/raketasks/backup_gitlab.md
index 3f241ef2f05..e80fea21064 100644
--- a/doc/raketasks/backup_gitlab.md
+++ b/doc/raketasks/backup_gitlab.md
@@ -744,8 +744,8 @@ You can send backups to a locally-mounted share (for example, `NFS`,`CIFS`, or `
To do this, you must set the following configuration keys:
-- `backup_upload_remote_directory`: mounted directory that backups are copied to.
-- `backup_upload_connection.local_root`: subdirectory of the `backup_upload_remote_directory` directory. It is created if it doesn't exist.
+- `backup_upload_connection.local_root`: mounted directory that backups are copied to.
+- `backup_upload_remote_directory`: subdirectory of the `backup_upload_connection.local_root` directory. It is created if it doesn't exist.
If you want to copy the tarballs to the root of your mounted directory, use `.`.
When mounted, the directory set in the `local_root` key must be owned by either:
diff --git a/doc/user/group/subgroups/index.md b/doc/user/group/subgroups/index.md
index 12434de5efc..94491aeac4e 100644
--- a/doc/user/group/subgroups/index.md
+++ b/doc/user/group/subgroups/index.md
@@ -201,7 +201,7 @@ role on an ancestor group, add the user to the subgroup again with a higher role
## Mention subgroups
Mentioning subgroups ([`@`](../../discussions/index.md#mentions)) in issues, commits, and merge requests
-notifies all members of that group. Mentioning works the same as for projects and groups, and you can choose the group
+notifies all direct members of that group. Inherited members of a sub-group are not notified by mentions. Mentioning works the same as for projects and groups, and you can choose the group
of people to be notified.