From 693e435f2fb8be8f60f73ee69fcb8f5297522a82 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 6 Sep 2018 05:13:44 +0100 Subject: port EE --- .../clusters/components/gcp_signup_offer.js | 25 ++--------------- app/assets/javascripts/persistent_user_callout.js | 32 ++++++++++++++++++++++ app/models/user_callout.rb | 3 +- app/views/dashboard/activity.html.haml | 3 ++ app/views/dashboard/groups/index.html.haml | 3 ++ app/views/dashboard/issues.html.haml | 3 ++ app/views/dashboard/merge_requests.html.haml | 3 ++ app/views/dashboard/projects/index.html.haml | 3 ++ app/views/dashboard/projects/starred.html.haml | 3 ++ app/views/dashboard/todos/index.html.haml | 3 ++ app/views/layouts/nav/_breadcrumbs.html.haml | 1 + .../clusters/_gcp_signup_offer_banner.html.haml | 4 +-- db/schema.rb | 12 ++++---- 13 files changed, 67 insertions(+), 31 deletions(-) create mode 100644 app/assets/javascripts/persistent_user_callout.js diff --git a/app/assets/javascripts/clusters/components/gcp_signup_offer.js b/app/assets/javascripts/clusters/components/gcp_signup_offer.js index 8bc20a1c09f..38663bd4898 100644 --- a/app/assets/javascripts/clusters/components/gcp_signup_offer.js +++ b/app/assets/javascripts/clusters/components/gcp_signup_offer.js @@ -1,27 +1,8 @@ -import $ from 'jquery'; -import axios from '~/lib/utils/axios_utils'; -import { __ } from '~/locale'; -import Flash from '~/flash'; +import PersistentUserCallout from '../../persistent_user_callout'; export default function gcpSignupOffer() { const alertEl = document.querySelector('.gcp-signup-offer'); - if (!alertEl) { - return; - } + if (!alertEl) return; - const closeButtonEl = alertEl.getElementsByClassName('close')[0]; - const { dismissEndpoint, featureId } = closeButtonEl.dataset; - - closeButtonEl.addEventListener('click', () => { - axios - .post(dismissEndpoint, { - feature_name: featureId, - }) - .then(() => { - $(alertEl).alert('close'); - }) - .catch(() => { - Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.')); - }); - }); + new PersistentUserCallout(alertEl).init(); } diff --git a/app/assets/javascripts/persistent_user_callout.js b/app/assets/javascripts/persistent_user_callout.js new file mode 100644 index 00000000000..d136b3bfee9 --- /dev/null +++ b/app/assets/javascripts/persistent_user_callout.js @@ -0,0 +1,32 @@ +import axios from './lib/utils/axios_utils'; +import { __ } from './locale'; +import Flash from './flash'; + +export default class PersistentUserCallout { + constructor(container) { + const { dismissEndpoint, featureId } = container.dataset; + this.container = container; + this.dismissEndpoint = dismissEndpoint; + this.featureId = featureId; + } + + init() { + const closeButton = this.container.querySelector('.js-close'); + closeButton.addEventListener('click', event => this.dismiss(event)); + } + + dismiss(event) { + event.preventDefault(); + + axios + .post(this.dismissEndpoint, { + feature_name: this.featureId, + }) + .then(() => { + this.container.remove(); + }) + .catch(() => { + Flash(__('An error occurred while dismissing the alert. Refresh the page and try again.')); + }); + } +} diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb index 97e955ace36..15a2155b0c5 100644 --- a/app/models/user_callout.rb +++ b/app/models/user_callout.rb @@ -5,7 +5,8 @@ class UserCallout < ActiveRecord::Base enum feature_name: { gke_cluster_integration: 1, - gcp_signup_offer: 2 + gcp_signup_offer: 2, + gold_trial: 3 } validates :user, presence: true diff --git a/app/views/dashboard/activity.html.haml b/app/views/dashboard/activity.html.haml index 31d4b3da4f1..3cee5841bbc 100644 --- a/app/views/dashboard/activity.html.haml +++ b/app/views/dashboard/activity.html.haml @@ -4,6 +4,9 @@ = content_for :meta_tags do = auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity") += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + - page_title "Activity" - header_title "Activity", activity_dashboard_path diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml index 50f39f93283..985928305a2 100644 --- a/app/views/dashboard/groups/index.html.haml +++ b/app/views/dashboard/groups/index.html.haml @@ -3,6 +3,9 @@ - header_title "Groups", dashboard_groups_path = render 'dashboard/groups_head' += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + - if params[:filter].blank? && @groups.empty? = render 'shared/groups/empty_state' - else diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index 86a21e24ac9..91f58ddcfcc 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -4,6 +4,9 @@ = content_for :meta_tags do = auto_discovery_link_tag(:atom, safe_params.merge(rss_url_options).to_h, title: "#{current_user.name} issues") += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + .top-area = render 'shared/issuable/nav', type: :issues, display_count: !@no_filters_set .nav-controls diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index 61aae31be60..27f53a8d1c6 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -2,6 +2,9 @@ - page_title _("Merge Requests") - @breadcrumb_link = merge_requests_dashboard_path(assignee_id: current_user.id) += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + .top-area = render 'shared/issuable/nav', type: :merge_requests, display_count: !@no_filters_set .nav-controls diff --git a/app/views/dashboard/projects/index.html.haml b/app/views/dashboard/projects/index.html.haml index deed774a4a5..f0d16936a51 100644 --- a/app/views/dashboard/projects/index.html.haml +++ b/app/views/dashboard/projects/index.html.haml @@ -4,6 +4,9 @@ = content_for :meta_tags do = auto_discovery_link_tag(:atom, dashboard_projects_url(rss_url_options), title: "All activity") += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + - page_title "Projects" - header_title "Projects", dashboard_projects_path diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml index 8933d9e31ff..42638b8528d 100644 --- a/app/views/dashboard/projects/starred.html.haml +++ b/app/views/dashboard/projects/starred.html.haml @@ -4,6 +4,9 @@ - page_title "Starred Projects" - header_title "Projects", dashboard_projects_path += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + %div{ class: container_class } = render "projects/last_push" = render 'dashboard/projects_head' diff --git a/app/views/dashboard/todos/index.html.haml b/app/views/dashboard/todos/index.html.haml index 8b3974d97f8..bbfa4cc7413 100644 --- a/app/views/dashboard/todos/index.html.haml +++ b/app/views/dashboard/todos/index.html.haml @@ -2,6 +2,9 @@ - page_title "Todos" - header_title "Todos", dashboard_todos_path += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + - if current_user.todos.any? .top-area %ul.nav-links.mobile-separator.nav.nav-tabs diff --git a/app/views/layouts/nav/_breadcrumbs.html.haml b/app/views/layouts/nav/_breadcrumbs.html.haml index f53bd2b5e4d..c35451827c8 100644 --- a/app/views/layouts/nav/_breadcrumbs.html.haml +++ b/app/views/layouts/nav/_breadcrumbs.html.haml @@ -1,6 +1,7 @@ - container = @no_breadcrumb_container ? 'container-fluid' : container_class - hide_top_links = @hide_top_links || false += yield :above_breadcrumbs_content %nav.breadcrumbs{ role: "navigation", class: [container, @content_class] } .breadcrumbs-container - if defined?(@left_sidebar) diff --git a/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml b/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml index 73b11d509d3..6e53c747a33 100644 --- a/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml +++ b/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml @@ -1,6 +1,6 @@ - link = link_to(s_('ClusterIntegration|sign up'), 'https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer') -.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.append-bottom-default{ role: 'alert' } - %button.close{ type: "button", data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } } × +.bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.append-bottom-default{ role: 'alert', data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } } + %button.close{ type: "button" } × .gcp-signup-offer--content .gcp-signup-offer--icon.append-right-8 = sprite_icon("information", size: 16) diff --git a/db/schema.rb b/db/schema.rb index f48df68b785..eb91fbaecee 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -164,14 +164,14 @@ ActiveRecord::Schema.define(version: 20180826111825) do t.boolean "authorized_keys_enabled", default: true, null: false t.string "auto_devops_domain" t.boolean "pages_domain_verification_enabled", default: true, null: false - t.string "user_default_internal_regex" t.boolean "allow_local_requests_from_hooks_and_services", default: false, null: false t.boolean "enforce_terms", default: false t.boolean "mirror_available", default: true, null: false t.boolean "hide_third_party_offers", default: false, null: false - t.boolean "instance_statistics_visibility_private", default: false, null: false + t.boolean "instance_statistics_visibility_private", default: true, null: false t.boolean "web_ide_clientside_preview_enabled", default: false, null: false t.boolean "user_show_add_ssh_key_message", default: true, null: false + t.string "user_default_internal_regex" end create_table "audit_events", force: :cascade do |t| @@ -330,10 +330,10 @@ ActiveRecord::Schema.define(version: 20180826111825) do t.integer "auto_canceled_by_id" t.boolean "retried" t.integer "stage_id" - t.integer "artifacts_file_store" - t.integer "artifacts_metadata_store" t.boolean "protected" t.integer "failure_reason" + t.integer "artifacts_file_store" + t.integer "artifacts_metadata_store" end add_index "ci_builds", ["artifacts_expire_at"], name: "index_ci_builds_on_artifacts_expire_at", where: "(artifacts_file <> ''::text)", using: :btree @@ -390,13 +390,13 @@ ActiveRecord::Schema.define(version: 20180826111825) do t.integer "project_id", null: false t.integer "job_id", null: false t.integer "file_type", null: false - t.integer "file_store" t.integer "size", limit: 8 t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "updated_at", null: false t.datetime_with_timezone "expire_at" t.string "file" t.binary "file_sha256" + t.integer "file_store" t.integer "file_format", limit: 2 t.integer "file_location", limit: 2 end @@ -2401,7 +2401,7 @@ ActiveRecord::Schema.define(version: 20180826111825) do add_foreign_key "term_agreements", "users", on_delete: :cascade add_foreign_key "timelogs", "issues", name: "fk_timelogs_issues_issue_id", on_delete: :cascade add_foreign_key "timelogs", "merge_requests", name: "fk_timelogs_merge_requests_merge_request_id", on_delete: :cascade - add_foreign_key "todos", "namespaces", column: "group_id", on_delete: :cascade + add_foreign_key "todos", "namespaces", column: "group_id", name: "fk_a27c483435", on_delete: :cascade add_foreign_key "todos", "notes", name: "fk_91d1f47b13", on_delete: :cascade add_foreign_key "todos", "projects", name: "fk_45054f9c45", on_delete: :cascade add_foreign_key "todos", "users", column: "author_id", name: "fk_ccf0373936", on_delete: :cascade -- cgit v1.2.1 From 769808732d5b3542726a5839d6a22da62fd9abeb Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 6 Sep 2018 14:42:36 +0100 Subject: fix gcp signup offer close js class --- app/views/projects/clusters/_gcp_signup_offer_banner.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml b/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml index 6e53c747a33..85d1002243b 100644 --- a/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml +++ b/app/views/projects/clusters/_gcp_signup_offer_banner.html.haml @@ -1,6 +1,6 @@ - link = link_to(s_('ClusterIntegration|sign up'), 'https://console.cloud.google.com/freetrial?utm_campaign=2018_cpanel&utm_source=gitlab&utm_medium=referral', target: '_blank', rel: 'noopener noreferrer') .bs-callout.gcp-signup-offer.alert.alert-block.alert-dismissable.prepend-top-default.append-bottom-default{ role: 'alert', data: { feature_id: UserCalloutsHelper::GCP_SIGNUP_OFFER, dismiss_endpoint: user_callouts_path } } - %button.close{ type: "button" } × + %button.close.js-close{ type: "button" } × .gcp-signup-offer--content .gcp-signup-offer--icon.append-right-8 = sprite_icon("information", size: 16) -- cgit v1.2.1 From 4899dfcafe55ca2935d699f85c13fc35a8e16545 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eva=20Kadlecova=CC=81?= Date: Sun, 26 Aug 2018 17:57:51 +0200 Subject: Redirect to the pipeline builds page when a build is canceled --- app/controllers/projects/jobs_controller.rb | 8 ++- app/views/projects/ci/builds/_build.html.haml | 2 +- changelogs/unreleased/Fix-pipeline-redirect.yml | 5 ++ spec/controllers/projects/jobs_controller_spec.rb | 65 +++++++++++++++++------ 4 files changed, 62 insertions(+), 18 deletions(-) create mode 100644 changelogs/unreleased/Fix-pipeline-redirect.yml diff --git a/app/controllers/projects/jobs_controller.rb b/app/controllers/projects/jobs_controller.rb index e69faae754a..009d8afd523 100644 --- a/app/controllers/projects/jobs_controller.rb +++ b/app/controllers/projects/jobs_controller.rb @@ -1,5 +1,6 @@ class Projects::JobsController < Projects::ApplicationController include SendFileUpload + include ContinueParams before_action :build, except: [:index, :cancel_all] before_action :authorize_read_build! @@ -101,7 +102,12 @@ class Projects::JobsController < Projects::ApplicationController return respond_422 unless @build.cancelable? @build.cancel - redirect_to build_path(@build) + + if continue_params + redirect_to continue_params[:to] + else + redirect_to builds_project_pipeline_path(@project, @build.pipeline.id) + end end def status diff --git a/app/views/projects/ci/builds/_build.html.haml b/app/views/projects/ci/builds/_build.html.haml index 44c1453e239..44446f15ddc 100644 --- a/app/views/projects/ci/builds/_build.html.haml +++ b/app/views/projects/ci/builds/_build.html.haml @@ -99,7 +99,7 @@ = sprite_icon('download') - if can?(current_user, :update_build, job) - if job.active? - = link_to cancel_project_job_path(job.project, job, return_to: request.original_url), method: :post, title: 'Cancel', class: 'btn btn-build' do + = link_to cancel_project_job_path(job.project, job, continue: { to: request.fullpath }), method: :post, title: 'Cancel', class: 'btn btn-build' do = icon('remove', class: 'cred') - elsif allow_retry - if job.playable? && !admin && can?(current_user, :update_build, job) diff --git a/changelogs/unreleased/Fix-pipeline-redirect.yml b/changelogs/unreleased/Fix-pipeline-redirect.yml new file mode 100644 index 00000000000..459273c7740 --- /dev/null +++ b/changelogs/unreleased/Fix-pipeline-redirect.yml @@ -0,0 +1,5 @@ +--- +title: Redirect to the pipeline builds page when a build is canceled +merge_request: +author: Eva Kadlecova +type: fixed diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index 1aca44c6e74..44c4d3ac3ce 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -356,35 +356,68 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do before do project.add_developer(user) sign_in(user) - - post_cancel end - context 'when job is cancelable' do + context 'when continue url is present' do let(:job) { create(:ci_build, :cancelable, pipeline: pipeline) } - it 'redirects to the canceled job page' do - expect(response).to have_gitlab_http_status(:found) - expect(response).to redirect_to(namespace_project_job_path(id: job.id)) + context 'when continue to is a safe url' do + let(:url) { '/test' } + + before do + post_cancel(continue: { to: url }) + end + + it 'redirects to the continue url' do + expect(response).to have_gitlab_http_status(:found) + expect(response).to redirect_to(url) + end + + it 'transits to canceled' do + expect(job.reload).to be_canceled + end end - it 'transits to canceled' do - expect(job.reload).to be_canceled + context 'when continue to is not a safe url' do + let(:url) { 'http://example.com' } + + it 'raises an error' do + expect { cancel_with_redirect(url) }.to raise_error + end end end - context 'when job is not cancelable' do - let(:job) { create(:ci_build, :canceled, pipeline: pipeline) } + context 'when continue url is not present' do + before do + post_cancel + end - it 'returns unprocessable_entity' do - expect(response).to have_gitlab_http_status(:unprocessable_entity) + context 'when job is cancelable' do + let(:job) { create(:ci_build, :cancelable, pipeline: pipeline) } + + it 'redirects to the builds page' do + expect(response).to have_gitlab_http_status(:found) + expect(response).to redirect_to(builds_namespace_project_pipeline_path(id: pipeline.id)) + end + + it 'transits to canceled' do + expect(job.reload).to be_canceled + end + end + + context 'when job is not cancelable' do + let(:job) { create(:ci_build, :canceled, pipeline: pipeline) } + + it 'returns unprocessable_entity' do + expect(response).to have_gitlab_http_status(:unprocessable_entity) + end end end - def post_cancel - post :cancel, namespace_id: project.namespace, - project_id: project, - id: job.id + def post_cancel(additional_params = {}) + post :cancel, { namespace_id: project.namespace, + project_id: project, + id: job.id }.merge(additional_params) end end -- cgit v1.2.1 From 6c31b607de3528f78f340a2bc5bfdb163378981c Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Thu, 20 Sep 2018 21:47:34 +0500 Subject: Fix SpaceBeforeFirstArg cop --- .rubocop_todo.yml | 14 -------------- config/routes/project.rb | 2 +- ...517_add_foreign_key_pipeline_schedules_and_pipelines.rb | 2 +- lib/api/runners.rb | 2 +- spec/features/search/user_uses_search_filters_spec.rb | 4 ++-- spec/routing/project_routing_spec.rb | 8 ++++---- spec/services/system_note_service_spec.rb | 2 +- 7 files changed, 10 insertions(+), 24 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index d8c4e965190..c7b82af08df 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -53,20 +53,6 @@ Layout/IndentArray: Layout/IndentHash: Enabled: false -# Offense count: 11 -# Cop supports --auto-correct. -# Configuration parameters: AllowForAlignment. -Layout/SpaceBeforeFirstArg: - Exclude: - - 'config/routes/project.rb' - - 'db/migrate/20170506185517_add_foreign_key_pipeline_schedules_and_pipelines.rb' - - 'features/steps/project/source/browse_files.rb' - - 'features/steps/project/source/markdown_render.rb' - - 'lib/api/runners.rb' - - 'spec/features/search/user_uses_search_filters_spec.rb' - - 'spec/routing/project_routing_spec.rb' - - 'spec/services/system_note_service_spec.rb' - # Offense count: 93 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle. diff --git a/config/routes/project.rb b/config/routes/project.rb index 8a5310b5c23..6946f954019 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -365,7 +365,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do get :discussions, format: :json end collection do - post :bulk_update + post :bulk_update end end diff --git a/db/migrate/20170506185517_add_foreign_key_pipeline_schedules_and_pipelines.rb b/db/migrate/20170506185517_add_foreign_key_pipeline_schedules_and_pipelines.rb index 55bf40ba24d..cc5cb355579 100644 --- a/db/migrate/20170506185517_add_foreign_key_pipeline_schedules_and_pipelines.rb +++ b/db/migrate/20170506185517_add_foreign_key_pipeline_schedules_and_pipelines.rb @@ -13,7 +13,7 @@ class AddForeignKeyPipelineSchedulesAndPipelines < ActiveRecord::Migration 'SET NULL' end - add_concurrent_foreign_key :ci_pipelines, :ci_pipeline_schedules, + add_concurrent_foreign_key :ci_pipelines, :ci_pipeline_schedules, column: :pipeline_schedule_id, on_delete: on_delete end diff --git a/lib/api/runners.rb b/lib/api/runners.rb index 30abd0b63e9..15faf36ce0a 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -94,7 +94,7 @@ module API optional :status, type: String, desc: 'Status of the job', values: Ci::Build::AVAILABLE_STATUSES use :pagination end - get ':id/jobs' do + get ':id/jobs' do runner = get_runner(params[:id]) authenticate_list_runners_jobs!(runner) diff --git a/spec/features/search/user_uses_search_filters_spec.rb b/spec/features/search/user_uses_search_filters_spec.rb index 66afe163447..0725ff178ac 100644 --- a/spec/features/search/user_uses_search_filters_spec.rb +++ b/spec/features/search/user_uses_search_filters_spec.rb @@ -14,7 +14,7 @@ describe 'User uses search filters', :js do visit(search_path) end - context' when filtering by group' do + context 'when filtering by group' do it 'shows group projects' do find('.js-search-group-dropdown').click @@ -36,7 +36,7 @@ describe 'User uses search filters', :js do end end - context' when filtering by project' do + context 'when filtering by project' do it 'shows a project' do page.within('.project-filter') do find('.js-search-project-dropdown').click diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 5abc6d81958..56df8dddbc1 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -258,10 +258,10 @@ describe 'project routing' do end it 'to #logs_tree' do - expect(get('/gitlab/gitlabhq/refs/stable/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable') - expect(get('/gitlab/gitlabhq/refs/feature%2345/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45') - expect(get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45') - expect(get('/gitlab/gitlabhq/refs/feature@45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45') + expect(get('/gitlab/gitlabhq/refs/stable/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable') + expect(get('/gitlab/gitlabhq/refs/feature%2345/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45') + expect(get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45') + expect(get('/gitlab/gitlabhq/refs/feature@45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45') expect(get('/gitlab/gitlabhq/refs/stable/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable', path: 'foo/bar/baz') expect(get('/gitlab/gitlabhq/refs/feature%2345/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45', path: 'foo/bar/baz') expect(get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45', path: 'foo/bar/baz') diff --git a/spec/services/system_note_service_spec.rb b/spec/services/system_note_service_spec.rb index f4b7cb8c90a..a18126ee339 100644 --- a/spec/services/system_note_service_spec.rb +++ b/spec/services/system_note_service_spec.rb @@ -324,7 +324,7 @@ describe SystemNoteService do end it "posts the 'merge when pipeline succeeds' system note" do - expect(subject.note).to eq "canceled the automatic merge" + expect(subject.note).to eq "canceled the automatic merge" end end -- cgit v1.2.1 From 6c0907894f920c7681cff671542c69b914d4d03b Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Thu, 20 Sep 2018 21:49:26 +0500 Subject: Fix SpaceInsideArrayLiteralBrackets cop --- .rubocop_todo.yml | 9 --------- spec/lib/gitlab/import_export/relation_factory_spec.rb | 4 +--- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index c7b82af08df..7288dad3483 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -60,15 +60,6 @@ Layout/IndentHash: Layout/SpaceInLambdaLiteral: Enabled: false -# Offense count: 1 -# Cop supports --auto-correct. -# Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBrackets. -# SupportedStyles: space, no_space, compact -# SupportedStylesForEmptyBrackets: space, no_space -Layout/SpaceInsideArrayLiteralBrackets: - Exclude: - - 'spec/lib/gitlab/import_export/relation_factory_spec.rb' - # Offense count: 327 # Cop supports --auto-correct. # Configuration parameters: EnforcedStyle, EnforcedStyleForEmptyBraces, SpaceBeforeBlockParameters. diff --git a/spec/lib/gitlab/import_export/relation_factory_spec.rb b/spec/lib/gitlab/import_export/relation_factory_spec.rb index cf9e0f71910..a31f77484d8 100644 --- a/spec/lib/gitlab/import_export/relation_factory_spec.rb +++ b/spec/lib/gitlab/import_export/relation_factory_spec.rb @@ -191,9 +191,7 @@ describe Gitlab::ImportExport::RelationFactory do "author" => { "name" => "Administrator" }, - "events" => [ - - ] + "events" => [] } end -- cgit v1.2.1 From 67cdb1d1be14c893bf195a9f0bc8ba82e5fce91c Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Thu, 20 Sep 2018 21:54:14 +0500 Subject: Fix SpaceInsidePercentLiteralDelimiters cop --- .rubocop_todo.yml | 8 -------- lib/gitlab/git_access.rb | 4 ++-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 7288dad3483..77ebfd64b81 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -73,14 +73,6 @@ Layout/SpaceInsideBlockBraces: Layout/SpaceInsideParens: Enabled: false -# Offense count: 14 -# Cop supports --auto-correct. -Layout/SpaceInsidePercentLiteralDelimiters: - Exclude: - - 'lib/gitlab/git_access.rb' - - 'lib/gitlab/health_checks/fs_shards_check.rb' - - 'spec/lib/gitlab/health_checks/fs_shards_check_spec.rb' - # Offense count: 26 Lint/DuplicateMethods: Exclude: diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 30cd09a0ca7..240a0d7d1b8 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -24,8 +24,8 @@ module Gitlab cannot_push_to_read_only: "You can't push code to a read-only GitLab instance." }.freeze - DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }.freeze - PUSH_COMMANDS = %w{ git-receive-pack }.freeze + DOWNLOAD_COMMANDS = %w{git-upload-pack git-upload-archive}.freeze + PUSH_COMMANDS = %w{git-receive-pack}.freeze ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS attr_reader :actor, :project, :protocol, :authentication_abilities, :namespace_path, :project_path, :redirected_path, :auth_result_type, :changes -- cgit v1.2.1 From 87b85ef81cadacc753a5c538280d045a1bf35549 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Thu, 20 Sep 2018 22:02:36 +0500 Subject: Fix DynamicAttributeDefinedStatically cop --- .rubocop_todo.yml | 18 ------------------ spec/factories/broadcast_messages.rb | 12 ++++++------ spec/factories/ci/builds.rb | 6 +++--- spec/factories/ci/runners.rb | 2 +- spec/factories/clusters/applications/helm.rb | 2 +- spec/factories/clusters/platforms/kubernetes.rb | 3 +-- spec/factories/emails.rb | 2 +- spec/factories/gpg_keys.rb | 4 ++-- spec/factories/group_members.rb | 2 +- spec/factories/merge_requests.rb | 2 +- spec/factories/notes.rb | 2 +- spec/factories/oauth_access_grants.rb | 2 +- spec/factories/project_members.rb | 2 +- spec/factories/todos.rb | 2 +- spec/factories/uploads.rb | 10 +++++----- 15 files changed, 26 insertions(+), 45 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index 77ebfd64b81..b3b58c83269 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -10,24 +10,6 @@ Capybara/CurrentPathExpectation: Enabled: false -# Offense count: 23 -FactoryBot/DynamicAttributeDefinedStatically: - Exclude: - - 'spec/factories/broadcast_messages.rb' - - 'spec/factories/ci/builds.rb' - - 'spec/factories/ci/runners.rb' - - 'spec/factories/clusters/applications/helm.rb' - - 'spec/factories/clusters/platforms/kubernetes.rb' - - 'spec/factories/emails.rb' - - 'spec/factories/gpg_keys.rb' - - 'spec/factories/group_members.rb' - - 'spec/factories/merge_requests.rb' - - 'spec/factories/notes.rb' - - 'spec/factories/oauth_access_grants.rb' - - 'spec/factories/project_members.rb' - - 'spec/factories/todos.rb' - - 'spec/factories/uploads.rb' - # Offense count: 167 # Cop supports --auto-correct. Layout/EmptyLinesAroundArguments: diff --git a/spec/factories/broadcast_messages.rb b/spec/factories/broadcast_messages.rb index 9a65e7f8a3f..1a2be5e9552 100644 --- a/spec/factories/broadcast_messages.rb +++ b/spec/factories/broadcast_messages.rb @@ -1,17 +1,17 @@ FactoryBot.define do factory :broadcast_message do message "MyText" - starts_at 1.day.ago - ends_at 1.day.from_now + starts_at { 1.day.ago } + ends_at { 1.day.from_now } trait :expired do - starts_at 5.days.ago - ends_at 3.days.ago + starts_at { 5.days.ago } + ends_at { 3.days.ago } end trait :future do - starts_at 5.days.from_now - ends_at 6.days.from_now + starts_at { 5.days.from_now } + ends_at { 6.days.from_now } end end end diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 9813190925b..0baa4ecc4e0 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -159,12 +159,12 @@ FactoryBot.define do end trait :erased do - erased_at Time.now + erased_at { Time.now } erased_by factory: :user end trait :queued do - queued_at Time.now + queued_at { Time.now } runner factory: :ci_runner end @@ -194,7 +194,7 @@ FactoryBot.define do end trait :expired do - artifacts_expire_at 1.minute.ago + artifacts_expire_at { 1.minute.ago } end trait :with_commit do diff --git a/spec/factories/ci/runners.rb b/spec/factories/ci/runners.rb index 347e4f433e2..f564e7bee47 100644 --- a/spec/factories/ci/runners.rb +++ b/spec/factories/ci/runners.rb @@ -9,7 +9,7 @@ FactoryBot.define do runner_type :instance_type trait :online do - contacted_at Time.now + contacted_at { Time.now } end trait :instance do diff --git a/spec/factories/clusters/applications/helm.rb b/spec/factories/clusters/applications/helm.rb index c13b0249d94..d76d6a7334a 100644 --- a/spec/factories/clusters/applications/helm.rb +++ b/spec/factories/clusters/applications/helm.rb @@ -29,7 +29,7 @@ FactoryBot.define do trait :timeouted do installing - updated_at ClusterWaitForAppInstallationWorker::TIMEOUT.ago + updated_at { ClusterWaitForAppInstallationWorker::TIMEOUT.ago } end factory :clusters_applications_ingress, class: Clusters::Applications::Ingress do diff --git a/spec/factories/clusters/platforms/kubernetes.rb b/spec/factories/clusters/platforms/kubernetes.rb index 36ac2372204..4a0d1b181ea 100644 --- a/spec/factories/clusters/platforms/kubernetes.rb +++ b/spec/factories/clusters/platforms/kubernetes.rb @@ -3,11 +3,10 @@ FactoryBot.define do cluster namespace nil api_url 'https://kubernetes.example.com' - token 'a' * 40 + token { 'a' * 40 } trait :configured do api_url 'https://kubernetes.example.com' - token 'a' * 40 username 'xxxxxx' password 'xxxxxx' diff --git a/spec/factories/emails.rb b/spec/factories/emails.rb index d23ddf9d79b..feacd5ccf15 100644 --- a/spec/factories/emails.rb +++ b/spec/factories/emails.rb @@ -3,7 +3,7 @@ FactoryBot.define do user email { generate(:email_alias) } - trait(:confirmed) { confirmed_at Time.now } + trait(:confirmed) { confirmed_at { Time.now } } trait(:skip_validate) { to_create {|instance| instance.save(validate: false) } } end end diff --git a/spec/factories/gpg_keys.rb b/spec/factories/gpg_keys.rb index 51b8ddc9934..3c0f43cc1b6 100644 --- a/spec/factories/gpg_keys.rb +++ b/spec/factories/gpg_keys.rb @@ -2,11 +2,11 @@ require_relative '../support/helpers/gpg_helpers' FactoryBot.define do factory :gpg_key do - key GpgHelpers::User1.public_key + key { GpgHelpers::User1.public_key } user factory :gpg_key_with_subkeys do - key GpgHelpers::User1.public_key_with_extra_signing_key + key { GpgHelpers::User1.public_key_with_extra_signing_key } end end end diff --git a/spec/factories/group_members.rb b/spec/factories/group_members.rb index 47036560b9d..12be63e5d92 100644 --- a/spec/factories/group_members.rb +++ b/spec/factories/group_members.rb @@ -9,7 +9,7 @@ FactoryBot.define do trait(:developer) { access_level GroupMember::DEVELOPER } trait(:maintainer) { access_level GroupMember::MAINTAINER } trait(:owner) { access_level GroupMember::OWNER } - trait(:access_request) { requested_at Time.now } + trait(:access_request) { requested_at { Time.now } } trait(:invited) do user_id nil diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index b8b089b069b..8094c43b065 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -80,7 +80,7 @@ FactoryBot.define do trait :merge_when_pipeline_succeeds do merge_when_pipeline_succeeds true - merge_user author + merge_user { author } end trait :remove_source_branch do diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index 6844ed8aa4a..2d1f48bf249 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -90,7 +90,7 @@ FactoryBot.define do noteable nil noteable_type 'Commit' noteable_id nil - commit_id RepoHelpers.sample_commit.id + commit_id { RepoHelpers.sample_commit.id } end trait :legacy_diff_note do diff --git a/spec/factories/oauth_access_grants.rb b/spec/factories/oauth_access_grants.rb index 9e6af24c4eb..02c51cd9899 100644 --- a/spec/factories/oauth_access_grants.rb +++ b/spec/factories/oauth_access_grants.rb @@ -3,7 +3,7 @@ FactoryBot.define do resource_owner_id { create(:user).id } application token { Doorkeeper::OAuth::Helpers::UniqueToken.generate } - expires_in 2.hours + expires_in { 2.hours } redirect_uri { application.redirect_uri } scopes { application.scopes } diff --git a/spec/factories/project_members.rb b/spec/factories/project_members.rb index 22a8085ea45..c72e0487895 100644 --- a/spec/factories/project_members.rb +++ b/spec/factories/project_members.rb @@ -8,7 +8,7 @@ FactoryBot.define do trait(:reporter) { access_level ProjectMember::REPORTER } trait(:developer) { access_level ProjectMember::DEVELOPER } trait(:maintainer) { access_level ProjectMember::MAINTAINER } - trait(:access_request) { requested_at Time.now } + trait(:access_request) { requested_at { Time.now } } trait(:invited) do user_id nil diff --git a/spec/factories/todos.rb b/spec/factories/todos.rb index 14486c80341..ed3d87eb76b 100644 --- a/spec/factories/todos.rb +++ b/spec/factories/todos.rb @@ -49,7 +49,7 @@ FactoryBot.define do author user action { Todo::ASSIGNED } - commit_id RepoHelpers.sample_commit.id + commit_id { RepoHelpers.sample_commit.id } target_type "Commit" end end diff --git a/spec/factories/uploads.rb b/spec/factories/uploads.rb index 81c485fba1a..7256f785e1f 100644 --- a/spec/factories/uploads.rb +++ b/spec/factories/uploads.rb @@ -1,7 +1,7 @@ FactoryBot.define do factory :upload do model { build(:project) } - size 100.kilobytes + size { 100.kilobytes } uploader "AvatarUploader" mount_point :avatar secret nil @@ -19,13 +19,13 @@ FactoryBot.define do uploader "PersonalFileUploader" path { File.join(secret, filename) } model { build(:personal_snippet) } - secret SecureRandom.hex + secret { SecureRandom.hex } end trait :issuable_upload do uploader "FileUploader" path { File.join(secret, filename) } - secret SecureRandom.hex + secret { SecureRandom.hex } end trait :with_file do @@ -43,14 +43,14 @@ FactoryBot.define do model { build(:group) } path { File.join(secret, filename) } uploader "NamespaceFileUploader" - secret SecureRandom.hex + secret { SecureRandom.hex } end trait :favicon_upload do model { build(:appearance) } path { File.join(secret, filename) } uploader "FaviconUploader" - secret SecureRandom.hex + secret { SecureRandom.hex } end trait :attachment_upload do -- cgit v1.2.1 From b1a4c123f111cdc7bcb6e4be75505d8630cb292a Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Thu, 20 Sep 2018 22:08:07 +0500 Subject: Remove unused cops from todo --- .rubocop_todo.yml | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index b3b58c83269..a6bc4b57aab 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -86,23 +86,11 @@ Lint/InterpolationCheck: Lint/MissingCopEnableDirective: Enabled: false -# Offense count: 2 -Lint/NestedPercentLiteral: - Exclude: - - 'lib/gitlab/git/repository.rb' - - 'spec/support/shared_examples/email_format_shared_examples.rb' - # Offense count: 1 Lint/ReturnInVoidContext: Exclude: - 'app/models/project.rb' -# Offense count: 1 -# Configuration parameters: IgnoreImplicitReferences. -Lint/ShadowedArgument: - Exclude: - - 'lib/gitlab/database/sha_attribute.rb' - # Offense count: 3 # Cop supports --auto-correct. Lint/UnneededRequireStatement: @@ -150,11 +138,6 @@ Naming/HeredocDelimiterCase: Naming/HeredocDelimiterNaming: Enabled: false -# Offense count: 1 -Performance/UnfreezeString: - Exclude: - - 'features/steps/project/commits/commits.rb' - # Offense count: 1 # Cop supports --auto-correct. Performance/UriDefaultParser: -- cgit v1.2.1 From 2d0839e54295bd14cfbd2e7d18f46522324670b9 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Thu, 20 Sep 2018 22:19:45 +0500 Subject: Fix UnneededRequireStatement cop --- .rubocop_todo.yml | 8 -------- db/post_migrate/20161221153951_rename_reserved_project_names.rb | 2 -- .../20170313133418_rename_more_reserved_project_names.rb | 2 -- lib/declarative_policy.rb | 2 -- 4 files changed, 14 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index a6bc4b57aab..e18fbd1fc07 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -91,14 +91,6 @@ Lint/ReturnInVoidContext: Exclude: - 'app/models/project.rb' -# Offense count: 3 -# Cop supports --auto-correct. -Lint/UnneededRequireStatement: - Exclude: - - 'db/post_migrate/20161221153951_rename_reserved_project_names.rb' - - 'db/post_migrate/20170313133418_rename_more_reserved_project_names.rb' - - 'lib/declarative_policy.rb' - # Offense count: 9 Lint/UriEscapeUnescape: Exclude: diff --git a/db/post_migrate/20161221153951_rename_reserved_project_names.rb b/db/post_migrate/20161221153951_rename_reserved_project_names.rb index 017c58477ac..08d7f499eec 100644 --- a/db/post_migrate/20161221153951_rename_reserved_project_names.rb +++ b/db/post_migrate/20161221153951_rename_reserved_project_names.rb @@ -1,5 +1,3 @@ -require 'thread' - class RenameReservedProjectNames < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers include Gitlab::ShellAdapter diff --git a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb index 3e8ccfdb899..43a37667250 100644 --- a/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb +++ b/db/post_migrate/20170313133418_rename_more_reserved_project_names.rb @@ -1,5 +1,3 @@ -require 'thread' - class RenameMoreReservedProjectNames < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers include Gitlab::ShellAdapter diff --git a/lib/declarative_policy.rb b/lib/declarative_policy.rb index dda6cd38dcd..10d34b0c6e7 100644 --- a/lib/declarative_policy.rb +++ b/lib/declarative_policy.rb @@ -10,8 +10,6 @@ require_dependency 'declarative_policy/step' require_dependency 'declarative_policy/base' -require 'thread' - module DeclarativePolicy CLASS_CACHE_MUTEX = Mutex.new CLASS_CACHE_IVAR = :@__DeclarativePolicy_CLASS_CACHE -- cgit v1.2.1 From 869d8e814e4fcc4809dada1e2b34d4993461bda5 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Fri, 21 Sep 2018 11:55:02 +0500 Subject: Fix UriDefaultParser cop --- .rubocop_todo.yml | 5 ----- lib/gitlab/url_sanitizer.rb | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml index e18fbd1fc07..a654c5bb631 100644 --- a/.rubocop_todo.yml +++ b/.rubocop_todo.yml @@ -130,11 +130,6 @@ Naming/HeredocDelimiterCase: Naming/HeredocDelimiterNaming: Enabled: false -# Offense count: 1 -# Cop supports --auto-correct. -Performance/UriDefaultParser: - Exclude: - - 'lib/gitlab/url_sanitizer.rb' # Offense count: 3821 # Configuration parameters: Prefixes. diff --git a/lib/gitlab/url_sanitizer.rb b/lib/gitlab/url_sanitizer.rb index 308a95d2f09..29672d68cad 100644 --- a/lib/gitlab/url_sanitizer.rb +++ b/lib/gitlab/url_sanitizer.rb @@ -3,7 +3,7 @@ module Gitlab ALLOWED_SCHEMES = %w[http https ssh git].freeze def self.sanitize(content) - regexp = URI::Parser.new.make_regexp(ALLOWED_SCHEMES) + regexp = URI::DEFAULT_PARSER.make_regexp(ALLOWED_SCHEMES) content.gsub(regexp) { |url| new(url).masked_url } rescue Addressable::URI::InvalidURIError -- cgit v1.2.1 From f8e74da72115bc8e9908385da9c46ae0e100e8e5 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 13 Sep 2018 14:04:43 +0100 Subject: Review changes --- .../clusters/components/gcp_signup_offer.js | 2 +- app/assets/javascripts/pages/root/index.js | 1 + app/assets/javascripts/persistent_user_callout.js | 2 + app/helpers/dashboard_helper.rb | 23 +++ app/views/explore/groups/index.html.haml | 3 + app/views/explore/projects/index.html.haml | 3 + app/views/explore/projects/starred.html.haml | 3 + app/views/explore/projects/trending.html.haml | 3 + ee/app/assets/stylesheets/pages/promotions.scss | 193 +++++++++++++++++++++ ee/app/views/shared/_gold_trial_callout.html.haml | 16 ++ 10 files changed, 248 insertions(+), 1 deletion(-) create mode 100644 app/assets/javascripts/pages/root/index.js create mode 100644 ee/app/assets/stylesheets/pages/promotions.scss create mode 100644 ee/app/views/shared/_gold_trial_callout.html.haml diff --git a/app/assets/javascripts/clusters/components/gcp_signup_offer.js b/app/assets/javascripts/clusters/components/gcp_signup_offer.js index 38663bd4898..04b778c6be9 100644 --- a/app/assets/javascripts/clusters/components/gcp_signup_offer.js +++ b/app/assets/javascripts/clusters/components/gcp_signup_offer.js @@ -4,5 +4,5 @@ export default function gcpSignupOffer() { const alertEl = document.querySelector('.gcp-signup-offer'); if (!alertEl) return; - new PersistentUserCallout(alertEl).init(); + new PersistentUserCallout(alertEl); } diff --git a/app/assets/javascripts/pages/root/index.js b/app/assets/javascripts/pages/root/index.js new file mode 100644 index 00000000000..bb3e672a000 --- /dev/null +++ b/app/assets/javascripts/pages/root/index.js @@ -0,0 +1 @@ +import '../dashboard/projects/index'; diff --git a/app/assets/javascripts/persistent_user_callout.js b/app/assets/javascripts/persistent_user_callout.js index d136b3bfee9..1e34e74a152 100644 --- a/app/assets/javascripts/persistent_user_callout.js +++ b/app/assets/javascripts/persistent_user_callout.js @@ -8,6 +8,8 @@ export default class PersistentUserCallout { this.container = container; this.dismissEndpoint = dismissEndpoint; this.featureId = featureId; + + this.init(); } init() { diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb index 19aa55a8d49..7a0e2135b94 100644 --- a/app/helpers/dashboard_helper.rb +++ b/app/helpers/dashboard_helper.rb @@ -19,6 +19,29 @@ module DashboardHelper links.any? { |link| dashboard_nav_link?(link) } end + def controller_action_to_child_dashboards(controller = controller_name, action = action_name) + case "#{controller}##{action}" + when 'projects#index', 'root#index', 'projects#starred', 'projects#trending' + ['projects', 'stars'] + when 'dashboard#activity' + ['starred_project_activity', 'project_activity'] + when 'groups#index' + ['groups'] + when 'todos#index' + ['todos'] + when 'dashboard#issues' + ['issues'] + when 'dashboard#merge_requests' + ['merge_requests'] + else + [] + end + end + + def is_default_dashboard?(user = current_user) + controller_action_to_child_dashboards.any? {|dashboard| dashboard == user.dashboard } + end + private def get_dashboard_nav_links diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index 387c37b7a91..1d8b9c5bc8f 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -2,6 +2,9 @@ - page_title _("Groups") - header_title _("Groups"), dashboard_groups_path += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + - if current_user = render 'dashboard/groups_head' - else diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index 452f390695c..16be5791f83 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -2,6 +2,9 @@ - page_title _("Projects") - header_title _("Projects"), dashboard_projects_path += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + - if current_user = render 'dashboard/projects_head' - else diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml index 452f390695c..16be5791f83 100644 --- a/app/views/explore/projects/starred.html.haml +++ b/app/views/explore/projects/starred.html.haml @@ -2,6 +2,9 @@ - page_title _("Projects") - header_title _("Projects"), dashboard_projects_path += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + - if current_user = render 'dashboard/projects_head' - else diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml index 452f390695c..16be5791f83 100644 --- a/app/views/explore/projects/trending.html.haml +++ b/app/views/explore/projects/trending.html.haml @@ -2,6 +2,9 @@ - page_title _("Projects") - header_title _("Projects"), dashboard_projects_path += content_for :above_breadcrumbs_content do + = render_if_exists "shared/gold_trial_callout" + - if current_user = render 'dashboard/projects_head' - else diff --git a/ee/app/assets/stylesheets/pages/promotions.scss b/ee/app/assets/stylesheets/pages/promotions.scss new file mode 100644 index 00000000000..7b04164ca1d --- /dev/null +++ b/ee/app/assets/stylesheets/pages/promotions.scss @@ -0,0 +1,193 @@ +.user-callout.promotion-callout { + margin: 20px 0 0; + + &.prepend-top-10 { + margin-top: 10px; + } + + &.append-bottom-20 { + margin-bottom: 20px; + } + + .user-callout-copy { + max-width: 700px; + margin-left: auto; + margin-right: auto; + } + + .bordered-box { + padding: 20px; + border-color: $border-color; + background-color: $white-light; + align-items: flex-start; + + .close { + .dismiss-icon { + color: $gray-darkest; + } + + &:hover { + .dismiss-icon { + color: $text-color; + } + } + } + + .svg-container { + margin-right: 15px; + } + } + + &.promotion-empty-page { + margin-top: 56px; + } + + &.promotion-advanced-search { + margin: 0; + border-bottom: solid 1px $border-color; + + h5 { + margin-top: 0; + } + + .bordered-box.content-block { + border: 0; + padding: 20px 0; + justify-content: left; + + svg { + height: auto; + } + } + + .user-callout-copy { + margin-left: 0; + } + } + + .promotion-burndown-charts-content { + justify-content: end; + + .svg-container { + margin-left: 4px; + margin-right: 24px; + } + + .user-callout-copy { + margin: 0; + } + } + + &.thin-callout { + .bordered-box { + padding: $gl-padding; + padding-left: 40px; + + .close { + position: relative; + top: 0; + right: 0; + } + } + + .svg-container .svg { + max-width: 39px; + max-height: 39px; + } + } +} + +.promotion-modal { + .modal-dialog { + width: 540px; + } + + .modal-header { + border-bottom: 0; + } + + .modal-body { + margin-top: -20px; + padding: 16px 16px 32px; + } + + .modal-footer { + border-top: 0; + } +} + +.promotion-backdrop { + background-color: $white-transparent; + position: absolute; + padding-top: 72px; + + .user-callout-copy { + max-width: 700px; + } +} + +.promotion-issue-sidebar { + .promotion-issue-sidebar-message { + padding: $gl-padding-top; + + .dropdown-title { + margin: 0 0 10px; + } + + .btn + .btn { + margin-top: $gl-padding-4; + } + } + + .btn { + padding: $gl-vert-padding $gl-btn-padding; + border-radius: $border-radius-default; + display: block; + line-height: $line-height-base; + } + + .right-sidebar & .btn-link { + display: inline; + color: $blue-600; + background-color: transparent; + padding: 0; + + &:hover { + background-color: transparent; + color: $blue-800; + } + } +} + +.promotion-issue-template-message { + padding: $gl-padding 30px 20px $gl-padding; + + p { + margin-right: 20px; + } + + .dropdown-title { + margin: 0 -25px; + padding: 0; + overflow: initial; + border-bottom: 0; + } + + .btn { + padding: $gl-vert-padding $gl-btn-padding; + border-radius: $border-radius-default; + display: inline-block; + line-height: $line-height-base; + } + + .btn-link { + display: inline; + color: $blue-600; + padding: 0; + + &:hover { + background-color: transparent; + color: $blue-800; + } + } +} diff --git a/ee/app/views/shared/_gold_trial_callout.html.haml b/ee/app/views/shared/_gold_trial_callout.html.haml new file mode 100644 index 00000000000..300ff5634c3 --- /dev/null +++ b/ee/app/views/shared/_gold_trial_callout.html.haml @@ -0,0 +1,16 @@ +- if show_gold_trial? && is_default_dashboard? + .pt-1.d-none.d-md-block{ class: container_class } + .user-callout.promotion-callout.thin-callout.js-gold-trial-callout{ data: { uid: 'trial_callout_dismissed', feature_id: UserCalloutsHelper::GOLD_TRIAL, dismiss_endpoint: user_callouts_path } } + .bordered-box.justify-content-left.align-items-center + .svg-container + = image_tag 'illustrations/golden_tanuki.svg', class: 'svg' + .d-flex.flex-grow.align-items-center + .user-callout-copy.ml-0 + %h5.mb-0.mt-0= _('Free Trial of GitLab.com Gold') + %p.mb-0 + %span= _('Try all GitLab has to offer for 30 days.') + %span.d-none.d-sm-inline= _('No credit card required.') + = link_to _('Start your trial'), 'https://customers.gitlab.com/trials/new?gl_com=true', class: 'btn btn-primary mr-5 mt-2 mt-sm-0', target: '_blank' + %button.btn.btn-default.close.js-close{ type: 'button', + 'aria-label' => _('Dismiss trial promotion') } + = icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true') -- cgit v1.2.1 From 1ea69589160fbc7e7281d2a4fd6126a7a9673731 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 13 Sep 2018 14:59:38 +0100 Subject: Update user_has_namespace_with_gold? to check for any plan instead of just gold plan --- ee/app/helpers/ee/users_helper.rb | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 ee/app/helpers/ee/users_helper.rb diff --git a/ee/app/helpers/ee/users_helper.rb b/ee/app/helpers/ee/users_helper.rb new file mode 100644 index 00000000000..2d6f6cb4a0d --- /dev/null +++ b/ee/app/helpers/ee/users_helper.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module EE + module UsersHelper + def users_sentence(users, link_class: nil) + users.map { |user| link_to(user.name, user, class: link_class) }.to_sentence.html_safe + end + + def user_namespace_union(user = current_user, select = :id) + ::Gitlab::SQL::Union.new([ + ::Namespace.select(select).where(type: nil, owner: user), + user.owned_groups.select(select).where(parent_id: nil) + ]).to_sql + end + + def user_has_namespace_with_trial?(user = current_user) + ::Namespace + .from("(#{user_namespace_union(user, :trial_ends_on)}) #{::Namespace.table_name}") + .where('trial_ends_on > ?', Time.now.utc) + .any? + end + + def user_has_namespace_with_gold?(user = current_user) + ::Namespace + .includes(:plan) + .where("namespaces.id IN (#{user_namespace_union(user)})") # rubocop:disable GitlabSecurity/SqlInjection + .where.not(plans: { id: nil }) + .any? + end + end +end -- cgit v1.2.1 From 76bc9eefbbbfd697e6ae1c6bef35524e2eca5783 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 13 Sep 2018 15:18:59 +0100 Subject: Fix dismissable_callout import --- app/assets/javascripts/dismissable_callout.js | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 app/assets/javascripts/dismissable_callout.js diff --git a/app/assets/javascripts/dismissable_callout.js b/app/assets/javascripts/dismissable_callout.js new file mode 100644 index 00000000000..94f456bb3fc --- /dev/null +++ b/app/assets/javascripts/dismissable_callout.js @@ -0,0 +1,10 @@ +import PersistentUserCallout from './persistent_user_callout'; + +export default function initDismissableCallout(alertSelector) { + const alertEl = document.querySelector(alertSelector); + if (!alertEl) { + return; + } + + new PersistentUserCallout(alertEl); // eslint-disable-line no-new +} -- cgit v1.2.1 From 8c4399e530e8560321ab805bce426c99fb104d84 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 13 Sep 2018 16:01:33 +0100 Subject: Lint --- app/helpers/dashboard_helper.rb | 12 ++++++------ app/models/user_callout.rb | 3 ++- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb index 7a0e2135b94..267ccbd8c5d 100644 --- a/app/helpers/dashboard_helper.rb +++ b/app/helpers/dashboard_helper.rb @@ -22,17 +22,17 @@ module DashboardHelper def controller_action_to_child_dashboards(controller = controller_name, action = action_name) case "#{controller}##{action}" when 'projects#index', 'root#index', 'projects#starred', 'projects#trending' - ['projects', 'stars'] + %w(projects, 'stars) when 'dashboard#activity' - ['starred_project_activity', 'project_activity'] + %w(starred_project_activity, 'project_activity) when 'groups#index' - ['groups'] + %w(groups) when 'todos#index' - ['todos'] + %w(todos) when 'dashboard#issues' - ['issues'] + %w(issues) when 'dashboard#merge_requests' - ['merge_requests'] + %w(merge_requests) else [] end diff --git a/app/models/user_callout.rb b/app/models/user_callout.rb index 15a2155b0c5..2c0e8659fc1 100644 --- a/app/models/user_callout.rb +++ b/app/models/user_callout.rb @@ -6,7 +6,8 @@ class UserCallout < ActiveRecord::Base enum feature_name: { gke_cluster_integration: 1, gcp_signup_offer: 2, - gold_trial: 3 + cluster_security_warning: 3, + gold_trial: 4 } validates :user, presence: true -- cgit v1.2.1 From aee0abc893f82adba40a432d25e6441b1641ea18 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 14 Sep 2018 02:31:29 +0100 Subject: Use initDismissableCallout for trial callout --- ee/app/assets/javascripts/pages/dashboard/index.js | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 ee/app/assets/javascripts/pages/dashboard/index.js diff --git a/ee/app/assets/javascripts/pages/dashboard/index.js b/ee/app/assets/javascripts/pages/dashboard/index.js new file mode 100644 index 00000000000..46ef461fcf7 --- /dev/null +++ b/ee/app/assets/javascripts/pages/dashboard/index.js @@ -0,0 +1,3 @@ +import initDismissableCallout from '~/dismissable_callout'; + +document.addEventListener('DOMContentLoaded', () => initDismissableCallout('.js-gold-trial-callout')); -- cgit v1.2.1 From 2ab3e57cc98cc48c921b2c7c529acc1c75063b3a Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 14 Sep 2018 02:35:01 +0100 Subject: Update clusters/_banner --- app/views/projects/clusters/_banner.html.haml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/views/projects/clusters/_banner.html.haml b/app/views/projects/clusters/_banner.html.haml index f18caa3f4ac..84362580a90 100644 --- a/app/views/projects/clusters/_banner.html.haml +++ b/app/views/projects/clusters/_banner.html.haml @@ -8,7 +8,8 @@ .hidden.js-cluster-creating.alert.alert-info.alert-block.append-bottom-10{ role: 'alert' } = s_('ClusterIntegration|Kubernetes cluster is being created on Google Kubernetes Engine...') - .hidden.js-cluster-success.alert.alert-success.alert-block.append-bottom-10{ role: 'alert' } - = s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details") - - %p= s_('ClusterIntegration|Control how your Kubernetes cluster integrates with GitLab') +- if show_cluster_security_warning? + .js-cluster-security-warning.alert.alert-block.alert-dismissable.bs-callout.bs-callout-warning + %button.close.js-close{ type: "button", data: { feature_id: UserCalloutsHelper::CLUSTER_SECURITY_WARNING, dismiss_endpoint: user_callouts_path } } × + = s_("ClusterIntegration|The default cluster configuration grants access to many functionalities needed to successfully build and deploy a containerised application.") + = link_to s_("More information"), help_page_path('user/project/clusters/index.md', anchor: 'security-implications') -- cgit v1.2.1 From c58a4ea81e6969594050bc7c0d6b548fd7112e16 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Fri, 14 Sep 2018 03:09:32 +0100 Subject: Lint --- app/helpers/dashboard_helper.rb | 6 +++--- ee/app/views/shared/_gold_trial_callout.html.haml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb index 267ccbd8c5d..31551fba862 100644 --- a/app/helpers/dashboard_helper.rb +++ b/app/helpers/dashboard_helper.rb @@ -22,9 +22,9 @@ module DashboardHelper def controller_action_to_child_dashboards(controller = controller_name, action = action_name) case "#{controller}##{action}" when 'projects#index', 'root#index', 'projects#starred', 'projects#trending' - %w(projects, 'stars) + %w(projects stars) when 'dashboard#activity' - %w(starred_project_activity, 'project_activity) + %w(starred_project_activity project_activity) when 'groups#index' %w(groups) when 'todos#index' @@ -38,7 +38,7 @@ module DashboardHelper end end - def is_default_dashboard?(user = current_user) + def user_default_dashboard?(user = current_user) controller_action_to_child_dashboards.any? {|dashboard| dashboard == user.dashboard } end diff --git a/ee/app/views/shared/_gold_trial_callout.html.haml b/ee/app/views/shared/_gold_trial_callout.html.haml index 300ff5634c3..d8e1adaf796 100644 --- a/ee/app/views/shared/_gold_trial_callout.html.haml +++ b/ee/app/views/shared/_gold_trial_callout.html.haml @@ -1,4 +1,4 @@ -- if show_gold_trial? && is_default_dashboard? +- if show_gold_trial? && user_default_dashboard? .pt-1.d-none.d-md-block{ class: container_class } .user-callout.promotion-callout.thin-callout.js-gold-trial-callout{ data: { uid: 'trial_callout_dismissed', feature_id: UserCalloutsHelper::GOLD_TRIAL, dismiss_endpoint: user_callouts_path } } .bordered-box.justify-content-left.align-items-center -- cgit v1.2.1 From 086549d986a453e1b2dd0d09ffbd19d0487d9c51 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 24 Sep 2018 12:03:06 +0100 Subject: fix cherry picking --- db/schema.rb | 11 +- ee/app/assets/javascripts/pages/dashboard/index.js | 3 - ee/app/assets/stylesheets/pages/promotions.scss | 193 --------------------- ee/app/helpers/ee/users_helper.rb | 31 ---- ee/app/views/shared/_gold_trial_callout.html.haml | 16 -- 5 files changed, 6 insertions(+), 248 deletions(-) delete mode 100644 ee/app/assets/javascripts/pages/dashboard/index.js delete mode 100644 ee/app/assets/stylesheets/pages/promotions.scss delete mode 100644 ee/app/helpers/ee/users_helper.rb delete mode 100644 ee/app/views/shared/_gold_trial_callout.html.haml diff --git a/db/schema.rb b/db/schema.rb index eb91fbaecee..4631d31297f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -164,11 +164,12 @@ ActiveRecord::Schema.define(version: 20180826111825) do t.boolean "authorized_keys_enabled", default: true, null: false t.string "auto_devops_domain" t.boolean "pages_domain_verification_enabled", default: true, null: false + t.string "user_default_internal_regex" t.boolean "allow_local_requests_from_hooks_and_services", default: false, null: false t.boolean "enforce_terms", default: false t.boolean "mirror_available", default: true, null: false t.boolean "hide_third_party_offers", default: false, null: false - t.boolean "instance_statistics_visibility_private", default: true, null: false + t.boolean "instance_statistics_visibility_private", default: false, null: false t.boolean "web_ide_clientside_preview_enabled", default: false, null: false t.boolean "user_show_add_ssh_key_message", default: true, null: false t.string "user_default_internal_regex" @@ -330,10 +331,10 @@ ActiveRecord::Schema.define(version: 20180826111825) do t.integer "auto_canceled_by_id" t.boolean "retried" t.integer "stage_id" - t.boolean "protected" - t.integer "failure_reason" t.integer "artifacts_file_store" t.integer "artifacts_metadata_store" + t.boolean "protected" + t.integer "failure_reason" end add_index "ci_builds", ["artifacts_expire_at"], name: "index_ci_builds_on_artifacts_expire_at", where: "(artifacts_file <> ''::text)", using: :btree @@ -390,13 +391,13 @@ ActiveRecord::Schema.define(version: 20180826111825) do t.integer "project_id", null: false t.integer "job_id", null: false t.integer "file_type", null: false + t.integer "file_store" t.integer "size", limit: 8 t.datetime_with_timezone "created_at", null: false t.datetime_with_timezone "updated_at", null: false t.datetime_with_timezone "expire_at" t.string "file" t.binary "file_sha256" - t.integer "file_store" t.integer "file_format", limit: 2 t.integer "file_location", limit: 2 end @@ -2401,7 +2402,7 @@ ActiveRecord::Schema.define(version: 20180826111825) do add_foreign_key "term_agreements", "users", on_delete: :cascade add_foreign_key "timelogs", "issues", name: "fk_timelogs_issues_issue_id", on_delete: :cascade add_foreign_key "timelogs", "merge_requests", name: "fk_timelogs_merge_requests_merge_request_id", on_delete: :cascade - add_foreign_key "todos", "namespaces", column: "group_id", name: "fk_a27c483435", on_delete: :cascade + add_foreign_key "todos", "namespaces", column: "group_id", on_delete: :cascade add_foreign_key "todos", "notes", name: "fk_91d1f47b13", on_delete: :cascade add_foreign_key "todos", "projects", name: "fk_45054f9c45", on_delete: :cascade add_foreign_key "todos", "users", column: "author_id", name: "fk_ccf0373936", on_delete: :cascade diff --git a/ee/app/assets/javascripts/pages/dashboard/index.js b/ee/app/assets/javascripts/pages/dashboard/index.js deleted file mode 100644 index 46ef461fcf7..00000000000 --- a/ee/app/assets/javascripts/pages/dashboard/index.js +++ /dev/null @@ -1,3 +0,0 @@ -import initDismissableCallout from '~/dismissable_callout'; - -document.addEventListener('DOMContentLoaded', () => initDismissableCallout('.js-gold-trial-callout')); diff --git a/ee/app/assets/stylesheets/pages/promotions.scss b/ee/app/assets/stylesheets/pages/promotions.scss deleted file mode 100644 index 7b04164ca1d..00000000000 --- a/ee/app/assets/stylesheets/pages/promotions.scss +++ /dev/null @@ -1,193 +0,0 @@ -.user-callout.promotion-callout { - margin: 20px 0 0; - - &.prepend-top-10 { - margin-top: 10px; - } - - &.append-bottom-20 { - margin-bottom: 20px; - } - - .user-callout-copy { - max-width: 700px; - margin-left: auto; - margin-right: auto; - } - - .bordered-box { - padding: 20px; - border-color: $border-color; - background-color: $white-light; - align-items: flex-start; - - .close { - .dismiss-icon { - color: $gray-darkest; - } - - &:hover { - .dismiss-icon { - color: $text-color; - } - } - } - - .svg-container { - margin-right: 15px; - } - } - - &.promotion-empty-page { - margin-top: 56px; - } - - &.promotion-advanced-search { - margin: 0; - border-bottom: solid 1px $border-color; - - h5 { - margin-top: 0; - } - - .bordered-box.content-block { - border: 0; - padding: 20px 0; - justify-content: left; - - svg { - height: auto; - } - } - - .user-callout-copy { - margin-left: 0; - } - } - - .promotion-burndown-charts-content { - justify-content: end; - - .svg-container { - margin-left: 4px; - margin-right: 24px; - } - - .user-callout-copy { - margin: 0; - } - } - - &.thin-callout { - .bordered-box { - padding: $gl-padding; - padding-left: 40px; - - .close { - position: relative; - top: 0; - right: 0; - } - } - - .svg-container .svg { - max-width: 39px; - max-height: 39px; - } - } -} - -.promotion-modal { - .modal-dialog { - width: 540px; - } - - .modal-header { - border-bottom: 0; - } - - .modal-body { - margin-top: -20px; - padding: 16px 16px 32px; - } - - .modal-footer { - border-top: 0; - } -} - -.promotion-backdrop { - background-color: $white-transparent; - position: absolute; - padding-top: 72px; - - .user-callout-copy { - max-width: 700px; - } -} - -.promotion-issue-sidebar { - .promotion-issue-sidebar-message { - padding: $gl-padding-top; - - .dropdown-title { - margin: 0 0 10px; - } - - .btn + .btn { - margin-top: $gl-padding-4; - } - } - - .btn { - padding: $gl-vert-padding $gl-btn-padding; - border-radius: $border-radius-default; - display: block; - line-height: $line-height-base; - } - - .right-sidebar & .btn-link { - display: inline; - color: $blue-600; - background-color: transparent; - padding: 0; - - &:hover { - background-color: transparent; - color: $blue-800; - } - } -} - -.promotion-issue-template-message { - padding: $gl-padding 30px 20px $gl-padding; - - p { - margin-right: 20px; - } - - .dropdown-title { - margin: 0 -25px; - padding: 0; - overflow: initial; - border-bottom: 0; - } - - .btn { - padding: $gl-vert-padding $gl-btn-padding; - border-radius: $border-radius-default; - display: inline-block; - line-height: $line-height-base; - } - - .btn-link { - display: inline; - color: $blue-600; - padding: 0; - - &:hover { - background-color: transparent; - color: $blue-800; - } - } -} diff --git a/ee/app/helpers/ee/users_helper.rb b/ee/app/helpers/ee/users_helper.rb deleted file mode 100644 index 2d6f6cb4a0d..00000000000 --- a/ee/app/helpers/ee/users_helper.rb +++ /dev/null @@ -1,31 +0,0 @@ -# frozen_string_literal: true - -module EE - module UsersHelper - def users_sentence(users, link_class: nil) - users.map { |user| link_to(user.name, user, class: link_class) }.to_sentence.html_safe - end - - def user_namespace_union(user = current_user, select = :id) - ::Gitlab::SQL::Union.new([ - ::Namespace.select(select).where(type: nil, owner: user), - user.owned_groups.select(select).where(parent_id: nil) - ]).to_sql - end - - def user_has_namespace_with_trial?(user = current_user) - ::Namespace - .from("(#{user_namespace_union(user, :trial_ends_on)}) #{::Namespace.table_name}") - .where('trial_ends_on > ?', Time.now.utc) - .any? - end - - def user_has_namespace_with_gold?(user = current_user) - ::Namespace - .includes(:plan) - .where("namespaces.id IN (#{user_namespace_union(user)})") # rubocop:disable GitlabSecurity/SqlInjection - .where.not(plans: { id: nil }) - .any? - end - end -end diff --git a/ee/app/views/shared/_gold_trial_callout.html.haml b/ee/app/views/shared/_gold_trial_callout.html.haml deleted file mode 100644 index d8e1adaf796..00000000000 --- a/ee/app/views/shared/_gold_trial_callout.html.haml +++ /dev/null @@ -1,16 +0,0 @@ -- if show_gold_trial? && user_default_dashboard? - .pt-1.d-none.d-md-block{ class: container_class } - .user-callout.promotion-callout.thin-callout.js-gold-trial-callout{ data: { uid: 'trial_callout_dismissed', feature_id: UserCalloutsHelper::GOLD_TRIAL, dismiss_endpoint: user_callouts_path } } - .bordered-box.justify-content-left.align-items-center - .svg-container - = image_tag 'illustrations/golden_tanuki.svg', class: 'svg' - .d-flex.flex-grow.align-items-center - .user-callout-copy.ml-0 - %h5.mb-0.mt-0= _('Free Trial of GitLab.com Gold') - %p.mb-0 - %span= _('Try all GitLab has to offer for 30 days.') - %span.d-none.d-sm-inline= _('No credit card required.') - = link_to _('Start your trial'), 'https://customers.gitlab.com/trials/new?gl_com=true', class: 'btn btn-primary mr-5 mt-2 mt-sm-0', target: '_blank' - %button.btn.btn-default.close.js-close{ type: 'button', - 'aria-label' => _('Dismiss trial promotion') } - = icon('times', class: 'dismiss-icon', 'aria-hidden' => 'true') -- cgit v1.2.1 From 4f3da02887be8f55b3369d250fc630c80ff85f93 Mon Sep 17 00:00:00 2001 From: Steve Azzopardi Date: Mon, 24 Sep 2018 14:45:39 +0200 Subject: Add note for docker executor The docker executor does not work the same as the others. Add a note for the users and points them to an issue that describes the issue. --- doc/ci/interactive_web_terminal/index.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/doc/ci/interactive_web_terminal/index.md b/doc/ci/interactive_web_terminal/index.md index 8ce4fe55cec..1a409479aeb 100644 --- a/doc/ci/interactive_web_terminal/index.md +++ b/doc/ci/interactive_web_terminal/index.md @@ -25,6 +25,12 @@ Two things need to be configured for the interactive web terminal to work: NOTE: **Note:** Not all executors are [supported](https://docs.gitlab.com/runner/executors/#compatibility-chart). +NOTE: **Note:** The `docker` executor does not keep running +after the build script is finished. At that point, the terminal will automatically +disconnect and will not wait for the user to finish. Please follow [this +issue](https://gitlab.com/gitlab-org/gitlab-runner/issues/3605) for updates on +improving this behavior. + Sometimes, when a job is running, things don't go as you would expect, and it would be helpful if one can have a shell to aid debugging. When a job is running, on the right panel you can see a button `debug` that will open the terminal -- cgit v1.2.1 From ba2ad0f0458d0793b3367a429dd9054ba2d137c8 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Mon, 24 Sep 2018 14:59:30 +0100 Subject: fix persistent_user_callout import --- app/assets/javascripts/dismissable_callout.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/dismissable_callout.js b/app/assets/javascripts/dismissable_callout.js index 27a3742f667..8dcd8e9a47e 100644 --- a/app/assets/javascripts/dismissable_callout.js +++ b/app/assets/javascripts/dismissable_callout.js @@ -1,4 +1,4 @@ -import PersistentUserCallout from '../../persistent_user_callout'; +import PersistentUserCallout from '.persistent_user_callout'; export default function initDismissableCallout(alertSelector) { const alertEl = document.querySelector(alertSelector); -- cgit v1.2.1 From 9f5a7e2d612ed49c97d19a71a78d2dfe88fcc312 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 25 Sep 2018 15:17:34 +0000 Subject: Update dismissable_callout.js to correct missing import path slash --- app/assets/javascripts/dismissable_callout.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/dismissable_callout.js b/app/assets/javascripts/dismissable_callout.js index 8dcd8e9a47e..30430e93e8b 100644 --- a/app/assets/javascripts/dismissable_callout.js +++ b/app/assets/javascripts/dismissable_callout.js @@ -1,4 +1,4 @@ -import PersistentUserCallout from '.persistent_user_callout'; +import PersistentUserCallout from './persistent_user_callout'; export default function initDismissableCallout(alertSelector) { const alertEl = document.querySelector(alertSelector); -- cgit v1.2.1 From 45613341689c81789d3da44f23a46135249969e3 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Tue, 25 Sep 2018 17:24:05 +0100 Subject: Remove dismissable_callout as it has become an unneeded wrapper for persistent_user_callout --- app/assets/javascripts/clusters/clusters_bundle.js | 10 ++++++++-- app/assets/javascripts/clusters/clusters_index.js | 5 +++-- app/assets/javascripts/dismissable_callout.js | 10 ---------- app/assets/javascripts/pages/projects/index.js | 6 ++++-- 4 files changed, 15 insertions(+), 16 deletions(-) delete mode 100644 app/assets/javascripts/dismissable_callout.js diff --git a/app/assets/javascripts/clusters/clusters_bundle.js b/app/assets/javascripts/clusters/clusters_bundle.js index ebf76af5966..65e7cee7039 100644 --- a/app/assets/javascripts/clusters/clusters_bundle.js +++ b/app/assets/javascripts/clusters/clusters_bundle.js @@ -1,6 +1,6 @@ import Visibility from 'visibilityjs'; import Vue from 'vue'; -import initDismissableCallout from '~/dismissable_callout'; +import PersistentUserCallout from '../persistent_user_callout'; import { s__, sprintf } from '../locale'; import Flash from '../flash'; import Poll from '../lib/utils/poll'; @@ -62,7 +62,7 @@ export default class Clusters { this.showTokenButton = document.querySelector('.js-show-cluster-token'); this.tokenField = document.querySelector('.js-cluster-token'); - initDismissableCallout('.js-cluster-security-warning'); + Clusters.initDismissableCallout(); initSettingsPanels(); setupToggleButtons(document.querySelector('.js-cluster-enable-toggle-area')); this.initApplications(); @@ -105,6 +105,12 @@ export default class Clusters { }); } + static initDismissableCallout() { + const callout = document.querySelector('.js-cluster-security-warning'); + + if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new + } + addListeners() { if (this.showTokenButton) this.showTokenButton.addEventListener('click', this.showToken); eventHub.$on('installApplication', this.installApplication); diff --git a/app/assets/javascripts/clusters/clusters_index.js b/app/assets/javascripts/clusters/clusters_index.js index 789c8360124..e32d507d1f7 100644 --- a/app/assets/javascripts/clusters/clusters_index.js +++ b/app/assets/javascripts/clusters/clusters_index.js @@ -1,14 +1,15 @@ import createFlash from '~/flash'; import { __ } from '~/locale'; import setupToggleButtons from '~/toggle_buttons'; -import initDismissableCallout from '~/dismissable_callout'; +import PersistentUserCallout from '../persistent_user_callout'; import ClustersService from './services/clusters_service'; export default () => { const clusterList = document.querySelector('.js-clusters-list'); - initDismissableCallout('.gcp-signup-offer'); + const callout = document.querySelector('.gcp-signup-offer'); + if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new // The empty state won't have a clusterList if (clusterList) { diff --git a/app/assets/javascripts/dismissable_callout.js b/app/assets/javascripts/dismissable_callout.js deleted file mode 100644 index 8dcd8e9a47e..00000000000 --- a/app/assets/javascripts/dismissable_callout.js +++ /dev/null @@ -1,10 +0,0 @@ -import PersistentUserCallout from '.persistent_user_callout'; - -export default function initDismissableCallout(alertSelector) { - const alertEl = document.querySelector(alertSelector); - if (!alertEl) { - return; - } - - new PersistentUserCallout(alertEl); -} diff --git a/app/assets/javascripts/pages/projects/index.js b/app/assets/javascripts/pages/projects/index.js index 5659e13981a..b0345b4e50d 100644 --- a/app/assets/javascripts/pages/projects/index.js +++ b/app/assets/javascripts/pages/projects/index.js @@ -1,5 +1,5 @@ -import initDismissableCallout from '~/dismissable_callout'; import initGkeDropdowns from '~/projects/gke_cluster_dropdowns'; +import PersistentUserCallout from '../../persistent_user_callout'; import Project from './project'; import ShortcutsNavigation from '../../behaviors/shortcuts/shortcuts_navigation'; @@ -12,7 +12,9 @@ document.addEventListener('DOMContentLoaded', () => { ]; if (newClusterViews.indexOf(page) > -1) { - initDismissableCallout('.gcp-signup-offer'); + const callout = document.querySelector('.gcp-signup-offer'); + if (callout) new PersistentUserCallout(callout); // eslint-disable-line no-new + initGkeDropdowns(); } -- cgit v1.2.1 From 3c2d6b4870afa9cae25007a2ccf90ebf92c37d42 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 28 Sep 2018 11:00:30 +0200 Subject: Add a class that represents a git push operation --- app/services/merge_requests/base_service.rb | 6 +++--- lib/gitlab/git/push.rb | 25 +++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 lib/gitlab/git/push.rb diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index aa5d8406d0f..28c3219b37b 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -57,10 +57,10 @@ module MergeRequests # Returns all origin and fork merge requests from `@project` satisfying passed arguments. # rubocop: disable CodeReuse/ActiveRecord def merge_requests_for(source_branch, mr_states: [:opened]) - MergeRequest + @project.source_of_merge_requests .with_state(mr_states) - .where(source_branch: source_branch, source_project_id: @project.id) - .preload(:source_project) # we don't need a #includes since we're just preloading for the #select + .where(source_branch: source_branch) + .preload(:source_project) # we don't need #includes since we're just preloading for the #select .select(&:source_project) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/lib/gitlab/git/push.rb b/lib/gitlab/git/push.rb new file mode 100644 index 00000000000..d4fe3c623c1 --- /dev/null +++ b/lib/gitlab/git/push.rb @@ -0,0 +1,25 @@ +# frozen_string_literal: true + +module Gitlab + module Git + class Push + def initialize(project, oldrev, newrev, ref) + @project, @oldrev, @newrev = project, oldrev, newrev + @repository = project.repository + @branch_name = Gitlab::Git.ref_name(ref) + end + + def branch_added? + Gitlab::Git.blank_ref?(@oldrev) + end + + def branch_removed? + Gitlab::Git.blank_ref?(@newrev) + end + + def force_push? + Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev) + end + end + end +end -- cgit v1.2.1 From 76d9e29a65e488a316347e054920924a3c5f8b3d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 28 Sep 2018 12:08:11 +0200 Subject: Extract git push from merge request refresh service --- app/services/merge_requests/refresh_service.rb | 59 +++++++++++--------------- lib/gitlab/git/push.rb | 17 ++++++-- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index bcdd752ddc4..d3e4f3def23 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -3,17 +3,16 @@ module MergeRequests class RefreshService < MergeRequests::BaseService def execute(oldrev, newrev, ref) - return true unless Gitlab::Git.branch_ref?(ref) + @push = Gitlab::Git::Push.new(@project, oldrev, newrev, ref) - do_execute(oldrev, newrev, ref) + return true unless @push.branch_push? + + refresh_merge_requests! end private - def do_execute(oldrev, newrev, ref) - @oldrev, @newrev = oldrev, newrev - @branch_name = Gitlab::Git.ref_name(ref) - + def refresh_merge_requests! Gitlab::GitalyClient.allow_n_plus_1_calls(&method(:find_new_commits)) # Be sure to close outstanding MRs before reloading them to avoid generating an # empty diff during a manual merge @@ -25,7 +24,7 @@ module MergeRequests cache_merge_requests_closing_issues # Leave a system note if a branch was deleted/added - if branch_added? || branch_removed? + if @push.branch_added? || @push.branch_removed? comment_mr_branch_presence_changed end @@ -54,8 +53,10 @@ module MergeRequests # rubocop: disable CodeReuse/ActiveRecord def post_merge_manually_merged commit_ids = @commits.map(&:id) - merge_requests = @project.merge_requests.preload(:latest_merge_request_diff).opened.where(target_branch: @branch_name).to_a - merge_requests = merge_requests.select(&:diff_head_commit) + merge_requests = @project.merge_requests.opened + .preload(:latest_merge_request_diff) + .where(target_branch: @push.branch_name).to_a + .select(&:diff_head_commit) merge_requests = merge_requests.select do |merge_request| commit_ids.include?(merge_request.diff_head_sha) && @@ -70,24 +71,20 @@ module MergeRequests end # rubocop: enable CodeReuse/ActiveRecord - def force_push? - Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev) - end - # Refresh merge request diff if we push to source or target branch of merge request # Note: we should update merge requests from forks too # rubocop: disable CodeReuse/ActiveRecord def reload_merge_requests merge_requests = @project.merge_requests.opened - .by_source_or_target_branch(@branch_name).to_a + .by_source_or_target_branch(@push.branch_name).to_a # Fork merge requests merge_requests += MergeRequest.opened - .where(source_branch: @branch_name, source_project: @project) + .where(source_branch: @push.branch_name, source_project: @project) .where.not(target_project: @project).to_a filter_merge_requests(merge_requests).each do |merge_request| - if merge_request.source_branch == @branch_name || force_push? + if merge_request.source_branch == @push.branch_name || @push.force_push? merge_request.reload_diff(current_user) else mr_commit_ids = merge_request.commit_shas @@ -117,7 +114,7 @@ module MergeRequests end def find_new_commits - if branch_added? + if @push.branch_added? @commits = [] merge_request = merge_requests_for_source_branch.first @@ -126,28 +123,28 @@ module MergeRequests begin # Since any number of commits could have been made to the restored branch, # find the common root to see what has been added. - common_ref = @project.repository.merge_base(merge_request.diff_head_sha, @newrev) + common_ref = @project.repository.merge_base(merge_request.diff_head_sha, @push.newrev) # If the a commit no longer exists in this repo, gitlab_git throws # a Rugged::OdbError. This is fixed in https://gitlab.com/gitlab-org/gitlab_git/merge_requests/52 - @commits = @project.repository.commits_between(common_ref, @newrev) if common_ref + @commits = @project.repository.commits_between(common_ref, @push.newrev) if common_ref rescue end - elsif branch_removed? + elsif @push.branch_removed? # No commits for a deleted branch. @commits = [] else - @commits = @project.repository.commits_between(@oldrev, @newrev) + @commits = @project.repository.commits_between(@push.oldrev, @push.newrev) end end # Add comment about branches being deleted or added to merge requests def comment_mr_branch_presence_changed - presence = branch_added? ? :add : :delete + presence = @push.branch_added? ? :add : :delete merge_requests_for_source_branch.each do |merge_request| SystemNoteService.change_branch_presence( merge_request, merge_request.project, @current_user, - :source, @branch_name, presence) + :source, @push.branch_name, presence) end end @@ -164,7 +161,7 @@ module MergeRequests SystemNoteService.add_commits(merge_request, merge_request.project, @current_user, new_commits, - existing_commits, @oldrev) + existing_commits, @push.oldrev) notification_service.push_to_merge_request(merge_request, @current_user, new_commits: new_commits, existing_commits: existing_commits) end @@ -195,7 +192,7 @@ module MergeRequests # Call merge request webhook with update branches def execute_mr_web_hooks merge_requests_for_source_branch.each do |merge_request| - execute_hooks(merge_request, 'update', old_rev: @oldrev) + execute_hooks(merge_request, 'update', old_rev: @push.oldrev) end end @@ -203,7 +200,7 @@ module MergeRequests # `MergeRequestsClosingIssues` model (as a performance optimization). # rubocop: disable CodeReuse/ActiveRecord def cache_merge_requests_closing_issues - @project.merge_requests.where(source_branch: @branch_name).each do |merge_request| + @project.merge_requests.where(source_branch: @push.branch_name).each do |merge_request| merge_request.cache_merge_request_closes_issues!(@current_user) end end @@ -215,15 +212,7 @@ module MergeRequests def merge_requests_for_source_branch(reload: false) @source_merge_requests = nil if reload - @source_merge_requests ||= merge_requests_for(@branch_name) - end - - def branch_added? - Gitlab::Git.blank_ref?(@oldrev) - end - - def branch_removed? - Gitlab::Git.blank_ref?(@newrev) + @source_merge_requests ||= merge_requests_for(@push.branch_name) end end end diff --git a/lib/gitlab/git/push.rb b/lib/gitlab/git/push.rb index d4fe3c623c1..8173af31a0d 100644 --- a/lib/gitlab/git/push.rb +++ b/lib/gitlab/git/push.rb @@ -3,10 +3,17 @@ module Gitlab module Git class Push + attr_reader :oldrev, :newrev + def initialize(project, oldrev, newrev, ref) - @project, @oldrev, @newrev = project, oldrev, newrev - @repository = project.repository - @branch_name = Gitlab::Git.ref_name(ref) + @project = project + @oldrev = oldrev + @newrev = newrev + @ref = ref + end + + def branch_name + @branch_name ||= Gitlab::Git.ref_name(@ref) end def branch_added? @@ -20,6 +27,10 @@ module Gitlab def force_push? Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev) end + + def branch_push? + Gitlab::Git.branch_ref?(@ref) + end end end end -- cgit v1.2.1 From 4abba28944803d9a818925e62211a0f65458e011 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 28 Sep 2018 14:46:22 +0200 Subject: Add specs for extracted git push class --- lib/gitlab/git/push.rb | 10 ++++- spec/lib/gitlab/git/push_spec.rb | 83 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 spec/lib/gitlab/git/push_spec.rb diff --git a/lib/gitlab/git/push.rb b/lib/gitlab/git/push.rb index 8173af31a0d..b6e414927ea 100644 --- a/lib/gitlab/git/push.rb +++ b/lib/gitlab/git/push.rb @@ -3,6 +3,8 @@ module Gitlab module Git class Push + include Gitlab::Utils::StrongMemoize + attr_reader :oldrev, :newrev def initialize(project, oldrev, newrev, ref) @@ -13,7 +15,9 @@ module Gitlab end def branch_name - @branch_name ||= Gitlab::Git.ref_name(@ref) + strong_memoize(:branch_name) do + Gitlab::Git.branch_name(@ref) + end end def branch_added? @@ -29,7 +33,9 @@ module Gitlab end def branch_push? - Gitlab::Git.branch_ref?(@ref) + strong_memoize(:branch_push) do + Gitlab::Git.branch_ref?(@ref) + end end end end diff --git a/spec/lib/gitlab/git/push_spec.rb b/spec/lib/gitlab/git/push_spec.rb new file mode 100644 index 00000000000..c87e972f31a --- /dev/null +++ b/spec/lib/gitlab/git/push_spec.rb @@ -0,0 +1,83 @@ +require 'spec_helper' + +describe Gitlab::Git::Push do + set(:project) { create(:project, :repository) } + + let(:oldrev) { project.commit('HEAD~10').id } + let(:newrev) { project.commit.id } + let(:ref) { 'refs/heads/some-branch' } + + subject { described_class.new(project, oldrev, newrev, ref) } + + describe '#branch_name' do + context 'when it is a branch push' do + let(:ref) { 'refs/heads/my-branch' } + + it 'returns branch name' do + expect(subject.branch_name).to eq 'my-branch' + end + end + + context 'when it is a tag push' do + let(:ref) { 'refs/tags/my-branch' } + + it 'returns nil' do + expect(subject.branch_name).to be_nil + end + end + end + + describe '#branch_push?' do + context 'when pushing a branch ref' do + let(:ref) { 'refs/heads/my-branch' } + + it { is_expected.to be_branch_push } + end + + context 'when it is a tag push' do + let(:ref) { 'refs/tags/my-branch' } + + it { is_expected.not_to be_branch_push } + end + end + + describe '#force_push?' do + context 'when old revision is an ancestor of the new revision' do + let(:oldrev) { 'HEAD~3' } + let(:newrev) { 'HEAD~1' } + + it { is_expected.not_to be_force_push } + end + + context 'when old revision is not an ancestor of the new revision' do + let(:oldrev) { 'HEAD~3' } + let(:newrev) { '123456' } + + it { is_expected.to be_force_push } + end + end + + describe '#branch_added?' do + context 'when old revision is defined' do + it { is_expected.not_to be_branch_added } + end + + context 'when old revision is not defined' do + let(:oldrev) { Gitlab::Git::BLANK_SHA } + + it { is_expected.to be_branch_added } + end + end + + describe '#branch_removed?' do + context 'when new revision is defined' do + it { is_expected.not_to be_branch_removed } + end + + context 'when new revision is not defined' do + let(:newrev) { Gitlab::Git::BLANK_SHA } + + it { is_expected.to be_branch_removed } + end + end +end -- cgit v1.2.1 From 419d8cc7a2d0e5ffd2f3f8894a739827d0c2c549 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 28 Sep 2018 15:13:04 +0200 Subject: Calculate modified paths of a git push operation --- lib/gitlab/git/diff_stats_collection.rb | 4 ++ lib/gitlab/git/push.rb | 14 +++++++ spec/lib/gitlab/git/diff_stats_collection_spec.rb | 8 +++- spec/lib/gitlab/git/push_spec.rb | 49 ++++++++++++++++++++++- 4 files changed, 72 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/git/diff_stats_collection.rb b/lib/gitlab/git/diff_stats_collection.rb index d4033f56387..998c41497a2 100644 --- a/lib/gitlab/git/diff_stats_collection.rb +++ b/lib/gitlab/git/diff_stats_collection.rb @@ -18,6 +18,10 @@ module Gitlab indexed_by_path[path] end + def paths + @collection.map(&:path) + end + private def indexed_by_path diff --git a/lib/gitlab/git/push.rb b/lib/gitlab/git/push.rb index b6e414927ea..603f860e4f8 100644 --- a/lib/gitlab/git/push.rb +++ b/lib/gitlab/git/push.rb @@ -28,6 +28,10 @@ module Gitlab Gitlab::Git.blank_ref?(@newrev) end + def branch_updated? + branch_push? && !branch_added? && !branch_removed? + end + def force_push? Gitlab::Checks::ForcePush.force_push?(@project, @oldrev, @newrev) end @@ -37,6 +41,16 @@ module Gitlab Gitlab::Git.branch_ref?(@ref) end end + + def modified_paths + unless branch_updated? + raise ArgumentError, 'Unable to calculate modified paths!' + end + + strong_memoize(:modified_paths) do + @project.repository.diff_stats(@oldrev, @newrev).paths + end + end end end end diff --git a/spec/lib/gitlab/git/diff_stats_collection_spec.rb b/spec/lib/gitlab/git/diff_stats_collection_spec.rb index 89927cbb3a6..b07690ef39c 100644 --- a/spec/lib/gitlab/git/diff_stats_collection_spec.rb +++ b/spec/lib/gitlab/git/diff_stats_collection_spec.rb @@ -14,7 +14,7 @@ describe Gitlab::Git::DiffStatsCollection do let(:diff_stats) { [stats_a, stats_b] } let(:collection) { described_class.new(diff_stats) } - describe '.find_by_path' do + describe '#find_by_path' do it 'returns stats by path when found' do expect(collection.find_by_path('foo')).to eq(stats_a) end @@ -23,4 +23,10 @@ describe Gitlab::Git::DiffStatsCollection do expect(collection.find_by_path('no-file')).to be_nil end end + + describe '#paths' do + it 'returns only modified paths' do + expect(collection.paths).to eq %w[foo bar] + end + end end diff --git a/spec/lib/gitlab/git/push_spec.rb b/spec/lib/gitlab/git/push_spec.rb index c87e972f31a..f19e05c4451 100644 --- a/spec/lib/gitlab/git/push_spec.rb +++ b/spec/lib/gitlab/git/push_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Gitlab::Git::Push do set(:project) { create(:project, :repository) } - let(:oldrev) { project.commit('HEAD~10').id } + let(:oldrev) { project.commit('HEAD~2').id } let(:newrev) { project.commit.id } let(:ref) { 'refs/heads/some-branch' } @@ -35,12 +35,36 @@ describe Gitlab::Git::Push do end context 'when it is a tag push' do - let(:ref) { 'refs/tags/my-branch' } + let(:ref) { 'refs/tags/my-tag' } it { is_expected.not_to be_branch_push } end end + describe '#branch_updated?' do + context 'when it is a branch push with correct old and new revisions' do + it { is_expected.to be_branch_updated } + end + + context 'when it is not a branch push' do + let(:ref) { 'refs/tags/my-tag' } + + it { is_expected.not_to be_branch_updated } + end + + context 'when old revision is blank' do + let(:oldrev) { Gitlab::Git::BLANK_SHA } + + it { is_expected.not_to be_branch_updated } + end + + context 'when it is not a branch push' do + let(:newrev) { Gitlab::Git::BLANK_SHA } + + it { is_expected.not_to be_branch_updated } + end + end + describe '#force_push?' do context 'when old revision is an ancestor of the new revision' do let(:oldrev) { 'HEAD~3' } @@ -80,4 +104,25 @@ describe Gitlab::Git::Push do it { is_expected.to be_branch_removed } end end + + describe '#modified_paths' do + context 'when a push is a branch update' do + let(:oldrev) { '281d3a76f31c812dbf48abce82ccf6860adedd81' } + let(:new_rev) { '1b12f15a11fc6e62177bef08f47bc7b5ce50b141' } + + it 'returns modified paths' do + expect(subject.modified_paths).to eq ['bar/branch-test.txt', + 'files/js/commit.coffee', + 'with space/README.md'] + end + end + + context 'when a push is not a branch update' do + let(:oldrev) { Gitlab::Git::BLANK_SHA } + + it 'raises an error' do + expect { subject.modified_paths }.to raise_error(ArgumentError) + end + end + end end -- cgit v1.2.1 From 9bad9b0a68074d4b0fe5de7f3c5cefd4a808c726 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 28 Sep 2018 15:38:36 +0200 Subject: Improve specs for git push class This helps to prevent problems with off-by-one commmit problem in `Gitlab::Git::Push#modified_paths`. --- spec/lib/gitlab/git/push_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/lib/gitlab/git/push_spec.rb b/spec/lib/gitlab/git/push_spec.rb index f19e05c4451..2a7a357f384 100644 --- a/spec/lib/gitlab/git/push_spec.rb +++ b/spec/lib/gitlab/git/push_spec.rb @@ -107,8 +107,8 @@ describe Gitlab::Git::Push do describe '#modified_paths' do context 'when a push is a branch update' do - let(:oldrev) { '281d3a76f31c812dbf48abce82ccf6860adedd81' } - let(:new_rev) { '1b12f15a11fc6e62177bef08f47bc7b5ce50b141' } + let(:newrev) { '498214d' } + let(:oldrev) { '281d3a7' } it 'returns modified paths' do expect(subject.modified_paths).to eq ['bar/branch-test.txt', -- cgit v1.2.1 From b0c197cc2302945222e898d92951c2b3faa8d43e Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Fri, 28 Sep 2018 00:52:02 +0300 Subject: Update environment item empty state --- app/views/projects/environments/show.html.haml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/views/projects/environments/show.html.haml b/app/views/projects/environments/show.html.haml index c7890b37381..8c5b6e089ea 100644 --- a/app/views/projects/environments/show.html.haml +++ b/app/views/projects/environments/show.html.haml @@ -49,15 +49,16 @@ .environments-container - if @deployments.blank? - .blank-state-row - .blank-state-center - %h2.blank-state-title + .empty-state + .text-content + %h4.state-title You don't have any deployments right now. %p.blank-state-text Define environments in the deploy stage(s) in %code .gitlab-ci.yml to track deployments here. - = link_to "Read more", help_page_path("ci/environments"), class: "btn btn-success" + .text-center + = link_to _("Read more"), help_page_path("ci/environments"), class: "btn btn-success" - else .table-holder .ci-table.environments{ role: 'grid' } -- cgit v1.2.1 From 829c9c65f9b730b3ecad7d3ba222e3dcd6489b85 Mon Sep 17 00:00:00 2001 From: Brett Walker Date: Wed, 19 Sep 2018 14:58:43 -0500 Subject: post_process markdown redered by API --- app/models/project_services/hipchat_service.rb | 2 +- ...bw-confidential-titles-through-markdown-api.yml | 5 +++ lib/api/markdown.rb | 7 ++-- lib/banzai.rb | 7 ++++ spec/requests/api/markdown_spec.rb | 46 ++++++++++++++++++++++ 5 files changed, 62 insertions(+), 5 deletions(-) create mode 100644 changelogs/unreleased/security-bw-confidential-titles-through-markdown-api.yml diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 66012f0da99..a69b7b4c4b6 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -149,7 +149,7 @@ class HipchatService < Service context.merge!(options) - html = Banzai.post_process(Banzai.render(text, context), context) + html = Banzai.render_and_post_process(text, context) sanitized_html = sanitize(html, tags: HIPCHAT_ALLOWED_TAGS, attributes: %w[href title alt]) sanitized_html.truncate(200, separator: ' ', omission: '...') diff --git a/changelogs/unreleased/security-bw-confidential-titles-through-markdown-api.yml b/changelogs/unreleased/security-bw-confidential-titles-through-markdown-api.yml new file mode 100644 index 00000000000..e0231b7962f --- /dev/null +++ b/changelogs/unreleased/security-bw-confidential-titles-through-markdown-api.yml @@ -0,0 +1,5 @@ +--- +title: Markdown API no longer displays confidential title references unless authorized +merge_request: +author: +type: security diff --git a/lib/api/markdown.rb b/lib/api/markdown.rb index 5d55224c1a7..09a8c34c5c0 100644 --- a/lib/api/markdown.rb +++ b/lib/api/markdown.rb @@ -10,7 +10,8 @@ module API detail "This feature was introduced in GitLab 11.0." end post do - context = { only_path: false } + context = { only_path: false, current_user: current_user } + context[:pipeline] = params[:gfm] ? :full : :plain_markdown if params[:project] project = Project.find_by_full_path(params[:project]) @@ -22,9 +23,7 @@ module API context[:skip_project_check] = true end - context[:pipeline] = params[:gfm] ? :full : :plain_markdown - - { html: Banzai.render(params[:text], context) } + { html: Banzai.render_and_post_process(params[:text], context) } end end end diff --git a/lib/banzai.rb b/lib/banzai.rb index 5df98f66f3b..788f29a6c08 100644 --- a/lib/banzai.rb +++ b/lib/banzai.rb @@ -1,4 +1,11 @@ module Banzai + # if you need to render markdown, then you probably need to post_process as well, + # such as removing references that the current user doesn't have + # permission to make + def self.render_and_post_process(text, context = {}) + post_process(render(text, context), context) + end + def self.render(text, context = {}) Renderer.render(text, context) end diff --git a/spec/requests/api/markdown_spec.rb b/spec/requests/api/markdown_spec.rb index a55796cf343..e369c1435f0 100644 --- a/spec/requests/api/markdown_spec.rb +++ b/spec/requests/api/markdown_spec.rb @@ -106,6 +106,52 @@ describe API::Markdown do .and include("#1") end end + + context 'with a public project and confidential issue' do + let(:public_project) { create(:project, :public) } + let(:confidential_issue) { create(:issue, :confidential, project: public_project, title: 'Confidential title') } + + let(:text) { ":tada: Hello world! :100: #{confidential_issue.to_reference}" } + let(:params) { { text: text, gfm: true, project: public_project.full_path } } + + shared_examples 'user without proper access' do + it 'does not render the title or link' do + expect(response).to have_http_status(201) + expect(json_response["html"]).not_to include('Confidential title') + expect(json_response["html"]).not_to include('') + end + end + + context 'when not logged in' do + let(:user) { } + + it_behaves_like 'user without proper access' + end + + context 'when logged in as user without access' do + let(:user) { create(:user) } + + it_behaves_like 'user without proper access' + end + + context 'when logged in as author' do + let(:user) { confidential_issue.author } + + it 'renders the title or link' do + expect(response).to have_http_status(201) + expect(json_response["html"]).to include('Confidential title') + expect(json_response["html"]).to include('Hello world!') + .and include('data-name="tada"') + .and include('data-name="100"') + .and include("") + end + end + end end end end -- cgit v1.2.1 From 7f4452d4069b815612e53b7f20995137af608db2 Mon Sep 17 00:00:00 2001 From: Jan Provaznik Date: Sun, 30 Sep 2018 19:54:21 +0200 Subject: Preload project features in reference parser Preloading of project_features mitigates N+1 queries when checking references in other projects. When loading projects for resources referenced in comments it makes sense to include also associated project_features because in the following step (`can_read_reference?(user, projects[node], node)`) project features is used for checking permissions for the given project. --- changelogs/unreleased/load_project_features.yml | 5 +++++ lib/banzai/reference_parser/base_parser.rb | 2 +- spec/lib/banzai/reference_parser/commit_parser_spec.rb | 18 ++++++++++++++++++ spec/support/helpers/reference_parser_helpers.rb | 6 +++++- 4 files changed, 29 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/load_project_features.yml diff --git a/changelogs/unreleased/load_project_features.yml b/changelogs/unreleased/load_project_features.yml new file mode 100644 index 00000000000..0cf7f0e3a74 --- /dev/null +++ b/changelogs/unreleased/load_project_features.yml @@ -0,0 +1,5 @@ +--- +title: Mitigate N+1 queries when parsing commit references in comments. +merge_request: +author: +type: performance diff --git a/lib/banzai/reference_parser/base_parser.rb b/lib/banzai/reference_parser/base_parser.rb index 3ab154a7b1c..334ba97bfb3 100644 --- a/lib/banzai/reference_parser/base_parser.rb +++ b/lib/banzai/reference_parser/base_parser.rb @@ -215,7 +215,7 @@ module Banzai # def projects_for_nodes(nodes) @projects_for_nodes ||= - grouped_objects_for_nodes(nodes, Project, 'data-project') + grouped_objects_for_nodes(nodes, Project.includes(:project_feature), 'data-project') end def can?(user, permission, subject = :global) diff --git a/spec/lib/banzai/reference_parser/commit_parser_spec.rb b/spec/lib/banzai/reference_parser/commit_parser_spec.rb index cca53a8b9b9..f558dea209f 100644 --- a/spec/lib/banzai/reference_parser/commit_parser_spec.rb +++ b/spec/lib/banzai/reference_parser/commit_parser_spec.rb @@ -120,4 +120,22 @@ describe Banzai::ReferenceParser::CommitParser do expect(subject.find_commits(project, %w{123})).to eq([]) end end + + context 'when checking commits on another projects' do + let(:control_links) do + [commit_link] + end + + let(:actual_links) do + control_links + [commit_link, commit_link] + end + + def commit_link + project = create(:project, :repository, :public) + + Nokogiri::HTML.fragment(%Q{}).children[0] + end + + it_behaves_like 'no project N+1 queries' + end end diff --git a/spec/support/helpers/reference_parser_helpers.rb b/spec/support/helpers/reference_parser_helpers.rb index c01897ed1a1..9f27502aa52 100644 --- a/spec/support/helpers/reference_parser_helpers.rb +++ b/spec/support/helpers/reference_parser_helpers.rb @@ -3,7 +3,7 @@ module ReferenceParserHelpers Nokogiri::HTML.fragment('').children[0] end - shared_examples 'no N+1 queries' do + shared_examples 'no project N+1 queries' do it 'avoids N+1 queries in #nodes_visible_to_user', :request_store do context = Banzai::RenderContext.new(project, user) @@ -19,6 +19,10 @@ module ReferenceParserHelpers expect(actual.count).to be <= control.count expect(actual.cached_count).to be <= control.cached_count end + end + + shared_examples 'no N+1 queries' do + it_behaves_like 'no project N+1 queries' it 'avoids N+1 queries in #records_for_nodes', :request_store do context = Banzai::RenderContext.new(project, user) -- cgit v1.2.1 From b18345fac49e3b0ef35a22f3d784aef9f6bf9209 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Mon, 1 Oct 2018 10:53:08 -0300 Subject: Filter user sensitive data from discussions JSON --- app/serializers/discussion_entity.rb | 2 +- .../api/schemas/entities/note_user_entity.json | 21 +++++++++++++++++++++ spec/serializers/discussion_entity_spec.rb | 7 +++++++ 3 files changed, 29 insertions(+), 1 deletion(-) create mode 100644 spec/fixtures/api/schemas/entities/note_user_entity.json diff --git a/app/serializers/discussion_entity.rb b/app/serializers/discussion_entity.rb index ebe76c9fcda..b6786a0d597 100644 --- a/app/serializers/discussion_entity.rb +++ b/app/serializers/discussion_entity.rb @@ -27,7 +27,7 @@ class DiscussionEntity < Grape::Entity expose :resolved?, as: :resolved expose :resolved_by_push?, as: :resolved_by_push - expose :resolved_by + expose :resolved_by, using: NoteUserEntity expose :resolved_at expose :resolve_path, if: -> (d, _) { d.resolvable? } do |discussion| resolve_project_merge_request_discussion_path(discussion.project, discussion.noteable, discussion.id) diff --git a/spec/fixtures/api/schemas/entities/note_user_entity.json b/spec/fixtures/api/schemas/entities/note_user_entity.json new file mode 100644 index 00000000000..9b838054563 --- /dev/null +++ b/spec/fixtures/api/schemas/entities/note_user_entity.json @@ -0,0 +1,21 @@ +{ + "type": "object", + "required": [ + "id", + "state", + "avatar_url", + "path", + "name", + "username" + ], + "properties": { + "id": { "type": "integer" }, + "state": { "type": "string" }, + "avatar_url": { "type": "string" }, + "path": { "type": "string" }, + "name": { "type": "string" }, + "username": { "type": "string" }, + "status_tooltip_html": { "$ref": "../types/nullable_string.json" } + }, + "additionalProperties": false +} diff --git a/spec/serializers/discussion_entity_spec.rb b/spec/serializers/discussion_entity_spec.rb index 378540a35b6..0590304e832 100644 --- a/spec/serializers/discussion_entity_spec.rb +++ b/spec/serializers/discussion_entity_spec.rb @@ -36,6 +36,13 @@ describe DiscussionEntity do ) end + it 'resolved_by matches note_user_entity schema' do + Notes::ResolveService.new(note.project, user).execute(note) + + expect(subject[:resolved_by].with_indifferent_access) + .to match_schema('entities/note_user_entity') + end + context 'when is LegacyDiffDiscussion' do let(:project) { create(:project) } let(:merge_request) { create(:merge_request, source_project: project) } -- cgit v1.2.1 From f09303b00aa61825d2fed22aa04d8c6d1dace1b0 Mon Sep 17 00:00:00 2001 From: Mark Chao Date: Fri, 28 Sep 2018 17:54:32 +0800 Subject: Fix MR discussion not loaded issue Display `formatter` as the sole content of `position` object. This means `diff_file` data is not referenced, which is the caseu of "IOError: not opened for reading". --- app/assets/javascripts/diffs/store/utils.js | 5 +- app/assets/javascripts/notes/stores/getters.js | 4 +- .../unreleased/51958-fix-mr-discussion-loading.yml | 5 ++ lib/gitlab/diff/position.rb | 4 + .../diffs/mock_data/diff_discussions.js | 16 ++-- spec/javascripts/diffs/store/actions_spec.js | 9 +-- spec/javascripts/diffs/store/mutations_spec.js | 16 +--- spec/javascripts/diffs/store/utils_spec.js | 16 +--- spec/javascripts/notes/mock_data.js | 24 ++---- spec/lib/gitlab/diff/position_spec.rb | 88 ++++++++++++++-------- 10 files changed, 96 insertions(+), 91 deletions(-) create mode 100644 changelogs/unreleased/51958-fix-mr-discussion-loading.yml diff --git a/app/assets/javascripts/diffs/store/utils.js b/app/assets/javascripts/diffs/store/utils.js index 631e3de311e..8c13aaa1a7e 100644 --- a/app/assets/javascripts/diffs/store/utils.js +++ b/app/assets/javascripts/diffs/store/utils.js @@ -244,6 +244,7 @@ export function getDiffPositionByLineCode(diffFiles) { oldLine, newLine, lineCode, + positionType: 'text', }; } }); @@ -259,8 +260,8 @@ export function isDiscussionApplicableToLine({ discussion, diffPosition, latestD const { lineCode, ...diffPositionCopy } = diffPosition; if (discussion.original_position && discussion.position) { - const originalRefs = convertObjectPropsToCamelCase(discussion.original_position.formatter); - const refs = convertObjectPropsToCamelCase(discussion.position.formatter); + const originalRefs = convertObjectPropsToCamelCase(discussion.original_position); + const refs = convertObjectPropsToCamelCase(discussion.position); return _.isEqual(refs, diffPositionCopy) || _.isEqual(originalRefs, diffPositionCopy); } diff --git a/app/assets/javascripts/notes/stores/getters.js b/app/assets/javascripts/notes/stores/getters.js index d4babf1fab2..75832884711 100644 --- a/app/assets/javascripts/notes/stores/getters.js +++ b/app/assets/javascripts/notes/stores/getters.js @@ -126,8 +126,8 @@ export const unresolvedDiscussionsIdsByDiff = (state, getters) => const filenameComparison = a.diff_file.file_path.localeCompare(b.diff_file.file_path); // Get the line numbers, to compare within the same file - const aLines = [a.position.formatter.new_line, a.position.formatter.old_line]; - const bLines = [b.position.formatter.new_line, b.position.formatter.old_line]; + const aLines = [a.position.new_line, a.position.old_line]; + const bLines = [b.position.new_line, b.position.old_line]; return filenameComparison < 0 || (filenameComparison === 0 && diff --git a/changelogs/unreleased/51958-fix-mr-discussion-loading.yml b/changelogs/unreleased/51958-fix-mr-discussion-loading.yml new file mode 100644 index 00000000000..f80ee51291d --- /dev/null +++ b/changelogs/unreleased/51958-fix-mr-discussion-loading.yml @@ -0,0 +1,5 @@ +--- +title: Fix loading issue on some merge request discussion +merge_request: 21982 +author: +type: fixed diff --git a/lib/gitlab/diff/position.rb b/lib/gitlab/diff/position.rb index fc280f96ec1..f967494199e 100644 --- a/lib/gitlab/diff/position.rb +++ b/lib/gitlab/diff/position.rb @@ -69,6 +69,10 @@ module Gitlab JSON.generate(formatter.to_h, opts) end + def as_json(opts = nil) + to_h.as_json(opts) + end + def type formatter.line_age end diff --git a/spec/javascripts/diffs/mock_data/diff_discussions.js b/spec/javascripts/diffs/mock_data/diff_discussions.js index b29a22da7c2..0ad214ea4a4 100644 --- a/spec/javascripts/diffs/mock_data/diff_discussions.js +++ b/spec/javascripts/diffs/mock_data/diff_discussions.js @@ -2,15 +2,13 @@ export default { id: '6b232e05bea388c6b043ccc243ba505faac04ea8', reply_id: '6b232e05bea388c6b043ccc243ba505faac04ea8', position: { - formatter: { - old_line: null, - new_line: 2, - old_path: 'CHANGELOG', - new_path: 'CHANGELOG', - base_sha: 'e63f41fe459e62e1228fcef60d7189127aeba95a', - start_sha: 'd9eaefe5a676b820c57ff18cf5b68316025f7962', - head_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13', - }, + old_line: null, + new_line: 2, + old_path: 'CHANGELOG', + new_path: 'CHANGELOG', + base_sha: 'e63f41fe459e62e1228fcef60d7189127aeba95a', + start_sha: 'd9eaefe5a676b820c57ff18cf5b68316025f7962', + head_sha: 'c48ee0d1bf3b30453f5b32250ce03134beaa6d13', }, line_code: '1c497fbb3a46b78edf04cc2a2fa33f67e3ffbe2a_1_2', expanded: true, diff --git a/spec/javascripts/diffs/store/actions_spec.js b/spec/javascripts/diffs/store/actions_spec.js index 05b39bad6ea..30cd6a09a0c 100644 --- a/spec/javascripts/diffs/store/actions_spec.js +++ b/spec/javascripts/diffs/store/actions_spec.js @@ -145,12 +145,8 @@ describe('DiffsStoreActions', () => { }, fileHash: 'ABC', resolvable: true, - position: { - formatter: diffPosition, - }, - original_position: { - formatter: diffPosition, - }, + position: diffPosition, + original_position: diffPosition, }; const discussions = reduceDiscussionsToLineCodes([singleDiscussion]); @@ -175,6 +171,7 @@ describe('DiffsStoreActions', () => { oldLine: 5, oldPath: 'file2', lineCode: 'ABC_1_1', + positionType: 'text', }, }, }, diff --git a/spec/javascripts/diffs/store/mutations_spec.js b/spec/javascripts/diffs/store/mutations_spec.js index 9a5d8dfbd15..26bf63c4b99 100644 --- a/spec/javascripts/diffs/store/mutations_spec.js +++ b/spec/javascripts/diffs/store/mutations_spec.js @@ -193,24 +193,16 @@ describe('DiffsStoreMutations', () => { line_code: 'ABC_1', diff_discussion: true, resolvable: true, - original_position: { - formatter: diffPosition, - }, - position: { - formatter: diffPosition, - }, + original_position: diffPosition, + position: diffPosition, }, { id: 2, line_code: 'ABC_1', diff_discussion: true, resolvable: true, - original_position: { - formatter: diffPosition, - }, - position: { - formatter: diffPosition, - }, + original_position: diffPosition, + position: diffPosition, }, ]; diff --git a/spec/javascripts/diffs/store/utils_spec.js b/spec/javascripts/diffs/store/utils_spec.js index 897cd1483aa..85009a5838c 100644 --- a/spec/javascripts/diffs/store/utils_spec.js +++ b/spec/javascripts/diffs/store/utils_spec.js @@ -333,20 +333,12 @@ describe('DiffsStoreUtils', () => { const discussions = { upToDateDiscussion1: { - original_position: { - formatter: diffPosition, - }, - position: { - formatter: wrongDiffPosition, - }, + original_position: diffPosition, + position: wrongDiffPosition, }, outDatedDiscussion1: { - original_position: { - formatter: wrongDiffPosition, - }, - position: { - formatter: wrongDiffPosition, - }, + original_position: wrongDiffPosition, + position: wrongDiffPosition, }, }; diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js index 1f030e5af28..9a0e7f34a9c 100644 --- a/spec/javascripts/notes/mock_data.js +++ b/spec/javascripts/notes/mock_data.js @@ -1177,10 +1177,8 @@ export const discussion1 = { file_path: 'about.md', }, position: { - formatter: { - new_line: 50, - old_line: null, - }, + new_line: 50, + old_line: null, }, notes: [ { @@ -1197,10 +1195,8 @@ export const resolvedDiscussion1 = { file_path: 'about.md', }, position: { - formatter: { - new_line: 50, - old_line: null, - }, + new_line: 50, + old_line: null, }, notes: [ { @@ -1217,10 +1213,8 @@ export const discussion2 = { file_path: 'README.md', }, position: { - formatter: { - new_line: null, - old_line: 20, - }, + new_line: null, + old_line: 20, }, notes: [ { @@ -1237,10 +1231,8 @@ export const discussion3 = { file_path: 'README.md', }, position: { - formatter: { - new_line: 21, - old_line: null, - }, + new_line: 21, + old_line: null, }, notes: [ { diff --git a/spec/lib/gitlab/diff/position_spec.rb b/spec/lib/gitlab/diff/position_spec.rb index 677eb373d22..2d94356f386 100644 --- a/spec/lib/gitlab/diff/position_spec.rb +++ b/spec/lib/gitlab/diff/position_spec.rb @@ -5,6 +5,34 @@ describe Gitlab::Diff::Position do let(:project) { create(:project, :repository) } + let(:args_for_img) do + { + old_path: "files/any.img", + new_path: "files/any.img", + base_sha: nil, + head_sha: nil, + start_sha: nil, + width: 100, + height: 100, + x: 1, + y: 100, + position_type: "image" + } + end + + let(:args_for_text) do + { + old_path: "files/ruby/popen.rb", + new_path: "files/ruby/popen.rb", + old_line: nil, + new_line: 14, + base_sha: nil, + head_sha: nil, + start_sha: nil, + position_type: "text" + } + end + describe "position for an added text file" do let(:commit) { project.commit("2ea1f3dec713d940208fb5ce4a38765ecb5d3f73") } @@ -529,53 +557,49 @@ describe Gitlab::Diff::Position do end end + describe "#as_json" do + shared_examples "diff position json" do + let(:diff_position) { described_class.new(args) } + + it "returns the position as JSON" do + expect(diff_position.as_json).to eq(args.stringify_keys) + end + end + + context "for text positon" do + let(:args) { args_for_text } + + it_behaves_like "diff position json" + end + + context "for image positon" do + let(:args) { args_for_img } + + it_behaves_like "diff position json" + end + end + describe "#to_json" do shared_examples "diff position json" do + let(:diff_position) { described_class.new(args) } + it "returns the position as JSON" do - expect(JSON.parse(diff_position.to_json)).to eq(hash.stringify_keys) + expect(JSON.parse(diff_position.to_json)).to eq(args.stringify_keys) end it "works when nested under another hash" do - expect(JSON.parse(JSON.generate(pos: diff_position))).to eq('pos' => hash.stringify_keys) + expect(JSON.parse(JSON.generate(pos: diff_position))).to eq('pos' => args.stringify_keys) end end context "for text positon" do - let(:hash) do - { - old_path: "files/ruby/popen.rb", - new_path: "files/ruby/popen.rb", - old_line: nil, - new_line: 14, - base_sha: nil, - head_sha: nil, - start_sha: nil, - position_type: "text" - } - end - - let(:diff_position) { described_class.new(hash) } + let(:args) { args_for_text } it_behaves_like "diff position json" end context "for image positon" do - let(:hash) do - { - old_path: "files/any.img", - new_path: "files/any.img", - base_sha: nil, - head_sha: nil, - start_sha: nil, - width: 100, - height: 100, - x: 1, - y: 100, - position_type: "image" - } - end - - let(:diff_position) { described_class.new(hash) } + let(:args) { args_for_img } it_behaves_like "diff position json" end -- cgit v1.2.1 From 96faeb330860d8f6c509947e9f683c337ccdb6f8 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Mon, 1 Oct 2018 14:33:01 -0300 Subject: Add changelog --- changelogs/unreleased/security-osw-user-info-leak-discussions.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/security-osw-user-info-leak-discussions.yml diff --git a/changelogs/unreleased/security-osw-user-info-leak-discussions.yml b/changelogs/unreleased/security-osw-user-info-leak-discussions.yml new file mode 100644 index 00000000000..5acbb80fc3d --- /dev/null +++ b/changelogs/unreleased/security-osw-user-info-leak-discussions.yml @@ -0,0 +1,5 @@ +--- +title: Filter user sensitive data from discussions JSON +merge_request: 2536 +author: +type: security -- cgit v1.2.1 From 1c4187e38e09c8dfc4d0cdbd4c702aff5d695acb Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 2 Oct 2018 13:01:55 +0200 Subject: Treat nil git push revisons as a blank Git SHA value --- lib/gitlab/git/push.rb | 4 ++-- spec/lib/gitlab/git/push_spec.rb | 38 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/git/push.rb b/lib/gitlab/git/push.rb index 603f860e4f8..7c1309721fd 100644 --- a/lib/gitlab/git/push.rb +++ b/lib/gitlab/git/push.rb @@ -9,8 +9,8 @@ module Gitlab def initialize(project, oldrev, newrev, ref) @project = project - @oldrev = oldrev - @newrev = newrev + @oldrev = oldrev.presence || Gitlab::Git::BLANK_SHA + @newrev = newrev.presence || Gitlab::Git::BLANK_SHA @ref = ref end diff --git a/spec/lib/gitlab/git/push_spec.rb b/spec/lib/gitlab/git/push_spec.rb index 2a7a357f384..566c8209504 100644 --- a/spec/lib/gitlab/git/push_spec.rb +++ b/spec/lib/gitlab/git/push_spec.rb @@ -63,6 +63,12 @@ describe Gitlab::Git::Push do it { is_expected.not_to be_branch_updated } end + + context 'when oldrev is nil' do + let(:oldrev) { nil } + + it { is_expected.not_to be_branch_updated } + end end describe '#force_push?' do @@ -125,4 +131,36 @@ describe Gitlab::Git::Push do end end end + + describe '#oldrev' do + context 'when a valid oldrev is provided' do + it 'returns oldrev' do + expect(subject.oldrev).to eq oldrev + end + end + + context 'when a nil valud is provided' do + let(:oldrev) { nil } + + it 'returns blank SHA' do + expect(subject.oldrev).to eq Gitlab::Git::BLANK_SHA + end + end + end + + describe '#newrev' do + context 'when valid newrev is provided' do + it 'returns newrev' do + expect(subject.newrev).to eq newrev + end + end + + context 'when a nil valud is provided' do + let(:newrev) { nil } + + it 'returns blank SHA' do + expect(subject.newrev).to eq Gitlab::Git::BLANK_SHA + end + end + end end -- cgit v1.2.1 From 1490933090166d71ce009708a70a16b156415052 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 2 Oct 2018 13:10:56 +0200 Subject: Expose paths modified by a push from a pipeline class --- app/models/ci/pipeline.rb | 28 ++++++++++++++++++++++ spec/models/ci/pipeline_spec.rb | 51 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 6dac577c514..c0fb79a37e2 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -627,6 +627,18 @@ module Ci end end + def branch_updated? + strong_memoize(:branch_updated) do + push_details.branch_updated? + end + end + + def modified_paths + strong_memoize(:changes) do + push_details.modified_paths + end + end + private def ci_yaml_from_repo @@ -650,6 +662,22 @@ module Ci Gitlab::DataBuilder::Pipeline.build(self) end + def push_details + strong_memoize(:push_details) do + Gitlab::Git::Push.new(project, before_sha, sha, push_ref) + end + end + + def push_ref + if branch? + Gitlab::Git::BRANCH_REF_PREFIX + ref.to_s + elsif tag? + Gitlab::Git::TAG_REF_PREFIX + ref.to_s + else + raise ArgumentError, 'Invalid pipeline type!' + end + end + def latest_builds_status return 'failed' unless yaml_errors.blank? diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 4755702c0e9..1060417b03b 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -825,6 +825,57 @@ describe Ci::Pipeline, :mailer do end end + describe '#branch_updated?' do + context 'when pipeline has before SHA' do + before do + pipeline.update_column(:before_sha, 'a1b2c3d4') + end + + it 'runs on a branch update push' do + expect(pipeline.before_sha).not_to be Gitlab::Git::BLANK_SHA + expect(pipeline.branch_updated?).to be true + end + end + + context 'when pipeline does not have before SHA' do + before do + pipeline.update_column(:before_sha, Gitlab::Git::BLANK_SHA) + end + + it 'does not run on a branch updating push' do + expect(pipeline.branch_updated?).to be false + end + end + end + + describe '#modified_paths' do + context 'when old and new revisions are set' do + let(:project) { create(:project, :repository) } + + before do + pipeline.update(before_sha: '1234abcd', sha: '2345bcde') + end + + it 'fetches stats for changes between commits' do + expect(project.repository) + .to receive(:diff_stats).with('1234abcd', '2345bcde') + .and_call_original + + pipeline.modified_paths + end + end + + context 'when either old or new revision is missing' do + before do + pipeline.update_column(:before_sha, Gitlab::Git::BLANK_SHA) + end + + it 'raises an error' do + expect { pipeline.modified_paths }.to raise_error(ArgumentError) + end + end + end + describe '#has_kubernetes_active?' do context 'when kubernetes is active' do shared_examples 'same behavior between KubernetesService and Platform::Kubernetes' do -- cgit v1.2.1 From 740ee583b3b0950c9b8ab5346e749b52355ec416 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 2 Oct 2018 14:03:20 +0200 Subject: Make it possible to specifiy only: changes keywords --- lib/gitlab/ci/config/entry/policy.rb | 6 ++++-- spec/lib/gitlab/ci/config/entry/policy_spec.rb | 20 +++++++++++++++++++- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/ci/config/entry/policy.rb b/lib/gitlab/ci/config/entry/policy.rb index 09e8e52b60f..6ba2d9bbf50 100644 --- a/lib/gitlab/ci/config/entry/policy.rb +++ b/lib/gitlab/ci/config/entry/policy.rb @@ -25,17 +25,19 @@ module Gitlab include Entry::Validatable include Entry::Attributable - attributes :refs, :kubernetes, :variables + ALLOWED_KEYS = %i[refs kubernetes variables changes] + attributes :refs, :kubernetes, :variables, :changes validations do validates :config, presence: true - validates :config, allowed_keys: %i[refs kubernetes variables] + validates :config, allowed_keys: ALLOWED_KEYS validate :variables_expressions_syntax with_options allow_nil: true do validates :refs, array_of_strings_or_regexps: true validates :kubernetes, allowed_values: %w[active] validates :variables, array_of_strings: true + validates :changes, array_of_strings: true end def variables_expressions_syntax diff --git a/spec/lib/gitlab/ci/config/entry/policy_spec.rb b/spec/lib/gitlab/ci/config/entry/policy_spec.rb index 83d39b82068..bef93fe7af7 100644 --- a/spec/lib/gitlab/ci/config/entry/policy_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/policy_spec.rb @@ -1,4 +1,5 @@ -require 'spec_helper' +require 'fast_spec_helper' +require_dependency 'active_model' describe Gitlab::Ci::Config::Entry::Policy do let(:entry) { described_class.new(config) } @@ -124,6 +125,23 @@ describe Gitlab::Ci::Config::Entry::Policy do end end + context 'when specifying a valid changes policy' do + let(:config) { { changes: %w[some/* paths/**/*.rb] } } + + it 'is a correct configuraton' do + expect(entry).to be_valid + expect(entry.value).to eq(config) + end + end + + context 'when changes policy is invalid' do + let(:config) { { changes: [1, 2] } } + + it 'returns errors' do + expect(entry.errors).to include /changes should be an array of strings/ + end + end + context 'when specifying unknown policy' do let(:config) { { refs: ['master'], invalid: :something } } -- cgit v1.2.1 From 0f78ceca1bb5b9db9760a52a20b96e84892bd9ad Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 2 Oct 2018 14:48:18 +0200 Subject: Add only/except pipeline build policy for `changes` --- lib/gitlab/ci/build/policy/changes.rb | 21 ++++++++ spec/lib/gitlab/ci/build/policy/changes_spec.rb | 69 +++++++++++++++++++++++++ 2 files changed, 90 insertions(+) create mode 100644 lib/gitlab/ci/build/policy/changes.rb create mode 100644 spec/lib/gitlab/ci/build/policy/changes_spec.rb diff --git a/lib/gitlab/ci/build/policy/changes.rb b/lib/gitlab/ci/build/policy/changes.rb new file mode 100644 index 00000000000..4bcb27ad73b --- /dev/null +++ b/lib/gitlab/ci/build/policy/changes.rb @@ -0,0 +1,21 @@ +module Gitlab + module Ci + module Build + module Policy + class Changes < Policy::Specification + def initialize(globs) + @globs = Array(globs) + end + + def satisfied_by?(pipeline, seed) + return true unless pipeline.branch_updated? + + pipeline.modified_paths.any? do |path| + @globs.any? { |glob| File.fnmatch?(glob, path, File::FNM_PATHNAME) } + end + end + end + end + end + end +end diff --git a/spec/lib/gitlab/ci/build/policy/changes_spec.rb b/spec/lib/gitlab/ci/build/policy/changes_spec.rb new file mode 100644 index 00000000000..1eb1fbb67e7 --- /dev/null +++ b/spec/lib/gitlab/ci/build/policy/changes_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +describe Gitlab::Ci::Build::Policy::Changes do + set(:project) { create(:project) } + + let(:pipeline) do + build(:ci_empty_pipeline, project: project, + ref: 'master', + source: :push, + sha: '1234abcd', + before_sha: '0123aabb') + end + + let(:ci_build) do + build(:ci_build, pipeline: pipeline, project: project, ref: 'master') + end + + let(:seed) { double('build seed', to_resource: ci_build) } + + before do + allow(pipeline).to receive(:modified_paths) do + %w[some/modified/ruby/file.rb some/other_file.txt] + end + end + + describe '#satisfied_by?' do + it 'is satisfied by matching literal path' do + policy = described_class.new(%w[some/other_file.txt]) + + expect(policy).to be_satisfied_by(pipeline, seed) + end + + it 'is satisfied by matching simple pattern' do + policy = described_class.new(%w[some/*.txt]) + + expect(policy).to be_satisfied_by(pipeline, seed) + end + + it 'is satisfied by matching recusive pattern' do + policy = described_class.new(%w[some/**/*.rb]) + + expect(policy).to be_satisfied_by(pipeline, seed) + end + + it 'is not satisfied when pattern does not match path' do + policy = described_class.new(%w[some/*.rb]) + + expect(policy).not_to be_satisfied_by(pipeline, seed) + end + + it 'is not satisfied when pattern does not match' do + policy = described_class.new(%w[invalid/*.md]) + + expect(policy).not_to be_satisfied_by(pipeline, seed) + end + + context 'when pipelines does not run for a branch update' do + before do + pipeline.before_sha = Gitlab::Git::BLANK_SHA + end + + it 'is always satisfied' do + policy = described_class.new(%w[invalid/*]) + + expect(policy).to be_satisfied_by(pipeline, seed) + end + end + end +end -- cgit v1.2.1 From b772e7f4c63ffa12cba5df200352b509a26f3261 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 2 Oct 2018 15:04:32 +0200 Subject: Match a dot in paths configured for only: changes --- lib/gitlab/ci/build/policy/changes.rb | 4 +++- spec/lib/gitlab/ci/build/policy/changes_spec.rb | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/ci/build/policy/changes.rb b/lib/gitlab/ci/build/policy/changes.rb index 4bcb27ad73b..dc4f07bf05b 100644 --- a/lib/gitlab/ci/build/policy/changes.rb +++ b/lib/gitlab/ci/build/policy/changes.rb @@ -11,7 +11,9 @@ module Gitlab return true unless pipeline.branch_updated? pipeline.modified_paths.any? do |path| - @globs.any? { |glob| File.fnmatch?(glob, path, File::FNM_PATHNAME) } + @globs.any? do |glob| + File.fnmatch?(glob, path, File::FNM_PATHNAME | File::FNM_DOTMATCH) + end end end end diff --git a/spec/lib/gitlab/ci/build/policy/changes_spec.rb b/spec/lib/gitlab/ci/build/policy/changes_spec.rb index 1eb1fbb67e7..0ac611904cb 100644 --- a/spec/lib/gitlab/ci/build/policy/changes_spec.rb +++ b/spec/lib/gitlab/ci/build/policy/changes_spec.rb @@ -19,7 +19,7 @@ describe Gitlab::Ci::Build::Policy::Changes do before do allow(pipeline).to receive(:modified_paths) do - %w[some/modified/ruby/file.rb some/other_file.txt] + %w[some/modified/ruby/file.rb some/other_file.txt some/.dir/file] end end @@ -42,6 +42,12 @@ describe Gitlab::Ci::Build::Policy::Changes do expect(policy).to be_satisfied_by(pipeline, seed) end + it 'is satisfied by matching a pattern with a dot' do + policy = described_class.new(%w[some/*/file]) + + expect(policy).to be_satisfied_by(pipeline, seed) + end + it 'is not satisfied when pattern does not match path' do policy = described_class.new(%w[some/*.rb]) -- cgit v1.2.1 From c945112093d28437d3de6a75a3e4983be7198afe Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 2 Oct 2018 15:56:17 +0200 Subject: Add Gitaly integration tests for only: changes feature --- spec/lib/gitlab/ci/build/policy/changes_spec.rb | 122 +++++++++++++++--------- spec/lib/gitlab/ci/yaml_processor_spec.rb | 10 +- 2 files changed, 86 insertions(+), 46 deletions(-) diff --git a/spec/lib/gitlab/ci/build/policy/changes_spec.rb b/spec/lib/gitlab/ci/build/policy/changes_spec.rb index 0ac611904cb..ab401108c84 100644 --- a/spec/lib/gitlab/ci/build/policy/changes_spec.rb +++ b/spec/lib/gitlab/ci/build/policy/changes_spec.rb @@ -3,73 +3,105 @@ require 'spec_helper' describe Gitlab::Ci::Build::Policy::Changes do set(:project) { create(:project) } - let(:pipeline) do - build(:ci_empty_pipeline, project: project, - ref: 'master', - source: :push, - sha: '1234abcd', - before_sha: '0123aabb') - end + describe '#satisfied_by?' do + describe 'paths matching matching' do + let(:pipeline) do + build(:ci_empty_pipeline, project: project, + ref: 'master', + source: :push, + sha: '1234abcd', + before_sha: '0123aabb') + end - let(:ci_build) do - build(:ci_build, pipeline: pipeline, project: project, ref: 'master') - end + let(:ci_build) do + build(:ci_build, pipeline: pipeline, project: project, ref: 'master') + end - let(:seed) { double('build seed', to_resource: ci_build) } + let(:seed) { double('build seed', to_resource: ci_build) } - before do - allow(pipeline).to receive(:modified_paths) do - %w[some/modified/ruby/file.rb some/other_file.txt some/.dir/file] - end - end + before do + allow(pipeline).to receive(:modified_paths) do + %w[some/modified/ruby/file.rb some/other_file.txt some/.dir/file] + end + end - describe '#satisfied_by?' do - it 'is satisfied by matching literal path' do - policy = described_class.new(%w[some/other_file.txt]) + it 'is satisfied by matching literal path' do + policy = described_class.new(%w[some/other_file.txt]) - expect(policy).to be_satisfied_by(pipeline, seed) - end + expect(policy).to be_satisfied_by(pipeline, seed) + end - it 'is satisfied by matching simple pattern' do - policy = described_class.new(%w[some/*.txt]) + it 'is satisfied by matching simple pattern' do + policy = described_class.new(%w[some/*.txt]) - expect(policy).to be_satisfied_by(pipeline, seed) - end + expect(policy).to be_satisfied_by(pipeline, seed) + end - it 'is satisfied by matching recusive pattern' do - policy = described_class.new(%w[some/**/*.rb]) + it 'is satisfied by matching recusive pattern' do + policy = described_class.new(%w[some/**/*.rb]) - expect(policy).to be_satisfied_by(pipeline, seed) - end + expect(policy).to be_satisfied_by(pipeline, seed) + end - it 'is satisfied by matching a pattern with a dot' do - policy = described_class.new(%w[some/*/file]) + it 'is satisfied by matching a pattern with a dot' do + policy = described_class.new(%w[some/*/file]) - expect(policy).to be_satisfied_by(pipeline, seed) - end + expect(policy).to be_satisfied_by(pipeline, seed) + end - it 'is not satisfied when pattern does not match path' do - policy = described_class.new(%w[some/*.rb]) + it 'is not satisfied when pattern does not match path' do + policy = described_class.new(%w[some/*.rb]) - expect(policy).not_to be_satisfied_by(pipeline, seed) - end + expect(policy).not_to be_satisfied_by(pipeline, seed) + end + + it 'is not satisfied when pattern does not match' do + policy = described_class.new(%w[invalid/*.md]) + + expect(policy).not_to be_satisfied_by(pipeline, seed) + end + + context 'when pipelines does not run for a branch update' do + before do + pipeline.before_sha = Gitlab::Git::BLANK_SHA + end - it 'is not satisfied when pattern does not match' do - policy = described_class.new(%w[invalid/*.md]) + it 'is always satisfied' do + policy = described_class.new(%w[invalid/*]) - expect(policy).not_to be_satisfied_by(pipeline, seed) + expect(policy).to be_satisfied_by(pipeline, seed) + end + end end - context 'when pipelines does not run for a branch update' do - before do - pipeline.before_sha = Gitlab::Git::BLANK_SHA + describe 'gitaly integration' do + set(:project) { create(:project, :repository) } + + let(:pipeline) do + create(:ci_empty_pipeline, project: project, + ref: 'master', + source: :push, + sha: '498214d', + before_sha: '281d3a7') + end + + let(:build) do + create(:ci_build, pipeline: pipeline, project: project, ref: 'master') end - it 'is always satisfied' do - policy = described_class.new(%w[invalid/*]) + let(:seed) { double('build seed', to_resource: build) } + + it 'is satisfied by changes introduced by a push' do + policy = described_class.new(['with space/*.md']) expect(policy).to be_satisfied_by(pipeline, seed) end + + it 'is not satisfied by changes that are not in the push' do + policy = described_class.new(%w[files/js/commit.js]) + + expect(policy).not_to be_satisfied_by(pipeline, seed) + end end end end diff --git a/spec/lib/gitlab/ci/yaml_processor_spec.rb b/spec/lib/gitlab/ci/yaml_processor_spec.rb index a2d429fa859..564635cec2b 100644 --- a/spec/lib/gitlab/ci/yaml_processor_spec.rb +++ b/spec/lib/gitlab/ci/yaml_processor_spec.rb @@ -1354,7 +1354,7 @@ module Gitlab end.to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, "jobs:rspec dependencies should be an array of strings") end - it 'returns errors if pipeline variables expression is invalid' do + it 'returns errors if pipeline variables expression policy is invalid' do config = YAML.dump({ rspec: { script: 'test', only: { variables: ['== null'] } } }) expect { Gitlab::Ci::YamlProcessor.new(config) } @@ -1362,6 +1362,14 @@ module Gitlab 'jobs:rspec:only variables invalid expression syntax') end + it 'returns errors if pipeline changes policy is invalid' do + config = YAML.dump({ rspec: { script: 'test', only: { changes: [1] } } }) + + expect { Gitlab::Ci::YamlProcessor.new(config) } + .to raise_error(Gitlab::Ci::YamlProcessor::ValidationError, + 'jobs:rspec:only changes should be an array of strings') + end + it 'returns errors if extended hash configuration is invalid' do config = YAML.dump({ rspec: { extends: 'something', script: 'test' } }) -- cgit v1.2.1 From 23512484ef08557b39ef82d0b767b76a33111308 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 2 Oct 2018 15:56:56 +0200 Subject: Freeze mutable constant in CI entry policy class --- lib/gitlab/ci/config/entry/policy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/ci/config/entry/policy.rb b/lib/gitlab/ci/config/entry/policy.rb index 6ba2d9bbf50..c92562f8c85 100644 --- a/lib/gitlab/ci/config/entry/policy.rb +++ b/lib/gitlab/ci/config/entry/policy.rb @@ -25,7 +25,7 @@ module Gitlab include Entry::Validatable include Entry::Attributable - ALLOWED_KEYS = %i[refs kubernetes variables changes] + ALLOWED_KEYS = %i[refs kubernetes variables changes].freeze attributes :refs, :kubernetes, :variables, :changes validations do -- cgit v1.2.1 From 0972dbc799673654fed6f6507818bbf8bafe33a8 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 2 Oct 2018 15:59:08 +0200 Subject: Add frozen strong literal directive to policy changes class --- lib/gitlab/ci/build/policy/changes.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/gitlab/ci/build/policy/changes.rb b/lib/gitlab/ci/build/policy/changes.rb index dc4f07bf05b..7bf51519752 100644 --- a/lib/gitlab/ci/build/policy/changes.rb +++ b/lib/gitlab/ci/build/policy/changes.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + module Gitlab module Ci module Build -- cgit v1.2.1 From 0dd892aaa51e5e82c908dcbec5aef0082a9dd2c5 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 2 Oct 2018 16:00:16 +0200 Subject: Add changelog entry for only: changes feature --- .../feature-gb-pipeline-only-except-with-modified-paths.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/feature-gb-pipeline-only-except-with-modified-paths.yml diff --git a/changelogs/unreleased/feature-gb-pipeline-only-except-with-modified-paths.yml b/changelogs/unreleased/feature-gb-pipeline-only-except-with-modified-paths.yml new file mode 100644 index 00000000000..62676cdad62 --- /dev/null +++ b/changelogs/unreleased/feature-gb-pipeline-only-except-with-modified-paths.yml @@ -0,0 +1,5 @@ +--- +title: Add support for pipeline only/except policy for modified paths +merge_request: 21981 +author: +type: added -- cgit v1.2.1 From a52960f5a1aa039a57c17a577aeb9fb82e238f4a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 2 Oct 2018 16:13:49 +0200 Subject: Add basic documentation for only: changes feature --- doc/ci/yaml/README.md | 37 ++++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index e38628b288b..d1ae7326852 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -387,6 +387,8 @@ except master. > `refs` and `kubernetes` policies introduced in GitLab 10.0 > > `variables` policy introduced in 10.7 +> +> `changes` policy introduced in 11.4 CAUTION: **Warning:** This an _alpha_ feature, and it it subject to change at any time without @@ -398,10 +400,15 @@ policy configuration. GitLab now supports both, simple and complex strategies, so it is possible to use an array and a hash configuration scheme. -Three keys are now available: `refs`, `kubernetes` and `variables`. +Four keys are now available: `refs`, `kubernetes` and `variables` and `changes`. + +### `refs` and `kubernetes` + Refs strategy equals to simplified only/except configuration, whereas kubernetes strategy accepts only `active` keyword. +### `variables` + `variables` keyword is used to define variables expressions. In other words you can use predefined variables / project / group or environment-scoped variables to define an expression GitLab is going to @@ -445,6 +452,34 @@ end-to-end: Learn more about variables expressions on [a separate page][variables-expressions]. +### `changes` + +Using `changes` keyword with `only` or `except` makes it possible to define if +a job should be created, based on files modified by a git push event. + +```yaml +docker build: + script: docker build -t my-image:$CI_COMMIT_REF_SLUG . + only: + changes: + - Dockerfile + - docker/scripts/* +``` + +In the scenario above, if you are pushing multiple commits to GitLab, to an +exising branch, GitLab is going to create and trigger `docker build` if one of +the commits contains changes to the `Dockerfile` file or any of the files +inside `docker/scripts/` directory. + +CAUTION: **Warning:** +If you are pushing a **new** branch or a tag to GitLab, only/changes is going +to always evaluate to truth and GitLab will create a job. This feature is not +connected with merge requests yet, GitLab is creating pipelines before an user +creates a merge requests and specifies a target branch. Without a target branch +it is not possible to know what the common ancestor is in case of pushing a new +branch, thus we always create a job in that case. This feature works best for +stable branches like `master`. + ## `tags` `tags` is used to select specific Runners from the list of all Runners that are -- cgit v1.2.1 From 09075759a428220ddfb5dacf6a6974c11956e391 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Tue, 2 Oct 2018 16:21:53 +0200 Subject: Copy-edit docs for only: changes feature --- doc/ci/yaml/README.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index d1ae7326852..ef9a00266e1 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -467,18 +467,19 @@ docker build: ``` In the scenario above, if you are pushing multiple commits to GitLab, to an -exising branch, GitLab is going to create and trigger `docker build` if one of -the commits contains changes to the `Dockerfile` file or any of the files -inside `docker/scripts/` directory. +exising branch, GitLab is going to create and trigger `docker build` job, +provided that one of the commits contains changes to the `Dockerfile` file or +changes to any of the files inside `docker/scripts/` directory. CAUTION: **Warning:** -If you are pushing a **new** branch or a tag to GitLab, only/changes is going -to always evaluate to truth and GitLab will create a job. This feature is not -connected with merge requests yet, GitLab is creating pipelines before an user -creates a merge requests and specifies a target branch. Without a target branch -it is not possible to know what the common ancestor is in case of pushing a new -branch, thus we always create a job in that case. This feature works best for -stable branches like `master`. +If you are pushing a **new** branch or a new tag to GitLab, only/changes is +going to always evaluate to truth and GitLab will create a job. This feature is +not combined with merge requests yet, and because GitLab is creating pipelines +before an user can create a merge request we don't know a target branch at +this point. Without a target branchit is not possible to know what the common +ancestor is, thus we always create a job in that case. This feature works best for +stable branches like `master` because in that case GitLab uses previous commit, +that is present in a branch, to compare against a newly pushed latest SHA. ## `tags` -- cgit v1.2.1 From 9ba554c8a053c5c9ad52a4e38956c4b9a6f140f7 Mon Sep 17 00:00:00 2001 From: Brett Walker Date: Thu, 27 Sep 2018 13:58:23 -0500 Subject: Filter system notes with public and private cross references --- app/models/note.rb | 27 +++++---- app/models/system_note_metadata.rb | 5 ++ ...urity-fix-leaking-private-project-namespace.yml | 5 ++ lib/banzai/object_renderer.rb | 1 + lib/banzai/redactor.rb | 8 ++- spec/models/note_spec.rb | 67 +++++++++++++++------- 6 files changed, 80 insertions(+), 33 deletions(-) create mode 100644 changelogs/unreleased/security-fix-leaking-private-project-namespace.yml diff --git a/app/models/note.rb b/app/models/note.rb index bea02d69b65..1b595ef60b4 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -38,10 +38,12 @@ class Note < ActiveRecord::Base alias_attribute :last_edited_at, :updated_at alias_attribute :last_edited_by, :updated_by - # Attribute containing rendered and redacted Markdown as generated by - # Banzai::ObjectRenderer. + # Number of user visible references as generated by Banzai::ObjectRenderer attr_accessor :redacted_note_html + # Total of all references as generated by Banzai::ObjectRenderer + attr_accessor :total_reference_count + # An Array containing the number of visible references as generated by # Banzai::ObjectRenderer attr_accessor :user_visible_reference_count @@ -288,15 +290,7 @@ class Note < ActiveRecord::Base end def cross_reference_not_visible_for?(user) - cross_reference? && !has_referenced_mentionables?(user) - end - - def has_referenced_mentionables?(user) - if user_visible_reference_count.present? - user_visible_reference_count > 0 - else - referenced_mentionables(user).any? - end + cross_reference? && !all_referenced_mentionables_allowed?(user) end def award_emoji? @@ -466,9 +460,18 @@ class Note < ActiveRecord::Base self.discussion_id ||= discussion_class.discussion_id(self) end + def all_referenced_mentionables_allowed?(user) + if user_visible_reference_count.present? && total_reference_count.present? + # if they are not equal, then there are private/confidential references as well + user_visible_reference_count > 0 && user_visible_reference_count == total_reference_count + else + referenced_mentionables(user).any? + end + end + def force_cross_reference_regex_check? return unless system? - SystemNoteMetadata::TYPES_WITH_CROSS_REFERENCES.include?(system_note_metadata&.action) + system_note_metadata&.cross_reference_types&.include?(system_note_metadata&.action) end end diff --git a/app/models/system_note_metadata.rb b/app/models/system_note_metadata.rb index 6fadbcefa53..d555ebe5322 100644 --- a/app/models/system_note_metadata.rb +++ b/app/models/system_note_metadata.rb @@ -9,6 +9,7 @@ class SystemNoteMetadata < ActiveRecord::Base TYPES_WITH_CROSS_REFERENCES = %w[ commit cross_reference close duplicate + moved ].freeze ICON_TYPES = %w[ @@ -26,4 +27,8 @@ class SystemNoteMetadata < ActiveRecord::Base def icon_types ICON_TYPES end + + def cross_reference_types + TYPES_WITH_CROSS_REFERENCES + end end diff --git a/changelogs/unreleased/security-fix-leaking-private-project-namespace.yml b/changelogs/unreleased/security-fix-leaking-private-project-namespace.yml new file mode 100644 index 00000000000..589d16c0c35 --- /dev/null +++ b/changelogs/unreleased/security-fix-leaking-private-project-namespace.yml @@ -0,0 +1,5 @@ +--- +title: Properly filter private references from system notes +merge_request: +author: +type: security diff --git a/lib/banzai/object_renderer.rb b/lib/banzai/object_renderer.rb index a176f1e261b..7137c1da57d 100644 --- a/lib/banzai/object_renderer.rb +++ b/lib/banzai/object_renderer.rb @@ -38,6 +38,7 @@ module Banzai redacted_data = redacted[index] object.__send__("redacted_#{attribute}_html=", redacted_data[:document].to_html(save_options).html_safe) # rubocop:disable GitlabSecurity/PublicSend object.user_visible_reference_count = redacted_data[:visible_reference_count] if object.respond_to?(:user_visible_reference_count) + object.total_reference_count = redacted_data[:total_reference_count] if object.respond_to?(:total_reference_count) end end diff --git a/lib/banzai/redactor.rb b/lib/banzai/redactor.rb index 28928d6f376..e77bee78496 100644 --- a/lib/banzai/redactor.rb +++ b/lib/banzai/redactor.rb @@ -37,7 +37,13 @@ module Banzai all_document_nodes.each do |entry| nodes_for_document = entry[:nodes] - doc_data = { document: entry[:document], visible_reference_count: nodes_for_document.count } + + doc_data = { + document: entry[:document], + total_reference_count: nodes_for_document.count, + visible_reference_count: nodes_for_document.count + } + metadata << doc_data nodes_for_document.each do |node| diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index 947be44c903..1783dd3206b 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -231,33 +231,60 @@ describe Note do let(:ext_proj) { create(:project, :public) } let(:ext_issue) { create(:issue, project: ext_proj) } - let(:note) do - create :note, - noteable: ext_issue, project: ext_proj, - note: "mentioned in issue #{private_issue.to_reference(ext_proj)}", - system: true - end + shared_examples "checks references" do + it "returns true" do + expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_truthy + end - it "returns true" do - expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_truthy - end + it "returns false" do + expect(note.cross_reference_not_visible_for?(private_user)).to be_falsy + end - it "returns false" do - expect(note.cross_reference_not_visible_for?(private_user)).to be_falsy + it "returns false if user visible reference count set" do + note.user_visible_reference_count = 1 + note.total_reference_count = 1 + + expect(note).not_to receive(:reference_mentionables) + expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_falsy + end + + it "returns true if ref count is 0" do + note.user_visible_reference_count = 0 + + expect(note).not_to receive(:reference_mentionables) + expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_truthy + end end - it "returns false if user visible reference count set" do - note.user_visible_reference_count = 1 + context "when there is one reference in note" do + let(:note) do + create :note, + noteable: ext_issue, project: ext_proj, + note: "mentioned in issue #{private_issue.to_reference(ext_proj)}", + system: true + end - expect(note).not_to receive(:reference_mentionables) - expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_falsy + it_behaves_like "checks references" end - it "returns true if ref count is 0" do - note.user_visible_reference_count = 0 + context "when there are two references in note" do + let(:note) do + create :note, + noteable: ext_issue, project: ext_proj, + note: "mentioned in issue #{private_issue.to_reference(ext_proj)} and " \ + "public issue #{ext_issue.to_reference(ext_proj)}", + system: true + end + + it_behaves_like "checks references" - expect(note).not_to receive(:reference_mentionables) - expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_truthy + it "returns true if user visible reference count set and there is a private reference" do + note.user_visible_reference_count = 1 + note.total_reference_count = 2 + + expect(note).not_to receive(:reference_mentionables) + expect(note.cross_reference_not_visible_for?(ext_issue.author)).to be_truthy + end end end @@ -269,7 +296,7 @@ describe Note do end context 'when the note might contain cross references' do - SystemNoteMetadata::TYPES_WITH_CROSS_REFERENCES.each do |type| + SystemNoteMetadata.new.cross_reference_types.each do |type| let(:note) { create(:note, :system) } let!(:metadata) { create(:system_note_metadata, note: note, action: type) } -- cgit v1.2.1 From 4000358531668e07021160e436f3cdc1b873e9d7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Wed, 3 Oct 2018 12:21:41 +0200 Subject: Few minor fixed in code and docs for only: changes --- app/models/ci/pipeline.rb | 2 +- doc/ci/yaml/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index c0fb79a37e2..a51745d41bb 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -634,7 +634,7 @@ module Ci end def modified_paths - strong_memoize(:changes) do + strong_memoize(:modified_paths) do push_details.modified_paths end end diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index ef9a00266e1..0d07b63a2dc 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -476,7 +476,7 @@ If you are pushing a **new** branch or a new tag to GitLab, only/changes is going to always evaluate to truth and GitLab will create a job. This feature is not combined with merge requests yet, and because GitLab is creating pipelines before an user can create a merge request we don't know a target branch at -this point. Without a target branchit is not possible to know what the common +this point. Without a target branch it is not possible to know what the common ancestor is, thus we always create a job in that case. This feature works best for stable branches like `master` because in that case GitLab uses previous commit, that is present in a branch, to compare against a newly pushed latest SHA. -- cgit v1.2.1 From 07c3778aee658a7e77c14f22745f78893da023f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarka=20Ko=C5=A1anov=C3=A1?= Date: Fri, 28 Sep 2018 12:57:58 +0200 Subject: Add checks to InterpretService conditions --- app/services/quick_actions/interpret_service.rb | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/app/services/quick_actions/interpret_service.rb b/app/services/quick_actions/interpret_service.rb index 8933bef29ee..defa579f9a8 100644 --- a/app/services/quick_actions/interpret_service.rb +++ b/app/services/quick_actions/interpret_service.rb @@ -210,9 +210,14 @@ module QuickActions end params '~label1 ~"label 2"' condition do - available_labels = LabelsFinder.new(current_user, project_id: project.id, include_ancestor_groups: true).execute + if project + available_labels = LabelsFinder + .new(current_user, project_id: project.id, include_ancestor_groups: true) + .execute + end - current_user.can?(:"admin_#{issuable.to_ability_name}", project) && + project && + current_user.can?(:"admin_#{issuable.to_ability_name}", project) && available_labels.any? end command :label do |labels_param| @@ -286,7 +291,8 @@ module QuickActions end params '#issue | !merge_request' condition do - current_user.can?(:"update_#{issuable.to_ability_name}", issuable) + [MergeRequest, Issue].include?(issuable.class) && + current_user.can?(:"update_#{issuable.to_ability_name}", issuable) end parse_params do |issuable_param| extract_references(issuable_param, :issue).first || @@ -443,7 +449,8 @@ module QuickActions end params ' ' condition do - current_user.can?(:"admin_#{issuable.to_ability_name}", issuable) + issuable.is_a?(TimeTrackable) && + current_user.can?(:"admin_#{issuable.to_ability_name}", issuable) end parse_params do |raw_time_date| Gitlab::QuickActions::SpendTimeAndDateSeparator.new(raw_time_date).execute @@ -493,7 +500,7 @@ module QuickActions desc "Lock the discussion" explanation "Locks the discussion" condition do - issuable.is_a?(Issuable) && + [MergeRequest, Issue].include?(issuable.class) && issuable.persisted? && !issuable.discussion_locked? && current_user.can?(:"admin_#{issuable.to_ability_name}", issuable) @@ -505,7 +512,7 @@ module QuickActions desc "Unlock the discussion" explanation "Unlocks the discussion" condition do - issuable.is_a?(Issuable) && + [MergeRequest, Issue].include?(issuable.class) && issuable.persisted? && issuable.discussion_locked? && current_user.can?(:"admin_#{issuable.to_ability_name}", issuable) -- cgit v1.2.1 From a1c3d40739ed133e1ca1cd9191628acf938809cf Mon Sep 17 00:00:00 2001 From: Jacopo Date: Wed, 3 Oct 2018 13:00:03 +0200 Subject: Allows to filter issues by `Any milestone` in the API In GET `api/v4/projects/:id/issues` the user can filter issues that have an assigned milestone through the parameter `milestone=Any+Milestone`. --- app/finders/issuable_finder.rb | 6 ++++++ app/models/concerns/issuable.rb | 1 + changelogs/unreleased/51748-filter-any-milestone-via-api.yml | 5 +++++ doc/api/issues.md | 2 +- spec/finders/issues_finder_spec.rb | 8 ++++++++ spec/requests/api/issues_spec.rb | 10 ++++++++++ 6 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/51748-filter-any-milestone-via-api.yml diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index 0209a1397b9..794c2f293c4 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -424,6 +424,10 @@ class IssuableFinder params[:milestone_title] == Milestone::Upcoming.name end + def filter_by_any_milestone? + params[:milestone_title] == Milestone::Any.title + end + def filter_by_started_milestone? params[:milestone_title] == Milestone::Started.name end @@ -433,6 +437,8 @@ class IssuableFinder if milestones? if filter_by_no_milestone? items = items.left_joins_milestones.where(milestone_id: [-1, nil]) + elsif filter_by_any_milestone? + items = items.any_milestone elsif filter_by_upcoming_milestone? upcoming_ids = Milestone.upcoming_ids_by_projects(projects(items)) items = items.left_joins_milestones.where(milestone_id: upcoming_ids) diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 5f65fceb7af..2aa52bbaeea 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -76,6 +76,7 @@ module Issuable scope :recent, -> { reorder(id: :desc) } scope :of_projects, ->(ids) { where(project_id: ids) } scope :of_milestones, ->(ids) { where(milestone_id: ids) } + scope :any_milestone, -> { where('milestone_id IS NOT NULL') } scope :with_milestone, ->(title) { left_joins_milestones.where(milestones: { title: title }) } scope :opened, -> { with_state(:opened) } scope :only_opened, -> { with_state(:opened) } diff --git a/changelogs/unreleased/51748-filter-any-milestone-via-api.yml b/changelogs/unreleased/51748-filter-any-milestone-via-api.yml new file mode 100644 index 00000000000..30304e5a4ac --- /dev/null +++ b/changelogs/unreleased/51748-filter-any-milestone-via-api.yml @@ -0,0 +1,5 @@ +--- +title: Allows to filter issues by Any milestone in the API +merge_request: 22080 +author: Jacopo Beschi @jacopo-beschi +type: added diff --git a/doc/api/issues.md b/doc/api/issues.md index f4c0f4ea65b..cc1d6834a20 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -37,7 +37,7 @@ GET /issues?my_reaction_emoji=star | ------------------- | ---------------- | ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | | `state` | string | no | Return all issues or just those that are `opened` or `closed` | | `labels` | string | no | Comma-separated list of label names, issues must have all labels to be returned. `No+Label` lists all issues with no labels | -| `milestone` | string | no | The milestone title. `No+Milestone` lists all issues with no milestone | +| `milestone` | string | no | The milestone title. `No+Milestone` lists all issues with no milestone. `Any+Milestone` lists all issues that have an assigned milestone | | `scope` | string | no | Return issues for the given scope: `created_by_me`, `assigned_to_me` or `all`. Defaults to `created_by_me`
For versions before 11.0, use the now deprecated `created-by-me` or `assigned-to-me` scopes instead.
_([Introduced][ce-13004] in GitLab 9.5. [Changed to snake_case][ce-18935] in GitLab 11.0)_ | | `author_id` | integer | no | Return issues created by the given user `id`. Combine with `scope=all` or `scope=assigned_to_me`. _([Introduced][ce-13004] in GitLab 9.5)_ | | `assignee_id` | integer | no | Return issues assigned to the given user `id` _([Introduced][ce-13004] in GitLab 9.5)_ | diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index d78451112ec..0689c843104 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -125,6 +125,14 @@ describe IssuesFinder do end end + context 'filtering by any milestone' do + let(:params) { { milestone_title: Milestone::Any.title } } + + it 'returns issues with any assigned milestone' do + expect(issues).to contain_exactly(issue1) + end + end + context 'filtering by upcoming milestone' do let(:params) { { milestone_title: Milestone::Upcoming.name } } diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 1e2e13a723c..9f6cf12f9a7 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -56,6 +56,7 @@ describe API::Issues do let!(:note) { create(:note_on_issue, author: user, project: project, noteable: issue) } let(:no_milestone_title) { URI.escape(Milestone::None.title) } + let(:any_milestone_title) { URI.escape(Milestone::Any.title) } before(:all) do project.add_reporter(user) @@ -811,6 +812,15 @@ describe API::Issues do expect(json_response.first['id']).to eq(confidential_issue.id) end + it 'returns an array of issues with any milestone' do + get api("#{base_url}/issues?milestone=#{any_milestone_title}", user) + + response_ids = json_response.map { |issue| issue['id'] } + + expect_paginated_array_response(size: 2) + expect(response_ids).to contain_exactly(closed_issue.id, issue.id) + end + it 'sorts by created_at descending by default' do get api("#{base_url}/issues", user) -- cgit v1.2.1 From d49397f747bee52db9d6593e5656f98eb41953e2 Mon Sep 17 00:00:00 2001 From: Tiago Botelho Date: Wed, 3 Oct 2018 15:08:56 +0100 Subject: Instance Configuration page now displays correct SSH fingerprints Replaces the use of OpenSSL::Digest for Gitlab::SSHPublicKey#fingerprint --- app/models/instance_configuration.rb | 4 ++-- ...0636-instance-configuration-shows-incorrect-ssh-fingerprints.yml | 5 +++++ spec/fixtures/ssh_host_example_key.pub | 2 +- spec/models/instance_configuration_spec.rb | 6 +++--- 4 files changed, 11 insertions(+), 6 deletions(-) create mode 100644 changelogs/unreleased/40636-instance-configuration-shows-incorrect-ssh-fingerprints.yml diff --git a/app/models/instance_configuration.rb b/app/models/instance_configuration.rb index 7d8ce0bbd05..11289887e00 100644 --- a/app/models/instance_configuration.rb +++ b/app/models/instance_configuration.rb @@ -64,10 +64,10 @@ class InstanceConfiguration end def ssh_algorithm_md5(ssh_file_content) - OpenSSL::Digest::MD5.hexdigest(ssh_file_content).scan(/../).join(':') + Gitlab::SSHPublicKey.new(ssh_file_content).fingerprint end def ssh_algorithm_sha256(ssh_file_content) - OpenSSL::Digest::SHA256.hexdigest(ssh_file_content) + Gitlab::SSHPublicKey.new(ssh_file_content).fingerprint('SHA256') end end diff --git a/changelogs/unreleased/40636-instance-configuration-shows-incorrect-ssh-fingerprints.yml b/changelogs/unreleased/40636-instance-configuration-shows-incorrect-ssh-fingerprints.yml new file mode 100644 index 00000000000..1ebad500e9f --- /dev/null +++ b/changelogs/unreleased/40636-instance-configuration-shows-incorrect-ssh-fingerprints.yml @@ -0,0 +1,5 @@ +--- +title: Instance Configuration page now displays correct SSH fingerprints +merge_request: 22081 +author: +type: fixed diff --git a/spec/fixtures/ssh_host_example_key.pub b/spec/fixtures/ssh_host_example_key.pub index 6bac42b3ad0..d43315ddae8 100644 --- a/spec/fixtures/ssh_host_example_key.pub +++ b/spec/fixtures/ssh_host_example_key.pub @@ -1 +1 @@ -random content +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCuRkAgwaap/pXThwCpjX8Wd5tR36Tqx3sW2sVVHs3UKB7kd+xNknw7e4qpuEATv56xHrhKm2+ye/JidTuQ/1EwFhjaz7I5wTslfVawQpeH1ZqAGmvdO/xTw+l7fgEFVlGVx9y0HV3m52y2C9yw82qmg+BohbTVgPtjjutpFc+CwLQxLTnTrRhZf5udQgz+YlwLv+Y0kDx6+DWWOl8N9+TWuGyFKBln79CyBgFcK5NFmF48kYn8W+r7rmawfw9XbuF1aa+6JF+6cNR1mCEonyrRLdXP+vWcxpLKYfejB0NmA1y+W9M/K53AcIHA5zlRQ49tFh0P22eh/Gl8JQ6yyuin foo@bar.mynet diff --git a/spec/models/instance_configuration_spec.rb b/spec/models/instance_configuration_spec.rb index 34db94920f3..cb3d6c7cda2 100644 --- a/spec/models/instance_configuration_spec.rb +++ b/spec/models/instance_configuration_spec.rb @@ -1,11 +1,11 @@ require 'spec_helper' -RSpec.describe InstanceConfiguration do +describe InstanceConfiguration do context 'without cache' do describe '#settings' do describe '#ssh_algorithms_hashes' do - let(:md5) { '54:e0:f8:70:d6:4f:4c:b1:b3:02:44:77:cf:cd:0d:fc' } - let(:sha256) { '9327f0d15a48c4d9f6a3aee65a1825baf9a3412001c98169c5fd022ac27762fc' } + let(:md5) { '5a:65:6c:4d:d4:4c:6d:e6:59:25:b8:cf:ba:34:e7:64' } + let(:sha256) { 'SHA256:2KJDT7xf2i68mBgJ3TVsjISntg4droLbXYLfQj0VvSY' } it 'does not return anything if file does not exist' do stub_pub_file(exist: false) -- cgit v1.2.1 From 34687cf05deadccdd6b4b321c71d276ee0e72f49 Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Wed, 12 Sep 2018 11:53:42 +0300 Subject: Add reliable fetcher for Sidekiq --- Gemfile | 1 + Gemfile.lock | 3 +++ Gemfile.rails5.lock | 3 +++ changelogs/unreleased/add_reliable_fetcher.yml | 5 +++++ config/initializers/sidekiq.rb | 10 +++++++--- 5 files changed, 19 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/add_reliable_fetcher.yml diff --git a/Gemfile b/Gemfile index 1b35e2110d5..2df82a0a92e 100644 --- a/Gemfile +++ b/Gemfile @@ -299,6 +299,7 @@ gem 'peek-mysql2', '~> 1.1.0', group: :mysql gem 'peek-pg', '~> 1.3.0', group: :postgres gem 'peek-rblineprof', '~> 0.2.0' gem 'peek-redis', '~> 1.2.0' +gem 'gitlab-sidekiq-fetcher', require: 'sidekiq-reliable-fetch' # Metrics group :metrics do diff --git a/Gemfile.lock b/Gemfile.lock index 4de78f3ec44..d144d46dbe1 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -306,6 +306,8 @@ GEM mime-types (>= 1.16) posix-spawn (~> 0.3) gitlab-markup (1.6.4) + gitlab-sidekiq-fetcher (0.3.0) + sidekiq (~> 5) gitlab-styles (2.4.1) rubocop (~> 0.54.0) rubocop-gitlab-security (~> 0.1.0) @@ -1037,6 +1039,7 @@ DEPENDENCIES gitlab-gollum-lib (~> 4.2) gitlab-gollum-rugged_adapter (~> 0.4.4) gitlab-markup (~> 1.6.4) + gitlab-sidekiq-fetcher gitlab-styles (~> 2.4) gitlab_omniauth-ldap (~> 2.0.4) gon (~> 6.2) diff --git a/Gemfile.rails5.lock b/Gemfile.rails5.lock index 8f4e1550a52..f918410ea97 100644 --- a/Gemfile.rails5.lock +++ b/Gemfile.rails5.lock @@ -309,6 +309,8 @@ GEM mime-types (>= 1.16) posix-spawn (~> 0.3) gitlab-markup (1.6.4) + gitlab-sidekiq-fetcher (0.3.0) + sidekiq (~> 5) gitlab-styles (2.4.1) rubocop (~> 0.54.0) rubocop-gitlab-security (~> 0.1.0) @@ -1046,6 +1048,7 @@ DEPENDENCIES gitlab-gollum-lib (~> 4.2) gitlab-gollum-rugged_adapter (~> 0.4.4) gitlab-markup (~> 1.6.4) + gitlab-sidekiq-fetcher gitlab-styles (~> 2.4) gitlab_omniauth-ldap (~> 2.0.4) gon (~> 6.2) diff --git a/changelogs/unreleased/add_reliable_fetcher.yml b/changelogs/unreleased/add_reliable_fetcher.yml new file mode 100644 index 00000000000..c08c755e546 --- /dev/null +++ b/changelogs/unreleased/add_reliable_fetcher.yml @@ -0,0 +1,5 @@ +--- +title: Use Reliable Sidekiq fetch +merge_request: 21715 +author: +type: fixed diff --git a/config/initializers/sidekiq.rb b/config/initializers/sidekiq.rb index 6c1079faad1..bc6b7aed6aa 100644 --- a/config/initializers/sidekiq.rb +++ b/config/initializers/sidekiq.rb @@ -40,6 +40,10 @@ Sidekiq.configure_server do |config| ActiveRecord::Base.clear_all_connections! end + if Feature.enabled?(:gitlab_sidekiq_reliable_fetcher) + Sidekiq::ReliableFetcher.setup_reliable_fetch!(config) + end + # Sidekiq-cron: load recurring jobs from gitlab.yml # UGLY Hack to get nested hash from settingslogic cron_jobs = JSON.parse(Gitlab.config.cron_jobs.to_json) @@ -57,10 +61,10 @@ Sidekiq.configure_server do |config| Gitlab::SidekiqVersioning.install! - config = Gitlab::Database.config || + db_config = Gitlab::Database.config || Rails.application.config.database_configuration[Rails.env] - config['pool'] = Sidekiq.options[:concurrency] - ActiveRecord::Base.establish_connection(config) + db_config['pool'] = Sidekiq.options[:concurrency] + ActiveRecord::Base.establish_connection(db_config) Rails.logger.debug("Connection Pool size for Sidekiq Server is now: #{ActiveRecord::Base.connection.pool.instance_variable_get('@size')}") # Avoid autoload issue such as 'Mail::Parsers::AddressStruct' -- cgit v1.2.1 From 2334d9b6118f429209632285e90b6e9fd892f912 Mon Sep 17 00:00:00 2001 From: Johann Hubert Sonntagbauer Date: Tue, 2 Oct 2018 07:12:05 +0200 Subject: add percentage information to repository language bar --- app/assets/stylesheets/pages/projects.scss | 8 ++++++++ app/helpers/repository_languages_helper.rb | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 7c42dcad959..da3d8aa53ad 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -830,6 +830,14 @@ } } +.repository-language-bar-tooltip-language { + font-weight: $gl-font-weight-bold; +} + +.repository-language-bar-tooltip-share { + color: $theme-gray-400; +} + pre.light-well { border-color: $well-light-border; } diff --git a/app/helpers/repository_languages_helper.rb b/app/helpers/repository_languages_helper.rb index c1505b52808..4f32a6597e2 100644 --- a/app/helpers/repository_languages_helper.rb +++ b/app/helpers/repository_languages_helper.rb @@ -13,6 +13,7 @@ module RepositoryLanguagesHelper content_tag :div, nil, class: "progress-bar has-tooltip", style: "width: #{lang.share}%; background-color:#{lang.color}", - title: lang.name + :data => { :html => true}, + title: "#{lang.name} #{lang.share.round(1)}%" end end -- cgit v1.2.1 From bc17637ded0219022c06c409c7e393c925ab0d9a Mon Sep 17 00:00:00 2001 From: Johann Hubert Sonntagbauer Date: Tue, 2 Oct 2018 17:14:50 +0200 Subject: fix linting --- app/helpers/repository_languages_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/repository_languages_helper.rb b/app/helpers/repository_languages_helper.rb index 4f32a6597e2..8eb782a3c60 100644 --- a/app/helpers/repository_languages_helper.rb +++ b/app/helpers/repository_languages_helper.rb @@ -13,7 +13,7 @@ module RepositoryLanguagesHelper content_tag :div, nil, class: "progress-bar has-tooltip", style: "width: #{lang.share}%; background-color:#{lang.color}", - :data => { :html => true}, + data: { html: true }, title: "#{lang.name} #{lang.share.round(1)}%" end end -- cgit v1.2.1 From 98c4942672a4c65b998754137f0a88baac228f35 Mon Sep 17 00:00:00 2001 From: Johann Hubert Sonntagbauer Date: Tue, 2 Oct 2018 17:21:03 +0200 Subject: add changelog --- ...457-Show-percentage-of-language-detection-on-the-language-bar.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/-51457-Show-percentage-of-language-detection-on-the-language-bar.yml diff --git a/changelogs/unreleased/-51457-Show-percentage-of-language-detection-on-the-language-bar.yml b/changelogs/unreleased/-51457-Show-percentage-of-language-detection-on-the-language-bar.yml new file mode 100644 index 00000000000..074cc9d642b --- /dev/null +++ b/changelogs/unreleased/-51457-Show-percentage-of-language-detection-on-the-language-bar.yml @@ -0,0 +1,5 @@ +--- +title: Show percentage of language detection on the language bar +merge_request: 22056 +author: Johann Hubert Sonntagbauer +type: added -- cgit v1.2.1 From 2bda51f507f11408fe532c4b0ee13780dc3a1fc2 Mon Sep 17 00:00:00 2001 From: Johann Hubert Sonntagbauer Date: Wed, 3 Oct 2018 17:50:15 +0200 Subject: proper escape language name --- app/helpers/repository_languages_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/repository_languages_helper.rb b/app/helpers/repository_languages_helper.rb index 8eb782a3c60..cf7eee7fff3 100644 --- a/app/helpers/repository_languages_helper.rb +++ b/app/helpers/repository_languages_helper.rb @@ -14,6 +14,6 @@ module RepositoryLanguagesHelper class: "progress-bar has-tooltip", style: "width: #{lang.share}%; background-color:#{lang.color}", data: { html: true }, - title: "#{lang.name} #{lang.share.round(1)}%" + title: "#{escape_javascript(lang.name)} #{lang.share.round(1)}%" end end -- cgit v1.2.1 From f3834e9c2d00b20bb7c55cb8845a98f0d8c86443 Mon Sep 17 00:00:00 2001 From: Jacopo Date: Thu, 27 Sep 2018 17:35:15 +0200 Subject: Includes commit stats in POST project commits API --- .../51803-include-commits-stats-in-projects-api.yml | 5 +++++ doc/api/commits.md | 1 + lib/api/commits.rb | 3 ++- spec/requests/api/commits_spec.rb | 14 ++++++++++++++ 4 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/51803-include-commits-stats-in-projects-api.yml diff --git a/changelogs/unreleased/51803-include-commits-stats-in-projects-api.yml b/changelogs/unreleased/51803-include-commits-stats-in-projects-api.yml new file mode 100644 index 00000000000..e67cc27f852 --- /dev/null +++ b/changelogs/unreleased/51803-include-commits-stats-in-projects-api.yml @@ -0,0 +1,5 @@ +--- +title: Includes commit stats in POST project commits API +merge_request: 21968 +author: Jacopo Beschi @jacopo-beschi +type: fixed diff --git a/doc/api/commits.md b/doc/api/commits.md index 5ff1e1f60e0..9b7ca4b6e70 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -79,6 +79,7 @@ POST /projects/:id/repository/commits | `actions[]` | array | yes | An array of action hashes to commit as a batch. See the next table for what attributes it can take. | | `author_email` | string | no | Specify the commit author's email address | | `author_name` | string | no | Specify the commit author's name | +| `stats` | boolean | no | Include commit stats. Default is true | | `actions[]` Attribute | Type | Required | Description | diff --git a/lib/api/commits.rb b/lib/api/commits.rb index ff927d1aa3c..e59abd3e3d0 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -98,6 +98,7 @@ module API optional :start_branch, type: String, desc: 'Name of the branch to start the new commit from' optional :author_email, type: String, desc: 'Author email for commit' optional :author_name, type: String, desc: 'Author name for commit' + optional :stats, type: Boolean, default: true, desc: 'Include commit stats' end post ':id/repository/commits' do authorize_push_to_branch!(params[:branch]) @@ -113,7 +114,7 @@ module API Gitlab::WebIdeCommitsCounter.increment if find_user_from_warden - present commit_detail, with: Entities::CommitDetail + present commit_detail, with: Entities::CommitDetail, stats: params[:stats] else render_api_error!(result[:message], 400) end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index 06ccf383362..98399471f9a 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -572,6 +572,20 @@ describe API::Commits do expect(json_response['title']).to eq(message) end + it 'includes the commit stats' do + post api(url, user), valid_mo_params + + expect(response).to have_gitlab_http_status(201) + expect(json_response).to include 'stats' + end + + it "doesn't include the commit stats when stats is false" do + post api(url, user), valid_mo_params.merge(stats: false) + + expect(response).to have_gitlab_http_status(201) + expect(json_response).not_to include 'stats' + end + it 'return a 400 bad request if there are any issues' do post api(url, user), invalid_mo_params -- cgit v1.2.1 From df6e9ed38e7fd4c985a4809b09eedc886ebe6a54 Mon Sep 17 00:00:00 2001 From: Chantal Rollison Date: Fri, 28 Sep 2018 06:47:58 -0700 Subject: Create labels_as_hash_ concern, modify params for method --- app/controllers/concerns/labels_as_hash.rb | 28 +++++++++++++++++++++ app/services/projects/autocomplete_service.rb | 29 ++++------------------ .../services/projects/autocomplete_service_spec.rb | 4 +-- 3 files changed, 35 insertions(+), 26 deletions(-) create mode 100644 app/controllers/concerns/labels_as_hash.rb diff --git a/app/controllers/concerns/labels_as_hash.rb b/app/controllers/concerns/labels_as_hash.rb new file mode 100644 index 00000000000..1171aa9cf44 --- /dev/null +++ b/app/controllers/concerns/labels_as_hash.rb @@ -0,0 +1,28 @@ +# frozen_string_literal: true + +module LabelsAsHash + extend ActiveSupport::Concern + + def labels_as_hash(target = nil, params = {}) + available_labels = LabelsFinder.new( + current_user, + params + ).execute + + label_hashes = available_labels.as_json(only: [:title, :color]) + + if target&.respond_to?(:labels) + already_set_labels = available_labels & target.labels + if already_set_labels.present? + titles = already_set_labels.map(&:title) + label_hashes.each do |hash| + if titles.include?(hash['title']) + hash[:set] = true + end + end + end + end + + label_hashes + end +end diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb index 5286b92ab6b..7b747171d9c 100644 --- a/app/services/projects/autocomplete_service.rb +++ b/app/services/projects/autocomplete_service.rb @@ -2,6 +2,7 @@ module Projects class AutocompleteService < BaseService + include LabelsAsHash def issues IssuesFinder.new(current_user, project_id: project.id, state: 'opened').execute.select([:iid, :title]) end @@ -22,34 +23,14 @@ module Projects MergeRequestsFinder.new(current_user, project_id: project.id, state: 'opened').execute.select([:iid, :title]) end - def labels_as_hash(target = nil) - available_labels = LabelsFinder.new( - current_user, - project_id: project.id, - include_ancestor_groups: true - ).execute - - label_hashes = available_labels.as_json(only: [:title, :color]) - - if target&.respond_to?(:labels) - already_set_labels = available_labels & target.labels - if already_set_labels.present? - titles = already_set_labels.map(&:title) - label_hashes.each do |hash| - if titles.include?(hash['title']) - hash[:set] = true - end - end - end - end - - label_hashes - end - def commands(noteable, type) return [] unless noteable QuickActions::InterpretService.new(project, current_user).available_commands(noteable) end + + def labels_as_hash(target) + super(target, project_id: project.id, include_ancestor_groups: true) + end end end diff --git a/spec/services/projects/autocomplete_service_spec.rb b/spec/services/projects/autocomplete_service_spec.rb index e98df375d48..373fe7cb7dd 100644 --- a/spec/services/projects/autocomplete_service_spec.rb +++ b/spec/services/projects/autocomplete_service_spec.rb @@ -148,7 +148,7 @@ describe Projects::AutocompleteService do let!(:label1) { create(:label, project: project) } let!(:label2) { create(:label, project: project) } let!(:sub_group_label) { create(:group_label, group: sub_group) } - let!(:parent_group_label) { create(:group_label, group: group.parent) } + let!(:parent_group_label) { create(:group_label, group: group.parent, group_id: group.id) } before do create(:group_member, group: group, user: user) @@ -156,7 +156,7 @@ describe Projects::AutocompleteService do it 'returns labels from project and ancestor groups' do service = described_class.new(project, user) - results = service.labels_as_hash + results = service.labels_as_hash(nil) expected_labels = [label1, label2, parent_group_label] expect_labels_to_equal(results, expected_labels) -- cgit v1.2.1 From 7d55c1353d6402f33a9fef734148fb776da076d3 Mon Sep 17 00:00:00 2001 From: Ronald Claveau Date: Thu, 28 Jun 2018 08:13:21 +0200 Subject: List public ssh keys by id or username without authentication --- .../unreleased/features-unauth-access-ssh-keys.yml | 5 +++ doc/api/users.md | 2 +- lib/api/users.rb | 6 ++-- spec/requests/api/users_spec.rb | 38 ++++++++-------------- 4 files changed, 22 insertions(+), 29 deletions(-) create mode 100644 changelogs/unreleased/features-unauth-access-ssh-keys.yml diff --git a/changelogs/unreleased/features-unauth-access-ssh-keys.yml b/changelogs/unreleased/features-unauth-access-ssh-keys.yml new file mode 100644 index 00000000000..bae2bcfaabd --- /dev/null +++ b/changelogs/unreleased/features-unauth-access-ssh-keys.yml @@ -0,0 +1,5 @@ +--- +title: Enable unauthenticated access to public SSH keys via the API +merge_request: 20118 +author: Ronald Claveau +type: changed diff --git a/doc/api/users.md b/doc/api/users.md index 762ea53edee..433f5d30449 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -556,7 +556,7 @@ Parameters: ## List SSH keys for user -Get a list of a specified user's SSH keys. Available only for admin +Get a list of a specified user's SSH keys. ``` GET /users/:id/keys diff --git a/lib/api/users.rb b/lib/api/users.rb index ac09ca7f7b7..e96887948b1 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -254,7 +254,7 @@ module API end # rubocop: enable CodeReuse/ActiveRecord - desc 'Get the SSH keys of a specified user. Available only for admins.' do + desc 'Get the SSH keys of a specified user.' do success Entities::SSHKey end params do @@ -263,10 +263,8 @@ module API end # rubocop: disable CodeReuse/ActiveRecord get ':id/keys' do - authenticated_as_admin! - user = User.find_by(id: params[:id]) - not_found!('User') unless user + not_found!('User') unless user && can?(current_user, :read_user, user) present paginate(user.keys), with: Entities::SSHKey end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index b7d62df0663..09c1d016081 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -785,35 +785,25 @@ describe API::Users do end describe 'GET /user/:id/keys' do - before do - admin - end + it 'returns 404 for non-existing user' do + user_id = not_existing_user_id - context 'when unauthenticated' do - it 'returns authentication error' do - get api("/users/#{user.id}/keys") - expect(response).to have_gitlab_http_status(401) - end - end + get api("/users/#{user_id}/keys") - context 'when authenticated' do - it 'returns 404 for non-existing user' do - get api('/users/999999/keys', admin) - expect(response).to have_gitlab_http_status(404) - expect(json_response['message']).to eq('404 User Not Found') - end + expect(response).to have_gitlab_http_status(404) + expect(json_response['message']).to eq('404 User Not Found') + end - it 'returns array of ssh keys' do - user.keys << key - user.save + it 'returns array of ssh keys' do + user.keys << key + user.save - get api("/users/#{user.id}/keys", admin) + get api("/users/#{user.id}/keys") - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.first['title']).to eq(key.title) - end + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.first['title']).to eq(key.title) end end -- cgit v1.2.1 From cdc6fdb25f14197f26c45713683de20dfa34f72f Mon Sep 17 00:00:00 2001 From: Victor Wu Date: Wed, 3 Oct 2018 18:01:31 +0000 Subject: Docs lock/unlock quick action --- doc/user/project/quick_actions.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md index df045822740..21a9d2adf4f 100644 --- a/doc/user/project/quick_actions.md +++ b/doc/user/project/quick_actions.md @@ -38,7 +38,9 @@ discussions, and descriptions: | `/remove_estimate` | Remove time estimate | ✓ | ✓ | | /spend <time(1h 30m | -1h 5m)> <date(YYYY-MM-DD)> | Add or subtract spent time; optionally, specify the date that time was spent on | ✓ | ✓ | | `/remove_time_spent` | Remove time spent | ✓ | ✓ | -| /due <in 2 days | this Friday | December 31st>| Set due date | ✓ | +| `/lock` | Lock the discussion | ✓ | ✓ | +| `/unlock` | Unlock the discussion | ✓ | ✓ | +| /due <in 2 days | this Friday | December 31st>| Set due date | ✓ | | | `/remove_due_date` | Remove due date | ✓ | | | `/weight 0,1,2, ...` | Set weight **[STARTER]** | ✓ | | | `/clear_weight` | Clears weight **[STARTER]** | ✓ | | -- cgit v1.2.1 From 5a286eb7a3a0c395d35c722ce6a067aca47473f2 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Wed, 3 Oct 2018 01:44:18 -0500 Subject: Add signature badge to diffs/commit_item **Notes:** - Also exposes commit.signature_html in diffs.json --- .../javascripts/diffs/components/commit_item.vue | 4 ++++ .../projects/merge_requests/diffs_controller.rb | 8 +++++++- app/serializers/commit_entity.rb | 10 ++++++++++ spec/javascripts/diffs/components/commit_item_spec.js | 18 ++++++++++++++++++ spec/serializers/commit_entity_spec.rb | 16 +++++++++++++++- 5 files changed, 54 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue index 5758588e82e..4028002febd 100644 --- a/app/assets/javascripts/diffs/components/commit_item.vue +++ b/app/assets/javascripts/diffs/components/commit_item.vue @@ -102,6 +102,10 @@ export default { >
+
(partial, locals) { view_to_html_string(partial, locals) } + } + + render json: DiffsSerializer.new(request).represent(@diffs, additional_attributes) end def define_diff_vars diff --git a/app/serializers/commit_entity.rb b/app/serializers/commit_entity.rb index 396e95a03c8..90406701bbb 100644 --- a/app/serializers/commit_entity.rb +++ b/app/serializers/commit_entity.rb @@ -25,4 +25,14 @@ class CommitEntity < API::Entities::Commit expose :title_html, if: { type: :full } do |commit| markdown_field(commit, :title) end + + expose :signature_html, if: { type: :full } do |commit| + render('projects/commit/_signature', signature: commit.signature) if commit.has_signature? + end + + def render(*args) + return unless request.respond_to?(:render) && request.render.respond_to?(:call) + + request.render.call(*args) + end end diff --git a/spec/javascripts/diffs/components/commit_item_spec.js b/spec/javascripts/diffs/components/commit_item_spec.js index 627fb8c490a..50651d67172 100644 --- a/spec/javascripts/diffs/components/commit_item_spec.js +++ b/spec/javascripts/diffs/components/commit_item_spec.js @@ -9,6 +9,7 @@ import getDiffWithCommit from '../mock_data/diff_with_commit'; const TEST_AUTHOR_NAME = 'test'; const TEST_AUTHOR_EMAIL = 'test+test@gitlab.com'; const TEST_AUTHOR_GRAVATAR = `${TEST_HOST}/avatar/test?s=36`; +const TEST_SIGNATURE_HTML = 'Legit commit'; const getTitleElement = vm => vm.$el.querySelector('.commit-row-message.item-title'); const getDescElement = vm => vm.$el.querySelector('pre.commit-row-description'); @@ -16,6 +17,7 @@ const getDescExpandElement = vm => vm.$el.querySelector('.commit-content .text-e const getShaElement = vm => vm.$el.querySelector('.commit-sha-group'); const getAvatarElement = vm => vm.$el.querySelector('.user-avatar-link'); const getCommitterElement = vm => vm.$el.querySelector('.commiter'); +const getCommitActionsElement = vm => vm.$el.querySelector('.commit-actions'); describe('diffs/components/commit_widget', () => { const Component = Vue.extend(CommitItem); @@ -125,4 +127,20 @@ describe('diffs/components/commit_widget', () => { expect(nameElement).toHaveText(TEST_AUTHOR_NAME); }); }); + + describe('with signature', () => { + beforeEach(done => { + vm.commit.signatureHtml = TEST_SIGNATURE_HTML; + + vm.$nextTick() + .then(done) + .catch(done.fail); + }); + + it('renders signature html', () => { + const actionsElement = getCommitActionsElement(vm); + + expect(actionsElement).toContainHtml(TEST_SIGNATURE_HTML); + }); + }); }); diff --git a/spec/serializers/commit_entity_spec.rb b/spec/serializers/commit_entity_spec.rb index 35215e06f5f..b8cd24c69a6 100644 --- a/spec/serializers/commit_entity_spec.rb +++ b/spec/serializers/commit_entity_spec.rb @@ -1,10 +1,11 @@ require 'spec_helper' describe CommitEntity do + SIGNATURE_HTML = 'TEST'.freeze + let(:entity) do described_class.new(commit, request: request) end - let(:request) { double('request') } let(:project) { create(:project, :repository) } let(:commit) { project.commit } @@ -12,7 +13,11 @@ describe CommitEntity do subject { entity.as_json } before do + render = double('render') + allow(render).to receive(:call).and_return(SIGNATURE_HTML) + allow(request).to receive(:project).and_return(project) + allow(request).to receive(:render).and_return(render) end context 'when commit author is a user' do @@ -70,6 +75,15 @@ describe CommitEntity do expect(subject.fetch(:description_html)).not_to be_nil expect(subject.fetch(:title_html)).not_to be_nil end + + context 'when commit has signature' do + let(:commit) { project.commit(TestEnv::BRANCH_SHA['signed-commits']) } + + it 'exposes "signature_html"' do + expect(request.render).to receive(:call) + expect(subject.fetch(:signature_html)).to be SIGNATURE_HTML + end + end end context 'when commit_url_params is set' do -- cgit v1.2.1 From becb86ea4e8032788b151caac004b2635b57c6a4 Mon Sep 17 00:00:00 2001 From: Paul Slaughter Date: Wed, 3 Oct 2018 04:08:00 -0500 Subject: Add pipeline status to diffs/commit_item **Notes:** - Also exposes commit.pipeline_status_path in diffs.json --- app/assets/javascripts/diffs/components/commit_item.vue | 6 ++++++ app/models/commit.rb | 6 +++++- app/serializers/commit_entity.rb | 11 +++++++++++ app/serializers/diffs_entity.rb | 4 +++- spec/javascripts/diffs/components/commit_item_spec.js | 17 +++++++++++++++++ spec/serializers/commit_entity_spec.rb | 12 +++++++++++- 6 files changed, 53 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/diffs/components/commit_item.vue b/app/assets/javascripts/diffs/components/commit_item.vue index 4028002febd..993206b2e73 100644 --- a/app/assets/javascripts/diffs/components/commit_item.vue +++ b/app/assets/javascripts/diffs/components/commit_item.vue @@ -5,6 +5,7 @@ import Icon from '~/vue_shared/components/icon.vue'; import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; import CIIcon from '~/vue_shared/components/ci_icon.vue'; import TimeAgoTooltip from '~/vue_shared/components/time_ago_tooltip.vue'; +import CommitPipelineStatus from '~/projects/tree/components/commit_pipeline_status_component.vue'; /** * CommitItem @@ -29,6 +30,7 @@ export default { ClipboardButton, CIIcon, TimeAgoTooltip, + CommitPipelineStatus, }, props: { commit: { @@ -106,6 +108,10 @@ export default { v-if="commit.signatureHtml" v-html="commit.signatureHtml" >
+
vm.$el.querySelector('.commit-row-message.item-title'); const getDescElement = vm => vm.$el.querySelector('pre.commit-row-description'); @@ -143,4 +144,20 @@ describe('diffs/components/commit_widget', () => { expect(actionsElement).toContainHtml(TEST_SIGNATURE_HTML); }); }); + + describe('with pipeline status', () => { + beforeEach(done => { + vm.commit.pipelineStatusPath = TEST_PIPELINE_STATUS_PATH; + + vm.$nextTick() + .then(done) + .catch(done.fail); + }); + + it('renders pipeline status', () => { + const actionsElement = getCommitActionsElement(vm); + + expect(actionsElement).toContainElement('.ci-status-link'); + }); + }); }); diff --git a/spec/serializers/commit_entity_spec.rb b/spec/serializers/commit_entity_spec.rb index b8cd24c69a6..b9995818e98 100644 --- a/spec/serializers/commit_entity_spec.rb +++ b/spec/serializers/commit_entity_spec.rb @@ -66,7 +66,7 @@ describe CommitEntity do context 'when type is "full"' do let(:entity) do - described_class.new(commit, request: request, type: :full) + described_class.new(commit, request: request, type: :full, pipeline_ref: project.default_branch, pipeline_project: project) end it 'exposes extra properties' do @@ -84,6 +84,16 @@ describe CommitEntity do expect(subject.fetch(:signature_html)).to be SIGNATURE_HTML end end + + context 'when commit has pipeline' do + before do + create(:ci_pipeline, project: project, sha: commit.id) + end + + it 'exposes "pipeline_status_path"' do + expect(subject.fetch(:pipeline_status_path)).not_to be_nil + end + end end context 'when commit_url_params is set' do -- cgit v1.2.1 From a624f841ab3959d56c2493c3f666a5681debe7d1 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Wed, 3 Oct 2018 23:26:01 +0000 Subject: docs: moving repositories cleanup * use `git` user * formatting cleanup --- .../operations/moving_repositories.md | 26 +++++++++------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/doc/administration/operations/moving_repositories.md b/doc/administration/operations/moving_repositories.md index 54adb99386a..ec11a92db1b 100644 --- a/doc/administration/operations/moving_repositories.md +++ b/doc/administration/operations/moving_repositories.md @@ -22,9 +22,8 @@ However, it is not possible to resume an interrupted tar pipe: if that happens then all data must be copied again. ``` -# As the git user -tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ - tar -C /mnt/gitlab/repositories -xf - +sudo -u git sh -c 'tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ + tar -C /mnt/gitlab/repositories -xf -' ``` If you want to see progress, replace `-xf` with `-xvf`. @@ -36,9 +35,8 @@ You can also use a tar pipe to copy data to another server. If your can pipe the data through SSH. ``` -# As the git user -tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ - ssh git@newserver tar -C /mnt/gitlab/repositories -xf - +sudo -u git sh -c 'tar -C /var/opt/gitlab/git-data/repositories -cf - -- . |\ + ssh git@newserver tar -C /mnt/gitlab/repositories -xf -' ``` If you want to compress the data before it goes over the network @@ -53,9 +51,8 @@ is either already installed on your system or easily installable via apt, yum etc. ``` -# As the 'git' user -rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ - /mnt/gitlab/repositories +sudo -u git sh -c 'rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ + /mnt/gitlab/repositories' ``` The `/.` in the command above is very important, without it you can @@ -68,9 +65,8 @@ If the 'git' user on your source system has SSH access to the target server you can send the repositories over the network with rsync. ``` -# As the 'git' user -rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ - git@newserver:/mnt/gitlab/repositories +sudo -u git sh -c 'rsync -a --delete /var/opt/gitlab/git-data/repositories/. \ + git@newserver:/mnt/gitlab/repositories' ``` ## Thousands of Git repositories: use one rsync per repository @@ -125,7 +121,7 @@ sudo -u git -H sh -c 'bundle exec rake gitlab:list_repos > /home/git/transfer-lo Now we can start the transfer. The command below is idempotent, and the number of jobs done by GNU Parallel should converge to zero. If it -does not some repositories listed in all-repos-1234.txt may have been +does not, some repositories listed in `all-repos-1234.txt` may have been deleted/renamed before they could be copied. ``` @@ -155,8 +151,8 @@ cat /home/git/transfer-logs/* | sort | uniq -u |\ Suppose you have already done one sync that started after 2015-10-1 12:00 UTC. Then you might only want to sync repositories that were changed via GitLab -_after_ that time. You can use the 'SINCE' variable to tell 'rake -gitlab:list_repos' to only print repositories with recent activity. +_after_ that time. You can use the `SINCE` variable to tell `rake +gitlab:list_repos` to only print repositories with recent activity. ``` # Omnibus -- cgit v1.2.1 From bfd281a06baed2874c09e8e130fb1143eaf795d7 Mon Sep 17 00:00:00 2001 From: Evan Read Date: Thu, 4 Oct 2018 09:28:12 +1000 Subject: Fix links to external site and minor Markdown improvements --- doc/user/project/import/svn.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/doc/user/project/import/svn.md b/doc/user/project/import/svn.md index 16bc5121027..a5923986292 100644 --- a/doc/user/project/import/svn.md +++ b/doc/user/project/import/svn.md @@ -29,7 +29,7 @@ directly in a filesystem level. 1. Install Oracle JRE 1.8 or newer. On Debian-based Linux distributions you can follow [this article](http://www.webupd8.org/2012/09/install-oracle-java-8-in-ubuntu-via-ppa.html). -1. Download SubGit from https://subgit.com/download/. +1. Download SubGit from . 1. Unpack the downloaded SubGit zip archive to the `/opt` directory. The `subgit` command will be available at `/opt/subgit-VERSION/bin/subgit`. @@ -71,7 +71,7 @@ edit $GIT_REPO_PATH/subgit/config ``` For more information regarding the SubGit configuration options, refer to -[SubGit's documentation](https://subgit.com/documentation.html) website. +[SubGit's documentation](https://subgit.com/documentation/) website. ### Initial translation @@ -97,7 +97,7 @@ subgit import $GIT_REPO_PATH ### SubGit licensing Running SubGit in a mirror mode requires a -[registration](https://subgit.com/pricing.html). Registration is free for open +[registration](https://subgit.com/pricing/). Registration is free for open source, academic and startup projects. We're currently working on deeper GitLab/SubGit integration. You may track our @@ -179,5 +179,6 @@ git push --tags origin ``` ## Contribute to this guide + We welcome all contributions that would expand this guide with instructions on how to migrate from SVN and other version control systems. -- cgit v1.2.1 From 476c44ae5e1a9fd50bd867dc38fdf0d9d42a9f44 Mon Sep 17 00:00:00 2001 From: Evan Read Date: Thu, 4 Oct 2018 09:57:18 +1000 Subject: Change image for docs linting --- .gitlab-ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2a299ea79ef..c652b6c75e2 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -606,7 +606,7 @@ static-analysis: docs lint: <<: *dedicated-runner <<: *except-qa - image: "registry.gitlab.com/gitlab-org/gitlab-build-images:nanoc-bootstrap-ruby-2.4-alpine" + image: "registry.gitlab.com/gitlab-org/gitlab-build-images:gitlab-docs-lint" stage: test cache: {} dependencies: [] @@ -614,8 +614,8 @@ docs lint: script: - scripts/lint-doc.sh - scripts/lint-changelog-yaml - - mv doc/ /nanoc/content/ - - cd /nanoc + - mv doc/ /tmp/gitlab-docs/content/ + - cd /tmp/gitlab-docs # Build HTML from Markdown - bundle exec nanoc # Check the internal links -- cgit v1.2.1 From 56f309dd53619b688415b62625a9baa86c837560 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Thu, 4 Oct 2018 10:07:28 +1300 Subject: Remove `rbac_clusters` feature flag Now that Auto DevOps can run in a RBAC enabled cluster from https://gitlab.com/gitlab-org/gitlab-ce/issues/51942, we can now remove the FF. As the flag only governed if UI elements would appear or not when creating/adding existing clusters; the effect of removing the FF would be that the checkbox to choose to enable RBAC clusters will now always appear. Remove FF stubs from specs Improve spec context names --- app/helpers/clusters_helper.rb | 4 ---- app/views/projects/clusters/gcp/_form.html.haml | 17 ++++++++--------- app/views/projects/clusters/gcp/_show.html.haml | 15 +++++++-------- app/views/projects/clusters/user/_form.html.haml | 17 ++++++++--------- app/views/projects/clusters/user/_show.html.haml | 15 +++++++-------- .../51009-remove-rbac-clusters-feature-flag.yml | 5 +++++ spec/features/projects/clusters/gcp_spec.rb | 4 +--- spec/features/projects/clusters/user_spec.rb | 4 +--- spec/support/services/clusters/create_service_shared.rb | 4 ---- 9 files changed, 37 insertions(+), 48 deletions(-) create mode 100644 changelogs/unreleased/51009-remove-rbac-clusters-feature-flag.yml diff --git a/app/helpers/clusters_helper.rb b/app/helpers/clusters_helper.rb index a67c91b21d7..19eb763e1de 100644 --- a/app/helpers/clusters_helper.rb +++ b/app/helpers/clusters_helper.rb @@ -13,8 +13,4 @@ module ClustersHelper render 'projects/clusters/gcp_signup_offer_banner' end end - - def rbac_clusters_feature_enabled? - Feature.enabled?(:rbac_clusters) - end end diff --git a/app/views/projects/clusters/gcp/_form.html.haml b/app/views/projects/clusters/gcp/_form.html.haml index 0222bbf7338..eaf3a93bd15 100644 --- a/app/views/projects/clusters/gcp/_form.html.haml +++ b/app/views/projects/clusters/gcp/_form.html.haml @@ -61,15 +61,14 @@ %p.form-text.text-muted = s_('ClusterIntegration|Learn more about %{help_link_start_machine_type}machine types%{help_link_end} and %{help_link_start_pricing}pricing%{help_link_end}.').html_safe % { help_link_start_machine_type: help_link_start % { url: machine_type_link_url }, help_link_start_pricing: help_link_start % { url: pricing_link_url }, help_link_end: help_link_end } - - if rbac_clusters_feature_enabled? - .form-group - .form-check - = provider_gcp_field.check_box :legacy_abac, { class: 'form-check-input' }, false, true - = provider_gcp_field.label :legacy_abac, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' - .form-text.text-muted - = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') - = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') - = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'role-based-access-control-rbac-experimental-support'), target: '_blank' + .form-group + .form-check + = provider_gcp_field.check_box :legacy_abac, { class: 'form-check-input' }, false, true + = provider_gcp_field.label :legacy_abac, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' + .form-text.text-muted + = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') + = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') + = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'role-based-access-control-rbac-experimental-support'), target: '_blank' .form-group = field.submit s_('ClusterIntegration|Create Kubernetes cluster'), class: 'js-gke-cluster-creation-submit btn btn-success', disabled: true diff --git a/app/views/projects/clusters/gcp/_show.html.haml b/app/views/projects/clusters/gcp/_show.html.haml index be84f2ae67c..779c9c245c1 100644 --- a/app/views/projects/clusters/gcp/_show.html.haml +++ b/app/views/projects/clusters/gcp/_show.html.haml @@ -37,14 +37,13 @@ = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)') = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') - - if rbac_clusters_feature_enabled? - .form-group - .form-check - = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input', disabled: true }, 'rbac', 'abac' - = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' - .form-text.text-muted - = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') - = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') + .form-group + .form-check + = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input', disabled: true }, 'rbac', 'abac' + = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' + .form-text.text-muted + = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') + = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') .form-group = field.submit s_('ClusterIntegration|Save changes'), class: 'btn btn-success' diff --git a/app/views/projects/clusters/user/_form.html.haml b/app/views/projects/clusters/user/_form.html.haml index f497f5b606c..30513d0f91b 100644 --- a/app/views/projects/clusters/user/_form.html.haml +++ b/app/views/projects/clusters/user/_form.html.haml @@ -25,15 +25,14 @@ = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold' = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') - - if rbac_clusters_feature_enabled? - .form-group - .form-check - = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input' }, 'rbac', 'abac' - = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' - .form-text.text-muted - = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') - = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') - = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'role-based-access-control-rbac-experimental-support'), target: '_blank' + .form-group + .form-check + = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input' }, 'rbac', 'abac' + = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' + .form-text.text-muted + = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') + = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') + = link_to _('More information'), help_page_path('user/project/clusters/index.md', anchor: 'role-based-access-control-rbac-experimental-support'), target: '_blank' .form-group = field.submit s_('ClusterIntegration|Add Kubernetes cluster'), class: 'btn btn-success' diff --git a/app/views/projects/clusters/user/_show.html.haml b/app/views/projects/clusters/user/_show.html.haml index 56b597d295a..5b57f7ceb7d 100644 --- a/app/views/projects/clusters/user/_show.html.haml +++ b/app/views/projects/clusters/user/_show.html.haml @@ -26,14 +26,13 @@ = platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)'), class: 'label-bold' = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: s_('ClusterIntegration|Project namespace') - - if rbac_clusters_feature_enabled? - .form-group - .form-check - = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input', disabled: true }, 'rbac', 'abac' - = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' - .form-text.text-muted - = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') - = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') + .form-group + .form-check + = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input', disabled: true }, 'rbac', 'abac' + = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' + .form-text.text-muted + = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') + = s_('ClusterIntegration|This option will allow you to install applications on RBAC clusters.') .form-group = field.submit s_('ClusterIntegration|Save changes'), class: 'btn btn-success' diff --git a/changelogs/unreleased/51009-remove-rbac-clusters-feature-flag.yml b/changelogs/unreleased/51009-remove-rbac-clusters-feature-flag.yml new file mode 100644 index 00000000000..99946b954ce --- /dev/null +++ b/changelogs/unreleased/51009-remove-rbac-clusters-feature-flag.yml @@ -0,0 +1,5 @@ +--- +title: Remove 'rbac_clusters' feature flag +merge_request: 22096 +author: +type: changed diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index edc763ad0ad..8b92b9fc869 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -84,10 +84,8 @@ describe 'Gcp Cluster', :js do it_behaves_like 'valid cluster gcp form' - context 'rbac_clusters feature flag is enabled' do + context 'RBAC is enabled for the cluster' do before do - stub_feature_flags(rbac_clusters: true) - check 'cluster_provider_gcp_attributes_legacy_abac' end diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb index 2b4998ed5ac..9ae1dba60b5 100644 --- a/spec/features/projects/clusters/user_spec.rb +++ b/spec/features/projects/clusters/user_spec.rb @@ -44,10 +44,8 @@ describe 'User Cluster', :js do it_behaves_like 'valid cluster user form' - context 'rbac_clusters feature flag is enabled' do + context 'RBAC is enabled for the cluster' do before do - stub_feature_flags(rbac_clusters: true) - check 'cluster_platform_kubernetes_attributes_authorization_type' end diff --git a/spec/support/services/clusters/create_service_shared.rb b/spec/support/services/clusters/create_service_shared.rb index 22f712f3fcf..b0bf942aa09 100644 --- a/spec/support/services/clusters/create_service_shared.rb +++ b/spec/support/services/clusters/create_service_shared.rb @@ -30,10 +30,6 @@ shared_context 'invalid cluster create params' do end shared_examples 'create cluster service success' do - before do - stub_feature_flags(rbac_clusters: false) - end - it 'creates a cluster object and performs a worker' do expect(ClusterProvisionWorker).to receive(:perform_async) -- cgit v1.2.1 From 2ec43d9e79c0c77f911c48d23fb1ec94a7ca3020 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Thu, 4 Oct 2018 11:15:43 +1300 Subject: Update docs to remove mention of feature flag --- doc/user/project/clusters/index.md | 29 ++--------------------------- 1 file changed, 2 insertions(+), 27 deletions(-) diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index 41768998a59..ee8b1af7b4a 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -134,36 +134,11 @@ authorization is [experimental](#role-based-access-control-rbac). > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/21401) in GitLab 11.4. CAUTION: **Warning:** -The RBAC authorization is experimental. To enable it you need access to the -server where GitLab is installed. +The RBAC authorization is experimental. -The support for RBAC-enabled clusters is hidden behind a feature flag. Once -the feature flag is enabled, GitLab will create the necessary service accounts +Once RBAC is enabled for a cluster, GitLab will create the necessary service accounts and privileges in order to install and run [GitLab managed applications](#installing-applications). -To enable the feature flag: - -1. SSH into the server where GitLab is installed. -1. Enter the Rails console: - - **For Omnibus GitLab** - - ```sh - sudo gitlab-rails console - ``` - - **For installations from source** - - ```sh - sudo -u git -H bundle exec rails console - ``` - -1. Enable the RBAC authorization: - - ```ruby - Feature.enable('rbac_clusters') - ``` - If you are creating a [new GKE cluster via GitLab](#adding-and-creating-a-new-gke-cluster-via-gitlab), you will be asked if you would like to create an RBAC-enabled cluster. Enabling this -- cgit v1.2.1 From 8661a35e5f20943ff749093f7f8aeb2758d2a993 Mon Sep 17 00:00:00 2001 From: Olivier Gonzalez Date: Thu, 4 Oct 2018 02:23:29 -0400 Subject: Add pipeline.default_branch? mehod Helps to know if the pipeline ref matches the project's default branch --- app/models/ci/pipeline.rb | 4 ++++ spec/models/ci/pipeline_spec.rb | 30 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/app/models/ci/pipeline.rb b/app/models/ci/pipeline.rb index 6dac577c514..038c3b5ae8d 100644 --- a/app/models/ci/pipeline.rb +++ b/app/models/ci/pipeline.rb @@ -627,6 +627,10 @@ module Ci end end + def default_branch? + ref == project.default_branch + end + private def ci_yaml_from_repo diff --git a/spec/models/ci/pipeline_spec.rb b/spec/models/ci/pipeline_spec.rb index 4755702c0e9..ec0bf580115 100644 --- a/spec/models/ci/pipeline_spec.rb +++ b/spec/models/ci/pipeline_spec.rb @@ -1968,4 +1968,34 @@ describe Ci::Pipeline, :mailer do end end end + + describe '#default_branch?' do + let(:default_branch) { 'master'} + + subject { pipeline.default_branch? } + + before do + allow(project).to receive(:default_branch).and_return(default_branch) + end + + context 'when pipeline ref is the default branch of the project' do + let(:pipeline) do + build(:ci_empty_pipeline, status: :created, project: project, ref: default_branch) + end + + it "returns true" do + expect(subject).to be_truthy + end + end + + context 'when pipeline ref is not the default branch of the project' do + let(:pipeline) do + build(:ci_empty_pipeline, status: :created, project: project, ref: 'another_branch') + end + + it "returns false" do + expect(subject).to be_falsey + end + end + end end -- cgit v1.2.1 From 42af229510adfef78772d5ec1cba9b990a645ac3 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 10 Sep 2018 23:57:03 +0200 Subject: Simplify runner registration token resetting This icommit adds several changes related to the same topic - resetting a Runner registration token: 1. On Project settings page it adds a button for resetting the registration token and it removes the Runner token field that was confusing all GitLab users. 2. On Group settings page it adds the same button for resetting the registration token. 3. On Admin Runners settings page it moves the button to the same place as in Project and Group settings and it changes slightly the page layout to make it more similar to Group and Project setting pages. 4. It refactorizes a little the partial that prints runner registration description. Thanks to this Project, Group and Admin settings of the Runner are re-using the same code to generate the button. 5. Updates the translations of changed text. --- .../admin/application_settings_controller.rb | 3 +- .../groups/settings/ci_cd_controller.rb | 7 + .../projects/settings/ci_cd_controller.rb | 7 + app/views/admin/runners/index.html.haml | 183 ++++++++++----------- app/views/ci/runner/_how_to_setup_runner.html.haml | 4 + .../runner/_how_to_setup_shared_runner.html.haml | 3 - .../runner/_how_to_setup_specific_runner.html.haml | 26 --- app/views/groups/runners/_group_runners.html.haml | 4 +- .../projects/runners/_specific_runners.html.haml | 30 +++- app/views/projects/settings/ci_cd/_form.html.haml | 10 -- app/views/projects/settings/ci_cd/show.html.haml | 2 +- config/routes/admin.rb | 2 +- config/routes/group.rb | 4 +- config/routes/project.rb | 1 + locale/ar_SA/gitlab.po | 2 +- locale/bg/gitlab.po | 2 +- locale/ca_ES/gitlab.po | 2 +- locale/cs_CZ/gitlab.po | 2 +- locale/da_DK/gitlab.po | 2 +- locale/de/gitlab.po | 2 +- locale/eo/gitlab.po | 2 +- locale/es/gitlab.po | 2 +- locale/et_EE/gitlab.po | 2 +- locale/fil_PH/gitlab.po | 2 +- locale/fr/gitlab.po | 2 +- locale/gitlab.pot | 12 +- locale/gl_ES/gitlab.po | 2 +- locale/he_IL/gitlab.po | 2 +- locale/id_ID/gitlab.po | 2 +- locale/it/gitlab.po | 2 +- locale/ja/gitlab.po | 2 +- locale/ko/gitlab.po | 2 +- locale/nl_NL/gitlab.po | 2 +- locale/pl_PL/gitlab.po | 2 +- locale/pt_BR/gitlab.po | 2 +- locale/ro_RO/gitlab.po | 2 +- locale/ru/gitlab.po | 2 +- locale/sq_AL/gitlab.po | 2 +- locale/tr_TR/gitlab.po | 2 +- locale/uk/gitlab.po | 2 +- locale/zh_CN/gitlab.po | 2 +- locale/zh_HK/gitlab.po | 2 +- locale/zh_TW/gitlab.po | 2 +- .../admin/application_settings_controller_spec.rb | 18 ++ .../groups/settings/ci_cd_controller_spec.rb | 14 ++ .../projects/settings/ci_cd_controller_spec.rb | 13 ++ spec/features/groups/settings/ci_cd_spec.rb | 39 +++++ .../projects/settings/pipelines_settings_spec.rb | 24 +++ 48 files changed, 286 insertions(+), 176 deletions(-) delete mode 100644 app/views/ci/runner/_how_to_setup_shared_runner.html.haml delete mode 100644 app/views/ci/runner/_how_to_setup_specific_runner.html.haml create mode 100644 spec/features/groups/settings/ci_cd_spec.rb diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb index b7c758a42ed..8040a14ef56 100644 --- a/app/controllers/admin/application_settings_controller.rb +++ b/app/controllers/admin/application_settings_controller.rb @@ -67,8 +67,9 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController end end - def reset_runners_token + def reset_registration_token @application_setting.reset_runners_registration_token! + flash[:notice] = 'New runners registration token has been generated!' redirect_to admin_runners_path end diff --git a/app/controllers/groups/settings/ci_cd_controller.rb b/app/controllers/groups/settings/ci_cd_controller.rb index 6d9a225b771..93f3eb2be6d 100644 --- a/app/controllers/groups/settings/ci_cd_controller.rb +++ b/app/controllers/groups/settings/ci_cd_controller.rb @@ -10,6 +10,13 @@ module Groups define_secret_variables end + def reset_registration_token + @group.reset_runners_token! + + flash[:notice] = 'New runners registration token has been generated!' + redirect_to group_settings_ci_cd_path + end + private def define_secret_variables diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb index a2d1b7866c2..3a1344651df 100644 --- a/app/controllers/projects/settings/ci_cd_controller.rb +++ b/app/controllers/projects/settings/ci_cd_controller.rb @@ -36,6 +36,13 @@ module Projects end end + def reset_registration_token + @project.reset_runners_token! + + flash[:notice] = 'New runners registration token has been generated!' + redirect_to namespace_project_settings_ci_cd_path + end + private def update_params diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index a5326f4b909..e9e4e0847d3 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -2,106 +2,103 @@ - @no_container = true %div{ class: container_class } - .bs-callout - %p - = (_"A 'Runner' is a process which runs a job. You can set up as many Runners as you need.") - %br - = _('Runners can be placed on separate users, servers, even on your local machine.') - %br + .row + .col-sm-6 + .bs-callout + %p + = (_"A 'Runner' is a process which runs a job. You can set up as many Runners as you need.") + %br + = _('Runners can be placed on separate users, servers, even on your local machine.') + %br - %div - %span= _('Each Runner can be in one of the following states:') - %ul - %li - %span.badge.badge-success shared - \- - = _('Runner runs jobs from all unassigned projects') - %li - %span.badge.badge-success group - \- - = _('Runner runs jobs from all unassigned projects in its group') - %li - %span.badge.badge-info specific - \- - = _('Runner runs jobs from assigned projects') - %li - %span.badge.badge-warning locked - \- - = _('Runner cannot be assigned to other projects') - %li - %span.badge.badge-danger paused - \- - = _('Runner will not receive any new jobs') + %div + %span= _('Each Runner can be in one of the following states:') + %ul + %li + %span.badge.badge-success shared + \- + = _('Runner runs jobs from all unassigned projects') + %li + %span.badge.badge-success group + \- + = _('Runner runs jobs from all unassigned projects in its group') + %li + %span.badge.badge-info specific + \- + = _('Runner runs jobs from assigned projects') + %li + %span.badge.badge-warning locked + \- + = _('Runner cannot be assigned to other projects') + %li + %span.badge.badge-danger paused + \- + = _('Runner will not receive any new jobs') - .bs-callout.clearfix - .float-left - %p - = _('You can reset runners registration token by pressing a button below.') - .prepend-top-10 - = button_to _('Reset runners registration token'), reset_runners_token_admin_application_settings_path, - method: :put, class: 'btn btn-default', - data: { confirm: _('Are you sure you want to reset registration token?') } + .col-sm-6 + .bs-callout + = render partial: 'ci/runner/how_to_setup_runner', + locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token, + type: 'shared', + reset_token_url: reset_registration_token_admin_application_settings_path } - = render partial: 'ci/runner/how_to_setup_shared_runner', - locals: { registration_token: Gitlab::CurrentSettings.runners_registration_token } + .row + .col-sm-9 + = form_tag admin_runners_path, id: 'runners-search', method: :get, class: 'filter-form js-filter-form' do + .filtered-search-wrapper + .filtered-search-box + = dropdown_tag(custom_icon('icon_history'), + options: { wrapper_class: 'filtered-search-history-dropdown-wrapper', + toggle_class: 'filtered-search-history-dropdown-toggle-button', + dropdown_class: 'filtered-search-history-dropdown', + content_class: 'filtered-search-history-dropdown-content', + title: _('Recent searches') }) do + .js-filtered-search-history-dropdown{ data: { full_path: admin_runners_path } } + .filtered-search-box-input-container.droplab-dropdown + .scroll-container + %ul.tokens-container.list-unstyled + %li.input-token + %input.form-control.filtered-search{ { id: 'filtered-search-runners', placeholder: _('Search or filter results...') } } + #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown + %ul{ data: { dropdown: true } } + %li.filter-dropdown-item{ data: { action: 'submit' } } + = button_tag class: %w[btn btn-link] do + = sprite_icon('search') + %span + = _('Press Enter or click to search') + %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } + %li.filter-dropdown-item + = button_tag class: %w[btn btn-link] do + -# Encapsulate static class name `{{icon}}` inside #{} to bypass + -# haml lint's ClassAttributeWithStaticValue + %svg + %use{ 'xlink:href': "#{'{{icon}}'}" } + %span.js-filter-hint + {{hint}} + %span.js-filter-tag.dropdown-light-content + {{tag}} - .bs-callout - %p - = _('Runners currently online: %{active_runners_count}') % { active_runners_count: @active_runners_count } + #js-dropdown-admin-runner-status.filtered-search-input-dropdown-menu.dropdown-menu + %ul{ data: { dropdown: true } } + - Ci::Runner::AVAILABLE_STATUSES.each do |status| + %li.filter-dropdown-item{ data: { value: status } } + = button_tag class: %w[btn btn-link] do + = status.titleize - .row-content-block.second-block - = form_tag admin_runners_path, id: 'runners-search', method: :get, class: 'filter-form js-filter-form' do - .filtered-search-wrapper - .filtered-search-box - = dropdown_tag(custom_icon('icon_history'), - options: { wrapper_class: 'filtered-search-history-dropdown-wrapper', - toggle_class: 'filtered-search-history-dropdown-toggle-button', - dropdown_class: 'filtered-search-history-dropdown', - content_class: 'filtered-search-history-dropdown-content', - title: _('Recent searches') }) do - .js-filtered-search-history-dropdown{ data: { full_path: admin_runners_path } } - .filtered-search-box-input-container.droplab-dropdown - .scroll-container - %ul.tokens-container.list-unstyled - %li.input-token - %input.form-control.filtered-search{ { id: 'filtered-search-runners', placeholder: _('Search or filter results...') } } - #js-dropdown-hint.filtered-search-input-dropdown-menu.dropdown-menu.hint-dropdown - %ul{ data: { dropdown: true } } - %li.filter-dropdown-item{ data: { action: 'submit' } } - = button_tag class: %w[btn btn-link] do - = sprite_icon('search') - %span - = _('Press Enter or click to search') - %ul.filter-dropdown{ data: { dynamic: true, dropdown: true } } - %li.filter-dropdown-item - = button_tag class: %w[btn btn-link] do - -# Encapsulate static class name `{{icon}}` inside #{} to bypass - -# haml lint's ClassAttributeWithStaticValue - %svg - %use{ 'xlink:href': "#{'{{icon}}'}" } - %span.js-filter-hint - {{hint}} - %span.js-filter-tag.dropdown-light-content - {{tag}} + #js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu + %ul{ data: { dropdown: true } } + - Ci::Runner::AVAILABLE_TYPES.each do |runner_type| + %li.filter-dropdown-item{ data: { value: runner_type } } + = button_tag class: %w[btn btn-link] do + = runner_type.titleize - #js-dropdown-admin-runner-status.filtered-search-input-dropdown-menu.dropdown-menu - %ul{ data: { dropdown: true } } - - Ci::Runner::AVAILABLE_STATUSES.each do |status| - %li.filter-dropdown-item{ data: { value: status } } - = button_tag class: %w[btn btn-link] do - = status.titleize + = button_tag class: %w[clear-search hidden] do + = icon('times') + .filter-dropdown-container + = render 'sort_dropdown' - #js-dropdown-admin-runner-type.filtered-search-input-dropdown-menu.dropdown-menu - %ul{ data: { dropdown: true } } - - Ci::Runner::AVAILABLE_TYPES.each do |runner_type| - %li.filter-dropdown-item{ data: { value: runner_type } } - = button_tag class: %w[btn btn-link] do - = runner_type.titleize - - = button_tag class: %w[clear-search hidden] do - = icon('times') - .filter-dropdown-container - = render 'sort_dropdown' + .col-sm-3.text-right-lg + = _('Runners currently online: %{active_runners_count}') % { active_runners_count: @active_runners_count } - if @runners.any? .runners-content.content-list diff --git a/app/views/ci/runner/_how_to_setup_runner.html.haml b/app/views/ci/runner/_how_to_setup_runner.html.haml index b1b142460b0..4307060d748 100644 --- a/app/views/ci/runner/_how_to_setup_runner.html.haml +++ b/app/views/ci/runner/_how_to_setup_runner.html.haml @@ -13,5 +13,9 @@ = _("Use the following registration token during setup:") %code#registration_token= registration_token = clipboard_button(target: '#registration_token', title: _("Copy token to clipboard"), class: "btn-transparent btn-clipboard") + .prepend-top-10.append-bottom-10 + = button_to _("Reset runners registration token"), reset_token_url, + method: :put, class: 'btn btn-default', + data: { confirm: _("Are you sure you want to reset registration token?") } %li = _("Start the Runner!") diff --git a/app/views/ci/runner/_how_to_setup_shared_runner.html.haml b/app/views/ci/runner/_how_to_setup_shared_runner.html.haml deleted file mode 100644 index 2a190cb9250..00000000000 --- a/app/views/ci/runner/_how_to_setup_shared_runner.html.haml +++ /dev/null @@ -1,3 +0,0 @@ -.bs-callout.help-callout - = render partial: 'ci/runner/how_to_setup_runner', - locals: { registration_token: registration_token, type: 'shared' } diff --git a/app/views/ci/runner/_how_to_setup_specific_runner.html.haml b/app/views/ci/runner/_how_to_setup_specific_runner.html.haml deleted file mode 100644 index afe57bdfa01..00000000000 --- a/app/views/ci/runner/_how_to_setup_specific_runner.html.haml +++ /dev/null @@ -1,26 +0,0 @@ -.bs-callout.help-callout - .append-bottom-10 - %h4= _('Set up a specific Runner automatically') - - %p - - link_to_help_page = link_to(_('Learn more about Kubernetes'), - help_page_path('user/project/clusters/index'), - target: '_blank', - rel: 'noopener noreferrer') - - = _('You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page } - - %ol - %li - = _('Click the button below to begin the install process by navigating to the Kubernetes page') - %li - = _('Select an existing Kubernetes cluster or create a new one') - %li - = _('From the Kubernetes cluster details view, install Runner from the applications list') - - = link_to _('Install Runner on Kubernetes'), - project_clusters_path(@project), - class: 'btn btn-info' - %hr - = render partial: 'ci/runner/how_to_setup_runner', - locals: { registration_token: registration_token, type: 'specific' } diff --git a/app/views/groups/runners/_group_runners.html.haml b/app/views/groups/runners/_group_runners.html.haml index e6c089c3494..bcfb6d99716 100644 --- a/app/views/groups/runners/_group_runners.html.haml +++ b/app/views/groups/runners/_group_runners.html.haml @@ -11,7 +11,9 @@ -# https://gitlab.com/gitlab-org/gitlab-ce/issues/45894 - if can?(current_user, :admin_pipeline, @group) = render partial: 'ci/runner/how_to_setup_runner', - locals: { registration_token: @group.runners_token, type: 'group' } + locals: { registration_token: @group.runners_token, + type: 'group', + reset_token_url: reset_registration_token_group_settings_ci_cd_path } - if @group.runners.empty? %h4.underlined-title diff --git a/app/views/projects/runners/_specific_runners.html.haml b/app/views/projects/runners/_specific_runners.html.haml index 314af44490e..ec503cd8bef 100644 --- a/app/views/projects/runners/_specific_runners.html.haml +++ b/app/views/projects/runners/_specific_runners.html.haml @@ -1,8 +1,34 @@ %h3 = _('Specific Runners') -= render partial: 'ci/runner/how_to_setup_specific_runner', - locals: { registration_token: @project.runners_token } +.bs-callout.help-callout + .append-bottom-10 + %h4= _('Set up a specific Runner automatically') + + %p + - link_to_help_page = link_to(_('Learn more about Kubernetes'), + help_page_path('user/project/clusters/index'), + target: '_blank', + rel: 'noopener noreferrer') + + = _('You can easily install a Runner on a Kubernetes cluster. %{link_to_help_page}').html_safe % { link_to_help_page: link_to_help_page } + + %ol + %li + = _('Click the button below to begin the install process by navigating to the Kubernetes page') + %li + = _('Select an existing Kubernetes cluster or create a new one') + %li + = _('From the Kubernetes cluster details view, install Runner from the applications list') + + = link_to _('Install Runner on Kubernetes'), + project_clusters_path(@project), + class: 'btn btn-info' + %hr + = render partial: 'ci/runner/how_to_setup_runner', + locals: { registration_token: @project.runners_token, + type: 'specific', + reset_token_url: reset_registration_token_namespace_project_settings_ci_cd_path } - if @project_runners.any? %h4.underlined-title Runners activated for this project diff --git a/app/views/projects/settings/ci_cd/_form.html.haml b/app/views/projects/settings/ci_cd/_form.html.haml index ae923d8e6dc..41afaa9ffc0 100644 --- a/app/views/projects/settings/ci_cd/_form.html.haml +++ b/app/views/projects/settings/ci_cd/_form.html.haml @@ -3,16 +3,6 @@ = form_for @project, url: project_settings_ci_cd_path(@project, anchor: 'js-general-pipeline-settings') do |f| = form_errors(@project) %fieldset.builds-feature - .form-group.append-bottom-default.js-secret-runner-token - = f.label :runners_token, _("Runner token"), class: 'label-bold' - .form-control.js-secret-value-placeholder - = '*' * 20 - = f.text_field :runners_token, class: "form-control hide js-secret-value", placeholder: 'xEeFCaDAB89' - %p.form-text.text-muted= _("The secure token used by the Runner to checkout the project") - %button.btn.btn-info.prepend-top-10.js-secret-value-reveal-button{ type: 'button', data: { secret_reveal_status: 'false' } } - = _('Reveal value') - - %hr .form-group %h5.prepend-top-0 = _("Git strategy for pipelines") diff --git a/app/views/projects/settings/ci_cd/show.html.haml b/app/views/projects/settings/ci_cd/show.html.haml index 16961784e00..98e2829ba43 100644 --- a/app/views/projects/settings/ci_cd/show.html.haml +++ b/app/views/projects/settings/ci_cd/show.html.haml @@ -12,7 +12,7 @@ %button.btn.js-settings-toggle{ type: 'button' } = expanded ? _('Collapse') : _('Expand') %p - = _("Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report.") + = _("Customize your pipeline configuration, view your pipeline status and coverage report.") .settings-content = render 'form' diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 7489b01ded6..7cdaa2daa14 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -107,7 +107,7 @@ namespace :admin do resource :application_settings, only: [:show, :update] do resources :services, only: [:index, :edit, :update] get :usage_data - put :reset_runners_token + put :reset_registration_token put :reset_health_check_token put :clear_repository_check_states get :integrations, :repository, :templates, :ci_cd, :reporting, :metrics_and_profiling, :network, :geo, :preferences diff --git a/config/routes/group.rb b/config/routes/group.rb index 893ec8a4e58..602bbe837cf 100644 --- a/config/routes/group.rb +++ b/config/routes/group.rb @@ -27,7 +27,9 @@ constraints(::Constraints::GroupUrlConstrainer.new) do as: :group, constraints: { group_id: Gitlab::PathRegex.full_namespace_route_regex }) do namespace :settings do - resource :ci_cd, only: [:show], controller: 'ci_cd' + resource :ci_cd, only: [:show], controller: 'ci_cd' do + put :reset_registration_token + end end resource :variables, only: [:show, :update] diff --git a/config/routes/project.rb b/config/routes/project.rb index 8a5310b5c23..ef1bc4bbf42 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -437,6 +437,7 @@ constraints(::Constraints::ProjectUrlConstrainer.new) do get :members, to: redirect("%{namespace_id}/%{project_id}/project_members") resource :ci_cd, only: [:show, :update], controller: 'ci_cd' do post :reset_cache + put :reset_registration_token end resource :integrations, only: [:show] resource :repository, only: [:show], controller: :repository do diff --git a/locale/ar_SA/gitlab.po b/locale/ar_SA/gitlab.po index 1b03fe9ce28..d196fac6c60 100644 --- a/locale/ar_SA/gitlab.po +++ b/locale/ar_SA/gitlab.po @@ -480,7 +480,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/bg/gitlab.po b/locale/bg/gitlab.po index 3a925c27e9b..0d5026c0f4a 100644 --- a/locale/bg/gitlab.po +++ b/locale/bg/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/ca_ES/gitlab.po b/locale/ca_ES/gitlab.po index 007b2a4d393..1a052348522 100644 --- a/locale/ca_ES/gitlab.po +++ b/locale/ca_ES/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/cs_CZ/gitlab.po b/locale/cs_CZ/gitlab.po index 013152917e6..3a2267c4bf7 100644 --- a/locale/cs_CZ/gitlab.po +++ b/locale/cs_CZ/gitlab.po @@ -420,7 +420,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/da_DK/gitlab.po b/locale/da_DK/gitlab.po index ed25f935b9a..1a6e564ed36 100644 --- a/locale/da_DK/gitlab.po +++ b/locale/da_DK/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/de/gitlab.po b/locale/de/gitlab.po index 41848faeb30..c27a0dea04d 100644 --- a/locale/de/gitlab.po +++ b/locale/de/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "Zugriff auf fehlerhafte Speicher wurde vorübergehend deaktiviert, um die Wiederherstellung zu ermöglichen. Für den zukünftigen Zugriff, behebe bitte das Problem und setze danach die Speicherinformationen zurück." -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/eo/gitlab.po b/locale/eo/gitlab.po index 8bd42855b44..d0a67a1d089 100644 --- a/locale/eo/gitlab.po +++ b/locale/eo/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/es/gitlab.po b/locale/es/gitlab.po index 2a8fb756192..6ce5b6a3aff 100644 --- a/locale/es/gitlab.po +++ b/locale/es/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/et_EE/gitlab.po b/locale/et_EE/gitlab.po index a9637d4098e..8e4edc84c83 100644 --- a/locale/et_EE/gitlab.po +++ b/locale/et_EE/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/fil_PH/gitlab.po b/locale/fil_PH/gitlab.po index 0929e3dd7cb..73eeb56bea2 100644 --- a/locale/fil_PH/gitlab.po +++ b/locale/fil_PH/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/fr/gitlab.po b/locale/fr/gitlab.po index cf00055272c..93b30d0ef31 100644 --- a/locale/fr/gitlab.po +++ b/locale/fr/gitlab.po @@ -360,7 +360,7 @@ msgstr "Accès à « %{classification_label} » non autorisé" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "L’accès aux stockages défaillants a été temporairement désactivé pour permettre la récupération du montage. Réinitialisez les informations de stockage quand le problème sera résolu pour permettre à nouveau l’accès." -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "Accédez à votre jeton d’exécuteur, personnalisez la configuration de votre pipeline et affichez l’état de votre pipeline et le rapport de couverture." msgid "Account" diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 5591b0b8bb2..568c49606af 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -317,9 +317,6 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." -msgstr "" - msgid "Account" msgstr "" @@ -2090,6 +2087,9 @@ msgstr "" msgid "Customize how Google Code email addresses and usernames are imported into GitLab. In the next step, you'll be able to select the projects you want to import." msgstr "" +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." +msgstr "" + msgid "Cycle Analytics" msgstr "" @@ -5950,9 +5950,6 @@ msgstr "" msgid "The review stage shows the time from creating the merge request to merging it. The data will automatically be added after you merge your first merge request." msgstr "" -msgid "The secure token used by the Runner to checkout the project" -msgstr "" - msgid "The staging stage shows the time between merging the MR and deploying code to the production environment. The data will be automatically added once you deploy to production for the first time." msgstr "" @@ -6869,9 +6866,6 @@ msgstr "" msgid "You can only edit files when you are on a branch" msgstr "" -msgid "You can reset runners registration token by pressing a button below." -msgstr "" - msgid "You can resolve the merge conflict using either the Interactive mode, by choosing %{use_ours} or %{use_theirs} buttons, or by editing the files directly. Commit these changes into %{branch_name}" msgstr "" diff --git a/locale/gl_ES/gitlab.po b/locale/gl_ES/gitlab.po index 2b6dcc6595e..c77dc236458 100644 --- a/locale/gl_ES/gitlab.po +++ b/locale/gl_ES/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/he_IL/gitlab.po b/locale/he_IL/gitlab.po index f34f862b9b1..ab014982a72 100644 --- a/locale/he_IL/gitlab.po +++ b/locale/he_IL/gitlab.po @@ -420,7 +420,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/id_ID/gitlab.po b/locale/id_ID/gitlab.po index ba43d50b726..d5c48520155 100644 --- a/locale/id_ID/gitlab.po +++ b/locale/id_ID/gitlab.po @@ -330,7 +330,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/it/gitlab.po b/locale/it/gitlab.po index 375311ccf72..87bcd939fb1 100644 --- a/locale/it/gitlab.po +++ b/locale/it/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "L'accesso agli storages è stato temporaneamente disabilitato per consentire il mount di ripristino. Resetta le info d'archiviazione dopo che l'issue è stato risolto per consentire nuovamente l'accesso." -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/ja/gitlab.po b/locale/ja/gitlab.po index a618fa97381..ee5ea023fb5 100644 --- a/locale/ja/gitlab.po +++ b/locale/ja/gitlab.po @@ -330,7 +330,7 @@ msgstr "'%{classification_label}'へのアクセスは許可されていませ msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "mount によって復旧できるように、失敗が発生しているストレージへのアクセスを一時的に抑止しました。再度アクセスするためには、問題を解決してからストレージ情報をリセットしてください。" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "Runner トークンにアクセスし、パイプラインの設定をカスタマイズ、そしてパイプラインの状態とカバレッジレポートを閲覧します。" msgid "Account" diff --git a/locale/ko/gitlab.po b/locale/ko/gitlab.po index ce0c069712d..3c3bcf9688a 100644 --- a/locale/ko/gitlab.po +++ b/locale/ko/gitlab.po @@ -330,7 +330,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "오동작중인 저장공간에 대한 접근이 복구 작업을 위해 마운트할 수 있도록 임시로 허용되었습니다. 문제가 해결된 후 다시 접근을 허용할 수 있게 저장공간 정보를 리셋 해주세요." -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/nl_NL/gitlab.po b/locale/nl_NL/gitlab.po index d039b51ce40..f354ca50f32 100644 --- a/locale/nl_NL/gitlab.po +++ b/locale/nl_NL/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/pl_PL/gitlab.po b/locale/pl_PL/gitlab.po index 3f30108892f..1d6dc4c4399 100644 --- a/locale/pl_PL/gitlab.po +++ b/locale/pl_PL/gitlab.po @@ -420,7 +420,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/pt_BR/gitlab.po b/locale/pt_BR/gitlab.po index bcc7659e5a2..c76c639e8db 100644 --- a/locale/pt_BR/gitlab.po +++ b/locale/pt_BR/gitlab.po @@ -360,7 +360,7 @@ msgstr "Acesso a '%{classification_label}' não permitido" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "Os acessos à storages com defeito foram temporariamente desabilitados para permitir a sua remontagem. Redefina as informações de armazenamento depois que o problema foi resolvido para permitir o acesso de novo." -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "Acesse seu runner token, personalize sua configuração de pipeline e visualize o status do seu pipeline e o relatório de coverage." msgid "Account" diff --git a/locale/ro_RO/gitlab.po b/locale/ro_RO/gitlab.po index 3fb198ae037..bae64f360fc 100644 --- a/locale/ro_RO/gitlab.po +++ b/locale/ro_RO/gitlab.po @@ -390,7 +390,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/ru/gitlab.po b/locale/ru/gitlab.po index dc7a0fc9f51..bc2c26da457 100644 --- a/locale/ru/gitlab.po +++ b/locale/ru/gitlab.po @@ -420,7 +420,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "Доступ к вышедшим из строя хранилищам временно отключен для возможности монтирования в целях восстановления. Сбросьте информацию о хранилищах после устранения проблемы, чтобы разрешить доступ." -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/sq_AL/gitlab.po b/locale/sq_AL/gitlab.po index 681827065da..42eeed11534 100644 --- a/locale/sq_AL/gitlab.po +++ b/locale/sq_AL/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/tr_TR/gitlab.po b/locale/tr_TR/gitlab.po index cb77032cc50..d1087ffd29e 100644 --- a/locale/tr_TR/gitlab.po +++ b/locale/tr_TR/gitlab.po @@ -360,7 +360,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/uk/gitlab.po b/locale/uk/gitlab.po index 57a131c4ee6..33019a3e5a8 100644 --- a/locale/uk/gitlab.po +++ b/locale/uk/gitlab.po @@ -420,7 +420,7 @@ msgstr "Доступ до \"%{classification_label}\" заборонено" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "Доступ до сховищ, що вийшли з ладу, тимчасово прибраний задля відновлення монтування. Після вирішення проблеми обнуліть інформацію сховища для відновлення доступу." -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "Отримайте доступ до Gitlab Runner токену, налаштуйте конфігурацію конвеєра та перегляньте його статус, а також звіт про покриття." msgid "Account" diff --git a/locale/zh_CN/gitlab.po b/locale/zh_CN/gitlab.po index 9629a63e976..861e459bcac 100644 --- a/locale/zh_CN/gitlab.po +++ b/locale/zh_CN/gitlab.po @@ -330,7 +330,7 @@ msgstr "不允许访问%{classification_label}" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "为方便修复挂载问题,访问故障存储已被暂时禁用。在问题解决后请重置存储运行状况信息,以允许再次访问。" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "访问您的 runner 令牌,自定义流水线配置,以及查看流水线状态和覆盖率报告。" msgid "Account" diff --git a/locale/zh_HK/gitlab.po b/locale/zh_HK/gitlab.po index 632da40cd54..3ecd9fc4cd2 100644 --- a/locale/zh_HK/gitlab.po +++ b/locale/zh_HK/gitlab.po @@ -330,7 +330,7 @@ msgstr "" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "因恢復安裝,訪問故障存儲已被暫時禁用。在問題解決後將重置存儲信息,以便再次訪問。" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "" msgid "Account" diff --git a/locale/zh_TW/gitlab.po b/locale/zh_TW/gitlab.po index 0d6fe4395ef..bb907d9a583 100644 --- a/locale/zh_TW/gitlab.po +++ b/locale/zh_TW/gitlab.po @@ -330,7 +330,7 @@ msgstr "不允許存取「%{classification_label}」" msgid "Access to failing storages has been temporarily disabled to allow the mount to recover. Reset storage information after the issue has been resolved to allow access again." msgstr "已暫時停用失敗的 Git 儲存空間。當儲存空間恢復正常後,請重置儲存空間健康指數。" -msgid "Access your runner token, customize your pipeline configuration, and view your pipeline status and coverage report." +msgid "Customize your pipeline configuration, view your pipeline status and coverage report." msgstr "存取您執行器憑證,自定義您的流水線配置,並查看你的流水現狀態及測試涵蓋率報告。" msgid "Account" diff --git a/spec/controllers/admin/application_settings_controller_spec.rb b/spec/controllers/admin/application_settings_controller_spec.rb index 10e1bfc30f9..2e0f79cd313 100644 --- a/spec/controllers/admin/application_settings_controller_spec.rb +++ b/spec/controllers/admin/application_settings_controller_spec.rb @@ -86,4 +86,22 @@ describe Admin::ApplicationSettingsController do expect(ApplicationSetting.current.receive_max_input_size).to eq(1024) end end + + describe 'PUT #reset_registration_token' do + before do + sign_in(admin) + end + + subject { put :reset_registration_token } + + it 'resets runner registration token' do + expect { subject }.to change { ApplicationSetting.current.runners_registration_token } + end + + it 'redirects the user to admin runners page' do + subject + + expect(response).to redirect_to(admin_runners_path) + end + end end diff --git a/spec/controllers/groups/settings/ci_cd_controller_spec.rb b/spec/controllers/groups/settings/ci_cd_controller_spec.rb index ea18122e0c3..06ccace8242 100644 --- a/spec/controllers/groups/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/groups/settings/ci_cd_controller_spec.rb @@ -17,4 +17,18 @@ describe Groups::Settings::CiCdController do expect(response).to render_template(:show) end end + + describe 'PUT #reset_registration_token' do + subject { put :reset_registration_token, group_id: group } + + it 'resets runner registration token' do + expect { subject }.to change { group.reload.runners_token } + end + + it 'redirects the user to admin runners page' do + subject + + expect(response).to redirect_to(group_settings_ci_cd_path) + end + end end diff --git a/spec/controllers/projects/settings/ci_cd_controller_spec.rb b/spec/controllers/projects/settings/ci_cd_controller_spec.rb index 1f14a0cc381..4629929f9af 100644 --- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb @@ -74,6 +74,19 @@ describe Projects::Settings::CiCdController do end end + describe 'PUT #reset_registration_token' do + subject { put :reset_registration_token, namespace_id: project.namespace, project_id: project } + it 'resets runner registration token' do + expect { subject }.to change { project.reload.runners_token } + end + + it 'redirects the user to admin runners page' do + subject + + expect(response).to redirect_to(namespace_project_settings_ci_cd_path) + end + end + describe 'PATCH update' do let(:params) { { ci_config_path: '' } } diff --git a/spec/features/groups/settings/ci_cd_spec.rb b/spec/features/groups/settings/ci_cd_spec.rb new file mode 100644 index 00000000000..d422fd18346 --- /dev/null +++ b/spec/features/groups/settings/ci_cd_spec.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +require 'spec_helper' + +describe 'Group CI/CD settings' do + include WaitForRequests + + let(:user) {create(:user)} + let(:group) {create(:group)} + + before do + group.add_owner(user) + sign_in(user) + end + + describe 'runners registration token' do + let!(:token) { group.runners_token } + + before do + visit group_settings_ci_cd_path(group) + end + + it 'has a registration token' do + expect(page.find('#registration_token')).to have_content(token) + end + + describe 'reload registration token' do + let(:page_token) { find('#registration_token').text } + + before do + click_button 'Reset runners registration token' + end + + it 'changes registration token' do + expect(page_token).not_to eq token + end + end + end +end diff --git a/spec/features/projects/settings/pipelines_settings_spec.rb b/spec/features/projects/settings/pipelines_settings_spec.rb index 30b0a5578ea..6f8ec0015ad 100644 --- a/spec/features/projects/settings/pipelines_settings_spec.rb +++ b/spec/features/projects/settings/pipelines_settings_spec.rb @@ -137,5 +137,29 @@ describe "Projects > Settings > Pipelines settings" do end end end + + describe 'runners registration token' do + let!(:token) { project.runners_token } + + before do + visit project_settings_ci_cd_path(project) + end + + it 'has a registration token' do + expect(page.find('#registration_token')).to have_content(token) + end + + describe 'reload registration token' do + let(:page_token) { find('#registration_token').text } + + before do + click_button 'Reset runners registration token' + end + + it 'changes registration token' do + expect(page_token).not_to eq token + end + end + end end end -- cgit v1.2.1 From 5a60a54679ecc25c910385947811bcbd0d1b792f Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 11 Sep 2018 00:55:28 +0200 Subject: Add CHANGELOG entry --- .../41922-simplify-runner-registration-token-resetting.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/41922-simplify-runner-registration-token-resetting.yml diff --git a/changelogs/unreleased/41922-simplify-runner-registration-token-resetting.yml b/changelogs/unreleased/41922-simplify-runner-registration-token-resetting.yml new file mode 100644 index 00000000000..582d7824d27 --- /dev/null +++ b/changelogs/unreleased/41922-simplify-runner-registration-token-resetting.yml @@ -0,0 +1,5 @@ +--- +title: Simplify runner registration token resetting +merge_request: 21658 +author: +type: changed -- cgit v1.2.1 From 337b2c80f32cbd9986843a04296252d40f9847c5 Mon Sep 17 00:00:00 2001 From: Martin Wortschack Date: Thu, 4 Oct 2018 07:55:37 +0000 Subject: Resolve "Add new "Overview" tab on user profile page" --- .../javascripts/pages/users/activity_calendar.js | 26 +++-- .../javascripts/pages/users/user_overview_block.js | 42 +++++++ app/assets/javascripts/pages/users/user_tabs.js | 84 +++++++++++--- app/assets/stylesheets/framework/calendar.scss | 12 +- app/controllers/users_controller.rb | 13 ++- app/finders/user_recent_events_finder.rb | 2 +- app/helpers/users_helper.rb | 2 +- app/views/users/_overview.html.haml | 32 ++++++ app/views/users/show.html.haml | 38 ++++--- ...1-add-new-overview-tab-on-user-profile-page.yml | 5 + config/routes/user.rb | 1 + locale/gitlab.pot | 54 +++++++++ spec/features/calendar_spec.rb | 21 ++-- .../dashboard/datetime_on_tooltips_spec.rb | 2 +- spec/features/users/overview_spec.rb | 123 +++++++++++++++++++++ spec/features/users/show_spec.rb | 2 + 16 files changed, 398 insertions(+), 61 deletions(-) create mode 100644 app/assets/javascripts/pages/users/user_overview_block.js create mode 100644 app/views/users/_overview.html.haml create mode 100644 changelogs/unreleased/49801-add-new-overview-tab-on-user-profile-page.yml create mode 100644 spec/features/users/overview_spec.rb diff --git a/app/assets/javascripts/pages/users/activity_calendar.js b/app/assets/javascripts/pages/users/activity_calendar.js index 9892a039941..bf592ba7a3c 100644 --- a/app/assets/javascripts/pages/users/activity_calendar.js +++ b/app/assets/javascripts/pages/users/activity_calendar.js @@ -43,7 +43,15 @@ const initColorKey = () => .domain([0, 3]); export default class ActivityCalendar { - constructor(container, timestamps, calendarActivitiesPath, utcOffset = 0, firstDayOfWeek = 0) { + constructor( + container, + activitiesContainer, + timestamps, + calendarActivitiesPath, + utcOffset = 0, + firstDayOfWeek = 0, + monthsAgo = 12, + ) { this.calendarActivitiesPath = calendarActivitiesPath; this.clickDay = this.clickDay.bind(this); this.currentSelectedDate = ''; @@ -66,6 +74,8 @@ export default class ActivityCalendar { ]; this.months = []; this.firstDayOfWeek = firstDayOfWeek; + this.activitiesContainer = activitiesContainer; + this.container = container; // Loop through the timestamps to create a group of objects // The group of objects will be grouped based on the day of the week they are @@ -75,13 +85,13 @@ export default class ActivityCalendar { const today = getSystemDate(utcOffset); today.setHours(0, 0, 0, 0, 0); - const oneYearAgo = new Date(today); - oneYearAgo.setFullYear(today.getFullYear() - 1); + const timeAgo = new Date(today); + timeAgo.setMonth(today.getMonth() - monthsAgo); - const days = getDayDifference(oneYearAgo, today); + const days = getDayDifference(timeAgo, today); for (let i = 0; i <= days; i += 1) { - const date = new Date(oneYearAgo); + const date = new Date(timeAgo); date.setDate(date.getDate() + i); const day = date.getDay(); @@ -280,7 +290,7 @@ export default class ActivityCalendar { this.currentSelectedDate.getDate(), ].join('-'); - $('.user-calendar-activities').html(LOADING_HTML); + $(this.activitiesContainer).html(LOADING_HTML); axios .get(this.calendarActivitiesPath, { @@ -289,11 +299,11 @@ export default class ActivityCalendar { }, responseType: 'text', }) - .then(({ data }) => $('.user-calendar-activities').html(data)) + .then(({ data }) => $(this.activitiesContainer).html(data)) .catch(() => flash(__('An error occurred while retrieving calendar activity'))); } else { this.currentSelectedDate = ''; - $('.user-calendar-activities').html(''); + $(this.activitiesContainer).html(''); } } } diff --git a/app/assets/javascripts/pages/users/user_overview_block.js b/app/assets/javascripts/pages/users/user_overview_block.js new file mode 100644 index 00000000000..0009419cd0c --- /dev/null +++ b/app/assets/javascripts/pages/users/user_overview_block.js @@ -0,0 +1,42 @@ +import axios from '~/lib/utils/axios_utils'; + +export default class UserOverviewBlock { + constructor(options = {}) { + this.container = options.container; + this.url = options.url; + this.limit = options.limit || 20; + this.loadData(); + } + + loadData() { + const loadingEl = document.querySelector(`${this.container} .loading`); + + loadingEl.classList.remove('hide'); + + axios + .get(this.url, { + params: { + limit: this.limit, + }, + }) + .then(({ data }) => this.render(data)) + .catch(() => loadingEl.classList.add('hide')); + } + + render(data) { + const { html, count } = data; + const contentList = document.querySelector(`${this.container} .overview-content-list`); + + contentList.innerHTML += html; + + const loadingEl = document.querySelector(`${this.container} .loading`); + + if (count && count > 0) { + document.querySelector(`${this.container} .js-view-all`).classList.remove('hide'); + } else { + document.querySelector(`${this.container} .nothing-here-block`).classList.add('text-left', 'p-0'); + } + + loadingEl.classList.add('hide'); + } +} diff --git a/app/assets/javascripts/pages/users/user_tabs.js b/app/assets/javascripts/pages/users/user_tabs.js index a2ca03536f2..23b0348a99f 100644 --- a/app/assets/javascripts/pages/users/user_tabs.js +++ b/app/assets/javascripts/pages/users/user_tabs.js @@ -2,9 +2,10 @@ import $ from 'jquery'; import axios from '~/lib/utils/axios_utils'; import Activities from '~/activities'; import { localTimeAgo } from '~/lib/utils/datetime_utility'; -import { __ } from '~/locale'; +import { __, sprintf } from '~/locale'; import flash from '~/flash'; import ActivityCalendar from './activity_calendar'; +import UserOverviewBlock from './user_overview_block'; /** * UserTabs @@ -61,19 +62,28 @@ import ActivityCalendar from './activity_calendar'; *
*/ -const CALENDAR_TEMPLATE = ` -
-
-
- Summary of issues, merge requests, push events, and comments +const CALENDAR_TEMPLATES = { + activity: ` +
+
+
-
-`; + `, + overview: ` +
+
+
+
+ `, +}; + +const CALENDAR_PERIOD_6_MONTHS = 6; +const CALENDAR_PERIOD_12_MONTHS = 12; export default class UserTabs { constructor({ defaultAction, action, parentEl }) { this.loaded = {}; - this.defaultAction = defaultAction || 'activity'; + this.defaultAction = defaultAction || 'overview'; this.action = action || this.defaultAction; this.$parentEl = $(parentEl) || $(document); this.windowLocation = window.location; @@ -124,6 +134,8 @@ export default class UserTabs { } if (action === 'activity') { this.loadActivities(); + } else if (action === 'overview') { + this.loadOverviewTab(); } const loadableActions = ['groups', 'contributed', 'projects', 'snippets']; @@ -154,7 +166,40 @@ export default class UserTabs { if (this.loaded.activity) { return; } - const $calendarWrap = this.$parentEl.find('.user-calendar'); + + this.loadActivityCalendar('activity'); + + // eslint-disable-next-line no-new + new Activities(); + + this.loaded.activity = true; + } + + loadOverviewTab() { + if (this.loaded.overview) { + return; + } + + this.loadActivityCalendar('overview'); + + UserTabs.renderMostRecentBlocks('#js-overview .activities-block', 5); + UserTabs.renderMostRecentBlocks('#js-overview .projects-block', 10); + + this.loaded.overview = true; + } + + static renderMostRecentBlocks(container, limit) { + // eslint-disable-next-line no-new + new UserOverviewBlock({ + container, + url: $(`${container} .overview-content-list`).data('href'), + limit, + }); + } + + loadActivityCalendar(action) { + const monthsAgo = action === 'overview' ? CALENDAR_PERIOD_6_MONTHS : CALENDAR_PERIOD_12_MONTHS; + const $calendarWrap = this.$parentEl.find('.tab-pane.active .user-calendar'); const calendarPath = $calendarWrap.data('calendarPath'); const calendarActivitiesPath = $calendarWrap.data('calendarActivitiesPath'); const utcOffset = $calendarWrap.data('utcOffset'); @@ -166,17 +211,22 @@ export default class UserTabs { axios .get(calendarPath) .then(({ data }) => { - $calendarWrap.html(CALENDAR_TEMPLATE); - $calendarWrap.find('.calendar-hint').append(`(Timezone: ${utcFormatted})`); + $calendarWrap.html(CALENDAR_TEMPLATES[action]); + + let calendarHint = ''; + + if (action === 'activity') { + calendarHint = sprintf(__('Summary of issues, merge requests, push events, and comments (Timezone: %{utcFormatted})'), { utcFormatted }); + } else if (action === 'overview') { + calendarHint = __('Issues, merge requests, pushes and comments.'); + } + + $calendarWrap.find('.calendar-hint').text(calendarHint); // eslint-disable-next-line no-new - new ActivityCalendar('.js-contrib-calendar', data, calendarActivitiesPath, utcOffset); + new ActivityCalendar('.tab-pane.active .js-contrib-calendar', '.tab-pane.active .user-calendar-activities', data, calendarActivitiesPath, utcOffset, 0, monthsAgo); }) .catch(() => flash(__('There was an error loading users activity calendar.'))); - - // eslint-disable-next-line no-new - new Activities(); - this.loaded.activity = true; } toggleLoading(status) { diff --git a/app/assets/stylesheets/framework/calendar.scss b/app/assets/stylesheets/framework/calendar.scss index 0b9dff64b0b..9638fee6078 100644 --- a/app/assets/stylesheets/framework/calendar.scss +++ b/app/assets/stylesheets/framework/calendar.scss @@ -1,8 +1,7 @@ -.calender-block { +.calendar-block { padding-left: 0; padding-right: 0; border-top: 0; - direction: rtl; @media (min-width: map-get($grid-breakpoints, sm)) and (max-width: map-get($grid-breakpoints, sm)) { overflow-x: auto; @@ -42,10 +41,13 @@ } .calendar-hint { - margin-top: -23px; - float: right; font-size: 12px; - direction: ltr; + + &.bottom-right { + direction: ltr; + margin-top: -23px; + float: right; + } } .pika-single.gitlab-theme { diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index e509098d778..d16240af404 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -29,11 +29,17 @@ class UsersController < ApplicationController format.json do load_events - pager_json("events/_events", @events.count) + pager_json("events/_events", @events.count, events: @events) end end end + def activity + respond_to do |format| + format.html { render 'show' } + end + end + def groups load_groups @@ -53,9 +59,7 @@ class UsersController < ApplicationController respond_to do |format| format.html { render 'show' } format.json do - render json: { - html: view_to_html_string("shared/projects/_list", projects: @projects) - } + pager_json("shared/projects/_list", @projects.count, projects: @projects) end end end @@ -125,6 +129,7 @@ class UsersController < ApplicationController @projects = PersonalProjectsFinder.new(user).execute(current_user) .page(params[:page]) + .per(params[:limit]) prepare_projects_for_rendering(@projects) end diff --git a/app/finders/user_recent_events_finder.rb b/app/finders/user_recent_events_finder.rb index eeca5026da1..3f2e813d381 100644 --- a/app/finders/user_recent_events_finder.rb +++ b/app/finders/user_recent_events_finder.rb @@ -31,7 +31,7 @@ class UserRecentEventsFinder recent_events(params[:offset] || 0) .joins(:project) .with_associations - .limit_recent(LIMIT, params[:offset]) + .limit_recent(params[:limit].presence || LIMIT, params[:offset]) end # rubocop: enable CodeReuse/ActiveRecord diff --git a/app/helpers/users_helper.rb b/app/helpers/users_helper.rb index bcd91f619c8..42b533ad772 100644 --- a/app/helpers/users_helper.rb +++ b/app/helpers/users_helper.rb @@ -76,7 +76,7 @@ module UsersHelper tabs = [] if can?(current_user, :read_user_profile, @user) - tabs += [:activity, :groups, :contributed, :projects, :snippets] + tabs += [:overview, :activity, :groups, :contributed, :projects, :snippets] end tabs diff --git a/app/views/users/_overview.html.haml b/app/views/users/_overview.html.haml new file mode 100644 index 00000000000..f8b3754840d --- /dev/null +++ b/app/views/users/_overview.html.haml @@ -0,0 +1,32 @@ +.row + .col-md-12.col-lg-6 + .calendar-block + .content-block.hide-bottom-border + %h4 + = s_('UserProfile|Activity') + .user-calendar.d-none.d-sm-block.text-left{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path, utc_offset: Time.zone.utc_offset } } + %h4.center.light + %i.fa.fa-spinner.fa-spin + .user-calendar-activities.d-none.d-sm-block + + - if can?(current_user, :read_cross_project) + .activities-block + .content-block + %h5.prepend-top-10 + = s_('UserProfile|Recent contributions') + .overview-content-list{ data: { href: user_path } } + .center.light.loading + %i.fa.fa-spinner.fa-spin + .prepend-top-10 + = link_to s_('UserProfile|View all'), user_activity_path, class: "hide js-view-all" + + .col-md-12.col-lg-6 + .projects-block + .content-block + %h4 + = s_('UserProfile|Personal projects') + .overview-content-list{ data: { href: user_projects_path } } + .center.light.loading + %i.fa.fa-spinner.fa-spin + .prepend-top-10 + = link_to s_('UserProfile|View all'), user_projects_path, class: "hide js-view-all" diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 7a38d290915..d6c8420b744 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -12,22 +12,22 @@ .cover-block.user-cover-block.top-area .cover-controls - if @user == current_user - = link_to profile_path, class: 'btn btn-default has-tooltip', title: 'Edit profile', 'aria-label': 'Edit profile' do + = link_to profile_path, class: 'btn btn-default has-tooltip', title: s_('UserProfile|Edit profile'), 'aria-label': 'Edit profile' do = icon('pencil') - elsif current_user - if @user.abuse_report - %button.btn.btn-danger{ title: 'Already reported for abuse', + %button.btn.btn-danger{ title: s_('UserProfile|Already reported for abuse'), data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } } = icon('exclamation-circle') - else = link_to new_abuse_report_path(user_id: @user.id, ref_url: request.referrer), class: 'btn', - title: 'Report abuse', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do + title: s_('UserProfile|Report abuse'), data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do = icon('exclamation-circle') - if can?(current_user, :read_user_profile, @user) - = link_to user_path(@user, rss_url_options), class: 'btn btn-default has-tooltip', title: 'Subscribe', 'aria-label': 'Subscribe' do + = link_to user_path(@user, rss_url_options), class: 'btn btn-default has-tooltip', title: s_('UserProfile|Subscribe'), 'aria-label': 'Subscribe' do = icon('rss') - if current_user && current_user.admin? - = link_to [:admin, @user], class: 'btn btn-default', title: 'View user in admin area', + = link_to [:admin, @user], class: 'btn btn-default', title: s_('UserProfile|View user in admin area'), data: {toggle: 'tooltip', placement: 'bottom', container: 'body'} do = icon('users') @@ -51,7 +51,7 @@ @#{@user.username} - if can?(current_user, :read_user_profile, @user) %span.middle-dot-divider - Member since #{@user.created_at.to_date.to_s(:long)} + = s_('Member since %{date}') % { date: @user.created_at.to_date.to_s(:long) } .cover-desc - unless @user.public_email.blank? @@ -91,32 +91,40 @@ .fade-left= icon('angle-left') .fade-right= icon('angle-right') %ul.nav-links.user-profile-nav.scrolling-tabs.nav.nav-tabs + - if profile_tab?(:overview) + %li.js-overview-tab + = link_to user_path, data: { target: 'div#js-overview', action: 'overview', toggle: 'tab' } do + = s_('UserProfile|Overview') - if profile_tab?(:activity) %li.js-activity-tab - = link_to user_path, data: { target: 'div#activity', action: 'activity', toggle: 'tab' } do - Activity + = link_to user_activity_path, data: { target: 'div#activity', action: 'activity', toggle: 'tab' } do + = s_('UserProfile|Activity') - if profile_tab?(:groups) %li.js-groups-tab = link_to user_groups_path, data: { target: 'div#groups', action: 'groups', toggle: 'tab', endpoint: user_groups_path(format: :json) } do - Groups + = s_('UserProfile|Groups') - if profile_tab?(:contributed) %li.js-contributed-tab = link_to user_contributed_projects_path, data: { target: 'div#contributed', action: 'contributed', toggle: 'tab', endpoint: user_contributed_projects_path(format: :json) } do - Contributed projects + = s_('UserProfile|Contributed projects') - if profile_tab?(:projects) %li.js-projects-tab = link_to user_projects_path, data: { target: 'div#projects', action: 'projects', toggle: 'tab', endpoint: user_projects_path(format: :json) } do - Personal projects + = s_('UserProfile|Personal projects') - if profile_tab?(:snippets) %li.js-snippets-tab = link_to user_snippets_path, data: { target: 'div#snippets', action: 'snippets', toggle: 'tab', endpoint: user_snippets_path(format: :json) } do - Snippets + = s_('UserProfile|Snippets') %div{ class: container_class } .tab-content + - if profile_tab?(:overview) + #js-overview.tab-pane + = render "users/overview" + - if profile_tab?(:activity) #activity.tab-pane - .row-content-block.calender-block.white.second-block.d-none.d-sm-block + .row-content-block.calendar-block.white.second-block.d-none.d-sm-block .user-calendar{ data: { calendar_path: user_calendar_path(@user, :json), calendar_activities_path: user_calendar_activities_path, utc_offset: Time.zone.utc_offset } } %h4.center.light %i.fa.fa-spinner.fa-spin @@ -124,7 +132,7 @@ - if can?(current_user, :read_cross_project) %h4.prepend-top-20 - Most Recent Activity + = s_('UserProfile|Most Recent Activity') .content_list{ data: { href: user_path } } = spinner @@ -155,4 +163,4 @@ .col-12.text-center .text-content %h4 - This user has a private profile + = s_('UserProfile|This user has a private profile') diff --git a/changelogs/unreleased/49801-add-new-overview-tab-on-user-profile-page.yml b/changelogs/unreleased/49801-add-new-overview-tab-on-user-profile-page.yml new file mode 100644 index 00000000000..5e2be42c8b7 --- /dev/null +++ b/changelogs/unreleased/49801-add-new-overview-tab-on-user-profile-page.yml @@ -0,0 +1,5 @@ +--- +title: Adds new 'Overview' tab on user profile page +merge_request: 21663 +author: +type: other diff --git a/config/routes/user.rb b/config/routes/user.rb index bc7df5e7584..e0ae264e2c0 100644 --- a/config/routes/user.rb +++ b/config/routes/user.rb @@ -45,6 +45,7 @@ scope(constraints: { username: Gitlab::PathRegex.root_namespace_route_regex }) d get :contributed, as: :contributed_projects get :snippets get :exists + get :activity get '/', to: redirect('%{username}'), as: nil end diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 5591b0b8bb2..be002d84f0d 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -3345,6 +3345,9 @@ msgstr "" msgid "Issues can be bugs, tasks or ideas to be discussed. Also, issues are searchable and filterable." msgstr "" +msgid "Issues, merge requests, pushes and comments." +msgstr "" + msgid "Jan" msgstr "" @@ -3709,6 +3712,9 @@ msgstr "" msgid "Median" msgstr "" +msgid "Member since %{date}" +msgstr "" + msgid "Members" msgstr "" @@ -5753,6 +5759,9 @@ msgstr "" msgid "Subscribe at project level" msgstr "" +msgid "Summary of issues, merge requests, push events, and comments (Timezone: %{utcFormatted})" +msgstr "" + msgid "Switch branch/tag" msgstr "" @@ -6581,6 +6590,51 @@ msgstr "" msgid "User map" msgstr "" +msgid "UserProfile|Activity" +msgstr "" + +msgid "UserProfile|Already reported for abuse" +msgstr "" + +msgid "UserProfile|Contributed projects" +msgstr "" + +msgid "UserProfile|Edit profile" +msgstr "" + +msgid "UserProfile|Groups" +msgstr "" + +msgid "UserProfile|Most Recent Activity" +msgstr "" + +msgid "UserProfile|Overview" +msgstr "" + +msgid "UserProfile|Personal projects" +msgstr "" + +msgid "UserProfile|Recent contributions" +msgstr "" + +msgid "UserProfile|Report abuse" +msgstr "" + +msgid "UserProfile|Snippets" +msgstr "" + +msgid "UserProfile|Subscribe" +msgstr "" + +msgid "UserProfile|This user has a private profile" +msgstr "" + +msgid "UserProfile|View all" +msgstr "" + +msgid "UserProfile|View user in admin area" +msgstr "" + msgid "Users" msgstr "" diff --git a/spec/features/calendar_spec.rb b/spec/features/calendar_spec.rb index f08946b0593..aa3ca8923ff 100644 --- a/spec/features/calendar_spec.rb +++ b/spec/features/calendar_spec.rb @@ -64,7 +64,7 @@ describe 'Contributions Calendar', :js do end def selected_day_activities(visible: true) - find('.user-calendar-activities', visible: visible).text + find('.tab-pane#activity .user-calendar-activities', visible: visible).text end before do @@ -74,15 +74,16 @@ describe 'Contributions Calendar', :js do describe 'calendar day selection' do before do visit user.username + page.find('.js-activity-tab a').click wait_for_requests end it 'displays calendar' do - expect(page).to have_css('.js-contrib-calendar') + expect(find('.tab-pane#activity')).to have_css('.js-contrib-calendar') end describe 'select calendar day' do - let(:cells) { page.all('.user-contrib-cell') } + let(:cells) { page.all('.tab-pane#activity .user-contrib-cell') } before do cells[0].click @@ -108,6 +109,7 @@ describe 'Contributions Calendar', :js do describe 'deselect calendar day' do before do cells[0].click + page.find('.js-activity-tab a').click wait_for_requests end @@ -122,6 +124,7 @@ describe 'Contributions Calendar', :js do shared_context 'visit user page' do before do visit user.username + page.find('.js-activity-tab a').click wait_for_requests end end @@ -130,12 +133,12 @@ describe 'Contributions Calendar', :js do include_context 'visit user page' it 'displays calendar activity square color for 1 contribution' do - expect(page).to have_selector(get_cell_color_selector(contribution_count), count: 1) + expect(find('.tab-pane#activity')).to have_selector(get_cell_color_selector(contribution_count), count: 1) end it 'displays calendar activity square on the correct date' do today = Date.today.strftime(date_format) - expect(page).to have_selector(get_cell_date_selector(contribution_count, today), count: 1) + expect(find('.tab-pane#activity')).to have_selector(get_cell_date_selector(contribution_count, today), count: 1) end end @@ -150,7 +153,7 @@ describe 'Contributions Calendar', :js do include_context 'visit user page' it 'displays calendar activity log' do - expect(find('.content_list .event-note')).to have_content issue_title + expect(find('.tab-pane#activity .content_list .event-note')).to have_content issue_title end end end @@ -182,17 +185,17 @@ describe 'Contributions Calendar', :js do include_context 'visit user page' it 'displays calendar activity squares for both days' do - expect(page).to have_selector(get_cell_color_selector(1), count: 2) + expect(find('.tab-pane#activity')).to have_selector(get_cell_color_selector(1), count: 2) end it 'displays calendar activity square for yesterday' do yesterday = Date.yesterday.strftime(date_format) - expect(page).to have_selector(get_cell_date_selector(1, yesterday), count: 1) + expect(find('.tab-pane#activity')).to have_selector(get_cell_date_selector(1, yesterday), count: 1) end it 'displays calendar activity square for today' do today = Date.today.strftime(date_format) - expect(page).to have_selector(get_cell_date_selector(1, today), count: 1) + expect(find('.tab-pane#activity')).to have_selector(get_cell_date_selector(1, today), count: 1) end end end diff --git a/spec/features/dashboard/datetime_on_tooltips_spec.rb b/spec/features/dashboard/datetime_on_tooltips_spec.rb index d7234158fa1..0db8093411b 100644 --- a/spec/features/dashboard/datetime_on_tooltips_spec.rb +++ b/spec/features/dashboard/datetime_on_tooltips_spec.rb @@ -14,7 +14,7 @@ describe 'Tooltips on .timeago dates', :js do updated_at: created_date, created_at: created_date) sign_in user - visit user_path(user) + visit user_activity_path(user) wait_for_requests() page.find('.js-timeago').hover diff --git a/spec/features/users/overview_spec.rb b/spec/features/users/overview_spec.rb new file mode 100644 index 00000000000..11f357cbaa5 --- /dev/null +++ b/spec/features/users/overview_spec.rb @@ -0,0 +1,123 @@ +require 'spec_helper' + +describe 'Overview tab on a user profile', :js do + let(:user) { create(:user) } + let(:contributed_project) { create(:project, :public, :repository) } + + def push_code_contribution + event = create(:push_event, project: contributed_project, author: user) + + create(:push_event_payload, + event: event, + commit_from: '11f9ac0a48b62cef25eedede4c1819964f08d5ce', + commit_to: '1cf19a015df3523caf0a1f9d40c98a267d6a2fc2', + commit_count: 3, + ref: 'master') + end + + before do + sign_in user + end + + describe 'activities section' do + shared_context 'visit overview tab' do + before do + visit user.username + page.find('.js-overview-tab a').click + wait_for_requests + end + end + + describe 'user has no activities' do + include_context 'visit overview tab' + + it 'does not show any entries in the list of activities' do + page.within('.activities-block') do + expect(page).not_to have_selector('.event-item') + end + end + + it 'does not show a link to the activity list' do + expect(find('#js-overview .activities-block')).to have_selector('.js-view-all', visible: false) + end + end + + describe 'user has 3 activities' do + before do + 3.times { push_code_contribution } + end + + include_context 'visit overview tab' + + it 'display 3 entries in the list of activities' do + expect(find('#js-overview')).to have_selector('.event-item', count: 3) + end + end + + describe 'user has 10 activities' do + before do + 10.times { push_code_contribution } + end + + include_context 'visit overview tab' + + it 'displays 5 entries in the list of activities' do + expect(find('#js-overview')).to have_selector('.event-item', count: 5) + end + + it 'shows a link to the activity list' do + expect(find('#js-overview .activities-block')).to have_selector('.js-view-all', visible: true) + end + + it 'links to the activity tab' do + page.within('.activities-block') do + find('.js-view-all').click + wait_for_requests + expect(URI.parse(current_url).path).to eq("/users/#{user.username}/activity") + end + end + end + end + + describe 'projects section' do + shared_context 'visit overview tab' do + before do + visit user.username + page.find('.js-overview-tab a').click + wait_for_requests + end + end + + describe 'user has no personal projects' do + include_context 'visit overview tab' + + it 'it shows an empty project list with an info message' do + page.within('.projects-block') do + expect(page).to have_content('No projects found') + expect(page).not_to have_selector('.project-row') + end + end + + it 'does not show a link to the project list' do + expect(find('#js-overview .projects-block')).to have_selector('.js-view-all', visible: false) + end + end + + describe 'user has a personal project' do + let(:private_project) { create(:project, :private, namespace: user.namespace, creator: user) { |p| p.add_maintainer(user) } } + let!(:private_event) { create(:event, project: private_project, author: user) } + + include_context 'visit overview tab' + + it 'it shows one entry in the list of projects' do + page.within('.projects-block') do + expect(page).to have_selector('.project-row', count: 1) + end + end + + it 'shows a link to the project list' do + expect(find('#js-overview .projects-block')).to have_selector('.js-view-all', visible: true) + end + end + end +end diff --git a/spec/features/users/show_spec.rb b/spec/features/users/show_spec.rb index bc07ab48c39..86379164cf0 100644 --- a/spec/features/users/show_spec.rb +++ b/spec/features/users/show_spec.rb @@ -8,6 +8,7 @@ describe 'User page' do visit(user_path(user)) page.within '.nav-links' do + expect(page).to have_link('Overview') expect(page).to have_link('Activity') expect(page).to have_link('Groups') expect(page).to have_link('Contributed projects') @@ -44,6 +45,7 @@ describe 'User page' do visit(user_path(user)) page.within '.nav-links' do + expect(page).to have_link('Overview') expect(page).to have_link('Activity') expect(page).to have_link('Groups') expect(page).to have_link('Contributed projects') -- cgit v1.2.1 From aa2ae1715699b51e315371b356c015a0acb15885 Mon Sep 17 00:00:00 2001 From: GitLab Release Tools Bot Date: Thu, 4 Oct 2018 08:02:09 +0000 Subject: Update CHANGELOG.md for 11.3.3 [ci skip] --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index be204a76645..a02d1bc38b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 11.3.3 (2018-10-04) + +- No changes. + ## 11.3.2 (2018-10-03) ### Fixed (4 changes) -- cgit v1.2.1 From 4edcb02f94ba832929c054097d2f8badc0a34060 Mon Sep 17 00:00:00 2001 From: Dennis Tang Date: Thu, 4 Oct 2018 08:19:51 +0000 Subject: Resolve "Add status message from within user menu" --- app/assets/javascripts/api.js | 10 + app/assets/javascripts/awards_handler.js | 32 +-- app/assets/javascripts/header.js | 55 +++++ .../javascripts/pages/profiles/show/index.js | 4 +- .../set_status_modal/emoji_menu_in_modal.js | 21 ++ .../javascripts/set_status_modal/event_hub.js | 3 + .../set_status_modal/set_status_modal_trigger.vue | 33 +++ .../set_status_modal/set_status_modal_wrapper.vue | 241 +++++++++++++++++++ app/assets/stylesheets/framework/header.scss | 36 ++- app/assets/stylesheets/framework/mixins.scss | 56 +++++ app/assets/stylesheets/framework/variables.scss | 4 +- app/assets/stylesheets/pages/notes.scss | 54 +---- app/assets/stylesheets/pages/profile.scss | 10 +- .../header/_current_user_dropdown.html.haml | 7 + app/views/layouts/header/_default.html.haml | 3 + ...75-add-status-message-from-within-user-menu.yml | 5 + doc/user/profile/index.md | 7 + locale/gitlab.pot | 27 +++ spec/features/profiles/user_edit_profile_spec.rb | 254 ++++++++++++++++----- 19 files changed, 730 insertions(+), 132 deletions(-) create mode 100644 app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js create mode 100644 app/assets/javascripts/set_status_modal/event_hub.js create mode 100644 app/assets/javascripts/set_status_modal/set_status_modal_trigger.vue create mode 100644 app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue create mode 100644 changelogs/unreleased/49075-add-status-message-from-within-user-menu.yml diff --git a/app/assets/javascripts/api.js b/app/assets/javascripts/api.js index cd800d75f7a..ecc2440c7e6 100644 --- a/app/assets/javascripts/api.js +++ b/app/assets/javascripts/api.js @@ -22,6 +22,7 @@ const Api = { dockerfilePath: '/api/:version/templates/dockerfiles/:key', issuableTemplatePath: '/:namespace_path/:project_path/templates/:type/:key', usersPath: '/api/:version/users.json', + userStatusPath: '/api/:version/user/status', commitPath: '/api/:version/projects/:id/repository/commits', commitPipelinesPath: '/:project_id/commit/:sha/pipelines', branchSinglePath: '/api/:version/projects/:id/repository/branches/:branch', @@ -266,6 +267,15 @@ const Api = { }); }, + postUserStatus({ emoji, message }) { + const url = Api.buildUrl(this.userStatusPath); + + return axios.put(url, { + emoji, + message, + }); + }, + templates(key, params = {}) { const url = Api.buildUrl(this.templatesPath).replace(':key', key); diff --git a/app/assets/javascripts/awards_handler.js b/app/assets/javascripts/awards_handler.js index 5b0c4285339..cace8bb9dba 100644 --- a/app/assets/javascripts/awards_handler.js +++ b/app/assets/javascripts/awards_handler.js @@ -42,10 +42,11 @@ export class AwardsHandler { } bindEvents() { + const $parentEl = this.targetContainerEl ? $(this.targetContainerEl) : $(document); // If the user shows intent let's pre-build the menu this.registerEventListener( 'one', - $(document), + $parentEl, 'mouseenter focus', this.toggleButtonSelector, 'mouseenter focus', @@ -58,7 +59,7 @@ export class AwardsHandler { } }, ); - this.registerEventListener('on', $(document), 'click', this.toggleButtonSelector, e => { + this.registerEventListener('on', $parentEl, 'click', this.toggleButtonSelector, e => { e.stopPropagation(); e.preventDefault(); this.showEmojiMenu($(e.currentTarget)); @@ -76,7 +77,7 @@ export class AwardsHandler { }); const emojiButtonSelector = `.js-awards-block .js-emoji-btn, .${this.menuClass} .js-emoji-btn`; - this.registerEventListener('on', $(document), 'click', emojiButtonSelector, e => { + this.registerEventListener('on', $parentEl, 'click', emojiButtonSelector, e => { e.preventDefault(); const $target = $(e.currentTarget); const $glEmojiElement = $target.find('gl-emoji'); @@ -168,7 +169,8 @@ export class AwardsHandler {
`; - document.body.insertAdjacentHTML('beforeend', emojiMenuMarkup); + const targetEl = this.targetContainerEl ? this.targetContainerEl : document.body; + targetEl.insertAdjacentHTML('beforeend', emojiMenuMarkup); this.addRemainingEmojiMenuCategories(); this.setupSearch(); @@ -250,6 +252,12 @@ export class AwardsHandler { } positionMenu($menu, $addBtn) { + if (this.targetContainerEl) { + return $menu.css({ + top: `${$addBtn.outerHeight()}px`, + }); + } + const position = $addBtn.data('position'); // The menu could potentially be off-screen or in a hidden overflow element // So we position the element absolute in the body @@ -424,9 +432,7 @@ export class AwardsHandler { users = origTitle.trim().split(FROM_SENTENCE_REGEX); } users.unshift('You'); - return awardBlock - .attr('title', this.toSentence(users)) - .tooltip('_fixTitle'); + return awardBlock.attr('title', this.toSentence(users)).tooltip('_fixTitle'); } createAwardButtonForVotesBlock(votesBlock, emojiName) { @@ -609,13 +615,11 @@ export class AwardsHandler { let awardsHandlerPromise = null; export default function loadAwardsHandler(reload = false) { if (!awardsHandlerPromise || reload) { - awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji').then( - Emoji => { - const awardsHandler = new AwardsHandler(Emoji); - awardsHandler.bindEvents(); - return awardsHandler; - }, - ); + awardsHandlerPromise = import(/* webpackChunkName: 'emoji' */ './emoji').then(Emoji => { + const awardsHandler = new AwardsHandler(Emoji); + awardsHandler.bindEvents(); + return awardsHandler; + }); } return awardsHandlerPromise; } diff --git a/app/assets/javascripts/header.js b/app/assets/javascripts/header.js index 4ae3a714bee..175d0b8498b 100644 --- a/app/assets/javascripts/header.js +++ b/app/assets/javascripts/header.js @@ -1,5 +1,9 @@ import $ from 'jquery'; +import Vue from 'vue'; +import Translate from '~/vue_shared/translate'; import { highCountTrim } from '~/lib/utils/text_utility'; +import SetStatusModalTrigger from './set_status_modal/set_status_modal_trigger.vue'; +import SetStatusModalWrapper from './set_status_modal/set_status_modal_wrapper.vue'; /** * Updates todo counter when todos are toggled. @@ -17,3 +21,54 @@ export default function initTodoToggle() { $todoPendingCount.toggleClass('hidden', parsedCount === 0); }); } + +document.addEventListener('DOMContentLoaded', () => { + const setStatusModalTriggerEl = document.querySelector('.js-set-status-modal-trigger'); + const setStatusModalWrapperEl = document.querySelector('.js-set-status-modal-wrapper'); + + if (setStatusModalTriggerEl || setStatusModalWrapperEl) { + Vue.use(Translate); + + // eslint-disable-next-line no-new + new Vue({ + el: setStatusModalTriggerEl, + data() { + const { hasStatus } = this.$options.el.dataset; + + return { + hasStatus: hasStatus === 'true', + }; + }, + render(createElement) { + return createElement(SetStatusModalTrigger, { + props: { + hasStatus: this.hasStatus, + }, + }); + }, + }); + + // eslint-disable-next-line no-new + new Vue({ + el: setStatusModalWrapperEl, + data() { + const { currentEmoji, currentMessage } = this.$options.el.dataset; + + return { + currentEmoji, + currentMessage, + }; + }, + render(createElement) { + const { currentEmoji, currentMessage } = this; + + return createElement(SetStatusModalWrapper, { + props: { + currentEmoji, + currentMessage, + }, + }); + }, + }); + } +}); diff --git a/app/assets/javascripts/pages/profiles/show/index.js b/app/assets/javascripts/pages/profiles/show/index.js index aea7b649c20..c7ce4675573 100644 --- a/app/assets/javascripts/pages/profiles/show/index.js +++ b/app/assets/javascripts/pages/profiles/show/index.js @@ -11,7 +11,7 @@ document.addEventListener('DOMContentLoaded', () => { const statusEmojiField = document.getElementById('js-status-emoji-field'); const statusMessageField = document.getElementById('js-status-message-field'); - const toggleNoEmojiPlaceholder = (isVisible) => { + const toggleNoEmojiPlaceholder = isVisible => { const placeholderElement = document.getElementById('js-no-emoji-placeholder'); placeholderElement.classList.toggle('hidden', !isVisible); }; @@ -69,5 +69,5 @@ document.addEventListener('DOMContentLoaded', () => { } }); }) - .catch(() => createFlash('Failed to load emoji list!')); + .catch(() => createFlash('Failed to load emoji list.')); }); diff --git a/app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js b/app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js new file mode 100644 index 00000000000..14a89ef9293 --- /dev/null +++ b/app/assets/javascripts/set_status_modal/emoji_menu_in_modal.js @@ -0,0 +1,21 @@ +import { AwardsHandler } from '~/awards_handler'; + +class EmojiMenuInModal extends AwardsHandler { + constructor(emoji, toggleButtonSelector, menuClass, selectEmojiCallback, targetContainerEl) { + super(emoji); + + this.selectEmojiCallback = selectEmojiCallback; + this.toggleButtonSelector = toggleButtonSelector; + this.menuClass = menuClass; + this.targetContainerEl = targetContainerEl; + + this.bindEvents(); + } + + postEmoji($emojiButton, awardUrl, selectedEmoji, callback) { + this.selectEmojiCallback(selectedEmoji, this.emoji.glEmojiTag(selectedEmoji)); + callback(); + } +} + +export default EmojiMenuInModal; diff --git a/app/assets/javascripts/set_status_modal/event_hub.js b/app/assets/javascripts/set_status_modal/event_hub.js new file mode 100644 index 00000000000..0948c2e5352 --- /dev/null +++ b/app/assets/javascripts/set_status_modal/event_hub.js @@ -0,0 +1,3 @@ +import Vue from 'vue'; + +export default new Vue(); diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_trigger.vue b/app/assets/javascripts/set_status_modal/set_status_modal_trigger.vue new file mode 100644 index 00000000000..48e5ede80f2 --- /dev/null +++ b/app/assets/javascripts/set_status_modal/set_status_modal_trigger.vue @@ -0,0 +1,33 @@ + + + diff --git a/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue new file mode 100644 index 00000000000..43f0b6651b9 --- /dev/null +++ b/app/assets/javascripts/set_status_modal/set_status_modal_wrapper.vue @@ -0,0 +1,241 @@ + + + diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 11a30d83f03..c430009bfe0 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -529,9 +529,10 @@ } .header-user { - .dropdown-menu { + &.show .dropdown-menu { width: auto; min-width: unset; + max-height: 323px; margin-top: 4px; color: $gl-text-color; left: auto; @@ -542,6 +543,18 @@ .user-name { display: block; } + + .user-status-emoji { + margin-right: 0; + display: block; + vertical-align: text-top; + max-width: 148px; + font-size: 12px; + + gl-emoji { + font-size: $gl-font-size; + } + } } svg { @@ -573,3 +586,24 @@ } } } + +.set-user-status-modal { + .modal-body { + min-height: unset; + } + + .input-lg { + max-width: unset; + } + + .no-emoji-placeholder, + .clear-user-status { + svg { + fill: $gl-text-color-secondary; + } + } + + .emoji-menu-toggle-button { + @include emoji-menu-toggle-button; + } +} diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 7f37dd3de91..11597c7e85e 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -266,3 +266,59 @@ border-radius: 50%; } } + +@mixin emoji-menu-toggle-button { + line-height: 1; + padding: 0; + min-width: 16px; + color: $gray-darkest; + fill: $gray-darkest; + + .fa { + position: relative; + font-size: 16px; + } + + svg { + @include btn-svg; + margin: 0; + } + + .award-control-icon-positive, + .award-control-icon-super-positive { + position: absolute; + top: 0; + left: 0; + opacity: 0; + } + + &:hover, + &.is-active { + .danger-highlight { + color: $red-500; + } + + .link-highlight { + color: $blue-600; + fill: $blue-600; + } + + .award-control-icon-neutral { + opacity: 0; + } + + .award-control-icon-positive { + opacity: 1; + } + } + + &.is-active { + .award-control-icon-positive { + opacity: 0; + } + + .award-control-icon-super-positive { + opacity: 1; + } + } +} diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 837016db67b..b7a95f604b8 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -314,7 +314,8 @@ $diff-jagged-border-gradient-color: darken($white-normal, 8%); $monospace-font: 'Menlo', 'DejaVu Sans Mono', 'Liberation Mono', 'Consolas', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace; $regular-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen-Sans, Ubuntu, Cantarell, - 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; + 'Helvetica Neue', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', + 'Noto Color Emoji'; /* * Dropdowns @@ -634,5 +635,4 @@ Modals */ $modal-body-height: 134px; - $priority-label-empty-state-width: 114px; diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss index c9e0899425f..a2070087963 100644 --- a/app/assets/stylesheets/pages/notes.scss +++ b/app/assets/stylesheets/pages/notes.scss @@ -519,59 +519,7 @@ ul.notes { } .note-action-button { - line-height: 1; - padding: 0; - min-width: 16px; - color: $gray-darkest; - fill: $gray-darkest; - - .fa { - position: relative; - font-size: 16px; - } - - svg { - @include btn-svg; - margin: 0; - } - - .award-control-icon-positive, - .award-control-icon-super-positive { - position: absolute; - top: 0; - left: 0; - opacity: 0; - } - - &:hover, - &.is-active { - .danger-highlight { - color: $red-500; - } - - .link-highlight { - color: $blue-600; - fill: $blue-600; - } - - .award-control-icon-neutral { - opacity: 0; - } - - .award-control-icon-positive { - opacity: 1; - } - } - - &.is-active { - .award-control-icon-positive { - opacity: 0; - } - - .award-control-icon-super-positive { - opacity: 1; - } - } + @include emoji-menu-toggle-button; } .discussion-toggle-button { diff --git a/app/assets/stylesheets/pages/profile.scss b/app/assets/stylesheets/pages/profile.scss index caa839c32a5..f084adaf5d3 100644 --- a/app/assets/stylesheets/pages/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -81,14 +81,14 @@ // Middle dot divider between each element in a list of items. .middle-dot-divider { &::after { - content: "\00B7"; // Middle Dot + content: '\00B7'; // Middle Dot padding: 0 6px; font-weight: $gl-font-weight-bold; } &:last-child { &::after { - content: ""; + content: ''; padding: 0; } } @@ -191,7 +191,6 @@ @include media-breakpoint-down(xs) { width: auto; } - } .profile-crop-image-container { @@ -215,7 +214,6 @@ } } - .user-profile { .cover-controls a { margin-left: 5px; @@ -418,7 +416,7 @@ table.u2f-registrations { } &.unverified { - @include status-color($gray-dark, color("gray"), $common-gray-dark); + @include status-color($gray-dark, color('gray'), $common-gray-dark); } } } @@ -431,7 +429,7 @@ table.u2f-registrations { } .emoji-menu-toggle-button { - @extend .note-action-button; + @include emoji-menu-toggle-button; .no-emoji-placeholder { position: relative; diff --git a/app/views/layouts/header/_current_user_dropdown.html.haml b/app/views/layouts/header/_current_user_dropdown.html.haml index 9ed05d6e3d0..261d758622b 100644 --- a/app/views/layouts/header/_current_user_dropdown.html.haml +++ b/app/views/layouts/header/_current_user_dropdown.html.haml @@ -5,7 +5,14 @@ .user-name.bold = current_user.name = current_user.to_reference + - if current_user.status + .user-status-emoji.str-truncated.has-tooltip{ title: current_user.status.message_html, data: { html: 'true', placement: 'bottom' } } + = emoji_icon current_user.status.emoji + = current_user.status.message_html.html_safe %li.divider + - if can?(current_user, :update_user_status, current_user) + %li + .js-set-status-modal-trigger{ data: { has_status: current_user.status.present? ? 'true' : 'false' } } - if current_user_menu?(:profile) %li = link_to s_("CurrentUser|Profile"), current_user, class: 'profile-link', data: { user: current_user.username } diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 044b49c12cc..39604611440 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -74,3 +74,6 @@ %span.sr-only= _('Toggle navigation') = sprite_icon('ellipsis_h', size: 12, css_class: 'more-icon js-navbar-toggle-right') = sprite_icon('close', size: 12, css_class: 'close-icon js-navbar-toggle-left') + +- if can?(current_user, :update_user_status, current_user) + .js-set-status-modal-wrapper{ data: { current_emoji: current_user.status.present? ? current_user.status.emoji : '', current_message: current_user.status.present? ? current_user.status.message : '' } } diff --git a/changelogs/unreleased/49075-add-status-message-from-within-user-menu.yml b/changelogs/unreleased/49075-add-status-message-from-within-user-menu.yml new file mode 100644 index 00000000000..2c65c92dd8b --- /dev/null +++ b/changelogs/unreleased/49075-add-status-message-from-within-user-menu.yml @@ -0,0 +1,5 @@ +--- +title: Set user status from within user menu +merge_request: 21643 +author: +type: added diff --git a/doc/user/profile/index.md b/doc/user/profile/index.md index 8604ea27f99..ab62762f343 100644 --- a/doc/user/profile/index.md +++ b/doc/user/profile/index.md @@ -115,6 +115,13 @@ Please be aware that your status is publicly visible even if your [profile is pr To set your current status: +1. Open the user menu in the top-right corner of the navigation bar. +1. Hit **Set status**, or **Edit status** if you have already set a status. +1. Set the emoji and/or status message to your liking. +1. Hit **Set status**. Alternatively, you can also hit **Remove status** to remove your user status entirely. + +or + 1. Navigate to your personal [profile settings](#profile-settings). 1. In the text field below `Your status`, enter your status message. 1. Select an emoji from the dropdown if you like. diff --git a/locale/gitlab.pot b/locale/gitlab.pot index be002d84f0d..93c7ff32946 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -2722,6 +2722,9 @@ msgstr "" msgid "Failed to check related branches." msgstr "" +msgid "Failed to load emoji list." +msgstr "" + msgid "Failed to remove issue from board, please try again." msgstr "" @@ -5445,6 +5448,30 @@ msgstr "" msgid "SetPasswordToCloneLink|set a password" msgstr "" +msgid "SetStatusModal|Add status emoji" +msgstr "" + +msgid "SetStatusModal|Clear status" +msgstr "" + +msgid "SetStatusModal|Edit status" +msgstr "" + +msgid "SetStatusModal|Remove status" +msgstr "" + +msgid "SetStatusModal|Set a status" +msgstr "" + +msgid "SetStatusModal|Set status" +msgstr "" + +msgid "SetStatusModal|Sorry, we weren't able to set your status. Please try again later." +msgstr "" + +msgid "SetStatusModal|What's your status?" +msgstr "" + msgid "Settings" msgstr "" diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index 206a3a4fe9a..e168bb0fc89 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -61,83 +61,229 @@ describe 'User edit profile' do end context 'user status', :js do - def select_emoji(emoji_name) + def select_emoji(emoji_name, is_modal = false) + emoji_menu_class = is_modal ? '.js-modal-status-emoji-menu' : '.js-status-emoji-menu' toggle_button = find('.js-toggle-emoji-menu') toggle_button.click - emoji_button = find(%Q{.js-status-emoji-menu .js-emoji-btn gl-emoji[data-name="#{emoji_name}"]}) + emoji_button = find(%Q{#{emoji_menu_class} .js-emoji-btn gl-emoji[data-name="#{emoji_name}"]}) emoji_button.click end - it 'shows the user status form' do - visit(profile_path) + context 'profile edit form' do + it 'shows the user status form' do + visit(profile_path) - expect(page).to have_content('Current status') - end + expect(page).to have_content('Current status') + end - it 'adds emoji to user status' do - emoji = 'biohazard' - visit(profile_path) - select_emoji(emoji) - submit_settings + it 'adds emoji to user status' do + emoji = 'biohazard' + visit(profile_path) + select_emoji(emoji) + submit_settings - visit user_path(user) - within('.cover-status') do - expect(page).to have_emoji(emoji) + visit user_path(user) + within('.cover-status') do + expect(page).to have_emoji(emoji) + end end - end - it 'adds message to user status' do - message = 'I have something to say' - visit(profile_path) - fill_in 'js-status-message-field', with: message - submit_settings + it 'adds message to user status' do + message = 'I have something to say' + visit(profile_path) + fill_in 'js-status-message-field', with: message + submit_settings - visit user_path(user) - within('.cover-status') do - expect(page).to have_emoji('speech_balloon') - expect(page).to have_content message + visit user_path(user) + within('.cover-status') do + expect(page).to have_emoji('speech_balloon') + expect(page).to have_content message + end end - end - it 'adds message and emoji to user status' do - emoji = 'tanabata_tree' - message = 'Playing outside' - visit(profile_path) - select_emoji(emoji) - fill_in 'js-status-message-field', with: message - submit_settings + it 'adds message and emoji to user status' do + emoji = 'tanabata_tree' + message = 'Playing outside' + visit(profile_path) + select_emoji(emoji) + fill_in 'js-status-message-field', with: message + submit_settings - visit user_path(user) - within('.cover-status') do - expect(page).to have_emoji(emoji) - expect(page).to have_content message + visit user_path(user) + within('.cover-status') do + expect(page).to have_emoji(emoji) + expect(page).to have_content message + end end - end - it 'clears the user status' do - user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread') + it 'clears the user status' do + user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread') + + visit user_path(user) + within('.cover-status') do + expect(page).to have_emoji(user_status.emoji) + expect(page).to have_content user_status.message + end + + visit(profile_path) + click_button 'js-clear-user-status-button' + submit_settings - visit user_path(user) - within('.cover-status') do - expect(page).to have_emoji(user_status.emoji) - expect(page).to have_content user_status.message + visit user_path(user) + expect(page).not_to have_selector '.cover-status' end - visit(profile_path) - click_button 'js-clear-user-status-button' - submit_settings + it 'displays a default emoji if only message is entered' do + message = 'a status without emoji' + visit(profile_path) + fill_in 'js-status-message-field', with: message - visit user_path(user) - expect(page).not_to have_selector '.cover-status' + within('.js-toggle-emoji-menu') do + expect(page).to have_emoji('speech_balloon') + end + end end - it 'displays a default emoji if only message is entered' do - message = 'a status without emoji' - visit(profile_path) - fill_in 'js-status-message-field', with: message + context 'user menu' do + def open_user_status_modal + find('.header-user-dropdown-toggle').click + + page.within ".header-user" do + click_button 'Set status' + end + end + + def set_user_status_in_modal + page.within "#set-user-status-modal" do + click_button 'Set status' + end + end + + before do + visit root_path(user) + end + + it 'shows the "Set status" menu item in the user menu' do + find('.header-user-dropdown-toggle').click + + page.within ".header-user" do + expect(page).to have_content('Set status') + end + end + + it 'shows the "Edit status" menu item in the user menu' do + user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread') + visit root_path(user) + + find('.header-user-dropdown-toggle').click + + page.within ".header-user" do + expect(page).to have_emoji(user_status.emoji) + expect(page).to have_content user_status.message + expect(page).to have_content('Edit status') + end + end + + it 'shows user status modal' do + open_user_status_modal + + expect(page.find('#set-user-status-modal')).to be_visible + expect(page).to have_content('Set a status') + end + + it 'adds emoji to user status' do + emoji = 'biohazard' + open_user_status_modal + select_emoji(emoji, true) + set_user_status_in_modal + + visit user_path(user) + within('.cover-status') do + expect(page).to have_emoji(emoji) + end + end + + it 'adds message to user status' do + message = 'I have something to say' + open_user_status_modal + find('.js-status-message-field').native.send_keys(message) + set_user_status_in_modal + + visit user_path(user) + within('.cover-status') do + expect(page).to have_emoji('speech_balloon') + expect(page).to have_content message + end + end + + it 'adds message and emoji to user status' do + emoji = 'tanabata_tree' + message = 'Playing outside' + open_user_status_modal + select_emoji(emoji, true) + find('.js-status-message-field').native.send_keys(message) + set_user_status_in_modal + + visit user_path(user) + within('.cover-status') do + expect(page).to have_emoji(emoji) + expect(page).to have_content message + end + end + + it 'clears the user status with the "X" button' do + user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread') + + visit user_path(user) + within('.cover-status') do + expect(page).to have_emoji(user_status.emoji) + expect(page).to have_content user_status.message + end + + find('.header-user-dropdown-toggle').click + + page.within ".header-user" do + click_button 'Edit status' + end + + find('.js-clear-user-status-button').click + set_user_status_in_modal + + visit user_path(user) + expect(page).not_to have_selector '.cover-status' + end + + it 'clears the user status with the "Remove status" button' do + user_status = create(:user_status, user: user, message: 'Eating bread', emoji: 'stuffed_flatbread') + + visit user_path(user) + within('.cover-status') do + expect(page).to have_emoji(user_status.emoji) + expect(page).to have_content user_status.message + end + + find('.header-user-dropdown-toggle').click + + page.within ".header-user" do + click_button 'Edit status' + end + + page.within "#set-user-status-modal" do + click_button 'Remove status' + end + + visit user_path(user) + expect(page).not_to have_selector '.cover-status' + end + + it 'displays a default emoji if only message is entered' do + message = 'a status without emoji' + open_user_status_modal + find('.js-status-message-field').native.send_keys(message) - within('.js-toggle-emoji-menu') do - expect(page).to have_emoji('speech_balloon') + within('.js-toggle-emoji-menu') do + expect(page).to have_emoji('speech_balloon') + end end end end -- cgit v1.2.1 From c0e9eb0eaca198f5ae12d6bd693ddcd705f79369 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jarka=20Ko=C5=A1anov=C3=A1?= Date: Sat, 22 Sep 2018 10:07:20 +0200 Subject: Support short reference to group entities from project entities - add a direct project parent (group) to Banzai context - if an epic is referenced from a direct descendant -> change epic to_reference to use short reference --- doc/user/project/quick_actions.md | 8 ++++---- lib/banzai/filter/epic_reference_filter.rb | 6 ++++++ spec/models/concerns/cache_markdown_field_spec.rb | 5 +++-- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/doc/user/project/quick_actions.md b/doc/user/project/quick_actions.md index 21a9d2adf4f..c2f53540089 100644 --- a/doc/user/project/quick_actions.md +++ b/doc/user/project/quick_actions.md @@ -1,9 +1,9 @@ # GitLab quick actions -Quick actions are textual shortcuts for common actions on issues, epics, merge requests, +Quick actions are textual shortcuts for common actions on issues, epics, merge requests, and commits that are usually done by clicking buttons or dropdowns in GitLab's UI. You can enter these commands while creating a new issue or merge request, or -in comments of issues, epics, merge requests, and commits. Each command should be +in comments of issues, epics, merge requests, and commits. Each command should be on a separate line in order to be properly detected and executed. Once executed, the commands are removed from the text body and not visible to anyone else. @@ -70,7 +70,7 @@ The following quick actions are applicable for epics threads and description: |:---------------------------|:----------------------------------------| | `/tableflip ` | Append the comment with `(╯°□°)╯︵ ┻━┻` | | `/shrug ` | Append the comment with `¯\_(ツ)_/¯` | -| `/todo` | Add a todo | +| `/todo` | Add a todo | | `/done` | Mark todo as done | | `/subscribe` | Subscribe | | `/unsubscribe` | Unsubscribe | @@ -80,4 +80,4 @@ The following quick actions are applicable for epics threads and description: | `/award :emoji:` | Toggle emoji award | | `/label ~label1 ~label2` | Add label(s) | | `/unlabel ~label1 ~label2` | Remove all or specific label(s) | -| `/relabel ~label1 ~label2` | Replace label | \ No newline at end of file +| `/relabel ~label1 ~label2` | Replace label | diff --git a/lib/banzai/filter/epic_reference_filter.rb b/lib/banzai/filter/epic_reference_filter.rb index e06e2fb3870..26bcf5c04b4 100644 --- a/lib/banzai/filter/epic_reference_filter.rb +++ b/lib/banzai/filter/epic_reference_filter.rb @@ -9,6 +9,12 @@ module Banzai def self.object_class Epic end + + private + + def group + context[:group] || context[:project]&.group + end end end end diff --git a/spec/models/concerns/cache_markdown_field_spec.rb b/spec/models/concerns/cache_markdown_field_spec.rb index da26d802688..f8d50e89d40 100644 --- a/spec/models/concerns/cache_markdown_field_spec.rb +++ b/spec/models/concerns/cache_markdown_field_spec.rb @@ -331,11 +331,12 @@ describe CacheMarkdownField do end context 'with a project' do - let(:thing) { thing_subclass(:project).new(foo: markdown, foo_html: html, project: :project_value) } + let(:project) { create(:project, group: create(:group)) } + let(:thing) { thing_subclass(:project).new(foo: markdown, foo_html: html, project: project) } it 'sets the project in the context' do is_expected.to have_key(:project) - expect(context[:project]).to eq(:project_value) + expect(context[:project]).to eq(project) end it 'invalidates the cache when project changes' do -- cgit v1.2.1 From 295b0466b38f261ad10be72e18c0915f4f646379 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 3 Oct 2018 17:00:09 +0100 Subject: Removes icon key from job endpoint This was added because we thought we'd need to render the environment status but we already have this information in the build status --- app/serializers/build_details_entity.rb | 8 -------- changelogs/unreleased/50904-remove-icon-env.yml | 5 +++++ spec/controllers/projects/jobs_controller_spec.rb | 1 - spec/fixtures/api/schemas/job/deployment_status.json | 2 -- 4 files changed, 5 insertions(+), 11 deletions(-) create mode 100644 changelogs/unreleased/50904-remove-icon-env.yml diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb index c85b1790e73..3d508a9a407 100644 --- a/app/serializers/build_details_entity.rb +++ b/app/serializers/build_details_entity.rb @@ -1,10 +1,6 @@ # frozen_string_literal: true class BuildDetailsEntity < JobEntity - include EnvironmentHelper - include RequestAwareEntity - include CiStatusHelper - expose :coverage, :erased_at, :duration expose :tag_list, as: :tags expose :has_trace?, as: :has_trace @@ -15,10 +11,6 @@ class BuildDetailsEntity < JobEntity expose :deployment_status, if: -> (*) { build.has_environment? } do expose :deployment_status, as: :status - expose :icon do |build| - ci_label_for_status(build.status) - end - expose :persisted_environment, as: :environment, with: EnvironmentEntity end diff --git a/changelogs/unreleased/50904-remove-icon-env.yml b/changelogs/unreleased/50904-remove-icon-env.yml new file mode 100644 index 00000000000..7915d45d6bd --- /dev/null +++ b/changelogs/unreleased/50904-remove-icon-env.yml @@ -0,0 +1,5 @@ +--- +title: Remove icon key from job endopoint +merge_request: +author: +type: other diff --git a/spec/controllers/projects/jobs_controller_spec.rb b/spec/controllers/projects/jobs_controller_spec.rb index fd11cb31a2a..30a418c0e88 100644 --- a/spec/controllers/projects/jobs_controller_spec.rb +++ b/spec/controllers/projects/jobs_controller_spec.rb @@ -225,7 +225,6 @@ describe Projects::JobsController, :clean_gitlab_redis_shared_state do expect(response).to have_gitlab_http_status(:ok) expect(json_response).to match_schema('job/job_details') expect(json_response['deployment_status']["status"]).to eq 'creating' - expect(json_response['deployment_status']["icon"]).to eq 'passed' expect(json_response['deployment_status']["environment"]).not_to be_nil end end diff --git a/spec/fixtures/api/schemas/job/deployment_status.json b/spec/fixtures/api/schemas/job/deployment_status.json index a90b8b35654..83b1899fdf3 100644 --- a/spec/fixtures/api/schemas/job/deployment_status.json +++ b/spec/fixtures/api/schemas/job/deployment_status.json @@ -2,7 +2,6 @@ "type": "object", "required": [ "status", - "icon", "environment" ], "properties": { @@ -20,7 +19,6 @@ { "type": "null" } ] }, - "icon": { "type": "string" }, "environment": { "$ref": "../environment.json" } }, "additionalProperties": false -- cgit v1.2.1 From 600a10b9d14c0d581efd270a68944957af762a17 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Thu, 27 Sep 2018 17:19:28 +0300 Subject: Add subscribe filter to labels page Signed-off-by: Dmitriy Zaporozhets --- app/controllers/groups/labels_controller.rb | 3 +- app/controllers/projects/labels_controller.rb | 1 + app/finders/group_labels_finder.rb | 16 ++++++- app/finders/labels_finder.rb | 13 ++++++ app/models/label.rb | 9 ++++ app/views/groups/labels/index.html.haml | 20 +++++++-- app/views/projects/labels/index.html.haml | 21 +++++++-- app/views/shared/labels/_sort_dropdown.html.haml | 2 +- .../unreleased/dz-labels-subscribe-filter.yml | 5 +++ locale/gitlab.pot | 6 +++ spec/features/groups/labels/subscription_spec.rb | 50 +++++++++++++++++++--- spec/finders/group_labels_finder_spec.rb | 19 +++++--- spec/finders/labels_finder_spec.rb | 10 +++++ spec/models/label_spec.rb | 36 ++++++++++++++++ 14 files changed, 190 insertions(+), 21 deletions(-) create mode 100644 changelogs/unreleased/dz-labels-subscribe-filter.yml diff --git a/app/controllers/groups/labels_controller.rb b/app/controllers/groups/labels_controller.rb index cb9ab35de85..26768c628ca 100644 --- a/app/controllers/groups/labels_controller.rb +++ b/app/controllers/groups/labels_controller.rb @@ -12,7 +12,8 @@ class Groups::LabelsController < Groups::ApplicationController def index respond_to do |format| format.html do - @labels = GroupLabelsFinder.new(@group, params.merge(sort: sort)).execute + @labels = GroupLabelsFinder + .new(current_user, @group, params.merge(sort: sort)).execute end format.json do render json: LabelSerializer.new.represent_appearance(available_labels) diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index a0ce3b08d9f..640038818f2 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -163,6 +163,7 @@ class Projects::LabelsController < Projects::ApplicationController project_id: @project.id, include_ancestor_groups: params[:include_ancestor_groups], search: params[:search], + subscribed: params[:subscribed], sort: sort).execute end diff --git a/app/finders/group_labels_finder.rb b/app/finders/group_labels_finder.rb index 903023033ed..a668a0f0fae 100644 --- a/app/finders/group_labels_finder.rb +++ b/app/finders/group_labels_finder.rb @@ -1,17 +1,29 @@ # frozen_string_literal: true class GroupLabelsFinder - attr_reader :group, :params + attr_reader :current_user, :group, :params - def initialize(group, params = {}) + def initialize(current_user, group, params = {}) + @current_user = current_user @group = group @params = params end def execute group.labels + .optionally_subscribed_by(subscriber_id) .optionally_search(params[:search]) .order_by(params[:sort]) .page(params[:page]) end + + private + + def subscriber_id + current_user&.id if subscribed? + end + + def subscribed? + params[:subscribed] == 'true' + end end diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index 08fc2968e77..82e0b2ed9e1 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -17,6 +17,7 @@ class LabelsFinder < UnionFinder @skip_authorization = skip_authorization items = find_union(label_ids, Label) || Label.none items = with_title(items) + items = by_subscription(items) items = by_search(items) sort(items) end @@ -84,6 +85,18 @@ class LabelsFinder < UnionFinder labels.search(params[:search]) end + def by_subscription(labels) + labels.optionally_subscribed_by(subscriber_id) + end + + def subscriber_id + current_user&.id if subscribed? + end + + def subscribed? + params[:subscribed] == 'true' + end + # Gets redacted array of group ids # which can include the ancestors and descendants of the requested group. def group_ids_for(group) diff --git a/app/models/label.rb b/app/models/label.rb index 9ef57a05b3e..43b49445765 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -45,6 +45,7 @@ class Label < ActiveRecord::Base scope :on_project_boards, ->(project_id) { with_lists_and_board.where(boards: { project_id: project_id }) } scope :order_name_asc, -> { reorder(title: :asc) } scope :order_name_desc, -> { reorder(title: :desc) } + scope :subscribed_by, ->(user_id) { joins(:subscriptions).where(subscriptions: { user_id: user_id, subscribed: true }) } def self.prioritized(project) joins(:priorities) @@ -74,6 +75,14 @@ class Label < ActiveRecord::Base joins(label_priorities) end + def self.optionally_subscribed_by(user_id) + if user_id + subscribed_by(user_id) + else + all + end + end + alias_attribute :name, :title def self.reference_prefix diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index 003bd25dd06..77be21ae2d0 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -3,20 +3,29 @@ - can_admin_label = can?(current_user, :admin_label, @group) - issuables = ['issues', 'merge requests'] - search = params[:search] +- subscribed = params[:subscribed] +- labels_or_filters = @labels.exists? || search.present? || subscribed.present? - if can_admin_label - content_for(:header_content) do .nav-controls = link_to _('New label'), new_group_label_path(@group), class: "btn btn-success" -- if @labels.exists? || search.present? +- if labels_or_filters #promote-label-modal %div{ class: container_class } .top-area.adjust - .nav-text - = _('Labels can be applied to %{features}. Group labels are available for any project within the group.') % { features: issuables.to_sentence } + %ul.nav-links.nav.nav-tabs + %li{ class: active_when(subscribed != 'true') }> + = link_to group_labels_path(@group) do + = _('All') + - if current_user + %li{ class: active_when(subscribed == 'true') }> + = link_to group_labels_path(@group, subscribed: 'true') do + = _('Subscribed') .nav-controls = form_tag group_labels_path(@group), method: :get do + = hidden_field_tag :subscribed, params[:subscribed] .input-group = search_field_tag :search, params[:search], { placeholder: _('Filter'), id: 'label-search', class: 'form-control search-text-input input-short', spellcheck: false } %span.input-group-append @@ -26,6 +35,8 @@ .labels-container.prepend-top-5 - if @labels.any? + .text-muted + = _('Labels can be applied to %{features}. Group labels are available for any project within the group.') % { features: issuables.to_sentence } .other-labels %h5= _('Labels') %ul.content-list.manage-labels-list.js-other-labels @@ -34,6 +45,9 @@ - elsif search.present? .nothing-here-block = _('No labels with such name or description') + - elsif subscribed.present? + .nothing-here-block + = _('You do not have any subscriptions yet') - else = render 'shared/empty_states/labels' diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 683dda4f166..c6b98c03ce5 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -2,21 +2,30 @@ - page_title "Labels" - can_admin_label = can?(current_user, :admin_label, @project) - search = params[:search] +- subscribed = params[:subscribed] +- labels_or_filters = @labels.exists? || @prioritized_labels.exists? || search.present? || subscribed.present? - if can_admin_label - content_for(:header_content) do .nav-controls = link_to _('New label'), new_project_label_path(@project), class: "btn btn-success" -- if @labels.exists? || @prioritized_labels.exists? || search.present? +- if labels_or_filters #promote-label-modal %div{ class: container_class } .top-area.adjust - .nav-text - = _('Labels can be applied to issues and merge requests.') + %ul.nav-links.nav.nav-tabs + %li{ class: active_when(subscribed != 'true') }> + = link_to project_labels_path(@project) do + = _('All') + - if current_user + %li{ class: active_when(subscribed == 'true') }> + = link_to project_labels_path(@project, subscribed: 'true') do + = _('Subscribed') .nav-controls = form_tag project_labels_path(@project), method: :get do + = hidden_field_tag :subscribed, params[:subscribed] .input-group = search_field_tag :search, params[:search], { placeholder: _('Filter'), id: 'label-search', class: 'form-control search-text-input input-short', spellcheck: false } %span.input-group-append @@ -28,6 +37,8 @@ - if can_admin_label - if search.blank? %p.text-muted + = _('Labels can be applied to issues and merge requests.') + %br = _('Star a label to make it a priority label. Order the prioritized labels to change their relative priority, by dragging.') -# Only show it in the first page - hide = @available_labels.empty? || (params[:page].present? && params[:page] != '1') @@ -59,7 +70,9 @@ - else .nothing-here-block = _('No labels with such name or description') - + - elsif subscribed.present? + .nothing-here-block + = _('You do not have any subscriptions yet') - else = render 'shared/empty_states/labels' diff --git a/app/views/shared/labels/_sort_dropdown.html.haml b/app/views/shared/labels/_sort_dropdown.html.haml index ff6e2947ffd..8a7d037e15b 100644 --- a/app/views/shared/labels/_sort_dropdown.html.haml +++ b/app/views/shared/labels/_sort_dropdown.html.haml @@ -6,4 +6,4 @@ %ul.dropdown-menu.dropdown-menu-right.dropdown-menu-sort %li - label_sort_options_hash.each do |value, title| - = sortable_item(title, page_filter_path(sort: value, label: true), sort_title) + = sortable_item(title, page_filter_path(sort: value, label: true, subscribed: params[:subscribed]), sort_title) diff --git a/changelogs/unreleased/dz-labels-subscribe-filter.yml b/changelogs/unreleased/dz-labels-subscribe-filter.yml new file mode 100644 index 00000000000..768c20c77c7 --- /dev/null +++ b/changelogs/unreleased/dz-labels-subscribe-filter.yml @@ -0,0 +1,5 @@ +--- +title: Add subscribe filter to group and project labels pages +merge_request: 21965 +author: +type: added diff --git a/locale/gitlab.pot b/locale/gitlab.pot index 93c7ff32946..47ea026a447 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -5786,6 +5786,9 @@ msgstr "" msgid "Subscribe at project level" msgstr "" +msgid "Subscribed" +msgstr "" + msgid "Summary of issues, merge requests, push events, and comments (Timezone: %{utcFormatted})" msgstr "" @@ -6962,6 +6965,9 @@ msgstr "" msgid "You cannot write to this read-only GitLab instance." msgstr "" +msgid "You do not have any subscriptions yet" +msgstr "" + msgid "You don't have any applications" msgstr "" diff --git a/spec/features/groups/labels/subscription_spec.rb b/spec/features/groups/labels/subscription_spec.rb index d9543bfa97f..22b51b297a6 100644 --- a/spec/features/groups/labels/subscription_spec.rb +++ b/spec/features/groups/labels/subscription_spec.rb @@ -3,7 +3,8 @@ require 'spec_helper' describe 'Labels subscription' do let(:user) { create(:user) } let(:group) { create(:group) } - let!(:feature) { create(:group_label, group: group, title: 'feature') } + let!(:label1) { create(:group_label, group: group, title: 'foo') } + let!(:label2) { create(:group_label, group: group, title: 'bar') } context 'when signed in' do before do @@ -14,9 +15,9 @@ describe 'Labels subscription' do it 'users can subscribe/unsubscribe to group labels', :js do visit group_labels_path(group) - expect(page).to have_content('feature') + expect(page).to have_content(label1.title) - within "#group_label_#{feature.id}" do + within "#group_label_#{label1.id}" do expect(page).not_to have_button 'Unsubscribe' click_button 'Subscribe' @@ -30,15 +31,48 @@ describe 'Labels subscription' do expect(page).not_to have_button 'Unsubscribe' end end + + context 'subscription filter' do + before do + visit group_labels_path(group) + end + + it 'shows only subscribed labels' do + label1.subscribe(user) + + click_subscribed_tab + + page.within('.labels-container') do + expect(page).to have_content label1.title + end + end + + it 'shows no subscribed labels message' do + click_subscribed_tab + + page.within('.labels-container') do + expect(page).not_to have_content label1.title + expect(page).to have_content('You do not have any subscriptions yet') + end + end + end end context 'when not signed in' do - it 'users can not subscribe/unsubscribe to labels' do + before do visit group_labels_path(group) + end - expect(page).to have_content 'feature' + it 'users can not subscribe/unsubscribe to labels' do + expect(page).to have_content label1.title expect(page).not_to have_button('Subscribe') end + + it 'does not show subscribed tab' do + page.within('.nav-tabs') do + expect(page).not_to have_link 'Subscribed' + end + end end def click_link_on_dropdown(text) @@ -48,4 +82,10 @@ describe 'Labels subscription' do find('a.js-subscribe-button', text: text).click end end + + def click_subscribed_tab + page.within('.nav-tabs') do + click_link 'Subscribed' + end + end end diff --git a/spec/finders/group_labels_finder_spec.rb b/spec/finders/group_labels_finder_spec.rb index ef68fc105e4..7bdd312eff0 100644 --- a/spec/finders/group_labels_finder_spec.rb +++ b/spec/finders/group_labels_finder_spec.rb @@ -4,29 +4,38 @@ require 'spec_helper' describe GroupLabelsFinder, '#execute' do let!(:group) { create(:group) } + let!(:user) { create(:user) } let!(:label1) { create(:group_label, title: 'Foo', description: 'Lorem ipsum', group: group) } let!(:label2) { create(:group_label, title: 'Bar', description: 'Fusce consequat', group: group) } it 'returns all group labels sorted by name if no params' do - result = described_class.new(group).execute + result = described_class.new(user, group).execute expect(result.to_a).to match_array([label2, label1]) end it 'returns all group labels sorted by name desc' do - result = described_class.new(group, sort: 'name_desc').execute + result = described_class.new(user, group, sort: 'name_desc').execute expect(result.to_a).to match_array([label2, label1]) end - it 'returns group labels that march search' do - result = described_class.new(group, search: 'Foo').execute + it 'returns group labels that match search' do + result = described_class.new(user, group, search: 'Foo').execute expect(result.to_a).to match_array([label1]) end + it 'returns group labels user subscribed to' do + label2.subscribe(user) + + result = described_class.new(user, group, subscribed: 'true').execute + + expect(result.to_a).to match_array([label2]) + end + it 'returns second page of labels' do - result = described_class.new(group, page: '2').execute + result = described_class.new(user, group, page: '2').execute expect(result.to_a).to match_array([]) end diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb index f5cec8e349a..9abc52aa664 100644 --- a/spec/finders/labels_finder_spec.rb +++ b/spec/finders/labels_finder_spec.rb @@ -210,5 +210,15 @@ describe LabelsFinder do expect(finder.execute).to eq [project_label_1] end end + + context 'filter by subscription' do + it 'returns labels user subscribed to' do + project_label_1.subscribe(user) + + finder = described_class.new(user, subscribed: 'true') + + expect(finder.execute).to eq [project_label_1] + end + end end end diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 99670af786a..3fc6c06b7fa 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -155,4 +155,40 @@ describe Label do expect(described_class.search('feature')).to be_empty end end + + describe '.subscribed_by' do + let!(:user) { create(:user) } + let!(:label) { create(:label) } + let!(:label2) { create(:label) } + + before do + label.subscribe(user) + end + + it 'returns subscribed labels' do + expect(described_class.subscribed_by(user.id)).to eq([label]) + end + + it 'returns nothing' do + expect(described_class.subscribed_by(0)).to be_empty + end + end + + describe '.optionally_subscribed_by' do + let!(:user) { create(:user) } + let!(:label) { create(:label) } + let!(:label2) { create(:label) } + + before do + label.subscribe(user) + end + + it 'returns subscribed labels' do + expect(described_class.optionally_subscribed_by(user.id)).to eq([label]) + end + + it 'returns all labels if user_id is nil' do + expect(described_class.optionally_subscribed_by(nil)).to match_array([label, label2]) + end + end end -- cgit v1.2.1 From 9d476b11426ca757114e85343f548ab3ed15beeb Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Wed, 3 Oct 2018 13:36:35 +0300 Subject: Refactor labels_filter_path and labels nav Refactor labels_filter_path method to be consistent with other similar methods like search_filter_path, milestones_filter_path etc. Also move repeating code in labels index page nav into shared partial Signed-off-by: Dmitriy Zaporozhets --- app/helpers/boards_helper.rb | 4 ++-- app/helpers/labels_helper.rb | 20 +++++++++++++------- app/views/groups/labels/index.html.haml | 19 +------------------ app/views/projects/labels/index.html.haml | 20 +------------------- app/views/shared/boards/_show.html.haml | 2 +- .../boards/components/sidebar/_labels.html.haml | 2 +- app/views/shared/issuable/_label_dropdown.html.haml | 2 +- app/views/shared/issuable/_sidebar.html.haml | 2 +- app/views/shared/labels/_nav.html.haml | 20 ++++++++++++++++++++ 9 files changed, 41 insertions(+), 50 deletions(-) create mode 100644 app/views/shared/labels/_nav.html.haml diff --git a/app/helpers/boards_helper.rb b/app/helpers/boards_helper.rb index e3b74f443f7..be1e7016a1e 100644 --- a/app/helpers/boards_helper.rb +++ b/app/helpers/boards_helper.rb @@ -59,8 +59,8 @@ module BoardsHelper { toggle: "dropdown", - list_labels_path: labels_filter_path(true, include_ancestor_groups: true), - labels: labels_filter_path(true, include_descendant_groups: include_descendant_groups), + list_labels_path: labels_filter_path_with_defaults(only_group_labels: true, include_ancestor_groups: true), + labels: labels_filter_path_with_defaults(only_group_labels: true, include_descendant_groups: include_descendant_groups), labels_endpoint: @labels_endpoint, namespace_path: @namespace_path, project_path: @project&.path, diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 6c51739ba1a..76ed8efe2c6 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -131,20 +131,26 @@ module LabelsHelper end end - def labels_filter_path(only_group_labels = false, include_ancestor_groups: true, include_descendant_groups: false) - project = @target_project || @project - + def labels_filter_path_with_defaults(only_group_labels: false, include_ancestor_groups: true, include_descendant_groups: false) options = {} options[:include_ancestor_groups] = include_ancestor_groups if include_ancestor_groups options[:include_descendant_groups] = include_descendant_groups if include_descendant_groups + options[:only_group_labels] = only_group_labels if only_group_labels && @group + options[:format] = :json + + labels_filter_path(options) + end + + def labels_filter_path(options = {}) + project = @target_project || @project + format = options.delete(:format) || :html if project - project_labels_path(project, :json, options) + project_labels_path(project, format, options) elsif @group - options[:only_group_labels] = only_group_labels if only_group_labels - group_labels_path(@group, :json, options) + group_labels_path(@group, format, options) else - dashboard_labels_path(:json) + dashboard_labels_path(format, options) end end diff --git a/app/views/groups/labels/index.html.haml b/app/views/groups/labels/index.html.haml index 77be21ae2d0..5b78ce910b8 100644 --- a/app/views/groups/labels/index.html.haml +++ b/app/views/groups/labels/index.html.haml @@ -14,24 +14,7 @@ - if labels_or_filters #promote-label-modal %div{ class: container_class } - .top-area.adjust - %ul.nav-links.nav.nav-tabs - %li{ class: active_when(subscribed != 'true') }> - = link_to group_labels_path(@group) do - = _('All') - - if current_user - %li{ class: active_when(subscribed == 'true') }> - = link_to group_labels_path(@group, subscribed: 'true') do - = _('Subscribed') - .nav-controls - = form_tag group_labels_path(@group), method: :get do - = hidden_field_tag :subscribed, params[:subscribed] - .input-group - = search_field_tag :search, params[:search], { placeholder: _('Filter'), id: 'label-search', class: 'form-control search-text-input input-short', spellcheck: false } - %span.input-group-append - %button.btn.btn-default{ type: "submit", "aria-label" => _('Submit search') } - = icon("search") - = render 'shared/labels/sort_dropdown' + = render 'shared/labels/nav' .labels-container.prepend-top-5 - if @labels.any? diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index c6b98c03ce5..11a05eada30 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -13,25 +13,7 @@ - if labels_or_filters #promote-label-modal %div{ class: container_class } - .top-area.adjust - %ul.nav-links.nav.nav-tabs - %li{ class: active_when(subscribed != 'true') }> - = link_to project_labels_path(@project) do - = _('All') - - if current_user - %li{ class: active_when(subscribed == 'true') }> - = link_to project_labels_path(@project, subscribed: 'true') do - = _('Subscribed') - - .nav-controls - = form_tag project_labels_path(@project), method: :get do - = hidden_field_tag :subscribed, params[:subscribed] - .input-group - = search_field_tag :search, params[:search], { placeholder: _('Filter'), id: 'label-search', class: 'form-control search-text-input input-short', spellcheck: false } - %span.input-group-append - %button.btn.btn-default{ type: "submit", "aria-label" => _('Submit search') } - = icon("search") - = render 'shared/labels/sort_dropdown' + = render 'shared/labels/nav' .labels-container.prepend-top-10 - if can_admin_label diff --git a/app/views/shared/boards/_show.html.haml b/app/views/shared/boards/_show.html.haml index 28e6fe1b16d..0d2f6bb77d6 100644 --- a/app/views/shared/boards/_show.html.haml +++ b/app/views/shared/boards/_show.html.haml @@ -33,7 +33,7 @@ - if @project %board-add-issues-modal{ "new-issue-path" => new_project_issue_path(@project), "milestone-path" => milestones_filter_dropdown_path, - "label-path" => labels_filter_path, + "label-path" => labels_filter_path_with_defaults, "empty-state-svg" => image_path('illustrations/issues.svg'), ":issue-link-base" => "issueLinkBase", ":root-path" => "rootPath", diff --git a/app/views/shared/boards/components/sidebar/_labels.html.haml b/app/views/shared/boards/components/sidebar/_labels.html.haml index 532045f3697..6138914206b 100644 --- a/app/views/shared/boards/components/sidebar/_labels.html.haml +++ b/app/views/shared/boards/components/sidebar/_labels.html.haml @@ -25,7 +25,7 @@ show_no: "true", show_any: "true", project_id: @project&.try(:id), - labels: labels_filter_path(false), + labels: labels_filter_path_with_defaults, namespace_path: @namespace_path, project_path: @project.try(:path) } } %span.dropdown-toggle-text diff --git a/app/views/shared/issuable/_label_dropdown.html.haml b/app/views/shared/issuable/_label_dropdown.html.haml index 0b42b33581a..6eb1f8f0853 100644 --- a/app/views/shared/issuable/_label_dropdown.html.haml +++ b/app/views/shared/issuable/_label_dropdown.html.haml @@ -8,7 +8,7 @@ - classes = local_assigns.fetch(:classes, []) - selected = local_assigns.fetch(:selected, nil) - dropdown_title = local_assigns.fetch(:dropdown_title, "Filter by label") -- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), labels: labels_filter_path, default_label: "Labels"} +- dropdown_data = {toggle: 'dropdown', field_name: "label_name[]", show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), labels: labels_filter_path_with_defaults, default_label: "Labels"} - dropdown_data.merge!(data_options) - label_name = local_assigns.fetch(:label_name, "Labels") - no_default_styles = local_assigns.fetch(:no_default_styles, false) diff --git a/app/views/shared/issuable/_sidebar.html.haml b/app/views/shared/issuable/_sidebar.html.haml index 32b609eed0d..aa136af1955 100644 --- a/app/views/shared/issuable/_sidebar.html.haml +++ b/app/views/shared/issuable/_sidebar.html.haml @@ -109,7 +109,7 @@ - selected_labels.each do |label| = hidden_field_tag "#{issuable.to_ability_name}[label_names][]", label.id, id: nil .dropdown - %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (labels_filter_path(false) if @project), display: 'static' } } + %button.dropdown-menu-toggle.js-label-select.js-multiselect.js-label-sidebar-dropdown{ type: "button", data: {toggle: "dropdown", default_label: "Labels", field_name: "#{issuable.to_ability_name}[label_names][]", ability_name: issuable.to_ability_name, show_no: "true", show_any: "true", namespace_path: @project.try(:namespace).try(:full_path), project_path: @project.try(:path), issue_update: issuable_json_path(issuable), labels: (labels_filter_path_with_defaults if @project), display: 'static' } } %span.dropdown-toggle-text{ class: ("is-default" if selected_labels.empty?) } = multi_label_name(selected_labels, "Labels") = icon('chevron-down', 'aria-hidden': 'true') diff --git a/app/views/shared/labels/_nav.html.haml b/app/views/shared/labels/_nav.html.haml new file mode 100644 index 00000000000..98572db738b --- /dev/null +++ b/app/views/shared/labels/_nav.html.haml @@ -0,0 +1,20 @@ +- subscribed = params[:subscribed] + +.top-area.adjust + %ul.nav-links.nav.nav-tabs + %li{ class: active_when(subscribed != 'true') }> + = link_to labels_filter_path do + = _('All') + - if current_user + %li{ class: active_when(subscribed == 'true') }> + = link_to labels_filter_path(subscribed: 'true') do + = _('Subscribed') + .nav-controls + = form_tag labels_filter_path, method: :get do + = hidden_field_tag :subscribed, params[:subscribed] + .input-group + = search_field_tag :search, params[:search], { placeholder: _('Filter'), id: 'label-search', class: 'form-control search-text-input input-short', spellcheck: false } + %span.input-group-append + %button.btn.btn-default{ type: "submit", "aria-label" => _('Submit search') } + = icon("search") + = render 'shared/labels/sort_dropdown' -- cgit v1.2.1 From 81bff65fa8342b4e5d995825b1bb3266ff71d6ce Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 4 Oct 2018 09:27:56 +0100 Subject: Adds MR reference in job sidebar In the refactor the `!` reference was missed This commit adds it back and adds coverage for it. --- app/assets/javascripts/jobs/components/commit_block.vue | 2 +- spec/javascripts/jobs/components/commit_block_spec.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/jobs/components/commit_block.vue b/app/assets/javascripts/jobs/components/commit_block.vue index 39a4ff159e2..4b1788a1c16 100644 --- a/app/assets/javascripts/jobs/components/commit_block.vue +++ b/app/assets/javascripts/jobs/components/commit_block.vue @@ -46,7 +46,7 @@ v-if="mergeRequest" :href="mergeRequest.path" class="js-link-commit link-commit" - >{{ mergeRequest.iid }} + >!{{ mergeRequest.iid }}

diff --git a/spec/javascripts/jobs/components/commit_block_spec.js b/spec/javascripts/jobs/components/commit_block_spec.js index 61ee993f46a..0bcc4ff940f 100644 --- a/spec/javascripts/jobs/components/commit_block_spec.js +++ b/spec/javascripts/jobs/components/commit_block_spec.js @@ -56,7 +56,7 @@ describe('Commit block', () => { props.mergeRequest.path, ); expect(vm.$el.querySelector('.js-link-commit').textContent.trim()).toEqual( - props.mergeRequest.iid, + `!${props.mergeRequest.iid}`, ); }); }); -- cgit v1.2.1 From 68c7975b9c59addd853da00191de42a283927517 Mon Sep 17 00:00:00 2001 From: jerasmus Date: Thu, 4 Oct 2018 10:37:25 +0200 Subject: fix environment name overlapping + tooltip positioning --- app/assets/javascripts/environments/components/environment_item.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/environments/components/environment_item.vue b/app/assets/javascripts/environments/components/environment_item.vue index 11e3b781e5a..a1d8e531940 100644 --- a/app/assets/javascripts/environments/components/environment_item.vue +++ b/app/assets/javascripts/environments/components/environment_item.vue @@ -466,7 +466,9 @@ export default { class="gl-responsive-table-row" role="row">

{{ model.name }} -- cgit v1.2.1 From 7e3e895d744e3ee4ab3d209e08c835530a239874 Mon Sep 17 00:00:00 2001 From: Steve Azzopardi Date: Thu, 4 Oct 2018 10:59:20 +0200 Subject: Remove addtionalProperties in runners schema This resulted into failure on EE pipeline, for more information follow from https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/7600#note_105310648 onwards. --- spec/fixtures/api/schemas/job/runners.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/fixtures/api/schemas/job/runners.json b/spec/fixtures/api/schemas/job/runners.json index bebb0c88652..646bfd3a82d 100644 --- a/spec/fixtures/api/schemas/job/runners.json +++ b/spec/fixtures/api/schemas/job/runners.json @@ -8,6 +8,5 @@ "online": { "type": "boolean" }, "available": { "type": "boolean" }, "settings_path": { "type": "string" } - }, - "additionalProperties": false + } } -- cgit v1.2.1 From 28f895e495a7868e44602781c99bcd51fac08ccc Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 4 Oct 2018 18:10:30 +0900 Subject: Enable ci_enable_scheduled_build feature flag by default --- app/models/ci/build.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 8f1547463ba..77907053af0 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -259,7 +259,7 @@ module Ci end def schedulable? - Feature.enabled?('ci_enable_scheduled_build') && + Feature.enabled?('ci_enable_scheduled_build', default_enabled: true) && self.when == 'delayed' && options[:start_in].present? end -- cgit v1.2.1 From c9078e3d9d19c22a2718e6c71459a83c4dd58c64 Mon Sep 17 00:00:00 2001 From: Dylan Griffith Date: Mon, 1 Oct 2018 13:58:47 +0000 Subject: Add a QA spec for RBAC cluster and auto devops This fails now because we have not yet implemented support for this --- app/views/projects/clusters/user/_form.html.haml | 2 +- qa/qa/factory/resource/kubernetes_cluster.rb | 1 + .../project/operations/kubernetes/add_existing.rb | 5 ++ qa/qa/service/kubernetes_cluster.rb | 62 +++++++++++++- qa/qa/service/shellout.rb | 6 +- .../create_project_with_auto_devops_spec.rb | 94 +++++++++++----------- 6 files changed, 118 insertions(+), 52 deletions(-) diff --git a/app/views/projects/clusters/user/_form.html.haml b/app/views/projects/clusters/user/_form.html.haml index 30513d0f91b..56551ed4d65 100644 --- a/app/views/projects/clusters/user/_form.html.haml +++ b/app/views/projects/clusters/user/_form.html.haml @@ -27,7 +27,7 @@ .form-group .form-check - = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input' }, 'rbac', 'abac' + = platform_kubernetes_field.check_box :authorization_type, { class: 'form-check-input qa-rbac-checkbox' }, 'rbac', 'abac' = platform_kubernetes_field.label :authorization_type, s_('ClusterIntegration|RBAC-enabled cluster (experimental)'), class: 'form-check-label label-bold' .form-text.text-muted = s_('ClusterIntegration|Enable this setting if using role-based access control (RBAC).') diff --git a/qa/qa/factory/resource/kubernetes_cluster.rb b/qa/qa/factory/resource/kubernetes_cluster.rb index 94d7df7128b..ed9d0329081 100644 --- a/qa/qa/factory/resource/kubernetes_cluster.rb +++ b/qa/qa/factory/resource/kubernetes_cluster.rb @@ -31,6 +31,7 @@ module QA page.set_api_url(@cluster.api_url) page.set_ca_certificate(@cluster.ca_certificate) page.set_token(@cluster.token) + page.check_rbac! if @cluster.rbac page.add_cluster! end diff --git a/qa/qa/page/project/operations/kubernetes/add_existing.rb b/qa/qa/page/project/operations/kubernetes/add_existing.rb index eef82b5f329..38f8527b9b4 100644 --- a/qa/qa/page/project/operations/kubernetes/add_existing.rb +++ b/qa/qa/page/project/operations/kubernetes/add_existing.rb @@ -10,6 +10,7 @@ module QA element :ca_certificate, 'text_area :ca_cert' element :token, 'text_field :token' element :add_cluster_button, "submit s_('ClusterIntegration|Add Kubernetes cluster')" + element :rbac_checkbox end def set_cluster_name(name) @@ -31,6 +32,10 @@ module QA def add_cluster! click_on 'Add Kubernetes cluster' end + + def check_rbac! + check_element :rbac_checkbox + end end end end diff --git a/qa/qa/service/kubernetes_cluster.rb b/qa/qa/service/kubernetes_cluster.rb index abd9d53554f..d868515555c 100644 --- a/qa/qa/service/kubernetes_cluster.rb +++ b/qa/qa/service/kubernetes_cluster.rb @@ -1,12 +1,17 @@ require 'securerandom' require 'mkmf' +require 'pathname' module QA module Service class KubernetesCluster include Service::Shellout - attr_reader :api_url, :ca_certificate, :token + attr_reader :api_url, :ca_certificate, :token, :rbac + + def initialize(rbac: false) + @rbac = rbac + end def cluster_name @cluster_name ||= "qa-cluster-#{SecureRandom.hex(4)}-#{Time.now.utc.strftime("%Y%m%d%H%M%S")}" @@ -19,7 +24,7 @@ module QA shell <<~CMD.tr("\n", ' ') gcloud container clusters create #{cluster_name} - --enable-legacy-authorization + #{auth_options} --zone #{Runtime::Env.gcloud_zone} && gcloud container clusters get-credentials @@ -28,8 +33,21 @@ module QA CMD @api_url = `kubectl config view --minify -o jsonpath='{.clusters[].cluster.server}'` - @ca_certificate = Base64.decode64(`kubectl get secrets -o jsonpath="{.items[0].data['ca\\.crt']}"`) - @token = Base64.decode64(`kubectl get secrets -o jsonpath='{.items[0].data.token}'`) + if rbac + create_service_account + + secrets = JSON.parse(`kubectl get secrets -o json`) + gitlab_account = secrets['items'].find do |item| + item['metadata']['annotations']['kubernetes.io/service-account.name'] == 'gitlab-account' + end + + @ca_certificate = Base64.decode64(gitlab_account['data']['ca.crt']) + @token = Base64.decode64(gitlab_account['data']['token']) + else + @ca_certificate = Base64.decode64(`kubectl get secrets -o jsonpath="{.items[0].data['ca\\.crt']}"`) + @token = Base64.decode64(`kubectl get secrets -o jsonpath='{.items[0].data.token}'`) + end + self end @@ -44,6 +62,42 @@ module QA private + def create_service_account + shell('kubectl create -f -', stdin_data: service_account) + shell('kubectl create -f -', stdin_data: service_account_role_binding) + end + + def service_account + <<~YAML + apiVersion: v1 + kind: ServiceAccount + metadata: + name: gitlab-account + namespace: default + YAML + end + + def service_account_role_binding + <<~YAML + kind: ClusterRoleBinding + apiVersion: rbac.authorization.k8s.io/v1 + metadata: + name: gitlab-account-binding + subjects: + - kind: ServiceAccount + name: gitlab-account + namespace: default + roleRef: + kind: ClusterRole + name: cluster-admin + apiGroup: rbac.authorization.k8s.io + YAML + end + + def auth_options + "--enable-legacy-authorization" unless rbac + end + def validate_dependencies find_executable('gcloud') || raise("You must first install `gcloud` executable to run these tests.") find_executable('kubectl') || raise("You must first install `kubectl` executable to run these tests.") diff --git a/qa/qa/service/shellout.rb b/qa/qa/service/shellout.rb index 1ca9504bb33..43dc0851571 100644 --- a/qa/qa/service/shellout.rb +++ b/qa/qa/service/shellout.rb @@ -11,10 +11,12 @@ module QA # TODO, make it possible to use generic QA framework classes # as a library - gitlab-org/gitlab-qa#94 # - def shell(command) + def shell(command, stdin_data: nil) puts "Executing `#{command}`" - Open3.popen2e(*command) do |_in, out, wait| + Open3.popen2e(*command) do |stdin, out, wait| + stdin.puts(stdin_data) if stdin_data + stdin.close if stdin_data out.each { |line| puts line } if wait.value.exited? && wait.value.exitstatus.nonzero? diff --git a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb index 844cc1236c7..4604936916b 100644 --- a/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb +++ b/qa/qa/specs/features/browser_ui/7_configure/auto_devops/create_project_with_auto_devops_spec.rb @@ -9,59 +9,63 @@ module QA @cluster&.remove! end - it 'user creates a new project and runs auto devops' do - Runtime::Browser.visit(:gitlab, Page::Main::Login) - Page::Main::Login.act { sign_in_using_credentials } + [true, false].each do |rbac| + context "when rbac is #{rbac ? 'enabled' : 'disabled'}" do + it 'user creates a new project and runs auto devops' do + Runtime::Browser.visit(:gitlab, Page::Main::Login) + Page::Main::Login.act { sign_in_using_credentials } - project = Factory::Resource::Project.fabricate! do |p| - p.name = 'project-with-autodevops' - p.description = 'Project with Auto Devops' - end + project = Factory::Resource::Project.fabricate! do |p| + p.name = 'project-with-autodevops' + p.description = 'Project with Auto Devops' + end - # Disable code_quality check in Auto DevOps pipeline as it takes - # too long and times out the test - Factory::Resource::SecretVariable.fabricate! do |resource| - resource.project = project - resource.key = 'CODE_QUALITY_DISABLED' - resource.value = '1' - end + # Disable code_quality check in Auto DevOps pipeline as it takes + # too long and times out the test + Factory::Resource::SecretVariable.fabricate! do |resource| + resource.project = project + resource.key = 'CODE_QUALITY_DISABLED' + resource.value = '1' + end - # Create Auto Devops compatible repo - Factory::Repository::ProjectPush.fabricate! do |push| - push.project = project - push.directory = Pathname - .new(__dir__) - .join('../../../../../fixtures/auto_devops_rack') - push.commit_message = 'Create Auto DevOps compatible rack application' - end + # Create Auto Devops compatible repo + Factory::Repository::ProjectPush.fabricate! do |push| + push.project = project + push.directory = Pathname + .new(__dir__) + .join('../../../../../fixtures/auto_devops_rack') + push.commit_message = 'Create Auto DevOps compatible rack application' + end - Page::Project::Show.act { wait_for_push } + Page::Project::Show.act { wait_for_push } - # Create and connect K8s cluster - @cluster = Service::KubernetesCluster.new.create! - kubernetes_cluster = Factory::Resource::KubernetesCluster.fabricate! do |cluster| - cluster.project = project - cluster.cluster = @cluster - cluster.install_helm_tiller = true - cluster.install_ingress = true - cluster.install_prometheus = true - cluster.install_runner = true - end + # Create and connect K8s cluster + @cluster = Service::KubernetesCluster.new(rbac: rbac).create! + kubernetes_cluster = Factory::Resource::KubernetesCluster.fabricate! do |cluster| + cluster.project = project + cluster.cluster = @cluster + cluster.install_helm_tiller = true + cluster.install_ingress = true + cluster.install_prometheus = true + cluster.install_runner = true + end - project.visit! - Page::Menu::Side.act { click_ci_cd_settings } - Page::Project::Settings::CICD.perform do |p| - p.enable_auto_devops_with_domain("#{kubernetes_cluster.ingress_ip}.nip.io") - end + project.visit! + Page::Menu::Side.act { click_ci_cd_settings } + Page::Project::Settings::CICD.perform do |p| + p.enable_auto_devops_with_domain("#{kubernetes_cluster.ingress_ip}.nip.io") + end - project.visit! - Page::Menu::Side.act { click_ci_cd_pipelines } - Page::Project::Pipeline::Index.act { go_to_latest_pipeline } + project.visit! + Page::Menu::Side.act { click_ci_cd_pipelines } + Page::Project::Pipeline::Index.act { go_to_latest_pipeline } - Page::Project::Pipeline::Show.perform do |pipeline| - expect(pipeline).to have_build('build', status: :success, wait: 600) - expect(pipeline).to have_build('test', status: :success, wait: 600) - expect(pipeline).to have_build('production', status: :success, wait: 1200) + Page::Project::Pipeline::Show.perform do |pipeline| + expect(pipeline).to have_build('build', status: :success, wait: 600) + expect(pipeline).to have_build('test', status: :success, wait: 600) + expect(pipeline).to have_build('production', status: :success, wait: 1200) + end + end end end end -- cgit v1.2.1 From 89b06a7020473f6ac24b6947fcaa8bf6154a1e0b Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 4 Oct 2018 09:43:37 +0100 Subject: Remove vestigial method from MergeRequestsFinder This isn't called from anywhere. --- app/finders/merge_requests_finder.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb index 50c051c3aa1..e190d5d90c9 100644 --- a/app/finders/merge_requests_finder.rb +++ b/app/finders/merge_requests_finder.rb @@ -66,10 +66,6 @@ class MergeRequestsFinder < IssuableFinder items.where(target_branch: target_branch) end - def item_project_ids(items) - items&.reorder(nil)&.select(:target_project_id) - end - def by_wip(items) if params[:wip] == 'yes' items.where(wip_match(items.arel_table)) -- cgit v1.2.1 From 36a7344901b0caa47083d3685e6044d68cd39c34 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Thu, 4 Oct 2018 09:44:30 +0100 Subject: Allow Gitaly N+1s in MR finder spec These can be triggered by project creation in the setup phase if a spec uses the RequestStore, but we really don't care about that - it's not an N+1, it's just several projects being created! --- spec/finders/merge_requests_finder_spec.rb | 32 ++++++++++++++++++++++-------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 33d01697c75..ff4c6b8dd42 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -3,21 +3,37 @@ require 'spec_helper' describe MergeRequestsFinder do include ProjectForksHelper + # We need to explicitly permit Gitaly N+1s because of the specs that use + # :request_store. Gitaly N+1 detection is only enabled when :request_store is, + # but we don't care about potential N+1s when we're just creating several + # projects in the setup phase. + def create_project_without_n_plus_1(*args) + Gitlab::GitalyClient.allow_n_plus_1_calls do + create(:project, :public, *args) + end + end + let(:user) { create :user } let(:user2) { create :user } let(:group) { create(:group) } let(:subgroup) { create(:group, parent: group) } - let(:project1) { create(:project, :public, group: group) } - let(:project2) { fork_project(project1, user) } + let(:project1) { create_project_without_n_plus_1(group: group) } + let(:project2) do + Gitlab::GitalyClient.allow_n_plus_1_calls do + fork_project(project1, user) + end + end let(:project3) do - p = fork_project(project1, user) - p.update!(archived: true) - p + Gitlab::GitalyClient.allow_n_plus_1_calls do + p = fork_project(project1, user) + p.update!(archived: true) + p + end end - let(:project4) { create(:project, :public, group: subgroup) } - let(:project5) { create(:project, :public, group: subgroup) } - let(:project6) { create(:project, :public, group: subgroup) } + let(:project4) { create_project_without_n_plus_1(group: subgroup) } + let(:project5) { create_project_without_n_plus_1(group: subgroup) } + let(:project6) { create_project_without_n_plus_1(group: subgroup) } let!(:merge_request1) { create(:merge_request, :simple, author: user, source_project: project2, target_project: project1) } let!(:merge_request2) { create(:merge_request, :conflict, author: user, source_project: project2, target_project: project1, state: 'closed') } -- cgit v1.2.1 From bfdac6324c717d014b7271a83c2bf883814f93d7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 4 Oct 2018 11:50:06 +0200 Subject: Copy-edit documentation for only/except changes feature --- doc/ci/yaml/README.md | 38 +++++++++++++++++++++++++------------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 0d07b63a2dc..442a8ba643b 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -388,7 +388,7 @@ except master. > > `variables` policy introduced in 10.7 > -> `changes` policy introduced in 11.4 +> `changes` policy [introduced in 11.4][changes-policy-issue] CAUTION: **Warning:** This an _alpha_ feature, and it it subject to change at any time without @@ -455,7 +455,9 @@ Learn more about variables expressions on [a separate page][variables-expression ### `changes` Using `changes` keyword with `only` or `except` makes it possible to define if -a job should be created, based on files modified by a git push event. +a job should be created based on files modified by a git push event. + +For example: ```yaml docker build: @@ -466,20 +468,29 @@ docker build: - docker/scripts/* ``` -In the scenario above, if you are pushing multiple commits to GitLab, to an -exising branch, GitLab is going to create and trigger `docker build` job, -provided that one of the commits contains changes to the `Dockerfile` file or -changes to any of the files inside `docker/scripts/` directory. +In the scenario above, if you are pushing multiple commits to GitLab to an +existing branch, GitLab creates and triggers `docker build` job, provided that +one of the commits contains changes to either: + +- The `Dockerfile` file. +- Any of the files inside `docker/scripts/` directory. CAUTION: **Warning:** -If you are pushing a **new** branch or a new tag to GitLab, only/changes is -going to always evaluate to truth and GitLab will create a job. This feature is -not combined with merge requests yet, and because GitLab is creating pipelines +There are some caveats when using this feature with new branches and tags. See +the section below. + +#### Using `changes` with new branches and tags + +If you are pushing a **new** branch or a **new** tag to GitLab, the policy +always evaluates to truth and GitLab will create a job. This feature is not +connected with merge requests yet, and because GitLab is creating pipelines before an user can create a merge request we don't know a target branch at -this point. Without a target branch it is not possible to know what the common -ancestor is, thus we always create a job in that case. This feature works best for -stable branches like `master` because in that case GitLab uses previous commit, -that is present in a branch, to compare against a newly pushed latest SHA. +this point. + +Without a target branch, it is not possible to know what the common ancestor is, +thus we always create a job in that case. This feature works best for stable +branches like `master` because in that case GitLab uses the previous commit +that is present in a branch to compare against the latest SHA that was pushed. ## `tags` @@ -1976,3 +1987,4 @@ CI with various languages. [ce-12909]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12909 [schedules]: ../../user/project/pipelines/schedules.md [variables-expressions]: ../variables/README.md#variables-expressions +[changes-policy-issue]: https://gitlab.com/gitlab-org/gitlab-ce/issues/19232 -- cgit v1.2.1 From e84230ebb6db5ff9e5990e945aa1e1aebf4e9fa9 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 4 Oct 2018 18:59:34 +0900 Subject: Add limitation for start_in keyword --- lib/gitlab/ci/config/entry/job.rb | 2 +- lib/gitlab/ci/config/entry/legacy_validation_helpers.rb | 9 +++++++++ lib/gitlab/ci/config/entry/validators.rb | 6 ++++++ spec/lib/gitlab/ci/config/entry/job_spec.rb | 15 +++++++++++---- 4 files changed, 27 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/ci/config/entry/job.rb b/lib/gitlab/ci/config/entry/job.rb index 03971254310..f290ff3a565 100644 --- a/lib/gitlab/ci/config/entry/job.rb +++ b/lib/gitlab/ci/config/entry/job.rb @@ -36,7 +36,7 @@ module Gitlab validates :extends, type: String end - validates :start_in, duration: true, if: :delayed? + validates :start_in, duration: { limit: '1 day' }, if: :delayed? validates :start_in, absence: true, unless: :delayed? end diff --git a/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb b/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb index a78a85397bd..a3d4432be82 100644 --- a/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb +++ b/lib/gitlab/ci/config/entry/legacy_validation_helpers.rb @@ -11,6 +11,15 @@ module Gitlab false end + def validate_duration_limit(value, limit) + return false unless value.is_a?(String) + + ChronicDuration.parse(value).second.from_now < + ChronicDuration.parse(limit).second.from_now + rescue ChronicDuration::DurationParseError + false + end + def validate_array_of_strings(values) values.is_a?(Array) && values.all? { |value| validate_string(value) } end diff --git a/lib/gitlab/ci/config/entry/validators.rb b/lib/gitlab/ci/config/entry/validators.rb index b3c889ee92f..f6b4ba7843e 100644 --- a/lib/gitlab/ci/config/entry/validators.rb +++ b/lib/gitlab/ci/config/entry/validators.rb @@ -49,6 +49,12 @@ module Gitlab unless validate_duration(value) record.errors.add(attribute, 'should be a duration') end + + if options[:limit] + unless validate_duration_limit(value, options[:limit]) + record.errors.add(attribute, 'should not exceed the limit') + end + end end end diff --git a/spec/lib/gitlab/ci/config/entry/job_spec.rb b/spec/lib/gitlab/ci/config/entry/job_spec.rb index d745c4ca2ad..1169938b80c 100644 --- a/spec/lib/gitlab/ci/config/entry/job_spec.rb +++ b/spec/lib/gitlab/ci/config/entry/job_spec.rb @@ -44,9 +44,7 @@ describe Gitlab::Ci::Config::Entry::Job do context 'when start_in is specified' do let(:config) { { script: 'echo', when: 'delayed', start_in: '1 day' } } - it 'returns error about invalid type' do - expect(entry).to be_valid - end + it { expect(entry).to be_valid } end end end @@ -158,7 +156,7 @@ describe Gitlab::Ci::Config::Entry::Job do end end - context 'when start_in is not formateed ad a duration' do + context 'when start_in is not formatted as a duration' do let(:config) { { when: 'delayed', start_in: 'test' } } it 'returns error about invalid type' do @@ -166,6 +164,15 @@ describe Gitlab::Ci::Config::Entry::Job do expect(entry.errors).to include 'job start in should be a duration' end end + + context 'when start_in is longer than one day' do + let(:config) { { when: 'delayed', start_in: '2 days' } } + + it 'returns error about exceeding the limit' do + expect(entry).not_to be_valid + expect(entry.errors).to include 'job start in should not exceed the limit' + end + end end context 'when start_in specified without delayed specification' do -- cgit v1.2.1 From 1e6ed7442824fb9639d1cf438ae58046731b7dba Mon Sep 17 00:00:00 2001 From: Lukas Eipert Date: Thu, 4 Oct 2018 12:11:03 +0200 Subject: Fix `babel-messages` dependency I don't know if the issue [0] was caused by a faulty merge, or why, but it seems like babel-messages had the wrong URL in there and resolved to babel-plugin-transform-object-rest-spread, which is wrong. So the integrity check did actually work and catch that! [0]: https://gitlab.com/gitlab-org/gitlab-ce/issues/52192 --- yarn.lock | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/yarn.lock b/yarn.lock index e9a8d25de67..3e03aa32cdd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1194,10 +1194,9 @@ babel-loader@^8.0.4: babel-messages@^6.23.0: version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.23.0.tgz#875d6bc9be761c58a2ae3feee5dc4895d8c7f921" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" integrity sha1-8830cDhYA1sqKVHG7F7fbGLyYw4= dependencies: - babel-plugin-syntax-object-rest-spread "^6.8.0" babel-runtime "^6.22.0" babel-plugin-istanbul@^5.1.0: @@ -1214,11 +1213,6 @@ babel-plugin-rewire@^1.2.0: resolved "https://registry.yarnpkg.com/babel-plugin-rewire/-/babel-plugin-rewire-1.2.0.tgz#822562d72ed2c84e47c0f95ee232c920853e9d89" integrity sha512-JBZxczHw3tScS+djy6JPLMjblchGhLI89ep15H3SyjujIzlxo5nr6Yjo7AXotdeVczeBmWs0tF8PgJWDdgzAkQ== -babel-plugin-syntax-object-rest-spread@^6.8.0: - version "6.13.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" - integrity sha1-/WU28rzhODb/o6VFjEkDpZe7O/U= - babel-polyfill@6.23.0: version "6.23.0" resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.23.0.tgz#8364ca62df8eafb830499f699177466c3b03499d" -- cgit v1.2.1 From d1d6b26265c9e39f4d66318a37851c84f55b9ead Mon Sep 17 00:00:00 2001 From: Lukas Eipert Date: Thu, 4 Oct 2018 12:11:54 +0200 Subject: Dedupe yarn dependencies --- yarn.lock | 71 ++++++++------------------------------------------------------- 1 file changed, 9 insertions(+), 62 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3e03aa32cdd..eb856637bd6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -29,18 +29,7 @@ semver "^5.4.1" source-map "^0.5.0" -"@babel/generator@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.0.0.tgz#1efd58bffa951dc846449e58ce3a1d7f02d393aa" - integrity sha512-/BM2vupkpbZXq22l1ALO7MqXJZH2k8bKVv8Y+pABFnzWdztDB/ZLveP5At21vLz5c2YtSE6p7j2FZEsqafMz5Q== - dependencies: - "@babel/types" "^7.0.0" - jsesc "^2.5.1" - lodash "^4.17.10" - source-map "^0.5.0" - trim-right "^1.0.1" - -"@babel/generator@^7.1.2": +"@babel/generator@^7.0.0", "@babel/generator@^7.1.2": version "7.1.2" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.1.2.tgz#fde75c072575ce7abbd97322e8fef5bae67e4630" integrity sha512-70A9HWLS/1RHk3Ck8tNHKxOoKQuSKocYgwDN85Pyl/RBduss6AKxUR7RIZ/lzduQMSYfWEM4DDBu6A+XGbkFig== @@ -224,12 +213,7 @@ esutils "^2.0.2" js-tokens "^4.0.0" -"@babel/parser@^7.0.0", "@babel/parser@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.0.tgz#a7cd42cb3c12aec52e24375189a47b39759b783e" - integrity sha512-SmjnXCuPAlai75AFtzv+KCBcJ3sDDWbIn+WytKw1k+wAtEy6phqI2RqKh/zAnw53i1NR8su3Ep/UoqaKcimuLg== - -"@babel/parser@^7.1.2": +"@babel/parser@^7.0.0", "@babel/parser@^7.1.0", "@babel/parser@^7.1.2": version "7.1.2" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.1.2.tgz#85c5c47af6d244fab77bce6b9bd830e38c978409" integrity sha512-x5HFsW+E/nQalGMw7hu+fvPqnBeBaIr0lWJ2SG0PPL2j+Pm9lYvCrsZJGIgauPIENx0v10INIyFjmSNUD/gSqQ== @@ -599,7 +583,7 @@ js-levenshtein "^1.1.3" semver "^5.3.0" -"@babel/template@^7.0.0", "@babel/template@^7.1.2": +"@babel/template@^7.0.0", "@babel/template@^7.1.0", "@babel/template@^7.1.2": version "7.1.2" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.2.tgz#090484a574fef5a2d2d7726a674eceda5c5b5644" integrity sha512-SY1MmplssORfFiLDcOETrW7fCLl+PavlwMh92rrGcikQaRq4iWPVH0MpwPpY3etVMx6RnDjXtr6VZYr/IbP/Ag== @@ -608,15 +592,6 @@ "@babel/parser" "^7.1.2" "@babel/types" "^7.1.2" -"@babel/template@^7.1.0": - version "7.1.0" - resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.1.0.tgz#58cc9572e1bfe24fe1537fdf99d839d53e517e22" - integrity sha512-yZ948B/pJrwWGY6VxG6XRFsVTee3IQ7bihq9zFpM00Vydu6z5Xwg0C3J644kxI9WOTzd+62xcIsQ+AT1MGhqhA== - dependencies: - "@babel/code-frame" "^7.0.0" - "@babel/parser" "^7.1.0" - "@babel/types" "^7.0.0" - "@babel/traverse@^7.0.0", "@babel/traverse@^7.1.0": version "7.1.0" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.1.0.tgz#503ec6669387efd182c3888c4eec07bcc45d91b2" @@ -632,16 +607,7 @@ globals "^11.1.0" lodash "^4.17.10" -"@babel/types@^7.0.0": - version "7.0.0" - resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.0.0.tgz#6e191793d3c854d19c6749989e3bc55f0e962118" - integrity sha512-5tPDap4bGKTLPtci2SUl/B7Gv8RnuJFuQoWx26RJobS0fFrz4reUA3JnwIM+HVHEmWE0C1mzKhDtTp8NsWY02Q== - dependencies: - esutils "^2.0.2" - lodash "^4.17.10" - to-fast-properties "^2.0.0" - -"@babel/types@^7.1.2": +"@babel/types@^7.0.0", "@babel/types@^7.1.2": version "7.1.2" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.1.2.tgz#183e7952cf6691628afdc2e2b90d03240bac80c0" integrity sha512-pb1I05sZEKiSlMUV9UReaqsCPUpgbHHHu2n1piRm7JkuBkm6QxcaIzKu6FMnMtCbih/cEYTR+RGYYC96Yk9HAg== @@ -2674,12 +2640,12 @@ delegates@^1.0.0: resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= -depd@1.1.1, depd@~1.1.1: +depd@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" integrity sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k= -depd@~1.1.2: +depd@~1.1.1, depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= @@ -5209,14 +5175,7 @@ lz-string@^1.4.4: resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.4.4.tgz#c0d8eaf36059f705796e1e344811cf4c498d3a26" integrity sha1-wNjq82BZ9wV5bh40SBHPTEmNOiY= -make-dir@^1.0.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.2.0.tgz#6d6a49eead4aae296c53bbf3a1a008bd6c89469b" - integrity sha512-aNUAa4UMg/UougV25bbrU4ZaaKNjJ/3/xnvg/twpmKROPdKZPZ9wGgI0opdZzO8q/zUFawoUuixuOv33eZ61Iw== - dependencies: - pify "^3.0.0" - -make-dir@^1.3.0: +make-dir@^1.0.0, make-dir@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== @@ -6609,12 +6568,7 @@ regenerate-unicode-properties@^7.0.0: dependencies: regenerate "^1.4.0" -regenerate@^1.2.1: - version "1.3.2" - resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260" - integrity sha1-0ZQcZ7rUN+G+dkM63Vs4X5WxkmA= - -regenerate@^1.4.0: +regenerate@^1.2.1, regenerate@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== @@ -6779,20 +6733,13 @@ resolve@1.1.x: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@^1.3.2: +resolve@^1.3.2, resolve@^1.4.0, resolve@^1.5.0, resolve@^1.6.0: version "1.8.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.8.1.tgz#82f1ec19a423ac1fbd080b0bab06ba36e84a7a26" integrity sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA== dependencies: path-parse "^1.0.5" -resolve@^1.4.0, resolve@^1.5.0, resolve@^1.6.0: - version "1.7.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" - integrity sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw== - dependencies: - path-parse "^1.0.5" - responselike@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7" -- cgit v1.2.1 From 21089192fea61ca9f0efe74ba00b7f4a1cbed1c0 Mon Sep 17 00:00:00 2001 From: jerasmus Date: Thu, 4 Oct 2018 12:20:35 +0200 Subject: add changelog entry --- changelogs/unreleased/22104-fix-environment-name-overlap.yml | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 changelogs/unreleased/22104-fix-environment-name-overlap.yml diff --git a/changelogs/unreleased/22104-fix-environment-name-overlap.yml b/changelogs/unreleased/22104-fix-environment-name-overlap.yml new file mode 100644 index 00000000000..aaa1a1709c8 --- /dev/null +++ b/changelogs/unreleased/22104-fix-environment-name-overlap.yml @@ -0,0 +1,4 @@ +--- +title: "Fix the issue where long environment names aren't being truncated, causing the environment name to overlap into the column next to it." +merge_request: 22104 +type: fixed -- cgit v1.2.1 From e4ed981f3304dba0eb54bb08efaea6222815f9d0 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 4 Oct 2018 19:26:15 +0900 Subject: Make Ci::Stage status transition event consistent with Ci::Pipeline --- app/models/ci/stage.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/models/ci/stage.rb b/app/models/ci/stage.rb index 811261a252e..58f3fe2460a 100644 --- a/app/models/ci/stage.rb +++ b/app/models/ci/stage.rb @@ -66,7 +66,7 @@ module Ci transition any - [:manual] => :manual end - event :schedule do + event :delay do transition any - [:scheduled] => :scheduled end end @@ -81,7 +81,7 @@ module Ci when 'failed' then drop when 'canceled' then cancel when 'manual' then block - when 'scheduled' then schedule + when 'scheduled' then delay when 'skipped', nil then skip else raise HasStatus::UnknownStatusError, -- cgit v1.2.1 From a18074691a83d75b078d797fb70dcaed7d2e6001 Mon Sep 17 00:00:00 2001 From: Simon Knox Date: Thu, 4 Oct 2018 21:07:24 +1000 Subject: Make perfbar z-index above modal background --- app/assets/stylesheets/framework/variables_overrides.scss | 1 + app/assets/stylesheets/performance_bar.scss | 3 ++- changelogs/unreleased/50552-unable-to-close-performance-bar.yml | 5 +++++ 3 files changed, 8 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/50552-unable-to-close-performance-bar.yml diff --git a/app/assets/stylesheets/framework/variables_overrides.scss b/app/assets/stylesheets/framework/variables_overrides.scss index 7d90452e1f4..759b4f333ca 100644 --- a/app/assets/stylesheets/framework/variables_overrides.scss +++ b/app/assets/stylesheets/framework/variables_overrides.scss @@ -18,3 +18,4 @@ $success: $green-500; $info: $blue-500; $warning: $orange-500; $danger: $red-500; +$zindex-modal-backdrop: 1040; diff --git a/app/assets/stylesheets/performance_bar.scss b/app/assets/stylesheets/performance_bar.scss index 2e2ab8532d2..59fdbf31fe9 100644 --- a/app/assets/stylesheets/performance_bar.scss +++ b/app/assets/stylesheets/performance_bar.scss @@ -1,4 +1,5 @@ @import 'framework/variables'; +@import 'framework/variables_overrides'; @import 'peek/views/rblineprof'; #js-peek { @@ -6,7 +7,7 @@ left: 0; top: 0; width: 100%; - z-index: 1039; + z-index: #{$zindex-modal-backdrop + 1}; height: $performance-bar-height; background: $black; diff --git a/changelogs/unreleased/50552-unable-to-close-performance-bar.yml b/changelogs/unreleased/50552-unable-to-close-performance-bar.yml new file mode 100644 index 00000000000..e3619149d2a --- /dev/null +++ b/changelogs/unreleased/50552-unable-to-close-performance-bar.yml @@ -0,0 +1,5 @@ +--- +title: Fix performance bar modal position +merge_request: 21577 +author: +type: fixed -- cgit v1.2.1 From 4741c07f3fa61326665c36d4b9974025bf9dd9e9 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 4 Oct 2018 20:52:14 +0900 Subject: Groomed specs --- spec/features/projects/jobs_spec.rb | 2 +- spec/features/projects/pipelines/pipeline_spec.rb | 18 +++++++++++------- spec/features/projects/pipelines/pipelines_spec.rb | 19 +++++++++---------- 3 files changed, 21 insertions(+), 18 deletions(-) diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 45fc492f23f..6a7bf0860e9 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -574,7 +574,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do expect(page).to have_link('Unschedule job') end - it 'unschedule delayed job and shows manual action', :js do + it 'unschedules delayed job and shows manual action', :js do click_link 'Unschedule job' wait_for_requests diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index e6cb137b023..491c64fc329 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -84,7 +84,7 @@ describe 'Pipeline', :js do end end - it 'should be possible to cancel the running build' do + it 'cancels the running build and shows retry button' do find('#ci-badge-deploy .ci-action-icon-container').click page.within('#ci-badge-deploy') do @@ -112,8 +112,8 @@ describe 'Pipeline', :js do end end - context 'when pipeline has scheduled builds' do - it 'shows the scheduled icon and a unschedule action for the scheduled build' do + context 'when pipeline has a delayed job' do + it 'shows the scheduled icon and an unschedule action for the delayed job' do page.within('#ci-badge-delayed-job') do expect(page).to have_selector('.js-ci-status-icon-scheduled') expect(page).to have_content('delayed-job') @@ -124,10 +124,12 @@ describe 'Pipeline', :js do end end - it 'should be possible to unschedule the scheduled job' do + it 'unschedules the delayed job and shows play button as a manual job' do find('#ci-badge-delayed-job .ci-action-icon-container').click - expect(page).not_to have_content('Unschedule job') + page.within('#ci-badge-delayed-job') do + expect(page).to have_css('.js-icon-play') + end end end @@ -341,14 +343,16 @@ describe 'Pipeline', :js do it { expect(build_manual.reload).to be_pending } end - context 'unscheduling scheduled job' do + context 'when user unschedules a delayed job' do before do within '.pipeline-holder' do click_link('Unschedule') end end - it { expect(build_scheduled.reload).to be_manual } + it 'unschedules the delayed job and shows play button as a manual job' do + expect(page).to have_content('Trigger this manual action') + end end context 'failed jobs' do diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 43f9608e8e3..f6197a31ee1 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -232,7 +232,7 @@ describe 'Pipelines', :js do end end - context 'with delayed job' do + context 'when there is a delayed job' do let!(:delayed_job) do create(:ci_build, :scheduled, pipeline: pipeline, @@ -245,26 +245,25 @@ describe 'Pipelines', :js do visit_project_pipelines end - it 'has a dropdown with play button' do + it 'has a dropdown for actionable jobs' do expect(page).to have_selector('.dropdown-new.btn.btn-default .icon-play') end - it 'has link to the scheduled action' do + it "has link to the delayed job's action" do find('.js-pipeline-dropdown-manual-actions').click expect(page).to have_button('delayed job') end - context 'when scheduled action was played' do + context 'when user played a delayed job immediately' do before do - accept_confirm do - find('.js-pipeline-dropdown-manual-actions').click - click_button('delayed job') - end + find('.js-pipeline-dropdown-manual-actions').click + page.accept_confirm { click_button('delayed job') } + wait_for_requests end - it 'enqueues scheduled action job' do - expect(page).to have_selector('.js-pipeline-dropdown-manual-actions:disabled') + it 'enqueues the delayed job', :js do + expect(delayed_job.reload).to be_pending end end end -- cgit v1.2.1 From 1ddf1942e07c1b484213f3473573d3b234e0d5c3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 3 Oct 2018 18:51:48 +0200 Subject: Improve the MR documentation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- doc/api/merge_requests.md | 682 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 473 insertions(+), 209 deletions(-) diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index b37e7698ab4..862ee398a84 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -54,35 +54,38 @@ Parameters: { "id": 1, "iid": 1, - "target_branch": "master", - "source_branch": "test1", "project_id": 3, "title": "test1", + "description": "fixed login page css paddings", "state": "opened", "created_at": "2017-04-29T08:46:00Z", "updated_at": "2017-04-29T08:46:00Z", + "target_branch": "master", + "source_branch": "test1", "upvotes": 0, "downvotes": 0, "author": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, "source_project_id": 2, "target_project_id": 3, - "labels": [ ], - "description": "fixed login page css paddings", + "labels": [ + "Community contribution", + "Manage" + ], "work_in_progress": false, "milestone": { "id": 5, @@ -93,23 +96,28 @@ Parameters: "state": "closed", "created_at": "2015-02-02T19:49:26.013Z", "updated_at": "2015-02-02T19:49:26.013Z", - "due_date": null + "due_date": "2018-09-22", + "start_date": "2018-08-08", + "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1" }, "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", "sha": "8888888888888888888888888888888888888888", "merge_commit_sha": null, "user_notes_count": 1, + "discussion_locked": null, "should_remove_source_branch": true, "force_remove_source_branch": false, - "squash": false, - "web_url": "http://example.com/example/example/merge_requests/1", + "allow_collaboration": false, + "allow_maintainer_to_push": false, + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", "time_stats": { "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, "human_total_time_spent": null - } + }, + "squash": false } ] ``` @@ -169,35 +177,38 @@ Parameters: { "id": 1, "iid": 1, - "target_branch": "master", - "source_branch": "test1", "project_id": 3, "title": "test1", + "description": "fixed login page css paddings", "state": "opened", "created_at": "2017-04-29T08:46:00Z", "updated_at": "2017-04-29T08:46:00Z", + "target_branch": "master", + "source_branch": "test1", "upvotes": 0, "downvotes": 0, "author": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, "source_project_id": 2, "target_project_id": 3, - "labels": [ ], - "description": "fixed login page css paddings", + "labels": [ + "Community contribution", + "Manage" + ], "work_in_progress": false, "milestone": { "id": 5, @@ -208,24 +219,28 @@ Parameters: "state": "closed", "created_at": "2015-02-02T19:49:26.013Z", "updated_at": "2015-02-02T19:49:26.013Z", - "due_date": null + "due_date": "2018-09-22", + "start_date": "2018-08-08", + "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1" }, "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", "sha": "8888888888888888888888888888888888888888", "merge_commit_sha": null, "user_notes_count": 1, + "discussion_locked": null, "should_remove_source_branch": true, "force_remove_source_branch": false, - "squash": false, - "web_url": "http://example.com/example/example/merge_requests/1", - "discussion_locked": false, + "allow_collaboration": false, + "allow_maintainer_to_push": false, + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", "time_stats": { "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, "human_total_time_spent": null - } + }, + "squash": false } ] ``` @@ -275,35 +290,38 @@ Parameters: { "id": 1, "iid": 1, - "target_branch": "master", - "source_branch": "test1", "project_id": 3, "title": "test1", + "description": "fixed login page css paddings", "state": "opened", "created_at": "2017-04-29T08:46:00Z", "updated_at": "2017-04-29T08:46:00Z", + "target_branch": "master", + "source_branch": "test1", "upvotes": 0, "downvotes": 0, "author": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, "source_project_id": 2, "target_project_id": 3, - "labels": [ ], - "description": "fixed login page css paddings", + "labels": [ + "Community contribution", + "Manage" + ], "work_in_progress": false, "milestone": { "id": 5, @@ -314,23 +332,26 @@ Parameters: "state": "closed", "created_at": "2015-02-02T19:49:26.013Z", "updated_at": "2015-02-02T19:49:26.013Z", - "due_date": null + "due_date": "2018-10-22", + "start_date": "2018-09-08", + "web_url": "gitlab.example.com/my-group/my-project/milestones/1" }, "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", "sha": "8888888888888888888888888888888888888888", "merge_commit_sha": null, "user_notes_count": 1, + "discussion_locked": null, "should_remove_source_branch": true, "force_remove_source_branch": false, - "web_url": "http://example.com/example/example/merge_requests/1", - "discussion_locked": false, + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", "time_stats": { "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, "human_total_time_spent": null - } + }, + "squash": false } ] ``` @@ -359,35 +380,38 @@ Parameters: { "id": 1, "iid": 1, - "target_branch": "master", - "source_branch": "test1", "project_id": 3, "title": "test1", - "state": "merged", + "description": "fixed login page css paddings", + "state": "opened", "created_at": "2017-04-29T08:46:00Z", "updated_at": "2017-04-29T08:46:00Z", + "target_branch": "master", + "source_branch": "test1", "upvotes": 0, "downvotes": 0, "author": { - "state" : "active", - "web_url" : "https://gitlab.example.com/root", - "avatar_url" : null, - "username" : "root", - "id" : 1, - "name" : "Administrator" + "id": 1, + "name": "Administrator", + "username": "admin", + "state": "active", + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "assignee": { - "state" : "active", - "web_url" : "https://gitlab.example.com/root", - "avatar_url" : null, - "username" : "root", - "id" : 1, - "name" : "Administrator" + "id": 1, + "name": "Administrator", + "username": "admin", + "state": "active", + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "source_project_id": 2, "target_project_id": 3, - "labels": [ ], - "description": "fixed login page css paddings", + "labels": [ + "Community contribution", + "Manage" + ], "work_in_progress": false, "milestone": { "id": 5, @@ -398,50 +422,55 @@ Parameters: "state": "closed", "created_at": "2015-02-02T19:49:26.013Z", "updated_at": "2015-02-02T19:49:26.013Z", - "due_date": null + "due_date": "2018-09-22", + "start_date": "2018-08-08", + "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1" }, "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", - "subscribed" : true, "sha": "8888888888888888888888888888888888888888", - "merge_commit_sha": "9999999999999999999999999999999999999999", + "merge_commit_sha": null, "user_notes_count": 1, - "changes_count": "1", + "discussion_locked": null, "should_remove_source_branch": true, "force_remove_source_branch": false, - "squash": false, - "web_url": "http://example.com/example/example/merge_requests/1", - "discussion_locked": false, + "allow_collaboration": false, + "allow_maintainer_to_push": false, + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", "time_stats": { "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, "human_total_time_spent": null }, - "closed_at": "2018-01-19T14:36:11.086Z", - "latest_build_started_at": null, - "latest_build_finished_at": null, + "squash": false, + "subscribed": false, + "changes_count": "1", + "merged_by": { + "id": 87854, + "name": "Douwe Maan", + "username": "DouweM", + "state": "active", + "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png", + "web_url": "https://gitlab.com/DouweM" + }, + "merged_at": "2018-09-07T11:16:17.520Z", + "closed_by": null, + "closed_at": null, + "latest_build_started_at": "2018-09-07T07:27:38.472Z", + "latest_build_finished_at": "2018-09-07T08:07:06.012Z", "first_deployed_to_production_at": null, "pipeline": { - "id": 8, - "ref": "master", - "sha": "2dc6aa325a317eda67812f05600bdf0fcdc70ab0", - "status": "created" - }, - "merged_by": null, - "merged_at": null, - "closed_by": { - "state" : "active", - "web_url" : "https://gitlab.example.com/root", - "avatar_url" : null, - "username" : "root", - "id" : 1, - "name" : "Administrator" + "id": 29626725, + "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "ref": "patch-28", + "status": "success", + "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725" }, "diff_refs": { - "base_sha": "1111111111111111111111111111111111111111", - "head_sha": "2222222222222222222222222222222222222222", - "start_sha": "3333333333333333333333333333333333333333" + "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00", + "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" }, "diverged_commits_count": 2 } @@ -663,65 +692,99 @@ POST /projects/:id/merge_requests { "id": 1, "iid": 1, - "target_branch": "master", - "source_branch": "test1", - "project_id": 4, + "project_id": 3, "title": "test1", + "description": "fixed login page css paddings", "state": "opened", + "created_at": "2017-04-29T08:46:00Z", + "updated_at": "2017-04-29T08:46:00Z", + "target_branch": "master", + "source_branch": "test1", "upvotes": 0, "downvotes": 0, "author": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, - "source_project_id": 3, - "target_project_id": 4, - "labels": [ ], - "description": "fixed login page css paddings", + "source_project_id": 2, + "target_project_id": 3, + "labels": [ + "Community contribution", + "Manage" + ], "work_in_progress": false, "milestone": { "id": 5, "iid": 1, - "project_id": 4, + "project_id": 3, "title": "v2.0", "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.", "state": "closed", "created_at": "2015-02-02T19:49:26.013Z", "updated_at": "2015-02-02T19:49:26.013Z", - "due_date": null + "due_date": "2018-09-22", + "start_date": "2018-08-08", + "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1" }, "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", - "subscribed" : true, "sha": "8888888888888888888888888888888888888888", "merge_commit_sha": null, - "user_notes_count": 0, - "changes_count": "1", + "user_notes_count": 1, + "discussion_locked": null, "should_remove_source_branch": true, "force_remove_source_branch": false, - "squash": false, - "web_url": "http://example.com/example/example/merge_requests/1", - "discussion_locked": false, "allow_collaboration": false, "allow_maintainer_to_push": false, + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", "time_stats": { "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, "human_total_time_spent": null - } + }, + "squash": false, + "subscribed": false, + "changes_count": "1", + "merged_by": { + "id": 87854, + "name": "Douwe Maan", + "username": "DouweM", + "state": "active", + "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png", + "web_url": "https://gitlab.com/DouweM" + }, + "merged_at": "2018-09-07T11:16:17.520Z", + "closed_by": null, + "closed_at": null, + "latest_build_started_at": "2018-09-07T07:27:38.472Z", + "latest_build_finished_at": "2018-09-07T08:07:06.012Z", + "first_deployed_to_production_at": null, + "pipeline": { + "id": 29626725, + "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "ref": "patch-28", + "status": "success", + "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725" + }, + "diff_refs": { + "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00", + "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" + }, + "diverged_commits_count": 2 } ``` @@ -756,64 +819,99 @@ Must include at least one non-required attribute from above. { "id": 1, "iid": 1, - "target_branch": "master", - "project_id": 4, + "project_id": 3, "title": "test1", + "description": "fixed login page css paddings", "state": "opened", + "created_at": "2017-04-29T08:46:00Z", + "updated_at": "2017-04-29T08:46:00Z", + "target_branch": "master", + "source_branch": "test1", "upvotes": 0, "downvotes": 0, "author": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, - "source_project_id": 3, - "target_project_id": 4, - "labels": [ ], - "description": "description1", + "source_project_id": 2, + "target_project_id": 3, + "labels": [ + "Community contribution", + "Manage" + ], "work_in_progress": false, "milestone": { "id": 5, "iid": 1, - "project_id": 4, + "project_id": 3, "title": "v2.0", "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.", "state": "closed", "created_at": "2015-02-02T19:49:26.013Z", "updated_at": "2015-02-02T19:49:26.013Z", - "due_date": null + "due_date": "2018-09-22", + "start_date": "2018-08-08", + "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1" }, "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", - "subscribed" : true, "sha": "8888888888888888888888888888888888888888", "merge_commit_sha": null, "user_notes_count": 1, - "changes_count": "1", + "discussion_locked": null, "should_remove_source_branch": true, "force_remove_source_branch": false, - "squash": false, - "web_url": "http://example.com/example/example/merge_requests/1", - "discussion_locked": false, "allow_collaboration": false, "allow_maintainer_to_push": false, + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", "time_stats": { "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, "human_total_time_spent": null - } + }, + "squash": false, + "subscribed": false, + "changes_count": "1", + "merged_by": { + "id": 87854, + "name": "Douwe Maan", + "username": "DouweM", + "state": "active", + "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png", + "web_url": "https://gitlab.com/DouweM" + }, + "merged_at": "2018-09-07T11:16:17.520Z", + "closed_by": null, + "closed_at": null, + "latest_build_started_at": "2018-09-07T07:27:38.472Z", + "latest_build_finished_at": "2018-09-07T08:07:06.012Z", + "first_deployed_to_production_at": null, + "pipeline": { + "id": 29626725, + "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "ref": "patch-28", + "status": "success", + "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725" + }, + "diff_refs": { + "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00", + "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" + }, + "diverged_commits_count": 2 } ``` @@ -857,70 +955,106 @@ Parameters: - `merge_request_iid` (required) - Internal ID of MR - `merge_commit_message` (optional) - Custom merge commit message - `should_remove_source_branch` (optional) - if `true` removes the source branch -- `merge_when_pipeline_succeeds` (optional) - if `true` the MR is merged when the pipeline succeeds +- `merge_when_pipeline_succeeds` (optional) - if `true` the MR is merged when the pipeline succeeds - `sha` (optional) - if present, then this SHA must match the HEAD of the source branch, otherwise the merge will fail ```json { "id": 1, "iid": 1, - "target_branch": "master", - "source_branch": "test1", "project_id": 3, "title": "test1", - "state": "merged", + "description": "fixed login page css paddings", + "state": "opened", + "created_at": "2017-04-29T08:46:00Z", + "updated_at": "2017-04-29T08:46:00Z", + "target_branch": "master", + "source_branch": "test1", "upvotes": 0, "downvotes": 0, "author": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, - "source_project_id": 4, - "target_project_id": 4, - "labels": [ ], - "description": "fixed login page css paddings", + "source_project_id": 2, + "target_project_id": 3, + "labels": [ + "Community contribution", + "Manage" + ], "work_in_progress": false, "milestone": { "id": 5, "iid": 1, - "project_id": 4, + "project_id": 3, "title": "v2.0", "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.", "state": "closed", "created_at": "2015-02-02T19:49:26.013Z", "updated_at": "2015-02-02T19:49:26.013Z", - "due_date": null + "due_date": "2018-09-22", + "start_date": "2018-08-08", + "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1" }, "merge_when_pipeline_succeeds": true, "merge_status": "can_be_merged", - "subscribed" : true, "sha": "8888888888888888888888888888888888888888", - "merge_commit_sha": "9999999999999999999999999999999999999999", + "merge_commit_sha": null, "user_notes_count": 1, - "changes_count": "1", + "discussion_locked": null, "should_remove_source_branch": true, "force_remove_source_branch": false, - "squash": false, - "web_url": "http://example.com/example/example/merge_requests/1", - "discussion_locked": false, + "allow_collaboration": false, + "allow_maintainer_to_push": false, + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", "time_stats": { "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, "human_total_time_spent": null - } + }, + "squash": false, + "subscribed": false, + "changes_count": "1", + "merged_by": { + "id": 87854, + "name": "Douwe Maan", + "username": "DouweM", + "state": "active", + "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png", + "web_url": "https://gitlab.com/DouweM" + }, + "merged_at": "2018-09-07T11:16:17.520Z", + "closed_by": null, + "closed_at": null, + "latest_build_started_at": "2018-09-07T07:27:38.472Z", + "latest_build_finished_at": "2018-09-07T08:07:06.012Z", + "first_deployed_to_production_at": null, + "pipeline": { + "id": 29626725, + "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "ref": "patch-28", + "status": "success", + "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725" + }, + "diff_refs": { + "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00", + "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" + }, + "diverged_commits_count": 2 } ``` @@ -937,69 +1071,105 @@ PUT /projects/:id/merge_requests/:merge_request_iid/cancel_merge_when_pipeline_s Parameters: - `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user -- `merge_request_iid` (required) - Internal ID of MR +- `merge_request_iid` (required) - Internal ID of MR ```json { "id": 1, "iid": 1, - "target_branch": "master", - "source_branch": "test1", "project_id": 3, "title": "test1", + "description": "fixed login page css paddings", "state": "opened", + "created_at": "2017-04-29T08:46:00Z", + "updated_at": "2017-04-29T08:46:00Z", + "target_branch": "master", + "source_branch": "test1", "upvotes": 0, "downvotes": 0, "author": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, "assignee": { "id": 1, - "username": "admin", "name": "Administrator", + "username": "admin", "state": "active", "avatar_url": null, "web_url" : "https://gitlab.example.com/admin" }, - "source_project_id": 4, - "target_project_id": 4, - "labels": [ ], - "description": "fixed login page css paddings", + "source_project_id": 2, + "target_project_id": 3, + "labels": [ + "Community contribution", + "Manage" + ], "work_in_progress": false, "milestone": { "id": 5, "iid": 1, - "project_id": 4, + "project_id": 3, "title": "v2.0", "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.", "state": "closed", "created_at": "2015-02-02T19:49:26.013Z", "updated_at": "2015-02-02T19:49:26.013Z", - "due_date": null + "due_date": "2018-09-22", + "start_date": "2018-08-08", + "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1" }, - "merge_when_pipeline_succeeds": true, + "merge_when_pipeline_succeeds": false, "merge_status": "can_be_merged", - "subscribed" : true, "sha": "8888888888888888888888888888888888888888", "merge_commit_sha": null, "user_notes_count": 1, - "changes_count": "1", + "discussion_locked": null, "should_remove_source_branch": true, "force_remove_source_branch": false, - "squash": false, - "web_url": "http://example.com/example/example/merge_requests/1", - "discussion_locked": false, + "allow_collaboration": false, + "allow_maintainer_to_push": false, + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", "time_stats": { "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, "human_total_time_spent": null - } + }, + "squash": false, + "subscribed": false, + "changes_count": "1", + "merged_by": { + "id": 87854, + "name": "Douwe Maan", + "username": "DouweM", + "state": "active", + "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png", + "web_url": "https://gitlab.com/DouweM" + }, + "merged_at": "2018-09-07T11:16:17.520Z", + "closed_by": null, + "closed_at": null, + "latest_build_started_at": "2018-09-07T07:27:38.472Z", + "latest_build_finished_at": "2018-09-07T08:07:06.012Z", + "first_deployed_to_production_at": null, + "pipeline": { + "id": 29626725, + "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "ref": "patch-28", + "status": "success", + "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725" + }, + "diff_refs": { + "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00", + "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" + }, + "diverged_commits_count": 2 } ``` @@ -1067,7 +1237,7 @@ Example response when the GitLab issue tracker is used: "labels" : [], "user_notes_count": 1, "changes_count": "1" - }, + } ] ``` @@ -1104,54 +1274,101 @@ Example response: ```json { - "id": 17, + "id": 1, "iid": 1, - "project_id": 5, - "title": "Et et sequi est impedit nulla ut rem et voluptatem.", - "description": "Consequatur velit eos rerum optio autem. Quia id officia quaerat dolorum optio. Illo laudantium aut ipsum dolorem.", + "project_id": 3, + "title": "test1", + "description": "fixed login page css paddings", "state": "opened", - "created_at": "2016-04-05T21:42:23.233Z", - "updated_at": "2016-04-05T22:11:52.900Z", - "target_branch": "ui-dev-kit", - "source_branch": "version-1-9", + "created_at": "2017-04-29T08:46:00Z", + "updated_at": "2017-04-29T08:46:00Z", + "target_branch": "master", + "source_branch": "test1", "upvotes": 0, "downvotes": 0, "author": { - "name": "Eileen Skiles", - "username": "leila", - "id": 19, + "id": 1, + "name": "Administrator", + "username": "admin", "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/39ce4a2822cc896933ffbd68c1470e55?s=80&d=identicon", - "web_url": "https://gitlab.example.com/leila" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "assignee": { - "name": "Celine Wehner", - "username": "carli", - "id": 16, + "id": 1, + "name": "Administrator", + "username": "admin", "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/f4cd5605b769dd2ce405a27c6e6f2684?s=80&d=identicon", - "web_url": "https://gitlab.example.com/carli" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, - "source_project_id": 5, - "target_project_id": 5, - "labels": [], + "source_project_id": 2, + "target_project_id": 3, + "labels": [ + "Community contribution", + "Manage" + ], "work_in_progress": false, "milestone": { - "id": 7, + "id": 5, "iid": 1, - "project_id": 5, + "project_id": 3, "title": "v2.0", - "description": "Corrupti eveniet et velit occaecati dolorem est rerum aut.", + "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.", "state": "closed", - "created_at": "2016-04-05T21:41:40.905Z", - "updated_at": "2016-04-05T21:41:40.905Z", - "due_date": null + "created_at": "2015-02-02T19:49:26.013Z", + "updated_at": "2015-02-02T19:49:26.013Z", + "due_date": "2018-09-22", + "start_date": "2018-08-08", + "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1" }, - "merge_when_pipeline_succeeds": false, - "merge_status": "cannot_be_merged", - "subscribed": true, + "merge_when_pipeline_succeeds": true, + "merge_status": "can_be_merged", "sha": "8888888888888888888888888888888888888888", - "merge_commit_sha": null + "merge_commit_sha": null, + "user_notes_count": 1, + "discussion_locked": null, + "should_remove_source_branch": true, + "force_remove_source_branch": false, + "allow_collaboration": false, + "allow_maintainer_to_push": false, + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + }, + "squash": false, + "subscribed": false, + "changes_count": "1", + "merged_by": { + "id": 87854, + "name": "Douwe Maan", + "username": "DouweM", + "state": "active", + "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png", + "web_url": "https://gitlab.com/DouweM" + }, + "merged_at": "2018-09-07T11:16:17.520Z", + "closed_by": null, + "closed_at": null, + "latest_build_started_at": "2018-09-07T07:27:38.472Z", + "latest_build_finished_at": "2018-09-07T08:07:06.012Z", + "first_deployed_to_production_at": null, + "pipeline": { + "id": 29626725, + "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "ref": "patch-28", + "status": "success", + "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725" + }, + "diff_refs": { + "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00", + "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" + }, + "diverged_commits_count": 2 } ``` @@ -1178,54 +1395,101 @@ Example response: ```json { - "id": 17, + "id": 1, "iid": 1, - "project_id": 5, - "title": "Et et sequi est impedit nulla ut rem et voluptatem.", - "description": "Consequatur velit eos rerum optio autem. Quia id officia quaerat dolorum optio. Illo laudantium aut ipsum dolorem.", + "project_id": 3, + "title": "test1", + "description": "fixed login page css paddings", "state": "opened", - "created_at": "2016-04-05T21:42:23.233Z", - "updated_at": "2016-04-05T22:11:52.900Z", - "target_branch": "ui-dev-kit", - "source_branch": "version-1-9", + "created_at": "2017-04-29T08:46:00Z", + "updated_at": "2017-04-29T08:46:00Z", + "target_branch": "master", + "source_branch": "test1", "upvotes": 0, "downvotes": 0, "author": { - "name": "Eileen Skiles", - "username": "leila", - "id": 19, + "id": 1, + "name": "Administrator", + "username": "admin", "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/39ce4a2822cc896933ffbd68c1470e55?s=80&d=identicon", - "web_url": "https://gitlab.example.com/leila" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, "assignee": { - "name": "Celine Wehner", - "username": "carli", - "id": 16, + "id": 1, + "name": "Administrator", + "username": "admin", "state": "active", - "avatar_url": "http://www.gravatar.com/avatar/f4cd5605b769dd2ce405a27c6e6f2684?s=80&d=identicon", - "web_url": "https://gitlab.example.com/carli" + "avatar_url": null, + "web_url" : "https://gitlab.example.com/admin" }, - "source_project_id": 5, - "target_project_id": 5, - "labels": [], + "source_project_id": 2, + "target_project_id": 3, + "labels": [ + "Community contribution", + "Manage" + ], "work_in_progress": false, "milestone": { - "id": 7, + "id": 5, "iid": 1, - "project_id": 5, + "project_id": 3, "title": "v2.0", - "description": "Corrupti eveniet et velit occaecati dolorem est rerum aut.", + "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.", "state": "closed", - "created_at": "2016-04-05T21:41:40.905Z", - "updated_at": "2016-04-05T21:41:40.905Z", - "due_date": null + "created_at": "2015-02-02T19:49:26.013Z", + "updated_at": "2015-02-02T19:49:26.013Z", + "due_date": "2018-09-22", + "start_date": "2018-08-08", + "web_url": "https://gitlab.example.com/my-group/my-project/milestones/1" }, - "merge_when_pipeline_succeeds": false, - "merge_status": "cannot_be_merged", - "subscribed": false, + "merge_when_pipeline_succeeds": true, + "merge_status": "can_be_merged", "sha": "8888888888888888888888888888888888888888", - "merge_commit_sha": null + "merge_commit_sha": null, + "user_notes_count": 1, + "discussion_locked": null, + "should_remove_source_branch": true, + "force_remove_source_branch": false, + "allow_collaboration": false, + "allow_maintainer_to_push": false, + "web_url": "http://gitlab.example.com/my-group/my-project/merge_requests/1", + "time_stats": { + "time_estimate": 0, + "total_time_spent": 0, + "human_time_estimate": null, + "human_total_time_spent": null + }, + "squash": false, + "subscribed": false, + "changes_count": "1", + "merged_by": { + "id": 87854, + "name": "Douwe Maan", + "username": "DouweM", + "state": "active", + "avatar_url": "https://gitlab.example.com/uploads/-/system/user/avatar/87854/avatar.png", + "web_url": "https://gitlab.com/DouweM" + }, + "merged_at": "2018-09-07T11:16:17.520Z", + "closed_by": null, + "closed_at": null, + "latest_build_started_at": "2018-09-07T07:27:38.472Z", + "latest_build_finished_at": "2018-09-07T08:07:06.012Z", + "first_deployed_to_production_at": null, + "pipeline": { + "id": 29626725, + "sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "ref": "patch-28", + "status": "success", + "web_url": "https://gitlab.example.com/my-group/my-project/pipelines/29626725" + }, + "diff_refs": { + "base_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00", + "head_sha": "2be7ddb704c7b6b83732fdd5b9f09d5a397b5f8f", + "start_sha": "c380d3acebd181f13629a25d2e2acca46ffe1e00" + }, + "diverged_commits_count": 2 } ``` -- cgit v1.2.1 From 1b16ef307f9206afd0abdb4dcf7faa45a5daafd8 Mon Sep 17 00:00:00 2001 From: Luke Bennett Date: Thu, 4 Oct 2018 13:34:18 +0100 Subject: maintainer review --- app/assets/javascripts/pages/root/index.js | 4 ++++ app/views/projects/clusters/_banner.html.haml | 4 ++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/pages/root/index.js b/app/assets/javascripts/pages/root/index.js index bb3e672a000..09f8185d3b5 100644 --- a/app/assets/javascripts/pages/root/index.js +++ b/app/assets/javascripts/pages/root/index.js @@ -1 +1,5 @@ +// if the "projects dashboard" is a user's default dashboard, when they visit the +// instance root index, the dashboard will be served by the root controller instead +// of a dashboard controller. The root index redirects for all other default dashboards. + import '../dashboard/projects/index'; diff --git a/app/views/projects/clusters/_banner.html.haml b/app/views/projects/clusters/_banner.html.haml index 4616d1359b4..141314b4e4e 100644 --- a/app/views/projects/clusters/_banner.html.haml +++ b/app/views/projects/clusters/_banner.html.haml @@ -9,7 +9,7 @@ = s_("ClusterIntegration|Kubernetes cluster was successfully created on Google Kubernetes Engine. Refresh the page to see Kubernetes cluster's details") - if show_cluster_security_warning? - .js-cluster-security-warning.alert.alert-block.alert-dismissable.bs-callout.bs-callout-warning - %button.close.js-close{ type: "button", data: { feature_id: UserCalloutsHelper::CLUSTER_SECURITY_WARNING, dismiss_endpoint: user_callouts_path } } × + .js-cluster-security-warning.alert.alert-block.alert-dismissable.bs-callout.bs-callout-warning{ data: { feature_id: UserCalloutsHelper::CLUSTER_SECURITY_WARNING, dismiss_endpoint: user_callouts_path } } + %button.close.js-close{ type: "button" } × = s_("ClusterIntegration|The default cluster configuration grants access to many functionalities needed to successfully build and deploy a containerised application.") = link_to s_("More information"), help_page_path('user/project/clusters/index.md', anchor: 'security-implications') -- cgit v1.2.1 From 0fc93df02b7d1be4ec243298ff17045cbd4f049a Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Thu, 4 Oct 2018 14:53:15 +0200 Subject: Add component tests for scheduled job frontend --- .../javascripts/lib/utils/datetime_utility.js | 1 + .../pipelines/components/pipelines_actions.vue | 2 +- .../pipelines/components/pipelines_table_row.vue | 6 +- .../pipelines/pipelines_actions_spec.js | 89 +++++++++++++++------- .../pipelines/pipelines_table_row_spec.js | 7 ++ 5 files changed, 75 insertions(+), 30 deletions(-) diff --git a/app/assets/javascripts/lib/utils/datetime_utility.js b/app/assets/javascripts/lib/utils/datetime_utility.js index abfcf1eaf3f..833dbefd3dc 100644 --- a/app/assets/javascripts/lib/utils/datetime_utility.js +++ b/app/assets/javascripts/lib/utils/datetime_utility.js @@ -373,6 +373,7 @@ window.gl.utils = { /** * Formats milliseconds as timestamp (e.g. 01:02:03). + * This takes durations longer than a day into account (e.g. two days would be 48:00:00). * * @param milliseconds * @returns {string} diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue index f0e845d0773..743d241ee7a 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue @@ -27,7 +27,7 @@ export default { onClickAction(action) { if (action.scheduled_at) { const confirmationMessage = sprintf(s__("DelayedJobs|Are you sure you want to run %{jobName} immediately? This job will run automatically after it's timer finishes."), { jobName: action.name }); - // https://gitlab.com/gitlab-org/gitlab-ce/issues/52099 + // https://gitlab.com/gitlab-org/gitlab-ce/issues/52156 // eslint-disable-next-line no-alert if (!window.confirm(confirmationMessage)) { return; diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue index 88957554d12..09ee190b8ca 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue @@ -63,10 +63,10 @@ export default { if (!this.pipeline || !this.pipeline.details) { return []; } - const { details: pipelineDetails } = this.pipeline; + const { details } = this.pipeline; return [ - ...(pipelineDetails.manual_actions || []), - ...(pipelineDetails.scheduled_actions || []), + ...(details.manual_actions || []), + ...(details.scheduled_actions || []), ]; }, /** diff --git a/spec/javascripts/pipelines/pipelines_actions_spec.js b/spec/javascripts/pipelines/pipelines_actions_spec.js index 72fb0a8f9ef..fe60a883f77 100644 --- a/spec/javascripts/pipelines/pipelines_actions_spec.js +++ b/spec/javascripts/pipelines/pipelines_actions_spec.js @@ -1,46 +1,83 @@ import Vue from 'vue'; -import pipelinesActionsComp from '~/pipelines/components/pipelines_actions.vue'; +import eventHub from '~/pipelines/event_hub'; +import PipelinesActions from '~/pipelines/components/pipelines_actions.vue'; +import mountComponent from 'spec/helpers/vue_mount_component_helper'; +import { TEST_HOST } from 'spec/test_constants'; describe('Pipelines Actions dropdown', () => { - let component; - let actions; - let ActionsComponent; + const Component = Vue.extend(PipelinesActions); + let vm; - beforeEach(() => { - ActionsComponent = Vue.extend(pipelinesActionsComp); + afterEach(() => { + vm.$destroy(); + }); - actions = [ + describe('manual actions', () => { + const actions = [ { name: 'stop_review', - path: '/root/review-app/builds/1893/play', + path: `${TEST_HOST}/root/review-app/builds/1893/play`, }, { name: 'foo', - path: '#', + path: `${TEST_HOST}/disabled/pipeline/action`, playable: false, }, ]; - component = new ActionsComponent({ - propsData: { - actions, - }, - }).$mount(); - }); + beforeEach(() => { + vm = mountComponent(Component, { actions }); + }); - it('should render a dropdown with the provided actions', () => { - expect( - component.$el.querySelectorAll('.dropdown-menu li').length, - ).toEqual(actions.length); + it('renders a dropdown with the provided actions', () => { + const dropdownItems = vm.$el.querySelectorAll('.dropdown-menu li'); + expect(dropdownItems.length).toEqual(actions.length); + }); + + it("renders a disabled action when it's not playable", () => { + const dropdownItem = vm.$el.querySelector('.dropdown-menu li:last-child button'); + expect(dropdownItem).toBeDisabled(); + }); }); - it('should render a disabled action when it\'s not playable', () => { - expect( - component.$el.querySelector('.dropdown-menu li:last-child button').getAttribute('disabled'), - ).toEqual('disabled'); + describe('scheduled jobs', () => { + const scheduledJobAction = { + name: 'scheduled action', + path: `${TEST_HOST}/scheduled/job/action`, + playable: true, + scheduled_at: '2063-04-05T00:42:00Z', + }; + const findDropdownItem = () => vm.$el.querySelector('.dropdown-menu li button'); + + beforeEach(() => { + spyOn(Date, 'now').and.callFake(() => new Date('2063-04-04T00:42:00Z').getTime()); + vm = mountComponent(Component, { actions: [scheduledJobAction] }); + }); + + it('emits postAction event after confirming', () => { + const emitSpy = jasmine.createSpy('emit'); + eventHub.$on('postAction', emitSpy); + spyOn(window, 'confirm').and.callFake(() => true); + + findDropdownItem().click(); + + expect(window.confirm).toHaveBeenCalled(); + expect(emitSpy).toHaveBeenCalledWith(scheduledJobAction.path); + }); + + it('does not emit postAction event if confirmation is cancelled', () => { + const emitSpy = jasmine.createSpy('emit'); + eventHub.$on('postAction', emitSpy); + spyOn(window, 'confirm').and.callFake(() => false); + + findDropdownItem().click(); + + expect(window.confirm).toHaveBeenCalled(); + expect(emitSpy).not.toHaveBeenCalled(); + }); - expect( - component.$el.querySelector('.dropdown-menu li:last-child button').classList.contains('disabled'), - ).toEqual(true); + it('displays the remaining time in the dropdown', () => { + expect(findDropdownItem()).toContainText('24:00:00'); + }); }); }); diff --git a/spec/javascripts/pipelines/pipelines_table_row_spec.js b/spec/javascripts/pipelines/pipelines_table_row_spec.js index 03ffc122795..42795f5c134 100644 --- a/spec/javascripts/pipelines/pipelines_table_row_spec.js +++ b/spec/javascripts/pipelines/pipelines_table_row_spec.js @@ -158,8 +158,13 @@ describe('Pipelines Table Row', () => { }); describe('actions column', () => { + const scheduledJobAction = { + name: 'some scheduled job', + }; + beforeEach(() => { const withActions = Object.assign({}, pipeline); + withActions.details.scheduled_actions = [scheduledJobAction]; withActions.flags.cancelable = true; withActions.flags.retryable = true; withActions.cancel_path = '/cancel'; @@ -171,6 +176,8 @@ describe('Pipelines Table Row', () => { it('should render the provided actions', () => { expect(component.$el.querySelector('.js-pipelines-retry-button')).not.toBeNull(); expect(component.$el.querySelector('.js-pipelines-cancel-button')).not.toBeNull(); + const dropdownMenu = component.$el.querySelectorAll('.dropdown-menu'); + expect(dropdownMenu).toContainText(scheduledJobAction.name); }); it('emits `retryPipeline` event when retry button is clicked and toggles loading', () => { -- cgit v1.2.1 From d277f0bbeee360f679b5171ca23fe5b32bcfe840 Mon Sep 17 00:00:00 2001 From: Amit Rathi Date: Thu, 4 Oct 2018 12:59:21 +0000 Subject: Clone nurtch demo notebooks at Jupyter startup --- changelogs/unreleased/clone-nurtch-demo-repo.yml | 5 +++++ doc/user/project/clusters/index.md | 2 +- vendor/jupyter/values.yaml | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/clone-nurtch-demo-repo.yml diff --git a/changelogs/unreleased/clone-nurtch-demo-repo.yml b/changelogs/unreleased/clone-nurtch-demo-repo.yml new file mode 100644 index 00000000000..c77138d27f0 --- /dev/null +++ b/changelogs/unreleased/clone-nurtch-demo-repo.yml @@ -0,0 +1,5 @@ +--- +title: Copy nurtch demo notebooks at Jupyter startup +merge_request: 21698 +author: Amit Rathi +type: added diff --git a/doc/user/project/clusters/index.md b/doc/user/project/clusters/index.md index ee8b1af7b4a..3ec17806490 100644 --- a/doc/user/project/clusters/index.md +++ b/doc/user/project/clusters/index.md @@ -215,7 +215,7 @@ twice, which can lead to confusion during deployments. | [Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) | 10.2+ | Ingress can provide load balancing, SSL termination, and name-based virtual hosting. It acts as a web proxy for your applications and is useful if you want to use [Auto DevOps] or deploy your own web apps. | [stable/nginx-ingress](https://github.com/helm/charts/tree/master/stable/nginx-ingress) | | [Prometheus](https://prometheus.io/docs/introduction/overview/) | 10.4+ | Prometheus is an open-source monitoring and alerting system useful to supervise your deployed applications. | [stable/prometheus](https://github.com/helm/charts/tree/master/stable/prometheus) | | [GitLab Runner](https://docs.gitlab.com/runner/) | 10.6+ | GitLab Runner is the open source project that is used to run your jobs and send the results back to GitLab. It is used in conjunction with [GitLab CI/CD](https://about.gitlab.com/features/gitlab-ci-cd/), the open-source continuous integration service included with GitLab that coordinates the jobs. When installing the GitLab Runner via the applications, it will run in **privileged mode** by default. Make sure you read the [security implications](#security-implications) before doing so. | [runner/gitlab-runner](https://gitlab.com/charts/gitlab-runner) | -| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use [this](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) custom Jupyter image that installs additional useful packages on top of the base Jupyter. **Note**: Authentication will be enabled for any user of the GitLab server via OAuth2. HTTPS will be supported in a future release. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) | +| [JupyterHub](http://jupyter.org/) | 11.0+ | [JupyterHub](https://jupyterhub.readthedocs.io/en/stable/) is a multi-user service for managing notebooks across a team. [Jupyter Notebooks](https://jupyter-notebook.readthedocs.io/en/latest/) provide a web-based interactive programming environment used for data analysis, visualization, and machine learning. We use [this](https://gitlab.com/gitlab-org/jupyterhub-user-image/blob/master/Dockerfile) custom Jupyter image that installs additional useful packages on top of the base Jupyter. You will also see ready-to-use DevOps Runbooks built with [Rubix](https://github.com/amit1rrr/rubix). **Note**: Authentication will be enabled for any user of the GitLab server via OAuth2. HTTPS will be supported in a future release. | [jupyter/jupyterhub](https://jupyterhub.github.io/helm-chart/) | ## Getting the external IP address diff --git a/vendor/jupyter/values.yaml b/vendor/jupyter/values.yaml index 4ea5b44c59c..049ffcc3407 100644 --- a/vendor/jupyter/values.yaml +++ b/vendor/jupyter/values.yaml @@ -13,6 +13,10 @@ auth: singleuser: defaultUrl: "/lab" + lifecycleHooks: + postStart: + exec: + command: ["git", "clone", "https://gitlab.com/gitlab-org/nurtch-demo.git", "DevOps-Runbook-Demo"] ingress: enabled: true -- cgit v1.2.1 From e13baff004810fba3b7fd6eeffc3e4de5af952a1 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 4 Oct 2018 12:59:57 +0000 Subject: Renders empty states in the Vue app in Job page --- .../javascripts/jobs/components/empty_state.vue | 4 +- app/assets/javascripts/jobs/components/job_app.vue | 15 +++ app/assets/javascripts/jobs/store/getters.js | 13 +- app/serializers/detailed_status_entity.rb | 8 +- app/views/projects/jobs/_empty_state.html.haml | 18 --- app/views/projects/jobs/_empty_states.html.haml | 9 -- app/views/projects/jobs/show.html.haml | 2 - spec/features/projects/jobs_spec.rb | 12 +- .../jobs/components/empty_state_spec.js | 2 +- spec/javascripts/jobs/components/job_app_spec.js | 139 +++++++++++++++++++++ spec/javascripts/jobs/store/getters_spec.js | 92 ++++++++++++++ 11 files changed, 274 insertions(+), 40 deletions(-) delete mode 100644 app/views/projects/jobs/_empty_state.html.haml delete mode 100644 app/views/projects/jobs/_empty_states.html.haml diff --git a/app/assets/javascripts/jobs/components/empty_state.vue b/app/assets/javascripts/jobs/components/empty_state.vue index ff45a5b05f8..ff500eddddb 100644 --- a/app/assets/javascripts/jobs/components/empty_state.vue +++ b/app/assets/javascripts/jobs/components/empty_state.vue @@ -27,7 +27,7 @@ value === null || (Object.prototype.hasOwnProperty.call(value, 'path') && Object.prototype.hasOwnProperty.call(value, 'method') && - Object.prototype.hasOwnProperty.call(value, 'title')) + Object.prototype.hasOwnProperty.call(value, 'button_title')) ); }, }, @@ -67,7 +67,7 @@ :data-method="action.method" class="js-job-empty-state-action btn btn-primary" > - {{ action.title }} + {{ action.button_title }}
diff --git a/app/assets/javascripts/jobs/components/job_app.vue b/app/assets/javascripts/jobs/components/job_app.vue index bac8bd71d64..047e55866ce 100644 --- a/app/assets/javascripts/jobs/components/job_app.vue +++ b/app/assets/javascripts/jobs/components/job_app.vue @@ -2,6 +2,7 @@ import { mapGetters, mapState } from 'vuex'; import CiHeader from '~/vue_shared/components/header_ci_component.vue'; import Callout from '~/vue_shared/components/callout.vue'; + import EmptyState from './empty_state.vue'; import EnvironmentsBlock from './environments_block.vue'; import ErasedBlock from './erased_block.vue'; import StuckBlock from './stuck_block.vue'; @@ -11,6 +12,7 @@ components: { CiHeader, Callout, + EmptyState, EnvironmentsBlock, ErasedBlock, StuckBlock, @@ -31,6 +33,8 @@ 'jobHasStarted', 'hasEnvironment', 'isJobStuck', + 'hasTrace', + 'emptyStateIllustration', ]), }, }; @@ -77,12 +81,14 @@ @@ -91,6 +97,15 @@ + diff --git a/app/assets/javascripts/jobs/store/getters.js b/app/assets/javascripts/jobs/store/getters.js index 62d154ff584..afe5f88b292 100644 --- a/app/assets/javascripts/jobs/store/getters.js +++ b/app/assets/javascripts/jobs/store/getters.js @@ -29,6 +29,16 @@ export const jobHasStarted = state => !(state.job.started === false); export const hasEnvironment = state => !_.isEmpty(state.job.deployment_status); +/** + * Checks if it the job has trace. + * Used to check if it should render the job log or the empty state + * @returns {Boolean} + */ +export const hasTrace = state => state.job.has_trace || state.job.status.group === 'running'; + +export const emptyStateIllustration = state => + (state.job && state.job.status && state.job.status.illustration) || {}; + /** * When the job is pending and there are no available runners * we need to render the stuck block; @@ -36,7 +46,8 @@ export const hasEnvironment = state => !_.isEmpty(state.job.deployment_status); * @returns {Boolean} */ export const isJobStuck = state => - state.job.status.group === 'pending' && state.job.runners && state.job.runners.available === false; + state.job.status.group === 'pending' && + (!_.isEmpty(state.job.runners) && state.job.runners.available === false); // prevent babel-plugin-rewire from generating an invalid default during karma tests export default () => {}; diff --git a/app/serializers/detailed_status_entity.rb b/app/serializers/detailed_status_entity.rb index c772c807f76..da994d78286 100644 --- a/app/serializers/detailed_status_entity.rb +++ b/app/serializers/detailed_status_entity.rb @@ -10,7 +10,12 @@ class DetailedStatusEntity < Grape::Entity expose :illustration do |status| begin - status.illustration + illustration = { + image: ActionController::Base.helpers.image_path(status.illustration[:image]) + } + illustration = status.illustration.merge(illustration) + + illustration rescue NotImplementedError # ignored end @@ -25,5 +30,6 @@ class DetailedStatusEntity < Grape::Entity expose :action_title, as: :title expose :action_path, as: :path expose :action_method, as: :method + expose :action_button_title, as: :button_title end end diff --git a/app/views/projects/jobs/_empty_state.html.haml b/app/views/projects/jobs/_empty_state.html.haml deleted file mode 100644 index ea552c73c92..00000000000 --- a/app/views/projects/jobs/_empty_state.html.haml +++ /dev/null @@ -1,18 +0,0 @@ -- illustration = local_assigns.fetch(:illustration) -- illustration_size = local_assigns.fetch(:illustration_size) -- title = local_assigns.fetch(:title) -- content = local_assigns.fetch(:content, nil) -- action = local_assigns.fetch(:action, nil) - -.row.empty-state - .col-12 - .svg-content{ class: illustration_size } - = image_tag illustration - .col-12 - .text-content - %h4.text-center= title - - if content - %p= content - - if action - .text-center - = action diff --git a/app/views/projects/jobs/_empty_states.html.haml b/app/views/projects/jobs/_empty_states.html.haml deleted file mode 100644 index e5198d047df..00000000000 --- a/app/views/projects/jobs/_empty_states.html.haml +++ /dev/null @@ -1,9 +0,0 @@ -- detailed_status = @build.detailed_status(current_user) -- illustration = detailed_status.illustration - -= render 'empty_state', - illustration: illustration[:image], - illustration_size: illustration[:size], - title: illustration[:title], - content: illustration[:content], - action: detailed_status.has_action? ? link_to(detailed_status.action_button_title, detailed_status.action_path, method: detailed_status.action_method, class: 'btn btn-primary', title: detailed_status.action_button_title) : nil diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index ab7963737ca..a5f814b722d 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -42,8 +42,6 @@ = custom_icon('scroll_down') = render 'shared/builds/build_output' - - else - = render "empty_states" #js-details-block-vue{ data: { terminal_path: can?(current_user, :create_build_terminal, @build) && @build.has_terminal? ? terminal_project_job_path(@project, @build) : nil } } diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 6213cabfaad..9fe56d840e1 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -542,7 +542,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do visit project_job_path(project, job) end - it 'shows manual action empty state' do + it 'shows manual action empty state', :js do expect(page).to have_content(job.detailed_status(user).illustration[:title]) expect(page).to have_content('This job requires a manual action') expect(page).to have_content('This job depends on a user to trigger its process. Often they are used to deploy code to production environments') @@ -566,14 +566,14 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do visit project_job_path(project, job) end - it 'shows empty state' do + it 'shows empty state', :js do expect(page).to have_content(job.detailed_status(user).illustration[:title]) expect(page).to have_content('This job has not been triggered yet') expect(page).to have_content('This job depends on upstream jobs that need to succeed in order for this job to be triggered') end end - context 'Pending job' do + context 'Pending job', :js do let(:job) { create(:ci_build, :pending, pipeline: pipeline) } before do @@ -600,7 +600,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do end end - context 'without log' do + context 'without log', :js do let(:job) { create(:ci_build, :canceled, pipeline: pipeline) } before do @@ -615,7 +615,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do end end - context 'Skipped job' do + context 'Skipped job', :js do let(:job) { create(:ci_build, :skipped, pipeline: pipeline) } before do @@ -629,7 +629,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do end end - context 'when job is failed but has no trace' do + context 'when job is failed but has no trace', :js do let(:job) { create(:ci_build, :failed, pipeline: pipeline) } it 'renders empty state' do diff --git a/spec/javascripts/jobs/components/empty_state_spec.js b/spec/javascripts/jobs/components/empty_state_spec.js index 872cc1e3864..73488eaab9b 100644 --- a/spec/javascripts/jobs/components/empty_state_spec.js +++ b/spec/javascripts/jobs/components/empty_state_spec.js @@ -67,7 +67,7 @@ describe('Empty State', () => { content, action: { path: 'runner', - title: 'Check runner', + button_title: 'Check runner', method: 'post', }, }); diff --git a/spec/javascripts/jobs/components/job_app_spec.js b/spec/javascripts/jobs/components/job_app_spec.js index c31fa6f9887..e02eb9723fe 100644 --- a/spec/javascripts/jobs/components/job_app_spec.js +++ b/spec/javascripts/jobs/components/job_app_spec.js @@ -37,6 +37,7 @@ describe('Job App ', () => { available: false, }, tags: ['docker'], + has_trace: true, }; const props = { @@ -182,4 +183,142 @@ describe('Job App ', () => { expect(vm.$el.querySelector('.js-job-stuck')).toBeNull(); }); }); + + describe('environments block', () => { + it('renders environment block when job has environment', () => { + store.dispatch( + 'receiveJobSuccess', + Object.assign({}, job, { + deployment_status: { + environment: { + environment_path: '/path', + name: 'foo', + }, + }, + }), + ); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-environment')).not.toBeNull(); + }); + + it('does not render environment block when job has environment', () => { + store.dispatch('receiveJobSuccess', job); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-environment')).toBeNull(); + }); + }); + + describe('erased block', () => { + it('renders erased block when `erased` is true', () => { + store.dispatch( + 'receiveJobSuccess', + Object.assign({}, job, { + erased: true, + erased_by: { + username: 'root', + web_url: 'gitlab.com/root', + }, + erased_at: '2016-11-07T11:11:16.525Z', + }), + ); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-erased')).not.toBeNull(); + }); + + it('does not render erased block when `erased` is false', () => { + store.dispatch('receiveJobSuccess', Object.assign({}, job, { erased: false })); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-erased')).toBeNull(); + }); + }); + + describe('empty states block', () => { + it('renders empty state when job does not have trace and is not running', () => { + store.dispatch( + 'receiveJobSuccess', + Object.assign({}, job, { + has_trace: false, + status: { + group: 'pending', + icon: 'status_pending', + label: 'pending', + text: 'pending', + details_path: 'path', + illustration: { + image: 'path', + size: '340', + title: 'Empty State', + content: 'This is an empty state', + }, + action: { + button_title: 'Retry job', + method: 'post', + path: '/path', + }, + }, + }), + ); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-empty-state')).not.toBeNull(); + }); + + it('does not render empty state when job does not have trace but it is running', () => { + store.dispatch( + 'receiveJobSuccess', + Object.assign({}, job, { + has_trace: false, + status: { + group: 'running', + icon: 'status_running', + label: 'running', + text: 'running', + details_path: 'path', + }, + }), + ); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull(); + }); + + it('does not render empty state when job has trace but it is not running', () => { + store.dispatch('receiveJobSuccess', Object.assign({}, job, { has_trace: true })); + + vm = mountComponentWithStore(Component, { + props, + store, + }); + + expect(vm.$el.querySelector('.js-job-empty-state')).toBeNull(); + }); + }); }); diff --git a/spec/javascripts/jobs/store/getters_spec.js b/spec/javascripts/jobs/store/getters_spec.js index 63ef4135d83..160b2f4b34a 100644 --- a/spec/javascripts/jobs/store/getters_spec.js +++ b/spec/javascripts/jobs/store/getters_spec.js @@ -99,12 +99,14 @@ describe('Job Store Getters', () => { expect(getters.hasEnvironment(localState)).toEqual(false); }); }); + describe('with an empty object for `deployment_status`', () => { it('returns false', () => { localState.job.deployment_status = {}; expect(getters.hasEnvironment(localState)).toEqual(false); }); }); + describe('when `deployment_status` is defined and not empty', () => { it('returns true', () => { localState.job.deployment_status = { @@ -118,4 +120,94 @@ describe('Job Store Getters', () => { }); }); }); + + describe('hasTrace', () => { + describe('when has_trace is true', () => { + it('returns true', () => { + localState.job.has_trace = true; + localState.job.status = {}; + + expect(getters.hasTrace(localState)).toEqual(true); + }); + }); + + describe('when job is running', () => { + it('returns true', () => { + localState.job.has_trace = false; + localState.job.status = { group: 'running' }; + + expect(getters.hasTrace(localState)).toEqual(true); + }); + }); + + describe('when has_trace is false and job is not running', () => { + it('returns false', () => { + localState.job.has_trace = false; + localState.job.status = { group: 'pending' }; + + expect(getters.hasTrace(localState)).toEqual(false); + }); + }); + }); + + describe('emptyStateIllustration', () => { + describe('with defined illustration', () => { + it('returns the state illustration object', () => { + localState.job.status = { + illustration: { + path: 'foo', + }, + }; + + expect(getters.emptyStateIllustration(localState)).toEqual({ path: 'foo' }); + }); + }); + + describe('when illustration is not defined', () => { + it('returns an empty object', () => { + expect(getters.emptyStateIllustration(localState)).toEqual({}); + }); + }); + }); + + describe('isJobStuck', () => { + describe('when job is pending and runners are not available', () => { + it('returns true', () => { + localState.job.status = { + group: 'pending', + }; + localState.job.runners = { + available: false, + }; + + expect(getters.isJobStuck(localState)).toEqual(true); + }); + }); + + describe('when job is not pending', () => { + it('returns false', () => { + localState.job.status = { + group: 'running', + }; + localState.job.runners = { + available: false, + }; + + expect(getters.isJobStuck(localState)).toEqual(false); + }); + }); + + describe('when runners are available', () => { + it('returns false', () => { + localState.job.status = { + group: 'pending', + }; + localState.job.runners = { + available: true, + }; + + expect(getters.isJobStuck(localState)).toEqual(false); + }); + }); + }); }); -- cgit v1.2.1 From 819ecd5fb01ab64cacc31253c51984a7564949d7 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 2 Oct 2018 13:39:46 +0100 Subject: Fix N+1 for notification recipients in subscribers --- app/models/concerns/subscribable.rb | 8 ++-- .../notification_recipient_service_spec.rb | 47 ++++++++++++++++------ 2 files changed, 40 insertions(+), 15 deletions(-) diff --git a/app/models/concerns/subscribable.rb b/app/models/concerns/subscribable.rb index 1d0a61364b0..92a5c1112af 100644 --- a/app/models/concerns/subscribable.rb +++ b/app/models/concerns/subscribable.rb @@ -31,9 +31,11 @@ module Subscribable end def subscribers(project) - subscriptions_available(project) - .where(subscribed: true) - .map(&:user) + relation = subscriptions_available(project) + .where(subscribed: true) + .select(:user_id) + + User.where(id: relation) end def toggle_subscription(user, project = nil) diff --git a/spec/services/notification_recipient_service_spec.rb b/spec/services/notification_recipient_service_spec.rb index 14ba6b7bed2..9a24821860e 100644 --- a/spec/services/notification_recipient_service_spec.rb +++ b/spec/services/notification_recipient_service_spec.rb @@ -10,27 +10,50 @@ describe NotificationRecipientService do let(:issue) { create(:issue, project: project, assignees: [assignee]) } let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id) } - def create_watcher - watcher = create(:user) - create(:notification_setting, source: project, user: watcher, level: :watch) + context 'when there are multiple watchers' do + def create_watcher + watcher = create(:user) + create(:notification_setting, source: project, user: watcher, level: :watch) + + other_projects.each do |other_project| + create(:notification_setting, source: other_project, user: watcher, level: :watch) + end + end + + it 'avoids N+1 queries', :request_store do + create_watcher + + service.build_new_note_recipients(note) + + control_count = ActiveRecord::QueryRecorder.new do + service.build_new_note_recipients(note) + end - other_projects.each do |other_project| - create(:notification_setting, source: other_project, user: watcher, level: :watch) + create_watcher + + expect { service.build_new_note_recipients(note) }.not_to exceed_query_limit(control_count) end end - it 'avoids N+1 queries', :request_store do - create_watcher + context 'when there are multiple subscribers' do + def create_subscriber + subscriber = create(:user) + issue.subscriptions.create(user: subscriber, project: project, subscribed: true) + end - service.build_new_note_recipients(note) + it 'avoids N+1 queries' do + create_subscriber - control_count = ActiveRecord::QueryRecorder.new do service.build_new_note_recipients(note) - end - create_watcher + control_count = ActiveRecord::QueryRecorder.new do + service.build_new_note_recipients(note) + end - expect { service.build_new_note_recipients(note) }.not_to exceed_query_limit(control_count) + create_subscriber + + expect { service.build_new_note_recipients(note) }.not_to exceed_query_limit(control_count) + end end end end -- cgit v1.2.1 From 28e6af88aa16a33717dbdbe870a8423300aef042 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 2 Oct 2018 15:29:45 +0100 Subject: Fix N+1 for notification recipients on private projects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If we don't call #to_a, we're relying on the members already being loaded from elsewhere. Otherwise we'll do a separate query for each user: [1] pry(main)> Project.first.team.members.include?(User.first) Project Load (0.7ms) SELECT "projects".* FROM "projects" ORDER BY "projects"."id" ASC LIMIT 1 ↳ (pry):3 User Load (1.8ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1 ↳ (pry):3 User Exists (0.6ms) SELECT 1 AS one FROM "users" INNER JOIN "project_authorizations" ON "users"."id" = "project_authorizations"."user_id" WHERE "project_authorizations"."project_id" = $1 AND "users"."id" = $2 LIMIT 1 [["project_id", 1], ["id", 1]] ↳ (pry):3 => true [2] pry(main)> Project.first.team.members.to_a.include?(User.first) Project Load (12.8ms) SELECT "projects".* FROM "projects" ORDER BY "projects"."id" ASC LIMIT 1 ↳ (pry):1 User Load (9.6ms) SELECT "users".* FROM "users" INNER JOIN "project_authorizations" ON "users"."id" = "project_authorizations"."user_id" WHERE "project_authorizations"."project_id" = $1 [["project_id", 1]] ↳ (pry):1 User Load (0.6ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1 ↳ (pry):1 => true --- app/policies/project_policy.rb | 6 ++- .../notification_recipient_service_spec.rb | 44 +++++++++++----------- 2 files changed, 27 insertions(+), 23 deletions(-) diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index d0e84b1aa38..f2c246cd969 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -390,7 +390,11 @@ class ProjectPolicy < BasePolicy greedy_load_subject ||= !@user.persisted? if greedy_load_subject - project.team.members.include?(user) + # We want to load all the members with one query. Calling #include? on + # project.team.members will perform a separate query for each user, unless + # project.team.members was loaded before somewhere else. Calling #to_a + # ensures it's always loaded before checking for membership. + project.team.members.to_a.include?(user) else # otherwise we just make a specific query for # this particular user. diff --git a/spec/services/notification_recipient_service_spec.rb b/spec/services/notification_recipient_service_spec.rb index 9a24821860e..cea5ea125b9 100644 --- a/spec/services/notification_recipient_service_spec.rb +++ b/spec/services/notification_recipient_service_spec.rb @@ -10,18 +10,9 @@ describe NotificationRecipientService do let(:issue) { create(:issue, project: project, assignees: [assignee]) } let(:note) { create(:note_on_issue, noteable: issue, project_id: issue.project_id) } - context 'when there are multiple watchers' do - def create_watcher - watcher = create(:user) - create(:notification_setting, source: project, user: watcher, level: :watch) - - other_projects.each do |other_project| - create(:notification_setting, source: other_project, user: watcher, level: :watch) - end - end - + shared_examples 'no N+1 queries' do it 'avoids N+1 queries', :request_store do - create_watcher + create_user service.build_new_note_recipients(note) @@ -29,30 +20,39 @@ describe NotificationRecipientService do service.build_new_note_recipients(note) end - create_watcher + create_user expect { service.build_new_note_recipients(note) }.not_to exceed_query_limit(control_count) end end + context 'when there are multiple watchers' do + def create_user + watcher = create(:user) + create(:notification_setting, source: project, user: watcher, level: :watch) + + other_projects.each do |other_project| + create(:notification_setting, source: other_project, user: watcher, level: :watch) + end + end + + include_examples 'no N+1 queries' + end + context 'when there are multiple subscribers' do - def create_subscriber + def create_user subscriber = create(:user) issue.subscriptions.create(user: subscriber, project: project, subscribed: true) end - it 'avoids N+1 queries' do - create_subscriber + include_examples 'no N+1 queries' - service.build_new_note_recipients(note) - - control_count = ActiveRecord::QueryRecorder.new do - service.build_new_note_recipients(note) + context 'when the project is private' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) end - create_subscriber - - expect { service.build_new_note_recipients(note) }.not_to exceed_query_limit(control_count) + include_examples 'no N+1 queries' end end end -- cgit v1.2.1 From 0199c92cc9fd7722f2ec5796936dd35341df4ef5 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Tue, 2 Oct 2018 15:33:47 +0100 Subject: Add changelog entry --- .../47496-more-n-1s-in-calculating-notification-recipients.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/47496-more-n-1s-in-calculating-notification-recipients.yml diff --git a/changelogs/unreleased/47496-more-n-1s-in-calculating-notification-recipients.yml b/changelogs/unreleased/47496-more-n-1s-in-calculating-notification-recipients.yml new file mode 100644 index 00000000000..f70011ac827 --- /dev/null +++ b/changelogs/unreleased/47496-more-n-1s-in-calculating-notification-recipients.yml @@ -0,0 +1,5 @@ +--- +title: Reduce queries needed to compute notification recipients +merge_request: 22050 +author: +type: performance -- cgit v1.2.1 From 4b5e00a848bb55ea8494c61d4a4744a7ead86c79 Mon Sep 17 00:00:00 2001 From: Marcos Barrera Date: Thu, 4 Oct 2018 13:47:40 +0000 Subject: Resolve "Error while updating todo status: the button is stuck in the loading state" --- app/assets/javascripts/pages/dashboard/todos/index/todos.js | 12 +++++++++--- changelogs/unreleased/48222-fix-todos-status-button.yml | 6 ++++++ 2 files changed, 15 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/48222-fix-todos-status-button.yml diff --git a/app/assets/javascripts/pages/dashboard/todos/index/todos.js b/app/assets/javascripts/pages/dashboard/todos/index/todos.js index 9aa83ce6269..72f3f70b98f 100644 --- a/app/assets/javascripts/pages/dashboard/todos/index/todos.js +++ b/app/assets/javascripts/pages/dashboard/todos/index/todos.js @@ -79,10 +79,13 @@ export default class Todos { .then(({ data }) => { this.updateRowState(target); this.updateBadges(data); - }).catch(() => flash(__('Error updating todo status.'))); + }).catch(() => { + this.updateRowState(target, true); + return flash(__('Error updating todo status.')); + }); } - updateRowState(target) { + updateRowState(target, isInactive = false) { const row = target.closest('li'); const restoreBtn = row.querySelector('.js-undo-todo'); const doneBtn = row.querySelector('.js-done-todo'); @@ -91,7 +94,10 @@ export default class Todos { target.removeAttribute('disabled'); target.classList.remove('disabled'); - if (target === doneBtn) { + if (isInactive === true) { + restoreBtn.classList.add('hidden'); + doneBtn.classList.remove('hidden'); + } else if (target === doneBtn) { row.classList.add('done-reversible'); restoreBtn.classList.remove('hidden'); } else if (target === restoreBtn) { diff --git a/changelogs/unreleased/48222-fix-todos-status-button.yml b/changelogs/unreleased/48222-fix-todos-status-button.yml new file mode 100644 index 00000000000..2f7c79a07d0 --- /dev/null +++ b/changelogs/unreleased/48222-fix-todos-status-button.yml @@ -0,0 +1,6 @@ +--- +title: Fix the state of the Done button when there is an error in the GitLab Todos + section +merge_request: +author: marcos8896 +type: fixed -- cgit v1.2.1 From eb6e0f4b6f881e2bd7cd90822b4b7ff0952a56b0 Mon Sep 17 00:00:00 2001 From: Zsolt Kovari Date: Thu, 4 Oct 2018 13:55:57 +0000 Subject: Use proper Configure label instead of Configuration in issue_workflow.md --- doc/development/contributing/issue_workflow.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/development/contributing/issue_workflow.md b/doc/development/contributing/issue_workflow.md index fed29d37b26..2f06677bfec 100644 --- a/doc/development/contributing/issue_workflow.md +++ b/doc/development/contributing/issue_workflow.md @@ -60,7 +60,7 @@ people. The current team labels are: -- ~Configuration +- ~Configure - ~"CI/CD" - ~Create - ~Distribution -- cgit v1.2.1 From acfe7ec65c75f65f2f87bf3e8598c1725c9150f4 Mon Sep 17 00:00:00 2001 From: Alessio Caiazza Date: Thu, 4 Oct 2018 16:15:28 +0200 Subject: fix method name in stage spec --- spec/models/ci/stage_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/models/ci/stage_spec.rb b/spec/models/ci/stage_spec.rb index 060a1d95293..5076f7faeac 100644 --- a/spec/models/ci/stage_spec.rb +++ b/spec/models/ci/stage_spec.rb @@ -200,8 +200,8 @@ describe Ci::Stage, :models do end end - describe '#schedule' do - subject { stage.schedule } + describe '#delay' do + subject { stage.delay } let(:stage) { create(:ci_stage_entity, status: :created) } -- cgit v1.2.1 From 42a227e4397986a3adce82f786f87e9def487af9 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Thu, 4 Oct 2018 14:27:42 +0000 Subject: Fix markdown table border and background color --- app/assets/stylesheets/framework/mixins.scss | 10 +++++++--- changelogs/unreleased/52178-markdown-table-borders.yml | 5 +++++ 2 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/52178-markdown-table-borders.yml diff --git a/app/assets/stylesheets/framework/mixins.scss b/app/assets/stylesheets/framework/mixins.scss index 11597c7e85e..be41dbfc61f 100644 --- a/app/assets/stylesheets/framework/mixins.scss +++ b/app/assets/stylesheets/framework/mixins.scss @@ -20,20 +20,24 @@ display: inline-block; overflow-x: auto; border: 0; - border-color: $gray-100; + border-color: $gl-gray-100; @supports (width: fit-content) { display: block; width: fit-content; } + tbody { + background-color: $white-light; + } + tr { th { - border-bottom: solid 2px $gray-100; + border-bottom: solid 2px $gl-gray-100; } td { - border-color: $gray-100; + border-color: $gl-gray-100; } } } diff --git a/changelogs/unreleased/52178-markdown-table-borders.yml b/changelogs/unreleased/52178-markdown-table-borders.yml new file mode 100644 index 00000000000..965f21f2a97 --- /dev/null +++ b/changelogs/unreleased/52178-markdown-table-borders.yml @@ -0,0 +1,5 @@ +--- +title: Add borders and white background to markdown tables +merge_request: +author: +type: fixed -- cgit v1.2.1 From 7cb2755617a2f29498f4523b8915bc77abcae4df Mon Sep 17 00:00:00 2001 From: Mario de la Ossa Date: Tue, 2 Oct 2018 20:56:51 -0600 Subject: Banzai project ref- share context more aggresively Changes `Banzai::CrossProjectReference#parent_from_ref` to return the project in the context if the project's `full_path` matches the ref we're looking for, as it makes no sense to go to the database to find a Project we already have loaded. --- app/finders/labels_finder.rb | 4 ++-- .../unreleased/mao-48221-issues_show_sql_count.yml | 5 +++++ lib/banzai/cross_project_reference.rb | 1 + lib/banzai/filter/label_reference_filter.rb | 2 +- spec/controllers/projects/issues_controller_spec.rb | 12 ++++++++++++ spec/lib/banzai/cross_project_reference_spec.rb | 19 ++++++++++++------- .../filter/commit_range_reference_filter_spec.rb | 1 + 7 files changed, 34 insertions(+), 10 deletions(-) create mode 100644 changelogs/unreleased/mao-48221-issues_show_sql_count.yml diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index 08fc2968e77..0293101f3c1 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -116,7 +116,7 @@ class LabelsFinder < UnionFinder end def project? - params[:project_id].present? + params[:project].present? || params[:project_id].present? end def projects? @@ -139,7 +139,7 @@ class LabelsFinder < UnionFinder return @project if defined?(@project) if project? - @project = Project.find(params[:project_id]) + @project = params[:project] || Project.find(params[:project_id]) @project = nil unless authorized_to_read_labels?(@project) else @project = nil diff --git a/changelogs/unreleased/mao-48221-issues_show_sql_count.yml b/changelogs/unreleased/mao-48221-issues_show_sql_count.yml new file mode 100644 index 00000000000..d634d15946e --- /dev/null +++ b/changelogs/unreleased/mao-48221-issues_show_sql_count.yml @@ -0,0 +1,5 @@ +--- +title: Banzai label ref finder - minimize SQL calls by sharing context more aggresively +merge_request: 22070 +author: +type: performance diff --git a/lib/banzai/cross_project_reference.rb b/lib/banzai/cross_project_reference.rb index 3f1e95d4cc0..43f913a8859 100644 --- a/lib/banzai/cross_project_reference.rb +++ b/lib/banzai/cross_project_reference.rb @@ -13,6 +13,7 @@ module Banzai # Returns a Project, or nil if the reference can't be found def parent_from_ref(ref) return context[:project] || context[:group] unless ref + return context[:project] if context[:project]&.full_path == ref Project.find_by_full_path(ref) end diff --git a/lib/banzai/filter/label_reference_filter.rb b/lib/banzai/filter/label_reference_filter.rb index b92e9e55bb9..04ec38209c7 100644 --- a/lib/banzai/filter/label_reference_filter.rb +++ b/lib/banzai/filter/label_reference_filter.rb @@ -48,7 +48,7 @@ module Banzai include_ancestor_groups: true, only_group_labels: true } else - { project_id: parent.id, + { project: parent, include_ancestor_groups: true } end diff --git a/spec/controllers/projects/issues_controller_spec.rb b/spec/controllers/projects/issues_controller_spec.rb index 5b347b1109a..44419ed6a2a 100644 --- a/spec/controllers/projects/issues_controller_spec.rb +++ b/spec/controllers/projects/issues_controller_spec.rb @@ -637,6 +637,18 @@ describe Projects::IssuesController do project_id: project, id: id end + + it 'avoids (most) N+1s loading labels' do + label = create(:label, project: project).to_reference + labels = create_list(:label, 10, project: project).map(&:to_reference) + issue = create(:issue, project: project, description: 'Test issue') + + control_count = ActiveRecord::QueryRecorder.new { issue.update(description: [issue.description, label].join(' ')) }.count + + # Follow-up to get rid of this `2 * label.count` requirement: https://gitlab.com/gitlab-org/gitlab-ce/issues/52230 + expect { issue.update(description: [issue.description, labels].join(' ')) } + .not_to exceed_query_limit(control_count + 2 * labels.count) + end end describe 'GET #realtime_changes' do diff --git a/spec/lib/banzai/cross_project_reference_spec.rb b/spec/lib/banzai/cross_project_reference_spec.rb index aadfe7637dd..ba995e16be7 100644 --- a/spec/lib/banzai/cross_project_reference_spec.rb +++ b/spec/lib/banzai/cross_project_reference_spec.rb @@ -1,16 +1,21 @@ require 'spec_helper' describe Banzai::CrossProjectReference do - include described_class + let(:including_class) { Class.new.include(described_class).new } + + before do + allow(including_class).to receive(:context).and_return({}) + allow(including_class).to receive(:parent_from_ref).and_call_original + end describe '#parent_from_ref' do context 'when no project was referenced' do it 'returns the project from context' do project = double - allow(self).to receive(:context).and_return({ project: project }) + allow(including_class).to receive(:context).and_return({ project: project }) - expect(parent_from_ref(nil)).to eq project + expect(including_class.parent_from_ref(nil)).to eq project end end @@ -18,15 +23,15 @@ describe Banzai::CrossProjectReference do it 'returns the group from context' do group = double - allow(self).to receive(:context).and_return({ group: group }) + allow(including_class).to receive(:context).and_return({ group: group }) - expect(parent_from_ref(nil)).to eq group + expect(including_class.parent_from_ref(nil)).to eq group end end context 'when referenced project does not exist' do it 'returns nil' do - expect(parent_from_ref('invalid/reference')).to be_nil + expect(including_class.parent_from_ref('invalid/reference')).to be_nil end end @@ -37,7 +42,7 @@ describe Banzai::CrossProjectReference do expect(Project).to receive(:find_by_full_path) .with('cross/reference').and_return(project2) - expect(parent_from_ref('cross/reference')).to eq project2 + expect(including_class.parent_from_ref('cross/reference')).to eq project2 end end end diff --git a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb index e1af5a15371..cbff2fdab14 100644 --- a/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb +++ b/spec/lib/banzai/filter/commit_range_reference_filter_spec.rb @@ -60,6 +60,7 @@ describe Banzai::Filter::CommitRangeReferenceFilter do exp = act = "See #{commit1.id.reverse}...#{commit2.id}" allow(project.repository).to receive(:commit).with(commit1.id.reverse) + allow(project.repository).to receive(:commit).with(commit2.id) expect(reference_filter(act).to_html).to eq exp end -- cgit v1.2.1 From 6aec81a0280570f46282f88237b45061d28dcb39 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Thu, 4 Oct 2018 10:21:23 -0700 Subject: Bump Gitaly to v0.124.0 Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/52235 --- GITALY_SERVER_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index 3ba7bd5ba83..ee476f3ae84 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.123.0 +0.124.0 -- cgit v1.2.1 From f8d11e8216c67b71f1cb82c85adf8f82412909be Mon Sep 17 00:00:00 2001 From: Jacopo Date: Thu, 4 Oct 2018 19:11:40 +0200 Subject: Trim whitespace when inviting a new user by email --- app/assets/javascripts/users_select.js | 2 +- changelogs/unreleased/52194-trim-extra-whitespace-invite-member.yml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/52194-trim-extra-whitespace-invite-member.yml diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index e19bbbacf4d..b9dfa22dd49 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -592,7 +592,7 @@ function UsersSelect(currentUser, els, options = {}) { if (showEmailUser && data.results.length === 0 && query.term.match(/^[^@]+@[^@]+$/)) { var trimmed = query.term.trim(); emailUser = { - name: "Invite \"" + query.term + "\" by email", + name: "Invite \"" + trimmed + "\" by email", username: trimmed, id: trimmed, invite: true diff --git a/changelogs/unreleased/52194-trim-extra-whitespace-invite-member.yml b/changelogs/unreleased/52194-trim-extra-whitespace-invite-member.yml new file mode 100644 index 00000000000..d96c2bc7acd --- /dev/null +++ b/changelogs/unreleased/52194-trim-extra-whitespace-invite-member.yml @@ -0,0 +1,5 @@ +--- +title: Trim whitespace when inviting a new user by email +merge_request: 22119 +author: Jacopo Beschi @jacopo-beschi +type: fixed -- cgit v1.2.1 From 7ee8771c5cdedcffadee081358e1239ba71aede0 Mon Sep 17 00:00:00 2001 From: Brett Walker Date: Wed, 3 Oct 2018 11:39:42 -0500 Subject: Don't build project services unneccesarily --- .../projects/merge_requests_controller.rb | 4 +-- app/models/project.rb | 35 +++++++++++----------- .../projects/merge_requests_controller_spec.rb | 28 +++++++++++------ spec/models/project_spec.rb | 14 +++++++++ 4 files changed, 52 insertions(+), 29 deletions(-) diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index d691744d72a..6a5da9b8292 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -207,7 +207,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo environments = begin @merge_request.environments_for(current_user).map do |environment| - project = environment.project + project = environment.project deployment = environment.first_deployment_for(@merge_request.diff_head_sha) stop_url = @@ -217,7 +217,7 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo metrics_url = if can?(current_user, :read_environment, environment) && environment.has_metrics? - metrics_project_environment_deployment_path(environment.project, environment, deployment) + metrics_project_environment_deployment_path(project, environment, deployment) end metrics_monitoring_url = diff --git a/app/models/project.rb b/app/models/project.rb index 59f088156c7..6a9397a36d7 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1082,26 +1082,10 @@ class Project < ActiveRecord::Base end def find_or_initialize_services(exceptions: []) - services_templates = Service.where(template: true) - available_services_names = Service.available_services_names - exceptions available_services = available_services_names.map do |service_name| - service = find_service(services, service_name) - - if service - service - else - # We should check if template for the service exists - template = find_service(services_templates, service_name) - - if template.nil? - # If no template, we should create an instance. Ex `build_gitlab_ci_service` - public_send("build_#{service_name}_service") # rubocop:disable GitlabSecurity/PublicSend - else - Service.build_from_template(id, template) - end - end + find_or_initialize_service(service_name) end available_services.reject do |service| @@ -1114,7 +1098,18 @@ class Project < ActiveRecord::Base end def find_or_initialize_service(name) - find_or_initialize_services.find { |service| service.to_param == name } + service = find_service(services, name) + return service if service + + # We should check if template for the service exists + template = find_service(services_templates, name) + + if template + Service.build_from_template(id, template) + else + # If no template, we should create an instance. Ex `build_gitlab_ci_service` + public_send("build_#{name}_service") # rubocop:disable GitlabSecurity/PublicSend + end end # rubocop: disable CodeReuse/ServiceClass @@ -2277,4 +2272,8 @@ class Project < ActiveRecord::Base check_access.call end end + + def services_templates + @services_templates ||= Service.where(template: true) + end end diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 7446e0650f7..41c92a392aa 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -763,25 +763,35 @@ describe Projects::MergeRequestsController do describe 'GET ci_environments_status' do context 'the environment is from a forked project' do - let!(:forked) { fork_project(project, user, repository: true) } - let!(:environment) { create(:environment, project: forked) } - let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') } - let(:admin) { create(:admin) } + let!(:forked) { fork_project(project, user, repository: true) } + let!(:environment) { create(:environment, project: forked) } + let!(:deployment) { create(:deployment, environment: environment, sha: forked.commit.id, ref: 'master') } + let(:admin) { create(:admin) } let(:merge_request) do create(:merge_request, source_project: forked, target_project: project) end - before do + it 'links to the environment on that project' do + get_ci_environments_status + + expect(json_response.first['url']).to match /#{forked.full_path}/ + end + + # we're trying to reduce the overall number of queries for this method. + # set a hard limit for now. https://gitlab.com/gitlab-org/gitlab-ce/issues/43109 + it 'keeps queries in check' do + control_count = ActiveRecord::QueryRecorder.new { get_ci_environments_status }.count + + expect(control_count).to be <= 137 + end + + def get_ci_environments_status get :ci_environments_status, namespace_id: merge_request.project.namespace.to_param, project_id: merge_request.project, id: merge_request.iid, format: 'json' end - - it 'links to the environment on that project' do - expect(json_response.first['url']).to match /#{forked.full_path}/ - end end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 8b71919544e..ff259dc12b3 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -4037,6 +4037,20 @@ describe Project do expect(result).to be_empty end end + + describe "#find_or_initialize_service" do + subject { build(:project) } + + it 'avoids N+1 database queries' do + allow(Service).to receive(:available_services_names).and_return(%w(prometheus pushover)) + + control_count = ActiveRecord::QueryRecorder.new { project.find_or_initialize_service('prometheus') }.count + + allow(Service).to receive(:available_services_names).and_call_original + + expect { project.find_or_initialize_service('prometheus') }.not_to exceed_query_limit(control_count) + end + end end def rugged_config -- cgit v1.2.1 From 0ae48e068794a732f728de76ffbf3f95e702ba9a Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 3 Oct 2018 17:45:39 -0300 Subject: Move issue related_branches to service Moves the related_branches method from Issue model to RelatedBranchesService --- app/controllers/projects/issues_controller.rb | 2 +- app/models/issue.rb | 18 ---------- app/services/issues/related_branches_service.rb | 26 +++++++++++++++ spec/models/issue_spec.rb | 39 ---------------------- .../issues/related_branches_service_spec.rb | 39 ++++++++++++++++++++++ 5 files changed, 66 insertions(+), 58 deletions(-) create mode 100644 app/services/issues/related_branches_service.rb create mode 100644 spec/services/issues/related_branches_service_spec.rb diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index 4e859de6fde..b06a6f3bb0d 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -127,7 +127,7 @@ class Projects::IssuesController < Projects::ApplicationController end def related_branches - @related_branches = @issue.related_branches(current_user) + @related_branches = Issues::RelatedBranchesService.new(project, current_user).execute(issue) respond_to do |format| format.json do diff --git a/app/models/issue.rb b/app/models/issue.rb index d13fbcf002c..4ace5d3ab97 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -170,24 +170,6 @@ class Issue < ActiveRecord::Base "#{project.to_reference(from, full: full)}#{reference}" end - # All branches containing the current issue's ID, except for - # those with a merge request open referencing the current issue. - # rubocop: disable CodeReuse/ServiceClass - def related_branches(current_user) - branches_with_iid = project.repository.branch_names.select do |branch| - branch =~ /\A#{iid}-(?!\d+-stable)/i - end - - branches_with_merge_request = - Issues::ReferencedMergeRequestsService - .new(project, current_user) - .referenced_merge_requests(self) - .map(&:source_branch) - - branches_with_iid - branches_with_merge_request - end - # rubocop: enable CodeReuse/ServiceClass - def suggested_branch_name return to_branch_name unless project.repository.branch_exists?(to_branch_name) diff --git a/app/services/issues/related_branches_service.rb b/app/services/issues/related_branches_service.rb new file mode 100644 index 00000000000..76af482b7ac --- /dev/null +++ b/app/services/issues/related_branches_service.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +# This service fetches all branches containing the current issue's ID, except for +# those with a merge request open referencing the current issue. +module Issues + class RelatedBranchesService < Issues::BaseService + def execute(issue) + branches_with_iid_of(issue) - branches_with_merge_request_for(issue) + end + + private + + def branches_with_merge_request_for(issue) + Issues::ReferencedMergeRequestsService + .new(project, current_user) + .referenced_merge_requests(issue) + .map(&:source_branch) + end + + def branches_with_iid_of(issue) + project.repository.branch_names.select do |branch| + branch =~ /\A#{issue.iid}-(?!\d+-stable)/i + end + end + end +end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 19bc2713ef5..6f900a60213 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -268,45 +268,6 @@ describe Issue do end end - describe '#related_branches' do - let(:user) { create(:admin) } - - before do - allow(subject.project.repository).to receive(:branch_names) - .and_return(["mpempe", "#{subject.iid}mepmep", subject.to_branch_name, "#{subject.iid}-branch"]) - - # Without this stub, the `create(:merge_request)` above fails because it can't find - # the source branch. This seems like a reasonable compromise, in comparison with - # setting up a full repo here. - allow_any_instance_of(MergeRequest).to receive(:create_merge_request_diff) - end - - it "selects the right branches when there are no referenced merge requests" do - expect(subject.related_branches(user)).to eq([subject.to_branch_name, "#{subject.iid}-branch"]) - end - - it "selects the right branches when there is a referenced merge request" do - merge_request = create(:merge_request, { description: "Closes ##{subject.iid}", - source_project: subject.project, - source_branch: "#{subject.iid}-branch" }) - merge_request.create_cross_references!(user) - - referenced_merge_requests = Issues::ReferencedMergeRequestsService - .new(subject.project, user) - .referenced_merge_requests(subject) - - expect(referenced_merge_requests).not_to be_empty - expect(subject.related_branches(user)).to eq([subject.to_branch_name]) - end - - it 'excludes stable branches from the related branches' do - allow(subject.project.repository).to receive(:branch_names) - .and_return(["#{subject.iid}-0-stable"]) - - expect(subject.related_branches(user)).to eq [] - end - end - describe '#suggested_branch_name' do let(:repository) { double } diff --git a/spec/services/issues/related_branches_service_spec.rb b/spec/services/issues/related_branches_service_spec.rb new file mode 100644 index 00000000000..c2e1eba6a63 --- /dev/null +++ b/spec/services/issues/related_branches_service_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe Issues::RelatedBranchesService do + let(:user) { create(:admin) } + let(:issue) { create(:issue) } + + subject { described_class.new(issue.project, user) } + + describe '#execute' do + before do + allow(issue.project.repository).to receive(:branch_names).and_return(["mpempe", "#{issue.iid}mepmep", issue.to_branch_name, "#{issue.iid}-branch"]) + end + + it "selects the right branches when there are no referenced merge requests" do + expect(subject.execute(issue)).to eq([issue.to_branch_name, "#{issue.iid}-branch"]) + end + + it "selects the right branches when there is a referenced merge request" do + merge_request = create(:merge_request, { description: "Closes ##{issue.iid}", + source_project: issue.project, + source_branch: "#{issue.iid}-branch" }) + merge_request.create_cross_references!(user) + + referenced_merge_requests = Issues::ReferencedMergeRequestsService + .new(issue.project, user) + .referenced_merge_requests(issue) + + expect(referenced_merge_requests).not_to be_empty + expect(subject.execute(issue)).to eq([issue.to_branch_name]) + end + + it 'excludes stable branches from the related branches' do + allow(issue.project.repository).to receive(:branch_names) + .and_return(["#{issue.iid}-0-stable"]) + + expect(subject.execute(issue)).to eq [] + end + end +end -- cgit v1.2.1 From 939467885851eb0f3fc5bcbaab6cb585bb85710d Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 21 Sep 2018 15:43:24 -0500 Subject: Add ProjectFeature check for feature flag This will allow an explicitly-disabled feature flag to override a feature being available for a project. As an extreme example, we could quickly disable issues across all projects at runtime by running `Feature.disable(:issues)`. --- app/models/project_feature.rb | 3 +++ spec/models/project_feature_spec.rb | 16 ++++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb index 4a0324e8b5c..754c2461d23 100644 --- a/app/models/project_feature.rb +++ b/app/models/project_feature.rb @@ -55,6 +55,9 @@ class ProjectFeature < ActiveRecord::Base default_value_for :repository_access_level, value: ENABLED, allows_nil: false def feature_available?(feature, user) + # This feature might not be behind a feature flag at all, so default to true + return false unless ::Feature.enabled?(feature, user, default_enabled: true) + get_permission(user, access_level(feature)) end diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb index cd7f77024da..2a193864e46 100644 --- a/spec/models/project_feature_spec.rb +++ b/spec/models/project_feature_spec.rb @@ -73,6 +73,22 @@ describe ProjectFeature do end end end + + context 'when feature is disabled by a feature flag' do + it 'returns false' do + stub_feature_flags(issues: false) + + expect(project.feature_available?(:issues, user)).to eq(false) + end + end + + context 'when feature is enabled by a feature flag' do + it 'returns true' do + stub_feature_flags(issues: true) + + expect(project.feature_available?(:issues, user)).to eq(true) + end + end end context 'repository related features' do -- cgit v1.2.1 From cf0a61c20229f4415991ea55262ba78275320daf Mon Sep 17 00:00:00 2001 From: Jasper Maes Date: Thu, 4 Oct 2018 21:46:03 +0200 Subject: Rails5: fix artifacts controller download spec Rails5 has params[:file_type] as '' if file_type is included as nil in the request --- .../unreleased/rails5-fix-artifacts-controller-spec.yml | 6 ++++++ spec/controllers/projects/artifacts_controller_spec.rb | 14 ++++++++------ 2 files changed, 14 insertions(+), 6 deletions(-) create mode 100644 changelogs/unreleased/rails5-fix-artifacts-controller-spec.yml diff --git a/changelogs/unreleased/rails5-fix-artifacts-controller-spec.yml b/changelogs/unreleased/rails5-fix-artifacts-controller-spec.yml new file mode 100644 index 00000000000..3a399bb836e --- /dev/null +++ b/changelogs/unreleased/rails5-fix-artifacts-controller-spec.yml @@ -0,0 +1,6 @@ +--- +title: 'Rails5: fix artifacts controller download spec Rails5 has params[:file_type] + as '''' if file_type is included as nil in the request' +merge_request: 22123 +author: Jasper Maes +type: other diff --git a/spec/controllers/projects/artifacts_controller_spec.rb b/spec/controllers/projects/artifacts_controller_spec.rb index 436f4525093..6091185e252 100644 --- a/spec/controllers/projects/artifacts_controller_spec.rb +++ b/spec/controllers/projects/artifacts_controller_spec.rb @@ -19,15 +19,17 @@ describe Projects::ArtifactsController do end describe 'GET download' do - subject { get :download, namespace_id: project.namespace, project_id: project, job_id: job, file_type: file_type } + def download_artifact(extra_params = {}) + params = { namespace_id: project.namespace, project_id: project, job_id: job }.merge(extra_params) - context 'when no file type is supplied' do - let(:file_type) { nil } + get :download, params + end + context 'when no file type is supplied' do it 'sends the artifacts file' do expect(controller).to receive(:send_file).with(job.artifacts_file.path, hash_including(disposition: 'attachment')).and_call_original - subject + download_artifact end end @@ -36,7 +38,7 @@ describe Projects::ArtifactsController do let(:file_type) { 'invalid' } it 'returns 404' do - subject + download_artifact(file_type: file_type) expect(response).to have_gitlab_http_status(404) end @@ -52,7 +54,7 @@ describe Projects::ArtifactsController do it 'sends the codequality report' do expect(controller).to receive(:send_file).with(job.job_artifacts_codequality.file.path, hash_including(disposition: 'attachment')).and_call_original - subject + download_artifact(file_type: file_type) end end end -- cgit v1.2.1 From b6cf8b816856c170dbb457d3deb111d493131acb Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Mon, 1 Oct 2018 23:50:48 -0300 Subject: Add css class to Admin > Project > show page --- app/views/admin/projects/show.html.haml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index ccba1c461fc..32f6eefc16a 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -1,6 +1,8 @@ - add_to_breadcrumbs "Projects", admin_projects_path - breadcrumb_title @project.full_name - page_title @project.full_name, "Projects" +- @content_class = "admin-projects" + %h3.page-title Project: #{@project.full_name} = link_to edit_project_path(@project), class: "btn btn-nr float-right" do -- cgit v1.2.1 From d7fa6679cbf01b934c7080b515579c233e5ddad7 Mon Sep 17 00:00:00 2001 From: Mario de la Ossa Date: Thu, 4 Oct 2018 15:45:00 -0600 Subject: Backport CE changes from draft_notes addition in EE --- app/models/concerns/diff_positionable_note.rb | 81 +++++++++++++++++++++++++++ app/models/diff_note.rb | 68 +--------------------- 2 files changed, 82 insertions(+), 67 deletions(-) create mode 100644 app/models/concerns/diff_positionable_note.rb diff --git a/app/models/concerns/diff_positionable_note.rb b/app/models/concerns/diff_positionable_note.rb new file mode 100644 index 00000000000..b61bf29e6ad --- /dev/null +++ b/app/models/concerns/diff_positionable_note.rb @@ -0,0 +1,81 @@ +# frozen_string_literal: true +module DiffPositionableNote + extend ActiveSupport::Concern + + included do + before_validation :set_original_position, on: :create + before_validation :update_position, on: :create, if: :on_text? + + serialize :original_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize + serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize + serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize + end + + %i(original_position position change_position).each do |meth| + define_method "#{meth}=" do |new_position| + if new_position.is_a?(String) + new_position = JSON.parse(new_position) rescue nil + end + + if new_position.is_a?(Hash) + new_position = new_position.with_indifferent_access + new_position = Gitlab::Diff::Position.new(new_position) + end + + return if new_position == read_attribute(meth) + + super(new_position) + end + end + + def on_text? + position&.position_type == "text" + end + + def on_image? + position&.position_type == "image" + end + + def supported? + for_commit? || self.noteable.has_complete_diff_refs? + end + + def active?(diff_refs = nil) + return false unless supported? + return true if for_commit? + + diff_refs ||= noteable.diff_refs + + self.position.diff_refs == diff_refs + end + + def set_original_position + return unless position + + self.original_position = self.position.dup unless self.original_position&.complete? + end + + def update_position + return unless supported? + return if for_commit? + + return if active? + return unless position + + tracer = Gitlab::Diff::PositionTracer.new( + project: self.project, + old_diff_refs: self.position.diff_refs, + new_diff_refs: self.noteable.diff_refs, + paths: self.position.paths + ) + + result = tracer.trace(self.position) + return unless result + + if result[:outdated] + self.change_position = result[:position] + else + self.position = result[:position] + end + end +end diff --git a/app/models/diff_note.rb b/app/models/diff_note.rb index 047d353b4b5..95694377fe3 100644 --- a/app/models/diff_note.rb +++ b/app/models/diff_note.rb @@ -5,14 +5,11 @@ # A note of this type can be resolvable. class DiffNote < Note include NoteOnDiff + include DiffPositionableNote include Gitlab::Utils::StrongMemoize NOTEABLE_TYPES = %w(MergeRequest Commit).freeze - serialize :original_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize - serialize :position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize - serialize :change_position, Gitlab::Diff::Position # rubocop:disable Cop/ActiveRecordSerialize - validates :original_position, presence: true validates :position, presence: true validates :line_code, presence: true, line_code: true, if: :on_text? @@ -21,8 +18,6 @@ class DiffNote < Note validate :verify_supported validate :diff_refs_match_commit, if: :for_commit? - before_validation :set_original_position, on: :create - before_validation :update_position, on: :create, if: :on_text? before_validation :set_line_code, if: :on_text? after_save :keep_around_commits after_commit :create_diff_file, on: :create @@ -31,31 +26,6 @@ class DiffNote < Note DiffDiscussion end - %i(original_position position change_position).each do |meth| - define_method "#{meth}=" do |new_position| - if new_position.is_a?(String) - new_position = JSON.parse(new_position) rescue nil - end - - if new_position.is_a?(Hash) - new_position = new_position.with_indifferent_access - new_position = Gitlab::Diff::Position.new(new_position) - end - - return if new_position == read_attribute(meth) - - super(new_position) - end - end - - def on_text? - position.position_type == "text" - end - - def on_image? - position.position_type == "image" - end - def create_diff_file return unless should_create_diff_file? @@ -87,15 +57,6 @@ class DiffNote < Note self.diff_file.line_code(self.diff_line) end - def active?(diff_refs = nil) - return false unless supported? - return true if for_commit? - - diff_refs ||= noteable.diff_refs - - self.position.diff_refs == diff_refs - end - def created_at_diff?(diff_refs) return false unless supported? return true if for_commit? @@ -141,37 +102,10 @@ class DiffNote < Note for_commit? || self.noteable.has_complete_diff_refs? end - def set_original_position - self.original_position = self.position.dup unless self.original_position&.complete? - end - def set_line_code self.line_code = self.position.line_code(self.project.repository) end - def update_position - return unless supported? - return if for_commit? - - return if active? - - tracer = Gitlab::Diff::PositionTracer.new( - project: self.project, - old_diff_refs: self.position.diff_refs, - new_diff_refs: self.noteable.diff_refs, - paths: self.position.paths - ) - - result = tracer.trace(self.position) - return unless result - - if result[:outdated] - self.change_position = result[:position] - else - self.position = result[:position] - end - end - def verify_supported return if supported? -- cgit v1.2.1 From ca665d01e6a7f94fe75ef537577d3aecb2984d17 Mon Sep 17 00:00:00 2001 From: Marc Schwede Date: Thu, 4 Oct 2018 22:04:49 +0000 Subject: Resolve "2FA mobile options should be rephrased" --- app/views/profiles/two_factor_auths/_codes.html.haml | 2 +- app/views/profiles/two_factor_auths/show.html.haml | 6 +++--- .../40140-2FA-mobile-options-should-be-rephrased.yml | 5 +++++ doc/user/profile/account/two_factor_authentication.md | 16 ++++++++-------- spec/features/u2f_spec.rb | 4 ++-- 5 files changed, 19 insertions(+), 14 deletions(-) create mode 100644 changelogs/unreleased/40140-2FA-mobile-options-should-be-rephrased.yml diff --git a/app/views/profiles/two_factor_auths/_codes.html.haml b/app/views/profiles/two_factor_auths/_codes.html.haml index fb4fff12027..759d39cf5f5 100644 --- a/app/views/profiles/two_factor_auths/_codes.html.haml +++ b/app/views/profiles/two_factor_auths/_codes.html.haml @@ -1,5 +1,5 @@ %p.slead - Should you ever lose your phone, each of these recovery codes can be used one + Should you ever lose your phone or access to your one time password secret, each of these recovery codes can be used one time each to regain access to your account. Please save them in a safe place, or you %b will lose access to your account. diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index cd10b8758f6..94ec0cc5db8 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -6,13 +6,13 @@ .row.prepend-top-default .col-lg-4 %h4.prepend-top-0 - Register Two-Factor Authentication App + Register Two-Factor Authenticator %p - Use an app on your mobile device to enable two-factor authentication (2FA). + Use an one time password authenticator on your mobile device or computer to enable two-factor authentication (2FA). .col-lg-8 - if current_user.two_factor_otp_enabled? %p - You've already enabled two-factor authentication using mobile authenticator applications. In order to register a different device, you must first disable two-factor authentication. + You've already enabled two-factor authentication using one time password authenticators. In order to register a different device, you must first disable two-factor authentication. %p If you lose your recovery codes you can generate new ones, invalidating all previous codes. %div diff --git a/changelogs/unreleased/40140-2FA-mobile-options-should-be-rephrased.yml b/changelogs/unreleased/40140-2FA-mobile-options-should-be-rephrased.yml new file mode 100644 index 00000000000..8131e2ff54f --- /dev/null +++ b/changelogs/unreleased/40140-2FA-mobile-options-should-be-rephrased.yml @@ -0,0 +1,5 @@ +--- +title: Rephrase 2FA and TOTP documentation and view +merge_request: 21998 +author: Marc Schwede +type: other diff --git a/doc/user/profile/account/two_factor_authentication.md b/doc/user/profile/account/two_factor_authentication.md index e5411662511..bc6ecdf4f32 100644 --- a/doc/user/profile/account/two_factor_authentication.md +++ b/doc/user/profile/account/two_factor_authentication.md @@ -2,18 +2,18 @@ Two-factor Authentication (2FA) provides an additional level of security to your GitLab account. Once enabled, in addition to supplying your username and -password to login, you'll be prompted for a code generated by an application on -your phone. +password to login, you'll be prompted for a code generated by your one time password +authenticator. For example, a password manager on one of your devices. By enabling 2FA, the only way someone other than you can log into your account -is to know your username and password *and* have access to your phone. +is to know your username and password *and* have access to your one time password secret. ## Overview > **Note:** When you enable 2FA, don't forget to back up your recovery codes. -In addition to a phone application, GitLab supports U2F (universal 2nd factor) devices as +In addition to one time authenticators (TOTP), GitLab supports U2F (universal 2nd factor) devices as the second factor of authentication. Once enabled, in addition to supplying your username and password to login, you'll be prompted to activate your U2F device (usually by pressing a button on it), and it will perform secure authentication on your behalf. @@ -24,10 +24,10 @@ from other browsers. ## Enabling 2FA -There are two ways to enable two-factor authentication: via a mobile application +There are two ways to enable two-factor authentication: via a one time password authenticator or a U2F device. -### Enable 2FA via mobile application +### Enable 2FA via one time password authenticator **In GitLab:** @@ -82,7 +82,7 @@ Click on **Register U2F Device** to complete the process. > **Note:** Recovery codes are not generated for U2F devices. -Should you ever lose access to your phone, you can use one of the ten provided +Should you ever lose access to your one time password authenticator, you can use one of the ten provided backup codes to login to your account. We suggest copying or printing them for storage in a safe place. **Each code can be used only once** to log in to your account. @@ -98,7 +98,7 @@ be presented with a second prompt, depending on which type of 2FA you've enabled ### Log in via mobile application -Enter the pin from your phone's application or a recovery code to log in. +Enter the pin from your one time password authenticator's application or a recovery code to log in. ![Two-Factor Authentication on sign in via OTP](img/2fa_auth.png) diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb index f1192f48b86..ae9b65d1a39 100644 --- a/spec/features/u2f_spec.rb +++ b/spec/features/u2f_spec.rb @@ -42,7 +42,7 @@ describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do it 'allows registering a new device with a name' do visit profile_account_path manage_two_factor_authentication - expect(page).to have_content("You've already enabled two-factor authentication using mobile") + expect(page).to have_content("You've already enabled two-factor authentication using one time password authenticators") u2f_device = register_u2f_device @@ -70,7 +70,7 @@ describe 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do it 'allows deleting a device' do visit profile_account_path manage_two_factor_authentication - expect(page).to have_content("You've already enabled two-factor authentication using mobile") + expect(page).to have_content("You've already enabled two-factor authentication using one time password authenticators") first_u2f_device = register_u2f_device second_u2f_device = register_u2f_device(name: 'My other device') -- cgit v1.2.1 From 2062560ef9480d6fa07a95d9ef1a7f6fab5317f5 Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Wed, 3 Oct 2018 23:04:18 +1300 Subject: Use tiller directly for Auto DevOps This saves a external network call to fetch a helm plugin. The cost is a few lines of shell script --- lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml index a96595b33a5..7ab814a0ed8 100644 --- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @@ -689,9 +689,6 @@ rollout 100%: helm version --client tiller -version - helm init --client-only - helm plugin install https://github.com/adamreese/helm-local - curl -L -o /usr/bin/kubectl "https://storage.googleapis.com/kubernetes-release/release/v${KUBERNETES_VERSION}/bin/linux/amd64/kubectl" chmod +x /usr/bin/kubectl kubectl version --client @@ -800,9 +797,9 @@ rollout 100%: function initialize_tiller() { echo "Checking Tiller..." - helm local start - helm local status export HELM_HOST=":44134" + tiller -listen ${HELM_HOST} -alsologtostderr & + echo "Tiller is listening on ${HELM_HOST}" if ! helm version --debug; then echo "Failed to init Tiller." -- cgit v1.2.1 From cab875eeb7ec6babbab8e5e36c0a0ff4db77f73c Mon Sep 17 00:00:00 2001 From: Thong Kuah Date: Fri, 5 Oct 2018 10:24:33 +1300 Subject: Redirect IO streams to prevent hanging https://en.wikipedia.org/wiki/Nohup#Overcoming_hanging https://gitlab.com/gitlab-org/gitlab-runner/issues/2880 --- lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml index 7ab814a0ed8..ed4ec7e6385 100644 --- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @@ -798,7 +798,7 @@ rollout 100%: echo "Checking Tiller..." export HELM_HOST=":44134" - tiller -listen ${HELM_HOST} -alsologtostderr & + tiller -listen ${HELM_HOST} -alsologtostderr > /dev/null 2>&1 & echo "Tiller is listening on ${HELM_HOST}" if ! helm version --debug; then -- cgit v1.2.1 From 7501639049a4486da60e0a00869f9f026ba83660 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Thu, 4 Oct 2018 21:40:07 -0300 Subject: Require spec helpers loaded by other spec helpers first --- spec/spec_helper.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index d1337325973..cd69160be10 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -34,6 +34,11 @@ Rainbow.enabled = false # Requires supporting ruby files with custom matchers and macros, etc, # in spec/support/ and its subdirectories. # Requires helpers, and shared contexts/examples first since they're used in other support files + +# Load these first since they may be required by other helpers +require Rails.root.join("spec/support/helpers/git_helpers.rb") + +# Then the rest Dir[Rails.root.join("spec/support/helpers/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/support/shared_contexts/*.rb")].each { |f| require f } Dir[Rails.root.join("spec/support/shared_examples/*.rb")].each { |f| require f } -- cgit v1.2.1 From 66809b207472b9aa37e452c79c98f4d47844fb6a Mon Sep 17 00:00:00 2001 From: Evan Read Date: Fri, 5 Oct 2018 10:52:25 +1000 Subject: Fix links and Markdown. --- doc/administration/pages/index.md | 75 +++++++++++++++++++-------------------- 1 file changed, 37 insertions(+), 38 deletions(-) diff --git a/doc/administration/pages/index.md b/doc/administration/pages/index.md index 3af0a5759a7..2952a98626a 100644 --- a/doc/administration/pages/index.md +++ b/doc/administration/pages/index.md @@ -92,9 +92,8 @@ where `example.io` is the domain under which GitLab Pages will be served and `192.0.2.1` is the IPv4 address of your GitLab instance and `2001::1` is the IPv6 address. If you don't have IPv6, you can omit the AAAA record. -> **Note:** -You should not use the GitLab domain to serve user pages. For more information -see the [security section](#security). +NOTE: **Note:** +You should not use the GitLab domain to serve user pages. For more information see the [security section](#security). [wiki-wildcard-dns]: https://en.wikipedia.org/wiki/Wildcard_DNS_record @@ -107,12 +106,13 @@ since that is needed in all configurations. ### Wildcard domains -> **Requirements:** -> - [Wildcard DNS setup](#dns-configuration) -> -> --- -> -> URL scheme: `http://page.example.io` +**Requirements:** + +- [Wildcard DNS setup](#dns-configuration) + +--- + +URL scheme: `http://page.example.io` This is the minimum setup that you can use Pages with. It is the base for all other setups as described below. Nginx will proxy all requests to the daemon. @@ -126,18 +126,18 @@ The Pages daemon doesn't listen to the outside world. 1. [Reconfigure GitLab][reconfigure] - Watch the [video tutorial][video-admin] for this configuration. ### Wildcard domains with TLS support -> **Requirements:** -> - [Wildcard DNS setup](#dns-configuration) -> - Wildcard TLS certificate -> -> --- -> -> URL scheme: `https://page.example.io` +**Requirements:** + +- [Wildcard DNS setup](#dns-configuration) +- Wildcard TLS certificate + +--- + +URL scheme: `https://page.example.io` Nginx will proxy all requests to the daemon. Pages daemon doesn't listen to the outside world. @@ -168,13 +168,14 @@ you have IPv6 as well as IPv4 addresses, you can use them both. ### Custom domains -> **Requirements:** -> - [Wildcard DNS setup](#dns-configuration) -> - Secondary IP -> -> --- -> -> URL scheme: `http://page.example.io` and `http://domain.com` +**Requirements:** + +- [Wildcard DNS setup](#dns-configuration) +- Secondary IP + +--- + +URL scheme: `http://page.example.io` and `http://domain.com` In that case, the Pages daemon is running, Nginx still proxies requests to the daemon but the daemon is also able to receive requests from the outside @@ -197,14 +198,15 @@ world. Custom domains are supported, but no TLS. ### Custom domains with TLS support -> **Requirements:** -> - [Wildcard DNS setup](#dns-configuration) -> - Wildcard TLS certificate -> - Secondary IP -> -> --- -> -> URL scheme: `https://page.example.io` and `https://domain.com` +**Requirements:** + +- [Wildcard DNS setup](#dns-configuration) +- Wildcard TLS certificate +- Secondary IP + +--- + +URL scheme: `https://page.example.io` and `https://domain.com` In that case, the Pages daemon is running, Nginx still proxies requests to the daemon but the daemon is also able to receive requests from the outside @@ -320,12 +322,12 @@ latest previous version. --- -**GitLab 8.17 ([documentation][8-17-docs])** +**GitLab 8.17 ([documentation](https://gitlab.com/gitlab-org/gitlab-ce/blob/8-17-stable/doc/administration/pages/index.md))** - GitLab Pages were ported to Community Edition in GitLab 8.17. - Documentation was refactored to be more modular and easy to follow. -**GitLab 8.5 ([documentation][8-5-docs])** +**GitLab 8.5 ([documentation](https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md))** - In GitLab 8.5 we introduced the [gitlab-pages][] daemon which is now the recommended way to set up GitLab Pages. @@ -334,13 +336,10 @@ latest previous version. - Custom CNAME and TLS certificates support. - Documentation was moved to one place. -**GitLab 8.3 ([documentation][8-3-docs])** +**GitLab 8.3 ([documentation](https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md))** - GitLab Pages feature was introduced. -[8-3-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-3-stable-ee/doc/pages/administration.md -[8-5-docs]: https://gitlab.com/gitlab-org/gitlab-ee/blob/8-5-stable-ee/doc/pages/administration.md -[8-17-docs]: https://gitlab.com/gitlab-org/gitlab-ce/blob/8-17-stable-ce/doc/administration/pages/index.md [backup]: ../../raketasks/backup_restore.md [ce-14605]: https://gitlab.com/gitlab-org/gitlab-ce/issues/14605 [ee-80]: https://gitlab.com/gitlab-org/gitlab-ee/merge_requests/80 -- cgit v1.2.1 From aeaa0261c7376485bdaa6b58682d19729a0a8c8c Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 5 Oct 2018 11:54:09 +0900 Subject: Add :js tag for a feature spec --- spec/features/projects/jobs_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 7bebd066b80..4e440c1befa 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -567,7 +567,7 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do visit project_job_path(project, job) end - it 'shows delayed job' do + it 'shows delayed job', :js do expect(page).to have_content(job.detailed_status(user).illustration[:title]) expect(page).to have_content('This is a scheduled to run in') expect(page).to have_content("This job will automatically run after it's timer finishes.") -- cgit v1.2.1 From 6ac0c17e183a11226627140ad04e8b224f5cb287 Mon Sep 17 00:00:00 2001 From: Franklin Yu Date: Fri, 5 Oct 2018 04:35:14 +0000 Subject: Fix indentation in GPG documentation --- doc/user/project/repository/gpg_signed_commits/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/user/project/repository/gpg_signed_commits/index.md b/doc/user/project/repository/gpg_signed_commits/index.md index 4f076ee01b8..c6239c8e41c 100644 --- a/doc/user/project/repository/gpg_signed_commits/index.md +++ b/doc/user/project/repository/gpg_signed_commits/index.md @@ -57,7 +57,7 @@ started: gpg --full-gen-key ``` -_NOTE: In some cases like Gpg4win on Windows and other Mac OS versions the command here may be ` gpg --gen-key`_ + _NOTE: In some cases like Gpg4win on Windows and other Mac OS versions the command here may be ` gpg --gen-key`_ This will spawn a series of questions. -- cgit v1.2.1 From 278c9e9e533bb01d2509331a4c6f384177f6d995 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 5 Oct 2018 06:44:09 +0000 Subject: Revert "Merge branch 'feature/git-v2-flag' into 'master'" This reverts merge request !21520 --- lib/gitlab/gitaly_client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/gitaly_client.rb b/lib/gitlab/gitaly_client.rb index 500aabcbbb8..4ec87f6a3e7 100644 --- a/lib/gitlab/gitaly_client.rb +++ b/lib/gitlab/gitaly_client.rb @@ -220,7 +220,7 @@ module Gitlab result end - SERVER_FEATURE_FLAGS = %w[gogit_findcommit git_v2].freeze + SERVER_FEATURE_FLAGS = %w[gogit_findcommit].freeze def self.server_feature_flags SERVER_FEATURE_FLAGS.map do |f| -- cgit v1.2.1 From e8f14ef8859c2b6f870090666331f538702c40b9 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 5 Oct 2018 16:07:28 +0900 Subject: Add feature spec for remaining time --- spec/features/projects/jobs_spec.rb | 3 +++ spec/features/projects/pipelines/pipelines_spec.rb | 18 ++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 4e440c1befa..67b4a520184 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -568,9 +568,12 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do end it 'shows delayed job', :js do + time_diff = [0, job.scheduled_at - Time.now].max + expect(page).to have_content(job.detailed_status(user).illustration[:title]) expect(page).to have_content('This is a scheduled to run in') expect(page).to have_content("This job will automatically run after it's timer finishes.") + expect(page).to have_content(Time.at(time_diff).utc.strftime("%H:%M:%S")) expect(page).to have_link('Unschedule job') end diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index f6197a31ee1..16a1ef813e3 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -252,7 +252,25 @@ describe 'Pipelines', :js do it "has link to the delayed job's action" do find('.js-pipeline-dropdown-manual-actions').click + time_diff = [0, delayed_job.scheduled_at - Time.now].max expect(page).to have_button('delayed job') + expect(page).to have_content(Time.at(time_diff).utc.strftime("%H:%M:%S")) + end + + context 'when delayed job is expired already' do + let!(:delayed_job) do + create(:ci_build, :expired_scheduled, + pipeline: pipeline, + name: 'delayed job', + stage: 'test', + commands: 'test') + end + + it "shows 00:00:00 as the remaining time" do + find('.js-pipeline-dropdown-manual-actions').click + + expect(page).to have_content(Time.at(time_diff).utc.strftime("00:00:00")) + end end context 'when user played a delayed job immediately' do -- cgit v1.2.1 From c5dead78558e552801205bf6e0a9b78f1e88711d Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Fri, 5 Oct 2018 09:34:08 +0200 Subject: Make sure remaining time of scheduled jobs is positive in pipelines list --- .../pipelines/components/pipelines_actions.vue | 9 ++++++-- .../pipelines/pipelines_actions_spec.js | 25 +++++++++++++++++----- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/app/assets/javascripts/pipelines/components/pipelines_actions.vue b/app/assets/javascripts/pipelines/components/pipelines_actions.vue index 743d241ee7a..16e69759091 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_actions.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_actions.vue @@ -26,7 +26,12 @@ export default { methods: { onClickAction(action) { if (action.scheduled_at) { - const confirmationMessage = sprintf(s__("DelayedJobs|Are you sure you want to run %{jobName} immediately? This job will run automatically after it's timer finishes."), { jobName: action.name }); + const confirmationMessage = sprintf( + s__( + "DelayedJobs|Are you sure you want to run %{jobName} immediately? This job will run automatically after it's timer finishes.", + ), + { jobName: action.name }, + ); // https://gitlab.com/gitlab-org/gitlab-ce/issues/52156 // eslint-disable-next-line no-alert if (!window.confirm(confirmationMessage)) { @@ -49,7 +54,7 @@ export default { remainingTime(action) { const remainingMilliseconds = new Date(action.scheduled_at).getTime() - Date.now(); - return formatTime(remainingMilliseconds); + return formatTime(Math.max(0, remainingMilliseconds)); }, }, }; diff --git a/spec/javascripts/pipelines/pipelines_actions_spec.js b/spec/javascripts/pipelines/pipelines_actions_spec.js index fe60a883f77..0566bc55693 100644 --- a/spec/javascripts/pipelines/pipelines_actions_spec.js +++ b/spec/javascripts/pipelines/pipelines_actions_spec.js @@ -47,11 +47,22 @@ describe('Pipelines Actions dropdown', () => { playable: true, scheduled_at: '2063-04-05T00:42:00Z', }; - const findDropdownItem = () => vm.$el.querySelector('.dropdown-menu li button'); + const expiredJobAction = { + name: 'expired action', + path: `${TEST_HOST}/expired/job/action`, + playable: true, + scheduled_at: '2018-10-05T08:23:00Z', + }; + const findDropdownItem = action => { + const buttons = vm.$el.querySelectorAll('.dropdown-menu li button'); + return Array.prototype.find.call(buttons, element => + element.innerText.trim().startsWith(action.name), + ); + }; beforeEach(() => { spyOn(Date, 'now').and.callFake(() => new Date('2063-04-04T00:42:00Z').getTime()); - vm = mountComponent(Component, { actions: [scheduledJobAction] }); + vm = mountComponent(Component, { actions: [scheduledJobAction, expiredJobAction] }); }); it('emits postAction event after confirming', () => { @@ -59,7 +70,7 @@ describe('Pipelines Actions dropdown', () => { eventHub.$on('postAction', emitSpy); spyOn(window, 'confirm').and.callFake(() => true); - findDropdownItem().click(); + findDropdownItem(scheduledJobAction).click(); expect(window.confirm).toHaveBeenCalled(); expect(emitSpy).toHaveBeenCalledWith(scheduledJobAction.path); @@ -70,14 +81,18 @@ describe('Pipelines Actions dropdown', () => { eventHub.$on('postAction', emitSpy); spyOn(window, 'confirm').and.callFake(() => false); - findDropdownItem().click(); + findDropdownItem(scheduledJobAction).click(); expect(window.confirm).toHaveBeenCalled(); expect(emitSpy).not.toHaveBeenCalled(); }); it('displays the remaining time in the dropdown', () => { - expect(findDropdownItem()).toContainText('24:00:00'); + expect(findDropdownItem(scheduledJobAction)).toContainText('24:00:00'); + }); + + it('displays 00:00:00 for expired jobs in the dropdown', () => { + expect(findDropdownItem(expiredJobAction)).toContainText('00:00:00'); }); }); }); -- cgit v1.2.1 From ada45a7496971656af180f1790d61d75806361d2 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 5 Oct 2018 17:11:37 +0900 Subject: Fix pipelines spec --- spec/features/projects/pipelines/pipelines_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 16a1ef813e3..17772a35779 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -269,7 +269,7 @@ describe 'Pipelines', :js do it "shows 00:00:00 as the remaining time" do find('.js-pipeline-dropdown-manual-actions').click - expect(page).to have_content(Time.at(time_diff).utc.strftime("00:00:00")) + expect(page).to have_content("00:00:00") end end -- cgit v1.2.1 From 13773d65157d32bf1b29fa051e75631af85c22f0 Mon Sep 17 00:00:00 2001 From: Alessio Caiazza Date: Fri, 5 Oct 2018 10:21:36 +0200 Subject: Add gitlab-templates code owners With https://gitlab.com/gitlab-org/gitlab-ce/issues/50289 we moved CI templates into gitlab repository. The previous gitlab-ci-yml maintainers are now marked as code owners. Auto DevOps is assigned to configure team backend developers. --- .gitlab/CODEOWNERS | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab/CODEOWNERS b/.gitlab/CODEOWNERS index 96a157648fb..a4b773b15a9 100644 --- a/.gitlab/CODEOWNERS +++ b/.gitlab/CODEOWNERS @@ -16,3 +16,5 @@ db/ @abrandl @NikolayS /ee/lib/gitlab/code_owners/ @reprazent /ee/lib/ee/gitlab/auth/ldap/ @dblessing @mkozono /lib/gitlab/auth/ldap/ @dblessing @mkozono +/lib/gitlab/ci/templates/ @nolith @zj +/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @DylanGriffith @mayra-cabrera @tkuah -- cgit v1.2.1 From a545703e739fea0b7d79cc8f0d59e0a64c0eb15b Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 5 Oct 2018 10:28:42 +0200 Subject: Improve only: changes feature documentation Align with current technical writing style-guides. --- doc/ci/yaml/README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index 442a8ba643b..15dde36cca8 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -388,7 +388,7 @@ except master. > > `variables` policy introduced in 10.7 > -> `changes` policy [introduced in 11.4][changes-policy-issue] +> `changes` policy [introduced](https://gitlab.com/gitlab-org/gitlab-ce/issues/19232) in 11.4 CAUTION: **Warning:** This an _alpha_ feature, and it it subject to change at any time without @@ -482,7 +482,7 @@ the section below. #### Using `changes` with new branches and tags If you are pushing a **new** branch or a **new** tag to GitLab, the policy -always evaluates to truth and GitLab will create a job. This feature is not +always evaluates to true and GitLab will create a job. This feature is not connected with merge requests yet, and because GitLab is creating pipelines before an user can create a merge request we don't know a target branch at this point. @@ -1987,4 +1987,3 @@ CI with various languages. [ce-12909]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12909 [schedules]: ../../user/project/pipelines/schedules.md [variables-expressions]: ../variables/README.md#variables-expressions -[changes-policy-issue]: https://gitlab.com/gitlab-org/gitlab-ce/issues/19232 -- cgit v1.2.1 From 5159d0c7e28c59b0c63f43c9b52fbacfa3a1021e Mon Sep 17 00:00:00 2001 From: GitLab Release Tools Bot Date: Fri, 5 Oct 2018 08:47:56 +0000 Subject: Update CHANGELOG.md for 11.1.8 [ci skip] --- CHANGELOG.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a02d1bc38b7..33638fdb6a2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -558,6 +558,15 @@ entry. - Moves help_popover component to a common location. +## 11.1.8 (2018-10-05) + +### Security (3 changes) + +- Filter user sensitive data from discussions JSON. !2539 +- Properly filter private references from system notes. +- Markdown API no longer displays confidential title references unless authorized. + + ## 11.1.7 (2018-09-26) ### Security (6 changes) -- cgit v1.2.1 From 637df35f0823a529d985821ab232cf3d90b76a0c Mon Sep 17 00:00:00 2001 From: Ramya Authappan Date: Fri, 5 Oct 2018 09:00:55 +0000 Subject: Adding qa-selectors for e2e tests --- .../issue_show/components/edit_actions.vue | 4 +-- .../issue_show/components/fields/description.vue | 3 +- .../issue_show/components/fields/title.vue | 2 +- .../javascripts/issue_show/components/title.vue | 3 +- .../javascripts/notes/components/comment_form.vue | 15 +++++++--- qa/qa.rb | 3 ++ qa/qa/page/component/issuable/common.rb | 35 ++++++++++++++++++++++ qa/qa/page/project/issue/show.rb | 2 ++ 8 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 qa/qa/page/component/issuable/common.rb diff --git a/app/assets/javascripts/issue_show/components/edit_actions.vue b/app/assets/javascripts/issue_show/components/edit_actions.vue index bcf8686afcc..c3d39082714 100644 --- a/app/assets/javascripts/issue_show/components/edit_actions.vue +++ b/app/assets/javascripts/issue_show/components/edit_actions.vue @@ -66,7 +66,7 @@
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index a9417369ca2..ee438e160f2 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -366,6 +366,7 @@ class ProjectsController < Projects::ApplicationController repository_access_level snippets_access_level wiki_access_level + pages_access_level ] ] end diff --git a/app/graphql/types/permission_types/project.rb b/app/graphql/types/permission_types/project.rb index 066ce64a254..ab37c282fe5 100644 --- a/app/graphql/types/permission_types/project.rb +++ b/app/graphql/types/permission_types/project.rb @@ -16,7 +16,7 @@ module Types :create_deployment, :push_to_delete_protected_branch, :admin_wiki, :admin_project, :update_pages, :admin_remote_mirror, :create_label, :update_wiki, :destroy_wiki, - :create_pages, :destroy_pages + :create_pages, :destroy_pages, :read_pages_content end end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 8b17e6ef75d..0016f89db5c 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -454,6 +454,7 @@ module ProjectsHelper buildsAccessLevel: feature.builds_access_level, wikiAccessLevel: feature.wiki_access_level, snippetsAccessLevel: feature.snippets_access_level, + pagesAccessLevel: feature.pages_access_level, containerRegistryEnabled: !!project.container_registry_enabled, lfsEnabled: !!project.lfs_enabled } @@ -468,7 +469,10 @@ module ProjectsHelper registryAvailable: Gitlab.config.registry.enabled, registryHelpPath: help_page_path('user/project/container_registry'), lfsAvailable: Gitlab.config.lfs.enabled, - lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs') + lfsHelpPath: help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs'), + pagesAvailable: Gitlab.config.pages.enabled, + pagesAccessControlEnabled: Gitlab.config.pages.access_control, + pagesHelpPath: help_page_path('user/project/pages/index.md') } end diff --git a/app/models/project.rb b/app/models/project.rb index 59f088156c7..dc2732cc6c2 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -55,8 +55,8 @@ class Project < ActiveRecord::Base cache_markdown_field :description, pipeline: :description delegate :feature_available?, :builds_enabled?, :wiki_enabled?, - :merge_requests_enabled?, :issues_enabled?, to: :project_feature, - allow_nil: true + :merge_requests_enabled?, :issues_enabled?, :pages_enabled?, :public_pages?, + to: :project_feature, allow_nil: true delegate :base_dir, :disk_path, :ensure_storage_path_exists, to: :storage @@ -356,7 +356,7 @@ class Project < ActiveRecord::Base # "enabled" here means "not disabled". It includes private features! scope :with_feature_enabled, ->(feature) { access_level_attribute = ProjectFeature.access_level_attribute(feature) - with_project_feature.where(project_features: { access_level_attribute => [nil, ProjectFeature::PRIVATE, ProjectFeature::ENABLED] }) + with_project_feature.where(project_features: { access_level_attribute => [nil, ProjectFeature::PRIVATE, ProjectFeature::ENABLED, ProjectFeature::PUBLIC] }) } # Picks a feature where the level is exactly that given. @@ -418,15 +418,15 @@ class Project < ActiveRecord::Base end end - # project features may be "disabled", "internal" or "enabled". If "internal", + # project features may be "disabled", "internal", "enabled" or "public". If "internal", # they are only available to team members. This scope returns projects where - # the feature is either enabled, or internal with permission for the user. + # the feature is either public, enabled, or internal with permission for the user. # # This method uses an optimised version of `with_feature_access_level` for # logged in users to more efficiently get private projects with the given # feature. def self.with_feature_available_for_user(feature, user) - visible = [nil, ProjectFeature::ENABLED] + visible = [nil, ProjectFeature::ENABLED, ProjectFeature::PUBLIC] if user&.admin? with_feature_enabled(feature) diff --git a/app/models/project_feature.rb b/app/models/project_feature.rb index 754c2461d23..39f2b8fe0de 100644 --- a/app/models/project_feature.rb +++ b/app/models/project_feature.rb @@ -13,14 +13,16 @@ class ProjectFeature < ActiveRecord::Base # Disabled: not enabled for anyone # Private: enabled only for team members # Enabled: enabled for everyone able to access the project + # Public: enabled for everyone (only allowed for pages) # # Permission levels DISABLED = 0 PRIVATE = 10 ENABLED = 20 + PUBLIC = 30 - FEATURES = %i(issues merge_requests wiki snippets builds repository).freeze + FEATURES = %i(issues merge_requests wiki snippets builds repository pages).freeze class << self def access_level_attribute(feature) @@ -46,6 +48,7 @@ class ProjectFeature < ActiveRecord::Base validates :project, presence: true validate :repository_children_level + validate :allowed_access_levels default_value_for :builds_access_level, value: ENABLED, allows_nil: false default_value_for :issues_access_level, value: ENABLED, allows_nil: false @@ -81,6 +84,16 @@ class ProjectFeature < ActiveRecord::Base issues_access_level > DISABLED end + def pages_enabled? + pages_access_level > DISABLED + end + + def public_pages? + return true unless Gitlab.config.pages.access_control + + pages_access_level == PUBLIC || pages_access_level == ENABLED && project.public? + end + private # Validates builds and merge requests access level @@ -95,6 +108,17 @@ class ProjectFeature < ActiveRecord::Base %i(merge_requests_access_level builds_access_level).each(&validator) end + # Validates access level for other than pages cannot be PUBLIC + def allowed_access_levels + validator = lambda do |field| + level = public_send(field) || ProjectFeature::ENABLED # rubocop:disable GitlabSecurity/PublicSend + not_allowed = level > ProjectFeature::ENABLED + self.errors.add(field, "cannot have public visibility level") if not_allowed + end + + (FEATURES - %i(pages)).each {|f| validator.call("#{f}_access_level")} + end + def get_permission(user, level) case level when DISABLED @@ -103,6 +127,8 @@ class ProjectFeature < ActiveRecord::Base user && (project.team.member?(user) || user.full_private_access?) when ENABLED true + when PUBLIC + true else true end diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index f2c246cd969..a76a083bceb 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -110,6 +110,7 @@ class ProjectPolicy < BasePolicy snippets wiki builds + pages ] features.each do |f| @@ -167,6 +168,7 @@ class ProjectPolicy < BasePolicy enable :upload_file enable :read_cycle_analytics enable :award_emoji + enable :read_pages_content end # These abilities are not allowed to admins that are not members of the project, @@ -286,6 +288,8 @@ class ProjectPolicy < BasePolicy prevent(*create_read_update_admin_destroy(:merge_request)) end + rule { pages_disabled }.prevent :read_pages_content + rule { issues_disabled & merge_requests_disabled }.policy do prevent(*create_read_update_admin_destroy(:label)) prevent(*create_read_update_admin_destroy(:milestone)) @@ -345,6 +349,7 @@ class ProjectPolicy < BasePolicy enable :download_code enable :download_wiki_code enable :read_cycle_analytics + enable :read_pages_content # NOTE: may be overridden by IssuePolicy enable :read_issue diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb index efbd4c7b323..abf40b3ad7a 100644 --- a/app/services/projects/update_pages_configuration_service.rb +++ b/app/services/projects/update_pages_configuration_service.rb @@ -21,7 +21,9 @@ module Projects def pages_config { domains: pages_domains_config, - https_only: project.pages_https_only? + https_only: project.pages_https_only?, + id: project.project_id, + access_control: !project.public_pages? } end @@ -31,7 +33,9 @@ module Projects domain: domain.domain, certificate: domain.certificate, key: domain.key, - https_only: project.pages_https_only? && domain.https? + https_only: project.pages_https_only? && domain.https?, + id: project.project_id, + access_control: !project.public_pages? } end end diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index d6d9bacf232..f25a4e30938 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -72,7 +72,11 @@ module Projects system_hook_service.execute_hooks_for(project, :update) end - update_pages_config if changing_pages_https_only? + update_pages_config if changing_pages_related_config? + end + + def changing_pages_related_config? + changing_pages_https_only? || changing_pages_access_level? end def update_failed! @@ -102,6 +106,10 @@ module Projects params.dig(:project_feature_attributes, :wiki_access_level).to_i > ProjectFeature::DISABLED end + def changing_pages_access_level? + params.dig(:project_feature_attributes, :pages_access_level) + end + def ensure_wiki_exists ProjectWiki.new(project, project.owner).wiki rescue ProjectWiki::CouldNotCreateWikiError diff --git a/changelogs/unreleased/auth.yml b/changelogs/unreleased/auth.yml new file mode 100644 index 00000000000..cd4bbf0059e --- /dev/null +++ b/changelogs/unreleased/auth.yml @@ -0,0 +1,5 @@ +--- +title: Add access control to GitLab pages and make it possible to enable/disable it in project settings +merge_request: 18589 +author: Tuomo Ala-Vannesluoma +type: added diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 67337f4b82f..749cdd0f869 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -210,6 +210,7 @@ production: &base ## GitLab Pages pages: enabled: false + access_control: false # The location where pages are stored (default: shared/pages). # path: shared/pages diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 0caa4962128..bd02b85c7ce 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -200,6 +200,7 @@ Settings.registry['path'] = Settings.absolute(Settings.registry['path # Settings['pages'] ||= Settingslogic.new({}) Settings.pages['enabled'] = false if Settings.pages['enabled'].nil? +Settings.pages['access_control'] = false if Settings.pages['access_control'].nil? Settings.pages['path'] = Settings.absolute(Settings.pages['path'] || File.join(Settings.shared['path'], "pages")) Settings.pages['https'] = false if Settings.pages['https'].nil? Settings.pages['host'] ||= "example.com" diff --git a/db/migrate/20180423204600_add_pages_access_level_to_project_feature.rb b/db/migrate/20180423204600_add_pages_access_level_to_project_feature.rb new file mode 100644 index 00000000000..1d2f8cf9c76 --- /dev/null +++ b/db/migrate/20180423204600_add_pages_access_level_to_project_feature.rb @@ -0,0 +1,16 @@ +class AddPagesAccessLevelToProjectFeature < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + disable_ddl_transaction! + + DOWNTIME = false + + def up + add_column_with_default(:project_features, :pages_access_level, :integer, default: ProjectFeature::PUBLIC, allow_null: false) + + change_column_default(:project_features, :pages_access_level, ProjectFeature::ENABLED) + end + + def down + remove_column :project_features, :pages_access_level + end +end diff --git a/db/schema.rb b/db/schema.rb index b3d4badaf82..7d78756c16f 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1578,6 +1578,7 @@ ActiveRecord::Schema.define(version: 20180924141949) do t.datetime "created_at" t.datetime "updated_at" t.integer "repository_access_level", default: 20, null: false + t.integer "pages_access_level", default: 20, null: false end add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", unique: true, using: :btree diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 8369cff2386..4a1ca41ab97 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -110,6 +110,7 @@ which visibility level you select on project settings. - Disabled: disabled for everyone - Only team members: only team members will see even if your project is public or internal - Everyone with access: everyone can see depending on your project visibility level +- Everyone: enabled for everyone (only available for GitLab Pages) ### Protected branches @@ -242,6 +243,7 @@ which visibility level you select on project settings. - Disabled: disabled for everyone - Only team members: only team members will see even if your project is public or internal - Everyone with access: everyone can see depending on your project visibility level +- Everyone: enabled for everyone (only available for GitLab Pages) ## GitLab CI/CD permissions diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 00bad49ebdc..ae2d327e45b 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -287,6 +287,12 @@ module API present_projects forks end + desc 'Check pages access of this project' + get ':id/pages_access' do + authorize! :read_pages_content, user_project unless user_project.public_pages? + status 200 + end + desc 'Update an existing project' do success Entities::Project end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 80801eb1082..e4823a5adf1 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -1,6 +1,8 @@ require_relative '../support/helpers/test_env' FactoryBot.define do + PAGES_ACCESS_LEVEL_SCHEMA_VERSION = 20180423204600 + # Project without repository # # Project does not have bare repository. @@ -23,6 +25,7 @@ FactoryBot.define do issues_access_level ProjectFeature::ENABLED merge_requests_access_level ProjectFeature::ENABLED repository_access_level ProjectFeature::ENABLED + pages_access_level ProjectFeature::ENABLED # we can't assign the delegated `#ci_cd_settings` attributes directly, as the # `#ci_cd_settings` relation needs to be created first @@ -34,13 +37,20 @@ FactoryBot.define do builds_access_level = [evaluator.builds_access_level, evaluator.repository_access_level].min merge_requests_access_level = [evaluator.merge_requests_access_level, evaluator.repository_access_level].min - project.project_feature.update( + hash = { wiki_access_level: evaluator.wiki_access_level, builds_access_level: builds_access_level, snippets_access_level: evaluator.snippets_access_level, issues_access_level: evaluator.issues_access_level, merge_requests_access_level: merge_requests_access_level, - repository_access_level: evaluator.repository_access_level) + repository_access_level: evaluator.repository_access_level + } + + if ActiveRecord::Migrator.current_version >= PAGES_ACCESS_LEVEL_SCHEMA_VERSION + hash.store("pages_access_level", evaluator.pages_access_level) + end + + project.project_feature.update(hash) # Normally the class Projects::CreateService is used for creating # projects, and this class takes care of making sure the owner and current @@ -244,6 +254,10 @@ FactoryBot.define do trait(:repository_enabled) { repository_access_level ProjectFeature::ENABLED } trait(:repository_disabled) { repository_access_level ProjectFeature::DISABLED } trait(:repository_private) { repository_access_level ProjectFeature::PRIVATE } + trait(:pages_public) { pages_access_level ProjectFeature::PUBLIC } + trait(:pages_enabled) { pages_access_level ProjectFeature::ENABLED } + trait(:pages_disabled) { pages_access_level ProjectFeature::DISABLED } + trait(:pages_private) { pages_access_level ProjectFeature::PRIVATE } trait :auto_devops do association :auto_devops, factory: :project_auto_devops diff --git a/spec/graphql/types/permission_types/project_spec.rb b/spec/graphql/types/permission_types/project_spec.rb index 89eecef096e..927153adc5b 100644 --- a/spec/graphql/types/permission_types/project_spec.rb +++ b/spec/graphql/types/permission_types/project_spec.rb @@ -10,7 +10,7 @@ describe Types::PermissionTypes::Project do :read_commit_status, :request_access, :create_pipeline, :create_pipeline_schedule, :create_merge_request_from, :create_wiki, :push_code, :create_deployment, :push_to_delete_protected_branch, :admin_wiki, :admin_project, :update_pages, :admin_remote_mirror, :create_label, - :update_wiki, :destroy_wiki, :create_pages, :destroy_pages + :update_wiki, :destroy_wiki, :create_pages, :destroy_pages, :read_pages_content ] expect(described_class).to have_graphql_fields(expected_permissions) diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index e9f1be172b0..7be1bf6e0bf 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -492,6 +492,7 @@ ProjectFeature: - snippets_access_level - builds_access_level - repository_access_level +- pages_access_level - created_at - updated_at ProtectedBranch::MergeAccessLevel: diff --git a/spec/migrations/add_pages_access_level_to_project_feature_spec.rb b/spec/migrations/add_pages_access_level_to_project_feature_spec.rb new file mode 100644 index 00000000000..3946602c5be --- /dev/null +++ b/spec/migrations/add_pages_access_level_to_project_feature_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' +require Rails.root.join('db', 'migrate', '20180423204600_add_pages_access_level_to_project_feature.rb') + +describe AddPagesAccessLevelToProjectFeature, :migration do + let(:namespaces) { table(:namespaces) } + let(:projects) { table(:projects) } + let(:features) { table(:project_features) } + let!(:namespace) { namespaces.create(name: 'gitlab', path: 'gitlab') } + let!(:first_project) { projects.create(name: 'gitlab1', path: 'gitlab1', namespace_id: namespace.id) } + let!(:first_project_features) { features.create(project_id: first_project.id) } + let!(:second_project) { projects.create(name: 'gitlab2', path: 'gitlab2', namespace_id: namespace.id) } + let!(:second_project_features) { features.create(project_id: second_project.id) } + + it 'correctly migrate pages for old projects to be public' do + migrate! + + # For old projects pages should be public + expect(first_project_features.reload.pages_access_level).to eq ProjectFeature::PUBLIC + expect(second_project_features.reload.pages_access_level).to eq ProjectFeature::PUBLIC + end + + it 'after migration pages are enabled as default' do + migrate! + + # For new project default is enabled + third_project = projects.create(name: 'gitlab3', path: 'gitlab3', namespace_id: namespace.id) + third_project_features = features.create(project_id: third_project.id) + expect(third_project_features.reload.pages_access_level).to eq ProjectFeature::ENABLED + end +end diff --git a/spec/models/internal_id_spec.rb b/spec/models/internal_id_spec.rb index f2aad455d5f..52c00a74b4b 100644 --- a/spec/models/internal_id_spec.rb +++ b/spec/models/internal_id_spec.rb @@ -65,7 +65,8 @@ describe InternalId do context 'with an insufficient schema version' do before do described_class.reset_column_information - expect(ActiveRecord::Migrator).to receive(:current_version).and_return(InternalId::REQUIRED_SCHEMA_VERSION - 1) + # Project factory will also call the current_version + expect(ActiveRecord::Migrator).to receive(:current_version).twice.and_return(InternalId::REQUIRED_SCHEMA_VERSION - 1) end let(:init) { double('block') } diff --git a/spec/models/project_feature_spec.rb b/spec/models/project_feature_spec.rb index 2a193864e46..fee7d65c217 100644 --- a/spec/models/project_feature_spec.rb +++ b/spec/models/project_feature_spec.rb @@ -17,7 +17,7 @@ describe ProjectFeature do end describe '#feature_available?' do - let(:features) { %w(issues wiki builds merge_requests snippets repository) } + let(:features) { %w(issues wiki builds merge_requests snippets repository pages) } context 'when features are disabled' do it "returns false" do @@ -112,6 +112,19 @@ describe ProjectFeature do end end + context 'public features' do + it "does not allow public for other than pages" do + features = %w(issues wiki builds merge_requests snippets repository) + project_feature = project.project_feature + + features.each do |feature| + field = "#{feature}_access_level".to_sym + project_feature.update_attribute(field, ProjectFeature::PUBLIC) + expect(project_feature.valid?).to be_falsy + end + end + end + describe '#*_enabled?' do let(:features) { %w(wiki builds merge_requests) } diff --git a/spec/requests/api/pages/internal_access_spec.rb b/spec/requests/api/pages/internal_access_spec.rb new file mode 100644 index 00000000000..c41eabe0a48 --- /dev/null +++ b/spec/requests/api/pages/internal_access_spec.rb @@ -0,0 +1,88 @@ +require 'spec_helper' + +describe "Internal Project Pages Access" do + using RSpec::Parameterized::TableSyntax + include AccessMatchers + + set(:group) { create(:group) } + set(:project) { create(:project, :internal, pages_access_level: ProjectFeature::ENABLED, namespace: group) } + + set(:admin) { create(:admin) } + set(:owner) { create(:user) } + set(:master) { create(:user) } + set(:developer) { create(:user) } + set(:reporter) { create(:user) } + set(:guest) { create(:user) } + set(:user) { create(:user) } + + before do + allow(Gitlab.config.pages).to receive(:access_control).and_return(true) + group.add_owner(owner) + project.add_master(master) + project.add_developer(developer) + project.add_reporter(reporter) + project.add_guest(guest) + end + + describe "Project should be internal" do + describe '#internal?' do + subject { project.internal? } + it { is_expected.to be_truthy } + end + end + + describe "GET /projects/:id/pages_access" do + context 'access depends on the level' do + where(:pages_access_level, :with_user, :expected_result) do + ProjectFeature::DISABLED | "admin" | 403 + ProjectFeature::DISABLED | "owner" | 403 + ProjectFeature::DISABLED | "master" | 403 + ProjectFeature::DISABLED | "developer" | 403 + ProjectFeature::DISABLED | "reporter" | 403 + ProjectFeature::DISABLED | "guest" | 403 + ProjectFeature::DISABLED | "user" | 403 + ProjectFeature::DISABLED | nil | 404 + ProjectFeature::PUBLIC | "admin" | 200 + ProjectFeature::PUBLIC | "owner" | 200 + ProjectFeature::PUBLIC | "master" | 200 + ProjectFeature::PUBLIC | "developer" | 200 + ProjectFeature::PUBLIC | "reporter" | 200 + ProjectFeature::PUBLIC | "guest" | 200 + ProjectFeature::PUBLIC | "user" | 200 + ProjectFeature::PUBLIC | nil | 404 + ProjectFeature::ENABLED | "admin" | 200 + ProjectFeature::ENABLED | "owner" | 200 + ProjectFeature::ENABLED | "master" | 200 + ProjectFeature::ENABLED | "developer" | 200 + ProjectFeature::ENABLED | "reporter" | 200 + ProjectFeature::ENABLED | "guest" | 200 + ProjectFeature::ENABLED | "user" | 200 + ProjectFeature::ENABLED | nil | 404 + ProjectFeature::PRIVATE | "admin" | 200 + ProjectFeature::PRIVATE | "owner" | 200 + ProjectFeature::PRIVATE | "master" | 200 + ProjectFeature::PRIVATE | "developer" | 200 + ProjectFeature::PRIVATE | "reporter" | 200 + ProjectFeature::PRIVATE | "guest" | 200 + ProjectFeature::PRIVATE | "user" | 403 + ProjectFeature::PRIVATE | nil | 404 + end + + with_them do + before do + project.project_feature.update(pages_access_level: pages_access_level) + end + it "correct return value" do + if !with_user.nil? + user = public_send(with_user) + get api("/projects/#{project.id}/pages_access", user) + else + get api("/projects/#{project.id}/pages_access") + end + + expect(response).to have_gitlab_http_status(expected_result) + end + end + end + end +end diff --git a/spec/requests/api/pages/private_access_spec.rb b/spec/requests/api/pages/private_access_spec.rb new file mode 100644 index 00000000000..d69c15b0477 --- /dev/null +++ b/spec/requests/api/pages/private_access_spec.rb @@ -0,0 +1,88 @@ +require 'spec_helper' + +describe "Private Project Pages Access" do + using RSpec::Parameterized::TableSyntax + include AccessMatchers + + set(:group) { create(:group) } + set(:project) { create(:project, :private, pages_access_level: ProjectFeature::ENABLED, namespace: group) } + + set(:admin) { create(:admin) } + set(:owner) { create(:user) } + set(:master) { create(:user) } + set(:developer) { create(:user) } + set(:reporter) { create(:user) } + set(:guest) { create(:user) } + set(:user) { create(:user) } + + before do + allow(Gitlab.config.pages).to receive(:access_control).and_return(true) + group.add_owner(owner) + project.add_master(master) + project.add_developer(developer) + project.add_reporter(reporter) + project.add_guest(guest) + end + + describe "Project should be private" do + describe '#private?' do + subject { project.private? } + it { is_expected.to be_truthy } + end + end + + describe "GET /projects/:id/pages_access" do + context 'access depends on the level' do + where(:pages_access_level, :with_user, :expected_result) do + ProjectFeature::DISABLED | "admin" | 403 + ProjectFeature::DISABLED | "owner" | 403 + ProjectFeature::DISABLED | "master" | 403 + ProjectFeature::DISABLED | "developer" | 403 + ProjectFeature::DISABLED | "reporter" | 403 + ProjectFeature::DISABLED | "guest" | 403 + ProjectFeature::DISABLED | "user" | 404 + ProjectFeature::DISABLED | nil | 404 + ProjectFeature::PUBLIC | "admin" | 200 + ProjectFeature::PUBLIC | "owner" | 200 + ProjectFeature::PUBLIC | "master" | 200 + ProjectFeature::PUBLIC | "developer" | 200 + ProjectFeature::PUBLIC | "reporter" | 200 + ProjectFeature::PUBLIC | "guest" | 200 + ProjectFeature::PUBLIC | "user" | 404 + ProjectFeature::PUBLIC | nil | 404 + ProjectFeature::ENABLED | "admin" | 200 + ProjectFeature::ENABLED | "owner" | 200 + ProjectFeature::ENABLED | "master" | 200 + ProjectFeature::ENABLED | "developer" | 200 + ProjectFeature::ENABLED | "reporter" | 200 + ProjectFeature::ENABLED | "guest" | 200 + ProjectFeature::ENABLED | "user" | 404 + ProjectFeature::ENABLED | nil | 404 + ProjectFeature::PRIVATE | "admin" | 200 + ProjectFeature::PRIVATE | "owner" | 200 + ProjectFeature::PRIVATE | "master" | 200 + ProjectFeature::PRIVATE | "developer" | 200 + ProjectFeature::PRIVATE | "reporter" | 200 + ProjectFeature::PRIVATE | "guest" | 200 + ProjectFeature::PRIVATE | "user" | 404 + ProjectFeature::PRIVATE | nil | 404 + end + + with_them do + before do + project.project_feature.update(pages_access_level: pages_access_level) + end + it "correct return value" do + if !with_user.nil? + user = public_send(with_user) + get api("/projects/#{project.id}/pages_access", user) + else + get api("/projects/#{project.id}/pages_access") + end + + expect(response).to have_gitlab_http_status(expected_result) + end + end + end + end +end diff --git a/spec/requests/api/pages/public_access_spec.rb b/spec/requests/api/pages/public_access_spec.rb new file mode 100644 index 00000000000..882ca26ac51 --- /dev/null +++ b/spec/requests/api/pages/public_access_spec.rb @@ -0,0 +1,88 @@ +require 'spec_helper' + +describe "Public Project Pages Access" do + using RSpec::Parameterized::TableSyntax + include AccessMatchers + + set(:group) { create(:group) } + set(:project) { create(:project, :public, pages_access_level: ProjectFeature::ENABLED, namespace: group) } + + set(:admin) { create(:admin) } + set(:owner) { create(:user) } + set(:master) { create(:user) } + set(:developer) { create(:user) } + set(:reporter) { create(:user) } + set(:guest) { create(:user) } + set(:user) { create(:user) } + + before do + allow(Gitlab.config.pages).to receive(:access_control).and_return(true) + group.add_owner(owner) + project.add_master(master) + project.add_developer(developer) + project.add_reporter(reporter) + project.add_guest(guest) + end + + describe "Project should be public" do + describe '#public?' do + subject { project.public? } + it { is_expected.to be_truthy } + end + end + + describe "GET /projects/:id/pages_access" do + context 'access depends on the level' do + where(:pages_access_level, :with_user, :expected_result) do + ProjectFeature::DISABLED | "admin" | 403 + ProjectFeature::DISABLED | "owner" | 403 + ProjectFeature::DISABLED | "master" | 403 + ProjectFeature::DISABLED | "developer" | 403 + ProjectFeature::DISABLED | "reporter" | 403 + ProjectFeature::DISABLED | "guest" | 403 + ProjectFeature::DISABLED | "user" | 403 + ProjectFeature::DISABLED | nil | 403 + ProjectFeature::PUBLIC | "admin" | 200 + ProjectFeature::PUBLIC | "owner" | 200 + ProjectFeature::PUBLIC | "master" | 200 + ProjectFeature::PUBLIC | "developer" | 200 + ProjectFeature::PUBLIC | "reporter" | 200 + ProjectFeature::PUBLIC | "guest" | 200 + ProjectFeature::PUBLIC | "user" | 200 + ProjectFeature::PUBLIC | nil | 200 + ProjectFeature::ENABLED | "admin" | 200 + ProjectFeature::ENABLED | "owner" | 200 + ProjectFeature::ENABLED | "master" | 200 + ProjectFeature::ENABLED | "developer" | 200 + ProjectFeature::ENABLED | "reporter" | 200 + ProjectFeature::ENABLED | "guest" | 200 + ProjectFeature::ENABLED | "user" | 200 + ProjectFeature::ENABLED | nil | 200 + ProjectFeature::PRIVATE | "admin" | 200 + ProjectFeature::PRIVATE | "owner" | 200 + ProjectFeature::PRIVATE | "master" | 200 + ProjectFeature::PRIVATE | "developer" | 200 + ProjectFeature::PRIVATE | "reporter" | 200 + ProjectFeature::PRIVATE | "guest" | 200 + ProjectFeature::PRIVATE | "user" | 403 + ProjectFeature::PRIVATE | nil | 403 + end + + with_them do + before do + project.project_feature.update(pages_access_level: pages_access_level) + end + it "correct return value" do + if !with_user.nil? + user = public_send(with_user) + get api("/projects/#{project.id}/pages_access", user) + else + get api("/projects/#{project.id}/pages_access") + end + + expect(response).to have_gitlab_http_status(expected_result) + end + end + end + end +end diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 695b9980548..d58ff2cedc0 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -340,6 +340,27 @@ describe Projects::UpdateService do call_service end end + + context 'when updating #pages_access_level' do + subject(:call_service) do + update_project(project, admin, project_feature_attributes: { pages_access_level: ProjectFeature::PRIVATE }) + end + + it 'updates the attribute' do + expect { call_service } + .to change { project.project_feature.pages_access_level } + .to(ProjectFeature::PRIVATE) + end + + it 'calls Projects::UpdatePagesConfigurationService' do + expect(Projects::UpdatePagesConfigurationService) + .to receive(:new) + .with(project) + .and_call_original + + call_service + end + end end describe '#run_auto_devops_pipeline?' do -- cgit v1.2.1 From 3142eec0ff5a2850f54ca2779e5ade241297fb85 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Fri, 5 Oct 2018 07:01:04 -0700 Subject: Revert "Merge branch 'deploy-tokens' into 'master'" This reverts commit 3df32ac1290160680473248b48d1b12c96458bf7, reversing changes made to dd1295a30f28eeb3c7058b1899e00db7943f3e54. --- app/controllers/projects/settings/repository_controller.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb index 6d83d24cdb8..1d76c90d4eb 100644 --- a/app/controllers/projects/settings/repository_controller.rb +++ b/app/controllers/projects/settings/repository_controller.rb @@ -14,10 +14,10 @@ module Projects @new_deploy_token = DeployTokens::CreateService.new(@project, current_user, deploy_token_params).execute if @new_deploy_token.persisted? - flash[:notice] = s_('DeployTokens|Your new project deploy token has been created.') + flash.now[:notice] = s_('DeployTokens|Your new project deploy token has been created.') end - redirect_to action: :show + render_show end private -- cgit v1.2.1 From 91ef6989d15c5fd8e93e93eb980d03abe1a15d0e Mon Sep 17 00:00:00 2001 From: "Balasankar \"Balu\" C" Date: Fri, 5 Oct 2018 19:34:22 +0530 Subject: Add installation type to backup information file --- lib/backup/manager.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index 5d4a7efc456..afdc6f383c1 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -243,6 +243,7 @@ module Backup backup_created_at: Time.now, gitlab_version: Gitlab::VERSION, tar_version: tar_version, + installation_type: Gitlab::INSTALLATION_TYPE, skipped: ENV["SKIP"] } end -- cgit v1.2.1 From 75ddf0d48d0fee6f9209376b0751d414946f9c67 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 5 Oct 2018 11:20:19 -0300 Subject: Refactor Feature.flipper method --- lib/feature.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/feature.rb b/lib/feature.rb index 0e90ad9a333..a8324d99c10 100644 --- a/lib/feature.rb +++ b/lib/feature.rb @@ -72,7 +72,11 @@ class Feature end def flipper - @flipper ||= (Gitlab::SafeRequestStore[:flipper] ||= build_flipper_instance) + if Gitlab::SafeRequestStore.active? + Gitlab::SafeRequestStore[:flipper] ||= build_flipper_instance + else + @flipper ||= build_flipper_instance + end end def build_flipper_instance -- cgit v1.2.1 From 7bc606592da43bbe6286d7a1c55bdad69927eb34 Mon Sep 17 00:00:00 2001 From: "Balasankar \"Balu\" C" Date: Fri, 5 Oct 2018 20:00:54 +0530 Subject: Add changelog entry --- changelogs/unreleased/add-installation-type-backup-information.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/add-installation-type-backup-information.yml diff --git a/changelogs/unreleased/add-installation-type-backup-information.yml b/changelogs/unreleased/add-installation-type-backup-information.yml new file mode 100644 index 00000000000..24cf4cc21f4 --- /dev/null +++ b/changelogs/unreleased/add-installation-type-backup-information.yml @@ -0,0 +1,5 @@ +--- +title: Add installation type to backup information file +merge_request: 22150 +author: +type: changed -- cgit v1.2.1 From 9f9cac67ab7292be0037082120f0995152624599 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Fri, 5 Oct 2018 17:35:59 +0200 Subject: Fix CE to EE merge (backport) --- app/services/merge_requests/refresh_service.rb | 11 ++++++----- lib/gitlab/git/push.rb | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb index d3e4f3def23..b03d14fa3cc 100644 --- a/app/services/merge_requests/refresh_service.rb +++ b/app/services/merge_requests/refresh_service.rb @@ -3,16 +3,17 @@ module MergeRequests class RefreshService < MergeRequests::BaseService def execute(oldrev, newrev, ref) - @push = Gitlab::Git::Push.new(@project, oldrev, newrev, ref) + push = Gitlab::Git::Push.new(@project, oldrev, newrev, ref) + return true unless push.branch_push? - return true unless @push.branch_push? - - refresh_merge_requests! + refresh_merge_requests!(push) end private - def refresh_merge_requests! + def refresh_merge_requests!(push) + @push = push + Gitlab::GitalyClient.allow_n_plus_1_calls(&method(:find_new_commits)) # Be sure to close outstanding MRs before reloading them to avoid generating an # empty diff during a manual merge diff --git a/lib/gitlab/git/push.rb b/lib/gitlab/git/push.rb index 7c1309721fd..b6577ba17f1 100644 --- a/lib/gitlab/git/push.rb +++ b/lib/gitlab/git/push.rb @@ -5,7 +5,7 @@ module Gitlab class Push include Gitlab::Utils::StrongMemoize - attr_reader :oldrev, :newrev + attr_reader :ref, :oldrev, :newrev def initialize(project, oldrev, newrev, ref) @project = project -- cgit v1.2.1 From deb6b429bfd06ff077f85c03a7ce1f4ba0186bec Mon Sep 17 00:00:00 2001 From: Simon Knox Date: Fri, 5 Oct 2018 16:26:16 +0000 Subject: Backport changes from gitlab-ee!7538 --- .../javascripts/monitoring/components/graph.vue | 9 +- .../monitoring/utils/multiple_time_series.js | 103 ++++++++++++++------- spec/javascripts/monitoring/dashboard_spec.js | 38 ++++---- spec/javascripts/monitoring/graph/legend_spec.js | 2 +- .../monitoring/graph/track_info_spec.js | 2 +- .../monitoring/graph/track_line_spec.js | 2 +- spec/javascripts/monitoring/graph_path_spec.js | 2 +- spec/javascripts/monitoring/mock_data.js | 1 + .../monitoring/utils/multiple_time_series_spec.js | 2 +- 9 files changed, 102 insertions(+), 59 deletions(-) diff --git a/app/assets/javascripts/monitoring/components/graph.vue b/app/assets/javascripts/monitoring/components/graph.vue index ff44f51b8f8..3cccaf72ed7 100644 --- a/app/assets/javascripts/monitoring/components/graph.vue +++ b/app/assets/javascripts/monitoring/components/graph.vue @@ -82,6 +82,7 @@ export default { showFlag: false, showFlagContent: false, timeSeries: [], + graphDrawData: {}, realPixelRatio: 1, seriesUnderMouse: [], }; @@ -180,12 +181,12 @@ export default { }); }, renderAxesPaths() { - this.timeSeries = createTimeSeries( + ({ timeSeries: this.timeSeries, graphDrawData: this.graphDrawData } = createTimeSeries( this.graphData.queries, this.graphWidth, this.graphHeight, this.graphHeightOffset, - ); + )); if (_.findWhere(this.timeSeries, { renderCanary: true })) { this.timeSeries = this.timeSeries.map(series => ({ ...series, renderCanary: true })); @@ -288,6 +289,10 @@ export default { :viewBox="innerViewBox" class="graph-data" > + !Number.isNaN(d.value) && d.value != null; - - const lineFunction = d3 - .line() - .defined(defined) - .curve(d3.curveLinear) // d3 v4 uses curbe instead of interpolate - .x(d => timeSeriesScaleX(d.time)) - .y(d => timeSeriesScaleY(d.value)); - - const areaFunction = d3 - .area() - .defined(defined) - .curve(d3.curveLinear) - .x(d => timeSeriesScaleX(d.time)) - .y0(graphHeight - graphHeightOffset) - .y1(d => timeSeriesScaleY(d.value)); - const timeSeriesMetricLabel = timeSeries.metric[Object.keys(timeSeries.metric)[0]]; const seriesCustomizationData = query.series != null && _.findWhere(query.series[0].when, { value: timeSeriesMetricLabel }); @@ -144,10 +119,10 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom })); timeSeriesParsed.push({ - linePath: lineFunction(values), - areaPath: areaFunction(values), - timeSeriesScaleX, - timeSeriesScaleY, + linePath: graphDrawData.lineFunction(values), + areaPath: graphDrawData.areaBelowLine(values), + timeSeriesScaleX: graphDrawData.timeSeriesScaleX, + timeSeriesScaleY: graphDrawData.timeSeriesScaleY, values: timeSeries.values, max: maximumValue, average: accum / timeSeries.values.length, @@ -164,7 +139,7 @@ function queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom return timeSeriesParsed; } -export default function createTimeSeries(queries, graphWidth, graphHeight, graphHeightOffset) { +function xyDomain(queries) { const allValues = queries.reduce( (allQueryResults, query) => allQueryResults.concat( @@ -176,10 +151,70 @@ export default function createTimeSeries(queries, graphWidth, graphHeight, graph const xDom = d3.extent(allValues, d => d.time); const yDom = [0, d3.max(allValues.map(d => d.value))]; - return queries.reduce((series, query, index) => { + return { + xDom, + yDom, + }; +} + +export function generateGraphDrawData(queries, graphWidth, graphHeight, graphHeightOffset) { + const { xDom, yDom } = xyDomain(queries); + + const timeSeriesScaleX = d3.scaleTime().range([0, graphWidth - 70]); + const timeSeriesScaleY = d3.scaleLinear().range([graphHeight - graphHeightOffset, 0]); + + timeSeriesScaleX.domain(xDom); + timeSeriesScaleX.ticks(d3.timeMinute, 60); + timeSeriesScaleY.domain(yDom); + + const defined = d => !Number.isNaN(d.value) && d.value != null; + + const lineFunction = d3 + .line() + .defined(defined) + .curve(d3.curveLinear) // d3 v4 uses curbe instead of interpolate + .x(d => timeSeriesScaleX(d.time)) + .y(d => timeSeriesScaleY(d.value)); + + const areaBelowLine = d3 + .area() + .defined(defined) + .curve(d3.curveLinear) + .x(d => timeSeriesScaleX(d.time)) + .y0(graphHeight - graphHeightOffset) + .y1(d => timeSeriesScaleY(d.value)); + + const areaAboveLine = d3 + .area() + .defined(defined) + .curve(d3.curveLinear) + .x(d => timeSeriesScaleX(d.time)) + .y0(0) + .y1(d => timeSeriesScaleY(d.value)); + + return { + lineFunction, + areaBelowLine, + areaAboveLine, + xDom, + yDom, + timeSeriesScaleX, + timeSeriesScaleY, + }; +} + +export default function createTimeSeries(queries, graphWidth, graphHeight, graphHeightOffset) { + const graphDrawData = generateGraphDrawData(queries, graphWidth, graphHeight, graphHeightOffset); + + const timeSeries = queries.reduce((series, query, index) => { const lineStyle = defaultStyleOrder[index % defaultStyleOrder.length]; return series.concat( - queryTimeSeries(query, graphWidth, graphHeight, graphHeightOffset, xDom, yDom, lineStyle), + queryTimeSeries(query, graphDrawData, lineStyle), ); }, []); + + return { + timeSeries, + graphDrawData, + }; } diff --git a/spec/javascripts/monitoring/dashboard_spec.js b/spec/javascripts/monitoring/dashboard_spec.js index f0d53b2d8d7..732c37a24bf 100644 --- a/spec/javascripts/monitoring/dashboard_spec.js +++ b/spec/javascripts/monitoring/dashboard_spec.js @@ -4,30 +4,32 @@ import Dashboard from '~/monitoring/components/dashboard.vue'; import axios from '~/lib/utils/axios_utils'; import { metricsGroupsAPIResponse, mockApiEndpoint, environmentData } from './mock_data'; +const propsData = { + hasMetrics: false, + documentationPath: '/path/to/docs', + settingsPath: '/path/to/settings', + clustersPath: '/path/to/clusters', + tagsPath: '/path/to/tags', + projectPath: '/path/to/project', + metricsEndpoint: mockApiEndpoint, + deploymentEndpoint: null, + emptyGettingStartedSvgPath: '/path/to/getting-started.svg', + emptyLoadingSvgPath: '/path/to/loading.svg', + emptyNoDataSvgPath: '/path/to/no-data.svg', + emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg', + environmentsEndpoint: '/root/hello-prometheus/environments/35', + currentEnvironmentName: 'production', +}; + +export default propsData; + describe('Dashboard', () => { let DashboardComponent; - const propsData = { - hasMetrics: false, - documentationPath: '/path/to/docs', - settingsPath: '/path/to/settings', - clustersPath: '/path/to/clusters', - tagsPath: '/path/to/tags', - projectPath: '/path/to/project', - metricsEndpoint: mockApiEndpoint, - deploymentEndpoint: null, - emptyGettingStartedSvgPath: '/path/to/getting-started.svg', - emptyLoadingSvgPath: '/path/to/loading.svg', - emptyNoDataSvgPath: '/path/to/no-data.svg', - emptyUnableToConnectSvgPath: '/path/to/unable-to-connect.svg', - environmentsEndpoint: '/root/hello-prometheus/environments/35', - currentEnvironmentName: 'production', - }; - beforeEach(() => { setFixtures(`
- + `); DashboardComponent = Vue.extend(Dashboard); }); diff --git a/spec/javascripts/monitoring/graph/legend_spec.js b/spec/javascripts/monitoring/graph/legend_spec.js index abcc51aa077..9209e77dcf4 100644 --- a/spec/javascripts/monitoring/graph/legend_spec.js +++ b/spec/javascripts/monitoring/graph/legend_spec.js @@ -8,7 +8,7 @@ const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeri const defaultValuesComponent = {}; -const timeSeries = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120); +const { timeSeries } = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120); defaultValuesComponent.timeSeries = timeSeries; diff --git a/spec/javascripts/monitoring/graph/track_info_spec.js b/spec/javascripts/monitoring/graph/track_info_spec.js index d3121d553f9..ce93ae28842 100644 --- a/spec/javascripts/monitoring/graph/track_info_spec.js +++ b/spec/javascripts/monitoring/graph/track_info_spec.js @@ -5,7 +5,7 @@ import createTimeSeries from '~/monitoring/utils/multiple_time_series'; import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from '../mock_data'; const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries); -const timeSeries = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120); +const { timeSeries } = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120); describe('TrackInfo component', () => { let vm; diff --git a/spec/javascripts/monitoring/graph/track_line_spec.js b/spec/javascripts/monitoring/graph/track_line_spec.js index 27602a861eb..2a4f89ddf6e 100644 --- a/spec/javascripts/monitoring/graph/track_line_spec.js +++ b/spec/javascripts/monitoring/graph/track_line_spec.js @@ -5,7 +5,7 @@ import createTimeSeries from '~/monitoring/utils/multiple_time_series'; import { singleRowMetricsMultipleSeries, convertDatesMultipleSeries } from '../mock_data'; const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries); -const timeSeries = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120); +const { timeSeries } = createTimeSeries(convertedMetrics[0].queries, 500, 300, 120); describe('TrackLine component', () => { let vm; diff --git a/spec/javascripts/monitoring/graph_path_spec.js b/spec/javascripts/monitoring/graph_path_spec.js index 2515e2ad897..5f270c5cfe9 100644 --- a/spec/javascripts/monitoring/graph_path_spec.js +++ b/spec/javascripts/monitoring/graph_path_spec.js @@ -13,7 +13,7 @@ const createComponent = (propsData) => { const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries); -const timeSeries = createTimeSeries(convertedMetrics[0].queries, 428, 272, 120); +const { timeSeries } = createTimeSeries(convertedMetrics[0].queries, 428, 272, 120); const firstTimeSeries = timeSeries[0]; describe('Monitoring Paths', () => { diff --git a/spec/javascripts/monitoring/mock_data.js b/spec/javascripts/monitoring/mock_data.js index e4c98a3bcb5..6c833b17f98 100644 --- a/spec/javascripts/monitoring/mock_data.js +++ b/spec/javascripts/monitoring/mock_data.js @@ -8,6 +8,7 @@ export const metricsGroupsAPIResponse = { priority: 1, metrics: [ { + id: 5, title: 'Memory usage', weight: 1, queries: [ diff --git a/spec/javascripts/monitoring/utils/multiple_time_series_spec.js b/spec/javascripts/monitoring/utils/multiple_time_series_spec.js index 99584c75287..8937b7d9680 100644 --- a/spec/javascripts/monitoring/utils/multiple_time_series_spec.js +++ b/spec/javascripts/monitoring/utils/multiple_time_series_spec.js @@ -2,7 +2,7 @@ import createTimeSeries from '~/monitoring/utils/multiple_time_series'; import { convertDatesMultipleSeries, singleRowMetricsMultipleSeries } from '../mock_data'; const convertedMetrics = convertDatesMultipleSeries(singleRowMetricsMultipleSeries); -const timeSeries = createTimeSeries(convertedMetrics[0].queries, 428, 272, 120); +const { timeSeries } = createTimeSeries(convertedMetrics[0].queries, 428, 272, 120); const firstTimeSeries = timeSeries[0]; describe('Multiple time series', () => { -- cgit v1.2.1 From f5abc2e8f99e67aaca8d5c5268f3aadb8302085d Mon Sep 17 00:00:00 2001 From: Brett Walker Date: Tue, 25 Sep 2018 11:32:04 -0500 Subject: Use a CTE to remove the query timeout --- ...anceling-statement-due-to-statement-timeout.yml | 5 ++ ...81002172433_remove_restricted_todos_with_cte.rb | 32 +++++++++ db/schema.rb | 2 +- .../remove_restricted_todos.rb | 84 +++++++++++++++++++--- 4 files changed, 113 insertions(+), 10 deletions(-) create mode 100644 changelogs/unreleased/50359-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml create mode 100644 db/migrate/20181002172433_remove_restricted_todos_with_cte.rb diff --git a/changelogs/unreleased/50359-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml b/changelogs/unreleased/50359-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml new file mode 100644 index 00000000000..09ec4b8d73d --- /dev/null +++ b/changelogs/unreleased/50359-activerecord-statementinvalid-pg-querycanceled-error-canceling-statement-due-to-statement-timeout.yml @@ -0,0 +1,5 @@ +--- +title: Fix timeout when running the RemoveRestrictedTodos background migration +merge_request: 21893 +author: +type: fixed diff --git a/db/migrate/20181002172433_remove_restricted_todos_with_cte.rb b/db/migrate/20181002172433_remove_restricted_todos_with_cte.rb new file mode 100644 index 00000000000..0a8f4a12266 --- /dev/null +++ b/db/migrate/20181002172433_remove_restricted_todos_with_cte.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +# rescheduling of the revised RemoveRestrictedTodos background migration +class RemoveRestrictedTodosWithCte < ActiveRecord::Migration + DOWNTIME = false + disable_ddl_transaction! + + MIGRATION = 'RemoveRestrictedTodos'.freeze + BATCH_SIZE = 1000 + DELAY_INTERVAL = 5.minutes.to_i + + class Project < ActiveRecord::Base + include EachBatch + + self.table_name = 'projects' + end + + def up + Project.where('EXISTS (SELECT 1 FROM todos WHERE todos.project_id = projects.id)') + .each_batch(of: BATCH_SIZE) do |batch, index| + range = batch.pluck('MIN(id)', 'MAX(id)').first + + BackgroundMigrationWorker.perform_in(index * DELAY_INTERVAL, MIGRATION, range) + end + end + + def down + # nothing to do + end +end diff --git a/db/schema.rb b/db/schema.rb index 773bfb96b93..4ff0272428a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20180924201039) do +ActiveRecord::Schema.define(version: 20181002172433) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/lib/gitlab/background_migration/remove_restricted_todos.rb b/lib/gitlab/background_migration/remove_restricted_todos.rb index 68f3fa62170..9941c2fe6d9 100644 --- a/lib/gitlab/background_migration/remove_restricted_todos.rb +++ b/lib/gitlab/background_migration/remove_restricted_todos.rb @@ -1,5 +1,6 @@ # frozen_string_literal: true # rubocop:disable Style/Documentation +# rubocop:disable Metrics/ClassLength module Gitlab module BackgroundMigration @@ -49,11 +50,14 @@ module Gitlab private def remove_non_members_todos(project_id) - Todo.where(project_id: project_id) - .where('user_id NOT IN (?)', authorized_users(project_id)) - .each_batch(of: 5000) do |batch| - batch.delete_all - end + if Gitlab::Database.postgresql? + batch_remove_todos_cte(project_id) + else + unauthorized_project_todos(project_id) + .each_batch(of: 5000) do |batch| + batch.delete_all + end + end end def remove_confidential_issue_todos(project_id) @@ -86,10 +90,13 @@ module Gitlab next if target_types.empty? - Todo.where(project_id: project_id) - .where('user_id NOT IN (?)', authorized_users(project_id)) - .where(target_type: target_types) - .delete_all + if Gitlab::Database.postgresql? + batch_remove_todos_cte(project_id, target_types) + else + unauthorized_project_todos(project_id) + .where(target_type: target_types) + .delete_all + end end end @@ -100,6 +107,65 @@ module Gitlab def authorized_users(project_id) ProjectAuthorization.select(:user_id).where(project_id: project_id) end + + def unauthorized_project_todos(project_id) + Todo.where(project_id: project_id) + .where('user_id NOT IN (?)', authorized_users(project_id)) + end + + def batch_remove_todos_cte(project_id, target_types = nil) + loop do + count = remove_todos_cte(project_id, target_types) + + break if count == 0 + end + end + + def remove_todos_cte(project_id, target_types = nil) + sql = [] + sql << with_all_todos_sql(project_id, target_types) + sql << as_deleted_sql + sql << "SELECT count(*) FROM deleted" + + result = Todo.connection.exec_query(sql.join(' ')) + result.rows[0][0].to_i + end + + def with_all_todos_sql(project_id, target_types = nil) + if target_types + table = Arel::Table.new(:todos) + in_target = table[:target_type].in(target_types) + target_types_sql = " AND #{in_target.to_sql}" + end + + <<-SQL + WITH all_todos AS ( + SELECT id + FROM "todos" + WHERE "todos"."project_id" = #{project_id} + AND (user_id NOT IN ( + SELECT "project_authorizations"."user_id" + FROM "project_authorizations" + WHERE "project_authorizations"."project_id" = #{project_id}) + #{target_types_sql} + ) + ), + SQL + end + + def as_deleted_sql + <<-SQL + deleted AS ( + DELETE FROM todos + WHERE id IN ( + SELECT id + FROM all_todos + LIMIT 5000 + ) + RETURNING id + ) + SQL + end end end end -- cgit v1.2.1 From a78bc746083db825f0b59296823d1d7f98df3335 Mon Sep 17 00:00:00 2001 From: Robert Speicher Date: Fri, 5 Oct 2018 11:12:24 -0500 Subject: Documentation for feature flags defaulting to on Closes https://gitlab.com/gitlab-org/gitlab-ee/issues/7883 --- .../rolling_out_changes_using_feature_flags.md | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/doc/development/rolling_out_changes_using_feature_flags.md b/doc/development/rolling_out_changes_using_feature_flags.md index 905aa26a40b..dada59ce242 100644 --- a/doc/development/rolling_out_changes_using_feature_flags.md +++ b/doc/development/rolling_out_changes_using_feature_flags.md @@ -151,3 +151,27 @@ most cases this will translate to a feature (with a feature flag) being shipped in RC1, followed by the feature flag being removed in RC2. This in turn means the feature will be stable by the time we publish a stable package around the 22nd of the month. + +## Undefined feature flags default to "on" + +By default, the [`Project#feature_available?`][project-fa], +[`Namespace#feature_available?`][namespace-fa] (EE), and +[`License.feature_available?`][license-fa] (EE) methods will check if the +specified feature is behind a feature flag. Unless the feature is explicitly +disabled or limited to a percentage of users, the feature flag check will +default to `true`. + +As an example, if you were to ship the backend half of a feature behind a flag, +you'd want to explicitly disable that flag until the frontend half is also ready +to be shipped. You can do this via ChatOps: + +``` +/chatops run feature set some_feature 0 +``` + +Note that you can do this at any time, even before the merge request using the +flag has been merged! + +[project-fa]: https://gitlab.com/gitlab-org/gitlab-ee/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/app/models/project_feature.rb#L63-68 +[namespace-fa]: https://gitlab.com/gitlab-org/gitlab-ee/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/ee/app/models/ee/namespace.rb#L71-85 +[license-fa]: https://gitlab.com/gitlab-org/gitlab-ee/blob/4cc1c62918aa4c31750cb21dfb1a6c3492d71080/ee/app/models/license.rb#L293-300 -- cgit v1.2.1 From d7f1b3dd697e2c560500098c684ab0e7ce3e003a Mon Sep 17 00:00:00 2001 From: Jeremy Watson Date: Fri, 5 Oct 2018 17:07:00 +0000 Subject: Updated existing spec for new 2 year timeframe --- spec/workers/prune_old_events_worker_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/workers/prune_old_events_worker_spec.rb b/spec/workers/prune_old_events_worker_spec.rb index ea974355050..fd5e4ca0ee4 100644 --- a/spec/workers/prune_old_events_worker_spec.rb +++ b/spec/workers/prune_old_events_worker_spec.rb @@ -4,11 +4,11 @@ describe PruneOldEventsWorker do describe '#perform' do let(:user) { create(:user) } - let!(:expired_event) { create(:event, :closed, author: user, created_at: 13.months.ago) } + let!(:expired_event) { create(:event, :closed, author: user, created_at: 25.months.ago) } let!(:not_expired_event) { create(:event, :closed, author: user, created_at: 1.day.ago) } - let!(:exactly_12_months_event) { create(:event, :closed, author: user, created_at: 12.months.ago) } + let!(:exactly_2_years_event) { create(:event, :closed, author: user, created_at: 2.years.ago) } - it 'prunes events older than 12 months' do + it 'prunes events older than 2 years' do expect { subject.perform }.to change { Event.count }.by(-1) expect(Event.find_by(id: expired_event.id)).to be_nil end -- cgit v1.2.1 From df833931b65499ea71937b7d20bddd85d5f27c54 Mon Sep 17 00:00:00 2001 From: Jeremy Watson Date: Fri, 5 Oct 2018 17:10:20 +0000 Subject: Renamed test events --- spec/workers/prune_old_events_worker_spec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/spec/workers/prune_old_events_worker_spec.rb b/spec/workers/prune_old_events_worker_spec.rb index fd5e4ca0ee4..d9fe08ceb50 100644 --- a/spec/workers/prune_old_events_worker_spec.rb +++ b/spec/workers/prune_old_events_worker_spec.rb @@ -5,8 +5,9 @@ describe PruneOldEventsWorker do let(:user) { create(:user) } let!(:expired_event) { create(:event, :closed, author: user, created_at: 25.months.ago) } - let!(:not_expired_event) { create(:event, :closed, author: user, created_at: 1.day.ago) } - let!(:exactly_2_years_event) { create(:event, :closed, author: user, created_at: 2.years.ago) } + let!(:not_expired_1_day_event) { create(:event, :closed, author: user, created_at: 1.day.ago) } + let!(:not_expired_13_month_event) { create(:event, :closed, author: user, created_at: 13.months.ago) } + let!(:not_expired_2_years_event) { create(:event, :closed, author: user, created_at: 2.years.ago) } it 'prunes events older than 2 years' do expect { subject.perform }.to change { Event.count }.by(-1) -- cgit v1.2.1 From 25874a878f1c4f2f21caf65a15fa8b83e1b04323 Mon Sep 17 00:00:00 2001 From: Jeremy Watson Date: Fri, 5 Oct 2018 17:12:31 +0000 Subject: Added new test and updated all event ids --- spec/workers/prune_old_events_worker_spec.rb | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/spec/workers/prune_old_events_worker_spec.rb b/spec/workers/prune_old_events_worker_spec.rb index d9fe08ceb50..b999a6fd5b6 100644 --- a/spec/workers/prune_old_events_worker_spec.rb +++ b/spec/workers/prune_old_events_worker_spec.rb @@ -16,12 +16,17 @@ describe PruneOldEventsWorker do it 'leaves fresh events' do subject.perform - expect(not_expired_event.reload).to be_present + expect(not_expired_1_day_event.reload).to be_present end - it 'leaves events from exactly 12 months ago' do + it 'leaves events from 13 months ago' do subject.perform - expect(exactly_12_months_event).to be_present + expect(not_expired_13_month_event.reload).to be_present + end + + it 'leaves events from 2 years ago' do + subject.perform + expect(not_expired_2_years_event).to be_present end end end -- cgit v1.2.1 From 71961608db04740faa086e6fd7b66e7f6e55fd4e Mon Sep 17 00:00:00 2001 From: Alessio Caiazza Date: Fri, 5 Oct 2018 20:28:47 +0200 Subject: Fix time dependent jobs spec --- spec/features/projects/jobs_spec.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 67b4a520184..2076ce7b4f7 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -568,12 +568,8 @@ describe 'Jobs', :clean_gitlab_redis_shared_state do end it 'shows delayed job', :js do - time_diff = [0, job.scheduled_at - Time.now].max - - expect(page).to have_content(job.detailed_status(user).illustration[:title]) expect(page).to have_content('This is a scheduled to run in') expect(page).to have_content("This job will automatically run after it's timer finishes.") - expect(page).to have_content(Time.at(time_diff).utc.strftime("%H:%M:%S")) expect(page).to have_link('Unschedule job') end -- cgit v1.2.1 From 869ec04904027550278cc84b132a0482dda44322 Mon Sep 17 00:00:00 2001 From: Gilbert Roulot Date: Fri, 5 Oct 2018 18:30:49 +0000 Subject: Document Security and Licence Management features permissions --- doc/user/permissions.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/doc/user/permissions.md b/doc/user/permissions.md index 4a1ca41ab97..4359592905d 100644 --- a/doc/user/permissions.md +++ b/doc/user/permissions.md @@ -42,6 +42,8 @@ The following table depicts the various user permission levels in a project. | See a job log | ✓ [^3] | ✓ | ✓ | ✓ | ✓ | | Download and browse job artifacts | ✓ [^3] | ✓ | ✓ | ✓ | ✓ | | View wiki pages | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | +| View license management reports **[ULTIMATE]** | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | +| View Security reports **[ULTIMATE]** | ✓ [^1] | ✓ | ✓ | ✓ | ✓ | | Pull project code | [^1] | ✓ | ✓ | ✓ | ✓ | | Download project | [^1] | ✓ | ✓ | ✓ | ✓ | | Assign issues | | ✓ | ✓ | ✓ | ✓ | @@ -57,6 +59,7 @@ The following table depicts the various user permission levels in a project. | See a list of merge requests | | ✓ | ✓ | ✓ | ✓ | | Manage related issues **[STARTER]** | | ✓ | ✓ | ✓ | ✓ | | Lock issue discussions | | ✓ | ✓ | ✓ | ✓ | +| Create issue from vulnerability **[ULTIMATE]** | | ✓ | ✓ | ✓ | ✓ | | Lock merge request discussions | | | ✓ | ✓ | ✓ | | Create new environments | | | ✓ | ✓ | ✓ | | Stop environments | | | ✓ | ✓ | ✓ | @@ -73,6 +76,9 @@ The following table depicts the various user permission levels in a project. | Update a container registry | | | ✓ | ✓ | ✓ | | Remove a container registry image | | | ✓ | ✓ | ✓ | | Create/edit/delete project milestones | | | ✓ | ✓ | ✓ | +| View approved/blacklisted licenses **[ULTIMATE]** | | | ✓ | ✓ | ✓ | +| Use security dashboard **[ULTIMATE]** | | | ✓ | ✓ | ✓ | +| Dismiss vulnerability **[ULTIMATE]** | | | ✓ | ✓ | ✓ | | Use environment terminals | | | | ✓ | ✓ | | Add new team members | | | | ✓ | ✓ | | Push to protected branches | | | | ✓ | ✓ | @@ -90,6 +96,7 @@ The following table depicts the various user permission levels in a project. | Manage GitLab Pages domains and certificates | | | | ✓ | ✓ | | Remove GitLab Pages | | | | | ✓ | | Manage clusters | | | | ✓ | ✓ | +| Manage license policy **[ULTIMATE]** | | | | ✓ | ✓ | | Edit comments (posted by any user) | | | | ✓ | ✓ | | Switch visibility level | | | | | ✓ | | Transfer project to another namespace | | | | | ✓ | @@ -98,7 +105,7 @@ The following table depicts the various user permission levels in a project. | Remove pages | | | | | ✓ | | Force push to protected branches [^4] | | | | | | | Remove protected branches [^4] | | | | | | -| View project Audit Events | | | | ✓ | ✓ | +| View project Audit Events | | | | ✓ | ✓ | ## Project features permissions -- cgit v1.2.1 From db39b0d6da85e4d33aeb4e0bd92f1b127f968964 Mon Sep 17 00:00:00 2001 From: Evan Read Date: Fri, 5 Oct 2018 18:37:31 +0000 Subject: Fix documentation for variables --- doc/ci/variables/where_variables_can_be_used.md | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/doc/ci/variables/where_variables_can_be_used.md b/doc/ci/variables/where_variables_can_be_used.md index b0d2ea6484d..4e8ce10c9cb 100644 --- a/doc/ci/variables/where_variables_can_be_used.md +++ b/doc/ci/variables/where_variables_can_be_used.md @@ -17,8 +17,8 @@ There are two places defined variables can be used. On the: | Definition | Can be expanded? | Expansion place | Description | |--------------------------------------|-------------------|-----------------|--------------| -| `environment:url` | yes | GitLab | The variable expansion is made by GitLab's [internal variable expansion mechanism](#gitlab-internal-variable-expansion-mechanism).
  • **Supported:** all variables defined for a job (project/group variables, variables from `.gitlab-ci.yml`, variables from triggers, variables from pipeline schedules)
  • **Not suported:** variables defined in Runner's `config.toml` and variables created in job's `script`
| -| `environment:name` | yes | GitLab | Similar to `environment:url`, but the variables expansion **doesn't support**:
  • variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`)
  • any other variables related to environment (currently only `CI_ENVIRONMENT_URL`)
  • [persisted variables](#persisted-variables)
| +| `environment:url` | yes | GitLab | The variable expansion is made by GitLab's [internal variable expansion mechanism](#gitlab-internal-variable-expansion-mechanism).
  • Supported: all variables defined for a job (project/group variables, variables from `.gitlab-ci.yml`, variables from triggers, variables from pipeline schedules)
  • Not suported: variables defined in Runner's `config.toml` and variables created in job's `script`
| +| `environment:name` | yes | GitLab | Similar to `environment:url`, but the variables expansion doesn't support:
  • variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`)
  • any other variables related to environment (currently only `CI_ENVIRONMENT_URL`)
  • [persisted variables](#persisted-variables)
| | `variables` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) | | `image` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) | | `services:[]` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) | @@ -26,7 +26,7 @@ There are two places defined variables can be used. On the: | `cache:key` | yes | Runner | The variable expansion is made by GitLab Runner's [internal variable expansion mechanism](#gitlab-runner-internal-variable-expansion-mechanism) | | `artifacts:name` | yes | Runner | The variable expansion is made by GitLab Runner's shell environment | | `script`, `before_script`, `after_script` | yes | Script execution shell | The variable expansion is made by the [execution shell environment](#execution-shell-environment) | -| `only:variables:[]`, `except:variables:[]` | no | n/a | The variable must be in the form of `$variable`.
**Not supported:**
  • variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`)
  • any other variables related to environment (currently only `CI_ENVIRONMENT_URL`)
  • [persisted variables](#persisted-variables)
| +| `only:variables:[]`, `except:variables:[]` | no | n/a | The variable must be in the form of `$variable`.
Not supported:
  • variables that are based on the environment's name (`CI_ENVIRONMENT_NAME`, `CI_ENVIRONMENT_SLUG`)
  • any other variables related to environment (currently only `CI_ENVIRONMENT_URL`)
  • [persisted variables](#persisted-variables)
| ### `config.toml` file @@ -55,9 +55,9 @@ since the expansion is done in GitLab before any Runner will get the job. ### GitLab Runner internal variable expansion mechanism -- **Supported:** project/group variables, `.gitlab-ci.yml` variables, `config.toml` variables, and +- Supported: project/group variables, `.gitlab-ci.yml` variables, `config.toml` variables, and variables from triggers, pipeline schedules, and manual pipelines. -- **Not supported:** variables defined inside of scripts (e.g., `export MY_VARIABLE="test"`). +- Not supported: variables defined inside of scripts (e.g., `export MY_VARIABLE="test"`). The Runner uses Go's `os.Expand()` method for variable expansion. It means that it will handle only variables defined as `$variable` and `${variable}`. What's also important, is that @@ -73,7 +73,7 @@ by bash/sh (leaving empty strings or some values depending whether the variables defined or not), but will not work with Windows' cmd/PowerShell, since these shells are using a different variables syntax. -**Supported:** +Supported: - The `script` may use all available variables that are default for the shell (e.g., `$PATH` which should be present in all bash/sh shells) and all variables defined by GitLab CI/CD (project/group variables, @@ -106,7 +106,9 @@ The following variables are known as "persisted": They are: -- **Supported** for all definitions as [described in the table](#gitlab-ci-yml-file) where the "Expansion place" is "Runner". -- **Not supported:** - - By the definitions [described in the table](#gitlab-ci-yml-file) where the "Expansion place" is "GitLab". +- Supported for definitions where the ["Expansion place"](#gitlab-ci-yml-file) is: + - Runner. + - Script execution shell. +- Not supported: + - For definitions where the ["Expansion place"](#gitlab-ci-yml-file) is GitLab. - In the `only` and `except` [variables expressions](README.md#variables-expressions). -- cgit v1.2.1 From cc339aa60877214820f9445336b5cf7766601176 Mon Sep 17 00:00:00 2001 From: Brett Walker Date: Fri, 5 Oct 2018 13:47:55 -0500 Subject: Update spec comment to point to correct issue --- spec/controllers/projects/merge_requests_controller_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 41c92a392aa..f2467bfd525 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -779,7 +779,7 @@ describe Projects::MergeRequestsController do end # we're trying to reduce the overall number of queries for this method. - # set a hard limit for now. https://gitlab.com/gitlab-org/gitlab-ce/issues/43109 + # set a hard limit for now. https://gitlab.com/gitlab-org/gitlab-ce/issues/52287 it 'keeps queries in check' do control_count = ActiveRecord::QueryRecorder.new { get_ci_environments_status }.count -- cgit v1.2.1 From 94fc0619365c7df284a29e76b1abc194a266efc2 Mon Sep 17 00:00:00 2001 From: Alessio Caiazza Date: Mon, 1 Oct 2018 15:03:48 +0200 Subject: Add timed incremental rollout to Auto DevOps Auto DevOps deployment strategies now supports timed incremental rollout. We are deprecating the usage of INCREMENTAL_ROLLOUT_ENABLED environment variable in Auto DevOps template. The new behavior will be driven by the INCREMENTAL_ROLLOUT_MODE variable that can either be manual (same as INCREMENTAL_ROLLOUT_ENABLED) or timed. Rollout deployments will be executed using a 5 minute delay between each job. --- app/models/project_auto_devops.rb | 20 +++-- .../settings/ci_cd/_autodevops_form.html.haml | 9 ++- .../autodevops-timed-incremental-rollout.yml | 5 ++ doc/topics/autodevops/index.md | 66 +++++++++++----- lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml | 88 ++++++++++++++-------- locale/gitlab.pot | 3 + spec/factories/project_auto_devops.rb | 12 ++- spec/models/project_auto_devops_spec.rb | 27 ++++--- 8 files changed, 162 insertions(+), 68 deletions(-) create mode 100644 changelogs/unreleased/autodevops-timed-incremental-rollout.yml diff --git a/app/models/project_auto_devops.rb b/app/models/project_auto_devops.rb index dc6736dd9cd..2253ad7b543 100644 --- a/app/models/project_auto_devops.rb +++ b/app/models/project_auto_devops.rb @@ -5,7 +5,8 @@ class ProjectAutoDevops < ActiveRecord::Base enum deploy_strategy: { continuous: 0, - manual: 1 + manual: 1, + timed_incremental: 2 } scope :enabled, -> { where(enabled: true) } @@ -30,10 +31,7 @@ class ProjectAutoDevops < ActiveRecord::Base value: domain.presence || instance_domain) end - if manual? - variables.append(key: 'STAGING_ENABLED', value: '1') - variables.append(key: 'INCREMENTAL_ROLLOUT_ENABLED', value: '1') - end + variables.concat(deployment_strategy_default_variables) end end @@ -51,4 +49,16 @@ class ProjectAutoDevops < ActiveRecord::Base !project.public? && !project.deploy_tokens.find_by(name: DeployToken::GITLAB_DEPLOY_TOKEN_NAME).present? end + + def deployment_strategy_default_variables + Gitlab::Ci::Variables::Collection.new.tap do |variables| + if manual? + variables.append(key: 'STAGING_ENABLED', value: '1') + variables.append(key: 'INCREMENTAL_ROLLOUT_ENABLED', value: '1') # deprecated + variables.append(key: 'INCREMENTAL_ROLLOUT_MODE', value: 'manual') + elsif timed_incremental? + variables.append(key: 'INCREMENTAL_ROLLOUT_MODE', value: 'timed') + end + end + end end diff --git a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml index ab92b757836..5ec5a06396e 100644 --- a/app/views/projects/settings/ci_cd/_autodevops_form.html.haml +++ b/app/views/projects/settings/ci_cd/_autodevops_form.html.haml @@ -39,10 +39,17 @@ = form.label :deploy_strategy_continuous, class: 'form-check-label' do = s_('CICD|Continuous deployment to production') = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'auto-deploy'), target: '_blank' + + .form-check + = form.radio_button :deploy_strategy, 'timed_incremental', class: 'form-check-input' + = form.label :deploy_strategy_timed_incremental, class: 'form-check-label' do + = s_('CICD|Continuous deployment to production using timed incremental rollout') + = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'timed-incremental-rollout-to-production'), target: '_blank' + .form-check = form.radio_button :deploy_strategy, 'manual', class: 'form-check-input' = form.label :deploy_strategy_manual, class: 'form-check-label' do = s_('CICD|Automatic deployment to staging, manual deployment to production') - = link_to icon('question-circle'), help_page_path('ci/environments.md', anchor: 'manually-deploying-to-environments'), target: '_blank' + = link_to icon('question-circle'), help_page_path('topics/autodevops/index.md', anchor: 'incremental-rollout-to-production'), target: '_blank' = f.submit _('Save changes'), class: "btn btn-success prepend-top-15" diff --git a/changelogs/unreleased/autodevops-timed-incremental-rollout.yml b/changelogs/unreleased/autodevops-timed-incremental-rollout.yml new file mode 100644 index 00000000000..72c7b41177d --- /dev/null +++ b/changelogs/unreleased/autodevops-timed-incremental-rollout.yml @@ -0,0 +1,5 @@ +--- +title: Add timed incremental rollout to Auto DevOps +merge_request: 22023 +author: +type: added diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index b5a9e469965..0d1ba3e8f9a 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -239,14 +239,19 @@ project's **Settings > CI/CD > Auto DevOps**. The available options are: -- **Continuous deployment to production** - enables [Auto Deploy](#auto-deploy) - by setting the [`STAGING_ENABLED`](#deploy-policy-for-staging-and-production-environments) and - [`INCREMENTAL_ROLLOUT_ENABLED`](#incremental-rollout-to-production) variables - to false. -- **Automatic deployment to staging, manual deployment to production** - sets the +- **Continuous deployment to production**: Enables [Auto Deploy](#auto-deploy) + with `master` branch directly deployed to production. +- **Continuous deployment to production using timed incremental rollout**: Sets the + [`INCREMENTAL_ROLLOUT_MODE`](#timed-incremental-rollout-to-production) variable + to `timed`, and production deployment will be executed with a 5 minute delay between + each increment in rollout. +- **Automatic deployment to staging, manual deployment to production**: Sets the [`STAGING_ENABLED`](#deploy-policy-for-staging-and-production-environments) and - [`INCREMENTAL_ROLLOUT_ENABLED`](#incremental-rollout-to-production) variables - to true, and the user is responsible for manually deploying to staging and production. + [`INCREMENTAL_ROLLOUT_MODE`](#incremental-rollout-to-production) variables + to `1` and `manual`. This means: + + - `master` branch is directly deployed to staging. + - Manual actions are provided for incremental rollout to production. ## Stages of Auto DevOps @@ -609,7 +614,7 @@ also be customized, and you can easily use a [custom buildpack](#custom-buildpac | `DB_MIGRATE` | From GitLab 11.4, this variable can be used to specify the command to run to migrate the application's PostgreSQL database. It runs inside the application pod. | | `STAGING_ENABLED` | From GitLab 10.8, this variable can be used to define a [deploy policy for staging and production environments](#deploy-policy-for-staging-and-production-environments). | | `CANARY_ENABLED` | From GitLab 11.0, this variable can be used to define a [deploy policy for canary environments](#deploy-policy-for-canary-environments). | -| `INCREMENTAL_ROLLOUT_ENABLED`| From GitLab 10.8, this variable can be used to enable an [incremental rollout](#incremental-rollout-to-production) of your application for the production environment. | +| `INCREMENTAL_ROLLOUT_MODE`| From GitLab 11.4, this variable, if present, can be used to enable an [incremental rollout](#incremental-rollout-to-production) of your application for the production environment.
Set to:
  • `manual`, for manual deployment jobs.
  • `timed`, for automatic rollout deployments with a 5 minute delay each one.
| | `TEST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `test` job. If the variable is present, the job will not be created. | | `CODE_QUALITY_DISABLED` | From GitLab 11.0, this variable can be used to disable the `codequality` job. If the variable is present, the job will not be created. | | `SAST_DISABLED` | From GitLab 11.0, this variable can be used to disable the `sast` job. If the variable is present, the job will not be created. | @@ -730,9 +735,8 @@ to use an incremental rollout to replace just a few pods with the latest code. This will allow you to first check how the app is behaving, and later manually increasing the rollout up to 100%. -If `INCREMENTAL_ROLLOUT_ENABLED` is defined in your project (e.g., set -`INCREMENTAL_ROLLOUT_ENABLED` to `1` as a secret variable), then instead of the -standard `production` job, 4 different +If `INCREMENTAL_ROLLOUT_MODE` is set to `manual` in your project, then instead +of the standard `production` job, 4 different [manual jobs](../../ci/pipelines.md#manual-actions-from-the-pipeline-graph) will be created: @@ -756,21 +760,45 @@ environment page. Below, you can see how the pipeline will look if the rollout or staging variables are defined. -- **Without `INCREMENTAL_ROLLOUT_ENABLED` and without `STAGING_ENABLED`** +Without `INCREMENTAL_ROLLOUT_MODE` and without `STAGING_ENABLED`: + +![Staging and rollout disabled](img/rollout_staging_disabled.png) + +Without `INCREMENTAL_ROLLOUT_MODE` and with `STAGING_ENABLED`: - ![Staging and rollout disabled](img/rollout_staging_disabled.png) +![Staging enabled](img/staging_enabled.png) -- **Without `INCREMENTAL_ROLLOUT_ENABLED` and with `STAGING_ENABLED`** +With `INCREMENTAL_ROLLOUT_MODE` set to `manual` and without `STAGING_ENABLED`: - ![Staging enabled](img/staging_enabled.png) +![Rollout enabled](img/rollout_enabled.png) -- **With `INCREMENTAL_ROLLOUT_ENABLED` and without `STAGING_ENABLED`** +With `INCREMENTAL_ROLLOUT_MODE` set to `manual` and with `STAGING_ENABLED` + +![Rollout and staging enabled](img/rollout_staging_enabled.png) + +CAUTION: **Caution:** +Before GitLab 11.4 this feature was enabled by the presence of the +`INCREMENTAL_ROLLOUT_ENABLED` environment variable. +This configuration is deprecated and will be removed in the future. + +#### Timed incremental rollout to production **[PREMIUM]** + +> [Introduced](https://gitlab.com/gitlab-org/gitlab-ee/issues/7545) in GitLab 11.4. + +TIP: **Tip:** +You can also set this inside your [project's settings](#deployment-strategy). - ![Rollout enabled](img/rollout_enabled.png) +This configuration based on +[incremental rollout to production](#incremental-rollout-to-production). -- **With `INCREMENTAL_ROLLOUT_ENABLED` and with `STAGING_ENABLED`** +Everything behaves the same way, except: - ![Rollout and staging enabled](img/rollout_staging_enabled.png) +- It's enabled by setting the `INCREMENTAL_ROLLOUT_MODE` variable to `timed`. +- Instead of the standard `production` job, the following jobs with a 5 minute delay between each are created: + 1. `timed rollout 10%` + 1. `timed rollout 25%` + 1. `timed rollout 50%` + 1. `timed rollout 100%` ## Currently supported languages diff --git a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml index ed4ec7e6385..72547c1b407 100644 --- a/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Auto-DevOps.gitlab-ci.yml @@ -25,8 +25,9 @@ # level, or manually added below. # # Continuous deployment to production is enabled by default. -# If you want to deploy to staging first, or enable incremental rollouts, -# set STAGING_ENABLED or INCREMENTAL_ROLLOUT_ENABLED environment variables. +# If you want to deploy to staging first, set STAGING_ENABLED environment variable. +# If you want to enable incremental rollout, either manual or time based, +# set INCREMENTAL_ROLLOUT_TYPE environment variable to "manual" or "timed". # If you want to use canary deployments, set CANARY_ENABLED environment variable. # # If Auto DevOps fails to detect the proper buildpack, or if you want to @@ -61,6 +62,10 @@ stages: - staging - canary - production + - incremental rollout 10% + - incremental rollout 25% + - incremental rollout 50% + - incremental rollout 100% - performance - cleanup @@ -282,11 +287,6 @@ stop_review: variables: - $REVIEW_DISABLED -# Keys that start with a dot (.) will not be processed by GitLab CI. -# Staging and canary jobs are disabled by default, to enable them -# remove the dot (.) before the job name. -# https://docs.gitlab.com/ee/ci/yaml/README.html#hidden-keys - # Staging deploys are disabled by default since # continuous deployment to production is enabled by default # If you prefer to automatically deploy to staging and @@ -368,6 +368,7 @@ production: - $STAGING_ENABLED - $CANARY_ENABLED - $INCREMENTAL_ROLLOUT_ENABLED + - $INCREMENTAL_ROLLOUT_MODE production_manual: <<: *production_template @@ -383,11 +384,11 @@ production_manual: except: variables: - $INCREMENTAL_ROLLOUT_ENABLED + - $INCREMENTAL_ROLLOUT_MODE # This job implements incremental rollout on for every push to `master`. .rollout: &rollout_template - stage: production script: - check_kube_domain - install_dependencies @@ -405,52 +406,77 @@ production_manual: artifacts: paths: [environment_url.txt] -rollout 10%: +.manual_rollout_template: &manual_rollout_template <<: *rollout_template - variables: - ROLLOUT_PERCENTAGE: 10 + stage: production when: manual + # This selectors are backward compatible mode with $INCREMENTAL_ROLLOUT_ENABLED (before 11.4) only: refs: - master kubernetes: active variables: + - $INCREMENTAL_ROLLOUT_MODE == "manual" - $INCREMENTAL_ROLLOUT_ENABLED + except: + variables: + - $INCREMENTAL_ROLLOUT_MODE == "timed" -rollout 25%: +.timed_rollout_template: &timed_rollout_template <<: *rollout_template - variables: - ROLLOUT_PERCENTAGE: 25 - when: manual + when: delayed + start_in: 5 minutes only: refs: - master kubernetes: active variables: - - $INCREMENTAL_ROLLOUT_ENABLED + - $INCREMENTAL_ROLLOUT_MODE == "timed" + +timed rollout 10%: + <<: *timed_rollout_template + stage: incremental rollout 10% + variables: + ROLLOUT_PERCENTAGE: 10 + +timed rollout 25%: + <<: *timed_rollout_template + stage: incremental rollout 25% + variables: + ROLLOUT_PERCENTAGE: 25 + +timed rollout 50%: + <<: *timed_rollout_template + stage: incremental rollout 50% + variables: + ROLLOUT_PERCENTAGE: 50 + +timed rollout 100%: + <<: *timed_rollout_template + <<: *production_template + stage: incremental rollout 100% + variables: + ROLLOUT_PERCENTAGE: 100 + +rollout 10%: + <<: *manual_rollout_template + variables: + ROLLOUT_PERCENTAGE: 10 + +rollout 25%: + <<: *manual_rollout_template + variables: + ROLLOUT_PERCENTAGE: 25 rollout 50%: - <<: *rollout_template + <<: *manual_rollout_template variables: ROLLOUT_PERCENTAGE: 50 - when: manual - only: - refs: - - master - kubernetes: active - variables: - - $INCREMENTAL_ROLLOUT_ENABLED rollout 100%: + <<: *manual_rollout_template <<: *production_template - when: manual allow_failure: false - only: - refs: - - master - kubernetes: active - variables: - - $INCREMENTAL_ROLLOUT_ENABLED # --------------------------------------------------------------------------- diff --git a/locale/gitlab.pot b/locale/gitlab.pot index d16a72b76b8..20b70bc2cd3 100644 --- a/locale/gitlab.pot +++ b/locale/gitlab.pot @@ -1081,6 +1081,9 @@ msgstr "" msgid "CICD|Continuous deployment to production" msgstr "" +msgid "CICD|Continuous deployment to production using timed incremental rollout" +msgstr "" + msgid "CICD|Default to Auto DevOps pipeline" msgstr "" diff --git a/spec/factories/project_auto_devops.rb b/spec/factories/project_auto_devops.rb index b77f702f9e1..75ac7cc7687 100644 --- a/spec/factories/project_auto_devops.rb +++ b/spec/factories/project_auto_devops.rb @@ -5,8 +5,16 @@ FactoryBot.define do domain "example.com" deploy_strategy :continuous - trait :manual do - deploy_strategy :manual + trait :continuous_deployment do + deploy_strategy ProjectAutoDevops.deploy_strategies[:continuous] # rubocop:disable FactoryBot/DynamicAttributeDefinedStatically + end + + trait :manual_deployment do + deploy_strategy ProjectAutoDevops.deploy_strategies[:manual] # rubocop:disable FactoryBot/DynamicAttributeDefinedStatically + end + + trait :timed_incremental_deployment do + deploy_strategy ProjectAutoDevops.deploy_strategies[:timed_incremental] # rubocop:disable FactoryBot/DynamicAttributeDefinedStatically end trait :disabled do diff --git a/spec/models/project_auto_devops_spec.rb b/spec/models/project_auto_devops_spec.rb index 797d767465a..342798f730b 100644 --- a/spec/models/project_auto_devops_spec.rb +++ b/spec/models/project_auto_devops_spec.rb @@ -70,24 +70,31 @@ describe ProjectAutoDevops do end context 'when deploy_strategy is manual' do - let(:domain) { 'example.com' } - - before do - auto_devops.deploy_strategy = 'manual' + let(:auto_devops) { build_stubbed(:project_auto_devops, :manual_deployment, project: project) } + let(:expected_variables) do + [ + { key: 'INCREMENTAL_ROLLOUT_MODE', value: 'manual' }, + { key: 'STAGING_ENABLED', value: '1' }, + { key: 'INCREMENTAL_ROLLOUT_ENABLED', value: '1' } + ] end + it { expect(auto_devops.predefined_variables).to include(*expected_variables) } + end + + context 'when deploy_strategy is continuous' do + let(:auto_devops) { build_stubbed(:project_auto_devops, :continuous_deployment, project: project) } + it do expect(auto_devops.predefined_variables.map { |var| var[:key] }) - .to include("STAGING_ENABLED", "INCREMENTAL_ROLLOUT_ENABLED") + .not_to include("STAGING_ENABLED", "INCREMENTAL_ROLLOUT_ENABLED") end end - context 'when deploy_strategy is continuous' do - let(:domain) { 'example.com' } + context 'when deploy_strategy is timed_incremental' do + let(:auto_devops) { build_stubbed(:project_auto_devops, :timed_incremental_deployment, project: project) } - before do - auto_devops.deploy_strategy = 'continuous' - end + it { expect(auto_devops.predefined_variables).to include(key: 'INCREMENTAL_ROLLOUT_MODE', value: 'timed') } it do expect(auto_devops.predefined_variables.map { |var| var[:key] }) -- cgit v1.2.1 From ddc30871bc56e7aae87d7f7fb3f5b60767a57a76 Mon Sep 17 00:00:00 2001 From: Ben Bodenmiller Date: Fri, 5 Oct 2018 19:39:44 +0000 Subject: docs: cleanup term settings wording --- doc/user/admin_area/settings/terms.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/user/admin_area/settings/terms.md b/doc/user/admin_area/settings/terms.md index aa817c9a209..e2290bf0598 100644 --- a/doc/user/admin_area/settings/terms.md +++ b/doc/user/admin_area/settings/terms.md @@ -35,17 +35,17 @@ continue their registration afterwards. ## Accepting terms -When this feature was enabled, the users that have not accepted the +When this feature is enabled, the users that have not accepted the terms of service will be presented with a screen where they can either accept or decline the terms. ![Respond to terms](img/respond_to_terms.png) -When the user accepts the terms, they will be directed to where they +If the user accepts the terms, they will be directed to where they were going. After a sign-in or sign-up this will most likely be the dashboard. -When the user was already logged in when the feature was turned on, +If the user was already logged in when the feature was turned on, they will be asked to accept the terms on their next interaction. -When a user declines the terms, they will be signed out. +If a user declines the terms, they will be signed out. -- cgit v1.2.1 From 11460c4a828de63c4f77401880b269a7cfdcb28c Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Tue, 2 Oct 2018 20:23:46 +0000 Subject: Add note that hostname:port need to match for `DOCKER_AUTH_CONFIG` --- doc/ci/docker/using_docker_images.md | 32 +++++++++++++++++++------------- 1 file changed, 19 insertions(+), 13 deletions(-) diff --git a/doc/ci/docker/using_docker_images.md b/doc/ci/docker/using_docker_images.md index 9abedcc6acb..31649ee2792 100644 --- a/doc/ci/docker/using_docker_images.md +++ b/doc/ci/docker/using_docker_images.md @@ -461,25 +461,25 @@ that runner. > - If the repository is private you need to authenticate your GitLab Runner in the > registry. Learn more about how [GitLab Runner works in this case][runner-priv-reg]. -As an example, let's assume that you want to use the `registry.example.com/private/image:latest` +As an example, let's assume that you want to use the `registry.example.com:5000/private/image:latest` image which is private and requires you to login into a private container registry. Let's also assume that these are the login credentials: -| Key | Value | -|----------|----------------------| -| registry | registry.example.com | -| username | my_username | -| password | my_password | +| Key | Value | +|----------|---------------------------| +| registry | registry.example.com:5000 | +| username | my_username | +| password | my_password | -To configure access for `registry.example.com`, follow these steps: +To configure access for `registry.example.com:5000`, follow these steps: 1. Find what the value of `DOCKER_AUTH_CONFIG` should be. There are two ways to accomplish this: - **First way -** Do a `docker login` on your local machine: ```bash - docker login registry.example.com --username my_username --password my_password + docker login registry.example.com:5000 --username my_username --password my_password ``` Then copy the content of `~/.docker/config.json`. @@ -503,7 +503,7 @@ To configure access for `registry.example.com`, follow these steps: ```json { "auths": { - "registry.example.com": { + "registry.example.com:5000": { "auth": "bXlfdXNlcm5hbWU6bXlfcGFzc3dvcmQ=" } } @@ -515,22 +515,28 @@ To configure access for `registry.example.com`, follow these steps: registry from it: ```bash - docker logout registry.example.com + docker logout registry.example.com:5000 ``` -1. You can now use any private image from `registry.example.com` defined in +1. You can now use any private image from `registry.example.com:5000` defined in `image` and/or `services` in your `.gitlab-ci.yml` file: ```yaml - image: my.registry.tld:5000/namespace/image:tag + image: registry.example.com:5000/namespace/image:tag ``` - In the example above, GitLab Runner will look at `my.registry.tld:5000` for the + In the example above, GitLab Runner will look at `registry.example.com:5000` for the image `namespace/image:tag`. You can add configuration for as many registries as you want, adding more registries to the `"auths"` hash as described above. +NOTE: **Note:** The full `hostname:port` combination is required everywhere +for the Runner to match the `DOCKER_AUTH_CONFIG`. For example, if +`registry.example.com:5000/namespace/image:tag` is specified in `.gitlab-ci.yml`, +then the `DOCKER_AUTH_CONFIG` must also specify `registry.example.com:5000`. +Specifying only `registry.example.com` will not work. + ## Configuring services Many services accept environment variables which allow you to easily change -- cgit v1.2.1 From 54579baf533d9102ddc3c1047e7420d7fbc8d7db Mon Sep 17 00:00:00 2001 From: Jason Colyer Date: Fri, 5 Oct 2018 15:47:15 -0500 Subject: Added note to clarify values for hook-name.d --- doc/administration/custom_hooks.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/administration/custom_hooks.md b/doc/administration/custom_hooks.md index 9b0fabb9259..d64c2968616 100644 --- a/doc/administration/custom_hooks.md +++ b/doc/administration/custom_hooks.md @@ -69,6 +69,8 @@ pattern (`*~`). The hooks of the same type are executed in order and execution stops on the first script exiting with a non-zero value. +> **Note:** In the above examples, `.d` would need to be either `pre-receive.d`, `post-receive.d`, or `update.d` to work properly. Any other names would be ignored. + ## Custom error messages > [Introduced][5073] in GitLab 8.10. -- cgit v1.2.1 From 7b8b2563c4d6b0090fd33fd50aad81561519f9cd Mon Sep 17 00:00:00 2001 From: Gabriel Mazetto Date: Mon, 1 Oct 2018 23:50:48 -0300 Subject: Prepare admin/projects/show view to allow EE specific feature In EE it will render a Geo Status widget when Geo is enabled and it is in a secondary node. Also added minimal specs to that action. --- app/views/admin/projects/show.html.haml | 2 ++ spec/controllers/admin/projects_controller_spec.rb | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 32f6eefc16a..fefb4c7455d 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -112,6 +112,8 @@ = visibility_level_icon(@project.visibility_level) = visibility_level_label(@project.visibility_level) + = render_if_exists 'admin/projects/geo_status_widget', locals: { project: @project } + .card .card-header Transfer project diff --git a/spec/controllers/admin/projects_controller_spec.rb b/spec/controllers/admin/projects_controller_spec.rb index cc200b9fed9..ee1aff09bdf 100644 --- a/spec/controllers/admin/projects_controller_spec.rb +++ b/spec/controllers/admin/projects_controller_spec.rb @@ -42,4 +42,15 @@ describe Admin::ProjectsController do expect { get :index }.not_to exceed_query_limit(control_count) end end + + describe 'GET /projects/:id' do + render_views + + it 'renders show page' do + get :show, namespace_id: project.namespace.path, id: project.path + + expect(response).to have_gitlab_http_status(200) + expect(response.body).to match(project.name) + end + end end -- cgit v1.2.1 From 72273d3d508c858710a0eb45920c41f6cfc31112 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Fri, 5 Oct 2018 12:20:32 -0300 Subject: Remove dead code on main MR page --- .../projects/merge_requests_controller.rb | 6 ------ app/helpers/notes_helper.rb | 2 +- .../unreleased/osw-remove-dead-code-on-mr-show.yml | 5 +++++ .../projects/merge_requests_controller_spec.rb | 22 ---------------------- 4 files changed, 6 insertions(+), 29 deletions(-) create mode 100644 changelogs/unreleased/osw-remove-dead-code-on-mr-show.yml diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index dfb69de650b..ce9fe6f70fe 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -44,12 +44,6 @@ class Projects::MergeRequestsController < Projects::MergeRequests::ApplicationCo @noteable = @merge_request @commits_count = @merge_request.commits_count - # TODO cleanup- Fatih Simon Create an issue to remove these after the refactoring - # we no longer render notes here. I see it will require a small frontend refactoring, - # since we gather some data from this collection. - @discussions = @merge_request.discussions - @notes = prepare_notes_for_rendering(@discussions.flat_map(&:notes), @noteable) - labels set_pipeline_variables diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index a80c8f273a8..52a29bf01dc 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -142,7 +142,7 @@ module NotesHelper def initial_notes_data(autocomplete) { notesUrl: notes_url, - notesIds: @notes.map(&:id), + notesIds: @noteable.notes.pluck(:id), # rubocop: disable CodeReuse/ActiveRecord now: Time.now.to_i, diffView: diff_view, enableGFM: { diff --git a/changelogs/unreleased/osw-remove-dead-code-on-mr-show.yml b/changelogs/unreleased/osw-remove-dead-code-on-mr-show.yml new file mode 100644 index 00000000000..d4e2641daf5 --- /dev/null +++ b/changelogs/unreleased/osw-remove-dead-code-on-mr-show.yml @@ -0,0 +1,5 @@ +--- +title: Removes expensive dead code on main MR page request +merge_request: 22153 +author: +type: performance diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb index 7446e0650f7..77c14e0d1b9 100644 --- a/spec/controllers/projects/merge_requests_controller_spec.rb +++ b/spec/controllers/projects/merge_requests_controller_spec.rb @@ -76,28 +76,6 @@ describe Projects::MergeRequestsController do expect(response).to be_success end - context "loads notes" do - let(:first_contributor) { create(:user) } - let(:contributor) { create(:user) } - let(:merge_request) { create(:merge_request, author: first_contributor, target_project: project, source_project: project) } - let(:contributor_merge_request) { create(:merge_request, :merged, author: contributor, target_project: project, source_project: project) } - # the order here is important - # as the controller reloads these from DB, references doesn't correspond after - let!(:first_contributor_note) { create(:note, author: first_contributor, noteable: merge_request, project: project) } - let!(:contributor_note) { create(:note, author: contributor, noteable: merge_request, project: project) } - let!(:owner_note) { create(:note, author: user, noteable: merge_request, project: project) } - - it "with special_role FIRST_TIME_CONTRIBUTOR" do - go(format: :html) - - notes = assigns(:notes) - expect(notes).to match(a_collection_containing_exactly(an_object_having_attributes(special_role: Note::SpecialRole::FIRST_TIME_CONTRIBUTOR), - an_object_having_attributes(special_role: nil), - an_object_having_attributes(special_role: nil) - )) - end - end - context "that is invalid" do let(:merge_request) { create(:invalid_merge_request, target_project: project, source_project: project) } -- cgit v1.2.1 From caf10464c0e78817c91ff01e3be5f2f9472aba19 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Fri, 5 Oct 2018 22:36:20 +0000 Subject: Fix LFS uploaded images not being rendered --- .../javascripts/diffs/components/diff_content.vue | 2 +- app/serializers/diff_file_entity.rb | 4 ++++ app/serializers/diff_viewer_entity.rb | 7 +++++++ .../unreleased/osw-fix-lfs-images-not-rendering.yml | 5 +++++ spec/fixtures/api/schemas/entities/diff_viewer.json | 8 ++++++++ .../javascripts/diffs/components/diff_content_spec.js | 5 ++--- spec/javascripts/diffs/components/diff_file_spec.js | 5 ++--- spec/javascripts/diffs/mock_data/diff_file.js | 3 +++ spec/serializers/diff_file_entity_spec.rb | 5 +++++ spec/serializers/diff_viewer_entity_spec.rb | 19 +++++++++++++++++++ 10 files changed, 56 insertions(+), 7 deletions(-) create mode 100644 app/serializers/diff_viewer_entity.rb create mode 100644 changelogs/unreleased/osw-fix-lfs-images-not-rendering.yml create mode 100644 spec/fixtures/api/schemas/entities/diff_viewer.json create mode 100644 spec/serializers/diff_viewer_entity_spec.rb diff --git a/app/assets/javascripts/diffs/components/diff_content.vue b/app/assets/javascripts/diffs/components/diff_content.vue index 02d5be1821b..fb5556e3cd7 100644 --- a/app/assets/javascripts/diffs/components/diff_content.vue +++ b/app/assets/javascripts/diffs/components/diff_content.vue @@ -28,7 +28,7 @@ export default { return diffModes[diffModeKey] || diffModes.replaced; }, isTextFile() { - return this.diffFile.text; + return this.diffFile.viewer.name === 'text'; }, }, }; diff --git a/app/serializers/diff_file_entity.rb b/app/serializers/diff_file_entity.rb index c193ed10fef..63ea8e8f95f 100644 --- a/app/serializers/diff_file_entity.rb +++ b/app/serializers/diff_file_entity.rb @@ -116,6 +116,10 @@ class DiffFileEntity < Grape::Entity project_blob_path(project, tree_join(diff_file.content_sha, diff_file.new_path)) end + expose :viewer, using: DiffViewerEntity do |diff_file| + diff_file.rich_viewer || diff_file.simple_viewer + end + expose :replaced_view_path, if: -> (_, options) { options[:merge_request] } do |diff_file| image_diff = diff_file.rich_viewer && diff_file.rich_viewer.partial_name == 'image' image_replaced = diff_file.old_content_sha && diff_file.old_content_sha != diff_file.content_sha diff --git a/app/serializers/diff_viewer_entity.rb b/app/serializers/diff_viewer_entity.rb new file mode 100644 index 00000000000..27fba03cb3f --- /dev/null +++ b/app/serializers/diff_viewer_entity.rb @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +class DiffViewerEntity < Grape::Entity + # Partial name refers directly to a Rails feature, let's avoid + # using this on the frontend. + expose :partial_name, as: :name +end diff --git a/changelogs/unreleased/osw-fix-lfs-images-not-rendering.yml b/changelogs/unreleased/osw-fix-lfs-images-not-rendering.yml new file mode 100644 index 00000000000..5dde22d3158 --- /dev/null +++ b/changelogs/unreleased/osw-fix-lfs-images-not-rendering.yml @@ -0,0 +1,5 @@ +--- +title: Fix LFS uploaded images not being rendered +merge_request: 22092 +author: +type: fixed diff --git a/spec/fixtures/api/schemas/entities/diff_viewer.json b/spec/fixtures/api/schemas/entities/diff_viewer.json new file mode 100644 index 00000000000..19780f49a88 --- /dev/null +++ b/spec/fixtures/api/schemas/entities/diff_viewer.json @@ -0,0 +1,8 @@ +{ + "type": "object", + "required": ["name"], + "properties": { + "name": { "type": ["string"] } + }, + "additionalProperties": false +} diff --git a/spec/javascripts/diffs/components/diff_content_spec.js b/spec/javascripts/diffs/components/diff_content_spec.js index dea600a783a..67f7b569f47 100644 --- a/spec/javascripts/diffs/components/diff_content_spec.js +++ b/spec/javascripts/diffs/components/diff_content_spec.js @@ -8,13 +8,12 @@ import diffFileMockData from '../mock_data/diff_file'; describe('DiffContent', () => { const Component = Vue.extend(DiffContentComponent); let vm; - const getDiffFileMock = () => Object.assign({}, diffFileMockData); beforeEach(() => { vm = mountComponentWithStore(Component, { store, props: { - diffFile: getDiffFileMock(), + diffFile: JSON.parse(JSON.stringify(diffFileMockData)), }, }); }); @@ -43,7 +42,7 @@ describe('DiffContent', () => { describe('Non-Text diffs', () => { beforeEach(() => { - vm.diffFile.text = false; + vm.diffFile.viewer.name = 'image'; }); describe('image diff', () => { diff --git a/spec/javascripts/diffs/components/diff_file_spec.js b/spec/javascripts/diffs/components/diff_file_spec.js index 2a52cd2b179..13859f43e98 100644 --- a/spec/javascripts/diffs/components/diff_file_spec.js +++ b/spec/javascripts/diffs/components/diff_file_spec.js @@ -6,11 +6,10 @@ import diffFileMockData from '../mock_data/diff_file'; describe('DiffFile', () => { let vm; - const getDiffFileMock = () => Object.assign({}, diffFileMockData); beforeEach(() => { vm = createComponentWithStore(Vue.extend(DiffFileComponent), store, { - file: getDiffFileMock(), + file: JSON.parse(JSON.stringify(diffFileMockData)), canCurrentUserFork: false, }).$mount(); }); @@ -18,7 +17,7 @@ describe('DiffFile', () => { describe('template', () => { it('should render component with file header, file content components', () => { const el = vm.$el; - const { fileHash, filePath } = diffFileMockData; + const { fileHash, filePath } = vm.file; expect(el.id).toEqual(fileHash); expect(el.classList.contains('diff-file')).toEqual(true); diff --git a/spec/javascripts/diffs/mock_data/diff_file.js b/spec/javascripts/diffs/mock_data/diff_file.js index 2aa2f8f3528..d7bc0dbe431 100644 --- a/spec/javascripts/diffs/mock_data/diff_file.js +++ b/spec/javascripts/diffs/mock_data/diff_file.js @@ -23,6 +23,9 @@ export default { aMode: '100644', bMode: '100644', text: true, + viewer: { + name: 'text', + }, addedLines: 2, removedLines: 0, diffRefs: { diff --git a/spec/serializers/diff_file_entity_spec.rb b/spec/serializers/diff_file_entity_spec.rb index 3d90ce44dfb..7497b8f27bd 100644 --- a/spec/serializers/diff_file_entity_spec.rb +++ b/spec/serializers/diff_file_entity_spec.rb @@ -26,6 +26,11 @@ describe DiffFileEntity do ) end + it 'includes viewer' do + expect(subject[:viewer].with_indifferent_access) + .to match_schema('entities/diff_viewer') + end + # Converted diff files from GitHub import does not contain blob file # and content sha. context 'when diff file does not have a blob and content sha' do diff --git a/spec/serializers/diff_viewer_entity_spec.rb b/spec/serializers/diff_viewer_entity_spec.rb new file mode 100644 index 00000000000..66ac6ef2adc --- /dev/null +++ b/spec/serializers/diff_viewer_entity_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe DiffViewerEntity do + include RepoHelpers + + let(:project) { create(:project, :repository) } + let(:repository) { project.repository } + let(:commit) { project.commit(sample_commit.id) } + let(:diff_refs) { commit.diff_refs } + let(:diff) { commit.raw_diffs.first } + let(:diff_file) { Gitlab::Diff::File.new(diff, diff_refs: diff_refs, repository: repository) } + let(:viewer) { diff_file.simple_viewer } + + subject { described_class.new(viewer).as_json } + + it 'serializes diff file viewer' do + expect(subject.with_indifferent_access).to match_schema('entities/diff_viewer') + end +end -- cgit v1.2.1 From ef6f2b28237c76a75bd0903f11d88c5283411e3a Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Thu, 27 Sep 2018 23:53:01 +0300 Subject: Update operations metrics empty state --- app/views/projects/environments/empty.html.haml | 18 +++++++++--------- .../update-operations-metrics-empty-state.yml | 5 +++++ 2 files changed, 14 insertions(+), 9 deletions(-) create mode 100644 changelogs/unreleased/update-operations-metrics-empty-state.yml diff --git a/app/views/projects/environments/empty.html.haml b/app/views/projects/environments/empty.html.haml index 1413930ebdb..129dbbf4e56 100644 --- a/app/views/projects/environments/empty.html.haml +++ b/app/views/projects/environments/empty.html.haml @@ -1,14 +1,14 @@ - page_title _("Metrics") -.row +.row.empty-state .col-sm-12 .svg-content = image_tag 'illustrations/operations_metrics_empty.svg' -.row.empty-environments - .col-sm-12.text-center - %h4 - = s_('Metrics|No deployed environments') - .state-description - = s_('Metrics|Check out the CI/CD documentation on deploying to an environment') - .prepend-top-10 - = link_to s_("Metrics|Learn about environments"), help_page_path('ci/environments'), class: 'btn btn-success' + .col-12 + .text-content + %h4.text-center + = s_('Metrics|No deployed environments') + %p.state-description + = s_('Metrics|Check out the CI/CD documentation on deploying to an environment') + .text-center + = link_to s_("Metrics|Learn about environments"), help_page_path('ci/environments'), class: 'btn btn-success' diff --git a/changelogs/unreleased/update-operations-metrics-empty-state.yml b/changelogs/unreleased/update-operations-metrics-empty-state.yml new file mode 100644 index 00000000000..51f3935b769 --- /dev/null +++ b/changelogs/unreleased/update-operations-metrics-empty-state.yml @@ -0,0 +1,5 @@ +--- +title: Update operations metrics empty state +merge_request: 21974 +author: George Tsiolis +type: other -- cgit v1.2.1 From b0ae67a4ecb2d11c1ce6ea286adfaa1b4e2a3449 Mon Sep 17 00:00:00 2001 From: Jeremy Watson Date: Sat, 6 Oct 2018 01:02:29 +0000 Subject: Added changelog --- changelogs/unreleased/copy-changes-for-abuse-clarity.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/copy-changes-for-abuse-clarity.yml diff --git a/changelogs/unreleased/copy-changes-for-abuse-clarity.yml b/changelogs/unreleased/copy-changes-for-abuse-clarity.yml new file mode 100644 index 00000000000..00d9fec5e42 --- /dev/null +++ b/changelogs/unreleased/copy-changes-for-abuse-clarity.yml @@ -0,0 +1,5 @@ +--- +title: Increased retained event data by extending events pruner timeframe to 2 years +merge_request: 22145 +author: +type: changed -- cgit v1.2.1 From 71ffca52bd8b923d056aebfa2becbf8a5c271686 Mon Sep 17 00:00:00 2001 From: Paul Giberson Date: Sat, 6 Oct 2018 05:44:07 +0000 Subject: Updates Laravel.gitlab-ci.yml template Updates to add gnupgp (required for node setup) Upgrades node install to version 8 --- lib/gitlab/ci/templates/Laravel.gitlab-ci.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml b/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml index 0688f77a1d2..d0cad285572 100644 --- a/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml +++ b/lib/gitlab/ci/templates/Laravel.gitlab-ci.yml @@ -25,9 +25,12 @@ before_script: # Update packages - apt-get update -yqq - # Upgrade to Node 7 - - curl -sL https://deb.nodesource.com/setup_7.x | bash - - + # Prep for Node + - apt-get install gnupg -yqq + + # Upgrade to Node 8 + - curl -sL https://deb.nodesource.com/setup_8.x | bash - + # Install dependencies - apt-get install git nodejs libcurl4-gnutls-dev libicu-dev libmcrypt-dev libvpx-dev libjpeg-dev libpng-dev libxpm-dev zlib1g-dev libfreetype6-dev libxml2-dev libexpat1-dev libbz2-dev libgmp3-dev libldap2-dev unixodbc-dev libpq-dev libsqlite3-dev libaspell-dev libsnmp-dev libpcre3-dev libtidy-dev -yqq -- cgit v1.2.1 From c812d17e74ba521145716cc3ddf7a5aae205a77c Mon Sep 17 00:00:00 2001 From: James Lopez Date: Sat, 6 Oct 2018 07:52:13 +0000 Subject: Update GITALY_SERVER_VERSION --- GITALY_SERVER_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION index ee476f3ae84..b1fa68e5df9 100644 --- a/GITALY_SERVER_VERSION +++ b/GITALY_SERVER_VERSION @@ -1 +1 @@ -0.124.0 +0.125.0 -- cgit v1.2.1 From 533a6fef314a3ad0b007b2ce0885f148d29a4081 Mon Sep 17 00:00:00 2001 From: Jeremy Watson Date: Sat, 6 Oct 2018 10:09:49 +0000 Subject: Updated code comments for clarity based on 2 years --- app/workers/prune_old_events_worker.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/workers/prune_old_events_worker.rb b/app/workers/prune_old_events_worker.rb index 958d613b859..dc4b7670131 100644 --- a/app/workers/prune_old_events_worker.rb +++ b/app/workers/prune_old_events_worker.rb @@ -6,9 +6,8 @@ class PruneOldEventsWorker # rubocop: disable CodeReuse/ActiveRecord def perform - # Contribution calendar shows maximum 12 months of events. - # Double nested query is used because MySQL doesn't allow DELETE subqueries - # on the same table. + # Contribution calendar shows maximum 12 months of events, we retain 2 years for data integrity. + # Double nested query is used because MySQL doesn't allow DELETE subqueries on the same table. Event.unscoped.where( '(id IN (SELECT id FROM (?) ids_to_remove))', Event.unscoped.where( -- cgit v1.2.1 From 6170bfd8f5837f2d9d416559fdedab6ecbb16b84 Mon Sep 17 00:00:00 2001 From: Jeremy Watson Date: Sat, 6 Oct 2018 10:12:30 +0000 Subject: Updated docs to reflect new 2 year period --- doc/api/events.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/events.md b/doc/api/events.md index fb5ebb71a86..cd84b32029e 100644 --- a/doc/api/events.md +++ b/doc/api/events.md @@ -44,7 +44,7 @@ YYYY-MM-DD ### Event Time Period Limit -GitLab removes events older than 1 year from the events table for performance reasons. The range of 1 year was chosen because user contribution calendars only show contributions of the past year. +GitLab removes events older than 2 years from the events table for performance reasons. ## List currently authenticated user's events -- cgit v1.2.1 From 6c07c24c8ddedec6a8326736c12be48a087526ce Mon Sep 17 00:00:00 2001 From: Jasper Maes Date: Sat, 6 Oct 2018 13:43:14 +0200 Subject: Rails5: fix user edit profile clear status spec --- changelogs/unreleased/rails5-user-status-spec.yml | 5 +++++ spec/features/profiles/user_edit_profile_spec.rb | 2 ++ 2 files changed, 7 insertions(+) create mode 100644 changelogs/unreleased/rails5-user-status-spec.yml diff --git a/changelogs/unreleased/rails5-user-status-spec.yml b/changelogs/unreleased/rails5-user-status-spec.yml new file mode 100644 index 00000000000..818d480e9fc --- /dev/null +++ b/changelogs/unreleased/rails5-user-status-spec.yml @@ -0,0 +1,5 @@ +--- +title: 'Rails5: fix user edit profile clear status spec' +merge_request: 22169 +author: Jasper Maes +type: other diff --git a/spec/features/profiles/user_edit_profile_spec.rb b/spec/features/profiles/user_edit_profile_spec.rb index e168bb0fc89..5e0434c1c2c 100644 --- a/spec/features/profiles/user_edit_profile_spec.rb +++ b/spec/features/profiles/user_edit_profile_spec.rb @@ -129,6 +129,8 @@ describe 'User edit profile' do click_button 'js-clear-user-status-button' submit_settings + wait_for_requests + visit user_path(user) expect(page).not_to have_selector '.cover-status' end -- cgit v1.2.1 From 043f899b2a90f91f95b902316ad200759b3cdc7c Mon Sep 17 00:00:00 2001 From: Jasper Maes Date: Sat, 6 Oct 2018 13:21:47 +0200 Subject: Rails 5: fix mysql milliseconds problems in scheduled build specs --- changelogs/unreleased/rails5-mysql-schedule-build.yml | 5 +++++ spec/lib/gitlab/ci/status/build/scheduled_spec.rb | 2 +- spec/models/ci/build_spec.rb | 2 +- spec/presenters/ci/build_presenter_spec.rb | 2 +- 4 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/rails5-mysql-schedule-build.yml diff --git a/changelogs/unreleased/rails5-mysql-schedule-build.yml b/changelogs/unreleased/rails5-mysql-schedule-build.yml new file mode 100644 index 00000000000..cbc481fbf89 --- /dev/null +++ b/changelogs/unreleased/rails5-mysql-schedule-build.yml @@ -0,0 +1,5 @@ +--- +title: 'Rails 5: fix mysql milliseconds problems in scheduled build specs' +merge_request: 22170 +author: Jasper Maes +type: other diff --git a/spec/lib/gitlab/ci/status/build/scheduled_spec.rb b/spec/lib/gitlab/ci/status/build/scheduled_spec.rb index 3098a17c50d..f98183d6d18 100644 --- a/spec/lib/gitlab/ci/status/build/scheduled_spec.rb +++ b/spec/lib/gitlab/ci/status/build/scheduled_spec.rb @@ -17,7 +17,7 @@ describe Gitlab::Ci::Status::Build::Scheduled do let(:build) { create(:ci_build, scheduled_at: 1.minute.since, project: project) } it 'shows execute_in of the scheduled job' do - Timecop.freeze do + Timecop.freeze(Time.now.change(usec: 0)) do expect(subject.status_tooltip).to include('00:01:00') end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index cebc822d525..a046541031e 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -261,7 +261,7 @@ describe Ci::Build do it 'schedules BuildScheduleWorker at the right time' do Timecop.freeze do expect(Ci::BuildScheduleWorker) - .to receive(:perform_at).with(1.minute.since, build.id) + .to receive(:perform_at).with(be_like_time(1.minute.since), build.id) subject end diff --git a/spec/presenters/ci/build_presenter_spec.rb b/spec/presenters/ci/build_presenter_spec.rb index b2fe10bb0b0..d7992f0a4a9 100644 --- a/spec/presenters/ci/build_presenter_spec.rb +++ b/spec/presenters/ci/build_presenter_spec.rb @@ -227,7 +227,7 @@ describe Ci::BuildPresenter do it 'returns execution time' do Timecop.freeze do - is_expected.to eq(60.0) + is_expected.to be_like_time(60.0) end end end -- cgit v1.2.1 From 32eebfcfe11144e2be2ac8b9adc74c98427849cd Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sat, 6 Oct 2018 05:47:30 -0700 Subject: Fix comments in app/models/note.rb This resolves a conflict and inconsistency with the EE version of app/models/note.rb. --- app/models/note.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/note.rb b/app/models/note.rb index 1b595ef60b4..95e1d3afa00 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -38,14 +38,14 @@ class Note < ActiveRecord::Base alias_attribute :last_edited_at, :updated_at alias_attribute :last_edited_by, :updated_by - # Number of user visible references as generated by Banzai::ObjectRenderer + # Attribute containing rendered and redacted Markdown as generated by + # Banzai::ObjectRenderer. attr_accessor :redacted_note_html # Total of all references as generated by Banzai::ObjectRenderer attr_accessor :total_reference_count - # An Array containing the number of visible references as generated by - # Banzai::ObjectRenderer + # Number of user visible references as generated by Banzai::ObjectRenderer attr_accessor :user_visible_reference_count # Attribute used to store the attributes that have been changed by quick actions. -- cgit v1.2.1 From e9f7908f5eb88c43c5e1689190a3d6b0c840a650 Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Sat, 6 Oct 2018 14:30:35 +0000 Subject: Add button to insert table in markdown editor --- .../vue_shared/components/markdown/header.vue | 16 ++++++++++++++++ app/views/projects/_md_preview.html.haml | 2 ++ .../add-button-to-insert-table-in-markdown.yml | 5 +++++ locale/gitlab.pot | 6 ++++++ .../vue_shared/components/markdown/header_spec.js | 13 +++++++++++-- 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/add-button-to-insert-table-in-markdown.yml diff --git a/app/assets/javascripts/vue_shared/components/markdown/header.vue b/app/assets/javascripts/vue_shared/components/markdown/header.vue index afc4196c729..ccd53158820 100644 --- a/app/assets/javascripts/vue_shared/components/markdown/header.vue +++ b/app/assets/javascripts/vue_shared/components/markdown/header.vue @@ -18,6 +18,16 @@ required: true, }, }, + computed: { + mdTable() { + return [ + '| header | header |', + '| ------ | ------ |', + '| cell | cell |', + '| cell | cell |', + ].join('\n'); + }, + }, mounted() { $(document).on('markdown-preview:show.vue', this.previewMarkdownTab); $(document).on('markdown-preview:hide.vue', this.writeMarkdownTab); @@ -129,6 +139,12 @@ button-title="Add a task list" icon="task-done" /> +
+ +
+ // // -/* eslint-enable max-len */ // Store the `location` object, allowing for easier stubbing in tests let { location } = window; diff --git a/app/assets/javascripts/milestone_select.js b/app/assets/javascripts/milestone_select.js index 640a4c8260f..67c2d7909a2 100644 --- a/app/assets/javascripts/milestone_select.js +++ b/app/assets/javascripts/milestone_select.js @@ -1,4 +1,4 @@ -/* eslint-disable max-len, one-var, one-var-declaration-per-line, no-unused-vars, object-shorthand, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */ +/* eslint-disable one-var, no-unused-vars, object-shorthand, no-else-return, no-self-compare, consistent-return, no-param-reassign, no-shadow */ /* global Issuable */ /* global ListMilestone */ diff --git a/app/assets/javascripts/namespace_select.js b/app/assets/javascripts/namespace_select.js index 17370edeb0c..ec4c0910e92 100644 --- a/app/assets/javascripts/namespace_select.js +++ b/app/assets/javascripts/namespace_select.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, comma-dangle, object-shorthand, no-else-return, prefer-template, quotes, prefer-arrow-callback, max-len */ +/* eslint-disable func-names, object-shorthand, no-else-return, prefer-template, prefer-arrow-callback */ import $ from 'jquery'; import Api from './api'; diff --git a/app/assets/javascripts/network/branch_graph.js b/app/assets/javascripts/network/branch_graph.js index 94da1be4066..ad7136adb8c 100644 --- a/app/assets/javascripts/network/branch_graph.js +++ b/app/assets/javascripts/network/branch_graph.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, no-var, wrap-iife, quotes, comma-dangle, one-var, one-var-declaration-per-line, no-loop-func, no-floating-decimal, consistent-return, no-unused-vars, prefer-template, prefer-arrow-callback, camelcase, max-len */ +/* eslint-disable func-names, no-var, one-var, no-loop-func, consistent-return, no-unused-vars, prefer-template, prefer-arrow-callback, camelcase */ import $ from 'jquery'; import { __ } from '../locale'; diff --git a/app/assets/javascripts/new_branch_form.js b/app/assets/javascripts/new_branch_form.js index 205d9766656..8f6ea9e61c1 100644 --- a/app/assets/javascripts/new_branch_form.js +++ b/app/assets/javascripts/new_branch_form.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, no-var, one-var, max-len, wrap-iife, consistent-return, comma-dangle, one-var-declaration-per-line, quotes, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return, max-len */ +/* eslint-disable func-names, no-var, one-var, consistent-return, no-return-assign, prefer-arrow-callback, prefer-template, no-shadow, no-else-return */ import $ from 'jquery'; import RefSelectDropdown from './ref_select_dropdown'; diff --git a/app/assets/javascripts/notes.js b/app/assets/javascripts/notes.js index f301f093ef4..1369b5820d5 100644 --- a/app/assets/javascripts/notes.js +++ b/app/assets/javascripts/notes.js @@ -1,5 +1,5 @@ -/* eslint-disable no-restricted-properties, func-names, no-var, wrap-iife, camelcase, -no-unused-expressions, max-len, one-var, one-var-declaration-per-line, default-case, +/* eslint-disable no-restricted-properties, func-names, no-var, camelcase, +no-unused-expressions, one-var, default-case, prefer-template, consistent-return, no-alert, no-return-assign, no-param-reassign, prefer-arrow-callback, no-else-return, vars-on-top, no-unused-vars, no-shadow, no-useless-escape, class-methods-use-this */ diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js index 6c1788dc160..58bb8c5b0c8 100644 --- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js +++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, wrap-iife, no-var, one-var, camelcase, one-var-declaration-per-line, quotes, no-param-reassign, quote-props, comma-dangle, prefer-template, max-len, no-return-assign */ +/* eslint-disable func-names, no-var, one-var, camelcase, no-param-reassign, prefer-template, no-return-assign */ import $ from 'jquery'; import _ from 'underscore'; diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js index a02ec9e5f00..5f91686347a 100644 --- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js +++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_graph.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, max-len, no-restricted-syntax, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, wrap-iife, comma-dangle, no-return-assign, prefer-arrow-callback, quotes, prefer-template, newline-per-chained-call, no-else-return, no-shadow */ +/* eslint-disable func-names, no-restricted-syntax, no-use-before-define, no-param-reassign, new-cap, no-underscore-dangle, no-return-assign, prefer-arrow-callback, prefer-template, no-else-return, no-shadow */ import $ from 'jquery'; import _ from 'underscore'; diff --git a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js index d12249bf612..cd0e2bc023c 100644 --- a/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js +++ b/app/assets/javascripts/pages/projects/graphs/show/stat_graph_contributors_util.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, object-shorthand, no-var, one-var, camelcase, one-var-declaration-per-line, comma-dangle, no-param-reassign, no-return-assign, quotes, prefer-arrow-callback, wrap-iife, consistent-return, no-unused-vars, max-len, no-cond-assign, no-else-return, max-len */ +/* eslint-disable func-names, object-shorthand, no-var, one-var, camelcase, no-param-reassign, no-return-assign, prefer-arrow-callback, consistent-return, no-unused-vars, no-cond-assign, no-else-return */ import _ from 'underscore'; export default { diff --git a/app/assets/javascripts/pages/projects/network/network.js b/app/assets/javascripts/pages/projects/network/network.js index 77368c47451..70fbb3f301c 100644 --- a/app/assets/javascripts/pages/projects/network/network.js +++ b/app/assets/javascripts/pages/projects/network/network.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, wrap-iife, no-var, quotes, quote-props, prefer-template, comma-dangle, max-len */ +/* eslint-disable func-names, no-var, prefer-template */ import $ from 'jquery'; import BranchGraph from '../../../network/branch_graph'; diff --git a/app/assets/javascripts/pages/projects/project.js b/app/assets/javascripts/pages/projects/project.js index 34a13eb3251..52d66beefc9 100644 --- a/app/assets/javascripts/pages/projects/project.js +++ b/app/assets/javascripts/pages/projects/project.js @@ -1,5 +1,4 @@ -/* eslint-disable func-names, no-var, no-return-assign, one-var, - one-var-declaration-per-line, object-shorthand, vars-on-top */ +/* eslint-disable func-names, no-var, no-return-assign, one-var, object-shorthand, vars-on-top */ import $ from 'jquery'; import Cookies from 'js-cookie'; diff --git a/app/assets/javascripts/pages/sessions/new/username_validator.js b/app/assets/javascripts/pages/sessions/new/username_validator.js index 97cf1aeaadc..d621f988d86 100644 --- a/app/assets/javascripts/pages/sessions/new/username_validator.js +++ b/app/assets/javascripts/pages/sessions/new/username_validator.js @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, consistent-return, class-methods-use-this */ +/* eslint-disable consistent-return, class-methods-use-this */ import $ from 'jquery'; import _ from 'underscore'; diff --git a/app/assets/javascripts/profile/gl_crop.js b/app/assets/javascripts/profile/gl_crop.js index f641b23e519..af134881f31 100644 --- a/app/assets/javascripts/profile/gl_crop.js +++ b/app/assets/javascripts/profile/gl_crop.js @@ -1,4 +1,4 @@ -/* eslint-disable no-useless-escape, max-len, no-var, no-underscore-dangle, func-names, no-unused-vars, no-return-assign, object-shorthand, one-var, one-var-declaration-per-line, comma-dangle, consistent-return, class-methods-use-this, new-parens */ +/* eslint-disable no-useless-escape, no-var, no-underscore-dangle, func-names, no-unused-vars, no-return-assign, object-shorthand, one-var, consistent-return, class-methods-use-this */ import $ from 'jquery'; import 'cropper'; diff --git a/app/assets/javascripts/project_find_file.js b/app/assets/javascripts/project_find_file.js index 05485e352dc..12cfa7de316 100644 --- a/app/assets/javascripts/project_find_file.js +++ b/app/assets/javascripts/project_find_file.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, no-var, wrap-iife, quotes, consistent-return, one-var, one-var-declaration-per-line, no-cond-assign, max-len, prefer-template, no-unused-vars, no-return-assign */ +/* eslint-disable func-names, no-var, consistent-return, one-var, no-cond-assign, prefer-template, no-unused-vars, no-return-assign */ import $ from 'jquery'; import fuzzaldrinPlus from 'fuzzaldrin-plus'; diff --git a/app/assets/javascripts/project_select.js b/app/assets/javascripts/project_select.js index 6f3b32f8eea..eaaeda8b339 100644 --- a/app/assets/javascripts/project_select.js +++ b/app/assets/javascripts/project_select.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, wrap-iife, no-var, comma-dangle, object-shorthand, one-var, one-var-declaration-per-line, no-else-return, quotes, max-len */ +/* eslint-disable func-names, no-var, object-shorthand, one-var, no-else-return */ import $ from 'jquery'; import Api from './api'; diff --git a/app/assets/javascripts/right_sidebar.js b/app/assets/javascripts/right_sidebar.js index b27d635c6ac..64f3dde5be7 100644 --- a/app/assets/javascripts/right_sidebar.js +++ b/app/assets/javascripts/right_sidebar.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, no-var, no-unused-vars, consistent-return, one-var, one-var-declaration-per-line, quotes, prefer-template, no-else-return, no-param-reassign, max-len */ +/* eslint-disable func-names, no-var, no-unused-vars, consistent-return, one-var, prefer-template, no-else-return, no-param-reassign */ import $ from 'jquery'; import _ from 'underscore'; diff --git a/app/assets/javascripts/search_autocomplete.js b/app/assets/javascripts/search_autocomplete.js index 50dd3c12382..7bde4860973 100644 --- a/app/assets/javascripts/search_autocomplete.js +++ b/app/assets/javascripts/search_autocomplete.js @@ -1,4 +1,4 @@ -/* eslint-disable no-return-assign, one-var, no-var, one-var-declaration-per-line, no-unused-vars, consistent-return, object-shorthand, prefer-template, class-methods-use-this, no-lonely-if, vars-on-top, max-len */ +/* eslint-disable no-return-assign, one-var, no-var, no-unused-vars, consistent-return, object-shorthand, prefer-template, class-methods-use-this, no-lonely-if, vars-on-top */ import $ from 'jquery'; import { escape, throttle } from 'underscore'; diff --git a/app/assets/javascripts/templates/issuable_template_selector.js b/app/assets/javascripts/templates/issuable_template_selector.js index 6fea03af46a..74166313940 100644 --- a/app/assets/javascripts/templates/issuable_template_selector.js +++ b/app/assets/javascripts/templates/issuable_template_selector.js @@ -1,4 +1,4 @@ -/* eslint-disable no-useless-return, max-len */ +/* eslint-disable no-useless-return */ import $ from 'jquery'; import Api from '../api'; diff --git a/app/assets/javascripts/tree.js b/app/assets/javascripts/tree.js index 85123a63a45..066fd6278a7 100644 --- a/app/assets/javascripts/tree.js +++ b/app/assets/javascripts/tree.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, max-len, quotes, consistent-return, no-var, one-var, one-var-declaration-per-line, no-else-return, prefer-arrow-callback, class-methods-use-this */ +/* eslint-disable func-names, consistent-return, no-var, one-var, no-else-return, prefer-arrow-callback, class-methods-use-this */ import $ from 'jquery'; import { visitUrl } from './lib/utils/url_utility'; diff --git a/app/assets/javascripts/users_select.js b/app/assets/javascripts/users_select.js index b9dfa22dd49..e2259a8d07b 100644 --- a/app/assets/javascripts/users_select.js +++ b/app/assets/javascripts/users_select.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, one-var, no-var, prefer-rest-params, quotes, max-len, one-var-declaration-per-line, vars-on-top, prefer-arrow-callback, consistent-return, comma-dangle, object-shorthand, no-shadow, no-unused-vars, no-else-return, no-self-compare, prefer-template, no-unused-expressions, yoda, prefer-spread, no-void, camelcase, no-param-reassign */ +/* eslint-disable func-names, one-var, no-var, prefer-rest-params, vars-on-top, prefer-arrow-callback, consistent-return, object-shorthand, no-shadow, no-unused-vars, no-else-return, no-self-compare, prefer-template, no-unused-expressions, yoda, prefer-spread, no-void, camelcase, no-param-reassign */ /* global Issuable */ /* global emitSidebarEvent */ diff --git a/app/assets/javascripts/zen_mode.js b/app/assets/javascripts/zen_mode.js index 0138c9be803..bdb2351c344 100644 --- a/app/assets/javascripts/zen_mode.js +++ b/app/assets/javascripts/zen_mode.js @@ -1,4 +1,4 @@ -/* eslint-disable func-names, wrap-iife, prefer-arrow-callback, no-unused-vars, consistent-return, camelcase, comma-dangle, max-len, class-methods-use-this */ +/* eslint-disable func-names, prefer-arrow-callback, no-unused-vars, consistent-return, camelcase, class-methods-use-this */ // Zen Mode (full screen) textarea // diff --git a/package.json b/package.json index 35984e6d81f..ac9a73cd2c9 100644 --- a/package.json +++ b/package.json @@ -121,6 +121,7 @@ "commander": "^2.18.0", "eslint": "~5.6.0", "eslint-config-airbnb-base": "^13.1.0", + "eslint-config-prettier": "^3.1.0", "eslint-import-resolver-webpack": "^0.10.1", "eslint-plugin-filenames": "^1.3.2", "eslint-plugin-html": "4.0.5", @@ -144,7 +145,7 @@ "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "^4.0.0-beta.0", "nodemon": "^1.18.4", - "prettier": "1.12.1", + "prettier": "1.14.3", "webpack-dev-server": "^3.1.8" } } diff --git a/spec/javascripts/awards_handler_spec.js b/spec/javascripts/awards_handler_spec.js index ada26b37f4a..0c5d68990d5 100644 --- a/spec/javascripts/awards_handler_spec.js +++ b/spec/javascripts/awards_handler_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable no-var, one-var, one-var-declaration-per-line, no-unused-expressions, no-unused-vars, prefer-template, max-len */ +/* eslint-disable no-var, one-var, no-unused-expressions, no-unused-vars, prefer-template */ import $ from 'jquery'; import Cookies from 'js-cookie'; diff --git a/spec/javascripts/boards/boards_store_spec.js b/spec/javascripts/boards/boards_store_spec.js index 1ee6f4cf680..ed43ce9029e 100644 --- a/spec/javascripts/boards/boards_store_spec.js +++ b/spec/javascripts/boards/boards_store_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, no-unused-vars */ +/* eslint-disable no-unused-vars */ /* global ListIssue */ import Vue from 'vue'; diff --git a/spec/javascripts/boards/issue_spec.js b/spec/javascripts/boards/issue_spec.js index db68096e3bd..0beb5782283 100644 --- a/spec/javascripts/boards/issue_spec.js +++ b/spec/javascripts/boards/issue_spec.js @@ -1,4 +1,3 @@ -/* eslint-disable comma-dangle */ /* global ListIssue */ import Vue from 'vue'; diff --git a/spec/javascripts/boards/list_spec.js b/spec/javascripts/boards/list_spec.js index ac8bbb8f2a8..4232e0fc221 100644 --- a/spec/javascripts/boards/list_spec.js +++ b/spec/javascripts/boards/list_spec.js @@ -1,4 +1,3 @@ -/* eslint-disable comma-dangle */ /* global List */ /* global ListIssue */ diff --git a/spec/javascripts/diff_comments_store_spec.js b/spec/javascripts/diff_comments_store_spec.js index d6fc6b56b82..c6f2e66cebd 100644 --- a/spec/javascripts/diff_comments_store_spec.js +++ b/spec/javascripts/diff_comments_store_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable jasmine/no-global-setup, dot-notation, jasmine/no-expect-in-setup-teardown, max-len */ +/* eslint-disable jasmine/no-global-setup, dot-notation, jasmine/no-expect-in-setup-teardown */ /* global CommentsStore */ import '~/diff_notes/models/discussion'; diff --git a/spec/javascripts/droplab/plugins/input_setter_spec.js b/spec/javascripts/droplab/plugins/input_setter_spec.js index bd625f4ae80..1988811a305 100644 --- a/spec/javascripts/droplab/plugins/input_setter_spec.js +++ b/spec/javascripts/droplab/plugins/input_setter_spec.js @@ -1,5 +1,3 @@ -/* eslint-disable */ - import InputSetter from '~/droplab/plugins/input_setter'; describe('InputSetter', function () { diff --git a/spec/javascripts/gl_dropdown_spec.js b/spec/javascripts/gl_dropdown_spec.js index af58dff7da7..25b819543da 100644 --- a/spec/javascripts/gl_dropdown_spec.js +++ b/spec/javascripts/gl_dropdown_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable comma-dangle, no-param-reassign */ +/* eslint-disable no-param-reassign */ import $ from 'jquery'; import GLDropdown from '~/gl_dropdown'; diff --git a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js index d8a8c8cc260..4a4d6969e86 100644 --- a/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js +++ b/spec/javascripts/graphs/stat_graph_contributors_graph_spec.js @@ -1,4 +1,5 @@ -/* eslint-disable quotes, jasmine/no-suite-dupes, vars-on-top, no-var */ +/* eslint-disable jasmine/no-suite-dupes, vars-on-top, no-var */ + import { scaleLinear, scaleTime } from 'd3-scale'; import { timeParse } from 'd3-time-format'; import { ContributorsGraph, ContributorsMasterGraph } from '~/pages/projects/graphs/show/stat_graph_contributors_graph'; diff --git a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js index 22a9afe1a9d..02d1ca1cc3b 100644 --- a/spec/javascripts/graphs/stat_graph_contributors_util_spec.js +++ b/spec/javascripts/graphs/stat_graph_contributors_util_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable quotes, no-var, camelcase, object-property-newline, comma-dangle, max-len, vars-on-top, quote-props */ +/* eslint-disable no-var, camelcase, vars-on-top */ import ContributorsStatGraphUtil from '~/pages/projects/graphs/show/stat_graph_contributors_util'; diff --git a/spec/javascripts/issue_spec.js b/spec/javascripts/issue_spec.js index e12419b835d..62c71e00334 100644 --- a/spec/javascripts/issue_spec.js +++ b/spec/javascripts/issue_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable one-var, one-var-declaration-per-line, no-use-before-define, comma-dangle */ +/* eslint-disable one-var, no-use-before-define */ import $ from 'jquery'; import MockAdapter from 'axios-mock-adapter'; diff --git a/spec/javascripts/line_highlighter_spec.js b/spec/javascripts/line_highlighter_spec.js index 8cf0017f4d8..c32ecb17e89 100644 --- a/spec/javascripts/line_highlighter_spec.js +++ b/spec/javascripts/line_highlighter_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable no-var, quotes, prefer-template, no-else-return, dot-notation, no-return-assign, comma-dangle, no-new, one-var, one-var-declaration-per-line, no-underscore-dangle, max-len */ +/* eslint-disable no-var, prefer-template, no-else-return, dot-notation, no-return-assign, no-new, one-var, no-underscore-dangle */ import $ from 'jquery'; import LineHighlighter from '~/line_highlighter'; diff --git a/spec/javascripts/new_branch_spec.js b/spec/javascripts/new_branch_spec.js index 122e5bc58b2..e52ac686435 100644 --- a/spec/javascripts/new_branch_spec.js +++ b/spec/javascripts/new_branch_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable one-var, no-var, one-var-declaration-per-line, no-return-assign, quotes, max-len */ +/* eslint-disable one-var, no-var, no-return-assign */ import $ from 'jquery'; import NewBranchForm from '~/new_branch_form'; diff --git a/spec/javascripts/right_sidebar_spec.js b/spec/javascripts/right_sidebar_spec.js index c7190ea9960..f9395eedfea 100644 --- a/spec/javascripts/right_sidebar_spec.js +++ b/spec/javascripts/right_sidebar_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable no-var, one-var, one-var-declaration-per-line, no-return-assign, vars-on-top, jasmine/no-unsafe-spy, max-len */ +/* eslint-disable no-var, one-var, no-return-assign, vars-on-top, jasmine/no-unsafe-spy */ import $ from 'jquery'; import MockAdapter from 'axios-mock-adapter'; diff --git a/spec/javascripts/search_autocomplete_spec.js b/spec/javascripts/search_autocomplete_spec.js index 646d843162c..b96023a33c4 100644 --- a/spec/javascripts/search_autocomplete_spec.js +++ b/spec/javascripts/search_autocomplete_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable max-len, no-var, one-var, one-var-declaration-per-line, no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign, object-shorthand, prefer-template, vars-on-top, max-len */ +/* eslint-disable no-var, one-var, no-unused-expressions, consistent-return, no-param-reassign, default-case, no-return-assign, object-shorthand, prefer-template, vars-on-top */ import $ from 'jquery'; import '~/gl_dropdown'; diff --git a/spec/javascripts/syntax_highlight_spec.js b/spec/javascripts/syntax_highlight_spec.js index 1c3dac3584e..af3a5d58ba7 100644 --- a/spec/javascripts/syntax_highlight_spec.js +++ b/spec/javascripts/syntax_highlight_spec.js @@ -1,4 +1,4 @@ -/* eslint-disable no-var, no-return-assign, quotes */ +/* eslint-disable no-var, no-return-assign */ import $ from 'jquery'; import syntaxHighlight from '~/syntax_highlight'; diff --git a/spec/javascripts/u2f/mock_u2f_device.js b/spec/javascripts/u2f/mock_u2f_device.js index 012a1cefbbf..a8692be3546 100644 --- a/spec/javascripts/u2f/mock_u2f_device.js +++ b/spec/javascripts/u2f/mock_u2f_device.js @@ -1,4 +1,4 @@ -/* eslint-disable wrap-iife, no-unused-expressions, no-return-assign, no-param-reassign */ +/* eslint-disable no-unused-expressions, no-return-assign, no-param-reassign */ export default class MockU2FDevice { constructor() { diff --git a/yarn.lock b/yarn.lock index 7967ad0eb34..25ea8d7557c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3012,6 +3012,13 @@ eslint-config-airbnb-base@^13.1.0: object.assign "^4.1.0" object.entries "^1.0.4" +eslint-config-prettier@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-3.1.0.tgz#2c26d2cdcfa3a05f0642cd7e6e4ef3316cdabfa2" + integrity sha512-QYGfmzuc4q4J6XIhlp8vRKdI/fI0tQfQPy1dME3UOLprE+v4ssH/3W9LM2Q7h5qBcy5m0ehCrBDU2YF8q6OY8w== + dependencies: + get-stdin "^6.0.0" + eslint-import-resolver-node@^0.3.1: version "0.3.2" resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a" @@ -3708,6 +3715,11 @@ get-caller-file@^1.0.1: resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" integrity sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U= +get-stdin@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b" + integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g== + get-stream@3.0.0, get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" @@ -6271,16 +6283,16 @@ prepend-http@^2.0.0: resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897" integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc= -prettier@1.12.1: - version "1.12.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.12.1.tgz#c1ad20e803e7749faf905a409d2367e06bbe7325" - integrity sha1-wa0g6APndJ+vkFpAnSNn4Gu+cyU= - prettier@1.13.7: version "1.13.7" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.13.7.tgz#850f3b8af784a49a6ea2d2eaa7ed1428a34b7281" integrity sha512-KIU72UmYPGk4MujZGYMFwinB7lOf2LsDNGSOC8ufevsrPLISrZbNJlWstRi3m0AMuszbH+EFSQ/r6w56RSPK6w== +prettier@1.14.3: + version "1.14.3" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.14.3.tgz#90238dd4c0684b7edce5f83b0fb7328e48bd0895" + integrity sha512-qZDVnCrnpsRJJq5nSsiHCE3BYMED2OtsI+cmzIzF1QIfqm5ALf8tEJcO27zV1gKNKRPdhjO0dNWnrzssDQ1tFg== + prismjs@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.6.0.tgz#118d95fb7a66dba2272e343b345f5236659db365" -- cgit v1.2.1 From b675b6b88d196d0185bad75e707058cafee9852b Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 9 Oct 2018 16:16:54 +0000 Subject: Resolve "pipeline's page shows vue error on master" --- app/assets/javascripts/pipelines/components/pipelines_table.vue | 2 +- app/assets/javascripts/pipelines/components/pipelines_table_row.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/pipelines/components/pipelines_table.vue b/app/assets/javascripts/pipelines/components/pipelines_table.vue index 0d7324f3fb5..cb14d4400d1 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table.vue @@ -35,7 +35,7 @@ export default { }, data() { return { - pipelineId: '', + pipelineId: 0, endpoint: '', cancelingPipeline: null, }; diff --git a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue index 09ee190b8ca..b03438ddba1 100644 --- a/app/assets/javascripts/pipelines/components/pipelines_table_row.vue +++ b/app/assets/javascripts/pipelines/components/pipelines_table_row.vue @@ -47,7 +47,7 @@ export default { required: true, }, cancelingPipeline: { - type: String, + type: Number, required: false, default: null, }, -- cgit v1.2.1