Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot
2022-08-22 06:09:36 +00:00
parent a25809b2e5
commit dc90a96501
18 changed files with 182 additions and 255 deletions

View File

@ -7,7 +7,6 @@ import { resetServiceWorkersPublicPath } from '../lib/utils/webpack';
import MergeRequest from '../merge_request';
import discussionCounter from '../notes/components/discussion_counter.vue';
import initDiscussionFilters from '../notes/discussion_filters';
import initSortDiscussions from '../notes/sort_discussions';
import initNotesApp from './init_notes';
export default function initMrNotes() {
@ -52,6 +51,5 @@ export default function initMrNotes() {
}
initDiscussionFilters(store);
initSortDiscussions(store);
});
}

View File

@ -2,6 +2,9 @@
import { GlDropdown, GlDropdownItem, GlDropdownDivider } from '@gitlab/ui';
import { mapGetters, mapActions } from 'vuex';
import { getLocationHash, doesHashExistInUrl } from '~/lib/utils/url_utility';
import { __ } from '~/locale';
import Tracking from '~/tracking';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import {
DISCUSSION_FILTERS_DEFAULT_VALUE,
HISTORY_ONLY_FILTER_VALUE,
@ -9,15 +12,25 @@ import {
DISCUSSION_TAB_LABEL,
DISCUSSION_FILTER_TYPES,
NOTE_UNDERSCORE,
ASC,
DESC,
} from '../constants';
import notesEventHub from '../event_hub';
const SORT_OPTIONS = [
{ key: DESC, text: __('Newest first'), cls: 'js-newest-first' },
{ key: ASC, text: __('Oldest first'), cls: 'js-oldest-first' },
];
export default {
SORT_OPTIONS,
components: {
GlDropdown,
GlDropdownItem,
GlDropdownDivider,
LocalStorageSync,
},
mixins: [Tracking.mixin()],
props: {
filters: {
type: Array,
@ -39,11 +52,24 @@ export default {
};
},
computed: {
...mapGetters(['getNotesDataByProp', 'timelineEnabled', 'isLoading']),
...mapGetters([
'getNotesDataByProp',
'timelineEnabled',
'isLoading',
'sortDirection',
'persistSortOrder',
'noteableType',
]),
currentFilter() {
if (!this.currentValue) return this.filters[0];
return this.filters.find((filter) => filter.value === this.currentValue);
},
selectedSortOption() {
return SORT_OPTIONS.find(({ key }) => this.sortDirection === key);
},
sortStorageKey() {
return `sort_direction_${this.noteableType.toLowerCase()}`;
},
},
created() {
if (window.mrTabs) {
@ -69,6 +95,7 @@ export default {
'setCommentsDisabled',
'setTargetNoteHash',
'setTimelineView',
'setDiscussionSortDirection',
]),
selectFilter(value, persistFilter = true) {
const filter = parseInt(value, 10);
@ -108,31 +135,73 @@ export default {
}
return DISCUSSION_FILTER_TYPES.HISTORY;
},
fetchSortedDiscussions(direction) {
if (this.isSortDropdownItemActive(direction)) {
return;
}
this.setDiscussionSortDirection({ direction });
this.track('change_discussion_sort_direction', { property: direction });
},
isSortDropdownItemActive(sortDir) {
return sortDir === this.sortDirection;
},
},
};
</script>
<template>
<gl-dropdown
<div
v-if="displayFilters"
id="discussion-filter-dropdown"
class="full-width-mobile discussion-filter-container js-discussion-filter-container"
data-qa-selector="discussion_filter_dropdown"
:text="currentFilter.title"
:disabled="isLoading"
id="discussion-preferences"
data-testid="discussion-preferences"
class="gl-display-inline-block gl-vertical-align-bottom full-width-mobile"
>
<div v-for="filter in filters" :key="filter.value" class="dropdown-item-wrapper">
<gl-dropdown-item
:is-check-item="true"
:is-checked="filter.value === currentValue"
:class="{ 'is-active': filter.value === currentValue }"
:data-filter-type="filterType(filter.value)"
data-qa-selector="filter_menu_item"
@click.prevent="selectFilter(filter.value)"
<local-storage-sync
:value="sortDirection"
:storage-key="sortStorageKey"
:persist="persistSortOrder"
as-string
@input="setDiscussionSortDirection({ direction: $event })"
/>
<gl-dropdown
id="discussion-preferences-dropdown"
class="full-width-mobile"
data-qa-selector="discussion_preferences_dropdown"
text="Sort or filter"
:disabled="isLoading"
right
>
<div id="discussion-sort">
<gl-dropdown-item
v-for="{ text, key, cls } in $options.SORT_OPTIONS"
:key="text"
:class="cls"
:is-check-item="true"
:is-checked="isSortDropdownItemActive(key)"
@click="fetchSortedDiscussions(key)"
>
{{ text }}
</gl-dropdown-item>
</div>
<gl-dropdown-divider />
<div
id="discussion-filter"
class="discussion-filter-container js-discussion-filter-container"
>
{{ filter.title }}
</gl-dropdown-item>
<gl-dropdown-divider v-if="filter.value === defaultValue" />
</div>
</gl-dropdown>
<gl-dropdown-item
v-for="filter in filters"
:key="filter.value"
:is-check-item="true"
:is-checked="filter.value === currentValue"
:class="{ 'is-active': filter.value === currentValue }"
:data-filter-type="filterType(filter.value)"
data-qa-selector="filter_menu_item"
@click.prevent="selectFilter(filter.value)"
>
{{ filter.title }}
</gl-dropdown-item>
</div>
</gl-dropdown>
</div>
</template>

View File

@ -1,76 +0,0 @@
<script>
import { GlDropdown, GlDropdownItem } from '@gitlab/ui';
import { mapActions, mapGetters } from 'vuex';
import { __ } from '~/locale';
import Tracking from '~/tracking';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import { ASC, DESC } from '../constants';
const SORT_OPTIONS = [
{ key: DESC, text: __('Newest first'), cls: 'js-newest-first' },
{ key: ASC, text: __('Oldest first'), cls: 'js-oldest-first' },
];
export default {
SORT_OPTIONS,
components: {
GlDropdown,
GlDropdownItem,
LocalStorageSync,
},
mixins: [Tracking.mixin()],
computed: {
...mapGetters(['sortDirection', 'persistSortOrder', 'noteableType']),
selectedOption() {
return SORT_OPTIONS.find(({ key }) => this.sortDirection === key);
},
dropdownText() {
return this.selectedOption.text;
},
storageKey() {
return `sort_direction_${this.noteableType.toLowerCase()}`;
},
},
methods: {
...mapActions(['setDiscussionSortDirection']),
fetchSortedDiscussions(direction) {
if (this.isDropdownItemActive(direction)) {
return;
}
this.setDiscussionSortDirection({ direction });
this.track('change_discussion_sort_direction', { property: direction });
},
isDropdownItemActive(sortDir) {
return sortDir === this.sortDirection;
},
},
};
</script>
<template>
<div
data-testid="sort-discussion-filter"
class="gl-mr-3 gl-display-inline-block gl-vertical-align-bottom full-width-mobile"
>
<local-storage-sync
:value="sortDirection"
:storage-key="storageKey"
:persist="persistSortOrder"
as-string
@input="setDiscussionSortDirection({ direction: $event })"
/>
<gl-dropdown :text="dropdownText" class="js-dropdown-text full-width-mobile">
<gl-dropdown-item
v-for="{ text, key, cls } in $options.SORT_OPTIONS"
:key="key"
:class="cls"
:is-check-item="true"
:is-checked="isDropdownItemActive(key)"
@click="fetchSortedDiscussions(key)"
>
{{ text }}
</gl-dropdown-item>
</gl-dropdown>
</div>
</template>

View File

@ -53,7 +53,6 @@ export default {
:selected="timelineEnabled"
:title="tooltip"
:aria-label="tooltip"
class="gl-mr-3"
@click="toggleTimeline"
/>
</template>

View File

@ -1,7 +1,6 @@
import Vue from 'vue';
import notesApp from './components/notes_app.vue';
import initDiscussionFilters from './discussion_filters';
import initSortDiscussions from './sort_discussions';
import { store } from './stores';
import initTimelineToggle from './timeline';
@ -61,6 +60,5 @@ export default () => {
});
initDiscussionFilters(store);
initSortDiscussions(store);
initTimelineToggle(store);
};

View File

@ -1,17 +0,0 @@
import Vue from 'vue';
import SortDiscussion from './components/sort_discussion.vue';
export default (store) => {
const el = document.getElementById('js-vue-sort-issue-discussions');
if (!el) return null;
return new Vue({
el,
name: 'SortDiscussionRoot',
store,
render(createElement) {
return createElement(SortDiscussion);
},
});
};

View File

@ -8,7 +8,6 @@ module AcceptsPendingInvitations
if user.pending_invitations.load.any?
user.accept_pending_invitations!
clear_stored_location_for(user: user)
after_pending_invitations_hook
end
end
@ -16,10 +15,4 @@ module AcceptsPendingInvitations
def after_pending_invitations_hook
# no-op
end
def clear_stored_location_for(user:)
session_key = stored_location_key_for(user)
session.delete(session_key)
end
end

View File

@ -4,8 +4,7 @@
.row.gl-m-0.gl-justify-content-space-between
.js-noteable-awards
= render 'award_emoji/awards_block', awardable: issuable, inline: true, api_awards_path: api_awards_path
.new-branch-col.gl-my-2.gl-font-size-0
.new-branch-col.gl-display-flex.gl-my-2.gl-font-size-0.gl-gap-3
= render_if_exists "projects/issues/timeline_toggle", issuable: issuable
#js-vue-sort-issue-discussions
#js-vue-discussion-filter{ data: { default_filter: current_user&.notes_filter_for(issuable), notes_filters: UserPreference.notes_filters.to_json } }
= render 'new_branch' if show_new_branch_button?

View File

@ -301,8 +301,7 @@ test:
If your project uses [pip](https://pip.pypa.io/en/stable/) to install
Python dependencies, the following example defines `cache` globally so that
all jobs inherit it. Python libraries are installed in a virtual environment under `venv/`.
pip's cache is defined under `.cache/pip/` and both are cached per-branch:
all jobs inherit it. pip's cache is defined under `.cache/pip/` and is cached per-branch:
```yaml
#
@ -317,13 +316,9 @@ variables:
# Pip's cache doesn't store the python packages
# https://pip.pypa.io/en/stable/reference/pip_install/#caching
#
# If you want to also cache the installed packages, you have to install
# them in a virtualenv and cache it as well.
cache:
paths:
- .cache/pip
- venv/
before_script:
- python -V # Print out python version for debugging

View File

@ -34329,9 +34329,15 @@ msgstr ""
msgid "Saving project."
msgstr ""
msgid "ScanExecutionPolicy|%{ifLabelStart}if%{ifLabelEnd} %{rules} actions for the %{scopes} %{branches}"
msgstr ""
msgid "ScanExecutionPolicy|%{ifLabelStart}if%{ifLabelEnd} %{rules} for the %{branches} branch(es)"
msgstr ""
msgid "ScanExecutionPolicy|%{period} %{days} at %{time}"
msgstr ""
msgid "ScanExecutionPolicy|%{thenLabelStart}Then%{thenLabelEnd} Require a %{scan} scan to run"
msgstr ""
@ -34347,6 +34353,9 @@ msgstr ""
msgid "ScanExecutionPolicy|Select branches"
msgstr ""
msgid "ScanExecutionPolicy|branch"
msgstr ""
msgid "ScanResultPolicy|%{ifLabelStart}if%{ifLabelEnd} %{scanners} find(s) more than %{vulnerabilitiesAllowed} %{severities} %{vulnerabilityStates} vulnerabilities in an open merge request targeting %{branches}"
msgstr ""
@ -46258,6 +46267,9 @@ msgstr ""
msgid "created by"
msgstr ""
msgid "daily"
msgstr ""
msgid "data"
msgstr ""
@ -47574,6 +47586,9 @@ msgstr ""
msgid "was scheduled to merge after pipeline succeeds by"
msgstr ""
msgid "weekly"
msgstr ""
msgid "wiki page"
msgstr ""

View File

@ -28,7 +28,7 @@ module QA
end
base.view 'app/assets/javascripts/notes/components/discussion_filter.vue' do
element :discussion_filter_dropdown, required: true
element :discussion_preferences_dropdown, required: true
element :filter_menu_item
end
@ -169,7 +169,7 @@ module QA
def select_filter_with_text(text)
retry_on_exception do
click_element(:title_content)
click_element :discussion_filter_dropdown
click_element :discussion_preferences_dropdown
find_element(:filter_menu_item, text: text).click
wait_for_requests

View File

@ -16,7 +16,9 @@ RSpec.describe 'Comment sort direction' do
it 'saves sort order' do
# open dropdown, and select 'Newest first'
page.within('.issuable-details') do
find('#discussion-preferences-dropdown').click
click_button('Oldest first')
find('#discussion-preferences-dropdown').click
click_button('Newest first')
end

View File

@ -20,7 +20,7 @@ RSpec.describe 'User opens link to comment', :js do
wait_for_requests
expect(page.find('#discussion-filter-dropdown')).to have_content('Show all activity')
expect(find('#discussion-preferences-dropdown')).to have_content('Sort or filter')
expect(page).not_to have_content('Something went wrong while fetching comments')
# Auto-switching to show all notes shouldn't be persisted

View File

@ -8,7 +8,14 @@ import createEventHub from '~/helpers/event_hub_factory';
import axios from '~/lib/utils/axios_utils';
import DiscussionFilter from '~/notes/components/discussion_filter.vue';
import { DISCUSSION_FILTERS_DEFAULT_VALUE, DISCUSSION_FILTER_TYPES } from '~/notes/constants';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
import Tracking from '~/tracking';
import {
DISCUSSION_FILTERS_DEFAULT_VALUE,
DISCUSSION_FILTER_TYPES,
ASC,
DESC,
} from '~/notes/constants';
import notesModule from '~/notes/stores/modules';
import { discussionFiltersMock, discussionMock } from '../mock_data';
@ -28,6 +35,8 @@ describe('DiscussionFilter component', () => {
const findFilter = (filterType) =>
wrapper.find(`.dropdown-item[data-filter-type="${filterType}"]`);
const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
const mountComponent = () => {
const discussions = [
{
@ -68,6 +77,7 @@ describe('DiscussionFilter component', () => {
mock.onGet(DISCUSSION_PATH).reply(200, '');
window.mrTabs = undefined;
wrapper = mountComponent();
jest.spyOn(Tracking, 'event');
});
afterEach(() => {
@ -75,6 +85,65 @@ describe('DiscussionFilter component', () => {
mock.restore();
});
describe('default', () => {
beforeEach(() => {
jest.spyOn(store, 'dispatch').mockImplementation();
});
it('has local storage sync with the correct props', () => {
expect(findLocalStorageSync().props('asString')).toBe(true);
});
it('calls setDiscussionSortDirection when update is emitted', () => {
findLocalStorageSync().vm.$emit('input', ASC);
expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', { direction: ASC });
});
});
describe('when asc', () => {
beforeEach(() => {
jest.spyOn(store, 'dispatch').mockImplementation();
});
describe('when the dropdown is clicked', () => {
it('calls the right actions', () => {
wrapper.find('.js-newest-first').vm.$emit('click');
expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', {
direction: DESC,
});
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'change_discussion_sort_direction', {
property: DESC,
});
});
});
});
describe('when desc', () => {
beforeEach(() => {
store.state.discussionSortOrder = DESC;
jest.spyOn(store, 'dispatch').mockImplementation();
});
describe('when the dropdown item is clicked', () => {
it('calls the right actions', () => {
wrapper.find('.js-oldest-first').vm.$emit('click');
expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', {
direction: ASC,
});
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'change_discussion_sort_direction', {
property: ASC,
});
});
it('sets is-checked to true on the active button in the dropdown', () => {
expect(wrapper.find('.js-newest-first').props('isChecked')).toBe(true);
});
});
});
it('renders the all filters', () => {
expect(wrapper.findAll('.discussion-filter-container .dropdown-item').length).toBe(
discussionFiltersMock.length,
@ -82,7 +151,7 @@ describe('DiscussionFilter component', () => {
});
it('renders the default selected item', () => {
expect(wrapper.find('#discussion-filter-dropdown .dropdown-item').text().trim()).toBe(
expect(wrapper.find('.discussion-filter-container .dropdown-item').text().trim()).toBe(
discussionFiltersMock[0].title,
);
});
@ -127,14 +196,6 @@ describe('DiscussionFilter component', () => {
expect(wrapper.vm.$store.state.commentsDisabled).toBe(false);
});
it('renders a dropdown divider for the default filter', () => {
const defaultFilter = wrapper.findAll(
`.discussion-filter-container .dropdown-item-wrapper > *`,
);
expect(defaultFilter.at(1).classes('gl-new-dropdown-divider')).toBe(true);
});
describe('Merge request tabs', () => {
eventHub = createEventHub();

View File

@ -1,102 +0,0 @@
import { shallowMount } from '@vue/test-utils';
import Vue from 'vue';
import Vuex from 'vuex';
import SortDiscussion from '~/notes/components/sort_discussion.vue';
import { ASC, DESC } from '~/notes/constants';
import createStore from '~/notes/stores';
import Tracking from '~/tracking';
import LocalStorageSync from '~/vue_shared/components/local_storage_sync.vue';
Vue.use(Vuex);
describe('Sort Discussion component', () => {
let wrapper;
let store;
const createComponent = () => {
jest.spyOn(store, 'dispatch').mockImplementation();
wrapper = shallowMount(SortDiscussion, {
store,
});
};
const findLocalStorageSync = () => wrapper.findComponent(LocalStorageSync);
beforeEach(() => {
store = createStore();
jest.spyOn(Tracking, 'event');
});
afterEach(() => {
wrapper.destroy();
wrapper = null;
});
describe('default', () => {
beforeEach(() => {
createComponent();
});
it('has local storage sync with the correct props', () => {
expect(findLocalStorageSync().props('asString')).toBe(true);
});
it('calls setDiscussionSortDirection when update is emitted', () => {
findLocalStorageSync().vm.$emit('input', ASC);
expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', { direction: ASC });
});
});
describe('when asc', () => {
describe('when the dropdown is clicked', () => {
it('calls the right actions', () => {
createComponent();
wrapper.find('.js-newest-first').vm.$emit('click');
expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', {
direction: DESC,
});
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'change_discussion_sort_direction', {
property: DESC,
});
});
});
it('shows the "Oldest First" as the dropdown', () => {
createComponent();
expect(wrapper.find('.js-dropdown-text').props('text')).toBe('Oldest first');
});
});
describe('when desc', () => {
beforeEach(() => {
store.state.discussionSortOrder = DESC;
createComponent();
});
describe('when the dropdown item is clicked', () => {
it('calls the right actions', () => {
wrapper.find('.js-oldest-first').vm.$emit('click');
expect(store.dispatch).toHaveBeenCalledWith('setDiscussionSortDirection', {
direction: ASC,
});
expect(Tracking.event).toHaveBeenCalledWith(undefined, 'change_discussion_sort_direction', {
property: ASC,
});
});
it('sets is-checked to true on the active button in the dropdown', () => {
expect(wrapper.find('.js-newest-first').props('isChecked')).toBe(true);
});
});
it('shows the "Newest First" as the dropdown', () => {
expect(wrapper.find('.js-dropdown-text').props('text')).toBe('Newest first');
});
});
});

View File

@ -4,7 +4,6 @@ require 'spec_helper'
RSpec.describe GitlabSchema.types['Project'] do
include GraphqlHelpers
include Ci::TemplateHelpers
specify { expect(described_class).to expose_permissions_using(Types::PermissionTypes::Project) }

View File

@ -3,8 +3,6 @@
require 'spec_helper'
RSpec.describe Security::CiConfiguration::SastParserService do
include Ci::TemplateHelpers
describe '#configuration' do
include_context 'read ci configuration for sast enabled project'

View File

@ -2,10 +2,6 @@
module Ci
module TemplateHelpers
def secure_analyzers_prefix
'registry.gitlab.com/security-products'
end
def template_registry_host
'registry.gitlab.com'
end