diff options
22 files changed, 140 insertions, 98 deletions
diff --git a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js index b70125c80ca..e22f542b7bf 100644 --- a/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js +++ b/app/assets/javascripts/filtered_search/issuable_filtered_search_token_keys.js @@ -58,11 +58,16 @@ export const alternativeTokenKeys = [ export const conditions = [ { - url: 'assignee_id=0', + url: 'assignee_id=None', tokenKey: 'assignee', value: 'none', }, { + url: 'assignee_id=Any', + tokenKey: 'assignee', + value: 'any', + }, + { url: 'milestone_title=No+Milestone', tokenKey: 'milestone', value: 'none', diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb index 6e17bc212e4..3802aa5f40f 100644 --- a/app/controllers/dashboard/milestones_controller.rb +++ b/app/controllers/dashboard/milestones_controller.rb @@ -4,12 +4,13 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController include MilestoneActions before_action :projects + before_action :groups, only: :index before_action :milestone, only: [:show, :merge_requests, :participants, :labels] def index respond_to do |format| format.html do - @milestone_states = GlobalMilestone.states_count(@projects) + @milestone_states = Milestone.states_count(@projects.select(:id), @groups.select(:id)) @milestones = Kaminari.paginate_array(milestones).page(params[:page]) end format.json do @@ -42,4 +43,8 @@ class Dashboard::MilestonesController < Dashboard::ApplicationController @milestone = DashboardMilestone.build(@projects, params[:title]) render_404 unless @milestone end + + def groups + @groups ||= GroupsFinder.new(current_user, state_all: true).execute + end end diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index a7cee426cf1..b42116b0f36 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -10,7 +10,7 @@ class Groups::MilestonesController < Groups::ApplicationController def index respond_to do |format| format.html do - @milestone_states = GlobalMilestone.states_count(group_projects, group) + @milestone_states = Milestone.states_count(group_projects, [group]) @milestones = Kaminari.paginate_array(milestones).page(params[:page]) end format.json do diff --git a/app/models/global_milestone.rb b/app/models/global_milestone.rb index a6cebabe089..085ffd16c6a 100644 --- a/app/models/global_milestone.rb +++ b/app/models/global_milestone.rb @@ -34,50 +34,6 @@ class GlobalMilestone new(title, child_milestones) end - def self.states_count(projects, group = nil) - legacy_group_milestones_count = legacy_group_milestone_states_count(projects) - group_milestones_count = group_milestones_states_count(group) - - legacy_group_milestones_count.merge(group_milestones_count) do |k, legacy_group_milestones_count, group_milestones_count| - legacy_group_milestones_count + group_milestones_count - end - end - - def self.group_milestones_states_count(group) - return STATE_COUNT_HASH unless group - - params = { group_ids: [group.id], state: 'all' } - - relation = MilestonesFinder.new(params).execute # rubocop: disable CodeReuse/Finder - grouped_by_state = relation.reorder(nil).group(:state).count - - { - opened: grouped_by_state['active'] || 0, - closed: grouped_by_state['closed'] || 0, - all: grouped_by_state.values.sum - } - end - - # Counts the legacy group milestones which must be grouped by title - def self.legacy_group_milestone_states_count(projects) - return STATE_COUNT_HASH unless projects - - params = { project_ids: projects.map(&:id), state: 'all' } - - relation = MilestonesFinder.new(params).execute # rubocop: disable CodeReuse/Finder - project_milestones_by_state_and_title = relation.reorder(nil).group(:state, :title).count - - opened = count_by_state(project_milestones_by_state_and_title, 'active') - closed = count_by_state(project_milestones_by_state_and_title, 'closed') - all = project_milestones_by_state_and_title.map { |(_, title), _| title }.uniq.count - - { - opened: opened, - closed: closed, - all: all - } - end - def self.count_by_state(milestones_by_state_and_title, state) milestones_by_state_and_title.count do |(milestone_state, _), _| milestone_state == state diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 892a680f221..9f2c4efaa96 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -170,6 +170,22 @@ class Milestone < ActiveRecord::Base sorted.with_order_id_desc end + def self.states_count(projects, groups = nil) + return STATE_COUNT_HASH unless projects || groups + + counts = Milestone + .for_projects_and_groups(projects&.map(&:id), groups&.map(&:id)) + .reorder(nil) + .group(:state) + .count + + { + opened: counts['active'] || 0, + closed: counts['closed'] || 0, + all: counts.values.sum + } + end + ## # Returns the String necessary to reference this Milestone in Markdown. Group # milestones only support name references, and do not support cross-project diff --git a/app/views/shared/issuable/_search_bar.html.haml b/app/views/shared/issuable/_search_bar.html.haml index cb45928d9a5..1d876cc4a5d 100644 --- a/app/views/shared/issuable/_search_bar.html.haml +++ b/app/views/shared/issuable/_search_bar.html.haml @@ -61,7 +61,10 @@ %ul{ data: { dropdown: true } } %li.filter-dropdown-item{ data: { value: 'none' } } %button.btn.btn-link{ type: 'button' } - = _('No Assignee') + = _('None') + %li.filter-dropdown-item{ data: { value: 'any' } } + %button.btn.btn-link{ type: 'button' } + = _('Any') %li.divider.droplab-item-ignore - if current_user = render 'shared/issuable/user_dropdown_item', @@ -81,7 +84,7 @@ %li.filter-dropdown-item{ data: { value: 'upcoming' } } %button.btn.btn-link{ type: 'button' } = _('Upcoming') - %li.filter-dropdown-item{ 'data-value' => 'started' } + %li.filter-dropdown-item{ data: { value: 'started' } } %button.btn.btn-link{ type: 'button' } = _('Started') %li.divider.droplab-item-ignore diff --git a/changelogs/unreleased/52383-ui-filter-assignee-none-any.yml b/changelogs/unreleased/52383-ui-filter-assignee-none-any.yml new file mode 100644 index 00000000000..adf153f33ce --- /dev/null +++ b/changelogs/unreleased/52383-ui-filter-assignee-none-any.yml @@ -0,0 +1,5 @@ +--- +title: Add None/Any option for assignee_id in search bar +merge_request: 22599 +author: Heinrich Lee Yu +type: added diff --git a/changelogs/unreleased/52780-stale-pipeline-status-cache-for-_project-after-disabling-pipelines.yml b/changelogs/unreleased/52780-stale-pipeline-status-cache-for-_project-after-disabling-pipelines.yml new file mode 100644 index 00000000000..7586d7995b7 --- /dev/null +++ b/changelogs/unreleased/52780-stale-pipeline-status-cache-for-_project-after-disabling-pipelines.yml @@ -0,0 +1,5 @@ +--- +title: Cache pipeline status per SHA. +merge_request: 22589 +author: +type: fixed diff --git a/changelogs/unreleased/rz_fix_milestone_count.yml b/changelogs/unreleased/rz_fix_milestone_count.yml new file mode 100644 index 00000000000..1013b88e0bc --- /dev/null +++ b/changelogs/unreleased/rz_fix_milestone_count.yml @@ -0,0 +1,5 @@ +--- +title: Fixing count on Milestones +merge_request: 21446 +author: +type: fixed diff --git a/config/routes.rb b/config/routes.rb index 8723a928cc3..37c7f98ec98 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -80,6 +80,7 @@ Rails.application.routes.draw do get 'ide' => 'ide#index' get 'ide/*vueroute' => 'ide#index', format: false + draw :operations draw :instance_statistics end diff --git a/doc/user/search/img/issues_filter_none_any.png b/doc/user/search/img/issues_filter_none_any.png Binary files differnew file mode 100644 index 00000000000..9682fc55315 --- /dev/null +++ b/doc/user/search/img/issues_filter_none_any.png diff --git a/doc/user/search/index.md b/doc/user/search/index.md index 4f1b96b775c..3f9d07dacaa 100644 --- a/doc/user/search/index.md +++ b/doc/user/search/index.md @@ -40,6 +40,16 @@ The same process is valid for merge requests. Navigate to your project's **Merge and click **Search or filter results...**. Merge requests can be filtered by author, assignee, milestone, and label. +### Filtering by **None** / **Any** + +Some filter fields like milestone and assignee, allow you to filter by **None** or **Any**. + +![filter by none any](img/issues_filter_none_any.png) + +Selecting **None** returns results that have an empty value for that field. E.g.: no milestone, no assignee. + +Selecting **Any** does the opposite. It returns results that have a non-empty value for that field. + ### Searching for specific terms You can filter issues and merge requests by specific terms included in titles or descriptions. diff --git a/lib/gitlab/cache/ci/project_pipeline_status.rb b/lib/gitlab/cache/ci/project_pipeline_status.rb index b369b9e7600..dfbb83f7bb9 100644 --- a/lib/gitlab/cache/ci/project_pipeline_status.rb +++ b/lib/gitlab/cache/ci/project_pipeline_status.rb @@ -42,7 +42,7 @@ module Gitlab end def self.cache_key_for_project(project) - "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}:projects/#{project.id}/pipeline_status" + "#{Gitlab::Redis::Cache::CACHE_NAMESPACE}:projects/#{project.id}/pipeline_status/#{project.commit&.sha}" end def self.update_for_pipeline(pipeline) @@ -84,9 +84,7 @@ module Gitlab def load_from_project return unless commit - self.sha = commit.sha - self.status = commit.status - self.ref = project.default_branch + self.sha, self.status, self.ref = commit.sha, commit.status, project.default_branch end # We only cache the status for the HEAD commit of a project @@ -104,6 +102,8 @@ module Gitlab def load_from_cache Gitlab::Redis::Cache.with do |redis| self.sha, self.status, self.ref = redis.hmget(cache_key, :sha, :status, :ref) + + self.status = nil if self.status.empty? end end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 26270595c6a..463a58fff7c 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -4030,9 +4030,6 @@ msgstr "" msgid "No" msgstr "" -msgid "No Assignee" -msgstr "" - msgid "No Label" msgstr "" diff --git a/spec/controllers/dashboard/milestones_controller_spec.rb b/spec/controllers/dashboard/milestones_controller_spec.rb index 56047c0c8d2..278b980b6d8 100644 --- a/spec/controllers/dashboard/milestones_controller_spec.rb +++ b/spec/controllers/dashboard/milestones_controller_spec.rb @@ -45,6 +45,8 @@ describe Dashboard::MilestonesController do end describe "#index" do + render_views + it 'returns group and project milestones to which the user belongs' do get :index, format: :json @@ -53,5 +55,12 @@ describe Dashboard::MilestonesController do expect(json_response.map { |i| i["first_milestone"]["id"] }).to match_array([group_milestone.id, project_milestone.id]) expect(json_response.map { |i| i["group_name"] }.compact).to match_array(group.name) end + + it 'should contain group and project milestones to which the user belongs to' do + get :index + + expect(response.body).to include("Open\n<span class=\"badge badge-pill\">3</span>") + expect(response.body).to include("Closed\n<span class=\"badge badge-pill\">0</span>") + end end end diff --git a/spec/features/boards/modal_filter_spec.rb b/spec/features/boards/modal_filter_spec.rb index 615223a2a88..2cdd3f55b50 100644 --- a/spec/features/boards/modal_filter_spec.rb +++ b/spec/features/boards/modal_filter_spec.rb @@ -106,7 +106,7 @@ describe 'Issue Boards add issue modal filtering', :js do it 'filters by unassigned' do set_filter('assignee') - click_filter_link('No Assignee') + click_filter_link('None') submit_filter page.within('.add-issues-modal') do diff --git a/spec/features/groups/milestone_spec.rb b/spec/features/groups/milestone_spec.rb index e8ca6a6714f..174840794ed 100644 --- a/spec/features/groups/milestone_spec.rb +++ b/spec/features/groups/milestone_spec.rb @@ -95,9 +95,9 @@ describe 'Group milestones' do end it 'counts milestones correctly' do - expect(find('.top-area .active .badge').text).to eq("2") - expect(find('.top-area .closed .badge').text).to eq("2") - expect(find('.top-area .all .badge').text).to eq("4") + expect(find('.top-area .active .badge').text).to eq("3") + expect(find('.top-area .closed .badge').text).to eq("3") + expect(find('.top-area .all .badge').text).to eq("6") end it 'lists legacy group milestones and group milestones' do diff --git a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb index d011d2545bb..e910fb54d23 100644 --- a/spec/features/issues/filtered_search/dropdown_assignee_spec.rb +++ b/spec/features/issues/filtered_search/dropdown_assignee_spec.rb @@ -156,13 +156,21 @@ describe 'Dropdown assignee', :js do expect_filtered_search_input_empty end - it 'selects `no assignee`' do - find('#js-dropdown-assignee .filter-dropdown-item', text: 'No Assignee').click + it 'selects `None`' do + find('#js-dropdown-assignee .filter-dropdown-item', text: 'None').click expect(page).to have_css(js_dropdown_assignee, visible: false) expect_tokens([assignee_token('none')]) expect_filtered_search_input_empty end + + it 'selects `Any`' do + find('#js-dropdown-assignee .filter-dropdown-item', text: 'Any').click + + expect(page).to have_css(js_dropdown_assignee, visible: false) + expect_tokens([assignee_token('any')]) + expect_filtered_search_input_empty + end end describe 'selecting from dropdown without Ajax call' do diff --git a/spec/features/issues/filtered_search/visual_tokens_spec.rb b/spec/features/issues/filtered_search/visual_tokens_spec.rb index 6ac7ccd00f7..1e1dd5691ab 100644 --- a/spec/features/issues/filtered_search/visual_tokens_spec.rb +++ b/spec/features/issues/filtered_search/visual_tokens_spec.rb @@ -118,7 +118,7 @@ describe 'Visual tokens', :js do describe 'selecting static option from dropdown' do before do - find("#js-dropdown-assignee").find('.filter-dropdown-item', text: 'No Assignee').click + find("#js-dropdown-assignee").find('.filter-dropdown-item', text: 'None').click end it 'changes value in visual token' do diff --git a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb index 4d5081b0a75..e5999a1c509 100644 --- a/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb +++ b/spec/lib/gitlab/cache/ci/project_pipeline_status_spec.rb @@ -282,6 +282,21 @@ describe Gitlab::Cache::Ci::ProjectPipelineStatus, :clean_gitlab_redis_cache do expect(pipeline_status.status).to eq(status) expect(pipeline_status.ref).to eq(ref) end + + context 'when status is empty string' do + before do + Gitlab::Redis::Cache.with do |redis| + redis.mapped_hmset(cache_key, + { sha: sha, status: '', ref: ref }) + end + end + + it 'reads the status as nil' do + pipeline_status.load_from_cache + + expect(pipeline_status.status).to eq(nil) + end + end end describe '#has_cache?' do diff --git a/spec/models/global_milestone_spec.rb b/spec/models/global_milestone_spec.rb index ab58f5c5021..b6355455c1d 100644 --- a/spec/models/global_milestone_spec.rb +++ b/spec/models/global_milestone_spec.rb @@ -92,41 +92,6 @@ describe GlobalMilestone do end end - describe '.states_count' do - context 'when the projects have milestones' do - before do - create(:closed_milestone, title: 'Active Group Milestone', project: project3) - create(:active_milestone, title: 'Active Group Milestone', project: project1) - create(:active_milestone, title: 'Active Group Milestone', project: project2) - create(:closed_milestone, title: 'Closed Group Milestone', project: project1) - create(:closed_milestone, title: 'Closed Group Milestone', project: project2) - create(:closed_milestone, title: 'Closed Group Milestone', project: project3) - end - - it 'returns the quantity of global milestones in each possible state' do - expected_count = { opened: 1, closed: 2, all: 2 } - - count = described_class.states_count(Project.all) - - expect(count).to eq(expected_count) - end - end - - context 'when the projects do not have milestones' do - before do - project1 - end - - it 'returns 0 as the quantity of global milestones in each state' do - expected_count = { opened: 0, closed: 0, all: 0 } - - count = described_class.states_count(Project.all) - - expect(count).to eq(expected_count) - end - end - end - describe '#initialize' do let(:milestone1_project1) { create(:milestone, title: "Milestone v1.2", project: project1) } let(:milestone1_project2) { create(:milestone, title: "Milestone v1.2", project: project2) } diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index 27d4e622710..d11eb46159e 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -348,4 +348,41 @@ describe Milestone do end end end + + describe '.states_count' do + context 'when the projects have milestones' do + before do + project_1 = create(:project) + project_2 = create(:project) + group_1 = create(:group) + group_2 = create(:group) + + create(:active_milestone, title: 'Active Group Milestone', project: project_1) + create(:closed_milestone, title: 'Closed Group Milestone', project: project_1) + create(:active_milestone, title: 'Active Group Milestone', project: project_2) + create(:closed_milestone, title: 'Closed Group Milestone', project: project_2) + create(:closed_milestone, title: 'Active Group Milestone', group: group_1) + create(:closed_milestone, title: 'Closed Group Milestone', group: group_1) + create(:closed_milestone, title: 'Active Group Milestone', group: group_2) + create(:closed_milestone, title: 'Closed Group Milestone', group: group_2) + end + + it 'returns the quantity of milestones in each possible state' do + expected_count = { opened: 5, closed: 6, all: 11 } + + count = described_class.states_count(Project.all, Group.all) + expect(count).to eq(expected_count) + end + end + + context 'when the projects do not have milestones' do + it 'returns 0 as the quantity of global milestones in each state' do + expected_count = { opened: 0, closed: 0, all: 0 } + + count = described_class.states_count([project]) + + expect(count).to eq(expected_count) + end + end + end end |