diff --git a/.browserslistrc b/.browserslistrc new file mode 100644 index 00000000000..3ae7766c325 --- /dev/null +++ b/.browserslistrc @@ -0,0 +1,16 @@ +# +# This list of browsers is a conservative first definition, based on +# https://docs.gitlab.com/ee/install/requirements.html#supported-web-browsers +# with the following reasoning: +# +# - Edge: Pick the last two major version before the Chrome switch +# - Rest: We should support the latest ESR of Firefox: 68, because it used quite a lot. +# For the rest, pick browser versions that have a similar age to Firefox 68. +# +# See also this follow-up epic: +# https://gitlab.com/groups/gitlab-org/-/epics/3957 +# +chrome >= 73 +edge >= 17 +firefox >= 68 +safari >= 12 diff --git a/.gitlab/issue_templates/Feature Flag Roll Out.md b/.gitlab/issue_templates/Feature Flag Roll Out.md index 67686b654bd..3017ab85f9e 100644 --- a/.gitlab/issue_templates/Feature Flag Roll Out.md +++ b/.gitlab/issue_templates/Feature Flag Roll Out.md @@ -31,7 +31,6 @@ If applicable, any groups/projects that are happy to have this feature turned on ## Roll Out Steps -- [ ] Confirm that QA tests pass with the feature flag enabled (if you're unsure how, contact the relevant [stable counterpart in the Quality department](https://about.gitlab.com/handbook/engineering/quality/#individual-contributors)) - [ ] Enable on staging (`/chatops run feature set feature_name true --staging`) - [ ] Test on staging - [ ] Ensure that documentation has been updated diff --git a/Gemfile b/Gemfile index 551cbd9d229..1b1e7fb8e82 100644 --- a/Gemfile +++ b/Gemfile @@ -284,6 +284,7 @@ gem 'gitlab_chronic_duration', '~> 0.10.6.2' gem 'rack-proxy', '~> 0.6.0' gem 'sassc-rails', '~> 2.1.0' +gem 'autoprefixer-rails', '10.2.0.0' gem 'terser', '1.0.2' gem 'addressable', '~> 2.7' @@ -336,6 +337,7 @@ end group :development do gem 'brakeman', '~> 4.2', require: false gem 'danger', '~> 8.0.6', require: false + gem 'lefthook', '~> 0.7', require: false gem 'letter_opener_web', '~> 1.3.4' diff --git a/Gemfile.lock b/Gemfile.lock index ee5cbc7e2a0..ac97a5d1433 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -94,6 +94,8 @@ GEM attr_encrypted (3.1.0) encryptor (~> 3.0.0) attr_required (1.0.1) + autoprefixer-rails (10.2.0.0) + execjs awesome_print (1.8.0) awrence (1.1.1) aws-eventstream (1.1.0) @@ -658,6 +660,7 @@ GEM rest-client (~> 2.0) launchy (2.4.3) addressable (~> 2.3) + lefthook (0.7.2) letter_opener (1.7.0) launchy (~> 2.2) letter_opener_web (1.3.4) @@ -1285,6 +1288,7 @@ DEPENDENCIES asciidoctor-plantuml (~> 0.0.12) atlassian-jwt (~> 0.2.0) attr_encrypted (~> 3.1.0) + autoprefixer-rails (= 10.2.0.0) awesome_print aws-sdk-cloudformation (~> 1) aws-sdk-core (~> 3) @@ -1410,6 +1414,7 @@ DEPENDENCIES knapsack (~> 1.17) kramdown (~> 2.3.0) kubeclient (~> 4.9.1) + lefthook (~> 0.7) letter_opener_web (~> 1.3.4) license_finder (~> 6.0) licensee (~> 8.9) diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue index 31050eef83d..4d3ce50275b 100644 --- a/app/assets/javascripts/boards/components/board_card.vue +++ b/app/assets/javascripts/boards/components/board_card.vue @@ -1,5 +1,6 @@ + + diff --git a/app/assets/javascripts/boards/components/board_configuration_options.vue b/app/assets/javascripts/boards/components/board_configuration_options.vue index b8ee930a8c9..4d79f2a4bc6 100644 --- a/app/assets/javascripts/boards/components/board_configuration_options.vue +++ b/app/assets/javascripts/boards/components/board_configuration_options.vue @@ -14,6 +14,10 @@ export default { type: Boolean, required: true, }, + readonly: { + type: Boolean, + required: true, + }, }, }; @@ -28,12 +32,14 @@ export default {

