summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.rubocop.yml2
-rw-r--r--Gemfile4
-rw-r--r--Gemfile.lock7
-rw-r--r--app/assets/javascripts/main.js1
-rw-r--r--app/assets/javascripts/monitoring/components/charts/area.vue27
-rw-r--r--app/assets/javascripts/monitoring/components/dashboard.vue17
-rw-r--r--app/assets/javascripts/monitoring/components/panel_type.vue28
-rw-r--r--app/assets/javascripts/monitoring/monitoring_bundle.js1
-rw-r--r--app/assets/javascripts/monitoring/stores/actions.js8
-rw-r--r--app/assets/javascripts/monitoring/stores/mutation_types.js1
-rw-r--r--app/assets/javascripts/monitoring/stores/mutations.js3
-rw-r--r--app/assets/javascripts/monitoring/stores/state.js1
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue2
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue10
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue7
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js3
-rw-r--r--app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js3
-rw-r--r--app/assets/stylesheets/framework/callout.scss8
-rw-r--r--app/assets/stylesheets/pages/merge_requests.scss3
-rw-r--r--app/assets/stylesheets/pages/wiki.scss17
-rw-r--r--app/controllers/application_controller.rb6
-rw-r--r--app/controllers/concerns/invisible_captcha.rb51
-rw-r--r--app/controllers/projects/cycle_analytics_controller.rb10
-rw-r--r--app/controllers/projects/environments_controller.rb1
-rw-r--r--app/controllers/registrations_controller.rb1
-rw-r--r--app/helpers/application_settings_helper.rb6
-rw-r--r--app/helpers/sessions_helper.rb7
-rw-r--r--app/helpers/todos_helper.rb21
-rw-r--r--app/helpers/tracking_helper.rb17
-rw-r--r--app/models/application_setting.rb5
-rw-r--r--app/models/application_setting_implementation.rb4
-rw-r--r--app/models/clusters/applications/cert_manager.rb8
-rw-r--r--app/models/clusters/applications/knative.rb12
-rw-r--r--app/models/clusters/applications/prometheus.rb4
-rw-r--r--app/models/commit_status.rb21
-rw-r--r--app/models/concerns/has_status.rb5
-rw-r--r--app/models/project.rb8
-rw-r--r--app/models/project_services/slash_commands_service.rb2
-rw-r--r--app/policies/global_policy.rb3
-rw-r--r--app/services/ci/process_pipeline_service.rb27
-rw-r--r--app/services/git/base_hooks_service.rb45
-rw-r--r--app/views/admin/application_settings/_snowplow.html.haml30
-rw-r--r--app/views/devise/sessions/_new_base.html.haml17
-rw-r--r--app/views/devise/shared/_signup_box.html.haml2
-rw-r--r--app/views/layouts/_snowplow.html.haml29
-rw-r--r--app/views/profiles/preferences/show.html.haml8
-rw-r--r--app/views/projects/project_members/_new_project_member.html.haml2
-rw-r--r--app/views/projects/wikis/_form.html.haml2
-rw-r--r--app/views/projects/wikis/_new.html.haml2
-rw-r--r--app/views/projects/wikis/edit.html.haml2
-rw-r--r--app/views/projects/wikis/git_access.html.haml2
-rw-r--r--app/views/projects/wikis/history.html.haml2
-rw-r--r--app/views/projects/wikis/pages.html.haml2
-rw-r--r--app/views/projects/wikis/show.html.haml2
-rw-r--r--changelogs/unreleased/10-adjust-copy-for-adding-additional-members.yml5
-rw-r--r--changelogs/unreleased/12502-add-view-stats-to-cycle-analytics.yml5
-rw-r--r--changelogs/unreleased/59829-fix-style-lint-wiki.yml5
-rw-r--r--changelogs/unreleased/62286-Consistent-selection-elements-in-user-settings-preferences.yml5
-rw-r--r--changelogs/unreleased/64950-move-download-csv-button-functionality-in-metrics-dashboard-cards-i.yml5
-rw-r--r--changelogs/unreleased/65483-add-a-resend-confirmation-link.yml5
-rw-r--r--changelogs/unreleased/georgekoltsov-48854-fix-empty-flash-message.yml6
-rw-r--r--changelogs/unreleased/post-migrate-private-profile.yml5
-rw-r--r--changelogs/unreleased/security-2873-blocked-user-slash-command-bypass-master.yml5
-rw-r--r--changelogs/unreleased/sh-optimize-commit-deltas-post-receive.yml5
-rw-r--r--config/initializers/elastic_client_setup.rb74
-rw-r--r--config/initializers/invisible_captcha.rb7
-rw-r--r--config/locales/invisible_captcha.en.yml4
-rw-r--r--config/routes/project.rb2
-rw-r--r--db/post_migrate/20190812070645_migrate_private_profile_nulls.rb31
-rw-r--r--db/schema.rb2
-rw-r--r--doc/README.md1
-rw-r--r--doc/administration/container_registry.md34
-rw-r--r--doc/administration/monitoring/prometheus/gitlab_metrics.md1
-rw-r--r--doc/api/dependencies.md2
-rw-r--r--doc/api/settings.md4
-rw-r--r--doc/ci/runners/README.md6
-rw-r--r--doc/customization/issue_and_merge_request_template.md4
-rw-r--r--doc/development/automatic_ce_ee_merge.md13
-rw-r--r--doc/development/documentation/styleguide.md3
-rw-r--r--doc/development/elasticsearch.md30
-rw-r--r--doc/development/testing_guide/best_practices.md1
-rw-r--r--doc/install/installation.md4
-rw-r--r--doc/install/requirements.md2
-rw-r--r--doc/update/upgrading_from_source.md4
-rw-r--r--doc/user/analytics/productivity_analytics.md69
-rw-r--r--doc/user/application_security/dependency_list/img/dependency_list_v12_2.pngbin0 -> 207114 bytes
-rw-r--r--doc/user/application_security/dependency_list/index.md49
-rw-r--r--doc/user/application_security/dependency_scanning/index.md13
-rw-r--r--doc/user/application_security/index.md1
-rw-r--r--doc/user/permissions.md10
-rw-r--r--doc/user/project/cycle_analytics.md28
-rw-r--r--doc/user/project/description_templates.md16
-rw-r--r--doc/user/project/img/description_templates_default_settings.pngbin26395 -> 0 bytes
-rw-r--r--doc/user/project/img/description_templates_issue_settings.pngbin0 -> 34698 bytes
-rw-r--r--doc/user/project/img/description_templates_merge_request_settings.pngbin0 -> 144128 bytes
-rw-r--r--doc/user/project/index.md1
-rw-r--r--doc/user/project/integrations/mattermost.md2
-rw-r--r--doc/user/project/issues/issue_data_and_actions.md7
-rw-r--r--doc/user/project/merge_requests/index.md6
-rw-r--r--lib/api/settings.rb6
-rw-r--r--lib/api/todos.rb13
-rw-r--r--lib/gitlab/ci/pipeline/chain/populate.rb11
-rw-r--r--lib/gitlab/ci/pipeline/seed/base.rb4
-rw-r--r--lib/gitlab/ci/pipeline/seed/build.rb60
-rw-r--r--lib/gitlab/ci/pipeline/seed/stage.rb6
-rw-r--r--lib/gitlab/data_builder/push.rb5
-rw-r--r--lib/gitlab/kubernetes/helm/reset_command.rb4
-rw-r--r--lib/gitlab/kubernetes/kubectl_cmd.rb19
-rw-r--r--lib/gitlab/metrics/samplers/puma_sampler.rb3
-rw-r--r--lib/gitlab/snowplow_tracker.rb35
-rw-r--r--lib/gitlab/usage_data.rb1
-rw-r--r--lib/gitlab/usage_data_counters/cycle_analytics_counter.rb8
-rw-r--r--lib/tasks/services.rake2
-rw-r--r--locale/gitlab.pot30
-rw-r--r--qa/qa/page/main/login.rb31
-rw-r--r--qa/qa/page/project/issue/new.rb4
-rw-r--r--qa/qa/page/project/issue/show.rb2
-rw-r--r--qa/qa/page/project/menu.rb2
-rw-r--r--qa/qa/resource/merge_request.rb25
-rw-r--r--qa/qa/scenario/test/sanity/selectors.rb8
-rw-r--r--qa/qa/service/omnibus.rb11
-rw-r--r--qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb13
-rw-r--r--qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb23
-rw-r--r--qa/qa/vendor/saml_idp/page/login.rb8
-rw-r--r--qa/spec/resource/repository/push_spec.rb6
-rw-r--r--qa/spec/runtime/env_spec.rb24
-rw-r--r--spec/controllers/application_controller_spec.rb28
-rw-r--r--spec/controllers/projects/cycle_analytics_controller_spec.rb14
-rw-r--r--spec/controllers/registrations_controller_spec.rb86
-rw-r--r--spec/features/invites_spec.rb1
-rw-r--r--spec/features/profiles/user_visits_profile_preferences_page_spec.rb4
-rw-r--r--spec/features/users/login_spec.rb36
-rw-r--r--spec/features/users/signup_spec.rb4
-rw-r--r--spec/helpers/sessions_helper_spec.rb17
-rw-r--r--spec/helpers/tracking_helper_spec.rb28
-rw-r--r--spec/javascripts/monitoring/charts/area_spec.js29
-rw-r--r--spec/javascripts/monitoring/dashboard_spec.js35
-rw-r--r--spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js24
-rw-r--r--spec/javascripts/vue_mr_widget/mock_data.js2
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/build_spec.rb53
-rw-r--r--spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb10
-rw-r--r--spec/lib/gitlab/data_builder/push_spec.rb34
-rw-r--r--spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb48
-rw-r--r--spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb2
-rw-r--r--spec/lib/gitlab/snowplow_tracker_spec.rb45
-rw-r--r--spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb9
-rw-r--r--spec/lib/gitlab/usage_data_spec.rb2
-rw-r--r--spec/models/project_spec.rb33
-rw-r--r--spec/policies/global_policy_spec.rb28
-rw-r--r--spec/requests/api/settings_spec.rb51
-rw-r--r--spec/services/ci/create_pipeline_service_spec.rb3
-rw-r--r--spec/services/git/base_hooks_service_spec.rb72
-rw-r--r--spec/services/git/branch_hooks_service_spec.rb4
-rw-r--r--spec/services/git/branch_push_service_spec.rb9
-rw-r--r--spec/services/git/tag_hooks_service_spec.rb6
-rw-r--r--spec/spec_helper.rb3
-rw-r--r--spec/support/shared_examples/chat_slash_commands_shared_examples.rb13
-rw-r--r--spec/views/layouts/_head.html.haml_spec.rb17
-rw-r--r--spec/workers/post_receive_spec.rb2
159 files changed, 1753 insertions, 364 deletions
diff --git a/.rubocop.yml b/.rubocop.yml
index 79e06439ac2..b75c63e1f58 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -8,7 +8,7 @@ require:
- rubocop-rspec
AllCops:
- TargetRubyVersion: 2.5
+ TargetRubyVersion: 2.6
TargetRailsVersion: 5.0
Exclude:
- 'vendor/**/*'
diff --git a/Gemfile b/Gemfile
index 55143693d5c..a91399ab3ad 100644
--- a/Gemfile
+++ b/Gemfile
@@ -51,6 +51,7 @@ gem 'jwt', '~> 2.1.0'
# Spam and anti-bot protection
gem 'recaptcha', '~> 4.11', require: 'recaptcha/rails'
gem 'akismet', '~> 2.0'
+gem 'invisible_captcha', '~> 0.12.1'
# Two-factor authentication
gem 'devise-two-factor', '~> 3.0.0'
@@ -297,6 +298,9 @@ gem 'batch-loader', '~> 1.4.0'
# Perf bar
gem 'peek', '~> 1.0.1'
+# Snowplow events tracking
+gem 'snowplow-tracker', '~> 0.6.1'
+
# Memory benchmarks
gem 'derailed_benchmarks', require: false
diff --git a/Gemfile.lock b/Gemfile.lock
index 6aa96d54abb..16d7f63cb66 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -152,6 +152,7 @@ GEM
concurrent-ruby-ext (1.1.5)
concurrent-ruby (= 1.1.5)
connection_pool (2.2.2)
+ contracts (0.11.0)
crack (0.4.3)
safe_yaml (~> 1.0.0)
crass (1.0.4)
@@ -437,6 +438,8 @@ GEM
influxdb (0.2.3)
cause
json
+ invisible_captcha (0.12.1)
+ rails (>= 3.2.0)
ipaddress (0.8.3)
jaeger-client (0.10.0)
opentracing (~> 0.3)
@@ -901,6 +904,8 @@ GEM
simplecov-html (~> 0.10.0)
simplecov-html (0.10.2)
slack-notifier (1.5.1)
+ snowplow-tracker (0.6.1)
+ contracts (~> 0.7, <= 0.11)
spring (2.0.2)
activesupport (>= 4.2)
spring-commands-rspec (1.0.4)
@@ -1126,6 +1131,7 @@ DEPENDENCIES
httparty (~> 0.16.4)
icalendar
influxdb (~> 0.2)
+ invisible_captcha (~> 0.12.1)
jira-ruby (~> 1.4)
js_regex (~> 3.1)
json-schema (~> 2.8.0)
@@ -1229,6 +1235,7 @@ DEPENDENCIES
simple_po_parser (~> 1.1.2)
simplecov (~> 0.16.1)
slack-notifier (~> 1.5.1)
+ snowplow-tracker (~> 0.6.1)
spring (~> 2.0.0)
spring-commands-rspec (~> 1.0.4)
sprockets (~> 3.7.0)
diff --git a/app/assets/javascripts/main.js b/app/assets/javascripts/main.js
index 9e97f345717..ba33d72b1f3 100644
--- a/app/assets/javascripts/main.js
+++ b/app/assets/javascripts/main.js
@@ -107,6 +107,7 @@ function deferredInitialisation() {
.then(() => {
$('select.select2').select2({
width: 'resolve',
+ minimumResultsForSearch: 10,
dropdownAutoWidth: true,
});
diff --git a/app/assets/javascripts/monitoring/components/charts/area.vue b/app/assets/javascripts/monitoring/components/charts/area.vue
index 5b950f8c966..838447e6c75 100644
--- a/app/assets/javascripts/monitoring/components/charts/area.vue
+++ b/app/assets/javascripts/monitoring/components/charts/area.vue
@@ -1,7 +1,6 @@
<script>
import { __ } from '~/locale';
-import { mapState } from 'vuex';
-import { GlLink, GlButton } from '@gitlab/ui';
+import { GlLink } from '@gitlab/ui';
import { GlAreaChart, GlChartSeriesLabel } from '@gitlab/ui/dist/charts';
import dateFormat from 'dateformat';
import { debounceByAnimationFrame, roundOffFloat } from '~/lib/utils/common_utils';
@@ -16,7 +15,6 @@ let debouncedResize;
export default {
components: {
GlAreaChart,
- GlButton,
GlChartSeriesLabel,
GlLink,
Icon,
@@ -69,7 +67,6 @@ export default {
};
},
computed: {
- ...mapState('monitoringDashboard', ['exportMetricsToCsvEnabled']),
chartData() {
// Transforms & supplements query data to render appropriate labels & styles
// Input: [{ queryAttributes1 }, { queryAttributes2 }]
@@ -179,18 +176,6 @@ export default {
yAxisLabel() {
return `${this.graphData.y_label}`;
},
- csvText() {
- const chartData = this.chartData[0].data;
- const header = `timestamp,${this.graphData.y_label}\r\n`; // eslint-disable-line @gitlab/i18n/no-non-i18n-strings
- return chartData.reduce((csv, data) => {
- const row = data.join(',');
- return `${csv}${row}\r\n`;
- }, header);
- },
- downloadLink() {
- const data = new Blob([this.csvText], { type: 'text/plain' });
- return window.URL.createObjectURL(data);
- },
},
watch: {
containerWidth: 'onResize',
@@ -259,16 +244,6 @@ export default {
<div :class="{ 'prometheus-graph-embed w-100 p-3': showBorder }">
<div class="prometheus-graph-header">
<h5 ref="graphTitle" class="prometheus-graph-title">{{ graphData.title }}</h5>
- <gl-button
- v-if="exportMetricsToCsvEnabled"
- :href="downloadLink"
- :title="__('Download CSV')"
- :aria-label="__('Download CSV')"
- style="margin-left: 200px;"
- download="chart_metrics.csv"
- >
- {{ __('Download CSV') }}
- </gl-button>
<div ref="graphWidgets" class="prometheus-graph-widgets"><slot></slot></div>
</div>
<gl-area-chart
diff --git a/app/assets/javascripts/monitoring/components/dashboard.vue b/app/assets/javascripts/monitoring/components/dashboard.vue
index 782e4310f3e..587392adbc3 100644
--- a/app/assets/javascripts/monitoring/components/dashboard.vue
+++ b/app/assets/javascripts/monitoring/components/dashboard.vue
@@ -235,6 +235,19 @@ export default {
chart.metrics.some(metric => this.metricsWithData.includes(metric.metric_id)),
);
},
+ csvText(graphData) {
+ const chartData = graphData.queries[0].result[0].values;
+ const yLabel = graphData.y_label;
+ const header = `timestamp,${yLabel}\r\n`; // eslint-disable-line @gitlab/i18n/no-non-i18n-strings
+ return chartData.reduce((csv, data) => {
+ const row = data.join(',');
+ return `${csv}${row}\r\n`;
+ }, header);
+ },
+ downloadCsv(graphData) {
+ const data = new Blob([this.csvText(graphData)], { type: 'text/plain' });
+ return window.URL.createObjectURL(data);
+ },
// TODO: BEGIN, Duplicated code with panel_type until feature flag is removed
// Issue number: https://gitlab.com/gitlab-org/gitlab-ce/issues/63845
getGraphAlerts(queries) {
@@ -448,7 +461,6 @@ export default {
@setAlerts="setAlerts"
/>
<gl-dropdown
- v-if="alertWidgetAvailable"
v-gl-tooltip
class="mx-2"
toggle-class="btn btn-transparent border-0"
@@ -459,6 +471,9 @@ export default {
<template slot="button-content">
<icon name="ellipsis_v" class="text-secondary" />
</template>
+ <gl-dropdown-item :href="downloadCsv(graphData)" download="chart_metrics.csv">
+ {{ __('Download CSV') }}
+ </gl-dropdown-item>
<gl-dropdown-item
v-if="alertWidgetAvailable"
v-gl-modal="`alert-modal-${index}-${graphIndex}`"
diff --git a/app/assets/javascripts/monitoring/components/panel_type.vue b/app/assets/javascripts/monitoring/components/panel_type.vue
index 295c0851f12..3fbac71f3d7 100644
--- a/app/assets/javascripts/monitoring/components/panel_type.vue
+++ b/app/assets/javascripts/monitoring/components/panel_type.vue
@@ -1,7 +1,14 @@
<script>
import { mapState } from 'vuex';
import _ from 'underscore';
-import { GlDropdown, GlDropdownItem, GlModal, GlModalDirective } from '@gitlab/ui';
+import {
+ GlDropdown,
+ GlDropdownItem,
+ GlModal,
+ GlModalDirective,
+ GlTooltipDirective,
+} from '@gitlab/ui';
+import Icon from '~/vue_shared/components/icon.vue';
import MonitorAreaChart from './charts/area.vue';
import MonitorSingleStatChart from './charts/single_stat.vue';
import MonitorEmptyChart from './charts/empty_chart.vue';
@@ -11,12 +18,14 @@ export default {
MonitorAreaChart,
MonitorSingleStatChart,
MonitorEmptyChart,
+ Icon,
GlDropdown,
GlDropdownItem,
GlModal,
},
directives: {
GlModal: GlModalDirective,
+ GlTooltip: GlTooltipDirective,
},
props: {
graphData: {
@@ -41,6 +50,19 @@ export default {
graphDataHasMetrics() {
return this.graphData.queries[0].result.length > 0;
},
+ csvText() {
+ const chartData = this.graphData.queries[0].result[0].values;
+ const yLabel = this.graphData.y_label;
+ const header = `timestamp,${yLabel}\r\n`; // eslint-disable-line @gitlab/i18n/no-non-i18n-strings
+ return chartData.reduce((csv, data) => {
+ const row = data.join(',');
+ return `${csv}${row}\r\n`;
+ }, header);
+ },
+ downloadCsv() {
+ const data = new Blob([this.csvText], { type: 'text/plain' });
+ return window.URL.createObjectURL(data);
+ },
},
methods: {
getGraphAlerts(queries) {
@@ -81,7 +103,6 @@ export default {
@setAlerts="setAlerts"
/>
<gl-dropdown
- v-if="alertWidgetAvailable"
v-gl-tooltip
class="mx-2"
toggle-class="btn btn-transparent border-0"
@@ -92,6 +113,9 @@ export default {
<template slot="button-content">
<icon name="ellipsis_v" class="text-secondary" />
</template>
+ <gl-dropdown-item :href="downloadCsv" download="chart_metrics.csv">
+ {{ __('Download CSV') }}
+ </gl-dropdown-item>
<gl-dropdown-item v-if="alertWidgetAvailable" v-gl-modal="`alert-modal-${index}`">
{{ __('Alerts') }}
</gl-dropdown-item>
diff --git a/app/assets/javascripts/monitoring/monitoring_bundle.js b/app/assets/javascripts/monitoring/monitoring_bundle.js
index 366034becd0..c0fee1ebb99 100644
--- a/app/assets/javascripts/monitoring/monitoring_bundle.js
+++ b/app/assets/javascripts/monitoring/monitoring_bundle.js
@@ -13,7 +13,6 @@ export default (props = {}) => {
prometheusEndpointEnabled: gon.features.environmentMetricsUsePrometheusEndpoint,
multipleDashboardsEnabled: gon.features.environmentMetricsShowMultipleDashboards,
additionalPanelTypesEnabled: gon.features.environmentMetricsAdditionalPanelTypes,
- exportMetricsToCsvEnabled: gon.features.exportMetricsToCsvEnabled,
});
}
diff --git a/app/assets/javascripts/monitoring/stores/actions.js b/app/assets/javascripts/monitoring/stores/actions.js
index a9c491c7c6c..0cbad179f17 100644
--- a/app/assets/javascripts/monitoring/stores/actions.js
+++ b/app/assets/javascripts/monitoring/stores/actions.js
@@ -37,17 +37,11 @@ export const setEndpoints = ({ commit }, endpoints) => {
export const setFeatureFlags = (
{ commit },
- {
- prometheusEndpointEnabled,
- multipleDashboardsEnabled,
- additionalPanelTypesEnabled,
- exportMetricsToCsvEnabled,
- },
+ { prometheusEndpointEnabled, multipleDashboardsEnabled, additionalPanelTypesEnabled },
) => {
commit(types.SET_DASHBOARD_ENABLED, prometheusEndpointEnabled);
commit(types.SET_MULTIPLE_DASHBOARDS_ENABLED, multipleDashboardsEnabled);
commit(types.SET_ADDITIONAL_PANEL_TYPES_ENABLED, additionalPanelTypesEnabled);
- commit(types.SET_EXPORT_METRICS_TO_CSV_ENABLED, exportMetricsToCsvEnabled);
};
export const setShowErrorBanner = ({ commit }, enabled) => {
diff --git a/app/assets/javascripts/monitoring/stores/mutation_types.js b/app/assets/javascripts/monitoring/stores/mutation_types.js
index 9ec8214b167..4b1aadbcf05 100644
--- a/app/assets/javascripts/monitoring/stores/mutation_types.js
+++ b/app/assets/javascripts/monitoring/stores/mutation_types.js
@@ -17,4 +17,3 @@ export const SET_ENDPOINTS = 'SET_ENDPOINTS';
export const SET_GETTING_STARTED_EMPTY_STATE = 'SET_GETTING_STARTED_EMPTY_STATE';
export const SET_NO_DATA_EMPTY_STATE = 'SET_NO_DATA_EMPTY_STATE';
export const SET_SHOW_ERROR_BANNER = 'SET_SHOW_ERROR_BANNER';
-export const SET_EXPORT_METRICS_TO_CSV_ENABLED = 'SET_EXPORT_METRICS_TO_CSV_ENABLED';
diff --git a/app/assets/javascripts/monitoring/stores/mutations.js b/app/assets/javascripts/monitoring/stores/mutations.js
index a2dceb21fc0..b19520d6638 100644
--- a/app/assets/javascripts/monitoring/stores/mutations.js
+++ b/app/assets/javascripts/monitoring/stores/mutations.js
@@ -99,7 +99,4 @@ export default {
[types.SET_SHOW_ERROR_BANNER](state, enabled) {
state.showErrorBanner = enabled;
},
- [types.SET_EXPORT_METRICS_TO_CSV_ENABLED](state, enabled) {
- state.exportMetricsToCsvEnabled = enabled;
- },
};
diff --git a/app/assets/javascripts/monitoring/stores/state.js b/app/assets/javascripts/monitoring/stores/state.js
index a14a25e3a20..440bdc951e0 100644
--- a/app/assets/javascripts/monitoring/stores/state.js
+++ b/app/assets/javascripts/monitoring/stores/state.js
@@ -10,7 +10,6 @@ export default () => ({
useDashboardEndpoint: false,
multipleDashboardsEnabled: false,
additionalPanelTypesEnabled: false,
- exportMetricsToCsvEnabled: false,
emptyState: 'gettingStarted',
showEmptyState: true,
showErrorBanner: true,
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
index a347269c916..53bf9d5ab6f 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_merge_help.vue
@@ -23,7 +23,7 @@ export default {
};
</script>
<template>
- <section class="mr-widget-help">
+ <section class="mr-widget-help font-italic">
<template v-if="missingBranch">
{{ missingBranchInfo }}
</template>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
index 76b96c8c1c0..8fdf61a6b8d 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/mr_widget_pipeline_container.vue
@@ -18,8 +18,8 @@ export default {
Deployment,
MrWidgetContainer,
MrWidgetPipeline,
- MergeTrainInfo: () =>
- import('ee_component/vue_merge_request_widget/components/merge_train_info.vue'),
+ MergeTrainPositionIndicator: () =>
+ import('ee_component/vue_merge_request_widget/components/merge_train_position_indicator.vue'),
},
props: {
mr: {
@@ -62,7 +62,7 @@ export default {
showVisualReviewAppLink() {
return this.mr.visualReviewAppAvailable;
},
- showMergeTrainInfo() {
+ showMergeTrainPositionIndicator() {
return _.isNumber(this.mr.mergeTrainIndex);
},
},
@@ -90,8 +90,8 @@ export default {
:visual-review-app-meta="visualReviewAppMeta"
/>
</div>
- <merge-train-info
- v-if="showMergeTrainInfo"
+ <merge-train-position-indicator
+ v-if="showMergeTrainPositionIndicator"
class="mr-widget-extension"
:merge-train-index="mr.mergeTrainIndex"
/>
diff --git a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
index d4514767912..e294e1de976 100644
--- a/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
+++ b/app/assets/javascripts/vue_merge_request_widget/components/states/ready_to_merge.vue
@@ -94,9 +94,6 @@ export default {
return __('Merge');
},
- shouldShowMergeOptionsDropdown() {
- return this.isAutoMergeAvailable && !this.mr.onlyAllowMergeIfPipelineSucceeds;
- },
isRemoveSourceBranchButtonDisabled() {
return this.isMergeButtonDisabled;
},
@@ -246,7 +243,7 @@ export default {
{{ mergeButtonText }}
</button>
<button
- v-if="isAutoMergeAvailable"
+ v-if="shouldShowMergeImmediatelyDropdown"
:disabled="isMergeButtonDisabled"
type="button"
class="btn btn-sm btn-info dropdown-toggle js-merge-moment"
@@ -256,7 +253,7 @@ export default {
<i class="fa fa-chevron-down qa-merge-moment-dropdown" aria-hidden="true"></i>
</button>
<ul
- v-if="shouldShowMergeOptionsDropdown"
+ v-if="shouldShowMergeImmediatelyDropdown"
class="dropdown-menu dropdown-menu-right"
role="menu"
>
diff --git a/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js b/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js
index 116d537c463..eef49e20159 100644
--- a/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js
+++ b/app/assets/javascripts/vue_merge_request_widget/mixins/ready_to_merge.js
@@ -15,5 +15,8 @@ export default {
// MWPS is currently the only auto merge strategy available in CE
return __('Merge when pipeline succeeds');
},
+ shouldShowMergeImmediatelyDropdown() {
+ return this.mr.isPipelineActive && !this.mr.onlyAllowMergeIfPipelineSucceeds;
+ },
},
};
diff --git a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
index 3eab8e6fc0b..0f55bebd3fc 100644
--- a/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
+++ b/app/assets/javascripts/vue_merge_request_widget/stores/mr_widget_store.js
@@ -31,6 +31,9 @@ export default class MergeRequestStore {
this.targetBranchSha = data.target_branch_sha;
this.sourceBranch = data.source_branch;
this.sourceBranchProtected = data.source_branch_protected;
+ this.conflictsDocsPath = data.conflicts_docs_path;
+ this.mergeRequestPipelinesHelpPath = data.merge_request_pipelines_docs_path;
+ this.mergeTrainWhenPipelineSucceedsDocsPath = data.merge_train_when_pipeline_succeeds_docs_path;
this.mergeStatus = data.merge_status;
this.commitMessage = data.default_merge_commit_message;
this.shortMergeCommitSha = data.short_merge_commit_sha;
diff --git a/app/assets/stylesheets/framework/callout.scss b/app/assets/stylesheets/framework/callout.scss
index 643b20c56bc..c5bb2a1256a 100644
--- a/app/assets/stylesheets/framework/callout.scss
+++ b/app/assets/stylesheets/framework/callout.scss
@@ -9,7 +9,9 @@
.bs-callout {
margin: $gl-padding 0;
padding: $gl-padding;
- border-left: 3px solid $border-color;
+ border-color: $border-color;
+ border-style: solid;
+ border-width: 0 0 0 3px;
color: $text-color;
background: $gray-light;
@@ -48,6 +50,10 @@
background-color: $blue-100;
border-color: $blue-200;
color: $blue-700;
+
+ h4 {
+ color: $blue-700;
+ }
}
.bs-callout-success {
diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss
index 3c1e384d6ed..c8d155706a9 100644
--- a/app/assets/stylesheets/pages/merge_requests.scss
+++ b/app/assets/stylesheets/pages/merge_requests.scss
@@ -397,7 +397,6 @@
.mr-widget-help {
padding: 10px 16px 10px ($gl-padding-8 * 7);
- font-style: italic;
}
.ci-coverage {
@@ -906,7 +905,7 @@
}
.deploy-heading,
-.merge-train-info {
+.merge-train-position-indicator {
@include media-breakpoint-up(md) {
padding: $gl-padding-8 $gl-padding;
}
diff --git a/app/assets/stylesheets/pages/wiki.scss b/app/assets/stylesheets/pages/wiki.scss
index 60400f10ca5..379df1c4db1 100644
--- a/app/assets/stylesheets/pages/wiki.scss
+++ b/app/assets/stylesheets/pages/wiki.scss
@@ -1,19 +1,3 @@
-.new-wiki-page {
- .new-wiki-page-slug-tip {
- display: inline-block;
- max-width: 100%;
- margin-top: 5px;
- }
-}
-
-.wiki-form {
- .edit-wiki-page-slug-tip {
- display: inline-block;
- max-width: 100%;
- margin-top: 5px;
- }
-}
-
.title .edit-wiki-header {
width: 780px;
margin-left: auto;
@@ -22,7 +6,6 @@
}
.wiki-page-header {
- @extend .top-area;
position: relative;
.wiki-breadcrumb {
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 1d55a073f3b..5e65084a110 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -116,7 +116,7 @@ class ApplicationController < ActionController::Base
def render(*args)
super.tap do
# Set a header for custom error pages to prevent them from being intercepted by gitlab-workhorse
- if response.content_type == 'text/html' && (400..599).cover?(response.status)
+ if (400..599).cover?(response.status) && workhorse_excluded_content_types.include?(response.content_type)
response.headers['X-GitLab-Custom-Error'] = '1'
end
end
@@ -124,6 +124,10 @@ class ApplicationController < ActionController::Base
protected
+ def workhorse_excluded_content_types
+ @workhorse_excluded_content_types ||= %w(text/html application/json)
+ end
+
def append_info_to_payload(payload)
super
diff --git a/app/controllers/concerns/invisible_captcha.rb b/app/controllers/concerns/invisible_captcha.rb
new file mode 100644
index 00000000000..c9f66e5c194
--- /dev/null
+++ b/app/controllers/concerns/invisible_captcha.rb
@@ -0,0 +1,51 @@
+# frozen_string_literal: true
+
+module InvisibleCaptcha
+ extend ActiveSupport::Concern
+
+ included do
+ invisible_captcha only: :create, on_spam: :on_honeypot_spam_callback, on_timestamp_spam: :on_timestamp_spam_callback
+ end
+
+ def on_honeypot_spam_callback
+ return unless Feature.enabled?(:invisible_captcha)
+
+ invisible_captcha_honeypot_counter.increment
+ log_request('Invisible_Captcha_Honeypot_Request')
+
+ head(200)
+ end
+
+ def on_timestamp_spam_callback
+ return unless Feature.enabled?(:invisible_captcha)
+
+ invisible_captcha_timestamp_counter.increment
+ log_request('Invisible_Captcha_Timestamp_Request')
+
+ redirect_to new_user_session_path, alert: InvisibleCaptcha.timestamp_error_message
+ end
+
+ def invisible_captcha_honeypot_counter
+ @invisible_captcha_honeypot_counter ||=
+ Gitlab::Metrics.counter(:bot_blocked_by_invisible_captcha_honeypot,
+ 'Counter of blocked sign up attempts with filled honeypot')
+ end
+
+ def invisible_captcha_timestamp_counter
+ @invisible_captcha_timestamp_counter ||=
+ Gitlab::Metrics.counter(:bot_blocked_by_invisible_captcha_timestamp,
+ 'Counter of blocked sign up attempts with invalid timestamp')
+ end
+
+ def log_request(message)
+ request_information = {
+ message: message,
+ env: :invisible_captcha_signup_bot_detected,
+ ip: request.ip,
+ request_method: request.request_method,
+ fullpath: request.fullpath
+ }
+
+ Gitlab::AuthLogger.error(request_information)
+ end
+end
diff --git a/app/controllers/projects/cycle_analytics_controller.rb b/app/controllers/projects/cycle_analytics_controller.rb
index 2d46a71bf99..3b0abecf2c9 100644
--- a/app/controllers/projects/cycle_analytics_controller.rb
+++ b/app/controllers/projects/cycle_analytics_controller.rb
@@ -14,8 +14,14 @@ class Projects::CycleAnalyticsController < Projects::ApplicationController
@cycle_analytics_no_data = @cycle_analytics.no_stats?
respond_to do |format|
- format.html
- format.json { render json: cycle_analytics_json }
+ format.html do
+ Gitlab::UsageDataCounters::CycleAnalyticsCounter.count(:views)
+
+ render :show
+ end
+ format.json do
+ render json: cycle_analytics_json
+ end
end
end
diff --git a/app/controllers/projects/environments_controller.rb b/app/controllers/projects/environments_controller.rb
index df9e55fda2a..5a1f93dc609 100644
--- a/app/controllers/projects/environments_controller.rb
+++ b/app/controllers/projects/environments_controller.rb
@@ -15,7 +15,6 @@ class Projects::EnvironmentsController < Projects::ApplicationController
push_frontend_feature_flag(:environment_metrics_show_multiple_dashboards)
push_frontend_feature_flag(:environment_metrics_additional_panel_types)
push_frontend_feature_flag(:prometheus_computed_alerts)
- push_frontend_feature_flag(:export_metrics_to_csv_enabled)
end
def index
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 638934694e0..db10515c0b4 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -4,6 +4,7 @@ class RegistrationsController < Devise::RegistrationsController
include Recaptcha::Verify
include AcceptsPendingInvitations
include RecaptchaExperimentHelper
+ include InvisibleCaptcha
prepend_before_action :check_captcha, only: :create
before_action :whitelist_query_limiting, only: [:destroy]
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index acbcf0ded17..0ab19f1d2d2 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -270,7 +270,11 @@ module ApplicationSettingsHelper
:diff_max_patch_bytes,
:commit_email_hostname,
:protected_ci_variables,
- :local_markdown_version
+ :local_markdown_version,
+ :snowplow_collector_hostname,
+ :snowplow_cookie_domain,
+ :snowplow_enabled,
+ :snowplow_site_id
]
end
diff --git a/app/helpers/sessions_helper.rb b/app/helpers/sessions_helper.rb
new file mode 100644
index 00000000000..af98a611b8b
--- /dev/null
+++ b/app/helpers/sessions_helper.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+module SessionsHelper
+ def unconfirmed_email?
+ flash[:alert] == t(:unconfirmed, scope: [:devise, :failure])
+ end
+end
diff --git a/app/helpers/todos_helper.rb b/app/helpers/todos_helper.rb
index 645160077f5..38142bc68cb 100644
--- a/app/helpers/todos_helper.rb
+++ b/app/helpers/todos_helper.rb
@@ -26,7 +26,7 @@ module TodosHelper
end
def todo_target_link(todo)
- text = raw("#{todo.target_type.titleize.downcase} ") +
+ text = raw(todo_target_type_name(todo) + ' ') +
if todo.for_commit?
content_tag(:span, todo.target_reference, class: 'commit-sha')
else
@@ -36,23 +36,34 @@ module TodosHelper
link_to text, todo_target_path(todo), class: 'has-tooltip', title: todo.target.title
end
+ def todo_target_type_name(todo)
+ todo.target_type.titleize.downcase
+ end
+
def todo_target_path(todo)
return unless todo.target.present?
- anchor = dom_id(todo.note) if todo.note.present?
+ path_options = todo_target_path_options(todo)
if todo.for_commit?
- project_commit_path(todo.project,
- todo.target, anchor: anchor)
+ project_commit_path(todo.project, todo.target, path_options)
else
path = [todo.parent, todo.target]
path.unshift(:pipelines) if todo.build_failed?
- polymorphic_path(path, anchor: anchor)
+ polymorphic_path(path, path_options)
end
end
+ def todo_target_path_options(todo)
+ { anchor: todo_target_path_anchor(todo) }
+ end
+
+ def todo_target_path_anchor(todo)
+ dom_id(todo.note) if todo.note.present?
+ end
+
def todo_target_state_pill(todo)
return unless show_todo_state?(todo)
diff --git a/app/helpers/tracking_helper.rb b/app/helpers/tracking_helper.rb
index 51ea79d1ddd..221d9692661 100644
--- a/app/helpers/tracking_helper.rb
+++ b/app/helpers/tracking_helper.rb
@@ -2,6 +2,21 @@
module TrackingHelper
def tracking_attrs(label, event, property)
- {} # CE has no tracking features
+ return {} unless tracking_enabled?
+
+ {
+ data: {
+ track_label: label,
+ track_event: event,
+ track_property: property
+ }
+ }
+ end
+
+ private
+
+ def tracking_enabled?
+ Rails.env.production? &&
+ ::Gitlab::CurrentSettings.snowplow_enabled?
end
end
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index cb6346421ec..2a99c6e5c59 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -99,6 +99,11 @@ class ApplicationSetting < ApplicationRecord
presence: true,
if: :plantuml_enabled
+ validates :snowplow_collector_hostname,
+ presence: true,
+ hostname: true,
+ if: :snowplow_enabled
+
validates :max_attachment_size,
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 b7a4d7aa803..55ac1e129cf 100644
--- a/app/models/application_setting_implementation.rb
+++ b/app/models/application_setting_implementation.rb
@@ -97,6 +97,10 @@ module ApplicationSettingImplementation
usage_stats_set_by_user_id: nil,
diff_max_patch_bytes: Gitlab::Git::Diff::DEFAULT_MAX_PATCH_BYTES,
commit_email_hostname: default_commit_email_hostname,
+ snowplow_collector_hostname: nil,
+ snowplow_cookie_domain: nil,
+ snowplow_enabled: false,
+ snowplow_site_id: nil,
protected_ci_variables: false,
local_markdown_version: 0,
outbound_local_requests_whitelist: [],
diff --git a/app/models/clusters/applications/cert_manager.rb b/app/models/clusters/applications/cert_manager.rb
index 2fc1b67dfd2..6bd7473c8ff 100644
--- a/app/models/clusters/applications/cert_manager.rb
+++ b/app/models/clusters/applications/cert_manager.rb
@@ -64,11 +64,15 @@ module Clusters
end
def delete_private_key
- "kubectl delete secret -n #{Gitlab::Kubernetes::Helm::NAMESPACE} #{private_key_name} --ignore-not-found" if private_key_name.present?
+ return unless private_key_name.present?
+
+ args = %W(secret -n #{Gitlab::Kubernetes::Helm::NAMESPACE} #{private_key_name} --ignore-not-found)
+
+ Gitlab::Kubernetes::KubectlCmd.delete(*args)
end
def delete_crd(definition)
- "kubectl delete crd #{definition} --ignore-not-found"
+ Gitlab::Kubernetes::KubectlCmd.delete("crd", definition, "--ignore-not-found")
end
def cluster_issuer_file
diff --git a/app/models/clusters/applications/knative.rb b/app/models/clusters/applications/knative.rb
index 5eae23659ae..244fe738396 100644
--- a/app/models/clusters/applications/knative.rb
+++ b/app/models/clusters/applications/knative.rb
@@ -89,7 +89,7 @@ module Clusters
def delete_knative_services
cluster.kubernetes_namespaces.map do |kubernetes_namespace|
- "kubectl delete ksvc --all -n #{kubernetes_namespace.namespace}"
+ Gitlab::Kubernetes::KubectlCmd.delete("ksvc", "--all", "-n", kubernetes_namespace.namespace)
end
end
@@ -99,14 +99,14 @@ module Clusters
def delete_knative_namespaces
[
- "kubectl delete --ignore-not-found ns knative-serving",
- "kubectl delete --ignore-not-found ns knative-build"
+ Gitlab::Kubernetes::KubectlCmd.delete("--ignore-not-found", "ns", "knative-serving"),
+ Gitlab::Kubernetes::KubectlCmd.delete("--ignore-not-found", "ns", "knative-build")
]
end
def delete_knative_and_istio_crds
api_resources.map do |crd|
- "kubectl delete --ignore-not-found crd #{crd}"
+ Gitlab::Kubernetes::KubectlCmd.delete("--ignore-not-found", "crd", "#{crd}")
end
end
@@ -119,13 +119,13 @@ module Clusters
def install_knative_metrics
return [] unless cluster.application_prometheus_available?
- ["kubectl apply -f #{METRICS_CONFIG}"]
+ [Gitlab::Kubernetes::KubectlCmd.apply_file(METRICS_CONFIG)]
end
def delete_knative_istio_metrics
return [] unless cluster.application_prometheus_available?
- ["kubectl delete --ignore-not-found -f #{METRICS_CONFIG}"]
+ [Gitlab::Kubernetes::KubectlCmd.delete("--ignore-not-found", "-f", METRICS_CONFIG)]
end
def verify_cluster?
diff --git a/app/models/clusters/applications/prometheus.rb b/app/models/clusters/applications/prometheus.rb
index 08e52f32bb3..f31a6b8b50e 100644
--- a/app/models/clusters/applications/prometheus.rb
+++ b/app/models/clusters/applications/prometheus.rb
@@ -106,13 +106,13 @@ module Clusters
def install_knative_metrics
return [] unless cluster.application_knative_available?
- ["kubectl apply -f #{Clusters::Applications::Knative::METRICS_CONFIG}"]
+ [Gitlab::Kubernetes::KubectlCmd.apply_file(Clusters::Applications::Knative::METRICS_CONFIG)]
end
def delete_knative_istio_metrics
return [] unless cluster.application_knative_available?
- ["kubectl delete -f #{Clusters::Applications::Knative::METRICS_CONFIG}"]
+ [Gitlab::Kubernetes::KubectlCmd.delete("-f", Clusters::Applications::Knative::METRICS_CONFIG)]
end
end
end
diff --git a/app/models/commit_status.rb b/app/models/commit_status.rb
index a88cac6b8e6..4be4d95b4a1 100644
--- a/app/models/commit_status.rb
+++ b/app/models/commit_status.rb
@@ -40,8 +40,11 @@ class CommitStatus < ApplicationRecord
scope :ordered, -> { order(:name) }
scope :latest_ordered, -> { latest.ordered.includes(project: :namespace) }
scope :retried_ordered, -> { retried.ordered.includes(project: :namespace) }
+ scope :before_stage, -> (index) { where('stage_idx < ?', index) }
+ scope :for_stage, -> (index) { where(stage_idx: index) }
scope :after_stage, -> (index) { where('stage_idx > ?', index) }
scope :processables, -> { where(type: %w[Ci::Build Ci::Bridge]) }
+ scope :for_ids, -> (ids) { where(id: ids) }
scope :with_needs, -> (names = nil) do
needs = Ci::BuildNeed.scoped_build.select(1)
@@ -49,8 +52,10 @@ class CommitStatus < ApplicationRecord
where('EXISTS (?)', needs).preload(:needs)
end
- scope :without_needs, -> do
- where('NOT EXISTS (?)', Ci::BuildNeed.scoped_build.select(1))
+ scope :without_needs, -> (names = nil) do
+ needs = Ci::BuildNeed.scoped_build.select(1)
+ needs = needs.where(name: names) if names
+ where('NOT EXISTS (?)', needs)
end
# We use `CommitStatusEnums.failure_reasons` here so that EE can more easily
@@ -149,6 +154,18 @@ class CommitStatus < ApplicationRecord
end
end
+ def self.names
+ select(:name)
+ end
+
+ def self.status_for_prior_stages(index)
+ before_stage(index).latest.status || 'success'
+ end
+
+ def self.status_for_names(names)
+ where(name: names).latest.status || 'success'
+ end
+
def locking_enabled?
will_save_change_to_status?
end
diff --git a/app/models/concerns/has_status.rb b/app/models/concerns/has_status.rb
index 27a5c3d5286..71ebb586c13 100644
--- a/app/models/concerns/has_status.rb
+++ b/app/models/concerns/has_status.rb
@@ -106,10 +106,15 @@ module HasStatus
scope :running_or_pending, -> { with_status(:running, :pending) }
scope :finished, -> { with_status(:success, :failed, :canceled) }
scope :failed_or_canceled, -> { with_status(:failed, :canceled) }
+ scope :incomplete, -> { without_statuses(completed_statuses) }
scope :cancelable, -> do
where(status: [:running, :preparing, :pending, :created, :scheduled])
end
+
+ scope :without_statuses, -> (names) do
+ with_status(all_state_names - names.to_a)
+ end
end
def started?
diff --git a/app/models/project.rb b/app/models/project.rb
index a6e43efa1f3..0c57ed3e43a 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -1230,6 +1230,14 @@ class Project < ApplicationRecord
end
end
+ def has_active_hooks?(hooks_scope = :push_hooks)
+ hooks.hooks_for(hooks_scope).any? || SystemHook.hooks_for(hooks_scope).any?
+ end
+
+ def has_active_services?(hooks_scope = :push_hooks)
+ services.public_send(hooks_scope).any? # rubocop:disable GitlabSecurity/PublicSend
+ end
+
def valid_repo?
repository.exists?
rescue
diff --git a/app/models/project_services/slash_commands_service.rb b/app/models/project_services/slash_commands_service.rb
index 5f5cff97808..cb16ad75d14 100644
--- a/app/models/project_services/slash_commands_service.rb
+++ b/app/models/project_services/slash_commands_service.rb
@@ -35,6 +35,8 @@ class SlashCommandsService < Service
chat_user = find_chat_user(params)
if chat_user&.user
+ return Gitlab::SlashCommands::Presenters::Access.new.access_denied unless chat_user.user.can?(:use_slash_commands)
+
Gitlab::SlashCommands::Command.new(project, chat_user, params).execute
else
url = authorize_chat_name_url(params)
diff --git a/app/policies/global_policy.rb b/app/policies/global_policy.rb
index 134de1c9ace..311aab0dcd4 100644
--- a/app/policies/global_policy.rb
+++ b/app/policies/global_policy.rb
@@ -33,6 +33,7 @@ class GlobalPolicy < BasePolicy
enable :access_git
enable :receive_notifications
enable :use_quick_actions
+ enable :use_slash_commands
end
rule { blocked | internal }.policy do
@@ -40,6 +41,7 @@ class GlobalPolicy < BasePolicy
prevent :access_api
prevent :access_git
prevent :receive_notifications
+ prevent :use_slash_commands
end
rule { required_terms_not_accepted }.policy do
@@ -57,6 +59,7 @@ class GlobalPolicy < BasePolicy
rule { access_locked }.policy do
prevent :log_in
+ prevent :use_slash_commands
end
rule { ~(anonymous & restricted_public_level) }.policy do
diff --git a/app/services/ci/process_pipeline_service.rb b/app/services/ci/process_pipeline_service.rb
index 99d4ff9ecd1..f4bd457ebc6 100644
--- a/app/services/ci/process_pipeline_service.rb
+++ b/app/services/ci/process_pipeline_service.rb
@@ -42,14 +42,19 @@ module Ci
return false unless trigger_build_ids.present?
return false unless Feature.enabled?(:ci_dag_support, project)
- # rubocop: disable CodeReuse/ActiveRecord
- trigger_build_names = pipeline.statuses
- .where(id: trigger_build_ids)
- .select(:name)
- # rubocop: enable CodeReuse/ActiveRecord
+ # we find processables that are dependent:
+ # 1. because of current dependency,
+ trigger_build_names = pipeline.processables.latest
+ .for_ids(trigger_build_ids).names
+ # 2. does not have builds that not yet complete
+ incomplete_build_names = pipeline.processables.latest
+ .incomplete.names
+
+ # Each found processable is guaranteed here to have completed status
created_processables
.with_needs(trigger_build_names)
+ .without_needs(incomplete_build_names)
.find_each
.map(&method(:process_build_with_needs))
.any?
@@ -70,17 +75,13 @@ module Ci
end
end
- # rubocop: disable CodeReuse/ActiveRecord
def status_for_prior_stages(index)
- pipeline.builds.where('stage_idx < ?', index).latest.status || 'success'
+ pipeline.processables.status_for_prior_stages(index)
end
- # rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
def status_for_build_needs(needs)
- pipeline.builds.where(name: needs).latest.status || 'success'
+ pipeline.processables.status_for_names(needs)
end
- # rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def stage_indexes_of_created_processables_without_needs
@@ -89,12 +90,10 @@ module Ci
end
# rubocop: enable CodeReuse/ActiveRecord
- # rubocop: disable CodeReuse/ActiveRecord
def created_processables_in_stage_without_needs(index)
created_processables_without_needs
- .where(stage_idx: index)
+ .for_stage(index)
end
- # rubocop: enable CodeReuse/ActiveRecord
def created_processables_without_needs
if Feature.enabled?(:ci_dag_support, project)
diff --git a/app/services/git/base_hooks_service.rb b/app/services/git/base_hooks_service.rb
index d30df34e54b..1db18fcf401 100644
--- a/app/services/git/base_hooks_service.rb
+++ b/app/services/git/base_hooks_service.rb
@@ -19,7 +19,7 @@ module Git
update_remote_mirrors
- push_data
+ success
end
private
@@ -33,7 +33,7 @@ module Git
end
def limited_commits
- commits.last(PROCESS_COMMIT_LIMIT)
+ @limited_commits ||= commits.last(PROCESS_COMMIT_LIMIT)
end
def commits_count
@@ -48,21 +48,25 @@ module Git
[]
end
+ # Push events in the activity feed only show information for the
+ # last commit.
def create_events
- EventCreateService.new.push(project, current_user, push_data)
+ EventCreateService.new.push(project, current_user, event_push_data)
end
def create_pipelines
return unless params.fetch(:create_pipelines, true)
Ci::CreatePipelineService
- .new(project, current_user, push_data)
+ .new(project, current_user, base_params)
.execute(:push, pipeline_options)
end
def execute_project_hooks
- project.execute_hooks(push_data, hook_name)
- project.execute_services(push_data, hook_name)
+ # Creating push_data invokes one CommitDelta RPC per commit. Only
+ # build this data if we actually need it.
+ project.execute_hooks(push_data, hook_name) if project.has_active_hooks?(hook_name)
+ project.execute_services(push_data, hook_name) if project.has_active_services?(hook_name)
end
def enqueue_invalidate_cache
@@ -73,18 +77,35 @@ module Git
)
end
- def push_data
- @push_data ||= Gitlab::DataBuilder::Push.build(
- project: project,
- user: current_user,
+ def base_params
+ {
oldrev: params[:oldrev],
newrev: params[:newrev],
ref: params[:ref],
- commits: limited_commits,
+ push_options: params[:push_options] || {}
+ }
+ end
+
+ def push_data_params(commits:, with_changed_files: true)
+ base_params.merge(
+ project: project,
+ user: current_user,
+ commits: commits,
message: event_message,
commits_count: commits_count,
- push_options: params[:push_options] || {}
+ with_changed_files: with_changed_files
)
+ end
+
+ def event_push_data
+ # We only need the last commit for the event push, and we don't
+ # need the full deltas either.
+ @event_push_data ||= Gitlab::DataBuilder::Push.build(
+ push_data_params(commits: commits.last, with_changed_files: false))
+ end
+
+ def push_data
+ @push_data ||= Gitlab::DataBuilder::Push.build(push_data_params(commits: limited_commits))
# Dependent code may modify the push data, so return a duplicate each time
@push_data.dup
diff --git a/app/views/admin/application_settings/_snowplow.html.haml b/app/views/admin/application_settings/_snowplow.html.haml
new file mode 100644
index 00000000000..b60b5d55a1b
--- /dev/null
+++ b/app/views/admin/application_settings/_snowplow.html.haml
@@ -0,0 +1,30 @@
+- expanded = true if !@application_setting.valid? && @application_setting.errors.any? { |k| k.to_s.start_with?('snowplow_') }
+%section.settings.as-snowplow.no-animate#js-snowplow-settings{ class: ('expanded' if expanded) }
+ .settings-header
+ %h4
+ = _('Snowplow')
+ %button.btn.btn-default.js-settings-toggle{ type: 'button' }
+ = expanded ? _('Collapse') : _('Expand')
+ %p
+ = _('Configure the %{link} integration.').html_safe % { link: link_to('Snowplow', 'https://snowplowanalytics.com/', target: '_blank') }
+ .settings-content
+
+ = form_for @application_setting, url: integrations_admin_application_settings_path, html: { class: 'fieldset-form' } do |f|
+ = form_errors(@application_setting)
+
+ %fieldset
+ .form-group
+ .form-check
+ = f.check_box :snowplow_enabled, class: 'form-check-input'
+ = f.label :snowplow_enabled, _('Enable snowplow tracking'), class: 'form-check-label'
+ .form-group
+ = f.label :snowplow_collector_hostname, _('Collector hostname'), class: 'label-light'
+ = f.text_field :snowplow_collector_hostname, class: 'form-control', placeholder: 'snowplow.example.com'
+ .form-group
+ = f.label :snowplow_site_id, _('Site ID'), class: 'label-light'
+ = f.text_field :snowplow_site_id, class: 'form-control'
+ .form-group
+ = f.label :snowplow_cookie_domain, _('Cookie domain'), class: 'label-light'
+ = f.text_field :snowplow_cookie_domain, class: 'form-control'
+
+ = f.submit _('Save changes'), class: 'btn btn-success'
diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml
index 2f10f08c839..0b1d3d1ddb3 100644
--- a/app/views/devise/sessions/_new_base.html.haml
+++ b/app/views/devise/sessions/_new_base.html.haml
@@ -1,20 +1,23 @@
= form_for(resource, as: resource_name, url: session_path(resource_name), html: { class: 'new_user gl-show-field-errors', 'aria-live' => 'assertive'}) do |f|
.form-group
- = f.label "Username or email", for: "user_login", class: 'label-bold'
- = f.text_field :login, class: "form-control top", autofocus: "autofocus", autocapitalize: "off", autocorrect: "off", required: true, title: "This field is required.", data: { qa_selector: 'login_field' }
+ = f.label _('Username or email'), for: 'user_login', class: 'label-bold'
+ = f.text_field :login, class: 'form-control top', autofocus: 'autofocus', autocapitalize: 'off', autocorrect: 'off', required: true, title: _('This field is required.'), data: { qa_selector: 'login_field' }
.form-group
= f.label :password, class: 'label-bold'
- = f.password_field :password, class: "form-control bottom", required: true, title: "This field is required.", data: { qa_selector: 'password_field' }
+ = f.password_field :password, class: 'form-control bottom', required: true, title: _('This field is required.'), data: { qa_selector: 'password_field' }
- if devise_mapping.rememberable?
.remember-me
- %label{ for: "user_remember_me" }
+ %label{ for: 'user_remember_me' }
= f.check_box :remember_me, class: 'remember-me-checkbox'
%span Remember me
- .float-right.forgot-password
- = link_to "Forgot your password?", new_password_path(:user)
+ .float-right
+ - if unconfirmed_email?
+ = link_to _('Resend confirmation email'), new_user_confirmation_path
+ - else
+ = link_to _('Forgot your password?'), new_password_path(:user)
%div
- if captcha_enabled?
= recaptcha_tags
.submit-container.move-submit-down
- = f.submit "Sign in", class: "btn btn-success", data: { qa_selector: 'sign_in_button' }
+ = f.submit _('Sign in'), class: 'btn btn-success', data: { qa_selector: 'sign_in_button' }
diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml
index 074edf645ba..2cd77af6877 100644
--- a/app/views/devise/shared/_signup_box.html.haml
+++ b/app/views/devise/shared/_signup_box.html.haml
@@ -5,6 +5,8 @@
= form_for(resource, as: "new_#{resource_name}", url: registration_path(resource_name), html: { class: "new_new_user gl-show-field-errors", "aria-live" => "assertive" }) do |f|
.devise-errors
= render "devise/shared/error_messages", resource: resource
+ - if Feature.enabled?(:invisible_captcha)
+ = invisible_captcha
.name.form-group
= f.label :name, _('Full name'), class: 'label-bold'
= f.text_field :name, class: "form-control top js-block-emoji js-validate-length", :data => { :max_length => max_name_length, :max_length_message => s_("SignUp|Name is too long (maximum is %{max_length} characters).") % { max_length: max_name_length }, :qa_selector => 'new_user_name_field' }, required: true, title: _("This field is required.")
diff --git a/app/views/layouts/_snowplow.html.haml b/app/views/layouts/_snowplow.html.haml
new file mode 100644
index 00000000000..5f5c5e984c5
--- /dev/null
+++ b/app/views/layouts/_snowplow.html.haml
@@ -0,0 +1,29 @@
+- return unless Gitlab::CurrentSettings.snowplow_enabled?
+
+= javascript_tag nonce: true do
+ :plain
+ ;(function(p,l,o,w,i,n,g){if(!p[i]){p.GlobalSnowplowNamespace=p.GlobalSnowplowNamespace||[];
+ p.GlobalSnowplowNamespace.push(i);p[i]=function(){(p[i].q=p[i].q||[]).push(arguments)
+ };p[i].q=p[i].q||[];n=l.createElement(o);g=l.getElementsByTagName(o)[0];n.async=1;
+ n.src=w;g.parentNode.insertBefore(n,g)}}(window,document,"script","#{asset_url('snowplow/sp.js')}","snowplow"));
+
+ window.snowplow('newTracker', '#{Gitlab::SnowplowTracker::NAMESPACE}', '#{Gitlab::CurrentSettings.snowplow_collector_hostname}', {
+ appId: '#{Gitlab::CurrentSettings.snowplow_site_id}',
+ cookieDomain: '#{Gitlab::CurrentSettings.snowplow_cookie_domain}',
+ userFingerprint: false,
+ respectDoNotTrack: true,
+ forceSecureTracker: true,
+ post: true,
+ contexts: { webPage: true },
+ stateStorageStrategy: "localStorage"
+ });
+
+ window.snowplow('enableActivityTracking', 30, 30);
+ window.snowplow('trackPageView');
+
+- return unless Feature.enabled?(:additional_snowplow_tracking, @group)
+
+= javascript_tag nonce: true do
+ :plain
+ window.snowplow('enableFormTracking');
+ window.snowplow('enableLinkClickTracking');
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index d16e2dddbe0..d99063e344f 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -45,20 +45,20 @@
.form-group
= f.label :layout, class: 'label-bold' do
= s_('Preferences|Layout width')
- = f.select :layout, layout_choices, {}, class: 'form-control'
+ = f.select :layout, layout_choices, {}, class: 'select2'
.form-text.text-muted
= s_('Preferences|Choose between fixed (max. 1280px) and fluid (100%%) application layout.')
.form-group
= f.label :dashboard, class: 'label-bold' do
= s_('Preferences|Default dashboard')
- = f.select :dashboard, dashboard_choices, {}, class: 'form-control'
+ = f.select :dashboard, dashboard_choices, {}, class: 'select2'
= render_if_exists 'profiles/preferences/group_overview_selector', f: f # EE-specific
.form-group
= f.label :project_view, class: 'label-bold' do
= s_('Preferences|Project overview content')
- = f.select :project_view, project_view_choices, {}, class: 'form-control'
+ = f.select :project_view, project_view_choices, {}, class: 'select2'
.form-text.text-muted
= s_('Preferences|Choose what content you want to see on a project’s overview page.')
@@ -82,7 +82,7 @@
.form-group
= f.label :first_day_of_week, class: 'label-bold' do
= _('First day of the week')
- = f.select :first_day_of_week, first_day_of_week_choices_with_default, {}, class: 'form-control'
+ = f.select :first_day_of_week, first_day_of_week_choices_with_default, {}, class: 'select2'
- if Feature.enabled?(:user_time_settings)
.col-sm-12
%hr
diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml
index efabb7f7b19..149b0d6cddd 100644
--- a/app/views/projects/project_members/_new_project_member.html.haml
+++ b/app/views/projects/project_members/_new_project_member.html.haml
@@ -2,7 +2,7 @@
.col-sm-12
= form_for @project_member, as: :project_member, url: project_project_members_path(@project), html: { class: 'users-project-form' } do |f|
.form-group
- = label_tag :user_ids, _("Select members to invite"), class: "label-bold"
+ = label_tag :user_ids, _("GitLab member or Email address"), class: "label-bold"
= users_select_tag(:user_ids, multiple: true, class: "input-clamp qa-member-select-input", scope: :all, email_user: true, placeholder: "Search for members to update or invite")
.form-group
= label_tag :access_level, _("Choose a role permission"), class: "label-bold"
diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml
index 66a614b0197..858731b2dda 100644
--- a/app/views/projects/wikis/_form.html.haml
+++ b/app/views/projects/wikis/_form.html.haml
@@ -14,7 +14,7 @@
.col-sm-12
= f.text_field :title, class: 'form-control qa-wiki-title-textbox', value: @page.title
- if @page.persisted?
- %span.edit-wiki-page-slug-tip
+ %span.d-inline-block.mw-100.prepend-top-5
= icon('lightbulb-o')
= s_("WikiEditPageTip|Tip: You can move this page by adding the path to the beginning of the title.")
= link_to icon('question-circle'), help_page_path('user/project/wiki/index', anchor: 'moving-a-wiki-page'), target: '_blank'
diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml
index dc12e368b35..2c675c0de9c 100644
--- a/app/views/projects/wikis/_new.html.haml
+++ b/app/views/projects/wikis/_new.html.haml
@@ -11,7 +11,7 @@
= label_tag :new_wiki_path do
%span= s_("WikiPage|Page slug")
= text_field_tag :new_wiki_path, nil, placeholder: s_("WikiNewPagePlaceholder|how-to-setup"), class: 'form-control', required: true, :'data-wikis-path' => project_wikis_path(@project), autofocus: true
- %span.new-wiki-page-slug-tip
+ %span.d-inline-block.mw-100.prepend-top-5
= icon('lightbulb-o')
= s_("WikiNewPageTip|Tip: You can specify the full path for the new file. We will automatically create any missing directories.")
.form-actions
diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml
index 1277ea6c743..e8b59a3b8c4 100644
--- a/app/views/projects/wikis/edit.html.haml
+++ b/app/views/projects/wikis/edit.html.haml
@@ -5,7 +5,7 @@
= wiki_page_errors(@error)
-.wiki-page-header.has-sidebar-toggle
+.wiki-page-header.top-area.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
diff --git a/app/views/projects/wikis/git_access.html.haml b/app/views/projects/wikis/git_access.html.haml
index 8c2cbd495a0..009133be117 100644
--- a/app/views/projects/wikis/git_access.html.haml
+++ b/app/views/projects/wikis/git_access.html.haml
@@ -1,7 +1,7 @@
- @content_class = "limit-container-width" unless fluid_layout
- page_title s_("WikiClone|Git Access"), _("Wiki")
-.wiki-page-header.has-sidebar-toggle
+.wiki-page-header.top-area.has-sidebar-toggle
%button.btn.btn-default.d-block.d-sm-block.d-md-none.float-right.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml
index c5fbeeafa54..f8468ef9a78 100644
--- a/app/views/projects/wikis/history.html.haml
+++ b/app/views/projects/wikis/history.html.haml
@@ -1,6 +1,6 @@
- page_title _("History"), @page.human_title, _("Wiki")
-.wiki-page-header.has-sidebar-toggle
+.wiki-page-header.top-area.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml
index 2191e5ab287..f7999c3f1bd 100644
--- a/app/views/projects/wikis/pages.html.haml
+++ b/app/views/projects/wikis/pages.html.haml
@@ -5,7 +5,7 @@
- sort_title = wiki_sort_title(params[:sort])
%div{ class: container_class }
- .wiki-page-header
+ .wiki-page-header.top-area
.nav-text.flex-fill
%h2.wiki-page-title
diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml
index 95cd3356ec8..1d649886331 100644
--- a/app/views/projects/wikis/show.html.haml
+++ b/app/views/projects/wikis/show.html.haml
@@ -4,7 +4,7 @@
- page_title @page.human_title, _("Wiki")
- add_to_breadcrumbs _("Wiki"), project_wiki_path(@project, :home)
-.wiki-page-header.has-sidebar-toggle
+.wiki-page-header.top-area.has-sidebar-toggle
%button.btn.btn-default.sidebar-toggle.js-sidebar-wiki-toggle{ role: "button", type: "button" }
= icon('angle-double-left')
diff --git a/changelogs/unreleased/10-adjust-copy-for-adding-additional-members.yml b/changelogs/unreleased/10-adjust-copy-for-adding-additional-members.yml
new file mode 100644
index 00000000000..f249eff572c
--- /dev/null
+++ b/changelogs/unreleased/10-adjust-copy-for-adding-additional-members.yml
@@ -0,0 +1,5 @@
+---
+title: Adjust copy for adding additional members
+merge_request: 31726
+author:
+type: changed
diff --git a/changelogs/unreleased/12502-add-view-stats-to-cycle-analytics.yml b/changelogs/unreleased/12502-add-view-stats-to-cycle-analytics.yml
new file mode 100644
index 00000000000..ccfd929b6ba
--- /dev/null
+++ b/changelogs/unreleased/12502-add-view-stats-to-cycle-analytics.yml
@@ -0,0 +1,5 @@
+---
+title: Track page views for cycle analytics show page
+merge_request: 31717
+author:
+type: added
diff --git a/changelogs/unreleased/59829-fix-style-lint-wiki.yml b/changelogs/unreleased/59829-fix-style-lint-wiki.yml
new file mode 100644
index 00000000000..48242a77c6b
--- /dev/null
+++ b/changelogs/unreleased/59829-fix-style-lint-wiki.yml
@@ -0,0 +1,5 @@
+---
+title: Fix the style-lint errors and warnings for `app/assets/stylesheets/pages/wiki.scss`
+merge_request: 31656
+author:
+type: other
diff --git a/changelogs/unreleased/62286-Consistent-selection-elements-in-user-settings-preferences.yml b/changelogs/unreleased/62286-Consistent-selection-elements-in-user-settings-preferences.yml
new file mode 100644
index 00000000000..10f2b7eaed5
--- /dev/null
+++ b/changelogs/unreleased/62286-Consistent-selection-elements-in-user-settings-preferences.yml
@@ -0,0 +1,5 @@
+---
+title: Harmonize selections in user settings
+merge_request: 31110
+author: Marc Schwede
+type: other
diff --git a/changelogs/unreleased/64950-move-download-csv-button-functionality-in-metrics-dashboard-cards-i.yml b/changelogs/unreleased/64950-move-download-csv-button-functionality-in-metrics-dashboard-cards-i.yml
new file mode 100644
index 00000000000..21771c76873
--- /dev/null
+++ b/changelogs/unreleased/64950-move-download-csv-button-functionality-in-metrics-dashboard-cards-i.yml
@@ -0,0 +1,5 @@
+---
+title: 'feat: adds a download to csv functionality to the dropdown in prometheus metrics'
+merge_request: 31679
+author:
+type: changed
diff --git a/changelogs/unreleased/65483-add-a-resend-confirmation-link.yml b/changelogs/unreleased/65483-add-a-resend-confirmation-link.yml
new file mode 100644
index 00000000000..a5f62dbcd56
--- /dev/null
+++ b/changelogs/unreleased/65483-add-a-resend-confirmation-link.yml
@@ -0,0 +1,5 @@
+---
+title: Allow users to resend a confirmation link when the grace period has expired
+merge_request: 31476
+author:
+type: changed
diff --git a/changelogs/unreleased/georgekoltsov-48854-fix-empty-flash-message.yml b/changelogs/unreleased/georgekoltsov-48854-fix-empty-flash-message.yml
new file mode 100644
index 00000000000..e28dbd6f0c4
--- /dev/null
+++ b/changelogs/unreleased/georgekoltsov-48854-fix-empty-flash-message.yml
@@ -0,0 +1,6 @@
+---
+title: Fix empty error flash message on profile:account page when updating username
+ with username that has already been taken
+merge_request: 31809
+author:
+type: fixed
diff --git a/changelogs/unreleased/post-migrate-private-profile.yml b/changelogs/unreleased/post-migrate-private-profile.yml
new file mode 100644
index 00000000000..53a55661aa0
--- /dev/null
+++ b/changelogs/unreleased/post-migrate-private-profile.yml
@@ -0,0 +1,5 @@
+---
+title: Migrate remaining users with null private_profile
+merge_request: 31708
+author:
+type: other
diff --git a/changelogs/unreleased/security-2873-blocked-user-slash-command-bypass-master.yml b/changelogs/unreleased/security-2873-blocked-user-slash-command-bypass-master.yml
new file mode 100644
index 00000000000..cd31fe0f35c
--- /dev/null
+++ b/changelogs/unreleased/security-2873-blocked-user-slash-command-bypass-master.yml
@@ -0,0 +1,5 @@
+---
+title: Restrict slash commands to users who can log in
+merge_request:
+author:
+type: security
diff --git a/changelogs/unreleased/sh-optimize-commit-deltas-post-receive.yml b/changelogs/unreleased/sh-optimize-commit-deltas-post-receive.yml
new file mode 100644
index 00000000000..cd63b9bf425
--- /dev/null
+++ b/changelogs/unreleased/sh-optimize-commit-deltas-post-receive.yml
@@ -0,0 +1,5 @@
+---
+title: Reduce Gitaly calls in PostReceive
+merge_request: 31741
+author:
+type: performance
diff --git a/config/initializers/elastic_client_setup.rb b/config/initializers/elastic_client_setup.rb
index 2ecb7956007..a1abb29838b 100644
--- a/config/initializers/elastic_client_setup.rb
+++ b/config/initializers/elastic_client_setup.rb
@@ -5,46 +5,42 @@
require 'gitlab/current_settings'
Gitlab.ee do
+ require 'elasticsearch/model'
+
+ ### Modified from elasticsearch-model/lib/elasticsearch/model.rb
+
+ [
+ Elasticsearch::Model::Client::ClassMethods,
+ Elasticsearch::Model::Naming::ClassMethods,
+ Elasticsearch::Model::Indexing::ClassMethods,
+ Elasticsearch::Model::Searching::ClassMethods
+ ].each do |mod|
+ Elasticsearch::Model::Proxy::ClassMethodsProxy.include mod
+ end
+
+ [
+ Elasticsearch::Model::Client::InstanceMethods,
+ Elasticsearch::Model::Naming::InstanceMethods,
+ Elasticsearch::Model::Indexing::InstanceMethods,
+ Elasticsearch::Model::Serializing::InstanceMethods
+ ].each do |mod|
+ Elasticsearch::Model::Proxy::InstanceMethodsProxy.include mod
+ end
+
+ Elasticsearch::Model::Proxy::InstanceMethodsProxy.class_eval <<-CODE, __FILE__, __LINE__ + 1
+ def as_indexed_json(options={})
+ target.respond_to?(:as_indexed_json) ? target.__send__(:as_indexed_json, options) : super
+ end
+ CODE
+
+ ### Monkey patches
+
Elasticsearch::Model::Response::Records.prepend GemExtensions::Elasticsearch::Model::Response::Records
Elasticsearch::Model::Adapter::Multiple::Records.prepend GemExtensions::Elasticsearch::Model::Adapter::Multiple::Records
Elasticsearch::Model::Indexing::InstanceMethods.prepend GemExtensions::Elasticsearch::Model::Indexing::InstanceMethods
-
- module Elasticsearch
- module Model
- module Client
- # This mutex is only used to synchronize *creation* of a new client, so
- # all including classes can share the same client instance
- CLIENT_MUTEX = Mutex.new
-
- cattr_accessor :cached_client
- cattr_accessor :cached_config
-
- module ClassMethods
- # Override the default ::Elasticsearch::Model::Client implementation to
- # return a client configured from application settings. All including
- # classes will use the same instance, which is refreshed automatically
- # if the settings change.
- #
- # _client is present to match the arity of the overridden method, where
- # it is also not used.
- #
- # @return [Elasticsearch::Transport::Client]
- def client(_client = nil)
- store = ::Elasticsearch::Model::Client
-
- store::CLIENT_MUTEX.synchronize do
- config = Gitlab::CurrentSettings.elasticsearch_config
-
- if store.cached_client.nil? || config != store.cached_config
- store.cached_client = ::Gitlab::Elastic::Client.build(config)
- store.cached_config = config
- end
- end
-
- store.cached_client
- end
- end
- end
- end
- end
+ Elasticsearch::Model::Adapter::ActiveRecord::Importing.prepend GemExtensions::Elasticsearch::Model::Adapter::ActiveRecord::Importing
+ Elasticsearch::Model::Client::InstanceMethods.prepend GemExtensions::Elasticsearch::Model::Client
+ Elasticsearch::Model::Client::ClassMethods.prepend GemExtensions::Elasticsearch::Model::Client
+ Elasticsearch::Model::ClassMethods.prepend GemExtensions::Elasticsearch::Model::Client
+ Elasticsearch::Model.singleton_class.prepend GemExtensions::Elasticsearch::Model::Client
end
diff --git a/config/initializers/invisible_captcha.rb b/config/initializers/invisible_captcha.rb
new file mode 100644
index 00000000000..5177c730596
--- /dev/null
+++ b/config/initializers/invisible_captcha.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+InvisibleCaptcha.setup do |config|
+ config.honeypots = %w(firstname lastname)
+ config.timestamp_enabled = true
+ config.timestamp_threshold = 4
+end
diff --git a/config/locales/invisible_captcha.en.yml b/config/locales/invisible_captcha.en.yml
new file mode 100644
index 00000000000..5978549c0c3
--- /dev/null
+++ b/config/locales/invisible_captcha.en.yml
@@ -0,0 +1,4 @@
+en:
+ invisible_captcha:
+ sentence_for_humans: If you are human, please ignore this field.
+ timestamp_error_message: That was a bit too quick! Please resubmit.
diff --git a/config/routes/project.rb b/config/routes/project.rb
index b9258a35f0c..a207ee44d47 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -505,7 +505,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do
get :discussions, format: :json
Gitlab.ee do
- get 'designs(/*vueroute)', to: 'issues#show', format: false
+ get 'designs(/*vueroute)', to: 'issues#show', as: :designs, format: false
end
end
diff --git a/db/post_migrate/20190812070645_migrate_private_profile_nulls.rb b/db/post_migrate/20190812070645_migrate_private_profile_nulls.rb
new file mode 100644
index 00000000000..063c1e16c27
--- /dev/null
+++ b/db/post_migrate/20190812070645_migrate_private_profile_nulls.rb
@@ -0,0 +1,31 @@
+# frozen_string_literal: true
+
+class MigratePrivateProfileNulls < ActiveRecord::Migration[5.2]
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+ DELAY = 5.minutes.to_i
+ BATCH_SIZE = 1_000
+
+ disable_ddl_transaction!
+
+ class User < ActiveRecord::Base
+ self.table_name = 'users'
+
+ include ::EachBatch
+ end
+
+ def up
+ # Migration will take about 7 hours
+ User.where(private_profile: nil).each_batch(of: BATCH_SIZE) do |batch, index|
+ range = batch.pluck(Arel.sql("MIN(id)"), Arel.sql("MAX(id)")).first
+ delay = index * DELAY
+
+ BackgroundMigrationWorker.perform_in(delay.seconds, 'MigrateNullPrivateProfileToFalse', [*range])
+ end
+ end
+
+ def down
+ # noop
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 003f12b5171..7c4a91da750 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 2019_08_06_071559) do
+ActiveRecord::Schema.define(version: 2019_08_12_070645) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_trgm"
diff --git a/doc/README.md b/doc/README.md
index c60e4eb177d..8ce5d2e240a 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -354,6 +354,7 @@ The following documentation relates to the DevOps **Secure** stage:
| Secure Topics | Description |
|:------------------------------------------------------------------------------------------------------|:-----------------------------------------------------------------------|
| [Container Scanning](user/application_security/container_scanning/index.md) **(ULTIMATE)** | Use Clair to scan docker images for known vulnerabilities. |
+| [Dependency List](user/application_security/dependency_list/index.md) **(ULTIMATE)** | View your project's dependencies and their known vulnerabilities. |
| [Dependency Scanning](user/application_security/dependency_scanning/index.md) **(ULTIMATE)** | Analyze your dependencies for known vulnerabilities. |
| [Dynamic Application Security Testing (DAST)](user/application_security/dast/index.md) **(ULTIMATE)** | Analyze running web applications for known vulnerabilities. |
| [Group Security Dashboard](user/application_security/security_dashboard/index.md) **(ULTIMATE)** | View vulnerabilities in all the projects in a group and its subgroups. |
diff --git a/doc/administration/container_registry.md b/doc/administration/container_registry.md
index e418938451a..d0adeb89543 100644
--- a/doc/administration/container_registry.md
+++ b/doc/administration/container_registry.md
@@ -669,6 +669,39 @@ To get around this, you can [change the group path](../user/group/index.md#chang
branch name. Another option is to create a [push rule](../push_rules/push_rules.html) to prevent
this at the instance level.
+### Image push errors
+
+When getting errors or "retrying" loops in an attempt to push an image but `docker login` works fine,
+there is likely an issue with the headers forwarded to the registry by NGINX. The default recommended
+NGINX configurations should handle this, but it might occur in custom setups where the SSL is
+offloaded to a third party reverse proxy.
+
+This problem was discussed in a [docker project issue][docker-image-push-issue] and a simple solution
+would be to enable relative urls in the registry.
+
+**For Omnibus installations**
+
+1. Edit `/etc/gitlab/gitlab.rb`:
+
+ ```ruby
+ registry['env'] = {
+ "REGISTRY_HTTP_RELATIVEURLS" => true
+ }
+ ```
+
+1. Save the file and [reconfigure GitLab][] for the changes to take effect.
+
+**For installations from source**
+
+1. Edit the YML configuration file you created when you [deployed the registry][registry-deploy]. Add the following snippet:
+
+ ```yaml
+ http:
+ relativeurls: true
+ ```
+
+1. Restart the registry for the changes to take affect.
+
[ce-18239]: https://gitlab.com/gitlab-org/gitlab-ce/issues/18239
[docker-insecure-self-signed]: https://docs.docker.com/registry/insecure/#use-self-signed-certificates
[reconfigure gitlab]: restart_gitlab.md#omnibus-gitlab-reconfigure
@@ -687,3 +720,4 @@ this at the instance level.
[new-domain]: #configure-container-registry-under-its-own-domain
[notifications-config]: https://docs.docker.com/registry/notifications/
[registry-notifications-config]: https://docs.docker.com/registry/configuration/#notifications
+[docker-image-push-issue]: https://github.com/docker/distribution/issues/970
diff --git a/doc/administration/monitoring/prometheus/gitlab_metrics.md b/doc/administration/monitoring/prometheus/gitlab_metrics.md
index 054fa547704..ec26c0b2e7e 100644
--- a/doc/administration/monitoring/prometheus/gitlab_metrics.md
+++ b/doc/administration/monitoring/prometheus/gitlab_metrics.md
@@ -120,7 +120,6 @@ When Puma is used instead of Unicorn, following metrics are available:
| puma_workers | Gauge | 12.0 | Total number of workers |
| puma_running_workers | Gauge | 12.0 | Number of booted workers |
| puma_stale_workers | Gauge | 12.0 | Number of old workers |
-| puma_phase | Gauge | 12.0 | Phase number (increased during phased restarts) |
| puma_running | Gauge | 12.0 | Number of running threads |
| puma_queued_connections | Gauge | 12.0 | Number of connections in that worker's "todo" set waiting for a worker thread |
| puma_active_connections | Gauge | 12.0 | Number of threads processing a request |
diff --git a/doc/api/dependencies.md b/doc/api/dependencies.md
index 2496b038c7f..015ffbe60f6 100644
--- a/doc/api/dependencies.md
+++ b/doc/api/dependencies.md
@@ -11,7 +11,7 @@ Every call to this endpoint requires authentication. To perform this call, user
## List project dependencies
Get a list of project dependencies. This API partially mirroring
-[Dependency List](../user/application_security/dependency_scanning/index.md#dependency-list) feature.
+[Dependency List](../user/application_security/dependency_list/index.md) feature.
This list can be generated only for [languages and package managers](../user/application_security/dependency_scanning/index.md#supported-languages-and-package-managers)
supported by Gemnasium.
diff --git a/doc/api/settings.md b/doc/api/settings.md
index 83125aff264..248d19461f6 100644
--- a/doc/api/settings.md
+++ b/doc/api/settings.md
@@ -321,4 +321,8 @@ are listed in the descriptions of the relevant settings.
| `user_show_add_ssh_key_message` | boolean | no | When set to `false` disable the "You won't be able to pull or push project code via SSH" warning shown to users with no uploaded SSH key. |
| `version_check_enabled` | boolean | no | Let GitLab inform you when an update is available. |
| `local_markdown_version` | integer | no | Increase this value when any cached markdown should be invalidated. |
+| `snowplow_enabled` | boolean | no | Enable snowplow tracking. |
+| `snowplow_collector_hostname` | string | required by: `snowplow_enabled` | The Snowplow collector hostname. (e.g. `snowplow.trx.gitlab.net`) |
+| `snowplow_site_id` | string | no | The Snowplow site name / application id. (e.g. `gitlab`) |
+| `snowplow_cookie_domain` | string | no | The Snowplow cookie domain. (e.g. `.gitlab.com`) |
| `geo_node_allowed_ips` | string | yes | **(PREMIUM)** Comma-separated list of IPs and CIDRs of allowed secondary nodes. For example, `1.1.1.1, 2.2.2.0/24`. |
diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md
index c3678fc948e..8474d4ef66e 100644
--- a/doc/ci/runners/README.md
+++ b/doc/ci/runners/README.md
@@ -319,21 +319,21 @@ How this feature will work:
1. You set the _maximum job timeout_ for a Runner to 24 hours
1. You set the _CI/CD Timeout_ for a project to **2 hours**
1. You start a job
-1. The job, if running longer, will be timeouted after **2 hours**
+1. The job, if running longer, will be timed out after **2 hours**
**Example 2 - Runner timeout not configured**
1. You remove the _maximum job timeout_ configuration from a Runner
1. You set the _CI/CD Timeout_ for a project to **2 hours**
1. You start a job
-1. The job, if running longer, will be timeouted after **2 hours**
+1. The job, if running longer, will be timed out after **2 hours**
**Example 3 - Runner timeout smaller than project timeout**
1. You set the _maximum job timeout_ for a Runner to **30 minutes**
1. You set the _CI/CD Timeout_ for a project to 2 hours
1. You start a job
-1. The job, if running longer, will be timeouted after **30 minutes**
+1. The job, if running longer, will be timed out after **30 minutes**
### Be careful with sensitive information
diff --git a/doc/customization/issue_and_merge_request_template.md b/doc/customization/issue_and_merge_request_template.md
index adaa120a37e..ebf711e105b 100644
--- a/doc/customization/issue_and_merge_request_template.md
+++ b/doc/customization/issue_and_merge_request_template.md
@@ -1,5 +1,5 @@
---
-redirect_to: '../user/project/description_templates.md#setting-a-default-template-for-issues-and-merge-requests--starter'
+redirect_to: '../user/project/description_templates.md#setting-a-default-template-for-merge-requests-and-issues--starter'
---
-This document was moved to [description_templates](../user/project/description_templates.md#setting-a-default-template-for-issues-and-merge-requests--starter).
+This document was moved to [description_templates](../user/project/description_templates.md#setting-a-default-template-for-merge-requests-and-issues--starter).
diff --git a/doc/development/automatic_ce_ee_merge.md b/doc/development/automatic_ce_ee_merge.md
index 423b35a9e3a..98b8a48abf4 100644
--- a/doc/development/automatic_ce_ee_merge.md
+++ b/doc/development/automatic_ce_ee_merge.md
@@ -171,6 +171,19 @@ Now, every time you create an MR for CE and EE:
job failed, you are required to submit the EE MR so that you can fix the conflicts in EE
before merging your changes into CE.
+## How we run the Automatic CE->EE merge at GitLab
+
+At GitLab, we use the [Merge Train](https://gitlab.com/gitlab-org/merge-train)
+project to keep our [gitlab-ee](https://gitlab.com/gitlab-org/gitlab-ee)
+repository updated with commits from
+[gitlab-ce](https://gitlab.com/gitlab-org/gitlab-ce).
+
+We have a mirror of the [Merge Train](https://gitlab.com/gitlab-org/merge-train)
+project [configured](https://ops.gitlab.net/gitlab-org/merge-train) to run an
+automatic CE->EE merge job every twenty minutes as a scheduled CI job. The
+[configured](https://ops.gitlab.net/gitlab-org/merge-train) Merge Train project
+is only accessible to authorized GitLab staff.
+
## FAQ
### How does automatic merging work?
diff --git a/doc/development/documentation/styleguide.md b/doc/development/documentation/styleguide.md
index 59c8bfe2964..680f2cd13c2 100644
--- a/doc/development/documentation/styleguide.md
+++ b/doc/development/documentation/styleguide.md
@@ -877,10 +877,10 @@ Other text includes deprecation notices and version-specific how-to information.
When a feature is available in EE-only tiers, add the corresponding tier according to the
feature availability:
+- For GitLab Core and GitLab.com Free: `**(CORE)**`.
- For GitLab Starter and GitLab.com Bronze: `**(STARTER)**`.
- For GitLab Premium and GitLab.com Silver: `**(PREMIUM)**`.
- For GitLab Ultimate and GitLab.com Gold: `**(ULTIMATE)**`.
-- For GitLab Core and GitLab.com Free: `**(CORE)**`.
To exclude GitLab.com tiers (when the feature is not available in GitLab.com), add the
keyword "only":
@@ -892,6 +892,7 @@ keyword "only":
For GitLab.com only tiers (when the feature is not available for self-hosted instances):
+- For GitLab Free and higher tiers: `**(FREE ONLY)**`.
- For GitLab Bronze and higher tiers: `**(BRONZE ONLY)**`.
- For GitLab Silver and higher tiers: `**(SILVER ONLY)**`.
- For GitLab Gold: `**(GOLD ONLY)**`.
diff --git a/doc/development/elasticsearch.md b/doc/development/elasticsearch.md
index 0965db29557..635895051bc 100644
--- a/doc/development/elasticsearch.md
+++ b/doc/development/elasticsearch.md
@@ -148,6 +148,36 @@ Uses an [Edge NGram token filter](https://www.elastic.co/guide/en/elasticsearch/
- Searches can have their own analyzers. Remember to check when editing analyzers
- `Character` filters (as opposed to token filters) always replace the original character, so they're not a good choice as they can hinder exact searches
+## Architecture
+
+GitLab uses `elasticsearch-rails` for handling communication with Elasticsearch server. However, in order to achieve zero-downtime deployment during schema changes, an extra abstraction layer is built to allow:
+
+* Indexing (writes) to multiple indexes, with different mappings
+* Switching to different index for searches (reads) on the fly
+
+Currently we are on the process of migrating models to this new design (e.g. `Snippet`), and it is hardwired to work with a single version for now.
+
+Traditionally, `elasticsearch-rails` provides class and instance level `__elasticsearch__` proxy methods. If you call `Issue.__elasticsearch__`, you will get an instance of `Elasticsearch::Model::Proxy::ClassMethodsProxy`, and if you call `Issue.first.__elasticsearch__`, you will get an instance of `Elasticsearch::Model::Proxy::InstanceMethodsProxy`. These proxy objects would talk to Elasticsearch server directly.
+
+In the new design, `__elasticsearch__` instead represents one extra layer of proxy. It would keep multiple versions of the actual proxy objects, and it would forward read and write calls to the proxy of the intended version.
+
+The `elasticsearch-rails`'s way of specifying each model's mappings and other settings is to create a module for the model to include. However in the new design, each model would have its own corresponding subclassed proxy object, where the settings reside in. For example, snippet related setting in the past reside in `SnippetsSearch` module, but in the new design would reside in `SnippetClassProxy` (which is a subclass of `Elasticsearch::Model::Proxy::ClassMethodsProxy`). This reduces namespace pollution in model classes.
+
+The global configurations per version are now in the `Elastic::(Version)::Config` class. You can change mappings there.
+
+### Creating new version of schema
+
+Currently GitLab would still work with a single version of setting. Once it is implemented, multiple versions of setting can exists in different folders (e.g. `ee/lib/elastic/v12p1` and `ee/lib/elastic/v12p3`). To keep a continuous git history, the latest version lives under the `/latest` folder, but is aliased as the latest version.
+
+If the current version is `v12p1`, and we need to create a new version for `v12p3`, the steps are as follows:
+
+1. Copy the entire folder of `v12p1` as `v12p3`
+1. Change the namespace for files under `v12p3` folder from `V12p1` to `V12p3` (which are still aliased to `Latest`)
+1. Delete `v12p1` folder
+1. Copy the entire folder of `latest` as `v12p1`
+1. Change the namespace for files under `v12p1` folder from `Latest` to `V12p1`
+1. Make changes to `Latest` as needed
+
## Troubleshooting
### Getting `flood stage disk watermark [95%] exceeded`
diff --git a/doc/development/testing_guide/best_practices.md b/doc/development/testing_guide/best_practices.md
index 448d9fd01c4..9d6792e9139 100644
--- a/doc/development/testing_guide/best_practices.md
+++ b/doc/development/testing_guide/best_practices.md
@@ -70,6 +70,7 @@ bundle exec rspec spec/[path]/[to]/[spec].rb
- On `before` and `after` hooks, prefer it scoped to `:context` over `:all`
- When using `evaluate_script("$('.js-foo').testSomething()")` (or `execute_script`) which acts on a given element,
use a Capyabara matcher beforehand (e.g. `find('.js-foo')`) to ensure the element actually exists.
+- Use `focus: true` to isolate parts of the specs you want to run.
[four-phase-test]: https://robots.thoughtbot.com/four-phase-test
diff --git a/doc/install/installation.md b/doc/install/installation.md
index df6c485b1cb..295d9804497 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -202,8 +202,8 @@ Then select 'Internet Site' and press enter to confirm the hostname.
The Ruby interpreter is required to run GitLab.
-**Note:** The current supported Ruby (MRI) version is 2.5.x. GitLab 11.6
- dropped support for Ruby 2.4.x.
+**Note:** The current supported Ruby (MRI) version is 2.6.x. GitLab 12.2
+ dropped support for Ruby 2.5.x.
The use of Ruby version managers such as [RVM], [rbenv] or [chruby] with GitLab
in production, frequently leads to hard to diagnose problems. For example,
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 83a9e7fe294..234e5acb394 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -40,7 +40,7 @@ Please consider using a virtual machine to run GitLab.
## Ruby versions
-GitLab requires Ruby (MRI) 2.5. Support for Ruby versions below 2.5 (2.3, 2.4) will stop with GitLab 11.6.
+GitLab requires Ruby (MRI) 2.6. Support for Ruby versions below 2.6 (2.4, 2.5) will stop with GitLab 12.2.
You will have to use the standard MRI implementation of Ruby.
We love [JRuby](https://www.jruby.org/) and [Rubinius](https://rubinius.com) but GitLab
diff --git a/doc/update/upgrading_from_source.md b/doc/update/upgrading_from_source.md
index 0aef40262c9..df35638cba2 100644
--- a/doc/update/upgrading_from_source.md
+++ b/doc/update/upgrading_from_source.md
@@ -47,8 +47,8 @@ sudo service gitlab stop
### 3. Update Ruby
-NOTE: Beginning in GitLab 11.6, we only support Ruby 2.5 or higher, and dropped
-support for Ruby 2.4. Be sure to upgrade if necessary.
+NOTE: Beginning in GitLab 12.2, we only support Ruby 2.6 or higher, and dropped
+support for Ruby 2.5. Be sure to upgrade if necessary.
You can check which version you are running with `ruby -v`.
diff --git a/doc/user/analytics/productivity_analytics.md b/doc/user/analytics/productivity_analytics.md
new file mode 100644
index 00000000000..db3e37bd0fb
--- /dev/null
+++ b/doc/user/analytics/productivity_analytics.md
@@ -0,0 +1,69 @@
+# Productivity Analytics **(PREMIUM)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/12079) in [GitLab Premium](https://about.gitlab.com/pricing/) 12.2 (enabled by feature flags `analytics` and `productivity_analytics`).
+
+Track development velocity with Productivity Analytics.
+
+For many companies, the development cycle is a blackbox and getting an estimate of how
+long, on average, it takes to deliver features is an enormous endeavor.
+
+While [Cycle Analytics](../project/cycle_analytics.md) focuses on the entire
+SDLC process, Productivity Analytics provides a way for Engineering Management to
+drill down in a systematic way to uncover patterns and causes for success or failure at
+an individual, project or group level.
+
+Productivity can slow down for many reasons ranging from degrading code base to quickly
+growing teams. In order to investigate, department or team leaders can start by visualizing the time
+it takes for merge requests to be merged.
+
+## Supported features
+
+Productivity Analytics allows GitLab users to:
+
+- Visualize typical Merge Request lifetime and statistics. Use a histogram
+ that shows the distribution of the time elapsed between creating and merging
+ Merge Requests.
+- Drill down into the most time consuming Merge Requests, select a number of outliers,
+ and filter down all subsequent charts to investigate potential causes.
+- Filter by group, project, author, label, milestone, or a specific date range.
+ Filter down, for example, to the Merge Requests of a specific author in a group
+ or project during a milestone or specific date range.
+
+## Accessing metrics and visualizations
+
+To access the **Productivity Analytics** page, go to **Analytics > Productivity Analytics**.
+
+The following metrics and visualizations are available on a project or group level:
+
+- Histogram showing the number of Merge Request that took a specified number of days to
+ merge after creation. Select a specific column to filter down subsequent charts.
+- Histogram showing a breakdown of the time taken (in hours) to merge a Merge Request.
+ The following intervals are available:
+ - Time from first commit to first comment.
+ - Time from first comment until last commit.
+ - Time from last commit to merge.
+- Histogram showing the size or complexity of a Merge Request, using the following:
+ - Number of commits per Merge Request.
+ - Number of lines of code per commit.
+ - Number of files touched.
+- Table showing list of Merge Requests with their respective times and size metrics.
+ Can be sorted by the above metrics.
+ - Users can sort by any of the above metrics
+
+## Retrieving data
+
+Users can retrieve three months of data when they deploy Productivity Analytics for the first time.
+
+To retrieve data for a different time span, run the following in the GitLab directory:
+
+```sh
+MERGED_AT_AFTER = <your_date> rake gitlab:productivity_analytics:recalc
+```
+
+## Permissions
+
+The **Productivity Analytics** dashboard can be accessed only:
+
+- On GitLab instances and namespaces on
+ [Premium or Silver tier](https://about.gitlab.com/pricing/) and above.
+- By users with [Reporter access](../permissions.md) and above.
diff --git a/doc/user/application_security/dependency_list/img/dependency_list_v12_2.png b/doc/user/application_security/dependency_list/img/dependency_list_v12_2.png
new file mode 100644
index 00000000000..af9cee08d71
--- /dev/null
+++ b/doc/user/application_security/dependency_list/img/dependency_list_v12_2.png
Binary files differ
diff --git a/doc/user/application_security/dependency_list/index.md b/doc/user/application_security/dependency_list/index.md
new file mode 100644
index 00000000000..38c38bbd8a9
--- /dev/null
+++ b/doc/user/application_security/dependency_list/index.md
@@ -0,0 +1,49 @@
+# Dependency List **(ULTIMATE)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/10075) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
+
+The Dependency list allows you to see your project's dependencies, and key
+details about them, including their known vulnerabilities. To see it,
+navigate to **Security & Compliance > Dependency List** in your project's
+sidebar.
+
+## Requirements
+
+1. The [Dependency Scanning](../dependency_scanning/index.md) CI job must be
+ configured for your project.
+1. Your project uses at least one of the
+ [languages and package managers](../dependency_scanning/index.md#supported-languages-and-package-managers)
+ supported by Gemnasium.
+
+## Viewing dependencies
+
+![Dependency List](img/dependency_list_v12_2.png)
+
+Dependencies are displayed with the following information:
+
+| Field | Description |
+| --------- | ----------- |
+| Status | Displays whether or not the dependency has any known vulnerabilities |
+| Component | The dependency's name |
+| Version | The exact locked version of the dependency your project uses |
+| Packager | The packager used to install the depedency |
+| Location | A link to the packager-specific lockfile in your project that declared the dependency |
+
+Dependencies shown are initially sorted by their names. They can also be sorted
+by the packager they were installed by, or by the severity of their known
+vulnerabilities.
+
+There is a second list under the `Vulnerable components` tab displaying only
+those dependencies with known vulnerabilities. If there are none, this tab is
+disabled.
+
+### Vulnerabilities
+
+If a dependency has known vulnerabilities, they can be viewed by clicking on the
+`Status` cell of that dependency. The severity and description of each
+vulnerability will then be displayed below it.
+
+## Downloading the Dependency List
+
+Your project's full list of dependencies and their details can be downloaded in
+`JSON` format by clicking on the download button.
diff --git a/doc/user/application_security/dependency_scanning/index.md b/doc/user/application_security/dependency_scanning/index.md
index 10b4d9d4c7c..3148ec63c79 100644
--- a/doc/user/application_security/dependency_scanning/index.md
+++ b/doc/user/application_security/dependency_scanning/index.md
@@ -327,16 +327,11 @@ Once a vulnerability is found, you can interact with it. Read more on how to
For more information about the vulnerabilities database update, check the
[maintenance table](../index.md#maintenance-and-update-of-the-vulnerabilities-database).
-## Dependency List
+## Dependency List **(ULTIMATE)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/10075) in [GitLab Ultimate](https://about.gitlab.com/pricing/) 12.0.
-
-An additional benefit of Dependency Scanning is the ability to get a list of your
-project's dependencies with their versions. This list can be generated only for
-[languages and package managers](#supported-languages-and-package-managers)
-supported by Gemnasium.
-
-To see the generated dependency list, navigate to your project's **Security & Compliance > Dependency List**.
+An additional benefit of Dependency Scanning is the ability to view your
+project's dependencies and their known vulnerabilities. Read more about
+the [Dependency List](../dependency_list/index.md).
## Versioning and release process
diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md
index 4dcb416c110..83ea0ea3386 100644
--- a/doc/user/application_security/index.md
+++ b/doc/user/application_security/index.md
@@ -25,6 +25,7 @@ GitLab can scan and report any vulnerabilities found in your project.
| Secure scanning tool | Description |
|:-----------------------------------------------------------------------------|:-----------------------------------------------------------------------|
| [Container Scanning](container_scanning/index.md) **(ULTIMATE)** | Scan Docker containers for known vulnerabilities. |
+| [Dependency List](dependency_list/index.md) **(ULTIMATE)** | View your project's dependencies and their known vulnerabilities. |
| [Dependency Scanning](dependency_scanning/index.md) **(ULTIMATE)** | Analyze your dependencies for known vulnerabilities. |
| [Dynamic Application Security Testing (DAST)](dast/index.md) **(ULTIMATE)** | Analyze running web applications for known vulnerabilities. |
| [License Management](license_management/index.md) **(ULTIMATE)** | Search your project's dependencies for their licenses. |
diff --git a/doc/user/permissions.md b/doc/user/permissions.md
index 1d457099ebc..16684b9f72b 100644
--- a/doc/user/permissions.md
+++ b/doc/user/permissions.md
@@ -233,6 +233,16 @@ nested groups if you have membership in one of its parents.
To learn more, read through the documentation on
[subgroups memberships](group/subgroups/index.md#membership).
+## Guest User
+
+Create a user and assign to a project with a role as `Guest` user, this user
+will be considered as guest user by GitLab and will not take up the license.
+There is no specific `Guest` role for newly created users. If this user will
+be assigned a higher role to any of the projects and groups then this user will
+take a license seat. If a user creates a project this user becomes a maintainer,
+therefore, takes up a license seat as well, in order to prevent this you have
+to go and edit user profile and mark the user as External.
+
## External users permissions
In cases where it is desired that a user has access only to some internal or
diff --git a/doc/user/project/cycle_analytics.md b/doc/user/project/cycle_analytics.md
index 6707b88c317..424bee6e9f1 100644
--- a/doc/user/project/cycle_analytics.md
+++ b/doc/user/project/cycle_analytics.md
@@ -1,5 +1,7 @@
# Cycle Analytics
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/12077) at a group level in [GitLab Premium and Silver](https://about.gitlab.com/pricing/) 12.2 (enabled by feature flag `analytics`).
+
Cycle Analytics measures the time spent to go from an [idea to production] - also known
as cycle time - for each of your projects. Cycle Analytics displays the median time for an idea to
reach production, along with the time typically spent in each DevOps stage along the way.
@@ -13,10 +15,16 @@ calculates a separate median for each stage.
## Overview
-You can find the Cycle Analytics page under your project's **Project âž” Cycle
-Analytics** tab.
+Cycle Analytics are available at a:
+
+- Group level from the top navigation bar **Analytics > Cycle Analytics**. **(PREMIUM)**
+
+ In the future, multiple groups will be selectable which will effectively make this an
+ instance-level feature.
+
+- Project level from a project's **Project > Cycle Analytics**.
-![Cycle Analytics landing page](img/cycle_analytics_landing_page.png)
+ ![Cycle Analytics landing page](img/cycle_analytics_landing_page.png)
There are seven stages that are tracked as part of the Cycle Analytics calculations.
@@ -134,7 +142,7 @@ A few notes:
## Permissions
-The current permissions on the Cycle Analytics dashboard are:
+The current permissions on the Project Cycle Analytics dashboard are:
- Public projects - anyone can access
- Internal projects - any authenticated user can access
@@ -142,6 +150,18 @@ The current permissions on the Cycle Analytics dashboard are:
You can [read more about permissions][permissions] in general.
+NOTE: **Note:**
+As of GitLab 12.2, the project-level page is deprecated. You should access
+project-level Cycle Analytics from **Analytics > Cycle Analytics** in the top
+navigation bar. We will ensure that the same project-level functionality is available
+to CE users in the new analytics space.
+
+For Cycle Analytics functionality introduced in GitLab 12.2 and later:
+
+- Users must have Reporter access or above.
+- Features are available only on
+ [Premium or Silver tiers](https://about.gitlab.com/pricing/) and above.
+
## More resources
Learn more about Cycle Analytics in the following resources:
diff --git a/doc/user/project/description_templates.md b/doc/user/project/description_templates.md
index 196874fdc86..f53dc056010 100644
--- a/doc/user/project/description_templates.md
+++ b/doc/user/project/description_templates.md
@@ -55,7 +55,7 @@ changes you made after picking the template and return it to its initial status.
![Description templates](img/description_templates.png)
-## Setting a default template for issues and merge requests **(STARTER)**
+## Setting a default template for merge requests and issues **(STARTER)**
> **Notes:**
>
@@ -66,20 +66,20 @@ changes you made after picking the template and return it to its initial status.
> - Templates for merge requests were [introduced][ee-7478ece] in GitLab EE 6.9.
The visibility of issues and/or merge requests should be set to either "Everyone
-with access" or "Only Project Members" in your project's **Settings** otherwise the
+with access" or "Only Project Members" in your project's **Settings / Visibility, project features, permissions** section, otherwise the
template text areas won't show. This is the default behavior so in most cases
you should be fine.
-Go to your project's **Settings** and fill in the "Default description template
-for issues" and "Default description template for merge requests" text areas
-for issues and merge requests respectively. Since GitLab issues and merge
-request support [Markdown](../markdown.md), you can use special markup like
+Go to your project's **Settings** and under the **Merge requests** header, click *Expand* and fill in the "Default description template
+for merge requests" text area. Under the **Default issue template**, click *Expand* and fill in "Default description template for issues" text area. Since GitLab merge request and issues
+ support [Markdown](../markdown.md), you can use special markup like
headings, lists, etc.
-![Default description templates](img/description_templates_default_settings.png)
+![Default merge request description templates](img/description_templates_merge_request_settings.png)
+![Default issue description templates](img/description_templates_issue_settings.png)
After you add the description, hit **Save changes** for the settings to take
-effect. Now, every time a new issue or merge request is created, it will be
+effect. Now, every time a new merge request or issue is created, it will be
pre-filled with the text you entered in the template(s).
## Description template example
diff --git a/doc/user/project/img/description_templates_default_settings.png b/doc/user/project/img/description_templates_default_settings.png
deleted file mode 100644
index ab314e83d06..00000000000
--- a/doc/user/project/img/description_templates_default_settings.png
+++ /dev/null
Binary files differ
diff --git a/doc/user/project/img/description_templates_issue_settings.png b/doc/user/project/img/description_templates_issue_settings.png
new file mode 100644
index 00000000000..53328108835
--- /dev/null
+++ b/doc/user/project/img/description_templates_issue_settings.png
Binary files differ
diff --git a/doc/user/project/img/description_templates_merge_request_settings.png b/doc/user/project/img/description_templates_merge_request_settings.png
new file mode 100644
index 00000000000..eda264f7f37
--- /dev/null
+++ b/doc/user/project/img/description_templates_merge_request_settings.png
Binary files differ
diff --git a/doc/user/project/index.md b/doc/user/project/index.md
index 45e96437517..30ff0e9ff07 100644
--- a/doc/user/project/index.md
+++ b/doc/user/project/index.md
@@ -99,6 +99,7 @@ When you create a project in GitLab, you'll have access to a large number of
- [NPM packages](packages/npm_registry.md): your private NPM package registry in GitLab. **(PREMIUM)**
- [Code owners](code_owners.md): specify code owners for certain files **(STARTER)**
- [License Management](../application_security/license_management/index.md): approve and blacklist licenses for projects. **(ULTIMATE)**
+- [Dependency List](../application_security/dependency_list/index.md): view project dependencies. **(ULTIMATE)**
### Project integrations
diff --git a/doc/user/project/integrations/mattermost.md b/doc/user/project/integrations/mattermost.md
index ea58a08e127..6e0f39956d3 100644
--- a/doc/user/project/integrations/mattermost.md
+++ b/doc/user/project/integrations/mattermost.md
@@ -14,7 +14,7 @@ To enable Mattermost integration you must create an incoming webhook integration
1. Save it, copy the **Webhook URL**, we'll need this later for GitLab.
There might be some cases that Incoming Webhooks are blocked by admin, ask your mattermost admin to enable
-it on `https://mattermost.example/admin_console/integrations/custom`.
+it on **Mattermost System Console > Integrations > Integration Management**, or on **Mattermost System Console > Integrations > Custom Integrations** in Mattermost versions 5.11 and earlier.
Display name override is not enabled by default, you need to ask your admin to enable it on that same section.
diff --git a/doc/user/project/issues/issue_data_and_actions.md b/doc/user/project/issues/issue_data_and_actions.md
index 7b031f83cb1..d7d168710ef 100644
--- a/doc/user/project/issues/issue_data_and_actions.md
+++ b/doc/user/project/issues/issue_data_and_actions.md
@@ -50,7 +50,12 @@ The button to do this has a different label depending on whether the issue is al
#### 3. Assignee
-An issue can be assigned to yourself, another person, or [many people](#31-multiple-assignees-STARTER).
+An issue can be assigned to:
+
+- Yourself.
+- Another person.
+- [Many people](#31-multiple-assignees-STARTER). **(STARTER)**
+
The assignee(s) can be changed as often as needed. The idea is that the assignees are
responsible for that issue until it's reassigned to someone else to take it from there.
When assigned to someone, it will appear in their assigned issues list.
diff --git a/doc/user/project/merge_requests/index.md b/doc/user/project/merge_requests/index.md
index 7637e30dfb4..4c8bd230b3f 100644
--- a/doc/user/project/merge_requests/index.md
+++ b/doc/user/project/merge_requests/index.md
@@ -334,6 +334,8 @@ git push -o merge_request.create -o merge_request.merge_when_pipeline_succeeds
### Set removing the source branch using git push options
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/64320) in GitLab 12.2.
+
To set an existing merge request to remove the source branch when the
merge request is merged, the
`merge_request.remove_source_branch` push option can be used:
@@ -347,6 +349,8 @@ You can also use this push option in addition to the
### Set merge request title using git push options
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/64320) in GitLab 12.2.
+
To set the title of an existing merge request, use
the `merge_request.title` push option:
@@ -359,6 +363,8 @@ You can also use this push option in addition to the
### Set merge request description using git push options
+> [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/64320) in GitLab 12.2.
+
To set the description of an existing merge request, use
the `merge_request.description` push option:
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
index 196ef1fcdfa..c36ee5af63f 100644
--- a/lib/api/settings.rb
+++ b/lib/api/settings.rb
@@ -125,6 +125,12 @@ module API
optional :instance_statistics_visibility_private, type: Boolean, desc: 'When set to `true` Instance statistics will only be available to admins'
optional :local_markdown_version, type: Integer, desc: "Local markdown version, increase this value when any cached markdown should be invalidated"
optional :allow_local_requests_from_hooks_and_services, type: Boolean, desc: 'Deprecated: Use :allow_local_requests_from_web_hooks_and_services instead. Allow requests to the local network from hooks and services.' # support legacy names, can be removed in v5
+ optional :snowplow_enabled, type: Grape::API::Boolean, desc: 'Enable Snowplow tracking'
+ given snowplow_enabled: ->(val) { val } do
+ requires :snowplow_collector_hostname, type: String, desc: 'The Snowplow collector hostname'
+ optional :snowplow_cookie_domain, type: String, desc: 'The Snowplow cookie domain'
+ optional :snowplow_site_id, type: String, desc: 'The Snowplow site name / application ic'
+ end
ApplicationSetting::SUPPORTED_KEY_TYPES.each do |type|
optional :"#{type}_key_restriction",
diff --git a/lib/api/todos.rb b/lib/api/todos.rb
index 7260ecfb5ee..404675bfaec 100644
--- a/lib/api/todos.rb
+++ b/lib/api/todos.rb
@@ -13,6 +13,13 @@ module API
'issues' => ->(iid) { find_project_issue(iid) }
}.freeze
+ helpers do
+ # EE::API::Todos would override this method
+ def find_todos
+ TodosFinder.new(current_user, params).execute
+ end
+ end
+
params do
requires :id, type: String, desc: 'The ID of a project'
end
@@ -41,10 +48,6 @@ module API
resource :todos do
helpers do
- def find_todos
- TodosFinder.new(current_user, params).execute
- end
-
def issuable_and_awardable?(type)
obj_type = Object.const_get(type)
@@ -107,3 +110,5 @@ module API
end
end
end
+
+API::Todos.prepend_if_ee('EE::API::Todos')
diff --git a/lib/gitlab/ci/pipeline/chain/populate.rb b/lib/gitlab/ci/pipeline/chain/populate.rb
index 0405292a25b..65029f5ce7f 100644
--- a/lib/gitlab/ci/pipeline/chain/populate.rb
+++ b/lib/gitlab/ci/pipeline/chain/populate.rb
@@ -23,12 +23,17 @@ module Gitlab
@command.seeds_block&.call(pipeline)
##
- # Populate pipeline with all stages, and stages with builds.
+ # Gather all runtime build/stage errors
#
- pipeline.stage_seeds.each do |stage|
- pipeline.stages << stage.to_resource
+ if seeds_errors = pipeline.stage_seeds.flat_map(&:errors).compact.presence
+ return error(seeds_errors.join("\n"))
end
+ ##
+ # Populate pipeline with all stages, and stages with builds.
+ #
+ pipeline.stages = pipeline.stage_seeds.map(&:to_resource)
+
if pipeline.stages.none?
return error('No stages / jobs for this pipeline.')
end
diff --git a/lib/gitlab/ci/pipeline/seed/base.rb b/lib/gitlab/ci/pipeline/seed/base.rb
index 1fd3a61017f..e9e22569ae0 100644
--- a/lib/gitlab/ci/pipeline/seed/base.rb
+++ b/lib/gitlab/ci/pipeline/seed/base.rb
@@ -13,6 +13,10 @@ module Gitlab
raise NotImplementedError
end
+ def errors
+ raise NotImplementedError
+ end
+
def to_resource
raise NotImplementedError
end
diff --git a/lib/gitlab/ci/pipeline/seed/build.rb b/lib/gitlab/ci/pipeline/seed/build.rb
index ab0d4c38ab6..b0ce7457926 100644
--- a/lib/gitlab/ci/pipeline/seed/build.rb
+++ b/lib/gitlab/ci/pipeline/seed/build.rb
@@ -9,10 +9,15 @@ module Gitlab
delegate :dig, to: :@attributes
+ # When the `ci_dag_limit_needs` is enabled it uses the lower limit
+ LOW_NEEDS_LIMIT = 5
+ HARD_NEEDS_LIMIT = 50
+
def initialize(pipeline, attributes, previous_stages)
@pipeline = pipeline
@attributes = attributes
@previous_stages = previous_stages
+ @needs_attributes = dig(:needs_attributes)
@only = Gitlab::Ci::Build::Policy
.fabricate(attributes.delete(:only))
@@ -27,8 +32,15 @@ module Gitlab
def included?
strong_memoize(:inclusion) do
all_of_only? &&
- none_of_except? &&
- all_of_needs?
+ none_of_except?
+ end
+ end
+
+ def errors
+ return unless included?
+
+ strong_memoize(:errors) do
+ needs_errors
end
end
@@ -48,6 +60,18 @@ module Gitlab
@attributes.to_h.dig(:options, :trigger).present?
end
+ def to_resource
+ strong_memoize(:resource) do
+ if bridge?
+ ::Ci::Bridge.new(attributes)
+ else
+ ::Ci::Build.new(attributes)
+ end
+ end
+ end
+
+ private
+
def all_of_only?
@only.all? { |spec| spec.satisfied_by?(@pipeline, self) }
end
@@ -56,24 +80,30 @@ module Gitlab
@except.none? { |spec| spec.satisfied_by?(@pipeline, self) }
end
- def all_of_needs?
- return true unless Feature.enabled?(:ci_dag_support, @pipeline.project)
- return true if dig(:needs_attributes).nil?
+ def needs_errors
+ return if @needs_attributes.nil?
+
+ if @needs_attributes.size > max_needs_allowed
+ return [
+ "#{name}: one job can only need #{max_needs_allowed} others, but you have listed #{@needs_attributes.size}. " \
+ "See needs keyword documentation for more details"
+ ]
+ end
- dig(:needs_attributes).all? do |need|
- @previous_stages.any? do |stage|
+ @needs_attributes.flat_map do |need|
+ result = @previous_stages.any? do |stage|
stage.seeds_names.include?(need[:name])
end
- end
+
+ "#{name}: needs '#{need[:name]}'" unless result
+ end.compact
end
- def to_resource
- strong_memoize(:resource) do
- if bridge?
- ::Ci::Bridge.new(attributes)
- else
- ::Ci::Build.new(attributes)
- end
+ def max_needs_allowed
+ if Feature.enabled?(:ci_dag_limit_needs, @project, default_enabled: true)
+ LOW_NEEDS_LIMIT
+ else
+ HARD_NEEDS_LIMIT
end
end
end
diff --git a/lib/gitlab/ci/pipeline/seed/stage.rb b/lib/gitlab/ci/pipeline/seed/stage.rb
index 7c737027445..b600df2f656 100644
--- a/lib/gitlab/ci/pipeline/seed/stage.rb
+++ b/lib/gitlab/ci/pipeline/seed/stage.rb
@@ -33,6 +33,12 @@ module Gitlab
end
end
+ def errors
+ strong_memoize(:errors) do
+ seeds.flat_map(&:errors).compact
+ end
+ end
+
def seeds_names
strong_memoize(:seeds_names) do
seeds.map(&:name).to_set
diff --git a/lib/gitlab/data_builder/push.rb b/lib/gitlab/data_builder/push.rb
index 40bda3410e1..37fadb47736 100644
--- a/lib/gitlab/data_builder/push.rb
+++ b/lib/gitlab/data_builder/push.rb
@@ -60,7 +60,8 @@ module Gitlab
# rubocop:disable Metrics/ParameterLists
def build(
project:, user:, ref:, oldrev: nil, newrev: nil,
- commits: [], commits_count: nil, message: nil, push_options: {})
+ commits: [], commits_count: nil, message: nil, push_options: {},
+ with_changed_files: true)
commits = Array(commits)
@@ -75,7 +76,7 @@ module Gitlab
# n+1: https://gitlab.com/gitlab-org/gitlab-ce/issues/38259
commit_attrs = Gitlab::GitalyClient.allow_n_plus_1_calls do
commits_limited.map do |commit|
- commit.hook_attrs(with_changed_files: true)
+ commit.hook_attrs(with_changed_files: with_changed_files)
end
end
diff --git a/lib/gitlab/kubernetes/helm/reset_command.rb b/lib/gitlab/kubernetes/helm/reset_command.rb
index 37e1d8573ab..a35ffa34c58 100644
--- a/lib/gitlab/kubernetes/helm/reset_command.rb
+++ b/lib/gitlab/kubernetes/helm/reset_command.rb
@@ -38,9 +38,9 @@ module Gitlab
# Tracking this method to be removed here:
# https://gitlab.com/gitlab-org/gitlab-ce/issues/52791#note_199374155
def delete_tiller_replicaset
- command = %w[kubectl delete replicaset -n gitlab-managed-apps -l name=tiller]
+ delete_args = %w[replicaset -n gitlab-managed-apps -l name=tiller]
- command.shelljoin
+ Gitlab::Kubernetes::KubectlCmd.delete(*delete_args)
end
def reset_helm_command
diff --git a/lib/gitlab/kubernetes/kubectl_cmd.rb b/lib/gitlab/kubernetes/kubectl_cmd.rb
new file mode 100644
index 00000000000..981eb5681dc
--- /dev/null
+++ b/lib/gitlab/kubernetes/kubectl_cmd.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Kubernetes
+ module KubectlCmd
+ class << self
+ def delete(*args)
+ %w(kubectl delete).concat(args).shelljoin
+ end
+
+ def apply_file(filename, *args)
+ raise ArgumentError, "filename is not present" unless filename.present?
+
+ %w(kubectl apply -f).concat([filename], args).shelljoin
+ end
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/metrics/samplers/puma_sampler.rb b/lib/gitlab/metrics/samplers/puma_sampler.rb
index 4e835f37c04..8a24d4f3663 100644
--- a/lib/gitlab/metrics/samplers/puma_sampler.rb
+++ b/lib/gitlab/metrics/samplers/puma_sampler.rb
@@ -15,7 +15,6 @@ module Gitlab
puma_workers: ::Gitlab::Metrics.gauge(:puma_workers, 'Total number of workers'),
puma_running_workers: ::Gitlab::Metrics.gauge(:puma_running_workers, 'Number of active workers'),
puma_stale_workers: ::Gitlab::Metrics.gauge(:puma_stale_workers, 'Number of stale workers'),
- puma_phase: ::Gitlab::Metrics.gauge(:puma_phase, 'Phase number (increased during phased restarts)'),
puma_running: ::Gitlab::Metrics.gauge(:puma_running, 'Number of running threads'),
puma_queued_connections: ::Gitlab::Metrics.gauge(:puma_queued_connections, 'Number of connections in that worker\'s "todo" set waiting for a worker thread'),
puma_active_connections: ::Gitlab::Metrics.gauge(:puma_active_connections, 'Number of threads processing a request'),
@@ -54,7 +53,6 @@ module Gitlab
last_status = worker['last_status']
labels = { worker: "worker_#{worker['index']}" }
- metrics[:puma_phase].set(labels, worker['phase'])
set_worker_metrics(last_status, labels) if last_status.present?
end
end
@@ -76,7 +74,6 @@ module Gitlab
metrics[:puma_workers].set(labels, stats['workers'])
metrics[:puma_running_workers].set(labels, stats['booted_workers'])
metrics[:puma_stale_workers].set(labels, stats['old_workers'])
- metrics[:puma_phase].set(labels, stats['phase'])
end
def set_worker_metrics(stats, labels = {})
diff --git a/lib/gitlab/snowplow_tracker.rb b/lib/gitlab/snowplow_tracker.rb
new file mode 100644
index 00000000000..9f12513e09e
--- /dev/null
+++ b/lib/gitlab/snowplow_tracker.rb
@@ -0,0 +1,35 @@
+# frozen_string_literal: true
+
+require 'snowplow-tracker'
+
+module Gitlab
+ module SnowplowTracker
+ NAMESPACE = 'cf'
+
+ class << self
+ def track_event(category, action, label: nil, property: nil, value: nil, context: nil)
+ tracker&.track_struct_event(category, action, label, property, value, context, Time.now.to_i)
+ end
+
+ private
+
+ def tracker
+ return unless enabled?
+
+ @tracker ||= ::SnowplowTracker::Tracker.new(emitter, subject, NAMESPACE, Gitlab::CurrentSettings.snowplow_site_id)
+ end
+
+ def subject
+ ::SnowplowTracker::Subject.new
+ end
+
+ def emitter
+ ::SnowplowTracker::Emitter.new(Gitlab::CurrentSettings.snowplow_collector_hostname)
+ end
+
+ def enabled?
+ Gitlab::CurrentSettings.snowplow_enabled?
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/usage_data.rb b/lib/gitlab/usage_data.rb
index 16a076a8811..038553c5dd7 100644
--- a/lib/gitlab/usage_data.rb
+++ b/lib/gitlab/usage_data.rb
@@ -143,6 +143,7 @@ module Gitlab
Gitlab::UsageDataCounters::NoteCounter,
Gitlab::UsageDataCounters::SnippetCounter,
Gitlab::UsageDataCounters::SearchCounter,
+ Gitlab::UsageDataCounters::CycleAnalyticsCounter,
Gitlab::UsageDataCounters::SourceCodeCounter
]
end
diff --git a/lib/gitlab/usage_data_counters/cycle_analytics_counter.rb b/lib/gitlab/usage_data_counters/cycle_analytics_counter.rb
new file mode 100644
index 00000000000..1ff4296ef65
--- /dev/null
+++ b/lib/gitlab/usage_data_counters/cycle_analytics_counter.rb
@@ -0,0 +1,8 @@
+# frozen_string_literal: true
+
+module Gitlab::UsageDataCounters
+ class CycleAnalyticsCounter < BaseCounter
+ KNOWN_EVENTS = %w[views].freeze
+ PREFIX = 'cycle_analytics'
+ end
+end
diff --git a/lib/tasks/services.rake b/lib/tasks/services.rake
index 56b81106c5f..4ec4fdd281f 100644
--- a/lib/tasks/services.rake
+++ b/lib/tasks/services.rake
@@ -86,7 +86,7 @@ namespace :services do
doc_start = Time.now
doc_path = File.join(Rails.root, 'doc', 'api', 'services.md')
- result = ERB.new(services_template, 0, '>')
+ result = ERB.new(services_template, trim_mode: '>')
.result(OpenStruct.new(services: services).instance_eval { binding })
File.open(doc_path, 'w') do |f|
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 27435576495..dd69fa1f8f6 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2953,6 +2953,9 @@ msgstr ""
msgid "Collapse sidebar"
msgstr ""
+msgid "Collector hostname"
+msgstr ""
+
msgid "ComboSearch is not defined"
msgstr ""
@@ -3120,6 +3123,9 @@ msgstr ""
msgid "Configure storage path settings."
msgstr ""
+msgid "Configure the %{link} integration."
+msgstr ""
+
msgid "Configure the way a user creates a new account."
msgstr ""
@@ -3261,6 +3267,9 @@ msgstr ""
msgid "ConvDev Index"
msgstr ""
+msgid "Cookie domain"
+msgstr ""
+
msgid "Copied"
msgstr ""
@@ -4253,6 +4262,9 @@ msgstr ""
msgid "Enable shared Runners"
msgstr ""
+msgid "Enable snowplow tracking"
+msgstr ""
+
msgid "Enable two-factor authentication"
msgstr ""
@@ -5065,6 +5077,9 @@ msgstr ""
msgid "For public projects, anyone can view pipelines and access job details (output logs and artifacts)"
msgstr ""
+msgid "Forgot your password?"
+msgstr ""
+
msgid "Fork"
msgstr ""
@@ -5215,6 +5230,9 @@ msgstr ""
msgid "GitLab User"
msgstr ""
+msgid "GitLab member or Email address"
+msgstr ""
+
msgid "GitLab project export"
msgstr ""
@@ -9912,9 +9930,6 @@ msgstr ""
msgid "Select branch/tag"
msgstr ""
-msgid "Select members to invite"
-msgstr ""
-
msgid "Select merge moment"
msgstr ""
@@ -10286,6 +10301,9 @@ msgstr ""
msgid "Similar issues"
msgstr ""
+msgid "Site ID"
+msgstr ""
+
msgid "Size and domain settings for static websites"
msgstr ""
@@ -10316,6 +10334,9 @@ msgstr ""
msgid "SnippetsEmptyState|They can be either public or private."
msgstr ""
+msgid "Snowplow"
+msgstr ""
+
msgid "Some email servers do not support overriding the email sender name. Enable this option to include the name of the author of the issue, merge request or comment in the email body instead."
msgstr ""
@@ -12509,6 +12530,9 @@ msgstr ""
msgid "Username is available."
msgstr ""
+msgid "Username or email"
+msgstr ""
+
msgid "Users"
msgstr ""
diff --git a/qa/qa/page/main/login.rb b/qa/qa/page/main/login.rb
index c2b0482d789..94245bbfcba 100644
--- a/qa/qa/page/main/login.rb
+++ b/qa/qa/page/main/login.rb
@@ -52,13 +52,11 @@ module QA
raise NotImplementedError if Runtime::User.ldap_user? && user&.credentials_given?
if Runtime::User.ldap_user?
- sign_in_using_ldap_credentials
+ sign_in_using_ldap_credentials(user || Runtime::User)
else
sign_in_using_gitlab_credentials(user || Runtime::User)
end
end
-
- Page::Main::Menu.perform(&:has_personal_area?)
end
def sign_in_using_admin_credentials
@@ -76,6 +74,25 @@ module QA
Page::Main::Menu.perform(&:has_personal_area?)
end
+ def sign_in_using_ldap_credentials(user)
+ # Log out if already logged in
+ Page::Main::Menu.perform do |menu|
+ menu.sign_out if menu.has_personal_area?(wait: 0)
+ end
+
+ using_wait_time 0 do
+ set_initial_password_if_present
+
+ switch_to_ldap_tab
+
+ fill_element :username_field, user.ldap_username
+ fill_element :password_field, user.ldap_password
+ click_element :sign_in_button
+ end
+
+ Page::Main::Menu.perform(&:has_personal_area?)
+ end
+
def self.path
'/users/sign_in'
end
@@ -133,14 +150,6 @@ module QA
private
- def sign_in_using_ldap_credentials
- switch_to_ldap_tab
-
- fill_element :username_field, Runtime::User.ldap_username
- fill_element :password_field, Runtime::User.ldap_password
- click_element :sign_in_button
- end
-
def sign_in_using_gitlab_credentials(user)
switch_to_sign_in_tab if has_sign_in_tab?
switch_to_standard_tab if has_standard_tab?
diff --git a/qa/qa/page/project/issue/new.rb b/qa/qa/page/project/issue/new.rb
index 0d138417176..65c02801d67 100644
--- a/qa/qa/page/project/issue/new.rb
+++ b/qa/qa/page/project/issue/new.rb
@@ -6,7 +6,7 @@ module QA
module Issue
class New < Page::Base
view 'app/views/shared/issuable/_form.html.haml' do
- element :submit_issue_button, 'form.submit "Submit' # rubocop:disable QA/ElementWithPattern
+ element :issuable_create_button
end
view 'app/views/shared/issuable/form/_title.html.haml' do
@@ -26,7 +26,7 @@ module QA
end
def create_new_issue
- click_on 'Submit issue'
+ click_element :issuable_create_button, Page::Project::Issue::Show
end
end
end
diff --git a/qa/qa/page/project/issue/show.rb b/qa/qa/page/project/issue/show.rb
index 507dccb52d0..45dad9bc0ae 100644
--- a/qa/qa/page/project/issue/show.rb
+++ b/qa/qa/page/project/issue/show.rb
@@ -14,7 +14,7 @@ module QA
end
view 'app/assets/javascripts/notes/components/discussion_filter.vue' do
- element :discussion_filter
+ element :discussion_filter, required: true
element :filter_options
end
diff --git a/qa/qa/page/project/menu.rb b/qa/qa/page/project/menu.rb
index 3fe048f752a..838d59b59cb 100644
--- a/qa/qa/page/project/menu.rb
+++ b/qa/qa/page/project/menu.rb
@@ -5,7 +5,7 @@ module QA
module Project
class Menu < Page::Base
include SubMenus::Common
-
+ include SubMenus::Project
include SubMenus::CiCd
include SubMenus::Issues
include SubMenus::Operations
diff --git a/qa/qa/resource/merge_request.rb b/qa/qa/resource/merge_request.rb
index 7969de726e4..45ab2396a04 100644
--- a/qa/qa/resource/merge_request.rb
+++ b/qa/qa/resource/merge_request.rb
@@ -5,7 +5,8 @@ require 'securerandom'
module QA
module Resource
class MergeRequest < Base
- attr_accessor :title,
+ attr_accessor :id,
+ :title,
:description,
:source_branch,
:target_branch,
@@ -74,6 +75,28 @@ module QA
page.create_merge_request
end
end
+
+ def fabricate_via_api!
+ populate(:target, :source)
+ super
+ end
+
+ def api_get_path
+ "/projects/#{project.id}/merge_requests/#{id}"
+ end
+
+ def api_post_path
+ "/projects/#{project.id}/merge_requests"
+ end
+
+ def api_post_body
+ {
+ description: @description,
+ source_branch: @source_branch,
+ target_branch: @target_branch,
+ title: @title
+ }
+ end
end
end
end
diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb
index b4d70fc191a..632a0f5f2a9 100644
--- a/qa/qa/scenario/test/sanity/selectors.rb
+++ b/qa/qa/scenario/test/sanity/selectors.rb
@@ -7,11 +7,13 @@ module QA
class Selectors < Scenario::Template
include Scenario::Bootable
- PAGES = [QA::Page].freeze
+ def pages
+ @pages ||= [QA::Page]
+ end
def perform(*)
- validators = PAGES.map do |pages|
- Page::Validator.new(pages)
+ validators = pages.map do |page|
+ Page::Validator.new(page)
end
validators.flat_map(&:errors).tap do |errors|
diff --git a/qa/qa/service/omnibus.rb b/qa/qa/service/omnibus.rb
index b54fd5628f2..c5cddff56cd 100644
--- a/qa/qa/service/omnibus.rb
+++ b/qa/qa/service/omnibus.rb
@@ -11,11 +11,12 @@ module QA
end
def gitlab_ctl(command, input: nil)
- if input.nil?
- shell "docker exec #{@name} gitlab-ctl #{command}"
- else
- shell "docker exec #{@name} bash -c '#{input} | gitlab-ctl #{command}'"
- end
+ docker_exec("gitlab-ctl #{command}", input: input)
+ end
+
+ def docker_exec(command, input: nil)
+ command = "#{input} | #{command}" if input
+ shell "docker exec #{@name} bash -c '#{command}'"
end
end
end
diff --git a/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb
index 9e48ee7ca2a..891cef6c420 100644
--- a/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb
+++ b/qa/qa/specs/features/browser_ui/3_create/merge_request/view_merge_request_diff_patch_spec.rb
@@ -7,13 +7,18 @@ module QA
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
- @merge_request = Resource::MergeRequest.fabricate! do |merge_request|
+ project = Resource::Project.fabricate_via_api! do |project|
+ project.name = 'project'
+ end
+
+ @merge_request = Resource::MergeRequest.fabricate_via_api! do |merge_request|
+ merge_request.project = project
merge_request.title = 'This is a merge request'
- merge_request.description = 'For downloading patches and diffs'
+ merge_request.description = '... for downloading patches and diffs'
end
end
- it 'user views merge request email patches' do
+ it 'views the merge request email patches' do
@merge_request.visit!
Page::MergeRequest::Show.perform(&:view_email_patches)
@@ -22,7 +27,7 @@ module QA
expect(page).to have_content('diff --git a/added_file.txt b/added_file.txt')
end
- it 'user views merge request plain diff' do
+ it 'views the merge request plain diff' do
@merge_request.visit!
Page::MergeRequest::Show.perform(&:view_plain_diff)
diff --git a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
index 2fe4e4d9d1f..f6411d8c5ad 100644
--- a/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
+++ b/qa/qa/specs/features/browser_ui/6_release/deploy_key/clone_using_deploy_key_spec.rb
@@ -3,36 +3,29 @@
require 'digest/sha1'
module QA
- # Failure issue: https://gitlab.com/gitlab-org/quality/nightly/issues/70
- context 'Release', :docker, :quarantine do
+ context 'Release', :docker do
describe 'Git clone using a deploy key' do
- def login
+ before do
Runtime::Browser.visit(:gitlab, Page::Main::Login)
Page::Main::Login.perform(&:sign_in_using_credentials)
- end
-
- before(:all) do
- login
@runner_name = "qa-runner-#{Time.now.to_i}"
- @project = Resource::Project.fabricate! do |resource|
+ @project = Resource::Project.fabricate_via_api! do |resource|
resource.name = 'deploy-key-clone-project'
end
@repository_location = @project.repository_ssh_location
- Resource::Runner.fabricate! do |resource|
+ Resource::Runner.fabricate_via_browser_ui! do |resource|
resource.project = @project
resource.name = @runner_name
resource.tags = %w[qa docker]
resource.image = 'gitlab/gitlab-runner:ubuntu'
end
-
- Page::Main::Menu.perform(&:sign_out)
end
- after(:all) do
+ after do
Service::Runner.new(@runner_name).remove!
end
@@ -46,9 +39,7 @@ module QA
it "user sets up a deploy key with #{key_class}(#{bits}) to clone code using pipelines" do
key = key_class.new(*bits)
- login
-
- Resource::DeployKey.fabricate! do |resource|
+ Resource::DeployKey.fabricate_via_browser_ui! do |resource|
resource.project = @project
resource.title = "deploy key #{key.name}(#{key.bits})"
resource.key = key.public_key
@@ -56,7 +47,7 @@ module QA
deploy_key_name = "DEPLOY_KEY_#{key.name}_#{key.bits}"
- Resource::CiVariable.fabricate! do |resource|
+ Resource::CiVariable.fabricate_via_browser_ui! do |resource|
resource.project = @project
resource.key = deploy_key_name
resource.value = key.private_key
diff --git a/qa/qa/vendor/saml_idp/page/login.rb b/qa/qa/vendor/saml_idp/page/login.rb
index 9c1f9904a7a..1b8c926532a 100644
--- a/qa/qa/vendor/saml_idp/page/login.rb
+++ b/qa/qa/vendor/saml_idp/page/login.rb
@@ -12,6 +12,14 @@ module QA
fill_in 'password', with: 'user1pass'
click_on 'Login'
end
+
+ def login_if_required
+ login if login_required?
+ end
+
+ def login_required?
+ page.has_text?('Enter your username and password')
+ end
end
end
end
diff --git a/qa/spec/resource/repository/push_spec.rb b/qa/spec/resource/repository/push_spec.rb
index bf3ebce0cfe..2f9e4958ae1 100644
--- a/qa/spec/resource/repository/push_spec.rb
+++ b/qa/spec/resource/repository/push_spec.rb
@@ -19,7 +19,11 @@ describe QA::Resource::Repository::Push do
expect { subject.files = [] }.to raise_error(ArgumentError)
end
- it 'does not raise if files is an array' do
+ it 'raises an error if files is not an array of hashes with :name and :content keys' do
+ expect { subject.files = [{ foo: 'foo' }] }.to raise_error(ArgumentError)
+ end
+
+ it 'does not raise if files is an array of hashes with :name and :content keys' do
expect { subject.files = files }.not_to raise_error
end
end
diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb
index caf96a213e1..340831aa06d 100644
--- a/qa/spec/runtime/env_spec.rb
+++ b/qa/spec/runtime/env_spec.rb
@@ -192,6 +192,30 @@ describe QA::Runtime::Env do
end
end
+ describe '.knapsack?' do
+ it 'returns true if KNAPSACK_GENERATE_REPORT is defined' do
+ stub_env('KNAPSACK_GENERATE_REPORT', 'true')
+
+ expect(described_class.knapsack?).to be_truthy
+ end
+
+ it 'returns true if KNAPSACK_REPORT_PATH is defined' do
+ stub_env('KNAPSACK_REPORT_PATH', '/a/path')
+
+ expect(described_class.knapsack?).to be_truthy
+ end
+
+ it 'returns true if KNAPSACK_TEST_FILE_PATTERN is defined' do
+ stub_env('KNAPSACK_TEST_FILE_PATTERN', '/a/**/pattern')
+
+ expect(described_class.knapsack?).to be_truthy
+ end
+
+ it 'returns false if neither KNAPSACK_GENERATE_REPORT nor KNAPSACK_REPORT_PATH nor KNAPSACK_TEST_FILE_PATTERN are defined' do
+ expect(described_class.knapsack?).to be_falsey
+ end
+ end
+
describe '.require_github_access_token!' do
it 'raises ArgumentError if GITHUB_ACCESS_TOKEN is not defined' do
stub_env('GITHUB_ACCESS_TOKEN', nil)
diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb
index 84bbbac39b0..0b3833e6515 100644
--- a/spec/controllers/application_controller_spec.rb
+++ b/spec/controllers/application_controller_spec.rb
@@ -641,24 +641,32 @@ describe ApplicationController do
end
end
- it 'does not set a custom header' do
+ it 'sets a custom header' do
get :index, format: :json
- expect(response.headers['X-GitLab-Custom-Error']).to be_nil
+ expect(response.headers['X-GitLab-Custom-Error']).to eq '1'
end
- end
- context 'given a json response for an html request' do
- controller do
- def index
- render json: {}, status: :unprocessable_entity
+ context 'for html request' do
+ it 'sets a custom header' do
+ get :index
+
+ expect(response.headers['X-GitLab-Custom-Error']).to eq '1'
end
end
- it 'does not set a custom header' do
- get :index
+ context 'for 200 response' do
+ controller do
+ def index
+ render json: {}, status: :ok
+ end
+ end
- expect(response.headers['X-GitLab-Custom-Error']).to be_nil
+ it 'does not set a custom header' do
+ get :index, format: :json
+
+ expect(response.headers['X-GitLab-Custom-Error']).to be_nil
+ end
end
end
end
diff --git a/spec/controllers/projects/cycle_analytics_controller_spec.rb b/spec/controllers/projects/cycle_analytics_controller_spec.rb
index 2dc97e18113..5e6ceef2517 100644
--- a/spec/controllers/projects/cycle_analytics_controller_spec.rb
+++ b/spec/controllers/projects/cycle_analytics_controller_spec.rb
@@ -11,6 +11,20 @@ describe Projects::CycleAnalyticsController do
project.add_maintainer(user)
end
+ context "counting page views for 'show'" do
+ it 'increases the counter' do
+ expect(Gitlab::UsageDataCounters::CycleAnalyticsCounter).to receive(:count).with(:views)
+
+ get(:show,
+ params: {
+ namespace_id: project.namespace,
+ project_id: project
+ })
+
+ expect(response).to be_success
+ end
+ end
+
describe 'cycle analytics not set up flag' do
context 'with no data' do
it 'is true' do
diff --git a/spec/controllers/registrations_controller_spec.rb b/spec/controllers/registrations_controller_spec.rb
index faf3c990cb2..d05482f095e 100644
--- a/spec/controllers/registrations_controller_spec.rb
+++ b/spec/controllers/registrations_controller_spec.rb
@@ -5,6 +5,10 @@ require 'spec_helper'
describe RegistrationsController do
include TermsHelper
+ before do
+ stub_feature_flags(invisible_captcha: false)
+ end
+
describe '#create' do
let(:base_user_params) { { name: 'new_user', username: 'new_username', email: 'new@user.com', password: 'Any_password' } }
let(:user_params) { { user: base_user_params } }
@@ -88,6 +92,88 @@ describe RegistrationsController do
end
end
+ context 'when invisible captcha is enabled' do
+ before do
+ stub_feature_flags(invisible_captcha: true)
+ InvisibleCaptcha.timestamp_threshold = treshold
+ end
+
+ let(:treshold) { 4 }
+ let(:session_params) { { invisible_captcha_timestamp: form_rendered_time.iso8601 } }
+ let(:form_rendered_time) { Time.current }
+ let(:submit_time) { form_rendered_time + treshold }
+ let(:auth_log_attributes) do
+ {
+ message: auth_log_message,
+ env: :invisible_captcha_signup_bot_detected,
+ ip: '0.0.0.0',
+ request_method: 'POST',
+ fullpath: '/users'
+ }
+ end
+
+ describe 'the honeypot has not been filled and the signup form has not been submitted too quickly' do
+ it 'creates an account' do
+ travel_to(submit_time) do
+ expect { post(:create, params: user_params, session: session_params) }.to change(User, :count).by(1)
+ end
+ end
+ end
+
+ describe 'honeypot spam detection' do
+ let(:user_params) { super().merge(firstname: 'Roy', lastname: 'Batty') }
+ let(:auth_log_message) { 'Invisible_Captcha_Honeypot_Request' }
+
+ it 'logs the request, refuses to create an account and renders an empty body' do
+ travel_to(submit_time) do
+ expect(Gitlab::Metrics).to receive(:counter)
+ .with(:bot_blocked_by_invisible_captcha_honeypot, 'Counter of blocked sign up attempts with filled honeypot')
+ .and_call_original
+ expect(Gitlab::AuthLogger).to receive(:error).with(auth_log_attributes).once
+ expect { post(:create, params: user_params, session: session_params) }.not_to change(User, :count)
+ expect(response).to have_gitlab_http_status(200)
+ expect(response.body).to be_empty
+ end
+ end
+ end
+
+ describe 'timestamp spam detection' do
+ let(:auth_log_message) { 'Invisible_Captcha_Timestamp_Request' }
+
+ context 'the sign up form has been submitted without the invisible_captcha_timestamp parameter' do
+ let(:session_params) { nil }
+
+ it 'logs the request, refuses to create an account and displays a flash alert' do
+ travel_to(submit_time) do
+ expect(Gitlab::Metrics).to receive(:counter)
+ .with(:bot_blocked_by_invisible_captcha_timestamp, 'Counter of blocked sign up attempts with invalid timestamp')
+ .and_call_original
+ expect(Gitlab::AuthLogger).to receive(:error).with(auth_log_attributes).once
+ expect { post(:create, params: user_params, session: session_params) }.not_to change(User, :count)
+ expect(response).to redirect_to(new_user_session_path)
+ expect(flash[:alert]).to include 'That was a bit too quick! Please resubmit.'
+ end
+ end
+ end
+
+ context 'the sign up form has been submitted too quickly' do
+ let(:submit_time) { form_rendered_time }
+
+ it 'logs the request, refuses to create an account and displays a flash alert' do
+ travel_to(submit_time) do
+ expect(Gitlab::Metrics).to receive(:counter)
+ .with(:bot_blocked_by_invisible_captcha_timestamp, 'Counter of blocked sign up attempts with invalid timestamp')
+ .and_call_original
+ expect(Gitlab::AuthLogger).to receive(:error).with(auth_log_attributes).once
+ expect { post(:create, params: user_params, session: session_params) }.not_to change(User, :count)
+ expect(response).to redirect_to(new_user_session_path)
+ expect(flash[:alert]).to include 'That was a bit too quick! Please resubmit.'
+ end
+ end
+ end
+ end
+ end
+
context 'when terms are enforced' do
before do
enforce_terms
diff --git a/spec/features/invites_spec.rb b/spec/features/invites_spec.rb
index 855cf22642e..832c4a57aa3 100644
--- a/spec/features/invites_spec.rb
+++ b/spec/features/invites_spec.rb
@@ -10,6 +10,7 @@ describe 'Invites' do
let(:group_invite) { group.group_members.invite.last }
before do
+ stub_feature_flags(invisible_captcha: false)
project.add_maintainer(owner)
group.add_user(owner, Gitlab::Access::OWNER)
group.add_developer('user@example.com', owner)
diff --git a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
index 5e52c82a234..4dbdea02e27 100644
--- a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
+++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb
@@ -38,7 +38,7 @@ describe 'User visits the profile preferences page' do
describe 'User changes their default dashboard', :js do
it 'creates a flash message' do
- select 'Starred Projects', from: 'user_dashboard'
+ select2('stars', from: '#user_dashboard')
click_button 'Save'
wait_for_requests
@@ -47,7 +47,7 @@ describe 'User visits the profile preferences page' do
end
it 'updates their preference' do
- select 'Starred Projects', from: 'user_dashboard'
+ select2('stars', from: '#user_dashboard')
click_button 'Save'
wait_for_requests
diff --git a/spec/features/users/login_spec.rb b/spec/features/users/login_spec.rb
index dac8c8e7a29..1d8c9e7e426 100644
--- a/spec/features/users/login_spec.rb
+++ b/spec/features/users/login_spec.rb
@@ -95,6 +95,42 @@ describe 'Login' do
end
end
+ describe 'with an unconfirmed email address' do
+ let!(:user) { create(:user, confirmed_at: nil) }
+ let(:grace_period) { 2.days }
+
+ before do
+ stub_application_setting(send_user_confirmation_email: true)
+ allow(User).to receive(:allow_unconfirmed_access_for).and_return grace_period
+ end
+
+ context 'within the grace period' do
+ it 'allows to login' do
+ expect(authentication_metrics).to increment(:user_authenticated_counter)
+
+ gitlab_sign_in(user)
+
+ expect(page).not_to have_content('You have to confirm your email address before continuing.')
+ expect(page).not_to have_link('Resend confirmation email', href: new_user_confirmation_path)
+ end
+ end
+
+ context 'when the confirmation grace period is expired' do
+ it 'prevents the user from logging in and renders a resend confirmation email link' do
+ travel_to((grace_period + 1.day).from_now) do
+ expect(authentication_metrics)
+ .to increment(:user_unauthenticated_counter)
+ .and increment(:user_session_destroyed_counter).twice
+
+ gitlab_sign_in(user)
+
+ expect(page).to have_content('You have to confirm your email address before continuing.')
+ expect(page).to have_link('Resend confirmation email', href: new_user_confirmation_path)
+ end
+ end
+ end
+ end
+
describe 'with the ghost user' do
it 'disallows login' do
expect(authentication_metrics)
diff --git a/spec/features/users/signup_spec.rb b/spec/features/users/signup_spec.rb
index f5897bffaf0..cf57fafc4f5 100644
--- a/spec/features/users/signup_spec.rb
+++ b/spec/features/users/signup_spec.rb
@@ -5,6 +5,10 @@ require 'spec_helper'
describe 'Signup' do
include TermsHelper
+ before do
+ stub_feature_flags(invisible_captcha: false)
+ end
+
let(:new_user) { build_stubbed(:user) }
describe 'username validation', :js do
diff --git a/spec/helpers/sessions_helper_spec.rb b/spec/helpers/sessions_helper_spec.rb
new file mode 100644
index 00000000000..647771ace92
--- /dev/null
+++ b/spec/helpers/sessions_helper_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe SessionsHelper do
+ describe '#unconfirmed_email?' do
+ it 'returns true when the flash alert contains a devise failure unconfirmed message' do
+ flash[:alert] = t(:unconfirmed, scope: [:devise, :failure])
+ expect(helper.unconfirmed_email?).to be_truthy
+ end
+
+ it 'returns false when the flash alert does not contain a devise failure unconfirmed message' do
+ flash[:alert] = 'something else'
+ expect(helper.unconfirmed_email?).to be_falsey
+ end
+ end
+end
diff --git a/spec/helpers/tracking_helper_spec.rb b/spec/helpers/tracking_helper_spec.rb
index 71505e8ea69..b0c98be4130 100644
--- a/spec/helpers/tracking_helper_spec.rb
+++ b/spec/helpers/tracking_helper_spec.rb
@@ -4,8 +4,32 @@ require 'spec_helper'
describe TrackingHelper do
describe '#tracking_attrs' do
- it 'returns an empty hash' do
- expect(helper.tracking_attrs('a', 'b', 'c')).to eq({})
+ using RSpec::Parameterized::TableSyntax
+
+ let(:input) { %w(a b c) }
+ let(:results) do
+ {
+ no_data: {},
+ with_data: { data: { track_label: 'a', track_event: 'b', track_property: 'c' } }
+ }
+ end
+
+ where(:snowplow_enabled, :environment, :result) do
+ true | 'production' | :with_data
+ false | 'production' | :no_data
+ true | 'development' | :no_data
+ false | 'development' | :no_data
+ true | 'test' | :no_data
+ false | 'test' | :no_data
+ end
+
+ with_them do
+ it 'returns a hash' do
+ stub_application_setting(snowplow_enabled: snowplow_enabled)
+ allow(Rails).to receive(:env).and_return(environment.inquiry)
+
+ expect(helper.tracking_attrs(*input)).to eq(results[result])
+ end
end
end
end
diff --git a/spec/javascripts/monitoring/charts/area_spec.js b/spec/javascripts/monitoring/charts/area_spec.js
index 4541119dd2e..57f99a09002 100644
--- a/spec/javascripts/monitoring/charts/area_spec.js
+++ b/spec/javascripts/monitoring/charts/area_spec.js
@@ -24,7 +24,6 @@ describe('Area component', () => {
store.commit(`monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`, MonitoringMock.data);
store.commit(`monitoringDashboard/${types.RECEIVE_DEPLOYMENTS_DATA_SUCCESS}`, deploymentData);
- store.dispatch('monitoringDashboard/setFeatureFlags', { exportMetricsToCsvEnabled: true });
[mockGraphData] = store.state.monitoringDashboard.groups[0].metrics;
areaChart = shallowMount(Area, {
@@ -109,16 +108,6 @@ describe('Area component', () => {
});
});
- describe('when exportMetricsToCsvEnabled is disabled', () => {
- beforeEach(() => {
- store.dispatch('monitoringDashboard/setFeatureFlags', { exportMetricsToCsvEnabled: false });
- });
-
- it('does not render the Download CSV button', () => {
- expect(areaChart.contains('glbutton-stub')).toBe(false);
- });
- });
-
describe('methods', () => {
describe('formatTooltipText', () => {
const mockDate = deploymentData[0].created_at;
@@ -264,23 +253,5 @@ describe('Area component', () => {
expect(areaChart.vm.yAxisLabel).toBe('CPU');
});
});
-
- describe('csvText', () => {
- it('converts data from json to csv', () => {
- const header = `timestamp,${mockGraphData.y_label}`;
- const data = mockGraphData.queries[0].result[0].values;
- const firstRow = `${data[0][0]},${data[0][1]}`;
-
- expect(areaChart.vm.csvText).toMatch(`^${header}\r\n${firstRow}`);
- });
- });
-
- describe('downloadLink', () => {
- it('produces a link to download metrics as csv', () => {
- const link = areaChart.vm.downloadLink;
-
- expect(link).toContain('blob:');
- });
- });
});
});
diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js
index 36f650d5933..b78896c45fc 100644
--- a/spec/javascripts/monitoring/dashboard_spec.js
+++ b/spec/javascripts/monitoring/dashboard_spec.js
@@ -5,7 +5,7 @@ import { timeWindows, timeWindowsKeyNames } from '~/monitoring/constants';
import * as types from '~/monitoring/stores/mutation_types';
import { createStore } from '~/monitoring/stores';
import axios from '~/lib/utils/axios_utils';
-import {
+import MonitoringMock, {
metricsGroupsAPIResponse,
mockApiEndpoint,
environmentData,
@@ -40,6 +40,7 @@ describe('Dashboard', () => {
let mock;
let store;
let component;
+ let mockGraphData;
beforeEach(() => {
setFixtures(`
@@ -482,4 +483,36 @@ describe('Dashboard', () => {
});
});
});
+
+ describe('when downloading metrics data as CSV', () => {
+ beforeEach(() => {
+ component = new DashboardComponent({
+ propsData: {
+ ...propsData,
+ },
+ store,
+ });
+ store.commit(
+ `monitoringDashboard/${types.RECEIVE_METRICS_DATA_SUCCESS}`,
+ MonitoringMock.data,
+ );
+ [mockGraphData] = component.$store.state.monitoringDashboard.groups[0].metrics;
+ });
+
+ describe('csvText', () => {
+ it('converts metrics data from json to csv', () => {
+ const header = `timestamp,${mockGraphData.y_label}`;
+ const data = mockGraphData.queries[0].result[0].values;
+ const firstRow = `${data[0][0]},${data[0][1]}`;
+
+ expect(component.csvText(mockGraphData)).toMatch(`^${header}\r\n${firstRow}`);
+ });
+ });
+
+ describe('downloadCsv', () => {
+ it('produces a link with a Blob', () => {
+ expect(component.downloadCsv(mockGraphData)).toContain(`blob:`);
+ });
+ });
+ });
});
diff --git a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
index ba3ba01944d..53e1f077610 100644
--- a/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
+++ b/spec/javascripts/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js
@@ -236,24 +236,26 @@ describe('ReadyToMerge', () => {
});
});
- describe('shouldShowMergeOptionsDropdown', () => {
- it('should return false when no auto merge strategies are available', () => {
- Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
+ describe('shouldShowMergeImmediatelyDropdown', () => {
+ it('should return false if no pipeline is active', () => {
+ Vue.set(vm.mr, 'isPipelineActive', false);
+ Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', false);
- expect(vm.shouldShowMergeOptionsDropdown).toBe(false);
+ expect(vm.shouldShowMergeImmediatelyDropdown).toBe(false);
});
- it('should return true when at least one auto merge strategy is available', () => {
- Vue.set(vm.mr, 'availableAutoMergeStrategies', [ATMTWPS_MERGE_STRATEGY]);
+ it('should return false if "Pipelines must succeed" is enabled for the current project', () => {
+ Vue.set(vm.mr, 'isPipelineActive', true);
+ Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', true);
- expect(vm.shouldShowMergeOptionsDropdown).toBe(true);
+ expect(vm.shouldShowMergeImmediatelyDropdown).toBe(false);
});
- it('should return false when pipeline active but only merge when pipeline succeeds set in project options', () => {
- Vue.set(vm.mr, 'availableAutoMergeStrategies', [ATMTWPS_MERGE_STRATEGY]);
- Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', true);
+ it('should return true if the MR\'s pipeline is active and "Pipelines must succeed" is not enabled for the current project', () => {
+ Vue.set(vm.mr, 'isPipelineActive', true);
+ Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', false);
- expect(vm.shouldShowMergeOptionsDropdown).toBe(false);
+ expect(vm.shouldShowMergeImmediatelyDropdown).toBe(true);
});
});
diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js
index 253413ae43e..a55d5537df7 100644
--- a/spec/javascripts/vue_mr_widget/mock_data.js
+++ b/spec/javascripts/vue_mr_widget/mock_data.js
@@ -233,6 +233,8 @@ export default {
'http://localhost:3000/root/acets-app/commit/53027d060246c8f47e4a9310fb332aa52f221775',
troubleshooting_docs_path: 'help',
merge_request_pipelines_docs_path: '/help/ci/merge_request_pipelines/index.md',
+ merge_train_when_pipeline_succeeds_docs_path:
+ '/help/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/#startadd-to-merge-train-when-pipeline-succeeds',
squash: true,
visual_review_app_available: true,
merge_trains_enabled: true,
diff --git a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
index 762025f9bd9..5d4dec5899a 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/build_spec.rb
@@ -386,17 +386,28 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
describe 'applying needs: dependency' do
subject { seed_build }
+ let(:needs_count) { 1 }
+
+ let(:needs_attributes) do
+ Array.new(needs_count, name: 'build')
+ end
+
let(:attributes) do
{
name: 'rspec',
- needs_attributes: [{
- name: 'build'
- }]
+ needs_attributes: needs_attributes
}
end
context 'when build job is not present in prior stages' do
- it { is_expected.not_to be_included }
+ it "is included" do
+ is_expected.to be_included
+ end
+
+ it "returns an error" do
+ expect(subject.errors).to contain_exactly(
+ "rspec: needs 'build'")
+ end
end
context 'when build job is part of prior stages' do
@@ -414,7 +425,39 @@ describe Gitlab::Ci::Pipeline::Seed::Build do
let(:previous_stages) { [stage_seed] }
- it { is_expected.to be_included }
+ it "is included" do
+ is_expected.to be_included
+ end
+
+ it "does not have errors" do
+ expect(subject.errors).to be_empty
+ end
+ end
+
+ context 'when lower limit of needs is reached' do
+ before do
+ stub_feature_flags(ci_dag_limit_needs: true)
+ end
+
+ let(:needs_count) { described_class::LOW_NEEDS_LIMIT + 1 }
+
+ it "returns an error" do
+ expect(subject.errors).to contain_exactly(
+ "rspec: one job can only need 5 others, but you have listed 6. See needs keyword documentation for more details")
+ end
+ end
+
+ context 'when upper limit of needs is reached' do
+ before do
+ stub_feature_flags(ci_dag_limit_needs: false)
+ end
+
+ let(:needs_count) { described_class::HARD_NEEDS_LIMIT + 1 }
+
+ it "returns an error" do
+ expect(subject.errors).to contain_exactly(
+ "rspec: one job can only need 50 others, but you have listed 51. See needs keyword documentation for more details")
+ end
end
end
end
diff --git a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
index 6fba9f37d91..a13335f63d5 100644
--- a/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
+++ b/spec/lib/gitlab/ci/pipeline/seed/stage_spec.rb
@@ -121,6 +121,16 @@ describe Gitlab::Ci::Pipeline::Seed::Stage do
end
end
+ describe '#seeds_errors' do
+ it 'returns all errors from seeds' do
+ expect(subject.seeds.first)
+ .to receive(:errors) { ["build error"] }
+
+ expect(subject.errors).to contain_exactly(
+ "build error")
+ end
+ end
+
describe '#to_resource' do
it 'builds a valid stage object with all builds' do
subject.to_resource.save!
diff --git a/spec/lib/gitlab/data_builder/push_spec.rb b/spec/lib/gitlab/data_builder/push_spec.rb
index cc31f88d365..e8a9f0b06a8 100644
--- a/spec/lib/gitlab/data_builder/push_spec.rb
+++ b/spec/lib/gitlab/data_builder/push_spec.rb
@@ -3,9 +3,43 @@
require 'spec_helper'
describe Gitlab::DataBuilder::Push do
+ include RepoHelpers
+
let(:project) { create(:project, :repository) }
let(:user) { build(:user, public_email: 'public-email@example.com') }
+ describe '.build' do
+ let(:sample) { RepoHelpers.sample_compare }
+ let(:commits) { project.repository.commits_between(sample.commits.first, sample.commits.last) }
+ let(:subject) do
+ described_class.build(project: project,
+ user: user,
+ ref: sample.target_branch,
+ commits: commits,
+ commits_count: commits.length,
+ message: 'test message',
+ with_changed_files: with_changed_files)
+ end
+
+ context 'with changed files' do
+ let(:with_changed_files) { true }
+
+ it 'returns commit hook data' do
+ expect(subject[:project]).to eq(project.hook_attrs)
+ expect(subject[:commits].first.keys).to include(*%i(added removed modified))
+ end
+ end
+
+ context 'without changed files' do
+ let(:with_changed_files) { false }
+
+ it 'returns commit hook data without include deltas' do
+ expect(subject[:project]).to eq(project.hook_attrs)
+ expect(subject[:commits].first.keys).not_to include(*%i(added removed modified))
+ end
+ end
+ end
+
describe '.build_sample' do
let(:data) { described_class.build_sample(project, user) }
diff --git a/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb b/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb
new file mode 100644
index 00000000000..f24ab5579df
--- /dev/null
+++ b/spec/lib/gitlab/kubernetes/kubectl_cmd_spec.rb
@@ -0,0 +1,48 @@
+# frozen_string_literal: true
+
+require 'fast_spec_helper'
+
+describe Gitlab::Kubernetes::KubectlCmd do
+ describe '.delete' do
+ it 'constructs string properly' do
+ args = %w(resource_type type --flag-1 --flag-2)
+
+ expected_command = 'kubectl delete resource_type type --flag-1 --flag-2'
+
+ expect(described_class.delete(*args)).to eq expected_command
+ end
+ end
+
+ describe '.apply_file' do
+ context 'without optional args' do
+ it 'requires filename to be present' do
+ expect { described_class.apply_file(nil) }.to raise_error(ArgumentError, "filename is not present")
+ expect { described_class.apply_file(" ") }.to raise_error(ArgumentError, "filename is not present")
+ end
+
+ it 'constructs string properly' do
+ expected_command = 'kubectl apply -f filename'
+
+ expect(described_class.apply_file('filename')).to eq expected_command
+ end
+ end
+
+ context 'with optional args' do
+ it 'constructs command properly with many args' do
+ args = %w(arg-1 --flag-0-1 arg-2 --flag-0-2)
+
+ expected_command = 'kubectl apply -f filename arg-1 --flag-0-1 arg-2 --flag-0-2'
+
+ expect(described_class.apply_file('filename', *args)).to eq expected_command
+ end
+
+ it 'constructs command properly with single arg' do
+ args = "arg-1"
+
+ expected_command = 'kubectl apply -f filename arg-1'
+
+ expect(described_class.apply_file('filename', args)).to eq(expected_command)
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
index f4a6e1fc7d9..b8add3c1324 100644
--- a/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
+++ b/spec/lib/gitlab/metrics/samplers/puma_sampler_spec.rb
@@ -46,8 +46,6 @@ describe Gitlab::Metrics::Samplers::PumaSampler do
expect(subject.metrics[:puma_workers]).to receive(:set).with(labels, 2)
expect(subject.metrics[:puma_running_workers]).to receive(:set).with(labels, 2)
expect(subject.metrics[:puma_stale_workers]).to receive(:set).with(labels, 0)
- expect(subject.metrics[:puma_phase]).to receive(:set).once.with(labels, 2)
- expect(subject.metrics[:puma_phase]).to receive(:set).once.with({ worker: 'worker_0' }, 1)
subject.sample
end
diff --git a/spec/lib/gitlab/snowplow_tracker_spec.rb b/spec/lib/gitlab/snowplow_tracker_spec.rb
new file mode 100644
index 00000000000..073a33e5973
--- /dev/null
+++ b/spec/lib/gitlab/snowplow_tracker_spec.rb
@@ -0,0 +1,45 @@
+# frozen_string_literal: true
+require 'spec_helper'
+
+describe Gitlab::SnowplowTracker do
+ let(:timestamp) { Time.utc(2017, 3, 22) }
+
+ around do |example|
+ Timecop.freeze(timestamp) { example.run }
+ end
+
+ subject { described_class.track_event('epics', 'action', property: 'what', value: 'doit') }
+
+ context '.track_event' do
+ context 'when Snowplow tracker is disabled' do
+ it 'does not track the event' do
+ expect(SnowplowTracker::Tracker).not_to receive(:new)
+
+ subject
+ end
+ end
+
+ context 'when Snowplow tracker is enabled' do
+ before do
+ stub_application_setting(snowplow_enabled: true)
+ stub_application_setting(snowplow_site_id: 'awesome gitlab')
+ stub_application_setting(snowplow_collector_hostname: 'url.com')
+ end
+
+ it 'tracks the event' do
+ tracker = double
+
+ expect(::SnowplowTracker::Tracker).to receive(:new)
+ .with(
+ an_instance_of(::SnowplowTracker::Emitter),
+ an_instance_of(::SnowplowTracker::Subject),
+ 'cf', 'awesome gitlab'
+ ).and_return(tracker)
+ expect(tracker).to receive(:track_struct_event)
+ .with('epics', 'action', nil, 'what', 'doit', nil, timestamp.to_i)
+
+ subject
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb b/spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb
new file mode 100644
index 00000000000..71be37692e2
--- /dev/null
+++ b/spec/lib/gitlab/usage_data_counters/cycle_analytics_counter_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::UsageDataCounters::CycleAnalyticsCounter do
+ it_behaves_like 'a redis usage counter', 'CycleAnalytics', :views
+
+ it_behaves_like 'a redis usage counter with totals', :cycle_analytics, views: 3
+end
diff --git a/spec/lib/gitlab/usage_data_spec.rb b/spec/lib/gitlab/usage_data_spec.rb
index f63f3b454e7..588c68d1fb0 100644
--- a/spec/lib/gitlab/usage_data_spec.rb
+++ b/spec/lib/gitlab/usage_data_spec.rb
@@ -59,6 +59,7 @@ describe Gitlab::UsageData do
avg_cycle_analytics
influxdb_metrics_enabled
prometheus_metrics_enabled
+ cycle_analytics_views
))
expect(subject).to include(
@@ -72,6 +73,7 @@ describe Gitlab::UsageData do
web_ide_commits: a_kind_of(Integer),
web_ide_merge_requests: a_kind_of(Integer),
navbar_searches: a_kind_of(Integer),
+ cycle_analytics_views: a_kind_of(Integer),
source_code_pushes: a_kind_of(Integer)
)
end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 83ce58235dd..2afe1253e29 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -4312,6 +4312,39 @@ describe Project do
end
end
+ describe '#has_active_hooks?' do
+ set(:project) { create(:project) }
+
+ it { expect(project.has_active_hooks?).to be_falsey }
+
+ it 'returns true when a matching push hook exists' do
+ create(:project_hook, push_events: true, project: project)
+
+ expect(project.has_active_hooks?(:merge_request_events)).to be_falsey
+ expect(project.has_active_hooks?).to be_truthy
+ end
+
+ it 'returns true when a matching system hook exists' do
+ create(:system_hook, push_events: true)
+
+ expect(project.has_active_hooks?(:merge_request_events)).to be_falsey
+ expect(project.has_active_hooks?).to be_truthy
+ end
+ end
+
+ describe '#has_active_services?' do
+ set(:project) { create(:project) }
+
+ it { expect(project.has_active_services?).to be_falsey }
+
+ it 'returns true when a matching service exists' do
+ create(:custom_issue_tracker_service, push_events: true, merge_requests_events: false, project: project)
+
+ expect(project.has_active_services?(:merge_request_hooks)).to be_falsey
+ expect(project.has_active_services?).to be_truthy
+ end
+ end
+
describe '#badges' do
let(:project_group) { create(:group) }
let(:project) { create(:project, path: 'avatar', namespace: project_group) }
diff --git a/spec/policies/global_policy_spec.rb b/spec/policies/global_policy_spec.rb
index 12be3927e18..df6cc526eb0 100644
--- a/spec/policies/global_policy_spec.rb
+++ b/spec/policies/global_policy_spec.rb
@@ -226,4 +226,32 @@ describe GlobalPolicy do
it { is_expected.not_to be_allowed(:read_instance_statistics) }
end
end
+
+ describe 'slash commands' do
+ context 'regular user' do
+ it { is_expected.to be_allowed(:use_slash_commands) }
+ end
+
+ context 'when internal' do
+ let(:current_user) { User.ghost }
+
+ it { is_expected.not_to be_allowed(:use_slash_commands) }
+ end
+
+ context 'when blocked' do
+ before do
+ current_user.block
+ end
+
+ it { is_expected.not_to be_allowed(:use_slash_commands) }
+ end
+
+ context 'when access locked' do
+ before do
+ current_user.lock_access!
+ end
+
+ it { is_expected.not_to be_allowed(:use_slash_commands) }
+ end
+ end
end
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
index 184c00a356a..590107d5161 100644
--- a/spec/requests/api/settings_spec.rb
+++ b/spec/requests/api/settings_spec.rb
@@ -144,6 +144,7 @@ describe API::Settings, 'Settings' do
external_auth_client_key_pass: "5iveL!fe"
}
end
+
let(:attribute_names) { settings.keys.map(&:to_s) }
it 'includes the attributes in the API' do
@@ -165,6 +166,56 @@ describe API::Settings, 'Settings' do
end
end
+ context "snowplow tracking settings" do
+ let(:settings) do
+ {
+ snowplow_collector_hostname: "snowplow.example.com",
+ snowplow_cookie_domain: ".example.com",
+ snowplow_enabled: true,
+ snowplow_site_id: "site_id"
+ }
+ end
+
+ let(:attribute_names) { settings.keys.map(&:to_s) }
+
+ it "includes the attributes in the API" do
+ get api("/application/settings", admin)
+
+ expect(response).to have_gitlab_http_status(200)
+ attribute_names.each do |attribute|
+ expect(json_response.keys).to include(attribute)
+ end
+ end
+
+ it "allows updating the settings" do
+ put api("/application/settings", admin), params: settings
+
+ expect(response).to have_gitlab_http_status(200)
+ settings.each do |attribute, value|
+ expect(ApplicationSetting.current.public_send(attribute)).to eq(value)
+ end
+ end
+
+ context "missing snowplow_collector_hostname value when snowplow_enabled is true" do
+ it "returns a blank parameter error message" do
+ put api("/application/settings", admin), params: { snowplow_enabled: true }
+
+ expect(response).to have_gitlab_http_status(400)
+ expect(json_response["error"]).to eq("snowplow_collector_hostname is missing")
+ end
+
+ it "handles validation errors" do
+ put api("/application/settings", admin), params: settings.merge({
+ snowplow_collector_hostname: nil
+ })
+
+ expect(response).to have_gitlab_http_status(400)
+ message = json_response["message"]
+ expect(message["snowplow_collector_hostname"]).to include("can't be blank")
+ end
+ end
+ end
+
context "missing plantuml_url value when plantuml_enabled is true" do
it "returns a blank parameter error message" do
put api("/application/settings", admin), params: { plantuml_enabled: true }
diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb
index 7e2f311a065..deb68899309 100644
--- a/spec/services/ci/create_pipeline_service_spec.rb
+++ b/spec/services/ci/create_pipeline_service_spec.rb
@@ -1113,7 +1113,7 @@ describe Ci::CreatePipelineService do
test_a: {
stage: "test",
script: "ls",
- only: %w[master feature tags],
+ only: %w[master feature],
needs: %w[build_a]
},
deploy: {
@@ -1143,6 +1143,7 @@ describe Ci::CreatePipelineService do
it 'does not create a pipeline as test_a depends on build_a' do
expect(pipeline).not_to be_persisted
expect(pipeline.builds).to be_empty
+ expect(pipeline.errors[:base]).to contain_exactly("test_a: needs 'build_a'")
end
end
diff --git a/spec/services/git/base_hooks_service_spec.rb b/spec/services/git/base_hooks_service_spec.rb
index 4a2ec769116..874df9a68cd 100644
--- a/spec/services/git/base_hooks_service_spec.rb
+++ b/spec/services/git/base_hooks_service_spec.rb
@@ -14,6 +14,78 @@ describe Git::BaseHooksService do
let(:newrev) { "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" } # gitlab-test: git rev-parse refs/tags/v1.1.0
let(:ref) { 'refs/tags/v1.1.0' }
+ describe '#execute_project_hooks' do
+ class TestService < described_class
+ def hook_name
+ :push_hooks
+ end
+
+ def commits
+ []
+ end
+ end
+
+ let(:project) { create(:project, :repository) }
+
+ subject { TestService.new(project, user, oldrev: oldrev, newrev: newrev, ref: ref) }
+
+ context '#execute_hooks' do
+ before do
+ expect(project).to receive(:has_active_hooks?).and_return(active)
+ end
+
+ context 'active hooks' do
+ let(:active) { true }
+
+ it 'executes the hooks' do
+ expect(subject).to receive(:push_data).at_least(:once).and_call_original
+ expect(project).to receive(:execute_hooks)
+
+ subject.execute
+ end
+ end
+
+ context 'inactive hooks' do
+ let(:active) { false }
+
+ it 'does not execute the hooks' do
+ expect(subject).not_to receive(:push_data)
+ expect(project).not_to receive(:execute_hooks)
+
+ subject.execute
+ end
+ end
+ end
+
+ context '#execute_services' do
+ before do
+ expect(project).to receive(:has_active_services?).and_return(active)
+ end
+
+ context 'active services' do
+ let(:active) { true }
+
+ it 'executes the services' do
+ expect(subject).to receive(:push_data).at_least(:once).and_call_original
+ expect(project).to receive(:execute_services)
+
+ subject.execute
+ end
+ end
+
+ context 'inactive services' do
+ let(:active) { false }
+
+ it 'does not execute the services' do
+ expect(subject).not_to receive(:push_data)
+ expect(project).not_to receive(:execute_services)
+
+ subject.execute
+ end
+ end
+ end
+ end
+
describe 'with remote mirrors' do
class TestService < described_class
def commits
diff --git a/spec/services/git/branch_hooks_service_spec.rb b/spec/services/git/branch_hooks_service_spec.rb
index 23be400059e..8af51848b7b 100644
--- a/spec/services/git/branch_hooks_service_spec.rb
+++ b/spec/services/git/branch_hooks_service_spec.rb
@@ -25,7 +25,7 @@ describe Git::BranchHooksService do
end
describe "Git Push Data" do
- subject(:push_data) { service.execute }
+ subject(:push_data) { service.send(:push_data) }
it 'has expected push data attributes' do
is_expected.to match a_hash_including(
@@ -109,6 +109,7 @@ describe Git::BranchHooksService do
expect(event.push_event_payload).to be_an_instance_of(PushEventPayload)
expect(event.push_event_payload.commit_from).to eq(oldrev)
expect(event.push_event_payload.commit_to).to eq(newrev)
+ expect(event.push_event_payload.commit_title).to eq('Change some files')
expect(event.push_event_payload.ref).to eq('master')
expect(event.push_event_payload.commit_count).to eq(1)
end
@@ -124,6 +125,7 @@ describe Git::BranchHooksService do
expect(event.push_event_payload).to be_an_instance_of(PushEventPayload)
expect(event.push_event_payload.commit_from).to be_nil
expect(event.push_event_payload.commit_to).to eq(newrev)
+ expect(event.push_event_payload.commit_title).to eq('Initial commit')
expect(event.push_event_payload.ref).to eq('master')
expect(event.push_event_payload.commit_count).to be > 1
end
diff --git a/spec/services/git/branch_push_service_spec.rb b/spec/services/git/branch_push_service_spec.rb
index 6e39fa6b3c0..ad5d296f5c1 100644
--- a/spec/services/git/branch_push_service_spec.rb
+++ b/spec/services/git/branch_push_service_spec.rb
@@ -78,7 +78,10 @@ describe Git::BranchPushService, services: true do
it "creates a new pipeline" do
expect { subject }.to change { Ci::Pipeline.count }
- expect(Ci::Pipeline.last).to be_push
+
+ pipeline = Ci::Pipeline.last
+ expect(pipeline).to be_push
+ expect(Gitlab::Git::BRANCH_REF_PREFIX + pipeline.ref).to eq(ref)
end
end
@@ -123,6 +126,10 @@ describe Git::BranchPushService, services: true do
describe "Webhooks" do
context "execute webhooks" do
+ before do
+ create(:project_hook, push_events: true, project: project)
+ end
+
it "when pushing a branch for the first time" do
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
diff --git a/spec/services/git/tag_hooks_service_spec.rb b/spec/services/git/tag_hooks_service_spec.rb
index f5938a5c708..e362577d289 100644
--- a/spec/services/git/tag_hooks_service_spec.rb
+++ b/spec/services/git/tag_hooks_service_spec.rb
@@ -26,7 +26,8 @@ describe Git::TagHooksService, :service do
describe 'System hooks' do
it 'Executes system hooks' do
- push_data = service.execute
+ push_data = service.send(:push_data)
+ expect(project).to receive(:has_active_hooks?).and_return(true)
expect_next_instance_of(SystemHooksService) do |system_hooks_service|
expect(system_hooks_service)
@@ -40,6 +41,7 @@ describe Git::TagHooksService, :service do
describe "Webhooks" do
it "executes hooks on the project" do
+ expect(project).to receive(:has_active_hooks?).and_return(true)
expect(project).to receive(:execute_hooks)
service.execute
@@ -61,7 +63,7 @@ describe Git::TagHooksService, :service do
describe 'Push data' do
shared_examples_for 'tag push data expectations' do
- subject(:push_data) { service.execute }
+ subject(:push_data) { service.send(:push_data) }
it 'has expected push data attributes' do
is_expected.to match a_hash_including(
object_kind: 'tag_push',
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index bcc133790d1..bd504f1553b 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -48,6 +48,9 @@ Dir[Rails.root.join("spec/support/**/*.rb")].each { |f| require f }
quality_level = Quality::TestLevel.new
RSpec.configure do |config|
+ config.filter_run focus: true
+ config.run_all_when_everything_filtered = true
+
config.use_transactional_fixtures = true
config.use_instantiated_fixtures = false
config.fixture_path = Rails.root
diff --git a/spec/support/shared_examples/chat_slash_commands_shared_examples.rb b/spec/support/shared_examples/chat_slash_commands_shared_examples.rb
index 82975027e5b..dcc92dda950 100644
--- a/spec/support/shared_examples/chat_slash_commands_shared_examples.rb
+++ b/spec/support/shared_examples/chat_slash_commands_shared_examples.rb
@@ -93,6 +93,19 @@ RSpec.shared_examples 'chat slash commands service' do
subject.trigger(params)
end
+
+ context 'when user is blocked' do
+ before do
+ chat_name.user.block
+ end
+
+ it 'blocks command execution' do
+ expect_any_instance_of(Gitlab::SlashCommands::Command).not_to receive(:execute)
+
+ result = subject.trigger(params)
+ expect(result).to include(text: /^Whoops! This action is not allowed/)
+ end
+ end
end
end
end
diff --git a/spec/views/layouts/_head.html.haml_spec.rb b/spec/views/layouts/_head.html.haml_spec.rb
index cbb4199954a..70cdc08b4b6 100644
--- a/spec/views/layouts/_head.html.haml_spec.rb
+++ b/spec/views/layouts/_head.html.haml_spec.rb
@@ -70,6 +70,23 @@ describe 'layouts/_head' do
expect(rendered).to match('<link rel="stylesheet" media="all" href="/stylesheets/highlight/themes/solarised-light.css" />')
end
+ context 'when an asset_host is set and snowplow url is set' do
+ let(:asset_host) { 'http://test.host' }
+
+ before do
+ allow(ActionController::Base).to receive(:asset_host).and_return(asset_host)
+ allow(Gitlab::CurrentSettings).to receive(:snowplow_enabled?).and_return(true)
+ allow(Gitlab::CurrentSettings).to receive(:snowplow_collector_hostname).and_return('www.snow.plow')
+ end
+
+ it 'add a snowplow script tag with asset host' do
+ render
+ expect(rendered).to match('http://test.host/assets/snowplow/')
+ expect(rendered).to match('window.snowplow')
+ expect(rendered).to match('www.snow.plow')
+ end
+ end
+
def stub_helper_with_safe_string(method)
allow_any_instance_of(PageLayoutHelper).to receive(method)
.and_return(%q{foo" http-equiv="refresh}.html_safe)
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index 4c9fc0e3a24..3b69b81f12e 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -273,6 +273,8 @@ describe PostReceive do
end
it "asks the project to trigger all hooks" do
+ create(:project_hook, push_events: true, tag_push_events: true, project: project)
+ create(:custom_issue_tracker_service, push_events: true, merge_requests_events: false, project: project)
allow(Project).to receive(:find_by).and_return(project)
expect(project).to receive(:execute_hooks).twice