Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-06-06 09:08:48 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-06-06 09:08:48 +0300
commitc77b780ee080b978bd5a63f642f741e8892383dc (patch)
tree7c50ab6450a61d27485f9557b666af46ec5adfdd
parent5e448ff06309854c838fb5eaa46fd05ebc5218ab (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.rubocop_todo/gitlab/strong_memoize_attr.yml22
-rw-r--r--.rubocop_todo/layout/line_continuation_spacing.yml2
-rw-r--r--app/assets/javascripts/analytics/shared/constants.js23
-rw-r--r--app/assets/javascripts/ci/artifacts/components/artifact_row.vue5
-rw-r--r--app/assets/javascripts/ci/artifacts/components/job_artifacts_table.vue5
-rw-r--r--app/assets/javascripts/ci/artifacts/constants.js1
-rw-r--r--app/assets/javascripts/groups/components/group_name_and_path.vue2
-rw-r--r--app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue18
-rw-r--r--app/assets/javascripts/issues/dashboard/index.js6
-rw-r--r--app/assets/javascripts/issues/dashboard/queries/get_issues.query.graphql5
-rw-r--r--app/assets/javascripts/issues/dashboard/queries/issue.fragment.graphql56
-rw-r--r--app/assets/javascripts/lib/apollo/persistence_mapper.js4
-rw-r--r--app/controllers/dashboard_controller.rb4
-rw-r--r--app/controllers/projects/artifacts_controller.rb4
-rw-r--r--app/graphql/mutations/ci/job_artifact/bulk_destroy.rb5
-rw-r--r--app/models/user.rb7
-rw-r--r--app/services/packages/cleanup/execute_policy_service.rb5
-rw-r--r--app/services/packages/cleanup/update_policy_service.rb5
-rw-r--r--app/services/packages/composer/create_package_service.rb5
-rw-r--r--app/services/packages/debian/extract_changes_metadata_service.rb30
-rw-r--r--app/services/packages/debian/generate_distribution_key_service.rb5
-rw-r--r--app/services/packages/debian/generate_distribution_service.rb10
-rw-r--r--app/services/packages/debian/process_changes_service.rb22
-rw-r--r--app/services/packages/helm/process_file_service.rb27
-rw-r--r--app/services/packages/maven/metadata/base_create_xml_service.rb7
-rw-r--r--app/services/packages/maven/metadata/create_plugins_xml_service.rb39
-rw-r--r--app/services/packages/maven/metadata/create_versions_xml_service.rb42
-rw-r--r--app/services/packages/maven/metadata/sync_service.rb19
-rw-r--r--app/services/packages/npm/create_package_service.rb53
-rw-r--r--app/services/packages/npm/create_tag_service.rb5
-rw-r--r--app/services/packages/nuget/metadata_extraction_service.rb5
-rw-r--r--app/services/packages/nuget/search_service.rb22
-rw-r--r--app/services/packages/pypi/create_package_service.rb5
-rw-r--r--app/services/packages/rpm/parse_package_service.rb5
-rw-r--r--app/services/packages/rubygems/dependency_resolver_service.rb5
-rw-r--r--app/services/packages/rubygems/process_gem_service.rb18
-rw-r--r--app/services/packages/terraform_module/create_package_service.rb10
-rw-r--r--app/services/packages/update_tags_service.rb5
-rw-r--r--app/views/profiles/accounts/show.html.haml2
-rw-r--r--app/views/shared/runners/_form.html.haml4
-rw-r--r--app/workers/gitlab/github_import/stage/import_pull_requests_worker.rb17
-rw-r--r--config/feature_flags/development/ci_job_artifact_bulk_destroy.yml8
-rw-r--r--doc/api/graphql/reference/index.md8
-rw-r--r--doc/api/group_import_export.md29
-rw-r--r--doc/api/group_relations_export.md16
-rw-r--r--doc/api/project_import_export.md11
-rw-r--r--doc/api/project_relations_export.md25
-rw-r--r--doc/ci/jobs/job_artifacts.md8
-rw-r--r--doc/ci/pipelines/cicd_minutes.md2
-rw-r--r--doc/ci/runners/runners_scope.md4
-rw-r--r--doc/user/admin_area/settings/continuous_integration.md4
-rw-r--r--doc/user/analytics/value_streams_dashboard.md1
-rw-r--r--doc/user/application_security/sast/customize_rulesets.md2
-rw-r--r--doc/user/group/import/index.md7
-rw-r--r--locale/gitlab.pot44
-rw-r--r--spec/frontend/ci/artifacts/components/artifact_row_spec.js52
-rw-r--r--spec/frontend/ci/artifacts/components/job_artifacts_table_spec.js362
-rw-r--r--spec/frontend/issues/dashboard/mock_data.js4
-rw-r--r--spec/requests/api/graphql/mutations/ci/job_artifact/bulk_destroy_spec.rb17
-rw-r--r--spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb102
60 files changed, 586 insertions, 661 deletions
diff --git a/.rubocop_todo/gitlab/strong_memoize_attr.yml b/.rubocop_todo/gitlab/strong_memoize_attr.yml
index 357e1f55ab0..22d6c104750 100644
--- a/.rubocop_todo/gitlab/strong_memoize_attr.yml
+++ b/.rubocop_todo/gitlab/strong_memoize_attr.yml
@@ -218,28 +218,6 @@ Gitlab/StrongMemoizeAttr:
- 'app/services/metrics/dashboard/dynamic_embed_service.rb'
- 'app/services/metrics/dashboard/gitlab_alert_embed_service.rb'
- 'app/services/namespaces/package_settings/update_service.rb'
- - 'app/services/packages/cleanup/execute_policy_service.rb'
- - 'app/services/packages/cleanup/update_policy_service.rb'
- - 'app/services/packages/composer/create_package_service.rb'
- - 'app/services/packages/debian/extract_changes_metadata_service.rb'
- - 'app/services/packages/debian/generate_distribution_key_service.rb'
- - 'app/services/packages/debian/generate_distribution_service.rb'
- - 'app/services/packages/debian/process_changes_service.rb'
- - 'app/services/packages/helm/process_file_service.rb'
- - 'app/services/packages/maven/metadata/base_create_xml_service.rb'
- - 'app/services/packages/maven/metadata/create_plugins_xml_service.rb'
- - 'app/services/packages/maven/metadata/create_versions_xml_service.rb'
- - 'app/services/packages/maven/metadata/sync_service.rb'
- - 'app/services/packages/npm/create_package_service.rb'
- - 'app/services/packages/npm/create_tag_service.rb'
- - 'app/services/packages/nuget/metadata_extraction_service.rb'
- - 'app/services/packages/nuget/search_service.rb'
- - 'app/services/packages/pypi/create_package_service.rb'
- - 'app/services/packages/rpm/parse_package_service.rb'
- - 'app/services/packages/rubygems/dependency_resolver_service.rb'
- - 'app/services/packages/rubygems/process_gem_service.rb'
- - 'app/services/packages/terraform_module/create_package_service.rb'
- - 'app/services/packages/update_tags_service.rb'
- 'app/services/projects/container_repository/cleanup_tags_base_service.rb'
- 'app/services/projects/container_repository/third_party/cleanup_tags_service.rb'
- 'app/services/projects/create_from_template_service.rb'
diff --git a/.rubocop_todo/layout/line_continuation_spacing.yml b/.rubocop_todo/layout/line_continuation_spacing.yml
index 9095bd96bef..355ef3f3ff8 100644
--- a/.rubocop_todo/layout/line_continuation_spacing.yml
+++ b/.rubocop_todo/layout/line_continuation_spacing.yml
@@ -22,8 +22,6 @@ Layout/LineContinuationSpacing:
- 'app/services/uploads/destroy_service.rb'
- 'app/services/users/email_verification/validate_token_service.rb'
- 'config/initializers_before_autoloader/003_gc_compact.rb'
- - 'ee/app/components/namespaces/storage/pre_enforcement_alert_component.rb'
- - 'ee/app/components/namespaces/storage/user_pre_enforcement_alert_component.rb'
- 'ee/app/controllers/ee/ldap/omniauth_callbacks_controller.rb'
- 'ee/app/graphql/mutations/app_sec/fuzzing/api/ci_configuration/create.rb'
- 'ee/app/graphql/mutations/requirements_management/export_requirements.rb'
diff --git a/app/assets/javascripts/analytics/shared/constants.js b/app/assets/javascripts/analytics/shared/constants.js
index e9d6d121e6d..9463286e53e 100644
--- a/app/assets/javascripts/analytics/shared/constants.js
+++ b/app/assets/javascripts/analytics/shared/constants.js
@@ -20,10 +20,13 @@ const formatDateParam = (d) => dateFormat(d, dateFormats.isoDate, true);
export const METRIC_POPOVER_LABEL = s__('ValueStreamAnalytics|View details');
-export const KEY_METRICS = {
+export const ISSUES_COMPLETED_TYPE = 'issues_completed';
+
+export const FLOW_METRICS = {
LEAD_TIME: 'lead_time',
CYCLE_TIME: 'cycle_time',
ISSUES: 'issues',
+ ISSUES_COMPLETED: ISSUES_COMPLETED_TYPE,
COMMITS: 'commits',
DEPLOYS: 'deploys',
};
@@ -38,7 +41,7 @@ export const DORA_METRICS = {
const VSA_FLOW_METRICS_GROUP = {
key: 'key_metrics',
title: s__('ValueStreamAnalytics|Key metrics'),
- keys: Object.values(KEY_METRICS),
+ keys: Object.values(FLOW_METRICS),
};
export const VSA_METRICS_GROUPS = [VSA_FLOW_METRICS_GROUP];
@@ -90,7 +93,7 @@ export const METRIC_TOOLTIPS = {
projectLink: '-/pipelines/charts?chart=change-failure-rate',
docsLink: helpPagePath('user/analytics/dora_metrics', { anchor: 'change-failure-rate' }),
},
- [KEY_METRICS.LEAD_TIME]: {
+ [FLOW_METRICS.LEAD_TIME]: {
description: s__('ValueStreamAnalytics|Median time from issue created to issue closed.'),
groupLink: '-/analytics/value_stream_analytics',
projectLink: '-/value_stream_analytics',
@@ -98,7 +101,7 @@ export const METRIC_TOOLTIPS = {
anchor: 'view-the-lead-time-and-cycle-time-for-issues',
}),
},
- [KEY_METRICS.CYCLE_TIME]: {
+ [FLOW_METRICS.CYCLE_TIME]: {
description: s__(
"ValueStreamAnalytics|Median time from the earliest commit of a linked issue's merge request to when that issue is closed.",
),
@@ -108,13 +111,21 @@ export const METRIC_TOOLTIPS = {
anchor: 'view-the-lead-time-and-cycle-time-for-issues',
}),
},
- [KEY_METRICS.ISSUES]: {
+ [FLOW_METRICS.ISSUES]: {
description: s__('ValueStreamAnalytics|Number of new issues created.'),
groupLink: '-/issues_analytics',
projectLink: '-/analytics/issues_analytics',
docsLink: helpPagePath('user/analytics/issue_analytics'),
},
- [KEY_METRICS.DEPLOYS]: {
+ [FLOW_METRICS.ISSUES_COMPLETED]: {
+ description: s__('ValueStreamAnalytics|Number of issues closed by month.'),
+ groupLink: '-/analytics/value_stream_analytics',
+ projectLink: '-/value_stream_analytics',
+ docsLink: helpPagePath('user/analytics/value_streams_dashboard', {
+ anchor: 'dashboard-metrics-and-drill-down-reports',
+ }),
+ },
+ [FLOW_METRICS.DEPLOYS]: {
description: s__('ValueStreamAnalytics|Total number of deploys to production.'),
groupLink: '-/analytics/productivity_analytics',
projectLink: '-/analytics/merge_request_analytics',
diff --git a/app/assets/javascripts/ci/artifacts/components/artifact_row.vue b/app/assets/javascripts/ci/artifacts/components/artifact_row.vue
index 5b1c322f07a..d4de42b10a8 100644
--- a/app/assets/javascripts/ci/artifacts/components/artifact_row.vue
+++ b/app/assets/javascripts/ci/artifacts/components/artifact_row.vue
@@ -8,12 +8,10 @@ import {
GlTooltipDirective,
} from '@gitlab/ui';
import { numberToHumanSize } from '~/lib/utils/number_utils';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import {
I18N_EXPIRED,
I18N_DOWNLOAD,
I18N_DELETE,
- BULK_DELETE_FEATURE_FLAG,
I18N_BULK_DELETE_MAX_SELECTED,
} from '../constants';
@@ -29,7 +27,6 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
- mixins: [glFeatureFlagsMixin()],
inject: ['canDestroyArtifacts'],
props: {
artifact: {
@@ -66,7 +63,7 @@ export default {
return numberToHumanSize(this.artifact.size);
},
canBulkDestroyArtifacts() {
- return this.glFeatures[BULK_DELETE_FEATURE_FLAG] && this.canDestroyArtifacts;
+ return this.canDestroyArtifacts;
},
},
methods: {
diff --git a/app/assets/javascripts/ci/artifacts/components/job_artifacts_table.vue b/app/assets/javascripts/ci/artifacts/components/job_artifacts_table.vue
index 81739db9be2..88334488fdd 100644
--- a/app/assets/javascripts/ci/artifacts/components/job_artifacts_table.vue
+++ b/app/assets/javascripts/ci/artifacts/components/job_artifacts_table.vue
@@ -15,7 +15,6 @@ import { createAlert } from '~/alert';
import { getIdFromGraphQLId, convertToGraphQLId } from '~/graphql_shared/utils';
import TimeAgo from '~/vue_shared/components/time_ago_tooltip.vue';
import CiIcon from '~/vue_shared/components/ci_icon.vue';
-import glFeatureFlagsMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { TYPENAME_PROJECT } from '~/graphql_shared/constants';
import getJobArtifactsQuery from '../graphql/queries/get_job_artifacts.query.graphql';
import { totalArtifactsSizeForJob, mapArchivesToJobNodes, mapBooleansToJobNodes } from '../utils';
@@ -39,7 +38,6 @@ import {
INITIAL_NEXT_PAGE_CURSOR,
JOBS_PER_PAGE,
INITIAL_LAST_PAGE_SIZE,
- BULK_DELETE_FEATURE_FLAG,
I18N_BULK_DELETE_ERROR,
I18N_BULK_DELETE_PARTIAL_ERROR,
I18N_BULK_DELETE_CONFIRMATION_TOAST,
@@ -83,7 +81,6 @@ export default {
directives: {
GlTooltip: GlTooltipDirective,
},
- mixins: [glFeatureFlagsMixin()],
inject: ['projectId', 'projectPath', 'canDestroyArtifacts'],
apollo: {
jobArtifacts: {
@@ -161,7 +158,7 @@ export default {
return this.selectedArtifacts.length >= SELECTED_ARTIFACTS_MAX_COUNT;
},
canBulkDestroyArtifacts() {
- return this.glFeatures[BULK_DELETE_FEATURE_FLAG] && this.canDestroyArtifacts;
+ return this.canDestroyArtifacts;
},
isDeletingArtifactsForJob() {
return this.jobArtifactsToDelete.length > 0;
diff --git a/app/assets/javascripts/ci/artifacts/constants.js b/app/assets/javascripts/ci/artifacts/constants.js
index 4fb4b45fd7f..2d89b6541f3 100644
--- a/app/assets/javascripts/ci/artifacts/constants.js
+++ b/app/assets/javascripts/ci/artifacts/constants.js
@@ -54,7 +54,6 @@ export const I18N_FEEDBACK_BANNER_BODY = s__(
export const I18N_FEEDBACK_BANNER_BUTTON = s__('Artifacts|Take a quick survey');
export const FEEDBACK_URL = 'https://gitlab.fra1.qualtrics.com/jfe/form/SV_cI9rAUI20Vo2St8';
-export const BULK_DELETE_FEATURE_FLAG = 'ciJobArtifactBulkDestroy';
export const SELECTED_ARTIFACTS_MAX_COUNT = 50;
export const I18N_BULK_DELETE_MAX_SELECTED = s__(
'Artifacts|Maximum selected artifacts limit reached',
diff --git a/app/assets/javascripts/groups/components/group_name_and_path.vue b/app/assets/javascripts/groups/components/group_name_and_path.vue
index 1f9fc68a612..8d193310a98 100644
--- a/app/assets/javascripts/groups/components/group_name_and_path.vue
+++ b/app/assets/javascripts/groups/components/group_name_and_path.vue
@@ -56,7 +56,7 @@ export default {
'An error occurred while checking group path. Please refresh and try again.',
),
changingUrlWarningMessage: s__('Groups|Changing group URL can have unintended side effects.'),
- learnMore: s__('Groups|Learn more'),
+ learnMore: __('Learn more'),
},
inputSize: { md: 'lg' },
changingGroupPathHelpPagePath: helpPagePath('user/group/manage', {
diff --git a/app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue b/app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue
index 4d936428ea6..14fe88b8f61 100644
--- a/app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue
+++ b/app/assets/javascripts/issues/dashboard/components/issues_dashboard_app.vue
@@ -29,6 +29,7 @@ import {
getSortOptions,
isSortKey,
} from '~/issues/list/utils';
+import { fetchPolicies } from '~/lib/graphql';
import axios from '~/lib/utils/axios_utils';
import { scrollUp } from '~/lib/utils/scroll_utils';
import { getParameterByName } from '~/lib/utils/url_utility';
@@ -126,6 +127,10 @@ export default {
update(data) {
return data.issues.nodes ?? [];
},
+ fetchPolicy: fetchPolicies.CACHE_AND_NETWORK,
+ // We need this for handling loading state when using frontend cache
+ // See https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106004#note_1217325202 for details
+ notifyOnNetworkStatusChange: true,
result({ data }) {
this.pageInfo = data?.issues.pageInfo ?? {};
},
@@ -183,6 +188,17 @@ export default {
hasSearch() {
return Boolean(this.searchQuery || Object.keys(this.urlFilterParams).length);
},
+ // due to the issues with cache-and-network, we need this hack to check if there is any data for the query in the cache.
+ // if we have cached data, we disregard the loading state
+ isLoading() {
+ return (
+ this.$apollo.queries.issues.loading &&
+ !this.$apollo.provider.clients.defaultClient.readQuery({
+ query: getIssuesQuery,
+ variables: this.queryVariables,
+ })
+ );
+ },
queryVariables() {
return {
hideUsers: this.isPublicVisibilityRestricted && !this.isSignedIn,
@@ -446,7 +462,7 @@ export default {
:initial-filter-value="filterTokens"
:initial-sort-by="sortKey"
:issuables="renderedIssues"
- :issuables-loading="$apollo.queries.issues.loading"
+ :issuables-loading="isLoading"
namespace="dashboard"
recent-searches-storage-key="issues"
:search-input-placeholder="$options.i18n.searchPlaceholder"
diff --git a/app/assets/javascripts/issues/dashboard/index.js b/app/assets/javascripts/issues/dashboard/index.js
index 005ab5ce3b0..999f07781b2 100644
--- a/app/assets/javascripts/issues/dashboard/index.js
+++ b/app/assets/javascripts/issues/dashboard/index.js
@@ -1,10 +1,10 @@
import Vue from 'vue';
import VueApollo from 'vue-apollo';
-import createDefaultClient from '~/lib/graphql';
+import { gqlClient } from '~/issues/list/graphql';
import { parseBoolean } from '~/lib/utils/common_utils';
import IssuesDashboardApp from './components/issues_dashboard_app.vue';
-export function mountIssuesDashboardApp() {
+export async function mountIssuesDashboardApp() {
const el = document.querySelector('.js-issues-dashboard');
if (!el) {
@@ -34,7 +34,7 @@ export function mountIssuesDashboardApp() {
el,
name: 'IssuesDashboardRoot',
apolloProvider: new VueApollo({
- defaultClient: createDefaultClient(),
+ defaultClient: await gqlClient(),
}),
provide: {
autocompleteAwardEmojisPath,
diff --git a/app/assets/javascripts/issues/dashboard/queries/get_issues.query.graphql b/app/assets/javascripts/issues/dashboard/queries/get_issues.query.graphql
index 5625e6afad3..5c331fe95e2 100644
--- a/app/assets/javascripts/issues/dashboard/queries/get_issues.query.graphql
+++ b/app/assets/javascripts/issues/dashboard/queries/get_issues.query.graphql
@@ -1,5 +1,5 @@
#import "~/graphql_shared/fragments/page_info.fragment.graphql"
-#import "./issue.fragment.graphql"
+#import "~/issues/list/queries/issue.fragment.graphql"
query getDashboardIssues(
$hideUsers: Boolean = false
@@ -44,8 +44,9 @@ query getDashboardIssues(
before: $beforeCursor
first: $firstPageSize
last: $lastPageSize
- ) {
+ ) @persist {
nodes {
+ __persist
...IssueFragment
reference(full: true)
}
diff --git a/app/assets/javascripts/issues/dashboard/queries/issue.fragment.graphql b/app/assets/javascripts/issues/dashboard/queries/issue.fragment.graphql
deleted file mode 100644
index 040763f2ba4..00000000000
--- a/app/assets/javascripts/issues/dashboard/queries/issue.fragment.graphql
+++ /dev/null
@@ -1,56 +0,0 @@
-fragment IssueFragment on Issue {
- id
- iid
- confidential
- createdAt
- downvotes
- dueDate
- hidden
- humanTimeEstimate
- mergeRequestsCount
- moved
- state
- title
- updatedAt
- closedAt
- upvotes
- userDiscussionsCount @include(if: $isSignedIn)
- webPath
- webUrl
- type
- assignees @skip(if: $hideUsers) {
- nodes {
- id
- avatarUrl
- name
- username
- webUrl
- }
- }
- author @skip(if: $hideUsers) {
- id
- avatarUrl
- name
- username
- webUrl
- }
- labels {
- nodes {
- id
- color
- title
- description
- }
- }
- milestone {
- id
- dueDate
- startDate
- webPath
- title
- }
- taskCompletionStatus {
- completedCount
- count
- }
-}
diff --git a/app/assets/javascripts/lib/apollo/persistence_mapper.js b/app/assets/javascripts/lib/apollo/persistence_mapper.js
index 8fc7c69c79d..f8ae180107c 100644
--- a/app/assets/javascripts/lib/apollo/persistence_mapper.js
+++ b/app/assets/javascripts/lib/apollo/persistence_mapper.js
@@ -32,7 +32,9 @@ export const persistenceMapper = async (data) => {
persistEntities.push(...entities);
} else {
const entity = rootQuery[key].__ref;
- persistEntities.push(entity);
+ if (entity) {
+ persistEntities.push(entity);
+ }
}
}
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index d70b2e57a95..188a8540a58 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -12,6 +12,10 @@ class DashboardController < Dashboard::ApplicationController
before_action :set_show_full_reference, only: [:issues, :merge_requests]
before_action :check_filters_presence!, only: [:issues, :merge_requests]
+ before_action only: :issues do
+ push_frontend_feature_flag(:frontend_caching)
+ end
+
before_action only: :merge_requests do
push_frontend_feature_flag(:mr_approved_filter, type: :ops)
end
diff --git a/app/controllers/projects/artifacts_controller.rb b/app/controllers/projects/artifacts_controller.rb
index b5b023a4d64..2828d17c36f 100644
--- a/app/controllers/projects/artifacts_controller.rb
+++ b/app/controllers/projects/artifacts_controller.rb
@@ -19,10 +19,6 @@ class Projects::ArtifactsController < Projects::ApplicationController
before_action :validate_artifacts!, except: [:index, :download, :raw, :destroy]
before_action :entry, only: [:external_file, :file]
- before_action only: :index do
- push_frontend_feature_flag(:ci_job_artifact_bulk_destroy, @project)
- end
-
MAX_PER_PAGE = 20
feature_category :build_artifacts
diff --git a/app/graphql/mutations/ci/job_artifact/bulk_destroy.rb b/app/graphql/mutations/ci/job_artifact/bulk_destroy.rb
index 53036496de4..1ef59fbeba4 100644
--- a/app/graphql/mutations/ci/job_artifact/bulk_destroy.rb
+++ b/app/graphql/mutations/ci/job_artifact/bulk_destroy.rb
@@ -38,11 +38,6 @@ module Mutations
project = authorized_find!(id: project_id)
- if Feature.disabled?(:ci_job_artifact_bulk_destroy, project)
- raise Gitlab::Graphql::Errors::ResourceNotAvailable,
- '`ci_job_artifact_bulk_destroy` feature flag is disabled.'
- end
-
raise Gitlab::Graphql::Errors::ArgumentError, 'IDs array of job artifacts can not be empty' if ids.empty?
result = ::Ci::JobArtifacts::BulkDeleteByProjectService.new(
diff --git a/app/models/user.rb b/app/models/user.rb
index 9516d162066..03d2b5d19ba 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -2408,9 +2408,10 @@ class User < ApplicationRecord
def authorized_groups_without_shared_membership
Group.from_union(
[
- groups.select(Namespace.default_select_columns),
- authorized_projects.joins(:namespace).select(Namespace.default_select_columns)
- ])
+ groups,
+ Group.id_in(authorized_projects.select(:namespace_id))
+ ]
+ )
end
def authorized_groups_with_shared_membership
diff --git a/app/services/packages/cleanup/execute_policy_service.rb b/app/services/packages/cleanup/execute_policy_service.rb
index b432f6d0acb..891866bce5f 100644
--- a/app/services/packages/cleanup/execute_policy_service.rb
+++ b/app/services/packages/cleanup/execute_policy_service.rb
@@ -79,10 +79,9 @@ module Packages
end
def batch_deadline
- strong_memoize(:batch_deadline) do
- MAX_EXECUTION_TIME.from_now
- end
+ MAX_EXECUTION_TIME.from_now
end
+ strong_memoize_attr :batch_deadline
def response_success(timeout:)
ServiceResponse.success(
diff --git a/app/services/packages/cleanup/update_policy_service.rb b/app/services/packages/cleanup/update_policy_service.rb
index 6744accc007..911a060a18f 100644
--- a/app/services/packages/cleanup/update_policy_service.rb
+++ b/app/services/packages/cleanup/update_policy_service.rb
@@ -18,10 +18,9 @@ module Packages
private
def policy
- strong_memoize(:policy) do
- project.packages_cleanup_policy
- end
+ project.packages_cleanup_policy
end
+ strong_memoize_attr :policy
def allowed?
can?(current_user, :admin_package, project)
diff --git a/app/services/packages/composer/create_package_service.rb b/app/services/packages/composer/create_package_service.rb
index 0f5429f667e..ae5933fad7c 100644
--- a/app/services/packages/composer/create_package_service.rb
+++ b/app/services/packages/composer/create_package_service.rb
@@ -27,10 +27,9 @@ module Packages
end
def composer_json
- strong_memoize(:composer_json) do
- ::Packages::Composer::ComposerJsonService.new(project, target).execute
- end
+ ::Packages::Composer::ComposerJsonService.new(project, target).execute
end
+ strong_memoize_attr :composer_json
def package_name
composer_json['name']
diff --git a/app/services/packages/debian/extract_changes_metadata_service.rb b/app/services/packages/debian/extract_changes_metadata_service.rb
index fdca8c88fdc..5f06f46de58 100644
--- a/app/services/packages/debian/extract_changes_metadata_service.rb
+++ b/app/services/packages/debian/extract_changes_metadata_service.rb
@@ -26,10 +26,9 @@ module Packages
private
def metadata
- strong_memoize(:metadata) do
- ::Packages::Debian::ExtractMetadataService.new(@package_file).execute
- end
+ ::Packages::Debian::ExtractMetadataService.new(@package_file).execute
end
+ strong_memoize_attr :metadata
def file_type
metadata[:file_type]
@@ -40,20 +39,19 @@ module Packages
end
def files
- strong_memoize(:files) do
- raise ExtractionError, "is not a changes file" unless file_type == :changes
- raise ExtractionError, "Files field is missing" if fields['Files'].blank?
- raise ExtractionError, "Checksums-Sha1 field is missing" if fields['Checksums-Sha1'].blank?
- raise ExtractionError, "Checksums-Sha256 field is missing" if fields['Checksums-Sha256'].blank?
-
- init_entries_from_files
- entries_from_checksums_sha1
- entries_from_checksums_sha256
- entries_from_package_files
-
- @entries
- end
+ raise ExtractionError, "is not a changes file" unless file_type == :changes
+ raise ExtractionError, "Files field is missing" if fields['Files'].blank?
+ raise ExtractionError, "Checksums-Sha1 field is missing" if fields['Checksums-Sha1'].blank?
+ raise ExtractionError, "Checksums-Sha256 field is missing" if fields['Checksums-Sha256'].blank?
+
+ init_entries_from_files
+ entries_from_checksums_sha1
+ entries_from_checksums_sha256
+ entries_from_package_files
+
+ @entries
end
+ strong_memoize_attr :files
def init_entries_from_files
each_lines_for('Files') do |line|
diff --git a/app/services/packages/debian/generate_distribution_key_service.rb b/app/services/packages/debian/generate_distribution_key_service.rb
index 917965da58e..37e25c2db47 100644
--- a/app/services/packages/debian/generate_distribution_key_service.rb
+++ b/app/services/packages/debian/generate_distribution_key_service.rb
@@ -43,10 +43,9 @@ module Packages
attr_reader :params
def passphrase
- strong_memoize(:passphrase) do
- params[:passphrase] || ::User.random_password
- end
+ params[:passphrase] || ::User.random_password
end
+ strong_memoize_attr :passphrase
def pinentry_script_content
escaped_passphrase = Shellwords.escape(passphrase)
diff --git a/app/services/packages/debian/generate_distribution_service.rb b/app/services/packages/debian/generate_distribution_service.rb
index d69f6eb1511..9feb860ae87 100644
--- a/app/services/packages/debian/generate_distribution_service.rb
+++ b/app/services/packages/debian/generate_distribution_service.rb
@@ -213,10 +213,9 @@ module Packages
end
def release_content
- strong_memoize(:release_content) do
- release_header + release_sums
- end
+ release_header + release_sums
end
+ strong_memoize_attr :release_content
def release_header
[
@@ -235,10 +234,9 @@ module Packages
end
def release_date
- strong_memoize(:release_date) do
- Time.now.utc
- end
+ Time.now.utc
end
+ strong_memoize_attr :release_date
def release_sums
# NB: MD5Sum was removed for FIPS compliance
diff --git a/app/services/packages/debian/process_changes_service.rb b/app/services/packages/debian/process_changes_service.rb
index 129f2e5c9bc..abd631dbc70 100644
--- a/app/services/packages/debian/process_changes_service.rb
+++ b/app/services/packages/debian/process_changes_service.rb
@@ -76,10 +76,9 @@ module Packages
end
def metadata
- strong_memoize(:metadata) do
- ::Packages::Debian::ExtractChangesMetadataService.new(package_file).execute
- end
+ ::Packages::Debian::ExtractChangesMetadataService.new(package_file).execute
end
+ strong_memoize_attr :metadata
def files
metadata[:files]
@@ -90,16 +89,15 @@ module Packages
end
def package
- strong_memoize(:package) do
- params = {
- 'name': metadata[:fields]['Source'],
- 'version': metadata[:fields]['Version'],
- 'distribution_name': metadata[:fields]['Distribution']
- }
- response = Packages::Debian::FindOrCreatePackageService.new(project, creator, params).execute
- response.payload[:package]
- end
+ params = {
+ 'name': metadata[:fields]['Source'],
+ 'version': metadata[:fields]['Version'],
+ 'distribution_name': metadata[:fields]['Distribution']
+ }
+ response = Packages::Debian::FindOrCreatePackageService.new(project, creator, params).execute
+ response.payload[:package]
end
+ strong_memoize_attr :package
# used by ExclusiveLeaseGuard
def lease_key
diff --git a/app/services/packages/helm/process_file_service.rb b/app/services/packages/helm/process_file_service.rb
index f53c63d2b01..219f3d8c781 100644
--- a/app/services/packages/helm/process_file_service.rb
+++ b/app/services/packages/helm/process_file_service.rb
@@ -57,28 +57,25 @@ module Packages
end
def temp_package
- strong_memoize(:temp_package) do
- package_file.package
- end
+ package_file.package
end
+ strong_memoize_attr :temp_package
def package
- strong_memoize(:package) do
- project_packages = package_file.package.project.packages
- package = project_packages.with_package_type(:helm)
- .with_name(metadata['name'])
- .with_version(metadata['version'])
- .not_pending_destruction
- .last
- package || temp_package
- end
+ project_packages = package_file.package.project.packages
+ package = project_packages.with_package_type(:helm)
+ .with_name(metadata['name'])
+ .with_version(metadata['version'])
+ .not_pending_destruction
+ .last
+ package || temp_package
end
+ strong_memoize_attr :package
def metadata
- strong_memoize(:metadata) do
- ::Packages::Helm::ExtractFileMetadataService.new(package_file).execute
- end
+ ::Packages::Helm::ExtractFileMetadataService.new(package_file).execute
end
+ strong_memoize_attr :metadata
def file_name
"#{metadata['name']}-#{metadata['version']}.tgz"
diff --git a/app/services/packages/maven/metadata/base_create_xml_service.rb b/app/services/packages/maven/metadata/base_create_xml_service.rb
index 3b0d93e1dfb..d67d5a21a91 100644
--- a/app/services/packages/maven/metadata/base_create_xml_service.rb
+++ b/app/services/packages/maven/metadata/base_create_xml_service.rb
@@ -19,12 +19,11 @@ module Packages
attr_reader :logger
def xml_doc
- strong_memoize(:xml_doc) do
- Nokogiri::XML(@metadata_content) do |config|
- config.default_xml.noblanks
- end
+ Nokogiri::XML(@metadata_content) do |config|
+ config.default_xml.noblanks
end
end
+ strong_memoize_attr :xml_doc
def xml_node(name, content)
xml_doc.create_element(name).tap { |e| e.content = content }
diff --git a/app/services/packages/maven/metadata/create_plugins_xml_service.rb b/app/services/packages/maven/metadata/create_plugins_xml_service.rb
index 707a8c577ba..e99a72bc0ab 100644
--- a/app/services/packages/maven/metadata/create_plugins_xml_service.rb
+++ b/app/services/packages/maven/metadata/create_plugins_xml_service.rb
@@ -40,37 +40,34 @@ module Packages
end
def plugins_xml_node
- strong_memoize(:plugins_xml_node) do
- xml_doc.xpath(XPATH_PLUGINS)
+ xml_doc.xpath(XPATH_PLUGINS)
.first
- end
end
+ strong_memoize_attr :plugins_xml_node
def plugin_artifact_ids_from_xml
- strong_memoize(:plugin_artifact_ids_from_xml) do
- plugins_xml_node.xpath(XPATH_PLUGIN_ARTIFACT_ID)
+ plugins_xml_node.xpath(XPATH_PLUGIN_ARTIFACT_ID)
.map(&:content)
- end
end
+ strong_memoize_attr :plugin_artifact_ids_from_xml
def plugin_artifact_ids_from_database
- strong_memoize(:plugin_artifact_ids_from_database) do
- package_names = plugin_artifact_ids_from_xml.map do |artifact_id|
- "#{@package.name}/#{artifact_id}"
- end
-
- packages = @package.project.packages
- .maven
- .displayable
- .with_name(package_names)
- .has_version
-
- ::Packages::Maven::Metadatum.for_package_ids(packages.select(:id))
- .order_created
- .pluck_app_name
- .uniq
+ package_names = plugin_artifact_ids_from_xml.map do |artifact_id|
+ "#{@package.name}/#{artifact_id}"
end
+
+ packages = @package.project.packages
+ .maven
+ .displayable
+ .with_name(package_names)
+ .has_version
+
+ ::Packages::Maven::Metadatum.for_package_ids(packages.select(:id))
+ .order_created
+ .pluck_app_name
+ .uniq
end
+ strong_memoize_attr :plugin_artifact_ids_from_database
def plugin_node_for(artifact_id)
xml_doc.create_element('plugin').tap do |plugin_node|
diff --git a/app/services/packages/maven/metadata/create_versions_xml_service.rb b/app/services/packages/maven/metadata/create_versions_xml_service.rb
index c2ac7fea703..966540bcba2 100644
--- a/app/services/packages/maven/metadata/create_versions_xml_service.rb
+++ b/app/services/packages/maven/metadata/create_versions_xml_service.rb
@@ -91,49 +91,43 @@ module Packages
end
def versioning_xml_node
- strong_memoize(:versioning_xml_node) do
- xml_doc.xpath(XPATH_VERSIONING).first
- end
+ xml_doc.xpath(XPATH_VERSIONING).first
end
+ strong_memoize_attr :versioning_xml_node
def versions_xml_node
- strong_memoize(:versions_xml_node) do
- versioning_xml_node&.xpath(XPATH_VERSIONS)
+ versioning_xml_node&.xpath(XPATH_VERSIONS)
&.first
- end
end
+ strong_memoize_attr :versions_xml_node
def version_xml_nodes
versions_xml_node&.xpath(XPATH_VERSION)
end
def latest_xml_node
- strong_memoize(:latest_xml_node) do
- versioning_xml_node&.xpath(XPATH_LATEST)
+ versioning_xml_node&.xpath(XPATH_LATEST)
&.first
- end
end
+ strong_memoize_attr :latest_xml_node
def release_xml_node
- strong_memoize(:release_xml_node) do
- versioning_xml_node&.xpath(XPATH_RELEASE)
+ versioning_xml_node&.xpath(XPATH_RELEASE)
&.first
- end
end
+ strong_memoize_attr :release_xml_node
def last_updated_xml_node
- strong_memoize(:last_updated_xml_mode) do
- versioning_xml_node.xpath(XPATH_LAST_UPDATED)
+ versioning_xml_node.xpath(XPATH_LAST_UPDATED)
.first
- end
end
+ strong_memoize_attr :last_updated_xml_node
def versions_from_xml
- strong_memoize(:versions_from_xml) do
- versions_xml_node.xpath(XPATH_VERSION)
+ versions_xml_node.xpath(XPATH_VERSION)
.map(&:text)
- end
end
+ strong_memoize_attr :versions_from_xml
def latest_from_xml
latest_xml_node&.text
@@ -144,27 +138,25 @@ module Packages
end
def versions_from_database
- strong_memoize(:versions_from_database) do
- @package.project.packages
+ @package.project.packages
.maven
.displayable
.with_name(@package.name)
.has_version
.order_created
.pluck_versions
- end
end
+ strong_memoize_attr :versions_from_database
def latest_from_database
versions_from_database.last
end
def release_from_database
- strong_memoize(:release_from_database) do
- non_snapshot_versions_from_database = versions_from_database.reject { |v| v.ends_with?('SNAPSHOT') }
- non_snapshot_versions_from_database.last
- end
+ non_snapshot_versions_from_database = versions_from_database.reject { |v| v.ends_with?('SNAPSHOT') }
+ non_snapshot_versions_from_database.last
end
+ strong_memoize_attr :release_from_database
def log_malformed_content(reason)
logger.warn(
diff --git a/app/services/packages/maven/metadata/sync_service.rb b/app/services/packages/maven/metadata/sync_service.rb
index dacf6750412..14196f090dd 100644
--- a/app/services/packages/maven/metadata/sync_service.rb
+++ b/app/services/packages/maven/metadata/sync_service.rb
@@ -70,25 +70,22 @@ module Packages
end
def metadata_package_file_for_versions
- strong_memoize(:metadata_file_for_versions) do
- metadata_package_file_for(versionless_package_for_versions)
- end
+ metadata_package_file_for(versionless_package_for_versions)
end
+ strong_memoize_attr :metadata_package_file_for_versions
def versionless_package_for_versions
- strong_memoize(:versionless_package_for_versions) do
- versionless_package_named(package_name)
- end
+ versionless_package_named(package_name)
end
+ strong_memoize_attr :versionless_package_for_versions
def metadata_package_file_for_plugins
- strong_memoize(:metadata_package_file_for_plugins) do
- pkg_name = package_name_for_plugins
- next unless pkg_name
+ pkg_name = package_name_for_plugins
+ return unless pkg_name
- metadata_package_file_for(versionless_package_named(package_name_for_plugins))
- end
+ metadata_package_file_for(versionless_package_named(package_name_for_plugins))
end
+ strong_memoize_attr :metadata_package_file_for_plugins
def metadata_package_file_for(package)
return unless package
diff --git a/app/services/packages/npm/create_package_service.rb b/app/services/packages/npm/create_package_service.rb
index c71ae060dd9..2c578760cc5 100644
--- a/app/services/packages/npm/create_package_service.rb
+++ b/app/services/packages/npm/create_package_service.rb
@@ -61,10 +61,9 @@ module Packages
end
def version
- strong_memoize(:version) do
- params[:versions].each_key.first
- end
+ params[:versions].each_key.first
end
+ strong_memoize_attr :version
def version_data
params[:versions][version]
@@ -79,30 +78,27 @@ module Packages
end
def package_file_name
- strong_memoize(:package_file_name) do
- "#{name}-#{version}.tgz"
- end
+ "#{name}-#{version}.tgz"
end
+ strong_memoize_attr :package_file_name
def attachment
- strong_memoize(:attachment) do
- params['_attachments'][package_file_name]
- end
+ params['_attachments'][package_file_name]
end
+ strong_memoize_attr :attachment
# TODO (technical debt): Extract the package size calculation to its own component and unit test it separately.
def calculated_package_file_size
- strong_memoize(:calculated_package_file_size) do
- # This calculation is based on:
- # 1. 4 chars in a Base64 encoded string are 3 bytes in the original string. Meaning 1 char is 0.75 bytes.
- # 2. The encoded string may have 1 or 2 extra '=' chars used for padding. Each padding char means 1 byte less in the original string.
- # Reference:
- # - https://blog.aaronlenoir.com/2017/11/10/get-original-length-from-base-64-string/
- # - https://en.wikipedia.org/wiki/Base64#Decoding_Base64_with_padding
- encoded_data = attachment['data']
- ((encoded_data.length * 0.75) - encoded_data[-2..].count('=')).to_i
- end
+ # This calculation is based on:
+ # 1. 4 chars in a Base64 encoded string are 3 bytes in the original string. Meaning 1 char is 0.75 bytes.
+ # 2. The encoded string may have 1 or 2 extra '=' chars used for padding. Each padding char means 1 byte less in the original string.
+ # Reference:
+ # - https://blog.aaronlenoir.com/2017/11/10/get-original-length-from-base-64-string/
+ # - https://en.wikipedia.org/wiki/Base64#Decoding_Base64_with_padding
+ encoded_data = attachment['data']
+ ((encoded_data.length * 0.75) - encoded_data[-2..].count('=')).to_i
end
+ strong_memoize_attr :calculated_package_file_size
def file_params
{
@@ -134,29 +130,26 @@ module Packages
end
def field_sizes
- strong_memoize(:field_sizes) do
- package_json.transform_values do |value|
- value.to_s.size
- end
+ package_json.transform_values do |value|
+ value.to_s.size
end
end
+ strong_memoize_attr :field_sizes
def filtered_field_sizes
- strong_memoize(:filtered_field_sizes) do
- field_sizes.select do |_, size|
- size >= ::Packages::Npm::Metadatum::MIN_PACKAGE_JSON_FIELD_SIZE_FOR_ERROR_TRACKING
- end
+ field_sizes.select do |_, size|
+ size >= ::Packages::Npm::Metadatum::MIN_PACKAGE_JSON_FIELD_SIZE_FOR_ERROR_TRACKING
end
end
+ strong_memoize_attr :filtered_field_sizes
def largest_fields
- strong_memoize(:largest_fields) do
- field_sizes
+ field_sizes
.sort_by { |a| a[1] }
.reverse[0..::Packages::Npm::Metadatum::NUM_FIELDS_FOR_ERROR_TRACKING - 1]
.to_h
- end
end
+ strong_memoize_attr :largest_fields
def field_sizes_for_error_tracking
filtered_field_sizes.empty? ? largest_fields : filtered_field_sizes
diff --git a/app/services/packages/npm/create_tag_service.rb b/app/services/packages/npm/create_tag_service.rb
index 82974d0ca4b..e212b37c9ba 100644
--- a/app/services/packages/npm/create_tag_service.rb
+++ b/app/services/packages/npm/create_tag_service.rb
@@ -23,12 +23,11 @@ module Packages
private
def existing_tag
- strong_memoize(:existing_tag) do
- Packages::TagsFinder
+ Packages::TagsFinder
.new(package.project, package.name, package_type: package.package_type)
.find_by_name(tag_name)
- end
end
+ strong_memoize_attr :existing_tag
end
end
end
diff --git a/app/services/packages/nuget/metadata_extraction_service.rb b/app/services/packages/nuget/metadata_extraction_service.rb
index 3b92d977c79..5c60a2912ae 100644
--- a/app/services/packages/nuget/metadata_extraction_service.rb
+++ b/app/services/packages/nuget/metadata_extraction_service.rb
@@ -39,10 +39,9 @@ module Packages
private
def package_file
- strong_memoize(:package_file) do
- ::Packages::PackageFile.find_by_id(@package_file_id)
- end
+ ::Packages::PackageFile.find_by_id(@package_file_id)
end
+ strong_memoize_attr :package_file
def valid_package_file?
package_file &&
diff --git a/app/services/packages/nuget/search_service.rb b/app/services/packages/nuget/search_service.rb
index fea424b3aa8..7d1585f8903 100644
--- a/app/services/packages/nuget/search_service.rb
+++ b/app/services/packages/nuget/search_service.rb
@@ -89,17 +89,16 @@ module Packages
end
def base_matching_package_names
- strong_memoize(:base_matching_package_names) do
- # rubocop: disable CodeReuse/ActiveRecord
- pkgs = nuget_packages.order_name
+ # rubocop: disable CodeReuse/ActiveRecord
+ pkgs = nuget_packages.order_name
.select_distinct_name
.where(project_id: project_ids)
- pkgs = pkgs.without_version_like(PRE_RELEASE_VERSION_MATCHING_TERM) unless include_prerelease_versions?
- pkgs = pkgs.search_by_name(@search_term) if @search_term.present?
- pkgs
- # rubocop: enable CodeReuse/ActiveRecord
- end
+ pkgs = pkgs.without_version_like(PRE_RELEASE_VERSION_MATCHING_TERM) unless include_prerelease_versions?
+ pkgs = pkgs.search_by_name(@search_term) if @search_term.present?
+ pkgs
+ # rubocop: enable CodeReuse/ActiveRecord
end
+ strong_memoize_attr :base_matching_package_names
def nuget_packages
Packages::Package.nuget
@@ -111,11 +110,10 @@ module Packages
def project_ids_cte
return unless use_project_ids_cte?
- strong_memoize(:project_ids_cte) do
- query = projects_visible_to_user(@current_user, within_group: @project_or_group)
- Gitlab::SQL::CTE.new(:project_ids, query.select(:id))
- end
+ query = projects_visible_to_user(@current_user, within_group: @project_or_group)
+ Gitlab::SQL::CTE.new(:project_ids, query.select(:id))
end
+ strong_memoize_attr :project_ids_cte
def project_ids
return @project_or_group.id if project?
diff --git a/app/services/packages/pypi/create_package_service.rb b/app/services/packages/pypi/create_package_service.rb
index b464ce4504a..087a8e42a66 100644
--- a/app/services/packages/pypi/create_package_service.rb
+++ b/app/services/packages/pypi/create_package_service.rb
@@ -29,10 +29,9 @@ module Packages
private
def created_package
- strong_memoize(:created_package) do
- find_or_create_package!(:pypi)
- end
+ find_or_create_package!(:pypi)
end
+ strong_memoize_attr :created_package
def file_params
{
diff --git a/app/services/packages/rpm/parse_package_service.rb b/app/services/packages/rpm/parse_package_service.rb
index d2751c77c5b..3995eedef53 100644
--- a/app/services/packages/rpm/parse_package_service.rb
+++ b/app/services/packages/rpm/parse_package_service.rb
@@ -43,10 +43,9 @@ module Packages
end
def package_tags
- strong_memoize(:package_tags) do
- rpm.tags
- end
+ rpm.tags
end
+ strong_memoize_attr :package_tags
def extract_static_attributes
STATIC_ATTRIBUTES.index_with do |attribute|
diff --git a/app/services/packages/rubygems/dependency_resolver_service.rb b/app/services/packages/rubygems/dependency_resolver_service.rb
index 839a7683632..214a4adc47f 100644
--- a/app/services/packages/rubygems/dependency_resolver_service.rb
+++ b/app/services/packages/rubygems/dependency_resolver_service.rb
@@ -33,10 +33,9 @@ module Packages
private
def packages
- strong_memoize(:packages) do
- project.packages.with_name(gem_name)
- end
+ project.packages.with_name(gem_name)
end
+ strong_memoize_attr :packages
def gem_name
params[:gem_name]
diff --git a/app/services/packages/rubygems/process_gem_service.rb b/app/services/packages/rubygems/process_gem_service.rb
index c771af28f73..ca4aaa8fdde 100644
--- a/app/services/packages/rubygems/process_gem_service.rb
+++ b/app/services/packages/rubygems/process_gem_service.rb
@@ -64,10 +64,9 @@ module Packages
end
def gemspec
- strong_memoize(:gemspec) do
- gem.spec
- end
+ gem.spec
end
+ strong_memoize_attr :gemspec
def success
ServiceResponse.success(payload: { package: package })
@@ -78,24 +77,21 @@ module Packages
end
def temp_package
- strong_memoize(:temp_package) do
- package_file.package
- end
+ package_file.package
end
+ strong_memoize_attr :temp_package
def package
- strong_memoize(:package) do
- # if package with name/version already exists, use that package
- package = temp_package.project
+ package = temp_package.project
.packages
.rubygems
.with_name(gemspec.name)
.with_version(gemspec.version.to_s)
.not_pending_destruction
.last
- package || temp_package
- end
+ package || temp_package
end
+ strong_memoize_attr :package
def gem
# use_file will set an exclusive lease on the file for as long as
diff --git a/app/services/packages/terraform_module/create_package_service.rb b/app/services/packages/terraform_module/create_package_service.rb
index 3afecc6c1ca..9df722db529 100644
--- a/app/services/packages/terraform_module/create_package_service.rb
+++ b/app/services/packages/terraform_module/create_package_service.rb
@@ -43,16 +43,14 @@ module Packages
end
def name
- strong_memoize(:name) do
- "#{params[:module_name]}/#{params[:module_system]}"
- end
+ "#{params[:module_name]}/#{params[:module_system]}"
end
+ strong_memoize_attr :name
def file_name
- strong_memoize(:file_name) do
- "#{params[:module_name]}-#{params[:module_system]}-#{params[:module_version]}.tgz"
- end
+ "#{params[:module_name]}-#{params[:module_system]}-#{params[:module_version]}.tgz"
end
+ strong_memoize_attr :file_name
def file_params
{
diff --git a/app/services/packages/update_tags_service.rb b/app/services/packages/update_tags_service.rb
index f29c54dacb9..cf1acc6ee19 100644
--- a/app/services/packages/update_tags_service.rb
+++ b/app/services/packages/update_tags_service.rb
@@ -21,10 +21,9 @@ module Packages
private
def existing_tags
- strong_memoize(:existing_tags) do
- @package.tag_names
- end
+ @package.tag_names
end
+ strong_memoize_attr :existing_tags
def rows(tags)
now = Time.zone.now
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index 91a54f15305..fec5d2d5ff5 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -52,7 +52,7 @@
%p
= s_('Profiles|Changing your username can have unintended side effects.')
= succeed '.' do
- = link_to s_('Profiles|Learn more'), help_page_path('user/profile/index', anchor: 'change-your-username'), target: '_blank', rel: 'noopener noreferrer'
+ = link_to _('Learn more'), help_page_path('user/profile/index', anchor: 'change-your-username'), target: '_blank', rel: 'noopener noreferrer'
.col-lg-8
- data = { initial_username: current_user.username, root_url: root_url, action_url: update_username_profile_path(format: :json) }
#update-username{ data: data }
diff --git a/app/views/shared/runners/_form.html.haml b/app/views/shared/runners/_form.html.haml
index f4b6c3c3a50..216aaad443f 100644
--- a/app/views/shared/runners/_form.html.haml
+++ b/app/views/shared/runners/_form.html.haml
@@ -42,12 +42,12 @@
- if local_assigns[:in_gitlab_com_admin_context]
.form-group.row
= label_tag :public_projects_minutes_cost_factor, class: 'col-form-label col-sm-2' do
- = _('Public projects Minutes cost factor')
+ = _('Public projects compute cost factor')
.col-sm-10
= f.text_field :public_projects_minutes_cost_factor, class: 'form-control'
.form-group.row
= label_tag :private_projects_minutes_cost_factor, class: 'col-form-label col-sm-2' do
- = _('Private projects Minutes cost factor')
+ = _('Private projects compute cost factor')
.col-sm-10
= f.text_field :private_projects_minutes_cost_factor, class: 'form-control'
.form-actions
diff --git a/app/workers/gitlab/github_import/stage/import_pull_requests_worker.rb b/app/workers/gitlab/github_import/stage/import_pull_requests_worker.rb
index e7eee0915d5..b2dfded0280 100644
--- a/app/workers/gitlab/github_import/stage/import_pull_requests_worker.rb
+++ b/app/workers/gitlab/github_import/stage/import_pull_requests_worker.rb
@@ -16,6 +16,12 @@ module Gitlab
# project - An instance of Project.
def import(client, project)
info(project.id, message: "starting importer", importer: 'Importer::PullRequestsImporter')
+
+ # If a user creates a new merge request while the import is in progress, GitLab can assign an IID
+ # to this merge request that already exists for a GitHub Pull Request.
+ # The workaround is to allocate IIDs before starting the importer.
+ allocate_merge_requests_internal_id!(project, client)
+
waiter = Importer::PullRequestsImporter
.new(project, client)
.execute
@@ -41,6 +47,17 @@ module Gitlab
private
+ def allocate_merge_requests_internal_id!(project, client)
+ return if InternalId.exists?(project: project, usage: :merge_requests) # rubocop: disable CodeReuse/ActiveRecord
+
+ options = { state: 'all', sort: 'number', direction: 'desc', per_page: '1' }
+ last_github_pull_request = client.each_object(:pulls, project.import_source, options).first
+
+ return unless last_github_pull_request
+
+ MergeRequest.track_target_project_iid!(project, last_github_pull_request[:number])
+ end
+
def abort_on_failure
true
end
diff --git a/config/feature_flags/development/ci_job_artifact_bulk_destroy.yml b/config/feature_flags/development/ci_job_artifact_bulk_destroy.yml
deleted file mode 100644
index a99c892020f..00000000000
--- a/config/feature_flags/development/ci_job_artifact_bulk_destroy.yml
+++ /dev/null
@@ -1,8 +0,0 @@
----
-name: ci_job_artifact_bulk_destroy
-introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/110026
-rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/386768
-milestone: '15.10'
-type: development
-group: group::pipeline security
-default_enabled: false
diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md
index 428fd2743be..2bb170c47da 100644
--- a/doc/api/graphql/reference/index.md
+++ b/doc/api/graphql/reference/index.md
@@ -5637,8 +5637,8 @@ Input type: `RunnerUpdateInput`
| <a id="mutationrunnerupdatemaintenancenote"></a>`maintenanceNote` | [`String`](#string) | Runner's maintenance notes. |
| <a id="mutationrunnerupdatemaximumtimeout"></a>`maximumTimeout` | [`Int`](#int) | Maximum timeout (in seconds) for jobs processed by the runner. |
| <a id="mutationrunnerupdatepaused"></a>`paused` | [`Boolean`](#boolean) | Indicates the runner is not allowed to receive jobs. |
-| <a id="mutationrunnerupdateprivateprojectsminutescostfactor"></a>`privateProjectsMinutesCostFactor` | [`Float`](#float) | Private projects' "minutes cost factor" associated with the runner (GitLab.com only). |
-| <a id="mutationrunnerupdatepublicprojectsminutescostfactor"></a>`publicProjectsMinutesCostFactor` | [`Float`](#float) | Public projects' "minutes cost factor" associated with the runner (GitLab.com only). |
+| <a id="mutationrunnerupdateprivateprojectsminutescostfactor"></a>`privateProjectsMinutesCostFactor` | [`Float`](#float) | Private projects' "compute cost factor" associated with the runner (GitLab.com only). |
+| <a id="mutationrunnerupdatepublicprojectsminutescostfactor"></a>`publicProjectsMinutesCostFactor` | [`Float`](#float) | Public projects' "compute cost factor" associated with the runner (GitLab.com only). |
| <a id="mutationrunnerupdaterununtagged"></a>`runUntagged` | [`Boolean`](#boolean) | Indicates the runner is able to run untagged jobs. |
| <a id="mutationrunnerupdatetaglist"></a>`tagList` | [`[String!]`](#string) | Tags associated with the runner. |
@@ -12934,9 +12934,9 @@ CI/CD variables for a project.
| <a id="cirunnerownerproject"></a>`ownerProject` | [`Project`](#project) | Project that owns the runner. For project runners only. |
| <a id="cirunnerpaused"></a>`paused` | [`Boolean!`](#boolean) | Indicates the runner is paused and not available to run jobs. |
| <a id="cirunnerplatformname"></a>`platformName` | [`String`](#string) | Platform provided by the runner. |
-| <a id="cirunnerprivateprojectsminutescostfactor"></a>`privateProjectsMinutesCostFactor` | [`Float`](#float) | Private projects' "minutes cost factor" associated with the runner (GitLab.com only). |
+| <a id="cirunnerprivateprojectsminutescostfactor"></a>`privateProjectsMinutesCostFactor` | [`Float`](#float) | Private projects' "compute cost factor" associated with the runner (GitLab.com only). |
| <a id="cirunnerprojectcount"></a>`projectCount` | [`Int`](#int) | Number of projects that the runner is associated with. |
-| <a id="cirunnerpublicprojectsminutescostfactor"></a>`publicProjectsMinutesCostFactor` | [`Float`](#float) | Public projects' "minutes cost factor" associated with the runner (GitLab.com only). |
+| <a id="cirunnerpublicprojectsminutescostfactor"></a>`publicProjectsMinutesCostFactor` | [`Float`](#float) | Public projects' "compute cost factor" associated with the runner (GitLab.com only). |
| <a id="cirunnerregisteradminurl"></a>`registerAdminUrl` | [`String`](#string) | URL of the temporary registration page of the runner. Only available before the runner is registered. Only available for administrators. |
| <a id="cirunnerrevision"></a>`revision` | [`String`](#string) | Revision of the runner. |
| <a id="cirunnerrununtagged"></a>`runUntagged` | [`Boolean!`](#boolean) | Indicates the runner is able to run untagged jobs. |
diff --git a/doc/api/group_import_export.md b/doc/api/group_import_export.md
index c78f0ecb781..a409b2e3a11 100644
--- a/doc/api/group_import_export.md
+++ b/doc/api/group_import_export.md
@@ -17,20 +17,22 @@ Group exports include the following:
- Group labels
- Group badges
- Group members
-- Subgroups. Each subgroup includes all data above
- Group wikis **(PREMIUM SELF)**
+- Subgroups. Each subgroup includes all data above
-To preserve group-level relationships from imported projects, you should run group import and export first. This way, you can import project exports into the desired group structure.
+To preserve group-level relationships from imported projects, you should run group export and import first. This way,
+you can import project exports into the desired group structure.
-Imported groups have a `private` visibility level unless you import them into a parent group.
-If you import groups into a parent group, the subgroups inherit by default a similar level of visibility.
+Because of a [known issue](https://gitlab.com/gitlab-org/gitlab/-/issues/405168), imported groups have a `private`
+visibility level unless you import them into a parent group. By default, if you import groups into a parent group,
+the subgroups inherit the same level of visibility as the parent.
To preserve the member list and their respective permissions on imported groups, review the users in these groups. Make sure these users exist before importing the desired groups.
## Prerequisites
-For information on prerequisites for group import and export API, see prerequisites for
-[migrating groups by uploading an export file](../user/group/import/index.md#preparation).
+- For information on prerequisites for group import and export API, see prerequisites for
+ [migrating groups by uploading an export file](../user/group/import/index.md#preparation).
## Schedule new export
@@ -88,6 +90,15 @@ returns either:
## Import a file
+The maximum import file size can be set by the Administrator on self-managed instances (default is `0` (unlimited)).
+As an administrator, you can modify the maximum import file size either:
+
+- The admin [Admin Area](../user/admin_area/settings/account_and_limit_settings.md).
+- The `max_import_size` option in the [Application settings API](settings.md#change-application-settings).
+
+For information on the maximum import file size on GitLab.com, see
+[Account and limit settings](../user/gitlab_com/index.md#account-and-limit-settings).
+
```plaintext
POST /groups/import
```
@@ -110,6 +121,6 @@ curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
--form "file=@/path/to/file" "https://gitlab.example.com/api/v4/groups/import"
```
-NOTE:
-The maximum import file size can be set by the Administrator, default is `0` (unlimited).
-As an administrator, you can modify the maximum import file size. To do so, use the `max_import_size` option in the [Application settings API](settings.md#change-application-settings) or the [Admin Area](../user/admin_area/settings/account_and_limit_settings.md). Default [modified](https://gitlab.com/gitlab-org/gitlab/-/issues/251106) from 50 MB to 0 in GitLab 13.8.
+## Related topics
+
+- [Project import and export API](project_import_export.md)
diff --git a/doc/api/group_relations_export.md b/doc/api/group_relations_export.md
index 5118b2f00f5..3721e712dd9 100644
--- a/doc/api/group_relations_export.md
+++ b/doc/api/group_relations_export.md
@@ -4,14 +4,16 @@ group: Import and Integrate
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-# Group Relations Export API **(FREE)**
+# Group relations export API **(FREE)**
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/59978) in GitLab 13.12.
-With the Group Relations Export API, you can partially export group structure. This API is similar
-to [group export](group_import_export.md),
-but it exports each top-level relation (for example, milestones/boards/labels) as a separate file
-instead of one archive. The group relations export API is primarily used in [group migration](../user/group/import/index.md).
+The group relations export API partially exports a group's structure as separate files for each top-level
+relation (for example, milestones, boards, and labels).
+
+The group relations export API is primarily used in
+[group migration by direct transfer](../user/group/import/index.md#migrate-groups-by-direct-transfer-recommended) and
+can't be used with the [group import and export API](group_import_export.md).
## Schedule new export
@@ -101,3 +103,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" --remote-header-name \
ls labels.ndjson.gz
labels.ndjson.gz
```
+
+## Related topics
+
+- [Project relations export API](project_relations_export.md)
diff --git a/doc/api/project_import_export.md b/doc/api/project_import_export.md
index a162bc3e5af..235f3bf314c 100644
--- a/doc/api/project_import_export.md
+++ b/doc/api/project_import_export.md
@@ -1,6 +1,6 @@
---
-stage: Create
-group: Source Code
+stage: Manage
+group: Import and Integrate
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
@@ -13,7 +13,7 @@ Before using the project import and export API, you might want to use the
## Prerequisites
-For information on prerequisites for project import and export API, see:
+For prerequisites for project import and export API, see:
- Prerequisites for [project export](../user/project/settings/import_export.md#export-a-project-and-its-data).
- Prerequisites for [project import](../user/project/settings/import_export.md#import-a-project-and-its-data).
@@ -32,8 +32,10 @@ project to a web server or to any S3-compatible platform. For exports, GitLab:
time and is available throughout the export process.
- Administrators can modify the maximum export file size. By default, the maximum is unlimited (`0`). To change this,
edit `max_export_size` using either:
- - [Application settings API](settings.md#change-application-settings)
- [GitLab UI](../user/admin_area/settings/account_and_limit_settings.md).
+ - [Application settings API](settings.md#change-application-settings)
+- Has a fixed limit for the maximum import file size on GitLab.com. For more information, see
+ [Account and limit settings](../user/gitlab_com/index.md#account-and-limit-settings).
The `upload[url]` parameter is required if the `upload` parameter is present.
@@ -446,3 +448,4 @@ GitHub and how many were already imported:
- [Migrating projects using file exports](../user/project/settings/import_export.md).
- [Project import and export Rake tasks](../administration/raketasks/project_import_export.md).
+- [Group import and export API](group_import_export.md)
diff --git a/doc/api/project_relations_export.md b/doc/api/project_relations_export.md
index 835a53c7ecc..1fef2722bb8 100644
--- a/doc/api/project_relations_export.md
+++ b/doc/api/project_relations_export.md
@@ -4,22 +4,17 @@ group: Import and Integrate
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
-# Project Relations Export API **(FREE)**
+# Project relations export API **(FREE)**
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70330) in GitLab 14.4 behind the `bulk_import` [feature flag](../administration/feature_flags.md), disabled by default.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/70330) in GitLab 14.4 behind the `bulk_import` [feature flag](../administration/feature_flags.md), disabled by default.
+> - New application setting `bulk_import_enabled` introduced in GitLab 15.8. `bulk_import` feature flag removed.
-FLAG:
-On GitLab.com, this feature is available.
-On self-managed GitLab, by default this feature is available. To hide the feature, ask an administrator to
-[disable the `bulk_import` flag](../administration/feature_flags.md).
-The feature is not ready for production use. It is still in experimental stage and might change in the future.
+The project relations export API partially exports a project's structure as separate files for each top-level
+relation (for example, milestones, issues, and labels).
-With the Project Relations Export API, you can partially export project structure. This API is
-similar to [project export](project_import_export.md),
-but it exports each top-level relation (for example, milestones/boards/labels) as a separate file
-instead of one archive. The project relations export API is primarily used in
-[group migration](../user/group/import/index.md)
-to support group project import.
+The project relations export API is primarily used in
+[group migration](../user/group/import/index.md#migrate-groups-by-direct-transfer-recommended) can't be used with the
+[project import and export API](project_import_export.md).
## Schedule new export
@@ -109,3 +104,7 @@ curl --header "PRIVATE-TOKEN: <your_access_token>" --remote-header-name \
ls labels.ndjson.gz
labels.ndjson.gz
```
+
+## Related topics
+
+- [Group relations export API](group_relations_export.md)
diff --git a/doc/ci/jobs/job_artifacts.md b/doc/ci/jobs/job_artifacts.md
index d88a9ec09dd..6dbf14c9d43 100644
--- a/doc/ci/jobs/job_artifacts.md
+++ b/doc/ci/jobs/job_artifacts.md
@@ -294,12 +294,8 @@ You can also delete individual artifacts from the [**Artifacts** page](#bulk-del
### Bulk delete artifacts
-- [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33348) in GitLab 15.10 [with a flag](../../administration/feature_flags.md) named `ci_job_artifact_bulk_destroy`. Disabled by default.
-
-FLAG:
-On self-managed GitLab, by default this feature is not available. To make it available,
-ask an administrator to [enable the feature flag](../../administration/feature_flags.md) named `ci_job_artifact_bulk_destroy`.
-The feature is not ready for production use.
+> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/33348) in GitLab 15.10 [with a flag](../../administration/feature_flags.md) named `ci_job_artifact_bulk_destroy`. Disabled by default.
+> - [Feature flag removed](https://gitlab.com/gitlab-org/gitlab/-/issues/398581) in GitLab 16.1.
You can delete multiple artifacts at the same time:
diff --git a/doc/ci/pipelines/cicd_minutes.md b/doc/ci/pipelines/cicd_minutes.md
index 41a7f853de1..571a47c5426 100644
--- a/doc/ci/pipelines/cicd_minutes.md
+++ b/doc/ci/pipelines/cicd_minutes.md
@@ -76,7 +76,7 @@ To set a compute quota for a namespace:
1. On the top bar, select **Main menu > Admin**.
1. On the left sidebar, select **Overview > Groups**.
1. For the group you want to update, select **Edit**.
-1. In the **Compute quota** box, enter the maximum number of CI/CD minutes.
+1. In the **Compute quota** box, enter the maximum number of units of compute.
1. Select **Save changes**.
You can also use the [update group API](../../api/groups.md#update-group) or the
diff --git a/doc/ci/runners/runners_scope.md b/doc/ci/runners/runners_scope.md
index 7334260b8c5..ee679a90180 100644
--- a/doc/ci/runners/runners_scope.md
+++ b/doc/ci/runners/runners_scope.md
@@ -29,12 +29,12 @@ If you are using a self-managed instance of GitLab:
and selecting **Show runner installation instructions**.
These instructions are also available [in the documentation](https://docs.gitlab.com/runner/install/index.html).
- The administrator can also configure a maximum number of shared runner
- [CI/CD minutes for each group](../pipelines/cicd_minutes.md#set-the-compute-quota-for-a-specific-namespace).
+ [units of compute for each group](../pipelines/cicd_minutes.md#set-the-compute-quota-for-a-specific-namespace).
If you are using GitLab.com:
- You can select from a list of [shared runners that GitLab maintains](index.md).
-- The shared runners consume the [CI/CD minutes](../pipelines/cicd_minutes.md)
+- The shared runners consume the [units of compute](../pipelines/cicd_minutes.md)
included with your account.
### Enable shared runners for a project
diff --git a/doc/user/admin_area/settings/continuous_integration.md b/doc/user/admin_area/settings/continuous_integration.md
index 9417797c080..2aec441dc76 100644
--- a/doc/user/admin_area/settings/continuous_integration.md
+++ b/doc/user/admin_area/settings/continuous_integration.md
@@ -39,10 +39,10 @@ You can set all new projects to have the instance's shared runners available by
Any time a new project is created, the shared runners are available.
-## Shared runners CI/CD minutes
+## Shared runners compute quota
As an administrator you can set either a global or namespace-specific
-limit on the number of [CI/CD minutes](../../../ci/pipelines/cicd_minutes.md) you can use.
+limit on the number of [units of compute](../../../ci/pipelines/cicd_minutes.md) you can use.
## Enable a project runner for multiple projects
diff --git a/doc/user/analytics/value_streams_dashboard.md b/doc/user/analytics/value_streams_dashboard.md
index 1a58679cfc0..4bb29d53ad2 100644
--- a/doc/user/analytics/value_streams_dashboard.md
+++ b/doc/user/analytics/value_streams_dashboard.md
@@ -147,6 +147,7 @@ panels:
| Lead time | Median time from issue created to issue closed. | [Value Stream Analytics](https://gitlab.com/groups/gitlab-org/-/analytics/value_stream_analytics) | [View the lead time and cycle time for issues](../group/value_stream_analytics/index.md#key-metrics) | `lead_time` |
| Cycle time | Median time from the earliest commit of a linked issue's merge request to when that issue is closed. | [VSA overview](https://gitlab.com/groups/gitlab-org/-/analytics/value_stream_analytics) | [View the lead time and cycle time for issues](../group/value_stream_analytics/index.md#key-metrics) | `cycle_time` |
| New issues | Number of new issues created. | [Issue Analytics](https://gitlab.com/groups/gitlab-org/-/issues_analytics) | Issue analytics [for projects](issue_analytics.md) and [for groups](../../user/group/issues_analytics/index.md) | `issues` |
+| Closed issues | Number of issues closed by month. | [Value Stream Analytics](https://gitlab.com/groups/gitlab-org/-/analytics/value_stream_analytics) | [Value Stream Analytics](../group/value_stream_analytics/index.md) | `issues_completed` |
| Number of deploys | Total number of deploys to production. | [Merge Request Analytics](https://gitlab.com/gitlab-org/gitlab/-/analytics/merge_request_analytics) | [Merge request analytics](merge_request_analytics.md) | `deploys` |
| Critical vulnerabilities over time | Critical vulnerabilities over time in project or group | [Vulnerability report](https://gitlab.com/gitlab-org/gitlab/-/security/vulnerability_report) | [Vulnerability report](../application_security/vulnerability_report/index.md) | `vulnerability_critical` |
| High vulnerabilities over time | High vulnerabilities over time in project or group | [Vulnerability report](https://gitlab.com/gitlab-org/gitlab/-/security/vulnerability_report) | [Vulnerability report](../application_security/vulnerability_report/index.md) | `vulnerability_high` |
diff --git a/doc/user/application_security/sast/customize_rulesets.md b/doc/user/application_security/sast/customize_rulesets.md
index f0af7e00af5..385c5ffbae1 100644
--- a/doc/user/application_security/sast/customize_rulesets.md
+++ b/doc/user/application_security/sast/customize_rulesets.md
@@ -81,7 +81,7 @@ To create the ruleset configuration file:
## Specify a remote configuration file
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/393452) in 16.0.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/393452) in 16.1.
You can set a [CI/CD variable](../../../ci/variables/index.md) to use a ruleset configuration file that's stored outside of the current repository.
This can help you apply the same rules across multiple projects.
diff --git a/doc/user/group/import/index.md b/doc/user/group/import/index.md
index 2dd5347e5dc..fd4e67314ae 100644
--- a/doc/user/group/import/index.md
+++ b/doc/user/group/import/index.md
@@ -77,6 +77,13 @@ transfer.
| 8 hours | Time until migration times out. |
| 90 minutes | Time the destination is waiting for export to complete. |
+You can test the maximum relation size limit using these APIs:
+
+- [Group relations export API](../../../api/group_relations_export.md).
+- [Project relations export API](../../../api/project_relations_export.md)
+
+If either API produces files larger than the maximum relation size limit, group migration by direct transfer fails.
+
### Visibility rules
After migration:
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 20314c93f62..1b98e30f192 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -2924,9 +2924,6 @@ msgstr ""
msgid "Additional diagram formats"
msgstr ""
-msgid "Additional minutes:"
-msgstr ""
-
msgid "Additional text"
msgstr ""
@@ -2945,6 +2942,9 @@ msgstr ""
msgid "Additional units"
msgstr ""
+msgid "Additional units of compute:"
+msgstr ""
+
msgid "Address"
msgstr ""
@@ -9583,9 +9583,6 @@ msgstr ""
msgid "CiCatalog|Get started with the CI/CD catalog"
msgstr ""
-msgid "CiCatalog|Learn more"
-msgstr ""
-
msgid "CiCatalog|Mark project as a CI/CD Catalog resource"
msgstr ""
@@ -11704,6 +11701,9 @@ msgstr ""
msgid "Compute quota"
msgstr ""
+msgid "Compute quota:"
+msgstr ""
+
msgid "Confidence"
msgstr ""
@@ -13782,6 +13782,9 @@ msgstr ""
msgid "DORA4Metrics|Change failure rate (percentage)"
msgstr ""
+msgid "DORA4Metrics|Closed issues"
+msgstr ""
+
msgid "DORA4Metrics|Critical Vulnerabilities over time"
msgstr ""
@@ -13902,6 +13905,9 @@ msgstr ""
msgid "DORA4Metrics|The chart displays the median time between a merge request being merged and deployed to production environment(s) that are based on the %{linkStart}deployment_tier%{linkEnd} value."
msgstr ""
+msgid "DORA4Metrics|This is a lower-bound approximation. Your group has too many issues and MRs to calculate in real time."
+msgstr ""
+
msgid "DORA4Metrics|Time to Restore Service"
msgstr ""
@@ -22043,9 +22049,6 @@ msgstr ""
msgid "Groups|Group path is unavailable. Path has been replaced with a suggested available path."
msgstr ""
-msgid "Groups|Learn more"
-msgstr ""
-
msgid "Groups|Learn more about subgroups"
msgstr ""
@@ -34485,10 +34488,10 @@ msgstr ""
msgid "Private profile:"
msgstr ""
-msgid "Private projects Minutes cost factor"
+msgid "Private projects can be created in your personal namespace with:"
msgstr ""
-msgid "Private projects can be created in your personal namespace with:"
+msgid "Private projects compute cost factor"
msgstr ""
msgid "Problem with %{name} command: %{message}."
@@ -34977,9 +34980,6 @@ msgstr ""
msgid "Profiles|Last used:"
msgstr ""
-msgid "Profiles|Learn more"
-msgstr ""
-
msgid "Profiles|Location"
msgstr ""
@@ -36678,9 +36678,6 @@ msgstr ""
msgid "Promotions|Keep track of events in your project"
msgstr ""
-msgid "Promotions|Learn more"
-msgstr ""
-
msgid "Promotions|Merge request approvals"
msgstr ""
@@ -37165,10 +37162,10 @@ msgstr ""
msgid "Public pipelines"
msgstr ""
-msgid "Public projects Minutes cost factor"
+msgid "Public projects are an easy way to allow everyone to have read-only access."
msgstr ""
-msgid "Public projects are an easy way to allow everyone to have read-only access."
+msgid "Public projects compute cost factor"
msgstr ""
msgid "Publish to status page"
@@ -37411,9 +37408,6 @@ msgstr ""
msgid "Quick range"
msgstr ""
-msgid "Quota of CI/CD minutes:"
-msgstr ""
-
msgid "README"
msgstr ""
@@ -49756,6 +49750,9 @@ msgstr ""
msgid "ValueStreamAnalytics|Number of commits pushed to the default branch"
msgstr ""
+msgid "ValueStreamAnalytics|Number of issues closed by month."
+msgstr ""
+
msgid "ValueStreamAnalytics|Number of new issues created."
msgstr ""
@@ -54458,9 +54455,6 @@ msgstr ""
msgid "mrWidget|If the last pipeline ran in the fork project, it may be inaccurate. Before merge, we advise running a pipeline in this project."
msgstr ""
-msgid "mrWidget|Learn more"
-msgstr ""
-
msgid "mrWidget|Loading deployment statistics"
msgstr ""
diff --git a/spec/frontend/ci/artifacts/components/artifact_row_spec.js b/spec/frontend/ci/artifacts/components/artifact_row_spec.js
index 96ddedc3a9d..8bf1138bc85 100644
--- a/spec/frontend/ci/artifacts/components/artifact_row_spec.js
+++ b/spec/frontend/ci/artifacts/components/artifact_row_spec.js
@@ -4,7 +4,7 @@ import { numberToHumanSize } from '~/lib/utils/number_utils';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import ArtifactRow from '~/ci/artifacts/components/artifact_row.vue';
-import { BULK_DELETE_FEATURE_FLAG, I18N_BULK_DELETE_MAX_SELECTED } from '~/ci/artifacts/constants';
+import { I18N_BULK_DELETE_MAX_SELECTED } from '~/ci/artifacts/constants';
describe('ArtifactRow component', () => {
let wrapper;
@@ -18,7 +18,7 @@ describe('ArtifactRow component', () => {
const findDeleteButton = () => wrapper.findByTestId('job-artifact-row-delete-button');
const findCheckbox = () => wrapper.findComponent(GlFormCheckbox);
- const createComponent = ({ canDestroyArtifacts = true, glFeatures = {}, props = {} } = {}) => {
+ const createComponent = ({ canDestroyArtifacts = true, props = {} } = {}) => {
wrapper = shallowMountExtended(ArtifactRow, {
propsData: {
artifact,
@@ -28,7 +28,7 @@ describe('ArtifactRow component', () => {
isSelectedArtifactsLimitReached: false,
...props,
},
- provide: { canDestroyArtifacts, glFeatures },
+ provide: { canDestroyArtifacts },
stubs: { GlBadge, GlFriendlyWrap },
});
};
@@ -80,35 +80,31 @@ describe('ArtifactRow component', () => {
});
describe('bulk delete checkbox', () => {
- describe('with permission and feature flag enabled', () => {
- it('emits selectArtifact when toggled', () => {
- createComponent({ glFeatures: { [BULK_DELETE_FEATURE_FLAG]: true } });
-
- findCheckbox().vm.$emit('input', true);
+ it('emits selectArtifact when toggled', () => {
+ createComponent();
- expect(wrapper.emitted('selectArtifact')).toStrictEqual([[artifact, true]]);
- });
+ findCheckbox().vm.$emit('input', true);
- describe('when the selected artifacts limit is reached', () => {
- it('remains enabled if the artifact was selected', () => {
- createComponent({
- glFeatures: { [BULK_DELETE_FEATURE_FLAG]: true },
- props: { isSelected: true, isSelectedArtifactsLimitReached: true },
- });
+ expect(wrapper.emitted('selectArtifact')).toStrictEqual([[artifact, true]]);
+ });
- expect(findCheckbox().attributes('disabled')).toBeUndefined();
- expect(findCheckbox().attributes('title')).toBe('');
+ describe('when the selected artifacts limit is reached', () => {
+ it('remains enabled if the artifact was selected', () => {
+ createComponent({
+ props: { isSelected: true, isSelectedArtifactsLimitReached: true },
});
- it('is disabled if the artifact was not selected', () => {
- createComponent({
- glFeatures: { [BULK_DELETE_FEATURE_FLAG]: true },
- props: { isSelected: false, isSelectedArtifactsLimitReached: true },
- });
+ expect(findCheckbox().attributes('disabled')).toBeUndefined();
+ expect(findCheckbox().attributes('title')).toBe('');
+ });
- expect(findCheckbox().attributes('disabled')).toBeDefined();
- expect(findCheckbox().attributes('title')).toBe(I18N_BULK_DELETE_MAX_SELECTED);
+ it('is disabled if the artifact was not selected', () => {
+ createComponent({
+ props: { isSelected: false, isSelectedArtifactsLimitReached: true },
});
+
+ expect(findCheckbox().attributes('disabled')).toBeDefined();
+ expect(findCheckbox().attributes('title')).toBe(I18N_BULK_DELETE_MAX_SELECTED);
});
});
@@ -117,11 +113,5 @@ describe('ArtifactRow component', () => {
expect(findCheckbox().exists()).toBe(false);
});
-
- it('is not shown with feature flag disabled', () => {
- createComponent();
-
- expect(findCheckbox().exists()).toBe(false);
- });
});
});
diff --git a/spec/frontend/ci/artifacts/components/job_artifacts_table_spec.js b/spec/frontend/ci/artifacts/components/job_artifacts_table_spec.js
index 9a16fac7406..e062140246b 100644
--- a/spec/frontend/ci/artifacts/components/job_artifacts_table_spec.js
+++ b/spec/frontend/ci/artifacts/components/job_artifacts_table_spec.js
@@ -30,7 +30,6 @@ import {
JOBS_PER_PAGE,
I18N_FETCH_ERROR,
INITIAL_CURRENT_PAGE,
- BULK_DELETE_FEATURE_FLAG,
I18N_BULK_DELETE_ERROR,
SELECTED_ARTIFACTS_MAX_COUNT,
} from '~/ci/artifacts/constants';
@@ -152,7 +151,6 @@ describe('JobArtifactsTable component', () => {
},
data = {},
canDestroyArtifacts = true,
- glFeatures = {},
} = {}) => {
requestHandlers = handlers;
wrapper = mountExtended(JobArtifactsTable, {
@@ -165,7 +163,6 @@ describe('JobArtifactsTable component', () => {
projectId,
canDestroyArtifacts,
artifactsManagementFeedbackImagePath: 'banner/image/path',
- glFeatures,
},
mocks: {
$toast: {
@@ -332,6 +329,7 @@ describe('JobArtifactsTable component', () => {
it('is disabled when there is no download path', async () => {
const jobWithoutDownloadPath = {
...job,
+ hasArtifacts: true,
archive: { downloadPath: null },
};
@@ -358,6 +356,7 @@ describe('JobArtifactsTable component', () => {
it('is disabled when there is no browse path', async () => {
const jobWithoutBrowsePath = {
...job,
+ hasArtifacts: true,
browseArtifactsPath: null,
};
@@ -407,75 +406,71 @@ describe('JobArtifactsTable component', () => {
describe('delete button', () => {
const artifactsFromJob = job.artifacts.nodes.map((node) => node.id);
- describe('with delete permission and bulk delete feature flag enabled', () => {
- beforeEach(async () => {
- createComponent({
- canDestroyArtifacts: true,
- glFeatures: { [BULK_DELETE_FEATURE_FLAG]: true },
- });
-
- await waitForPromises();
+ beforeEach(async () => {
+ createComponent({
+ canDestroyArtifacts: true,
});
- it('opens the confirmation modal with the artifacts from the job', async () => {
- await findDeleteButton().vm.$emit('click');
-
- expect(findBulkDeleteModal().props()).toMatchObject({
- visible: true,
- artifactsToDelete: artifactsFromJob,
- });
- });
+ await waitForPromises();
+ });
- it('on confirm, deletes the artifacts from the job and shows a toast', async () => {
- findDeleteButton().vm.$emit('click');
- findBulkDeleteModal().vm.$emit('primary');
+ it('opens the confirmation modal with the artifacts from the job', async () => {
+ await findDeleteButton().vm.$emit('click');
- expect(bulkDestroyMutationHandler).toHaveBeenCalledWith({
- projectId: convertToGraphQLId(TYPENAME_PROJECT, projectId),
- ids: artifactsFromJob,
- });
+ expect(findBulkDeleteModal().props()).toMatchObject({
+ visible: true,
+ artifactsToDelete: artifactsFromJob,
+ });
+ });
- await waitForPromises();
+ it('on confirm, deletes the artifacts from the job and shows a toast', async () => {
+ findDeleteButton().vm.$emit('click');
+ findBulkDeleteModal().vm.$emit('primary');
- expect(mockToastShow).toHaveBeenCalledWith(
- `${artifactsFromJob.length} selected artifacts deleted`,
- );
+ expect(bulkDestroyMutationHandler).toHaveBeenCalledWith({
+ projectId: convertToGraphQLId(TYPENAME_PROJECT, projectId),
+ ids: artifactsFromJob,
});
- it('does not clear selected artifacts on success', async () => {
- // select job 2 via checkbox
- findJobCheckbox(2).vm.$emit('change', true);
+ await waitForPromises();
- // click delete button job 1
- findDeleteButton().vm.$emit('click');
+ expect(mockToastShow).toHaveBeenCalledWith(
+ `${artifactsFromJob.length} selected artifacts deleted`,
+ );
+ });
- // job 2's artifacts should still be selected
- expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual(
- job2.artifacts.nodes.map((node) => node.id),
- );
+ it('does not clear selected artifacts on success', async () => {
+ // select job 2 via checkbox
+ findJobCheckbox(2).vm.$emit('change', true);
- // confirm delete
- findBulkDeleteModal().vm.$emit('primary');
+ // click delete button job 1
+ findDeleteButton().vm.$emit('click');
- // job 1's artifacts should be deleted
- expect(bulkDestroyMutationHandler).toHaveBeenCalledWith({
- projectId: convertToGraphQLId(TYPENAME_PROJECT, projectId),
- ids: artifactsFromJob,
- });
+ // job 2's artifacts should still be selected
+ expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual(
+ job2.artifacts.nodes.map((node) => node.id),
+ );
- await waitForPromises();
+ // confirm delete
+ findBulkDeleteModal().vm.$emit('primary');
- // job 2's artifacts should still be selected
- expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual(
- job2.artifacts.nodes.map((node) => node.id),
- );
+ // job 1's artifacts should be deleted
+ expect(bulkDestroyMutationHandler).toHaveBeenCalledWith({
+ projectId: convertToGraphQLId(TYPENAME_PROJECT, projectId),
+ ids: artifactsFromJob,
});
+
+ await waitForPromises();
+
+ // job 2's artifacts should still be selected
+ expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual(
+ job2.artifacts.nodes.map((node) => node.id),
+ );
});
it('shows an alert and does not clear selected artifacts on error', async () => {
createComponent({
canDestroyArtifacts: true,
- glFeatures: { [BULK_DELETE_FEATURE_FLAG]: true },
handlers: {
getJobArtifactsQuery: jest.fn().mockResolvedValue(getJobArtifactsResponse),
bulkDestroyArtifactsMutation: jest.fn().mockRejectedValue(),
@@ -505,21 +500,9 @@ describe('JobArtifactsTable component', () => {
});
});
- it('is disabled when bulk delete feature flag is disabled', async () => {
- createComponent({
- canDestroyArtifacts: true,
- glFeatures: { [BULK_DELETE_FEATURE_FLAG]: false },
- });
-
- await waitForPromises();
-
- expect(findDeleteButton().attributes('disabled')).toBeDefined();
- });
-
it('is hidden when user does not have delete permission', async () => {
createComponent({
canDestroyArtifacts: false,
- glFeatures: { [BULK_DELETE_FEATURE_FLAG]: false },
});
await waitForPromises();
@@ -531,175 +514,168 @@ describe('JobArtifactsTable component', () => {
describe('bulk delete', () => {
const selectedArtifacts = job.artifacts.nodes.map((node) => node.id);
- describe('with permission and feature flag enabled', () => {
- beforeEach(async () => {
- createComponent({
- canDestroyArtifacts: true,
- glFeatures: { [BULK_DELETE_FEATURE_FLAG]: true },
- });
-
- await waitForPromises();
+ beforeEach(async () => {
+ createComponent({
+ canDestroyArtifacts: true,
});
- it('shows selected artifacts when a job is checked', async () => {
- expect(findBulkDeleteContainer().exists()).toBe(false);
-
- await findJobCheckbox().vm.$emit('change', true);
+ await waitForPromises();
+ });
- expect(findBulkDeleteContainer().exists()).toBe(true);
- expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual(selectedArtifacts);
- });
+ it('shows selected artifacts when a job is checked', async () => {
+ expect(findBulkDeleteContainer().exists()).toBe(false);
- it('disappears when selected artifacts are cleared', async () => {
- await findJobCheckbox().vm.$emit('change', true);
+ await findJobCheckbox().vm.$emit('change', true);
- expect(findBulkDeleteContainer().exists()).toBe(true);
+ expect(findBulkDeleteContainer().exists()).toBe(true);
+ expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual(selectedArtifacts);
+ });
- await findBulkDelete().vm.$emit('clearSelectedArtifacts');
+ it('disappears when selected artifacts are cleared', async () => {
+ await findJobCheckbox().vm.$emit('change', true);
- expect(findBulkDeleteContainer().exists()).toBe(false);
- });
+ expect(findBulkDeleteContainer().exists()).toBe(true);
- it('shows a modal to confirm bulk delete', async () => {
- findJobCheckbox().vm.$emit('change', true);
- findBulkDelete().vm.$emit('showBulkDeleteModal');
+ await findBulkDelete().vm.$emit('clearSelectedArtifacts');
- await nextTick();
+ expect(findBulkDeleteContainer().exists()).toBe(false);
+ });
- expect(findBulkDeleteModal().props('visible')).toBe(true);
- });
+ it('shows a modal to confirm bulk delete', async () => {
+ findJobCheckbox().vm.$emit('change', true);
+ findBulkDelete().vm.$emit('showBulkDeleteModal');
- it('deletes the selected artifacts and shows a toast', async () => {
- findJobCheckbox().vm.$emit('change', true);
- findBulkDelete().vm.$emit('showBulkDeleteModal');
- findBulkDeleteModal().vm.$emit('primary');
+ await nextTick();
- expect(bulkDestroyMutationHandler).toHaveBeenCalledWith({
- projectId: convertToGraphQLId(TYPENAME_PROJECT, projectId),
- ids: selectedArtifacts,
- });
+ expect(findBulkDeleteModal().props('visible')).toBe(true);
+ });
- await waitForPromises();
+ it('deletes the selected artifacts and shows a toast', async () => {
+ findJobCheckbox().vm.$emit('change', true);
+ findBulkDelete().vm.$emit('showBulkDeleteModal');
+ findBulkDeleteModal().vm.$emit('primary');
- expect(mockToastShow).toHaveBeenCalledWith(
- `${selectedArtifacts.length} selected artifacts deleted`,
- );
+ expect(bulkDestroyMutationHandler).toHaveBeenCalledWith({
+ projectId: convertToGraphQLId(TYPENAME_PROJECT, projectId),
+ ids: selectedArtifacts,
});
- it('clears selected artifacts on success', async () => {
- findJobCheckbox().vm.$emit('change', true);
- findBulkDelete().vm.$emit('showBulkDeleteModal');
- findBulkDeleteModal().vm.$emit('primary');
+ await waitForPromises();
- await waitForPromises();
+ expect(mockToastShow).toHaveBeenCalledWith(
+ `${selectedArtifacts.length} selected artifacts deleted`,
+ );
+ });
- expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual([]);
- });
+ it('clears selected artifacts on success', async () => {
+ findJobCheckbox().vm.$emit('change', true);
+ findBulkDelete().vm.$emit('showBulkDeleteModal');
+ findBulkDeleteModal().vm.$emit('primary');
- describe('select all checkbox', () => {
- describe('when no artifacts are selected', () => {
- it('is not checked', () => {
- expect(findSelectAllCheckboxChecked()).toBe(false);
- expect(findSelectAllCheckboxIndeterminate()).toBe(false);
- });
+ await waitForPromises();
- it('selects all artifacts when toggled', async () => {
- toggleSelectAllCheckbox();
+ expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual([]);
+ });
- await nextTick();
+ describe('select all checkbox', () => {
+ describe('when no artifacts are selected', () => {
+ it('is not checked', () => {
+ expect(findSelectAllCheckboxChecked()).toBe(false);
+ expect(findSelectAllCheckboxIndeterminate()).toBe(false);
+ });
- expect(findSelectAllCheckboxChecked()).toBe(true);
- expect(findSelectAllCheckboxIndeterminate()).toBe(false);
- expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual(allArtifacts);
- });
+ it('selects all artifacts when toggled', async () => {
+ toggleSelectAllCheckbox();
+
+ await nextTick();
+
+ expect(findSelectAllCheckboxChecked()).toBe(true);
+ expect(findSelectAllCheckboxIndeterminate()).toBe(false);
+ expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual(allArtifacts);
});
+ });
- describe('when some artifacts are selected', () => {
- beforeEach(async () => {
- findJobCheckbox().vm.$emit('change', true);
+ describe('when some artifacts are selected', () => {
+ beforeEach(async () => {
+ findJobCheckbox().vm.$emit('change', true);
- await nextTick();
- });
+ await nextTick();
+ });
- it('is indeterminate', () => {
- expect(findSelectAllCheckboxChecked()).toBe(true);
- expect(findSelectAllCheckboxIndeterminate()).toBe(true);
- });
+ it('is indeterminate', () => {
+ expect(findSelectAllCheckboxChecked()).toBe(true);
+ expect(findSelectAllCheckboxIndeterminate()).toBe(true);
+ });
- it('deselects all artifacts when toggled', async () => {
- toggleSelectAllCheckbox();
+ it('deselects all artifacts when toggled', async () => {
+ toggleSelectAllCheckbox();
- await nextTick();
+ await nextTick();
- expect(findSelectAllCheckboxChecked()).toBe(false);
- expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual([]);
- });
+ expect(findSelectAllCheckboxChecked()).toBe(false);
+ expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual([]);
});
+ });
- describe('when all artifacts are selected', () => {
- beforeEach(async () => {
- findJobCheckbox(1).vm.$emit('change', true);
- findJobCheckbox(2).vm.$emit('change', true);
+ describe('when all artifacts are selected', () => {
+ beforeEach(async () => {
+ findJobCheckbox(1).vm.$emit('change', true);
+ findJobCheckbox(2).vm.$emit('change', true);
- await nextTick();
- });
+ await nextTick();
+ });
- it('is checked', () => {
- expect(findSelectAllCheckboxChecked()).toBe(true);
- expect(findSelectAllCheckboxIndeterminate()).toBe(false);
- });
+ it('is checked', () => {
+ expect(findSelectAllCheckboxChecked()).toBe(true);
+ expect(findSelectAllCheckboxIndeterminate()).toBe(false);
+ });
- it('deselects all artifacts when toggled', async () => {
- toggleSelectAllCheckbox();
+ it('deselects all artifacts when toggled', async () => {
+ toggleSelectAllCheckbox();
- await nextTick();
+ await nextTick();
- expect(findSelectAllCheckboxChecked()).toBe(false);
- expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual([]);
- });
+ expect(findSelectAllCheckboxChecked()).toBe(false);
+ expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual([]);
});
+ });
- describe('when an artifact is selected on another page', () => {
- const otherPageArtifact = { id: 'gid://gitlab/Ci::JobArtifact/some/other/id' };
+ describe('when an artifact is selected on another page', () => {
+ const otherPageArtifact = { id: 'gid://gitlab/Ci::JobArtifact/some/other/id' };
- beforeEach(async () => {
- // expand the first job row to access the details component
- findCount().trigger('click');
+ beforeEach(async () => {
+ // expand the first job row to access the details component
+ findCount().trigger('click');
- await nextTick();
+ await nextTick();
- // mock the selection of an artifact on another page by emitting a select event
- findDetailsInRow(1).vm.$emit('selectArtifact', otherPageArtifact, true);
- });
+ // mock the selection of an artifact on another page by emitting a select event
+ findDetailsInRow(1).vm.$emit('selectArtifact', otherPageArtifact, true);
+ });
- it('is not checked even though an artifact is selected', () => {
- expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual([
- otherPageArtifact.id,
- ]);
- expect(findSelectAllCheckboxChecked()).toBe(false);
- expect(findSelectAllCheckboxIndeterminate()).toBe(false);
- });
+ it('is not checked even though an artifact is selected', () => {
+ expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual([otherPageArtifact.id]);
+ expect(findSelectAllCheckboxChecked()).toBe(false);
+ expect(findSelectAllCheckboxIndeterminate()).toBe(false);
+ });
- it('only toggles selection of visible artifacts, leaving the other artifact selected', async () => {
- toggleSelectAllCheckbox();
+ it('only toggles selection of visible artifacts, leaving the other artifact selected', async () => {
+ toggleSelectAllCheckbox();
- await nextTick();
+ await nextTick();
- expect(findSelectAllCheckboxChecked()).toBe(true);
- expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual([
- otherPageArtifact.id,
- ...allArtifacts,
- ]);
+ expect(findSelectAllCheckboxChecked()).toBe(true);
+ expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual([
+ otherPageArtifact.id,
+ ...allArtifacts,
+ ]);
- toggleSelectAllCheckbox();
+ toggleSelectAllCheckbox();
- await nextTick();
+ await nextTick();
- expect(findSelectAllCheckboxChecked()).toBe(false);
- expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual([
- otherPageArtifact.id,
- ]);
- });
+ expect(findSelectAllCheckboxChecked()).toBe(false);
+ expect(findBulkDelete().props('selectedArtifacts')).toStrictEqual([otherPageArtifact.id]);
});
});
});
@@ -711,7 +687,6 @@ describe('JobArtifactsTable component', () => {
beforeEach(async () => {
createComponent({
canDestroyArtifacts: true,
- glFeatures: { [BULK_DELETE_FEATURE_FLAG]: true },
data: {
selectedArtifacts: new Array(selectedArtifactsLength).fill('artifact-id'),
},
@@ -742,7 +717,6 @@ describe('JobArtifactsTable component', () => {
beforeEach(async () => {
createComponent({
canDestroyArtifacts: true,
- glFeatures: { [BULK_DELETE_FEATURE_FLAG]: true },
data: { selectedArtifacts: maxSelectedArtifacts },
});
@@ -775,7 +749,6 @@ describe('JobArtifactsTable component', () => {
beforeEach(async () => {
createComponent({
canDestroyArtifacts: true,
- glFeatures: { [BULK_DELETE_FEATURE_FLAG]: true },
data: {
selectedArtifacts: maxSelectedArtifactsIncludingCurrentPage,
},
@@ -811,7 +784,6 @@ describe('JobArtifactsTable component', () => {
it('shows an alert and does not clear selected artifacts on error', async () => {
createComponent({
canDestroyArtifacts: true,
- glFeatures: { [BULK_DELETE_FEATURE_FLAG]: true },
handlers: {
getJobArtifactsQuery: jest.fn().mockResolvedValue(getJobArtifactsResponse),
bulkDestroyArtifactsMutation: jest.fn().mockRejectedValue(),
@@ -837,18 +809,6 @@ describe('JobArtifactsTable component', () => {
it('shows no checkboxes without permission', async () => {
createComponent({
canDestroyArtifacts: false,
- glFeatures: { [BULK_DELETE_FEATURE_FLAG]: true },
- });
-
- await waitForPromises();
-
- expect(findAnyCheckbox().exists()).toBe(false);
- });
-
- it('shows no checkboxes with feature flag disabled', async () => {
- createComponent({
- canDestroyArtifacts: true,
- glFeatures: { [BULK_DELETE_FEATURE_FLAG]: false },
});
await waitForPromises();
diff --git a/spec/frontend/issues/dashboard/mock_data.js b/spec/frontend/issues/dashboard/mock_data.js
index e789360d1d5..adcd4268449 100644
--- a/spec/frontend/issues/dashboard/mock_data.js
+++ b/spec/frontend/issues/dashboard/mock_data.js
@@ -3,6 +3,7 @@ export const issuesQueryResponse = {
issues: {
nodes: [
{
+ __persist: true,
__typename: 'Issue',
id: 'gid://gitlab/Issue/123456',
iid: '789',
@@ -27,6 +28,7 @@ export const issuesQueryResponse = {
assignees: {
nodes: [
{
+ __persist: true,
__typename: 'UserCore',
id: 'gid://gitlab/User/234',
avatarUrl: 'avatar/url',
@@ -37,6 +39,7 @@ export const issuesQueryResponse = {
],
},
author: {
+ __persist: true,
__typename: 'UserCore',
id: 'gid://gitlab/User/456',
avatarUrl: 'avatar/url',
@@ -47,6 +50,7 @@ export const issuesQueryResponse = {
labels: {
nodes: [
{
+ __persist: true,
id: 'gid://gitlab/ProjectLabel/456',
color: '#333',
title: 'Label title',
diff --git a/spec/requests/api/graphql/mutations/ci/job_artifact/bulk_destroy_spec.rb b/spec/requests/api/graphql/mutations/ci/job_artifact/bulk_destroy_spec.rb
index 4e25669a0ca..5cb48ec44a0 100644
--- a/spec/requests/api/graphql/mutations/ci/job_artifact/bulk_destroy_spec.rb
+++ b/spec/requests/api/graphql/mutations/ci/job_artifact/bulk_destroy_spec.rb
@@ -41,23 +41,6 @@ RSpec.describe 'BulkDestroy', feature_category: :build_artifacts do
expect(first_artifact.reload).to be_persisted
end
- context 'when the `ci_job_artifact_bulk_destroy` feature flag is disabled' do
- before do
- stub_feature_flags(ci_job_artifact_bulk_destroy: false)
- project.add_maintainer(maintainer)
- end
-
- it 'returns a resource not available error' do
- post_graphql_mutation(mutation, current_user: maintainer)
-
- expect(graphql_errors).to contain_exactly(
- hash_including(
- 'message' => '`ci_job_artifact_bulk_destroy` feature flag is disabled.'
- )
- )
- end
- end
-
context "when the user is a developer in a project" do
before do
project.add_developer(developer)
diff --git a/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb b/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
index 6ebf93730eb..9b2cfead684 100644
--- a/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
+++ b/spec/workers/gitlab/github_import/stage/import_pull_requests_worker_spec.rb
@@ -5,38 +5,110 @@ require 'spec_helper'
RSpec.describe Gitlab::GithubImport::Stage::ImportPullRequestsWorker, feature_category: :importers do
let_it_be(:project) { create(:project) }
let_it_be(:import_state) { create(:import_state, project: project) }
+ let(:options) { { state: 'all', sort: 'number', direction: 'desc', per_page: '1' } }
let(:worker) { described_class.new }
let(:importer) { double(:importer) }
let(:client) { double(:client) }
describe '#import' do
- it 'imports all the pull requests' do
- waiter = Gitlab::JobWaiter.new(2, '123')
+ context 'with pull requests' do
+ it 'imports all the pull requests and allocates internal iids' do
+ waiter = Gitlab::JobWaiter.new(2, '123')
- expect(Gitlab::GithubImport::Importer::PullRequestsImporter)
- .to receive(:new)
- .with(project, client)
- .and_return(importer)
+ expect(Gitlab::GithubImport::Importer::PullRequestsImporter)
+ .to receive(:new)
+ .with(project, client)
+ .and_return(importer)
- expect(importer)
- .to receive(:execute)
- .and_return(waiter)
+ expect(importer)
+ .to receive(:execute)
+ .and_return(waiter)
- expect(import_state)
- .to receive(:refresh_jid_expiration)
+ expect(import_state)
+ .to receive(:refresh_jid_expiration)
- expect(Gitlab::GithubImport::AdvanceStageWorker)
- .to receive(:perform_async)
- .with(project.id, { '123' => 2 }, :collaborators)
+ expect(InternalId).to receive(:exists?).and_return(false)
- worker.import(client, project)
+ expect(client).to receive(:each_object).with(
+ :pulls, project.import_source, options
+ ).and_return([{ number: 4 }].each)
+
+ expect(Gitlab::GithubImport::AdvanceStageWorker)
+ .to receive(:perform_async)
+ .with(project.id, { '123' => 2 }, :collaborators)
+
+ expect(MergeRequest).to receive(:track_target_project_iid!)
+
+ worker.import(client, project)
+ end
+ end
+
+ context 'without pull requests' do
+ it 'does not allocate internal iids' do
+ waiter = Gitlab::JobWaiter.new(2, '123')
+
+ expect(Gitlab::GithubImport::Importer::PullRequestsImporter)
+ .to receive(:new)
+ .with(project, client)
+ .and_return(importer)
+
+ expect(importer)
+ .to receive(:execute)
+ .and_return(waiter)
+
+ expect(import_state)
+ .to receive(:refresh_jid_expiration)
+
+ expect(InternalId).to receive(:exists?).and_return(false)
+
+ expect(client).to receive(:each_object).with(
+ :pulls, project.import_source, options
+ ).and_return([nil].each)
+
+ expect(Gitlab::GithubImport::AdvanceStageWorker)
+ .to receive(:perform_async)
+ .with(project.id, { '123' => 2 }, :collaborators)
+
+ expect(MergeRequest).not_to receive(:track_target_project_iid!)
+
+ worker.import(client, project)
+ end
+ end
+
+ context 'when retrying' do
+ it 'does not allocate internal iids' do
+ waiter = Gitlab::JobWaiter.new(2, '123')
+
+ expect(Gitlab::GithubImport::Importer::PullRequestsImporter)
+ .to receive(:new)
+ .with(project, client)
+ .and_return(importer)
+
+ expect(importer)
+ .to receive(:execute)
+ .and_return(waiter)
+
+ expect(import_state)
+ .to receive(:refresh_jid_expiration)
+
+ expect(InternalId).to receive(:exists?).and_return(true)
+
+ expect(client).not_to receive(:each_object)
+ expect(MergeRequest).not_to receive(:track_target_project_iid!)
+
+ worker.import(client, project)
+ end
end
end
it 'raises an error' do
exception = StandardError.new('_some_error_')
+ expect(client).to receive(:each_object).with(
+ :pulls, project.import_source, options
+ ).and_return([{ number: 4 }].each)
+
expect_next_instance_of(Gitlab::GithubImport::Importer::PullRequestsImporter) do |importer|
expect(importer).to receive(:execute).and_raise(exception)
end