{{ __('Show the Open list') }} {{ __('Show the Closed list') }} diff --git a/app/assets/javascripts/boards/components/board_form.vue b/app/assets/javascripts/boards/components/board_form.vue index c701ecd3040..f9a81746851 100644 --- a/app/assets/javascripts/boards/components/board_form.vue +++ b/app/assets/javascripts/boards/components/board_form.vue @@ -308,6 +308,7 @@ export default { item.node); }, error() { diff --git a/app/assets/javascripts/boards/graphql/group_milestones.query.graphql b/app/assets/javascripts/boards/graphql/project_milestones.query.graphql similarity index 53% rename from app/assets/javascripts/boards/graphql/group_milestones.query.graphql rename to app/assets/javascripts/boards/graphql/project_milestones.query.graphql index f2ab12ef4a7..776530ebb83 100644 --- a/app/assets/javascripts/boards/graphql/group_milestones.query.graphql +++ b/app/assets/javascripts/boards/graphql/project_milestones.query.graphql @@ -1,11 +1,11 @@ query groupMilestones( $fullPath: ID! $state: MilestoneStateEnum - $includeDescendants: Boolean + $includeAncestors: Boolean $searchTitle: String ) { - group(fullPath: $fullPath) { - milestones(state: $state, includeDescendants: $includeDescendants, searchTitle: $searchTitle) { + project(fullPath: $fullPath) { + milestones(state: $state, includeAncestors: $includeAncestors, searchTitle: $searchTitle) { edges { node { id diff --git a/app/assets/javascripts/boards/stores/actions.js b/app/assets/javascripts/boards/stores/actions.js index 585e0615e90..f51cc547f32 100644 --- a/app/assets/javascripts/boards/stores/actions.js +++ b/app/assets/javascripts/boards/stores/actions.js @@ -534,6 +534,17 @@ export default { commit(types.SET_SELECTED_PROJECT, project); }, + toggleBoardItemMultiSelection: ({ commit, state }, boardItem) => { + const { selectedBoardItems } = state; + const index = selectedBoardItems.indexOf(boardItem); + + if (index === -1) { + commit(types.ADD_BOARD_ITEM_TO_SELECTION, boardItem); + } else { + commit(types.REMOVE_BOARD_ITEM_FROM_SELECTION, boardItem); + } + }, + fetchBacklog: () => { notImplemented(); }, diff --git a/app/assets/javascripts/boards/stores/mutation_types.js b/app/assets/javascripts/boards/stores/mutation_types.js index 2db754b7ac8..443c3461012 100644 --- a/app/assets/javascripts/boards/stores/mutation_types.js +++ b/app/assets/javascripts/boards/stores/mutation_types.js @@ -40,3 +40,5 @@ export const REQUEST_GROUP_PROJECTS = 'REQUEST_GROUP_PROJECTS'; export const RECEIVE_GROUP_PROJECTS_SUCCESS = 'RECEIVE_GROUP_PROJECTS_SUCCESS'; export const RECEIVE_GROUP_PROJECTS_FAILURE = 'RECEIVE_GROUP_PROJECTS_FAILURE'; export const SET_SELECTED_PROJECT = 'SET_SELECTED_PROJECT'; +export const ADD_BOARD_ITEM_TO_SELECTION = 'ADD_BOARD_ITEM_TO_SELECTION'; +export const REMOVE_BOARD_ITEM_FROM_SELECTION = 'REMOVE_BOARD_ITEM_FROM_SELECTION'; diff --git a/app/assets/javascripts/boards/stores/mutations.js b/app/assets/javascripts/boards/stores/mutations.js index 76476fce5a3..82a0ba578dc 100644 --- a/app/assets/javascripts/boards/stores/mutations.js +++ b/app/assets/javascripts/boards/stores/mutations.js @@ -258,4 +258,16 @@ export default { [mutationTypes.SET_SELECTED_PROJECT]: (state, project) => { state.selectedProject = project; }, + + [mutationTypes.ADD_BOARD_ITEM_TO_SELECTION]: (state, boardItem) => { + state.selectedBoardItems = [...state.selectedBoardItems, boardItem]; + }, + + [mutationTypes.REMOVE_BOARD_ITEM_FROM_SELECTION]: (state, boardItem) => { + Vue.set( + state, + 'selectedBoardItems', + state.selectedBoardItems.filter((obj) => obj !== boardItem), + ); + }, }; diff --git a/app/assets/javascripts/boards/stores/state.js b/app/assets/javascripts/boards/stores/state.js index 215195a2a4f..d2e10fbf29b 100644 --- a/app/assets/javascripts/boards/stores/state.js +++ b/app/assets/javascripts/boards/stores/state.js @@ -15,6 +15,7 @@ export default () => ({ filterParams: {}, boardConfig: {}, labels: [], + selectedBoardItems: [], groupProjects: [], groupProjectsFlags: { isLoading: false, diff --git a/app/assets/javascripts/notes/components/toggle_replies_widget.vue b/app/assets/javascripts/notes/components/toggle_replies_widget.vue index ab7fa793bdc..06de3104a47 100644 --- a/app/assets/javascripts/notes/components/toggle_replies_widget.vue +++ b/app/assets/javascripts/notes/components/toggle_replies_widget.vue @@ -39,13 +39,17 @@ export default { this.$emit('toggle'); }, }, + ICON_CLASS: 'gl-mr-3 gl-cursor-pointer', }; diff --git a/app/assets/javascripts/pages/search/show/index.js b/app/assets/javascripts/pages/search/show/index.js index b6171e08e01..a8c288c3663 100644 --- a/app/assets/javascripts/pages/search/show/index.js +++ b/app/assets/javascripts/pages/search/show/index.js @@ -1,7 +1,5 @@ -import Search from './search'; import { initSearchApp } from '~/search'; document.addEventListener('DOMContentLoaded', () => { - initSearchApp(); // Vue Bootstrap - return new Search(); // Legacy Search Methods + initSearchApp(); }); diff --git a/app/assets/javascripts/pages/search/show/search.js b/app/assets/javascripts/pages/search/show/search.js deleted file mode 100644 index cbef5ab1bbc..00000000000 --- a/app/assets/javascripts/pages/search/show/search.js +++ /dev/null @@ -1,54 +0,0 @@ -import $ from 'jquery'; -import setHighlightClass from 'ee_else_ce/search/highlight_blob_search_result'; -import Project from '~/pages/projects/project'; -import { visitUrl } from '~/lib/utils/url_utility'; -import refreshCounts from './refresh_counts'; - -export default class Search { - constructor() { - this.searchInput = '.js-search-input'; - this.searchClear = '.js-search-clear'; - - setHighlightClass(); // Code Highlighting - this.eventListeners(); // Search Form Actions - refreshCounts(); // Other Scope Tab Counts - Project.initRefSwitcher(); // Code Search Branch Picker - } - - eventListeners() { - $(document).off('keyup', this.searchInput).on('keyup', this.searchInput, this.searchKeyUp); - $(document) - .off('click', this.searchClear) - .on('click', this.searchClear, this.clearSearchField.bind(this)); - - $('a.js-search-clear').off('click', this.clearSearchFilter).on('click', this.clearSearchFilter); - } - - static submitSearch() { - return $('.js-search-form').submit(); - } - - searchKeyUp() { - const $input = $(this); - if ($input.val() === '') { - $('.js-search-clear').addClass('hidden'); - } else { - $('.js-search-clear').removeClass('hidden'); - } - } - - clearSearchField() { - return $(this.searchInput).val('').trigger('keyup').focus(); - } - - // We need to manually follow the link on the anchors - // that have this event bound, as their `click` default - // behavior is prevented by the toggle logic. - /* eslint-disable-next-line class-methods-use-this */ - clearSearchFilter(ev) { - const $target = $(ev.currentTarget); - - visitUrl($target.href); - ev.stopPropagation(); - } -} diff --git a/app/assets/javascripts/search/highlight_blob_search_result.js b/app/assets/javascripts/search/highlight_blob_search_result.js index 3c3ac3582d0..c553d5b14a0 100644 --- a/app/assets/javascripts/search/highlight_blob_search_result.js +++ b/app/assets/javascripts/search/highlight_blob_search_result.js @@ -1,7 +1,7 @@ -export default () => { +export default (search = '') => { const highlightLineClass = 'hll'; const contentBody = document.getElementById('content-body'); - const searchTerm = contentBody.querySelector('.js-search-input').value.toLowerCase(); + const searchTerm = search.toLowerCase(); const blobs = contentBody.querySelectorAll('.blob-result'); blobs.forEach((blob) => { diff --git a/app/assets/javascripts/search/index.js b/app/assets/javascripts/search/index.js index d2bb1ccfc44..0674bff2795 100644 --- a/app/assets/javascripts/search/index.js +++ b/app/assets/javascripts/search/index.js @@ -1,3 +1,6 @@ +import setHighlightClass from 'ee_else_ce/search/highlight_blob_search_result'; +import Project from '~/pages/projects/project'; +import refreshCounts from '~/pages/search/show/refresh_counts'; import { queryToObject } from '~/lib/utils/url_utility'; import createStore from './store'; import { initTopbar } from './topbar'; @@ -7,8 +10,14 @@ export const initSearchApp = () => { // Similar to url_utility.decodeUrlParameter // Our query treats + as %20. This replaces the query + symbols with %20. const sanitizedSearch = window.location.search.replace(/\+/g, '%20'); - const store = createStore({ query: queryToObject(sanitizedSearch) }); + const query = queryToObject(sanitizedSearch); + + const store = createStore({ query }); initTopbar(store); initSidebar(store); + + setHighlightClass(query.search); // Code Highlighting + refreshCounts(); // Other Scope Tab Counts + Project.initRefSwitcher(); // Code Search Branch Picker }; diff --git a/app/assets/javascripts/search/topbar/components/app.vue b/app/assets/javascripts/search/topbar/components/app.vue new file mode 100644 index 00000000000..8c9c4e58722 --- /dev/null +++ b/app/assets/javascripts/search/topbar/components/app.vue @@ -0,0 +1,73 @@ + + + diff --git a/app/assets/javascripts/search/topbar/components/group_filter.vue b/app/assets/javascripts/search/topbar/components/group_filter.vue index fce9ec17d23..efd753270f6 100644 --- a/app/assets/javascripts/search/topbar/components/group_filter.vue +++ b/app/assets/javascripts/search/topbar/components/group_filter.vue @@ -37,6 +37,7 @@ export default { diff --git a/app/assets/javascripts/search/topbar/index.js b/app/assets/javascripts/search/topbar/index.js index f0308109b32..87316e10e8d 100644 --- a/app/assets/javascripts/search/topbar/index.js +++ b/app/assets/javascripts/search/topbar/index.js @@ -1,44 +1,31 @@ import Vue from 'vue'; import Translate from '~/vue_shared/translate'; -import GroupFilter from './components/group_filter.vue'; -import ProjectFilter from './components/project_filter.vue'; +import GlobalSearchTopbar from './components/app.vue'; Vue.use(Translate); -const mountSearchableDropdown = (store, { id, component }) => { - const el = document.getElementById(id); +export const initTopbar = (store) => { + const el = document.getElementById('js-search-topbar'); if (!el) { return false; } - let { initialData } = el.dataset; + let { groupInitialData, projectInitialData } = el.dataset; - initialData = JSON.parse(initialData); + groupInitialData = JSON.parse(groupInitialData); + projectInitialData = JSON.parse(projectInitialData); return new Vue({ el, store, render(createElement) { - return createElement(component, { + return createElement(GlobalSearchTopbar, { props: { - initialData, + groupInitialData, + projectInitialData, }, }); }, }); }; - -const searchableDropdowns = [ - { - id: 'js-search-group-dropdown', - component: GroupFilter, - }, - { - id: 'js-search-project-dropdown', - component: ProjectFilter, - }, -]; - -export const initTopbar = (store) => - searchableDropdowns.map((dropdown) => mountSearchableDropdown(store, dropdown)); diff --git a/app/assets/javascripts/vue_shared/components/markdown/field.vue b/app/assets/javascripts/vue_shared/components/markdown/field.vue index b6e167524aa..40b5cc688ff 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/field.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/field.vue @@ -110,11 +110,6 @@ export default { return this.referencedUsers.length >= referencedUsersThreshold; }, lineContent() { - const [firstSuggestion] = this.suggestions; - if (firstSuggestion) { - return firstSuggestion.from_content; - } - if (this.line) { const { rich_text: richText, text } = this.line; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index 4216091e8a9..e05e71d9105 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -91,29 +91,10 @@ $note-form-margin-left: 72px; color: $blue-600; } - &.expanded { - span { - cursor: pointer; - } - - svg { - position: relative; - top: 3px; - } - } - &.collapsed { color: $gl-text-color-secondary; border-radius: 0 0 $border-radius-default $border-radius-default; - svg { - float: left; - position: relative; - top: $gl-padding-4; - margin-right: $gl-padding-8; - cursor: pointer; - } - img { margin: -2px 4px 0 0; } diff --git a/app/views/admin/applications/_form.html.haml b/app/views/admin/applications/_form.html.haml index 0c3a4e73e30..5df3ccbbc59 100644 --- a/app/views/admin/applications/_form.html.haml +++ b/app/views/admin/applications/_form.html.haml @@ -23,7 +23,7 @@ .col-sm-10 = f.check_box :trusted %span.form-text.text-muted - Trusted applications are automatically authorized on GitLab OAuth flow. + Trusted applications are automatically authorized on GitLab OAuth flow. It's highly recommended for the security of users that trusted applications have the confidential setting set to true. = content_tag :div, class: 'form-group row' do .col-sm-2.col-form-label.pt-0 diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml deleted file mode 100644 index e9c6b581c90..00000000000 --- a/app/views/search/_filter.html.haml +++ /dev/null @@ -1,14 +0,0 @@ -- if params[:group_id].present? - = hidden_field_tag :group_id, params[:group_id] -- if params[:project_id].present? - = hidden_field_tag :project_id, params[:project_id] -- project_attributes = @project&.attributes&.slice('id', 'namespace_id', 'name')&.merge(name_with_namespace: @project&.name_with_namespace) - -.dropdown.form-group.mb-lg-0.mx-lg-1.gl-p-0{ data: { testid: "group-filter" } } - %label.d-block{ for: "dashboard_search_group" } - = _("Group") - %input#js-search-group-dropdown.dropdown-menu-toggle{ value: "Loading...", data: { "initial-data": @group.to_json } } -.dropdown.form-group.mb-lg-0.mx-lg-1.gl-p-0{ data: { testid: "project-filter" } } - %label.d-block{ for: "dashboard_search_project" } - = _("Project") - %input#js-search-project-dropdown.dropdown-menu-toggle{ value: "Loading...", data: { "initial-data": project_attributes.to_json } } diff --git a/app/views/search/_form.html.haml b/app/views/search/_form.html.haml deleted file mode 100644 index a9eee1dd2d6..00000000000 --- a/app/views/search/_form.html.haml +++ /dev/null @@ -1,20 +0,0 @@ -= form_tag search_path, method: :get, class: 'search-page-form js-search-form' do |f| - = hidden_field_tag :snippets, params[:snippets] - = hidden_field_tag :scope, params[:scope] - = hidden_field_tag :repository_ref, params[:repository_ref] - - .d-lg-flex.align-items-end - .search-field-holder.form-group.mr-lg-1.mb-lg-0 - %label{ for: "dashboard_search" } - = _("What are you searching for?") - .gl-search-box-by-type - = search_field_tag :search, params[:search], placeholder: _("Search for projects, issues, etc."), class: "gl-form-input form-control search-text-input js-search-input", id: "dashboard_search", autofocus: true, spellcheck: false - = sprite_icon('search', css_class: 'gl-search-box-by-type-search-icon gl-icon') - %button.search-clear.js-search-clear{ class: [("hidden" if params[:search].blank?), "has-tooltip"], type: "button", tabindex: "-1", title: _('Clear') } - = sprite_icon('clear') - %span.sr-only - = _("Clear search") - - unless params[:snippets].eql? 'true' - = render 'filter' - .d-flex-center.flex-column.flex-lg-row - = button_tag _("Search"), class: "gl-button btn btn-success btn-search mt-lg-0 ml-lg-1 align-self-end" diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml index 3fb91428c56..d54310bfa82 100644 --- a/app/views/search/show.html.haml +++ b/app/views/search/show.html.haml @@ -1,6 +1,11 @@ - @hide_top_links = true - page_title @search_term - @hide_breadcrumbs = true +- if params[:group_id].present? + = hidden_field_tag :group_id, params[:group_id] +- if params[:project_id].present? + = hidden_field_tag :project_id, params[:project_id] +- project_attributes = @project&.attributes&.slice('id', 'namespace_id', 'name')&.merge(name_with_namespace: @project&.name_with_namespace) - if @search_results - page_description(_("%{count} %{scope} for term '%{term}'") % { count: @search_results.formatted_count(@scope), scope: @scope, term: @search_term }) @@ -11,7 +16,7 @@ = render_if_exists 'search/form_elasticsearch', attrs: { class: 'mb-2 mb-sm-0 align-self-center' } .gl-mt-3 - = render 'search/form' + #js-search-topbar{ data: { "group-initial-data": @group.to_json, "project-initial-data": project_attributes.to_json } } - if @search_term = render 'search/category' = render 'search/results' diff --git a/app/views/shared/web_hooks/_hook.html.haml b/app/views/shared/web_hooks/_hook.html.haml index 13fe8f76bd3..5437748a57e 100644 --- a/app/views/shared/web_hooks/_hook.html.haml +++ b/app/views/shared/web_hooks/_hook.html.haml @@ -12,5 +12,5 @@ .col-md-4.col-lg-5.text-right-md.gl-mt-2 %span>= render 'shared/web_hooks/test_button', hook: hook, button_class: 'btn-sm gl-mr-3' - %span>= link_to _('Edit'), edit_hook_path(hook), class: 'btn btn-sm gl-mr-3' - = link_to _('Delete'), destroy_hook_path(hook), data: { confirm: _('Are you sure?') }, method: :delete, class: 'btn btn-sm' + %span>= link_to _('Edit'), edit_hook_path(hook), class: 'gl-button btn btn-sm gl-mr-3' + = link_to _('Delete'), destroy_hook_path(hook), data: { confirm: _('Are you sure?') }, method: :delete, class: 'gl-button btn btn-sm' diff --git a/app/views/shared/web_hooks/_test_button.html.haml b/app/views/shared/web_hooks/_test_button.html.haml index c46b8a99886..a683f75c779 100644 --- a/app/views/shared/web_hooks/_test_button.html.haml +++ b/app/views/shared/web_hooks/_test_button.html.haml @@ -3,7 +3,7 @@ - triggers = hook.class.triggers .hook-test-button.dropdown.inline> - %button.btn{ 'data-toggle' => 'dropdown', class: button_class } + %button.btn.gl-button{ 'data-toggle' => 'dropdown', class: button_class } = _('Test') = sprite_icon('chevron-down') %ul.dropdown-menu.dropdown-menu-right{ role: 'menu' } diff --git a/babel.config.js b/babel.config.js index 64898bfdf50..6c9d4640535 100644 --- a/babel.config.js +++ b/babel.config.js @@ -9,24 +9,6 @@ let presets = [ useBuiltIns: 'usage', corejs: { version: 3, proposals: true }, modules: false, - /** - * This list of browsers is a conservative first definition, based on - * https://docs.gitlab.com/ee/install/requirements.html#supported-web-browsers - * with the following reasoning: - * - * - Edge: Pick the last two major version before the Chrome switch - * - Rest: We should support the latest ESR of Firefox: 68, because it used quite a lot. - * For the rest, pick browser versions that have a similar age to Firefox 68. - * - * See also this follow-up epic: - * https://gitlab.com/groups/gitlab-org/-/epics/3957 - */ - targets: { - chrome: '73', - edge: '17', - firefox: '68', - safari: '12', - }, }, ], ]; diff --git a/changelogs/unreleased/262063-global-search-topbar-vue.yml b/changelogs/unreleased/262063-global-search-topbar-vue.yml new file mode 100644 index 00000000000..c9c9c7516a9 --- /dev/null +++ b/changelogs/unreleased/262063-global-search-topbar-vue.yml @@ -0,0 +1,5 @@ +--- +title: Global Search - UX Cleanup of Search Bar +merge_request: 51409 +author: +type: changed diff --git a/changelogs/unreleased/295626-when-epic-swimlanes-are-enabled-milestones-are-incorrectly-scoped-.yml b/changelogs/unreleased/295626-when-epic-swimlanes-are-enabled-milestones-are-incorrectly-scoped-.yml new file mode 100644 index 00000000000..7125d944591 --- /dev/null +++ b/changelogs/unreleased/295626-when-epic-swimlanes-are-enabled-milestones-are-incorrectly-scoped-.yml @@ -0,0 +1,5 @@ +--- +title: Scope milestones on swimlane boards to project and its ancestors +merge_request: 52199 +author: +type: fixed diff --git a/changelogs/unreleased/298790-the-options-to-show-hide-the-open-and-closed-lists-in-boards-shoul.yml b/changelogs/unreleased/298790-the-options-to-show-hide-the-open-and-closed-lists-in-boards-shoul.yml new file mode 100644 index 00000000000..37ef33c8ad2 --- /dev/null +++ b/changelogs/unreleased/298790-the-options-to-show-hide-the-open-and-closed-lists-in-boards-shoul.yml @@ -0,0 +1,5 @@ +--- +title: Disable board configuration options for users without edit permission +merge_request: 52077 +author: +type: fixed diff --git a/changelogs/unreleased/ApplyGitLabUIbuttonstylestobuttonsin_hook-html-haml.yml b/changelogs/unreleased/ApplyGitLabUIbuttonstylestobuttonsin_hook-html-haml.yml new file mode 100644 index 00000000000..518fc1e18a4 --- /dev/null +++ b/changelogs/unreleased/ApplyGitLabUIbuttonstylestobuttonsin_hook-html-haml.yml @@ -0,0 +1,5 @@ +--- +title: Update buttons in _hook.html.haml to use GitLab UI +merge_request: 51065 +author: nuwe1 +type: other diff --git a/changelogs/unreleased/ApplyGitLabUIbuttonstylestobuttonsin_test_button-html-haml.yml b/changelogs/unreleased/ApplyGitLabUIbuttonstylestobuttonsin_test_button-html-haml.yml new file mode 100644 index 00000000000..9a140d2c1ee --- /dev/null +++ b/changelogs/unreleased/ApplyGitLabUIbuttonstylestobuttonsin_test_button-html-haml.yml @@ -0,0 +1,5 @@ +--- +title: Adds GitLabUI button styles in _test_button.html.haml +merge_request: 51070 +author: nuwe1 +type: other diff --git a/changelogs/unreleased/ph-293891-fixSuggestionsToReplies.yml b/changelogs/unreleased/ph-293891-fixSuggestionsToReplies.yml new file mode 100644 index 00000000000..1060ef22c5a --- /dev/null +++ b/changelogs/unreleased/ph-293891-fixSuggestionsToReplies.yml @@ -0,0 +1,5 @@ +--- +title: Fixed sdiff suggestions not working when replying to comments +merge_request: 52100 +author: +type: fixed diff --git a/changelogs/unreleased/yo-fix-toggle-collapse-replies.yml b/changelogs/unreleased/yo-fix-toggle-collapse-replies.yml new file mode 100644 index 00000000000..272cff27659 --- /dev/null +++ b/changelogs/unreleased/yo-fix-toggle-collapse-replies.yml @@ -0,0 +1,5 @@ +--- +title: Fix alignment of chevron-down icon in toggle replies +merge_request: 51872 +author: Yogi (@yo) +type: fixed diff --git a/data/whats_new/templates/YYYYMMDD0001_XX_YY.yml b/data/whats_new/templates/YYYYMMDD0001_XX_YY.yml new file mode 100644 index 00000000000..74915c19f95 --- /dev/null +++ b/data/whats_new/templates/YYYYMMDD0001_XX_YY.yml @@ -0,0 +1,32 @@ +# This is a template for a "Whats New" release. +# A release typically contains multiple entries of features that we'd like to highlight. +# +# Below is an example of what a single entry should look like, it's required attributes, +# and what types we expect those attribute values to be. All attributes are required. +# +# For more information please refer to the handbook documentation here: +# https://about.gitlab.com/handbook/marketing/blog/release-posts/index.html#create-mr-for-whats-new-entries +# +# Please delete this line and above before submitting your Merge Request. + +- title: # Match the release post entry + body: | # Do not modify this line, instead modify the lines below. + + stage: # String value of the stage that the feature was created in. e.g., Growth + self-managed: # Boolean value (true or false) + gitlab-com: # Boolean value (true or false) + packages: # Array of strings. The Array brackets are required here. e.g., [Core, Starter, Premium, Ultimate] + url: # This is the documentation URL, but can be a URL to a video if there is one + image_url: # This should be a full URL, generally taken from the release post content. If a video, use the youtube thumbnail URL with the structure of https://img.youtube.com/vi/UNIQUEID/hqdefault.jpg + published_at: # YYYY-MM-DD + release: # XX.Y diff --git a/doc/.vale/gitlab/spelling-exceptions.txt b/doc/.vale/gitlab/spelling-exceptions.txt index 2f17064e27b..f288da7d451 100644 --- a/doc/.vale/gitlab/spelling-exceptions.txt +++ b/doc/.vale/gitlab/spelling-exceptions.txt @@ -189,6 +189,7 @@ heatmaps Helm Heroku Herokuish +Hexo HipChat hostname hostnames @@ -204,6 +205,8 @@ inclusivity Ingress initializer initializers +innersource +innersourcing interdependencies interdependency interruptible @@ -432,6 +435,7 @@ sbt scatterplot scatterplots Schemastore +scrollable Sendmail Sentry serializer @@ -556,6 +560,7 @@ unpublish unpublished unpublishes unpublishing +unpushed unreferenced unregister unregistered diff --git a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md index e5b9ad030d0..49ab33c748a 100644 --- a/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md +++ b/doc/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/index.md @@ -84,9 +84,10 @@ To enable merge trains for your project: 1. If you are on a self-managed GitLab instance, ensure the [feature flag](#merge-trains-feature-flag) is set correctly. 1. [Configure your CI/CD configuration file](../../index.md#configuring-pipelines-for-merge-requests) so that the pipeline or individual jobs run for merge requests. -1. Visit your project's **Settings > General** and expand **Merge requests** -1. Check **Enable merged results pipelines.** (if not enabled) -1. Check **Enable merge trains.** +1. Visit your project's **Settings > General** and expand **Merge requests**. +1. In the **Merge method** section, verify that **Merge commit** is selected. + You cannot use **Merge commit with semi-linear history** or **Fast-forward merge** with merge trains. +1. In the **Merge options** section, select **Enable merged results pipelines.** (if not already selected) and **Enable merge trains.** 1. Click **Save changes** In GitLab 13.5 and earlier, there is only one checkbox, named diff --git a/doc/development/contributing/style_guides.md b/doc/development/contributing/style_guides.md index c316d50c88c..3ed23dcc298 100644 --- a/doc/development/contributing/style_guides.md +++ b/doc/development/contributing/style_guides.md @@ -15,52 +15,83 @@ settings automatically by default. If your editor/IDE does not automatically sup we suggest investigating to see if a plugin exists. For instance here is the [plugin for vim](https://github.com/editorconfig/editorconfig-vim). -## Pre-push static analysis +## Pre-push static analysis with Lefthook -We strongly recommend installing [Lefthook](https://github.com/Arkweid/lefthook) to automatically check -for static analysis offenses before pushing your changes. +[Lefthook](https://github.com/Arkweid/lefthook) is a Git hooks manager that allows +custom logic to be executed prior to Git committing or pushing. GitLab comes with +Lefthook configuration (`lefthook.yml`), but it must be installed. -To install `lefthook`, run the following in your GitLab source directory: +We have a `lefthook.yml` checked in but it is ignored until Lefthook is installed. + +### Uninstall Overcommit + +We were using Overcommit prior to Lefthook, so you may want to uninstall it first with `overcommit --uninstall`. + +### Install Lefthook + +1. Install the `lefthook` Ruby gem: + + ```shell + bundle install + ``` + +1. Install Lefthook managed Git hooks: + + ```shell + bundle exec lefthook install + ``` + +1. Test Lefthook is working by running the Lefthook `prepare-commit-msg` Git hook: + + ```shell + bundle exec lefthook run prepare-commit-msg + ``` + +This should return a fully qualified path command with no other output. + +### Lefthook configuration + +The current Lefthook configuration can be found in [`lefthook.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/lefthook.yml). + +Before you push your changes, Lefthook automatically runs the following checks: + +- Danger: Runs a subset of checks that `danger-review` runs on your merge requests. +- ES lint: Run `yarn eslint` checks (with the [`.eslintrc.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.eslintrc.yml) config) on the modified `*.{js,vue}` files. Tags: `frontend`, `style`. +- HAML lint: Run `bundle exec haml-lint` checks (with the [`.haml-lint.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.haml-lint.yml) config) on the modified `*.html.haml` files. Tags: `view`, `haml`, `style`. +- Markdown lint: Run `yarn markdownlint` checks on the modified `*.md` files. Tags: `documentation`, `style`. +- SCSS lint: Run `bundle exec scss-lint` checks (with the [`.scss-lint.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.scss-lint.yml) config) on the modified `*.scss{,.css}` files. Tags: `stylesheet`, `css`, `style`. +- RuboCop: Run `bundle exec rubocop` checks (with the [`.rubocop.yml`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.rubocop.yml) config) on the modified `*.rb` files. Tags: `backend`, `style`. +- Vale: Run `vale` checks (with the [`.vale.ini`](https://gitlab.com/gitlab-org/gitlab/-/blob/master/.vale.ini) config) on the modified `*.md` files. Tags: `documentation`, `style`. + +In addition to the default configuration, you can define a [local configuration](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#local-config). + +### Disable Lefthook temporarily + +To disable Lefthook temporarily, you can set the `LEFTHOOK` environment variable to `0`. For instance: ```shell -# 1. Make sure to uninstall Overcommit first -overcommit --uninstall - -# If using rbenv, at this point you may need to do: rbenv rehash - -# 2. Install lefthook... - -## With Homebrew (macOS) -brew install Arkweid/lefthook/lefthook - -## Or with Go -go get github.com/Arkweid/lefthook - -## Or with Rubygems -gem install lefthook - -### You may need to run the following if you're using rbenv -rbenv rehash - -# 3. Install the Git hooks -lefthook install -f +LEFTHOOK=0 git push ... ``` -Before you push your changes, Lefthook then automatically run Danger checks, and other checks -for changed files. This saves you time as you don't have to wait for the same errors to be detected -by CI/CD. +### Run Lefthook hooks manually -Lefthook relies on a pre-push hook to prevent commits that violate its ruleset. -To override this behavior, pass the environment variable `LEFTHOOK=0`. That is, -`LEFTHOOK=0 git push`. +To run the `pre-push` Git hook, run: -You can also: +```shell +bundle exec lefthook run pre-push +``` -- Define [local configuration](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#local-config). -- Skip [checks per tag on the fly](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#skip-some-tags-on-the-fly). - For example, `LEFTHOOK_EXCLUDE=frontend git push origin`. -- Run [hooks manually](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#run-githook-group-directly). - For example, `lefthook run pre-push`. +For more information, check out [Lefthook documentation](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#run-githook-group-directly). + +### Skip Lefthook checks per tag + +To skip some checks based on tags when pushing, you can set the `LEFTHOOK_EXCLUDE` environment variable. For instance: + +```shell +LEFTHOOK_EXCLUDE=frontend,documentation git push ... +``` + +For more information, check out [Lefthook documentation](https://github.com/Arkweid/lefthook/blob/master/docs/full_guide.md#skip-some-tags-on-the-fly). ## Ruby, Rails, RSpec diff --git a/doc/development/documentation/testing.md b/doc/development/documentation/testing.md index 561727648f0..7526464a46e 100644 --- a/doc/development/documentation/testing.md +++ b/doc/development/documentation/testing.md @@ -286,7 +286,7 @@ Configuration for `lefthook` is available in the [`lefthook.yml`](https://gitlab file for the [`gitlab`](https://gitlab.com/gitlab-org/gitlab) project. To set up `lefthook` for documentation linting, see -[Pre-push static analysis](../contributing/style_guides.md#pre-push-static-analysis). +[Pre-push static analysis](../contributing/style_guides.md#pre-push-static-analysis-with-lefthook). ### Show subset of Vale alerts diff --git a/doc/user/group/value_stream_analytics/index.md b/doc/user/group/value_stream_analytics/index.md index 438769872c0..4fcef07a04e 100644 --- a/doc/user/group/value_stream_analytics/index.md +++ b/doc/user/group/value_stream_analytics/index.md @@ -324,7 +324,7 @@ To delete a custom value stream: This chart visually depicts the total number of days it takes for cycles to be completed. (Totals are being replaced with averages in [this issue](https://gitlab.com/gitlab-org/gitlab/-/issues/262070).) This chart uses the global page filters for displaying data based on the selected -group, projects, and timeframe. In addition, specific stages can be selected +group, projects, and time frame. In addition, specific stages can be selected from within the chart itself. The chart data is limited to the last 500 items. @@ -345,7 +345,7 @@ Feature.disable(:cycle_analytics_scatterplot_enabled) This chart shows a cumulative count of issues and merge requests per day. This chart uses the global page filters for displaying data based on the selected -group, projects, and timeframe. The chart defaults to showing counts for issues but can be +group, projects, and time frame. The chart defaults to showing counts for issues but can be toggled to show data for merge requests and further refined for specific group-level labels. By default the top group-level labels (max. 10) are pre-selected, with the ability to diff --git a/doc/user/packages/package_registry/index.md b/doc/user/packages/package_registry/index.md index 5876ef19ad9..1f27ede568c 100644 --- a/doc/user/packages/package_registry/index.md +++ b/doc/user/packages/package_registry/index.md @@ -29,7 +29,7 @@ You can use [GitLab CI/CD](../../../ci/README.md) to build packages. For Maven, NuGet, NPM, Conan, and PyPI packages, and Composer dependencies, you can authenticate with GitLab by using the `CI_JOB_TOKEN`. -CI/CD templates, which you can use to get started, are in [this repo](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates). +CI/CD templates, which you can use to get started, are in [this repository](https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates). Learn more about using CI/CD to build: diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md index 6bcbde3da2f..1589c0f7046 100644 --- a/doc/user/profile/account/two_factor_authentication.md +++ b/doc/user/profile/account/two_factor_authentication.md @@ -240,7 +240,7 @@ following desktop browsers: NOTE: For Firefox 47-66, you can enable the FIDO U2F API in -[about:config](https://support.mozilla.org/en-US/kb/about-config-editor-firefox). +[`about:config`](https://support.mozilla.org/en-US/kb/about-config-editor-firefox). Search for `security.webauth.u2f` and double click on it to toggle to `true`. To set up 2FA with a U2F device: diff --git a/doc/user/project/clusters/protect/web_application_firewall/quick_start_guide.md b/doc/user/project/clusters/protect/web_application_firewall/quick_start_guide.md index e9a05b58fec..51e12086860 100644 --- a/doc/user/project/clusters/protect/web_application_firewall/quick_start_guide.md +++ b/doc/user/project/clusters/protect/web_application_firewall/quick_start_guide.md @@ -43,7 +43,7 @@ Google Kubernetes Engine integration. All you have to do is [follow this link](h ## Creating a new project from a template We use a GitLab project templates to get started. As the name suggests, -those projects provide a barebones application built on some well-known frameworks. +those projects provide a bare-bones application built on some well-known frameworks. 1. In GitLab, click the plus icon (**+**) at the top of the navigation bar and select **New project**. diff --git a/doc/user/project/issue_board.md b/doc/user/project/issue_board.md index 7119970fca0..eace2c114d2 100644 --- a/doc/user/project/issue_board.md +++ b/doc/user/project/issue_board.md @@ -563,7 +563,7 @@ another list. This makes it faster to reorder many issues at once. To select and move multiple cards: -1. Select each card with Ctrl+`Click` on Windows or Linux, or Cmd+`Click` on MacOS. +1. Select each card with Control+`Click` on Windows or Linux, or Command+`Click` on MacOS. 1. Drag one of the selected cards to another position or list and all selected cards are moved. ![Multi-select Issue Cards](img/issue_boards_multi_select_v12_4.png) diff --git a/doc/user/project/merge_requests/allow_collaboration.md b/doc/user/project/merge_requests/allow_collaboration.md index 8adaae3b2ef..89fbc4cd89e 100644 --- a/doc/user/project/merge_requests/allow_collaboration.md +++ b/doc/user/project/merge_requests/allow_collaboration.md @@ -23,7 +23,7 @@ of the merge request. ## Enabling commit edits from upstream members -From [GitLab 13.7 onwards](https://gitlab.com/gitlab-org/gitlab/-/issues/23308), +In [GitLab 13.7 and later](https://gitlab.com/gitlab-org/gitlab/-/issues/23308), this setting is enabled by default. It can be changed by users with Developer permissions to the source project. Once enabled, upstream members will also be able to retry the pipelines and jobs of the merge request: diff --git a/doc/user/project/merge_requests/code_quality.md b/doc/user/project/merge_requests/code_quality.md index 53922ad70d3..25058c35fdd 100644 --- a/doc/user/project/merge_requests/code_quality.md +++ b/doc/user/project/merge_requests/code_quality.md @@ -75,7 +75,7 @@ GitLab 11.4 or earlier, you can view the deprecated job definitions in the - Using shared runners, the job should be configured For the [Docker-in-Docker workflow](../../../ci/docker/using_docker_build.md#use-the-docker-executor-with-the-docker-image-docker-in-docker). - Using private runners, there is an [alternative configuration](#set-up-a-private-runner-for-code-quality-without-docker-in-docker) recommended for running CodeQuality analysis more efficiently. -In either configuration, the runner mmust have enough disk space to handle generated Code Quality files. For example on the [GitLab project](https://gitlab.com/gitlab-org/gitlab) the files are approximately 7 GB. +In either configuration, the runner must have enough disk space to handle generated Code Quality files. For example on the [GitLab project](https://gitlab.com/gitlab-org/gitlab) the files are approximately 7 GB. Once you set up GitLab Runner, include the Code Quality template in your CI configuration: diff --git a/doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md b/doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md index e2d6ba9ea1c..42fb5f59a99 100644 --- a/doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md +++ b/doc/user/project/merge_requests/reviewing_and_managing_merge_requests.md @@ -89,7 +89,7 @@ From there, when reviewing merge requests' **Changes** tab, you will see only on ![File-by-file diff navigation](img/file_by_file_v13_2.png) -From [GitLab 13.7](https://gitlab.com/gitlab-org/gitlab/-/issues/233898) onwards, if you want to change +In [GitLab 13.7](https://gitlab.com/gitlab-org/gitlab/-/issues/233898) and later, if you want to change this behavior, you can do so from your **User preferences** (as explained above) or directly in a merge request: diff --git a/doc/user/project/pages/getting_started/pages_forked_sample_project.md b/doc/user/project/pages/getting_started/pages_forked_sample_project.md index c7916b7c01e..60f8533afc8 100644 --- a/doc/user/project/pages/getting_started/pages_forked_sample_project.md +++ b/doc/user/project/pages/getting_started/pages_forked_sample_project.md @@ -52,5 +52,5 @@ You can take some **optional** further steps: ![Change repository's path](../img/change_path_v12_10.png) - - Now go to your SSG's configuration file and change the [base URL](../getting_started_part_one.md#urls-and-baseurls) + - Now go to your SSG's configuration file and change the [base URL](../getting_started_part_one.md#urls-and-base-urls) from `"project-name"` to `""`. The project name setting varies by SSG and may not be in the configuration file. diff --git a/doc/user/project/pages/getting_started_part_one.md b/doc/user/project/pages/getting_started_part_one.md index 801fe0c7ef0..dee021b8225 100644 --- a/doc/user/project/pages/getting_started_part_one.md +++ b/doc/user/project/pages/getting_started_part_one.md @@ -4,7 +4,7 @@ group: Release info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments --- -# GitLab Pages domain names, URLs, and baseurls +# GitLab Pages domain names, URLs, and base URLs On this document, learn how to name your project for GitLab Pages according to your intended website's URL. @@ -78,7 +78,7 @@ To understand Pages domains clearly, read the examples below. - On your GitLab instance, replace `gitlab.io` above with your Pages server domain. Ask your sysadmin for this information. -## URLs and baseurls +## URLs and base URLs NOTE: The `baseurl` option might be called named differently in some static site generators. diff --git a/doc/user/project/static_site_editor/index.md b/doc/user/project/static_site_editor/index.md index 07f122a7828..c0501de3546 100644 --- a/doc/user/project/static_site_editor/index.md +++ b/doc/user/project/static_site_editor/index.md @@ -10,7 +10,7 @@ description: "The static site editor enables users to edit content on static web > - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/28758) in GitLab 12.10. > - WYSIWYG editor [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/214559) in GitLab 13.0. -> - Non-Markdown content blocks uneditable on the WYSIWYG mode [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216836) in GitLab 13.3. +> - Non-Markdown content blocks not editable on the WYSIWYG mode [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/216836) in GitLab 13.3. > - Formatting Markdown [introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/49052) in GitLab 13.7. Static Site Editor (SSE) enables users to edit content on static websites without diff --git a/doc/user/project/web_ide/index.md b/doc/user/project/web_ide/index.md index af8e78afb28..0960e3d703f 100644 --- a/doc/user/project/web_ide/index.md +++ b/doc/user/project/web_ide/index.md @@ -26,7 +26,7 @@ and from merge requests. The file finder allows you to quickly open files in the current branch by searching for fragments of the file path. The file finder is launched using the keyboard shortcut -Cmd+p, Ctrl+p, or t +Command+p, Control+p, or t (when editor is not in focus). Type the filename or file path fragments to start seeing results. diff --git a/doc/user/shortcuts.md b/doc/user/shortcuts.md index 282e3296735..73823dd15e8 100644 --- a/doc/user/shortcuts.md +++ b/doc/user/shortcuts.md @@ -42,10 +42,10 @@ for example comments, replies, issue descriptions, and merge request description | Keyboard Shortcut | Description | | ---------------------------------------------------------------------- | ----------- | | | Edit your last comment. You must be in a blank text field below a thread, and you must already have at least one comment in the thread. | -| (Mac) / Ctrl + Shift + p | Toggle Markdown preview, when editing text in a text field that has **Write** and **Preview** tabs at the top. | -| (Mac) / Ctrl + b | Bold the selected text (surround it with `**`). | -| (Mac) / Ctrl + i | Italicize the selected text (surround it with `_`). | -| (Mac) / Ctrl + k | Add a link (surround the selected text with `[]()`). | +| (Mac) / Control + Shift + p | Toggle Markdown preview, when editing text in a text field that has **Write** and **Preview** tabs at the top. | +| (Mac) / Control + b | Bold the selected text (surround it with `**`). | +| (Mac) / Control + i | Italicize the selected text (surround it with `_`). | +| (Mac) / Control + k | Add a link (surround the selected text with `[]()`). | NOTE: The shortcuts for editing in text fields are always enabled, even when @@ -104,7 +104,7 @@ These shortcuts are available when browsing the files in a project (navigate to | | Move selection up. | | | Move selection down. | | enter | Open selection. | -| esc | Go back to file list screen (only while searching for files, **Repository > Files** then click on **Find File**). | +| Escape | Go back to file list screen (only while searching for files, **Repository > Files** then click on **Find File**). | | y | Go to file permalink (only while viewing a file). | ### Web IDE @@ -113,8 +113,8 @@ These shortcuts are available when editing a file with the [Web IDE](project/web | Keyboard Shortcut | Description | | ------------------------------------------------------- | ----------- | -| (Mac) / Ctrl + p | Search for, and then open another file for editing. | -| (Mac) / Ctrl + Enter | Commit (when editing the commit message). | +| (Mac) / Control + p | Search for, and then open another file for editing. | +| (Mac) / Control + Enter | Commit (when editing the commit message). | ### Repository Graph @@ -145,7 +145,7 @@ These shortcuts are available when using a [filtered search input](search/index. | Keyboard Shortcut | Description | | ----------------------------------------------------- | ----------- | | (Mac) + | Clear entire search filter. | -| (Mac) / Ctrl + | Clear one token at a time. | +| (Mac) / Control + | Clear one token at a time. | ## Epics **(ULTIMATE)** diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 798b8dba2fe..48e9d55d3ca 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -8291,12 +8291,21 @@ msgstr "" msgid "CreateValueStreamForm|'%{name}' Value Stream created" msgstr "" +msgid "CreateValueStreamForm|Add another stage" +msgstr "" + msgid "CreateValueStreamForm|Add stage" msgstr "" msgid "CreateValueStreamForm|All default stages are currently visible" msgstr "" +msgid "CreateValueStreamForm|Create from default template" +msgstr "" + +msgid "CreateValueStreamForm|Create from no template" +msgstr "" + msgid "CreateValueStreamForm|Default stages" msgstr "" @@ -8312,16 +8321,13 @@ msgstr "" msgid "CreateValueStreamForm|End event: " msgstr "" -msgid "CreateValueStreamForm|Enter a name for the stage" -msgstr "" - msgid "CreateValueStreamForm|Enter stage name" msgstr "" -msgid "CreateValueStreamForm|Maximum length %{maxLength} characters" +msgid "CreateValueStreamForm|Enter value stream name" msgstr "" -msgid "CreateValueStreamForm|Name" +msgid "CreateValueStreamForm|Maximum length %{maxLength} characters" msgstr "" msgid "CreateValueStreamForm|Name is required" @@ -8333,6 +8339,9 @@ msgstr "" msgid "CreateValueStreamForm|Please select a start event first" msgstr "" +msgid "CreateValueStreamForm|Please select an end event" +msgstr "" + msgid "CreateValueStreamForm|Recover hidden stage" msgstr "" @@ -8354,6 +8363,9 @@ msgstr "" msgid "CreateValueStreamForm|Stage name already exists" msgstr "" +msgid "CreateValueStreamForm|Stage name is required" +msgstr "" + msgid "CreateValueStreamForm|Start event" msgstr "" @@ -8369,6 +8381,9 @@ msgstr "" msgid "CreateValueStreamForm|Update stage" msgstr "" +msgid "CreateValueStreamForm|Value Stream name" +msgstr "" + msgid "Created" msgstr "" diff --git a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb index a2ec34335ec..bbeb91bbd19 100644 --- a/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb +++ b/spec/features/merge_request/user_suggests_changes_on_diff_spec.rb @@ -73,6 +73,23 @@ RSpec.describe 'User comments on a diff', :js do end end + it 'allows suggestions in replies' do + click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) + + page.within('.js-discussion-note-form') do + fill_in('note_note', with: "```suggestion\n# change to a comment\n```") + click_button('Add comment now') + end + + wait_for_requests + + click_button 'Reply...' + + find('.js-suggestion-btn').click + + expect(find('.js-vue-issue-note-form').value).to include("url = https://github.com/gitlabhq/gitlab-shell.git") + end + it 'suggestion is appliable' do click_diff_line(find("[id='#{sample_compare.changes[1][:line_code]}']")) diff --git a/spec/features/search/user_searches_for_commits_spec.rb b/spec/features/search/user_searches_for_commits_spec.rb index b860cd08e64..1a882050126 100644 --- a/spec/features/search/user_searches_for_commits_spec.rb +++ b/spec/features/search/user_searches_for_commits_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User searches for commits' do +RSpec.describe 'User searches for commits', :js do let(:project) { create(:project, :repository) } let(:sha) { '6d394385cf567f80a8fd85055db1ab4c5295806f' } let(:user) { create(:user) } @@ -41,7 +41,7 @@ RSpec.describe 'User searches for commits' do submit_search('See merge request') select_search_scope('Commits') - expect(page).to have_selector('.commit-row-description', count: 9) + expect(page).to have_selector('.commit-row-description', visible: false, count: 9) end end end diff --git a/spec/features/search/user_searches_for_projects_spec.rb b/spec/features/search/user_searches_for_projects_spec.rb index b64909dd42f..e34ae031679 100644 --- a/spec/features/search/user_searches_for_projects_spec.rb +++ b/spec/features/search/user_searches_for_projects_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' -RSpec.describe 'User searches for projects' do +RSpec.describe 'User searches for projects', :js do let!(:project) { create(:project, :public, name: 'Shop') } context 'when signed out' do diff --git a/spec/frontend/boards/components/board_card_layout_deprecated_spec.js b/spec/frontend/boards/components/board_card_layout_deprecated_spec.js new file mode 100644 index 00000000000..adeb96eb5eb --- /dev/null +++ b/spec/frontend/boards/components/board_card_layout_deprecated_spec.js @@ -0,0 +1,159 @@ +/* global List */ +/* global ListLabel */ + +import Vuex from 'vuex'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; + +import MockAdapter from 'axios-mock-adapter'; +import waitForPromises from 'helpers/wait_for_promises'; +import axios from '~/lib/utils/axios_utils'; + +import '~/boards/models/label'; +import '~/boards/models/assignee'; +import '~/boards/models/list'; +import boardsVuexStore from '~/boards/stores'; +import boardsStore from '~/boards/stores/boards_store'; +import BoardCardLayout from '~/boards/components/board_card_layout_deprecated.vue'; +import issueCardInner from '~/boards/components/issue_card_inner.vue'; +import { listObj, boardsMockInterceptor, setMockEndpoints } from '../mock_data'; + +import { ISSUABLE } from '~/boards/constants'; + +describe('Board card layout', () => { + let wrapper; + let mock; + let list; + let store; + + const localVue = createLocalVue(); + localVue.use(Vuex); + + const createStore = ({ getters = {}, actions = {} } = {}) => { + store = new Vuex.Store({ + ...boardsVuexStore, + actions, + getters, + }); + }; + + // this particular mount component needs to be used after the root beforeEach because it depends on list being initialized + const mountComponent = ({ propsData = {}, provide = {} } = {}) => { + wrapper = shallowMount(BoardCardLayout, { + localVue, + stubs: { + issueCardInner, + }, + store, + propsData: { + list, + issue: list.issues[0], + disabled: false, + index: 0, + ...propsData, + }, + provide: { + groupId: null, + rootPath: '/', + scopedLabelsAvailable: false, + ...provide, + }, + }); + }; + + const setupData = () => { + list = new List(listObj); + boardsStore.create(); + boardsStore.detail.issue = {}; + const label1 = new ListLabel({ + id: 3, + title: 'testing 123', + color: '#000cff', + text_color: 'white', + description: 'test', + }); + return waitForPromises().then(() => { + list.issues[0].labels.push(label1); + }); + }; + + beforeEach(() => { + mock = new MockAdapter(axios); + mock.onAny().reply(boardsMockInterceptor); + setMockEndpoints(); + return setupData(); + }); + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + list = null; + mock.restore(); + }); + + describe('mouse events', () => { + it('sets showDetail to true on mousedown', async () => { + createStore(); + mountComponent(); + + wrapper.trigger('mousedown'); + await wrapper.vm.$nextTick(); + + expect(wrapper.vm.showDetail).toBe(true); + }); + + it('sets showDetail to false on mousemove', async () => { + createStore(); + mountComponent(); + wrapper.trigger('mousedown'); + await wrapper.vm.$nextTick(); + expect(wrapper.vm.showDetail).toBe(true); + wrapper.trigger('mousemove'); + await wrapper.vm.$nextTick(); + expect(wrapper.vm.showDetail).toBe(false); + }); + + it("calls 'setActiveId' when 'graphqlBoardLists' feature flag is turned on", async () => { + const setActiveId = jest.fn(); + createStore({ + actions: { + setActiveId, + }, + }); + mountComponent({ + provide: { + glFeatures: { graphqlBoardLists: true }, + }, + }); + + wrapper.trigger('mouseup'); + await wrapper.vm.$nextTick(); + + expect(setActiveId).toHaveBeenCalledTimes(1); + expect(setActiveId).toHaveBeenCalledWith(expect.any(Object), { + id: list.issues[0].id, + sidebarType: ISSUABLE, + }); + }); + + it("calls 'setActiveId' when epic swimlanes is active", async () => { + const setActiveId = jest.fn(); + const isSwimlanesOn = () => true; + createStore({ + getters: { isSwimlanesOn }, + actions: { + setActiveId, + }, + }); + mountComponent(); + + wrapper.trigger('mouseup'); + await wrapper.vm.$nextTick(); + + expect(setActiveId).toHaveBeenCalledTimes(1); + expect(setActiveId).toHaveBeenCalledWith(expect.any(Object), { + id: list.issues[0].id, + sidebarType: ISSUABLE, + }); + }); + }); +}); diff --git a/spec/frontend/boards/components/board_card_layout_spec.js b/spec/frontend/boards/components/board_card_layout_spec.js index d8633871e8d..88407c3e451 100644 --- a/spec/frontend/boards/components/board_card_layout_spec.js +++ b/spec/frontend/boards/components/board_card_layout_spec.js @@ -1,28 +1,15 @@ -/* global List */ -/* global ListLabel */ - import Vuex from 'vuex'; import { createLocalVue, shallowMount } from '@vue/test-utils'; -import MockAdapter from 'axios-mock-adapter'; -import waitForPromises from 'helpers/wait_for_promises'; -import axios from '~/lib/utils/axios_utils'; - -import '~/boards/models/label'; -import '~/boards/models/assignee'; -import '~/boards/models/list'; -import boardsVuexStore from '~/boards/stores'; -import boardsStore from '~/boards/stores/boards_store'; +import defaultState from '~/boards/stores/state'; import BoardCardLayout from '~/boards/components/board_card_layout.vue'; -import issueCardInner from '~/boards/components/issue_card_inner.vue'; -import { listObj, boardsMockInterceptor, setMockEndpoints } from '../mock_data'; +import IssueCardInner from '~/boards/components/issue_card_inner.vue'; +import { mockLabelList, mockIssue } from '../mock_data'; import { ISSUABLE } from '~/boards/constants'; describe('Board card layout', () => { let wrapper; - let mock; - let list; let store; const localVue = createLocalVue(); @@ -30,7 +17,7 @@ describe('Board card layout', () => { const createStore = ({ getters = {}, actions = {} } = {}) => { store = new Vuex.Store({ - ...boardsVuexStore, + state: defaultState, actions, getters, }); @@ -41,12 +28,12 @@ describe('Board card layout', () => { wrapper = shallowMount(BoardCardLayout, { localVue, stubs: { - issueCardInner, + IssueCardInner, }, store, propsData: { - list, - issue: list.issues[0], + list: mockLabelList, + issue: mockIssue, disabled: false, index: 0, ...propsData, @@ -60,34 +47,9 @@ describe('Board card layout', () => { }); }; - const setupData = () => { - list = new List(listObj); - boardsStore.create(); - boardsStore.detail.issue = {}; - const label1 = new ListLabel({ - id: 3, - title: 'testing 123', - color: '#000cff', - text_color: 'white', - description: 'test', - }); - return waitForPromises().then(() => { - list.issues[0].labels.push(label1); - }); - }; - - beforeEach(() => { - mock = new MockAdapter(axios); - mock.onAny().reply(boardsMockInterceptor); - setMockEndpoints(); - return setupData(); - }); - afterEach(() => { wrapper.destroy(); wrapper = null; - list = null; - mock.restore(); }); describe('mouse events', () => { @@ -112,25 +74,21 @@ describe('Board card layout', () => { expect(wrapper.vm.showDetail).toBe(false); }); - it("calls 'setActiveId' when 'graphqlBoardLists' feature flag is turned on", async () => { + it("calls 'setActiveId'", async () => { const setActiveId = jest.fn(); createStore({ actions: { setActiveId, }, }); - mountComponent({ - provide: { - glFeatures: { graphqlBoardLists: true }, - }, - }); + mountComponent(); wrapper.trigger('mouseup'); await wrapper.vm.$nextTick(); expect(setActiveId).toHaveBeenCalledTimes(1); expect(setActiveId).toHaveBeenCalledWith(expect.any(Object), { - id: list.issues[0].id, + id: mockIssue.id, sidebarType: ISSUABLE, }); }); @@ -151,7 +109,7 @@ describe('Board card layout', () => { expect(setActiveId).toHaveBeenCalledTimes(1); expect(setActiveId).toHaveBeenCalledWith(expect.any(Object), { - id: list.issues[0].id, + id: mockIssue.id, sidebarType: ISSUABLE, }); }); diff --git a/spec/frontend/boards/components/board_configuration_options_spec.js b/spec/frontend/boards/components/board_configuration_options_spec.js index d9614c254e2..6f0971a9458 100644 --- a/spec/frontend/boards/components/board_configuration_options_spec.js +++ b/spec/frontend/boards/components/board_configuration_options_spec.js @@ -7,6 +7,7 @@ describe('BoardConfigurationOptions', () => { const defaultProps = { hideBacklogList: false, hideClosedList: false, + readonly: false, }; const createComponent = (props = {}) => { @@ -61,4 +62,18 @@ describe('BoardConfigurationOptions', () => { expect(wrapper.emitted('update:hideClosedList')).toEqual([[true]]); }); + + it('renders checkboxes disabled when user does not have edit rights', () => { + createComponent({ readonly: true }); + + expect(closedListCheckbox().attributes('disabled')).toBe('true'); + expect(backlogListCheckbox().attributes('disabled')).toBe('true'); + }); + + it('renders checkboxes enabled when user has edit rights', () => { + createComponent(); + + expect(closedListCheckbox().attributes('disabled')).toBeUndefined(); + expect(backlogListCheckbox().attributes('disabled')).toBeUndefined(); + }); }); diff --git a/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js b/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js index 74d88d9f34c..e6e2befedd0 100644 --- a/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js +++ b/spec/frontend/boards/components/sidebar/board_sidebar_milestone_select_spec.js @@ -20,7 +20,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () => wrapper = null; }); - const createWrapper = ({ milestone = null } = {}) => { + const createWrapper = ({ milestone = null, loading = false } = {}) => { store = createStore(); store.state.issues = { [TEST_ISSUE.id]: { ...TEST_ISSUE, milestone } }; store.state.activeId = TEST_ISSUE.id; @@ -38,7 +38,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () => }, mocks: { $apollo: { - loading: false, + loading, }, }, }); @@ -63,12 +63,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () => }); it('shows loader while Apollo is loading', async () => { - createWrapper({ milestone: TEST_MILESTONE }); - - expect(findLoader().exists()).toBe(false); - - wrapper.vm.$apollo.loading = true; - await wrapper.vm.$nextTick(); + createWrapper({ milestone: TEST_MILESTONE, loading: true }); expect(findLoader().exists()).toBe(true); }); @@ -76,8 +71,7 @@ describe('~/boards/components/sidebar/board_sidebar_milestone_select.vue', () => it('shows message when error or no milestones found', async () => { createWrapper(); - wrapper.setData({ milestones: [] }); - await wrapper.vm.$nextTick(); + await wrapper.setData({ milestones: [] }); expect(findNoMilestonesFoundItem().text()).toBe('No milestones found'); }); diff --git a/spec/frontend/boards/issue_spec.js b/spec/frontend/boards/issue_spec.js index d68e17c06a7..1f354fb04db 100644 --- a/spec/frontend/boards/issue_spec.js +++ b/spec/frontend/boards/issue_spec.js @@ -41,7 +41,7 @@ describe('Issue model', () => { }); expect(issue.labels.length).toBe(1); - expect(issue.labels[0].color).toBe('red'); + expect(issue.labels[0].color).toBe('#F0AD4E'); }); it('adds other label with same title', () => { diff --git a/spec/frontend/boards/mock_data.js b/spec/frontend/boards/mock_data.js index d5cfb9b7d07..6d60937f850 100644 --- a/spec/frontend/boards/mock_data.js +++ b/spec/frontend/boards/mock_data.js @@ -137,7 +137,7 @@ export const rawIssue = { { id: 1, title: 'test', - color: 'red', + color: '#F0AD4E', description: 'testing', }, ], @@ -165,7 +165,7 @@ export const mockIssue = { { id: 1, title: 'test', - color: 'red', + color: '#F0AD4E', description: 'testing', }, ], diff --git a/spec/frontend/boards/stores/actions_spec.js b/spec/frontend/boards/stores/actions_spec.js index 04b08b579f8..c285e04a042 100644 --- a/spec/frontend/boards/stores/actions_spec.js +++ b/spec/frontend/boards/stores/actions_spec.js @@ -1222,6 +1222,40 @@ describe('setSelectedProject', () => { }); }); +describe('toggleBoardItemMultiSelection', () => { + const boardItem = mockIssue; + + it('should commit mutation ADD_BOARD_ITEM_TO_SELECTION if item is not on selection state', () => { + testAction( + actions.toggleBoardItemMultiSelection, + boardItem, + { selectedBoardItems: [] }, + [ + { + type: types.ADD_BOARD_ITEM_TO_SELECTION, + payload: boardItem, + }, + ], + [], + ); + }); + + it('should commit mutation REMOVE_BOARD_ITEM_FROM_SELECTION if item is on selection state', () => { + testAction( + actions.toggleBoardItemMultiSelection, + boardItem, + { selectedBoardItems: [mockIssue] }, + [ + { + type: types.REMOVE_BOARD_ITEM_FROM_SELECTION, + payload: boardItem, + }, + ], + [], + ); + }); +}); + describe('fetchBacklog', () => { expectNotImplemented(actions.fetchBacklog); }); diff --git a/spec/frontend/boards/stores/mutations_spec.js b/spec/frontend/boards/stores/mutations_spec.js index 73283117bc7..2b6548534c3 100644 --- a/spec/frontend/boards/stores/mutations_spec.js +++ b/spec/frontend/boards/stores/mutations_spec.js @@ -594,4 +594,27 @@ describe('Board Store Mutations', () => { expect(state.selectedProject).toEqual(mockGroupProjects[0]); }); }); + + describe('ADD_BOARD_ITEM_TO_SELECTION', () => { + it('Should add boardItem to selectedBoardItems state', () => { + expect(state.selectedBoardItems).toEqual([]); + + mutations[types.ADD_BOARD_ITEM_TO_SELECTION](state, mockIssue); + + expect(state.selectedBoardItems).toEqual([mockIssue]); + }); + }); + + describe('REMOVE_BOARD_ITEM_FROM_SELECTION', () => { + it('Should remove boardItem to selectedBoardItems state', () => { + state = { + ...state, + selectedBoardItems: [mockIssue], + }; + + mutations[types.REMOVE_BOARD_ITEM_FROM_SELECTION](state, mockIssue); + + expect(state.selectedBoardItems).toEqual([]); + }); + }); }); diff --git a/spec/frontend/search/highlight_blob_search_result_spec.js b/spec/frontend/search/highlight_blob_search_result_spec.js index 112e6f5124f..c1b0c7d794b 100644 --- a/spec/frontend/search/highlight_blob_search_result_spec.js +++ b/spec/frontend/search/highlight_blob_search_result_spec.js @@ -1,6 +1,7 @@ import setHighlightClass from '~/search/highlight_blob_search_result'; const fixture = 'search/blob_search_result.html'; +const searchKeyword = 'Send'; // spec/frontend/fixtures/search.rb#79 describe('search/highlight_blob_search_result', () => { preloadFixtures(fixture); @@ -8,7 +9,7 @@ describe('search/highlight_blob_search_result', () => { beforeEach(() => loadFixtures(fixture)); it('highlights lines with search term occurrence', () => { - setHighlightClass(); + setHighlightClass(searchKeyword); expect(document.querySelectorAll('.blob-result .hll').length).toBe(4); }); diff --git a/spec/frontend/search/index_spec.js b/spec/frontend/search/index_spec.js index 023cd341345..1992a7f4437 100644 --- a/spec/frontend/search/index_spec.js +++ b/spec/frontend/search/index_spec.js @@ -1,9 +1,11 @@ +import setHighlightClass from 'ee_else_ce/search/highlight_blob_search_result'; import { initSearchApp } from '~/search'; import createStore from '~/search/store'; jest.mock('~/search/store'); jest.mock('~/search/topbar'); jest.mock('~/search/sidebar'); +jest.mock('ee_else_ce/search/highlight_blob_search_result'); describe('initSearchApp', () => { let defaultLocation; @@ -42,6 +44,7 @@ describe('initSearchApp', () => { it(`decodes ${search} to ${decodedSearch}`, () => { expect(createStore).toHaveBeenCalledWith({ query: { search: decodedSearch } }); + expect(setHighlightClass).toHaveBeenCalledWith(decodedSearch); }); }); }); diff --git a/spec/frontend/search/topbar/components/app_spec.js b/spec/frontend/search/topbar/components/app_spec.js new file mode 100644 index 00000000000..faf3629b444 --- /dev/null +++ b/spec/frontend/search/topbar/components/app_spec.js @@ -0,0 +1,113 @@ +import Vuex from 'vuex'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import { GlForm, GlSearchBoxByType, GlButton } from '@gitlab/ui'; +import { MOCK_QUERY } from 'jest/search/mock_data'; +import GlobalSearchTopbar from '~/search/topbar/components/app.vue'; +import GroupFilter from '~/search/topbar/components/group_filter.vue'; +import ProjectFilter from '~/search/topbar/components/project_filter.vue'; + +const localVue = createLocalVue(); +localVue.use(Vuex); + +describe('GlobalSearchTopbar', () => { + let wrapper; + + const actionSpies = { + applyQuery: jest.fn(), + setQuery: jest.fn(), + }; + + const createComponent = (initialState) => { + const store = new Vuex.Store({ + state: { + query: MOCK_QUERY, + ...initialState, + }, + actions: actionSpies, + }); + + wrapper = shallowMount(GlobalSearchTopbar, { + localVue, + store, + }); + }; + + afterEach(() => { + wrapper.destroy(); + wrapper = null; + }); + + const findTopbarForm = () => wrapper.find(GlForm); + const findGlSearchBox = () => wrapper.find(GlSearchBoxByType); + const findGroupFilter = () => wrapper.find(GroupFilter); + const findProjectFilter = () => wrapper.find(ProjectFilter); + const findSearchButton = () => wrapper.find(GlButton); + + describe('template', () => { + beforeEach(() => { + createComponent(); + }); + + it('renders Topbar Form always', () => { + expect(findTopbarForm().exists()).toBe(true); + }); + + describe('Search box', () => { + it('renders always', () => { + expect(findGlSearchBox().exists()).toBe(true); + }); + + describe('onSearch', () => { + const testSearch = 'test search'; + + beforeEach(() => { + findGlSearchBox().vm.$emit('input', testSearch); + }); + + it('calls setQuery when input event is fired from GlSearchBoxByType', () => { + expect(actionSpies.setQuery).toHaveBeenCalledWith(expect.any(Object), { + key: 'search', + value: testSearch, + }); + }); + }); + }); + + describe.each` + snippets | showFilters + ${null} | ${true} + ${{ query: { snippets: '' } }} | ${true} + ${{ query: { snippets: false } }} | ${true} + ${{ query: { snippets: true } }} | ${false} + ${{ query: { snippets: 'false' } }} | ${true} + ${{ query: { snippets: 'true' } }} | ${false} + `('topbar filters', ({ snippets, showFilters }) => { + beforeEach(() => { + createComponent(snippets); + }); + + it(`does${showFilters ? '' : ' not'} render when snippets is ${JSON.stringify( + snippets, + )}`, () => { + expect(findGroupFilter().exists()).toBe(showFilters); + expect(findProjectFilter().exists()).toBe(showFilters); + }); + }); + + it('renders SearchButton always', () => { + expect(findSearchButton().exists()).toBe(true); + }); + }); + + describe('actions', () => { + beforeEach(() => { + createComponent(); + }); + + it('clicking SearchButton calls applyQuery', () => { + findTopbarForm().vm.$emit('submit', { preventDefault: () => {} }); + + expect(actionSpies.applyQuery).toHaveBeenCalled(); + }); + }); +}); diff --git a/spec/frontend/search_spec.js b/spec/frontend/search_spec.js deleted file mode 100644 index d234a7fccb9..00000000000 --- a/spec/frontend/search_spec.js +++ /dev/null @@ -1,23 +0,0 @@ -import setHighlightClass from 'ee_else_ce/search/highlight_blob_search_result'; -import Search from '~/pages/search/show/search'; - -jest.mock('~/api'); -jest.mock('ee_else_ce/search/highlight_blob_search_result'); - -describe('Search', () => { - const fixturePath = 'search/show.html'; - - preloadFixtures(fixturePath); - - describe('constructor side effects', () => { - afterEach(() => { - jest.restoreAllMocks(); - }); - - it('highlights lines with search terms in blob search results', () => { - new Search(); // eslint-disable-line no-new - - expect(setHighlightClass).toHaveBeenCalled(); - }); - }); -}); diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 9fac6c8e192..7b3d413c02b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -221,7 +221,6 @@ RSpec.configure do |config| # of older sidebar. # See https://gitlab.com/groups/gitlab-org/-/epics/1863 stub_feature_flags(vue_issuable_sidebar: false) - stub_feature_flags(vue_issuable_epic_sidebar: false) # Merge request widget GraphQL requests are disabled in the tests # for now whilst we migrate as much as we can over the GraphQL diff --git a/spec/views/search/_filter.html.haml_spec.rb b/spec/views/search/_filter.html.haml_spec.rb deleted file mode 100644 index 868408f7beb..00000000000 --- a/spec/views/search/_filter.html.haml_spec.rb +++ /dev/null @@ -1,17 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'search/_filter' do - context 'when the search page is opened' do - it 'displays the correct elements' do - render - - expect(rendered).to have_selector('label[for="dashboard_search_group"]') - expect(rendered).to have_selector('input#js-search-group-dropdown') - - expect(rendered).to have_selector('label[for="dashboard_search_project"]') - expect(rendered).to have_selector('input#js-search-project-dropdown') - end - end -end diff --git a/spec/views/search/_form.html.haml_spec.rb b/spec/views/search/_form.html.haml_spec.rb deleted file mode 100644 index 073a39e4ed6..00000000000 --- a/spec/views/search/_form.html.haml_spec.rb +++ /dev/null @@ -1,14 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe 'search/_form' do - context 'when the search page is opened' do - it 'displays the correct elements' do - render - - expect(rendered).to have_selector('.search-field-holder.form-group') - expect(rendered).to have_selector('label[for="dashboard_search"]') - end - end -end diff --git a/yarn.lock b/yarn.lock index dad531a314c..c8e03828970 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2486,14 +2486,15 @@ browserify-zlib@^0.2.0: pako "~1.0.5" browserslist@^4.12.0, browserslist@^4.6.3, browserslist@^4.8.3: - version "4.12.0" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.12.0.tgz#06c6d5715a1ede6c51fc39ff67fd647f740b656d" - integrity sha512-UH2GkcEDSI0k/lRkuDSzFl9ZZ87skSy9w2XAn1MsZnL+4c4rqbBd3e82UWHbYDpztABrPBhZsTEeuxVfHppqDg== + version "4.16.1" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.16.1.tgz#bf757a2da376b3447b800a16f0f1c96358138766" + integrity sha512-UXhDrwqsNcpTYJBTZsbGATDxZbiVDsx6UjpmRUmtnP10pr8wAYr5LgFoEFw9ixriQH2mv/NX2SfGzE/o8GndLA== dependencies: - caniuse-lite "^1.0.30001043" - electron-to-chromium "^1.3.413" - node-releases "^1.1.53" - pkg-up "^2.0.0" + caniuse-lite "^1.0.30001173" + colorette "^1.2.1" + electron-to-chromium "^1.3.634" + escalade "^3.1.1" + node-releases "^1.1.69" bs-logger@0.x: version "0.2.6" @@ -2717,10 +2718,10 @@ camelcase@^6.0.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.0.0.tgz#5259f7c30e35e278f1bdc2a4d91230b37cad981e" integrity sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w== -caniuse-lite@^1.0.30000980, caniuse-lite@^1.0.30001043: - version "1.0.30001081" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001081.tgz#40615a3c416a047c5a4d45673e5257bf128eb3b5" - integrity sha512-iZdh3lu09jsUtLE6Bp8NAbJskco4Y3UDtkR3GTCJGsbMowBU5IWDFF79sV2ws7lSqTzWyKazxam2thasHymENQ== +caniuse-lite@^1.0.30000980, caniuse-lite@^1.0.30001173: + version "1.0.30001178" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001178.tgz#3ad813b2b2c7d585b0be0a2440e1e233c6eabdbc" + integrity sha512-VtdZLC0vsXykKni8Uztx45xynytOi71Ufx9T8kHptSw9AL4dpqailUJJHavttuzUe1KYuBYtChiWv+BAb7mPmQ== capture-exit@^2.0.0: version "2.0.0" @@ -3050,6 +3051,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +colorette@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b" + integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw== + colors@^1.1.0: version "1.3.3" resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.3.tgz#39e005d546afe01e01f9c4ca8fa50f686a01205d" @@ -4283,10 +4289,10 @@ ejs@^2.6.1: resolved "https://registry.yarnpkg.com/ejs/-/ejs-2.6.1.tgz#498ec0d495655abc6f23cd61868d926464071aa0" integrity sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ== -electron-to-chromium@^1.3.413: - version "1.3.466" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.466.tgz#89f716db3afc4bb482ea2aaaa16c4808f89f762a" - integrity sha512-eieqkoM2hCkZZRhETKyCouMziDV3l4XEKHRLuzcHG+HV+P7PeODU/z9HAmBgMQkzvHg2DoyQhfIDmmeguLZT/Q== +electron-to-chromium@^1.3.634: + version "1.3.642" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.642.tgz#8b884f50296c2ae2a9997f024d0e3e57facc2b94" + integrity sha512-cev+jOrz/Zm1i+Yh334Hed6lQVOkkemk2wRozfMF4MtTR7pxf3r3L5Rbd7uX1zMcEqVJ7alJBnJL7+JffkC6FQ== elliptic@^6.0.0: version "6.4.0" @@ -4480,6 +4486,11 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + escape-goat@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/escape-goat/-/escape-goat-2.1.1.tgz#1b2dc77003676c457ec760b2dc68edb648188675" @@ -8636,10 +8647,10 @@ node-notifier@^8.0.0: uuid "^8.3.0" which "^2.0.2" -node-releases@^1.1.53: - version "1.1.58" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.58.tgz#8ee20eef30fa60e52755fcc0942def5a734fe935" - integrity sha512-NxBudgVKiRh/2aPWMgPR7bPTX0VPmGx5QBwCtdHitnqFE5/O8DeBXuIMH1nwNnw/aMo6AjOrpsHzfY3UbUJ7yg== +node-releases@^1.1.69: + version "1.1.70" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.70.tgz#66e0ed0273aa65666d7fe78febe7634875426a08" + integrity sha512-Slf2s69+2/uAD79pVVQo8uSiC34+g8GWY8UH2Qtqv34ZfhYrxpYpfzs9Js9d6O0mbDmALuxaTlplnBTnSELcrw== node-sass@^4.14.1: version "4.14.1" @@ -9362,13 +9373,6 @@ pkg-dir@^4.1.0, pkg-dir@^4.2.0: dependencies: find-up "^4.0.0" -pkg-up@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-up/-/pkg-up-2.0.0.tgz#c819ac728059a461cab1c3889a2be3c49a004d7f" - integrity sha1-yBmscoBZpGHKscOImivjxJoATX8= - dependencies: - find-up "^2.1.0" - pngjs@^3.0.0: version "3.3.3" resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-3.3.3.tgz#85173703bde3edac8998757b96e5821d0966a21b"