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

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2023-05-02 12:18:33 +0300
committerGitLab Bot <gitlab-bot@gitlab.com>2023-05-02 12:18:33 +0300
commit74da249f7e22c20e144ba3c044c6bdeb5df86cd4 (patch)
tree0db7396e87d999b13cc61dff61e4f323e0a4a5d2
parente14edb3ce5fd3162d3cf10b88f58333066109f53 (diff)
Add latest changes from gitlab-org/gitlab@master
-rw-r--r--.gitlab/CODEOWNERS1
-rw-r--r--.rubocop_todo/gettext/static_identifier.yml28
-rw-r--r--GITALY_SERVER_VERSION2
-rw-r--r--GITLAB_KAS_VERSION2
-rw-r--r--app/assets/javascripts/boards/components/board_column.vue1
-rw-r--r--app/assets/javascripts/boards/components/board_list_header.vue72
-rw-r--r--app/assets/javascripts/boards/constants.js7
-rw-r--r--app/assets/javascripts/boards/graphql/client/board_toggle_collapsed.mutation.graphql9
-rw-r--r--app/assets/javascripts/graphql_shared/issuable_client.js16
-rw-r--r--app/assets/javascripts/lib/utils/datetime/date_format_utility.js7
-rw-r--r--app/assets/javascripts/super_sidebar/components/menu_section.vue4
-rw-r--r--app/assets/stylesheets/page_bundles/boards.scss1
-rw-r--r--app/models/merge_request/diff_llm_summary.rb13
-rw-r--r--config/feature_flags/development/disallow_environment_name_update.yml8
-rw-r--r--db/docs/merge_request_diff_llm_summaries.yml11
-rw-r--r--db/migrate/20230425173111_create_merge_request_diff_llm_summary.rb16
-rw-r--r--db/migrate/20230426161259_add_user_foreign_key_to_merge_request_diff_llm_summary.rb18
-rw-r--r--db/migrate/20230426161341_add_merge_request_diff_foreign_key_to_merge_request_diff_llm_summary.rb19
-rw-r--r--db/schema_migrations/202304251731111
-rw-r--r--db/schema_migrations/202304261612591
-rw-r--r--db/schema_migrations/202304261613411
-rw-r--r--db/structure.sql35
-rw-r--r--doc/administration/auth/crowd.md2
-rw-r--r--doc/ci/environments/index.md2
-rw-r--r--doc/development/performance.md19
-rw-r--r--doc/security/webhooks.md8
-rw-r--r--doc/user/group/saml_sso/troubleshooting_scim.md24
-rw-r--r--doc/user/project/releases/index.md150
-rw-r--r--doc/user/project/releases/release_evidence.md140
-rw-r--r--lib/api/environments.rb24
-rw-r--r--lib/tasks/benchmark.rake8
-rw-r--r--locale/gitlab.pot5
-rw-r--r--rubocop/cop/gettext/static_identifier.rb84
-rw-r--r--rubocop/cop/ruby_interpolation_in_translation.rb28
-rw-r--r--spec/benchmarks/banzai_benchmark.rb24
-rw-r--r--spec/factories/merge_requests_diff_llm_summary.rb10
-rw-r--r--spec/frontend/boards/components/board_list_header_spec.js70
-rw-r--r--spec/frontend/boards/mock_data.js8
-rw-r--r--spec/frontend/lib/utils/datetime/date_format_utility_spec.js15
-rw-r--r--spec/models/merge_request/diff_llm_summary_spec.rb17
-rw-r--r--spec/requests/api/environments_spec.rb50
-rw-r--r--spec/rubocop/cop/gettext/static_identifier_spec.rb174
-rw-r--r--spec/rubocop/cop/ruby_interpolation_in_translation_spec.rb48
43 files changed, 905 insertions, 278 deletions
diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS
index 7f289351fa7..5f3f060ac4f 100644
--- a/.gitlab/CODEOWNERS
+++ b/.gitlab/CODEOWNERS
@@ -952,6 +952,7 @@ lib/gitlab/checks/**
/doc/user/project/push_options.md @aqualls
/doc/user/project/quick_actions.md @msedlakjakubowski
/doc/user/project/releases/ @phillipwells
+/doc/user/project/releases/release_evidence.md @eread
/doc/user/project/remote_development/ @ashrafkhamis
/doc/user/project/repository/ @aqualls
/doc/user/project/repository/file_finder.md @ashrafkhamis
diff --git a/.rubocop_todo/gettext/static_identifier.yml b/.rubocop_todo/gettext/static_identifier.yml
new file mode 100644
index 00000000000..c330ffe1482
--- /dev/null
+++ b/.rubocop_todo/gettext/static_identifier.yml
@@ -0,0 +1,28 @@
+---
+Gettext/StaticIdentifier:
+ Details: grace period
+ Exclude:
+ - 'app/graphql/types/project_type.rb'
+ - 'app/models/integrations/apple_app_store.rb'
+ - 'app/models/integrations/confluence.rb'
+ - 'app/models/integrations/google_play.rb'
+ - 'app/services/import/fogbugz_service.rb'
+ - 'app/services/issuable_links/create_service.rb'
+ - 'app/services/issues/set_crm_contacts_service.rb'
+ - 'app/services/projects/create_from_template_service.rb'
+ - 'app/services/security/ci_configuration/base_create_service.rb'
+ - 'app/services/users/banned_user_base_service.rb'
+ - 'app/services/work_items/widgets/hierarchy_service/base_service.rb'
+ - 'ee/app/controllers/admin/licenses_controller.rb'
+ - 'ee/app/controllers/subscriptions/groups_controller.rb'
+ - 'ee/app/mailers/ee/emails/admin_notification.rb'
+ - 'ee/app/mailers/emails/namespace_storage_usage_mailer.rb'
+ - 'ee/app/models/ee/member.rb'
+ - 'ee/app/models/integrations/github.rb'
+ - 'ee/app/services/ee/projects/create_from_template_service.rb'
+ - 'ee/app/services/security/security_orchestration_policies/policy_configuration_validation_service.rb'
+ - 'ee/app/services/timebox/rollup_report_service.rb'
+ - 'ee/app/services/timebox_report_service.rb'
+ - 'ee/spec/controllers/groups/security/policies_controller_spec.rb'
+ - 'ee/spec/features/registrations/identity_verification_spec.rb'
+ - 'lib/gitlab/github_import/settings.rb'
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 7fe3c5c323f..c71ea0ca833 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-3da9f535e6e0c9194e2201ef389171c84ab8c0dc
+5db2d4b7c1b5f2cdb4dbf5c28b31b21e8d6f19e9
diff --git a/GITLAB_KAS_VERSION b/GITLAB_KAS_VERSION
index 9f28d5d9d5e..3ac9201d3ac 100644
--- a/GITLAB_KAS_VERSION
+++ b/GITLAB_KAS_VERSION
@@ -1 +1 @@
-v15.11.0
+v16.0.0-rc1
diff --git a/app/assets/javascripts/boards/components/board_column.vue b/app/assets/javascripts/boards/components/board_column.vue
index df280a03ac3..b2054d76e95 100644
--- a/app/assets/javascripts/boards/components/board_column.vue
+++ b/app/assets/javascripts/boards/components/board_column.vue
@@ -93,6 +93,7 @@ export default {
<board-list-header
:list="list"
:filter-params="filtersToUse"
+ :board-id="boardId"
@setActiveList="$emit('setActiveList', $event)"
/>
<board-list
diff --git a/app/assets/javascripts/boards/components/board_list_header.vue b/app/assets/javascripts/boards/components/board_list_header.vue
index b9592b5bd2c..1b711feb686 100644
--- a/app/assets/javascripts/boards/components/board_list_header.vue
+++ b/app/assets/javascripts/boards/components/board_list_header.vue
@@ -15,12 +15,20 @@ import { BV_HIDE_TOOLTIP } from '~/lib/utils/constants';
import { n__, s__ } from '~/locale';
import sidebarEventHub from '~/sidebar/event_hub';
import Tracking from '~/tracking';
+import { TYPE_ISSUE } from '~/issues/constants';
import { formatDate } from '~/lib/utils/datetime_utility';
import glFeatureFlagMixin from '~/vue_shared/mixins/gl_feature_flags_mixin';
import listQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql';
import setActiveBoardItemMutation from 'ee_else_ce/boards/graphql/client/set_active_board_item.mutation.graphql';
import AccessorUtilities from '~/lib/utils/accessor';
-import { inactiveId, LIST, ListType, toggleFormEventPrefix } from '../constants';
+import {
+ inactiveId,
+ LIST,
+ ListType,
+ toggleFormEventPrefix,
+ updateListQueries,
+ toggleCollapsedMutations,
+} from 'ee_else_ce/boards/constants';
import eventHub from '../eventhub';
import ItemCount from './item_count.vue';
@@ -65,6 +73,9 @@ export default {
disabled: {
default: true,
},
+ issuableType: {
+ default: TYPE_ISSUE,
+ },
isApolloBoard: {
default: false,
},
@@ -84,9 +95,13 @@ export default {
type: Object,
required: true,
},
+ boardId: {
+ type: String,
+ required: true,
+ },
},
computed: {
- ...mapState(['activeId', 'boardId']),
+ ...mapState(['activeId']),
isLoggedIn() {
return Boolean(this.currentUserId);
},
@@ -238,7 +253,7 @@ export default {
created() {
const localCollapsed = parseBoolean(localStorage.getItem(`${this.uniqueKey}.collapsed`));
if ((!this.isLoggedIn || this.isEpicBoard) && localCollapsed) {
- this.toggleListCollapsed({ listId: this.list.id, collapsed: true });
+ this.updateLocalCollapsedStatus(true);
}
},
methods: {
@@ -287,12 +302,12 @@ export default {
},
toggleExpanded() {
const collapsed = !this.list.collapsed;
- this.toggleListCollapsed({ listId: this.list.id, collapsed });
+ this.updateLocalCollapsedStatus(collapsed);
if (!this.isLoggedIn) {
- this.addToLocalStorage();
+ this.addToLocalStorage(collapsed);
} else {
- this.updateListFunction();
+ this.updateListFunction(collapsed);
}
// When expanding/collapsing, the tooltip on the caret button sometimes stays open.
@@ -304,13 +319,37 @@ export default {
property: collapsed ? 'closed' : 'open',
});
},
- addToLocalStorage() {
+ addToLocalStorage(collapsed) {
if (AccessorUtilities.canUseLocalStorage()) {
- localStorage.setItem(`${this.uniqueKey}.collapsed`, this.list.collapsed);
+ localStorage.setItem(`${this.uniqueKey}.collapsed`, collapsed);
}
},
- updateListFunction() {
- this.updateList({ listId: this.list.id, collapsed: this.list.collapsed });
+ async updateListFunction(collapsed) {
+ if (this.isApolloBoard) {
+ try {
+ await this.$apollo.mutate({
+ mutation: updateListQueries[this.issuableType].mutation,
+ variables: {
+ listId: this.list.id,
+ collapsed,
+ },
+ optimisticResponse: {
+ updateBoardList: {
+ __typename: 'UpdateBoardListPayload',
+ errors: [],
+ list: {
+ ...this.list,
+ collapsed,
+ },
+ },
+ },
+ });
+ } catch {
+ this.$emit('error');
+ }
+ } else {
+ this.updateList({ listId: this.list.id, collapsed });
+ }
},
/**
* TODO: https://gitlab.com/gitlab-org/gitlab/-/issues/344619
@@ -322,6 +361,19 @@ export default {
const due = formatDate(dueDate, 'mmm d, yyyy', true);
return `${start} - ${due}`;
},
+ updateLocalCollapsedStatus(collapsed) {
+ if (this.isApolloBoard) {
+ this.$apollo.mutate({
+ mutation: toggleCollapsedMutations[this.issuableType].mutation,
+ variables: {
+ list: this.list,
+ collapsed,
+ },
+ });
+ } else {
+ this.toggleListCollapsed({ listId: this.list.id, collapsed });
+ }
+ },
},
};
</script>
diff --git a/app/assets/javascripts/boards/constants.js b/app/assets/javascripts/boards/constants.js
index d12270e58a4..7fe89ffbb52 100644
--- a/app/assets/javascripts/boards/constants.js
+++ b/app/assets/javascripts/boards/constants.js
@@ -6,6 +6,7 @@ import updateEpicTitleMutation from '~/sidebar/queries/update_epic_title.mutatio
import destroyBoardListMutation from './graphql/board_list_destroy.mutation.graphql';
import updateBoardListMutation from './graphql/board_list_update.mutation.graphql';
+import toggleListCollapsedMutation from './graphql/client/board_toggle_collapsed.mutation.graphql';
import issueSetSubscriptionMutation from './graphql/issue_set_subscription.mutation.graphql';
import issueSetTitleMutation from './graphql/issue_set_title.mutation.graphql';
import groupBoardQuery from './graphql/group_board.query.graphql';
@@ -76,6 +77,12 @@ export const updateListQueries = {
},
};
+export const toggleCollapsedMutations = {
+ [TYPE_ISSUE]: {
+ mutation: toggleListCollapsedMutation,
+ },
+};
+
export const deleteListQueries = {
[TYPE_ISSUE]: {
mutation: destroyBoardListMutation,
diff --git a/app/assets/javascripts/boards/graphql/client/board_toggle_collapsed.mutation.graphql b/app/assets/javascripts/boards/graphql/client/board_toggle_collapsed.mutation.graphql
new file mode 100644
index 00000000000..890152989eb
--- /dev/null
+++ b/app/assets/javascripts/boards/graphql/client/board_toggle_collapsed.mutation.graphql
@@ -0,0 +1,9 @@
+#import "ee_else_ce/boards/graphql/board_list.fragment.graphql"
+
+mutation toggleListCollapsed($list: BoardList!, $collapsed: Boolean!) {
+ clientToggleListCollapsed(list: $list, collapsed: $collapsed) @client {
+ list {
+ ...BoardListFragment
+ }
+ }
+}
diff --git a/app/assets/javascripts/graphql_shared/issuable_client.js b/app/assets/javascripts/graphql_shared/issuable_client.js
index 740eb722629..ebe349b7411 100644
--- a/app/assets/javascripts/graphql_shared/issuable_client.js
+++ b/app/assets/javascripts/graphql_shared/issuable_client.js
@@ -217,6 +217,22 @@ export const resolvers = {
});
return boardItem;
},
+ clientToggleListCollapsed(_, { list = {}, collapsed = false }) {
+ return {
+ list: {
+ ...list,
+ collapsed,
+ },
+ };
+ },
+ clientToggleEpicListCollapsed(_, { list = {}, collapsed = false }) {
+ return {
+ list: {
+ ...list,
+ collapsed,
+ },
+ };
+ },
},
};
diff --git a/app/assets/javascripts/lib/utils/datetime/date_format_utility.js b/app/assets/javascripts/lib/utils/datetime/date_format_utility.js
index 04a82836f69..d54410a55e6 100644
--- a/app/assets/javascripts/lib/utils/datetime/date_format_utility.js
+++ b/app/assets/javascripts/lib/utils/datetime/date_format_utility.js
@@ -388,9 +388,10 @@ export const formatTimeAsSummary = ({ seconds, hours, days, minutes, weeks, mont
};
export const durationTimeFormatted = (duration) => {
- const date = new Date(duration * 1000);
+ const date = new Date(Math.abs(duration) * 1000);
- let hh = date.getUTCHours();
+ const days = date.getUTCDate() - 1;
+ let hh = 24 * days + date.getUTCHours();
let mm = date.getUTCMinutes();
let ss = date.getSeconds();
@@ -404,7 +405,7 @@ export const durationTimeFormatted = (duration) => {
ss = `0${ss}`;
}
- return `${hh}:${mm}:${ss}`;
+ return `${duration < 0 ? '-' : ''}${hh}:${mm}:${ss}`;
};
/**
diff --git a/app/assets/javascripts/super_sidebar/components/menu_section.vue b/app/assets/javascripts/super_sidebar/components/menu_section.vue
index 702678b9e15..86bf3837c10 100644
--- a/app/assets/javascripts/super_sidebar/components/menu_section.vue
+++ b/app/assets/javascripts/super_sidebar/components/menu_section.vue
@@ -66,7 +66,7 @@ export default {
<template>
<component :is="tag">
<button
- class="gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-py-3 gl-px-0 gl-line-height-normal gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-text-decoration-none! gl-appearance-none gl-border-0 gl-bg-transparent gl-text-left gl-w-full gl-focus--focus"
+ class="gl-rounded-base gl-relative gl-display-flex gl-align-items-center gl-py-3 gl-px-0 gl-line-height-normal gl-text-black-normal! gl-hover-bg-t-gray-a-08 gl-focus-bg-t-gray-a-08 gl-text-decoration-none! gl-appearance-none gl-border-0 gl-bg-transparent gl-text-left gl-w-full gl-focus--focus"
:class="computedLinkClasses"
data-qa-selector="menu_section_button"
:data-qa-section-name="item.title"
@@ -88,7 +88,7 @@ export default {
{{ item.title }}
</span>
- <span class="gl-flex-grow-1 gl-text-right gl-mr-3">
+ <span class="gl-flex-grow-1 gl-text-right gl-mr-3 gl-text-gray-400">
<gl-icon :name="collapseIcon" />
</span>
</button>
diff --git a/app/assets/stylesheets/page_bundles/boards.scss b/app/assets/stylesheets/page_bundles/boards.scss
index f7ab78c1bcc..8e20299a200 100644
--- a/app/assets/stylesheets/page_bundles/boards.scss
+++ b/app/assets/stylesheets/page_bundles/boards.scss
@@ -40,6 +40,7 @@
// We do this because the flow of elements isn't affected by the rotate transform, so we must ensure that a
// rotated element has square dimensions so it won't overlap with its siblings.
margin: calc(50% - 8px) 0;
+ max-width: 50vh;
transform-origin: center;
}
diff --git a/app/models/merge_request/diff_llm_summary.rb b/app/models/merge_request/diff_llm_summary.rb
new file mode 100644
index 00000000000..5e7d80712e2
--- /dev/null
+++ b/app/models/merge_request/diff_llm_summary.rb
@@ -0,0 +1,13 @@
+# rubocop:disable Style/ClassAndModuleChildren
+# frozen_string_literal: true
+
+class MergeRequest::DiffLlmSummary < ApplicationRecord
+ belongs_to :merge_request_diff
+ belongs_to :user, optional: true
+
+ validates :provider, presence: true
+ validates :content, presence: true, length: { maximum: 2056 }
+
+ enum provider: { openai: 0 }
+end
+# rubocop:enable Style/ClassAndModuleChildren
diff --git a/config/feature_flags/development/disallow_environment_name_update.yml b/config/feature_flags/development/disallow_environment_name_update.yml
new file mode 100644
index 00000000000..72b85ae569a
--- /dev/null
+++ b/config/feature_flags/development/disallow_environment_name_update.yml
@@ -0,0 +1,8 @@
+---
+name: disallow_environment_name_update
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118146
+rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/408165
+milestone: '16.0'
+type: development
+group: group::environments
+default_enabled: false
diff --git a/db/docs/merge_request_diff_llm_summaries.yml b/db/docs/merge_request_diff_llm_summaries.yml
new file mode 100644
index 00000000000..2ae4cd841e3
--- /dev/null
+++ b/db/docs/merge_request_diff_llm_summaries.yml
@@ -0,0 +1,11 @@
+---
+table_name: merge_request_diff_llm_summaries
+classes:
+- MergeRequest::DiffLlmSummary
+feature_categories:
+- code_review_workflow
+description: This is the table that stores information about the diff summaries produced
+ from different LLM's.
+introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118671
+milestone: '16.0'
+gitlab_schema: gitlab_main
diff --git a/db/migrate/20230425173111_create_merge_request_diff_llm_summary.rb b/db/migrate/20230425173111_create_merge_request_diff_llm_summary.rb
new file mode 100644
index 00000000000..f37459d764e
--- /dev/null
+++ b/db/migrate/20230425173111_create_merge_request_diff_llm_summary.rb
@@ -0,0 +1,16 @@
+# frozen_string_literal: true
+
+class CreateMergeRequestDiffLlmSummary < Gitlab::Database::Migration[2.1]
+ INDEX_NAME = "index_merge_request_diff_llm_summaries_on_mr_diff_id"
+
+ def change
+ create_table :merge_request_diff_llm_summaries do |t|
+ t.bigint :user_id, null: true, index: true
+ t.bigint :merge_request_diff_id, null: false, index:
+ { name: INDEX_NAME }
+ t.timestamps_with_timezone null: false
+ t.integer :provider, null: false, limit: 2
+ t.text :content, null: false, limit: 2056
+ end
+ end
+end
diff --git a/db/migrate/20230426161259_add_user_foreign_key_to_merge_request_diff_llm_summary.rb b/db/migrate/20230426161259_add_user_foreign_key_to_merge_request_diff_llm_summary.rb
new file mode 100644
index 00000000000..d5692b4d956
--- /dev/null
+++ b/db/migrate/20230426161259_add_user_foreign_key_to_merge_request_diff_llm_summary.rb
@@ -0,0 +1,18 @@
+# frozen_string_literal: true
+
+# See https://docs.gitlab.com/ee/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddUserForeignKeyToMergeRequestDiffLlmSummary < Gitlab::Database::Migration[2.1]
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :merge_request_diff_llm_summaries, :users, column: :user_id, on_delete: :cascade
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :merge_request_diff_llm_summaries, column: :user_id
+ end
+ end
+end
diff --git a/db/migrate/20230426161341_add_merge_request_diff_foreign_key_to_merge_request_diff_llm_summary.rb b/db/migrate/20230426161341_add_merge_request_diff_foreign_key_to_merge_request_diff_llm_summary.rb
new file mode 100644
index 00000000000..4271bd3c07c
--- /dev/null
+++ b/db/migrate/20230426161341_add_merge_request_diff_foreign_key_to_merge_request_diff_llm_summary.rb
@@ -0,0 +1,19 @@
+# frozen_string_literal: true
+
+# See https://docs.gitlab.com/ee/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class AddMergeRequestDiffForeignKeyToMergeRequestDiffLlmSummary < Gitlab::Database::Migration[2.1]
+ disable_ddl_transaction!
+
+ def up
+ add_concurrent_foreign_key :merge_request_diff_llm_summaries, :merge_request_diffs, column: :merge_request_diff_id,
+ on_delete: :cascade
+ end
+
+ def down
+ with_lock_retries do
+ remove_foreign_key :merge_request_diff_llm_summaries, column: :merge_request_diff_id
+ end
+ end
+end
diff --git a/db/schema_migrations/20230425173111 b/db/schema_migrations/20230425173111
new file mode 100644
index 00000000000..d1b86236552
--- /dev/null
+++ b/db/schema_migrations/20230425173111
@@ -0,0 +1 @@
+b1f6b1bbfdc4f2f5df1b360fdfbeffc99ca024d65a55c2a2d2fcebe1fdc90cfb \ No newline at end of file
diff --git a/db/schema_migrations/20230426161259 b/db/schema_migrations/20230426161259
new file mode 100644
index 00000000000..15cf2ef65f4
--- /dev/null
+++ b/db/schema_migrations/20230426161259
@@ -0,0 +1 @@
+176d8f13dc7743305a0637248aeb128e65d223d546a330869102c9d3c1714541 \ No newline at end of file
diff --git a/db/schema_migrations/20230426161341 b/db/schema_migrations/20230426161341
new file mode 100644
index 00000000000..07b903c06e9
--- /dev/null
+++ b/db/schema_migrations/20230426161341
@@ -0,0 +1 @@
+9c9634937e59a27f4f3e48da2d4dc6964dee50d1b043cc9d668ec5934e7b6fff \ No newline at end of file
diff --git a/db/structure.sql b/db/structure.sql
index 695768fbfab..948a85fb825 100644
--- a/db/structure.sql
+++ b/db/structure.sql
@@ -18194,6 +18194,26 @@ CREATE TABLE merge_request_diff_files (
external_diff_size integer
);
+CREATE TABLE merge_request_diff_llm_summaries (
+ id bigint NOT NULL,
+ user_id bigint,
+ merge_request_diff_id bigint NOT NULL,
+ created_at timestamp with time zone NOT NULL,
+ updated_at timestamp with time zone NOT NULL,
+ provider smallint NOT NULL,
+ content text NOT NULL,
+ CONSTRAINT check_93955f22ad CHECK ((char_length(content) <= 2056))
+);
+
+CREATE SEQUENCE merge_request_diff_llm_summaries_id_seq
+ START WITH 1
+ INCREMENT BY 1
+ NO MINVALUE
+ NO MAXVALUE
+ CACHE 1;
+
+ALTER SEQUENCE merge_request_diff_llm_summaries_id_seq OWNED BY merge_request_diff_llm_summaries.id;
+
CREATE TABLE merge_request_diffs (
id integer NOT NULL,
state character varying,
@@ -25298,6 +25318,8 @@ ALTER TABLE ONLY merge_request_diff_commit_users ALTER COLUMN id SET DEFAULT nex
ALTER TABLE ONLY merge_request_diff_details ALTER COLUMN merge_request_diff_id SET DEFAULT nextval('merge_request_diff_details_merge_request_diff_id_seq'::regclass);
+ALTER TABLE ONLY merge_request_diff_llm_summaries ALTER COLUMN id SET DEFAULT nextval('merge_request_diff_llm_summaries_id_seq'::regclass);
+
ALTER TABLE ONLY merge_request_diffs ALTER COLUMN id SET DEFAULT nextval('merge_request_diffs_id_seq'::regclass);
ALTER TABLE ONLY merge_request_metrics ALTER COLUMN id SET DEFAULT nextval('merge_request_metrics_id_seq'::regclass);
@@ -27458,6 +27480,9 @@ ALTER TABLE ONLY merge_request_diff_details
ALTER TABLE ONLY merge_request_diff_files
ADD CONSTRAINT merge_request_diff_files_pkey PRIMARY KEY (merge_request_diff_id, relative_order);
+ALTER TABLE ONLY merge_request_diff_llm_summaries
+ ADD CONSTRAINT merge_request_diff_llm_summaries_pkey PRIMARY KEY (id);
+
ALTER TABLE ONLY merge_request_diffs
ADD CONSTRAINT merge_request_diffs_pkey PRIMARY KEY (id);
@@ -31275,6 +31300,10 @@ CREATE INDEX index_merge_request_diff_details_on_verification_state ON merge_req
CREATE INDEX index_merge_request_diff_details_pending_verification ON merge_request_diff_details USING btree (verified_at NULLS FIRST) WHERE (verification_state = 0);
+CREATE INDEX index_merge_request_diff_llm_summaries_on_mr_diff_id ON merge_request_diff_llm_summaries USING btree (merge_request_diff_id);
+
+CREATE INDEX index_merge_request_diff_llm_summaries_on_user_id ON merge_request_diff_llm_summaries USING btree (user_id);
+
CREATE INDEX index_merge_request_diffs_by_id_partial ON merge_request_diffs USING btree (id) WHERE ((files_count > 0) AND ((NOT stored_externally) OR (stored_externally IS NULL)));
CREATE INDEX index_merge_request_diffs_on_external_diff ON merge_request_diffs USING btree (external_diff);
@@ -34826,6 +34855,9 @@ ALTER TABLE ONLY protected_environment_approval_rules
ALTER TABLE ONLY ci_pipeline_schedule_variables
ADD CONSTRAINT fk_41c35fda51 FOREIGN KEY (pipeline_schedule_id) REFERENCES ci_pipeline_schedules(id) ON DELETE CASCADE;
+ALTER TABLE ONLY merge_request_diff_llm_summaries
+ ADD CONSTRAINT fk_42551b9fea FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY namespace_bans
ADD CONSTRAINT fk_4275fbb1d7 FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE;
@@ -35474,6 +35506,9 @@ ALTER TABLE ONLY fork_networks
ALTER TABLE ONLY integrations
ADD CONSTRAINT fk_e8fe908a34 FOREIGN KEY (group_id) REFERENCES namespaces(id) ON DELETE CASCADE;
+ALTER TABLE ONLY merge_request_diff_llm_summaries
+ ADD CONSTRAINT fk_e98931c3cb FOREIGN KEY (merge_request_diff_id) REFERENCES merge_request_diffs(id) ON DELETE CASCADE;
+
ALTER TABLE ONLY pages_domains
ADD CONSTRAINT fk_ea2f6dfc6f FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE;
diff --git a/doc/administration/auth/crowd.md b/doc/administration/auth/crowd.md
index e403eb800d6..f89e1a00928 100644
--- a/doc/administration/auth/crowd.md
+++ b/doc/administration/auth/crowd.md
@@ -9,7 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
WARNING:
This feature was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/369117) in GitLab 15.3 and is planned for
-removal in 16.0.
+removal in 17.0.
Authenticate to GitLab using the Atlassian Crowd OmniAuth provider. Enabling
this provider also allows Crowd authentication for Git-over-https requests.
diff --git a/doc/ci/environments/index.md b/doc/ci/environments/index.md
index 577cbce2943..e48c8dffb90 100644
--- a/doc/ci/environments/index.md
+++ b/doc/ci/environments/index.md
@@ -163,7 +163,7 @@ deploy_review_app:
> - Renaming an environment by using the UI was [removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/68550) in GitLab 14.3.
> - Renaming an environment by using the API was [deprecated](https://gitlab.com/gitlab-org/gitlab/-/issues/338897) in GitLab 15.9 and is planned for removal in GitLab 16.0.
-You cannot rename an environment by using the UI, and the API method was deprecated in GitLab 15.9.
+You cannot rename an environment by using the UI, and the API method was deprecated in GitLab 15.9 and to be removed in GitLab 16.0.
To achieve the same result as renaming an environment:
diff --git a/doc/development/performance.md b/doc/development/performance.md
index 346f70e04b0..291165d8125 100644
--- a/doc/development/performance.md
+++ b/doc/development/performance.md
@@ -815,6 +815,25 @@ Make sure that you have relevant test data for your filter in the
[`spec/fixtures/markdown.md.erb`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/spec/fixtures/markdown.md.erb)
file.
+### Benchmarking specific filters
+
+A specific filter can be benchmarked by specifying the filter name as an environment variable.
+For example, to benchmark the `MarkdownFilter` use
+
+```plaintext
+FILTER=MarkdownFilter bin/rake benchmark:banzai
+```
+
+which generates the output
+
+```plaintext
+--> Benchmarking MarkdownFilter for FullPipeline
+Warming up --------------------------------------
+ Markdown 271.000 i/100ms
+Calculating -------------------------------------
+ Markdown 2.584k (±16.5%) i/s - 23.848k in 10.042503s
+```
+
## Reading from files and other data sources
Ruby offers several convenience functions that deal with file contents specifically
diff --git a/doc/security/webhooks.md b/doc/security/webhooks.md
index 81e7878c9df..774bfa106f6 100644
--- a/doc/security/webhooks.md
+++ b/doc/security/webhooks.md
@@ -70,15 +70,11 @@ Prerequisite:
## Filter requests
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/377371) in GitLab 15.10 [with a flag](../administration/feature_flags.md) named `deny_all_requests_except_allowed`. Disabled by default.
-
-FLAG:
-On self-managed GitLab, by default this feature is not available. To make it available, ask an administrator to [enable the feature flag](../administration/feature_flags.md) named `deny_all_requests_except_allowed`.
-On GitLab.com, this feature is not available.
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/377371) in GitLab 15.10.
Prerequisite:
-- You must have administrator access to the instance.
+- You must have administrator access to the GitLab instance.
To filter requests by blocking many requests:
diff --git a/doc/user/group/saml_sso/troubleshooting_scim.md b/doc/user/group/saml_sso/troubleshooting_scim.md
index 12144c7c080..33343c9b339 100644
--- a/doc/user/group/saml_sso/troubleshooting_scim.md
+++ b/doc/user/group/saml_sso/troubleshooting_scim.md
@@ -163,3 +163,27 @@ error. The error response can include a HTML result of the GitLab URL `https://g
This error is harmless and occurs because group provisioning was turned on but GitLab SCIM integration does not support
it nor require it. To remove the error, follow the instructions in the Azure configuration guide to disable the option
to [synchronize Azure Active Directory groups to AppName](scim_setup.md#configure-azure-active-directory).
+
+## Okta
+
+The following troubleshooting information is specifically for SCIM provisioned through Okta.
+
+### `Error authenticating: null` message when testing API SCIM credentials
+
+When testing the API credentials in your Okta SCIM application, you may encounter an error:
+
+```plaintext
+Error authenticating: null
+```
+
+Okta needs to be able to connect to your GitLab instance to provision or deprovision users.
+
+In your Okta SCIM application, check that the SCIM **Base URL** is correct and pointing to a valid GitLab
+SCIM API endpoint URL. Check the following documentation to find information on this URL for:
+
+- [GitLab.com groups](scim_setup.md#configure-gitlab).
+- [Self-managed GitLab instances](../../admin_area/settings/scim_setup.md#configure-gitlab).
+
+For self-managed GitLab instances, ensure that GitLab is publicly available so Okta can connect to it. If needed,
+you can [allow access to Okta IP addresses](https://help.okta.com/en-us/Content/Topics/Security/ip-address-allow-listing.htm)
+on your firewall.
diff --git a/doc/user/project/releases/index.md b/doc/user/project/releases/index.md
index f200b2b396c..e36601fd2dd 100644
--- a/doc/user/project/releases/index.md
+++ b/doc/user/project/releases/index.md
@@ -25,7 +25,7 @@ When you [create a release](#create-a-release):
- GitLab automatically archives source code and associates it with the release.
- GitLab automatically creates a JSON file that lists everything in the release,
- so you can compare and audit releases. This file is called [release evidence](#release-evidence).
+ so you can compare and audit releases. This file is called [release evidence](release_evidence.md).
When you create a release, or after, you can:
@@ -63,7 +63,7 @@ You can create a release:
- [In the Releases page](#create-a-release-in-the-releases-page).
- Using the [Releases API](../../../api/releases/index.md#create-a-release).
-We recommend creating a release as one of the last steps in your CI/CD pipeline.
+You should create a release as one of the last steps in your CI/CD pipeline.
### Create a release in the Releases page
@@ -180,7 +180,7 @@ release tag. When the `released_at` date and time has passed, the badge is autom
You can create a release in the past using either the
[Releases API](../../../api/releases/index.md#historical-releases) or the UI. When you set
a past `released_at` date, an **Historical release** badge is displayed next to
-the release tag. Due to being released in the past, [release evidence](#release-evidence)
+the release tag. Due to being released in the past, [release evidence](release_evidence.md)
is not available.
## Edit a release
@@ -254,7 +254,7 @@ Here is an example of milestones with no releases, one release, and two releases
![Milestones with and without Release associations](img/milestone_list_with_releases_v12_5.png)
NOTE:
-A subgroup's project releases cannot be associated with a supergroup's milestone. To learn
+A subgroup's project releases cannot be associated with a parent group's milestone. To learn
more, read issue #328054,
[Releases cannot be associated with a supergroup milestone](https://gitlab.com/gitlab-org/gitlab/-/issues/328054).
@@ -284,7 +284,7 @@ Deploy freezes help reduce uncertainty and risk when automating deployments.
A maintainer can set a deploy freeze window in the user interface or by using the [Freeze Periods API](../../../api/freeze_periods.md) to set a `freeze_start` and a `freeze_end`, which
are defined as [crontab](https://crontab.guru/) entries.
-If the job that's executing is within a freeze period, GitLab CI/CD creates an environment
+If the job that's executing is in a freeze period, GitLab CI/CD creates an environment
variable named `$CI_DEPLOY_FREEZE`.
To prevent the deployment job from executing, create a `rules` entry in your
@@ -317,141 +317,6 @@ complete overlapping period.
For more information, see [Deployment safety](../../../ci/environments/deployment_safety.md).
-## Release evidence
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/26019) in GitLab 12.6.
-
-Each time a release is created, GitLab takes a snapshot of data that's related to it.
-This data is saved in a JSON file and called *release evidence*. The feature
-includes test artifacts and linked milestones to facilitate
-internal processes, like external audits.
-
-To access the release evidence, on the Releases page, select the link to the JSON file that's listed
-under the **Evidence collection** heading.
-
-You can also [use the API](../../../api/releases/index.md#collect-release-evidence) to
-generate release evidence for an existing release. Because of this, each release
-can have multiple release evidence snapshots. You can view the release evidence and
-its details on the Releases page.
-
-When the issue tracker is disabled, release evidence [can't be downloaded](https://gitlab.com/gitlab-org/gitlab/-/issues/208397).
-
-Here is an example of a release evidence object:
-
-```json
-{
- "release": {
- "id": 5,
- "tag_name": "v4.0",
- "name": "New release",
- "project": {
- "id": 20,
- "name": "Project name",
- "created_at": "2019-04-14T11:12:13.940Z",
- "description": "Project description"
- },
- "created_at": "2019-06-28 13:23:40 UTC",
- "description": "Release description",
- "milestones": [
- {
- "id": 11,
- "title": "v4.0-rc1",
- "state": "closed",
- "due_date": "2019-05-12 12:00:00 UTC",
- "created_at": "2019-04-17 15:45:12 UTC",
- "issues": [
- {
- "id": 82,
- "title": "The top-right popup is broken",
- "author_name": "John Doe",
- "author_email": "john@doe.com",
- "state": "closed",
- "due_date": "2019-05-10 12:00:00 UTC"
- },
- {
- "id": 89,
- "title": "The title of this page is misleading",
- "author_name": "Jane Smith",
- "author_email": "jane@smith.com",
- "state": "closed",
- "due_date": "nil"
- }
- ]
- },
- {
- "id": 12,
- "title": "v4.0-rc2",
- "state": "closed",
- "due_date": "2019-05-30 18:30:00 UTC",
- "created_at": "2019-04-17 15:45:12 UTC",
- "issues": []
- }
- ],
- "report_artifacts": [
- {
- "url":"https://gitlab.example.com/root/project-name/-/jobs/111/artifacts/download"
- }
- ]
- }
-}
-```
-
-### Collect release evidence **(PREMIUM SELF)**
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/199065) in GitLab 12.10.
-
-When a release is created, release evidence is automatically collected. To initiate evidence collection any other time, use an [API call](../../../api/releases/index.md#collect-release-evidence). You can collect release evidence multiple times for one release.
-
-Evidence collection snapshots are visible on the Releases page, along with the timestamp the evidence was collected.
-
-### Include report artifacts as release evidence **(ULTIMATE)**
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32773) in GitLab 13.2.
-
-When you create a release, if [job artifacts](../../../ci/yaml/index.md#artifactsreports) are included in the last pipeline that ran, they are automatically included in the release as release evidence.
-
-Although job artifacts normally expire, artifacts included in release evidence do not expire.
-
-To enable job artifact collection you must specify both:
-
-1. [`artifacts:paths`](../../../ci/yaml/index.md#artifactspaths)
-1. [`artifacts:reports`](../../../ci/yaml/index.md#artifactsreports)
-
-```yaml
-ruby:
- script:
- - gem install bundler
- - bundle install
- - bundle exec rspec --format progress --format RspecJunitFormatter --out rspec.xml
- artifacts:
- paths:
- - rspec.xml
- reports:
- junit: rspec.xml
-```
-
-If the pipeline ran successfully, when you create your release, the `rspec.xml` file is saved as
-release evidence.
-
-If you [schedule release evidence collection](#schedule-release-evidence-collection),
-some artifacts may already be expired by the time of evidence collection. To avoid this you can use
-the [`artifacts:expire_in`](../../../ci/yaml/index.md#artifactsexpire_in)
-keyword. For more information, see [issue 222351](https://gitlab.com/gitlab-org/gitlab/-/issues/222351).
-
-### Schedule release evidence collection
-
-> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23697) in GitLab 12.8.
-
-In the API:
-
-- If you specify a future `released_at` date, the release becomes an **Upcoming release**
- and the evidence is collected on the date of the release. You cannot collect
- release evidence before then.
-- If you specify a past `released_at` date, the release becomes an **Historical
- release** and no evidence is collected.
-- If you do not specify a `released_at` date, release evidence is collected on the
- date the release is created.
-
## Release permissions
> Fixes to the permission model for create, update and delete actions [were introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/327505) in GitLab 14.1.
@@ -465,14 +330,15 @@ In the API:
- Users with the Guest role
have read and download access to the project releases.
This includes associated Git-tag-names, release description, author information of the releases.
- However, other repository-related information, such as [source code](release_fields.md#source-code), [release evidence](#release-evidence) are redacted.
+ However, other repository-related information, such as [source code](release_fields.md#source-code) and
+ [release evidence](release_evidence.md) are redacted.
### Publish releases without giving access to source code
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216485) in GitLab 15.6.
Releases can be made accessible to non-project members while keeping repository-related information such as
-[source code](release_fields.md#source-code) and [release evidence](#release-evidence) private. This is useful for
+[source code](release_fields.md#source-code) and [release evidence](release_evidence.md) private. Use this for
projects that use releases as a way to give access to new versions of software but do not want the source code to
be public.
diff --git a/doc/user/project/releases/release_evidence.md b/doc/user/project/releases/release_evidence.md
new file mode 100644
index 00000000000..3269bf8f44b
--- /dev/null
+++ b/doc/user/project/releases/release_evidence.md
@@ -0,0 +1,140 @@
+---
+stage: Govern
+group: Compliance
+info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/product/ux/technical-writing/#assignments
+---
+
+# Release evidence **(FREE)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/26019) in GitLab 12.6.
+
+Each time a release is created, GitLab takes a snapshot of data that's related to it.
+This data is saved in a JSON file and called *release evidence*. The feature
+includes test artifacts and linked milestones to facilitate
+internal processes, like external audits.
+
+To access the release evidence, on the Releases page, select the link to the JSON file that's listed
+under the **Evidence collection** heading.
+
+You can also [use the API](../../../api/releases/index.md#collect-release-evidence) to
+generate release evidence for an existing release. Because of this, each release
+can have multiple release evidence snapshots. You can view the release evidence and
+its details on the Releases page.
+
+When the issue tracker is disabled, release evidence [can't be downloaded](https://gitlab.com/gitlab-org/gitlab/-/issues/208397).
+
+Here is an example of a release evidence object:
+
+```json
+{
+ "release": {
+ "id": 5,
+ "tag_name": "v4.0",
+ "name": "New release",
+ "project": {
+ "id": 20,
+ "name": "Project name",
+ "created_at": "2019-04-14T11:12:13.940Z",
+ "description": "Project description"
+ },
+ "created_at": "2019-06-28 13:23:40 UTC",
+ "description": "Release description",
+ "milestones": [
+ {
+ "id": 11,
+ "title": "v4.0-rc1",
+ "state": "closed",
+ "due_date": "2019-05-12 12:00:00 UTC",
+ "created_at": "2019-04-17 15:45:12 UTC",
+ "issues": [
+ {
+ "id": 82,
+ "title": "The top-right popup is broken",
+ "author_name": "John Doe",
+ "author_email": "john@doe.com",
+ "state": "closed",
+ "due_date": "2019-05-10 12:00:00 UTC"
+ },
+ {
+ "id": 89,
+ "title": "The title of this page is misleading",
+ "author_name": "Jane Smith",
+ "author_email": "jane@smith.com",
+ "state": "closed",
+ "due_date": "nil"
+ }
+ ]
+ },
+ {
+ "id": 12,
+ "title": "v4.0-rc2",
+ "state": "closed",
+ "due_date": "2019-05-30 18:30:00 UTC",
+ "created_at": "2019-04-17 15:45:12 UTC",
+ "issues": []
+ }
+ ],
+ "report_artifacts": [
+ {
+ "url":"https://gitlab.example.com/root/project-name/-/jobs/111/artifacts/download"
+ }
+ ]
+ }
+}
+```
+
+## Collect release evidence **(PREMIUM SELF)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/199065) in GitLab 12.10.
+
+When a release is created, release evidence is automatically collected. To initiate evidence collection any other time, use an [API call](../../../api/releases/index.md#collect-release-evidence). You can collect release evidence multiple times for one release.
+
+Evidence collection snapshots are visible on the Releases page, along with the timestamp the evidence was collected.
+
+## Include report artifacts as release evidence **(ULTIMATE)**
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/32773) in GitLab 13.2.
+
+When you create a release, if [job artifacts](../../../ci/yaml/index.md#artifactsreports) are included in the last pipeline that ran, they are automatically included in the release as release evidence.
+
+Although job artifacts normally expire, artifacts included in release evidence do not expire.
+
+To enable job artifact collection you must specify both:
+
+1. [`artifacts:paths`](../../../ci/yaml/index.md#artifactspaths)
+1. [`artifacts:reports`](../../../ci/yaml/index.md#artifactsreports)
+
+```yaml
+ruby:
+ script:
+ - gem install bundler
+ - bundle install
+ - bundle exec rspec --format progress --format RspecJunitFormatter --out rspec.xml
+ artifacts:
+ paths:
+ - rspec.xml
+ reports:
+ junit: rspec.xml
+```
+
+If the pipeline ran successfully, when you create your release, the `rspec.xml` file is saved as
+release evidence.
+
+If you [schedule release evidence collection](#schedule-release-evidence-collection),
+some artifacts may already be expired by the time of evidence collection. To avoid this you can use
+the [`artifacts:expire_in`](../../../ci/yaml/index.md#artifactsexpire_in)
+keyword. For more information, see [issue 222351](https://gitlab.com/gitlab-org/gitlab/-/issues/222351).
+
+## Schedule release evidence collection
+
+> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/23697) in GitLab 12.8.
+
+In the API:
+
+- If you specify a future `released_at` date, the release becomes an **Upcoming release**
+ and the evidence is collected on the date of the release. You cannot collect
+ release evidence before then.
+- If you specify a past `released_at` date, the release becomes an **Historical
+ release** and no evidence is collected.
+- If you do not specify a `released_at` date, release evidence is collected on the
+ date the release is created.
diff --git a/lib/api/environments.rb b/lib/api/environments.rb
index 64510a9615a..0ce89e4240f 100644
--- a/lib/api/environments.rb
+++ b/lib/api/environments.rb
@@ -13,6 +13,13 @@ module API
urgency :low
MIN_SEARCH_LENGTH = 3
+ # rubocop:disable Gitlab/DocUrl
+ ENVIRONMENT_NAME_UPDATE_ERROR = <<~DESC
+ Updating environment name was deprecated in GitLab 15.9 and to be removed in GitLab 16.0.
+ For workaround, see [the documentation](https://docs.gitlab.com/ee/ci/environments/#rename-an-environment).
+ For more information, see [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/338897)
+ DESC
+ # rubocop:enable Gitlab/DocUrl
params do
requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project owned by the authenticated user'
@@ -90,8 +97,6 @@ module API
end
params do
requires :environment_id, type: Integer, desc: 'The ID of the environment'
- # TODO: disallow renaming via the API https://gitlab.com/gitlab-org/gitlab/-/issues/338897
- optional :name, type: String, desc: 'DEPRECATED: Renaming environment can lead to errors, this will be removed in 15.0'
optional :external_url, type: String, desc: 'The new URL on which this deployment is viewable'
optional :slug, absence: { message: "is automatically generated and cannot be changed" }, documentation: { hidden: true }
optional :tier, type: String, values: Environment.tiers.keys, desc: 'The tier of the new environment. Allowed values are `production`, `staging`, `testing`, `development`, and `other`'
@@ -101,8 +106,19 @@ module API
environment = user_project.environments.find(params[:environment_id])
- update_params = declared_params(include_missing: false).extract!(:name, :external_url, :tier)
- if environment.update(update_params)
+ update_params = declared_params(include_missing: false).extract!(:external_url, :tier)
+
+ # For the transition period, we implicitly extract `:name` field.
+ # This line should be removed when disallow_environment_name_update feature flag is removed.
+ update_params[:name] = params[:name] if params[:name].present?
+
+ environment.assign_attributes(update_params)
+
+ if environment.name_changed? && ::Feature.enabled?(:disallow_environment_name_update, user_project)
+ render_api_error!(ENVIRONMENT_NAME_UPDATE_ERROR, 400)
+ end
+
+ if environment.save
present environment, with: Entities::Environment, current_user: current_user
else
render_validation_error!(environment)
diff --git a/lib/tasks/benchmark.rake b/lib/tasks/benchmark.rake
index 6deafb2c351..cb95e53a0ee 100644
--- a/lib/tasks/benchmark.rake
+++ b/lib/tasks/benchmark.rake
@@ -3,9 +3,15 @@
return if Rails.env.production?
namespace :benchmark do
- desc 'Benchmark | Banzai pipeline/filters'
+ desc 'Benchmark | Banzai pipeline/filters (optionally specify FILTER=xxxxxFilter)'
RSpec::Core::RakeTask.new(:banzai) do |t|
t.pattern = 'spec/benchmarks/banzai_benchmark.rb'
+ t.rspec_opts = if ENV.key?('FILTER')
+ ['--tag specific_filter']
+ else
+ ['--tag \~specific_filter']
+ end
+
ENV['BENCHMARK'] = '1'
end
end
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index a3816f2429d..68a2ab94382 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -39885,7 +39885,10 @@ msgstr ""
msgid "SecurityOrchestration|After enabling a group-level policy, this policy automatically applies to all projects and sub-groups in this group."
msgstr ""
-msgid "SecurityOrchestration|All policies"
+msgid "SecurityOrchestration|All sources"
+msgstr ""
+
+msgid "SecurityOrchestration|All types"
msgstr ""
msgid "SecurityOrchestration|An error occurred assigning your security policy project"
diff --git a/rubocop/cop/gettext/static_identifier.rb b/rubocop/cop/gettext/static_identifier.rb
new file mode 100644
index 00000000000..9ca1c88f4b6
--- /dev/null
+++ b/rubocop/cop/gettext/static_identifier.rb
@@ -0,0 +1,84 @@
+# frozen_string_literal: true
+
+module RuboCop
+ module Cop
+ module Gettext
+ # Ensure that gettext identifiers are statically defined and not
+ # interpolated, formatted, or concatenated.
+ #
+ # @example
+ #
+ # # bad
+ # _('Hi #{name}')
+ # _('Hi %{name}' % { name: 'Luki' })
+ # _(format('Hi %{name}', name: 'Luki'))
+ #
+ # # good
+ # _('Hi %{name}') % { name: 'Luki' }
+ # format(_('Hi %{name}', name: 'Luki'))
+ #
+ # # also good
+ # var = "Hi"
+ # _(var)
+ # _(some_method_call)
+ # _(CONST)
+ class StaticIdentifier < RuboCop::Cop::Base
+ MSG = 'Ensure to pass static strings to translation method `%{method_name}(...)`.'
+
+ # Number of parameters to check for translation methods.
+ PARAMETERS_TO_CHECK = {
+ _: 1,
+ s_: 1,
+ N_: 1,
+ n_: 2
+ }.freeze
+
+ # RuboCop-specific optimization for `on_send`.
+ RESTRICT_ON_SEND = PARAMETERS_TO_CHECK.keys.freeze
+
+ DENIED_METHOD_CALLS = %i[% format + concat].freeze
+
+ def on_send(node)
+ method_name = node.method_name
+ arguments = node.arguments
+
+ each_invalid_argument(method_name, arguments) do |argument_node|
+ message = format(MSG, method_name: method_name)
+
+ add_offense(argument_node || node, message: message)
+ end
+ end
+
+ private
+
+ def each_invalid_argument(method_name, argument_nodes)
+ number = PARAMETERS_TO_CHECK.fetch(method_name)
+
+ argument_nodes.take(number).each do |argument_node|
+ yield argument_node unless valid_argument?(argument_node)
+ end
+ end
+
+ def valid_argument?(node)
+ return false unless node
+
+ basic_type?(node) || multiline_string?(node) || allowed_method_call?(node)
+ end
+
+ def basic_type?(node)
+ node.str_type? || node.lvar_type? || node.const_type?
+ end
+
+ def multiline_string?(node)
+ node.dstr_type? && node.children.all?(&:str_type?)
+ end
+
+ def allowed_method_call?(node)
+ return false unless node.send_type?
+
+ !DENIED_METHOD_CALLS.include?(node.method_name) # rubocop:disable Rails/NegateInclude
+ end
+ end
+ end
+ end
+end
diff --git a/rubocop/cop/ruby_interpolation_in_translation.rb b/rubocop/cop/ruby_interpolation_in_translation.rb
deleted file mode 100644
index fec550bf7c6..00000000000
--- a/rubocop/cop/ruby_interpolation_in_translation.rb
+++ /dev/null
@@ -1,28 +0,0 @@
-# frozen_string_literal: true
-
-module RuboCop
- module Cop
- class RubyInterpolationInTranslation < RuboCop::Cop::Base
- MSG = "Don't use ruby interpolation \#{} inside translated strings, instead use \%{}"
-
- TRANSLATION_METHODS = ':_ :s_ :N_ :n_'
-
- def_node_matcher :translation_method?, <<~PATTERN
- (send nil? {#{TRANSLATION_METHODS}} $dstr ...)
- PATTERN
-
- def_node_matcher :plural_translation_method?, <<~PATTERN
- (send nil? :n_ str $dstr ...)
- PATTERN
-
- def on_send(node)
- interpolation = translation_method?(node) || plural_translation_method?(node)
- return unless interpolation
-
- interpolation.descendants.each do |possible_violation|
- add_offense(possible_violation, message: MSG) if possible_violation.type != :str
- end
- end
- end
- end
-end
diff --git a/spec/benchmarks/banzai_benchmark.rb b/spec/benchmarks/banzai_benchmark.rb
index 7a60825c1e6..45f45bcc8dd 100644
--- a/spec/benchmarks/banzai_benchmark.rb
+++ b/spec/benchmarks/banzai_benchmark.rb
@@ -16,8 +16,15 @@ require 'benchmark/ips'
# or
# rake benchmark:banzai
#
+# A specific filter can also be benchmarked by using the `FILTER`
+# environment variable.
+#
+# BENCHMARK=1 FILTER=MathFilter rspec spec/benchmarks/banzai_benchmark.rb --tag specific_filter
+# or
+# FILTER=MathFilter rake benchmark:banzai
+#
# rubocop: disable RSpec/TopLevelDescribePath
-RSpec.describe 'GitLab Markdown Benchmark', :aggregate_failures do
+RSpec.describe 'GitLab Markdown Benchmark', :aggregate_failures, feature_category: :team_planning do
include MarkupHelper
let_it_be(:feature) { MarkdownFeature.new }
@@ -83,9 +90,15 @@ RSpec.describe 'GitLab Markdown Benchmark', :aggregate_failures do
benchmark_pipeline_filters(:plain_markdown)
end
- it 'benchmarks specified filters in the FullPipeline' do
- filter_klass_list = [Banzai::Filter::MathFilter]
- benchmark_pipeline_filters(:full, filter_klass_list)
+ it 'benchmarks specified filters in the FullPipeline', :specific_filter do
+ begin
+ filter = ENV['FILTER'] || 'MarkdownFilter'
+ filter_klass = "Banzai::Filter::#{filter}".constantize
+ rescue NameError
+ raise 'Incorrect filter specified. Correct example: FILTER=MathFilter'
+ end
+
+ benchmark_pipeline_filters(:full, [filter_klass])
end
end
@@ -114,7 +127,8 @@ RSpec.describe 'GitLab Markdown Benchmark', :aggregate_failures do
pipeline = Banzai::Pipeline[pipeline_type]
filter_source = build_filter_text(pipeline, markdown_text)
- puts "\n--> Benchmarking #{pipeline.name.demodulize} filters\n"
+ filter_msg = filter_klass_list ? filter_klass_list.first.name.demodulize : 'all filters'
+ puts "\n--> Benchmarking #{filter_msg} for #{pipeline.name.demodulize}\n"
Benchmark.ips do |x|
x.config(time: 10, warmup: 2)
diff --git a/spec/factories/merge_requests_diff_llm_summary.rb b/spec/factories/merge_requests_diff_llm_summary.rb
new file mode 100644
index 00000000000..c72ce97efcb
--- /dev/null
+++ b/spec/factories/merge_requests_diff_llm_summary.rb
@@ -0,0 +1,10 @@
+# frozen_string_literal: true
+
+FactoryBot.define do
+ factory :merge_request_diff_llm_summary, class: 'MergeRequest::DiffLlmSummary' do
+ association :user, factory: :user
+ association :merge_request_diff, factory: :merge_request_diff
+ provider { 0 }
+ content { 'test' }
+ end
+end
diff --git a/spec/frontend/boards/components/board_list_header_spec.js b/spec/frontend/boards/components/board_list_header_spec.js
index 0f91d2315cf..d4489b3c535 100644
--- a/spec/frontend/boards/components/board_list_header_spec.js
+++ b/spec/frontend/boards/components/board_list_header_spec.js
@@ -4,8 +4,13 @@ import VueApollo from 'vue-apollo';
import Vuex from 'vuex';
import createMockApollo from 'helpers/mock_apollo_helper';
import { shallowMountExtended } from 'helpers/vue_test_utils_helper';
-import { boardListQueryResponse, mockLabelList } from 'jest/boards/mock_data';
+import {
+ boardListQueryResponse,
+ mockLabelList,
+ updateBoardListResponse,
+} from 'jest/boards/mock_data';
import BoardListHeader from '~/boards/components/board_list_header.vue';
+import updateBoardListMutation from '~/boards/graphql/board_list_update.mutation.graphql';
import { ListType } from '~/boards/constants';
import listQuery from 'ee_else_ce/boards/graphql/board_lists_deferred.query.graphql';
@@ -19,6 +24,8 @@ describe('Board List Header Component', () => {
const updateListSpy = jest.fn();
const toggleListCollapsedSpy = jest.fn();
+ const mockClientToggleListCollapsedResolver = jest.fn();
+ const updateListHandler = jest.fn().mockResolvedValue(updateBoardListResponse);
afterEach(() => {
fakeApollo = null;
@@ -34,7 +41,7 @@ describe('Board List Header Component', () => {
listQueryHandler = jest.fn().mockResolvedValue(boardListQueryResponse()),
injectedProps = {},
} = {}) => {
- const boardId = '1';
+ const boardId = 'gid://gitlab/Board/1';
const listMock = {
...mockLabelList,
@@ -58,8 +65,17 @@ describe('Board List Header Component', () => {
state: {},
actions: { updateList: updateListSpy, toggleListCollapsed: toggleListCollapsedSpy },
});
-
- fakeApollo = createMockApollo([[listQuery, listQueryHandler]]);
+ fakeApollo = createMockApollo(
+ [
+ [listQuery, listQueryHandler],
+ [updateBoardListMutation, updateListHandler],
+ ],
+ {
+ Mutation: {
+ clientToggleListCollapsed: mockClientToggleListCollapsedResolver,
+ },
+ },
+ );
wrapper = shallowMountExtended(BoardListHeader, {
apolloProvider: fakeApollo,
@@ -67,9 +83,9 @@ describe('Board List Header Component', () => {
propsData: {
list: listMock,
filterParams: {},
+ boardId,
},
provide: {
- boardId,
weightFeatureAvailable: false,
currentUserId,
isEpicBoard: false,
@@ -191,7 +207,9 @@ describe('Board List Header Component', () => {
await nextTick();
expect(updateListSpy).not.toHaveBeenCalled();
- expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.collapsed`)).toBe(String(isCollapsed()));
+ expect(localStorage.getItem(`${wrapper.vm.uniqueKey}.collapsed`)).toBe(
+ String(!isCollapsed()),
+ );
});
});
@@ -214,4 +232,44 @@ describe('Board List Header Component', () => {
expect(findTitle().classes()).toContain('gl-cursor-grab');
});
});
+
+ describe('Apollo boards', () => {
+ beforeEach(async () => {
+ createComponent({ listType: ListType.label, injectedProps: { isApolloBoard: true } });
+ await nextTick();
+ });
+
+ it('set active board item on client when clicking on card', async () => {
+ findCaret().vm.$emit('click');
+ await nextTick();
+
+ expect(mockClientToggleListCollapsedResolver).toHaveBeenCalledWith(
+ {},
+ {
+ list: mockLabelList,
+ collapsed: true,
+ },
+ expect.anything(),
+ expect.anything(),
+ );
+ });
+
+ it('does not call update list mutation when user is not logged in', async () => {
+ createComponent({ currentUserId: null, injectedProps: { isApolloBoard: true } });
+
+ findCaret().vm.$emit('click');
+ await nextTick();
+
+ expect(updateListHandler).not.toHaveBeenCalled();
+ });
+
+ it('calls update list mutation when user is logged in', async () => {
+ createComponent({ currentUserId: 1, injectedProps: { isApolloBoard: true } });
+
+ findCaret().vm.$emit('click');
+ await nextTick();
+
+ expect(updateListHandler).toHaveBeenCalledWith({ listId: mockLabelList.id, collapsed: true });
+ });
+ });
});
diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js
index 832e24d477b..d9baf88c624 100644
--- a/spec/frontend/boards/mock_data.js
+++ b/spec/frontend/boards/mock_data.js
@@ -989,4 +989,12 @@ export const updateEpicTitleResponse = {
},
};
+export const updateBoardListResponse = {
+ data: {
+ updateBoardList: {
+ list: mockList,
+ },
+ },
+};
+
export const DEFAULT_COLOR = '#1068bf';
diff --git a/spec/frontend/lib/utils/datetime/date_format_utility_spec.js b/spec/frontend/lib/utils/datetime/date_format_utility_spec.js
index a83b0ed9fbe..6b515e9f96a 100644
--- a/spec/frontend/lib/utils/datetime/date_format_utility_spec.js
+++ b/spec/frontend/lib/utils/datetime/date_format_utility_spec.js
@@ -136,11 +136,16 @@ describe('formatTimeAsSummary', () => {
describe('durationTimeFormatted', () => {
it.each`
- duration | expectedOutput
- ${87} | ${'00:01:27'}
- ${141} | ${'00:02:21'}
- ${12} | ${'00:00:12'}
- ${60} | ${'00:01:00'}
+ duration | expectedOutput
+ ${0} | ${'00:00:00'}
+ ${12} | ${'00:00:12'}
+ ${60} | ${'00:01:00'}
+ ${60 + 27} | ${'00:01:27'}
+ ${120 + 21} | ${'00:02:21'}
+ ${4 * 60 * 60 + 25 * 60 + 37} | ${'04:25:37'}
+ ${35 * 60 * 60 + 3 * 60 + 7} | ${'35:03:07'}
+ ${-60} | ${'-00:01:00'}
+ ${-(35 * 60 * 60 + 3 * 60 + 7)} | ${'-35:03:07'}
`('returns $expectedOutput when provided $duration', ({ duration, expectedOutput }) => {
expect(utils.durationTimeFormatted(duration)).toBe(expectedOutput);
});
diff --git a/spec/models/merge_request/diff_llm_summary_spec.rb b/spec/models/merge_request/diff_llm_summary_spec.rb
new file mode 100644
index 00000000000..a94adae9fa5
--- /dev/null
+++ b/spec/models/merge_request/diff_llm_summary_spec.rb
@@ -0,0 +1,17 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+RSpec.describe ::MergeRequest::DiffLlmSummary, feature_category: :code_review_workflow do
+ let_it_be_with_reload(:project) { create(:project, :repository) }
+
+ subject(:merge_request_diff_llm_summary) { build(:merge_request_diff_llm_summary) }
+
+ describe 'associations' do
+ it { is_expected.to belong_to(:merge_request_diff) }
+ it { is_expected.to belong_to(:user).optional }
+ it { is_expected.to validate_presence_of(:content) }
+ it { is_expected.to validate_length_of(:content).is_at_most(2056) }
+ it { is_expected.to validate_presence_of(:provider) }
+ end
+end
diff --git a/spec/requests/api/environments_spec.rb b/spec/requests/api/environments_spec.rb
index 6164555ad19..eb1e02de4f1 100644
--- a/spec/requests/api/environments_spec.rb
+++ b/spec/requests/api/environments_spec.rb
@@ -229,17 +229,24 @@ RSpec.describe API::Environments, feature_category: :continuous_delivery do
end
describe 'PUT /projects/:id/environments/:environment_id' do
- it 'returns a 200 if name and external_url are changed' do
+ it 'returns a 200 if external_url is changed' do
url = 'https://mepmep.whatever.ninja'
put api("/projects/#{project.id}/environments/#{environment.id}", user),
- params: { name: 'Mepmep', external_url: url }
+ params: { external_url: url }
expect(response).to have_gitlab_http_status(:ok)
expect(response).to match_response_schema('public_api/v4/environment')
- expect(json_response['name']).to eq('Mepmep')
expect(json_response['external_url']).to eq(url)
end
+ it 'returns a 400 if name is changed' do
+ put api("/projects/#{project.id}/environments/#{environment.id}", user),
+ params: { name: 'Mepmep' }
+
+ expect(response).to have_gitlab_http_status(:bad_request)
+ expect(json_response['message']).to eq(described_class::ENVIRONMENT_NAME_UPDATE_ERROR)
+ end
+
it 'returns a 200 if tier is changed' do
put api("/projects/#{project.id}/environments/#{environment.id}", user),
params: { tier: 'production' }
@@ -258,21 +265,38 @@ RSpec.describe API::Environments, feature_category: :continuous_delivery do
expect(json_response["error"]).to eq("slug is automatically generated and cannot be changed")
end
- it "won't update the external_url if only the name is passed" do
- url = environment.external_url
- put api("/projects/#{project.id}/environments/#{environment.id}", user),
- params: { name: 'Mepmep' }
-
- expect(response).to have_gitlab_http_status(:ok)
- expect(json_response['name']).to eq('Mepmep')
- expect(json_response['external_url']).to eq(url)
- end
-
it 'returns a 404 if the environment does not exist' do
put api("/projects/#{project.id}/environments/#{non_existing_record_id}", user)
expect(response).to have_gitlab_http_status(:not_found)
end
+
+ context 'when disallow_environment_name_update feature flag is disabled' do
+ before do
+ stub_feature_flags(disallow_environment_name_update: false)
+ end
+
+ it 'returns a 200 if name and external_url are changed' do
+ url = 'https://mepmep.whatever.ninja'
+ put api("/projects/#{project.id}/environments/#{environment.id}", user),
+ params: { name: 'Mepmep', external_url: url }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(response).to match_response_schema('public_api/v4/environment')
+ expect(json_response['name']).to eq('Mepmep')
+ expect(json_response['external_url']).to eq(url)
+ end
+
+ it "won't update the external_url if only the name is passed" do
+ url = environment.external_url
+ put api("/projects/#{project.id}/environments/#{environment.id}", user),
+ params: { name: 'Mepmep' }
+
+ expect(response).to have_gitlab_http_status(:ok)
+ expect(json_response['name']).to eq('Mepmep')
+ expect(json_response['external_url']).to eq(url)
+ end
+ end
end
describe 'DELETE /projects/:id/environments/:environment_id' do
diff --git a/spec/rubocop/cop/gettext/static_identifier_spec.rb b/spec/rubocop/cop/gettext/static_identifier_spec.rb
new file mode 100644
index 00000000000..a0c15d8ad48
--- /dev/null
+++ b/spec/rubocop/cop/gettext/static_identifier_spec.rb
@@ -0,0 +1,174 @@
+# frozen_string_literal: true
+
+require 'rubocop_spec_helper'
+require 'rspec-parameterized'
+
+require_relative '../../../../rubocop/cop/gettext/static_identifier'
+
+RSpec.describe RuboCop::Cop::Gettext::StaticIdentifier, feature_category: :internationalization do
+ describe '#_()' do
+ it 'does not flag correct use' do
+ expect_no_offenses(<<~'RUBY')
+ _('Hello')
+ _('Hello #{name}')
+
+ _('Hello %{name}') % { name: name }
+ format(_('Hello %{name}') % { name: name })
+
+ _('Hello' \
+ 'Multiline')
+ _('Hello' \
+ 'Multiline %{name}') % { name: name }
+
+ var = "Hello"
+ _(var)
+ _(method_name)
+ list.each { |item| _(item) }
+ _(CONST)
+ RUBY
+ end
+
+ it 'flags incorrect use' do
+ expect_offense(<<~'RUBY')
+ _('Hello' + ' concat')
+ ^^^^^^^^^^^^^^^^^^^ Ensure to pass static strings to translation method `_(...)`.
+ _('Hello'.concat(' concat'))
+ ^^^^^^^^^^^^^^^^^^^^^^^^^ Ensure to pass static strings to translation method `_(...)`.
+ _("Hello #{name}")
+ ^^^^^^^^^^^^^^^ Ensure to pass static strings to translation method `_(...)`.
+ _('Hello %{name}' % { name: name })
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Ensure to pass static strings to translation method `_(...)`.
+ _(format('Hello %{name}') % { name: name })
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Ensure to pass static strings to translation method `_(...)`.
+ RUBY
+ end
+ end
+
+ describe '#N_()' do
+ it 'does not flag correct use' do
+ expect_no_offenses(<<~'RUBY')
+ N_('Hello')
+ N_('Hello #{name}')
+ N_('Hello %{name}') % { name: name }
+ format(_('Hello %{name}') % { name: name })
+
+ N_('Hello' \
+ 'Multiline')
+
+ var = "Hello"
+ N_(var)
+ N_(method_name)
+ list.each { |item| N_(item) }
+ N_(CONST)
+ RUBY
+ end
+
+ it 'flags incorrect use' do
+ expect_offense(<<~'RUBY')
+ N_('Hello' + ' concat')
+ ^^^^^^^^^^^^^^^^^^^ Ensure to pass static strings to translation method `N_(...)`.
+ N_("Hello #{name}")
+ ^^^^^^^^^^^^^^^ Ensure to pass static strings to translation method `N_(...)`.
+ N_('Hello %{name}' % { name: name })
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Ensure to pass static strings to translation method `N_(...)`.
+ N_('Hello' \
+ ^^^^^^^^^ Ensure to pass static strings to translation method `N_(...)`.
+ 'Multiline %{name}' % { name: name })
+ N_(format('Hello %{name}') % { name: name })
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Ensure to pass static strings to translation method `N_(...)`.
+ RUBY
+ end
+ end
+
+ describe '#s_()' do
+ it 'does not flag correct use' do
+ expect_no_offenses(<<~'RUBY')
+ s_('World|Hello')
+ s_('World|Hello #{name}')
+ s_('World|Hello %{name}') % { name: name }
+ format(s_('World|Hello %{name}') % { name: name })
+
+ s_('World|Hello' \
+ 'Multiline')
+
+ var = "Hello"
+ s_(var)
+ s_(method_name)
+ list.each { |item| s_(item) }
+ s_(CONST)
+ RUBY
+ end
+
+ it 'flags incorrect use' do
+ expect_offense(<<~'RUBY')
+ s_("World|Hello #{name}")
+ ^^^^^^^^^^^^^^^^^^^^^ Ensure to pass static strings to translation method `s_(...)`.
+ s_('World|Hello' + ' concat')
+ ^^^^^^^^^^^^^^^^^^^^^^^^^ Ensure to pass static strings to translation method `s_(...)`.
+ s_('World|Hello %{name}' % { name: name })
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Ensure to pass static strings to translation method `s_(...)`.
+ s_('World|Hello' \
+ ^^^^^^^^^^^^^^^ Ensure to pass static strings to translation method `s_(...)`.
+ 'Multiline %{name}' % { name: name })
+ s_(format('World|Hello %{name}') % { name: name })
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Ensure to pass static strings to translation method `s_(...)`.
+ RUBY
+ end
+ end
+
+ describe '#n_()' do
+ it 'does not flag correct use' do
+ expect_no_offenses(<<~'RUBY')
+ n_('Hello', 'Hellos', 2)
+ n_('Hello', 'Hellos', count)
+
+ n_('Hello' ' concat', 'Hellos', 2)
+ n_('Hello', 'Hello' 's', 2)
+
+ n_('Hello %{name}', 'Hellos %{name}', 2) % { name: name }
+ format(n_('Hello %{name}', 'Hellos %{name}', count) % { name: name })
+
+ n_('Hello', 'Hellos' \
+ 'Multiline', 2)
+
+ n_('Hello' \
+ 'Multiline', 'Hellos', 2)
+
+ n_('Hello' \
+ 'Multiline %{name}', 'Hellos %{name}', 2) % { name: name }
+
+ var = "Hello"
+ n_(var, var, 1)
+ n_(method_name, method_name, count)
+ list.each { |item| n_(item, item, 2) }
+ n_(CONST, CONST, 2)
+ RUBY
+ end
+
+ it 'flags incorrect use' do
+ expect_offense(<<~'RUBY')
+ n_('Hello' + ' concat', 'Hellos', 2)
+ ^^^^^^^^^^^^^^^^^^^ Ensure to pass static strings to translation method `n_(...)`.
+ n_('Hello', 'Hello' + 's', 2)
+ ^^^^^^^^^^^^^ Ensure to pass static strings to translation method `n_(...)`.
+ n_("Hello #{name}", "Hellos", 2)
+ ^^^^^^^^^^^^^^^ Ensure to pass static strings to translation method `n_(...)`.
+ n_('Hello %{name}' % { name: name }, 'Hellos', 2)
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Ensure to pass static strings to translation method `n_(...)`.
+ n_('Hello' \
+ ^^^^^^^^^ Ensure to pass static strings to translation method `n_(...)`.
+ 'Multiline %{name}' % { name: name }, 'Hellos %{name}', 2)
+ n_('Hello', format('Hellos %{name}') % { name: name }, count)
+ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Ensure to pass static strings to translation method `n_(...)`.
+ RUBY
+ end
+ end
+
+ describe 'edge cases' do
+ it 'does not flag' do
+ expect_no_offenses(<<~RUBY)
+ n_(s_('World|Hello'), s_('World|Hellos'), 2)
+ RUBY
+ end
+ end
+end
diff --git a/spec/rubocop/cop/ruby_interpolation_in_translation_spec.rb b/spec/rubocop/cop/ruby_interpolation_in_translation_spec.rb
deleted file mode 100644
index b687e91601c..00000000000
--- a/spec/rubocop/cop/ruby_interpolation_in_translation_spec.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-# frozen_string_literal: true
-
-require 'rubocop_spec_helper'
-
-require_relative '../../../rubocop/cop/ruby_interpolation_in_translation'
-
-# Disabling interpolation check as we deliberately want to have #{} in strings.
-# rubocop:disable Lint/InterpolationCheck
-RSpec.describe RuboCop::Cop::RubyInterpolationInTranslation do
- let(:msg) { "Don't use ruby interpolation \#{} inside translated strings, instead use %{}" }
-
- it 'does not add an offense for a regular messages' do
- expect_no_offenses('_("Hello world")')
- end
-
- it 'adds the correct offense when using interpolation in a string' do
- expect_offense(<<~CODE)
- _("Hello \#{world}")
- ^^^^^ #{msg}
- ^^^^^^^^ #{msg}
- CODE
- end
-
- it 'detects when using a ruby interpolation in the first argument of a pluralized string' do
- expect_offense(<<~CODE)
- n_("Hello \#{world}", "Hello world")
- ^^^^^ #{msg}
- ^^^^^^^^ #{msg}
- CODE
- end
-
- it 'detects when using a ruby interpolation in the second argument of a pluralized string' do
- expect_offense(<<~CODE)
- n_("Hello world", "Hello \#{world}")
- ^^^^^ #{msg}
- ^^^^^^^^ #{msg}
- CODE
- end
-
- it 'detects when using interpolation in a namespaced translation' do
- expect_offense(<<~CODE)
- s_("Hello|\#{world}")
- ^^^^^ #{msg}
- ^^^^^^^^ #{msg}
- CODE
- end
-end
-# rubocop:enable Lint/InterpolationCheck