diff --git a/app/assets/javascripts/pages/projects/tags/index/index.js b/app/assets/javascripts/pages/projects/tags/index/index.js index 98560c1193b008eb5af47bc68017b4d1d8b1d7c1..9e48dd9e46335defd6535da057ad8c6f9b11cba9 100644 --- a/app/assets/javascripts/pages/projects/tags/index/index.js +++ b/app/assets/javascripts/pages/projects/tags/index/index.js @@ -1,3 +1,4 @@ +import TagSortDropdown from '~/tags'; import { initRemoveTag } from '../remove_tag'; initRemoveTag({ @@ -5,3 +6,4 @@ initRemoveTag({ document.querySelector(`[data-path="${path}"]`).closest('.js-tag-list').remove(); }, }); +TagSortDropdown(); diff --git a/app/assets/javascripts/tags/components/sort_dropdown.vue b/app/assets/javascripts/tags/components/sort_dropdown.vue new file mode 100644 index 0000000000000000000000000000000000000000..036ce2cca78968152bb3b5ed22968ba6b05c06ad --- /dev/null +++ b/app/assets/javascripts/tags/components/sort_dropdown.vue @@ -0,0 +1,77 @@ + + diff --git a/app/assets/javascripts/tags/index.js b/app/assets/javascripts/tags/index.js new file mode 100644 index 0000000000000000000000000000000000000000..68510f3fe3a7100678bca8faeae081ac0be803b7 --- /dev/null +++ b/app/assets/javascripts/tags/index.js @@ -0,0 +1,24 @@ +import Vue from 'vue'; +import SortDropdown from './components/sort_dropdown.vue'; + +const mountDropdownApp = (el) => { + const { sortOptions, filterTagsPath } = el.dataset; + + return new Vue({ + el, + name: 'SortTagsDropdownApp', + components: { + SortDropdown, + }, + provide: { + sortOptions: JSON.parse(sortOptions), + filterTagsPath, + }, + render: (createElement) => createElement(SortDropdown), + }); +}; + +export default () => { + const el = document.getElementById('js-tags-sort-dropdown'); + return el ? mountDropdownApp(el) : null; +}; diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 94b0473e1f38769e4ddf8aaee119be0e26dd5662..3bf9988ca22bc1de2835ca91c9746b6bdff03c65 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -9,6 +9,9 @@ class Projects::TagsController < Projects::ApplicationController before_action :require_non_empty_project before_action :authorize_download_code! before_action :authorize_admin_tag!, only: [:new, :create, :destroy] + before_action do + push_frontend_feature_flag(:gldropdown_tags, default_enabled: :yaml) + end feature_category :source_code_management, [:index, :show, :new, :destroy] feature_category :release_evidence, [:create] diff --git a/app/views/projects/tags/index.html.haml b/app/views/projects/tags/index.html.haml index b5b394723ab09ce9159399d5b3bee9167b1a9918..229f13d0ff3a39bd30d608cfba4e0b915cc729e1 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -9,20 +9,23 @@ = s_('TagsPage|Tags give the ability to mark specific points in history as being important') .nav-controls - = form_tag(filter_tags_path, method: :get) do - = search_field_tag :search, params[:search], { placeholder: s_('TagsPage|Filter by tag name'), id: 'tag-search', class: 'form-control search-text-input input-short', spellcheck: false } + - unless Gitlab::Ci::Features.gldropdown_tags_enabled? + = form_tag(filter_tags_path, method: :get) do + = search_field_tag :search, params[:search], { placeholder: s_('TagsPage|Filter by tag name'), id: 'tag-search', class: 'form-control search-text-input input-short', spellcheck: false } - .dropdown - %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown'} } - %span.light - = tags_sort_options_hash[@sort] - = sprite_icon('chevron-down', css_class: 'dropdown-menu-toggle-icon gl-top-3') - %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable - %li.dropdown-header - = s_('TagsPage|Sort by') - - tags_sort_options_hash.each do |value, title| - %li - = link_to title, filter_tags_path(sort: value), class: ("is-active" if @sort == value) + .dropdown + %button.dropdown-menu-toggle{ type: 'button', data: { toggle: 'dropdown'} } + %span.light + = tags_sort_options_hash[@sort] + = sprite_icon('chevron-down', css_class: 'dropdown-menu-toggle-icon gl-top-3') + %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-selectable + %li.dropdown-header + = s_('TagsPage|Sort by') + - tags_sort_options_hash.each do |value, title| + %li + = link_to title, filter_tags_path(sort: value), class: ("is-active" if @sort == value) + - else + #js-tags-sort-dropdown{ data: { filter_tags_path: filter_tags_path, sort_options: tags_sort_options_hash.to_json } } - if can?(current_user, :admin_tag, @project) = link_to new_project_tag_path(@project), class: 'btn gl-button btn-confirm', data: { qa_selector: "new_tag_button" } do = s_('TagsPage|New tag') diff --git a/config/feature_flags/development/gldropdown_tags.yml b/config/feature_flags/development/gldropdown_tags.yml new file mode 100644 index 0000000000000000000000000000000000000000..704f276ac372da8228c2b19fa6fc036f1310166b --- /dev/null +++ b/config/feature_flags/development/gldropdown_tags.yml @@ -0,0 +1,8 @@ +--- +name: gldropdown_tags +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/58589 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/327055 +milestone: '13.11' +type: development +group: group::continuous integration +default_enabled: false diff --git a/lib/gitlab/ci/features.rb b/lib/gitlab/ci/features.rb index 320f3a959fafe962cfb0ec1db3b6c7760c45cfb5..a86109677085a859757d29a143c275db76e91e07 100644 --- a/lib/gitlab/ci/features.rb +++ b/lib/gitlab/ci/features.rb @@ -63,6 +63,10 @@ def self.display_codequality_backend_comparison?(project) def self.multiple_cache_per_job? ::Feature.enabled?(:multiple_cache_per_job, default_enabled: :yaml) end + + def self.gldropdown_tags_enabled? + ::Feature.enabled?(:gldropdown_tags, default_enabled: :yaml) + end end end end diff --git a/spec/frontend/tags/components/sort_dropdown_spec.js b/spec/frontend/tags/components/sort_dropdown_spec.js new file mode 100644 index 0000000000000000000000000000000000000000..b0fd98ec68e18e7a16e299005e3f49e9fa3d4a72 --- /dev/null +++ b/spec/frontend/tags/components/sort_dropdown_spec.js @@ -0,0 +1,81 @@ +import { GlDropdownItem, GlSearchBoxByClick } from '@gitlab/ui'; +import { mount } from '@vue/test-utils'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import * as urlUtils from '~/lib/utils/url_utility'; +import SortDropdown from '~/tags/components/sort_dropdown.vue'; + +describe('Tags sort dropdown', () => { + let wrapper; + + const createWrapper = (props = {}) => { + return extendedWrapper( + mount(SortDropdown, { + provide: { + filterTagsPath: '/root/ci-cd-project-demo/-/tags', + sortOptions: { + name_asc: 'Name', + updated_asc: 'Oldest updated', + updated_desc: 'Last updated', + }, + ...props, + }, + }), + ); + }; + + const findSearchBox = () => wrapper.findComponent(GlSearchBoxByClick); + const findTagsDropdown = () => wrapper.findByTestId('tags-dropdown'); + + afterEach(() => { + if (wrapper) { + wrapper.destroy(); + } + }); + + describe('default state', () => { + beforeEach(() => { + wrapper = createWrapper(); + }); + + it('should have a search box with a placeholder', () => { + const searchBox = findSearchBox(); + + expect(searchBox.exists()).toBe(true); + expect(searchBox.find('input').attributes('placeholder')).toBe('Filter by tag name'); + }); + + it('should have a sort order dropdown', () => { + const branchesDropdown = findTagsDropdown(); + + expect(branchesDropdown.exists()).toBe(true); + }); + }); + + describe('when submitting a search term', () => { + beforeEach(() => { + urlUtils.visitUrl = jest.fn(); + + wrapper = createWrapper(); + }); + + it('should call visitUrl', () => { + const searchBox = findSearchBox(); + + searchBox.vm.$emit('submit'); + + expect(urlUtils.visitUrl).toHaveBeenCalledWith( + '/root/ci-cd-project-demo/-/tags?sort=updated_desc', + ); + }); + + it('should send a sort parameter', () => { + const sortDropdownItems = findTagsDropdown().findAllComponents(GlDropdownItem).at(0); + + sortDropdownItems.vm.$emit('click'); + + expect(urlUtils.visitUrl).toHaveBeenCalledWith( + '/root/ci-cd-project-demo/-/tags?sort=name_asc', + ); + }); + }); +}); diff --git a/spec/views/projects/tags/index.html.haml_spec.rb b/spec/views/projects/tags/index.html.haml_spec.rb index dc008875062f6cb856ff6edc592798327990607f..18b42f98e0b47e331d96b54590c370296d446e68 100644 --- a/spec/views/projects/tags/index.html.haml_spec.rb +++ b/spec/views/projects/tags/index.html.haml_spec.rb @@ -21,6 +21,7 @@ end it 'defaults sort dropdown toggle to last updated' do + stub_feature_flags(gldropdown_tags: false) render expect(rendered).to have_button('Last updated') end