summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/assets/javascripts/badges/components/badge_list.vue2
-rw-r--r--app/assets/javascripts/contributors/components/contributors.vue2
-rw-r--r--app/assets/javascripts/deploy_keys/components/app.vue2
-rw-r--r--app/assets/javascripts/environments/components/environment_item.vue18
-rw-r--r--app/assets/javascripts/environments/components/environments_table.vue16
-rw-r--r--app/assets/javascripts/error_tracking/components/error_details.vue4
-rw-r--r--app/assets/javascripts/frequent_items/components/app.vue2
-rw-r--r--app/assets/javascripts/ide/components/branches/search_list.vue2
-rw-r--r--app/assets/javascripts/ide/components/file_templates/dropdown.vue2
-rw-r--r--app/assets/javascripts/ide/components/jobs/list.vue2
-rw-r--r--app/assets/javascripts/ide/components/merge_requests/list.vue2
-rw-r--r--app/assets/javascripts/ide/components/pipelines/list.vue2
-rw-r--r--app/assets/javascripts/ide/components/preview/clientside.vue2
-rw-r--r--app/assets/javascripts/lib/utils/common_utils.js15
-rw-r--r--app/assets/javascripts/monitoring/components/charts/time_series.vue6
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue1
-rw-r--r--app/assets/javascripts/monitoring/components/panel_type.vue4
-rw-r--r--app/assets/javascripts/monitoring/queries/getAnnotations.query.graphql13
-rw-r--r--app/assets/javascripts/monitoring/stores/actions.js49
-rw-r--r--app/assets/javascripts/monitoring/stores/mutation_types.js5
-rw-r--r--app/assets/javascripts/monitoring/stores/mutations.js10
-rw-r--r--app/assets/javascripts/monitoring/stores/state.js1
-rw-r--r--app/assets/javascripts/pipelines/components/graph/graph_component.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/header_component.vue2
-rw-r--r--app/assets/javascripts/pipelines/components/pipelines.vue2
-rw-r--r--app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue2
-rw-r--r--app/assets/javascripts/sentry_error_stack_trace/components/sentry_error_stack_trace.vue2
-rw-r--r--app/assets/javascripts/serverless/components/functions.vue4
-rw-r--r--app/assets/javascripts/smart_interval.js27
-rw-r--r--app/assets/javascripts/snippets/components/show.vue33
-rw-r--r--app/assets/javascripts/snippets/components/snippet_blob_edit.vue2
-rw-r--r--app/assets/javascripts/snippets/mixins/snippets.js39
-rw-r--r--app/assets/javascripts/static_site_editor/components/static_site_editor.vue4
-rw-r--r--app/assets/javascripts/static_site_editor/store/getters.js2
-rw-r--r--app/assets/javascripts/static_site_editor/store/mutations.js1
-rw-r--r--app/assets/javascripts/static_site_editor/store/state.js2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue17
-rw-r--r--app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue2
-rw-r--r--app/controllers/admin/application_settings_controller.rb1
-rw-r--r--app/controllers/projects/environments_controller.rb4
-rw-r--r--app/controllers/projects/issues_controller.rb19
-rw-r--r--app/controllers/projects/merge_requests_controller.rb1
-rw-r--r--app/mailers/emails/pages_domains.rb11
-rw-r--r--app/models/application_setting_implementation.rb1
-rw-r--r--app/models/ci/job_artifact.rb15
-rw-r--r--app/models/diff_note_position.rb36
-rw-r--r--app/models/lfs_object.rb13
-rw-r--r--app/services/clusters/create_service.rb7
-rw-r--r--app/services/clusters/management/validate_management_project_permissions_service.rb54
-rw-r--r--app/services/clusters/update_service.rb41
-rw-r--r--app/services/environments/auto_stop_service.rb2
-rw-r--r--app/services/notification_service.rb6
-rw-r--r--app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb2
-rw-r--r--app/uploaders/records_uploads.rb23
-rw-r--r--app/views/admin/application_settings/_issue_limits.html.haml9
-rw-r--r--app/views/admin/application_settings/network.html.haml11
-rw-r--r--app/views/admin/deploy_keys/index.html.haml2
-rw-r--r--app/views/notify/pages_domain_auto_ssl_failed_email.html.haml11
-rw-r--r--app/views/notify/pages_domain_auto_ssl_failed_email.text.haml7
-rw-r--r--app/views/profiles/emails/index.html.haml2
-rw-r--r--app/views/projects/issues/_related_branches.html.haml2
-rw-r--r--app/views/projects/pages/_list.html.haml4
-rw-r--r--app/workers/environments/auto_stop_cron_worker.rb2
63 files changed, 437 insertions, 154 deletions
diff --git a/app/assets/javascripts/badges/components/badge_list.vue b/app/assets/javascripts/badges/components/badge_list.vue
index d2767dd6c64..04c2d4a7493 100644
--- a/app/assets/javascripts/badges/components/badge_list.vue
+++ b/app/assets/javascripts/badges/components/badge_list.vue
@@ -28,7 +28,7 @@ export default {
{{ s__('Badges|Your badges') }}
<span v-show="!isLoading" class="badge badge-pill">{{ badges.length }}</span>
</div>
- <gl-loading-icon v-show="isLoading" :size="2" class="card-body" />
+ <gl-loading-icon v-show="isLoading" size="lg" class="card-body" />
<div v-if="hasNoBadges" class="card-body">
<span v-if="isGroupBadge">{{ s__('Badges|This group has no badges') }}</span>
<span v-else>{{ s__('Badges|This project has no badges') }}</span>
diff --git a/app/assets/javascripts/contributors/components/contributors.vue b/app/assets/javascripts/contributors/components/contributors.vue
index 19516a13d15..3de1b2f0707 100644
--- a/app/assets/javascripts/contributors/components/contributors.vue
+++ b/app/assets/javascripts/contributors/components/contributors.vue
@@ -197,7 +197,7 @@ export default {
<template>
<div>
<div v-if="loading" class="contributors-loader text-center">
- <gl-loading-icon :inline="true" :size="4" />
+ <gl-loading-icon :inline="true" size="xl" />
</div>
<div v-else-if="showChart" class="contributors-charts">
diff --git a/app/assets/javascripts/deploy_keys/components/app.vue b/app/assets/javascripts/deploy_keys/components/app.vue
index 048f3a2485c..5505704f430 100644
--- a/app/assets/javascripts/deploy_keys/components/app.vue
+++ b/app/assets/javascripts/deploy_keys/components/app.vue
@@ -119,7 +119,7 @@ export default {
<gl-loading-icon
v-if="isLoading && !hasKeys"
:label="s__('DeployKeys|Loading deploy keys')"
- :size="2"
+ size="lg"
/>
<template v-else-if="hasKeys">
<div class="top-area scrolling-tabs-container inner-page-scroll-tabs">
diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue
index 305d860a692..335c668474e 100644
--- a/app/assets/javascripts/environments/components/environment_item.vue
+++ b/app/assets/javascripts/environments/components/environment_item.vue
@@ -58,12 +58,6 @@ export default {
required: true,
},
- shouldShowAutoStopDate: {
- type: Boolean,
- required: false,
- default: false,
- },
-
tableData: {
type: Object,
required: true,
@@ -638,12 +632,7 @@ export default {
</span>
</div>
- <div
- v-if="!isFolder && shouldShowAutoStopDate"
- class="table-section"
- :class="tableData.autoStop.spacing"
- role="gridcell"
- >
+ <div v-if="!isFolder" class="table-section" :class="tableData.autoStop.spacing" role="gridcell">
<div role="rowheader" class="table-mobile-header">{{ tableData.autoStop.title }}</div>
<span
v-if="canShowAutoStopDate"
@@ -662,10 +651,7 @@ export default {
role="gridcell"
>
<div class="btn-group table-action-buttons" role="group">
- <pin-component
- v-if="canShowAutoStopDate && shouldShowAutoStopDate"
- :auto-stop-url="autoStopUrl"
- />
+ <pin-component v-if="canShowAutoStopDate" :auto-stop-url="autoStopUrl" />
<external-url-component
v-if="externalURL && canReadEnvironment"
diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue
index 01a00e03814..89e40faa23e 100644
--- a/app/assets/javascripts/environments/components/environments_table.vue
+++ b/app/assets/javascripts/environments/components/environments_table.vue
@@ -6,7 +6,6 @@ import { GlLoadingIcon } from '@gitlab/ui';
import { flow, reverse, sortBy } from 'lodash/fp';
import environmentTableMixin from 'ee_else_ce/environments/mixins/environments_table_mixin';
import { s__ } from '~/locale';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import EnvironmentItem from './environment_item.vue';
export default {
@@ -17,7 +16,7 @@ export default {
CanaryDeploymentCallout: () =>
import('ee_component/environments/components/canary_deployment_callout.vue'),
},
- mixins: [environmentTableMixin, glFeatureFlagsMixin()],
+ mixins: [environmentTableMixin],
props: {
environments: {
type: Array,
@@ -43,9 +42,6 @@ export default {
: env,
);
},
- shouldShowAutoStopDate() {
- return this.glFeatures.autoStopEnvironments;
- },
tableData() {
return {
// percent spacing for cols, should add up to 100
@@ -74,7 +70,7 @@ export default {
spacing: 'section-5',
},
actions: {
- spacing: this.shouldShowAutoStopDate ? 'section-25' : 'section-30',
+ spacing: 'section-25',
},
};
},
@@ -131,12 +127,7 @@ export default {
<div class="table-section" :class="tableData.date.spacing" role="columnheader">
{{ tableData.date.title }}
</div>
- <div
- v-if="shouldShowAutoStopDate"
- class="table-section"
- :class="tableData.autoStop.spacing"
- role="columnheader"
- >
+ <div class="table-section" :class="tableData.autoStop.spacing" role="columnheader">
{{ tableData.autoStop.title }}
</div>
</div>
@@ -146,7 +137,6 @@ export default {
:key="`environment-item-${i}`"
:model="model"
:can-read-environment="canReadEnvironment"
- :should-show-auto-stop-date="shouldShowAutoStopDate"
:table-data="tableData"
/>
diff --git a/app/assets/javascripts/error_tracking/components/error_details.vue b/app/assets/javascripts/error_tracking/components/error_details.vue
index a8103c80da0..148edfe3a51 100644
--- a/app/assets/javascripts/error_tracking/components/error_details.vue
+++ b/app/assets/javascripts/error_tracking/components/error_details.vue
@@ -225,7 +225,7 @@ export default {
<template>
<div>
<div v-if="errorLoading" class="py-3">
- <gl-loading-icon :size="3" />
+ <gl-loading-icon size="lg" />
</div>
<div v-else-if="error" class="error-details">
<gl-alert v-if="isAlertVisible" @dismiss="isAlertVisible = false">
@@ -405,7 +405,7 @@ export default {
</ul>
<div v-if="loadingStacktrace" class="py-3">
- <gl-loading-icon :size="3" />
+ <gl-loading-icon size="lg" />
</div>
<template v-else-if="showStacktrace">
diff --git a/app/assets/javascripts/frequent_items/components/app.vue b/app/assets/javascripts/frequent_items/components/app.vue
index 2ffecce0a56..1f1776a5487 100644
--- a/app/assets/javascripts/frequent_items/components/app.vue
+++ b/app/assets/javascripts/frequent_items/components/app.vue
@@ -107,7 +107,7 @@ export default {
<gl-loading-icon
v-if="isLoadingItems"
:label="translations.loadingMessage"
- :size="2"
+ size="lg"
class="loading-animation prepend-top-20"
/>
<div v-if="!isLoadingItems && !hasSearchQuery" class="section-header">
diff --git a/app/assets/javascripts/ide/components/branches/search_list.vue b/app/assets/javascripts/ide/components/branches/search_list.vue
index 76821bcd986..dd2d726d525 100644
--- a/app/assets/javascripts/ide/components/branches/search_list.vue
+++ b/app/assets/javascripts/ide/components/branches/search_list.vue
@@ -72,7 +72,7 @@ export default {
<div class="dropdown-content ide-merge-requests-dropdown-content d-flex">
<gl-loading-icon
v-if="isLoading"
- :size="2"
+ size="lg"
class="mt-3 mb-3 align-self-center ml-auto mr-auto"
/>
<ul v-else class="mb-0 w-100">
diff --git a/app/assets/javascripts/ide/components/file_templates/dropdown.vue b/app/assets/javascripts/ide/components/file_templates/dropdown.vue
index 35e5f9bcf69..d80662f6ae1 100644
--- a/app/assets/javascripts/ide/components/file_templates/dropdown.vue
+++ b/app/assets/javascripts/ide/components/file_templates/dropdown.vue
@@ -88,7 +88,7 @@ export default {
<i aria-hidden="true" class="fa fa-search dropdown-input-search"></i>
</div>
<div class="dropdown-content">
- <gl-loading-icon v-if="showLoading" :size="2" />
+ <gl-loading-icon v-if="showLoading" size="lg" />
<ul v-else>
<li v-for="(item, index) in outputData" :key="index">
<button type="button" @click="clickItem(item)">{{ item.name }}</button>
diff --git a/app/assets/javascripts/ide/components/jobs/list.vue b/app/assets/javascripts/ide/components/jobs/list.vue
index 2cb5050c3f0..b97b7289886 100644
--- a/app/assets/javascripts/ide/components/jobs/list.vue
+++ b/app/assets/javascripts/ide/components/jobs/list.vue
@@ -26,7 +26,7 @@ export default {
<template>
<div>
- <gl-loading-icon v-if="loading && !stages.length" :size="2" class="prepend-top-default" />
+ <gl-loading-icon v-if="loading && !stages.length" size="lg" class="prepend-top-default" />
<template v-else>
<stage
v-for="stage in stages"
diff --git a/app/assets/javascripts/ide/components/merge_requests/list.vue b/app/assets/javascripts/ide/components/merge_requests/list.vue
index 15c08988977..bf2a33be653 100644
--- a/app/assets/javascripts/ide/components/merge_requests/list.vue
+++ b/app/assets/javascripts/ide/components/merge_requests/list.vue
@@ -90,7 +90,7 @@ export default {
<div class="dropdown-content ide-merge-requests-dropdown-content d-flex">
<gl-loading-icon
v-if="isLoading"
- :size="2"
+ size="lg"
class="mt-3 mb-3 align-self-center ml-auto mr-auto"
/>
<template v-else>
diff --git a/app/assets/javascripts/ide/components/pipelines/list.vue b/app/assets/javascripts/ide/components/pipelines/list.vue
index 343b0b6e90c..d3e5add2e83 100644
--- a/app/assets/javascripts/ide/components/pipelines/list.vue
+++ b/app/assets/javascripts/ide/components/pipelines/list.vue
@@ -56,7 +56,7 @@ export default {
<template>
<div class="ide-pipeline">
- <gl-loading-icon v-if="showLoadingIcon" :size="2" class="prepend-top-default" />
+ <gl-loading-icon v-if="showLoadingIcon" size="lg" class="prepend-top-default" />
<template v-else-if="hasLoadedPipeline">
<header v-if="latestPipeline" class="ide-tree-header ide-pipeline-header">
<ci-icon :status="latestPipeline.details.status" :size="24" class="d-flex" />
diff --git a/app/assets/javascripts/ide/components/preview/clientside.vue b/app/assets/javascripts/ide/components/preview/clientside.vue
index 86a773499bc..3852f2fdfa4 100644
--- a/app/assets/javascripts/ide/components/preview/clientside.vue
+++ b/app/assets/javascripts/ide/components/preview/clientside.vue
@@ -176,6 +176,6 @@ export default {
{{ s__('IDE|Get started with Live Preview') }}
</a>
</div>
- <gl-loading-icon v-else :size="2" class="align-self-center mt-auto mb-auto" />
+ <gl-loading-icon v-else size="lg" class="align-self-center mt-auto mb-auto" />
</div>
</template>
diff --git a/app/assets/javascripts/lib/utils/common_utils.js b/app/assets/javascripts/lib/utils/common_utils.js
index 9b0ee40a30a..4a48852159a 100644
--- a/app/assets/javascripts/lib/utils/common_utils.js
+++ b/app/assets/javascripts/lib/utils/common_utils.js
@@ -910,3 +910,18 @@ export const setCookie = (name, value) => Cookies.set(name, value, { expires: 36
export const getCookie = name => Cookies.get(name);
export const removeCookie = name => Cookies.remove(name);
+
+/**
+ * Returns the status of a feature flag.
+ * Currently, there is no way to access feature
+ * flags in Vuex other than directly tapping into
+ * window.gon.
+ *
+ * This should only be used on Vuex. If feature flags
+ * need to be accessed in Vue components consider
+ * using the Vue feature flag mixin.
+ *
+ * @param {String} flag Feature flag
+ * @returns {Boolean} on/off
+ */
+export const isFeatureFlagEnabled = flag => window.gon.features?.[flag];
diff --git a/app/assets/javascripts/monitoring/components/charts/time_series.vue b/app/assets/javascripts/monitoring/components/charts/time_series.vue
index 24aa8480ce4..9041b01088c 100644
--- a/app/assets/javascripts/monitoring/components/charts/time_series.vue
+++ b/app/assets/javascripts/monitoring/components/charts/time_series.vue
@@ -55,6 +55,11 @@ export default {
required: false,
default: () => [],
},
+ annotations: {
+ type: Array,
+ required: false,
+ default: () => [],
+ },
projectPath: {
type: String,
required: false,
@@ -143,6 +148,7 @@ export default {
return (this.option.series || []).concat(
generateAnnotationsSeries({
deployments: this.recentDeployments,
+ annotations: this.annotations,
}),
);
},
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 15b17f01daf..4586ce70ad6 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -213,7 +213,6 @@ export default {
'dashboard',
'emptyState',
'showEmptyState',
- 'deploymentData',
'useDashboardEndpoint',
'allDashboards',
'additionalPanelTypesEnabled',
diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue
index d1394bca447..676fc0cca64 100644
--- a/app/assets/javascripts/monitoring/components/panel_type.vue
+++ b/app/assets/javascripts/monitoring/components/panel_type.vue
@@ -89,6 +89,9 @@ export default {
deploymentData(state) {
return state[this.namespace].deploymentData;
},
+ annotations(state) {
+ return state[this.namespace].annotations;
+ },
projectPath(state) {
return state[this.namespace].projectPath;
},
@@ -310,6 +313,7 @@ export default {
ref="timeChart"
:graph-data="graphData"
:deployment-data="deploymentData"
+ :annotations="annotations"
:project-path="projectPath"
:thresholds="getGraphAlertValues(graphData.metrics)"
:group-id="groupId"
diff --git a/app/assets/javascripts/monitoring/queries/getAnnotations.query.graphql b/app/assets/javascripts/monitoring/queries/getAnnotations.query.graphql
new file mode 100644
index 00000000000..e2edaa707b2
--- /dev/null
+++ b/app/assets/javascripts/monitoring/queries/getAnnotations.query.graphql
@@ -0,0 +1,13 @@
+query getAnnotations($projectPath: ID!) {
+ environment(name: $environmentName) {
+ metricDashboard(id: $dashboardId) {
+ annotations: nodes {
+ id
+ description
+ from
+ to
+ panelId
+ }
+ }
+ }
+}
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
index 06b99f572e7..5b2bd1f1493 100644
--- a/app/assets/javascripts/monitoring/stores/actions.js
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -6,8 +6,13 @@ import { convertToFixedRange } from '~/lib/utils/datetime_range';
import { gqClient, parseEnvironmentsResponse, removeLeadingSlash } from './utils';
import trackDashboardLoad from '../monitoring_tracking_helper';
import getEnvironments from '../queries/getEnvironments.query.graphql';
+import getAnnotations from '../queries/getAnnotations.query.graphql';
import statusCodes from '../../lib/utils/http_status';
-import { backOff, convertObjectPropsToCamelCase } from '../../lib/utils/common_utils';
+import {
+ backOff,
+ convertObjectPropsToCamelCase,
+ isFeatureFlagEnabled,
+} from '../../lib/utils/common_utils';
import { s__, sprintf } from '../../locale';
import { PROMETHEUS_TIMEOUT, ENVIRONMENT_AVAILABLE_STATE } from '../constants';
@@ -80,6 +85,14 @@ export const setShowErrorBanner = ({ commit }, enabled) => {
export const fetchData = ({ dispatch }) => {
dispatch('fetchEnvironmentsData');
dispatch('fetchDashboard');
+ /**
+ * Annotations data is not yet fetched. This will be
+ * ready after the BE piece is implemented.
+ * https://gitlab.com/gitlab-org/gitlab/-/issues/211330
+ */
+ if (isFeatureFlagEnabled('metrics_dashboard_annotations')) {
+ dispatch('fetchAnnotations');
+ }
};
// Metrics dashboard
@@ -269,6 +282,40 @@ export const receiveEnvironmentsDataFailure = ({ commit }) => {
commit(types.RECEIVE_ENVIRONMENTS_DATA_FAILURE);
};
+export const fetchAnnotations = ({ state, dispatch }) => {
+ dispatch('requestAnnotations');
+
+ return gqClient
+ .mutate({
+ mutation: getAnnotations,
+ variables: {
+ projectPath: removeLeadingSlash(state.projectPath),
+ dashboardId: state.currentDashboard,
+ environmentName: state.currentEnvironmentName,
+ },
+ })
+ .then(resp => resp.data?.project?.environment?.metricDashboard?.annotations)
+ .then(annotations => {
+ if (!annotations) {
+ createFlash(s__('Metrics|There was an error fetching annotations. Please try again.'));
+ }
+
+ dispatch('receiveAnnotationsSuccess', annotations);
+ })
+ .catch(err => {
+ Sentry.captureException(err);
+ dispatch('receiveAnnotationsFailure');
+ createFlash(s__('Metrics|There was an error getting annotations information.'));
+ });
+};
+
+// While this commit does not update the state it will
+// eventually be useful to show a loading state
+export const requestAnnotations = ({ commit }) => commit(types.REQUEST_ANNOTATIONS);
+export const receiveAnnotationsSuccess = ({ commit }, data) =>
+ commit(types.RECEIVE_ANNOTATIONS_SUCCESS, data);
+export const receiveAnnotationsFailure = ({ commit }) => commit(types.RECEIVE_ANNOTATIONS_FAILURE);
+
// Dashboard manipulation
/**
diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js
index 9a3489d53d7..2f9955da1b1 100644
--- a/app/assets/javascripts/monitoring/stores/mutation_types.js
+++ b/app/assets/javascripts/monitoring/stores/mutation_types.js
@@ -3,6 +3,11 @@ export const REQUEST_METRICS_DASHBOARD = 'REQUEST_METRICS_DASHBOARD';
export const RECEIVE_METRICS_DASHBOARD_SUCCESS = 'RECEIVE_METRICS_DASHBOARD_SUCCESS';
export const RECEIVE_METRICS_DASHBOARD_FAILURE = 'RECEIVE_METRICS_DASHBOARD_FAILURE';
+// Annotations
+export const REQUEST_ANNOTATIONS = 'REQUEST_ANNOTATIONS';
+export const RECEIVE_ANNOTATIONS_SUCCESS = 'RECEIVE_ANNOTATIONS_SUCCESS';
+export const RECEIVE_ANNOTATIONS_FAILURE = 'RECEIVE_ANNOTATIONS_FAILURE';
+
// Git project deployments
export const REQUEST_DEPLOYMENTS_DATA = 'REQUEST_DEPLOYMENTS_DATA';
export const RECEIVE_DEPLOYMENTS_DATA_SUCCESS = 'RECEIVE_DEPLOYMENTS_DATA_SUCCESS';
diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js
index 38c1524d904..aa31b6642d7 100644
--- a/app/assets/javascripts/monitoring/stores/mutations.js
+++ b/app/assets/javascripts/monitoring/stores/mutations.js
@@ -93,6 +93,16 @@ export default {
},
/**
+ * Annotations
+ */
+ [types.RECEIVE_ANNOTATIONS_SUCCESS](state, annotations) {
+ state.annotations = annotations;
+ },
+ [types.RECEIVE_ANNOTATIONS_FAILURE](state) {
+ state.annotations = [];
+ },
+
+ /**
* Individual panel/metric results
*/
[types.REQUEST_METRIC_RESULT](state, { metricId }) {
diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js
index 2b1907e8df7..e60510e747b 100644
--- a/app/assets/javascripts/monitoring/stores/state.js
+++ b/app/assets/javascripts/monitoring/stores/state.js
@@ -20,6 +20,7 @@ export default () => ({
allDashboards: [],
// Other project data
+ annotations: [],
deploymentData: [],
environments: [],
environmentsSearchTerm: '',
diff --git a/app/assets/javascripts/pipelines/components/graph/graph_component.vue b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
index ef3f4d0e3f6..1ff5b662d18 100644
--- a/app/assets/javascripts/pipelines/components/graph/graph_component.vue
+++ b/app/assets/javascripts/pipelines/components/graph/graph_component.vue
@@ -135,7 +135,7 @@ export default {
paddingRight: `${graphRightPadding}px`,
}"
>
- <gl-loading-icon v-if="isLoading" class="m-auto" :size="3" />
+ <gl-loading-icon v-if="isLoading" class="m-auto" size="lg" />
<pipeline-graph
v-if="pipelineTypeUpstream"
diff --git a/app/assets/javascripts/pipelines/components/header_component.vue b/app/assets/javascripts/pipelines/components/header_component.vue
index 2a3d022c5cd..e7777d0d3af 100644
--- a/app/assets/javascripts/pipelines/components/header_component.vue
+++ b/app/assets/javascripts/pipelines/components/header_component.vue
@@ -108,7 +108,7 @@ export default {
/>
</ci-header>
- <gl-loading-icon v-if="isLoading" :size="2" class="prepend-top-default append-bottom-default" />
+ <gl-loading-icon v-if="isLoading" size="lg" class="prepend-top-default append-bottom-default" />
<gl-modal
:modal-id="$options.DELETE_MODAL_ID"
diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue
index accd6bf71f4..d4f23697e09 100644
--- a/app/assets/javascripts/pipelines/components/pipelines.vue
+++ b/app/assets/javascripts/pipelines/components/pipelines.vue
@@ -271,7 +271,7 @@ export default {
<gl-loading-icon
v-if="stateToRender === $options.stateMap.loading"
:label="s__('Pipelines|Loading Pipelines')"
- :size="3"
+ size="lg"
class="prepend-top-20"
/>
diff --git a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
index f1106dc6ae9..571d305a50c 100644
--- a/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
+++ b/app/assets/javascripts/projects/tree/components/commit_pipeline_status_component.vue
@@ -94,7 +94,7 @@ export default {
</script>
<template>
<div class="ci-status-link">
- <gl-loading-icon v-if="isLoading" :size="3" label="Loading pipeline status" />
+ <gl-loading-icon v-if="isLoading" size="lg" label="Loading pipeline status" />
<a v-else :href="ciStatus.details_path">
<ci-icon
v-tooltip
diff --git a/app/assets/javascripts/sentry_error_stack_trace/components/sentry_error_stack_trace.vue b/app/assets/javascripts/sentry_error_stack_trace/components/sentry_error_stack_trace.vue
index c90478db620..807f10bd9c6 100644
--- a/app/assets/javascripts/sentry_error_stack_trace/components/sentry_error_stack_trace.vue
+++ b/app/assets/javascripts/sentry_error_stack_trace/components/sentry_error_stack_trace.vue
@@ -36,7 +36,7 @@ export default {
</div>
</div>
<div v-if="loadingStacktrace" class="card">
- <gl-loading-icon class="py-2" label="Fetching stack trace" :size="1" />
+ <gl-loading-icon class="py-2" label="Fetching stack trace" size="sm" />
</div>
<stacktrace v-else :entries="stacktrace" />
</div>
diff --git a/app/assets/javascripts/serverless/components/functions.vue b/app/assets/javascripts/serverless/components/functions.vue
index e06149f2bcb..2b1291ac70f 100644
--- a/app/assets/javascripts/serverless/components/functions.vue
+++ b/app/assets/javascripts/serverless/components/functions.vue
@@ -77,7 +77,7 @@ export default {
<section id="serverless-functions" class="flex-grow">
<gl-loading-icon
v-if="checkingInstalled"
- :size="2"
+ size="lg"
class="prepend-top-default append-bottom-default"
/>
@@ -97,7 +97,7 @@ export default {
</template>
<gl-loading-icon
v-if="isLoading"
- :size="2"
+ size="lg"
class="prepend-top-default append-bottom-default js-functions-loader"
/>
</div>
diff --git a/app/assets/javascripts/smart_interval.js b/app/assets/javascripts/smart_interval.js
index 8ca590123ae..0e52d2d8010 100644
--- a/app/assets/javascripts/smart_interval.js
+++ b/app/assets/javascripts/smart_interval.js
@@ -33,7 +33,7 @@ export default class SmartInterval {
this.state = {
intervalId: null,
currentInterval: this.cfg.startingInterval,
- pageVisibility: 'visible',
+ pagevisibile: true,
};
this.initInterval();
@@ -91,8 +91,10 @@ export default class SmartInterval {
}
destroy() {
+ document.removeEventListener('visibilitychange', this.onVisibilityChange);
+ window.removeEventListener('blur', this.onWindowVisibilityChange);
+ window.removeEventListener('focus', this.onWindowVisibilityChange);
this.cancel();
- document.removeEventListener('visibilitychange', this.handleVisibilityChange);
$(document)
.off('visibilitychange')
.off('beforeunload');
@@ -124,9 +126,21 @@ export default class SmartInterval {
});
}
+ onWindowVisibilityChange(e) {
+ this.state.pagevisibile = e.type === 'focus';
+ this.handleVisibilityChange();
+ }
+
+ onVisibilityChange(e) {
+ this.state.pagevisibile = e.target.visibilityState === 'visible';
+ this.handleVisibilityChange();
+ }
+
initVisibilityChangeHandling() {
- // cancel interval when tab no longer shown (prevents cached pages from polling)
- document.addEventListener('visibilitychange', this.handleVisibilityChange.bind(this));
+ // cancel interval when tab or window is no longer shown (prevents cached pages from polling)
+ document.addEventListener('visibilitychange', this.onVisibilityChange.bind(this));
+ window.addEventListener('blur', this.onWindowVisibilityChange.bind(this));
+ window.addEventListener('focus', this.onWindowVisibilityChange.bind(this));
}
initPageUnloadHandling() {
@@ -135,8 +149,7 @@ export default class SmartInterval {
$(document).on('beforeunload', () => this.cancel());
}
- handleVisibilityChange(e) {
- this.state.pageVisibility = e.target.visibilityState;
+ handleVisibilityChange() {
const intervalAction = this.isPageVisible()
? this.onVisibilityVisible
: this.onVisibilityHidden;
@@ -166,7 +179,7 @@ export default class SmartInterval {
}
isPageVisible() {
- return this.state.pageVisibility === 'visible';
+ return this.state.pagevisibile;
}
stopTimer() {
diff --git a/app/assets/javascripts/snippets/components/show.vue b/app/assets/javascripts/snippets/components/show.vue
index e98f56d87f5..bc0034d397e 100644
--- a/app/assets/javascripts/snippets/components/show.vue
+++ b/app/assets/javascripts/snippets/components/show.vue
@@ -1,10 +1,11 @@
<script>
-import GetSnippetQuery from '../queries/snippet.query.graphql';
import SnippetHeader from './snippet_header.vue';
import SnippetTitle from './snippet_title.vue';
import SnippetBlob from './snippet_blob_view.vue';
import { GlLoadingIcon } from '@gitlab/ui';
+import { getSnippetMixin } from '../mixins/snippets';
+
export default {
components: {
SnippetHeader,
@@ -12,33 +13,7 @@ export default {
GlLoadingIcon,
SnippetBlob,
},
- apollo: {
- snippet: {
- query: GetSnippetQuery,
- variables() {
- return {
- ids: this.snippetGid,
- };
- },
- update: data => data.snippets.edges[0].node,
- },
- },
- props: {
- snippetGid: {
- type: String,
- required: true,
- },
- },
- data() {
- return {
- snippet: {},
- };
- },
- computed: {
- isLoading() {
- return this.$apollo.queries.snippet.loading;
- },
- },
+ mixins: [getSnippetMixin],
};
</script>
<template>
@@ -46,7 +21,7 @@ export default {
<gl-loading-icon
v-if="isLoading"
:label="__('Loading snippet')"
- :size="2"
+ size="lg"
class="loading-animation prepend-top-20 append-bottom-20"
/>
<template v-else>
diff --git a/app/assets/javascripts/snippets/components/snippet_blob_edit.vue b/app/assets/javascripts/snippets/components/snippet_blob_edit.vue
index ae6f451df18..44b4607e5a9 100644
--- a/app/assets/javascripts/snippets/components/snippet_blob_edit.vue
+++ b/app/assets/javascripts/snippets/components/snippet_blob_edit.vue
@@ -37,7 +37,7 @@ export default {
<gl-loading-icon
v-if="isLoading"
:label="__('Loading snippet')"
- :size="2"
+ size="lg"
class="loading-animation prepend-top-20 append-bottom-20"
/>
<blob-content-edit
diff --git a/app/assets/javascripts/snippets/mixins/snippets.js b/app/assets/javascripts/snippets/mixins/snippets.js
new file mode 100644
index 00000000000..837c41cdf6b
--- /dev/null
+++ b/app/assets/javascripts/snippets/mixins/snippets.js
@@ -0,0 +1,39 @@
+import GetSnippetQuery from '../queries/snippet.query.graphql';
+
+export const getSnippetMixin = {
+ apollo: {
+ snippet: {
+ query: GetSnippetQuery,
+ variables() {
+ return {
+ ids: this.snippetGid,
+ };
+ },
+ update: data => data.snippets.edges[0]?.node,
+ result(res) {
+ if (this.onSnippetFetch) {
+ this.onSnippetFetch(res);
+ }
+ },
+ },
+ },
+ props: {
+ snippetGid: {
+ type: String,
+ required: true,
+ },
+ },
+ data() {
+ return {
+ snippet: {},
+ newSnippet: false,
+ };
+ },
+ computed: {
+ isLoading() {
+ return this.$apollo.queries.snippet.loading;
+ },
+ },
+};
+
+export default () => {};
diff --git a/app/assets/javascripts/static_site_editor/components/static_site_editor.vue b/app/assets/javascripts/static_site_editor/components/static_site_editor.vue
index e711510ba44..8deae2f2c8a 100644
--- a/app/assets/javascripts/static_site_editor/components/static_site_editor.vue
+++ b/app/assets/javascripts/static_site_editor/components/static_site_editor.vue
@@ -12,8 +12,8 @@ export default {
Toolbar,
},
computed: {
- ...mapState(['content', 'isLoadingContent', 'isSavingChanges']),
- ...mapGetters(['isContentLoaded', 'contentChanged']),
+ ...mapState(['content', 'isLoadingContent', 'isSavingChanges', 'isContentLoaded']),
+ ...mapGetters(['contentChanged']),
},
mounted() {
this.loadContent();
diff --git a/app/assets/javascripts/static_site_editor/store/getters.js b/app/assets/javascripts/static_site_editor/store/getters.js
index 41256201c26..ebc68f8e9e6 100644
--- a/app/assets/javascripts/static_site_editor/store/getters.js
+++ b/app/assets/javascripts/static_site_editor/store/getters.js
@@ -1,2 +1,2 @@
-export const isContentLoaded = ({ originalContent }) => Boolean(originalContent);
+// eslint-disable-next-line import/prefer-default-export
export const contentChanged = ({ originalContent, content }) => originalContent !== content;
diff --git a/app/assets/javascripts/static_site_editor/store/mutations.js b/app/assets/javascripts/static_site_editor/store/mutations.js
index f98177bbc18..4727d04439c 100644
--- a/app/assets/javascripts/static_site_editor/store/mutations.js
+++ b/app/assets/javascripts/static_site_editor/store/mutations.js
@@ -6,6 +6,7 @@ export default {
},
[types.RECEIVE_CONTENT_SUCCESS](state, { title, content }) {
state.isLoadingContent = false;
+ state.isContentLoaded = true;
state.title = title;
state.content = content;
state.originalContent = content;
diff --git a/app/assets/javascripts/static_site_editor/store/state.js b/app/assets/javascripts/static_site_editor/store/state.js
index 477ec540e02..d48cc8ed1a4 100644
--- a/app/assets/javascripts/static_site_editor/store/state.js
+++ b/app/assets/javascripts/static_site_editor/store/state.js
@@ -6,6 +6,8 @@ const createState = (initialState = {}) => ({
isLoadingContent: false,
isSavingChanges: false,
+ isContentLoaded: false,
+
originalContent: '',
content: '',
title: '',
diff --git a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
index 1bc28b15f74..05f73c4cdaf 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/mr_widget_options.vue
@@ -214,8 +214,6 @@ export default {
return new MRWidgetService(this.getServiceEndpoints(store));
},
checkStatus(cb, isRebased) {
- if (document.visibilityState !== 'visible') return Promise.resolve();
-
return this.service
.checkStatus()
.then(({ data }) => {
@@ -238,10 +236,10 @@ export default {
initPolling() {
this.pollingInterval = new SmartInterval({
callback: this.checkStatus,
- startingInterval: 10000,
- maxInterval: 30000,
- hiddenInterval: 120000,
- incrementByFactorOf: 5000,
+ startingInterval: 10 * 1000,
+ maxInterval: 240 * 1000,
+ hiddenInterval: window.gon?.features?.widgetVisibilityPolling && 360 * 1000,
+ incrementByFactorOf: 2,
});
},
initDeploymentsPolling() {
@@ -253,10 +251,9 @@ export default {
deploymentsPoll(callback) {
return new SmartInterval({
callback,
- startingInterval: 30000,
- maxInterval: 120000,
- hiddenInterval: 240000,
- incrementByFactorOf: 15000,
+ startingInterval: 30 * 1000,
+ maxInterval: 240 * 1000,
+ incrementByFactorOf: 4,
immediateExecution: true,
});
},
diff --git a/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue b/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue
index 30a9633b6dc..fd45ac52647 100644
--- a/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue
+++ b/app/assets/javascripts/vue_shared/components/project_selector/project_selector.vue
@@ -80,7 +80,7 @@ export default {
@input="onInput"
/>
<div class="d-flex flex-column">
- <gl-loading-icon v-if="showLoadingIndicator" :size="1" class="py-2 px-4" />
+ <gl-loading-icon v-if="showLoadingIndicator" size="sm" class="py-2 px-4" />
<gl-infinite-scroll
:max-list-height="402"
:fetched-items="projectSearchResults.length"
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index 210d488f5a3..16254c74ba4 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -219,6 +219,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:domain_blacklist_file,
:raw_blob_request_limit,
:namespace_storage_size_limit,
+ :issues_create_limit,
disabled_oauth_sign_in_sources: [],
import_sources: [],
repository_storages: [],
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index e51a5c7b84d..09dc4d118a1 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -14,9 +14,7 @@ class Projects::EnvironmentsController < Projects::ApplicationController
before_action :expire_etag_cache, only: [:index], unless: -> { request.format.json? }
before_action only: [:metrics, :additional_metrics, :metrics_dashboard] do
push_frontend_feature_flag(:prometheus_computed_alerts)
- end
- before_action do
- push_frontend_feature_flag(:auto_stop_environments, default_enabled: true)
+ push_frontend_feature_flag(:metrics_dashboard_annotations)
end
after_action :expire_etag_cache, only: [:cancel_auto_stop]
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index f552c471eb2..96650e2cae9 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -42,6 +42,9 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_import_issues!, only: [:import_csv]
before_action :authorize_download_code!, only: [:related_branches]
+ # Limit the amount of issues created per minute
+ before_action :create_rate_limit, only: [:create]
+
before_action do
push_frontend_feature_flag(:vue_issuable_sidebar, project.group)
push_frontend_feature_flag(:save_issuable_health_status, project.group, default_enabled: true)
@@ -296,6 +299,22 @@ class Projects::IssuesController < Projects::ApplicationController
# 3. https://gitlab.com/gitlab-org/gitlab-foss/issues/42426
Gitlab::QueryLimiting.whitelist('https://gitlab.com/gitlab-org/gitlab-foss/issues/42422')
end
+
+ private
+
+ def create_rate_limit
+ key = :issues_create
+
+ if rate_limiter.throttled?(key, scope: [@project, @current_user])
+ rate_limiter.log_request(request, "#{key}_request_limit".to_sym, current_user)
+
+ render plain: _('This endpoint has been requested too many times. Try again later.'), status: :too_many_requests
+ end
+ end
+
+ def rate_limiter
+ ::Gitlab::ApplicationRateLimiter
+ end
end
Projects::IssuesController.prepend_if_ee('EE::Projects::IssuesController')
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 26de200a1c1..038b6146bab 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -24,6 +24,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo
push_frontend_feature_flag(:single_mr_diff_view, @project, default_enabled: true)
push_frontend_feature_flag(:suggest_pipeline) if experiment_enabled?(:suggest_pipeline)
push_frontend_feature_flag(:code_navigation, @project)
+ push_frontend_feature_flag(:widget_visibility_polling, @project, default_enabled: true)
end
before_action do
diff --git a/app/mailers/emails/pages_domains.rb b/app/mailers/emails/pages_domains.rb
index 1caca6b3e44..6c3dcf8746b 100644
--- a/app/mailers/emails/pages_domains.rb
+++ b/app/mailers/emails/pages_domains.rb
@@ -41,5 +41,16 @@ module Emails
subject: subject("ACTION REQUIRED: Verification failed for GitLab Pages domain '#{domain.domain}'")
)
end
+
+ def pages_domain_auto_ssl_failed_email(domain, recipient)
+ @domain = domain
+ @project = domain.project
+
+ subject_text = _("ACTION REQUIRED: Something went wrong while obtaining the Let's Encrypt certificate for GitLab Pages domain '%{domain}'") % { domain: domain.domain }
+ mail(
+ to: recipient.notification_email_for(@project.group),
+ subject: subject(subject_text)
+ )
+ end
end
end
diff --git a/app/models/application_setting_implementation.rb b/app/models/application_setting_implementation.rb
index 920ad3286d1..c96f086684f 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -79,6 +79,7 @@ module ApplicationSettingImplementation
housekeeping_gc_period: 200,
housekeeping_incremental_repack_period: 10,
import_sources: Settings.gitlab['import_sources'],
+ issues_create_limit: 300,
local_markdown_version: 0,
max_artifacts_size: Settings.artifacts['max_size'],
max_attachment_size: Settings.gitlab['max_attachment_size'],
diff --git a/app/models/ci/job_artifact.rb b/app/models/ci/job_artifact.rb
index ef0701b3874..c4ac10814a9 100644
--- a/app/models/ci/job_artifact.rb
+++ b/app/models/ci/job_artifact.rb
@@ -73,12 +73,14 @@ module Ci
validates :file_format, presence: true, unless: :trace?, on: :create
validate :valid_file_format?, unless: :trace?, on: :create
- before_save :set_size, if: :file_changed?
- update_project_statistics project_statistics_name: :build_artifacts_size
+ before_save :set_size, if: :file_changed?
+ before_save :set_file_store, if: ->(job_artifact) { job_artifact.file_store.nil? }
after_save :update_file_store, if: :saved_change_to_file?
+ update_project_statistics project_statistics_name: :build_artifacts_size
+
scope :with_files_stored_locally, -> { where(file_store: [nil, ::JobArtifactUploader::Store::LOCAL]) }
scope :with_files_stored_remotely, -> { where(file_store: ::JobArtifactUploader::Store::REMOTE) }
scope :for_sha, ->(sha, project_id) { joins(job: :pipeline).where(ci_pipelines: { sha: sha, project_id: project_id }) }
@@ -226,6 +228,15 @@ module Ci
self.size = file.size
end
+ def set_file_store
+ self.file_store =
+ if JobArtifactUploader.object_store_enabled? && JobArtifactUploader.direct_upload_enabled?
+ JobArtifactUploader::Store::REMOTE
+ else
+ file.object_store
+ end
+ end
+
def project_destroyed?
# Use job.project to avoid extra DB query for project
job.project.pending_delete?
diff --git a/app/models/diff_note_position.rb b/app/models/diff_note_position.rb
new file mode 100644
index 00000000000..78e4fbc49eb
--- /dev/null
+++ b/app/models/diff_note_position.rb
@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+class DiffNotePosition < ApplicationRecord
+ belongs_to :note
+
+ enum diff_content_type: {
+ text: 0,
+ image: 1
+ }
+
+ enum diff_type: {
+ head: 0
+ }
+
+ def position
+ Gitlab::Diff::Position.new(
+ old_path: old_path,
+ new_path: new_path,
+ old_line: old_line,
+ new_line: new_line,
+ position_type: diff_content_type,
+ diff_refs: Gitlab::Diff::DiffRefs.new(
+ base_sha: base_sha,
+ start_sha: start_sha,
+ head_sha: head_sha
+ )
+ )
+ end
+
+ def position=(position)
+ position_attrs = position.to_h
+ position_attrs[:diff_content_type] = position_attrs.delete(:position_type)
+
+ assign_attributes(position_attrs)
+ end
+end
diff --git a/app/models/lfs_object.rb b/app/models/lfs_object.rb
index 6a86aebae39..c5233deaa96 100644
--- a/app/models/lfs_object.rb
+++ b/app/models/lfs_object.rb
@@ -17,6 +17,8 @@ class LfsObject < ApplicationRecord
mount_uploader :file, LfsObjectUploader
+ before_save :set_file_store, if: ->(lfs_object) { lfs_object.file_store.nil? }
+
after_save :update_file_store, if: :saved_change_to_file?
def self.not_linked_to_project(project)
@@ -55,6 +57,17 @@ class LfsObject < ApplicationRecord
def self.calculate_oid(path)
self.hexdigest(path)
end
+
+ private
+
+ def set_file_store
+ self.file_store =
+ if LfsObjectUploader.object_store_enabled? && LfsObjectUploader.direct_upload_enabled?
+ LfsObjectUploader::Store::REMOTE
+ else
+ file.object_store
+ end
+ end
end
LfsObject.prepend_if_ee('EE::LfsObject')
diff --git a/app/services/clusters/create_service.rb b/app/services/clusters/create_service.rb
index 5c26c611e00..7b5bf6b32c2 100644
--- a/app/services/clusters/create_service.rb
+++ b/app/services/clusters/create_service.rb
@@ -23,6 +23,8 @@ module Clusters
cluster.errors.add(:base, _('Instance does not support multiple Kubernetes clusters'))
end
+ validate_management_project_permissions(cluster)
+
return cluster if cluster.errors.present?
cluster.tap do |cluster|
@@ -57,6 +59,11 @@ module Clusters
def can_create_cluster?
clusterable.clusters.empty?
end
+
+ def validate_management_project_permissions(cluster)
+ Clusters::Management::ValidateManagementProjectPermissionsService.new(current_user)
+ .execute(cluster, params[:management_project_id])
+ end
end
end
diff --git a/app/services/clusters/management/validate_management_project_permissions_service.rb b/app/services/clusters/management/validate_management_project_permissions_service.rb
new file mode 100644
index 00000000000..e89a0afe6d2
--- /dev/null
+++ b/app/services/clusters/management/validate_management_project_permissions_service.rb
@@ -0,0 +1,54 @@
+# frozen_string_literal: true
+
+module Clusters
+ module Management
+ class ValidateManagementProjectPermissionsService
+ attr_reader :current_user
+
+ def initialize(user = nil)
+ @current_user = user
+ end
+
+ def execute(cluster, management_project_id)
+ if management_project_id.present?
+ management_project = management_project_scope(cluster).find_by_id(management_project_id)
+
+ unless management_project && can_admin_pipeline_for_project?(management_project)
+ cluster.errors.add(:management_project_id, _('Project does not exist or you don\'t have permission to perform this action'))
+
+ return false
+ end
+ end
+
+ true
+ end
+
+ private
+
+ def can_admin_pipeline_for_project?(project)
+ Ability.allowed?(current_user, :admin_pipeline, project)
+ end
+
+ def management_project_scope(cluster)
+ return ::Project.all if cluster.instance_type?
+
+ group =
+ if cluster.group_type?
+ cluster.first_group
+ elsif cluster.project_type?
+ cluster.first_project&.namespace
+ end
+
+ # Prevent users from selecting nested projects until
+ # https://gitlab.com/gitlab-org/gitlab/issues/34650 is resolved
+ include_subgroups = cluster.group_type?
+
+ ::GroupProjectsFinder.new(
+ group: group,
+ current_user: current_user,
+ options: { only_owned: true, include_subgroups: include_subgroups }
+ ).execute
+ end
+ end
+ end
+end
diff --git a/app/services/clusters/update_service.rb b/app/services/clusters/update_service.rb
index 8cb77040b14..2315df612a1 100644
--- a/app/services/clusters/update_service.rb
+++ b/app/services/clusters/update_service.rb
@@ -18,46 +18,9 @@ module Clusters
private
- def can_admin_pipeline_for_project?(project)
- Ability.allowed?(current_user, :admin_pipeline, project)
- end
-
def validate_params(cluster)
- if params[:management_project_id].present?
- management_project = management_project_scope(cluster).find_by_id(params[:management_project_id])
-
- unless management_project
- cluster.errors.add(:management_project_id, _('Project does not exist or you don\'t have permission to perform this action'))
-
- return false
- end
-
- unless can_admin_pipeline_for_project?(management_project)
- # Use same message as not found to prevent enumeration
- cluster.errors.add(:management_project_id, _('Project does not exist or you don\'t have permission to perform this action'))
-
- return false
- end
- end
-
- true
- end
-
- def management_project_scope(cluster)
- return ::Project.all if cluster.instance_type?
-
- group =
- if cluster.group_type?
- cluster.first_group
- elsif cluster.project_type?
- cluster.first_project&.namespace
- end
-
- # Prevent users from selecting nested projects until
- # https://gitlab.com/gitlab-org/gitlab/issues/34650 is resolved
- include_subgroups = cluster.group_type?
-
- ::GroupProjectsFinder.new(group: group, current_user: current_user, options: { only_owned: true, include_subgroups: include_subgroups }).execute
+ ::Clusters::Management::ValidateManagementProjectPermissionsService.new(current_user)
+ .execute(cluster, params[:management_project_id])
end
end
end
diff --git a/app/services/environments/auto_stop_service.rb b/app/services/environments/auto_stop_service.rb
index ee7f25a4d76..bde598abf66 100644
--- a/app/services/environments/auto_stop_service.rb
+++ b/app/services/environments/auto_stop_service.rb
@@ -30,7 +30,7 @@ module Environments
def stop_in_batch
environments = Environment.auto_stoppable(BATCH_SIZE)
- return false unless environments.exists? && Feature.enabled?(:auto_stop_environments, default_enabled: true)
+ return false unless environments.exists?
Ci::StopEnvironmentsService.execute_in_batch(environments)
end
diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb
index 62827f20929..91e19d190bd 100644
--- a/app/services/notification_service.rb
+++ b/app/services/notification_service.rb
@@ -489,6 +489,12 @@ class NotificationService
end
end
+ def pages_domain_auto_ssl_failed(domain)
+ project_maintainers_recipients(domain, action: 'disabled').each do |recipient|
+ mailer.pages_domain_auto_ssl_failed_email(domain, recipient.user).deliver_later
+ end
+ end
+
def issue_due(issue)
recipients = NotificationRecipients::BuildService.build_recipients(
issue,
diff --git a/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb b/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb
index 93445dd4ddd..1c03641469e 100644
--- a/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb
+++ b/app/services/pages_domains/obtain_lets_encrypt_certificate_service.rb
@@ -57,6 +57,8 @@ module PagesDomains
pages_domain.save!(validate: false)
acme_order.destroy!
+
+ NotificationService.new.pages_domain_auto_ssl_failed(pages_domain)
end
def log_error(api_order)
diff --git a/app/uploaders/records_uploads.rb b/app/uploaders/records_uploads.rb
index 967fcdc704e..427314a87bb 100644
--- a/app/uploaders/records_uploads.rb
+++ b/app/uploaders/records_uploads.rb
@@ -56,10 +56,31 @@ module RecordsUploads
size: file.size,
path: upload_path,
model: model,
- mount_point: mounted_as
+ mount_point: mounted_as,
+ store: initial_store
)
end
+ def initial_store
+ if immediately_remote_stored?
+ ::ObjectStorage::Store::REMOTE
+ else
+ ::ObjectStorage::Store::LOCAL
+ end
+ end
+
+ def immediately_remote_stored?
+ object_storage_available? && direct_upload_enabled?
+ end
+
+ def object_storage_available?
+ self.class.ancestors.include?(ObjectStorage::Concern)
+ end
+
+ def direct_upload_enabled?
+ self.class.object_store_enabled? && self.class.direct_upload_enabled?
+ end
+
# Before removing an attachment, destroy any Upload records at the same path
#
# Called `before :remove`
diff --git a/app/views/admin/application_settings/_issue_limits.html.haml b/app/views/admin/application_settings/_issue_limits.html.haml
new file mode 100644
index 00000000000..5906358fbb1
--- /dev/null
+++ b/app/views/admin/application_settings/_issue_limits.html.haml
@@ -0,0 +1,9 @@
+= form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-issue-limits-settings'), html: { class: 'fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ .form-group
+ = f.label :issues_create_limit, 'Max requests per second per user', class: 'label-bold'
+ = f.number_field :issues_create_limit, class: 'form-control'
+
+ = f.submit 'Save changes', class: "btn btn-success", 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 8d88dedf832..db4611964b4 100644
--- a/app/views/admin/application_settings/network.html.haml
+++ b/app/views/admin/application_settings/network.html.haml
@@ -46,4 +46,15 @@
.settings-content
= render 'protected_paths'
+%section.settings.as-issue-limits.no-animate#js-issue-limits-settings{ class: ('expanded' if expanded_by_default?) }
+ .settings-header
+ %h4
+ = _('Issues Rate Limits')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded_by_default? ? _('Collapse') : _('Expand')
+ %p
+ = _('Configure limit for issues created per minute by web and API requests.')
+ .settings-content
+ = render 'issue_limits'
+
= render_if_exists 'admin/application_settings/ee_network_settings'
diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml
index 9fffa97f969..4e9cfc13af0 100644
--- a/app/views/admin/deploy_keys/index.html.haml
+++ b/app/views/admin/deploy_keys/index.html.haml
@@ -1,7 +1,7 @@
- page_title _('Deploy Keys')
%h3.page-title.deploy-keys-title
- = _('Public deploy keys (%{deploy_keys_count})') % { deploy_keys_count: @deploy_keys.count }
+ = _('Public deploy keys (%{deploy_keys_count})') % { deploy_keys_count: @deploy_keys.load.size }
.float-right
= link_to _('New deploy key'), new_admin_deploy_key_path, class: 'btn btn-success btn-sm btn-inverted'
diff --git a/app/views/notify/pages_domain_auto_ssl_failed_email.html.haml b/app/views/notify/pages_domain_auto_ssl_failed_email.html.haml
new file mode 100644
index 00000000000..1bc2cc15616
--- /dev/null
+++ b/app/views/notify/pages_domain_auto_ssl_failed_email.html.haml
@@ -0,0 +1,11 @@
+%p
+ = _("Something went wrong while obtaining the Let's Encrypt certificate.")
+%p
+ #{_('Project')}: #{link_to @project.human_name, project_url(@project)}
+%p
+ #{_('Domain')}: #{link_to @domain.domain, project_pages_domain_url(@project, @domain)}
+%p
+ - docs_url = help_page_url('user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md', anchor: 'troubleshooting')
+ - link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: docs_url }
+ - link_end = '</a>'.html_safe
+ = _("Please follow the %{link_start}Let\'s Encrypt troubleshooting instructions%{link_end} to re-obtain your Let's Encrypt certificate.").html_safe % { link_start: link_start, link_end: link_end }
diff --git a/app/views/notify/pages_domain_auto_ssl_failed_email.text.haml b/app/views/notify/pages_domain_auto_ssl_failed_email.text.haml
new file mode 100644
index 00000000000..6f20d11c966
--- /dev/null
+++ b/app/views/notify/pages_domain_auto_ssl_failed_email.text.haml
@@ -0,0 +1,7 @@
+= _("Something went wrong while obtaining the Let's Encrypt certificate.").html_safe
+
+#{_('Project')}: #{project_url(@project)}
+#{_('Domain')}: #{project_pages_domain_url(@project, @domain)}
+
+- docs_url = help_page_url('user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md', anchor: 'troubleshooting')
+= _("Please follow the Let\'s Encrypt troubleshooting instructions to re-obtain your Let's Encrypt certificate: %{docs_url}.").html_safe % { docs_url: docs_url }
diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml
index 6ea4eeb66c5..e28c74dd650 100644
--- a/app/views/profiles/emails/index.html.haml
+++ b/app/views/profiles/emails/index.html.haml
@@ -18,7 +18,7 @@
= f.submit _('Add email address'), class: 'btn btn-success', data: { qa_selector: 'add_email_address_button' }
%hr
%h4.prepend-top-0
- = _('Linked emails (%{email_count})') % { email_count: @emails.count + 1 }
+ = _('Linked emails (%{email_count})') % { email_count: @emails.load.size + 1 }
.account-well.append-bottom-default
%ul
%li
diff --git a/app/views/projects/issues/_related_branches.html.haml b/app/views/projects/issues/_related_branches.html.haml
index 6da4956a036..69b030ed76a 100644
--- a/app/views/projects/issues/_related_branches.html.haml
+++ b/app/views/projects/issues/_related_branches.html.haml
@@ -1,6 +1,6 @@
- if @related_branches.any?
%h2.related-branches-title
- = pluralize(@related_branches.count, 'Related Branch')
+ = pluralize(@related_branches.size, 'Related Branch')
%ul.unstyled-list.related-merge-requests
- @related_branches.each do |branch|
%li
diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml
index 0d40f375926..a9e2cbac890 100644
--- a/app/views/projects/pages/_list.html.haml
+++ b/app/views/projects/pages/_list.html.haml
@@ -1,9 +1,9 @@
- verification_enabled = Gitlab::CurrentSettings.pages_domain_verification_enabled?
-- if can?(current_user, :update_pages, @project) && @domains.any?
+- if can?(current_user, :update_pages, @project) && @domains.load.any?
.card
.card-header
- Domains (#{@domains.count})
+ Domains (#{@domains.size})
%ul.list-group.list-group-flush.pages-domain-list{ class: ("has-verification-status" if verification_enabled) }
- @domains.each do |domain|
- domain = Gitlab::View::Presenter::Factory.new(domain, current_user: current_user).fabricate!
diff --git a/app/workers/environments/auto_stop_cron_worker.rb b/app/workers/environments/auto_stop_cron_worker.rb
index de5e10a0976..ada52d3402d 100644
--- a/app/workers/environments/auto_stop_cron_worker.rb
+++ b/app/workers/environments/auto_stop_cron_worker.rb
@@ -8,8 +8,6 @@ module Environments
feature_category :continuous_delivery
def perform
- return unless Feature.enabled?(:auto_stop_environments, default_enabled: true)
-
AutoStopService.new.execute
end
end