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:
-rw-r--r--app/assets/javascripts/boards/components/board_content_sidebar.vue22
-rw-r--r--app/assets/javascripts/merge_conflicts/merge_conflict_resolver_app.vue8
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue19
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql3
-rw-r--r--app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue5
-rw-r--r--app/assets/javascripts/packages_and_registries/shared/utils.js5
-rw-r--r--app/assets/javascripts/sidebar/components/sidebar_dropdown.vue22
-rw-r--r--app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue104
-rw-r--r--app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js2
-rw-r--r--app/components/projects/ml/show_ml_model_component.rb3
-rw-r--r--app/controllers/groups/boards_controller.rb1
-rw-r--r--app/controllers/projects/boards_controller.rb1
-rw-r--r--app/controllers/projects/issues_controller.rb2
-rw-r--r--app/helpers/dropdowns_helper.rb2
-rw-r--r--app/helpers/projects_helper.rb2
-rw-r--r--app/helpers/sidebars_helper.rb12
-rw-r--r--app/helpers/ssh_keys_helper.rb1
-rw-r--r--app/models/namespace_setting.rb1
-rw-r--r--app/presenters/ml/model_presenter.rb4
-rw-r--r--app/services/ml/create_model_service.rb6
-rw-r--r--app/services/ml/create_model_version_service.rb6
-rw-r--r--app/validators/kubernetes_container_resources_validator.rb77
-rw-r--r--app/views/groups/_home_panel.html.haml2
-rw-r--r--app/views/groups/edit.html.haml2
-rw-r--r--app/views/groups/projects.html.haml2
-rw-r--r--app/views/groups/settings/_export.html.haml2
-rw-r--r--app/views/groups/settings/_general.html.haml4
-rw-r--r--app/views/groups/settings/_lfs.html.haml2
-rw-r--r--app/views/groups/settings/_permissions.html.haml3
-rw-r--r--app/views/groups/settings/_project_creation_level.html.haml2
-rw-r--r--app/views/groups/settings/_two_factor_auth.html.haml2
-rw-r--r--app/views/shared/_allow_request_access.html.haml2
-rw-r--r--app/views/shared/issuable/_sidebar.html.haml2
-rw-r--r--app/workers/abuse/trust_score_worker.rb23
-rw-r--r--app/workers/all_queues.yml9
-rw-r--r--config/events/k8s_api_proxy_requests_unique_users_via_ci_access.yml2
-rw-r--r--config/events/k8s_api_proxy_requests_unique_users_via_pat_access.yml2
-rw-r--r--config/events/k8s_api_proxy_requests_unique_users_via_user_access.yml2
-rw-r--r--config/events/ml_model_created.yml24
-rw-r--r--config/events/ml_model_version_created.yml24
-rw-r--r--config/feature_flags/development/display_work_item_epic_issue_sidebar.yml8
-rw-r--r--config/feature_flags/development/exempt_paid_namespace_members_and_enterprise_users_from_identity_verification.yml8
-rw-r--r--config/metrics/counts_28d/count_total_model_registry_ml_model_created_28d.yml26
-rw-r--r--config/metrics/counts_28d/count_total_model_registry_ml_model_version_created_28d.yml26
-rw-r--r--config/metrics/counts_7d/count_total_code_suggestions_authenticate_7d.yml2
-rw-r--r--config/metrics/counts_7d/count_total_model_registry_ml_model_created_7d.yml26
-rw-r--r--config/metrics/counts_7d/count_total_model_registry_ml_model_version_created_7d.yml26
-rw-r--r--config/sidekiq_queues.yml2
-rw-r--r--db/migrate/20231129124754_add_cascading_toggle_security_policy_custom_ci_setting.rb17
-rw-r--r--db/post_migrate/20231218062442_remove_max_workspaces_from_remote_development_agent_configs.rb10
-rw-r--r--db/post_migrate/20231218062505_remove_max_workspaces_per_user_from_remote_development_agent_configs.rb10
-rw-r--r--db/schema_migrations/202311291247541
-rw-r--r--db/schema_migrations/202312180624421
-rw-r--r--db/schema_migrations/202312180625051
-rw-r--r--db/structure.sql6
-rw-r--r--doc/administration/environment_variables.md1
-rw-r--r--doc/administration/housekeeping.md4
-rw-r--r--doc/administration/pages/index.md4
-rw-r--r--doc/administration/redis/troubleshooting.md5
-rw-r--r--doc/administration/sidekiq/sidekiq_troubleshooting.md5
-rw-r--r--doc/ci/caching/index.md2
-rw-r--r--doc/ci/pipelines/schedules.md4
-rw-r--r--doc/ci/testing/code_coverage.md2
-rw-r--r--doc/development/code_review.md4
-rw-r--r--doc/development/database/batched_background_migrations.md35
-rw-r--r--doc/development/internal_api/index.md6
-rw-r--r--doc/integration/jira/issues.md2
-rw-r--r--doc/subscriptions/community_programs.md2
-rw-r--r--doc/update/patch_versions.md2
-rw-r--r--doc/user/application_security/index.md6
-rw-r--r--doc/user/free_user_limit.md2
-rw-r--r--doc/user/group/index.md2
-rw-r--r--doc/user/group/iterations/index.md2
-rw-r--r--doc/user/okrs.md6
-rw-r--r--doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md6
-rw-r--r--doc/user/project/pages/introduction.md4
-rw-r--r--doc/user/project/releases/index.md6
-rw-r--r--doc/user/project/repository/git_blame.md4
-rw-r--r--doc/user/project/repository/mirror/bidirectional.md2
-rw-r--r--doc/user/public_access.md2
-rw-r--r--doc/user/report_abuse.md4
-rw-r--r--doc/user/shortcuts.md2
-rw-r--r--doc/user/snippets.md2
-rw-r--r--lib/api/deployments.rb2
-rw-r--r--lib/gitlab/sidekiq_middleware/pause_control/server.rb4
-rw-r--r--lib/gitlab/sidekiq_middleware/pause_control/workers_map.rb3
-rw-r--r--lib/integrations/google_cloud_platform/jwt.rb8
-rw-r--r--locale/gitlab.pot42
-rw-r--r--package.json2
-rw-r--r--qa/qa/page/group/settings/general.rb74
-rw-r--r--qa/qa/page/group/show.rb6
-rw-r--r--spec/components/projects/ml/show_ml_model_component_spec.rb16
-rw-r--r--spec/factories/ml/experiments.rb8
-rw-r--r--spec/frontend/merge_conflicts/components/merge_conflict_resolver_app_spec.js17
-rw-r--r--spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js17
-rw-r--r--spec/frontend/packages_and_registries/package_registry/pages/list_spec.js9
-rw-r--r--spec/frontend/packages_and_registries/shared/utils_spec.js17
-rw-r--r--spec/lib/gitlab/sidekiq_middleware/pause_control/workers_map_spec.rb37
-rw-r--r--spec/lib/integrations/google_cloud_platform/jwt_spec.rb26
-rw-r--r--spec/presenters/ml/model_presenter_spec.rb14
-rw-r--r--spec/requests/api/deployments_spec.rb16
-rw-r--r--spec/services/ml/create_model_service_spec.rb13
-rw-r--r--spec/services/ml/create_model_version_service_spec.rb14
-rw-r--r--spec/validators/kubernetes_container_resources_validator_spec.rb42
-rw-r--r--spec/workers/abuse/trust_score_worker_spec.rb46
-rw-r--r--yarn.lock8
106 files changed, 967 insertions, 191 deletions
diff --git a/app/assets/javascripts/boards/components/board_content_sidebar.vue b/app/assets/javascripts/boards/components/board_content_sidebar.vue
index 26cd0c3affa..7929c1ad488 100644
--- a/app/assets/javascripts/boards/components/board_content_sidebar.vue
+++ b/app/assets/javascripts/boards/components/board_content_sidebar.vue
@@ -16,6 +16,7 @@ import SidebarSeverityWidget from '~/sidebar/components/severity/sidebar_severit
import SidebarSubscriptionsWidget from '~/sidebar/components/subscriptions/sidebar_subscriptions_widget.vue';
import SidebarTodoWidget from '~/sidebar/components/todo_toggle/sidebar_todo_widget.vue';
import SidebarLabelsWidget from '~/sidebar/components/labels/labels_select_widget/labels_select_root.vue';
+import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import { setError } from '../graphql/cache_updates';
export default {
@@ -39,6 +40,7 @@ export default {
SidebarWeightWidget: () =>
import('ee_component/sidebar/components/weight/sidebar_weight_widget.vue'),
},
+ mixins: [glFeatureFlagMixin()],
inject: {
multipleAssigneesFeatureAvailable: {
default: false,
@@ -143,6 +145,17 @@ export default {
const { referencePath = '' } = this.activeBoardIssuable;
return referencePath.slice(0, referencePath.indexOf('#'));
},
+ showWorkItemEpics() {
+ return this.glFeatures.displayWorkItemEpicIssueSidebar;
+ },
+ showEpicSidebarDropdownWidget() {
+ return this.epicFeatureAvailable && !this.isIncidentSidebar && this.activeBoardIssuable.id;
+ },
+ showIterationSidebarDropdownWidget() {
+ return (
+ this.iterationFeatureAvailable && !this.isIncidentSidebar && this.activeBoardIssuable.id
+ );
+ },
},
methods: {
handleClose() {
@@ -189,29 +202,34 @@ export default {
:editable="canUpdate"
/>
<sidebar-dropdown-widget
- v-if="epicFeatureAvailable && !isIncidentSidebar"
+ v-if="showEpicSidebarDropdownWidget"
:key="`epic-${activeBoardIssuable.iid}`"
:iid="activeBoardIssuable.iid"
issuable-attribute="epic"
:workspace-path="projectPathForActiveIssue"
:attr-workspace-path="groupPathForActiveIssue"
:issuable-type="issuableType"
+ :issue-id="activeBoardIssuable.id"
+ :show-work-item-epics="showWorkItemEpics"
data-testid="sidebar-epic"
/>
<div>
<sidebar-dropdown-widget
+ v-if="activeBoardIssuable.id"
:key="`milestone-${activeBoardIssuable.iid}`"
:iid="activeBoardIssuable.iid"
issuable-attribute="milestone"
:workspace-path="projectPathForActiveIssue"
:attr-workspace-path="projectPathForActiveIssue"
:issuable-type="issuableType"
+ :issue-id="activeBoardIssuable.id"
data-testid="sidebar-milestones"
/>
<sidebar-iteration-widget
- v-if="iterationFeatureAvailable && !isIncidentSidebar"
+ v-if="showIterationSidebarDropdownWidget"
:key="`iteration-${activeBoardIssuable.iid}`"
:iid="activeBoardIssuable.iid"
+ :issue-id="activeBoardIssuable.id"
:workspace-path="projectPathForActiveIssue"
:attr-workspace-path="groupPathForActiveIssue"
:issuable-type="issuableType"
diff --git a/app/assets/javascripts/merge_conflicts/merge_conflict_resolver_app.vue b/app/assets/javascripts/merge_conflicts/merge_conflict_resolver_app.vue
index d80517c1c1f..5f8f0e2b96c 100644
--- a/app/assets/javascripts/merge_conflicts/merge_conflict_resolver_app.vue
+++ b/app/assets/javascripts/merge_conflicts/merge_conflict_resolver_app.vue
@@ -2,6 +2,7 @@
import { GlSprintf, GlButton, GlButtonGroup, GlLoadingIcon } from '@gitlab/ui';
// eslint-disable-next-line no-restricted-imports
import { mapGetters, mapState, mapActions } from 'vuex';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { __ } from '~/locale';
import FileIcon from '~/vue_shared/components/file_icon.vue';
import DiffFileEditor from './components/diff_file_editor.vue';
@@ -23,6 +24,7 @@ export default {
components: {
GlButton,
GlButtonGroup,
+ ClipboardButton,
GlSprintf,
GlLoadingIcon,
FileIcon,
@@ -122,6 +124,12 @@ export default {
<div class="file-header-content" data-testid="file-name">
<file-icon :file-name="file.filePath" :size="16" css-classes="gl-mr-2" />
<strong class="file-title-name">{{ file.filePath }}</strong>
+ <clipboard-button
+ :title="__('Copy file path')"
+ :text="file.filePath"
+ size="small"
+ category="tertiary"
+ />
</div>
<div class="file-actions d-flex align-items-center gl-ml-auto gl-align-self-start">
<gl-button-group v-if="file.type === 'text'" class="gl-mr-3">
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue
index 1020cd0c533..df50f5a52b4 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/components/list/package_search.vue
@@ -1,10 +1,13 @@
<script>
+import { GlFilteredSearchToken } from '@gitlab/ui';
import { sortableFields } from '~/packages_and_registries/package_registry/utils';
import {
FILTERED_SEARCH_TERM,
OPERATORS_IS,
TOKEN_TITLE_TYPE,
TOKEN_TYPE_TYPE,
+ TOKEN_TITLE_VERSION,
+ TOKEN_TYPE_VERSION,
} from '~/vue_shared/components/filtered_search_bar/constants';
import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue';
import { LIST_KEY_CREATED_AT } from '~/packages_and_registries/package_registry/constants';
@@ -21,6 +24,14 @@ export default {
token: PackageTypeToken,
operators: OPERATORS_IS,
},
+ {
+ type: TOKEN_TYPE_VERSION,
+ icon: 'doc-versions',
+ title: TOKEN_TITLE_VERSION,
+ unique: true,
+ token: GlFilteredSearchToken,
+ operators: OPERATORS_IS,
+ },
],
components: {
LocalStorageSync,
@@ -57,6 +68,7 @@ export default {
const parsed = {
packageName: '',
packageType: undefined,
+ packageVersion: '',
};
return filters.reduce((acc, filter) => {
@@ -67,6 +79,13 @@ export default {
};
}
+ if (filter.type === TOKEN_TYPE_VERSION && filter.value?.data) {
+ return {
+ ...acc,
+ packageVersion: filter.value.data.trim(),
+ };
+ }
+
if (filter.type === FILTERED_SEARCH_TERM) {
return {
...acc,
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql
index f25f24cbc5f..77f09e7b76b 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql
+++ b/app/assets/javascripts/packages_and_registries/package_registry/graphql/queries/get_packages.query.graphql
@@ -9,6 +9,7 @@ query getPackages(
$groupSort: PackageGroupSort
$packageName: String
$packageType: PackageTypeEnum
+ $packageVersion: String
$first: Int
$last: Int
$after: String
@@ -20,6 +21,7 @@ query getPackages(
sort: $sort
packageName: $packageName
packageType: $packageType
+ packageVersion: $packageVersion
after: $after
before: $before
first: $first
@@ -43,6 +45,7 @@ query getPackages(
sort: $groupSort
packageName: $packageName
packageType: $packageType
+ packageVersion: $packageVersion
after: $after
before: $before
first: $first
diff --git a/app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue b/app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue
index 294c6baad1b..eb33c020f7d 100644
--- a/app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue
+++ b/app/assets/javascripts/packages_and_registries/package_registry/pages/list.vue
@@ -82,6 +82,7 @@ export default {
groupSort: this.isGroupPage ? this.sort : undefined,
packageName: this.filters?.packageName,
packageType: this.filters?.packageType,
+ packageVersion: this.filters?.packageVersion,
first: GRAPHQL_PAGE_SIZE,
...this.pageParams,
};
@@ -96,10 +97,10 @@ export default {
return this.packages?.count;
},
hasFilters() {
- return this.filters.packageName && this.filters.packageType;
+ return this.filters.packageName || this.filters.packageType || this.filters.packageVersion;
},
emptySearch() {
- return !this.filters.packageName && !this.filters.packageType;
+ return !this.filters.packageName && !this.filters.packageType && !this.filters.packageVersion;
},
emptyStateTitle() {
return this.emptySearch
diff --git a/app/assets/javascripts/packages_and_registries/shared/utils.js b/app/assets/javascripts/packages_and_registries/shared/utils.js
index a19c8ed5866..e7606936e6b 100644
--- a/app/assets/javascripts/packages_and_registries/shared/utils.js
+++ b/app/assets/javascripts/packages_and_registries/shared/utils.js
@@ -10,13 +10,16 @@ export const searchArrayToFilterTokens = (search) =>
search.map((s) => keyValueToFilterToken(FILTERED_SEARCH_TERM, s));
export const extractFilterAndSorting = (queryObject) => {
- const { type, search, sort, orderBy } = queryObject;
+ const { type, search, version, sort, orderBy } = queryObject;
const filters = [];
const sorting = {};
if (type) {
filters.push(keyValueToFilterToken('type', type));
}
+ if (version) {
+ filters.push(keyValueToFilterToken('version', version));
+ }
if (search) {
filters.push(...searchArrayToFilterTokens(search));
}
diff --git a/app/assets/javascripts/sidebar/components/sidebar_dropdown.vue b/app/assets/javascripts/sidebar/components/sidebar_dropdown.vue
index c9450244b40..5cc3c552bf8 100644
--- a/app/assets/javascripts/sidebar/components/sidebar_dropdown.vue
+++ b/app/assets/javascripts/sidebar/components/sidebar_dropdown.vue
@@ -87,6 +87,11 @@ export default {
return [WORKSPACE_GROUP, WORKSPACE_PROJECT].includes(value);
},
},
+ showWorkItemEpics: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
},
data() {
return {
@@ -115,6 +120,7 @@ export default {
fullPath: this.attrWorkspacePath,
state: this.issuableAttributesState[this.issuableAttribute],
sort: defaultEpicSort,
+ includeWorkItems: this.showWorkItemEpics,
};
if (epicIidPattern.test(this.searchTerm)) {
@@ -127,7 +133,12 @@ export default {
return variables;
},
- update: (data) => data?.workspace?.attributes?.nodes ?? [],
+ update(data) {
+ return [
+ ...(data?.workspace?.attributes?.nodes ?? []),
+ ...(data?.workspace?.workItems?.nodes ?? []),
+ ];
+ },
error(error) {
createAlert({ message: this.i18n.listFetchError, captureError: true, error });
},
@@ -188,7 +199,7 @@ export default {
this.skipQuery = false;
},
setFocus() {
- this.$refs.search.focusInput();
+ this.$refs?.search?.focusInput();
},
show() {
this.$refs.dropdown.show();
@@ -211,7 +222,12 @@ export default {
@show="handleShow"
@shown="setFocus"
>
- <gl-search-box-by-type ref="search" v-model="searchTerm" :placeholder="__('Search')" />
+ <gl-search-box-by-type
+ v-if="!showWorkItemEpics"
+ ref="search"
+ v-model="searchTerm"
+ :placeholder="__('Search')"
+ />
<gl-dropdown-item
:data-testid="`no-${formatIssuableAttribute.kebab}-item`"
is-check-item
diff --git a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
index 28b88a59405..0ecf89bd169 100644
--- a/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
+++ b/app/assets/javascripts/sidebar/components/sidebar_dropdown_widget.vue
@@ -3,10 +3,11 @@ import { GlButton, GlIcon, GlLink, GlPopover, GlTooltipDirective } from '@gitlab
import { kebabCase, snakeCase } from 'lodash';
import { createAlert } from '~/alert';
import { getIdFromGraphQLId } from '~/graphql_shared/utils';
-import { TYPE_EPIC, TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
+import { TYPE_ISSUE, TYPE_MERGE_REQUEST } from '~/issues/constants';
import { timeFor } from '~/lib/utils/datetime_utility';
import { __ } from '~/locale';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
+
import {
dropdowni18nText,
LocalizedIssuableAttributeType,
@@ -79,6 +80,21 @@ export default {
required: false,
default: undefined,
},
+ showWorkItemEpics: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ isEpicAttribute: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
+ issuableParent: {
+ type: Object,
+ required: false,
+ default: null,
+ },
},
apollo: {
issuable: {
@@ -98,7 +114,7 @@ export default {
return data.workspace?.issuable || {};
},
result({ data }) {
- if (this.glFeatures?.epicWidgetEditConfirmation && this.isEpic) {
+ if (this.glFeatures?.epicWidgetEditConfirmation && this.isEpicAttribute) {
this.hasCurrentAttribute = data?.workspace?.issuable.hasEpic;
}
},
@@ -140,6 +156,9 @@ export default {
},
computed: {
currentAttribute() {
+ if (this.isEpicAttribute && this.issuableParent?.attribute) {
+ return this.issuableParent.attribute;
+ }
return this.issuable.attribute;
},
issuableId() {
@@ -171,10 +190,6 @@ export default {
LocalizedIssuableAttributeType[IssuableAttributeTypeKeyMap[this.issuableAttribute]];
return dropdowni18nText(localizedAttribute, this.issuableType);
},
- isEpic() {
- // MV to EE https://gitlab.com/gitlab-org/gitlab/-/issues/345311
- return this.issuableAttribute === TYPE_EPIC;
- },
formatIssuableAttribute() {
return {
kebab: kebabCase(this.issuableAttribute),
@@ -186,7 +201,7 @@ export default {
return false;
}
- return this.isEpic && this.currentAttribute === null && this.hasCurrentAttribute
+ return this.isEpicAttribute && this.currentAttribute === null && this.hasCurrentAttribute
? !this.editConfirmation
: false;
},
@@ -195,46 +210,50 @@ export default {
},
},
methods: {
- updateAttribute({ id }) {
+ updateAttribute({ id, workItemType }) {
if (this.currentAttribute === null && id === null) return;
if (id === this.currentAttribute?.id) return;
- this.updating = true;
+ if (this.showWorkItemEpics && this.isEpicAttribute) {
+ this.$emit('updateAttribute', { id, workItemType });
+ } else {
+ this.updating = true;
- const { current } = this.issuableAttributeQuery;
- const { mutation } = current[this.issuableType];
+ const { current } = this.issuableAttributeQuery;
+ const { mutation } = current[this.issuableType];
- this.$apollo
- .mutate({
- mutation,
- variables: {
- fullPath: this.workspacePath,
- attributeId:
- this.issuableAttribute === IssuableAttributeType.Milestone &&
- this.issuableType === TYPE_ISSUE
- ? getIdFromGraphQLId(id)
- : id,
- iid: this.iid,
- },
- })
- .then(({ data }) => {
- if (data.issuableSetAttribute?.errors?.length) {
- createAlert({
- message: data.issuableSetAttribute.errors[0],
- captureError: true,
- error: data.issuableSetAttribute.errors[0],
- });
- } else {
- this.$emit('attribute-updated', data);
- }
- })
- .catch((error) => {
- createAlert({ message: this.i18n.updateError, captureError: true, error });
- })
- .finally(() => {
- this.updating = false;
- this.selectedTitle = null;
- });
+ this.$apollo
+ .mutate({
+ mutation,
+ variables: {
+ fullPath: this.workspacePath,
+ attributeId:
+ this.issuableAttribute === IssuableAttributeType.Milestone &&
+ this.issuableType === TYPE_ISSUE
+ ? getIdFromGraphQLId(id)
+ : id,
+ iid: this.iid,
+ },
+ })
+ .then(({ data }) => {
+ if (data.issuableSetAttribute?.errors?.length) {
+ createAlert({
+ message: data.issuableSetAttribute.errors[0],
+ captureError: true,
+ error: data.issuableSetAttribute.errors[0],
+ });
+ } else {
+ this.$emit('attribute-updated', data);
+ }
+ })
+ .catch((error) => {
+ createAlert({ message: this.i18n.updateError, captureError: true, error });
+ })
+ .finally(() => {
+ this.updating = false;
+ this.selectedTitle = null;
+ });
+ }
},
isAttributeOverdue(attribute) {
return this.issuableAttribute === IssuableAttributeType.Milestone
@@ -356,6 +375,7 @@ export default {
:current-attribute="currentAttribute"
:issuable-attribute="issuableAttribute"
:issuable-type="issuableType"
+ :show-work-item-epics="showWorkItemEpics"
@change="updateAttribute"
>
<template #list="{ attributesList, isAttributeChecked, updateAttribute: update }">
diff --git a/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js b/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js
index 952af4eeb5d..5362ceac9ee 100644
--- a/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js
+++ b/app/assets/javascripts/vue_shared/components/filtered_search_bar/constants.js
@@ -79,6 +79,7 @@ export const TOKEN_TITLE_STATUS = __('Status');
export const TOKEN_TITLE_JOBS_RUNNER_TYPE = s__('Job|Runner type');
export const TOKEN_TITLE_TARGET_BRANCH = __('Target Branch');
export const TOKEN_TITLE_TYPE = __('Type');
+export const TOKEN_TITLE_VERSION = __('Version');
export const TOKEN_TITLE_SEARCH_WITHIN = __('Search Within');
export const TOKEN_TITLE_CREATED = __('Created date');
export const TOKEN_TITLE_CLOSED = __('Closed date');
@@ -108,6 +109,7 @@ export const TOKEN_TYPE_STATUS = 'status';
export const TOKEN_TYPE_JOBS_RUNNER_TYPE = 'jobs-runner-type';
export const TOKEN_TYPE_TARGET_BRANCH = 'target-branch';
export const TOKEN_TYPE_TYPE = 'type';
+export const TOKEN_TYPE_VERSION = 'version';
export const TOKEN_TYPE_WEIGHT = 'weight';
export const TOKEN_TYPE_SEARCH_WITHIN = 'in';
export const TOKEN_TYPE_CREATED = 'created';
diff --git a/app/components/projects/ml/show_ml_model_component.rb b/app/components/projects/ml/show_ml_model_component.rb
index 26155df3e81..11a36a78b18 100644
--- a/app/components/projects/ml/show_ml_model_component.rb
+++ b/app/components/projects/ml/show_ml_model_component.rb
@@ -20,7 +20,8 @@ module Projects
path: model.path,
description: model.description,
latest_version: latest_version_view_model,
- version_count: model.version_count
+ version_count: model.version_count,
+ candidate_count: model.candidate_count
}
}
diff --git a/app/controllers/groups/boards_controller.rb b/app/controllers/groups/boards_controller.rb
index 6bb807be1c4..7cc0e6a8558 100644
--- a/app/controllers/groups/boards_controller.rb
+++ b/app/controllers/groups/boards_controller.rb
@@ -8,6 +8,7 @@ class Groups::BoardsController < Groups::ApplicationController
before_action do
push_frontend_feature_flag(:board_multi_select, group)
push_frontend_feature_flag(:apollo_boards, group)
+ push_frontend_feature_flag(:display_work_item_epic_issue_sidebar, group)
experiment(:prominent_create_board_btn, subject: current_user) do |e|
e.control {}
e.candidate {}
diff --git a/app/controllers/projects/boards_controller.rb b/app/controllers/projects/boards_controller.rb
index 84872d1e978..fd853b5aaed 100644
--- a/app/controllers/projects/boards_controller.rb
+++ b/app/controllers/projects/boards_controller.rb
@@ -8,6 +8,7 @@ class Projects::BoardsController < Projects::ApplicationController
before_action do
push_frontend_feature_flag(:board_multi_select, project)
push_frontend_feature_flag(:apollo_boards, project)
+ push_frontend_feature_flag(:display_work_item_epic_issue_sidebar, project)
experiment(:prominent_create_board_btn, subject: current_user) do |e|
e.control {}
e.candidate {}
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index a6444dc038c..d0eabf8d837 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -49,6 +49,7 @@ class Projects::IssuesController < Projects::ApplicationController
push_frontend_feature_flag(:service_desk_ticket)
push_frontend_feature_flag(:issues_list_drawer, project)
push_frontend_feature_flag(:linked_work_items, project)
+ push_frontend_feature_flag(:display_work_item_epic_issue_sidebar, project)
end
before_action only: [:index, :show] do
@@ -67,6 +68,7 @@ class Projects::IssuesController < Projects::ApplicationController
push_force_frontend_feature_flag(:work_items_mvc_2, project&.work_items_mvc_2_feature_flag_enabled?)
push_frontend_feature_flag(:epic_widget_edit_confirmation, project)
push_frontend_feature_flag(:moved_mr_sidebar, project)
+ push_frontend_feature_flag(:display_work_item_epic_issue_sidebar, project)
push_force_frontend_feature_flag(:linked_work_items, project.linked_work_items_feature_flag_enabled?)
push_frontend_feature_flag(:notifications_todos_buttons, current_user)
end
diff --git a/app/helpers/dropdowns_helper.rb b/app/helpers/dropdowns_helper.rb
index b6e0b2d6b20..97ca7dd7ed0 100644
--- a/app/helpers/dropdowns_helper.rb
+++ b/app/helpers/dropdowns_helper.rb
@@ -22,7 +22,7 @@ module DropdownsHelper
end
content_tag_options = { class: "dropdown-menu dropdown-select #{options[:dropdown_class] if options.key?(:dropdown_class)}" }
- content_tag_options[:data] = options[:dropdown_qa_selector] ? { qa_selector: (options[:dropdown_qa_selector]).to_s } : {}
+ content_tag_options[:data] ||= {}
content_tag_options[:data][:testid] = (options[:dropdown_testid]).to_s if options[:dropdown_testid]
dropdown_output << content_tag(:div, content_tag_options) do
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 7663055d3b0..c2014508f4f 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -88,7 +88,7 @@ module ProjectsHelper
link_to(author_html, user_path(author), class: inject_classes, data: data_attrs).html_safe
else
title = opts[:title].sub(":name", sanitize(author.name))
- link_to(author_html, user_path(author), class: inject_classes, title: title, data: { container: 'body', qa_selector: 'assignee_link' }).html_safe
+ link_to(author_html, user_path(author), class: inject_classes, title: title, data: { container: 'body' }).html_safe
end
end
diff --git a/app/helpers/sidebars_helper.rb b/app/helpers/sidebars_helper.rb
index 1670e628ffe..9933fa8e4d9 100644
--- a/app/helpers/sidebars_helper.rb
+++ b/app/helpers/sidebars_helper.rb
@@ -8,14 +8,6 @@ module SidebarsHelper
sidebar_attributes_for_object(object).fetch(:tracking_attrs, {})
end
- def sidebar_qa_selector(object)
- sidebar_attributes_for_object(object).fetch(:sidebar_qa_selector, nil)
- end
-
- def scope_qa_menu_item(object)
- sidebar_attributes_for_object(object).fetch(:scope_qa_menu_item, nil)
- end
-
def scope_avatar_classes(object)
%w[avatar-container rect-avatar s32].tap do |klasses|
klass = sidebar_attributes_for_object(object).fetch(:scope_avatar_class, nil)
@@ -267,8 +259,6 @@ module SidebarsHelper
def sidebar_project_attributes
{
tracking_attrs: sidebar_project_tracking_attrs,
- sidebar_qa_selector: 'project_sidebar',
- scope_qa_menu_item: 'Project scope',
scope_avatar_class: 'project_avatar'
}
end
@@ -276,8 +266,6 @@ module SidebarsHelper
def sidebar_group_attributes
{
tracking_attrs: sidebar_group_tracking_attrs,
- sidebar_qa_selector: 'group_sidebar',
- scope_qa_menu_item: 'Group scope',
scope_avatar_class: 'group_avatar'
}
end
diff --git a/app/helpers/ssh_keys_helper.rb b/app/helpers/ssh_keys_helper.rb
index d640e7ffba9..57bc873d446 100644
--- a/app/helpers/ssh_keys_helper.rb
+++ b/app/helpers/ssh_keys_helper.rb
@@ -29,7 +29,6 @@ module SshKeysHelper
{
path: path,
method: 'delete',
- qa_selector: 'revoke_ssh_key_button',
title: title,
aria_label: title,
modal_attributes: {
diff --git a/app/models/namespace_setting.rb b/app/models/namespace_setting.rb
index 13d2c5a62e2..0263942116d 100644
--- a/app/models/namespace_setting.rb
+++ b/app/models/namespace_setting.rb
@@ -6,6 +6,7 @@ class NamespaceSetting < ApplicationRecord
include ChronicDurationAttribute
cascading_attr :delayed_project_removal
+ cascading_attr :toggle_security_policy_custom_ci
belongs_to :namespace, inverse_of: :namespace_settings
diff --git a/app/presenters/ml/model_presenter.rb b/app/presenters/ml/model_presenter.rb
index 24d30af1d4e..77aba1991b6 100644
--- a/app/presenters/ml/model_presenter.rb
+++ b/app/presenters/ml/model_presenter.rb
@@ -14,6 +14,10 @@ module Ml
model.versions.size
end
+ def candidate_count
+ model.candidates.size
+ end
+
def latest_package_path
latest_version&.package_path
end
diff --git a/app/services/ml/create_model_service.rb b/app/services/ml/create_model_service.rb
index 5c179d8edf7..b87b13dd379 100644
--- a/app/services/ml/create_model_service.rb
+++ b/app/services/ml/create_model_service.rb
@@ -22,6 +22,12 @@ module Ml
add_metadata(model, @metadata)
+ Gitlab::InternalEvents.track_event(
+ 'model_registry_ml_model_created',
+ project: @project,
+ user: @user
+ )
+
model
end
end
diff --git a/app/services/ml/create_model_version_service.rb b/app/services/ml/create_model_version_service.rb
index e7a59210107..3b8c096b5b4 100644
--- a/app/services/ml/create_model_version_service.rb
+++ b/app/services/ml/create_model_version_service.rb
@@ -24,6 +24,12 @@ module Ml
{ model_version: model_version }
).execute
+ Gitlab::InternalEvents.track_event(
+ 'model_registry_ml_model_version_created',
+ project: @model.project,
+ user: @user
+ )
+
model_version
end
end
diff --git a/app/validators/kubernetes_container_resources_validator.rb b/app/validators/kubernetes_container_resources_validator.rb
new file mode 100644
index 00000000000..f261f8de27d
--- /dev/null
+++ b/app/validators/kubernetes_container_resources_validator.rb
@@ -0,0 +1,77 @@
+# frozen_string_literal: true
+
+# KubernetesPodContainerResourcesValidator
+#
+# Validates that value is a Kubernetes resource specifying cpu and memory.
+#
+# Example:
+#
+# class Group < ActiveRecord::Base
+# validates :resource, presence: true, kubernetes_pod_container_resources: true
+# end
+
+class KubernetesContainerResourcesValidator < ActiveModel::EachValidator # rubocop:disable Gitlab/NamespacedClass -- This is a globally shareable validator, but it's unclear what namespace it should belong in
+ # https://kubernetes.io/docs/tasks/configure-pod-container/assign-cpu-resource/#cpu-units
+ # The CPU resource is measured in CPU units. Fractional values are allowed. You can use the suffix m to mean milli.
+ # (\d+m|\d+(\.\d*)?): Two alternatives separated by |:
+ # \d+m: Matches positive whole numbers followed by "m".
+ # \d+(\.\d*)?: Matches positive decimal numbers.
+ CPU_UNITS = /^(\d+m|\d+(\.\d*)?)$/
+
+ # https://kubernetes.io/docs/tasks/configure-pod-container/assign-memory-resource/#memory-units
+ # The memory resource is measured in bytes. You can express memory as a plain integer or a fixed-point integer
+ # with one of these suffixes: E, P, T, G, M, K, Ei, Pi, Ti, Gi, Mi, Ki.
+ # \d+(\.\d*)?: Matches positive decimal numbers.
+ # ([EPTGMK]|[EPTGMK][i])?: Optional suffix part, where:
+ # [EPTGMK]: Matches a single character from the set E, P, T, G, M, K.
+ # [EPTGMK]i: Matches characters from the set followed by an "i".
+ MEMORY_UNITS = /^\d+(\.\d*)?([EPTGMK]|[EPTGMK]i)?$/
+
+ def validate_each(record, attribute, value)
+ unless value.is_a?(Hash)
+ record.errors.add(attribute, _("must be a hash"))
+ return
+ end
+
+ if value == {}
+ record.errors.add(
+ attribute,
+ _("must be a hash containing 'cpu' and 'memory' attribute of type string")
+ )
+ return
+ end
+
+ cpu = value.deep_symbolize_keys.fetch(:cpu, nil)
+ unless cpu.is_a?(String)
+ record.errors.add(
+ attribute,
+ format(_("'cpu: %{cpu}' must be a string"), cpu: cpu)
+ )
+ end
+
+ if cpu.is_a?(String) && !CPU_UNITS.match?(cpu)
+ record.errors.add(
+ attribute,
+ format(_("'cpu: %{cpu}' must match the regex '%{cpu_regex}'"), cpu: cpu, cpu_regex: CPU_UNITS.source)
+ )
+ end
+
+ memory = value.deep_symbolize_keys.fetch(:memory, nil)
+ unless memory.is_a?(String)
+ record.errors.add(
+ attribute,
+ format(_("'memory: %{memory}' must be a string"), memory: memory)
+ )
+ end
+
+ if memory.is_a?(String) && !MEMORY_UNITS.match?(memory) # rubocop:disable Style/GuardClause -- Easier to read this way
+ record.errors.add(
+ attribute,
+ format(_("'memory: %{memory}' must match the regex '%{memory_regex}'"),
+ memory: memory,
+ memory_regex: MEMORY_UNITS.source
+ )
+ )
+ end
+ end
+end
diff --git a/app/views/groups/_home_panel.html.haml b/app/views/groups/_home_panel.html.haml
index 52158f81c4f..0cc15ef2de3 100644
--- a/app/views/groups/_home_panel.html.haml
+++ b/app/views/groups/_home_panel.html.haml
@@ -20,7 +20,7 @@
.js-vue-notification-dropdown{ data: { disabled: emails_disabled.to_s, dropdown_items: notification_dropdown_items(@notification_setting).to_json, notification_level: @notification_setting.level, help_page_path: help_page_path('user/profile/notifications'), group_id: @group.id, container_class: 'gl-vertical-align-top', no_flip: 'true' } }
- if can_create_subgroups
.gl-sm-w-auto.gl-w-full
- = render Pajamas::ButtonComponent.new(href: new_group_path(parent_id: @group.id, anchor: 'create-group-pane'), button_options: { data: { qa_selector: 'new_subgroup_button' }, class: 'gl-sm-w-auto gl-w-full'}) do
+ = render Pajamas::ButtonComponent.new(href: new_group_path(parent_id: @group.id, anchor: 'create-group-pane'), button_options: { data: { testid: 'new-subgroup-button' }, class: 'gl-sm-w-auto gl-w-full'}) do
= _("New subgroup")
- if can_create_projects
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index b2ea15d0e47..5c09fdcd021 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -17,7 +17,7 @@
.settings-content
= render 'groups/settings/general'
-%section.settings.gs-permissions.no-animate#js-permissions-settings{ class: ('expanded' if expanded), data: { qa_selector: 'permission_lfs_2fa_content', testid: 'permissions-settings' } }
+%section.settings.gs-permissions.no-animate#js-permissions-settings{ class: ('expanded' if expanded), data: { testid: 'permissions-settings' } }
.settings-header
%h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only{ role: 'button' }
= _('Permissions and group features')
diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml
index e9ec0a9b6b0..050c5135344 100644
--- a/app/views/groups/projects.html.haml
+++ b/app/views/groups/projects.html.haml
@@ -28,7 +28,7 @@
- if project.namespace
= project.namespace.human_name
\/
- %span.project-name{ data: { qa_selector: 'project_name_content', qa_project_name: project.name } }
+ %span.project-name
= project.name
= visibility_level_content(project, css_class: 'visibility-icon gl-text-secondary gl-ml-2', icon_css_class: 'icon')
diff --git a/app/views/groups/settings/_export.html.haml b/app/views/groups/settings/_export.html.haml
index 059426fd596..ff1d76f470c 100644
--- a/app/views/groups/settings/_export.html.haml
+++ b/app/views/groups/settings/_export.html.haml
@@ -29,7 +29,7 @@
%li= _('Runner tokens')
%li= _('SAML discovery tokens')
- if group.export_file_exists?
- = render Pajamas::ButtonComponent.new(href: download_export_group_path(group), button_options: { rel: 'nofollow', data: { method: :get, qa_selector: 'download_export_link' } }) do
+ = render Pajamas::ButtonComponent.new(href: download_export_group_path(group), button_options: { rel: 'nofollow', data: { method: :get } }) do
= _('Download export')
= render Pajamas::ButtonComponent.new(href: export_group_path(group), button_options: { data: { method: :post } }) do
= _('Regenerate export')
diff --git a/app/views/groups/settings/_general.html.haml b/app/views/groups/settings/_general.html.haml
index e02a61243f7..56ee2af1562 100644
--- a/app/views/groups/settings/_general.html.haml
+++ b/app/views/groups/settings/_general.html.haml
@@ -6,7 +6,7 @@
.row
.form-group.col-md-5
= f.label :name, s_('Groups|Group name'), class: 'label-bold'
- = f.text_field :name, class: 'form-control', data: { qa_selector: 'group_name_field' }
+ = f.text_field :name, class: 'form-control', data: { testid: 'group-name-field' }
.text-muted
= s_('Groups|Must start with letter, digit, emoji, or underscore. Can also contain periods, dashes, spaces, and parentheses.')
@@ -36,4 +36,4 @@
= link_button_to s_('Groups|Remove avatar'), group_avatar_path(@group.to_param), aria: { label: s_('Groups|Remove avatar') }, data: { confirm: s_('Groups|Avatar will be removed. Are you sure?'), 'confirm-btn-variant': 'danger' }, method: :delete, variant: :danger, category: :secondary
.form-group.gl-form-group
= render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group
- = f.submit s_('Groups|Save changes'), pajamas_button: true, class: 'js-dirty-submit', data: { qa_selector: 'save_name_visibility_settings_button' }
+ = f.submit s_('Groups|Save changes'), pajamas_button: true, class: 'js-dirty-submit', data: { testid: 'save-name-visibility-settings-button' }
diff --git a/app/views/groups/settings/_lfs.html.haml b/app/views/groups/settings/_lfs.html.haml
index 74f9298133b..81d64d983a0 100644
--- a/app/views/groups/settings/_lfs.html.haml
+++ b/app/views/groups/settings/_lfs.html.haml
@@ -9,4 +9,4 @@
= f.gitlab_ui_checkbox_component :lfs_enabled,
_('Projects in this group can use Git LFS'),
help_text: _('Possible to override in each project.'),
- checkbox_options: { checked: @group.lfs_enabled?, data: { qa_selector: 'lfs_checkbox' } }
+ checkbox_options: { checked: @group.lfs_enabled?, data: { testid: 'lfs-checkbox' } }
diff --git a/app/views/groups/settings/_permissions.html.haml b/app/views/groups/settings/_permissions.html.haml
index b1fa63dbf56..4334c4996f2 100644
--- a/app/views/groups/settings/_permissions.html.haml
+++ b/app/views/groups/settings/_permissions.html.haml
@@ -48,6 +48,7 @@
= render 'groups/settings/two_factor_auth', f: f, group: @group
= render_if_exists 'groups/personal_access_token_expiration_policy', f: f, group: @group
= render 'groups/settings/membership', f: f, group: @group
+ = render_if_exists 'groups/settings/security_policies_custom_ci', f: f, group: @group
%h5= _('Customer relations')
.form-group.gl-mb-3
@@ -56,4 +57,4 @@
checkbox_options: { checked: @group.crm_enabled? },
help_text: s_('GroupSettings|Organizations and contacts can be created and associated with issues.')
- = f.submit _('Save changes'), pajamas_button: true, class: 'gl-mt-3 js-dirty-submit', data: { qa_selector: 'save_permissions_changes_button' }
+ = f.submit _('Save changes'), pajamas_button: true, class: 'gl-mt-3 js-dirty-submit', data: { testid: 'save-permissions-changes-button' }
diff --git a/app/views/groups/settings/_project_creation_level.html.haml b/app/views/groups/settings/_project_creation_level.html.haml
index ef535b8a21c..e0c62d3b800 100644
--- a/app/views/groups/settings/_project_creation_level.html.haml
+++ b/app/views/groups/settings/_project_creation_level.html.haml
@@ -1,3 +1,3 @@
.form-group
= f.label s_('ProjectCreationLevel|Roles allowed to create projects'), class: 'label-bold'
- = f.select :project_creation_level, options_for_select(::Gitlab::Access.project_creation_options, group.project_creation_level), {}, class: 'form-control', data: { qa_selector: 'project_creation_level_dropdown' }
+ = f.select :project_creation_level, options_for_select(::Gitlab::Access.project_creation_options, group.project_creation_level), {}, class: 'form-control', data: { testid: 'project-creation-level-dropdown' }
diff --git a/app/views/groups/settings/_two_factor_auth.html.haml b/app/views/groups/settings/_two_factor_auth.html.haml
index 03813f6f8a2..cf44f2b69b1 100644
--- a/app/views/groups/settings/_two_factor_auth.html.haml
+++ b/app/views/groups/settings/_two_factor_auth.html.haml
@@ -9,7 +9,7 @@
.form-group
= f.gitlab_ui_checkbox_component :require_two_factor_authentication,
_('All users in this group must set up two-factor authentication'),
- checkbox_options: { data: { qa_selector: 'require_2fa_checkbox' } }
+ checkbox_options: { data: { testid: 'require-2fa-checkbox' } }
.form-group
= f.label :two_factor_grace_period, _('Delay 2FA enforcement (hours)')
= f.text_field :two_factor_grace_period, class: 'form-control form-control-sm w-auto gl-form-input gl-mb-3'
diff --git a/app/views/shared/_allow_request_access.html.haml b/app/views/shared/_allow_request_access.html.haml
index ab5f2fb1772..c7df9354b11 100644
--- a/app/views/shared/_allow_request_access.html.haml
+++ b/app/views/shared/_allow_request_access.html.haml
@@ -1,3 +1,3 @@
= form.gitlab_ui_checkbox_component :request_access_enabled,
_('Users can request access (if visibility is public or internal)'),
- checkbox_options: { data: { qa_selector: 'request_access_checkbox' } }
+ checkbox_options: { data: { testid: 'request-access-checkbox' } }
diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml
index efb6d59a66d..9477d36c9b9 100644
--- a/app/views/shared/issuable/_sidebar.html.haml
+++ b/app/views/shared/issuable/_sidebar.html.haml
@@ -51,7 +51,7 @@
- if in_group_context_with_iterations
.block.gl-collapse-empty{ data: { testid: 'iteration-container' } }<
- = render_if_exists 'shared/issuable/iteration_select', can_edit: can_edit_issuable.to_s, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type
+ = render_if_exists 'shared/issuable/iteration_select', can_edit: can_edit_issuable.to_s, group_path: @project.group.full_path, project_path: issuable_sidebar[:project_full_path], issue_iid: issuable_sidebar[:iid], issuable_type: issuable_type, issue_id: issuable_sidebar[:id]
- if issuable_sidebar[:show_crm_contacts]
.block.contact
diff --git a/app/workers/abuse/trust_score_worker.rb b/app/workers/abuse/trust_score_worker.rb
new file mode 100644
index 00000000000..061042ffa8a
--- /dev/null
+++ b/app/workers/abuse/trust_score_worker.rb
@@ -0,0 +1,23 @@
+# frozen_string_literal: true
+
+module Abuse
+ class TrustScoreWorker
+ include ApplicationWorker
+
+ data_consistency :delayed
+
+ idempotent!
+ feature_category :instance_resiliency
+ urgency :low
+
+ def perform(user_id, source, score, correlation_id = '')
+ user = User.find_by_id(user_id)
+ unless user
+ logger.info(structured_payload(message: "User not found.", user_id: user_id))
+ return
+ end
+
+ Abuse::TrustScore.create!(user: user, source: source, score: score.to_f, correlation_id_value: correlation_id)
+ end
+ end
+end
diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml
index 63cc855e4bd..ec5156bb1d0 100644
--- a/app/workers/all_queues.yml
+++ b/app/workers/all_queues.yml
@@ -2316,6 +2316,15 @@
:weight: 1
:idempotent: true
:tags: []
+- :name: abuse_trust_score
+ :worker_name: Abuse::TrustScoreWorker
+ :feature_category: :instance_resiliency
+ :has_external_dependencies: false
+ :urgency: :low
+ :resource_boundary: :unknown
+ :weight: 1
+ :idempotent: true
+ :tags: []
- :name: analytics_usage_trends_counter_job
:worker_name: Analytics::UsageTrends::CounterJobWorker
:feature_category: :devops_reports
diff --git a/config/events/k8s_api_proxy_requests_unique_users_via_ci_access.yml b/config/events/k8s_api_proxy_requests_unique_users_via_ci_access.yml
index e1c7dc1bb28..78dae78fc30 100644
--- a/config/events/k8s_api_proxy_requests_unique_users_via_ci_access.yml
+++ b/config/events/k8s_api_proxy_requests_unique_users_via_ci_access.yml
@@ -8,6 +8,8 @@ value_description:
extra_properties:
identifiers:
- user
+- project
+- namespace
product_section: ops
product_stage: deploy
product_group: environments
diff --git a/config/events/k8s_api_proxy_requests_unique_users_via_pat_access.yml b/config/events/k8s_api_proxy_requests_unique_users_via_pat_access.yml
index d9b19bbfc1d..572758f9f44 100644
--- a/config/events/k8s_api_proxy_requests_unique_users_via_pat_access.yml
+++ b/config/events/k8s_api_proxy_requests_unique_users_via_pat_access.yml
@@ -8,6 +8,8 @@ value_description:
extra_properties:
identifiers:
- user
+- project
+- namespace
product_section: ops
product_stage: deploy
product_group: environments
diff --git a/config/events/k8s_api_proxy_requests_unique_users_via_user_access.yml b/config/events/k8s_api_proxy_requests_unique_users_via_user_access.yml
index 4dc5d4f7d4b..3cd54b0d1c3 100644
--- a/config/events/k8s_api_proxy_requests_unique_users_via_user_access.yml
+++ b/config/events/k8s_api_proxy_requests_unique_users_via_user_access.yml
@@ -8,6 +8,8 @@ value_description:
extra_properties:
identifiers:
- user
+- project
+- namespace
product_section: ops
product_stage: deploy
product_group: environments
diff --git a/config/events/ml_model_created.yml b/config/events/ml_model_created.yml
new file mode 100644
index 00000000000..ba9a4103956
--- /dev/null
+++ b/config/events/ml_model_created.yml
@@ -0,0 +1,24 @@
+---
+description: Tracks the creation of Machine learning models (Ml::Model) through Ml::CreateModelService
+category: InternalEventTracking
+action: model_registry_ml_model_created
+label_description:
+property_description:
+value_description:
+extra_properties:
+identifiers:
+- project
+- user
+- namespace
+product_section: data-science
+product_stage: modelops
+product_group: mlops
+milestone: "16.8"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/139798
+distributions:
+- ce
+- ee
+tiers:
+- free
+- premium
+- ultimate
diff --git a/config/events/ml_model_version_created.yml b/config/events/ml_model_version_created.yml
new file mode 100644
index 00000000000..655ea812aea
--- /dev/null
+++ b/config/events/ml_model_version_created.yml
@@ -0,0 +1,24 @@
+---
+description: Tracks the creation of Machine learning models versions (Ml::ModelVersion) through Ml::CreateModelVersionService
+category: InternalEventTracking
+action: model_registry_ml_model_version_created
+label_description:
+property_description:
+value_description:
+extra_properties:
+identifiers:
+- project
+- user
+- namespace
+product_section: data-science
+product_stage: modelops
+product_group: mlops
+milestone: "16.8"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/139798
+distributions:
+- ce
+- ee
+tiers:
+- free
+- premium
+- ultimate
diff --git a/config/feature_flags/development/display_work_item_epic_issue_sidebar.yml b/config/feature_flags/development/display_work_item_epic_issue_sidebar.yml
new file mode 100644
index 00000000000..b981895228a
--- /dev/null
+++ b/config/feature_flags/development/display_work_item_epic_issue_sidebar.yml
@@ -0,0 +1,8 @@
+---
+name: display_work_item_epic_issue_sidebar
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/135480
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/430337
+milestone: '16.8'
+type: development
+group: group::product planning
+default_enabled: false
diff --git a/config/feature_flags/development/exempt_paid_namespace_members_and_enterprise_users_from_identity_verification.yml b/config/feature_flags/development/exempt_paid_namespace_members_and_enterprise_users_from_identity_verification.yml
new file mode 100644
index 00000000000..497f8004b48
--- /dev/null
+++ b/config/feature_flags/development/exempt_paid_namespace_members_and_enterprise_users_from_identity_verification.yml
@@ -0,0 +1,8 @@
+---
+name: exempt_paid_namespace_members_and_enterprise_users_from_identity_verification
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/139101
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/434810
+milestone: '16.7'
+type: development
+group: group::anti-abuse
+default_enabled: false
diff --git a/config/metrics/counts_28d/count_total_model_registry_ml_model_created_28d.yml b/config/metrics/counts_28d/count_total_model_registry_ml_model_created_28d.yml
new file mode 100644
index 00000000000..1bf0b9df0c7
--- /dev/null
+++ b/config/metrics/counts_28d/count_total_model_registry_ml_model_created_28d.yml
@@ -0,0 +1,26 @@
+---
+key_path: count_total_model_registry_ml_model_created_28d
+description: Tracks the creation of Machine learning models (Ml::Model) through Ml::CreateModelService in the last 28 days.
+product_section: data-science
+product_stage: modelops
+product_group: mlops
+performance_indicator_type: []
+value_type: number
+status: active
+milestone: "16.8"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/139798
+time_frame: 28d
+data_source: internal_events
+data_category: optional
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
+options:
+ events:
+ - model_registry_ml_model_created
+events:
+ - name: model_registry_ml_model_created
diff --git a/config/metrics/counts_28d/count_total_model_registry_ml_model_version_created_28d.yml b/config/metrics/counts_28d/count_total_model_registry_ml_model_version_created_28d.yml
new file mode 100644
index 00000000000..500f9f118c4
--- /dev/null
+++ b/config/metrics/counts_28d/count_total_model_registry_ml_model_version_created_28d.yml
@@ -0,0 +1,26 @@
+---
+key_path: count_total_model_registry_ml_model_version_created_28d
+description: Tracks the creation of Machine learning models versions (Ml::ModelVersion) through Ml::CreateModelVersionService in the last 28 days.
+product_section: data-science
+product_stage: modelops
+product_group: mlops
+performance_indicator_type: []
+value_type: number
+status: active
+milestone: "16.8"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/139798
+time_frame: 28d
+data_source: internal_events
+data_category: optional
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
+options:
+ events:
+ - model_registry_ml_model_version_created
+events:
+ - name: model_registry_ml_model_version_created
diff --git a/config/metrics/counts_7d/count_total_code_suggestions_authenticate_7d.yml b/config/metrics/counts_7d/count_total_code_suggestions_authenticate_7d.yml
index e7a5ada2e4e..285edb002a1 100644
--- a/config/metrics/counts_7d/count_total_code_suggestions_authenticate_7d.yml
+++ b/config/metrics/counts_7d/count_total_code_suggestions_authenticate_7d.yml
@@ -7,7 +7,7 @@ product_group: code_creation
performance_indicator_type: []
value_type: number
status: active
-milestone: "16.7"
+milestone: "16.8"
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/138848
time_frame: 7d
data_source: internal_events
diff --git a/config/metrics/counts_7d/count_total_model_registry_ml_model_created_7d.yml b/config/metrics/counts_7d/count_total_model_registry_ml_model_created_7d.yml
new file mode 100644
index 00000000000..ce42cf61654
--- /dev/null
+++ b/config/metrics/counts_7d/count_total_model_registry_ml_model_created_7d.yml
@@ -0,0 +1,26 @@
+---
+key_path: count_total_model_registry_ml_model_created_7d
+description: Tracks the creation of Machine learning models (Ml::Model) through Ml::CreateModelService in the last 7 days.
+product_section: data-science
+product_stage: modelops
+product_group: mlops
+performance_indicator_type: []
+value_type: number
+status: active
+milestone: "16.8"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/139798
+time_frame: 7d
+data_source: internal_events
+data_category: optional
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
+options:
+ events:
+ - model_registry_ml_model_created
+events:
+ - name: model_registry_ml_model_created
diff --git a/config/metrics/counts_7d/count_total_model_registry_ml_model_version_created_7d.yml b/config/metrics/counts_7d/count_total_model_registry_ml_model_version_created_7d.yml
new file mode 100644
index 00000000000..e4bd8488cbf
--- /dev/null
+++ b/config/metrics/counts_7d/count_total_model_registry_ml_model_version_created_7d.yml
@@ -0,0 +1,26 @@
+---
+key_path: count_total_model_registry_ml_model_version_created_7d
+description: Tracks the creation of Machine learning models versions (Ml::ModelVersion) through Ml::CreateModelVersionService in the last 7 days.
+product_section: data-science
+product_stage: modelops
+product_group: mlops
+performance_indicator_type: []
+value_type: number
+status: active
+milestone: "16.8"
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/139798
+time_frame: 7d
+data_source: internal_events
+data_category: optional
+distribution:
+- ce
+- ee
+tier:
+- free
+- premium
+- ultimate
+options:
+ events:
+ - model_registry_ml_model_version_created
+events:
+ - name: model_registry_ml_model_version_created
diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml
index 92f034ba7e9..65320c3c8ae 100644
--- a/config/sidekiq_queues.yml
+++ b/config/sidekiq_queues.yml
@@ -27,6 +27,8 @@
- 1
- - abuse_spam_abuse_events
- 1
+- - abuse_trust_score
+ - 1
- - activity_pub
- 1
- - adjourned_project_deletion
diff --git a/db/migrate/20231129124754_add_cascading_toggle_security_policy_custom_ci_setting.rb b/db/migrate/20231129124754_add_cascading_toggle_security_policy_custom_ci_setting.rb
new file mode 100644
index 00000000000..0a97d500e3d
--- /dev/null
+++ b/db/migrate/20231129124754_add_cascading_toggle_security_policy_custom_ci_setting.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+class AddCascadingToggleSecurityPolicyCustomCiSetting < Gitlab::Database::Migration[2.2]
+ milestone '16.8'
+
+ include Gitlab::Database::MigrationHelpers::CascadingNamespaceSettings
+
+ enable_lock_retries!
+
+ def up
+ add_cascading_namespace_setting :toggle_security_policy_custom_ci, :boolean, default: false, null: false
+ end
+
+ def down
+ remove_cascading_namespace_setting :toggle_security_policy_custom_ci
+ end
+end
diff --git a/db/post_migrate/20231218062442_remove_max_workspaces_from_remote_development_agent_configs.rb b/db/post_migrate/20231218062442_remove_max_workspaces_from_remote_development_agent_configs.rb
new file mode 100644
index 00000000000..523aae9f214
--- /dev/null
+++ b/db/post_migrate/20231218062442_remove_max_workspaces_from_remote_development_agent_configs.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class RemoveMaxWorkspacesFromRemoteDevelopmentAgentConfigs < Gitlab::Database::Migration[2.2]
+ milestone '16.8'
+ enable_lock_retries!
+
+ def change
+ remove_column :remote_development_agent_configs, :max_workspaces, :bigint, default: -1, null: false
+ end
+end
diff --git a/db/post_migrate/20231218062505_remove_max_workspaces_per_user_from_remote_development_agent_configs.rb b/db/post_migrate/20231218062505_remove_max_workspaces_per_user_from_remote_development_agent_configs.rb
new file mode 100644
index 00000000000..10a5e314ef9
--- /dev/null
+++ b/db/post_migrate/20231218062505_remove_max_workspaces_per_user_from_remote_development_agent_configs.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+class RemoveMaxWorkspacesPerUserFromRemoteDevelopmentAgentConfigs < Gitlab::Database::Migration[2.2]
+ milestone '16.8'
+ enable_lock_retries!
+
+ def change
+ remove_column :remote_development_agent_configs, :max_workspaces_per_user, :bigint, default: -1, null: false
+ end
+end
diff --git a/db/schema_migrations/20231129124754 b/db/schema_migrations/20231129124754
new file mode 100644
index 00000000000..7a95a77c47b
--- /dev/null
+++ b/db/schema_migrations/20231129124754
@@ -0,0 +1 @@
+29c39f7290a075ead472b5b5d41e60160073d5d49f05ae2b281e48a123990dfc \ No newline at end of file
diff --git a/db/schema_migrations/20231218062442 b/db/schema_migrations/20231218062442
new file mode 100644
index 00000000000..0db2337f7b2
--- /dev/null
+++ b/db/schema_migrations/20231218062442
@@ -0,0 +1 @@
+90b8a5342c57f8383b20684774ee5f7a551be4e93dcdf6d17bb2c2490fcd5214 \ No newline at end of file
diff --git a/db/schema_migrations/20231218062505 b/db/schema_migrations/20231218062505
new file mode 100644
index 00000000000..c047198bcb3
--- /dev/null
+++ b/db/schema_migrations/20231218062505
@@ -0,0 +1 @@
+814dc93e655e9f4abb2af348b67069f2b747300614e8251346bf252477cf3dbe \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 70fd8f4e3b4..b429b3ab4f1 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -12282,6 +12282,8 @@ CREATE TABLE application_settings (
security_txt_content text,
encrypted_arkose_labs_data_exchange_key bytea,
encrypted_arkose_labs_data_exchange_key_iv bytea,
+ toggle_security_policy_custom_ci boolean DEFAULT false NOT NULL,
+ lock_toggle_security_policy_custom_ci boolean DEFAULT false NOT NULL,
CONSTRAINT app_settings_container_reg_cleanup_tags_max_list_size_positive CHECK ((container_registry_cleanup_tags_service_max_list_size >= 0)),
CONSTRAINT app_settings_container_registry_pre_import_tags_rate_positive CHECK ((container_registry_pre_import_tags_rate >= (0)::numeric)),
CONSTRAINT app_settings_dep_proxy_ttl_policies_worker_capacity_positive CHECK ((dependency_proxy_ttl_group_policy_worker_capacity >= 0)),
@@ -19608,6 +19610,8 @@ CREATE TABLE namespace_settings (
product_analytics_enabled boolean DEFAULT false NOT NULL,
allow_merge_without_pipeline boolean DEFAULT false NOT NULL,
enforce_ssh_certificates boolean DEFAULT false NOT NULL,
+ toggle_security_policy_custom_ci boolean,
+ lock_toggle_security_policy_custom_ci boolean DEFAULT false NOT NULL,
CONSTRAINT check_0ba93c78c7 CHECK ((char_length(default_branch_name) <= 255)),
CONSTRAINT namespace_settings_unique_project_download_limit_alertlist_size CHECK ((cardinality(unique_project_download_limit_alertlist) <= 100)),
CONSTRAINT namespace_settings_unique_project_download_limit_allowlist_size CHECK ((cardinality(unique_project_download_limit_allowlist) <= 100))
@@ -22774,8 +22778,6 @@ CREATE TABLE remote_development_agent_configs (
network_policy_egress jsonb DEFAULT '[{"allow": "0.0.0.0/0", "except": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"]}]'::jsonb NOT NULL,
default_resources_per_workspace_container jsonb DEFAULT '{}'::jsonb NOT NULL,
max_resources_per_workspace jsonb DEFAULT '{}'::jsonb NOT NULL,
- max_workspaces bigint DEFAULT '-1'::integer NOT NULL,
- max_workspaces_per_user bigint DEFAULT '-1'::integer NOT NULL,
workspaces_quota bigint DEFAULT '-1'::integer NOT NULL,
workspaces_per_user_quota bigint DEFAULT '-1'::integer NOT NULL,
CONSTRAINT check_72947a4495 CHECK ((char_length(gitlab_workspaces_proxy_namespace) <= 63)),
diff --git a/doc/administration/environment_variables.md b/doc/administration/environment_variables.md
index 6683f4702ad..c3bc4b0bc1f 100644
--- a/doc/administration/environment_variables.md
+++ b/doc/administration/environment_variables.md
@@ -39,6 +39,7 @@ You can use the following environment variables to override certain values:
| `GITLAB_RAILS_CACHE_DEFAULT_TTL_SECONDS` | integer | The default TTL used for entries stored in the Rails-cache. Default is `28800`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/95042) in 15.3. |
| `GITLAB_CI_CONFIG_FETCH_TIMEOUT_SECONDS` | integer | Timeout for resolving remote includes in CI config in seconds. Must be between `0` and `60`. Default is `30`. [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/116383) in 15.11. |
| `GITLAB_LFS_MAX_OID_TO_FETCH` | integer | Sets the maximum number of LFS objects to link. Default is `100,000`. |
+| `SIDEKIQ_SEMI_RELIABLE_FETCH_TIMEOUT` | integer | Sets the timeout for Sidekiq semi-reliable fetch. Default is `5`. [Before GitLab 16.7](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/139583), default was `3`. If you experience high Redis CPU consumption on GitLab 16.6 and earlier, or if you have customized this variable, you should update this variable to `5`. |
## Adding more variables
diff --git a/doc/administration/housekeeping.md b/doc/administration/housekeeping.md
index a0ef6a66495..7ee29035de4 100644
--- a/doc/administration/housekeeping.md
+++ b/doc/administration/housekeeping.md
@@ -109,7 +109,7 @@ housekeeping tasks. The manual trigger can be useful when either:
To trigger housekeeping tasks manually:
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Settings > General**.
+1. Select **Settings > General**.
1. Expand **Advanced**.
1. Select **Run housekeeping**.
@@ -136,7 +136,7 @@ reduce the likelihood of such race conditions.
To trigger a manual prune of unreachable objects:
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Settings > General**.
+1. Select **Settings > General**.
1. Expand **Advanced**.
1. Select **Run housekeeping**.
1. Wait 30 minutes for the operation to complete.
diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md
index c73de881470..0594c4b93bd 100644
--- a/doc/administration/pages/index.md
+++ b/doc/administration/pages/index.md
@@ -842,7 +842,7 @@ Prerequisites:
To set the maximum size of each GitLab Pages site in a group, overriding the inherited setting:
1. On the left sidebar, select **Search or go to** and find your group.
-1. On the left sidebar, select **Settings > General**.
+1. Select **Settings > General**.
1. Expand **Pages**.
1. Enter a value under **Maximum size** in MB.
1. Select **Save changes**.
@@ -856,7 +856,7 @@ Prerequisites:
To set the maximum size of GitLab Pages site in a project, overriding the inherited setting:
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Deploy > Pages**.
+1. Select **Deploy > Pages**.
1. In **Maximum size of pages**, enter the size in MB.
1. Select **Save changes**.
diff --git a/doc/administration/redis/troubleshooting.md b/doc/administration/redis/troubleshooting.md
index db4a8852248..0dcb19c1999 100644
--- a/doc/administration/redis/troubleshooting.md
+++ b/doc/administration/redis/troubleshooting.md
@@ -77,6 +77,11 @@ repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
```
+## High CPU usage on Redis instance
+
+High CPU usage on Redis instance can be cause by Sidekiq `BRPOP` calls. The `BRPOP` command is expensive and increases CPU usage on Redis.
+Increase the [`SIDEKIQ_SEMI_RELIABLE_FETCH_TIMEOUT` environment variable](../environment_variables.md) to improve CPU usage on Redis.
+
## Troubleshooting Sentinel
If you get an error like: `Redis::CannotConnectError: No sentinels available.`,
diff --git a/doc/administration/sidekiq/sidekiq_troubleshooting.md b/doc/administration/sidekiq/sidekiq_troubleshooting.md
index a229eca7ff4..591d1e5f64d 100644
--- a/doc/administration/sidekiq/sidekiq_troubleshooting.md
+++ b/doc/administration/sidekiq/sidekiq_troubleshooting.md
@@ -641,6 +641,11 @@ indicate that additional Sidekiq processes would be beneficial.
Consider [adding additional Sidekiq processes](extra_sidekiq_processes.md)
to compensate for removing the `sidekiq-cluster` service.
+## CPU saturation in Redis caused by Sidekiq BRPOP calls
+
+Sidekiq `BROP` calls can cause CPU usage to increase on Redis.
+Increase the [`SIDEKIQ_SEMI_RELIABLE_FETCH_TIMEOUT` environment variable](../environment_variables.md) to improve CPU usage on Redis.
+
## Related topics
- [Elasticsearch workers overload Sidekiq](../../integration/advanced_search/elasticsearch_troubleshooting.md#elasticsearch-workers-overload-sidekiq).
diff --git a/doc/ci/caching/index.md b/doc/ci/caching/index.md
index d332af418e5..55f18987490 100644
--- a/doc/ci/caching/index.md
+++ b/doc/ci/caching/index.md
@@ -633,7 +633,7 @@ The next time the pipeline runs, the cache is stored in a different location.
You can clear the cache in the GitLab UI:
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Build > Pipelines**.
+1. Select **Build > Pipelines**.
1. In the upper-right corner, select **Clear runner caches**.
On the next commit, your CI/CD jobs use a new cache.
diff --git a/doc/ci/pipelines/schedules.md b/doc/ci/pipelines/schedules.md
index 2b85ba495b0..f83868952f5 100644
--- a/doc/ci/pipelines/schedules.md
+++ b/doc/ci/pipelines/schedules.md
@@ -60,7 +60,7 @@ To trigger a pipeline schedule manually, so that it runs immediately instead of
the next scheduled time:
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Build > Pipeline schedules**.
+1. Select **Build > Pipeline schedules**.
1. On the right of the list, for
the pipeline you want to run, select **Play** (**{play}**).
@@ -79,7 +79,7 @@ including [protected environments](../environments/protected_environments.md) an
To take ownership of a pipeline created by a different user:
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Build > Pipeline schedules**.
+1. Select **Build > Pipeline schedules**.
1. On the right of the list, for
the pipeline you want to become owner of, select **Take ownership**.
diff --git a/doc/ci/testing/code_coverage.md b/doc/ci/testing/code_coverage.md
index 0691c6cfd93..43df79c44f5 100644
--- a/doc/ci/testing/code_coverage.md
+++ b/doc/ci/testing/code_coverage.md
@@ -76,7 +76,7 @@ To see the evolution of your project code coverage over time,
you can view a graph or download a CSV file with this data.
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Analyze > Repository analytics**.
+1. Select **Analyze > Repository analytics**.
The historic data for each job is listed in the dropdown list above the graph.
diff --git a/doc/development/code_review.md b/doc/development/code_review.md
index 07b4f30d2ae..25b9216183f 100644
--- a/doc/development/code_review.md
+++ b/doc/development/code_review.md
@@ -217,10 +217,10 @@ on merge request rates.
See the [Verify issue](https://gitlab.com/gitlab-org/gitlab/-/issues/411559) for a good example.
All other cases should not use mandatory sections as we favor
-[responsbility over ridigity](https://handbook.gitlab.com/handbook/values/#freedom-and-responsibility-over-rigidity).
+[responsibility over ridigity](https://handbook.gitlab.com/handbook/values/#freedom-and-responsibility-over-rigidity).
Additionally, the current structure of the monolith means that merge requests
-are likely to touch seemingly un-related parts.
+are likely to touch seemingly unrelated parts.
Multiple mandatory approvals means that such merge requests require the author
to seek approvals, which is not efficient.
diff --git a/doc/development/database/batched_background_migrations.md b/doc/development/database/batched_background_migrations.md
index 03f5eedb7f8..ec1a71eb4f4 100644
--- a/doc/development/database/batched_background_migrations.md
+++ b/doc/development/database/batched_background_migrations.md
@@ -257,6 +257,41 @@ Make sure the newly-created data is either migrated, or
saved in both the old and new version upon creation. Removals in
turn can be handled by defining foreign keys with cascading deletes.
+### Finalize a batched background migration
+
+Finalizing a batched background migration is done by calling
+`ensure_batched_background_migration_is_finished`.
+
+It is important to finalize all batched background migrations when it is safe
+to do so. Leaving around old batched background migration is a form of
+technical debt that needs to be maintained in tests and in application
+behavior. It is important to note that you cannot depend on any batched
+background migration being completed until after it is finalized.
+
+We recommend that batched background migrations are finalized after all of the
+following conditions are met:
+
+- The batched background migration is completed on GitLab.com
+- The batched background migration was added in or before the last [required stop](required_stops.md)
+
+The `ensure_batched_background_migration_is_finished` call must exactly match
+the migration that was used to enqueue it. Pay careful attention to:
+
+- The job arguments: Needs to exactly match or it will not find the queued migration
+- The `gitlab_schema`: Needs to exactly match or it will not find the queued
+ migration. Even if the `gitlab_schema` of the table has changed from
+ `gitlab_main` to `gitlab_main_cell` in the meantime you must finalize it
+ with `gitlab_main` if that's what was used when queueing the batched
+ background migration.
+
+When finalizing a batched background migration you also need to update the
+`finalized_by` in the corresponding `db/docs/batched_background_migrations`
+file. The value should be the timestamp/version of the migration you added to
+finalize it.
+
+See the below [Examples](#examples) for specific details on what the actual
+migration code should be.
+
### Use job arguments
`BatchedMigrationJob` provides the `job_arguments` helper method for job classes to define the job arguments they need.
diff --git a/doc/development/internal_api/index.md b/doc/development/internal_api/index.md
index 30976a88cf6..84f5d09418f 100644
--- a/doc/development/internal_api/index.md
+++ b/doc/development/internal_api/index.md
@@ -1274,6 +1274,9 @@ This group SCIM API is different to the [SCIM API](../../api/scim.md). The SCIM
- Does not implement the [RFC7644 protocol](https://www.rfc-editor.org/rfc/rfc7644).
- Gets, checks, updates, and deletes SCIM identities within groups.
+NOTE:
+This API does not require the `Gitlab-Shell-Api-Request` header.
+
### Get a list of SCIM provisioned users
This endpoint is used as part of the SCIM syncing mechanism. It returns a list of users depending on the filter used.
@@ -1519,6 +1522,9 @@ This instance SCIM API is different to the [SCIM API](../../api/scim.md). The SC
- Does not implement the [RFC7644 protocol](https://www.rfc-editor.org/rfc/rfc7644).
- Gets, checks, updates, and deletes SCIM identities within groups.
+NOTE:
+This API does not require the `Gitlab-Shell-Api-Request` header.
+
### Get a list of SCIM provisioned users
This endpoint is used as part of the SCIM syncing mechanism. It returns a list of users depending on the filter used.
diff --git a/doc/integration/jira/issues.md b/doc/integration/jira/issues.md
index a233d00bb1e..9385ec63373 100644
--- a/doc/integration/jira/issues.md
+++ b/doc/integration/jira/issues.md
@@ -146,7 +146,7 @@ provided your GitLab administrator [has configured the integration](configure.md
To view Jira issues:
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Plan > Jira issues**.
+1. Select **Plan > Jira issues**.
The issues are sorted by **Created date** by default, with the most recently created issues listed at the top.
diff --git a/doc/subscriptions/community_programs.md b/doc/subscriptions/community_programs.md
index d90884e865f..309a100de7a 100644
--- a/doc/subscriptions/community_programs.md
+++ b/doc/subscriptions/community_programs.md
@@ -46,7 +46,7 @@ Benefits of the GitLab Open Source Program apply to all projects in a GitLab nam
#### Screenshot 1: License overview
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select your project avatar. If you haven't specified an avatar for your project, the avatar displays as a single letter.
+1. Select your project avatar. If you haven't specified an avatar for your project, the avatar displays as a single letter.
1. Take a screenshot of the project overview that clearly displays the license you've chosen for your project.
![License overview](img/license-overview.png)
diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md
index 67f6e3916be..96a2654c579 100644
--- a/doc/update/patch_versions.md
+++ b/doc/update/patch_versions.md
@@ -10,7 +10,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
Make sure you view [this update guide](https://gitlab.com/gitlab-org/gitlab/-/blob/master/doc/update/patch_versions.md) from the tag (version) of GitLab you would like to install.
In most cases this should be the highest numbered production tag (without `rc` in it).
-You can select the tag in the version dropdown list in the upper-left corner of GitLab (below the menu bar).
+You can select the tag in the version dropdown list in the upper-left corner of GitLab.
### 0. Backup
diff --git a/doc/user/application_security/index.md b/doc/user/application_security/index.md
index 76a4f501d86..e31877d195a 100644
--- a/doc/user/application_security/index.md
+++ b/doc/user/application_security/index.md
@@ -95,6 +95,12 @@ against this, infrastructure analysis occurs on every merge request. Checks are
- Infrastructure as Code (IaC) configuration files that define your application's deployment
environment - [Infrastructure as Code (IaC) Scanning](iac_scanning/index.md).
+## Data privacy
+
+Concerning data privacy in the domain of security scanners, GitLab processes the source code and performs analysis locally on the GitLab Runner. No data is transmitted outside GitLab infrastructure (server and runners).
+
+Our scanners access the internet only to download the latest sets of signatures, rules, and patches. If you prefer the scanners do not access the internet, consider using an [offline environment](offline_deployments/index.md).
+
## Vulnerability scanner maintenance
The following vulnerability scanners and their databases are regularly updated:
diff --git a/doc/user/free_user_limit.md b/doc/user/free_user_limit.md
index 52c73c975d8..330f041b430 100644
--- a/doc/user/free_user_limit.md
+++ b/doc/user/free_user_limit.md
@@ -67,7 +67,7 @@ Prerequisite:
- You must have the Owner role for the group.
1. On the left sidebar, select **Search or go to** and find your group.
-1. On the left sidebar, select **Settings > Usage Quotas**.
+1. Select **Settings > Usage Quotas**.
1. To view all members, select the **Seats** tab.
1. To remove a member, select **Remove user**.
diff --git a/doc/user/group/index.md b/doc/user/group/index.md
index 22fe60a8000..f4b4f9f2d39 100644
--- a/doc/user/group/index.md
+++ b/doc/user/group/index.md
@@ -139,7 +139,7 @@ To leave a group:
To remove a group and its contents:
1. On the left sidebar, select **Search or go to** and find your group.
-1. On the left sidebar, select **Settings > General**.
+1. Select **Settings > General**.
1. Expand the **Advanced** section.
1. In the **Remove group** section, select **Remove group**.
1. On the confirmation dialog, type the group name and select **Confirm**.
diff --git a/doc/user/group/iterations/index.md b/doc/user/group/iterations/index.md
index 3d1190be776..bfbba8cc0da 100644
--- a/doc/user/group/iterations/index.md
+++ b/doc/user/group/iterations/index.md
@@ -178,7 +178,7 @@ Prerequisites:
To create an iteration:
1. On the left sidebar, select **Search or go to** and find your group.
-1. On the left sidebar, select **Plan > Iterations** and select an iteration cadence.
+1. Select **Plan > Iterations** and select an iteration cadence.
1. Select **New iteration**.
1. Enter the title, a description (optional), a start date, and a due date.
1. Select **Create iteration**. The iteration details page opens.
diff --git a/doc/user/okrs.md b/doc/user/okrs.md
index 2d24bf0a193..14e887fe297 100644
--- a/doc/user/okrs.md
+++ b/doc/user/okrs.md
@@ -57,7 +57,7 @@ To learn how to create better OKRs and how we use them at GitLab, see the
To create an objective:
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Plan > Issues**.
+1. Select **Plan > Issues**.
1. In the upper-right corner, next to **New issue**, select the down arrow **{chevron-lg-down}** and then select **New objective**.
1. Select **New objective** again.
1. Enter the objective title.
@@ -70,7 +70,7 @@ To create a key result, [add it as a child](#add-a-child-key-result) to an exist
To view an objective:
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Plan > Issues**.
+1. Select **Plan > Issues**.
1. [Filter the list of issues](project/issues/managing_issues.md#filter-the-list-of-issues)
for `Type = objective`.
1. Select the title of an objective from the list.
@@ -80,7 +80,7 @@ for `Type = objective`.
To view a key result:
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Plan > Issues**.
+1. Select **Plan > Issues**.
1. [Filter the list of issues](project/issues/managing_issues.md#filter-the-list-of-issues)
for `Type = key_result`.
1. Select the title of a key result from the list.
diff --git a/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md b/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
index 5bfd1e0515b..6ccd3033b57 100644
--- a/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
+++ b/doc/user/project/pages/custom_domains_ssl_tls_certification/lets_encrypt_integration.md
@@ -42,7 +42,7 @@ For **self-managed** GitLab instances, make sure your administrator has
Once you've met the requirements, enable Let's Encrypt integration:
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Deploy > Pages**.
+1. Select **Deploy > Pages**.
1. Next to the domain name, select **Edit**.
1. Turn on the **Automatic certificate management using Let's Encrypt** toggle.
@@ -69,7 +69,7 @@ associated Pages domain. GitLab also renews it automatically.
If you get an error **Something went wrong while obtaining the Let's Encrypt certificate**, first, make sure that your pages site is set to "Everyone" in your project's **Settings > General > Visibility**. This allows the Let's Encrypt Servers reach your pages site. Once this is confirmed, you can try obtaining the certificate again by following these steps:
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Deploy > Pages**.
+1. Select **Deploy > Pages**.
1. Next to the domain name, select **Edit**.
1. In **Verification status**, select **Retry verification** (**{retry}**).
1. If you're still getting the same error:
@@ -84,7 +84,7 @@ If you get an error **Something went wrong while obtaining the Let's Encrypt cer
If you've enabled Let's Encrypt integration, but a certificate is absent after an hour and you see the message, "GitLab is obtaining a Let's Encrypt SSL certificate for this domain. This process can take some time. Please try again later.", try to remove and add the domain for GitLab Pages again by following these steps:
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Deploy > Pages**.
+1. Select **Deploy > Pages**.
1. Next to the domain name, select **Remove**.
1. [Add the domain again, and verify it](index.md#1-add-a-custom-domain).
1. [Enable Let's Encrypt integration for your domain](#enabling-lets-encrypt-integration-for-your-custom-domain).
diff --git a/doc/user/project/pages/introduction.md b/doc/user/project/pages/introduction.md
index d4c1651931b..42a05e0b1bb 100644
--- a/doc/user/project/pages/introduction.md
+++ b/doc/user/project/pages/introduction.md
@@ -65,7 +65,7 @@ You can configure redirects for your site using a `_redirects` file. For more in
To remove your pages:
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Deploy > Pages**.
+1. Select **Deploy > Pages**.
1. Select **Remove pages**.
## Subdomains of subdomains
@@ -100,7 +100,7 @@ By default, every project in a group shares the same domain, for example, `group
To ensure your project uses a unique Pages domain, enable the unique domains feature for the project:
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Deploy > Pages**.
+1. Select **Deploy > Pages**.
1. Select the **Use unique domain** checkbox.
1. Select **Save changes**.
diff --git a/doc/user/project/releases/index.md b/doc/user/project/releases/index.md
index de159f732e0..6c31b2ad5d3 100644
--- a/doc/user/project/releases/index.md
+++ b/doc/user/project/releases/index.md
@@ -75,7 +75,7 @@ Prerequisites:
To create a release in the Releases page:
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Deploy > Releases** and select **New release**.
+1. Select **Deploy > Releases** and select **New release**.
1. From the [**Tag name**](release_fields.md#tag-name) dropdown list, either:
- Select an existing Git tag. Selecting an existing tag that is already associated with a release
results in a validation error.
@@ -216,7 +216,7 @@ To delete a release, use either the
In the UI:
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Deploy > Releases**.
+1. Select **Deploy > Releases**.
1. In the upper-right corner of the release you want to delete, select **Edit this release**
(**{pencil}**).
1. On the **Edit Release** page, select **Delete**.
@@ -321,7 +321,7 @@ To set a deploy freeze window in the UI, complete these steps:
1. Sign in to GitLab as a user with the Maintainer role.
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Settings > CI/CD**.
+1. Select **Settings > CI/CD**.
1. Scroll to **Deploy freezes**.
1. Select **Expand** to see the deploy freeze table.
1. Select **Add deploy freeze** to open the deploy freeze modal.
diff --git a/doc/user/project/repository/git_blame.md b/doc/user/project/repository/git_blame.md
index f1bf0e1ede9..a602638d244 100644
--- a/doc/user/project/repository/git_blame.md
+++ b/doc/user/project/repository/git_blame.md
@@ -21,7 +21,7 @@ Prerequisites:
To view the blame for a file:
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Code > Repository**.
+1. Select **Code > Repository**.
1. Select the file you want to review.
1. In the upper-right corner, select **Blame**, and go to the line you want to see.
@@ -39,7 +39,7 @@ changes to light gray.
To see earlier revisions of a specific line:
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Code > Repository**.
+1. Select **Code > Repository**.
1. Select the file you want to review.
1. In the upper-right corner, select **Blame**, and go to the line you want to see.
1. Select **View blame prior to this change** (**{doc-versions}**)
diff --git a/doc/user/project/repository/mirror/bidirectional.md b/doc/user/project/repository/mirror/bidirectional.md
index e18958935bf..d4ab550cb8a 100644
--- a/doc/user/project/repository/mirror/bidirectional.md
+++ b/doc/user/project/repository/mirror/bidirectional.md
@@ -45,7 +45,7 @@ To create the webhook in the downstream instance:
1. Create a [personal access token](../../../profile/personal_access_tokens.md) with `API` scope.
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Settings > Webhooks**.
+1. Select **Settings > Webhooks**.
1. Add the webhook **URL**, which (in this case) uses the
[Pull Mirror API](../../../../api/projects.md#start-the-pull-mirroring-process-for-a-project)
request to trigger an immediate pull after a repository update:
diff --git a/doc/user/public_access.md b/doc/user/public_access.md
index 87d64a0ff1d..b7ee354ed9a 100644
--- a/doc/user/public_access.md
+++ b/doc/user/public_access.md
@@ -100,7 +100,7 @@ Prerequisites:
to private if a subgroup or project in that group is public.
1. On the left sidebar, select **Search or go to** and find your group.
-1. On the left sidebar, select **Settings > General**.
+1. Select **Settings > General**.
1. Expand **Naming, visibility**.
1. For **Visibility level**, select an option.
The visibility setting for a project must be at least as restrictive
diff --git a/doc/user/report_abuse.md b/doc/user/report_abuse.md
index c5c73379aeb..1eb02b2f263 100644
--- a/doc/user/report_abuse.md
+++ b/doc/user/report_abuse.md
@@ -43,7 +43,7 @@ To report abuse from a user's profile page:
To report abuse from a user's comment:
1. In the comment, in the upper-right corner, select **More actions** (**{ellipsis_v}**).
-1. Select **Report abuse to administrator**.
+1. Select **Report abuse**.
1. Select a reason for reporting the user.
1. Complete an abuse report.
1. Select **Send report**.
@@ -63,7 +63,7 @@ A URL to the reported user's comment is pre-filled in the abuse report's
## Report abuse from a merge request
1. On the merge request, in the upper-right corner, select **Merge request actions** (**{ellipsis_v}**).
-1. Select **Report abuse to administrator**.
+1. Select **Report abuse**.
1. Select a reason for reporting this user.
1. Complete an abuse report.
1. Select **Send report**.
diff --git a/doc/user/shortcuts.md b/doc/user/shortcuts.md
index 3f817dd40bb..ff1e065de65 100644
--- a/doc/user/shortcuts.md
+++ b/doc/user/shortcuts.md
@@ -13,7 +13,7 @@ To display a window in GitLab that lists its keyboard shortcuts, use one of the
following methods:
- Press <kbd>?</kbd>.
-- In the Help menu, in the upper-right corner of the application, select **Keyboard shortcuts**.
+- In the lower-left corner of the application, select **Help** and then **Keyboard shortcuts**.
Although [global shortcuts](#global-shortcuts) work from any area of GitLab,
you must be in specific pages for the other shortcuts to be available, as
diff --git a/doc/user/snippets.md b/doc/user/snippets.md
index bca30bd185b..e1d6b857b31 100644
--- a/doc/user/snippets.md
+++ b/doc/user/snippets.md
@@ -232,7 +232,7 @@ Prerequisites:
To do this task:
1. On the left sidebar, select **Search or go to** and find your project.
-1. On the left sidebar, select **Code > Snippets**.
+1. Select **Code > Snippets**.
1. Select the snippet you want to report as spam.
1. Select **Submit as spam**.
diff --git a/lib/api/deployments.rb b/lib/api/deployments.rb
index 8161c2b850f..1468164081c 100644
--- a/lib/api/deployments.rb
+++ b/lib/api/deployments.rb
@@ -139,6 +139,8 @@ module API
authorize!(:create_deployment, user_project)
authorize!(:create_environment, user_project)
+ render_api_error!({ ref: ["The branch or tag does not exist"] }, 400) unless user_project.commit(declared_params[:ref])
+
environment = user_project
.environments
.find_or_create_by_name(params[:environment])
diff --git a/lib/gitlab/sidekiq_middleware/pause_control/server.rb b/lib/gitlab/sidekiq_middleware/pause_control/server.rb
index cfa02b3ec3a..7beb5f9ca5b 100644
--- a/lib/gitlab/sidekiq_middleware/pause_control/server.rb
+++ b/lib/gitlab/sidekiq_middleware/pause_control/server.rb
@@ -4,8 +4,8 @@ module Gitlab
module SidekiqMiddleware
module PauseControl
class Server
- def call(worker_class, job, _queue, &block)
- ::Gitlab::SidekiqMiddleware::PauseControl::StrategyHandler.new(worker_class, job).perform(&block)
+ def call(worker, job, _queue, &block)
+ ::Gitlab::SidekiqMiddleware::PauseControl::StrategyHandler.new(worker, job).perform(&block)
end
end
end
diff --git a/lib/gitlab/sidekiq_middleware/pause_control/workers_map.rb b/lib/gitlab/sidekiq_middleware/pause_control/workers_map.rb
index dc6aff92f50..97080dc91fc 100644
--- a/lib/gitlab/sidekiq_middleware/pause_control/workers_map.rb
+++ b/lib/gitlab/sidekiq_middleware/pause_control/workers_map.rb
@@ -17,7 +17,8 @@ module Gitlab
def strategy_for(worker:)
return unless @workers
- @workers.find { |_, v| v.include?(worker) }&.first
+ worker_class = worker.is_a?(Class) ? worker : worker.class
+ @workers.find { |_, v| v.include?(worker_class) }&.first
end
end
end
diff --git a/lib/integrations/google_cloud_platform/jwt.rb b/lib/integrations/google_cloud_platform/jwt.rb
index a103f379bc8..26343a3a9db 100644
--- a/lib/integrations/google_cloud_platform/jwt.rb
+++ b/lib/integrations/google_cloud_platform/jwt.rb
@@ -70,13 +70,19 @@ module Integrations
override :issuer
def issuer
- Settings.gitlab.host
+ Feature.enabled?(:oidc_issuer_url) ? Gitlab.config.gitlab.url : Settings.gitlab.base_url
end
override :audience
def audience
@claims[:audience]
end
+
+ override :kid
+ def kid
+ rsa_key = OpenSSL::PKey::RSA.new(key_data)
+ rsa_key.public_key.to_jwk[:kid]
+ end
end
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 9c631f1bd85..87f7daf2612 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -1386,9 +1386,21 @@ msgstr ""
msgid "'allow: %{allow}' must be a string"
msgstr ""
+msgid "'cpu: %{cpu}' must be a string"
+msgstr ""
+
+msgid "'cpu: %{cpu}' must match the regex '%{cpu_regex}'"
+msgstr ""
+
msgid "'except: %{except}' must be an array of string"
msgstr ""
+msgid "'memory: %{memory}' must be a string"
+msgstr ""
+
+msgid "'memory: %{memory}' must match the regex '%{memory_regex}'"
+msgstr ""
+
msgid "'projects' is not yet supported"
msgstr ""
@@ -10306,9 +10318,6 @@ msgstr ""
msgid "Choose a file"
msgstr ""
-msgid "Choose a group"
-msgstr ""
-
msgid "Choose a template"
msgstr ""
@@ -11984,9 +11993,6 @@ msgstr ""
msgid "CodeSuggestionsSM|Enable Code Suggestions for this instance"
msgstr ""
-msgid "CodeSuggestionsSM|Enable Code Suggestions for this instance %{beta}"
-msgstr ""
-
msgid "CodeSuggestionsSM|Enable Code Suggestions for users of this instance. %{link_start}What are Code Suggestions?%{link_end}"
msgstr ""
@@ -23530,6 +23536,9 @@ msgstr ""
msgid "GroupSettings|Reporting"
msgstr ""
+msgid "GroupSettings|Security policy custom CI Experiment"
+msgstr ""
+
msgid "GroupSettings|Select a subgroup to use as a source of custom templates for new projects in this group. %{link_start}Learn more%{link_end}."
msgstr ""
@@ -23572,6 +23581,9 @@ msgstr ""
msgid "GroupSettings|These features are being developed and might be unstable."
msgstr ""
+msgid "GroupSettings|This feature is being developed and might be unstable."
+msgstr ""
+
msgid "GroupSettings|This setting is applied on %{ancestor_group} and has been overridden on this subgroup."
msgstr ""
@@ -45323,6 +45335,12 @@ msgstr ""
msgid "Settings for the License Compliance feature"
msgstr ""
+msgid "Settings|Enable this feature allows you to add customized CI YAML file to run as part of the policies action. This features is your acceptance of the %{link_start}GitLab Testing Agreement%{link_end}."
+msgstr ""
+
+msgid "Settings|Run customized CI YAML file as security policy actions"
+msgstr ""
+
msgid "Settings|Unable to load the merge request options settings. Try reloading the page."
msgstr ""
@@ -58320,6 +58338,18 @@ msgstr ""
msgid "must be a boolean value"
msgstr ""
+msgid "must be a hash"
+msgstr ""
+
+msgid "must be a hash containing 'cpu' and 'memory' attribute of type string"
+msgstr ""
+
+msgid "must be a hash containing 'limits' attribute of type hash"
+msgstr ""
+
+msgid "must be a hash containing 'requests' attribute of type hash"
+msgstr ""
+
msgid "must be a root group."
msgstr ""
diff --git a/package.json b/package.json
index 40cd531113a..8b36ebd0ce1 100644
--- a/package.json
+++ b/package.json
@@ -60,7 +60,7 @@
"@gitlab/favicon-overlay": "2.0.0",
"@gitlab/fonts": "^1.3.0",
"@gitlab/svgs": "3.72.0",
- "@gitlab/ui": "^71.11.1",
+ "@gitlab/ui": "^72.0.0",
"@gitlab/visual-review-tools": "1.7.3",
"@gitlab/web-ide": "^0.0.1-dev-20231211152737",
"@mattiasbuelens/web-streams-adapter": "^0.1.0",
diff --git a/qa/qa/page/group/settings/general.rb b/qa/qa/page/group/settings/general.rb
index c12a9a60fa2..57e532be401 100644
--- a/qa/qa/page/group/settings/general.rb
+++ b/qa/qa/page/group/settings/general.rb
@@ -11,33 +11,33 @@ module QA
include Page::Component::NamespaceSelect
view 'app/views/groups/edit.html.haml' do
- element :permission_lfs_2fa_content
+ element 'permissions-settings'
element 'advanced-settings-content'
end
view 'app/views/groups/settings/_permissions.html.haml' do
- element :save_permissions_changes_button
+ element 'save-permissions-changes-button'
end
view 'app/views/groups/settings/_general.html.haml' do
- element :group_name_field
- element :save_name_visibility_settings_button
+ element 'group-name-field'
+ element 'save-name-visibility-settings-button'
end
view 'app/views/groups/settings/_lfs.html.haml' do
- element :lfs_checkbox
+ element 'lfs-checkbox'
end
view 'app/views/shared/_allow_request_access.html.haml' do
- element :request_access_checkbox
+ element 'request-access-checkbox'
end
view 'app/views/groups/settings/_two_factor_auth.html.haml' do
- element :require_2fa_checkbox
+ element 'require-2fa-checkbox'
end
view 'app/views/groups/settings/_project_creation_level.html.haml' do
- element :project_creation_level_dropdown
+ element 'project-creation-level-dropdown'
end
view 'app/views/groups/settings/_transfer.html.haml' do
@@ -49,66 +49,66 @@ module QA
end
def set_group_name(name)
- find_element(:group_name_field).send_keys([:command, 'a'], :backspace)
- find_element(:group_name_field).set name
+ find_element('group-name-field').send_keys([:command, 'a'], :backspace)
+ find_element('group-name-field').set name
end
def click_save_name_visibility_settings_button
- click_element(:save_name_visibility_settings_button)
+ click_element('save-name-visibility-settings-button')
end
def set_lfs_enabled
- expand_content(:permission_lfs_2fa_content)
- check_element(:lfs_checkbox, true)
- click_element(:save_permissions_changes_button)
+ expand_content('permissions-settings')
+ check_element('lfs-checkbox', true)
+ click_element('save-permissions-changes-button')
end
def set_lfs_disabled
- expand_content(:permission_lfs_2fa_content)
- uncheck_element(:lfs_checkbox, true)
- click_element(:save_permissions_changes_button)
+ expand_content('permissions-settings')
+ uncheck_element('lfs-checkbox', true)
+ click_element('save-permissions-changes-button')
end
def set_request_access_enabled
- expand_content(:permission_lfs_2fa_content)
- check_element(:request_access_checkbox, true)
- click_element(:save_permissions_changes_button)
+ expand_content('permissions-settings')
+ check_element('request-access-checkbox', true)
+ click_element('save-permissions-changes-button')
end
def set_request_access_disabled
- expand_content(:permission_lfs_2fa_content)
- uncheck_element(:request_access_checkbox, true)
- click_element(:save_permissions_changes_button)
+ expand_content('permissions-settings')
+ uncheck_element('request-access-checkbox', true)
+ click_element('save-permissions-changes-button')
end
def set_require_2fa_enabled
- expand_content(:permission_lfs_2fa_content)
- check_element(:require_2fa_checkbox, true)
- click_element(:save_permissions_changes_button)
+ expand_content('permissions-settings')
+ check_element('require-2fa-checkbox', true)
+ click_element('save-permissions-changes-button')
end
def set_require_2fa_disabled
- expand_content(:permission_lfs_2fa_content)
- uncheck_element(:require_2fa_checkbox, true)
- click_element(:save_permissions_changes_button)
+ expand_content('permissions-settings')
+ uncheck_element('require-2fa-checkbox', true)
+ click_element('save-permissions-changes-button')
end
def set_project_creation_level(value)
- expand_content(:permission_lfs_2fa_content)
- select_element(:project_creation_level_dropdown, value)
- click_element(:save_permissions_changes_button)
+ expand_content('permissions-settings')
+ select_element('project-creation-level-dropdown', value)
+ click_element('save-permissions-changes-button')
end
def toggle_request_access
- expand_content(:permission_lfs_2fa_content)
+ expand_content('permissions-settings')
- if find_element(:request_access_checkbox, visible: false).checked?
- uncheck_element(:request_access_checkbox, true)
+ if find_element('request-access-checkbox', visible: false).checked?
+ uncheck_element('request-access-checkbox', true)
else
- check_element(:request_access_checkbox, true)
+ check_element('request-access-checkbox', true)
end
- click_element(:save_permissions_changes_button)
+ click_element('save-permissions-changes-button')
end
def transfer_group(source_group, target_group)
diff --git a/qa/qa/page/group/show.rb b/qa/qa/page/group/show.rb
index 100b9f7fa6d..bf1cab510c8 100644
--- a/qa/qa/page/group/show.rb
+++ b/qa/qa/page/group/show.rb
@@ -9,7 +9,7 @@ module QA
view 'app/views/groups/_home_panel.html.haml' do
element 'new-project-button'
- element :new_subgroup_button
+ element 'new-subgroup-button'
end
def click_subgroup(name)
@@ -18,7 +18,7 @@ module QA
def has_new_project_and_new_subgroup_buttons?
has_element?('new_project_button')
- has_element?(:new_subgroup_button)
+ has_element?('new-subgroup-button')
end
def has_subgroup?(name)
@@ -26,7 +26,7 @@ module QA
end
def go_to_new_subgroup
- click_element :new_subgroup_button
+ click_element('new-subgroup-button')
end
def go_to_new_project
diff --git a/spec/components/projects/ml/show_ml_model_component_spec.rb b/spec/components/projects/ml/show_ml_model_component_spec.rb
index 2bb3e8fc9af..34b8cbe96ca 100644
--- a/spec/components/projects/ml/show_ml_model_component_spec.rb
+++ b/spec/components/projects/ml/show_ml_model_component_spec.rb
@@ -3,15 +3,14 @@
require "spec_helper"
RSpec.describe Projects::Ml::ShowMlModelComponent, type: :component, feature_category: :mlops do
- # rubocop:disable RSpec/FactoryBot/AvoidCreate -- build_stubbed breaks because it doesn't create iids properly.
- let_it_be(:project) { create(:project) }
+ let_it_be(:project) { build_stubbed(:project) }
let_it_be(:model1) do
- create(:ml_models, :with_latest_version_and_package, project: project, description: "A description")
+ build_stubbed(:ml_models, :with_latest_version_and_package, project: project, description: "A description")
end
- # rubocop:enable RSpec/FactoryBot/AvoidCreate
- let_it_be(:experiment) { model1.default_experiment }
- let_it_be(:candidate) { model1.latest_version.candidate }
+ let_it_be(:experiment) { model1.default_experiment.tap { |e| e.iid = 100 } }
+ let_it_be(:candidate) { model1.latest_version.candidate.tap { |c| c.iid = 101 } }
+ let_it_be(:candidates) { Array.new(2) { build_stubbed(:ml_candidates, experiment: experiment) } }
subject(:component) do
described_class.new(model: model1, current_user: model1.user)
@@ -19,6 +18,8 @@ RSpec.describe Projects::Ml::ShowMlModelComponent, type: :component, feature_cat
describe 'rendered' do
before do
+ allow(model1).to receive(:candidates).and_return(candidates)
+
render_inline component
end
@@ -52,7 +53,8 @@ RSpec.describe Projects::Ml::ShowMlModelComponent, type: :component, feature_cat
'metadata' => []
}
},
- 'versionCount' => 1
+ 'versionCount' => 1,
+ 'candidateCount' => 2
}
})
end
diff --git a/spec/factories/ml/experiments.rb b/spec/factories/ml/experiments.rb
index 0acb4c5c5fc..419a530bdcc 100644
--- a/spec/factories/ml/experiments.rb
+++ b/spec/factories/ml/experiments.rb
@@ -11,5 +11,13 @@ FactoryBot.define do
e.metadata = FactoryBot.create_list(:ml_experiment_metadata, 2, experiment: e) # rubocop:disable StrategyInCallback
end
end
+
+ trait :with_candidates do
+ candidates do
+ Array.new(2) do
+ association(:ml_candidates, project: project)
+ end
+ end
+ end
end
end
diff --git a/spec/frontend/merge_conflicts/components/merge_conflict_resolver_app_spec.js b/spec/frontend/merge_conflicts/components/merge_conflict_resolver_app_spec.js
index edd18c57f43..1a45ada98f9 100644
--- a/spec/frontend/merge_conflicts/components/merge_conflict_resolver_app_spec.js
+++ b/spec/frontend/merge_conflicts/components/merge_conflict_resolver_app_spec.js
@@ -6,6 +6,7 @@ import { shallowMountExtended, extendedWrapper } from 'helpers/vue_test_utils_he
import InlineConflictLines from '~/merge_conflicts/components/inline_conflict_lines.vue';
import ParallelConflictLines from '~/merge_conflicts/components/parallel_conflict_lines.vue';
import component from '~/merge_conflicts/merge_conflict_resolver_app.vue';
+import ClipboardButton from '~/vue_shared/components/clipboard_button.vue';
import { createStore } from '~/merge_conflicts/store';
import { decorateFiles } from '~/merge_conflicts/utils';
import { conflictsMock } from '../mock_data';
@@ -49,6 +50,7 @@ describe('Merge Conflict Resolver App', () => {
const findInlineConflictLines = (w = wrapper) => w.findComponent(InlineConflictLines);
const findParallelConflictLines = (w = wrapper) => w.findComponent(ParallelConflictLines);
const findCommitMessageTextarea = () => wrapper.findByTestId('commit-message');
+ const findClipboardButton = (w = wrapper) => w.findComponent(ClipboardButton);
it('shows the amount of conflicts', () => {
mountComponent();
@@ -131,6 +133,21 @@ describe('Merge Conflict Resolver App', () => {
expect(parallelConflictLinesComponent.props('file')).toEqual(decoratedMockFiles[0]);
});
});
+
+ describe('clipboard button', () => {
+ it('exists', () => {
+ mountComponent();
+ expect(findClipboardButton().exists()).toBe(true);
+ });
+
+ it('has the correct props', () => {
+ mountComponent();
+ expect(findClipboardButton().attributes()).toMatchObject({
+ text: decoratedMockFiles[0].filePath,
+ title: 'Copy file path',
+ });
+ });
+ });
});
describe('submit form', () => {
diff --git a/spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js b/spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js
index f4e36f51c27..6a1c34df596 100644
--- a/spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/components/list/packages_search_spec.js
@@ -1,4 +1,5 @@
import { nextTick } from 'vue';
+import { GlFilteredSearchToken } from '@gitlab/ui';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
import { sortableFields } from '~/packages_and_registries/package_registry/utils';
import component from '~/packages_and_registries/package_registry/components/list/package_search.vue';
@@ -7,7 +8,11 @@ import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import PersistedSearch from '~/packages_and_registries/shared/components/persisted_search.vue';
import { LIST_KEY_CREATED_AT } from '~/packages_and_registries/package_registry/constants';
-import { TOKEN_TYPE_TYPE } from '~/vue_shared/components/filtered_search_bar/constants';
+import {
+ OPERATORS_IS,
+ TOKEN_TYPE_TYPE,
+ TOKEN_TYPE_VERSION,
+} from '~/vue_shared/components/filtered_search_bar/constants';
describe('Package Search', () => {
let wrapper;
@@ -74,6 +79,13 @@ describe('Package Search', () => {
token: PackageTypeToken,
type: TOKEN_TYPE_TYPE,
icon: 'package',
+ operators: OPERATORS_IS,
+ }),
+ expect.objectContaining({
+ token: GlFilteredSearchToken,
+ type: TOKEN_TYPE_VERSION,
+ icon: 'doc-versions',
+ operators: OPERATORS_IS,
}),
]),
sortableFields: sortableFields(isGroupPage),
@@ -102,6 +114,7 @@ describe('Package Search', () => {
filters: {
packageName: '',
packageType: undefined,
+ packageVersion: '',
},
sort: payload.sort,
sorting: payload.sorting,
@@ -114,6 +127,7 @@ describe('Package Search', () => {
sort: 'CREATED_FOO',
filters: [
{ type: 'type', value: { data: 'Generic', operator: '=' }, id: 'token-3' },
+ { type: 'version', value: { data: '1.0.1', operator: '=' }, id: 'token-6' },
{ id: 'token-4', type: 'filtered-search-term', value: { data: 'gl' } },
{ id: 'token-5', type: 'filtered-search-term', value: { data: '' } },
],
@@ -133,6 +147,7 @@ describe('Package Search', () => {
filters: {
packageName: 'gl',
packageType: 'GENERIC',
+ packageVersion: '1.0.1',
},
sort: payload.sort,
sorting: payload.sorting,
diff --git a/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js b/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js
index 0ce2b86b9a4..db86be3b8ee 100644
--- a/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js
+++ b/spec/frontend/packages_and_registries/package_registry/pages/list_spec.js
@@ -44,7 +44,7 @@ describe('PackagesListApp', () => {
const searchPayload = {
sort: 'VERSION_DESC',
- filters: { packageName: 'foo', packageType: 'CONAN' },
+ filters: { packageName: 'foo', packageType: 'CONAN', packageVersion: '1.0.1' },
};
const findPackageTitle = () => wrapper.findComponent(PackageTitle);
@@ -304,7 +304,12 @@ describe('PackagesListApp', () => {
await waitForFirstRequest();
- findSearch().vm.$emit('update', searchPayload);
+ findSearch().vm.$emit('update', {
+ sort: 'VERSION_DESC',
+ filters: {
+ packageName: 'test',
+ },
+ });
return nextTick();
});
diff --git a/spec/frontend/packages_and_registries/shared/utils_spec.js b/spec/frontend/packages_and_registries/shared/utils_spec.js
index 1dc6bb261de..4676544c324 100644
--- a/spec/frontend/packages_and_registries/shared/utils_spec.js
+++ b/spec/frontend/packages_and_registries/shared/utils_spec.js
@@ -41,19 +41,20 @@ describe('Packages And Registries shared utils', () => {
});
describe('extractFilterAndSorting', () => {
it.each`
- search | type | sort | orderBy | result
- ${['one']} | ${'myType'} | ${'asc'} | ${'foo'} | ${{ sorting: { sort: 'asc', orderBy: 'foo' }, filters: [{ type: 'type', value: { data: 'myType' } }, { type: FILTERED_SEARCH_TERM, value: { data: 'one' } }] }}
- ${['one']} | ${null} | ${'asc'} | ${'foo'} | ${{ sorting: { sort: 'asc', orderBy: 'foo' }, filters: [{ type: FILTERED_SEARCH_TERM, value: { data: 'one' } }] }}
- ${[]} | ${null} | ${'asc'} | ${'foo'} | ${{ sorting: { sort: 'asc', orderBy: 'foo' }, filters: [] }}
- ${null} | ${null} | ${'asc'} | ${'foo'} | ${{ sorting: { sort: 'asc', orderBy: 'foo' }, filters: [] }}
- ${null} | ${null} | ${null} | ${'foo'} | ${{ sorting: { orderBy: 'foo' }, filters: [] }}
- ${null} | ${null} | ${null} | ${null} | ${{ sorting: {}, filters: [] }}
+ search | type | version | sort | orderBy | result
+ ${['one']} | ${'myType'} | ${'1.0.1'} | ${'asc'} | ${'foo'} | ${{ sorting: { sort: 'asc', orderBy: 'foo' }, filters: [{ type: 'type', value: { data: 'myType' } }, { type: 'version', value: { data: '1.0.1' } }, { type: FILTERED_SEARCH_TERM, value: { data: 'one' } }] }}
+ ${['one']} | ${null} | ${null} | ${'asc'} | ${'foo'} | ${{ sorting: { sort: 'asc', orderBy: 'foo' }, filters: [{ type: FILTERED_SEARCH_TERM, value: { data: 'one' } }] }}
+ ${[]} | ${null} | ${null} | ${'asc'} | ${'foo'} | ${{ sorting: { sort: 'asc', orderBy: 'foo' }, filters: [] }}
+ ${null} | ${null} | ${null} | ${'asc'} | ${'foo'} | ${{ sorting: { sort: 'asc', orderBy: 'foo' }, filters: [] }}
+ ${null} | ${null} | ${null} | ${null} | ${'foo'} | ${{ sorting: { orderBy: 'foo' }, filters: [] }}
+ ${null} | ${null} | ${null} | ${null} | ${null} | ${{ sorting: {}, filters: [] }}
`(
'returns sorting and filters objects in the correct form',
- ({ search, type, sort, orderBy, result }) => {
+ ({ search, type, version, sort, orderBy, result }) => {
const queryObject = {
search,
type,
+ version,
sort,
orderBy,
};
diff --git a/spec/lib/gitlab/sidekiq_middleware/pause_control/workers_map_spec.rb b/spec/lib/gitlab/sidekiq_middleware/pause_control/workers_map_spec.rb
new file mode 100644
index 00000000000..1aa4b470db0
--- /dev/null
+++ b/spec/lib/gitlab/sidekiq_middleware/pause_control/workers_map_spec.rb
@@ -0,0 +1,37 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Gitlab::SidekiqMiddleware::PauseControl::WorkersMap, feature_category: :global_search do
+ let(:worker_class) do
+ Class.new do
+ def self.name
+ 'TestPauseWorker'
+ end
+
+ include ApplicationWorker
+
+ pause_control :zoekt
+
+ def perform(*); end
+ end
+ end
+
+ before do
+ stub_const('TestPauseWorker', worker_class)
+ end
+
+ describe '.strategy_for' do
+ it 'accepts classname' do
+ expect(described_class.strategy_for(worker: worker_class)).to eq(:zoekt)
+ end
+
+ it 'accepts worker instance' do
+ expect(described_class.strategy_for(worker: worker_class.new)).to eq(:zoekt)
+ end
+
+ it 'returns nil for unknown worker' do
+ expect(described_class.strategy_for(worker: described_class)).to be_nil
+ end
+ end
+end
diff --git a/spec/lib/integrations/google_cloud_platform/jwt_spec.rb b/spec/lib/integrations/google_cloud_platform/jwt_spec.rb
index 16c71f3dc4f..51707c26a3a 100644
--- a/spec/lib/integrations/google_cloud_platform/jwt_spec.rb
+++ b/spec/lib/integrations/google_cloud_platform/jwt_spec.rb
@@ -20,7 +20,7 @@ RSpec.describe Integrations::GoogleCloudPlatform::Jwt, feature_category: :shared
end
it 'creates a valid jwt' do
- payload, _ = JWT.decode(encoded, rsa_key.public_key, true, { algorithm: 'RS256' })
+ payload, headers = JWT.decode(encoded, rsa_key.public_key, true, { algorithm: 'RS256' })
expect(payload).to include(
'root_namespace_path' => project.root_namespace.full_path,
@@ -31,7 +31,12 @@ RSpec.describe Integrations::GoogleCloudPlatform::Jwt, feature_category: :shared
'project_path' => project.full_path,
'user_id' => user.id.to_s,
'user_email' => user.email,
- 'sub' => "project_#{project.id}_user_#{user.id}"
+ 'sub' => "project_#{project.id}_user_#{user.id}",
+ 'iss' => Gitlab.config.gitlab.url
+ )
+
+ expect(headers).to include(
+ 'kid' => rsa_key.public_key.to_jwk[:kid]
)
end
@@ -60,5 +65,22 @@ RSpec.describe Integrations::GoogleCloudPlatform::Jwt, feature_category: :shared
expect { encoded }.to raise_error(described_class::NoSigningKeyError)
end
end
+
+ context 'with oidc_issuer_url feature flag disabled' do
+ before do
+ stub_feature_flags(oidc_issuer_url: false)
+ # Settings.gitlab.base_url and Gitlab.config.gitlab.url are the
+ # same for test. Changing that to assert the proper behavior here.
+ allow(Settings.gitlab).to receive(:base_url).and_return('test.dev')
+ end
+
+ it 'uses a different issuer' do
+ payload, _ = JWT.decode(encoded, rsa_key.public_key, true, { algorithm: 'RS256' })
+
+ expect(payload).to include(
+ 'iss' => Settings.gitlab.base_url
+ )
+ end
+ end
end
end
diff --git a/spec/presenters/ml/model_presenter_spec.rb b/spec/presenters/ml/model_presenter_spec.rb
index 31bf4e7ad6c..92398a8bb4d 100644
--- a/spec/presenters/ml/model_presenter_spec.rb
+++ b/spec/presenters/ml/model_presenter_spec.rb
@@ -8,6 +8,8 @@ RSpec.describe Ml::ModelPresenter, feature_category: :mlops do
let_it_be(:model2) { build_stubbed(:ml_models, :with_latest_version_and_package, project: project) }
let_it_be(:model3) { build_stubbed(:ml_models, :with_versions, project: project) }
+ let_it_be(:model4) { build_stubbed(:ml_models, project: project) }
+
describe '#latest_version_name' do
subject { model.present.latest_version_name }
@@ -42,6 +44,18 @@ RSpec.describe Ml::ModelPresenter, feature_category: :mlops do
end
end
+ describe '#candidate_count' do
+ let(:candidates) { build_stubbed_list(:ml_candidates, 2, experiment: model4.default_experiment) }
+
+ before do
+ allow(model4).to receive(:candidates).and_return(candidates)
+ end
+
+ subject { model4.present.candidate_count }
+
+ it { is_expected.to eq(2) }
+ end
+
describe '#latest_package_path' do
subject { model.present.latest_package_path }
diff --git a/spec/requests/api/deployments_spec.rb b/spec/requests/api/deployments_spec.rb
index d9248ad6855..5a8e1649e75 100644
--- a/spec/requests/api/deployments_spec.rb
+++ b/spec/requests/api/deployments_spec.rb
@@ -261,6 +261,22 @@ RSpec.describe API::Deployments, feature_category: :continuous_delivery do
expect(json_response['environment']['name']).to eq('production')
end
+ it 'errors when creating a deployment with an invalid ref', :aggregate_failures do
+ post(
+ api("/projects/#{project.id}/deployments", user),
+ params: {
+ environment: 'production',
+ sha: sha,
+ ref: 'doesnotexist',
+ tag: false,
+ status: 'success'
+ }
+ )
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to eq({ "ref" => ["The branch or tag does not exist"] })
+ end
+
it 'errors when creating a deployment with an invalid name' do
post(
api("/projects/#{project.id}/deployments", user),
diff --git a/spec/services/ml/create_model_service_spec.rb b/spec/services/ml/create_model_service_spec.rb
index 212f0940635..74c1dd5fec7 100644
--- a/spec/services/ml/create_model_service_spec.rb
+++ b/spec/services/ml/create_model_service_spec.rb
@@ -9,6 +9,10 @@ RSpec.describe ::Ml::CreateModelService, feature_category: :mlops do
let_it_be(:description) { 'description' }
let_it_be(:metadata) { [] }
+ before do
+ allow(Gitlab::InternalEvents).to receive(:track_event)
+ end
+
subject(:create_model) { described_class.new(project, name, user, description, metadata).execute }
describe '#execute' do
@@ -18,6 +22,10 @@ RSpec.describe ::Ml::CreateModelService, feature_category: :mlops do
it 'creates a model', :aggregate_failures do
expect { create_model }.to change { Ml::Model.count }.by(1)
+ expect(Gitlab::InternalEvents).to have_received(:track_event).with(
+ 'model_registry_ml_model_created',
+ { project: project, user: user }
+ )
expect(create_model.name).to eq(name)
end
@@ -29,6 +37,10 @@ RSpec.describe ::Ml::CreateModelService, feature_category: :mlops do
it 'creates a model', :aggregate_failures do
expect { create_model }.to change { Ml::Model.count }.by(1)
+ expect(Gitlab::InternalEvents).to have_received(:track_event).with(
+ 'model_registry_ml_model_created',
+ { project: project, user: user }
+ )
expect(create_model.name).to eq(name)
end
@@ -40,6 +52,7 @@ RSpec.describe ::Ml::CreateModelService, feature_category: :mlops do
it 'raises an error', :aggregate_failures do
expect { create_model }.to raise_error(ActiveRecord::RecordInvalid)
+ expect(Gitlab::InternalEvents).not_to have_received(:track_event)
end
end
diff --git a/spec/services/ml/create_model_version_service_spec.rb b/spec/services/ml/create_model_version_service_spec.rb
index 4e71c538f7e..b3aead4a92c 100644
--- a/spec/services/ml/create_model_version_service_spec.rb
+++ b/spec/services/ml/create_model_version_service_spec.rb
@@ -6,12 +6,21 @@ RSpec.describe ::Ml::CreateModelVersionService, feature_category: :mlops do
let(:model) { create(:ml_models) }
let(:params) { {} }
+ before do
+ allow(Gitlab::InternalEvents).to receive(:track_event)
+ end
+
subject(:service) { described_class.new(model, params).execute }
context 'when no versions exist' do
it 'creates a model version', :aggregate_failures do
expect { service }.to change { Ml::ModelVersion.count }.by(1).and change { Ml::Candidate.count }.by(1)
expect(model.reload.latest_version.version).to eq('1.0.0')
+
+ expect(Gitlab::InternalEvents).to have_received(:track_event).with(
+ 'model_registry_ml_model_version_created',
+ { project: model.project, user: nil }
+ )
end
end
@@ -23,6 +32,11 @@ RSpec.describe ::Ml::CreateModelVersionService, feature_category: :mlops do
it 'creates another model version and increments the version number', :aggregate_failures do
expect { service }.to change { Ml::ModelVersion.count }.by(1).and change { Ml::Candidate.count }.by(1)
expect(model.reload.latest_version.version).to eq('4.0.0')
+
+ expect(Gitlab::InternalEvents).to have_received(:track_event).with(
+ 'model_registry_ml_model_version_created',
+ { project: model.project, user: nil }
+ )
end
end
diff --git a/spec/validators/kubernetes_container_resources_validator_spec.rb b/spec/validators/kubernetes_container_resources_validator_spec.rb
new file mode 100644
index 00000000000..aea561bafb9
--- /dev/null
+++ b/spec/validators/kubernetes_container_resources_validator_spec.rb
@@ -0,0 +1,42 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe KubernetesContainerResourcesValidator, feature_category: :shared do
+ let(:model) do
+ Class.new do
+ include ActiveModel::Model
+ include ActiveModel::Validations
+
+ attr_accessor :resources
+ alias_method :resources_before_type_cast, :resources
+
+ validates :resources, kubernetes_container_resources: true
+ end.new
+ end
+
+ using RSpec::Parameterized::TableSyntax
+
+ # noinspection RubyMismatchedArgumentType - https://handbook.gitlab.com/handbook/tools-and-tips/editors-and-ides/jetbrains-ides/tracked-jetbrains-issues/#ruby-32041
+ where(:resources, :validity, :errors) do
+ # rubocop:disable Layout/LineLength -- The RSpec table syntax often requires long lines for errors
+ nil | false | { resources: ["must be a hash"] }
+ '' | false | { resources: ["must be a hash"] }
+ {} | false | { resources: ["must be a hash containing 'cpu' and 'memory' attribute of type string"] }
+ { cpu: nil, memory: nil } | false | { resources: ["'cpu: ' must be a string", "'memory: ' must be a string"] }
+ { cpu: "123di", memory: "123oi" } | false | { resources: ["'cpu: 123di' must match the regex '^(\\d+m|\\d+(\\.\\d*)?)$'", "'memory: 123oi' must match the regex '^\\d+(\\.\\d*)?([EPTGMK]|[EPTGMK]i)?$'"] }
+ { cpu: "123di", memory: "123oi" } | false | { resources: ["'cpu: 123di' must match the regex '^(\\d+m|\\d+(\\.\\d*)?)$'", "'memory: 123oi' must match the regex '^\\d+(\\.\\d*)?([EPTGMK]|[EPTGMK]i)?$'"] }
+ { cpu: "100m", memory: "123Mi" } | true | {}
+ # rubocop:enable Layout/LineLength
+ end
+
+ with_them do
+ before do
+ model.resources = resources
+ model.validate
+ end
+
+ it { expect(model.valid?).to eq(validity) }
+ it { expect(model.errors.messages).to eq(errors) }
+ end
+end
diff --git a/spec/workers/abuse/trust_score_worker_spec.rb b/spec/workers/abuse/trust_score_worker_spec.rb
new file mode 100644
index 00000000000..adc582ada94
--- /dev/null
+++ b/spec/workers/abuse/trust_score_worker_spec.rb
@@ -0,0 +1,46 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe Abuse::TrustScoreWorker, :clean_gitlab_redis_shared_state, feature_category: :instance_resiliency do
+ let(:worker) { described_class.new }
+ let_it_be(:user) { create(:user) }
+
+ subject(:perform) { worker.perform(user.id, :telesign, 0.85, 'foo') }
+
+ it_behaves_like 'an idempotent worker' do
+ let(:job_args) { [user.id, :telesign, 0.5] }
+ end
+
+ context "when the user does not exist" do
+ let(:log_payload) { { 'message' => 'User not found.', 'user_id' => user.id } }
+
+ before do
+ allow(User).to receive(:find_by_id).with(user.id).and_return(nil)
+ end
+
+ it 'logs an error' do
+ expect(Sidekiq.logger).to receive(:info).with(hash_including(log_payload))
+
+ expect { perform }.not_to raise_exception
+ end
+
+ it 'does not attempt to create the trust score' do
+ expect(Abuse::TrustScore).not_to receive(:create!)
+
+ perform
+ end
+ end
+
+ context "when the user exists" do
+ it 'creates an abuse trust score with the correct data' do
+ expect { perform }.to change { Abuse::TrustScore.count }.from(0).to(1)
+ expect(Abuse::TrustScore.last.attributes).to include({
+ user_id: user.id,
+ source: "telesign",
+ score: 0.85,
+ correlation_id_value: 'foo'
+ }.stringify_keys)
+ end
+ end
+end
diff --git a/yarn.lock b/yarn.lock
index 2b12f06abe5..c0cb00d3076 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1274,10 +1274,10 @@
resolved "https://registry.yarnpkg.com/@gitlab/svgs/-/svgs-3.72.0.tgz#5daaa7366913b52ea89439305067e030f967c8a5"
integrity sha512-VbSdwXxu9Y6NAXNFTROjZa83e2b8QeDAO7byqjJ0z+2Y3gGGXdw+HclAzz0Ns8B0+DMV5mV7dtmTlv/1xAXXYQ==
-"@gitlab/ui@^71.11.1":
- version "71.11.1"
- resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-71.11.1.tgz#8ce051fdfa6b564830b26e440fa5b394581bfbac"
- integrity sha512-59JZOwDOIl3kijD3Yr6pNUUP7wvTWMltOoLm3ySbZNPozuSUVYZqMNt/LS1s/SxMODiAgmJEoOO9p3j21P2T0A==
+"@gitlab/ui@^72.0.0":
+ version "72.0.0"
+ resolved "https://registry.yarnpkg.com/@gitlab/ui/-/ui-72.0.0.tgz#e29a9893f40bfb779b74c7ad6c2aa638dba7a267"
+ integrity sha512-OnPWWrXDFAAzXeW0COmQpcJ+6eslAb8RGp25yU0nAgF1P60DzDG/SAEWZB+X0+VSa0p6is7UhWf+XDHfEIW0ow==
dependencies:
"@floating-ui/dom" "1.2.9"
bootstrap-vue "2.23.1"