summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-04-21 15:09:35 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-04-21 15:09:35 +0000
commit9c6578ed4e0bc92cd838ef96d978df54403e9609 (patch)
tree5dff7ad20ae6402e4b7a5a44fe4e81ef04855cdf /app
parent2af44d609eb8a1579169f9a350bc531d1081d77f (diff)
downloadgitlab-ce-9c6578ed4e0bc92cd838ef96d978df54403e9609.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/pages/projects/snippets/show/index.js8
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue13
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue7
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_view_selector.vue135
-rw-r--r--app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue5
-rw-r--r--app/assets/javascripts/pipelines/components/graph_shared/links_layer.vue48
-rw-r--r--app/controllers/projects/snippets_controller.rb4
-rw-r--r--app/graphql/mutations/boards/lists/base_update.rb40
-rw-r--r--app/graphql/mutations/boards/lists/update.rb22
-rw-r--r--app/helpers/application_settings_helper.rb6
-rw-r--r--app/helpers/snippets_helper.rb6
-rw-r--r--app/models/application_setting.rb16
-rw-r--r--app/models/application_setting_implementation.rb6
-rw-r--r--app/models/project_services/confluence_service.rb10
-rw-r--r--app/models/wiki.rb11
-rw-r--r--app/views/admin/application_settings/_abuse.html.haml6
-rw-r--r--app/views/admin/application_settings/_package_registry_limits.html.haml37
-rw-r--r--app/views/admin/application_settings/network.html.haml11
-rw-r--r--app/views/admin/requests_profiles/index.html.haml6
-rw-r--r--app/views/projects/snippets/show.html.haml2
-rw-r--r--app/views/shared/milestones/_sidebar.html.haml4
-rw-r--r--app/workers/container_expiration_policies/cleanup_container_repository_worker.rb171
22 files changed, 379 insertions, 195 deletions
diff --git a/app/assets/javascripts/pages/projects/snippets/show/index.js b/app/assets/javascripts/pages/projects/snippets/show/index.js
index f955a41e18a..c719601ee0b 100644
--- a/app/assets/javascripts/pages/projects/snippets/show/index.js
+++ b/app/assets/javascripts/pages/projects/snippets/show/index.js
@@ -1 +1,9 @@
import '~/snippet/snippet_show';
+
+const awardEmojiEl = document.getElementById('js-vue-awards-block');
+
+if (awardEmojiEl) {
+ import('~/emoji/awards_app')
+ .then((m) => m.default(awardEmojiEl))
+ .catch(() => {});
+}
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index 63048777724..47505093140 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -25,6 +25,10 @@ export default {
type: Object,
required: true,
},
+ showLinks: {
+ type: Boolean,
+ required: true,
+ },
viewType: {
type: String,
required: true,
@@ -91,8 +95,8 @@ export default {
collectMetrics: true,
};
},
- shouldHideLinks() {
- return this.isStageView;
+ showJobLinks() {
+ return !this.isStageView && this.showLinks;
},
shouldShowStageName() {
return !this.isStageView;
@@ -188,6 +192,7 @@ export default {
:config-paths="configPaths"
:linked-pipelines="upstreamPipelines"
:column-title="__('Upstream')"
+ :show-links="showJobLinks"
:type="$options.pipelineTypeConstants.UPSTREAM"
:view-type="viewType"
@error="onError"
@@ -202,9 +207,8 @@ export default {
:container-measurements="measurements"
:highlighted-job="hoveredJobName"
:metrics-config="metricsConfig"
- :never-show-links="shouldHideLinks"
+ :show-links="showJobLinks"
:view-type="viewType"
- default-link-color="gl-stroke-transparent"
@error="onError"
@highlightedJobsChange="updateHighlightedJobs"
>
@@ -234,6 +238,7 @@ export default {
:config-paths="configPaths"
:linked-pipelines="downstreamPipelines"
:column-title="__('Downstream')"
+ :show-links="showJobLinks"
:type="$options.pipelineTypeConstants.DOWNSTREAM"
:view-type="viewType"
@downstreamHovered="setSourceJob"
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
index 0bc6d883245..bff5d3ccdab 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component_wrapper.vue
@@ -48,6 +48,7 @@ export default {
pipeline: null,
pipelineLayers: null,
showAlert: false,
+ showLinks: false,
};
},
errorTexts: {
@@ -182,6 +183,9 @@ export default {
}
},
/* eslint-enable @gitlab/require-i18n-strings */
+ updateShowLinksState(val) {
+ this.showLinks = val;
+ },
updateViewType(type) {
this.currentViewType = type;
},
@@ -202,7 +206,9 @@ export default {
<graph-view-selector
v-if="showGraphViewSelector"
:type="currentViewType"
+ :show-links="showLinks"
@updateViewType="updateViewType"
+ @updateShowLinksState="updateShowLinksState"
/>
</local-storage-sync>
<gl-loading-icon v-if="showLoadingIcon" class="gl-mx-auto gl-my-4" size="lg" />
@@ -211,6 +217,7 @@ export default {
:config-paths="configPaths"
:pipeline="pipeline"
:pipeline-layers="getPipelineLayers()"
+ :show-links="showLinks"
:view-type="currentViewType"
@error="reportFailure"
@refreshPipelineGraph="refreshPipelineGraph"
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_view_selector.vue b/app/assets/javascripts/pipelines/components/graph/graph_view_selector.vue
index f33e6290e37..bc038dde21c 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_view_selector.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_view_selector.vue
@@ -1,17 +1,20 @@
<script>
-import { GlDropdown, GlDropdownItem, GlIcon, GlSprintf } from '@gitlab/ui';
+import { GlLoadingIcon, GlSegmentedControl, GlToggle } from '@gitlab/ui';
import { __ } from '~/locale';
import { STAGE_VIEW, LAYER_VIEW } from './constants';
export default {
name: 'GraphViewSelector',
components: {
- GlDropdown,
- GlDropdownItem,
- GlIcon,
- GlSprintf,
+ GlLoadingIcon,
+ GlSegmentedControl,
+ GlToggle,
},
props: {
+ showLinks: {
+ type: Boolean,
+ required: true,
+ },
type: {
type: String,
required: true,
@@ -19,67 +22,119 @@ export default {
},
data() {
return {
- currentViewType: STAGE_VIEW,
+ currentViewType: this.type,
+ showLinksActive: false,
+ isToggleLoading: false,
+ isSwitcherLoading: false,
};
},
i18n: {
- labelText: __('Order jobs by'),
+ viewLabelText: __('Group jobs by'),
+ linksLabelText: __('Show dependencies'),
},
views: {
[STAGE_VIEW]: {
type: STAGE_VIEW,
text: {
primary: __('Stage'),
- secondary: __('View the jobs grouped into stages'),
},
},
[LAYER_VIEW]: {
type: LAYER_VIEW,
text: {
- primary: __('%{codeStart}needs:%{codeEnd} relationships'),
- secondary: __('View what jobs are needed for a job to run'),
+ primary: __('Job dependencies'),
},
},
},
computed: {
- currentDropdownText() {
- return this.$options.views[this.type].text.primary;
+ showLinksToggle() {
+ return this.currentViewType === LAYER_VIEW;
+ },
+ viewTypesList() {
+ return Object.keys(this.$options.views).map((key) => {
+ return {
+ value: key,
+ text: this.$options.views[key].text.primary,
+ };
+ });
+ },
+ },
+ watch: {
+ /*
+ How does this reset the loading? As we note in the methods comment below,
+ the loader is set to on before the update work is undertaken (in the parent).
+ Once the work is complete, one of these values will change, since that's the
+ point of the work. When that happens, the related value will update and we are done.
+
+ The bonus for this approach is that it works the same whichever "direction"
+ the work goes in.
+ */
+ showLinks() {
+ this.isToggleLoading = false;
+ },
+ type() {
+ this.isSwitcherLoading = false;
},
},
methods: {
- itemClick(type) {
- this.$emit('updateViewType', type);
+ /*
+ In both toggle methods, we use setTimeout so that the loading indicator displays,
+ then the work is done to update the DOM. The process is:
+ → user clicks
+ → call stack: set loading to true
+ → render: the loading icon appears on the screen
+ → callback queue: now do the work to calculate the new view / links
+ (note: this work is done in the parent after the event is emitted)
+
+ setTimeout is how we move the work to the callback queue.
+ We can't use nextTick because that is called before the render loop.
+
+ See https://www.hesselinkwebdesign.nl/2019/nexttick-vs-settimeout-in-vue/ for more details.
+ */
+ toggleView(type) {
+ this.isSwitcherLoading = true;
+ setTimeout(() => {
+ this.$emit('updateViewType', type);
+ });
+ },
+ toggleShowLinksActive(val) {
+ this.isToggleLoading = true;
+ setTimeout(() => {
+ this.$emit('updateShowLinksState', val);
+ });
},
},
};
</script>
<template>
- <div class="gl-display-flex gl-align-items-center gl-my-4">
- <span>{{ $options.i18n.labelText }}</span>
- <gl-dropdown data-testid="pipeline-view-selector" class="gl-ml-4">
- <template #button-content>
- <gl-sprintf :message="currentDropdownText">
- <template #code="{ content }">
- <code> {{ content }} </code>
- </template>
- </gl-sprintf>
- <gl-icon class="gl-px-2" name="angle-down" :size="16" />
- </template>
- <gl-dropdown-item
- v-for="view in $options.views"
- :key="view.type"
- :secondary-text="view.text.secondary"
- @click="itemClick(view.type)"
- >
- <b>
- <gl-sprintf :message="view.text.primary">
- <template #code="{ content }">
- <code> {{ content }} </code>
- </template>
- </gl-sprintf>
- </b>
- </gl-dropdown-item>
- </gl-dropdown>
+ <div class="gl-relative gl-display-flex gl-align-items-center gl-w-max-content gl-my-4">
+ <gl-loading-icon
+ v-if="isSwitcherLoading"
+ data-testid="switcher-loading-state"
+ class="gl-absolute gl-w-full gl-bg-white gl-opacity-5 gl-z-index-2"
+ size="lg"
+ />
+ <span class="gl-font-weight-bold">{{ $options.i18n.viewLabelText }}</span>
+ <gl-segmented-control
+ v-model="currentViewType"
+ :options="viewTypesList"
+ :disabled="isSwitcherLoading"
+ data-testid="pipeline-view-selector"
+ class="gl-mx-4"
+ @input="toggleView"
+ />
+
+ <div v-if="showLinksToggle">
+ <gl-toggle
+ v-model="showLinksActive"
+ data-testid="show-links-toggle"
+ class="gl-mx-4"
+ :label="$options.i18n.linksLabelText"
+ :is-loading="isToggleLoading"
+ label-position="left"
+ @change="toggleShowLinksActive"
+ />
+ </div>
</div>
</template>
diff --git a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
index 7f772e35e55..89ca6f43abc 100644
--- a/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
+++ b/app/assets/javascripts/pipelines/components/graph/linked_pipelines_column.vue
@@ -32,6 +32,10 @@ export default {
type: Array,
required: true,
},
+ showLinks: {
+ type: Boolean,
+ required: true,
+ },
type: {
type: String,
required: true,
@@ -217,6 +221,7 @@ export default {
:config-paths="configPaths"
:pipeline="currentPipeline"
:pipeline-layers="getPipelineLayers(pipeline.id)"
+ :show-links="showLinks"
:is-linked-pipeline="true"
:view-type="viewType"
/>
diff --git a/app/assets/javascripts/pipelines/components/graph_shared/links_layer.vue b/app/assets/javascripts/pipelines/components/graph_shared/links_layer.vue
index 8dbab245f44..83843de8085 100644
--- a/app/assets/javascripts/pipelines/components/graph_shared/links_layer.vue
+++ b/app/assets/javascripts/pipelines/components/graph_shared/links_layer.vue
@@ -1,5 +1,4 @@
<script>
-import { GlAlert } from '@gitlab/ui';
import { isEmpty } from 'lodash';
import { __ } from '~/locale';
import {
@@ -19,10 +18,8 @@ import LinksInner from './links_inner.vue';
export default {
name: 'LinksLayer',
components: {
- GlAlert,
LinksInner,
},
- MAX_GROUPS: 200,
props: {
containerMeasurements: {
type: Object,
@@ -37,10 +34,10 @@ export default {
required: false,
default: () => ({}),
},
- neverShowLinks: {
+ showLinks: {
type: Boolean,
required: false,
- default: false,
+ default: true,
},
},
data() {
@@ -67,29 +64,8 @@ export default {
shouldCollectMetrics() {
return this.metricsConfig.collectMetrics && this.metricsConfig.path;
},
- showAlert() {
- /*
- This is a hard override that allows us to turn off the links without
- needing to remove the component entirely for iteration or based on graph type.
- */
- if (this.neverShowLinks) {
- return false;
- }
-
- return !this.containerZero && !this.showLinkedLayers && !this.alertDismissed;
- },
showLinkedLayers() {
- /*
- This is a hard override that allows us to turn off the links without
- needing to remove the component entirely for iteration or based on graph type.
- */
- if (this.neverShowLinks) {
- return false;
- }
-
- return (
- !this.containerZero && (this.showLinksOverride || this.numGroups < this.$options.MAX_GROUPS)
- );
+ return this.showLinks && !this.containerZero;
},
},
errorCaptured(err, _vm, info) {
@@ -103,7 +79,7 @@ export default {
is closed and functionality is enabled by default.
*/
- if (this.neverShowLinks && !isEmpty(this.pipelineData)) {
+ if (!this.showLinks && !isEmpty(this.pipelineData)) {
window.requestAnimationFrame(() => {
this.prepareLinkData();
});
@@ -151,13 +127,6 @@ export default {
reportPerformance(this.metricsConfig.path, data);
});
},
- dismissAlert() {
- this.alertDismissed = true;
- },
- overrideShowLinks() {
- this.dismissAlert();
- this.showLinksOverride = true;
- },
prepareLinkData() {
this.beginPerfMeasure();
let numLinks;
@@ -185,15 +154,6 @@ export default {
<slot></slot>
</links-inner>
<div v-else>
- <gl-alert
- v-if="showAlert"
- class="gl-ml-4 gl-mb-4"
- :primary-button-text="$options.i18n.showLinksAnyways"
- @primaryAction="overrideShowLinks"
- @dismiss="dismissAlert"
- >
- {{ $options.i18n.tooManyJobs }}
- </gl-alert>
<div class="gl-display-flex gl-relative">
<slot></slot>
</div>
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index ff28c3be298..de2ab16b5b1 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -13,6 +13,10 @@ class Projects::SnippetsController < Projects::Snippets::ApplicationController
before_action :authorize_read_snippet!, except: [:new, :index]
before_action :authorize_update_snippet!, only: :edit
+ before_action only: [:show] do
+ push_frontend_feature_flag(:improved_emoji_picker, @project, default_enabled: :yaml)
+ end
+
def index
@snippet_counts = ::Snippets::CountService
.new(current_user, project: @project)
diff --git a/app/graphql/mutations/boards/lists/base_update.rb b/app/graphql/mutations/boards/lists/base_update.rb
new file mode 100644
index 00000000000..c0aa361936f
--- /dev/null
+++ b/app/graphql/mutations/boards/lists/base_update.rb
@@ -0,0 +1,40 @@
+# frozen_string_literal: true
+
+module Mutations
+ module Boards
+ module Lists
+ class BaseUpdate < BaseMutation
+ argument :position, GraphQL::INT_TYPE,
+ required: false,
+ description: 'Position of list within the board.'
+
+ argument :collapsed, GraphQL::BOOLEAN_TYPE,
+ required: false,
+ description: 'Indicates if the list is collapsed for this user.'
+
+ def resolve(list: nil, **args)
+ if list.nil? || !can_read_list?(list)
+ raise_resource_not_available_error!
+ end
+
+ update_result = update_list(list, args)
+
+ {
+ list: update_result[:list],
+ errors: list.errors.full_messages
+ }
+ end
+
+ private
+
+ def update_list(list, args)
+ raise NotImplementedError
+ end
+
+ def can_read_list?(list)
+ raise NotImplementedError
+ end
+ end
+ end
+ end
+end
diff --git a/app/graphql/mutations/boards/lists/update.rb b/app/graphql/mutations/boards/lists/update.rb
index 504082ec22c..d17dd5162a0 100644
--- a/app/graphql/mutations/boards/lists/update.rb
+++ b/app/graphql/mutations/boards/lists/update.rb
@@ -3,7 +3,7 @@
module Mutations
module Boards
module Lists
- class Update < BaseMutation
+ class Update < BaseUpdate
graphql_name 'UpdateBoardList'
argument :list_id, Types::GlobalIDType[List],
@@ -11,29 +11,11 @@ module Mutations
loads: Types::BoardListType,
description: 'Global ID of the list.'
- argument :position, GraphQL::INT_TYPE,
- required: false,
- description: 'Position of list within the board.'
-
- argument :collapsed, GraphQL::BOOLEAN_TYPE,
- required: false,
- description: 'Indicates if the list is collapsed for this user.'
-
field :list,
Types::BoardListType,
null: true,
description: 'Mutated list.'
- def resolve(list: nil, **args)
- raise_resource_not_available_error! unless can_read_list?(list)
- update_result = update_list(list, args)
-
- {
- list: update_result[:list],
- errors: list.errors.full_messages
- }
- end
-
private
def update_list(list, args)
@@ -42,8 +24,6 @@ module Mutations
end
def can_read_list?(list)
- return false unless list.present?
-
Ability.allowed?(current_user, :read_issue_board_list, list.board)
end
end
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 504ebb5606e..cda87cbd212 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -310,9 +310,15 @@ module ApplicationSettingsHelper
:throttle_authenticated_web_enabled,
:throttle_authenticated_web_period_in_seconds,
:throttle_authenticated_web_requests_per_period,
+ :throttle_authenticated_packages_api_enabled,
+ :throttle_authenticated_packages_api_period_in_seconds,
+ :throttle_authenticated_packages_api_requests_per_period,
:throttle_unauthenticated_enabled,
:throttle_unauthenticated_period_in_seconds,
:throttle_unauthenticated_requests_per_period,
+ :throttle_unauthenticated_packages_api_enabled,
+ :throttle_unauthenticated_packages_api_period_in_seconds,
+ :throttle_unauthenticated_packages_api_requests_per_period,
:throttle_protected_paths_enabled,
:throttle_protected_paths_period_in_seconds,
:throttle_protected_paths_requests_per_period,
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index f4af7a5a350..84eb0405c01 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -72,4 +72,10 @@ module SnippetsHelper
concat(file_count)
end
end
+
+ def project_snippets_award_api_path(snippet)
+ if Feature.enabled?(:improved_emoji_picker, snippet.project, default_enabled: :yaml)
+ api_v4_projects_snippets_award_emoji_path(id: snippet.project.id, snippet_id: snippet.id)
+ end
+ end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index f405f5ca5d3..caed36c9fe3 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -434,6 +434,14 @@ class ApplicationSetting < ApplicationRecord
presence: true,
numericality: { only_integer: true, greater_than: 0 }
+ validates :throttle_unauthenticated_packages_api_requests_per_period,
+ presence: true,
+ numericality: { only_integer: true, greater_than: 0 }
+
+ validates :throttle_unauthenticated_packages_api_period_in_seconds,
+ presence: true,
+ numericality: { only_integer: true, greater_than: 0 }
+
validates :throttle_authenticated_api_requests_per_period,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
@@ -450,6 +458,14 @@ class ApplicationSetting < ApplicationRecord
presence: true,
numericality: { only_integer: true, greater_than: 0 }
+ validates :throttle_authenticated_packages_api_requests_per_period,
+ presence: true,
+ numericality: { only_integer: true, greater_than: 0 }
+
+ validates :throttle_authenticated_packages_api_period_in_seconds,
+ presence: true,
+ numericality: { only_integer: true, greater_than: 0 }
+
validates :throttle_protected_paths_requests_per_period,
presence: true,
numericality: { only_integer: true, greater_than: 0 }
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index 66a8d1f8105..88fd17133f2 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -156,6 +156,9 @@ module ApplicationSettingImplementation
throttle_authenticated_web_enabled: false,
throttle_authenticated_web_period_in_seconds: 3600,
throttle_authenticated_web_requests_per_period: 7200,
+ throttle_authenticated_packages_api_enabled: false,
+ throttle_authenticated_packages_api_period_in_seconds: 15,
+ throttle_authenticated_packages_api_requests_per_period: 1000,
throttle_incident_management_notification_enabled: false,
throttle_incident_management_notification_per_period: 3600,
throttle_incident_management_notification_period_in_seconds: 3600,
@@ -165,6 +168,9 @@ module ApplicationSettingImplementation
throttle_unauthenticated_enabled: false,
throttle_unauthenticated_period_in_seconds: 3600,
throttle_unauthenticated_requests_per_period: 3600,
+ throttle_unauthenticated_packages_api_enabled: false,
+ throttle_unauthenticated_packages_api_period_in_seconds: 15,
+ throttle_unauthenticated_packages_api_requests_per_period: 800,
time_tracking_limit_to_hours: false,
two_factor_grace_period: 48,
unique_ips_limit_enabled: false,
diff --git a/app/models/project_services/confluence_service.rb b/app/models/project_services/confluence_service.rb
index 8a6f4de540c..028add66a14 100644
--- a/app/models/project_services/confluence_service.rb
+++ b/app/models/project_services/confluence_service.rb
@@ -27,7 +27,7 @@ class ConfluenceService < Service
end
def description
- s_('ConfluenceService|Connect a Confluence Cloud Workspace to GitLab')
+ s_('ConfluenceService|Link to a Confluence Workspace from the sidebar.')
end
def help
@@ -37,11 +37,11 @@ class ConfluenceService < Service
wiki_url = project.wiki.web_url
s_(
- 'ConfluenceService|Your GitLab Wiki can be accessed here: %{wiki_link}. To re-enable your GitLab Wiki, disable this integration' %
+ 'ConfluenceService|Your GitLab wiki is still available at %{wiki_link}. To re-enable the link to the GitLab wiki, disable this integration.' %
{ wiki_link: link_to(wiki_url, wiki_url) }
).html_safe
else
- s_('ConfluenceService|Enabling the Confluence Workspace will disable the default GitLab Wiki. Your GitLab Wiki data will be saved and you can always re-enable it later by turning off this integration').html_safe
+ s_('ConfluenceService|Link to a Confluence Workspace from the sidebar. Enabling this integration replaces the "Wiki" sidebar link with a link to the Confluence Workspace. The GitLab wiki is still available at the original URL.').html_safe
end
end
@@ -50,8 +50,8 @@ class ConfluenceService < Service
{
type: 'text',
name: 'confluence_url',
- title: 'Confluence Cloud Workspace URL',
- placeholder: s_('ConfluenceService|The URL of the Confluence Workspace'),
+ title: s_('Confluence Cloud Workspace URL'),
+ placeholder: 'https://example.atlassian.net/wiki',
required: true
}
]
diff --git a/app/models/wiki.rb b/app/models/wiki.rb
index 47fe40b0e57..089fc887d97 100644
--- a/app/models/wiki.rb
+++ b/app/models/wiki.rb
@@ -192,16 +192,9 @@ class Wiki
def delete_page(page, message = nil)
return unless page
- if Feature.enabled?(:gitaly_replace_wiki_delete_page, user, default_enabled: :yaml)
- capture_git_error(:deleted) do
- repository.delete_file(user, page.path, **multi_commit_options(:deleted, message, page.title))
+ capture_git_error(:deleted) do
+ repository.delete_file(user, page.path, **multi_commit_options(:deleted, message, page.title))
- after_wiki_activity
-
- true
- end
- else
- wiki.delete_page(page.path, commit_details(:deleted, message, page.title))
after_wiki_activity
true
diff --git a/app/views/admin/application_settings/_abuse.html.haml b/app/views/admin/application_settings/_abuse.html.haml
index f050c0816b1..fab3ce584f0 100644
--- a/app/views/admin/application_settings/_abuse.html.haml
+++ b/app/views/admin/application_settings/_abuse.html.haml
@@ -3,9 +3,9 @@
%fieldset
.form-group
- = f.label :abuse_notification_email, 'Abuse reports notification email', class: 'label-bold'
+ = f.label :abuse_notification_email, _('Abuse reports notification email'), class: 'label-bold'
= f.text_field :abuse_notification_email, class: 'form-control gl-form-input'
.form-text.text-muted
- Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.
+ = _('Abuse reports will be sent to this address if it is set. Abuse reports are always available in the admin area.')
- = f.submit 'Save changes', class: "gl-button btn btn-confirm"
+ = f.submit _('Save changes'), class: "gl-button btn btn-confirm"
diff --git a/app/views/admin/application_settings/_package_registry_limits.html.haml b/app/views/admin/application_settings/_package_registry_limits.html.haml
new file mode 100644
index 00000000000..b1dfd04c55e
--- /dev/null
+++ b/app/views/admin/application_settings/_package_registry_limits.html.haml
@@ -0,0 +1,37 @@
+= form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-packages-limits-settings'), html: { class: 'fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ %h5
+ = _('Unauthenticated API request rate limit')
+ .form-group
+ .form-check
+ = f.check_box :throttle_unauthenticated_packages_api_enabled, class: 'form-check-input', data: { qa_selector: 'throttle_unauthenticated_packages_api_checkbox' }
+ = f.label :throttle_unauthenticated_packages_api_enabled, class: 'form-check-label label-bold' do
+ = _('Enable unauthenticated API request rate limit')
+ %span.form-text.text-muted
+ = _('Helps reduce request volume (e.g. from crawlers or abusive bots)')
+ .form-group
+ = f.label :throttle_unauthenticated_packages_api_requests_per_period, 'Max unauthenticated API requests per period per IP', class: 'label-bold'
+ = f.number_field :throttle_unauthenticated_packages_api_requests_per_period, class: 'form-control gl-form-input'
+ .form-group
+ = f.label :throttle_unauthenticated_packages_api_period_in_seconds, 'Unauthenticated API rate limit period in seconds', class: 'label-bold'
+ = f.number_field :throttle_unauthenticated_packages_api_period_in_seconds, class: 'form-control gl-form-input'
+ %hr
+ %h5
+ = _('Authenticated API request rate limit')
+ .form-group
+ .form-check
+ = f.check_box :throttle_authenticated_packages_api_enabled, class: 'form-check-input', data: { qa_selector: 'throttle_authenticated_packages_api_checkbox' }
+ = f.label :throttle_authenticated_packages_api_enabled, class: 'form-check-label label-bold' do
+ = _('Enable authenticated API request rate limit')
+ %span.form-text.text-muted
+ = _('Helps reduce request volume (e.g. from crawlers or abusive bots)')
+ .form-group
+ = f.label :throttle_authenticated_packages_api_requests_per_period, 'Max authenticated API requests per period per user', class: 'label-bold'
+ = f.number_field :throttle_authenticated_packages_api_requests_per_period, class: 'form-control gl-form-input'
+ .form-group
+ = f.label :throttle_authenticated_packages_api_period_in_seconds, 'Authenticated API rate limit period in seconds', class: 'label-bold'
+ = f.number_field :throttle_authenticated_packages_api_period_in_seconds, class: 'form-control gl-form-input'
+
+ = f.submit 'Save changes', class: "gl-button btn btn-confirm", data: { qa_selector: 'save_changes_button' }
diff --git a/app/views/admin/application_settings/network.html.haml b/app/views/admin/application_settings/network.html.haml
index 72716e76013..72a27e4523f 100644
--- a/app/views/admin/application_settings/network.html.haml
+++ b/app/views/admin/application_settings/network.html.haml
@@ -24,6 +24,17 @@
.settings-content
= render 'ip_limits'
+%section.settings.as-packages-limits.no-animate#js-packages-limits-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'packages_limits_content' } }
+ .settings-header
+ %h4
+ = _('Package Registry Rate Limits')
+ %button.btn.gl-button.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Configure specific limits for Packages API requests that supersede the general user and IP rate limits.')
+ .settings-content
+ = render 'package_registry_limits'
+
%section.settings.as-outbound.no-animate#js-outbound-settings{ class: ('expanded' if expanded_by_default?), data: { qa_selector: 'outbound_requests_content' } }
.settings-header
%h4
diff --git a/app/views/admin/requests_profiles/index.html.haml b/app/views/admin/requests_profiles/index.html.haml
index 6c75dfe9733..9d42a2bfa93 100644
--- a/app/views/admin/requests_profiles/index.html.haml
+++ b/app/views/admin/requests_profiles/index.html.haml
@@ -4,9 +4,7 @@
= page_title
.bs-callout.clearfix
- Pass the header
- %code X-Profile-Token: #{@profile_token}
- to profile the request
+ = html_escape(_('Pass the header %{codeOpen} X-Profile-Token: %{profile_token} %{codeClose} to profile the request')) % { profile_token: @profile_token, codeOpen: '<code>'.html_safe, codeClose: '</code>'.html_safe }
- if @profiles.present?
.gl-mt-3
@@ -21,4 +19,4 @@
admin_requests_profile_path(profile)
- else
%p
- No profiles found
+ = _('No profiles found')
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index 726ab7d2372..a296394a2e0 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -6,6 +6,6 @@
#js-snippet-view{ data: {'qa-selector': 'snippet_view', 'snippet-gid': @snippet.to_global_id} }
.row-content-block.top-block.content-component-block
- = render 'award_emoji/awards_block', awardable: @snippet, inline: true
+ = render 'award_emoji/awards_block', awardable: @snippet, inline: true, api_awards_path: project_snippets_award_api_path(@snippet)
#notes.limited-width-notes= render "shared/notes/notes_with_form", :autocomplete => true
diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml
index 0e54f1a7672..0088cd35781 100644
--- a/app/views/shared/milestones/_sidebar.html.haml
+++ b/app/views/shared/milestones/_sidebar.html.haml
@@ -79,7 +79,7 @@
%span= milestone.issues_visible_to_user(current_user).count
.title.hide-collapsed
= s_('MilestoneSidebar|Issues')
- %span.badge.badge-pill= milestone.issues_visible_to_user(current_user).count
+ %span.badge.badge-muted.badge-pill.gl-badge.sm= milestone.issues_visible_to_user(current_user).count
- if show_new_issue_link?(project)
= link_to new_project_issue_path(project, issue: { milestone_id: milestone.id }), class: "float-right", title: s_('MilestoneSidebar|New Issue') do
= s_('MilestoneSidebar|New issue')
@@ -110,7 +110,7 @@
%span= milestone.merge_requests.count
.title.hide-collapsed
= s_('MilestoneSidebar|Merge requests')
- %span.badge.badge-pill= milestone.merge_requests.count
+ %span.badge.badge-muted.badge-pill.gl-badge.sm= milestone.merge_requests.count
.value.hide-collapsed.bold
- if !project || can?(current_user, :read_merge_request, project)
%span.milestone-stat
diff --git a/app/workers/container_expiration_policies/cleanup_container_repository_worker.rb b/app/workers/container_expiration_policies/cleanup_container_repository_worker.rb
index 53220a7afed..1868fe607c4 100644
--- a/app/workers/container_expiration_policies/cleanup_container_repository_worker.rb
+++ b/app/workers/container_expiration_policies/cleanup_container_repository_worker.rb
@@ -21,82 +21,34 @@ module ContainerExpirationPolicies
cleanup_tags_service_deleted_size
].freeze
- def perform_work
- return unless throttling_enabled?
- return unless container_repository
-
- log_extra_metadata_on_done(:container_repository_id, container_repository.id)
- log_extra_metadata_on_done(:project_id, project.id)
-
- unless allowed_to_run?(container_repository)
- container_repository.cleanup_unscheduled!
- log_extra_metadata_on_done(:cleanup_status, :skipped)
- return
+ delegate :perform_work, :remaining_work_count, to: :inner_instance
+
+ def inner_instance
+ strong_memoize(:inner_instance) do
+ if loopless_enabled?
+ Loopless.new(self)
+ else
+ Looping.new(self)
+ end
end
-
- result = ContainerExpirationPolicies::CleanupService.new(container_repository)
- .execute
- log_on_done(result)
- end
-
- def remaining_work_count
- cleanup_scheduled_count = ContainerRepository.cleanup_scheduled.count
- cleanup_unfinished_count = ContainerRepository.cleanup_unfinished.count
- total_count = cleanup_scheduled_count + cleanup_unfinished_count
-
- log_info(
- cleanup_scheduled_count: cleanup_scheduled_count,
- cleanup_unfinished_count: cleanup_unfinished_count,
- cleanup_total_count: total_count
- )
-
- total_count
end
def max_running_jobs
return 0 unless throttling_enabled?
- ::Gitlab::CurrentSettings.current_application_settings.container_registry_expiration_policies_worker_capacity
- end
-
- private
-
- def allowed_to_run?(container_repository)
- return false unless policy&.enabled && policy&.next_run_at
-
- Time.zone.now + max_cleanup_execution_time.seconds < policy.next_run_at
+ ::Gitlab::CurrentSettings.container_registry_expiration_policies_worker_capacity
end
def throttling_enabled?
Feature.enabled?(:container_registry_expiration_policies_throttling)
end
- def max_cleanup_execution_time
- ::Gitlab::CurrentSettings.current_application_settings.container_registry_delete_tags_service_timeout
- end
-
- def policy
- project.container_expiration_policy
+ def loopless_enabled?
+ Feature.enabled?(:container_registry_expiration_policies_loopless)
end
- def project
- container_repository.project
- end
-
- def container_repository
- strong_memoize(:container_repository) do
- ContainerRepository.transaction do
- # rubocop: disable CodeReuse/ActiveRecord
- # We need a lock to prevent two workers from picking up the same row
- container_repository = ContainerRepository.waiting_for_cleanup
- .order(:expiration_policy_cleanup_status, :expiration_policy_started_at)
- .limit(1)
- .lock('FOR UPDATE SKIP LOCKED')
- .first
- # rubocop: enable CodeReuse/ActiveRecord
- container_repository&.tap(&:cleanup_ongoing!)
- end
- end
+ def max_cleanup_execution_time
+ ::Gitlab::CurrentSettings.container_registry_delete_tags_service_timeout
end
def log_info(extra_structure)
@@ -120,5 +72,100 @@ module ContainerExpirationPolicies
log_extra_metadata_on_done(:cleanup_tags_service_truncated, !!truncated)
log_extra_metadata_on_done(:running_jobs_count, running_jobs_count)
end
+
+ # rubocop: disable Scalability/IdempotentWorker
+ # TODO: move the logic from this class to the parent one when container_registry_expiration_policies_loopless is removed
+ # Tracking issue: https://gitlab.com/gitlab-org/gitlab/-/issues/325273
+ class Loopless
+ # TODO fill the logic here with the approach documented in
+ # https://gitlab.com/gitlab-org/gitlab/-/issues/267546#limited-worker
+ def initialize(parent)
+ @parent = parent
+ end
+ end
+ # rubocop: enable Scalability/IdempotentWorker
+
+ # rubocop: disable Scalability/IdempotentWorker
+ # TODO remove this class when `container_registry_expiration_policies_loopless` is removed
+ # Tracking issue: https://gitlab.com/gitlab-org/gitlab/-/issues/325273
+ class Looping
+ include Gitlab::Utils::StrongMemoize
+
+ delegate :throttling_enabled?,
+ :log_extra_metadata_on_done,
+ :log_info,
+ :log_on_done,
+ :max_cleanup_execution_time,
+ to: :@parent
+
+ def initialize(parent)
+ @parent = parent
+ end
+
+ def perform_work
+ return unless throttling_enabled?
+ return unless container_repository
+
+ log_extra_metadata_on_done(:container_repository_id, container_repository.id)
+ log_extra_metadata_on_done(:project_id, project.id)
+
+ unless allowed_to_run?(container_repository)
+ container_repository.cleanup_unscheduled!
+ log_extra_metadata_on_done(:cleanup_status, :skipped)
+ return
+ end
+
+ result = ContainerExpirationPolicies::CleanupService.new(container_repository)
+ .execute
+ log_on_done(result)
+ end
+
+ def remaining_work_count
+ cleanup_scheduled_count = ContainerRepository.cleanup_scheduled.count
+ cleanup_unfinished_count = ContainerRepository.cleanup_unfinished.count
+ total_count = cleanup_scheduled_count + cleanup_unfinished_count
+
+ log_info(
+ cleanup_scheduled_count: cleanup_scheduled_count,
+ cleanup_unfinished_count: cleanup_unfinished_count,
+ cleanup_total_count: total_count
+ )
+
+ total_count
+ end
+
+ private
+
+ def allowed_to_run?(container_repository)
+ return false unless policy&.enabled && policy&.next_run_at
+
+ Time.zone.now + max_cleanup_execution_time.seconds < policy.next_run_at
+ end
+
+ def policy
+ project.container_expiration_policy
+ end
+
+ def project
+ container_repository.project
+ end
+
+ def container_repository
+ strong_memoize(:container_repository) do
+ ContainerRepository.transaction do
+ # rubocop: disable CodeReuse/ActiveRecord
+ # We need a lock to prevent two workers from picking up the same row
+ container_repository = ContainerRepository.waiting_for_cleanup
+ .order(:expiration_policy_cleanup_status, :expiration_policy_started_at)
+ .limit(1)
+ .lock('FOR UPDATE SKIP LOCKED')
+ .first
+ # rubocop: enable CodeReuse/ActiveRecord
+ container_repository&.tap(&:cleanup_ongoing!)
+ end
+ end
+ end
+ end
+ # rubocop: enable Scalability/IdempotentWorker
end
end