Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-05-31 06:09:17 +00:00
parent 226c5810c9
commit a9f214d421
18 changed files with 198 additions and 22 deletions

View file

@ -117,6 +117,7 @@ export default {
'autocompleteAwardEmojisPath', 'autocompleteAwardEmojisPath',
'calendarPath', 'calendarPath',
'canBulkUpdate', 'canBulkUpdate',
'canCreateProjects',
'canReadCrmContact', 'canReadCrmContact',
'canReadCrmOrganization', 'canReadCrmOrganization',
'emptyStateSvgPath', 'emptyStateSvgPath',
@ -136,6 +137,7 @@ export default {
'isSignedIn', 'isSignedIn',
'jiraIntegrationPath', 'jiraIntegrationPath',
'newIssuePath', 'newIssuePath',
'newProjectPath',
'releasesPath', 'releasesPath',
'rssPath', 'rssPath',
'showNewIssueLink', 'showNewIssueLink',
@ -844,12 +846,17 @@ export default {
</issuable-list> </issuable-list>
<template v-else-if="isSignedIn"> <template v-else-if="isSignedIn">
<gl-empty-state <gl-empty-state :title="$options.i18n.noIssuesSignedInTitle" :svg-path="emptyStateSvgPath">
:description="$options.i18n.noIssuesSignedInDescription" <template #description>
:title="$options.i18n.noIssuesSignedInTitle" <p>{{ $options.i18n.noIssuesSignedInDescription }}</p>
:svg-path="emptyStateSvgPath" <p v-if="canCreateProjects">
> <strong>{{ $options.i18n.noGroupIssuesSignedInDescription }}</strong>
</p>
</template>
<template #actions> <template #actions>
<gl-button v-if="canCreateProjects" :href="newProjectPath" variant="confirm">
{{ $options.i18n.newProjectLabel }}
</gl-button>
<gl-button v-if="showNewIssueLink" :href="newIssuePath" variant="confirm"> <gl-button v-if="showNewIssueLink" :href="newIssuePath" variant="confirm">
{{ $options.i18n.newIssueLabel }} {{ $options.i18n.newIssueLabel }}
</gl-button> </gl-button>

View file

@ -29,7 +29,11 @@ export const i18n = {
jiraIntegrationSecondaryMessage: s__('JiraService|This feature requires a Premium plan.'), jiraIntegrationSecondaryMessage: s__('JiraService|This feature requires a Premium plan.'),
jiraIntegrationTitle: s__('JiraService|Using Jira for issue tracking?'), jiraIntegrationTitle: s__('JiraService|Using Jira for issue tracking?'),
newIssueLabel: __('New issue'), newIssueLabel: __('New issue'),
newProjectLabel: __('New project'),
noClosedIssuesTitle: __('There are no closed issues'), noClosedIssuesTitle: __('There are no closed issues'),
noGroupIssuesSignedInDescription: __(
'Issues exist in projects, so to create an issue, first create a project.',
),
noOpenIssuesDescription: __('To keep this project going, create a new issue'), noOpenIssuesDescription: __('To keep this project going, create a new issue'),
noOpenIssuesTitle: __('There are no open issues'), noOpenIssuesTitle: __('There are no open issues'),
noIssuesSignedInDescription: __( noIssuesSignedInDescription: __(

View file

@ -80,6 +80,7 @@ export function mountIssuesListApp() {
autocompleteAwardEmojisPath, autocompleteAwardEmojisPath,
calendarPath, calendarPath,
canBulkUpdate, canBulkUpdate,
canCreateProjects,
canEdit, canEdit,
canImportIssues, canImportIssues,
canReadCrmContact, canReadCrmContact,
@ -109,6 +110,7 @@ export function mountIssuesListApp() {
markdownHelpPath, markdownHelpPath,
maxAttachmentSize, maxAttachmentSize,
newIssuePath, newIssuePath,
newProjectPath,
projectImportJiraPath, projectImportJiraPath,
quickActionsHelpPath, quickActionsHelpPath,
releasesPath, releasesPath,
@ -133,6 +135,7 @@ export function mountIssuesListApp() {
autocompleteAwardEmojisPath, autocompleteAwardEmojisPath,
calendarPath, calendarPath,
canBulkUpdate: parseBoolean(canBulkUpdate), canBulkUpdate: parseBoolean(canBulkUpdate),
canCreateProjects: parseBoolean(canCreateProjects),
canReadCrmContact: parseBoolean(canReadCrmContact), canReadCrmContact: parseBoolean(canReadCrmContact),
canReadCrmOrganization: parseBoolean(canReadCrmOrganization), canReadCrmOrganization: parseBoolean(canReadCrmOrganization),
emptyStateSvgPath, emptyStateSvgPath,
@ -153,6 +156,7 @@ export function mountIssuesListApp() {
isSignedIn: parseBoolean(isSignedIn), isSignedIn: parseBoolean(isSignedIn),
jiraIntegrationPath, jiraIntegrationPath,
newIssuePath, newIssuePath,
newProjectPath,
releasesPath, releasesPath,
rssPath, rssPath,
showNewIssueLink: parseBoolean(showNewIssueLink), showNewIssueLink: parseBoolean(showNewIssueLink),

View file

@ -238,10 +238,12 @@ module IssuesHelper
def group_issues_list_data(group, current_user) def group_issues_list_data(group, current_user)
common_issues_list_data(group, current_user).merge( common_issues_list_data(group, current_user).merge(
can_create_projects: can?(current_user, :create_projects, group).to_s,
can_read_crm_contact: can?(current_user, :read_crm_contact, group).to_s, can_read_crm_contact: can?(current_user, :read_crm_contact, group).to_s,
can_read_crm_organization: can?(current_user, :read_crm_organization, group).to_s, can_read_crm_organization: can?(current_user, :read_crm_organization, group).to_s,
has_any_issues: @has_issues.to_s, has_any_issues: @has_issues.to_s,
has_any_projects: @has_projects.to_s has_any_projects: @has_projects.to_s,
new_project_path: new_project_path(namespace_id: group.id)
) )
end end

View file

@ -56,8 +56,7 @@ class DiffFileEntity < DiffFileBaseEntity
# Used for inline diffs # Used for inline diffs
expose :highlighted_diff_lines, using: DiffLineEntity, if: -> (diff_file, options) { inline_diff_view?(options) && diff_file.text? } do |diff_file| expose :highlighted_diff_lines, using: DiffLineEntity, if: -> (diff_file, options) { inline_diff_view?(options) && diff_file.text? } do |diff_file|
file = conflict_file(options, diff_file) || diff_file highlighted_diff_lines_for(diff_file, options)
file.diff_lines_for_serializer
end end
expose :is_fully_expanded do |diff_file| expose :is_fully_expanded do |diff_file|
@ -89,6 +88,15 @@ class DiffFileEntity < DiffFileBaseEntity
# If nothing is present, inline will be the default. # If nothing is present, inline will be the default.
options.fetch(:diff_view, :inline).to_sym options.fetch(:diff_view, :inline).to_sym
end end
def highlighted_diff_lines_for(diff_file, options)
file = conflict_file(options, diff_file) || diff_file
file.diff_lines_for_serializer
rescue Gitlab::Git::Conflict::Parser::UnmergeableFile
# Fallback to diff_file as it means that conflict lines can't be parsed due to limit
diff_file.diff_lines_for_serializer
end
end end
DiffFileEntity.prepend_mod DiffFileEntity.prepend_mod

View file

@ -55,7 +55,10 @@ module AutoMerge
def available_for?(merge_request) def available_for?(merge_request)
strong_memoize("available_for_#{merge_request.id}") do strong_memoize("available_for_#{merge_request.id}") do
merge_request.can_be_merged_by?(current_user) && merge_request.can_be_merged_by?(current_user) &&
merge_request.mergeable_state?(skip_ci_check: true) && merge_request.open? &&
!merge_request.broken? &&
!merge_request.draft? &&
merge_request.mergeable_discussions_state? &&
yield yield
end end
end end

View file

@ -0,0 +1,15 @@
# frozen_string_literal: true
class CleanupBackfillIntegrationsEnableSslVerification < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
MIGRATION = 'BackfillIntegrationsEnableSslVerification'
def up
finalize_background_migration(MIGRATION)
end
def down
# no-op
end
end

View file

@ -0,0 +1,18 @@
# frozen_string_literal: true
class DropTemporaryIndexForBackfillIntegrationsEnableSslVerification < Gitlab::Database::Migration[2.0]
disable_ddl_transaction!
INDEX_NAME = 'tmp_index_integrations_on_id_where_type_droneci_or_teamcity'
INDEX_CONDITION = "type_new IN ('Integrations::DroneCi', 'Integrations::Teamcity') " \
"AND encrypted_properties IS NOT NULL"
def up
remove_concurrent_index_by_name :integrations, INDEX_NAME
end
def down
# this index is used in 20220209121435_backfill_integrations_enable_ssl_verification
add_concurrent_index :integrations, :id, where: INDEX_CONDITION, name: INDEX_NAME
end
end

View file

@ -0,0 +1 @@
fe0e9acc39c2408853ea3fc35574c553172ad381a5b6f243578f44ed77dc75f8

View file

@ -0,0 +1 @@
f34c6e7b75d375342f5c88a9c7b98e15031a6dcdadf7e7dad862ef5f32a54e68

View file

@ -29975,8 +29975,6 @@ CREATE INDEX tmp_index_for_null_project_namespace_id ON projects USING btree (id
CREATE INDEX tmp_index_for_project_namespace_id_migration_on_routes ON routes USING btree (id) WHERE ((namespace_id IS NULL) AND ((source_type)::text = 'Project'::text)); CREATE INDEX tmp_index_for_project_namespace_id_migration_on_routes ON routes USING btree (id) WHERE ((namespace_id IS NULL) AND ((source_type)::text = 'Project'::text));
CREATE INDEX tmp_index_integrations_on_id_where_type_droneci_or_teamcity ON integrations USING btree (id) WHERE ((type_new = ANY (ARRAY['Integrations::DroneCi'::text, 'Integrations::Teamcity'::text])) AND (encrypted_properties IS NOT NULL));
CREATE INDEX tmp_index_issues_on_issue_type_and_id ON issues USING btree (issue_type, id); CREATE INDEX tmp_index_issues_on_issue_type_and_id ON issues USING btree (issue_type, id);
CREATE INDEX tmp_index_members_on_state ON members USING btree (state) WHERE (state = 2); CREATE INDEX tmp_index_members_on_state ON members USING btree (state) WHERE (state = 2);

View file

@ -175,6 +175,8 @@ To configure streaming audit events for Git operations, see [Add a new event str
### Headers ### Headers
> `X-Gitlab-Audit-Event-Type` [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/86881) in GitLab 15.0.
Headers are formatted as follows: Headers are formatted as follows:
```plaintext ```plaintext
@ -182,6 +184,7 @@ POST /logs HTTP/1.1
Host: <DESTINATION_HOST> Host: <DESTINATION_HOST>
Content-Type: application/x-www-form-urlencoded Content-Type: application/x-www-form-urlencoded
X-Gitlab-Event-Streaming-Token: <DESTINATION_TOKEN> X-Gitlab-Event-Streaming-Token: <DESTINATION_TOKEN>
X-Gitlab-Audit-Event-Type: repository_git_operation
``` ```
### Example payloads for SSH events ### Example payloads for SSH events
@ -212,7 +215,8 @@ Fetch:
"target_details": "example-project", "target_details": "example-project",
"created_at": "2022-02-23T06:21:05.283Z", "created_at": "2022-02-23T06:21:05.283Z",
"target_type": "Project", "target_type": "Project",
"target_id": 29 "target_id": 29,
"event_type": "repository_git_operation"
} }
``` ```
@ -242,7 +246,8 @@ Push:
"target_details": "example-project", "target_details": "example-project",
"created_at": "2022-02-23T06:23:08.746Z", "created_at": "2022-02-23T06:23:08.746Z",
"target_type": "Project", "target_type": "Project",
"target_id": 29 "target_id": 29,
"event_type": "repository_git_operation"
} }
``` ```
@ -274,7 +279,8 @@ Fetch:
"target_details": "example-project", "target_details": "example-project",
"created_at": "2022-02-23T06:25:43.938Z", "created_at": "2022-02-23T06:25:43.938Z",
"target_type": "Project", "target_type": "Project",
"target_id": 29 "target_id": 29,
"event_type": "repository_git_operation"
} }
``` ```
@ -304,7 +310,8 @@ Push:
"target_details": "example-project", "target_details": "example-project",
"created_at": "2022-02-23T06:26:29.294Z", "created_at": "2022-02-23T06:26:29.294Z",
"target_type": "Project", "target_type": "Project",
"target_id": 29 "target_id": 29,
"event_type": "repository_git_operation"
} }
``` ```
@ -333,7 +340,8 @@ Fetch:
"target_details": "example-group/example-project", "target_details": "example-group/example-project",
"created_at": "2022-02-23T06:27:17.873Z", "created_at": "2022-02-23T06:27:17.873Z",
"target_type": "Project", "target_type": "Project",
"target_id": 29 "target_id": 29,
"event_type": "repository_git_operation"
} }
``` ```
@ -352,6 +360,7 @@ POST /logs HTTP/1.1
Host: <DESTINATION_HOST> Host: <DESTINATION_HOST>
Content-Type: application/x-www-form-urlencoded Content-Type: application/x-www-form-urlencoded
X-Gitlab-Event-Streaming-Token: <DESTINATION_TOKEN> X-Gitlab-Event-Streaming-Token: <DESTINATION_TOKEN>
X-Gitlab-Audit-Event-Type: audit_operation
``` ```
### Example payload ### Example payload
@ -377,6 +386,7 @@ X-Gitlab-Event-Streaming-Token: <DESTINATION_TOKEN>
"target_details": "merge request title", "target_details": "merge request title",
"created_at": "2022-03-09T06:53:11.181Z", "created_at": "2022-03-09T06:53:11.181Z",
"target_type": "MergeRequest", "target_type": "MergeRequest",
"target_id": 20 "target_id": 20,
"event_type": "audit_operation"
} }
``` ```

