Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot
2025-07-03 00:18:55 +00:00
parent a3271972b5
commit f95337d05e
14 changed files with 216 additions and 48 deletions

View File

@ -24,11 +24,10 @@ module Mutations
discussion_id = nil
if gid = args[:discussion_id]
discussion = GitlabSchema.find_by_gid(gid)
discussion_id = ::GitlabSchema.parse_gid(gid, expected_type: ::Discussion).model_id
discussion = noteable.notes.find_discussion(discussion_id)
authorize_discussion!(discussion)
discussion_id = discussion.id
end
super.merge({

View File

@ -17,6 +17,7 @@ module WorkItems
@target_noteable = target_noteable
@source_parent = source_noteable.resource_parent
@target_parent = target_noteable.resource_parent
@new_discussion_ids = {}
end
def execute
@ -40,7 +41,8 @@ module WorkItems
private
attr_reader :current_user, :source_noteable, :target_noteable, :source_parent, :target_parent
attr_reader :current_user, :source_noteable, :target_noteable, :source_parent, :target_parent,
:new_discussion_ids
def copy_notes_emoji(notes_ids_map)
notes_emoji = ::AwardEmoji.by_awardable('Note', notes_ids_map.keys)
@ -79,19 +81,26 @@ module WorkItems
ids.zip(allocated_ids).to_h
end
# rubocop: disable Metrics/AbcSize -- Despite being long, this method is straightforward.
def new_notes(notes_batch, notes_ids_map)
notes_batch.map do |note|
new_discussion_ids[note.discussion_id] ||= Note.new(
noteable_id: target_noteable.id,
noteable_type: target_noteable.class.base_class
).discussion_id
note.attributes.tap do |attrs|
attrs['id'] = notes_ids_map[note.id]
attrs['noteable_id'] = target_noteable.id
# we want this if we want to use this also to copy notes when promoting issue to epic
attrs['noteable_type'] = target_noteable.class.base_class
attrs['discussion_id'] = new_discussion_ids[note.discussion_id]
# need to use `try` to be able to handle Issue model and legacy Epic model instances
attrs['project_id'] = target_noteable.try(:project_id)
attrs['namespace_id'] = target_noteable.try(:namespace_id) || target_noteable.try(:group_id)
attrs['imported_from'] = 'none' # maintaining current copy notes implementation
# this data is not changed, but it is being serialized and we need it deserialized for bulk inserts
# this data is not changed, but it is being serialized, and we need it deserialized for bulk inserts
attrs['position'] = note.attributes_before_type_cast['position']
attrs['original_position'] = note.attributes_before_type_cast['original_position']
attrs['change_position'] = note.attributes_before_type_cast['change_position']
@ -104,6 +113,7 @@ module WorkItems
end
end
end
# rubocop: enable Metrics/AbcSize
def new_notes_emoji(notes_emoji, notes_ids_map)
notes_emoji.map do |note_emoji|

View File

@ -1,6 +1,6 @@
---
name: api_request_access_with_scope
description: A susbset of API requests authenticated by a token with an audited scope
description: A subset of API requests authenticated by a token with an audited scope
introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/499461
introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/172548
feature_category: duo_workflow

View File

@ -10097,6 +10097,25 @@ Input type: `PromoteToEpicInput`
| <a id="mutationpromotetoepicerrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during the mutation. |
| <a id="mutationpromotetoepicissue"></a>`issue` | [`Issue`](#issue) | Issue after mutation. |
### `Mutation.refreshFindingTokenStatus`
Input type: `RefreshFindingTokenStatusInput`
#### Arguments
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationrefreshfindingtokenstatusclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationrefreshfindingtokenstatusvulnerabilityid"></a>`vulnerabilityId` | [`VulnerabilityID!`](#vulnerabilityid) | Global ID of the Vulnerability whose token status should be refreshed. |
#### Fields
| Name | Type | Description |
| ---- | ---- | ----------- |
| <a id="mutationrefreshfindingtokenstatusclientmutationid"></a>`clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. |
| <a id="mutationrefreshfindingtokenstatuserrors"></a>`errors` | [`[String!]!`](#string) | Errors encountered during the mutation. |
| <a id="mutationrefreshfindingtokenstatusfindingtokenstatus"></a>`findingTokenStatus` | [`VulnerabilityFindingTokenStatus`](#vulnerabilityfindingtokenstatus) | Updated token status record for the given finding. |
### `Mutation.refreshStandardsAdherenceChecks`
Input type: `RefreshStandardsAdherenceChecksInput`

View File

@ -294,11 +294,11 @@ Audit event types belong to the following product categories.
|:----------|:---------------------|:------------------|:--------------|:------|
| [`project_feature_metrics_dashboard_access_level_updated`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/106919) | A project's metrics dashboard access level setting is updated | {{< icon name="check-circle" >}} Yes | GitLab [15.7](https://gitlab.com/gitlab-org/gitlab/-/issues/369289) | Project |
### GitLab Duo Workflow
### GitLab Duo Agent Platform
| Type name | Event triggered when | Saved to database | Introduced in | Scope |
|:----------|:---------------------|:------------------|:--------------|:------|
| [`api_request_access_with_scope`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/172548) | A susbset of API requests authenticated by a token with an audited scope | {{< icon name="check-circle" >}} Yes | GitLab [17.7](https://gitlab.com/gitlab-org/gitlab/-/issues/499461) | User |
| [`api_request_access_with_scope`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/172548) | A subset of API requests authenticated by a token with an audited scope | {{< icon name="check-circle" >}} Yes | GitLab [17.7](https://gitlab.com/gitlab-org/gitlab/-/issues/499461) | User |
### Dynamic application security testing

View File

@ -99,12 +99,20 @@ the immediate parent group.
## View groups
To explore all public groups you are a member of:
To explore all public or internal groups:
1. On the left sidebar, select **Search or go to**.
1. Select **View all my groups**.
1. In the upper right, select **Explore groups**.
## View groups you are a member of
{{< history >}}
- **Member** tab [introduced](https://gitlab.com/groups/gitlab-org/-/epics/13781) in GitLab 18.2 [with a flag](../../administration/feature_flags/_index.md) named `your_work_groups_vue`. Disabled by default.
{{< /history >}}
To view groups where you have direct or indirect membership:
1. On the left sidebar, select **Search or go to**.
@ -115,6 +123,8 @@ This page shows groups that you are a member of through:
- Membership of a subgroup's parent group.
- Direct or inherited membership of a project in the group or subgroup.
If the `your_work_groups_vue` feature flag is enabled, groups that you are a member of appear in the **Member** tab.
## View a group
{{< history >}}
@ -270,6 +280,12 @@ the deletion job will instead restore and unarchive the group, so the group will
### View groups pending deletion
{{< history >}}
- **Inactive** tab [introduced](https://gitlab.com/groups/gitlab-org/-/epics/13781) in GitLab 18.2 [with a flag](../../administration/feature_flags/_index.md) named `your_work_groups_vue`. Disabled by default.
{{< /history >}}
To view a list of the subgroups that are pending deletion in a group:
1. On the left sidebar, select **Search or go to** and find your group.
@ -277,6 +293,8 @@ To view a list of the subgroups that are pending deletion in a group:
Groups that are marked for deletion are labeled **Pending deletion**.
If the `your_work_groups_vue` feature flag is enabled, groups marked for deletion appear in the **Inactive** tab.
## Delete a group immediately
{{< history >}}

View File

@ -33,7 +33,13 @@ You can use Docker commands to build and push container images to your container
docker push registry.example.com/group/project/image
```
## Configure your `.gitlab-ci.yml` file
## Use GitLab CI/CD
You can use [GitLab CI/CD](../../../ci/_index.md) to build and push container images to the
Container Registry. You can use CI/CD to test, build, and deploy your project from the container
image you created.
### Configure your `.gitlab-ci.yml` file
You can configure your `.gitlab-ci.yml` file to build and push container images to the container registry.
@ -50,19 +56,26 @@ You can configure your `.gitlab-ci.yml` file to build and push container images
- Don't build directly to the `latest` tag because multiple jobs may be
happening simultaneously.
## Use GitLab CI/CD
### Use a Docker-in-Docker container image
You can use [GitLab CI/CD](../../../ci/_index.md) to build and push container images to the
Container Registry. You can use CI/CD to test, build, and deploy your project from the container
image you created.
You can use your own Docker-in-Docker (DinD)
container images with the container registry or Dependency Proxy.
### Use a Docker-in-Docker container image from your container registry
Use DinD to build, test, and deploy containerized
applications from your CI/CD pipeline.
You can use your own container images for Docker-in-Docker.
Prerequisites:
1. Set up [Docker-in-Docker](../../../ci/docker/using_docker_build.md#use-docker-in-docker).
1. Update the `image` and `service` to point to your registry.
1. Add a service [alias](../../../ci/services/_index.md#available-settings-for-services).
- Set up [Docker-in-Docker](../../../ci/docker/using_docker_build.md#use-docker-in-docker).
{{< tabs >}}
{{< tab title="From the container registry" >}}
In your `.gitlab-ci.yml` file:
- Update `image` and `services` to point to your registry.
- Add a service [alias](../../../ci/services/_index.md#available-settings-for-services).
Your `.gitlab-ci.yml` should look similar to this:
@ -78,20 +91,14 @@ build:
- docker run my-docker-image /script/to/run/tests
```
If you forget to set the service alias, the container image can't find the `dind` service,
and an error like the following is shown:
{{< /tab >}}
```plaintext
error during connect: Get http://docker:2376/v1.39/info: dial tcp: lookup docker on 192.168.0.1:53: no such host
```
{{< tab title="With the Dependency Proxy" >}}
### Use a Docker-in-Docker container image with Dependency Proxy
In your `.gitlab-ci.yml` file:
You can use your own container images with Dependency Proxy.
1. Set up [Docker-in-Docker](../../../ci/docker/using_docker_build.md#use-docker-in-docker).
1. Update the `image` and `service` to point to your registry.
1. Add a service [alias](../../../ci/services/_index.md#available-settings-for-services).
- Update `image` and `services` to point to your dependency proxy.
- Add a service [alias](../../../ci/services/_index.md#available-settings-for-services).
Your `.gitlab-ci.yml` should look similar to this:
@ -107,6 +114,10 @@ build:
- docker run my-docker-image /script/to/run/tests
```
{{< /tab >}}
{{< /tabs >}}
If you forget to set the service alias, the container image can't find the `dind` service,
and an error like the following is shown:
@ -116,7 +127,7 @@ error during connect: Get http://docker:2376/v1.39/info: dial tcp: lookup docker
## Container registry examples with GitLab CI/CD
If you're using Docker-in-Docker on your runners, your `.gitlab-ci.yml` file should look similar to this:
If you're using DinD on your runners, your `.gitlab-ci.yml` file should look similar to this:
```yaml
build:

View File

@ -86,7 +86,6 @@ spec/frontend/jira_import/components/jira_import_form_spec.js
spec/frontend/lib/utils/breadcrumbs_spec.js
spec/frontend/lib/utils/confirm_via_gl_modal/confirm_action_spec.js
spec/frontend/members/components/app_spec.js
spec/frontend/members/components/modals/leave_modal_spec.js
spec/frontend/members/components/table/max_role_spec.js
spec/frontend/members/components/table/members_table_spec.js
spec/frontend/ml/model_registry/components/model_edit_spec.js
@ -110,7 +109,6 @@ spec/frontend/super_sidebar/components/user_menu_spec.js
spec/frontend/tooltips/index_spec.js
spec/frontend/vue_alerts_spec.js
spec/frontend/vue_popovers_spec.js
spec/frontend/vue_shared/components/design_management/design_note_pin_spec.js
spec/frontend/vue_shared/components/file_tree_spec.js
spec/frontend/vue_shared/components/filtered_search_bar/tokens/date_token_spec.js
spec/frontend/vue_shared/components/metric_images/metric_image_details_modal_spec.js

View File

@ -180,7 +180,7 @@ describe('LeaveModal', () => {
const submitSpy = jest.spyOn(findForm().element, 'submit');
findModal().findByText('Leave').trigger('click');
findModal().vm.$emit('primary');
expect(submitSpy).toHaveBeenCalled();

View File

@ -1,4 +1,5 @@
import { shallowMount } from '@vue/test-utils';
import { assertProps } from 'helpers/assert_props';
import DesignNotePin from '~/vue_shared/components/design_management/design_note_pin.vue';
describe('Design note pin component', () => {
@ -61,12 +62,9 @@ describe('Design note pin component', () => {
});
it('throws when passed any other value except `sm` or `md`', () => {
jest.spyOn(console, 'error').mockImplementation(() => {});
createComponent({ size: 'lg' });
// eslint-disable-next-line no-console
expect(console.error).toHaveBeenCalled();
expect(() => {
assertProps(DesignNotePin, { size: 'lg' });
}).toThrow('Invalid prop: custom validator check failed');
});
});

View File

@ -61,11 +61,24 @@ RSpec.describe 'Adding a Note', feature_category: :team_planning do
context 'when the user has permission to create notes on the discussion' do
let(:discussion) { create(:discussion_note, project: project).to_discussion }
context 'when discussion is not on the noteable' do
it 'checks noteable and discussion noteable' do
expect(noteable.id).not_to eq(discussion.noteable_id)
end
it_behaves_like 'a mutation that returns top-level errors',
errors: ["The discussion does not exist or you don't have permission to perform this action"]
end
context 'when the discussion is on the noteable' do
let(:noteable) { discussion.noteable }
it 'creates a Note in a discussion' do
post_graphql_mutation(mutation, current_user: current_user)
expect(mutation_response['note']['discussion']).to match a_graphql_entity_for(discussion)
end
end
context 'when the discussion_id is not for a Discussion' do
let(:discussion) { create(:issue) }

View File

@ -63,7 +63,7 @@ RSpec.describe WorkItems::DataSync::Handlers::Notes::CopyService, feature_catego
# 4 notes are copied to the target work item: 2 system notes and 2 user notes
# 2 system notes had also description version metadata
# 2 user notes notes had also description version metadata
# 2 user notes had also description version metadata
expect { execute_service }.to change { ::Note.count }.by(5).and(
change { ::SystemNoteMetadata.count }.by(2)).and(
change { ::DescriptionVersion.count }.by(1)).and(
@ -71,7 +71,10 @@ RSpec.describe WorkItems::DataSync::Handlers::Notes::CopyService, feature_catego
change { ::IssueUserMention.count }.by(2))
notes_details = target_work_item.reload.notes.pluck(:note, :discussion_id)
expect(notes_details).to match_array(expected_notes_details)
# same number of discussions in target and source work items
expect(notes_details.size).to eq(expected_notes_details.size)
# but with different discussion ids
expect(notes_details).not_to match_array(expected_notes_details)
end
it 'sets correct attributes from target', :aggregate_failures do
@ -82,5 +85,100 @@ RSpec.describe WorkItems::DataSync::Handlers::Notes::CopyService, feature_catego
expect(expected_description_version.namespace_id).to eq(target_work_item.namespace_id)
expect(expected_description_version.issue_id).to eq(target_work_item.id)
end
describe 'discussion_id generation' do
it 'generates new discussion_ids for copied notes' do
original_discussion_ids = work_item.notes.pluck(:discussion_id).uniq
execute_service
copied_discussion_ids = target_work_item.reload.notes.pluck(:discussion_id).uniq
# All copied notes should have different discussion_ids from originals
expect(copied_discussion_ids).not_to include(*original_discussion_ids)
# Each copied discussion_id should be a valid 40-character hex string
copied_discussion_ids.all? { |discussion_id| expect(discussion_id).to match(/\A\h{40}\z/) }
end
it 'reuses the same new discussion_id for notes in the same discussion' do
# Create notes that are part of the same discussion
discussion_note = create(:note, project: work_item.project, noteable: work_item)
reply_note = create(:note,
project: work_item.project,
noteable: work_item,
discussion_id: discussion_note.discussion_id,
in_reply_to: discussion_note
)
execute_service
copied_notes = target_work_item.reload.notes.where(note: [discussion_note.note, reply_note.note])
copied_discussion_ids = copied_notes.pluck(:discussion_id).uniq
# Both copied notes should have the same new discussion_id
expect(copied_discussion_ids.size).to eq(1)
# But it should be different from the original
expect(copied_discussion_ids.first).not_to eq(discussion_note.discussion_id)
end
it 'generates different discussion_ids for different original discussions' do
# Create two separate discussions
discussion1_note = create(:note, project: work_item.project, noteable: work_item)
discussion2_note = create(:note, project: work_item.project, noteable: work_item)
execute_service
copied_notes = target_work_item.reload.notes.where(note: [discussion1_note.note, discussion2_note.note])
copied_discussion_ids = copied_notes.pluck(:discussion_id)
# Each copied note should have a different discussion_id
expect(copied_discussion_ids.uniq.size).to eq(2)
expect(copied_discussion_ids).not_to include(discussion1_note.discussion_id, discussion2_note.discussion_id)
end
it 'calls Discussion.discussion_id to generate new discussion_ids' do
expect(::Discussion).to receive(:discussion_id).at_least(:once).and_call_original
execute_service
end
it 'maintains discussion structure when copying notes with replies' do
# Create a discussion with multiple replies
parent_note = create(:note, project: work_item.project, noteable: work_item, note: 'Parent note')
create(:note,
project: work_item.project,
noteable: work_item,
note: 'Reply 1',
discussion_id: parent_note.discussion_id,
in_reply_to: parent_note
)
create(:note,
project: work_item.project,
noteable: work_item,
note: 'Reply 2',
discussion_id: parent_note.discussion_id,
in_reply_to: parent_note
)
execute_service
# Find the copied notes
copied_parent = target_work_item.reload.notes.find_by(note: 'Parent note')
copied_reply1 = target_work_item.notes.find_by(note: 'Reply 1')
copied_reply2 = target_work_item.notes.find_by(note: 'Reply 2')
# All copied notes should have the same new discussion_id
expect(copied_parent.discussion_id).to eq(copied_reply1.discussion_id)
expect(copied_parent.discussion_id).to eq(copied_reply2.discussion_id)
# But different from the original
expect(copied_parent.discussion_id).not_to eq(parent_note.discussion_id)
# Verify the discussion structure is maintained
discussion = target_work_item.notes.find_discussion(copied_parent.discussion_id)
expect(discussion.notes.size).to eq(3)
expect(discussion.notes.map(&:note)).to contain_exactly('Parent note', 'Reply 1', 'Reply 2')
end
end
end
end

View File

@ -105,7 +105,7 @@ RSpec.describe WorkItems::DataSync::Widgets::Notes, feature_category: :team_plan
# 4 notes are copied to the target work item: 2 system notes and 2 user notes
# 2 system notes had also description version metadata
# 2 user notes notes had also description version metadata
# 2 user notes had also description version metadata
expect { callback.after_create }.to change { ::Note.count }.by(5).and(
change { ::SystemNoteMetadata.count }.by(2)).and(
change { ::DescriptionVersion.count }.by(1)).and(
@ -116,7 +116,11 @@ RSpec.describe WorkItems::DataSync::Widgets::Notes, feature_category: :team_plan
change { ::IssueUserMention.count }.by(2))
notes_details = target_work_item.reload.notes.pluck(:note, :discussion_id)
expect(notes_details).to match_array(expected_notes_details)
# size and notes would match
expect(notes_details.size).to eq(expected_notes_details.size)
expect(notes_details.map(&:first)).to match_array(expected_notes_details.map(&:first))
# but discussion_ids would not
expect(notes_details.map(&:last)).not_to match_array(expected_notes_details.map(&:last))
end
end

View File

@ -9,7 +9,7 @@
<% def humanize(feature_category) %>
<% case feature_category %>
<% when 'duo_workflow' %>
<% "GitLab Duo Workflow" %>
<% "GitLab Duo Agent Platform" %>
<% when 'mlops' %>
<% "MLOps" %>
<% when 'not_owned' %>