Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2020-09-21 06:09:41 +00:00
parent 965a92325a
commit 54cf744926
21 changed files with 478 additions and 218 deletions

View File

@ -267,8 +267,8 @@ export default {
this.searchTerm = trimmedInput; this.searchTerm = trimmedInput;
} }
}, 500), }, 500),
navigateToAlertDetails({ iid }) { navigateToAlertDetails({ iid }, index, { metaKey }) {
return visitUrl(joinPaths(window.location.pathname, iid, 'details')); return visitUrl(joinPaths(window.location.pathname, iid, 'details'), metaKey);
}, },
trackPageViews() { trackPageViews() {
const { category, action } = trackAlertListViewsOptions; const { category, action } = trackAlertListViewsOptions;

View File

@ -1,7 +1,6 @@
<script> <script>
/* eslint-disable vue/no-v-html */
import { mapState, mapActions, mapGetters } from 'vuex'; import { mapState, mapActions, mapGetters } from 'vuex';
import { GlLink, GlLoadingIcon } from '@gitlab/ui'; import { GlLink, GlLoadingIcon, GlSafeHtmlDirective as SafeHtml } from '@gitlab/ui';
import { sprintf, s__ } from '~/locale'; import { sprintf, s__ } from '~/locale';
import EnvironmentRow from './environment_row.vue'; import EnvironmentRow from './environment_row.vue';
import EmptyState from './empty_state.vue'; import EmptyState from './empty_state.vue';
@ -14,6 +13,9 @@ export default {
GlLink, GlLink,
GlLoadingIcon, GlLoadingIcon,
}, },
directives: {
SafeHtml,
},
computed: { computed: {
...mapState(['installed', 'isLoading', 'hasFunctionData', 'helpPath', 'statusPath']), ...mapState(['installed', 'isLoading', 'hasFunctionData', 'helpPath', 'statusPath']),
...mapGetters(['getFunctions']), ...mapGetters(['getFunctions']),
@ -92,9 +94,9 @@ export default {
}} }}
</p> </p>
<ul> <ul>
<li v-html="noServerlessConfigFile"></li> <li v-safe-html="noServerlessConfigFile"></li>
<li v-html="noGitlabYamlConfigured"></li> <li v-safe-html="noGitlabYamlConfigured"></li>
<li v-html="mismatchedServerlessFunctions"></li> <li v-safe-html="mismatchedServerlessFunctions"></li>
<li>{{ s__('Serverless|The deploy job has not finished.') }}</li> <li>{{ s__('Serverless|The deploy job has not finished.') }}</li>
</ul> </ul>

View File

@ -71,4 +71,5 @@ module Types
end end
::Types::MutationType.prepend(::Types::DeprecatedMutations) ::Types::MutationType.prepend(::Types::DeprecatedMutations)
::Types::MutationType.prepend_if_ee('EE::Types::DeprecatedMutations')
::Types::MutationType.prepend_if_ee('::EE::Types::MutationType') ::Types::MutationType.prepend_if_ee('::EE::Types::MutationType')

View File

@ -19,7 +19,6 @@
= yield :customize_homepage_banner = yield :customize_homepage_banner
- unless @hide_breadcrumbs - unless @hide_breadcrumbs
= render "layouts/nav/breadcrumbs" = render "layouts/nav/breadcrumbs"
.d-flex
%div{ class: "#{(container_class unless @no_container)} #{@content_class}" } %div{ class: "#{(container_class unless @no_container)} #{@content_class}" }
.content{ id: "content-body" } .content{ id: "content-body" }
= render "layouts/flash", extra_flash_class: 'limit-container-width' = render "layouts/flash", extra_flash_class: 'limit-container-width'

View File

@ -102,7 +102,12 @@
%tbody %tbody
- @registrations.each do |registration| - @registrations.each do |registration|
%tr %tr
%td= registration[:name].presence || html_escape_once(_("&lt;no name set&gt;")).html_safe %td
- if registration[:name].present?
= registration[:name]
- else
%span.gl-text-gray-500
= _("no name set")
%td= registration[:created_at].to_date.to_s(:medium) %td= registration[:created_at].to_date.to_s(:medium)
%td= link_to _('Delete'), registration[:delete_path], method: :delete, class: "btn btn-danger float-right", data: { confirm: _('Are you sure you want to delete this device? This action cannot be undone.') } %td= link_to _('Delete'), registration[:delete_path], method: :delete, class: "btn btn-danger float-right", data: { confirm: _('Are you sure you want to delete this device? This action cannot be undone.') }

