Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2022-03-09 06:07:38 +00:00
parent 276941b2c4
commit 3feda79a55
31 changed files with 191 additions and 167 deletions

View File

@ -4,20 +4,28 @@ import {
GlDropdownSectionHeader,
GlDropdownDivider,
GlAvatar,
GlAlert,
GlLoadingIcon,
GlSafeHtmlDirective as SafeHtml,
} from '@gitlab/ui';
import { mapState, mapGetters } from 'vuex';
import { s__ } from '~/locale';
import highlight from '~/lib/utils/highlight';
import { GROUPS_CATEGORY, PROJECTS_CATEGORY, LARGE_AVATAR_PX, SMALL_AVATAR_PX } from '../constants';
export default {
name: 'HeaderSearchAutocompleteItems',
i18n: {
autocompleteErrorMessage: s__(
'GlobalSearch|There was an error fetching search autocomplete suggestions.',
),
},
components: {
GlDropdownItem,
GlDropdownSectionHeader,
GlDropdownDivider,
GlAvatar,
GlAlert,
GlLoadingIcon,
},
directives: {
@ -31,7 +39,7 @@ export default {
},
},
computed: {
...mapState(['search', 'loading']),
...mapState(['search', 'loading', 'autocompleteError']),
...mapGetters(['autocompleteGroupedSearchOptions']),
},
watch: {
@ -93,5 +101,13 @@ export default {
</div>
</template>
<gl-loading-icon v-else size="lg" class="my-4" />
<gl-alert
v-if="autocompleteError"
class="gl-text-body gl-mt-2"
:dismissible="false"
variant="danger"
>
{{ $options.i18n.autocompleteErrorMessage }}
</gl-alert>
</div>
</template>

View File

@ -1,6 +1,4 @@
import createFlash from '~/flash';
import axios from '~/lib/utils/axios_utils';
import { __ } from '~/locale';
import * as types from './mutation_types';
export const fetchAutocompleteOptions = ({ commit, getters }) => {
@ -10,7 +8,6 @@ export const fetchAutocompleteOptions = ({ commit, getters }) => {
.then(({ data }) => commit(types.RECEIVE_AUTOCOMPLETE_SUCCESS, data))
.catch(() => {
commit(types.RECEIVE_AUTOCOMPLETE_ERROR);
createFlash({ message: __('There was an error fetching search autocomplete suggestions') });
});
};

View File

@ -4,19 +4,23 @@ export default {
[types.REQUEST_AUTOCOMPLETE](state) {
state.loading = true;
state.autocompleteOptions = [];
state.autocompleteError = false;
},
[types.RECEIVE_AUTOCOMPLETE_SUCCESS](state, data) {
state.loading = false;
state.autocompleteOptions = data.map((d, i) => {
return { html_id: `autocomplete-${d.category}-${i}`, ...d };
});
state.autocompleteError = false;
},
[types.RECEIVE_AUTOCOMPLETE_ERROR](state) {
state.loading = false;
state.autocompleteOptions = [];
state.autocompleteError = true;
},
[types.CLEAR_AUTOCOMPLETE](state) {
state.autocompleteOptions = [];
state.autocompleteError = false;
},
[types.SET_SEARCH](state, value) {
state.search = value;

View File

@ -6,6 +6,7 @@ const createState = ({ searchPath, issuesPath, mrPath, autocompletePath, searchC
searchContext,
search: '',
autocompleteOptions: [],
autocompleteError: false,
loading: false,
});
export default createState;

View File

@ -3,7 +3,7 @@
query searchUsers($fullPath: ID!, $search: String, $isProject: Boolean = false) {
group(fullPath: $fullPath) @skip(if: $isProject) {
id
groupMembers(search: $search) {
groupMembers(search: $search, relations: [DIRECT, INHERITED, SHARED_FROM_GROUPS]) {
nodes {
id
user {
@ -14,7 +14,7 @@ query searchUsers($fullPath: ID!, $search: String, $isProject: Boolean = false)
}
project(fullPath: $fullPath) @include(if: $isProject) {
id
projectMembers(search: $search) {
projectMembers(search: $search, relations: [DIRECT, INHERITED, INVITED_GROUPS]) {
nodes {
id
user {

View File

@ -63,7 +63,7 @@ export default {
computed: {
shouldEnableRealtime() {
// Note: Realtime is only available on issues right now, future support for MR wil be built later.
return this.glFeatures.realTimeIssueSidebar && this.issuableType === 'issue';
return this.issuableType === 'issue';
},
queryVariables() {
return {

View File

@ -112,7 +112,7 @@ export default {
computed: {
shouldEnableRealtime() {
// Note: Realtime is only available on issues right now, future support for MR wil be built later.
return this.glFeatures.realTimeIssueSidebar && this.issuableType === IssuableType.Issue;
return this.issuableType === IssuableType.Issue;
},
queryVariables() {
return {

View File

@ -1810,6 +1810,9 @@ body.gl-dark .navbar-gitlab .navbar-sub-nav {
body.gl-dark .navbar-gitlab .nav > li {
color: #fafafa;
}
body.gl-dark .navbar-gitlab .nav > li.header-search-new {
color: #fafafa;
}
body.gl-dark .navbar-gitlab .nav > li > a .notification-dot {
border: 2px solid #fafafa;
}
@ -1847,8 +1850,8 @@ body.gl-dark
body.gl-dark .header-search {
background-color: rgba(250, 250, 250, 0.2) !important;
}
body.gl-dark .header-search svg {
color: rgba(250, 250, 250, 0.8) !important;
body.gl-dark .header-search svg.gl-search-box-by-type-search-icon {
color: rgba(250, 250, 250, 0.8);
}
body.gl-dark .header-search input {
background-color: transparent;

View File

@ -64,6 +64,10 @@
> li {
color: $search-and-nav-links;
&.header-search-new {
color: $sidebar-text;
}
> a {
.notification-dot {
border: 2px solid $nav-svg-color;
@ -151,10 +155,11 @@
background-color: rgba($search-and-nav-links, 0.3) !important;
}
svg {
color: rgba($search-and-nav-links, 0.8) !important;
svg.gl-search-box-by-type-search-icon {
color: rgba($search-and-nav-links, 0.8);
}
input {
background-color: transparent;
color: rgba($search-and-nav-links, 0.8);

View File

@ -4,7 +4,7 @@ module FlocOptOut
extend ActiveSupport::Concern
included do
after_action :set_floc_opt_out_header, unless: :floc_enabled?
before_action :set_floc_opt_out_header, unless: :floc_enabled?
end
def floc_enabled?

View File

@ -45,7 +45,6 @@ class Projects::IssuesController < Projects::ApplicationController
end
before_action only: :show do
push_frontend_feature_flag(:real_time_issue_sidebar, project, default_enabled: :yaml)
push_frontend_feature_flag(:confidential_notes, project&.group, default_enabled: :yaml)
push_frontend_feature_flag(:issue_assignees_widget, project, default_enabled: :yaml)
push_frontend_feature_flag(:paginated_issue_discussions, project, default_enabled: :yaml)

View File

@ -79,9 +79,7 @@ module Issues
todo_service.reassigned_assignable(issue, current_user, old_assignees)
track_incident_action(current_user, issue, :incident_assigned)
if Feature.enabled?(:broadcast_issue_updates, issue.project, default_enabled: :yaml)
GraphqlTriggers.issuable_assignees_updated(issue)
end
GraphqlTriggers.issuable_assignees_updated(issue)
end
def handle_task_changes(issuable)

View File

@ -38,7 +38,7 @@
= render 'layouts/header/new_dropdown', class: 'gl-display-none gl-sm-display-block'
- if top_nav_show_search
- search_menu_item = top_nav_search_menu_item_attrs
%li.nav-item.d-none.d-lg-block.m-auto
%li.nav-item.header-search-new.d-none.d-lg-block.m-auto
- unless current_controller?(:search)
- if Feature.enabled?(:new_header_search)
#js-header-search.header-search{ data: { 'search-context' => header_search_context.to_json,

View File

@ -8,7 +8,7 @@
.form-group.project-name.col-sm-12
= f.label :name, class: 'label-bold' do
%span= _("Project name")
= f.text_field :name, placeholder: "My awesome project", class: "form-control gl-form-input input-lg", data: { track_label: "#{track_label}", track_action: "activate_form_input", track_property: "project_name", track_value: "" }, required: true, aria: { required: true }
= f.text_field :name, placeholder: "My awesome project", class: "form-control gl-form-input input-lg", data: { qa_selector: 'project_name', track_label: "#{track_label}", track_action: "activate_form_input", track_property: "project_name", track_value: "" }, required: true, aria: { required: true }
.form-group.project-path.col-sm-6
= f.label :namespace_id, class: 'label-bold' do
%span= _('Project URL')
@ -29,7 +29,7 @@
.form-group.project-path.col-sm-6
= f.label :path, class: 'label-bold' do
%span= _("Project slug")
= f.text_field :path, placeholder: "my-awesome-project", class: "form-control gl-form-input", required: true, aria: { required: true }, data: { username: current_user.username }
= f.text_field :path, placeholder: "my-awesome-project", class: "form-control gl-form-input", required: true, aria: { required: true }, data: { qa_selector: 'project_path', username: current_user.username }
- if current_user.can_create_group?
.form-text.text-muted
- link_start_group_path = '<a href="%{path}">' % { path: new_group_path }
@ -44,14 +44,14 @@
.form-group
= f.label :description, class: 'label-bold' do
= s_('ProjectsNew|Project description %{tag_start}(optional)%{tag_end}').html_safe % { tag_start: '<span>'.html_safe, tag_end: '</span>'.html_safe }
= f.text_area :description, placeholder: s_('ProjectsNew|Description format'), class: "form-control gl-form-input", rows: 3, maxlength: 250, data: { track_label: "#{track_label}", track_action: "activate_form_input", track_property: "project_description", track_value: "" }
= f.text_area :description, placeholder: s_('ProjectsNew|Description format'), class: "form-control gl-form-input", rows: 3, maxlength: 250, data: { qa_selector: 'project_description', track_label: "#{track_label}", track_action: "activate_form_input", track_property: "project_description", track_value: "" }
.js-deployment-target-select
= f.label :visibility_level, class: 'label-bold' do
= s_('ProjectsNew|Visibility Level')
= link_to sprite_icon('question-o'), help_page_path('public_access/public_access'), aria: { label: 'Documentation for Visibility Level' }, target: '_blank', rel: 'noopener noreferrer'
= render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false
= render 'shared/visibility_level', f: f, visibility_level: visibility_level.to_i, can_change_visibility_level: true, form_model: @project, with_label: false, data: { qa_selector: 'visibility_radios'}
- if !hide_init_with_readme
= f.label :project_configuration, class: 'label-bold' do
@ -74,5 +74,5 @@
- e.variant(:unchecked_free_indicator) do
= render 'new_project_initialize_with_sast', experiment_name: e.name, track_label: track_label, checked: false, with_free_badge: true
= f.submit _('Create project'), class: "btn gl-button btn-confirm", data: { track_label: "#{track_label}", track_action: "click_button", track_property: "create_project", track_value: "" }
= f.submit _('Create project'), class: "btn gl-button btn-confirm", data: { qa_selector: 'project_create_button', track_label: "#{track_label}", track_action: "click_button", track_property: "create_project", track_value: "" }
= link_to _('Cancel'), dashboard_projects_path, class: 'btn gl-button btn-default btn-cancel', data: { track_label: "#{track_label}", track_action: "click_button", track_property: "cancel", track_value: "" }

View File

@ -1,8 +0,0 @@
---
name: broadcast_issue_updates
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30732
rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/production/-/issues/3413
milestone: '13.0'
type: development
group: group::project management
default_enabled: true

View File

@ -1,8 +0,0 @@
---
name: real_time_issue_sidebar
introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/30239
rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/1210
milestone: '13.0'
type: development
group: group::project management
default_enabled: true

View File

@ -578,13 +578,11 @@ To copy the issue's email address:
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/17589) in GitLab 13.3. Disabled by default.
> - [Enabled on GitLab.com](https://gitlab.com/gitlab-com/gl-infra/production/-/issues/3413) in GitLab 13.9.
> - [Enabled on self-managed](https://gitlab.com/gitlab-org/gitlab/-/issues/17589) in GitLab 14.5.
FLAG:
On self-managed GitLab, by default this feature is available. To hide the feature per project or for your entire instance, ask an administrator to
[disable the feature flags](../../../administration/feature_flags.md) named `real_time_issue_sidebar` and `broadcast_issue_updates`.
On GitLab.com, this feature is available.
> - [Generally available](https://gitlab.com/gitlab-org/gitlab/-/issues/17589) in GitLab 14.9. Feature flags `real_time_issue_sidebar` and `broadcast_issue_updates` removed.
Assignees in the sidebar are updated in real time.
When you're viewing an issue and somebody changes its assignee,
you can see the change without having to refresh the page.
## Assignee

View File

@ -702,6 +702,8 @@ module API
if user.ldap_blocked?
forbidden!('LDAP blocked users cannot be modified by the API')
elsif current_user == user
forbidden!('The API initiating user cannot be blocked by the API')
end
break if user.blocked?

View File

@ -16837,6 +16837,9 @@ msgstr ""
msgid "GlobalSearch|Search results are loading"
msgstr ""
msgid "GlobalSearch|There was an error fetching search autocomplete suggestions."
msgstr ""
msgid "GlobalSearch|Type and press the enter key to submit search."
msgstr ""
@ -37200,9 +37203,6 @@ msgstr ""
msgid "There was an error fetching projects"
msgstr ""
msgid "There was an error fetching search autocomplete suggestions"
msgstr ""
msgid "There was an error fetching stage total counts"
msgstr ""

View File

@ -13,11 +13,11 @@ module QA
view 'app/views/projects/_new_project_fields.html.haml' do
element :initialize_with_readme_checkbox
element :project_name, 'text_field :name' # rubocop:disable QA/ElementWithPattern
element :project_path, 'text_field :path' # rubocop:disable QA/ElementWithPattern
element :project_description, 'text_area :description' # rubocop:disable QA/ElementWithPattern
element :project_create_button, "submit _('Create project')" # rubocop:disable QA/ElementWithPattern
element :visibility_radios, 'visibility_level:' # rubocop:disable QA/ElementWithPattern
element :project_name
element :project_path
element :project_description
element :project_create_button
element :visibility_radios
end
view 'app/views/projects/_new_project_initialize_with_sast.html.haml' do

View File

@ -12,20 +12,12 @@ module QA
end
before do
Runtime::Feature.enable('real_time_issue_sidebar', project: project)
Runtime::Feature.enable('broadcast_issue_updates', project: project)
Flow::Login.sign_in
project.add_member(user1)
project.add_member(user2)
end
after do
Runtime::Feature.disable('real_time_issue_sidebar', project: project)
Runtime::Feature.disable('broadcast_issue_updates', project: project)
end
it 'update without refresh', testcase: 'https://gitlab.com/gitlab-org/gitlab/-/quality/test_cases/347941' do
issue = Resource::Issue.fabricate_via_api! do |issue|
issue.project = project

View File

@ -1059,15 +1059,25 @@ RSpec.describe ApplicationController do
describe 'setting permissions-policy header' do
controller do
skip_before_action :authenticate_user!
before_action :redirect_to_example, only: [:redirect]
def index
render html: 'It is a flock of sheep, not a floc of sheep.'
end
def redirect
raise 'Should not be reached'
end
def redirect_to_example
redirect_to('https://example.com')
end
end
before do
routes.draw do
get 'index' => 'anonymous#index'
get 'redirect' => 'anonymous#redirect'
end
end
@ -1093,6 +1103,13 @@ RSpec.describe ApplicationController do
expect(response.headers['Permissions-Policy']).to eq('interest-cohort=()')
end
it 'sets the Permissions-Policy header even when redirected before_action' do
get :redirect
expect(response).to have_gitlab_http_status(:redirect)
expect(response.headers['Permissions-Policy']).to eq('interest-cohort=()')
end
end
end
end

View File

@ -1065,35 +1065,6 @@ RSpec.describe Projects::IssuesController do
.not_to exceed_query_limit(control_count + 2 * labels.count)
end
context 'real-time sidebar feature flag' do
let_it_be(:project) { create(:project, :public) }
let_it_be(:issue) { create(:issue, project: project) }
context 'when enabled' do
before do
stub_feature_flags(real_time_issue_sidebar: true)
end
it 'pushes the correct value to the frontend' do
go(id: issue.to_param)
expect(Gon.features).to include('realTimeIssueSidebar' => true)
end
end
context 'when disabled' do
before do
stub_feature_flags(real_time_issue_sidebar: false)
end
it 'pushes the correct value to the frontend' do
go(id: issue.to_param)
expect(Gon.features).to include('realTimeIssueSidebar' => false)
end
end
end
it 'logs the view with Gitlab::Search::RecentIssues' do
sign_in(user)
recent_issues_double = instance_double(::Gitlab::Search::RecentIssues, log_view: nil)

View File

@ -12,14 +12,14 @@ RSpec.describe 'Dropdown assignee', :js do
let(:js_dropdown_assignee) { '#js-dropdown-assignee' }
let(:filter_dropdown) { find("#{js_dropdown_assignee} .filter-dropdown") }
before do
project.add_maintainer(user)
sign_in(user)
visit project_issues_path(project)
end
describe 'behavior' do
before do
project.add_maintainer(user)
sign_in(user)
visit project_issues_path(project)
end
it 'loads all the assignees when opened' do
input_filtered_search('assignee:=', submit: false, extra_space: false)
@ -35,6 +35,11 @@ RSpec.describe 'Dropdown assignee', :js do
describe 'selecting from dropdown without Ajax call' do
before do
project.add_maintainer(user)
sign_in(user)
visit project_issues_path(project)
Gitlab::Testing::RequestBlockerMiddleware.block_requests!
input_filtered_search('assignee:=', submit: false, extra_space: false)
end
@ -51,4 +56,60 @@ RSpec.describe 'Dropdown assignee', :js do
expect_filtered_search_input_empty
end
end
context 'assignee suggestions' do
let!(:group) { create(:group) }
let!(:group_project) { create(:project, namespace: group) }
let!(:group_user) { create(:user) }
let!(:subgroup) { create(:group, parent: group) }
let!(:subgroup_project) { create(:project, namespace: subgroup) }
let!(:subgroup_project_issue) { create(:issue, project: subgroup_project) }
let!(:subgroup_user) { create(:user) }
let!(:subsubgroup) { create(:group, parent: subgroup) }
let!(:subsubgroup_project) { create(:project, namespace: subsubgroup) }
let!(:subsubgroup_user) { create(:user) }
let!(:invited_to_group_group) { create(:group) }
let!(:invited_to_group_group_user) { create(:user) }
let!(:invited_to_project_group) { create(:group) }
let!(:invited_to_project_group_user) { create(:user) }
before do
group.add_developer(group_user)
subgroup.add_developer(subgroup_user)
subsubgroup.add_developer(subsubgroup_user)
invited_to_group_group.add_developer(invited_to_group_group_user)
invited_to_project_group.add_developer(invited_to_project_group_user)
create(:group_group_link, shared_group: subgroup, shared_with_group: invited_to_group_group)
create(:project_group_link, project: subgroup_project, group: invited_to_project_group)
sign_in(subgroup_user)
end
it 'shows inherited, direct, and invited group members but not descendent members', :aggregate_failures do
visit issues_group_path(subgroup)
input_filtered_search('assignee:=', submit: false, extra_space: false)
expect(page).to have_text group_user.name
expect(page).to have_text subgroup_user.name
expect(page).to have_text invited_to_group_group_user.name
expect(page).not_to have_text subsubgroup_user.name
expect(page).not_to have_text invited_to_project_group_user.name
visit project_issues_path(subgroup_project)
input_filtered_search('assignee:=', submit: false, extra_space: false)
expect(page).to have_text group_user.name
expect(page).to have_text subgroup_user.name
expect(page).to have_text invited_to_project_group_user.name
expect(page).not_to have_text subsubgroup_user.name
expect(page).not_to have_text invited_to_group_group_user.name
end
end
end

View File

@ -1,4 +1,4 @@
import { GlDropdownItem, GlLoadingIcon, GlAvatar } from '@gitlab/ui';
import { GlDropdownItem, GlLoadingIcon, GlAvatar, GlAlert } from '@gitlab/ui';
import { shallowMount } from '@vue/test-utils';
import Vue, { nextTick } from 'vue';
import Vuex from 'vuex';
@ -46,6 +46,7 @@ describe('HeaderSearchAutocompleteItems', () => {
const findDropdownItemLinks = () => findDropdownItems().wrappers.map((w) => w.attributes('href'));
const findGlLoadingIcon = () => wrapper.findComponent(GlLoadingIcon);
const findGlAvatar = () => wrapper.findComponent(GlAvatar);
const findGlAlert = () => wrapper.findComponent(GlAlert);
describe('template', () => {
describe('when loading is true', () => {
@ -62,6 +63,15 @@ describe('HeaderSearchAutocompleteItems', () => {
});
});
describe('when api returns error', () => {
beforeEach(() => {
createComponent({ autocompleteError: true });
});
it('renders Alert', () => {
expect(findGlAlert().exists()).toBe(true);
});
});
describe('when loading is false', () => {
beforeEach(() => {
createComponent({ loading: false });
@ -86,6 +96,7 @@ describe('HeaderSearchAutocompleteItems', () => {
expect(findDropdownItemLinks()).toStrictEqual(expectedLinks);
});
});
describe.each`
item | showAvatar | avatarSize
${{ data: [{ category: PROJECTS_CATEGORY, avatar_url: null }] }} | ${true} | ${String(LARGE_AVATAR_PX)}

View File

@ -1,6 +1,5 @@
import MockAdapter from 'axios-mock-adapter';
import testAction from 'helpers/vuex_action_helper';
import createFlash from '~/flash';
import * as actions from '~/header_search/store/actions';
import * as types from '~/header_search/store/mutation_types';
import createState from '~/header_search/store/state';
@ -13,11 +12,6 @@ describe('Header Search Store Actions', () => {
let state;
let mock;
const flashCallback = (callCount) => {
expect(createFlash).toHaveBeenCalledTimes(callCount);
createFlash.mockClear();
};
beforeEach(() => {
state = createState({});
mock = new MockAdapter(axios);
@ -29,10 +23,10 @@ describe('Header Search Store Actions', () => {
});
describe.each`
axiosMock | type | expectedMutations | flashCallCount
${{ method: 'onGet', code: 200, res: MOCK_AUTOCOMPLETE_OPTIONS_RES }} | ${'success'} | ${[{ type: types.REQUEST_AUTOCOMPLETE }, { type: types.RECEIVE_AUTOCOMPLETE_SUCCESS, payload: MOCK_AUTOCOMPLETE_OPTIONS_RES }]} | ${0}
${{ method: 'onGet', code: 500, res: null }} | ${'error'} | ${[{ type: types.REQUEST_AUTOCOMPLETE }, { type: types.RECEIVE_AUTOCOMPLETE_ERROR }]} | ${1}
`('fetchAutocompleteOptions', ({ axiosMock, type, expectedMutations, flashCallCount }) => {
axiosMock | type | expectedMutations
${{ method: 'onGet', code: 200, res: MOCK_AUTOCOMPLETE_OPTIONS_RES }} | ${'success'} | ${[{ type: types.REQUEST_AUTOCOMPLETE }, { type: types.RECEIVE_AUTOCOMPLETE_SUCCESS, payload: MOCK_AUTOCOMPLETE_OPTIONS_RES }]}
${{ method: 'onGet', code: 500, res: null }} | ${'error'} | ${[{ type: types.REQUEST_AUTOCOMPLETE }, { type: types.RECEIVE_AUTOCOMPLETE_ERROR }]}
`('fetchAutocompleteOptions', ({ axiosMock, type, expectedMutations }) => {
describe(`on ${type}`, () => {
beforeEach(() => {
mock[axiosMock.method]().replyOnce(axiosMock.code, axiosMock.res);
@ -42,7 +36,7 @@ describe('Header Search Store Actions', () => {
action: actions.fetchAutocompleteOptions,
state,
expectedMutations,
}).then(() => flashCallback(flashCallCount));
});
});
});
});

View File

@ -20,6 +20,7 @@ describe('Header Search Store Mutations', () => {
expect(state.loading).toBe(true);
expect(state.autocompleteOptions).toStrictEqual([]);
expect(state.autocompleteError).toBe(false);
});
});
@ -29,6 +30,7 @@ describe('Header Search Store Mutations', () => {
expect(state.loading).toBe(false);
expect(state.autocompleteOptions).toStrictEqual(MOCK_AUTOCOMPLETE_OPTIONS);
expect(state.autocompleteError).toBe(false);
});
});
@ -38,6 +40,7 @@ describe('Header Search Store Mutations', () => {
expect(state.loading).toBe(false);
expect(state.autocompleteOptions).toStrictEqual([]);
expect(state.autocompleteError).toBe(true);
});
});
@ -46,6 +49,7 @@ describe('Header Search Store Mutations', () => {
mutations[types.CLEAR_AUTOCOMPLETE](state);
expect(state.autocompleteOptions).toStrictEqual([]);
expect(state.autocompleteError).toBe(false);
});
});

View File

@ -340,21 +340,9 @@ describe('Sidebar assignees widget', () => {
});
});
it('when realtime feature flag is disabled', async () => {
it('includes the real-time assignees component', async () => {
createComponent();
await waitForPromises();
expect(findRealtimeAssignees().exists()).toBe(false);
});
it('when realtime feature flag is enabled', async () => {
createComponent({
provide: {
glFeatures: {
realTimeIssueSidebar: true,
},
},
});
await waitForPromises();
expect(findRealtimeAssignees().exists()).toBe(true);
});

View File

@ -14,7 +14,7 @@ describe('sidebar assignees', () => {
let wrapper;
let mediator;
let axiosMock;
const createComponent = (realTimeIssueSidebar = false, props) => {
const createComponent = (props) => {
wrapper = shallowMount(SidebarAssignees, {
propsData: {
issuableIid: '1',
@ -25,11 +25,6 @@ describe('sidebar assignees', () => {
changing: false,
...props,
},
provide: {
glFeatures: {
realTimeIssueSidebar,
},
},
// Attaching to document is required because this component emits something from the parent element :/
attachTo: document.body,
});
@ -86,27 +81,17 @@ describe('sidebar assignees', () => {
expect(wrapper.find(Assigness).exists()).toBe(true);
});
describe('when realTimeIssueSidebar is turned on', () => {
describe('when issuableType is issue', () => {
it('finds AssigneesRealtime componeont', () => {
createComponent(true);
describe('when issuableType is issue', () => {
it('finds AssigneesRealtime component', () => {
createComponent();
expect(wrapper.find(AssigneesRealtime).exists()).toBe(true);
});
});
describe('when issuableType is MR', () => {
it('does not find AssigneesRealtime componeont', () => {
createComponent(true, { issuableType: 'MR' });
expect(wrapper.find(AssigneesRealtime).exists()).toBe(false);
});
expect(wrapper.find(AssigneesRealtime).exists()).toBe(true);
});
});
describe('when realTimeIssueSidebar is turned off', () => {
it('does not find AssigneesRealtime', () => {
createComponent(false, { issuableType: 'issue' });
describe('when issuableType is MR', () => {
it('does not find AssigneesRealtime component', () => {
createComponent({ issuableType: 'MR' });
expect(wrapper.find(AssigneesRealtime).exists()).toBe(false);
});

View File

@ -3117,6 +3117,18 @@ RSpec.describe API::Users do
expect(response.body).to eq('null')
end
end
context 'with the API initiating user' do
let(:user_id) { admin.id }
it 'does not block the API initiating user, returns 403' do
block_user
expect(response).to have_gitlab_http_status(:forbidden)
expect(json_response['message']).to eq('403 Forbidden - The API initiating user cannot be blocked by the API')
expect(admin.reload.state).to eq('active')
end
end
end
it 'is not available for non admin users' do

View File

@ -1332,32 +1332,14 @@ RSpec.describe Issues::UpdateService, :mailer do
context 'broadcasting issue assignee updates' do
let(:update_params) { { assignee_ids: [user2.id] } }
context 'when feature flag is enabled' do
before do
stub_feature_flags(broadcast_issue_updates: true)
end
it 'triggers the GraphQL subscription' do
expect(GraphqlTriggers).to receive(:issuable_assignees_updated).with(issue)
it 'triggers the GraphQL subscription' do
expect(GraphqlTriggers).to receive(:issuable_assignees_updated).with(issue)
update_issue(update_params)
end
context 'when assignee is not updated' do
let(:update_params) { { title: 'Some other title' } }
it 'does not trigger the GraphQL subscription' do
expect(GraphqlTriggers).not_to receive(:issuable_assignees_updated).with(issue)
update_issue(update_params)
end
end
update_issue(update_params)
end
context 'when feature flag is disabled' do
before do
stub_feature_flags(broadcast_issue_updates: false)
end
context 'when assignee is not updated' do
let(:update_params) { { title: 'Some other title' } }
it 'does not trigger the GraphQL subscription' do
expect(GraphqlTriggers).not_to receive(:issuable_assignees_updated).with(issue)