diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index 60165e0d0a4..0e9cdbf7d23 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -170,6 +170,14 @@ module MilestonesHelper content.join('
').html_safe end + def milestone_releases_tooltip_text(milestone) + count = milestone.releases.count + + return _("Releases") if count.zero? + + n_("%{releases} release", "%{releases} releases", count) % { releases: count } + end + def recent_releases_with_counts(milestone) total_count = milestone.releases.size return [[], 0, 0] if total_count == 0 diff --git a/app/views/shared/milestones/_sidebar.html.haml b/app/views/shared/milestones/_sidebar.html.haml index 22a6d5e33f0..b6656e6283c 100644 --- a/app/views/shared/milestones/_sidebar.html.haml +++ b/app/views/shared/milestones/_sidebar.html.haml @@ -138,6 +138,27 @@ Merged: = milestone.merge_requests.merged.count + - if project + - recent_releases, total_count, more_count = recent_releases_with_counts(milestone) + .block.releases + .sidebar-collapsed-icon.has-tooltip{ title: milestone_releases_tooltip_text(milestone), data: { container: 'body', placement: 'left', boundary: 'viewport' } } + %strong + = icon('rocket') + %span= total_count + .title.hide-collapsed= n_('Release', 'Releases', total_count) + .hide-collapsed + - if total_count.zero? + .no-value= _('None') + - else + .font-weight-bold + - recent_releases.each do |release| + = link_to release.name, project_releases_path(project, :anchor => release.tag) + - unless release == recent_releases.last + %span.font-weight-normal • + - if more_count > 0 + %span.font-weight-normal • + = link_to n_('%{count} more release', '%{count} more releases', more_count) % { count: more_count }, project_releases_path(project), class: 'font-weight-normal' + - milestone_ref = milestone.try(:to_reference, full: true) - if milestone_ref.present? .block.reference diff --git a/changelogs/unreleased/34431-add-report-type-vulnerabilities.yml b/changelogs/unreleased/34431-add-report-type-vulnerabilities.yml new file mode 100644 index 00000000000..36d21162d20 --- /dev/null +++ b/changelogs/unreleased/34431-add-report-type-vulnerabilities.yml @@ -0,0 +1,5 @@ +--- +title: Added report_type attribute to Vulnerabilities +merge_request: 19179 +author: +type: changed diff --git a/changelogs/unreleased/nfriend-add-release-links-to-milestone-detail-page.yml b/changelogs/unreleased/nfriend-add-release-links-to-milestone-detail-page.yml new file mode 100644 index 00000000000..e2d57b3a65f --- /dev/null +++ b/changelogs/unreleased/nfriend-add-release-links-to-milestone-detail-page.yml @@ -0,0 +1,5 @@ +--- +title: Add links to associated release(s) to the milestone detail page +merge_request: 17278 +author: +type: added diff --git a/db/migrate/20191105094558_add_report_type_to_vulnerabilities.rb b/db/migrate/20191105094558_add_report_type_to_vulnerabilities.rb new file mode 100644 index 00000000000..8fb657bf9e7 --- /dev/null +++ b/db/migrate/20191105094558_add_report_type_to_vulnerabilities.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddReportTypeToVulnerabilities < ActiveRecord::Migration[5.2] + DOWNTIME = false + + def change + add_column :vulnerabilities, :report_type, :integer, limit: 2 + end +end diff --git a/db/post_migrate/20191105094625_set_report_type_for_vulnerabilities.rb b/db/post_migrate/20191105094625_set_report_type_for_vulnerabilities.rb new file mode 100644 index 00000000000..6b7a158584d --- /dev/null +++ b/db/post_migrate/20191105094625_set_report_type_for_vulnerabilities.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +class SetReportTypeForVulnerabilities < ActiveRecord::Migration[5.2] + DOWNTIME = false + + def up + # set report_type based on associated vulnerability_occurrences + execute <<~SQL + UPDATE vulnerabilities + SET report_type = vulnerability_occurrences.report_type + FROM vulnerability_occurrences + WHERE vulnerabilities.id = vulnerability_occurrences.vulnerability_id + SQL + + # set default report_type for orphan vulnerabilities (there should be none but...) + execute 'UPDATE vulnerabilities SET report_type = 0 WHERE report_type IS NULL' + + change_column_null :vulnerabilities, :report_type, false + end + + def down + change_column_null :vulnerabilities, :report_type, true + + execute 'UPDATE vulnerabilities SET report_type = NULL' + end +end diff --git a/db/schema.rb b/db/schema.rb index 066ae22cbd7..220e57527a5 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 2019_10_29_191901) do +ActiveRecord::Schema.define(version: 2019_11_05_094625) do # These are extensions that must be enabled in order to support this database enable_extension "pg_trgm" @@ -3915,6 +3915,7 @@ ActiveRecord::Schema.define(version: 2019_10_29_191901) do t.boolean "severity_overridden", default: false t.integer "confidence", limit: 2, null: false t.boolean "confidence_overridden", default: false + t.integer "report_type", limit: 2, null: false t.index ["author_id"], name: "index_vulnerabilities_on_author_id" t.index ["closed_by_id"], name: "index_vulnerabilities_on_closed_by_id" t.index ["due_date_sourcing_milestone_id"], name: "index_vulnerabilities_on_due_date_sourcing_milestone_id" diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 023432c1b9c..bc5744db4af 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -324,6 +324,11 @@ msgstr "" msgid "%{percent}%% complete" msgstr "" +msgid "%{releases} release" +msgid_plural "%{releases} releases" +msgstr[0] "" +msgstr[1] "" + msgid "%{service_title} activated." msgstr "" diff --git a/qa/qa/resource/user.rb b/qa/qa/resource/user.rb index 095233b54f0..57663afeef5 100644 --- a/qa/qa/resource/user.rb +++ b/qa/qa/resource/user.rb @@ -17,6 +17,10 @@ module QA @unique_id = SecureRandom.hex(8) end + def admin? + api_resource&.dig(:is_admin) || false + end + def username @username || "qa-user-#{unique_id}" end diff --git a/qa/qa/runtime/feature.rb b/qa/qa/runtime/feature.rb index b74f343ba7b..75cb9eded55 100644 --- a/qa/qa/runtime/feature.rb +++ b/qa/qa/runtime/feature.rb @@ -7,6 +7,7 @@ module QA extend Support::Api SetFeatureError = Class.new(RuntimeError) + AuthorizationError = Class.new(RuntimeError) def enable(key) QA::Runtime::Logger.info("Enabling feature: #{key}") @@ -26,7 +27,22 @@ module QA private def api_client - @api_client ||= Runtime::API::Client.new(:gitlab) + @api_client ||= begin + if Runtime::Env.admin_personal_access_token + Runtime::API::Client.new(:gitlab, personal_access_token: Runtime::Env.admin_personal_access_token) + else + user = Resource::User.fabricate_via_api! do |user| + user.username = Runtime::User.admin_username + user.password = Runtime::User.admin_password + end + + unless user.admin? + raise AuthorizationError, "Administrator access is required to enable/disable feature flags. User '#{user.username}' is not an administrator." + end + + Runtime::API::Client.new(:gitlab, user: user) + end + end end def set_feature(key, value) diff --git a/spec/features/projects/milestones/milestone_spec.rb b/spec/features/projects/milestones/milestone_spec.rb index 5e94b2f721e..4b3cd7db1ed 100644 --- a/spec/features/projects/milestones/milestone_spec.rb +++ b/spec/features/projects/milestones/milestone_spec.rb @@ -7,6 +7,18 @@ describe 'Project milestone' do let(:project) { create(:project, name: 'test', namespace: user.namespace) } let(:milestone) { create(:milestone, project: project) } + def toggle_sidebar + find('.milestone-sidebar .gutter-toggle').click + end + + def sidebar_release_block + find('.milestone-sidebar .block.releases') + end + + def sidebar_release_block_collapsed_icon + find('.milestone-sidebar .block.releases .sidebar-collapsed-icon') + end + before do sign_in(user) end @@ -75,17 +87,96 @@ describe 'Project milestone' do describe 'the collapsed sidebar' do before do - find('.milestone-sidebar .gutter-toggle').click + toggle_sidebar end it 'shows the total MR and issue counts' do find('.milestone-sidebar .block', match: :first) aggregate_failures 'MR and issue blocks' do - expect(find('.milestone-sidebar .block.issues')).to have_content 1 - expect(find('.milestone-sidebar .block.merge-requests')).to have_content 0 + expect(find('.milestone-sidebar .block.issues')).to have_content '1' + expect(find('.milestone-sidebar .block.merge-requests')).to have_content '0' end end end end + + context 'when the milestone is not associated with a release' do + before do + visit project_milestone_path(project, milestone) + end + + it 'shows "None" in the "Releases" section' do + expect(sidebar_release_block).to have_content 'Releases None' + end + + describe 'when the sidebar is collapsed' do + before do + toggle_sidebar + end + + it 'shows "0" in the "Releases" section' do + expect(sidebar_release_block).to have_content '0' + end + + it 'has a tooltip that reads "Releases"' do + expect(sidebar_release_block_collapsed_icon['title']).to eq 'Releases' + end + end + end + + context 'when the milestone is associated with one release' do + before do + create(:release, project: project, name: 'Version 5', milestones: [milestone]) + + visit project_milestone_path(project, milestone) + end + + it 'shows "Version 5" in the "Release" section' do + expect(sidebar_release_block).to have_content 'Release Version 5' + end + + describe 'when the sidebar is collapsed' do + before do + toggle_sidebar + end + + it 'shows "1" in the "Releases" section' do + expect(sidebar_release_block).to have_content '1' + end + + it 'has a tooltip that reads "1 release"' do + expect(sidebar_release_block_collapsed_icon['title']).to eq '1 release' + end + end + end + + context 'when the milestone is associated with multiple releases' do + before do + (5..10).each do |num| + released_at = Time.zone.parse('2019-10-04') + num.months + create(:release, project: project, name: "Version #{num}", milestones: [milestone], released_at: released_at) + end + + visit project_milestone_path(project, milestone) + end + + it 'shows a shortened list of releases in the "Releases" section' do + expect(sidebar_release_block).to have_content 'Releases Version 10 • Version 9 • Version 8 • 3 more releases' + end + + describe 'when the sidebar is collapsed' do + before do + toggle_sidebar + end + + it 'shows "6" in the "Releases" section' do + expect(sidebar_release_block).to have_content '6' + end + + it 'has a tooltip that reads "6 releases"' do + expect(sidebar_release_block_collapsed_icon['title']).to eq '6 releases' + end + end + end end diff --git a/spec/lib/gitlab/project_authorizations_spec.rb b/spec/lib/gitlab/project_authorizations_spec.rb index 006daa29ea1..6e5c36172e2 100644 --- a/spec/lib/gitlab/project_authorizations_spec.rb +++ b/spec/lib/gitlab/project_authorizations_spec.rb @@ -77,17 +77,17 @@ describe Gitlab::ProjectAuthorizations do let(:group_user) { create(:user) } let(:child_group_user) { create(:user) } - set(:group_parent) { create(:group, :private) } - set(:group) { create(:group, :private, parent: group_parent) } - set(:group_child) { create(:group, :private, parent: group) } + let_it_be(:group_parent) { create(:group, :private) } + let_it_be(:group) { create(:group, :private, parent: group_parent) } + let_it_be(:group_child) { create(:group, :private, parent: group) } - set(:shared_group_parent) { create(:group, :private) } - set(:shared_group) { create(:group, :private, parent: shared_group_parent) } - set(:shared_group_child) { create(:group, :private, parent: shared_group) } + let_it_be(:shared_group_parent) { create(:group, :private) } + let_it_be(:shared_group) { create(:group, :private, parent: shared_group_parent) } + let_it_be(:shared_group_child) { create(:group, :private, parent: shared_group) } - set(:project_parent) { create(:project, group: shared_group_parent) } - set(:project) { create(:project, group: shared_group) } - set(:project_child) { create(:project, group: shared_group_child) } + let_it_be(:project_parent) { create(:project, group: shared_group_parent) } + let_it_be(:project) { create(:project, group: shared_group) } + let_it_be(:project_child) { create(:project, group: shared_group_child) } before do group_parent.add_owner(parent_group_user) diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 9a89ffb1b2d..3fa9d71cc7d 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -531,13 +531,13 @@ describe Group do let(:group_user) { create(:user) } let(:child_group_user) { create(:user) } - set(:group_parent) { create(:group, :private) } - set(:group) { create(:group, :private, parent: group_parent) } - set(:group_child) { create(:group, :private, parent: group) } + let_it_be(:group_parent) { create(:group, :private) } + let_it_be(:group) { create(:group, :private, parent: group_parent) } + let_it_be(:group_child) { create(:group, :private, parent: group) } - set(:shared_group_parent) { create(:group, :private) } - set(:shared_group) { create(:group, :private, parent: shared_group_parent) } - set(:shared_group_child) { create(:group, :private, parent: shared_group) } + let_it_be(:shared_group_parent) { create(:group, :private) } + let_it_be(:shared_group) { create(:group, :private, parent: shared_group_parent) } + let_it_be(:shared_group_child) { create(:group, :private, parent: shared_group) } before do group_parent.add_owner(parent_group_user) diff --git a/spec/services/groups/group_links/create_service_spec.rb b/spec/services/groups/group_links/create_service_spec.rb index ca005536e0d..36faa69577e 100644 --- a/spec/services/groups/group_links/create_service_spec.rb +++ b/spec/services/groups/group_links/create_service_spec.rb @@ -7,17 +7,17 @@ describe Groups::GroupLinks::CreateService, '#execute' do let(:group_user) { create(:user) } let(:child_group_user) { create(:user) } - set(:group_parent) { create(:group, :private) } - set(:group) { create(:group, :private, parent: group_parent) } - set(:group_child) { create(:group, :private, parent: group) } + let_it_be(:group_parent) { create(:group, :private) } + let_it_be(:group) { create(:group, :private, parent: group_parent) } + let_it_be(:group_child) { create(:group, :private, parent: group) } - set(:shared_group_parent) { create(:group, :private) } - set(:shared_group) { create(:group, :private, parent: shared_group_parent) } - set(:shared_group_child) { create(:group, :private, parent: shared_group) } + let_it_be(:shared_group_parent) { create(:group, :private) } + let_it_be(:shared_group) { create(:group, :private, parent: shared_group_parent) } + let_it_be(:shared_group_child) { create(:group, :private, parent: shared_group) } - set(:project_parent) { create(:project, group: shared_group_parent) } - set(:project) { create(:project, group: shared_group) } - set(:project_child) { create(:project, group: shared_group_child) } + let_it_be(:project_parent) { create(:project, group: shared_group_parent) } + let_it_be(:project) { create(:project, group: shared_group) } + let_it_be(:project_child) { create(:project, group: shared_group_child) } let(:opts) do { diff --git a/spec/support/helpers/test_env.rb b/spec/support/helpers/test_env.rb index 538380a625c..6a23875f103 100644 --- a/spec/support/helpers/test_env.rb +++ b/spec/support/helpers/test_env.rb @@ -148,8 +148,6 @@ module TestEnv end def setup_gitaly - socket_path = Gitlab::GitalyClient.address('default').sub(/\Aunix:/, '') - gitaly_dir = File.dirname(socket_path) install_gitaly_args = [gitaly_dir, repos_path, gitaly_url].compact.join(',') component_timed_setup('Gitaly', @@ -162,8 +160,16 @@ module TestEnv end end + def gitaly_socket_path + Gitlab::GitalyClient.address('default').sub(/\Aunix:/, '') + end + + def gitaly_dir + File.dirname(gitaly_socket_path) + end + def start_gitaly(gitaly_dir) - if ENV['CI'].present? + if ci? # Gitaly has been spawned outside this process already return end @@ -172,8 +178,13 @@ module TestEnv spawn_script = Rails.root.join('scripts/gitaly-test-spawn').to_s Bundler.with_original_env do - raise "gitaly spawn failed" unless system(spawn_script) + unless system(spawn_script) + message = 'gitaly spawn failed' + message += " (try `rm -rf #{gitaly_dir}` ?)" unless ci? + raise message + end end + @gitaly_pid = Integer(File.read('tmp/tests/gitaly.pid')) Kernel.at_exit { stop_gitaly } @@ -386,7 +397,7 @@ module TestEnv ensure_component_dir_name_is_correct!(component, install_dir) # On CI, once installed, components never need update - return if File.exist?(install_dir) && ENV['CI'] + return if File.exist?(install_dir) && ci? if component_needs_update?(install_dir, version) # Cleanup the component entirely to ensure we start fresh @@ -407,6 +418,10 @@ module TestEnv puts " #{component} set up in #{Time.now - start} seconds...\n" end + def ci? + ENV['CI'].present? + end + def ensure_component_dir_name_is_correct!(component, path) actual_component_dir_name = File.basename(path) expected_component_dir_name = component.parameterize