View File

@ -0,0 +1,5 @@
---
title: Fix triggering multiple children pipeline with the same artifact
merge_request: 42595
author:
type: fixed

View File

@ -0,0 +1,5 @@
---
title: Allow alerts to open on new tab
merge_request: 42691
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Remove angle brackets from empty name in U2F device settings
merge_request: 42440
author:
type: changed

View File

@ -0,0 +1,5 @@
---
title: Remove an unnecessary element from every page
merge_request: 42769
author: Takuya Noguchi
type: other

View File

@ -4,4 +4,4 @@ introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/40268
rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/249588 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/249588
group: group::pipeline authoring group: group::pipeline authoring
type: development type: development
default_enabled: false default_enabled: true

View File

@ -10812,7 +10812,7 @@ type Mutation {
Toggles the resolved state of a discussion Toggles the resolved state of a discussion
""" """
discussionToggleResolve(input: DiscussionToggleResolveInput!): DiscussionToggleResolvePayload discussionToggleResolve(input: DiscussionToggleResolveInput!): DiscussionToggleResolvePayload
dismissVulnerability(input: DismissVulnerabilityInput!): DismissVulnerabilityPayload dismissVulnerability(input: DismissVulnerabilityInput!): DismissVulnerabilityPayload @deprecated(reason: "Use vulnerabilityDismiss. Deprecated in 13.5")
epicAddIssue(input: EpicAddIssueInput!): EpicAddIssuePayload epicAddIssue(input: EpicAddIssueInput!): EpicAddIssuePayload
epicSetSubscription(input: EpicSetSubscriptionInput!): EpicSetSubscriptionPayload epicSetSubscription(input: EpicSetSubscriptionInput!): EpicSetSubscriptionPayload
epicTreeReorder(input: EpicTreeReorderInput!): EpicTreeReorderPayload epicTreeReorder(input: EpicTreeReorderInput!): EpicTreeReorderPayload
@ -10876,6 +10876,7 @@ type Mutation {
updateRequirement(input: UpdateRequirementInput!): UpdateRequirementPayload updateRequirement(input: UpdateRequirementInput!): UpdateRequirementPayload
updateSnippet(input: UpdateSnippetInput!): UpdateSnippetPayload updateSnippet(input: UpdateSnippetInput!): UpdateSnippetPayload
vulnerabilityConfirm(input: VulnerabilityConfirmInput!): VulnerabilityConfirmPayload vulnerabilityConfirm(input: VulnerabilityConfirmInput!): VulnerabilityConfirmPayload
vulnerabilityDismiss(input: VulnerabilityDismissInput!): VulnerabilityDismissPayload
vulnerabilityResolve(input: VulnerabilityResolveInput!): VulnerabilityResolvePayload vulnerabilityResolve(input: VulnerabilityResolveInput!): VulnerabilityResolvePayload
} }
@ -18836,6 +18837,46 @@ type VulnerabilityConnection {
pageInfo: PageInfo! pageInfo: PageInfo!
} }
"""
Autogenerated input type of VulnerabilityDismiss
"""
input VulnerabilityDismissInput {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Reason why vulnerability should be dismissed
"""
comment: String
"""
ID of the vulnerability to be dismissed
"""
id: ID!
}
"""
Autogenerated return type of VulnerabilityDismiss
"""
type VulnerabilityDismissPayload {
"""
A unique identifier for the client performing the mutation.
"""
clientMutationId: String
"""
Errors encountered during execution of the mutation.
"""
errors: [String!]!
"""
The vulnerability after dismissal
"""
vulnerability: Vulnerability
}
""" """
An edge in a connection. An edge in a connection.
""" """

View File

@ -31135,8 +31135,8 @@
"name": "DismissVulnerabilityPayload", "name": "DismissVulnerabilityPayload",
"ofType": null "ofType": null
}, },
"isDeprecated": false, "isDeprecated": true,
"deprecationReason": null "deprecationReason": "Use vulnerabilityDismiss. Deprecated in 13.5"
}, },
{ {
"name": "epicAddIssue", "name": "epicAddIssue",
@ -32434,6 +32434,33 @@
"isDeprecated": false, "isDeprecated": false,
"deprecationReason": null "deprecationReason": null
}, },
{
"name": "vulnerabilityDismiss",
"description": null,
"args": [
{
"name": "input",
"description": null,
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "INPUT_OBJECT",
"name": "VulnerabilityDismissInput",
"ofType": null
}
},
"defaultValue": null
}
],
"type": {
"kind": "OBJECT",
"name": "VulnerabilityDismissPayload",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{ {
"name": "vulnerabilityResolve", "name": "vulnerabilityResolve",
"description": null, "description": null,
@ -55100,6 +55127,118 @@
"enumValues": null, "enumValues": null,
"possibleTypes": null "possibleTypes": null
}, },
{
"kind": "INPUT_OBJECT",
"name": "VulnerabilityDismissInput",
"description": "Autogenerated input type of VulnerabilityDismiss",
"fields": null,
"inputFields": [
{
"name": "id",
"description": "ID of the vulnerability to be dismissed",
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "ID",
"ofType": null
}
},
"defaultValue": null
},
{
"name": "comment",
"description": "Reason why vulnerability should be dismissed",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
},
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"defaultValue": null
}
],
"interfaces": null,
"enumValues": null,
"possibleTypes": null
},
{
"kind": "OBJECT",
"name": "VulnerabilityDismissPayload",
"description": "Autogenerated return type of VulnerabilityDismiss",
"fields": [
{
"name": "clientMutationId",
"description": "A unique identifier for the client performing the mutation.",
"args": [
],
"type": {
"kind": "SCALAR",
"name": "String",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "errors",
"description": "Errors encountered during execution of the mutation.",
"args": [
],
"type": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "LIST",
"name": null,
"ofType": {
"kind": "NON_NULL",
"name": null,
"ofType": {
"kind": "SCALAR",
"name": "String",
"ofType": null
}
}
}
},
"isDeprecated": false,
"deprecationReason": null
},
{
"name": "vulnerability",
"description": "The vulnerability after dismissal",
"args": [
],
"type": {
"kind": "OBJECT",
"name": "Vulnerability",
"ofType": null
},
"isDeprecated": false,
"deprecationReason": null
}
],
"inputFields": null,
"interfaces": [
],
"enumValues": null,
"possibleTypes": null
},
{ {
"kind": "OBJECT", "kind": "OBJECT",
"name": "VulnerabilityEdge", "name": "VulnerabilityEdge",

View File

@ -2710,6 +2710,16 @@ Autogenerated return type of VulnerabilityConfirm.
| `errors` | String! => Array | Errors encountered during execution of the mutation. | | `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `vulnerability` | Vulnerability | The vulnerability after state change | | `vulnerability` | Vulnerability | The vulnerability after state change |
### VulnerabilityDismissPayload
Autogenerated return type of VulnerabilityDismiss.
| Field | Type | Description |
| ----- | ---- | ----------- |
| `clientMutationId` | String | A unique identifier for the client performing the mutation. |
| `errors` | String! => Array | Errors encountered during execution of the mutation. |
| `vulnerability` | Vulnerability | The vulnerability after dismissal |
### VulnerabilityIdentifier ### VulnerabilityIdentifier
Represents a vulnerability identifier. Represents a vulnerability identifier.

View File

@ -13,7 +13,7 @@ module HamlLint
DOCS_DIRECTORY = File.join(File.expand_path('../..', __dir__), 'doc') DOCS_DIRECTORY = File.join(File.expand_path('../..', __dir__), 'doc')
HELP_PATH_LINK_PATTERN = <<~PATTERN HELP_PATH_LINK_PATTERN = <<~PATTERN
`(send nil? :help_page_path $...) `(send nil? {:help_page_url :help_page_path} $...)
PATTERN PATTERN
MARKDOWN_HEADER = %r{\A\#{1,6}\s+(?<header>.+)\Z}.freeze MARKDOWN_HEADER = %r{\A\#{1,6}\s+(?<header>.+)\Z}.freeze

View File

@ -72,7 +72,7 @@ module Gitlab
end end
def self.new_artifact_file_reader_enabled?(project) def self.new_artifact_file_reader_enabled?(project)
::Feature.enabled?(:ci_new_artifact_file_reader, project, default_enabled: false) ::Feature.enabled?(:ci_new_artifact_file_reader, project, default_enabled: true)
end end
end end
end end

View File

@ -857,9 +857,6 @@ msgstr ""
msgid "&lt; 1 hour" msgid "&lt; 1 hour"
msgstr "" msgstr ""
msgid "&lt;no name set&gt;"
msgstr ""
msgid "&lt;no scopes selected&gt;" msgid "&lt;no scopes selected&gt;"
msgstr "" msgstr ""
@ -30684,6 +30681,9 @@ msgstr ""
msgid "no expiration" msgid "no expiration"
msgstr "" msgstr ""
msgid "no name set"
msgstr ""
msgid "no one can merge" msgid "no one can merge"
msgstr "" msgstr ""

View File

@ -26,3 +26,5 @@ module QA
end end
end end
end end
QA::Page::Project::Packages::Index.prepend_if_ee('QA::EE::Page::Project::Packages::Index')

View File

@ -11,6 +11,8 @@ module QA
Waiter.wait_until(log: false) do Waiter.wait_until(log: false) do
finished_all_ajax_requests? && finished_all_axios_requests? && (!skip_finished_loading_check ? finished_loading?(wait: 1) : true) finished_all_ajax_requests? && finished_all_axios_requests? && (!skip_finished_loading_check ? finished_loading?(wait: 1) : true)
end end
rescue Repeater::WaitExceededError
raise $!, 'Page did not fully load. This could be due to an unending async request or loading icon.'
end end
def finished_all_axios_requests? def finished_all_axios_requests?

View File

@ -295,10 +295,30 @@ describe('AlertManagementTable', () => {
loading: false, loading: false,
}); });
expect(visitUrl).not.toHaveBeenCalled();
findAlerts() findAlerts()
.at(0) .at(0)
.trigger('click'); .trigger('click');
expect(visitUrl).toHaveBeenCalledWith('/1527542/details'); expect(visitUrl).toHaveBeenCalledWith('/1527542/details', false);
});
it('navigates to the detail page in new tab when alert row is clicked with the metaKey', () => {
mountComponent({
props: { alertManagementEnabled: true, userCanEnableAlertManagement: true },
data: { alerts: { list: mockAlerts }, alertsCount, hasError: false },
loading: false,
});
expect(visitUrl).not.toHaveBeenCalled();
findAlerts()
.at(0)
.trigger('click', {
metaKey: true,
});
expect(visitUrl).toHaveBeenCalledWith('/1527542/details', true);
}); });
describe('alert issue links', () => { describe('alert issue links', () => {

View File

@ -8,75 +8,85 @@ require Rails.root.join('haml_lint/linter/documentation_links')
RSpec.describe HamlLint::Linter::DocumentationLinks do RSpec.describe HamlLint::Linter::DocumentationLinks do
include_context 'linter' include_context 'linter'
context 'when link_to points to the existing file path' do shared_examples 'link validation rules' do |link_pattern|
let(:haml) { "= link_to 'Description', help_page_path('README.md')" } context 'when link_to points to the existing file path' do
let(:haml) { "= link_to 'Description', #{link_pattern}('README.md')" }
it { is_expected.not_to report_lint } it { is_expected.not_to report_lint }
end end
context 'when link_to points to the existing file with valid anchor' do context 'when link_to points to the existing file with valid anchor' do
let(:haml) { "= link_to 'Description', help_page_path('README.md', anchor: 'overview'), target: '_blank'" } let(:haml) { "= link_to 'Description', #{link_pattern}('README.md', anchor: 'overview'), target: '_blank'" }
it { is_expected.not_to report_lint } it { is_expected.not_to report_lint }
end end
context 'when link_to points to the existing file path without .md extension' do context 'when link_to points to the existing file path without .md extension' do
let(:haml) { "= link_to 'Description', help_page_path('README')" } let(:haml) { "= link_to 'Description', #{link_pattern}('README')" }
it { is_expected.not_to report_lint } it { is_expected.not_to report_lint }
end end
context 'when anchor is not correct' do context 'when anchor is not correct' do
let(:haml) { "= link_to 'Description', help_page_path('README.md', anchor: 'wrong')" } let(:haml) { "= link_to 'Description', #{link_pattern}('README.md', anchor: 'wrong')" }
it { is_expected.to report_lint } it { is_expected.to report_lint }
context 'when help_page_path has multiple options' do context "when #{link_pattern} has multiple options" do
let(:haml) { "= link_to 'Description', help_page_path('README.md', key: :value, anchor: 'wrong')" } let(:haml) { "= link_to 'Description', #{link_pattern}('README.md', key: :value, anchor: 'wrong')" }
it { is_expected.to report_lint }
end
end
context 'when file path is wrong' do
let(:haml) { "= link_to 'Description', #{link_pattern}('wrong.md'), target: '_blank'" }
it { is_expected.to report_lint }
end
context 'when link with wrong file path is assigned to a variable' do
let(:haml) { "- my_link = link_to 'Description', #{link_pattern}('wrong.md')" }
it { is_expected.to report_lint }
end
context 'when it is a broken code' do
let(:haml) { "= I am broken! ]]]]" }
it { is_expected.not_to report_lint }
end
context 'when anchor belongs to a different element' do
let(:haml) { "= link_to 'Description', #{link_pattern}('README.md'), target: (anchor: 'blank')" }
it { is_expected.not_to report_lint }
end
context "when a simple #{link_pattern}" do
let(:haml) { "- url = #{link_pattern}('wrong.md')" }
it { is_expected.to report_lint }
end
context 'when link is not a string' do
let(:haml) { "- url = #{link_pattern}(help_url)" }
it { is_expected.not_to report_lint }
end
context 'when link is a part of the tag' do
let(:haml) { ".data-form{ data: { url: #{link_pattern}('wrong.md') } }" }
it { is_expected.to report_lint } it { is_expected.to report_lint }
end end
end end
context 'when file path is wrong' do context 'help_page_path' do
let(:haml) { "= link_to 'Description', help_page_path('wrong.md'), target: '_blank'" } it_behaves_like 'link validation rules', 'help_page_path'
it { is_expected.to report_lint }
end end
context 'when link with wrong file path is assigned to a variable' do context 'help_page_url' do
let(:haml) { "- my_link = link_to 'Description', help_page_path('wrong.md')" } it_behaves_like 'link validation rules', 'help_page_url'
it { is_expected.to report_lint }
end
context 'when it is a broken code' do
let(:haml) { "= I am broken! ]]]]" }
it { is_expected.not_to report_lint }
end
context 'when anchor belongs to a different element' do
let(:haml) { "= link_to 'Description', help_page_path('README.md'), target: (anchor: 'blank')" }
it { is_expected.not_to report_lint }
end
context 'when a simple help_page_path' do
let(:haml) { "- url = help_page_path('wrong.md')" }
it { is_expected.to report_lint }
end
context 'when link is not a string' do
let(:haml) { "- url = help_page_path(help_url)" }
it { is_expected.not_to report_lint }
end
context 'when link is a part of the tag' do
let(:haml) { ".data-form{ data: { url: help_page_path('wrong.md') } }" }
it { is_expected.to report_lint }
end end
end end

View File

@ -3,77 +3,111 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Todos::Destroy::EntityLeaveService do RSpec.describe Todos::Destroy::EntityLeaveService do
let(:group) { create(:group, :private) } let_it_be(:group, reload: true) { create(:group, :private) }
let(:project) { create(:project, group: group) } let_it_be(:project, reload: true) { create(:project, group: group) }
let(:user) { create(:user) } let_it_be(:user, reload: true) { create(:user) }
let(:user2) { create(:user) } let_it_be(:user2, reload: true) { create(:user) }
let(:issue) { create(:issue, project: project, confidential: true) } let_it_be(:issue) { create(:issue, project: project) }
let(:mr) { create(:merge_request, source_project: project) } let_it_be(:issue_c) { create(:issue, project: project, confidential: true) }
let_it_be(:todo_group_user) { create(:todo, user: user, group: group) }
let_it_be(:todo_group_user2) { create(:todo, user: user2, group: group) }
let!(:todo_mr_user) { create(:todo, user: user, target: mr, project: project) } let(:mr) { create(:merge_request, source_project: project) }
let!(:todo_issue_user) { create(:todo, user: user, target: issue, project: project) } let!(:todo_mr_user) { create(:todo, user: user, target: mr, project: project) }
let!(:todo_group_user) { create(:todo, user: user, group: group) } let!(:todo_issue_user) { create(:todo, user: user, target: issue, project: project) }
let!(:todo_issue_user2) { create(:todo, user: user2, target: issue, project: project) } let!(:todo_issue_c_user) { create(:todo, user: user, target: issue_c, project: project) }
let!(:todo_group_user2) { create(:todo, user: user2, group: group) } let!(:todo_issue_c_user2) { create(:todo, user: user2, target: issue_c, project: project) }
shared_examples 'using different access permissions' do |access_table|
using RSpec::Parameterized::TableSyntax
where(:group_access, :project_access, :c_todos, :mr_todos, :method, &access_table)
with_them do
before do
set_access(project, user, project_access) if project_access
set_access(group, user, group_access) if group_access
end
it "#{params[:method].to_s.humanize(capitalize: false)}" do
send(method)
end
end
end
shared_examples 'does not remove any todos' do
it { does_not_remove_any_todos }
end
shared_examples 'removes only confidential issues todos' do
it { removes_only_confidential_issues_todos }
end
shared_examples 'removes confidential issues and merge request todos' do
it { removes_confidential_issues_and_merge_request_todos }
end
def does_not_remove_any_todos
expect { subject }.not_to change { Todo.count }
end
def removes_only_confidential_issues_todos
expect { subject }.to change { Todo.count }.from(6).to(5)
end
def removes_confidential_issues_and_merge_request_todos
expect { subject }.to change { Todo.count }.from(6).to(4)
expect(user.todos).to match_array([todo_issue_user, todo_group_user])
end
def set_access(object, user, access_name)
case access_name
when :developer
object.add_developer(user)
when :reporter
object.add_reporter(user)
when :guest
object.add_guest(user)
end
end
describe '#execute' do describe '#execute' do
context 'when a user leaves a project' do describe 'updating a Project' do
subject { described_class.new(user.id, project.id, 'Project').execute } subject { described_class.new(user.id, project.id, 'Project').execute }
context 'when project is private' do context 'when project is private' do
it 'removes project todos for the provided user' do context 'when a user leaves a project' do
expect { subject }.to change { Todo.count }.from(5).to(3) it 'removes project todos for the provided user' do
expect { subject }.to change { Todo.count }.from(6).to(3)
expect(user.todos).to match_array([todo_group_user]) expect(user.todos).to match_array([todo_group_user])
expect(user2.todos).to match_array([todo_issue_user2, todo_group_user2]) expect(user2.todos).to match_array([todo_issue_c_user2, todo_group_user2])
end
context 'when the user is member of the project' do
before do
project.add_developer(user)
end
it 'does not remove any todos' do
expect { subject }.not_to change { Todo.count }
end end
end end
context 'when the user is a project guest' do context 'access permissions' do
before do # rubocop:disable RSpec/LeakyConstantDeclaration
project.add_guest(user) PROJECT_PRIVATE_ACCESS_TABLE =
end lambda do |_|
[
# :group_access, :project_access, :c_todos, :mr_todos, :method
[nil, :reporter, :keep, :keep, :does_not_remove_any_todos],
[nil, :guest, :delete, :keep, :removes_only_confidential_issues_todos],
[:reporter, nil, :keep, :keep, :does_not_remove_any_todos],
[:guest, nil, :delete, :keep, :removes_only_confidential_issues_todos]
]
end
# rubocop:enable RSpec/LeakyConstantDeclaration
it 'removes only confidential issues todos' do it_behaves_like 'using different access permissions', PROJECT_PRIVATE_ACCESS_TABLE
expect { subject }.to change { Todo.count }.from(5).to(4)
end
end
context 'when the user is member of a parent group' do
before do
group.add_developer(user)
end
it 'does not remove any todos' do
expect { subject }.not_to change { Todo.count }
end
end
context 'when the user is guest of a parent group' do
before do
project.add_guest(user)
end
it 'removes only confidential issues todos' do
expect { subject }.to change { Todo.count }.from(5).to(4)
end
end end
end end
context 'when project is not private' do context 'when project is not private' do
before do let_it_be(:group, reload: true) { create(:group, :internal) }
group.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) let_it_be(:project, reload: true) { create(:project, :internal, group: group) }
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) let_it_be(:issue, reload: true) { create(:issue, project: project) }
end let_it_be(:issue_c, reload: true) { create(:issue, project: project, confidential: true) }
it 'enqueues the PrivateFeaturesWorker' do it 'enqueues the PrivateFeaturesWorker' do
expect(TodosDestroyer::PrivateFeaturesWorker) expect(TodosDestroyer::PrivateFeaturesWorker)
@ -84,50 +118,41 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
context 'confidential issues' do context 'confidential issues' do
context 'when a user is not an author of confidential issue' do context 'when a user is not an author of confidential issue' do
it 'removes only confidential issues todos' do it_behaves_like 'removes only confidential issues todos'
expect { subject }.to change { Todo.count }.from(5).to(4)
end
end end
context 'when a user is an author of confidential issue' do context 'when a user is an author of confidential issue' do
before do before do
issue.update!(author: user) issue_c.update!(author: user)
end end
it 'does not remove any todos' do it_behaves_like 'does not remove any todos'
expect { subject }.not_to change { Todo.count }
end
end end
context 'when a user is an assignee of confidential issue' do context 'when a user is an assignee of confidential issue' do
before do before do
issue.assignees << user issue_c.assignees << user
end end
it 'does not remove any todos' do it_behaves_like 'does not remove any todos'
expect { subject }.not_to change { Todo.count }
end
end end
context 'when a user is a project guest' do context 'access permissions' do
before do # rubocop:disable RSpec/LeakyConstantDeclaration
project.add_guest(user) PROJECT_NOT_PRIVATE_ACCESS_TABLE =
end lambda do |_|
[
# :group_access, :project_access, :c_todos, :mr_todos, :method
[nil, :reporter, :keep, :keep, :does_not_remove_any_todos],
[nil, :guest, :delete, :keep, :removes_only_confidential_issues_todos],
[:reporter, nil, :keep, :keep, :does_not_remove_any_todos],
[:guest, nil, :delete, :keep, :removes_only_confidential_issues_todos],
[:reporter, :guest, :keep, :keep, :does_not_remove_any_todos]
]
end
# rubocop:enable RSpec/LeakyConstantDeclaration
it 'removes only confidential issues todos' do it_behaves_like 'using different access permissions', PROJECT_NOT_PRIVATE_ACCESS_TABLE
expect { subject }.to change { Todo.count }.from(5).to(4)
end
end
context 'when a user is a project guest but group developer' do
before do
project.add_guest(user)
group.add_developer(user)
end
it 'does not remove any todos' do
expect { subject }.not_to change { Todo.count }
end
end end
end end
@ -138,42 +163,39 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
end end
it 'removes only users issue todos' do it 'removes only users issue todos' do
expect { subject }.to change { Todo.count }.from(5).to(4) expect { subject }.to change { Todo.count }.from(6).to(5)
end end
end end
end end
end end
end end
context 'when a user leaves a group' do describe 'updating a Group' do
subject { described_class.new(user.id, group.id, 'Group').execute } subject { described_class.new(user.id, group.id, 'Group').execute }
context 'when group is private' do context 'when group is private' do
it 'removes group and subproject todos for the user' do context 'when a user leaves a group' do
expect { subject }.to change { Todo.count }.from(5).to(2) it 'removes group and subproject todos for the user' do
expect { subject }.to change { Todo.count }.from(6).to(2)
expect(user.todos).to be_empty expect(user.todos).to be_empty
expect(user2.todos).to match_array([todo_issue_user2, todo_group_user2]) expect(user2.todos).to match_array([todo_issue_c_user2, todo_group_user2])
end
context 'when the user is member of the group' do
before do
group.add_developer(user)
end
it 'does not remove any todos' do
expect { subject }.not_to change { Todo.count }
end end
end end
context 'when the user is member of the group project but not the group' do context 'access permissions' do
before do # rubocop:disable RSpec/LeakyConstantDeclaration
project.add_developer(user) GROUP_PRIVATE_ACCESS_TABLE =
end lambda do |_|
[
# :group_access, :project_access, :c_todos, :mr_todos, :method
[nil, :reporter, :keep, :keep, :does_not_remove_any_todos],
[:reporter, nil, :keep, :keep, :does_not_remove_any_todos]
]
end
# rubocop:enable RSpec/LeakyConstantDeclaration
it 'does not remove any todos' do it_behaves_like 'using different access permissions', GROUP_PRIVATE_ACCESS_TABLE
expect { subject }.not_to change { Todo.count }
end
end end
context 'with nested groups' do context 'with nested groups' do
@ -191,12 +213,12 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
context 'when the user is not a member of any groups/projects' do context 'when the user is not a member of any groups/projects' do
it 'removes todos for the user including subprojects todos' do it 'removes todos for the user including subprojects todos' do
expect { subject }.to change { Todo.count }.from(11).to(4) expect { subject }.to change { Todo.count }.from(12).to(4)
expect(user.todos).to be_empty expect(user.todos).to be_empty
expect(user2.todos) expect(user2.todos)
.to match_array( .to match_array(
[todo_issue_user2, todo_group_user2, todo_subproject_user2, todo_subpgroup_user2] [todo_issue_c_user2, todo_group_user2, todo_subproject_user2, todo_subpgroup_user2]
) )
end end
end end
@ -208,9 +230,7 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
parent_group.add_developer(user) parent_group.add_developer(user)
end end
it 'does not remove any todos' do it_behaves_like 'does not remove any todos'
expect { subject }.not_to change { Todo.count }
end
end end
context 'when the user is member of a subgroup' do context 'when the user is member of a subgroup' do
@ -219,12 +239,12 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
end end
it 'does not remove group and subproject todos' do it 'does not remove group and subproject todos' do
expect { subject }.to change { Todo.count }.from(11).to(7) expect { subject }.to change { Todo.count }.from(12).to(7)
expect(user.todos).to match_array([todo_group_user, todo_subgroup_user, todo_subproject_user]) expect(user.todos).to match_array([todo_group_user, todo_subgroup_user, todo_subproject_user])
expect(user2.todos) expect(user2.todos)
.to match_array( .to match_array(
[todo_issue_user2, todo_group_user2, todo_subproject_user2, todo_subpgroup_user2] [todo_issue_c_user2, todo_group_user2, todo_subproject_user2, todo_subpgroup_user2]
) )
end end
end end
@ -235,12 +255,12 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
end end
it 'does not remove subproject and group todos' do it 'does not remove subproject and group todos' do
expect { subject }.to change { Todo.count }.from(11).to(7) expect { subject }.to change { Todo.count }.from(12).to(7)
expect(user.todos).to match_array([todo_subgroup_user, todo_group_user, todo_subproject_user]) expect(user.todos).to match_array([todo_subgroup_user, todo_group_user, todo_subproject_user])
expect(user2.todos) expect(user2.todos)
.to match_array( .to match_array(
[todo_issue_user2, todo_group_user2, todo_subproject_user2, todo_subpgroup_user2] [todo_issue_c_user2, todo_group_user2, todo_subproject_user2, todo_subpgroup_user2]
) )
end end
end end
@ -248,10 +268,10 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
end end
context 'when group is not private' do context 'when group is not private' do
before do let_it_be(:group, reload: true) { create(:group, :internal) }
group.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) let_it_be(:project, reload: true) { create(:project, :internal, group: group) }
project.update!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) let_it_be(:issue, reload: true) { create(:issue, project: project) }
end let_it_be(:issue_c, reload: true) { create(:issue, project: project, confidential: true) }
it 'enqueues the PrivateFeaturesWorker' do it 'enqueues the PrivateFeaturesWorker' do
expect(TodosDestroyer::PrivateFeaturesWorker) expect(TodosDestroyer::PrivateFeaturesWorker)
@ -260,31 +280,20 @@ RSpec.describe Todos::Destroy::EntityLeaveService do
subject subject
end end
context 'when user is not member' do context 'access permissions' do
it 'removes only confidential issues todos' do # rubocop:disable RSpec/LeakyConstantDeclaration
expect { subject }.to change { Todo.count }.from(5).to(4) GROUP_NOT_PRIVATE_ACCESS_TABLE =
end lambda do |_|
end [
# :group_access, :project_access, :c_todos, :mr_todos, :method
[nil, nil, :delete, :keep, :removes_only_confidential_issues_todos],
[nil, :guest, :delete, :keep, :removes_only_confidential_issues_todos],
[:reporter, :guest, :keep, :keep, :does_not_remove_any_todos]
]
end
# rubocop:enable RSpec/LeakyConstantDeclaration
context 'when user is a project guest' do it_behaves_like 'using different access permissions', GROUP_NOT_PRIVATE_ACCESS_TABLE
before do
project.add_guest(user)
end
it 'removes only confidential issues todos' do
expect { subject }.to change { Todo.count }.from(5).to(4)
end
end
context 'when user is a project guest & group developer' do
before do
project.add_guest(user)
group.add_developer(user)
end
it 'does not remove any todos' do
expect { subject }.not_to change { Todo.count }
end
end end
end end
end end