summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-03-20 21:14:25 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2023-03-20 21:14:25 +0000
commit40a329556f63d919a68dd80bb37b087d1f0e6336 (patch)
tree86c2712bf48f66d0e7dbe7e115e5f01c78193ad0
parent96b01499986c01d0c95176860a7606b7616a3836 (diff)
downloadgitlab-ce-40a329556f63d919a68dd80bb37b087d1f0e6336.tar.gz
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/ci/frontend.gitlab-ci.yml43
-rw-r--r--.rubocop_todo/rake/require.yml1
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--Gemfile2
-rw-r--r--Gemfile.checksum2
-rw-r--r--Gemfile.lock4
-rw-r--r--app/assets/javascripts/admin/abuse_reports/components/abuse_reports_filtered_search_bar.vue4
-rw-r--r--app/assets/javascripts/admin/abuse_reports/utils.js5
-rw-r--r--app/assets/javascripts/issues/show/components/app.vue14
-rw-r--r--app/assets/javascripts/issues/show/components/description.vue28
-rw-r--r--app/assets/javascripts/issues/show/components/edited.vue78
-rw-r--r--app/controllers/concerns/issuable_actions.rb2
-rw-r--r--app/controllers/concerns/product_analytics_tracking.rb40
-rw-r--r--app/controllers/projects/pipelines_controller.rb1
-rw-r--r--app/finders/abuse_reports_finder.rb7
-rw-r--r--app/helpers/ci/pipelines_helper.rb2
-rw-r--r--app/helpers/ide_helper.rb1
-rw-r--r--app/helpers/issuables_helper.rb5
-rw-r--r--app/services/notes/create_service.rb24
-rw-r--r--app/workers/namespaces/root_statistics_worker.rb16
-rw-r--r--app/workers/namespaces/schedule_aggregation_worker.rb16
-rw-r--r--config/feature_flags/development/remove_aggregation_schedule_lease.yml8
-rw-r--r--config/feature_flags/development/route_hll_to_snowplow_phase4.yml8
-rw-r--r--doc/administration/geo/disaster_recovery/background_verification.md24
-rw-r--r--doc/architecture/blueprints/ci_pipeline_components/index.md2
-rw-r--r--doc/install/docker.md24
-rw-r--r--doc/user/compliance/license_scanning_of_cyclonedx_files/index.md7
-rw-r--r--doc/user/group/saml_sso/index.md26
-rw-r--r--doc/user/project/wiki/group.md2
-rw-r--r--lib/tasks/tokens.rake10
-rw-r--r--locale/gitlab.pot6
-rw-r--r--scripts/gitlab_component_helpers.sh89
-rw-r--r--scripts/rspec_helpers.sh5
-rw-r--r--spec/controllers/concerns/product_analytics_tracking_spec.rb14
-rw-r--r--spec/controllers/projects/blob_controller_spec.rb2
-rw-r--r--spec/controllers/projects/issues_controller_spec.rb2
-rw-r--r--spec/experiments/application_experiment_spec.rb31
-rw-r--r--spec/experiments/security_reports_mr_widget_prompt_experiment_spec.rb2
-rw-r--r--spec/features/issues/incident_issue_spec.rb4
-rw-r--r--spec/finders/abuse_reports_finder_spec.rb18
-rw-r--r--spec/frontend/admin/abuse_reports/components/abuse_reports_filtered_search_bar_spec.js33
-rw-r--r--spec/frontend/admin/abuse_reports/utils_spec.js22
-rw-r--r--spec/frontend/issues/show/components/app_spec.js17
-rw-r--r--spec/frontend/issues/show/components/description_spec.js61
-rw-r--r--spec/frontend/issues/show/components/edited_spec.js73
-rw-r--r--spec/frontend/issues/show/mock_data/mock_data.js8
-rw-r--r--spec/frontend/work_items/components/notes/work_item_note_spec.js3
-rw-r--r--spec/frontend/work_items/components/work_item_description_spec.js2
-rw-r--r--spec/helpers/issuables_helper_spec.rb2
-rw-r--r--spec/lib/gitlab/experiment/rollout/feature_spec.rb2
-rw-r--r--spec/services/notes/create_service_spec.rb1
-rw-r--r--spec/workers/namespaces/root_statistics_worker_spec.rb120
-rw-r--r--spec/workers/namespaces/schedule_aggregation_worker_spec.rb100
53 files changed, 594 insertions, 431 deletions
diff --git a/.gitlab/ci/frontend.gitlab-ci.yml b/.gitlab/ci/frontend.gitlab-ci.yml
index c5d992cab63..4d89b5718f9 100644
--- a/.gitlab/ci/frontend.gitlab-ci.yml
+++ b/.gitlab/ci/frontend.gitlab-ci.yml
@@ -116,6 +116,27 @@ update-storybook-yarn-cache:
script:
- yarn_install_script
+retrieve-frontend-fixtures:
+ variables:
+ SETUP_DB: "false"
+ extends:
+ - .default-retry
+ - .frontend:rules:default-frontend-jobs
+ stage: prepare
+ script:
+ - export FIXTURES_SHA="${CI_MERGE_REQUEST_TARGET_BRANCH_SHA:-${CI_MERGE_REQUEST_DIFF_BASE_SHA:-$CI_COMMIT_SHA}}"
+ - source scripts/utils.sh
+ - source scripts/gitlab_component_helpers.sh
+ - |
+ if check_fixtures_download; then
+ run_timed_command "download_and_extract_fixtures"
+ fi
+ artifacts:
+ paths:
+ - tmp/tests/frontend/
+
+# Download fixtures only when a merge request contains changes to only JS files
+# and fixtures are present in the package registry.
.frontend-fixtures-base:
extends:
- .default-retry
@@ -125,8 +146,18 @@ update-storybook-yarn-cache:
stage: fixtures
needs: ["setup-test-env", "retrieve-tests-metadata"]
variables:
+ CRYSTALBALL: "false"
WEBPACK_VENDOR_DLL: "true"
script:
+ - export FIXTURES_SHA="${CI_MERGE_REQUEST_TARGET_BRANCH_SHA:-${CI_MERGE_REQUEST_DIFF_BASE_SHA:-$CI_COMMIT_SHA}}"
+ - source scripts/utils.sh
+ - source scripts/gitlab_component_helpers.sh
+ - |
+ if check_fixtures_download; then
+ exit 0
+ else
+ echo "No frontend fixtures directory, generating frontend fixtures."
+ fi
- run_timed_command "gem install knapsack --no-document"
- run_timed_command "scripts/gitaly-test-spawn"
- source ./scripts/rspec_helpers.sh
@@ -157,6 +188,11 @@ rspec-all frontend_fixture as-if-foss:
- .frontend-fixtures-base
- .frontend:rules:frontend_fixture-as-if-foss
- .as-if-foss
+ variables:
+ CRYSTALBALL: "false"
+ WEBPACK_VENDOR_DLL: "true"
+ KNAPSACK_GENERATE_REPORT: ""
+ FLAKY_RSPEC_GENERATE_REPORT: ""
needs:
- !reference [.frontend-fixtures-base, needs]
- "compile-test-assets as-if-foss"
@@ -170,6 +206,8 @@ upload-frontend-fixtures:
stage: fixtures
needs: ["rspec-all frontend_fixture"]
script:
+ - export FIXTURES_SHA="${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA:-$CI_COMMIT_SHA}"
+ - source scripts/utils.sh
- source scripts/gitlab_component_helpers.sh
- 'fixtures_archive_doesnt_exist || { echoinfo "INFO: Exiting early as package exists."; exit 0; }'
- run_timed_command "create_fixtures_package"
@@ -224,7 +262,7 @@ jest:
extends:
- .jest-base
- .frontend:rules:jest
- needs: ["rspec-all frontend_fixture"]
+ needs: ["rspec-all frontend_fixture", "retrieve-frontend-fixtures"]
artifacts:
name: coverage-frontend
expire_in: 31d
@@ -274,6 +312,7 @@ jest-integration:
- run_timed_command "yarn jest:integration --ci"
needs:
- job: "rspec-all frontend_fixture"
+ - job: "retrieve-frontend-fixtures"
- job: "graphql-schema-dump"
coverage-frontend:
@@ -360,6 +399,7 @@ startup-css-check:
needs:
- job: "compile-test-assets"
- job: "rspec-all frontend_fixture"
+ - job: "retrieve-frontend-fixtures"
startup-css-check as-if-foss:
extends:
@@ -386,6 +426,7 @@ compile-storybook:
needs:
- !reference [.compile-storybook-base, needs]
- job: "rspec-all frontend_fixture"
+ - job: "retrieve-frontend-fixtures"
artifacts:
name: storybook
expire_in: 31d
diff --git a/.rubocop_todo/rake/require.yml b/.rubocop_todo/rake/require.yml
index b24dd1e6540..0659dd11037 100644
--- a/.rubocop_todo/rake/require.yml
+++ b/.rubocop_todo/rake/require.yml
@@ -2,5 +2,4 @@
Rake/Require:
Details: grace period
Exclude:
- - 'lib/tasks/tokens.rake'
- 'qa/tasks/webdrivers.rake'
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 08783f09b58..e5ec85c5ac9 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-48b0e3bafe63bc6c7786a6b1ca6c0a06d14a721f
+5338468e55ae2bd6eba6ddc6021e160854348f2b
diff --git a/Gemfile b/Gemfile
index 3030dcc6363..8d090183d76 100644
--- a/Gemfile
+++ b/Gemfile
@@ -526,7 +526,7 @@ gem 'flipper', '~> 0.25.0'
gem 'flipper-active_record', '~> 0.25.0'
gem 'flipper-active_support_cache_store', '~> 0.25.0'
gem 'unleash', '~> 3.2.2'
-gem 'gitlab-experiment', '~> 0.7.1'
+gem 'gitlab-experiment', '~> 0.8.0'
# Structured logging
gem 'lograge', '~> 0.5'
diff --git a/Gemfile.checksum b/Gemfile.checksum
index a264fe1d13a..08ee1355967 100644
--- a/Gemfile.checksum
+++ b/Gemfile.checksum
@@ -206,7 +206,7 @@
{"name":"gitlab","version":"4.19.0","platform":"ruby","checksum":"3f645e3e195dbc24f0834fbf83e8ccfb2056d8e9712b01a640aad418a6949679"},
{"name":"gitlab-chronic","version":"0.10.5","platform":"ruby","checksum":"f80f18dc699b708870a80685243331290bc10cfeedb6b99c92219722f729c875"},
{"name":"gitlab-dangerfiles","version":"3.8.0","platform":"ruby","checksum":"7ef0c3205faa38a2ada19ee5b8e4012ea696611aa02564a4a95eaf3fb26d1a7e"},
-{"name":"gitlab-experiment","version":"0.7.1","platform":"ruby","checksum":"166dddb3aa83428bcaa93c35684ed01dc4d61f321fd2ae40b020806dc54a7824"},
+{"name":"gitlab-experiment","version":"0.8.0","platform":"ruby","checksum":"b4e2f73e0af19cdd899a745f5a846c1318d44054e068a8f4ac887f6b1017d3f9"},
{"name":"gitlab-fog-azure-rm","version":"1.7.0","platform":"ruby","checksum":"969c67943c54ad4c259a6acd040493f13922fbdf2211bb4eca00e71505263dc2"},
{"name":"gitlab-labkit","version":"0.31.1","platform":"ruby","checksum":"3e3a39370966b5d2739c2d9d9005c0ea27541d32cb7292e856e8bd74c720bffb"},
{"name":"gitlab-license","version":"2.2.1","platform":"ruby","checksum":"39fcf6be8b2887df8afe01b5dcbae8d08b7c5d937ff56b0fb40484a8c4f02d30"},
diff --git a/Gemfile.lock b/Gemfile.lock
index f71f7f1fb1d..91ebcfe15a4 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -590,7 +590,7 @@ GEM
danger (>= 8.4.5)
danger-gitlab (>= 8.0.0)
rake
- gitlab-experiment (0.7.1)
+ gitlab-experiment (0.8.0)
activesupport (>= 3.0)
request_store (>= 1.0)
gitlab-fog-azure-rm (1.7.0)
@@ -1716,7 +1716,7 @@ DEPENDENCIES
gitaly (~> 15.9.0.pre.rc3)
gitlab-chronic (~> 0.10.5)
gitlab-dangerfiles (~> 3.8.0)
- gitlab-experiment (~> 0.7.1)
+ gitlab-experiment (~> 0.8.0)
gitlab-fog-azure-rm (~> 1.7.0)
gitlab-labkit (~> 0.31.1)
gitlab-license (~> 2.2.1)
diff --git a/app/assets/javascripts/admin/abuse_reports/components/abuse_reports_filtered_search_bar.vue b/app/assets/javascripts/admin/abuse_reports/components/abuse_reports_filtered_search_bar.vue
index b60fe3ae9b8..e1989cadd86 100644
--- a/app/assets/javascripts/admin/abuse_reports/components/abuse_reports_filtered_search_bar.vue
+++ b/app/assets/javascripts/admin/abuse_reports/components/abuse_reports_filtered_search_bar.vue
@@ -8,7 +8,7 @@ import {
SORT_OPTIONS,
isValidSortKey,
} from '~/admin/abuse_reports/constants';
-import { buildFilteredSearchCategoryToken } from '~/admin/abuse_reports/utils';
+import { buildFilteredSearchCategoryToken, isValidStatus } from '~/admin/abuse_reports/utils';
export default {
name: 'AbuseReportsFilteredSearchBar',
@@ -32,7 +32,7 @@ export default {
// Backend shows open reports by default if status param is not specified.
// To match that behavior, update the current URL to include status=open
// query when no status query is specified on load.
- if (!query.status) {
+ if (!isValidStatus(query.status)) {
query.status = 'open';
updateHistory({ url: setUrlParams(query), replace: true });
}
diff --git a/app/assets/javascripts/admin/abuse_reports/utils.js b/app/assets/javascripts/admin/abuse_reports/utils.js
index 84221901089..d30e8fb0ae5 100644
--- a/app/assets/javascripts/admin/abuse_reports/utils.js
+++ b/app/assets/javascripts/admin/abuse_reports/utils.js
@@ -1,6 +1,9 @@
-import { FILTERED_SEARCH_TOKEN_CATEGORY } from './constants';
+import { FILTERED_SEARCH_TOKEN_CATEGORY, FILTERED_SEARCH_TOKEN_STATUS } from './constants';
export const buildFilteredSearchCategoryToken = (categories) => {
const options = categories.map((c) => ({ value: c, title: c }));
return { ...FILTERED_SEARCH_TOKEN_CATEGORY, options };
};
+
+export const isValidStatus = (status) =>
+ FILTERED_SEARCH_TOKEN_STATUS.options.map((o) => o.value).includes(status);
diff --git a/app/assets/javascripts/issues/show/components/app.vue b/app/assets/javascripts/issues/show/components/app.vue
index 15f97222971..6622fb7aa7b 100644
--- a/app/assets/javascripts/issues/show/components/app.vue
+++ b/app/assets/javascripts/issues/show/components/app.vue
@@ -95,10 +95,10 @@ export default {
required: false,
default: '',
},
- initialTaskStatus: {
- type: String,
+ initialTaskCompletionStatus: {
+ type: Object,
required: false,
- default: '',
+ default: () => ({}),
},
updatedAt: {
type: String,
@@ -197,7 +197,7 @@ export default {
updatedAt: this.updatedAt,
updatedByName: this.updatedByName,
updatedByPath: this.updatedByPath,
- taskStatus: this.initialTaskStatus,
+ taskCompletionStatus: this.initialTaskCompletionStatus,
lock_version: this.lockVersion,
});
@@ -222,9 +222,6 @@ export default {
formState() {
return this.store.formState;
},
- hasUpdated() {
- return Boolean(this.state.updatedAt);
- },
issueChanged() {
const {
store: {
@@ -557,7 +554,6 @@ export default {
:description-html="state.descriptionHtml"
:description-text="state.descriptionText"
:updated-at="state.updatedAt"
- :task-status="state.taskStatus"
:issuable-type="issuableType"
:update-url="updateEndpoint"
:lock-version="state.lock_version"
@@ -570,7 +566,7 @@ export default {
/>
<edited-component
- v-if="hasUpdated"
+ :task-completion-status="state.taskCompletionStatus"
:updated-at="state.updatedAt"
:updated-by-name="state.updatedByName"
:updated-by-path="state.updatedByPath"
diff --git a/app/assets/javascripts/issues/show/components/description.vue b/app/assets/javascripts/issues/show/components/description.vue
index bdee6c5fe9a..6412a13e3e6 100644
--- a/app/assets/javascripts/issues/show/components/description.vue
+++ b/app/assets/javascripts/issues/show/components/description.vue
@@ -1,6 +1,5 @@
<script>
import { GlToast } from '@gitlab/ui';
-import $ from 'jquery';
import Sortable from 'sortablejs';
import Vue from 'vue';
import getIssueDetailsQuery from 'ee_else_ce/work_items/graphql/get_issue_details.query.graphql';
@@ -59,11 +58,6 @@ export default {
required: false,
default: '',
},
- taskStatus: {
- type: String,
- required: false,
- default: '',
- },
issuableType: {
type: String,
required: false,
@@ -148,16 +142,12 @@ export default {
this.renderGFM();
});
},
- taskStatus() {
- this.updateTaskStatusText();
- },
},
mounted() {
eventHub.$on('convert-task-list-item', this.convertTaskListItem);
eventHub.$on('delete-task-list-item', this.deleteTaskListItem);
this.renderGFM();
- this.updateTaskStatusText();
},
beforeDestroy() {
eventHub.$off('convert-task-list-item', this.convertTaskListItem);
@@ -282,24 +272,6 @@ export default {
this.$emit('taskListUpdateFailed');
},
- updateTaskStatusText() {
- const taskRegexMatches = this.taskStatus.match(/(\d+) of ((?!0)\d+)/);
- const $issuableHeader = $('.issuable-meta');
- const $tasks = $('#task_status', $issuableHeader);
- const $tasksShort = $('#task_status_short', $issuableHeader);
-
- if (taskRegexMatches) {
- $tasks.text(this.taskStatus);
- $tasksShort.text(
- `${taskRegexMatches[1]}/${taskRegexMatches[2]} checklist item${
- taskRegexMatches[2] > 1 ? 's' : ''
- }`,
- );
- } else {
- $tasks.text('');
- $tasksShort.text('');
- }
- },
createTaskListItemActions(provide) {
const app = new Vue({
el: document.createElement('div'),
diff --git a/app/assets/javascripts/issues/show/components/edited.vue b/app/assets/javascripts/issues/show/components/edited.vue
index 5138a4530e9..6a0edb59b65 100644
--- a/app/assets/javascripts/issues/show/components/edited.vue
+++ b/app/assets/javascripts/issues/show/components/edited.vue
@@ -1,13 +1,20 @@
<script>
-import { GlSprintf } from '@gitlab/ui';
+import { GlLink, GlSprintf } from '@gitlab/ui';
+import { n__, sprintf } from '~/locale';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
export default {
components: {
TimeAgoTooltip,
+ GlLink,
GlSprintf,
},
props: {
+ taskCompletionStatus: {
+ type: Object,
+ required: false,
+ default: () => ({}),
+ },
updatedAt: {
type: String,
required: false,
@@ -25,36 +32,61 @@ export default {
},
},
computed: {
+ completedCount() {
+ return this.taskCompletionStatus.completed_count;
+ },
+ count() {
+ return this.taskCompletionStatus.count;
+ },
hasUpdatedBy() {
return this.updatedByName && this.updatedByPath;
},
+ showCheck() {
+ return this.completedCount === this.count;
+ },
+ taskStatus() {
+ const { completedCount, count } = this;
+ if (!count) {
+ return undefined;
+ }
+
+ return sprintf(
+ n__(
+ '%{completedCount} of %{count} checklist item completed',
+ '%{completedCount} of %{count} checklist items completed',
+ count,
+ ),
+ { completedCount, count },
+ );
+ },
},
};
</script>
<template>
<small class="edited-text js-issue-widgets">
- <gl-sprintf v-if="!hasUpdatedBy" :message="__('Edited %{timeago}')">
- <template #timeago>
- <time-ago-tooltip :time="updatedAt" tooltip-placement="bottom" />
- </template>
- </gl-sprintf>
- <gl-sprintf v-else-if="!updatedAt" :message="__('Edited by %{author}')">
- <template #author>
- <a :href="updatedByPath" class="author-link gl-hover-text-decoration-underline">
- <span>{{ updatedByName }}</span>
- </a>
- </template>
- </gl-sprintf>
- <gl-sprintf v-else :message="__('Edited %{timeago} by %{author}')">
- <template #timeago>
- <time-ago-tooltip :time="updatedAt" tooltip-placement="bottom" />
- </template>
- <template #author>
- <a :href="updatedByPath" class="author-link gl-hover-text-decoration-underline">
- <span>{{ updatedByName }}</span>
- </a>
- </template>
- </gl-sprintf>
+ <template v-if="taskStatus">
+ <template v-if="showCheck">&check;</template>
+ {{ taskStatus }}
+ <template v-if="updatedAt">&middot;</template>
+ </template>
+
+ <template v-if="updatedAt">
+ <gl-sprintf v-if="!hasUpdatedBy" :message="__('Edited %{timeago}')">
+ <template #timeago>
+ <time-ago-tooltip :time="updatedAt" tooltip-placement="bottom" />
+ </template>
+ </gl-sprintf>
+ <gl-sprintf v-else :message="__('Edited %{timeago} by %{author}')">
+ <template #timeago>
+ <time-ago-tooltip :time="updatedAt" tooltip-placement="bottom" />
+ </template>
+ <template #author>
+ <gl-link :href="updatedByPath" class="gl-font-sm gl-hover-text-gray-900 gl-text-gray-700">
+ {{ updatedByName }}
+ </gl-link>
+ </template>
+ </gl-sprintf>
+ </template>
</small>
</template>
diff --git a/app/controllers/concerns/issuable_actions.rb b/app/controllers/concerns/issuable_actions.rb
index d364daf93c3..b8d47586a15 100644
--- a/app/controllers/concerns/issuable_actions.rb
+++ b/app/controllers/concerns/issuable_actions.rb
@@ -76,7 +76,7 @@ module IssuableActions
title_text: issuable.title,
description: view_context.markdown_field(issuable, :description),
description_text: issuable.description,
- task_status: issuable.task_status,
+ task_completion_status: issuable.task_completion_status,
lock_version: issuable.lock_version
}
diff --git a/app/controllers/concerns/product_analytics_tracking.rb b/app/controllers/concerns/product_analytics_tracking.rb
index 5ed2b2a82eb..fc33770b4d8 100644
--- a/app/controllers/concerns/product_analytics_tracking.rb
+++ b/app/controllers/concerns/product_analytics_tracking.rb
@@ -5,30 +5,6 @@ module ProductAnalyticsTracking
include RedisTracking
extend ActiveSupport::Concern
- MIGRATED_EVENTS = %w[
- g_analytics_valuestream
- i_search_paid
- i_search_total
- i_search_advanced
- i_ecosystem_jira_service_list_issues
- users_viewing_analytics_group_devops_adoption
- i_analytics_dev_ops_adoption
- i_analytics_dev_ops_score
- p_analytics_merge_request
- i_analytics_instance_statistics
- g_analytics_contribution
- p_analytics_pipelines
- p_analytics_code_reviews
- p_analytics_valuestream
- p_analytics_insights
- p_analytics_issues
- p_analytics_repo
- g_analytics_insights
- g_analytics_issues
- g_analytics_productivity
- i_analytics_cohorts
- ].freeze
-
class_methods do
def track_event(*controller_actions, name:, action: nil, label: nil, conditions: nil, destinations: [:redis_hll], &block)
custom_conditions = [:trackable_html_request?, *conditions]
@@ -44,7 +20,7 @@ module ProductAnalyticsTracking
def route_events_to(destinations, name, action, label, &block)
track_unique_redis_hll_event(name, &block) if destinations.include?(:redis_hll)
- return unless destinations.include?(:snowplow) && event_enabled?(name)
+ return unless destinations.include?(:snowplow)
raise "action is required when destination is snowplow" unless action
raise "label is required when destination is snowplow" unless label
@@ -63,18 +39,4 @@ module ProductAnalyticsTracking
**optional_arguments
)
end
-
- def event_enabled?(event)
- return true if MIGRATED_EVENTS.include?(event)
-
- events_to_ff = {
- g_edit_by_sfe: :_phase4,
- g_compliance_dashboard: :_phase4,
- g_compliance_audit_events: :_phase4,
- i_compliance_audit_events: :_phase4,
- i_compliance_credential_inventory: :_phase4
- }
-
- Feature.enabled?("route_hll_to_snowplow#{events_to_ff[event.to_sym]}", tracking_namespace_source)
- end
end
diff --git a/app/controllers/projects/pipelines_controller.rb b/app/controllers/projects/pipelines_controller.rb
index 6fdd4906613..5b1a7f0bab7 100644
--- a/app/controllers/projects/pipelines_controller.rb
+++ b/app/controllers/projects/pipelines_controller.rb
@@ -339,6 +339,7 @@ class Projects::PipelinesController < Projects::ApplicationController
return if helpers.has_gitlab_ci?(project)
experiment(:runners_availability_section, namespace: project.root_ancestor) do |e|
+ e.control {}
e.candidate {}
end
end
diff --git a/app/finders/abuse_reports_finder.rb b/app/finders/abuse_reports_finder.rb
index c3159198261..6a6d0413194 100644
--- a/app/finders/abuse_reports_finder.rb
+++ b/app/finders/abuse_reports_finder.rb
@@ -3,6 +3,7 @@
class AbuseReportsFinder
attr_reader :params, :reports
+ DEFAULT_STATUS_FILTER = 'open'
DEFAULT_SORT = 'created_at_desc'
ALLOWED_SORT = [DEFAULT_SORT, *%w[created_at_asc updated_at_desc updated_at_asc]].freeze
@@ -30,9 +31,13 @@ class AbuseReportsFinder
end
def filter_by_status
+ return unless Feature.enabled?(:abuse_reports_list)
return unless params[:status].present?
- case params[:status]
+ status = params[:status]
+ status = DEFAULT_STATUS_FILTER unless status.in?(AbuseReport.statuses.keys)
+
+ case status
when 'open'
@reports = @reports.open
when 'closed'
diff --git a/app/helpers/ci/pipelines_helper.rb b/app/helpers/ci/pipelines_helper.rb
index 823332c3d1d..90806df1d64 100644
--- a/app/helpers/ci/pipelines_helper.rb
+++ b/app/helpers/ci/pipelines_helper.rb
@@ -105,10 +105,12 @@ module Ci
}
experiment(:runners_availability_section, namespace: project.root_ancestor) do |e|
+ e.control {} # rubocop:disable Lint/EmptyBlock
e.candidate { data[:any_runners_available] = project.active_runners.exists?.to_s }
end
experiment(:ios_specific_templates, actor: current_user, project: project, sticky_to: project) do |e|
+ e.control {} # rubocop:disable Lint/EmptyBlock
e.candidate do
data[:registration_token] = project.runners_token if can?(current_user, :register_project_runners, project)
data[:ios_runners_available] = (project.shared_runners_available? && Gitlab.com?).to_s
diff --git a/app/helpers/ide_helper.rb b/app/helpers/ide_helper.rb
index 063eef41f77..2f25afb8057 100644
--- a/app/helpers/ide_helper.rb
+++ b/app/helpers/ide_helper.rb
@@ -72,6 +72,7 @@ module IdeHelper
def enable_environments_guidance?(project)
experiment(:in_product_guidance_environments_webide, project: project) do |e|
+ e.control { 'false' }
e.candidate { !has_dismissed_ide_environments_callout? }
e.run
diff --git a/app/helpers/issuables_helper.rb b/app/helpers/issuables_helper.rb
index 9c68f54f42e..a2e8d268455 100644
--- a/app/helpers/issuables_helper.rb
+++ b/app/helpers/issuables_helper.rb
@@ -173,9 +173,6 @@ module IssuablesHelper
output << content_tag(:span, (sprite_icon('first-contribution', css_class: 'gl-icon gl-vertical-align-middle') if issuable.first_contribution?), class: 'has-tooltip gl-ml-2', title: _('1st contribution!'))
- output << content_tag(:span, (issuable.task_status if issuable.tasks?), id: "task_status", class: "d-none d-md-inline-block gl-ml-3")
- output << content_tag(:span, (issuable.task_status_short if issuable.tasks?), id: "task_status_short", class: "d-md-none")
-
output.join.html_safe
end
@@ -252,7 +249,7 @@ module IssuablesHelper
initialTitleText: issuable.title,
initialDescriptionHtml: markdown_field(issuable, :description),
initialDescriptionText: issuable.description,
- initialTaskStatus: issuable.task_status
+ initialTaskCompletionStatus: issuable.task_completion_status
}
data.merge!(issue_only_initial_data(issuable))
data.merge!(path_data(parent))
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 8898f7feb17..39d0d0a7923 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -164,19 +164,17 @@ module Notes
track_note_creation_in_ipynb(note)
track_note_creation_visual_review(note)
- if Feature.enabled?(:route_hll_to_snowplow_phase4, project&.namespace) && note.for_commit?
- metric_key_path = 'counts.commit_comment'
-
- Gitlab::Tracking.event(
- 'Notes::CreateService',
- 'create_commit_comment',
- project: project,
- namespace: project&.namespace,
- user: user,
- label: metric_key_path,
- context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis, key_path: metric_key_path).to_context]
- )
- end
+ metric_key_path = 'counts.commit_comment'
+
+ Gitlab::Tracking.event(
+ 'Notes::CreateService',
+ 'create_commit_comment',
+ project: project,
+ namespace: project&.namespace,
+ user: user,
+ label: metric_key_path,
+ context: [Gitlab::Tracking::ServicePingContext.new(data_source: :redis, key_path: metric_key_path).to_context]
+ )
end
def tracking_data_for(note)
diff --git a/app/workers/namespaces/root_statistics_worker.rb b/app/workers/namespaces/root_statistics_worker.rb
index 02b3468c052..c8527182d35 100644
--- a/app/workers/namespaces/root_statistics_worker.rb
+++ b/app/workers/namespaces/root_statistics_worker.rb
@@ -16,15 +16,23 @@ module Namespaces
def perform(namespace_id)
namespace = Namespace.find(namespace_id)
+ if Feature.enabled?(:remove_aggregation_schedule_lease, namespace)
+ Namespaces::StatisticsRefresherService.new.execute(namespace)
+ else
+ refresh_through_namespace_aggregation_schedule(namespace)
+ end
+
+ notify_storage_usage(namespace)
+ rescue ::Namespaces::StatisticsRefresherService::RefresherError, ActiveRecord::RecordNotFound => ex
+ Gitlab::ErrorTracking.track_exception(ex, namespace_id: namespace_id, namespace: namespace&.full_path)
+ end
+
+ def refresh_through_namespace_aggregation_schedule(namespace)
return unless namespace.aggregation_scheduled?
Namespaces::StatisticsRefresherService.new.execute(namespace)
namespace.aggregation_schedule.destroy
-
- notify_storage_usage(namespace)
- rescue ::Namespaces::StatisticsRefresherService::RefresherError, ActiveRecord::RecordNotFound => ex
- Gitlab::ErrorTracking.track_exception(ex, namespace_id: namespace_id, namespace: namespace&.full_path)
end
private
diff --git a/app/workers/namespaces/schedule_aggregation_worker.rb b/app/workers/namespaces/schedule_aggregation_worker.rb
index 7cd7f5223d6..bf48eb8180e 100644
--- a/app/workers/namespaces/schedule_aggregation_worker.rb
+++ b/app/workers/namespaces/schedule_aggregation_worker.rb
@@ -13,16 +13,24 @@ module Namespaces
idempotent!
def perform(namespace_id)
- return unless aggregation_schedules_table_exists?
-
namespace = Namespace.find(namespace_id)
root_ancestor = namespace.root_ancestor
+ if Feature.enabled?(:remove_aggregation_schedule_lease, root_ancestor)
+ Namespaces::RootStatisticsWorker.perform_async(root_ancestor.id)
+ else
+ schedule_through_aggregation_schedules_table(root_ancestor)
+ end
+ rescue ActiveRecord::RecordNotFound => ex
+ Gitlab::ErrorTracking.track_exception(ex, namespace_id: namespace_id)
+ end
+
+ def schedule_through_aggregation_schedules_table(root_ancestor)
+ return unless aggregation_schedules_table_exists?
+
return if root_ancestor.aggregation_scheduled?
Namespace::AggregationSchedule.safe_find_or_create_by!(namespace_id: root_ancestor.id)
- rescue ActiveRecord::RecordNotFound => ex
- Gitlab::ErrorTracking.track_exception(ex, namespace_id: namespace_id)
end
private
diff --git a/config/feature_flags/development/remove_aggregation_schedule_lease.yml b/config/feature_flags/development/remove_aggregation_schedule_lease.yml
new file mode 100644
index 00000000000..77367be3200
--- /dev/null
+++ b/config/feature_flags/development/remove_aggregation_schedule_lease.yml
@@ -0,0 +1,8 @@
+---
+name: remove_aggregation_schedule_lease
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/113959
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/395018
+milestone: '15.10'
+type: development
+group: group::utilization
+default_enabled: false
diff --git a/config/feature_flags/development/route_hll_to_snowplow_phase4.yml b/config/feature_flags/development/route_hll_to_snowplow_phase4.yml
deleted file mode 100644
index a4109b15533..00000000000
--- a/config/feature_flags/development/route_hll_to_snowplow_phase4.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: route_hll_to_snowplow_phase4
-introduced_by_url: "https://gitlab.com/gitlab-org/gitlab/-/merge_requests/103528"
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/366767
-milestone: '15.7'
-type: development
-group: group::product intelligence
-default_enabled: false
diff --git a/doc/administration/geo/disaster_recovery/background_verification.md b/doc/administration/geo/disaster_recovery/background_verification.md
index 99ac95cd536..ea4523c66e6 100644
--- a/doc/administration/geo/disaster_recovery/background_verification.md
+++ b/doc/administration/geo/disaster_recovery/background_verification.md
@@ -6,12 +6,6 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# Automatic background verification **(PREMIUM SELF)**
-NOTE:
-Automatic background verification of repositories and wikis was added in
-GitLab EE 10.6 but is enabled by default only on GitLab EE 11.1. You can
-disable or enable this feature manually by following
-[these instructions](#disabling-or-enabling-the-automatic-background-verification).
-
Automatic background verification ensures that the transferred data matches a
calculated checksum. If the checksum of the data on the **primary** site matches checksum of the
data on the **secondary** site, the data transferred successfully. Following a planned failover,
@@ -179,19 +173,5 @@ If the **primary** and **secondary** sites have a checksum verification mismatch
## Current limitations
-Automatic background verification doesn't cover attachments, LFS objects,
-job artifacts, and user uploads in file storage. You can keep track of the
-progress to include them in [Geo: Verify all replicated data](https://gitlab.com/groups/gitlab-org/-/epics/1430).
-
-For now, you can verify their integrity
-manually by following [these instructions](../../raketasks/check.md) on both
-sites, and comparing the output between them.
-
-In GitLab EE 12.1, Geo calculates checksums for attachments, LFS objects, and
-archived traces on secondary sites after the transfer, compares it with the
-stored checksums, and rejects transfers if mismatched. Geo
-currently does not support an automatic way to verify these data if they have
-been synced before GitLab EE 12.1.
-
-Data in object storage is **not verified**, as the object store is responsible
-for ensuring the integrity of the data.
+For more information on what replication and verification methods are supported,
+see the [supported Geo data types](../replication/datatypes.md).
diff --git a/doc/architecture/blueprints/ci_pipeline_components/index.md b/doc/architecture/blueprints/ci_pipeline_components/index.md
index c1094bc4290..6d8e87d2dd4 100644
--- a/doc/architecture/blueprints/ci_pipeline_components/index.md
+++ b/doc/architecture/blueprints/ci_pipeline_components/index.md
@@ -703,6 +703,6 @@ Domain experts:
| Area | Who
|------------------------------|------------------------|
| Verify / Pipeline authoring | Avielle Wolfe |
-| Verify / Pipeline authoring | Laura Montemayor-Rodriguez |
+| Verify / Pipeline authoring | Laura Montemayor |
<!-- vale gitlab.Spelling = YES -->
diff --git a/doc/install/docker.md b/doc/install/docker.md
index 40eb3a9796e..8d3c81d72c8 100644
--- a/doc/install/docker.md
+++ b/doc/install/docker.md
@@ -456,6 +456,30 @@ web browser under `<hostIP>:8929` and push using SSH under the port `2289`.
A `docker-compose.yml` example that uses different ports can be found in the
[Docker compose](#install-gitlab-using-docker-compose) section.
+### Configure multiple database connections
+
+In GitLab 16.0, GitLab will default to using two database connections that point to the same PostgreSQL database.
+
+If you want to opt-in for this feature:
+
+1. Edit `/etc/gitlab/gitlab.rb` inside the container:
+
+ ```shell
+ sudo docker exec -it gitlab editor /etc/gitlab/gitlab.rb
+ ```
+
+1. Add the following line:
+
+ ```ruby
+ gitlab_rails['databases']['ci']['enable'] = true
+ ```
+
+1. Restart the container:
+
+```shell
+sudo docker restart gitlab
+```
+
## Upgrade
In most cases, upgrading GitLab is as easy as downloading the newest Docker
diff --git a/doc/user/compliance/license_scanning_of_cyclonedx_files/index.md b/doc/user/compliance/license_scanning_of_cyclonedx_files/index.md
index 0ad73f9939d..6964234aecb 100644
--- a/doc/user/compliance/license_scanning_of_cyclonedx_files/index.md
+++ b/doc/user/compliance/license_scanning_of_cyclonedx_files/index.md
@@ -7,10 +7,9 @@ info: To determine the technical writer assigned to the Stage/Group associated w
# License scanning of CycloneDX files **(ULTIMATE)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/384932) in GitLab 15.9 [with two flags](../../../administration/feature_flags.md) named `license_scanning_sbom_scanner` and `package_metadata_synchronization`. Both flags are disabled by default and both flags must be enabled for this feature to work.
-
-FLAG:
-On self-managed GitLab, this feature is not available.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/384932) in GitLab 15.9 for GitLab SaaS [with two flags](../../../administration/feature_flags.md) named `license_scanning_sbom_scanner` and `package_metadata_synchronization`. Both flags are disabled by default and both flags must be enabled for this feature to work.
+> - [Enabled on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/385173) in GitLab 15.10.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/385173) in GitLab 15.10 for self-managed GitLab [with two flags](../../../administration/feature_flags.md) named `license_scanning_sbom_scanner` and `package_metadata_synchronization`, both of which must be enabled for this feature to work. The flags are disabled by default due to the initial performance load when the license database is first imported. Work to improve performance is being tracked in [issue 397670](https://gitlab.com/gitlab-org/gitlab/-/issues/397670).
To detect the licenses in use, License Compliance relies on running the
[Dependency Scanning CI Jobs](../../application_security/dependency_scanning/index.md),
diff --git a/doc/user/group/saml_sso/index.md b/doc/user/group/saml_sso/index.md
index 04dfdbc6892..61f248a0c63 100644
--- a/doc/user/group/saml_sso/index.md
+++ b/doc/user/group/saml_sso/index.md
@@ -52,7 +52,7 @@ If you have any questions on configuring the SAML app, contact your provider's s
### Set up Azure
-To set up SSO with Azure as your identification provider:
+To set up SSO with Azure as your identity provider:
1. In GitLab, on the top bar, select **Main menu > Groups** and find your group.
1. On the left sidebar, select **Settings > SAML SSO**.
@@ -78,19 +78,23 @@ To set up SSO with Azure as your identification provider:
<i class="fa fa-youtube-play youtube" aria-hidden="true"></i>
View a demo of [SCIM provisioning on Azure using SAML SSO for groups](https://youtu.be/24-ZxmTeEBU). The `objectID` mapping is outdated in this video. Follow the [SCIM documentation](scim_setup.md#configure-azure-active-directory) instead.
-View an [example configuration page](example_saml_config.md#azure-active-directory).
+For more information, see an [example configuration page](example_saml_config.md#azure-active-directory).
### Set up Google Workspace
-1. [Set up SSO with Google as your identity provider](https://support.google.com/a/answer/6087519?hl=en).
- The following GitLab settings correspond to the Google Workspace fields.
+To set up Google Workspace as your identity provider:
- | GitLab setting | Google Workspace field |
- |:-------------------------------------|:-----------------------|
- | Identifier | **Entity ID** |
- | Assertion consumer service URL | **ACS URL** |
- | GitLab single sign-on URL | **Start URL** |
- | Identity provider single sign-on URL | **SSO URL** |
+1. In GitLab, on the top bar, select **Main menu > Groups** and find your group.
+1. On the left sidebar, select **Settings > SAML SSO**.
+1. Note the information on this page.
+1. Follow the instructions for [setting up SSO with Google as your identity provider](https://support.google.com/a/answer/6087519?hl=en). The following GitLab settings correspond to the Google Workspace fields.
+
+ | GitLab setting | Google Workspace field |
+ |:-----------------------------------------|:-----------------------|
+ | **Identifier** | **Entity ID** |
+ | **Assertion consumer service URL** | **ACS URL** |
+ | **GitLab single sign-on URL** | **Start URL** |
+ | **Identity provider single sign-on URL** | **SSO URL** |
1. Google Workspace displays a SHA256 fingerprint. To retrieve the SHA1 fingerprint
required by GitLab to [configure SAML](#configure-gitlab):
@@ -111,7 +115,7 @@ View an [example configuration page](example_saml_config.md#azure-active-directo
On the GitLab SAML SSO page, when you select **Verify SAML Configuration**, disregard
the warning that recommends setting the **NameID** format to `persistent`.
-For details, see the [example configuration page](example_saml_config.md#google-workspace).
+For more information, see an [example configuration page](example_saml_config.md#google-workspace).
### Set up Okta
diff --git a/doc/user/project/wiki/group.md b/doc/user/project/wiki/group.md
index 1513ea18ed2..de6aed52fd8 100644
--- a/doc/user/project/wiki/group.md
+++ b/doc/user/project/wiki/group.md
@@ -71,7 +71,7 @@ To open group settings:
1. On the left sidebar, select **Settings > General**.
1. Expand **Permissions and group features**.
1. Scroll to **Wiki** and select one of these options:
- - **Enabled**: Everyone who can access the group can access the wiki.
+ - **Enabled**: For public groups, everyone can access the wiki. For internal groups, only authenticated users can access the wiki.
- **Private**: Only group members can access the wiki.
- **Disabled**: The wiki isn't accessible, and cannot be downloaded.
1. Select **Save changes**.
diff --git a/lib/tasks/tokens.rake b/lib/tasks/tokens.rake
index ff14ab51b49..81e24f4f7b6 100644
--- a/lib/tasks/tokens.rake
+++ b/lib/tasks/tokens.rake
@@ -1,11 +1,11 @@
# frozen_string_literal: true
-require_relative '../../app/models/concerns/token_authenticatable'
-require_relative '../../app/models/concerns/token_authenticatable_strategies/base'
-require_relative '../../app/models/concerns/token_authenticatable_strategies/insecure'
-require_relative '../../app/models/concerns/token_authenticatable_strategies/digest'
-
namespace :tokens do
+ require_relative '../../app/models/concerns/token_authenticatable'
+ require_relative '../../app/models/concerns/token_authenticatable_strategies/base'
+ require_relative '../../app/models/concerns/token_authenticatable_strategies/insecure'
+ require_relative '../../app/models/concerns/token_authenticatable_strategies/digest'
+
desc "Reset all GitLab incoming email tokens"
task reset_all_email: :environment do
reset_all_users_token(:reset_incoming_email_token!)
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 4355965db51..e62f0de5bf0 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -15652,9 +15652,6 @@ msgstr ""
msgid "Edited %{timeago} by %{author}"
msgstr ""
-msgid "Edited by %{author}"
-msgstr ""
-
msgid "Editing"
msgstr ""
@@ -35312,6 +35309,9 @@ msgstr ""
msgid "ProtectedEnvironments|Unified approval rules have been removed from the settings UI"
msgstr ""
+msgid "ProtectedEnvironments|Unprotect"
+msgstr ""
+
msgid "ProtectedEnvironments|Users"
msgstr ""
diff --git a/scripts/gitlab_component_helpers.sh b/scripts/gitlab_component_helpers.sh
index 309e339de01..798135a5490 100644
--- a/scripts/gitlab_component_helpers.sh
+++ b/scripts/gitlab_component_helpers.sh
@@ -52,22 +52,9 @@ export GITLAB_ASSETS_PACKAGE="assets-${NODE_ENV}-${GITLAB_EDITION}-${GITLAB_ASSE
export GITLAB_ASSETS_PACKAGE_URL="${API_PACKAGES_BASE_URL}/assets/${NODE_ENV}-${GITLAB_EDITION}-${GITLAB_ASSETS_HASH}/${GITLAB_ASSETS_PACKAGE}"
# Fixtures constants
-
-# Export the SHA variable for updating/downloading fixture packages, using the following order of precedence:
-# 1. If MERGE_BASE_SHA is defined, use its value.
-# 2. If CI_MERGE_REQUEST_SOURCE_BRANCH_SHA is defined, use its value for merge request pipelines.
-# 3. Otherwise, use the value of CI_COMMIT_SHA for default branch pipelines or merge requests with detached pipelines.
-if [ -n "${MERGE_BASE_SHA:-}" ]; then
- export FIXTURES_SHA="${MERGE_BASE_SHA}"
-elif [ -n "${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA:-}" ]; then
- export FIXTURES_SHA="${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA}"
-else
- export FIXTURES_SHA="${CI_COMMIT_SHA}"
-fi
-
export FIXTURES_PATH="tmp/tests/frontend/**/*"
-export FIXTURES_PACKAGE="fixtures-${FIXTURES_SHA}.tar.gz"
-export FIXTURES_PACKAGE_URL="${API_PACKAGES_BASE_URL}/fixtures/${FIXTURES_SHA}/${FIXTURES_PACKAGE}"
+export FIXTURES_PACKAGE="fixtures-${FIXTURES_SHA:-}.tar.gz"
+export FIXTURES_PACKAGE_URL="${API_PACKAGES_BASE_URL}/fixtures/${FIXTURES_SHA:-}/${FIXTURES_PACKAGE}"
# Generic helper functions
function archive_doesnt_exist() {
@@ -75,7 +62,13 @@ function archive_doesnt_exist() {
status=$(curl -I --silent --retry 3 --output /dev/null -w "%{http_code}" "${package_url}")
- [[ "${status}" != "200" ]]
+ if [[ "${status}" != "200" ]]; then
+ echoinfo "The archive was not found. The server returned status ${status}."
+ return 0
+ else
+ echoinfo "The archive was found. The server returned status ${status}."
+ return 1
+ fi
}
function create_package() {
@@ -167,10 +160,50 @@ function upload_gitlab_assets_package() {
}
# Fixtures functions
+function download_and_extract_fixtures() {
+ read_curl_package "${FIXTURES_PACKAGE_URL}" | extract_package
+}
+
function fixtures_archive_doesnt_exist() {
+ echoinfo "Checking if the package is available at ${FIXTURES_PACKAGE_URL} ..."
+
archive_doesnt_exist "${FIXTURES_PACKAGE_URL}"
}
+function fixtures_directory_exists() {
+ local fixtures_directory="tmp/tests/frontend/"
+
+ if [[ -d "${fixtures_directory}" ]]; then
+ echo "${fixtures_directory} directory exists"
+ return 0
+ else
+ echo "${fixtures_directory} directory does not exist"
+ return 1
+ fi
+}
+
+function check_fixtures_download() {
+ if [[ "${REUSE_FRONTEND_FIXTURES_ENABLED:-}" != "true" ]]; then
+ return 1
+ fi
+
+ # Note: Currently, reusing frontend fixtures is only supported in EE.
+ # Other projects will be supported through this issue in the future: https://gitlab.com/gitlab-org/gitlab/-/issues/393615.
+ if [[ "${CI_PROJECT_NAME}" != "gitlab" ]] || [[ "${CI_JOB_NAME}" =~ "foss" ]]; then
+ return 1
+ fi
+
+ if [[ -z "${CI_MERGE_REQUEST_IID:-}" ]]; then
+ return 1
+ else
+ if only_js_files_changed && ! fixtures_archive_doesnt_exist; then
+ return 0
+ else
+ return 1
+ fi
+ fi
+}
+
function create_fixtures_package() {
create_package "${FIXTURES_PACKAGE}" "${FIXTURES_PATH}"
}
@@ -178,3 +211,27 @@ function create_fixtures_package() {
function upload_fixtures_package() {
upload_package "${FIXTURES_PACKAGE}" "${FIXTURES_PACKAGE_URL}"
}
+
+function only_js_files_changed {
+ local target_branch_sha="${CI_MERGE_REQUEST_TARGET_BRANCH_SHA:-}"
+ local source_branch_sha="${CI_MERGE_REQUEST_SOURCE_BRANCH_SHA:-}"
+
+ if [[ -z "${target_branch_sha}" || -z "${source_branch_sha}" ]]; then
+ echoinfo "The commit hash(es) provided are missing or are empty."
+ echoinfo "Please provide valid commit hash(es)."
+ return 1
+ fi
+
+ local changed_files
+ changed_files=$(git diff --name-only "${target_branch_sha}..${source_branch_sha}")
+
+ for file in $changed_files; do
+ if [[ ! $file = *.js ]]; then
+ echoinfo "Changes were made to files other than JS files"
+ return 1
+ fi
+ done
+
+ echoinfo "Only JS files were changed"
+ return 0
+}
diff --git a/scripts/rspec_helpers.sh b/scripts/rspec_helpers.sh
index de735e03db0..bd2c01dac5e 100644
--- a/scripts/rspec_helpers.sh
+++ b/scripts/rspec_helpers.sh
@@ -164,7 +164,8 @@ function rspec_paralellized_job() {
local test_tool="${job_name[0]}"
local test_level="${job_name[1]}"
local report_name=$(echo "${CI_JOB_NAME}" | sed -E 's|[/ ]|_|g') # e.g. 'rspec unit pg12 1/24' would become 'rspec_unit_pg12_1_24'
- local rspec_opts="${1}"
+ local rspec_opts="${1:-}"
+ local rspec_tests_mapping_enabled="${RSPEC_TESTS_MAPPING_ENABLED:-}"
local spec_folder_prefixes=""
local rspec_flaky_folder_path="$(dirname "${FLAKY_RSPEC_SUITE_REPORT_PATH}")/"
local knapsack_folder_path="$(dirname "${KNAPSACK_RSPEC_SUITE_REPORT_PATH}")/"
@@ -218,7 +219,7 @@ function rspec_paralellized_job() {
debug_rspec_variables
- if [[ -n "${RSPEC_TESTS_MAPPING_ENABLED}" ]]; then
+ if [[ -n "${rspec_tests_mapping_enabled}" ]]; then
tooling/bin/parallel_rspec --rspec_args "$(rspec_args "${rspec_opts}")" --filter "${RSPEC_TESTS_FILTER_FILE}" || rspec_run_status=$?
else
tooling/bin/parallel_rspec --rspec_args "$(rspec_args "${rspec_opts}")" || rspec_run_status=$?
diff --git a/spec/controllers/concerns/product_analytics_tracking_spec.rb b/spec/controllers/concerns/product_analytics_tracking_spec.rb
index b0074b52aa2..65c2c77c027 100644
--- a/spec/controllers/concerns/product_analytics_tracking_spec.rb
+++ b/spec/controllers/concerns/product_analytics_tracking_spec.rb
@@ -107,20 +107,6 @@ RSpec.describe ProductAnalyticsTracking, :snowplow, feature_category: :product_a
expect_snowplow_tracking(user)
end
- context 'when FF is disabled' do
- before do
- stub_const("#{described_class}::MIGRATED_EVENTS", [])
- allow(Feature).to receive(:enabled?).and_call_original
- allow(Feature).to receive(:enabled?).with('route_hll_to_snowplow', anything).and_return(false)
- end
-
- it 'doesnt track snowplow event' do
- get :index
-
- expect_no_snowplow_event
- end
- end
-
it 'tracks the event if DNT is not enabled' do
stub_do_not_track('0')
diff --git a/spec/controllers/projects/blob_controller_spec.rb b/spec/controllers/projects/blob_controller_spec.rb
index c091badd09d..3f14317a84b 100644
--- a/spec/controllers/projects/blob_controller_spec.rb
+++ b/spec/controllers/projects/blob_controller_spec.rb
@@ -360,7 +360,6 @@ RSpec.describe Projects::BlobController do
let(:namespace) { project.namespace.reload }
let(:property) { target_event }
let(:label) { 'usage_activity_by_stage_monthly.create.action_monthly_active_users_sfe_edit' }
- let(:feature_flag_name) { 'route_hll_to_snowplow_phase4' }
end
end
end
@@ -520,7 +519,6 @@ RSpec.describe Projects::BlobController do
let(:namespace) { project.namespace }
let(:property) { target_event }
let(:label) { 'usage_activity_by_stage_monthly.create.action_monthly_active_users_sfe_edit' }
- let(:feature_flag_name) { 'route_hll_to_snowplow_phase4' }
end
it 'redirects to blob' do
diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb
index f1fe1940414..6747678b6fb 100644
--- a/spec/controllers/projects/issues_controller_spec.rb
+++ b/spec/controllers/projects/issues_controller_spec.rb
@@ -730,7 +730,7 @@ RSpec.describe Projects::IssuesController, feature_category: :team_planning do
go(id: issue.iid)
expect(json_response).to include('title_text', 'description', 'description_text')
- expect(json_response).to include('task_status', 'lock_version')
+ expect(json_response).to include('task_completion_status', 'lock_version')
end
end
end
diff --git a/spec/experiments/application_experiment_spec.rb b/spec/experiments/application_experiment_spec.rb
index ef8f8cbce3b..61c9fb5b4a4 100644
--- a/spec/experiments/application_experiment_spec.rb
+++ b/spec/experiments/application_experiment_spec.rb
@@ -211,7 +211,7 @@ RSpec.describe ApplicationExperiment, :experiment, feature_category: :experiment
application_experiment.variant(:variant1) {}
application_experiment.variant(:variant2) {}
- expect(application_experiment.assigned.name).to eq('variant2')
+ expect(application_experiment.assigned.name).to eq(:variant2)
end
end
@@ -248,7 +248,7 @@ RSpec.describe ApplicationExperiment, :experiment, feature_category: :experiment
end
it "caches the variant determined by the variant resolver" do
- expect(application_experiment.assigned.name).to eq('candidate') # we should be in the experiment
+ expect(application_experiment.assigned.name).to eq(:candidate) # we should be in the experiment
application_experiment.run
@@ -263,7 +263,7 @@ RSpec.describe ApplicationExperiment, :experiment, feature_category: :experiment
# the control.
stub_feature_flags(namespaced_stub: false) # simulate being not rolled out
- expect(application_experiment.assigned.name).to eq('control') # if we ask, it should be control
+ expect(application_experiment.assigned.name).to eq(:control) # if we ask, it should be control
application_experiment.run
@@ -299,29 +299,4 @@ RSpec.describe ApplicationExperiment, :experiment, feature_category: :experiment
end
end
end
-
- context "with deprecation warnings" do
- before do
- Gitlab::Experiment::Configuration.instance_variable_set(:@__dep_versions, nil) # clear the internal memoization
-
- allow(ActiveSupport::Deprecation).to receive(:new).and_call_original
- end
-
- it "doesn't warn on non dev/test environments" do
- allow(Gitlab).to receive(:dev_or_test_env?).and_return(false)
-
- expect { experiment(:example) { |e| e.use {} } }.not_to raise_error
- expect(ActiveSupport::Deprecation).not_to have_received(:new).with(anything, 'Gitlab::Experiment')
- end
-
- it "warns on dev and test environments" do
- allow(Gitlab).to receive(:dev_or_test_env?).and_return(true)
-
- # This will eventually raise an ActiveSupport::Deprecation exception,
- # it's ok to change it when that happens.
- expect { experiment(:example) { |e| e.use {} } }.not_to raise_error
-
- expect(ActiveSupport::Deprecation).to have_received(:new).with(anything, 'Gitlab::Experiment')
- end
- end
end
diff --git a/spec/experiments/security_reports_mr_widget_prompt_experiment_spec.rb b/spec/experiments/security_reports_mr_widget_prompt_experiment_spec.rb
index ee02fa5f1f2..9c6556745d7 100644
--- a/spec/experiments/security_reports_mr_widget_prompt_experiment_spec.rb
+++ b/spec/experiments/security_reports_mr_widget_prompt_experiment_spec.rb
@@ -4,6 +4,6 @@ require 'spec_helper'
RSpec.describe SecurityReportsMrWidgetPromptExperiment do
it "defines a control and candidate" do
- expect(subject.behaviors.keys).to match_array(%w[control candidate])
+ expect(subject.behaviors.keys).to match_array(%i[control candidate])
end
end
diff --git a/spec/features/issues/incident_issue_spec.rb b/spec/features/issues/incident_issue_spec.rb
index 41bbd79202f..145b51d207a 100644
--- a/spec/features/issues/incident_issue_spec.rb
+++ b/spec/features/issues/incident_issue_spec.rb
@@ -51,8 +51,8 @@ RSpec.describe 'Incident Detail', :js, feature_category: :team_planning do
aggregate_failures 'when on summary tab (default tab)' do
hidden_items = find_all('.js-issue-widgets')
- # Linked Issues/MRs and comment box and emoji block
- expect(hidden_items.count).to eq(3)
+ # Description footer + Linked Issues/MRs + comment box + emoji block
+ expect(hidden_items.count).to eq(4)
expect(hidden_items).to all(be_visible)
edit_button = find_all('[aria-label="Edit title and description"]')
diff --git a/spec/finders/abuse_reports_finder_spec.rb b/spec/finders/abuse_reports_finder_spec.rb
index d3b148375d4..ee93d042ca2 100644
--- a/spec/finders/abuse_reports_finder_spec.rb
+++ b/spec/finders/abuse_reports_finder_spec.rb
@@ -78,6 +78,24 @@ RSpec.describe AbuseReportsFinder, '#execute' do
expect(subject).to match_array([abuse_report_2])
end
end
+
+ context 'when value is not a valid status' do
+ let(:params) { { status: 'partial' } }
+
+ it 'defaults to returning open abuse reports' do
+ expect(subject).to match_array([abuse_report_1])
+ end
+ end
+
+ context 'when abuse_reports_list feature flag is disabled' do
+ before do
+ stub_feature_flags(abuse_reports_list: false)
+ end
+
+ it 'does not filter by status' do
+ expect(subject).to match_array([abuse_report_1, abuse_report_2])
+ end
+ end
end
context 'when params[:category] is present' do
diff --git a/spec/frontend/admin/abuse_reports/components/abuse_reports_filtered_search_bar_spec.js b/spec/frontend/admin/abuse_reports/components/abuse_reports_filtered_search_bar_spec.js
index 9efab8403a0..990503c453d 100644
--- a/spec/frontend/admin/abuse_reports/components/abuse_reports_filtered_search_bar_spec.js
+++ b/spec/frontend/admin/abuse_reports/components/abuse_reports_filtered_search_bar_spec.js
@@ -58,21 +58,28 @@ describe('AbuseReportsFilteredSearchBar', () => {
});
});
- it('sets status=open query when there is no initial status query', () => {
- createComponent();
+ it.each([undefined, 'invalid'])(
+ 'sets status=open query when initial status query is %s',
+ (status) => {
+ if (status) {
+ setWindowLocation(`?status=${status}`);
+ }
- expect(updateHistory).toHaveBeenCalledWith({
- url: 'https://localhost/?status=open',
- replace: true,
- });
+ createComponent();
- expect(findFilteredSearchBar().props('initialFilterValue')).toMatchObject([
- {
- type: FILTERED_SEARCH_TOKEN_STATUS.type,
- value: { data: 'open', operator: '=' },
- },
- ]);
- });
+ expect(updateHistory).toHaveBeenCalledWith({
+ url: 'https://localhost/?status=open',
+ replace: true,
+ });
+
+ expect(findFilteredSearchBar().props('initialFilterValue')).toMatchObject([
+ {
+ type: FILTERED_SEARCH_TOKEN_STATUS.type,
+ value: { data: 'open', operator: '=' },
+ },
+ ]);
+ },
+ );
it('parses and passes search param to `FilteredSearchBar` component as `initialFilterValue` prop', () => {
setWindowLocation('?status=closed&user=mr_abuser&reporter=ms_nitch');
diff --git a/spec/frontend/admin/abuse_reports/utils_spec.js b/spec/frontend/admin/abuse_reports/utils_spec.js
index 17f0b9acb26..3908edd3fdd 100644
--- a/spec/frontend/admin/abuse_reports/utils_spec.js
+++ b/spec/frontend/admin/abuse_reports/utils_spec.js
@@ -1,5 +1,8 @@
-import { FILTERED_SEARCH_TOKEN_CATEGORY } from '~/admin/abuse_reports/constants';
-import { buildFilteredSearchCategoryToken } from '~/admin/abuse_reports/utils';
+import {
+ FILTERED_SEARCH_TOKEN_CATEGORY,
+ FILTERED_SEARCH_TOKEN_STATUS,
+} from '~/admin/abuse_reports/constants';
+import { buildFilteredSearchCategoryToken, isValidStatus } from '~/admin/abuse_reports/utils';
describe('buildFilteredSearchCategoryToken', () => {
it('adds correctly formatted options to FILTERED_SEARCH_TOKEN_CATEGORY', () => {
@@ -11,3 +14,18 @@ describe('buildFilteredSearchCategoryToken', () => {
});
});
});
+
+describe('isValidStatus', () => {
+ const validStatuses = FILTERED_SEARCH_TOKEN_STATUS.options.map((o) => o.value);
+
+ it.each(validStatuses)(
+ 'returns true when status is an option value of FILTERED_SEARCH_TOKEN_STATUS',
+ (status) => {
+ expect(isValidStatus(status)).toBe(true);
+ },
+ );
+
+ it('return false when status is not an option value of FILTERED_SEARCH_TOKEN_STATUS', () => {
+ expect(isValidStatus('invalid')).toBe(false);
+ });
+});
diff --git a/spec/frontend/issues/show/components/app_spec.js b/spec/frontend/issues/show/components/app_spec.js
index 1006f54eeaf..554662c611d 100644
--- a/spec/frontend/issues/show/components/app_spec.js
+++ b/spec/frontend/issues/show/components/app_spec.js
@@ -1,6 +1,5 @@
import { GlIcon, GlIntersectionObserver } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
-import { setHTMLFixture } from 'helpers/fixtures';
import { createMockDirective, getBinding } from 'helpers/vue_mock_directive';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -100,22 +99,6 @@ describe('Issuable output', () => {
};
beforeEach(() => {
- setHTMLFixture(`
- <div>
- <title>Title</title>
- <div class="detail-page-description content-block">
- <details open>
- <summary>One</summary>
- </details>
- <details>
- <summary>Two</summary>
- </details>
- </div>
- <div class="flash-container"></div>
- <span id="task_status"></span>
- </div>
- `);
-
jest.spyOn(eventHub, '$emit');
axiosMock = new MockAdapter(axios);
diff --git a/spec/frontend/issues/show/components/description_spec.js b/spec/frontend/issues/show/components/description_spec.js
index 740b2f782e4..4b7b363f5da 100644
--- a/spec/frontend/issues/show/components/description_spec.js
+++ b/spec/frontend/issues/show/components/description_spec.js
@@ -1,8 +1,6 @@
-import $ from 'jquery';
import Vue, { nextTick } from 'vue';
import VueApollo from 'vue-apollo';
import getIssueDetailsQuery from 'ee_else_ce/work_items/graphql/get_issue_details.query.graphql';
-import setWindowLocation from 'helpers/set_window_location_helper';
import { TEST_HOST } from 'helpers/test_constants';
import createMockApollo from 'helpers/mock_apollo_helper';
import waitForPromises from 'helpers/wait_for_promises';
@@ -24,10 +22,6 @@ import {
import { descriptionProps as initialProps, descriptionHtmlWithList } from '../mock_data/mock_data';
jest.mock('~/alert');
-jest.mock('~/lib/utils/url_utility', () => ({
- ...jest.requireActual('~/lib/utils/url_utility'),
- updateHistory: jest.fn(),
-}));
jest.mock('~/task_list');
jest.mock('~/behaviors/markdown/render_gfm');
@@ -87,21 +81,6 @@ describe('Description component', () => {
beforeEach(() => {
window.gon = { sprite_icons: mockSpriteIcons };
-
- setWindowLocation(TEST_HOST);
-
- if (!document.querySelector('.issuable-meta')) {
- const metaData = document.createElement('div');
- metaData.classList.add('issuable-meta');
- metaData.innerHTML =
- '<div class="flash-container"></div><span id="task_status"></span><span id="task_status_short"></span>';
-
- document.body.appendChild(metaData);
- }
- });
-
- afterAll(() => {
- $('.issuable-meta .flash-container').remove();
});
it('doesnt animate first description changes', async () => {
@@ -202,46 +181,6 @@ describe('Description component', () => {
});
});
- describe('taskStatus', () => {
- it('adds full taskStatus', async () => {
- createComponent({
- props: {
- taskStatus: '1 of 1',
- },
- });
- await nextTick();
-
- expect(document.querySelector('.issuable-meta #task_status').textContent.trim()).toBe(
- '1 of 1',
- );
- });
-
- it('adds short taskStatus', async () => {
- createComponent({
- props: {
- taskStatus: '1 of 1',
- },
- });
- await nextTick();
-
- expect(document.querySelector('.issuable-meta #task_status_short').textContent.trim()).toBe(
- '1/1 checklist item',
- );
- });
-
- it('clears task status text when no tasks are present', async () => {
- createComponent({
- props: {
- taskStatus: '0 of 0',
- },
- });
-
- await nextTick();
-
- expect(document.querySelector('.issuable-meta #task_status').textContent.trim()).toBe('');
- });
- });
-
describe('with list', () => {
beforeEach(async () => {
createComponent({
diff --git a/spec/frontend/issues/show/components/edited_spec.js b/spec/frontend/issues/show/components/edited_spec.js
index a509627c347..dc0c7f5be46 100644
--- a/spec/frontend/issues/show/components/edited_spec.js
+++ b/spec/frontend/issues/show/components/edited_spec.js
@@ -1,41 +1,84 @@
+import { GlLink } from '@gitlab/ui';
import { mount } from '@vue/test-utils';
import { getTimeago } from '~/lib/utils/datetime_utility';
import Edited from '~/issues/show/components/edited.vue';
import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue';
-const timeago = getTimeago();
-
describe('Edited component', () => {
let wrapper;
- const findAuthorLink = () => wrapper.find('a');
+ const timeago = getTimeago();
+ const updatedAt = '2017-05-15T12:31:04.428Z';
+
+ const findAuthorLink = () => wrapper.findComponent(GlLink);
const findTimeAgoTooltip = () => wrapper.findComponent(TimeAgoTooltip);
const formatText = (text) => text.trim().replace(/\s\s+/g, ' ');
const mountComponent = (propsData) => mount(Edited, { propsData });
- const updatedAt = '2017-05-15T12:31:04.428Z';
- it('renders an edited at+by string', () => {
- wrapper = mountComponent({
- updatedAt,
- updatedByName: 'Some User',
- updatedByPath: '/some_user',
+ describe('task status section', () => {
+ describe('task status text', () => {
+ it('renders when there is a task status', () => {
+ wrapper = mountComponent({ taskCompletionStatus: { completed_count: 1, count: 3 } });
+
+ expect(wrapper.text()).toContain('1 of 3 checklist items completed');
+ });
+
+ it('does not render when task count is 0', () => {
+ wrapper = mountComponent({ taskCompletionStatus: { completed_count: 0, count: 0 } });
+
+ expect(wrapper.text()).not.toContain('0 of 0 checklist items completed');
+ });
});
- expect(formatText(wrapper.text())).toBe(`Edited ${timeago.format(updatedAt)} by Some User`);
- expect(findAuthorLink().attributes('href')).toBe('/some_user');
- expect(findTimeAgoTooltip().exists()).toBe(true);
+ describe('checkmark', () => {
+ it('renders when all tasks are completed', () => {
+ wrapper = mountComponent({ taskCompletionStatus: { completed_count: 3, count: 3 } });
+
+ expect(wrapper.text()).toContain('✓');
+ });
+
+ it('does not render when tasks are incomplete', () => {
+ wrapper = mountComponent({ taskCompletionStatus: { completed_count: 2, count: 3 } });
+
+ expect(wrapper.text()).not.toContain('✓');
+ });
+
+ it('does not render when task count is 0', () => {
+ wrapper = mountComponent({ taskCompletionStatus: { completed_count: 0, count: 0 } });
+
+ expect(wrapper.text()).not.toContain('✓');
+ });
+ });
+
+ describe('middot', () => {
+ it('renders when there is also "Edited by" text', () => {
+ wrapper = mountComponent({
+ taskCompletionStatus: { completed_count: 3, count: 3 },
+ updatedAt,
+ });
+
+ expect(wrapper.text()).toContain('·');
+ });
+
+ it('does not render when there is no "Edited by" text', () => {
+ wrapper = mountComponent({ taskCompletionStatus: { completed_count: 3, count: 3 } });
+
+ expect(wrapper.text()).not.toContain('·');
+ });
+ });
});
- it('if no updatedAt is provided, no time element will be rendered', () => {
+ it('renders an edited at+by string', () => {
wrapper = mountComponent({
+ updatedAt,
updatedByName: 'Some User',
updatedByPath: '/some_user',
});
- expect(formatText(wrapper.text())).toBe('Edited by Some User');
+ expect(formatText(wrapper.text())).toBe(`Edited ${timeago.format(updatedAt)} by Some User`);
expect(findAuthorLink().attributes('href')).toBe('/some_user');
- expect(findTimeAgoTooltip().exists()).toBe(false);
+ expect(findTimeAgoTooltip().exists()).toBe(true);
});
it('if no updatedByName and updatedByPath is provided, no user element will be rendered', () => {
diff --git a/spec/frontend/issues/show/mock_data/mock_data.js b/spec/frontend/issues/show/mock_data/mock_data.js
index 86d09665947..e923c0d97f4 100644
--- a/spec/frontend/issues/show/mock_data/mock_data.js
+++ b/spec/frontend/issues/show/mock_data/mock_data.js
@@ -5,7 +5,7 @@ export const initialRequest = {
title_text: 'this is a title',
description: '<p>this is a description!</p>',
description_text: 'this is a description',
- task_status: '2 of 4 completed',
+ task_completion_status: { completed_count: 2, count: 4 },
updated_at: '2015-05-15T12:31:04.428Z',
updated_by_name: 'Some User',
updated_by_path: '/some_user',
@@ -17,7 +17,7 @@ export const secondRequest = {
title_text: '2',
description: '<p>42</p>',
description_text: '42',
- task_status: '0 of 0 completed',
+ task_completion_status: { completed_count: 0, count: 0 },
updated_at: '2016-05-15T12:31:04.428Z',
updated_by_name: 'Other User',
updated_by_path: '/other_user',
@@ -30,7 +30,7 @@ export const putRequest = {
title_text: 'PUT',
description: '<p>PUT_DESC</p>',
description_text: 'PUT_DESC',
- task_status: '0 of 0 completed',
+ task_completion_status: { completed_count: 0, count: 0 },
updated_at: '2016-05-15T12:31:04.428Z',
updated_by_name: 'Other User',
updated_by_path: '/other_user',
@@ -41,7 +41,6 @@ export const descriptionProps = {
canUpdate: true,
descriptionHtml: 'test',
descriptionText: 'test',
- taskStatus: '',
updateUrl: TEST_HOST,
};
@@ -60,6 +59,7 @@ export const appProps = {
initialTitleText: '',
initialDescriptionHtml: 'test',
initialDescriptionText: 'test',
+ initialTaskCompletionStatus: { completed_count: 2, count: 4 },
lockVersion: 1,
issueType: 'issue',
markdownPreviewPath: '/',
diff --git a/spec/frontend/work_items/components/notes/work_item_note_spec.js b/spec/frontend/work_items/components/notes/work_item_note_spec.js
index 8e574dc1a81..2d6d12c69f9 100644
--- a/spec/frontend/work_items/components/notes/work_item_note_spec.js
+++ b/spec/frontend/work_items/components/notes/work_item_note_spec.js
@@ -178,8 +178,7 @@ describe('Work Item Note', () => {
},
});
- expect(findEditedAt().exists()).toBe(true);
- expect(findEditedAt().props()).toEqual({
+ expect(findEditedAt().props()).toMatchObject({
updatedAt: '2023-02-12T07:47:40Z',
updatedByName: 'Administrator',
updatedByPath: 'test-path',
diff --git a/spec/frontend/work_items/components/work_item_description_spec.js b/spec/frontend/work_items/components/work_item_description_spec.js
index b4b7b8989ea..3482d011be9 100644
--- a/spec/frontend/work_items/components/work_item_description_spec.js
+++ b/spec/frontend/work_items/components/work_item_description_spec.js
@@ -179,7 +179,7 @@ describe('WorkItemDescription', () => {
}),
});
- expect(findEditedAt().props()).toEqual({
+ expect(findEditedAt().props()).toMatchObject({
updatedAt: lastEditedAt,
updatedByName: lastEditedBy.name,
updatedByPath: lastEditedBy.webPath,
diff --git a/spec/helpers/issuables_helper_spec.rb b/spec/helpers/issuables_helper_spec.rb
index fd10b204e50..7549e37d4fd 100644
--- a/spec/helpers/issuables_helper_spec.rb
+++ b/spec/helpers/issuables_helper_spec.rb
@@ -416,7 +416,7 @@ RSpec.describe IssuablesHelper, feature_category: :team_planning do
initialTitleText: issue.title,
initialDescriptionHtml: '<p dir="auto">issue text</p>',
initialDescriptionText: 'issue text',
- initialTaskStatus: '0 of 0 checklist items completed',
+ initialTaskCompletionStatus: { completed_count: 0, count: 0 },
issueType: 'issue',
iid: issue.iid.to_s,
isHidden: false
diff --git a/spec/lib/gitlab/experiment/rollout/feature_spec.rb b/spec/lib/gitlab/experiment/rollout/feature_spec.rb
index a66f4fea207..cd46e7b3386 100644
--- a/spec/lib/gitlab/experiment/rollout/feature_spec.rb
+++ b/spec/lib/gitlab/experiment/rollout/feature_spec.rb
@@ -3,7 +3,7 @@
require 'spec_helper'
RSpec.describe Gitlab::Experiment::Rollout::Feature, :experiment do
- subject { described_class.new.for(subject_experiment) }
+ subject { described_class.new(subject_experiment) }
let(:subject_experiment) { experiment('namespaced/stub') }
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 05a41ddc6c5..7a31fd6a77d 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -136,7 +136,6 @@ RSpec.describe Notes::CreateService, feature_category: :team_planning do
let(:action) { 'create_commit_comment' }
let(:label) { 'counts.commit_comment' }
let(:namespace) { project.namespace }
- let(:feature_flag_name) { :route_hll_to_snowplow_phase4 }
end
end
diff --git a/spec/workers/namespaces/root_statistics_worker_spec.rb b/spec/workers/namespaces/root_statistics_worker_spec.rb
index edea9132850..bc2eca86711 100644
--- a/spec/workers/namespaces/root_statistics_worker_spec.rb
+++ b/spec/workers/namespaces/root_statistics_worker_spec.rb
@@ -3,48 +3,88 @@
require 'spec_helper'
RSpec.describe Namespaces::RootStatisticsWorker, '#perform', feature_category: :source_code_management do
- let(:group) { create(:group, :with_aggregation_schedule) }
+ let_it_be(:group) { create(:group, :with_aggregation_schedule) }
subject(:worker) { described_class.new }
- context 'with a namespace' do
+ RSpec.shared_examples 'bypasses aggregation schedule' do
it 'executes refresher service' do
expect_any_instance_of(Namespaces::StatisticsRefresherService)
.to receive(:execute).and_call_original
+ expect(group).not_to receive(:aggregation_scheduled?)
worker.perform(group.id)
end
- it 'deletes namespace aggregated schedule row' do
- worker.perform(group.id)
+ it 'does not change AggregationSchedule count' do
+ expect do
+ worker.perform(group.id)
+ end.not_to change { Namespace::AggregationSchedule.count }
+ end
+ end
+
+ context 'with a namespace' do
+ context 'with remove_aggregation_schedule_lease feature flag enabled' do
+ it_behaves_like 'bypasses aggregation schedule'
+
+ context 'when something goes wrong when updating' do
+ before do
+ allow_any_instance_of(Namespaces::StatisticsRefresherService)
+ .to receive(:execute)
+ .and_raise(Namespaces::StatisticsRefresherService::RefresherError, 'error')
+ end
+
+ it 'logs the error' do
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).once
- expect(group.reload.aggregation_schedule).to be_nil
+ worker.perform(group.id)
+ end
+ end
end
- context 'when something goes wrong when updating' do
+ context 'with remove_aggregation_schedule_lease feature flag disabled' do
before do
- allow_any_instance_of(Namespaces::StatisticsRefresherService)
- .to receive(:execute)
- .and_raise(Namespaces::StatisticsRefresherService::RefresherError, 'error')
+ stub_feature_flags(remove_aggregation_schedule_lease: false)
+ end
+
+ it 'executes refresher service' do
+ expect_any_instance_of(Namespaces::StatisticsRefresherService)
+ .to receive(:execute).and_call_original
+
+ worker.perform(group.id)
end
- it 'does not delete the aggregation schedule' do
+ it 'deletes namespace aggregated schedule row' do
worker.perform(group.id)
- expect(group.reload.aggregation_schedule).to be_present
+ expect(group.reload.aggregation_schedule).to be_nil
end
- it 'logs the error' do
- # A Namespace::RootStatisticsWorker is scheduled when
- # a Namespace::AggregationSchedule is created, so having
- # create(:group, :with_aggregation_schedule), will execute
- # another worker
- allow_any_instance_of(Namespace::AggregationSchedule)
- .to receive(:schedule_root_storage_statistics).and_return(nil)
+ context 'when something goes wrong when updating' do
+ before do
+ allow_any_instance_of(Namespaces::StatisticsRefresherService)
+ .to receive(:execute)
+ .and_raise(Namespaces::StatisticsRefresherService::RefresherError, 'error')
+ end
- expect(Gitlab::ErrorTracking).to receive(:track_exception).once
+ it 'does not delete the aggregation schedule' do
+ worker.perform(group.id)
- worker.perform(group.id)
+ expect(group.reload.aggregation_schedule).to be_present
+ end
+
+ it 'logs the error' do
+ # A Namespace::RootStatisticsWorker is scheduled when
+ # a Namespace::AggregationSchedule is created, so having
+ # create(:group, :with_aggregation_schedule), will execute
+ # another worker
+ allow_any_instance_of(Namespace::AggregationSchedule)
+ .to receive(:schedule_root_storage_statistics).and_return(nil)
+
+ expect(Gitlab::ErrorTracking).to receive(:track_exception).once
+
+ worker.perform(group.id)
+ end
end
end
end
@@ -67,26 +107,42 @@ RSpec.describe Namespaces::RootStatisticsWorker, '#perform', feature_category: :
group.aggregation_schedule.destroy!
end
- it 'does not execute the refresher service' do
- expect_any_instance_of(Namespaces::StatisticsRefresherService)
- .not_to receive(:execute)
+ context 'with remove_aggregation_schedule_lease feature flag disabled' do
+ before do
+ stub_feature_flags(remove_aggregation_schedule_lease: false)
+ end
- worker.perform(group.id)
+ it 'does not execute the refresher service' do
+ expect_any_instance_of(Namespaces::StatisticsRefresherService)
+ .not_to receive(:execute)
+
+ worker.perform(group.id)
+ end
+ end
+
+ context 'with remove_aggregation_schedule_lease feature flag enabled' do
+ it_behaves_like 'bypasses aggregation schedule'
end
end
it_behaves_like 'an idempotent worker' do
let(:job_args) { [group.id] }
- it 'deletes one aggregation schedule' do
- # Make sure the group and it's aggregation schedule are created before
- # counting
- group
+ context 'with remove_aggregation_schedule_lease feature flag disabled' do
+ before do
+ stub_feature_flags(remove_aggregation_schedule_lease: false)
+ end
- expect { worker.perform(*job_args) }
- .to change { Namespace::AggregationSchedule.count }.by(-1)
- expect { worker.perform(*job_args) }
- .not_to change { Namespace::AggregationSchedule.count }
+ it 'deletes one aggregation schedule' do
+ # Make sure the group and it's aggregation schedule are created before
+ # counting
+ group
+
+ expect { worker.perform(*job_args) }
+ .to change { Namespace::AggregationSchedule.count }.by(-1)
+ expect { worker.perform(*job_args) }
+ .not_to change { Namespace::AggregationSchedule.count }
+ end
end
end
diff --git a/spec/workers/namespaces/schedule_aggregation_worker_spec.rb b/spec/workers/namespaces/schedule_aggregation_worker_spec.rb
index 69bd0f1ce47..249c143606f 100644
--- a/spec/workers/namespaces/schedule_aggregation_worker_spec.rb
+++ b/spec/workers/namespaces/schedule_aggregation_worker_spec.rb
@@ -3,32 +3,68 @@
require 'spec_helper'
RSpec.describe Namespaces::ScheduleAggregationWorker, '#perform', :clean_gitlab_redis_shared_state, feature_category: :source_code_management do
- let(:group) { create(:group) }
+ let_it_be(:group) { create(:group) }
subject(:worker) { described_class.new }
+ RSpec.shared_examples 'schedule root statistic worker' do
+ it 'enqueues only RootStatisticsWorker' do
+ expect(Namespaces::RootStatisticsWorker).to receive(:perform_async).with(group.root_ancestor.id)
+ expect(Namespace::AggregationSchedule).not_to receive(:safe_find_or_create_by!)
+ .with(namespace_id: group.root_ancestor.id)
+
+ worker.perform(group.id)
+ end
+
+ it 'does not change AggregationSchedule count' do
+ expect do
+ worker.perform(group.root_ancestor.id)
+ end.not_to change { Namespace::AggregationSchedule.count }
+ end
+ end
+
context 'when group is the root ancestor' do
- context 'when aggregation schedule exists' do
- it 'does not create a new one' do
- stub_aggregation_schedule_statistics
+ context 'with remove_aggregation_schedule_lease feature flag enabled' do
+ context 'when aggregation schedule does not exist' do
+ it_behaves_like "schedule root statistic worker"
+ end
- Namespace::AggregationSchedule.safe_find_or_create_by!(namespace_id: group.id)
+ context 'when aggregation schedule does exist' do
+ before do
+ Namespace::AggregationSchedule.safe_find_or_create_by!(namespace_id: group.id)
+ end
- expect do
- worker.perform(group.id)
- end.not_to change { Namespace::AggregationSchedule.count }
+ it_behaves_like "schedule root statistic worker"
end
end
- context 'when aggregation schedule does not exist' do
- it 'creates one' do
- stub_aggregation_schedule_statistics
+ context 'with remove_aggregation_schedule_lease feature flag disabled' do
+ before do
+ stub_feature_flags(remove_aggregation_schedule_lease: false)
+ end
+
+ context 'when aggregation schedule exists' do
+ it 'does not create a new one' do
+ stub_aggregation_schedule_statistics
+
+ Namespace::AggregationSchedule.safe_find_or_create_by!(namespace_id: group.id)
- expect do
- worker.perform(group.id)
- end.to change { Namespace::AggregationSchedule.count }.by(1)
+ expect do
+ worker.perform(group.id)
+ end.not_to change { Namespace::AggregationSchedule.count }
+ end
+ end
+
+ context 'when aggregation schedule does not exist' do
+ it 'creates one' do
+ stub_aggregation_schedule_statistics
- expect(group.aggregation_schedule).to be_present
+ expect do
+ worker.perform(group.id)
+ end.to change { Namespace::AggregationSchedule.count }.by(1)
+
+ expect(group.aggregation_schedule).to be_present
+ end
end
end
end
@@ -37,12 +73,22 @@ RSpec.describe Namespaces::ScheduleAggregationWorker, '#perform', :clean_gitlab_
let(:parent_group) { create(:group) }
let(:group) { create(:group, parent: parent_group) }
- it 'creates an aggregation schedule for the root' do
- stub_aggregation_schedule_statistics
+ context 'with remove_aggregation_schedule_lease feature flag enabled' do
+ it_behaves_like "schedule root statistic worker"
+ end
- worker.perform(group.id)
+ context 'with remove_aggregation_schedule_lease feature flag disabled' do
+ before do
+ stub_feature_flags(remove_aggregation_schedule_lease: false)
+ end
- expect(parent_group.aggregation_schedule).to be_present
+ it 'creates an aggregation schedule for the root' do
+ stub_aggregation_schedule_statistics
+
+ worker.perform(group.id)
+
+ expect(parent_group.aggregation_schedule).to be_present
+ end
end
end
@@ -57,11 +103,17 @@ RSpec.describe Namespaces::ScheduleAggregationWorker, '#perform', :clean_gitlab_
it_behaves_like 'an idempotent worker' do
let(:job_args) { [group.id] }
- it 'creates a single aggregation schedule' do
- expect { worker.perform(*job_args) }
- .to change { Namespace::AggregationSchedule.count }.by(1)
- expect { worker.perform(*job_args) }
- .not_to change { Namespace::AggregationSchedule.count }
+ context 'with remove_aggregation_schedule_lease feature flag disabled' do
+ before do
+ stub_feature_flags(remove_aggregation_schedule_lease: false)
+ end
+
+ it 'creates a single aggregation schedule' do
+ expect { worker.perform(*job_args) }
+ .to change { Namespace::AggregationSchedule.count }.by(1)
+ expect { worker.perform(*job_args) }
+ .not_to change { Namespace::AggregationSchedule.count }
+ end
end
end