Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2023-05-02 09:18:33 +00:00
parent e14edb3ce5
commit 74da249f7e
43 changed files with 905 additions and 278 deletions

View File

@ -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

View File

@ -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'

View File

@ -1 +1 @@
3da9f535e6e0c9194e2201ef389171c84ab8c0dc
5db2d4b7c1b5f2cdb4dbf5c28b31b21e8d6f19e9

View File

@ -1 +1 @@
v15.11.0
v16.0.0-rc1

View File

@ -93,6 +93,7 @@ export default {
<board-list-header
:list="list"
:filter-params="filtersToUse"
:board-id="boardId"
@setActiveList="$emit('setActiveList', $event)"
/>
<board-list

View File

@ -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>

View File

@ -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,

View File

@ -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
}
}
}

View File

@ -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,
},
};
},
},
};

View File

@ -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}`;
};
/**

View File

@ -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>

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1 @@
b1f6b1bbfdc4f2f5df1b360fdfbeffc99ca024d65a55c2a2d2fcebe1fdc90cfb

View File

@ -0,0 +1 @@
176d8f13dc7743305a0637248aeb128e65d223d546a330869102c9d3c1714541

View File

@ -0,0 +1 @@
9c9634937e59a27f4f3e48da2d4dc6964dee50d1b043cc9d668ec5934e7b6fff

View File

@ -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;

View File

@ -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.

View File

@ -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:

View File

@ -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

View File

@ -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:

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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)

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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 });
});
});
});

View File

@ -989,4 +989,12 @@ export const updateEpicTitleResponse = {
},
};
export const updateBoardListResponse = {
data: {
updateBoardList: {
list: mockList,
},
},
};
export const DEFAULT_COLOR = '#1068bf';

View File

@ -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);
});

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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