View file

@ -21324,6 +21324,9 @@ msgstr ""
msgid "Issues closed" msgid "Issues closed"
msgstr "" msgstr ""
msgid "Issues exist in projects, so to create an issue, first create a project."
msgstr ""
msgid "Issues must match this scope to appear in this list." msgid "Issues must match this scope to appear in this list."
msgstr "" msgstr ""

View file

@ -1,7 +1,10 @@
# frozen_string_literal: true # frozen_string_literal: true
module QA module QA
RSpec.describe 'Create' do RSpec.describe 'Create', quarantine: {
issue: 'https://gitlab.com/gitlab-org/gitlab/-/issues/363807',
type: :stale
} do
describe 'Create a new merge request' do describe 'Create a new merge request' do
let(:project) do let(:project) do
Resource::Project.fabricate_via_api! do |project| Resource::Project.fabricate_via_api! do |project|

View file

@ -67,6 +67,7 @@ describe('CE IssuesListApp component', () => {
autocompleteAwardEmojisPath: 'autocomplete/award/emojis/path', autocompleteAwardEmojisPath: 'autocomplete/award/emojis/path',
calendarPath: 'calendar/path', calendarPath: 'calendar/path',
canBulkUpdate: false, canBulkUpdate: false,
canCreateProjects: false,
canReadCrmContact: false, canReadCrmContact: false,
canReadCrmOrganization: false, canReadCrmOrganization: false,
emptyStateSvgPath: 'empty-state.svg', emptyStateSvgPath: 'empty-state.svg',
@ -88,6 +89,7 @@ describe('CE IssuesListApp component', () => {
isSignedIn: true, isSignedIn: true,
jiraIntegrationPath: 'jira/integration/path', jiraIntegrationPath: 'jira/integration/path',
newIssuePath: 'new/issue/path', newIssuePath: 'new/issue/path',
newProjectPath: 'new/project/path',
releasesPath: 'releases/path', releasesPath: 'releases/path',
rssPath: 'rss/path', rssPath: 'rss/path',
showNewIssueLink: true, showNewIssueLink: true,
@ -118,6 +120,7 @@ describe('CE IssuesListApp component', () => {
issuesQueryResponse = jest.fn().mockResolvedValue(defaultQueryResponse), issuesQueryResponse = jest.fn().mockResolvedValue(defaultQueryResponse),
issuesCountsQueryResponse = jest.fn().mockResolvedValue(getIssuesCountsQueryResponse), issuesCountsQueryResponse = jest.fn().mockResolvedValue(getIssuesCountsQueryResponse),
sortPreferenceMutationResponse = jest.fn().mockResolvedValue(setSortPreferenceMutationResponse), sortPreferenceMutationResponse = jest.fn().mockResolvedValue(setSortPreferenceMutationResponse),
stubs = {},
mountFn = shallowMount, mountFn = shallowMount,
} = {}) => { } = {}) => {
const requestHandlers = [ const requestHandlers = [
@ -136,6 +139,7 @@ describe('CE IssuesListApp component', () => {
data() { data() {
return data; return data;
}, },
stubs,
}); });
}; };
@ -521,10 +525,12 @@ describe('CE IssuesListApp component', () => {
it('shows empty state', () => { it('shows empty state', () => {
expect(findGlEmptyState().props()).toMatchObject({ expect(findGlEmptyState().props()).toMatchObject({
description: IssuesListApp.i18n.noIssuesSignedInDescription,
title: IssuesListApp.i18n.noIssuesSignedInTitle, title: IssuesListApp.i18n.noIssuesSignedInTitle,
svgPath: defaultProvide.emptyStateSvgPath, svgPath: defaultProvide.emptyStateSvgPath,
}); });
expect(findGlEmptyState().text()).toContain(
IssuesListApp.i18n.noIssuesSignedInDescription,
);
}); });
it('shows "New issue" and import/export buttons', () => { it('shows "New issue" and import/export buttons', () => {
@ -538,11 +544,11 @@ describe('CE IssuesListApp component', () => {
it('shows Jira integration information', () => { it('shows Jira integration information', () => {
const paragraphs = wrapper.findAll('p'); const paragraphs = wrapper.findAll('p');
expect(paragraphs.at(1).text()).toContain(IssuesListApp.i18n.jiraIntegrationTitle); expect(paragraphs.at(2).text()).toContain(IssuesListApp.i18n.jiraIntegrationTitle);
expect(paragraphs.at(2).text()).toContain( expect(paragraphs.at(3).text()).toContain(
'Enable the Jira integration to view your Jira issues in GitLab.', 'Enable the Jira integration to view your Jira issues in GitLab.',
); );
expect(paragraphs.at(3).text()).toContain( expect(paragraphs.at(4).text()).toContain(
IssuesListApp.i18n.jiraIntegrationSecondaryMessage, IssuesListApp.i18n.jiraIntegrationSecondaryMessage,
); );
expect(findGlLink().text()).toBe('Enable the Jira integration'); expect(findGlLink().text()).toBe('Enable the Jira integration');
@ -550,6 +556,29 @@ describe('CE IssuesListApp component', () => {
}); });
}); });
describe('when user is logged in and can create projects', () => {
beforeEach(() => {
wrapper = mountComponent({
provide: { canCreateProjects: true, hasAnyIssues: false, isSignedIn: true },
stubs: { GlEmptyState },
});
});
it('shows empty state with additional description about creating projects', () => {
expect(findGlEmptyState().text()).toContain(
IssuesListApp.i18n.noIssuesSignedInDescription,
);
expect(findGlEmptyState().text()).toContain(
IssuesListApp.i18n.noGroupIssuesSignedInDescription,
);
});
it('shows "New project" button', () => {
expect(findGlButton().text()).toBe(IssuesListApp.i18n.newProjectLabel);
expect(findGlButton().attributes('href')).toBe(defaultProvide.newProjectPath);
});
});
describe('when user is logged out', () => { describe('when user is logged out', () => {
beforeEach(() => { beforeEach(() => {
wrapper = mountComponent({ wrapper = mountComponent({

View file

@ -355,12 +355,14 @@ RSpec.describe IssuesHelper do
expected = { expected = {
autocomplete_award_emojis_path: autocomplete_award_emojis_path, autocomplete_award_emojis_path: autocomplete_award_emojis_path,
calendar_path: '#', calendar_path: '#',
can_create_projects: 'true',
empty_state_svg_path: '#', empty_state_svg_path: '#',
full_path: group.full_path, full_path: group.full_path,
has_any_issues: false.to_s, has_any_issues: false.to_s,
has_any_projects: true.to_s, has_any_projects: true.to_s,
is_signed_in: current_user.present?.to_s, is_signed_in: current_user.present?.to_s,
jira_integration_path: help_page_url('integration/jira/issues', anchor: 'view-jira-issues'), jira_integration_path: help_page_url('integration/jira/issues', anchor: 'view-jira-issues'),
new_project_path: new_project_path(namespace_id: group.id),
rss_path: '#', rss_path: '#',
sign_in_path: new_user_session_path sign_in_path: new_user_session_path
} }

View file

@ -0,0 +1,35 @@
# frozen_string_literal: true
require 'spec_helper'
require_migration!
RSpec.describe CleanupBackfillIntegrationsEnableSslVerification, :migration do
let(:job_class_name) { 'BackfillIntegrationsEnableSslVerification' }
before do
# Jobs enqueued in Sidekiq.
Sidekiq::Testing.disable! do
BackgroundMigrationWorker.perform_in(10, job_class_name, [1, 2])
BackgroundMigrationWorker.perform_in(20, job_class_name, [3, 4])
end
# Jobs tracked in the database.
Gitlab::Database::BackgroundMigrationJob.create!(
class_name: job_class_name,
arguments: [5, 6],
status: Gitlab::Database::BackgroundMigrationJob.statuses['pending']
)
Gitlab::Database::BackgroundMigrationJob.create!(
class_name: job_class_name,
arguments: [7, 8],
status: Gitlab::Database::BackgroundMigrationJob.statuses['succeeded']
)
migrate!
end
it_behaves_like(
'finalized tracked background migration',
Gitlab::BackgroundMigration::BackfillIntegrationsEnableSslVerification
)
end

View file

@ -91,5 +91,38 @@ RSpec.describe DiffFileEntity do
end end
end end
describe '#highlighted_diff_lines' do
context 'file without a conflict' do
let(:options) { { conflicts: {} } }
it 'calls diff_lines_for_serializer on diff_file' do
# #diff_lines_for_serializer gets called in #fully_expanded? as well so we expect twice
expect(diff_file).to receive(:diff_lines_for_serializer).twice.and_return([])
expect(subject[:highlighted_diff_lines]).to eq([])
end
end
context 'file with a conflict' do
let(:conflict_file) { instance_double(Gitlab::Conflict::File, conflict_type: :both_modified) }
let(:options) { { conflicts: { diff_file.new_path => conflict_file } } }
it 'calls diff_lines_for_serializer on matching conflict file' do
expect(conflict_file).to receive(:diff_lines_for_serializer).and_return([])
expect(subject[:highlighted_diff_lines]).to eq([])
end
context 'when Gitlab::Git::Conflict::Parser::UnmergeableFile gets raised' do
before do
allow(conflict_file).to receive(:diff_lines_for_serializer).and_raise(Gitlab::Git::Conflict::Parser::UnmergeableFile)
end
it 'falls back to diff_file diff_lines_for_serializer' do
expect(diff_file).to receive(:diff_lines_for_serializer).and_return([])
expect(subject[:highlighted_diff_lines]).to eq([])
end
end
end
end
it_behaves_like 'diff file with conflict_type' it_behaves_like 'diff file with conflict_type'
end end