From 74bdaae6cb70f978f64792e7435621e961dd5e89 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 30 Nov 2017 18:09:32 +0100 Subject: Add link to create Google account in clusters page --- app/views/projects/clusters/login.html.haml | 2 ++ spec/features/projects/clusters_spec.rb | 1 + 2 files changed, 3 insertions(+) diff --git a/app/views/projects/clusters/login.html.haml b/app/views/projects/clusters/login.html.haml index fde030b500b..e4bc58b1fab 100644 --- a/app/views/projects/clusters/login.html.haml +++ b/app/views/projects/clusters/login.html.haml @@ -11,6 +11,8 @@ - if @authorize_url = link_to @authorize_url do = image_tag('auth_buttons/signin_with_google.png', width: '191px') + = s_('ClusterIntegration|or create a new') + = link_to('Google account', 'https://accounts.google.com/signup', target: '_blank', rel: 'noopener noreferrer') - else - link = link_to(s_('ClusterIntegration|properly configured'), help_page_path("integration/google"), target: '_blank', rel: 'noopener noreferrer') = s_('Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service.').html_safe % { link_to_documentation: link } diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb index 197e6df4997..018758d1bf1 100644 --- a/spec/features/projects/clusters_spec.rb +++ b/spec/features/projects/clusters_spec.rb @@ -201,6 +201,7 @@ feature 'Clusters', :js do it 'user sees a login page' do expect(page).to have_css('.signin-with-google') + expect(page).to have_link('Google account') end end end -- cgit v1.2.1 From d2ebc9b931d12cb2cb120d6f7c940744bc1be39c Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Thu, 19 Oct 2017 17:47:11 +0300 Subject: Prevent schema.rb reverting from datetime_with_timezone to datetime --- config/initializers/active_record_data_types.rb | 5 +++++ .../20171019141859_fix_dev_timezone_schema.rb | 25 ++++++++++++++++++++++ db/schema.rb | 8 +++---- 3 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 db/migrate/20171019141859_fix_dev_timezone_schema.rb diff --git a/config/initializers/active_record_data_types.rb b/config/initializers/active_record_data_types.rb index fef591c397d..0359e14b232 100644 --- a/config/initializers/active_record_data_types.rb +++ b/config/initializers/active_record_data_types.rb @@ -79,3 +79,8 @@ elsif Gitlab::Database.mysql? NATIVE_DATABASE_TYPES[:datetime_with_timezone] = { name: 'timestamp' } end end + +# Ensure `datetime_with_timezone` columns are correctly written to schema.rb +if (ActiveRecord::Base.connection.active? rescue false) + ActiveRecord::Base.connection.send :reload_type_map +end diff --git a/db/migrate/20171019141859_fix_dev_timezone_schema.rb b/db/migrate/20171019141859_fix_dev_timezone_schema.rb new file mode 100644 index 00000000000..fb7c17dd747 --- /dev/null +++ b/db/migrate/20171019141859_fix_dev_timezone_schema.rb @@ -0,0 +1,25 @@ +class FixDevTimezoneSchema < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # The this migrations tries to help solve unwanted changes to `schema.rb` + # while developing GitLab. Installations created before we started using + # `datetime_with_timezone` are likely to face this problem. Updating those + # columns to the new type should help fix this. + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + TIMEZONE_TABLES = %i(appearances ci_group_variables ci_pipeline_schedule_variables events gpg_keys gpg_signatures project_auto_devops) + + def up + return unless Rails.env.development? || Rails.env.test? + + TIMEZONE_TABLES.each do |table| + change_column table, :created_at, :datetime_with_timezone + change_column table, :updated_at, :datetime_with_timezone + end + end + + def down + end +end diff --git a/db/schema.rb b/db/schema.rb index 0984ca6487f..990456648b7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -649,8 +649,8 @@ ActiveRecord::Schema.define(version: 20171124150326) do t.datetime "created_at" t.datetime "updated_at" t.string "confirmation_token" - t.datetime "confirmed_at" - t.datetime "confirmation_sent_at" + t.datetime_with_timezone "confirmed_at" + t.datetime_with_timezone "confirmation_sent_at" end add_index "emails", ["confirmation_token"], name: "index_emails_on_confirmation_token", unique: true, using: :btree @@ -1762,8 +1762,8 @@ ActiveRecord::Schema.define(version: 20171124150326) do add_index "user_agent_details", ["subject_id", "subject_type"], name: "index_user_agent_details_on_subject_id_and_subject_type", using: :btree create_table "user_custom_attributes", force: :cascade do |t| - t.datetime "created_at", null: false - t.datetime "updated_at", null: false + t.datetime_with_timezone "created_at", null: false + t.datetime_with_timezone "updated_at", null: false t.integer "user_id", null: false t.string "key", null: false t.string "value", null: false -- cgit v1.2.1 From eb9f88311b0591586c73ddb35bdb07677464748c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sun, 10 Dec 2017 18:10:51 +0100 Subject: Use special new account link * Redirects to GCP free trial signup afterwards * Adds GitLab referral info --- app/views/projects/clusters/login.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/clusters/login.html.haml b/app/views/projects/clusters/login.html.haml index e4bc58b1fab..521121111b0 100644 --- a/app/views/projects/clusters/login.html.haml +++ b/app/views/projects/clusters/login.html.haml @@ -12,7 +12,7 @@ = link_to @authorize_url do = image_tag('auth_buttons/signin_with_google.png', width: '191px') = s_('ClusterIntegration|or create a new') - = link_to('Google account', 'https://accounts.google.com/signup', target: '_blank', rel: 'noopener noreferrer') + = link_to('Google account', 'https://accounts.google.com/SignUpWithoutGmail?service=cloudconsole&continue=https%3A%2F%2Fconsole.cloud.google.com%2Ffreetrial%3Futm_campaign%3D2018_cpanel%26utm_source%3Dgitlab%26utm_medium%3Dreferral', target: '_blank', rel: 'noopener noreferrer') - else - link = link_to(s_('ClusterIntegration|properly configured'), help_page_path("integration/google"), target: '_blank', rel: 'noopener noreferrer') = s_('Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service.').html_safe % { link_to_documentation: link } -- cgit v1.2.1 From 497a0cd62ca8dff2e7b91f3366b69f4f2b2fa826 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 13 Dec 2017 15:25:42 +0100 Subject: Fix cluster OAuth feature spec user flow --- spec/features/projects/clusters_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/features/projects/clusters_spec.rb b/spec/features/projects/clusters_spec.rb index dad943aac55..eae2910a8f6 100644 --- a/spec/features/projects/clusters_spec.rb +++ b/spec/features/projects/clusters_spec.rb @@ -82,6 +82,7 @@ feature 'Clusters', :js do before do visit project_clusters_path(project) + click_link 'Add cluster' click_link 'Create on GKE' end -- cgit v1.2.1 From fd88b0ca56b3a4230902f76a7b049228e53e6bb0 Mon Sep 17 00:00:00 2001 From: Tony Rom Date: Thu, 16 Nov 2017 21:10:15 +0300 Subject: Add `pipelines` endpoint to merge requests API --- changelogs/unreleased/39214__pipeline_api.yml | 5 +++ doc/api/merge_requests.md | 26 +++++++++++++++ lib/api/merge_requests.rb | 15 +++++++++ .../api/schemas/public_api/v4/pipelines.json | 4 +++ spec/requests/api/merge_requests_spec.rb | 37 ++++++++++++++++++++++ 5 files changed, 87 insertions(+) create mode 100644 changelogs/unreleased/39214__pipeline_api.yml create mode 100644 spec/fixtures/api/schemas/public_api/v4/pipelines.json diff --git a/changelogs/unreleased/39214__pipeline_api.yml b/changelogs/unreleased/39214__pipeline_api.yml new file mode 100644 index 00000000000..18ee2e43798 --- /dev/null +++ b/changelogs/unreleased/39214__pipeline_api.yml @@ -0,0 +1,5 @@ +--- +title: Add `pipelines` endpoint to merge requests API +merge_request: 15454 +author: Tony Rom +type: added diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 880b0ed2c65..a3261cf7ee4 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -431,6 +431,32 @@ Parameters: } ``` +## List MR pipelines + +Get a list of merge request pipelines. + +``` +GET /projects/:id/merge_requests/:merge_request_iid/pipelines +``` + +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) - The internal ID of the merge request + +Example of response + +```json +[ + { + "id": 77, + "sha": "959e04d7c7a30600c894bd3c0cd0e1ce7f42c11d", + "ref": "master", + "status": "success" + } +] +``` + ## Create MR Creates a new merge request. diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index d34886fca2e..77c563ec0b4 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -24,6 +24,12 @@ module API .preload(:notes, :author, :assignee, :milestone, :latest_merge_request_diff, :labels, :timelogs) end + def merge_request_pipelines_with_access(access_level = :read_pipeline) + authorize! access_level, user_project + mr = find_merge_request_with_access(params[:merge_request_iid]) + mr.all_pipelines + end + params :merge_requests_params do optional :state, type: String, values: %w[opened closed merged all], default: 'all', desc: 'Return opened, closed, merged, or all merge requests' @@ -203,6 +209,15 @@ module API present merge_request, with: Entities::MergeRequestChanges, current_user: current_user end + desc 'Get the merge request pipelines' do + success Entities::PipelineBasic + end + get ':id/merge_requests/:merge_request_iid/pipelines' do + pipelines = merge_request_pipelines_with_access + + present paginate(pipelines), with: Entities::PipelineBasic + end + desc 'Update a merge request' do success Entities::MergeRequest end diff --git a/spec/fixtures/api/schemas/public_api/v4/pipelines.json b/spec/fixtures/api/schemas/public_api/v4/pipelines.json new file mode 100644 index 00000000000..8b08a00f708 --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/pipelines.json @@ -0,0 +1,4 @@ +{ + "type": "array", + "items": { "$ref": "pipeline/basic.json" } +} diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 91616da6d9a..4278e32dc78 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -525,6 +525,43 @@ describe API::MergeRequests do end end + describe 'GET /projects/:id/merge_requests/:merge_request_iid/pipelines' do + context 'when authorized' do + let!(:pipeline) { create(:ci_empty_pipeline, project: project, user: user, ref: merge_request.source_branch, sha: merge_request.diff_head_sha) } + let!(:pipeline2) { create(:ci_empty_pipeline, project: project) } + + it 'returns a paginated array of corresponding pipelines' do + get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/pipelines") + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.count).to eq(1) + expect(json_response.first['id']).to eq(pipeline.id) + end + + it 'exposes basic attributes' do + get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/pipelines") + + expect(response).to have_gitlab_http_status(200) + expect(response).to match_response_schema('public_api/v4/pipelines') + end + end + + context 'when unauthorized' do + it 'returns 403' do + project = create(:project, public_builds: false) + merge_request = create(:merge_request, :simple, source_project: project) + guest = create(:user) + project.team << [guest, :guest] + + get api("/projects/#{project.id}/merge_requests/#{merge_request.iid}/pipelines", guest) + + expect(response).to have_gitlab_http_status(403) + end + end + end + describe "POST /projects/:id/merge_requests" do context 'between branches projects' do it "returns merge_request" do -- cgit v1.2.1 From 596ea9e3688537131fb918c1e9177d085a938d19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Fri, 15 Dec 2017 19:39:15 +0100 Subject: Add Google Cloud client project list --- lib/google_api/cloud_platform/client.rb | 10 ++++++++++ spec/lib/google_api/cloud_platform/client_spec.rb | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/lib/google_api/cloud_platform/client.rb b/lib/google_api/cloud_platform/client.rb index b0563fb2d69..f0dabcb1c5d 100644 --- a/lib/google_api/cloud_platform/client.rb +++ b/lib/google_api/cloud_platform/client.rb @@ -1,4 +1,5 @@ require 'google/apis/container_v1' +require 'google/apis/cloudresourcemanager_v1' module GoogleApi module CloudPlatform @@ -40,6 +41,15 @@ module GoogleApi true end + def projects_list + service = Google::Apis::CloudresourcemanagerV1::CloudResourceManagerService.new + service.authorization = access_token + + service.fetch_all(items: :projects) do |token| + service.list_projects(page_token: token) + end + end + def projects_zones_clusters_get(project_id, zone, cluster_id) service = Google::Apis::ContainerV1::ContainerService.new service.authorization = access_token diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb index ecb4034ec8b..0383b1080dc 100644 --- a/spec/lib/google_api/cloud_platform/client_spec.rb +++ b/spec/lib/google_api/cloud_platform/client_spec.rb @@ -50,6 +50,18 @@ describe GoogleApi::CloudPlatform::Client do end end + describe '#projects_list' do + subject { client.projects_list } + let(:projects) { double } + + before do + allow_any_instance_of(Google::Apis::CloudresourcemanagerV1::CloudResourceManagerService) + .to receive(:fetch_all).and_return(projects) + end + + it { is_expected.to eq(projects) } + end + describe '#projects_zones_clusters_get' do subject { client.projects_zones_clusters_get(spy, spy, spy) } let(:gke_cluster) { double } -- cgit v1.2.1 From 957bedb1c345644f633fdfd3440491810c5cb75c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Fri, 15 Dec 2017 23:54:50 +0100 Subject: Add Google Cloud client project billing info --- lib/google_api/cloud_platform/client.rb | 8 ++++++++ spec/lib/google_api/cloud_platform/client_spec.rb | 12 ++++++++++++ 2 files changed, 20 insertions(+) diff --git a/lib/google_api/cloud_platform/client.rb b/lib/google_api/cloud_platform/client.rb index f0dabcb1c5d..c107d001bec 100644 --- a/lib/google_api/cloud_platform/client.rb +++ b/lib/google_api/cloud_platform/client.rb @@ -1,4 +1,5 @@ require 'google/apis/container_v1' +require 'google/apis/cloudbilling_v1' require 'google/apis/cloudresourcemanager_v1' module GoogleApi @@ -50,6 +51,13 @@ module GoogleApi end end + def projects_get_billing_info(project_name) + service = Google::Apis::CloudbillingV1::CloudbillingService.new + service.authorization = access_token + + service.get_project_billing_info(project_name) + end + def projects_zones_clusters_get(project_id, zone, cluster_id) service = Google::Apis::ContainerV1::ContainerService.new service.authorization = access_token diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb index 0383b1080dc..2d14c3b1a3a 100644 --- a/spec/lib/google_api/cloud_platform/client_spec.rb +++ b/spec/lib/google_api/cloud_platform/client_spec.rb @@ -62,6 +62,18 @@ describe GoogleApi::CloudPlatform::Client do it { is_expected.to eq(projects) } end + def projects_get_billing_info + subject { client.projects_get_billing_info } + let(:billing_info) { double } + + before do + allow_any_instance_of(Google::Apis::CloudbillingV1::CloudbillingService) + .to receive(:get_project_billing_info).and_return(billing_info) + end + + it { is_expected.to eq(billing_info) } + end + describe '#projects_zones_clusters_get' do subject { client.projects_zones_clusters_get(spy, spy, spy) } let(:gke_cluster) { double } -- cgit v1.2.1 From 84d8ca1171faf8ab6df0c256381b8afbf5aeb099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sat, 16 Dec 2017 00:05:46 +0100 Subject: Change link for creating a new Google account --- app/views/projects/clusters/gcp/login.html.haml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/clusters/gcp/login.html.haml b/app/views/projects/clusters/gcp/login.html.haml index 5ef80ccf8f7..878ebaded88 100644 --- a/app/views/projects/clusters/gcp/login.html.haml +++ b/app/views/projects/clusters/gcp/login.html.haml @@ -12,8 +12,8 @@ - if @authorize_url = link_to @authorize_url do = image_tag('auth_buttons/signin_with_google.png', width: '191px') - = s_('ClusterIntegration|or create a new') - = link_to('Google account', 'https://accounts.google.com/SignUpWithoutGmail?service=cloudconsole&continue=https%3A%2F%2Fconsole.cloud.google.com%2Ffreetrial%3Futm_campaign%3D2018_cpanel%26utm_source%3Dgitlab%26utm_medium%3Dreferral', target: '_blank', rel: 'noopener noreferrer') + = _('or') + = link_to('create a new Google account', 'https://accounts.google.com/SignUpWithoutGmail?service=cloudconsole&continue=https%3A%2F%2Fconsole.cloud.google.com%2Ffreetrial%3Futm_campaign%3D2018_cpanel%26utm_source%3Dgitlab%26utm_medium%3Dreferral', target: '_blank', rel: 'noopener noreferrer') - else - link = link_to(s_('ClusterIntegration|properly configured'), help_page_path("integration/google"), target: '_blank', rel: 'noopener noreferrer') = s_('Google authentication is not %{link_to_documentation}. Ask your GitLab administrator if you want to use this service.').html_safe % { link_to_documentation: link } -- cgit v1.2.1 From 87f01506cb0f0ba9ee2d7153157b0a07d628a559 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sat, 16 Dec 2017 01:44:36 +0100 Subject: Add CheckGCPProjectBillingService --- app/services/check_gcp_project_billing_service.rb | 8 ++++++ .../check_gcp_project_billing_service_spec.rb | 30 ++++++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 app/services/check_gcp_project_billing_service.rb create mode 100644 spec/services/check_gcp_project_billing_service_spec.rb diff --git a/app/services/check_gcp_project_billing_service.rb b/app/services/check_gcp_project_billing_service.rb new file mode 100644 index 00000000000..45192a167eb --- /dev/null +++ b/app/services/check_gcp_project_billing_service.rb @@ -0,0 +1,8 @@ +class CheckGCPProjectBillingService + def execute(token) + client = GoogleApi::CloudPlatform::Client.new(token, nil) + client.projects_list.any? do |project| + client.projects_get_billing_info(project.name).billingEnabled + end + end +end diff --git a/spec/services/check_gcp_project_billing_service_spec.rb b/spec/services/check_gcp_project_billing_service_spec.rb new file mode 100644 index 00000000000..b7c42fcace1 --- /dev/null +++ b/spec/services/check_gcp_project_billing_service_spec.rb @@ -0,0 +1,30 @@ +require 'spec_helper' + +describe CheckGCPProjectBillingService do + let(:service) { described_class.new } + + describe '#execute' do + before do + expect_any_instance_of(GoogleApi::CloudPlatform::Client) + .to receive(:projects_list).and_return([double(name: 'project_name')]) + + expect_any_instance_of(GoogleApi::CloudPlatform::Client) + .to receive_message_chain(:projects_get_billing_info, :billingEnabled) + .and_return(project_billing_enabled) + end + + subject { service.execute('bogustoken') } + + context 'google account has a billing enabled gcp project' do + let(:project_billing_enabled) { true } + + it { is_expected.to eq(true) } + end + + context 'google account does not have a billing enabled gcp project' do + let(:project_billing_enabled) { false } + + it { is_expected.to eq(false) } + end + end +end -- cgit v1.2.1 From 291480f5e17fea424692f979db91d2ec62d24dbd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sat, 16 Dec 2017 02:12:44 +0100 Subject: Properly CamelCase service name --- app/services/check_gcp_project_billing_service.rb | 2 +- spec/lib/google_api/cloud_platform/client_spec.rb | 4 ++-- spec/services/check_gcp_project_billing_service_spec.rb | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/services/check_gcp_project_billing_service.rb b/app/services/check_gcp_project_billing_service.rb index 45192a167eb..88e9d7de8de 100644 --- a/app/services/check_gcp_project_billing_service.rb +++ b/app/services/check_gcp_project_billing_service.rb @@ -1,4 +1,4 @@ -class CheckGCPProjectBillingService +class CheckGcpProjectBillingService def execute(token) client = GoogleApi::CloudPlatform::Client.new(token, nil) client.projects_list.any? do |project| diff --git a/spec/lib/google_api/cloud_platform/client_spec.rb b/spec/lib/google_api/cloud_platform/client_spec.rb index 2d14c3b1a3a..f65e41dfea3 100644 --- a/spec/lib/google_api/cloud_platform/client_spec.rb +++ b/spec/lib/google_api/cloud_platform/client_spec.rb @@ -62,8 +62,8 @@ describe GoogleApi::CloudPlatform::Client do it { is_expected.to eq(projects) } end - def projects_get_billing_info - subject { client.projects_get_billing_info } + describe '#projects_get_billing_info' do + subject { client.projects_get_billing_info('project') } let(:billing_info) { double } before do diff --git a/spec/services/check_gcp_project_billing_service_spec.rb b/spec/services/check_gcp_project_billing_service_spec.rb index b7c42fcace1..1b23b43b0d5 100644 --- a/spec/services/check_gcp_project_billing_service_spec.rb +++ b/spec/services/check_gcp_project_billing_service_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe CheckGCPProjectBillingService do +describe CheckGcpProjectBillingService do let(:service) { described_class.new } describe '#execute' do -- cgit v1.2.1 From 68b95cd01e674cd2dbce45c49f5be04c223b718d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sat, 16 Dec 2017 02:39:55 +0100 Subject: Add CheckGcpProjectBillingWorker --- app/workers/check_gcp_project_billing_worker.rb | 16 +++++++++++++++ .../check_gcp_project_billing_worker_spec.rb | 23 ++++++++++++++++++++++ 2 files changed, 39 insertions(+) create mode 100644 app/workers/check_gcp_project_billing_worker.rb create mode 100644 spec/workers/check_gcp_project_billing_worker_spec.rb diff --git a/app/workers/check_gcp_project_billing_worker.rb b/app/workers/check_gcp_project_billing_worker.rb new file mode 100644 index 00000000000..97638f65e8d --- /dev/null +++ b/app/workers/check_gcp_project_billing_worker.rb @@ -0,0 +1,16 @@ +class CheckGcpProjectBillingWorker + include ApplicationWorker + + def self.redis_shared_state_key_for(token) + "gitlab:gcp:#{token}:billing_enabled" + end + + def perform(token) + return unless token + + billing_enabled = CheckGcpProjectBillingService.new.execute(token) + Gitlab::Redis::SharedState.with do |redis| + redis.set(self.class.redis_shared_state_key_for(token), billing_enabled) + end + end +end diff --git a/spec/workers/check_gcp_project_billing_worker_spec.rb b/spec/workers/check_gcp_project_billing_worker_spec.rb new file mode 100644 index 00000000000..c6e624f65be --- /dev/null +++ b/spec/workers/check_gcp_project_billing_worker_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe CheckGcpProjectBillingWorker do + describe '.perform' do + let(:token) { 'bogustoken' } + subject { described_class.new.perform(token) } + + it 'calls the service' do + expect(CheckGcpProjectBillingService).to receive_message_chain(:new, :execute) + + subject + end + + it 'stores billing status in redis' do + expect(CheckGcpProjectBillingService).to receive_message_chain(:new, :execute).and_return(true) + subject + + Gitlab::Redis::SharedState.with do |redis| + expect(redis.get("gitlab:gcp:#{token}:billing_enabled")).to eq('true') + end + end + end +end -- cgit v1.2.1 From 1de0261d5ec9385405291426f56b190148707700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sat, 16 Dec 2017 04:00:54 +0100 Subject: Isolate CheckGcpProjectBillingWorker specreturns --- spec/workers/check_gcp_project_billing_worker_spec.rb | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/spec/workers/check_gcp_project_billing_worker_spec.rb b/spec/workers/check_gcp_project_billing_worker_spec.rb index c6e624f65be..70738f36324 100644 --- a/spec/workers/check_gcp_project_billing_worker_spec.rb +++ b/spec/workers/check_gcp_project_billing_worker_spec.rb @@ -12,12 +12,13 @@ describe CheckGcpProjectBillingWorker do end it 'stores billing status in redis' do + redis_double = double + expect(CheckGcpProjectBillingService).to receive_message_chain(:new, :execute).and_return(true) - subject + expect(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) + expect(redis_double).to receive(:set).with(CheckGcpProjectBillingWorker.redis_shared_state_key_for(token), anything) - Gitlab::Redis::SharedState.with do |redis| - expect(redis.get("gitlab:gcp:#{token}:billing_enabled")).to eq('true') - end + subject end end end -- cgit v1.2.1 From 78f85f3fd3a6743948f044c332cd1243547ef0a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sat, 16 Dec 2017 04:11:44 +0100 Subject: Add check step for creating GCP clusters --- .../projects/clusters/gcp_controller.rb | 25 +++++++- app/views/projects/clusters/gcp/check.html.haml | 1 + app/views/projects/clusters/new.html.haml | 2 +- config/routes/project.rb | 1 + .../projects/clusters/gcp_controller_spec.rb | 71 +++++++++++++++++++++- 5 files changed, 97 insertions(+), 3 deletions(-) create mode 100644 app/views/projects/clusters/gcp/check.html.haml diff --git a/app/controllers/projects/clusters/gcp_controller.rb b/app/controllers/projects/clusters/gcp_controller.rb index b64f7a2a6bd..558dae8e228 100644 --- a/app/controllers/projects/clusters/gcp_controller.rb +++ b/app/controllers/projects/clusters/gcp_controller.rb @@ -1,11 +1,14 @@ class Projects::Clusters::GcpController < Projects::ApplicationController before_action :authorize_read_cluster! before_action :authorize_google_api, except: [:login] + before_action :authorize_google_project_billing, only: [:check] before_action :authorize_create_cluster!, only: [:new, :create] + STATUS_POLLING_INTERVAL = 10_000 + def login begin - state = generate_session_key_redirect(gcp_new_namespace_project_clusters_path.to_s) + state = generate_session_key_redirect(gcp_check_namespace_project_clusters_path.to_s) @authorize_url = GoogleApi::CloudPlatform::Client.new( nil, callback_google_api_auth_url, @@ -15,6 +18,18 @@ class Projects::Clusters::GcpController < Projects::ApplicationController end end + def check + respond_to do |format| + format.json do + Gitlab::PollingInterval.set_header(response, interval: STATUS_POLLING_INTERVAL) + + Gitlab::Redis::SharedState.with do |redis| + render json: { billing: redis.get(CheckGcpProjectBillingWorker.redis_shared_state_key_for(token_in_session)) } + end + end + end + end + def new @cluster = ::Clusters::Cluster.new.tap do |cluster| cluster.build_provider_gcp @@ -57,6 +72,14 @@ class Projects::Clusters::GcpController < Projects::ApplicationController end end + def authorize_google_project_billing + Gitlab::Redis::SharedState.with do |redis| + unless redis.get(CheckGcpProjectBillingWorker.redis_shared_state_key_for(token_in_session)) == 'true' + CheckGcpProjectBillingWorker.perform_async(token_in_session) + end + end + end + def token_in_session @token_in_session ||= session[GoogleApi::CloudPlatform::Client.session_key_for_token] diff --git a/app/views/projects/clusters/gcp/check.html.haml b/app/views/projects/clusters/gcp/check.html.haml new file mode 100644 index 00000000000..e965047ad7c --- /dev/null +++ b/app/views/projects/clusters/gcp/check.html.haml @@ -0,0 +1 @@ +Hello diff --git a/app/views/projects/clusters/new.html.haml b/app/views/projects/clusters/new.html.haml index ddd13f8ea96..22014c49677 100644 --- a/app/views/projects/clusters/new.html.haml +++ b/app/views/projects/clusters/new.html.haml @@ -8,6 +8,6 @@ %h4.prepend-top-0= s_('ClusterIntegration|Choose how to set up cluster integration') %p= s_('ClusterIntegration|Create a new cluster on Google Kubernetes Engine right from GitLab') - = link_to s_('ClusterIntegration|Create on GKE'), gcp_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20' + = link_to s_('ClusterIntegration|Create on GKE'), gcp_check_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20' %p= s_('ClusterIntegration|Enter the details for an existing Kubernetes cluster') = link_to s_('ClusterIntegration|Add an existing cluster'), user_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20' diff --git a/config/routes/project.rb b/config/routes/project.rb index 093da10f57f..26eb4fbeda3 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -189,6 +189,7 @@ constraints(ProjectUrlConstrainer.new) do get '/user/new', to: 'clusters/user#new' post '/user', to: 'clusters/user#create' + get '/gcp/check', to: 'clusters/gcp#check' get '/gcp/new', to: 'clusters/gcp#new' get '/gcp/login', to: 'clusters/gcp#login' post '/gcp', to: 'clusters/gcp#create' diff --git a/spec/controllers/projects/clusters/gcp_controller_spec.rb b/spec/controllers/projects/clusters/gcp_controller_spec.rb index ee7928beb7e..be4b8c1f8dc 100644 --- a/spec/controllers/projects/clusters/gcp_controller_spec.rb +++ b/spec/controllers/projects/clusters/gcp_controller_spec.rb @@ -30,7 +30,7 @@ describe Projects::Clusters::GcpController do go expect(assigns(:authorize_url)).to include(key) - expect(session[session_key_for_redirect_uri]).to eq(gcp_new_project_clusters_path(project)) + expect(session[session_key_for_redirect_uri]).to eq(gcp_check_project_clusters_path(project)) end end @@ -63,6 +63,75 @@ describe Projects::Clusters::GcpController do end end + describe 'GET check' do + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + end + + describe 'functionality' do + context 'when redis has wanted billing status' do + let(:token) { 'bogustoken' } + before do + redis_double = double + allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) + allow(redis_double).to receive(:get).and_return('true') + end + + it 'should render json with billing status' do + go + + expect(response).to have_http_status(:ok) + expect(response.body).to include_json(billing: 'true') + end + + it 'should not start worker' do + expect(CheckGcpProjectBillingWorker).not_to receive(:perform_async) + + go + end + end + + context 'when redis does not have billing status' do + before do + redis_double = double + allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) + allow(redis_double).to receive(:get).and_return(nil) + end + + it 'should render json with null billing status' do + go + + expect(response).to have_http_status(:ok) + expect(response.body).to include_json(billing: nil) + end + + it 'should start worker' do + expect(CheckGcpProjectBillingWorker).to receive(:perform_async) + + go + end + end + end + + describe 'security' do + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(project) } + it { expect { go }.to be_allowed_for(:master).of(project) } + it { expect { go }.to be_denied_for(:developer).of(project) } + it { expect { go }.to be_denied_for(:reporter).of(project) } + it { expect { go }.to be_denied_for(:guest).of(project) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + + def go + get :check, namespace_id: project.namespace, project_id: project, format: :json + end + end + describe 'GET new' do describe 'functionality' do let(:user) { create(:user) } -- cgit v1.2.1 From 99043d244c4d579f27382f003df9e3243287df2a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sat, 16 Dec 2017 04:21:13 +0100 Subject: Add lease to CheckGcpProjectBillingWorker --- app/workers/check_gcp_project_billing_worker.rb | 11 +++++++ .../check_gcp_project_billing_worker_spec.rb | 36 ++++++++++++++++------ 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/app/workers/check_gcp_project_billing_worker.rb b/app/workers/check_gcp_project_billing_worker.rb index 97638f65e8d..254b0959063 100644 --- a/app/workers/check_gcp_project_billing_worker.rb +++ b/app/workers/check_gcp_project_billing_worker.rb @@ -1,16 +1,27 @@ class CheckGcpProjectBillingWorker include ApplicationWorker + LEASE_TIMEOUT = 1.minute.to_i + def self.redis_shared_state_key_for(token) "gitlab:gcp:#{token}:billing_enabled" end def perform(token) return unless token + return unless try_obtain_lease_for(token) billing_enabled = CheckGcpProjectBillingService.new.execute(token) Gitlab::Redis::SharedState.with do |redis| redis.set(self.class.redis_shared_state_key_for(token), billing_enabled) end end + + private + + def try_obtain_lease_for(token) + Gitlab::ExclusiveLease + .new("check_gcp_project_billing_worker:#{token}", timeout: LEASE_TIMEOUT) + .try_obtain + end end diff --git a/spec/workers/check_gcp_project_billing_worker_spec.rb b/spec/workers/check_gcp_project_billing_worker_spec.rb index 70738f36324..ce9632549b6 100644 --- a/spec/workers/check_gcp_project_billing_worker_spec.rb +++ b/spec/workers/check_gcp_project_billing_worker_spec.rb @@ -5,20 +5,38 @@ describe CheckGcpProjectBillingWorker do let(:token) { 'bogustoken' } subject { described_class.new.perform(token) } - it 'calls the service' do - expect(CheckGcpProjectBillingService).to receive_message_chain(:new, :execute) + context 'when there is no lease' do + before do + allow_any_instance_of(CheckGcpProjectBillingWorker).to receive(:try_obtain_lease_for).and_return('randomuuid') + end - subject + it 'calls the service' do + expect(CheckGcpProjectBillingService).to receive_message_chain(:new, :execute) + + subject + end + + it 'stores billing status in redis' do + redis_double = double + + expect(CheckGcpProjectBillingService).to receive_message_chain(:new, :execute).and_return(true) + expect(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) + expect(redis_double).to receive(:set).with(CheckGcpProjectBillingWorker.redis_shared_state_key_for(token), anything) + + subject + end end - it 'stores billing status in redis' do - redis_double = double + context 'when there is a lease' do + before do + allow_any_instance_of(CheckGcpProjectBillingWorker).to receive(:try_obtain_lease_for).and_return(false) + end - expect(CheckGcpProjectBillingService).to receive_message_chain(:new, :execute).and_return(true) - expect(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) - expect(redis_double).to receive(:set).with(CheckGcpProjectBillingWorker.redis_shared_state_key_for(token), anything) + it 'does not call the service' do + expect(CheckGcpProjectBillingService).not_to receive(:new) - subject + subject + end end end end -- cgit v1.2.1 From 935a27cfef3c5a4dd9291c21af69b41a7169817d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sat, 16 Dec 2017 04:22:16 +0100 Subject: Use 1 minute for status polling interval --- app/controllers/projects/clusters/gcp_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/projects/clusters/gcp_controller.rb b/app/controllers/projects/clusters/gcp_controller.rb index 558dae8e228..940c2a5d84f 100644 --- a/app/controllers/projects/clusters/gcp_controller.rb +++ b/app/controllers/projects/clusters/gcp_controller.rb @@ -4,7 +4,7 @@ class Projects::Clusters::GcpController < Projects::ApplicationController before_action :authorize_google_project_billing, only: [:check] before_action :authorize_create_cluster!, only: [:new, :create] - STATUS_POLLING_INTERVAL = 10_000 + STATUS_POLLING_INTERVAL = 1.minute.to_i def login begin -- cgit v1.2.1 From 914260930f800342c495114f507947ae35471e80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sat, 16 Dec 2017 05:26:07 +0100 Subject: Expand controller test suite matrix --- .../projects/clusters/gcp_controller.rb | 5 +- app/views/projects/clusters/new.html.haml | 2 +- .../projects/clusters/gcp_controller_spec.rb | 138 +++++++++++++++------ spec/support/google_api/cloud_platform_helpers.rb | 6 + .../check_gcp_project_billing_worker_spec.rb | 6 +- 5 files changed, 111 insertions(+), 46 deletions(-) diff --git a/app/controllers/projects/clusters/gcp_controller.rb b/app/controllers/projects/clusters/gcp_controller.rb index 940c2a5d84f..95c947001a3 100644 --- a/app/controllers/projects/clusters/gcp_controller.rb +++ b/app/controllers/projects/clusters/gcp_controller.rb @@ -1,14 +1,14 @@ class Projects::Clusters::GcpController < Projects::ApplicationController before_action :authorize_read_cluster! before_action :authorize_google_api, except: [:login] - before_action :authorize_google_project_billing, only: [:check] + before_action :authorize_google_project_billing, except: [:login, :check] before_action :authorize_create_cluster!, only: [:new, :create] STATUS_POLLING_INTERVAL = 1.minute.to_i def login begin - state = generate_session_key_redirect(gcp_check_namespace_project_clusters_path.to_s) + state = generate_session_key_redirect(gcp_new_namespace_project_clusters_path.to_s) @authorize_url = GoogleApi::CloudPlatform::Client.new( nil, callback_google_api_auth_url, @@ -76,6 +76,7 @@ class Projects::Clusters::GcpController < Projects::ApplicationController Gitlab::Redis::SharedState.with do |redis| unless redis.get(CheckGcpProjectBillingWorker.redis_shared_state_key_for(token_in_session)) == 'true' CheckGcpProjectBillingWorker.perform_async(token_in_session) + redirect_to action: 'check' end end end diff --git a/app/views/projects/clusters/new.html.haml b/app/views/projects/clusters/new.html.haml index 22014c49677..ddd13f8ea96 100644 --- a/app/views/projects/clusters/new.html.haml +++ b/app/views/projects/clusters/new.html.haml @@ -8,6 +8,6 @@ %h4.prepend-top-0= s_('ClusterIntegration|Choose how to set up cluster integration') %p= s_('ClusterIntegration|Create a new cluster on Google Kubernetes Engine right from GitLab') - = link_to s_('ClusterIntegration|Create on GKE'), gcp_check_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20' + = link_to s_('ClusterIntegration|Create on GKE'), gcp_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20' %p= s_('ClusterIntegration|Enter the details for an existing Kubernetes cluster') = link_to s_('ClusterIntegration|Add an existing cluster'), user_new_namespace_project_clusters_path(@project.namespace, @project), class: 'btn append-bottom-20' diff --git a/spec/controllers/projects/clusters/gcp_controller_spec.rb b/spec/controllers/projects/clusters/gcp_controller_spec.rb index be4b8c1f8dc..852f3efe793 100644 --- a/spec/controllers/projects/clusters/gcp_controller_spec.rb +++ b/spec/controllers/projects/clusters/gcp_controller_spec.rb @@ -17,7 +17,6 @@ describe Projects::Clusters::GcpController do context 'when omniauth has been configured' do let(:key) { 'secret-key' } - let(:session_key_for_redirect_uri) do GoogleApi::CloudPlatform::Client.session_key_for_redirect_uri(key) end @@ -30,7 +29,7 @@ describe Projects::Clusters::GcpController do go expect(assigns(:authorize_url)).to include(key) - expect(session[session_key_for_redirect_uri]).to eq(gcp_check_project_clusters_path(project)) + expect(session[session_key_for_redirect_uri]).to eq(gcp_new_project_clusters_path(project)) end end @@ -72,47 +71,54 @@ describe Projects::Clusters::GcpController do end describe 'functionality' do - context 'when redis has wanted billing status' do - let(:token) { 'bogustoken' } + context 'when access token is valid' do before do - redis_double = double - allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) - allow(redis_double).to receive(:get).and_return('true') + stub_google_api_validate_token end - it 'should render json with billing status' do - go + context 'when redis has wanted billing status' do + let(:token) { 'bogustoken' } + + before do + redis_double = double + allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) + allow(redis_double).to receive(:get).and_return('true') + end - expect(response).to have_http_status(:ok) - expect(response.body).to include_json(billing: 'true') + it 'should render json with billing status' do + go + + expect(response).to have_http_status(:ok) + expect(JSON.parse(response.body)).to include('billing' => 'true') + end end - it 'should not start worker' do - expect(CheckGcpProjectBillingWorker).not_to receive(:perform_async) + context 'when redis does not have billing status' do + before do + redis_double = double + allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) + allow(redis_double).to receive(:get).and_return(nil) + end + + it 'should render json with null billing status' do + go - go + expect(response).to have_http_status(:ok) + expect(JSON.parse(response.body)).to include('billing' => nil) + end end end - context 'when redis does not have billing status' do + context 'when access token is expired' do before do - redis_double = double - allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) - allow(redis_double).to receive(:get).and_return(nil) - end - - it 'should render json with null billing status' do - go - - expect(response).to have_http_status(:ok) - expect(response.body).to include_json(billing: nil) + stub_google_api_expired_token end - it 'should start worker' do - expect(CheckGcpProjectBillingWorker).to receive(:perform_async) + it { expect(go).to redirect_to(gcp_login_project_clusters_path(project)) } + end - go - end + context 'when access token is not stored in session' do + it { expect(go).to redirect_to(gcp_login_project_clusters_path(project)) } end end @@ -146,10 +152,36 @@ describe Projects::Clusters::GcpController do stub_google_api_validate_token end - it 'has new object' do - go + context 'when google project billing status is true' do + before do + stub_google_project_billing_status + end + + it 'has new object' do + go + + expect(assigns(:cluster)).to be_an_instance_of(Clusters::Cluster) + end + end + + context 'when google project billing status is not true' do + before do + redis_double = double + allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) + allow(redis_double).to receive(:get).and_return(nil) + end + + it 'redirects to check page' do + allow(CheckGcpProjectBillingWorker).to receive(:perform_async) + + expect(go).to redirect_to(gcp_check_project_clusters_path(project)) + end + + it 'calls gcp project billing check worker' do + expect(CheckGcpProjectBillingWorker).to receive(:perform_async) - expect(assigns(:cluster)).to be_an_instance_of(Clusters::Cluster) + go + end end end @@ -207,14 +239,40 @@ describe Projects::Clusters::GcpController do stub_google_api_validate_token end - context 'when creates a cluster on gke' do - it 'creates a new cluster' do - expect(ClusterProvisionWorker).to receive(:perform_async) - expect { go }.to change { Clusters::Cluster.count } - .and change { Clusters::Providers::Gcp.count } - expect(response).to redirect_to(project_cluster_path(project, project.clusters.first)) - expect(project.clusters.first).to be_gcp - expect(project.clusters.first).to be_kubernetes + context 'when google project billing status is true' do + before do + stub_google_project_billing_status + end + + context 'when creates a cluster on gke' do + it 'creates a new cluster' do + expect(ClusterProvisionWorker).to receive(:perform_async) + expect { go }.to change { Clusters::Cluster.count } + .and change { Clusters::Providers::Gcp.count } + expect(response).to redirect_to(project_cluster_path(project, project.clusters.first)) + expect(project.clusters.first).to be_gcp + expect(project.clusters.first).to be_kubernetes + end + end + end + + context 'when google project billing status is not true' do + before do + redis_double = double + allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) + allow(redis_double).to receive(:get).and_return(nil) + end + + it 'redirects to check page' do + allow(CheckGcpProjectBillingWorker).to receive(:perform_async) + + expect(go).to redirect_to(gcp_check_project_clusters_path(project)) + end + + it 'calls gcp project billing check worker' do + expect(CheckGcpProjectBillingWorker).to receive(:perform_async) + + go end end end diff --git a/spec/support/google_api/cloud_platform_helpers.rb b/spec/support/google_api/cloud_platform_helpers.rb index 8a073e58db8..9f9289fa580 100644 --- a/spec/support/google_api/cloud_platform_helpers.rb +++ b/spec/support/google_api/cloud_platform_helpers.rb @@ -10,6 +10,12 @@ module GoogleApi request.session[GoogleApi::CloudPlatform::Client.session_key_for_expires_at] = 1.hour.ago.to_i.to_s end + def stub_google_project_billing_status + redis_double = double + allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) + allow(redis_double).to receive(:get).and_return('true') + end + def stub_cloud_platform_get_zone_cluster(project_id, zone, cluster_id, **options) WebMock.stub_request(:get, cloud_platform_get_zone_cluster_url(project_id, zone, cluster_id)) .to_return(cloud_platform_response(cloud_platform_cluster_body(options))) diff --git a/spec/workers/check_gcp_project_billing_worker_spec.rb b/spec/workers/check_gcp_project_billing_worker_spec.rb index ce9632549b6..d7984ad0c1b 100644 --- a/spec/workers/check_gcp_project_billing_worker_spec.rb +++ b/spec/workers/check_gcp_project_billing_worker_spec.rb @@ -7,7 +7,7 @@ describe CheckGcpProjectBillingWorker do context 'when there is no lease' do before do - allow_any_instance_of(CheckGcpProjectBillingWorker).to receive(:try_obtain_lease_for).and_return('randomuuid') + allow_any_instance_of(described_class).to receive(:try_obtain_lease_for).and_return('randomuuid') end it 'calls the service' do @@ -21,7 +21,7 @@ describe CheckGcpProjectBillingWorker do expect(CheckGcpProjectBillingService).to receive_message_chain(:new, :execute).and_return(true) expect(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) - expect(redis_double).to receive(:set).with(CheckGcpProjectBillingWorker.redis_shared_state_key_for(token), anything) + expect(redis_double).to receive(:set).with(described_class.redis_shared_state_key_for(token), anything) subject end @@ -29,7 +29,7 @@ describe CheckGcpProjectBillingWorker do context 'when there is a lease' do before do - allow_any_instance_of(CheckGcpProjectBillingWorker).to receive(:try_obtain_lease_for).and_return(false) + allow_any_instance_of(described_class).to receive(:try_obtain_lease_for).and_return(false) end it 'does not call the service' do -- cgit v1.2.1 From 6c0fd3c22dc767d8d4d90fa0a008874098a6f22c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sat, 16 Dec 2017 05:31:53 +0100 Subject: Handle html format in addition to json --- app/controllers/projects/clusters/gcp_controller.rb | 2 ++ config/routes/project.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/clusters/gcp_controller.rb b/app/controllers/projects/clusters/gcp_controller.rb index 95c947001a3..34d4fd7d7ca 100644 --- a/app/controllers/projects/clusters/gcp_controller.rb +++ b/app/controllers/projects/clusters/gcp_controller.rb @@ -27,6 +27,8 @@ class Projects::Clusters::GcpController < Projects::ApplicationController render json: { billing: redis.get(CheckGcpProjectBillingWorker.redis_shared_state_key_for(token_in_session)) } end end + + format.html { render :check } end end diff --git a/config/routes/project.rb b/config/routes/project.rb index 26eb4fbeda3..9fbd0476bb8 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -189,9 +189,9 @@ constraints(ProjectUrlConstrainer.new) do get '/user/new', to: 'clusters/user#new' post '/user', to: 'clusters/user#create' - get '/gcp/check', to: 'clusters/gcp#check' get '/gcp/new', to: 'clusters/gcp#new' get '/gcp/login', to: 'clusters/gcp#login' + get '/gcp/check', to: 'clusters/gcp#check' post '/gcp', to: 'clusters/gcp#create' end end -- cgit v1.2.1 From c98238f18ac8aa68c971f0742b881e24daf258aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sat, 16 Dec 2017 06:07:28 +0100 Subject: Inluce projects namespace when checking billing --- lib/google_api/cloud_platform/client.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/google_api/cloud_platform/client.rb b/lib/google_api/cloud_platform/client.rb index c107d001bec..f05d001fd02 100644 --- a/lib/google_api/cloud_platform/client.rb +++ b/lib/google_api/cloud_platform/client.rb @@ -55,7 +55,7 @@ module GoogleApi service = Google::Apis::CloudbillingV1::CloudbillingService.new service.authorization = access_token - service.get_project_billing_info(project_name) + service.get_project_billing_info("projects/#{project_name}") end def projects_zones_clusters_get(project_id, zone, cluster_id) -- cgit v1.2.1 From 63859419b284ff9c4eba0a1f0df6d8d72764fc50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sat, 16 Dec 2017 16:07:44 +0100 Subject: Add CheckGcpProjectBillingWorker to sidekiq queue --- config/sidekiq_queues.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/config/sidekiq_queues.yml b/config/sidekiq_queues.yml index bc7c431731a..0a7b4b7c101 100644 --- a/config/sidekiq_queues.yml +++ b/config/sidekiq_queues.yml @@ -66,5 +66,6 @@ - [propagate_service_template, 1] - [background_migration, 1] - [gcp_cluster, 1] + - [check_gcp_project_billing, 1] - [project_migrate_hashed_storage, 1] - [storage_migrator, 1] -- cgit v1.2.1 From 886fd13fceda053533a382d1652f9fcce475d0e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sat, 16 Dec 2017 17:02:26 +0100 Subject: Add Worker rerun action to GcpController --- .../projects/clusters/gcp_controller.rb | 11 ++++- config/routes/project.rb | 1 + .../projects/clusters/gcp_controller_spec.rb | 50 ++++++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/app/controllers/projects/clusters/gcp_controller.rb b/app/controllers/projects/clusters/gcp_controller.rb index 34d4fd7d7ca..c965a055fdd 100644 --- a/app/controllers/projects/clusters/gcp_controller.rb +++ b/app/controllers/projects/clusters/gcp_controller.rb @@ -1,7 +1,7 @@ class Projects::Clusters::GcpController < Projects::ApplicationController before_action :authorize_read_cluster! before_action :authorize_google_api, except: [:login] - before_action :authorize_google_project_billing, except: [:login, :check] + before_action :authorize_google_project_billing, except: [:login, :check, :run_check] before_action :authorize_create_cluster!, only: [:new, :create] STATUS_POLLING_INTERVAL = 1.minute.to_i @@ -32,6 +32,15 @@ class Projects::Clusters::GcpController < Projects::ApplicationController end end + def run_check + respond_to do |format| + format.json do + CheckGcpProjectBillingWorker.perform_async(token_in_session) + head :no_content + end + end + end + def new @cluster = ::Clusters::Cluster.new.tap do |cluster| cluster.build_provider_gcp diff --git a/config/routes/project.rb b/config/routes/project.rb index 9fbd0476bb8..d1e8c0ee267 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -192,6 +192,7 @@ constraints(ProjectUrlConstrainer.new) do get '/gcp/new', to: 'clusters/gcp#new' get '/gcp/login', to: 'clusters/gcp#login' get '/gcp/check', to: 'clusters/gcp#check' + post '/gcp/check', to: 'clusters/gcp#run_check' post '/gcp', to: 'clusters/gcp#create' end end diff --git a/spec/controllers/projects/clusters/gcp_controller_spec.rb b/spec/controllers/projects/clusters/gcp_controller_spec.rb index 852f3efe793..4fa798c5856 100644 --- a/spec/controllers/projects/clusters/gcp_controller_spec.rb +++ b/spec/controllers/projects/clusters/gcp_controller_spec.rb @@ -138,6 +138,56 @@ describe Projects::Clusters::GcpController do end end + describe 'POST check' do + let(:user) { create(:user) } + + before do + project.add_master(user) + sign_in(user) + end + + describe 'functionality' do + context 'when access token is valid' do + before do + stub_google_api_validate_token + end + + it 'calls check worker asynchronously' do + expect(CheckGcpProjectBillingWorker).to receive(:perform_async) + + expect(go).to have_http_status(:no_content) + end + end + + context 'when access token is expired' do + before do + stub_google_api_expired_token + end + + it { expect(go).to redirect_to(gcp_login_project_clusters_path(project)) } + end + + context 'when access token is not stored in session' do + it { expect(go).to redirect_to(gcp_login_project_clusters_path(project)) } + end + end + + describe 'security' do + it { expect { go }.to be_allowed_for(:admin) } + it { expect { go }.to be_allowed_for(:owner).of(project) } + it { expect { go }.to be_allowed_for(:master).of(project) } + it { expect { go }.to be_denied_for(:developer).of(project) } + it { expect { go }.to be_denied_for(:reporter).of(project) } + it { expect { go }.to be_denied_for(:guest).of(project) } + it { expect { go }.to be_denied_for(:user) } + it { expect { go }.to be_denied_for(:external) } + end + + def go + post :run_check, namespace_id: project.namespace, project_id: project, format: :json + end + end + describe 'GET new' do describe 'functionality' do let(:user) { create(:user) } -- cgit v1.2.1 From 614c0e0bf9c404ba43f835166183a2f1883071d1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sat, 16 Dec 2017 18:55:47 +0100 Subject: Update GCP feature spec with check page flow --- spec/features/projects/clusters/gcp_spec.rb | 153 ++++++++++++++++------------ 1 file changed, 86 insertions(+), 67 deletions(-) diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index 67b8901f8fb..4b682acd47e 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -20,105 +20,124 @@ feature 'Gcp Cluster', :js do .to receive(:expires_at_in_session).and_return(1.hour.since.to_i.to_s) end - context 'when user does not have a cluster and visits cluster index page' do + context 'when user has a GCP project with billing enabled' do before do - visit project_clusters_path(project) - - click_link 'Add cluster' - click_link 'Create on GKE' + stub_google_project_billing_status end - context 'when user filled form with valid parameters' do + context 'when user does not have a cluster and visits cluster index page' do before do - allow_any_instance_of(GoogleApi::CloudPlatform::Client) - .to receive(:projects_zones_clusters_create) do - OpenStruct.new( - self_link: 'projects/gcp-project-12345/zones/us-central1-a/operations/ope-123', - status: 'RUNNING' - ) + visit project_clusters_path(project) + + click_link 'Add cluster' + click_link 'Create on GKE' + end + + context 'when user filled form with valid parameters' do + before do + allow_any_instance_of(GoogleApi::CloudPlatform::Client) + .to receive(:projects_zones_clusters_create) do + OpenStruct.new( + self_link: 'projects/gcp-project-12345/zones/us-central1-a/operations/ope-123', + status: 'RUNNING' + ) + end + + allow(WaitForClusterCreationWorker).to receive(:perform_in).and_return(nil) + + fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123' + fill_in 'cluster_name', with: 'dev-cluster' + click_button 'Create cluster' end - allow(WaitForClusterCreationWorker).to receive(:perform_in).and_return(nil) + it 'user sees a cluster details page and creation status' do + expect(page).to have_content('Cluster is being created on Google Kubernetes Engine...') - fill_in 'cluster_provider_gcp_attributes_gcp_project_id', with: 'gcp-project-123' - fill_in 'cluster_name', with: 'dev-cluster' - click_button 'Create cluster' - end + Clusters::Cluster.last.provider.make_created! - it 'user sees a cluster details page and creation status' do - expect(page).to have_content('Cluster is being created on Google Kubernetes Engine...') + expect(page).to have_content('Cluster was successfully created on Google Kubernetes Engine') + end - Clusters::Cluster.last.provider.make_created! + it 'user sees a error if something worng during creation' do + expect(page).to have_content('Cluster is being created on Google Kubernetes Engine...') - expect(page).to have_content('Cluster was successfully created on Google Kubernetes Engine') - end + Clusters::Cluster.last.provider.make_errored!('Something wrong!') - it 'user sees a error if something worng during creation' do - expect(page).to have_content('Cluster is being created on Google Kubernetes Engine...') + expect(page).to have_content('Something wrong!') + end + end - Clusters::Cluster.last.provider.make_errored!('Something wrong!') + context 'when user filled form with invalid parameters' do + before do + click_button 'Create cluster' + end - expect(page).to have_content('Something wrong!') + it 'user sees a validation error' do + expect(page).to have_css('#error_explanation') + end end end - context 'when user filled form with invalid parameters' do + context 'when user does have a cluster and visits cluster page' do + let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } + before do - click_button 'Create cluster' + visit project_cluster_path(project, cluster) end - it 'user sees a validation error' do - expect(page).to have_css('#error_explanation') + it 'user sees a cluster details page' do + expect(page).to have_button('Save') + expect(page.find(:css, '.cluster-name').value).to eq(cluster.name) end - end - end - context 'when user does have a cluster and visits cluster page' do - let(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } + context 'when user disables the cluster' do + before do + page.find(:css, '.js-toggle-cluster').click + click_button 'Save' + end - before do - visit project_cluster_path(project, cluster) - end + it 'user sees the successful message' do + expect(page).to have_content('Cluster was successfully updated.') + end + end - it 'user sees a cluster details page' do - expect(page).to have_button('Save') - expect(page.find(:css, '.cluster-name').value).to eq(cluster.name) - end + context 'when user changes cluster parameters' do + before do + fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace' + click_button 'Save changes' + end - context 'when user disables the cluster' do - before do - page.find(:css, '.js-toggle-cluster').click - click_button 'Save' + it 'user sees the successful message' do + expect(page).to have_content('Cluster was successfully updated.') + expect(cluster.reload.platform_kubernetes.namespace).to eq('my-namespace') + end end - it 'user sees the successful message' do - expect(page).to have_content('Cluster was successfully updated.') + context 'when user destroy the cluster' do + before do + page.accept_confirm do + click_link 'Remove integration' + end + end + + it 'user sees creation form with the successful message' do + expect(page).to have_content('Cluster integration was successfully removed.') + expect(page).to have_link('Add cluster') + end end end + end - context 'when user changes cluster parameters' do - before do - fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace' - click_button 'Save changes' - end + context 'when user does not have a GCP project with billing enabled' do + before do + visit project_clusters_path(project) - it 'user sees the successful message' do - expect(page).to have_content('Cluster was successfully updated.') - expect(cluster.reload.platform_kubernetes.namespace).to eq('my-namespace') - end + click_link 'Add cluster' + click_link 'Create on GKE' end - context 'when user destroy the cluster' do - before do - page.accept_confirm do - click_link 'Remove integration' - end - end - - it 'user sees creation form with the successful message' do - expect(page).to have_content('Cluster integration was successfully removed.') - expect(page).to have_link('Add cluster') - end + it 'user sees a check page' do + expect(page).to have_link('Continue') end end end -- cgit v1.2.1 From b7f59772b16b39baca7dbe2cb5dd830b7382c038 Mon Sep 17 00:00:00 2001 From: Christiaan Van den Poel Date: Sun, 17 Dec 2017 21:07:29 +0100 Subject: remove the label --- app/assets/javascripts/boards/boards_bundle.js | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js index 20d23162940..0c1cff1da7a 100644 --- a/app/assets/javascripts/boards/boards_bundle.js +++ b/app/assets/javascripts/boards/boards_bundle.js @@ -102,7 +102,6 @@ $(() => { if (list.type === 'closed') { list.position = Infinity; - list.label = { description: 'Shows all closed issues. Moving an issue to this list closes it' }; } else if (list.type === 'backlog') { list.position = -1; } -- cgit v1.2.1 From 148c7533d701490eb5cd8aebc4fa033e0f0f6ec4 Mon Sep 17 00:00:00 2001 From: Christiaan Van den Poel Date: Sun, 17 Dec 2017 21:09:57 +0100 Subject: added changelog --- ...how_proper_labels_in_board_issue_sidebar_when_issue_is_closed.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/show_proper_labels_in_board_issue_sidebar_when_issue_is_closed.yml diff --git a/changelogs/unreleased/show_proper_labels_in_board_issue_sidebar_when_issue_is_closed.yml b/changelogs/unreleased/show_proper_labels_in_board_issue_sidebar_when_issue_is_closed.yml new file mode 100644 index 00000000000..c2ab34b20a5 --- /dev/null +++ b/changelogs/unreleased/show_proper_labels_in_board_issue_sidebar_when_issue_is_closed.yml @@ -0,0 +1,5 @@ +--- +title: show None when issue is in closed list and no labels assigned +merge_request: 15976 +author: Christiaan Van den Poel +type: fixed -- cgit v1.2.1 From 754c4d97e591c96dbe8e6525227deeaf21cf7987 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 18 Dec 2017 12:37:48 +0000 Subject: Remove wrong padding from pipelines.scss --- app/assets/stylesheets/pages/pipelines.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 9805fc4f882..bf8e515a3b7 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -792,7 +792,6 @@ button.mini-pipeline-graph-dropdown-toggle { // link to the build .mini-pipeline-graph-dropdown-item { - padding: 3px 7px 4px; align-items: center; clear: both; display: flex; -- cgit v1.2.1 From 572de0c1c2788d8d434a967f6ef0cb144f7f668f Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Tue, 19 Dec 2017 11:04:04 +0000 Subject: Adds illustrations for manual actions and non triggered jobs --- app/assets/images/icons.json | 2 +- app/assets/images/icons.svg | 2 +- .../images/illustrations/job_not_triggered.svg | 1 + app/assets/images/illustrations/manual_action.svg | 1 + .../images/illustrations/service_desk_callout.svg | 1 + .../images/illustrations/service_desk_empty.svg | 1 + app/assets/javascripts/jobs/components/header.vue | 6 +- .../vue_shared/components/header_ci_component.vue | 12 +++- app/serializers/job_entity.rb | 2 + app/views/projects/jobs/show.html.haml | 78 ++++++++++++--------- spec/features/projects/jobs_spec.rb | 28 ++++++++ spec/javascripts/jobs/header_spec.js | 3 +- .../components/header_ci_component_spec.js | 81 ++++++++++++---------- 13 files changed, 146 insertions(+), 72 deletions(-) create mode 100644 app/assets/images/illustrations/job_not_triggered.svg create mode 100644 app/assets/images/illustrations/manual_action.svg create mode 100644 app/assets/images/illustrations/service_desk_callout.svg create mode 100644 app/assets/images/illustrations/service_desk_empty.svg diff --git a/app/assets/images/icons.json b/app/assets/images/icons.json index 68d6528758b..1e06ef42b45 100644 --- a/app/assets/images/icons.json +++ b/app/assets/images/icons.json @@ -1 +1 @@ -{"iconCount":181,"spriteSize":81482,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","image-comment-light","import","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]} \ No newline at end of file +{"iconCount":184,"spriteSize":83050,"icons":["abuse","account","admin","angle-double-left","angle-double-right","angle-down","angle-left","angle-right","angle-up","appearance","applications","approval","arrow-down","arrow-right","assignee","bold","book","bookmark","branch","bullhorn","calendar","cancel","chart","chevron-down","chevron-left","chevron-right","chevron-up","clock","close","code","collapse","comment-dots","comment-next","comment","comments","commit","credit-card","cut","dashboard","disk","doc_code","doc_image","doc_text","double-headed-arrow","download","duplicate","earth","ellipsis_v","emoji_slightly_smiling_face","emoji_smile","emoji_smiley","epic","external-link","eye-slash","eye","file-addition","file-deletion","file-modified","filter","folder-o","folder-open","folder","fork","geo-nodes","git-merge","group","history","home","hook","hourglass","image-comment-dark","image-comment-light","import","issue-block","issue-child","issue-close","issue-duplicate","issue-new","issue-open-m","issue-open","issue-parent","issues","italic","key-2","key","label","labels","leave","level-up","license","link","list-bulleted","list-numbered","location-dot","location","lock-open","lock","log","mail","menu","merge-request-close","messages","mobile-issue-close","monitor","more","notifications-off","notifications","overview","pencil-square","pencil","pipeline","play","plus-square-o","plus-square","plus","preferences","profile","project","push-rules","question-o","question","quote","redo","remove","repeat","retry","scale","screen-full","screen-normal","scroll_down","scroll_up","search","settings","shield","slight-frown","slight-smile","smile","smiley","snippet","spam","spinner","star-o","star","status_canceled_borderless","status_canceled","status_closed","status_created_borderless","status_created","status_failed_borderless","status_failed","status_manual_borderless","status_manual","status_notfound_borderless","status_open","status_pending_borderless","status_pending","status_running_borderless","status_running","status_skipped_borderless","status_skipped","status_success_borderless","status_success_solid","status_success","status_warning_borderless","status_warning","stop","task-done","template","terminal","thumb-down","thumb-up","thumbtack","timer","todo-add","todo-done","token","unapproval","unassignee","unlink","user","users","volume-up","warning","work"]} \ No newline at end of file diff --git a/app/assets/images/icons.svg b/app/assets/images/icons.svg index fd8f7862911..d5b2f597b4d 100644 --- a/app/assets/images/icons.svg +++ b/app/assets/images/icons.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/app/assets/images/illustrations/job_not_triggered.svg b/app/assets/images/illustrations/job_not_triggered.svg new file mode 100644 index 00000000000..e13c1cb0a7d --- /dev/null +++ b/app/assets/images/illustrations/job_not_triggered.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/illustrations/manual_action.svg b/app/assets/images/illustrations/manual_action.svg new file mode 100644 index 00000000000..85735855b46 --- /dev/null +++ b/app/assets/images/illustrations/manual_action.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/illustrations/service_desk_callout.svg b/app/assets/images/illustrations/service_desk_callout.svg new file mode 100644 index 00000000000..2886388279e --- /dev/null +++ b/app/assets/images/illustrations/service_desk_callout.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/images/illustrations/service_desk_empty.svg b/app/assets/images/illustrations/service_desk_empty.svg new file mode 100644 index 00000000000..daaaeae6a17 --- /dev/null +++ b/app/assets/images/illustrations/service_desk_empty.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/javascripts/jobs/components/header.vue b/app/assets/javascripts/jobs/components/header.vue index 6d671845f8e..0de823831d9 100644 --- a/app/assets/javascripts/jobs/components/header.vue +++ b/app/assets/javascripts/jobs/components/header.vue @@ -30,6 +30,9 @@ shouldRenderContent() { return !this.isLoading && Object.keys(this.job).length; }, + wasTriggered() { + return this.job.started; + }, }, methods: { getActions() { @@ -63,7 +66,8 @@ :time="job.created_at" :user="job.user" :actions="actions" - :hasSidebarButton="true" + :has-sidebar-button="true" + :triggered="wasTriggered" /> - triggered + + diff --git a/app/serializers/job_entity.rb b/app/serializers/job_entity.rb index 72e56a2c77f..523b522d449 100644 --- a/app/serializers/job_entity.rb +++ b/app/serializers/job_entity.rb @@ -4,6 +4,8 @@ class JobEntity < Grape::Entity expose :id expose :name + expose :started?, as: :started + expose :build_path do |build| build.target_url || path_to(:namespace_project_job, build) end diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index 1d0aaa47b60..f7d06d8ceb9 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -54,41 +54,55 @@ Job has been erased by #{link_to(@build.erased_by_name, user_path(@build.erased_by))} #{time_ago_with_tooltip(@build.erased_at)} - else Job has been erased #{time_ago_with_tooltip(@build.erased_at)} + - if @build.started? + .build-trace-container.prepend-top-default + .top-bar.js-top-bar + .js-truncated-info.truncated-info.hidden-xs.pull-left.hidden< + Showing last + %span.js-truncated-info-size.truncated-info-size>< + KiB of log - + %a.js-raw-link.raw-link{ href: raw_project_job_path(@project, @build) }>< Complete Raw - .build-trace-container.prepend-top-default - .top-bar.js-top-bar - .js-truncated-info.truncated-info.hidden-xs.pull-left.hidden< - Showing last - %span.js-truncated-info-size.truncated-info-size>< - KiB of log - - %a.js-raw-link.raw-link{ href: raw_project_job_path(@project, @build) }>< Complete Raw + .controllers.pull-right + - if @build.has_trace? + = link_to raw_project_job_path(@project, @build), + title: 'Show complete raw', + data: { placement: 'top', container: 'body' }, + class: 'js-raw-link-controller has-tooltip controllers-buttons' do + = icon('file-text-o') - .controllers.pull-right - - if @build.has_trace? - = link_to raw_project_job_path(@project, @build), - title: 'Show complete raw', - data: { placement: 'top', container: 'body' }, - class: 'js-raw-link-controller has-tooltip controllers-buttons' do - = icon('file-text-o') - - - if @build.erasable? && can?(current_user, :erase_build, @build) - = link_to erase_project_job_path(@project, @build), - method: :post, - data: { confirm: 'Are you sure you want to erase this build?', placement: 'top', container: 'body' }, - title: 'Erase job log', - class: 'has-tooltip js-erase-link controllers-buttons' do - = icon('trash') - .has-tooltip.controllers-buttons{ title: 'Scroll to top', data: { placement: 'top', container: 'body'} } - %button.js-scroll-up.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true } - = custom_icon('scroll_up') - .has-tooltip.controllers-buttons{ title: 'Scroll to bottom', data: { placement: 'top', container: 'body'} } - %button.js-scroll-down.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true } - = custom_icon('scroll_down') - - %pre.build-trace#build-trace - %code.bash.js-build-output - .build-loader-animation.js-build-refresh + - if @build.erasable? && can?(current_user, :erase_build, @build) + = link_to erase_project_job_path(@project, @build), + method: :post, + data: { confirm: 'Are you sure you want to erase this build?', placement: 'top', container: 'body' }, + title: 'Erase job log', + class: 'has-tooltip js-erase-link controllers-buttons' do + = icon('trash') + .has-tooltip.controllers-buttons{ title: 'Scroll to top', data: { placement: 'top', container: 'body'} } + %button.js-scroll-up.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true } + = custom_icon('scroll_up') + .has-tooltip.controllers-buttons{ title: 'Scroll to bottom', data: { placement: 'top', container: 'body'} } + %button.js-scroll-down.btn-scroll.btn-transparent.btn-blank{ type: 'button', disabled: true } + = custom_icon('scroll_down') + %pre.build-trace#build-trace + %code.bash.js-build-output + .build-loader-animation.js-build-refresh + - else + - illustration = @build.playable? ? 'illustrations/manual_action.svg' : 'illustrations/job_not_triggered.svg' + - title = @build.playable? ? _('This job requires a manual action') : _('This job has not been triggered yet') + - content = @build.playable? ? _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments.') : _('This job depends on upstream jobs that need to succeed in order for this job to be triggered.') + .row.empty-state + .col-xs-12 + .svg-content + = image_tag illustration + .col-xs-12 + .text-content + %h4.text-center= title + %p= content + - if @build.playable? + .text-center + = link_to _('Trigger this manual action'), play_project_job_path(@project, @build), class: 'btn btn-primary', title: _('Trigger this manual action') = render "sidebar" diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index 0b0d5a2dce8..a3b8a1c387f 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -369,6 +369,34 @@ feature 'Jobs' do end end end + + context 'Playable manual action' do + let(:build) { create(:ci_build, :playable, pipeline: pipeline) } + + before do + project.add_developer(user) + visit project_job_path(project, job) + end + + it 'shows manual action empty state' do + 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.') + expect(page).to have_link('Trigger this manual action') + end + end + + context 'Non triggered job' do + let(:job) { create(:ci_build, :created, pipeline: pipeline) } + + before do + visit project_job_path(project, job) + end + + it 'shows manual action empty state' do + 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 end describe "POST /:project/jobs/:id/cancel", :js do diff --git a/spec/javascripts/jobs/header_spec.js b/spec/javascripts/jobs/header_spec.js index 4a210faa017..83395ea451e 100644 --- a/spec/javascripts/jobs/header_spec.js +++ b/spec/javascripts/jobs/header_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import headerComponent from '~/jobs/components/header.vue'; +import mountComponent from '../helpers/vue_mount_component_helper'; describe('Job details header', () => { let HeaderComponent; @@ -35,7 +36,7 @@ describe('Job details header', () => { isLoading: false, }; - vm = new HeaderComponent({ propsData: props }).$mount(); + vm = mountComponent(HeaderComponent, props); }); afterEach(() => { diff --git a/spec/javascripts/vue_shared/components/header_ci_component_spec.js b/spec/javascripts/vue_shared/components/header_ci_component_spec.js index b4553acb341..66c6f70a667 100644 --- a/spec/javascripts/vue_shared/components/header_ci_component_spec.js +++ b/spec/javascripts/vue_shared/components/header_ci_component_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import headerCi from '~/vue_shared/components/header_ci_component.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; describe('Header CI Component', () => { let HeaderCi; @@ -8,7 +9,6 @@ describe('Header CI Component', () => { beforeEach(() => { HeaderCi = Vue.extend(headerCi); - props = { status: { group: 'failed', @@ -45,54 +45,65 @@ describe('Header CI Component', () => { ], hasSidebarButton: true, }; - - vm = new HeaderCi({ - propsData: props, - }).$mount(); }); afterEach(() => { vm.$destroy(); }); - it('should render status badge', () => { - expect(vm.$el.querySelector('.ci-failed')).toBeDefined(); - expect(vm.$el.querySelector('.ci-status-icon-failed svg')).toBeDefined(); - expect( - vm.$el.querySelector('.ci-failed').getAttribute('href'), - ).toEqual(props.status.details_path); - }); + describe('render', () => { + beforeEach(() => { + vm = mountComponent(HeaderCi, props); + }); - it('should render item name and id', () => { - expect(vm.$el.querySelector('strong').textContent.trim()).toEqual('job #123'); - }); + it('should render status badge', () => { + expect(vm.$el.querySelector('.ci-failed')).toBeDefined(); + expect(vm.$el.querySelector('.ci-status-icon-failed svg')).toBeDefined(); + expect( + vm.$el.querySelector('.ci-failed').getAttribute('href'), + ).toEqual(props.status.details_path); + }); - it('should render timeago date', () => { - expect(vm.$el.querySelector('time')).toBeDefined(); - }); + it('should render item name and id', () => { + expect(vm.$el.querySelector('strong').textContent.trim()).toEqual('job #123'); + }); - it('should render user icon and name', () => { - expect(vm.$el.querySelector('.js-user-link').textContent.trim()).toEqual(props.user.name); - }); + it('should render timeago date', () => { + expect(vm.$el.querySelector('time')).toBeDefined(); + }); - it('should render provided actions', () => { - expect(vm.$el.querySelector('.btn').tagName).toEqual('BUTTON'); - expect(vm.$el.querySelector('.btn').textContent.trim()).toEqual(props.actions[0].label); - expect(vm.$el.querySelector('.link').tagName).toEqual('A'); - expect(vm.$el.querySelector('.link').textContent.trim()).toEqual(props.actions[1].label); - expect(vm.$el.querySelector('.link').getAttribute('href')).toEqual(props.actions[0].path); - }); + it('should render user icon and name', () => { + expect(vm.$el.querySelector('.js-user-link').textContent.trim()).toEqual(props.user.name); + }); + + it('should render provided actions', () => { + expect(vm.$el.querySelector('.btn').tagName).toEqual('BUTTON'); + expect(vm.$el.querySelector('.btn').textContent.trim()).toEqual(props.actions[0].label); + expect(vm.$el.querySelector('.link').tagName).toEqual('A'); + expect(vm.$el.querySelector('.link').textContent.trim()).toEqual(props.actions[1].label); + expect(vm.$el.querySelector('.link').getAttribute('href')).toEqual(props.actions[0].path); + }); - it('should show loading icon', (done) => { - vm.actions[0].isLoading = true; + it('should show loading icon', (done) => { + vm.actions[0].isLoading = true; - Vue.nextTick(() => { - expect(vm.$el.querySelector('.btn .fa-spinner').getAttribute('style')).toBeFalsy(); - done(); + Vue.nextTick(() => { + expect(vm.$el.querySelector('.btn .fa-spinner').getAttribute('style')).toBeFalsy(); + done(); + }); + }); + + it('should render sidebar toggle button', () => { + expect(vm.$el.querySelector('.js-sidebar-build-toggle')).toBeDefined(); }); }); - it('should render sidebar toggle button', () => { - expect(vm.$el.querySelector('.js-sidebar-build-toggle')).toBeDefined(); + describe('triggered', () => { + it('should rendered created keyword when the triggered is false', () => { + vm = mountComponent(HeaderCi, { ...props, triggered: false }); + + expect(vm.$el.textContent).toContain('created'); + expect(vm.$el.textContent).not.toContain('triggered'); + }); }); }); -- cgit v1.2.1 From ce8b53ea89b40e16c8da2e3b90c7ef3f1b148920 Mon Sep 17 00:00:00 2001 From: asaparov Date: Tue, 19 Dec 2017 19:10:14 -0500 Subject: Bumped mysql2 gem version from 0.4.5 to 0.4.10. --- Gemfile | 2 +- Gemfile.lock | 4 ++-- changelogs/unreleased/bump_mysql_gem.yml | 5 +++++ 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/bump_mysql_gem.yml diff --git a/Gemfile b/Gemfile index b6ffaf80f24..cc39714295e 100644 --- a/Gemfile +++ b/Gemfile @@ -12,7 +12,7 @@ gem 'sprockets', '~> 3.7.0' gem 'default_value_for', '~> 3.0.0' # Supported DBs -gem 'mysql2', '~> 0.4.5', group: :mysql +gem 'mysql2', '~> 0.4.10', group: :mysql gem 'pg', '~> 0.18.2', group: :postgres gem 'rugged', '~> 0.26.0' diff --git a/Gemfile.lock b/Gemfile.lock index a6e3c9e27cc..d11cadeec30 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -501,7 +501,7 @@ GEM mustermann (1.0.0) mustermann-grape (1.0.0) mustermann (~> 1.0.0) - mysql2 (0.4.5) + mysql2 (0.4.10) net-ldap (0.16.0) net-ssh (4.1.0) netrc (0.11.0) @@ -1082,7 +1082,7 @@ DEPENDENCIES method_source (~> 0.8) minitest (~> 5.7.0) mousetrap-rails (~> 1.4.6) - mysql2 (~> 0.4.5) + mysql2 (~> 0.4.10) net-ldap net-ssh (~> 4.1.0) nokogiri (~> 1.8.1) diff --git a/changelogs/unreleased/bump_mysql_gem.yml b/changelogs/unreleased/bump_mysql_gem.yml new file mode 100644 index 00000000000..58166949d72 --- /dev/null +++ b/changelogs/unreleased/bump_mysql_gem.yml @@ -0,0 +1,5 @@ +--- +title: Bump mysql2 gem version from 0.4.5 to 0.4.10 +merge_request: +author: asaparov +type: other -- cgit v1.2.1 From a331a06aa8da542afa985d61370b9518cc44b1e9 Mon Sep 17 00:00:00 2001 From: julien MILLAU Date: Wed, 20 Dec 2017 08:11:13 +0000 Subject: Ignore "lost+found" folder during backup on a volume --- lib/backup/files.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/backup/files.rb b/lib/backup/files.rb index 30a91647b77..287d591e88d 100644 --- a/lib/backup/files.rb +++ b/lib/backup/files.rb @@ -18,7 +18,7 @@ module Backup FileUtils.rm_f(backup_tarball) if ENV['STRATEGY'] == 'copy' - cmd = %W(cp -a #{app_files_dir} #{Gitlab.config.backup.path}) + cmd = %W(rsync -a --exclude=lost+found #{app_files_dir} #{Gitlab.config.backup.path}) output, status = Gitlab::Popen.popen(cmd) unless status.zero? @@ -26,10 +26,10 @@ module Backup abort 'Backup failed' end - run_pipeline!([%W(tar -C #{@backup_files_dir} -cf - .), %w(gzip -c -1)], out: [backup_tarball, 'w', 0600]) + run_pipeline!([%W(tar --exclude=lost+found -C #{@backup_files_dir} -cf - .), %w(gzip -c -1)], out: [backup_tarball, 'w', 0600]) FileUtils.rm_rf(@backup_files_dir) else - run_pipeline!([%W(tar -C #{app_files_dir} -cf - .), %w(gzip -c -1)], out: [backup_tarball, 'w', 0600]) + run_pipeline!([%W(tar --exclude=lost+found -C #{app_files_dir} -cf - .), %w(gzip -c -1)], out: [backup_tarball, 'w', 0600]) end end -- cgit v1.2.1 From 091c4989b3c1e18723aef2b28475866b1a89e282 Mon Sep 17 00:00:00 2001 From: julien MILLAU Date: Wed, 20 Dec 2017 08:55:15 +0000 Subject: Add changelog --- .../16036-ignore-lost-found-folder-during-backup-on-a-volume.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/16036-ignore-lost-found-folder-during-backup-on-a-volume.yml diff --git a/changelogs/unreleased/16036-ignore-lost-found-folder-during-backup-on-a-volume.yml b/changelogs/unreleased/16036-ignore-lost-found-folder-during-backup-on-a-volume.yml new file mode 100644 index 00000000000..833650559a3 --- /dev/null +++ b/changelogs/unreleased/16036-ignore-lost-found-folder-during-backup-on-a-volume.yml @@ -0,0 +1,5 @@ +--- +title: "Ignore lost+found folder during backup on a volume" +merge_request: 16036 +author: Julien Millau +type: fixed \ No newline at end of file -- cgit v1.2.1 From 2c4174028d4fb150fb1adbc7ec920007b72e0da2 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 20 Dec 2017 10:24:13 +0000 Subject: Adjust illustrations size --- app/assets/stylesheets/framework/images.scss | 11 +++++++---- app/views/projects/jobs/show.html.haml | 3 ++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/app/assets/stylesheets/framework/images.scss b/app/assets/stylesheets/framework/images.scss index aa2d30a3cef..fd5c3c81a53 100644 --- a/app/assets/stylesheets/framework/images.scss +++ b/app/assets/stylesheets/framework/images.scss @@ -20,10 +20,13 @@ width: 100%; } - &.svg-250 { - img, - svg { - width: 250px; + $image-widths: 250 306 394; + @each $width in $image-widths { + &.svg-#{$width} { + img, + svg { + width: #{$width + 'px'}; + } } } } diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index f7d06d8ceb9..85d802a9d9c 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -90,11 +90,12 @@ .build-loader-animation.js-build-refresh - else - illustration = @build.playable? ? 'illustrations/manual_action.svg' : 'illustrations/job_not_triggered.svg' + - illustration_size = @build.playable? ? 'svg-394' : 'sgv-306' - title = @build.playable? ? _('This job requires a manual action') : _('This job has not been triggered yet') - content = @build.playable? ? _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments.') : _('This job depends on upstream jobs that need to succeed in order for this job to be triggered.') .row.empty-state .col-xs-12 - .svg-content + .svg-content{ class: illustration_size } = image_tag illustration .col-xs-12 .text-content -- cgit v1.2.1 From 1d7875386059cb880e8ea8766359e87c42ff8ec6 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Wed, 20 Dec 2017 12:09:22 -0500 Subject: Initial install --- package.json | 1 + scripts/pre-commit | 11 +++++++++++ yarn.lock | 4 ++++ 3 files changed, 16 insertions(+) create mode 100644 scripts/pre-commit diff --git a/package.json b/package.json index 9e816e007ee..fe40cc75148 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "mousetrap": "^1.4.6", "name-all-modules-plugin": "^1.0.1", "pikaday": "^1.6.1", + "prettier": "^1.9.2", "prismjs": "^1.6.0", "raphael": "^2.2.7", "raven-js": "^3.14.0", diff --git a/scripts/pre-commit b/scripts/pre-commit new file mode 100644 index 00000000000..e3ff5927922 --- /dev/null +++ b/scripts/pre-commit @@ -0,0 +1,11 @@ +#!/bin/sh +jsfiles=$(git diff --cached --name-only --diff-filter=ACM "*.js" "*.jsx" | tr '\n' ' ') +[ -z "$jsfiles" ] && exit 0 + +# Prettify all staged .js files +echo "$jsfiles" | xargs ./node_modules/.bin/prettier --write + +# Add back the modified/prettified files to staging +echo "$jsfiles" | xargs git add + +exit 0 \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index c4d1bd3c682..ef1611de82f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5052,6 +5052,10 @@ prettier@^1.7.0: version "1.8.2" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.8.2.tgz#bff83e7fd573933c607875e5ba3abbdffb96aeb8" +prettier@^1.9.2: + version "1.9.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.9.2.tgz#96bc2132f7a32338e6078aeb29727178c6335827" + 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 39efc5c80c132657eaf0e1e4704b9e6f76c94d89 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Wed, 20 Dec 2017 15:19:09 -0500 Subject: Remove JSX since we don't use it. --- scripts/pre-commit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/pre-commit b/scripts/pre-commit index e3ff5927922..210e349fe64 100644 --- a/scripts/pre-commit +++ b/scripts/pre-commit @@ -1,5 +1,5 @@ #!/bin/sh -jsfiles=$(git diff --cached --name-only --diff-filter=ACM "*.js" "*.jsx" | tr '\n' ' ') +jsfiles=$(git diff --cached --name-only --diff-filter=ACM "*.js" | tr '\n' ' ') [ -z "$jsfiles" ] && exit 0 # Prettify all staged .js files -- cgit v1.2.1 From 6f45cbd197b055c1ac18b32fa612b1b462f631b0 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Wed, 20 Dec 2017 15:29:27 -0500 Subject: Add script to enable code formatters --- scripts/add-code-formatters | 4 ++++ 1 file changed, 4 insertions(+) create mode 100755 scripts/add-code-formatters diff --git a/scripts/add-code-formatters b/scripts/add-code-formatters new file mode 100755 index 00000000000..6a22c9a3866 --- /dev/null +++ b/scripts/add-code-formatters @@ -0,0 +1,4 @@ +#!/bin/sh + +touch ./.git/hooks/pre-commit +ln -s -f ./pre-commit .git/hooks/pre-commit \ No newline at end of file -- cgit v1.2.1 From 032658e0b53f6a25e7266a687655dff628abfe1a Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Wed, 20 Dec 2017 08:03:28 -0500 Subject: Update Auto DevOps template --- vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml | 70 ++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml index 275487071f3..c169d4eff2e 100644 --- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml @@ -41,6 +41,7 @@ stages: - staging - canary - production + - performance - cleanup build: @@ -83,6 +84,21 @@ codequality: artifacts: paths: [codeclimate.json] +performance: + stage: performance + image: + name: sitespeedio/sitespeed.io:6.0.3 + entrypoint: [""] + script: + - performance + artifacts: + paths: + - performance.json + only: + refs: + - branches + kubernetes: active + sast: image: registry.gitlab.com/gitlab-org/gl-sast:latest variables: @@ -92,6 +108,19 @@ sast: - sast . artifacts: paths: [gl-sast-report.json] + +sast:image: + image: docker:latest + variables: + DOCKER_DRIVER: overlay2 + allow_failure: true + services: + - docker:dind + script: + - setup_docker + - sast_image + artifacts: + paths: [gl-sast-image-report.json] review: stage: review @@ -103,10 +132,13 @@ review: - install_tiller - create_secret - deploy + - persist_environment_url environment: name: review/$CI_COMMIT_REF_NAME url: http://$CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_SLUG.$AUTO_DEVOPS_DOMAIN on_stop: stop_review + artifacts: + paths: [environment_url.txt] only: refs: - branches @@ -201,9 +233,12 @@ production: - create_secret - deploy - delete canary + - persist_environment_url environment: name: production url: http://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN + artifacts: + paths: [environment_url.txt] # when: manual only: refs: @@ -221,6 +256,18 @@ production: export CI_APPLICATION_TAG=$CI_COMMIT_SHA export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID} export TILLER_NAMESPACE=$KUBE_NAMESPACE + + function sast_image() { + docker run -d --name db arminc/clair-db:latest + docker run -p 6060:6060 --link db:postgres -d --name clair arminc/clair-local-scan:v2.0.1 + apk add -U wget ca-certificates + docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} + wget https://github.com/arminc/clair-scanner/releases/download/v6/clair-scanner_linux_386 + mv clair-scanner_linux_386 clair-scanner + chmod +x clair-scanner + touch clair-whitelist.yml + ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-sast-image-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true + } function codeclimate() { cc_opts="--env CODECLIMATE_CODE="$PWD" \ @@ -415,6 +462,29 @@ production: --docker-email="$GITLAB_USER_EMAIL" \ -o yaml --dry-run | kubectl replace -n "$KUBE_NAMESPACE" --force -f - } + + function performance() { + export CI_ENVIRONMENT_URL=$(cat environment_url.txt) + + mkdir gitlab-exporter + wget -O gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/10-3/index.js + + mkdir sitespeed-results + + if [ -f .gitlab-urls.txt ] + then + sed -i -e 's@^@'"$CI_ENVIRONMENT_URL"'@' .gitlab-urls.txt + /start.sh --plugins.add gitlab-exporter --outputFolder sitespeed-results .gitlab-urls.txt + else + /start.sh --plugins.add gitlab-exporter --outputFolder sitespeed-results $CI_ENVIRONMENT_URL + fi + + mv sitespeed-results/data/performance.json performance.json + } + + function persist_environment_url() { + echo $CI_ENVIRONMENT_URL > environment_url.txt + } function delete() { track="${1-stable}" -- cgit v1.2.1 From ab8138a7cc5eab87aab808f0af8a461d5c079116 Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Wed, 20 Dec 2017 15:41:26 +0000 Subject: Remove SAST:Image for now. --- vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml index c169d4eff2e..18910a46d11 100644 --- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml @@ -108,19 +108,6 @@ sast: - sast . artifacts: paths: [gl-sast-report.json] - -sast:image: - image: docker:latest - variables: - DOCKER_DRIVER: overlay2 - allow_failure: true - services: - - docker:dind - script: - - setup_docker - - sast_image - artifacts: - paths: [gl-sast-image-report.json] review: stage: review @@ -256,18 +243,6 @@ production: export CI_APPLICATION_TAG=$CI_COMMIT_SHA export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID} export TILLER_NAMESPACE=$KUBE_NAMESPACE - - function sast_image() { - docker run -d --name db arminc/clair-db:latest - docker run -p 6060:6060 --link db:postgres -d --name clair arminc/clair-local-scan:v2.0.1 - apk add -U wget ca-certificates - docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} - wget https://github.com/arminc/clair-scanner/releases/download/v6/clair-scanner_linux_386 - mv clair-scanner_linux_386 clair-scanner - chmod +x clair-scanner - touch clair-whitelist.yml - ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-sast-image-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true - } function codeclimate() { cc_opts="--env CODECLIMATE_CODE="$PWD" \ -- cgit v1.2.1 From b3b7d12496fda8680a8764701107e819d253fb86 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Wed, 20 Dec 2017 15:48:45 -0500 Subject: Add changelog and newlines --- changelogs/unreleased/pre-commit-prettier.yml | 5 +++++ scripts/add-code-formatters | 2 +- scripts/pre-commit | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/pre-commit-prettier.yml diff --git a/changelogs/unreleased/pre-commit-prettier.yml b/changelogs/unreleased/pre-commit-prettier.yml new file mode 100644 index 00000000000..7410496f071 --- /dev/null +++ b/changelogs/unreleased/pre-commit-prettier.yml @@ -0,0 +1,5 @@ +--- +title: Adds scripts to add code formatters in pre-commit via prettier +merge_request: +author: +type: added diff --git a/scripts/add-code-formatters b/scripts/add-code-formatters index 6a22c9a3866..583676be67c 100755 --- a/scripts/add-code-formatters +++ b/scripts/add-code-formatters @@ -1,4 +1,4 @@ #!/bin/sh touch ./.git/hooks/pre-commit -ln -s -f ./pre-commit .git/hooks/pre-commit \ No newline at end of file +ln -s -f ./pre-commit .git/hooks/pre-commit diff --git a/scripts/pre-commit b/scripts/pre-commit index 210e349fe64..380cc8d9022 100644 --- a/scripts/pre-commit +++ b/scripts/pre-commit @@ -8,4 +8,4 @@ echo "$jsfiles" | xargs ./node_modules/.bin/prettier --write # Add back the modified/prettified files to staging echo "$jsfiles" | xargs git add -exit 0 \ No newline at end of file +exit 0 -- cgit v1.2.1 From 060bceeacb899b57e4a3255e5610257ca8c28b67 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Wed, 20 Dec 2017 15:54:21 -0500 Subject: Remove `-f` to not ruin other people's existing pre-commit files. --- scripts/add-code-formatters | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/add-code-formatters b/scripts/add-code-formatters index 583676be67c..8e60875e3a6 100755 --- a/scripts/add-code-formatters +++ b/scripts/add-code-formatters @@ -1,4 +1,4 @@ #!/bin/sh touch ./.git/hooks/pre-commit -ln -s -f ./pre-commit .git/hooks/pre-commit +ln -s ./pre-commit .git/hooks/pre-commit -- cgit v1.2.1 From d69f0feea8c846cab2019a0afe9c602b25864d69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 21 Dec 2017 01:27:04 +0100 Subject: Stub out project#reset_cache --- app/controllers/projects_controller.rb | 3 +++ config/routes/project.rb | 1 + 2 files changed, 4 insertions(+) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 6f609348402..a2f1b27d0ec 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -175,6 +175,9 @@ class ProjectsController < Projects::ApplicationController ) end + def reset_cache + end + def export @project.add_export_job(current_user: current_user) diff --git a/config/routes/project.rb b/config/routes/project.rb index 239b5480321..d79c6e141c8 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -436,6 +436,7 @@ constraints(ProjectUrlConstrainer.new) do get :download_export get :activity get :refs + get :reset_cache put :new_issuable_address end end -- cgit v1.2.1 From a8c016d5bd53756dfc13546736d8a38bfc4333cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 21 Dec 2017 01:31:16 +0100 Subject: Stub ResetProjectCacheService --- app/services/reset_project_cache_service.rb | 4 ++++ spec/services/reset_project_cache_service_spec.rb | 12 ++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 app/services/reset_project_cache_service.rb create mode 100644 spec/services/reset_project_cache_service_spec.rb diff --git a/app/services/reset_project_cache_service.rb b/app/services/reset_project_cache_service.rb new file mode 100644 index 00000000000..85ba6d953a2 --- /dev/null +++ b/app/services/reset_project_cache_service.rb @@ -0,0 +1,4 @@ +class ResetProjectCacheService < BaseService + def execute + end +end diff --git a/spec/services/reset_project_cache_service_spec.rb b/spec/services/reset_project_cache_service_spec.rb new file mode 100644 index 00000000000..9623035a5a4 --- /dev/null +++ b/spec/services/reset_project_cache_service_spec.rb @@ -0,0 +1,12 @@ +require 'spec_helper' + +describe ResetProjectCacheService do + let(:project) { create(:project) } + let(:user) { create(:user) } + + subject { described_class.new(project, user).execute } + + it "resets project cache" do + fail + end +end -- cgit v1.2.1 From 1eb207f3c28f6a03dbd1174838a154da7e64e519 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 21 Dec 2017 02:08:52 +0100 Subject: Add tests for projects#reset_cache --- app/controllers/projects_controller.rb | 6 ++++ spec/controllers/projects_controller_spec.rb | 47 ++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index a2f1b27d0ec..928555c200b 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -176,6 +176,12 @@ class ProjectsController < Projects::ApplicationController end def reset_cache + if ResetProjectCacheService.new(@project, current_user).execute + flash[:notice] = _("Project cache successfully reset.") + else + flash[:error] = _("Unable to reset project cache.") + end + redirect_to project_pipelines_path(@project) end def export diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index e61187fb518..2fc827742fe 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -686,6 +686,53 @@ describe ProjectsController do end end + describe '#reset_cache' do + before do + sign_in(user) + + project.add_master(user) + + allow(ResetProjectCacheService).to receive_message_chain(:new, :execute).and_return(true) + end + + subject { get :reset_cache, namespace_id: project.namespace, id: project } + + it 'calls reset project cache service' do + expect(ResetProjectCacheService).to receive_message_chain(:new, :execute) + + subject + end + + it 'redirects to project pipelines path' do + subject + + expect(response).to have_gitlab_http_status(:redirect) + expect(response).to redirect_to(project_pipelines_path(project)) + end + + context 'when service returns successfully' do + it 'sets the flash notice variable' do + subject + + expect(controller).to set_flash[:notice] + expect(controller).not_to set_flash[:error] + end + end + + context 'when service does not return successfully' do + before do + allow(ResetProjectCacheService).to receive_message_chain(:new, :execute).and_return(false) + end + + it 'sets the flash error variable' do + subject + + expect(controller).not_to set_flash[:notice] + expect(controller).to set_flash[:error] + end + end + end + describe '#export' do before do sign_in(user) -- cgit v1.2.1 From e17f0e9e34e119ef2f516a0d8a9825dd8c54cebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 21 Dec 2017 02:30:03 +0100 Subject: Add reset cache button to pipelines view --- .../pipelines/components/nav_controls.vue | 10 ++++++++ .../javascripts/pipelines/components/pipelines.vue | 2 ++ app/views/projects/pipelines/index.html.haml | 3 ++- spec/javascripts/fixtures/pipelines.html.haml | 4 +++- spec/javascripts/pipelines/nav_controls_spec.js | 27 ++++++++++++++++++++-- 5 files changed, 42 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/pipelines/components/nav_controls.vue b/app/assets/javascripts/pipelines/components/nav_controls.vue index 632fc167f2b..a5717c23a44 100644 --- a/app/assets/javascripts/pipelines/components/nav_controls.vue +++ b/app/assets/javascripts/pipelines/components/nav_controls.vue @@ -17,6 +17,11 @@ export default { required: true, }, + resetCachePath: { + type: String, + required: true, + }, + ciLintPath: { type: String, required: true, @@ -45,6 +50,11 @@ export default { Get started with Pipelines + + Clear runner caches + + diff --git a/app/assets/javascripts/pipelines/components/pipelines.vue b/app/assets/javascripts/pipelines/components/pipelines.vue index fe1f3b4246a..8fa416168e7 100644 --- a/app/assets/javascripts/pipelines/components/pipelines.vue +++ b/app/assets/javascripts/pipelines/components/pipelines.vue @@ -50,6 +50,7 @@ canCreatePipeline: pipelinesData.canCreatePipeline, hasCi: pipelinesData.hasCi, ciLintPath: pipelinesData.ciLintPath, + resetCachePath: pipelinesData.resetCachePath, state: this.store.state, scope: getParameterByName('scope') || 'all', page: getParameterByName('page') || '1', @@ -220,6 +221,7 @@ :new-pipeline-path="newPipelinePath" :has-ci-enabled="hasCiEnabled" :help-page-path="helpPagePath" + :resetCachePath="resetCachePath" :ci-lint-path="ciLintPath" :can-create-pipeline="canCreatePipelineParsed " /> diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index b2e71cff6ce..d23cb626312 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -10,7 +10,8 @@ "new-pipeline-path" => new_project_pipeline_path(@project), "can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s, "has-ci" => @repository.gitlab_ci_yml, - "ci-lint-path" => ci_lint_path } } + "ci-lint-path" => ci_lint_path, + "reset-cache-path" => reset_cache_project_path(@project) } } = page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('pipelines') diff --git a/spec/javascripts/fixtures/pipelines.html.haml b/spec/javascripts/fixtures/pipelines.html.haml index 85ee61f0b54..0161c0550d1 100644 --- a/spec/javascripts/fixtures/pipelines.html.haml +++ b/spec/javascripts/fixtures/pipelines.html.haml @@ -7,4 +7,6 @@ "new-pipeline-path" => 'foo', "can-create-pipeline" => 'true', "has-ci" => 'foo', - "ci-lint-path" => 'foo' } } + "ci-lint-path" => 'foo', + "reset-cache-path" => 'foo' } } + diff --git a/spec/javascripts/pipelines/nav_controls_spec.js b/spec/javascripts/pipelines/nav_controls_spec.js index f1697840fcd..68009b111aa 100644 --- a/spec/javascripts/pipelines/nav_controls_spec.js +++ b/spec/javascripts/pipelines/nav_controls_spec.js @@ -14,6 +14,7 @@ describe('Pipelines Nav Controls', () => { hasCiEnabled: true, helpPagePath: 'foo', ciLintPath: 'foo', + resetCachePath: 'foo', canCreatePipeline: true, }; @@ -31,6 +32,7 @@ describe('Pipelines Nav Controls', () => { hasCiEnabled: true, helpPagePath: 'foo', ciLintPath: 'foo', + resetCachePath: 'foo', canCreatePipeline: false, }; @@ -41,12 +43,31 @@ describe('Pipelines Nav Controls', () => { expect(component.$el.querySelector('.btn-create')).toEqual(null); }); + it('should render link for resetting runner caches', () => { + const mockData = { + newPipelinePath: 'foo', + hasCiEnabled: true, + helpPagePath: 'foo', + ciLintPath: 'foo', + resetCachePath: 'foo', + canCreatePipeline: false, + }; + + const component = new NavControlsComponent({ + propsData: mockData, + }).$mount(); + + expect(component.$el.querySelectorAll('.btn-default')[0].textContent).toContain('Reset runner caches'); + expect(component.$el.querySelectorAll('.btn-default')[0].getAttribute('href')).toEqual(mockData.resetCachePath); + }); + it('should render link for CI lint', () => { const mockData = { newPipelinePath: 'foo', hasCiEnabled: true, helpPagePath: 'foo', ciLintPath: 'foo', + resetCachePath: 'foo', canCreatePipeline: true, }; @@ -54,8 +75,8 @@ describe('Pipelines Nav Controls', () => { propsData: mockData, }).$mount(); - expect(component.$el.querySelector('.btn-default').textContent).toContain('CI Lint'); - expect(component.$el.querySelector('.btn-default').getAttribute('href')).toEqual(mockData.ciLintPath); + expect(component.$el.querySelectorAll('.btn-default')[1].textContent).toContain('CI Lint'); + expect(component.$el.querySelectorAll('.btn-default')[1].getAttribute('href')).toEqual(mockData.ciLintPath); }); it('should render link to help page when CI is not enabled', () => { @@ -64,6 +85,7 @@ describe('Pipelines Nav Controls', () => { hasCiEnabled: false, helpPagePath: 'foo', ciLintPath: 'foo', + resetCachePath: 'foo', canCreatePipeline: true, }; @@ -81,6 +103,7 @@ describe('Pipelines Nav Controls', () => { hasCiEnabled: true, helpPagePath: 'foo', ciLintPath: 'foo', + resetCachePath: 'foo', canCreatePipeline: true, }; -- cgit v1.2.1 From f8c044bad3266ee4d31b9606024f4572d1fac3bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 21 Dec 2017 03:55:23 +0100 Subject: Fix clear/reset wording in nav_controls_spec --- spec/javascripts/pipelines/nav_controls_spec.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/javascripts/pipelines/nav_controls_spec.js b/spec/javascripts/pipelines/nav_controls_spec.js index 68009b111aa..09a0c14d96c 100644 --- a/spec/javascripts/pipelines/nav_controls_spec.js +++ b/spec/javascripts/pipelines/nav_controls_spec.js @@ -57,7 +57,7 @@ describe('Pipelines Nav Controls', () => { propsData: mockData, }).$mount(); - expect(component.$el.querySelectorAll('.btn-default')[0].textContent).toContain('Reset runner caches'); + expect(component.$el.querySelectorAll('.btn-default')[0].textContent).toContain('Clear runner caches'); expect(component.$el.querySelectorAll('.btn-default')[0].getAttribute('href')).toEqual(mockData.resetCachePath); }); -- cgit v1.2.1 From 51b416338a2ee9e287787850d11a3474e16f1474 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Wed, 20 Dec 2017 17:08:28 +0100 Subject: Backport a change made in EE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/api/members.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/api/members.rb b/lib/api/members.rb index 22e4bdead41..5446f6b54b1 100644 --- a/lib/api/members.rb +++ b/lib/api/members.rb @@ -59,7 +59,9 @@ module API member = source.add_user(params[:user_id], params[:access_level], current_user: current_user, expires_at: params[:expires_at]) - if member.persisted? && member.valid? + if !member + not_allowed! # This currently can only be reached in EE + elsif member.persisted? && member.valid? present member.user, with: Entities::Member, member: member else render_validation_error!(member) -- cgit v1.2.1 From b570b34787b9a83e6af4e39a1e7445f86ff20911 Mon Sep 17 00:00:00 2001 From: Vincent Lae Date: Thu, 21 Dec 2017 13:07:54 +0000 Subject: fix example in ci ssh_keys documentation --- doc/ci/ssh_keys/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/ssh_keys/README.md b/doc/ci/ssh_keys/README.md index df0e1521150..b8df0bfba20 100644 --- a/doc/ci/ssh_keys/README.md +++ b/doc/ci/ssh_keys/README.md @@ -181,7 +181,7 @@ before_script: ## Assuming you created the SSH_KNOWN_HOSTS variable, uncomment the ## following two lines. ## - - echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts' + - echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts - chmod 644 ~/.ssh/known_hosts ## -- cgit v1.2.1 From 6f01e7e3ea3e5e3c49f26ae42d0dba68141069f5 Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Wed, 20 Dec 2017 08:03:28 -0500 Subject: Update Auto DevOps template --- vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml | 70 ++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml index 275487071f3..c169d4eff2e 100644 --- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml @@ -41,6 +41,7 @@ stages: - staging - canary - production + - performance - cleanup build: @@ -83,6 +84,21 @@ codequality: artifacts: paths: [codeclimate.json] +performance: + stage: performance + image: + name: sitespeedio/sitespeed.io:6.0.3 + entrypoint: [""] + script: + - performance + artifacts: + paths: + - performance.json + only: + refs: + - branches + kubernetes: active + sast: image: registry.gitlab.com/gitlab-org/gl-sast:latest variables: @@ -92,6 +108,19 @@ sast: - sast . artifacts: paths: [gl-sast-report.json] + +sast:image: + image: docker:latest + variables: + DOCKER_DRIVER: overlay2 + allow_failure: true + services: + - docker:dind + script: + - setup_docker + - sast_image + artifacts: + paths: [gl-sast-image-report.json] review: stage: review @@ -103,10 +132,13 @@ review: - install_tiller - create_secret - deploy + - persist_environment_url environment: name: review/$CI_COMMIT_REF_NAME url: http://$CI_PROJECT_PATH_SLUG-$CI_ENVIRONMENT_SLUG.$AUTO_DEVOPS_DOMAIN on_stop: stop_review + artifacts: + paths: [environment_url.txt] only: refs: - branches @@ -201,9 +233,12 @@ production: - create_secret - deploy - delete canary + - persist_environment_url environment: name: production url: http://$CI_PROJECT_PATH_SLUG.$AUTO_DEVOPS_DOMAIN + artifacts: + paths: [environment_url.txt] # when: manual only: refs: @@ -221,6 +256,18 @@ production: export CI_APPLICATION_TAG=$CI_COMMIT_SHA export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID} export TILLER_NAMESPACE=$KUBE_NAMESPACE + + function sast_image() { + docker run -d --name db arminc/clair-db:latest + docker run -p 6060:6060 --link db:postgres -d --name clair arminc/clair-local-scan:v2.0.1 + apk add -U wget ca-certificates + docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} + wget https://github.com/arminc/clair-scanner/releases/download/v6/clair-scanner_linux_386 + mv clair-scanner_linux_386 clair-scanner + chmod +x clair-scanner + touch clair-whitelist.yml + ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-sast-image-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true + } function codeclimate() { cc_opts="--env CODECLIMATE_CODE="$PWD" \ @@ -415,6 +462,29 @@ production: --docker-email="$GITLAB_USER_EMAIL" \ -o yaml --dry-run | kubectl replace -n "$KUBE_NAMESPACE" --force -f - } + + function performance() { + export CI_ENVIRONMENT_URL=$(cat environment_url.txt) + + mkdir gitlab-exporter + wget -O gitlab-exporter/index.js https://gitlab.com/gitlab-org/gl-performance/raw/10-3/index.js + + mkdir sitespeed-results + + if [ -f .gitlab-urls.txt ] + then + sed -i -e 's@^@'"$CI_ENVIRONMENT_URL"'@' .gitlab-urls.txt + /start.sh --plugins.add gitlab-exporter --outputFolder sitespeed-results .gitlab-urls.txt + else + /start.sh --plugins.add gitlab-exporter --outputFolder sitespeed-results $CI_ENVIRONMENT_URL + fi + + mv sitespeed-results/data/performance.json performance.json + } + + function persist_environment_url() { + echo $CI_ENVIRONMENT_URL > environment_url.txt + } function delete() { track="${1-stable}" -- cgit v1.2.1 From 06175be1a646a7f9a531239079e7350847443ef1 Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Wed, 20 Dec 2017 15:41:26 +0000 Subject: Remove SAST:Image for now. --- vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml index c169d4eff2e..18910a46d11 100644 --- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml @@ -108,19 +108,6 @@ sast: - sast . artifacts: paths: [gl-sast-report.json] - -sast:image: - image: docker:latest - variables: - DOCKER_DRIVER: overlay2 - allow_failure: true - services: - - docker:dind - script: - - setup_docker - - sast_image - artifacts: - paths: [gl-sast-image-report.json] review: stage: review @@ -256,18 +243,6 @@ production: export CI_APPLICATION_TAG=$CI_COMMIT_SHA export CI_CONTAINER_NAME=ci_job_build_${CI_JOB_ID} export TILLER_NAMESPACE=$KUBE_NAMESPACE - - function sast_image() { - docker run -d --name db arminc/clair-db:latest - docker run -p 6060:6060 --link db:postgres -d --name clair arminc/clair-local-scan:v2.0.1 - apk add -U wget ca-certificates - docker pull ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} - wget https://github.com/arminc/clair-scanner/releases/download/v6/clair-scanner_linux_386 - mv clair-scanner_linux_386 clair-scanner - chmod +x clair-scanner - touch clair-whitelist.yml - ./clair-scanner -c http://docker:6060 --ip $(hostname -i) -r gl-sast-image-report.json -l clair.log -w clair-whitelist.yml ${CI_APPLICATION_REPOSITORY}:${CI_APPLICATION_TAG} || true - } function codeclimate() { cc_opts="--env CODECLIMATE_CODE="$PWD" \ -- cgit v1.2.1 From 3deb4e694d4dc68cf9d9548f9c101876fea4fdad Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Thu, 21 Dec 2017 10:16:53 -0500 Subject: Add docs for AutoDevOps --- doc/topics/autodevops/index.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index 0b48596006d..4056469e6c4 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -20,6 +20,7 @@ project in an easy and automatic way: 1. [Auto Test](#auto-test) 1. [Auto Code Quality](#auto-code-quality) 1. [Auto SAST (Static Application Security Testing)](#auto-sast) +1. [Auto Browser Performance Testing](#auto-browser-performance-testing) 1. [Auto Review Apps](#auto-review-apps) 1. [Auto Deploy](#auto-deploy) 1. [Auto Monitoring](#auto-monitoring) @@ -215,6 +216,20 @@ check out. Any security warnings are also [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/sast.html). +### Auto Browser Performance Testing + +> Introduced in [GitLab Enterprise Edition Premium][ee] 10.3. + +Auto Browser Performance Testing utilizes the [Sitespeed.io container](https://hub.docker.com/r/sitespeedio/sitespeed.io/) to measure the performance of a web page. A JSON report is created and uploaded as an artifact, which includes the overall performance score for each page. By default, the root page of Review and Production environments will be tested. If you would like to add additional URL's to test, simply add the paths to a file named `.gitlab-urls.txt` in the root directory, one per line. For example: + +``` +/ +/features +/direction +``` + +In GitLab Enterprise Edition Premium, performance differences between the source and target branches are [shown in the merge request widget](https://docs.gitlab.com/ee/user/project/merge_requests/browser_performance_testing.html). + ### Auto Review Apps NOTE: **Note:** -- cgit v1.2.1 From a0b90c7f7231c3dc36b4345e78cd917ef3a8ab42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Thu, 21 Dec 2017 16:49:15 +0100 Subject: Fix build factory to have properly filled started and finished date --- spec/factories/ci/builds.rb | 25 ++++++++++++++++--------- spec/features/projects/jobs_spec.rb | 2 +- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index dc1d88c92dc..6f66468570f 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -7,12 +7,10 @@ FactoryBot.define do stage_idx 0 ref 'master' tag false - status 'pending' - created_at 'Di 29. Okt 09:50:00 CET 2013' - started_at 'Di 29. Okt 09:51:28 CET 2013' - finished_at 'Di 29. Okt 09:53:28 CET 2013' commands 'ls -a' protected false + created_at 'Di 29. Okt 09:50:00 CET 2013' + pending options do { @@ -29,23 +27,37 @@ FactoryBot.define do pipeline factory: :ci_pipeline + trait :started do + started_at 'Di 29. Okt 09:51:28 CET 2013' + end + + trait :finished do + started + finished_at 'Di 29. Okt 09:53:28 CET 2013' + end + trait :success do + finished status 'success' end trait :failed do + finished status 'failed' end trait :canceled do + finished status 'canceled' end trait :skipped do + started status 'skipped' end trait :running do + started status 'running' end @@ -114,11 +126,6 @@ FactoryBot.define do build.project ||= build.pipeline.project end - factory :ci_not_started_build do - started_at nil - finished_at nil - end - trait :tag do tag true end diff --git a/spec/features/projects/jobs_spec.rb b/spec/features/projects/jobs_spec.rb index a3b8a1c387f..b59d8f37a5d 100644 --- a/spec/features/projects/jobs_spec.rb +++ b/spec/features/projects/jobs_spec.rb @@ -371,7 +371,7 @@ feature 'Jobs' do end context 'Playable manual action' do - let(:build) { create(:ci_build, :playable, pipeline: pipeline) } + let(:job) { create(:ci_build, :playable, pipeline: pipeline) } before do project.add_developer(user) -- cgit v1.2.1 From 7c636732fba98935922ffaadc49b328417e9036c Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 21 Dec 2017 15:48:24 +0000 Subject: Use non cached variables to get scroll position because of the performance bar --- app/assets/javascripts/job.js | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/app/assets/javascripts/job.js b/app/assets/javascripts/job.js index 198a7823381..e1b2506e437 100644 --- a/app/assets/javascripts/job.js +++ b/app/assets/javascripts/job.js @@ -96,14 +96,14 @@ export default class Job { // eslint-disable-next-line class-methods-use-this canScroll() { - return this.$document.height() > this.$window.height(); + return $(document).height() > $(window).height(); } toggleScroll() { - const currentPosition = this.$document.scrollTop(); - const scrollHeight = this.$document.height(); + const currentPosition = $(document).scrollTop(); + const scrollHeight = $(document).height(); - const windowHeight = this.$window.height(); + const windowHeight = $(window).height(); if (this.canScroll()) { if (currentPosition > 0 && (scrollHeight - currentPosition !== windowHeight)) { @@ -127,18 +127,22 @@ export default class Job { this.toggleDisableButton(this.$scrollBottomBtn, true); } } - + // eslint-disable-next-line class-methods-use-this isScrolledToBottom() { - const currentPosition = this.$document.scrollTop(); - const scrollHeight = this.$document.height(); + const $document = $(document); + + const currentPosition = $document.scrollTop(); + const scrollHeight = $document.height(); + + const windowHeight = $(window).height(); - const windowHeight = this.$window.height(); return scrollHeight - currentPosition === windowHeight; } // eslint-disable-next-line class-methods-use-this scrollDown() { - this.$document.scrollTop(this.$document.height()); + const $document = $(document); + $document.scrollTop($document.height()); } scrollToBottom() { @@ -148,7 +152,7 @@ export default class Job { } scrollToTop() { - this.$document.scrollTop(0); + $(document).scrollTop(0); this.hasBeenScrolled = true; this.toggleScroll(); } -- cgit v1.2.1 From 47e0a6cf429fed8d7bd527f8ab9fa53a86caedfe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 21 Dec 2017 17:47:39 +0100 Subject: Remove environment_scope in user/gcp show partial --- app/views/projects/clusters/gcp/_show.html.haml | 4 ---- app/views/projects/clusters/user/_show.html.haml | 4 ---- 2 files changed, 8 deletions(-) diff --git a/app/views/projects/clusters/gcp/_show.html.haml b/app/views/projects/clusters/gcp/_show.html.haml index bde85aed341..f3122a1bf47 100644 --- a/app/views/projects/clusters/gcp/_show.html.haml +++ b/app/views/projects/clusters/gcp/_show.html.haml @@ -9,10 +9,6 @@ = form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field| = form_errors(@cluster) - .form-group - = field.label :environment_scope, s_('ClusterIntegration|Environment scope') - = field.text_field :environment_scope, class: 'form-control js-select-on-focus', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope') - = field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field| .form-group = platform_kubernetes_field.label :api_url, s_('ClusterIntegration|API URL') diff --git a/app/views/projects/clusters/user/_show.html.haml b/app/views/projects/clusters/user/_show.html.haml index 89595bca007..5931e0b7f17 100644 --- a/app/views/projects/clusters/user/_show.html.haml +++ b/app/views/projects/clusters/user/_show.html.haml @@ -4,10 +4,6 @@ = field.label :name, s_('ClusterIntegration|Cluster name') = field.text_field :name, class: 'form-control', placeholder: s_('ClusterIntegration|Cluster name') - .form-group - = field.label :environment_scope, s_('ClusterIntegration|Environment scope') - = field.text_field :environment_scope, class: 'form-control js-select-on-focus', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope') - = field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field| .form-group = platform_kubernetes_field.label :api_url, s_('ClusterIntegration|API URL') -- cgit v1.2.1 From 9812d8c283abcd2b2ab3c7fea061c77f6f4eeb99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 21 Dec 2017 17:58:46 +0100 Subject: Add environment_scope to enabled partial --- app/views/projects/clusters/_banner.html.haml | 14 +++----------- app/views/projects/clusters/_enabled.html.haml | 26 +++++++++++++++++++++----- app/views/projects/clusters/show.html.haml | 2 +- 3 files changed, 25 insertions(+), 17 deletions(-) diff --git a/app/views/projects/clusters/_banner.html.haml b/app/views/projects/clusters/_banner.html.haml index 76a66fb92a2..6b9507c854f 100644 --- a/app/views/projects/clusters/_banner.html.haml +++ b/app/views/projects/clusters/_banner.html.haml @@ -1,6 +1,7 @@ -%h4= s_('ClusterIntegration|Enable cluster integration') -.settings-content +%h4= s_('ClusterIntegration|Cluster integration') +%p= s_('ClusterIntegration|Control how your cluster integrates with GitLab') +.settings-content .hidden.js-cluster-error.alert.alert-danger.alert-block.append-bottom-10{ role: 'alert' } = s_('ClusterIntegration|Something went wrong while creating your cluster on Google Kubernetes Engine') %p.js-error-reason @@ -10,12 +11,3 @@ .hidden.js-cluster-success.alert.alert-success.alert-block.append-bottom-10{ role: 'alert' } = s_('ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster\'s details') - - %p - - if @cluster.enabled? - - if can?(current_user, :update_cluster, @cluster) - = s_('ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab\'s connection to it.') - - else - = s_('ClusterIntegration|Cluster integration is enabled for this project.') - - else - = s_('ClusterIntegration|Cluster integration is disabled for this project.') diff --git a/app/views/projects/clusters/_enabled.html.haml b/app/views/projects/clusters/_enabled.html.haml index 547b3c8446f..1eac2c9dc1f 100644 --- a/app/views/projects/clusters/_enabled.html.haml +++ b/app/views/projects/clusters/_enabled.html.haml @@ -1,7 +1,16 @@ = form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field| = form_errors(@cluster) - .form-group.append-bottom-20 - %label.append-bottom-10 + .form-group + %h5= s_('ClusterIntegration|Integration status') + %p + - if @cluster.enabled? + - if can?(current_user, :update_cluster, @cluster) + = s_('ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab\'s connection to it.') + - else + = s_('ClusterIntegration|Cluster integration is enabled for this project.') + - else + = s_('ClusterIntegration|Cluster integration is disabled for this project.') + %label = field.hidden_field :enabled, { class: 'js-toggle-input'} %button{ type: 'button', @@ -12,6 +21,13 @@ = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked') = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked') - - if can?(current_user, :update_cluster, @cluster) - .form-group - = field.submit _('Save'), class: 'btn btn-success' + .form-group + %h5= s_('ClusterIntegration|Environment scope') + %p + = s_("ClusterIntegration|Choose which of your project's environments will use this cluster.") + = link_to s_("ClusterIntegration|Learn more about environments"), help_page_path('ci/environments') + = field.text_field :environment_scope, class: 'form-control js-select-on-focus', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope') + + - if can?(current_user, :update_cluster, @cluster) + .form-group + = field.submit _('Save changes'), class: 'btn btn-success' diff --git a/app/views/projects/clusters/show.html.haml b/app/views/projects/clusters/show.html.haml index fe6dacf1f0d..dfaef8716de 100644 --- a/app/views/projects/clusters/show.html.haml +++ b/app/views/projects/clusters/show.html.haml @@ -17,7 +17,7 @@ .js-cluster-application-notice .flash-container - %section.settings.no-animate.expanded + %section.settings.no-animate.expanded#cluster-integration = render 'banner' = render 'enabled' -- cgit v1.2.1 From d2d494196da1f9665034845c19bc5b87c3c67ebd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Mon, 18 Dec 2017 19:22:53 +0100 Subject: Match updated clusters/show in feature specs --- spec/features/projects/clusters/gcp_spec.rb | 6 +++--- spec/features/projects/clusters/user_spec.rb | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index 67b8901f8fb..882a2756b72 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -81,14 +81,14 @@ feature 'Gcp Cluster', :js do end it 'user sees a cluster details page' do - expect(page).to have_button('Save') + expect(page).to have_button('Save changes') expect(page.find(:css, '.cluster-name').value).to eq(cluster.name) end context 'when user disables the cluster' do before do page.find(:css, '.js-toggle-cluster').click - click_button 'Save' + page.within('#cluster-integration') { click_button 'Save changes' } end it 'user sees the successful message' do @@ -99,7 +99,7 @@ feature 'Gcp Cluster', :js do context 'when user changes cluster parameters' do before do fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace' - click_button 'Save changes' + page.within('#js-cluster-details') { click_button 'Save changes' } end it 'user sees the successful message' do diff --git a/spec/features/projects/clusters/user_spec.rb b/spec/features/projects/clusters/user_spec.rb index 414f4acba86..a519b9f9c7e 100644 --- a/spec/features/projects/clusters/user_spec.rb +++ b/spec/features/projects/clusters/user_spec.rb @@ -29,7 +29,7 @@ feature 'User Cluster', :js do end it 'user sees a cluster details page' do - expect(page).to have_content('Enable cluster integration') + expect(page).to have_content('Cluster integration') expect(page.find_field('cluster[name]').value).to eq('dev-cluster') expect(page.find_field('cluster[platform_kubernetes_attributes][api_url]').value) .to have_content('http://example.com') @@ -57,14 +57,14 @@ feature 'User Cluster', :js do end it 'user sees a cluster details page' do - expect(page).to have_button('Save') + expect(page).to have_button('Save changes') end context 'when user disables the cluster' do before do page.find(:css, '.js-toggle-cluster').click fill_in 'cluster_name', with: 'dev-cluster' - click_button 'Save' + page.within('#cluster-integration') { click_button 'Save changes' } end it 'user sees the successful message' do @@ -76,7 +76,7 @@ feature 'User Cluster', :js do before do fill_in 'cluster_name', with: 'my-dev-cluster' fill_in 'cluster_platform_kubernetes_attributes_namespace', with: 'my-namespace' - click_button 'Save changes' + page.within('#js-cluster-details') { click_button 'Save changes' } end it 'user sees the successful message' do -- cgit v1.2.1 From 9e2febf82fa63ff07e513cd400c7a41e4a4fe4bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Wed, 20 Dec 2017 18:19:34 +0100 Subject: Environment pattern -> Environment scope --- app/views/projects/clusters/_cluster.html.haml | 2 +- app/views/projects/clusters/index.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/projects/clusters/_cluster.html.haml b/app/views/projects/clusters/_cluster.html.haml index ad696daa259..3943dfc0856 100644 --- a/app/views/projects/clusters/_cluster.html.haml +++ b/app/views/projects/clusters/_cluster.html.haml @@ -4,7 +4,7 @@ .table-mobile-content = link_to cluster.name, namespace_project_cluster_path(@project.namespace, @project, cluster) .table-section.section-30 - .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Environment pattern") + .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Environment scope") .table-mobile-content= cluster.environment_scope .table-section.section-30 .table-mobile-header{ role: "rowheader" }= s_("ClusterIntegration|Project namespace") diff --git a/app/views/projects/clusters/index.html.haml b/app/views/projects/clusters/index.html.haml index bec512be91c..74dbe859eea 100644 --- a/app/views/projects/clusters/index.html.haml +++ b/app/views/projects/clusters/index.html.haml @@ -13,7 +13,7 @@ .table-section.section-30{ role: "rowheader" } = s_("ClusterIntegration|Cluster") .table-section.section-30{ role: "rowheader" } - = s_("ClusterIntegration|Environment pattern") + = s_("ClusterIntegration|Environment scope") .table-section.section-30{ role: "rowheader" } = s_("ClusterIntegration|Project namespace") .table-section.section-10{ role: "rowheader" } -- cgit v1.2.1 From 217c5225bf9d96cea7be7a32b50b4677146d721d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kamil=20Trzci=C5=84ski?= Date: Thu, 21 Dec 2017 18:21:27 +0100 Subject: Fix spec failures --- spec/features/projects/jobs/user_browses_job_spec.rb | 2 +- spec/requests/api/jobs_spec.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/features/projects/jobs/user_browses_job_spec.rb b/spec/features/projects/jobs/user_browses_job_spec.rb index 5d9208ebadd..4c49cff30d4 100644 --- a/spec/features/projects/jobs/user_browses_job_spec.rb +++ b/spec/features/projects/jobs/user_browses_job_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe 'User browses a job', :js do - let!(:build) { create(:ci_build, :coverage, pipeline: pipeline) } + let!(:build) { create(:ci_build, :running, :coverage, pipeline: pipeline) } let(:pipeline) { create(:ci_empty_pipeline, project: project, sha: project.commit.sha, ref: 'master') } let(:project) { create(:project, :repository, namespace: user.namespace) } let(:user) { create(:user) } diff --git a/spec/requests/api/jobs_spec.rb b/spec/requests/api/jobs_spec.rb index 2a83213e87a..e38cedc7042 100644 --- a/spec/requests/api/jobs_spec.rb +++ b/spec/requests/api/jobs_spec.rb @@ -11,7 +11,7 @@ describe API::Jobs do ref: project.default_branch) end - let!(:job) { create(:ci_build, pipeline: pipeline) } + let!(:job) { create(:ci_build, :success, pipeline: pipeline) } let(:user) { create(:user) } let(:api_user) { user } @@ -443,7 +443,7 @@ describe API::Jobs do context 'user with :update_build persmission' do it 'cancels running or pending job' do expect(response).to have_gitlab_http_status(201) - expect(project.builds.first.status).to eq('canceled') + expect(project.builds.first.status).to eq('success') end end -- cgit v1.2.1 From fea009bfdacad62f03d0cf3ea02d54ec9989390c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 21 Dec 2017 18:24:38 +0100 Subject: Rename enabled partial to integration_form --- app/views/projects/clusters/_banner.html.haml | 3 +- app/views/projects/clusters/_enabled.html.haml | 33 ---------------------- .../projects/clusters/_integration_form.html.haml | 33 ++++++++++++++++++++++ app/views/projects/clusters/show.html.haml | 2 +- 4 files changed, 36 insertions(+), 35 deletions(-) delete mode 100644 app/views/projects/clusters/_enabled.html.haml create mode 100644 app/views/projects/clusters/_integration_form.html.haml diff --git a/app/views/projects/clusters/_banner.html.haml b/app/views/projects/clusters/_banner.html.haml index 6b9507c854f..26ca3307a4a 100644 --- a/app/views/projects/clusters/_banner.html.haml +++ b/app/views/projects/clusters/_banner.html.haml @@ -1,5 +1,4 @@ %h4= s_('ClusterIntegration|Cluster integration') -%p= s_('ClusterIntegration|Control how your cluster integrates with GitLab') .settings-content .hidden.js-cluster-error.alert.alert-danger.alert-block.append-bottom-10{ role: 'alert' } @@ -11,3 +10,5 @@ .hidden.js-cluster-success.alert.alert-success.alert-block.append-bottom-10{ role: 'alert' } = s_('ClusterIntegration|Cluster was successfully created on Google Kubernetes Engine. Refresh the page to see cluster\'s details') + + %p= s_('ClusterIntegration|Control how your cluster integrates with GitLab') diff --git a/app/views/projects/clusters/_enabled.html.haml b/app/views/projects/clusters/_enabled.html.haml deleted file mode 100644 index 1eac2c9dc1f..00000000000 --- a/app/views/projects/clusters/_enabled.html.haml +++ /dev/null @@ -1,33 +0,0 @@ -= form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field| - = form_errors(@cluster) - .form-group - %h5= s_('ClusterIntegration|Integration status') - %p - - if @cluster.enabled? - - if can?(current_user, :update_cluster, @cluster) - = s_('ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab\'s connection to it.') - - else - = s_('ClusterIntegration|Cluster integration is enabled for this project.') - - else - = s_('ClusterIntegration|Cluster integration is disabled for this project.') - %label - = field.hidden_field :enabled, { class: 'js-toggle-input'} - - %button{ type: 'button', - class: "js-toggle-cluster project-feature-toggle #{'is-checked' unless !@cluster.enabled?} #{'is-disabled' unless can?(current_user, :update_cluster, @cluster)}", - "aria-label": s_("ClusterIntegration|Toggle Cluster"), - disabled: !can?(current_user, :update_cluster, @cluster) } - %span.toggle-icon - = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked') - = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked') - - .form-group - %h5= s_('ClusterIntegration|Environment scope') - %p - = s_("ClusterIntegration|Choose which of your project's environments will use this cluster.") - = link_to s_("ClusterIntegration|Learn more about environments"), help_page_path('ci/environments') - = field.text_field :environment_scope, class: 'form-control js-select-on-focus', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope') - - - if can?(current_user, :update_cluster, @cluster) - .form-group - = field.submit _('Save changes'), class: 'btn btn-success' diff --git a/app/views/projects/clusters/_integration_form.html.haml b/app/views/projects/clusters/_integration_form.html.haml new file mode 100644 index 00000000000..1eac2c9dc1f --- /dev/null +++ b/app/views/projects/clusters/_integration_form.html.haml @@ -0,0 +1,33 @@ += form_for @cluster, url: namespace_project_cluster_path(@project.namespace, @project, @cluster), as: :cluster do |field| + = form_errors(@cluster) + .form-group + %h5= s_('ClusterIntegration|Integration status') + %p + - if @cluster.enabled? + - if can?(current_user, :update_cluster, @cluster) + = s_('ClusterIntegration|Cluster integration is enabled for this project. Disabling this integration will not affect your cluster, it will only temporarily turn off GitLab\'s connection to it.') + - else + = s_('ClusterIntegration|Cluster integration is enabled for this project.') + - else + = s_('ClusterIntegration|Cluster integration is disabled for this project.') + %label + = field.hidden_field :enabled, { class: 'js-toggle-input'} + + %button{ type: 'button', + class: "js-toggle-cluster project-feature-toggle #{'is-checked' unless !@cluster.enabled?} #{'is-disabled' unless can?(current_user, :update_cluster, @cluster)}", + "aria-label": s_("ClusterIntegration|Toggle Cluster"), + disabled: !can?(current_user, :update_cluster, @cluster) } + %span.toggle-icon + = sprite_icon('status_success_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-checked') + = sprite_icon('status_failed_borderless', size: 16, css_class: 'toggle-icon-svg toggle-status-unchecked') + + .form-group + %h5= s_('ClusterIntegration|Environment scope') + %p + = s_("ClusterIntegration|Choose which of your project's environments will use this cluster.") + = link_to s_("ClusterIntegration|Learn more about environments"), help_page_path('ci/environments') + = field.text_field :environment_scope, class: 'form-control js-select-on-focus', readonly: !has_multiple_clusters?(@project), placeholder: s_('ClusterIntegration|Environment scope') + + - if can?(current_user, :update_cluster, @cluster) + .form-group + = field.submit _('Save changes'), class: 'btn btn-success' diff --git a/app/views/projects/clusters/show.html.haml b/app/views/projects/clusters/show.html.haml index dfaef8716de..c15785806b9 100644 --- a/app/views/projects/clusters/show.html.haml +++ b/app/views/projects/clusters/show.html.haml @@ -19,7 +19,7 @@ %section.settings.no-animate.expanded#cluster-integration = render 'banner' - = render 'enabled' + = render 'integration_form' .cluster-applications-table#js-cluster-applications -- cgit v1.2.1 From 0b5947849a32991904f8cbc5e4612d2438b97e0b Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 21 Dec 2017 17:25:50 +0000 Subject: Fix broken spinach test --- features/steps/shared/builds.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/steps/shared/builds.rb b/features/steps/shared/builds.rb index c267195f0e8..a3e4459f169 100644 --- a/features/steps/shared/builds.rb +++ b/features/steps/shared/builds.rb @@ -11,7 +11,7 @@ module SharedBuilds step 'project has a recent build' do @pipeline = create(:ci_empty_pipeline, project: @project, sha: @project.commit.sha, ref: 'master') - @build = create(:ci_build, :coverage, pipeline: @pipeline) + @build = create(:ci_build, :running, :coverage, pipeline: @pipeline) end step 'recent build is successful' do -- cgit v1.2.1 From a08bef4ebf3b87e52a32af7a1c297e0de99fa1a0 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 21 Dec 2017 17:34:20 +0000 Subject: Update Browse file to Choose file in all occurences --- app/views/profiles/show.html.haml | 2 +- app/views/projects/edit.html.haml | 2 +- changelogs/unreleased/40780-choose-file.yml | 5 +++++ 3 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 changelogs/unreleased/40780-choose-file.yml diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index 79f334176a5..f9dae310e01 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -26,7 +26,7 @@ Upload new avatar .prepend-top-5.append-bottom-10 %a.btn.js-choose-user-avatar-button - Browse file... + Choose file... %span.avatar-file-name.prepend-left-default.js-avatar-filename No file chosen = f.file_field_without_bootstrap :avatar, class: 'js-user-avatar-input hidden', accept: 'image/*' .help-block diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 71206f3a386..9d0d525a292 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -52,7 +52,7 @@ - if @project.avatar_in_git Project avatar in repository: #{ @project.avatar_in_git } %a.choose-btn.btn.js-choose-project-avatar-button - Browse file... + Choose file... %span.file_name.prepend-left-default.js-avatar-filename No file chosen = f.file_field :avatar, class: "js-project-avatar-input hidden" .help-block The maximum file size allowed is 200KB. diff --git a/changelogs/unreleased/40780-choose-file.yml b/changelogs/unreleased/40780-choose-file.yml new file mode 100644 index 00000000000..73e59dfcce8 --- /dev/null +++ b/changelogs/unreleased/40780-choose-file.yml @@ -0,0 +1,5 @@ +--- +title: Update Browse file to Choose file in all occurences +merge_request: +author: +type: other -- cgit v1.2.1 From 97b4e76f9b38720bd48d1ee72d339b4bf027d2bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 21 Dec 2017 18:50:17 +0100 Subject: Mark the gcp check page feature spec pending --- spec/features/projects/clusters/gcp_spec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index 4b682acd47e..4d0abb15b9a 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -137,7 +137,9 @@ feature 'Gcp Cluster', :js do end it 'user sees a check page' do - expect(page).to have_link('Continue') + pending 'the frontend still has not been implemented' do + expect(page).to have_link('Continue') + end end end end -- cgit v1.2.1 From ab2326f87cab96abc91e10d4a1602a5d2b78f1e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 21 Dec 2017 18:53:26 +0100 Subject: Make GCP billing check mock more specific --- spec/support/google_api/cloud_platform_helpers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/support/google_api/cloud_platform_helpers.rb b/spec/support/google_api/cloud_platform_helpers.rb index 9f9289fa580..887ea5c99b1 100644 --- a/spec/support/google_api/cloud_platform_helpers.rb +++ b/spec/support/google_api/cloud_platform_helpers.rb @@ -13,7 +13,7 @@ module GoogleApi def stub_google_project_billing_status redis_double = double allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) - allow(redis_double).to receive(:get).and_return('true') + allow(redis_double).to receive(:get).with(CheckGcpProjectBillingWorker.redis_shared_state_key_for).and_return('true') end def stub_cloud_platform_get_zone_cluster(project_id, zone, cluster_id, **options) -- cgit v1.2.1 From 59c7f46e2aa33d633fdc3f78c8a4faa792e40972 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 21 Dec 2017 19:02:06 +0100 Subject: Remove actions for async GCP project billing check --- .../projects/clusters/gcp_controller.rb | 31 ---- config/routes/project.rb | 2 - .../projects/clusters/gcp_controller_spec.rb | 200 ++------------------- 3 files changed, 11 insertions(+), 222 deletions(-) diff --git a/app/controllers/projects/clusters/gcp_controller.rb b/app/controllers/projects/clusters/gcp_controller.rb index 66a851c52c7..0c8305480ae 100644 --- a/app/controllers/projects/clusters/gcp_controller.rb +++ b/app/controllers/projects/clusters/gcp_controller.rb @@ -18,29 +18,6 @@ class Projects::Clusters::GcpController < Projects::ApplicationController end end - def check - respond_to do |format| - format.json do - Gitlab::PollingInterval.set_header(response, interval: STATUS_POLLING_INTERVAL) - - Gitlab::Redis::SharedState.with do |redis| - render json: { billing: redis.get(CheckGcpProjectBillingWorker.redis_shared_state_key_for(token_in_session)) } - end - end - - format.html { render :check } - end - end - - def run_check - respond_to do |format| - format.json do - CheckGcpProjectBillingWorker.perform_async(token_in_session) - head :no_content - end - end - end - def new @cluster = ::Clusters::Cluster.new.tap do |cluster| cluster.build_provider_gcp @@ -84,14 +61,6 @@ class Projects::Clusters::GcpController < Projects::ApplicationController end end - def authorize_google_project_billing - Gitlab::Redis::SharedState.with do |redis| - unless redis.get(CheckGcpProjectBillingWorker.redis_shared_state_key_for(token_in_session)) == 'true' - CheckGcpProjectBillingWorker.perform_async(token_in_session) - redirect_to action: 'check' - end - end - end def token_in_session @token_in_session ||= diff --git a/config/routes/project.rb b/config/routes/project.rb index b315b53f293..239b5480321 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -192,8 +192,6 @@ constraints(ProjectUrlConstrainer.new) do get '/gcp/new', to: 'clusters/gcp#new' get '/gcp/login', to: 'clusters/gcp#login' - get '/gcp/check', to: 'clusters/gcp#check' - post '/gcp/check', to: 'clusters/gcp#run_check' post '/gcp', to: 'clusters/gcp#create' end end diff --git a/spec/controllers/projects/clusters/gcp_controller_spec.rb b/spec/controllers/projects/clusters/gcp_controller_spec.rb index 4fa798c5856..485b67de30a 100644 --- a/spec/controllers/projects/clusters/gcp_controller_spec.rb +++ b/spec/controllers/projects/clusters/gcp_controller_spec.rb @@ -62,132 +62,6 @@ describe Projects::Clusters::GcpController do end end - describe 'GET check' do - let(:user) { create(:user) } - - before do - project.add_master(user) - sign_in(user) - end - - describe 'functionality' do - context 'when access token is valid' do - before do - stub_google_api_validate_token - end - - context 'when redis has wanted billing status' do - let(:token) { 'bogustoken' } - - before do - redis_double = double - allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) - allow(redis_double).to receive(:get).and_return('true') - end - - it 'should render json with billing status' do - go - - expect(response).to have_http_status(:ok) - expect(JSON.parse(response.body)).to include('billing' => 'true') - end - end - - context 'when redis does not have billing status' do - before do - redis_double = double - allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) - allow(redis_double).to receive(:get).and_return(nil) - end - - it 'should render json with null billing status' do - go - - expect(response).to have_http_status(:ok) - expect(JSON.parse(response.body)).to include('billing' => nil) - end - end - end - - context 'when access token is expired' do - before do - stub_google_api_expired_token - end - - it { expect(go).to redirect_to(gcp_login_project_clusters_path(project)) } - end - - context 'when access token is not stored in session' do - it { expect(go).to redirect_to(gcp_login_project_clusters_path(project)) } - end - end - - describe 'security' do - it { expect { go }.to be_allowed_for(:admin) } - it { expect { go }.to be_allowed_for(:owner).of(project) } - it { expect { go }.to be_allowed_for(:master).of(project) } - it { expect { go }.to be_denied_for(:developer).of(project) } - it { expect { go }.to be_denied_for(:reporter).of(project) } - it { expect { go }.to be_denied_for(:guest).of(project) } - it { expect { go }.to be_denied_for(:user) } - it { expect { go }.to be_denied_for(:external) } - end - - def go - get :check, namespace_id: project.namespace, project_id: project, format: :json - end - end - - describe 'POST check' do - let(:user) { create(:user) } - - before do - project.add_master(user) - sign_in(user) - end - - describe 'functionality' do - context 'when access token is valid' do - before do - stub_google_api_validate_token - end - - it 'calls check worker asynchronously' do - expect(CheckGcpProjectBillingWorker).to receive(:perform_async) - - expect(go).to have_http_status(:no_content) - end - end - - context 'when access token is expired' do - before do - stub_google_api_expired_token - end - - it { expect(go).to redirect_to(gcp_login_project_clusters_path(project)) } - end - - context 'when access token is not stored in session' do - it { expect(go).to redirect_to(gcp_login_project_clusters_path(project)) } - end - end - - describe 'security' do - it { expect { go }.to be_allowed_for(:admin) } - it { expect { go }.to be_allowed_for(:owner).of(project) } - it { expect { go }.to be_allowed_for(:master).of(project) } - it { expect { go }.to be_denied_for(:developer).of(project) } - it { expect { go }.to be_denied_for(:reporter).of(project) } - it { expect { go }.to be_denied_for(:guest).of(project) } - it { expect { go }.to be_denied_for(:user) } - it { expect { go }.to be_denied_for(:external) } - end - - def go - post :run_check, namespace_id: project.namespace, project_id: project, format: :json - end - end - describe 'GET new' do describe 'functionality' do let(:user) { create(:user) } @@ -202,36 +76,10 @@ describe Projects::Clusters::GcpController do stub_google_api_validate_token end - context 'when google project billing status is true' do - before do - stub_google_project_billing_status - end - - it 'has new object' do - go - - expect(assigns(:cluster)).to be_an_instance_of(Clusters::Cluster) - end - end - - context 'when google project billing status is not true' do - before do - redis_double = double - allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) - allow(redis_double).to receive(:get).and_return(nil) - end - - it 'redirects to check page' do - allow(CheckGcpProjectBillingWorker).to receive(:perform_async) - - expect(go).to redirect_to(gcp_check_project_clusters_path(project)) - end - - it 'calls gcp project billing check worker' do - expect(CheckGcpProjectBillingWorker).to receive(:perform_async) + it 'has new object' do + go - go - end + expect(assigns(:cluster)).to be_an_instance_of(Clusters::Cluster) end end @@ -289,40 +137,14 @@ describe Projects::Clusters::GcpController do stub_google_api_validate_token end - context 'when google project billing status is true' do - before do - stub_google_project_billing_status - end - - context 'when creates a cluster on gke' do - it 'creates a new cluster' do - expect(ClusterProvisionWorker).to receive(:perform_async) - expect { go }.to change { Clusters::Cluster.count } - .and change { Clusters::Providers::Gcp.count } - expect(response).to redirect_to(project_cluster_path(project, project.clusters.first)) - expect(project.clusters.first).to be_gcp - expect(project.clusters.first).to be_kubernetes - end - end - end - - context 'when google project billing status is not true' do - before do - redis_double = double - allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) - allow(redis_double).to receive(:get).and_return(nil) - end - - it 'redirects to check page' do - allow(CheckGcpProjectBillingWorker).to receive(:perform_async) - - expect(go).to redirect_to(gcp_check_project_clusters_path(project)) - end - - it 'calls gcp project billing check worker' do - expect(CheckGcpProjectBillingWorker).to receive(:perform_async) - - go + context 'when creates a cluster on gke' do + it 'creates a new cluster' do + expect(ClusterProvisionWorker).to receive(:perform_async) + expect { go }.to change { Clusters::Cluster.count } + .and change { Clusters::Providers::Gcp.count } + expect(response).to redirect_to(project_cluster_path(project, project.clusters.first)) + expect(project.clusters.first).to be_gcp + expect(project.clusters.first).to be_kubernetes end end end -- cgit v1.2.1 From e395a2c1901aa08c5d1f26f94406552db44140fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 21 Dec 2017 19:25:28 +0100 Subject: Implement GCP billing check in cluster form --- .../projects/clusters/gcp_controller.rb | 28 ++++++++++++++++------ .../projects/clusters/gcp_controller_spec.rb | 17 ++++++++++++- spec/support/google_api/cloud_platform_helpers.rb | 2 +- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/app/controllers/projects/clusters/gcp_controller.rb b/app/controllers/projects/clusters/gcp_controller.rb index 0c8305480ae..27c11ce554d 100644 --- a/app/controllers/projects/clusters/gcp_controller.rb +++ b/app/controllers/projects/clusters/gcp_controller.rb @@ -1,7 +1,7 @@ class Projects::Clusters::GcpController < Projects::ApplicationController before_action :authorize_read_cluster! before_action :authorize_google_api, except: [:login] - before_action :authorize_google_project_billing, except: [:login, :check, :run_check] + before_action :authorize_google_project_billing, only: [:new] before_action :authorize_create_cluster!, only: [:new, :create] STATUS_POLLING_INTERVAL = 1.minute.to_i @@ -25,15 +25,20 @@ class Projects::Clusters::GcpController < Projects::ApplicationController end def create - @cluster = ::Clusters::CreateService - .new(project, current_user, create_params) - .execute(token_in_session) + case google_project_billing_status + when 'true' + @cluster = ::Clusters::CreateService + .new(project, current_user, create_params) + .execute(token_in_session) - if @cluster.persisted? - redirect_to project_cluster_path(project, @cluster) + return redirect_to project_cluster_path(project, @cluster) if @cluster.persisted? + when 'false' + flash[:error] = _('Please enable billing for one of your projects to be able to create a cluster.') else - render :new + flash[:error] = _('We could not verify that one of your projects on GCP has billing enabled. Please try again.') end + + render :new end private @@ -61,6 +66,15 @@ class Projects::Clusters::GcpController < Projects::ApplicationController end end + def authorize_google_project_billing + CheckGcpProjectBillingWorker.perform_async(token_in_session) + end + + def google_project_billing_status + Gitlab::Redis::SharedState.with do |redis| + redis.get(CheckGcpProjectBillingWorker.redis_shared_state_key_for(token_in_session)) + end + end def token_in_session @token_in_session ||= diff --git a/spec/controllers/projects/clusters/gcp_controller_spec.rb b/spec/controllers/projects/clusters/gcp_controller_spec.rb index 485b67de30a..be19fa93183 100644 --- a/spec/controllers/projects/clusters/gcp_controller_spec.rb +++ b/spec/controllers/projects/clusters/gcp_controller_spec.rb @@ -77,6 +77,8 @@ describe Projects::Clusters::GcpController do end it 'has new object' do + expect(controller).to receive(:authorize_google_project_billing) + go expect(assigns(:cluster)).to be_an_instance_of(Clusters::Cluster) @@ -137,7 +139,11 @@ describe Projects::Clusters::GcpController do stub_google_api_validate_token end - context 'when creates a cluster on gke' do + context 'when google project billing is enabled' do + before do + stub_google_project_billing_status + end + it 'creates a new cluster' do expect(ClusterProvisionWorker).to receive(:perform_async) expect { go }.to change { Clusters::Cluster.count } @@ -147,6 +153,15 @@ describe Projects::Clusters::GcpController do expect(project.clusters.first).to be_kubernetes end end + + context 'when google project billing is not enabled' do + it 'renders the cluster form with an error' do + go + + expect(response).to set_flash[:error] + expect(response).to render_template('new') + end + end end context 'when access token is expired' do diff --git a/spec/support/google_api/cloud_platform_helpers.rb b/spec/support/google_api/cloud_platform_helpers.rb index 887ea5c99b1..99752ed396e 100644 --- a/spec/support/google_api/cloud_platform_helpers.rb +++ b/spec/support/google_api/cloud_platform_helpers.rb @@ -13,7 +13,7 @@ module GoogleApi def stub_google_project_billing_status redis_double = double allow(Gitlab::Redis::SharedState).to receive(:with).and_yield(redis_double) - allow(redis_double).to receive(:get).with(CheckGcpProjectBillingWorker.redis_shared_state_key_for).and_return('true') + allow(redis_double).to receive(:get).with(CheckGcpProjectBillingWorker.redis_shared_state_key_for('token')).and_return('true') end def stub_cloud_platform_get_zone_cluster(project_id, zone, cluster_id, **options) -- cgit v1.2.1 From 55f40164c9a012b31adc733d2f081b39970c6b2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 21 Dec 2017 20:45:00 +0100 Subject: Add CheckGcpProjectBilling worker to all_queues --- app/workers/all_queues.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 268b7028fd9..142e33e8325 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -97,3 +97,4 @@ - update_user_activity - upload_checksum - web_hook +- check_gcp_project_billing -- cgit v1.2.1 From 5be9a521df57d8dba6c4520220fe5f8a6a001dfa Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Thu, 21 Dec 2017 19:51:53 +0000 Subject: Set Auto Browser Performance Testing to 10.4 --- doc/topics/autodevops/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/topics/autodevops/index.md b/doc/topics/autodevops/index.md index 4056469e6c4..7863252dc17 100644 --- a/doc/topics/autodevops/index.md +++ b/doc/topics/autodevops/index.md @@ -218,7 +218,7 @@ Any security warnings are also [shown in the merge request widget](https://docs. ### Auto Browser Performance Testing -> Introduced in [GitLab Enterprise Edition Premium][ee] 10.3. +> Introduced in [GitLab Enterprise Edition Premium][ee] 10.4. Auto Browser Performance Testing utilizes the [Sitespeed.io container](https://hub.docker.com/r/sitespeedio/sitespeed.io/) to measure the performance of a web page. A JSON report is created and uploaded as an artifact, which includes the overall performance score for each page. By default, the root page of Review and Production environments will be tested. If you would like to add additional URL's to test, simply add the paths to a file named `.gitlab-urls.txt` in the root directory, one per line. For example: -- cgit v1.2.1 From ad1357d6ccb9be58f36f813c2296e48c18809c2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 21 Dec 2017 21:10:50 +0100 Subject: Fix use of pending decorator in spec --- spec/features/projects/clusters/gcp_spec.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index 4d0abb15b9a..ad8c1ebc1f8 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -137,9 +137,8 @@ feature 'Gcp Cluster', :js do end it 'user sees a check page' do - pending 'the frontend still has not been implemented' do - expect(page).to have_link('Continue') - end + pending 'the frontend still has not been implemented' + expect(page).to have_link('Continue') end end end -- cgit v1.2.1 From 9416193b30e2fd9cd793d078da5df1ee9d78c5c6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 21 Dec 2017 21:30:43 +0100 Subject: Fix clusters/gcp feature spec --- spec/features/projects/clusters/gcp_spec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/features/projects/clusters/gcp_spec.rb b/spec/features/projects/clusters/gcp_spec.rb index ad8c1ebc1f8..51bd09e88e0 100644 --- a/spec/features/projects/clusters/gcp_spec.rb +++ b/spec/features/projects/clusters/gcp_spec.rb @@ -22,6 +22,7 @@ feature 'Gcp Cluster', :js do context 'when user has a GCP project with billing enabled' do before do + allow(CheckGcpProjectBillingWorker).to receive(:perform_async) stub_google_project_billing_status end -- cgit v1.2.1 From 965617a05e4715a8687a16bc21ba9b75055d8dac Mon Sep 17 00:00:00 2001 From: asaparov Date: Tue, 19 Dec 2017 19:10:14 -0500 Subject: Bumped mysql2 gem version from 0.4.5 to 0.4.10. --- Gemfile | 2 +- Gemfile.lock | 4 ++-- changelogs/unreleased/bump_mysql_gem.yml | 5 +++++ 3 files changed, 8 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/bump_mysql_gem.yml diff --git a/Gemfile b/Gemfile index b6ffaf80f24..cc39714295e 100644 --- a/Gemfile +++ b/Gemfile @@ -12,7 +12,7 @@ gem 'sprockets', '~> 3.7.0' gem 'default_value_for', '~> 3.0.0' # Supported DBs -gem 'mysql2', '~> 0.4.5', group: :mysql +gem 'mysql2', '~> 0.4.10', group: :mysql gem 'pg', '~> 0.18.2', group: :postgres gem 'rugged', '~> 0.26.0' diff --git a/Gemfile.lock b/Gemfile.lock index a6e3c9e27cc..d11cadeec30 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -501,7 +501,7 @@ GEM mustermann (1.0.0) mustermann-grape (1.0.0) mustermann (~> 1.0.0) - mysql2 (0.4.5) + mysql2 (0.4.10) net-ldap (0.16.0) net-ssh (4.1.0) netrc (0.11.0) @@ -1082,7 +1082,7 @@ DEPENDENCIES method_source (~> 0.8) minitest (~> 5.7.0) mousetrap-rails (~> 1.4.6) - mysql2 (~> 0.4.5) + mysql2 (~> 0.4.10) net-ldap net-ssh (~> 4.1.0) nokogiri (~> 1.8.1) diff --git a/changelogs/unreleased/bump_mysql_gem.yml b/changelogs/unreleased/bump_mysql_gem.yml new file mode 100644 index 00000000000..58166949d72 --- /dev/null +++ b/changelogs/unreleased/bump_mysql_gem.yml @@ -0,0 +1,5 @@ +--- +title: Bump mysql2 gem version from 0.4.5 to 0.4.10 +merge_request: +author: asaparov +type: other -- cgit v1.2.1 From 17136f702a17e5a42268fd76796579ee95914b28 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 22 Dec 2017 06:59:13 +0000 Subject: Fix typo --- app/views/projects/jobs/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index 85d802a9d9c..ced7b65e940 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -90,7 +90,7 @@ .build-loader-animation.js-build-refresh - else - illustration = @build.playable? ? 'illustrations/manual_action.svg' : 'illustrations/job_not_triggered.svg' - - illustration_size = @build.playable? ? 'svg-394' : 'sgv-306' + - illustration_size = @build.playable? ? 'svg-394' : 'svg-306' - title = @build.playable? ? _('This job requires a manual action') : _('This job has not been triggered yet') - content = @build.playable? ? _('This job depends on a user to trigger its process. Often they are used to deploy code to production environments.') : _('This job depends on upstream jobs that need to succeed in order for this job to be triggered.') .row.empty-state -- cgit v1.2.1 From 32424e461bebebae2ad1bf302e8fb0d375771135 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 22 Dec 2017 11:51:25 +0100 Subject: Add QA sanity selectors scenario entrypoint --- qa/qa.rb | 4 ++++ qa/qa/scenario/test/sanity/selectors.rb | 14 ++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 qa/qa/scenario/test/sanity/selectors.rb diff --git a/qa/qa.rb b/qa/qa.rb index 340f5e35c67..6a878400287 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -56,6 +56,10 @@ module QA module Integration autoload :Mattermost, 'qa/scenario/test/integration/mattermost' end + + module Sanity + autoload :Selectors, 'qa/scenario/test/sanity/selectors' + end end end diff --git a/qa/qa/scenario/test/sanity/selectors.rb b/qa/qa/scenario/test/sanity/selectors.rb new file mode 100644 index 00000000000..892bb2966c7 --- /dev/null +++ b/qa/qa/scenario/test/sanity/selectors.rb @@ -0,0 +1,14 @@ +module QA + module Scenario + module Test + module Sanity + class Selectors < Scenario::Template + include Scenario::Bootable + + def perform(*) + end + end + end + end + end +end -- cgit v1.2.1 From 208411ee6242931670bdef5a8c92d34dee18498e Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 22 Dec 2017 11:54:08 +0100 Subject: Add QA classes that represent view partials and elements --- qa/qa.rb | 2 ++ qa/qa/page/element.rb | 6 ++++++ qa/qa/page/view.rb | 6 ++++++ 3 files changed, 14 insertions(+) create mode 100644 qa/qa/page/element.rb create mode 100644 qa/qa/page/view.rb diff --git a/qa/qa.rb b/qa/qa.rb index 6a878400287..fb3b646564a 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -70,6 +70,8 @@ module QA # module Page autoload :Base, 'qa/page/base' + autoload :View, 'qa/page/view' + autoload :Element, 'qa/page/element' module Main autoload :Login, 'qa/page/main/login' diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb new file mode 100644 index 00000000000..c634e834c96 --- /dev/null +++ b/qa/qa/page/element.rb @@ -0,0 +1,6 @@ +module QA + module Page + class Element + end + end +end diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb new file mode 100644 index 00000000000..076e4b8061b --- /dev/null +++ b/qa/qa/page/view.rb @@ -0,0 +1,6 @@ +module QA + module Page + class View + end + end +end -- cgit v1.2.1 From 856917520de2b1b254400b4501b4b98991280736 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 22 Dec 2017 13:01:12 +0100 Subject: Add domain specific language to define QA page elements --- qa/qa/page/base.rb | 21 +++++++++++++++++++++ qa/qa/page/element.rb | 26 ++++++++++++++++++++++++++ qa/qa/page/view.rb | 6 ++++++ qa/spec/page/base_spec.rb | 35 +++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+) create mode 100644 qa/spec/page/base_spec.rb diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 99eba02b6e3..4f79f24a629 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -5,6 +5,9 @@ module QA class Base include Capybara::DSL include Scenario::Actable + extend SingleForwardable + + def_delegators :evaluator, :view, :views def refresh visit current_url @@ -40,6 +43,24 @@ module QA def self.path raise NotImplementedError end + + def self.evaluator + @evaluator ||= Page::Base::DSL.new + end + + class DSL + attr_reader :views + + def initialize + @views = [] + end + + def view(path, &block) + Page::Element.evaluate(&block).tap do |elements| + @views.push(Page::View.new(path, elements)) + end + end + end end end end diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb index c634e834c96..e8e537070cb 100644 --- a/qa/qa/page/element.rb +++ b/qa/qa/page/element.rb @@ -1,6 +1,32 @@ module QA module Page class Element + attr_reader :name + + def initialize(name, pattern) + @name = name + @pattern = pattern + end + + def self.evaluate(&block) + Page::Element::DSL.new.tap do |evaluator| + evaluator.instance_exec(&block) + + return evaluator.elements + end + end + + class DSL + attr_reader :elements + + def initialize + @elements = [] + end + + def element(name, pattern) + @elements.push(Page::Element.new(name, pattern)) + end + end end end end diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb index 076e4b8061b..c54a0d738bc 100644 --- a/qa/qa/page/view.rb +++ b/qa/qa/page/view.rb @@ -1,6 +1,12 @@ module QA module Page class View + attr_reader :path, :elements + + def initialize(path, elements) + @path = path + @elements = elements + end end end end diff --git a/qa/spec/page/base_spec.rb b/qa/spec/page/base_spec.rb new file mode 100644 index 00000000000..31ff9e258a1 --- /dev/null +++ b/qa/spec/page/base_spec.rb @@ -0,0 +1,35 @@ +describe QA::Page::Base do + describe 'page helpers' do + it 'exposes helpful page helpers' do + expect(subject).to respond_to :refresh, :wait, :scroll_to + end + end + + describe 'DSL for defining view partials', '.view' do + subject do + Class.new(described_class) do + view 'path/to/some/view.html.haml' do + element :something, 'string pattern' + element :something_else, /regexp pattern/ + end + + view 'path/to/some/_partial.html.haml' do + element :something, 'string pattern' + end + end + end + + it 'makes it possible to define page views' do + expect(subject.views.size).to eq 2 + expect(subject.views).to all(be_an_instance_of QA::Page::View) + end + + it 'populates views objects with data about elements' do + subject.views.first.elements.tap do |elements| + expect(elements.size).to eq 2 + expect(elements).to all(be_an_instance_of QA::Page::Element) + expect(elements.map(&:name)).to eq [:something, :something_else] + end + end + end +end -- cgit v1.2.1 From b51ba96e4dddd847e42f0e41c3e1df2ff58d42e4 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 22 Dec 2017 13:13:09 +0100 Subject: Move QA elements DSL as it belongs to different class --- qa/qa/page/base.rb | 4 ++-- qa/qa/page/element.rb | 20 -------------------- qa/qa/page/view.rb | 18 ++++++++++++++++++ 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 4f79f24a629..d67407e7408 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -56,8 +56,8 @@ module QA end def view(path, &block) - Page::Element.evaluate(&block).tap do |elements| - @views.push(Page::View.new(path, elements)) + Page::View.evaluate(&block).tap do |view| + @views.push(Page::View.new(path, view.elements)) end end end diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb index e8e537070cb..fae5c81fbc2 100644 --- a/qa/qa/page/element.rb +++ b/qa/qa/page/element.rb @@ -7,26 +7,6 @@ module QA @name = name @pattern = pattern end - - def self.evaluate(&block) - Page::Element::DSL.new.tap do |evaluator| - evaluator.instance_exec(&block) - - return evaluator.elements - end - end - - class DSL - attr_reader :elements - - def initialize - @elements = [] - end - - def element(name, pattern) - @elements.push(Page::Element.new(name, pattern)) - end - end end end end diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb index c54a0d738bc..adec88e9918 100644 --- a/qa/qa/page/view.rb +++ b/qa/qa/page/view.rb @@ -7,6 +7,24 @@ module QA @path = path @elements = elements end + + def self.evaluate(&block) + Page::View::DSL.new.tap do |evaluator| + evaluator.instance_exec(&block) + end + end + + class DSL + attr_reader :elements + + def initialize + @elements = [] + end + + def element(name, pattern) + @elements.push(Page::Element.new(name, pattern)) + end + end end end end -- cgit v1.2.1 From 3f9b3d53f6c9e109f2ce0306a22ddb30ae9ed8f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Fri, 22 Dec 2017 14:36:06 +0100 Subject: Add url to link in new GCP cluster header partial --- app/views/projects/clusters/gcp/_header.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/clusters/gcp/_header.html.haml b/app/views/projects/clusters/gcp/_header.html.haml index f23d5b80e4f..e2d7326a312 100644 --- a/app/views/projects/clusters/gcp/_header.html.haml +++ b/app/views/projects/clusters/gcp/_header.html.haml @@ -10,5 +10,5 @@ - link_to_requirements = link_to(s_('ClusterIntegration|meets the requirements'), 'https://cloud.google.com/kubernetes-engine/docs/quickstart', target: '_blank', rel: 'noopener noreferrer') = s_('ClusterIntegration|Make sure your account %{link_to_requirements} to create clusters').html_safe % { link_to_requirements: link_to_requirements } %li - - link_to_container_project = link_to(s_('ClusterIntegration|Google Kubernetes Engine project'), target: '_blank', rel: 'noopener noreferrer') + - link_to_container_project = link_to(s_('ClusterIntegration|Google Kubernetes Engine project'), 'https://console.cloud.google.com/home/dashboard', target: '_blank', rel: 'noopener noreferrer') = s_('ClusterIntegration|This account must have permissions to create a cluster in the %{link_to_container_project} specified below').html_safe % { link_to_container_project: link_to_container_project } -- cgit v1.2.1 From 481f461380d4919077c543c51f58e37337167706 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 22 Dec 2017 15:22:54 +0100 Subject: Add implementation for matching view elements in QA --- qa/qa/page/base.rb | 4 ++++ qa/qa/page/element.rb | 12 ++++++++++ qa/qa/page/validator.rb | 18 ++++++++++++++ qa/qa/page/view.rb | 21 ++++++++++++++++ qa/spec/page/view_spec.rb | 61 +++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 116 insertions(+) create mode 100644 qa/qa/page/validator.rb create mode 100644 qa/spec/page/view_spec.rb diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index d67407e7408..48ac0569596 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -48,6 +48,10 @@ module QA @evaluator ||= Page::Base::DSL.new end + def self.validator + Page::Validator.new(self) + end + class DSL attr_reader :views diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb index fae5c81fbc2..c47776bb2f6 100644 --- a/qa/qa/page/element.rb +++ b/qa/qa/page/element.rb @@ -7,6 +7,18 @@ module QA @name = name @pattern = pattern end + + def expression? + @pattern.is_a?(Regexp) + end + + def matches?(line) + if expression? + line =~ pattern + else + line.includes?(pattern) + end + end end end end diff --git a/qa/qa/page/validator.rb b/qa/qa/page/validator.rb new file mode 100644 index 00000000000..bd9511595c5 --- /dev/null +++ b/qa/qa/page/validator.rb @@ -0,0 +1,18 @@ +module QA + module Page + class Validator + def initialize(page) + @page = page + @views = page.views + end + + def errors + @errors ||= @views.map do |view| + end + end + + def message + end + end + end +end diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb index adec88e9918..ec20045e26d 100644 --- a/qa/qa/page/view.rb +++ b/qa/qa/page/view.rb @@ -8,6 +8,27 @@ module QA @elements = elements end + def pathname + Pathname.new(File.join( __dir__, '../../../', @path)) + .cleanpath.expand_path + end + + def errors + ## + # Reduce required elements by streaming views and making assertions on + # elements' patterns. + # + @missing ||= @elements.dup.tap do |elements| + File.new(pathname.to_s).foreach do |line| + elements.reject! { |element| element.matches?(line) } + end + end + + @missing.map do |missing| + "Missing element `#{missing}` in `#{pathname}` view partial!" + end + end + def self.evaluate(&block) Page::View::DSL.new.tap do |evaluator| evaluator.instance_exec(&block) diff --git a/qa/spec/page/view_spec.rb b/qa/spec/page/view_spec.rb new file mode 100644 index 00000000000..27e83d35de1 --- /dev/null +++ b/qa/spec/page/view_spec.rb @@ -0,0 +1,61 @@ +describe QA::Page::View do + let(:element) do + double('element', name: :something, pattern: /some element/) + end + + subject { described_class.new('some/file.html', [element]) } + + describe '.evaluate' do + it 'evaluates a block and returns a DSL object' do + results = described_class.evaluate do + element :something, 'my pattern' + element :something_else, /another pattern/ + end + + expect(results.elements.size).to eq 2 + end + end + + describe '#pathname' do + it 'returns an absolute and clean path to the view' do + expect(subject.pathname.to_s).not_to include 'qa/page/' + expect(subject.pathname.to_s).to include 'some/file.html' + end + end + + describe '#errors' do + let(:file) { spy('file') } + + before do + allow(File).to receive(:new).and_return(file) + end + + context 'when pattern is found' do + before do + allow(file).to receive(:foreach) + .and_yield('some element').once + allow(element).to receive(:matches?) + .with('some element').and_return(true) + end + + it 'walks through the view and asserts on elements existence' do + expect(subject.errors).to be_empty + end + end + + context 'when pattern has not been found' do + before do + allow(file).to receive(:foreach) + .and_yield('some element').once + allow(element).to receive(:matches?) + .with('some element').and_return(false) + end + + it 'returns an array of errors related to missing elements' do + expect(subject.errors).not_to be_empty + expect(subject.errors.first) + .to match %r(Missing element `.*` in `.*/some/file.html` view) + end + end + end +end -- cgit v1.2.1 From d69e4541a4874208590a7387186f8929143fd2af Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 22 Dec 2017 15:40:46 +0100 Subject: Use composite pattern to return page view errors --- qa/qa/page/base.rb | 4 ++-- qa/qa/page/element.rb | 13 ++++++------- qa/qa/page/validator.rb | 18 ------------------ qa/qa/page/view.rb | 4 ++-- qa/spec/page/base_spec.rb | 17 ++++++++++++++++- qa/spec/page/element_spec.rb | 34 ++++++++++++++++++++++++++++++++++ qa/spec/page/view_spec.rb | 4 ++++ 7 files changed, 64 insertions(+), 30 deletions(-) delete mode 100644 qa/qa/page/validator.rb create mode 100644 qa/spec/page/element_spec.rb diff --git a/qa/qa/page/base.rb b/qa/qa/page/base.rb index 48ac0569596..9064c78b792 100644 --- a/qa/qa/page/base.rb +++ b/qa/qa/page/base.rb @@ -48,8 +48,8 @@ module QA @evaluator ||= Page::Base::DSL.new end - def self.validator - Page::Validator.new(self) + def self.errors + @errors ||= views.map(&:errors).flatten end class DSL diff --git a/qa/qa/page/element.rb b/qa/qa/page/element.rb index c47776bb2f6..173fdbf6d36 100644 --- a/qa/qa/page/element.rb +++ b/qa/qa/page/element.rb @@ -8,15 +8,14 @@ module QA @pattern = pattern end - def expression? - @pattern.is_a?(Regexp) - end - def matches?(line) - if expression? - line =~ pattern + case @pattern + when Regexp + !!(line =~ @pattern) + when String + line.include?(@pattern) else - line.includes?(pattern) + raise ArgumentError, 'Pattern should be either String or Regexp!' end end end diff --git a/qa/qa/page/validator.rb b/qa/qa/page/validator.rb deleted file mode 100644 index bd9511595c5..00000000000 --- a/qa/qa/page/validator.rb +++ /dev/null @@ -1,18 +0,0 @@ -module QA - module Page - class Validator - def initialize(page) - @page = page - @views = page.views - end - - def errors - @errors ||= @views.map do |view| - end - end - - def message - end - end - end -end diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb index ec20045e26d..b988b179e8f 100644 --- a/qa/qa/page/view.rb +++ b/qa/qa/page/view.rb @@ -15,8 +15,8 @@ module QA def errors ## - # Reduce required elements by streaming views and making assertions on - # elements' patterns. + # Reduce required elements by streaming view and making assertions on + # elements' existence. # @missing ||= @elements.dup.tap do |elements| File.new(pathname.to_s).foreach do |line| diff --git a/qa/spec/page/base_spec.rb b/qa/spec/page/base_spec.rb index 31ff9e258a1..63445d8f7bf 100644 --- a/qa/spec/page/base_spec.rb +++ b/qa/spec/page/base_spec.rb @@ -5,7 +5,7 @@ describe QA::Page::Base do end end - describe 'DSL for defining view partials', '.view' do + describe '.view', 'DSL for defining view partials' do subject do Class.new(described_class) do view 'path/to/some/view.html.haml' do @@ -32,4 +32,19 @@ describe QA::Page::Base do end end end + + describe '.errors' do + let(:view) { double('view') } + + before do + allow(described_class).to receive(:views) + .and_return([view]) + + allow(view).to receive(:errors).and_return(['some error']) + end + + it 'iterates views composite and returns errors' do + expect(described_class.errors).to eq ['some error'] + end + end end diff --git a/qa/spec/page/element_spec.rb b/qa/spec/page/element_spec.rb new file mode 100644 index 00000000000..238c4d1ac66 --- /dev/null +++ b/qa/spec/page/element_spec.rb @@ -0,0 +1,34 @@ +describe QA::Page::Element do + context 'when pattern is an expression' do + subject { described_class.new(:something, /button 'Sign in'/) } + + it 'is correctly matches against a string' do + expect(subject.matches?("button 'Sign in'")).to be true + end + + it 'does not match if string does not match against a pattern' do + expect(subject.matches?("button 'Sign out'")).to be false + end + end + + context 'when pattern is a string' do + subject { described_class.new(:something, 'button') } + + it 'is correctly matches against a string' do + expect(subject.matches?('some button in the view')).to be true + end + + it 'does not match if string does not match against a pattern' do + expect(subject.matches?('text_field :name')).to be false + end + end + + context 'when pattern is not supported' do + subject { described_class.new(:something, [/something/]) } + + it 'raises an error' do + expect { subject.matches?('some line') } + .to raise_error ArgumentError + end + end +end diff --git a/qa/spec/page/view_spec.rb b/qa/spec/page/view_spec.rb index 27e83d35de1..6a78e32db68 100644 --- a/qa/spec/page/view_spec.rb +++ b/qa/spec/page/view_spec.rb @@ -57,5 +57,9 @@ describe QA::Page::View do .to match %r(Missing element `.*` in `.*/some/file.html` view) end end + + context 'when view partial has not been found' do + pending + end end end -- cgit v1.2.1 From d2c2f93fe6ed557ecfbc53afedef899dd49b244d Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 22 Dec 2017 16:09:00 +0100 Subject: Append page validation error if view partial is missing --- qa/qa/page/view.rb | 6 +++++- qa/spec/page/view_spec.rb | 51 ++++++++++++++++++++++++++++------------------- 2 files changed, 36 insertions(+), 21 deletions(-) diff --git a/qa/qa/page/view.rb b/qa/qa/page/view.rb index b988b179e8f..fa0ed8be9d9 100644 --- a/qa/qa/page/view.rb +++ b/qa/qa/page/view.rb @@ -9,11 +9,15 @@ module QA end def pathname - Pathname.new(File.join( __dir__, '../../../', @path)) + @pathname ||= Pathname.new(File.join( __dir__, '../../../', @path)) .cleanpath.expand_path end def errors + unless pathname.readable? + return ["Missing view partial `#{pathname}`!"] + end + ## # Reduce required elements by streaming view and making assertions on # elements' existence. diff --git a/qa/spec/page/view_spec.rb b/qa/spec/page/view_spec.rb index 6a78e32db68..dd38b171ad5 100644 --- a/qa/spec/page/view_spec.rb +++ b/qa/spec/page/view_spec.rb @@ -30,36 +30,47 @@ describe QA::Page::View do allow(File).to receive(:new).and_return(file) end - context 'when pattern is found' do + context 'when view partial is present' do before do - allow(file).to receive(:foreach) - .and_yield('some element').once - allow(element).to receive(:matches?) - .with('some element').and_return(true) + allow(subject.pathname).to receive(:readable?) + .and_return(true) end - it 'walks through the view and asserts on elements existence' do - expect(subject.errors).to be_empty - end - end + context 'when pattern is found' do + before do + allow(file).to receive(:foreach) + .and_yield('some element').once + allow(element).to receive(:matches?) + .with('some element').and_return(true) + end - context 'when pattern has not been found' do - before do - allow(file).to receive(:foreach) - .and_yield('some element').once - allow(element).to receive(:matches?) - .with('some element').and_return(false) + it 'walks through the view and asserts on elements existence' do + expect(subject.errors).to be_empty + end end - it 'returns an array of errors related to missing elements' do - expect(subject.errors).not_to be_empty - expect(subject.errors.first) - .to match %r(Missing element `.*` in `.*/some/file.html` view) + context 'when pattern has not been found' do + before do + allow(file).to receive(:foreach) + .and_yield('some element').once + allow(element).to receive(:matches?) + .with('some element').and_return(false) + end + + it 'returns an array of errors related to missing elements' do + expect(subject.errors).not_to be_empty + expect(subject.errors.first) + .to match %r(Missing element `.*` in `.*/some/file.html` view) + end end end context 'when view partial has not been found' do - pending + it 'returns an error when it is not able to find the partial' do + expect(subject.errors).to be_one + expect(subject.errors.first) + .to match %r(Missing view partial `.*/some/file.html`!) + end end end end -- cgit v1.2.1 From 86c0bb49b79217e318122e8921f92ef78ee7a1cf Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Fri, 22 Dec 2017 18:23:14 +0000 Subject: Uniform style between all upload forms --- app/views/groups/edit.html.haml | 4 ++-- app/views/profiles/show.html.haml | 13 +++++------ app/views/projects/edit.html.haml | 25 +++++++++++----------- .../shared/_choose_group_avatar_button.html.haml | 11 ++++------ 4 files changed, 23 insertions(+), 30 deletions(-) diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 16038ef2f79..76a8099d7c0 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -13,13 +13,13 @@ = group_icon(@group, alt: '', class: 'avatar group-avatar s160') %p.light - if @group.avatar? - You can change your group avatar here + You can change the group avatar here - else You can upload a group avatar here = render 'shared/choose_group_avatar_button', f: f - if @group.avatar? %hr - = link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" + = link_to _('Remove avatar'), group_avatar_path(@group.to_param), data: { confirm: _("Avatar will be removed. Are you sure?")}, method: :delete, class: "btn btn-danger btn-inverted" = render 'shared/visibility_level', f: f, visibility_level: @group.visibility_level, can_change_visibility_level: can_change_group_visibility_level?(@group), form_model: @group diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index f9dae310e01..0f773933ac2 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -22,18 +22,15 @@ .clearfix.avatar-image.append-bottom-default = link_to avatar_icon(@user, 400), target: '_blank', rel: 'noopener noreferrer' do = image_tag avatar_icon(@user, 160), alt: '', class: 'avatar s160' - %h5.prepend-top-0 - Upload new avatar + %h5.prepend-top-0= _("Upload new avatar") .prepend-top-5.append-bottom-10 - %a.btn.js-choose-user-avatar-button - Choose file... - %span.avatar-file-name.prepend-left-default.js-avatar-filename No file chosen + %button.btn.js-choose-user-avatar-button{ type: 'button' }= _("Choose file...") + %span.avatar-file-name.prepend-left-default.js-avatar-filename= _("No file chosen") = f.file_field_without_bootstrap :avatar, class: 'js-user-avatar-input hidden', accept: 'image/*' - .help-block - The maximum file size allowed is 200KB. + .help-block= _("The maximum file size allowed is 200KB.") - if @user.avatar? %hr - = link_to 'Remove avatar', profile_avatar_path, data: { confirm: 'Avatar will be removed. Are you sure?' }, method: :delete, class: 'btn btn-gray' + = link_to _('Remove avatar'), profile_avatar_path, data: { confirm: _('Avatar will be removed. Are you sure?') }, method: :delete, class: 'btn btn-danger btn-inverted' %hr .row .col-lg-4.profile-settings-sidebar diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 9d0d525a292..e16d132f869 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -42,24 +42,23 @@ = f.text_field :tag_list, value: @project.tag_list.sort.join(', '), maxlength: 2000, class: "form-control" %p.help-block Separate tags with commas. %fieldset.features - %h5.prepend-top-0 - Project avatar + %h5.prepend-top-0= _("Project avatar") .form-group - if @project.avatar? - .avatar-container.s160 + .avatar-container.s160.append-bottom-15 = project_icon(@project.full_path, alt: '', class: 'avatar project-avatar s160') - %p.light - - if @project.avatar_in_git - Project avatar in repository: #{ @project.avatar_in_git } - %a.choose-btn.btn.js-choose-project-avatar-button - Choose file... - %span.file_name.prepend-left-default.js-avatar-filename No file chosen - = f.file_field :avatar, class: "js-project-avatar-input hidden" - .help-block The maximum file size allowed is 200KB. + - if @project.avatar_in_git + %p.light + = _("Project avatar in repository: %{link}").html_safe % { link: @project.avatar_in_git } + .prepend-top-5.append-bottom-10 + %button.btn.js-choose-project-avatar-button{ type: 'button' }= _("Choose file...") + %span.file_name.prepend-left-default.js-avatar-filename= _("No file chosen") + = f.file_field :avatar, class: "js-project-avatar-input hidden" + .help-block= _("The maximum file size allowed is 200KB.") - if @project.avatar? %hr - = link_to 'Remove avatar', project_avatar_path(@project), data: { confirm: "Project avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" - = f.submit 'Save changes', class: "btn btn-save" + = link_to _('Remove avatar'), project_avatar_path(@project), data: { confirm: _("Avatar will be removed. Are you sure?") }, method: :delete, class: "btn btn-danger btn-inverted" + = f.submit 'Save changes', class: "btn btn-success" %section.settings.sharing-permissions.no-animate{ class: ('expanded' if expanded) } .settings-header diff --git a/app/views/shared/_choose_group_avatar_button.html.haml b/app/views/shared/_choose_group_avatar_button.html.haml index 94295970acf..75c65520350 100644 --- a/app/views/shared/_choose_group_avatar_button.html.haml +++ b/app/views/shared/_choose_group_avatar_button.html.haml @@ -1,7 +1,4 @@ -%button.choose-btn.btn.btn-sm.js-choose-group-avatar-button{ type: 'button' } - %i.fa.fa-paperclip - %span Choose File ... -  -%span.file_name.js-avatar-filename File name... -= f.file_field :avatar, class: 'js-group-avatar-input hidden' -.light The maximum file size allowed is 200KB. +%button.btn.js-choose-group-avatar-button{ type: 'button' }= _("Choose File ...") +%span.file_name.js-avatar-filename= _("No file chosen") += f.file_field :avatar, class: "js-group-avatar-input hidden" +.help-block= _("The maximum file size allowed is 200KB.") -- cgit v1.2.1 From 705022d11422e42f5ff2473bdc1c80ddd0be9529 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Fri, 22 Dec 2017 20:07:49 +0100 Subject: Add cache_index migration --- .../20171222183504_add_cache_index_to_project.rb | 19 +++++++++++++++++++ db/schema.rb | 3 ++- 2 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20171222183504_add_cache_index_to_project.rb diff --git a/db/migrate/20171222183504_add_cache_index_to_project.rb b/db/migrate/20171222183504_add_cache_index_to_project.rb new file mode 100644 index 00000000000..f43be14fb42 --- /dev/null +++ b/db/migrate/20171222183504_add_cache_index_to_project.rb @@ -0,0 +1,19 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddCacheIndexToProject < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_column_with_default(:projects, :cache_index, :integer, default: 0) + end + + def down + remove_column(:projects, :cache_index) + end +end diff --git a/db/schema.rb b/db/schema.rb index aa5db5da4f0..5ecfb0651c6 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: 20171220191323) do +ActiveRecord::Schema.define(version: 20171222183504) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" @@ -1447,6 +1447,7 @@ ActiveRecord::Schema.define(version: 20171220191323) do t.boolean "repository_read_only" t.boolean "merge_requests_ff_only_enabled", default: false t.boolean "merge_requests_rebase_enabled", default: false, null: false + t.integer "cache_index", default: 0, null: false end add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree -- cgit v1.2.1 From f58cc2ee6044ab3445ff9a4438faa29b30b303fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Fri, 22 Dec 2017 20:22:29 +0100 Subject: Remove default value from cache_index migration --- db/migrate/20171222183504_add_cache_index_to_project.rb | 10 ++-------- db/schema.rb | 2 +- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/db/migrate/20171222183504_add_cache_index_to_project.rb b/db/migrate/20171222183504_add_cache_index_to_project.rb index f43be14fb42..e1d73db1ab0 100644 --- a/db/migrate/20171222183504_add_cache_index_to_project.rb +++ b/db/migrate/20171222183504_add_cache_index_to_project.rb @@ -7,13 +7,7 @@ class AddCacheIndexToProject < ActiveRecord::Migration # Set this constant to true if this migration requires downtime. DOWNTIME = false - disable_ddl_transaction! - - def up - add_column_with_default(:projects, :cache_index, :integer, default: 0) - end - - def down - remove_column(:projects, :cache_index) + def change + add_column :projects, :cache_index, :integer end end diff --git a/db/schema.rb b/db/schema.rb index 5ecfb0651c6..b7512f293a6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1447,7 +1447,7 @@ ActiveRecord::Schema.define(version: 20171222183504) do t.boolean "repository_read_only" t.boolean "merge_requests_ff_only_enabled", default: false t.boolean "merge_requests_rebase_enabled", default: false, null: false - t.integer "cache_index", default: 0, null: false + t.integer "cache_index" end add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree -- cgit v1.2.1 From 0a002e230badf96f5c89d55945ddd85113edd628 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Fri, 22 Dec 2017 21:28:25 +0100 Subject: Implement ResetProjectCacheService --- app/services/reset_project_cache_service.rb | 1 + spec/services/reset_project_cache_service_spec.rb | 20 ++++++++++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/app/services/reset_project_cache_service.rb b/app/services/reset_project_cache_service.rb index 85ba6d953a2..0886c6b8315 100644 --- a/app/services/reset_project_cache_service.rb +++ b/app/services/reset_project_cache_service.rb @@ -1,4 +1,5 @@ class ResetProjectCacheService < BaseService def execute + @project.increment!(:cache_index) end end diff --git a/spec/services/reset_project_cache_service_spec.rb b/spec/services/reset_project_cache_service_spec.rb index 9623035a5a4..df969d08f39 100644 --- a/spec/services/reset_project_cache_service_spec.rb +++ b/spec/services/reset_project_cache_service_spec.rb @@ -6,7 +6,23 @@ describe ResetProjectCacheService do subject { described_class.new(project, user).execute } - it "resets project cache" do - fail + context 'when project cache_index is nil' do + before do + project.cache_index = nil + end + + it 'sets project cache_index to one' do + expect { subject }.to change { project.reload.cache_index }.from(nil).to(1) + end + end + + context 'when project cache_index is a numeric value' do + before do + project.update_attributes(cache_index: 1) + end + + it 'increments project cache index' do + expect { subject }.to change { project.reload.cache_index }.by(1) + end end end -- cgit v1.2.1 From 771b97394a16ae8dcd9acc84ab6a076f68726fd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Fri, 22 Dec 2017 23:06:15 +0100 Subject: Use Project.cache_index in Build#cache --- app/models/ci/build.rb | 6 +++++- spec/models/ci/build_spec.rb | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 83fe23606d1..e4ca74f87f2 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -461,7 +461,11 @@ module Ci end def cache - [options[:cache]] + if options[:cache] && project.cache_index + options[:cache].merge(key: "#{options[:cache][:key]}:#{project.cache_index}") + else + [options[:cache]] + end end def credentials diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 871e8b47650..96513281994 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -255,6 +255,42 @@ describe Ci::Build do end end + describe '#cache' do + let(:options) { { cache: { key: "key", paths: ["public"], policy: "pull-push" } } } + + subject { build.cache } + + context 'when build has cache' do + before do + allow(build).to receive(:options).and_return(options) + end + + context 'when project has cache_index' do + before do + allow_any_instance_of(Project).to receive(:cache_index).and_return(1) + end + + it { is_expected.to include(key: "key:1") } + end + + context 'when project does not have cache_index' do + before do + allow_any_instance_of(Project).to receive(:cache_index).and_return(nil) + end + + it { is_expected.to eq([options[:cache]]) } + end + end + + context 'when build does not have cache' do + before do + allow(build).to receive(:options).and_return({}) + end + + it { is_expected.to eq([nil]) } + end + end + describe '#depends_on_builds' do let!(:build) { create(:ci_build, pipeline: pipeline, name: 'build', stage_idx: 0, stage: 'build') } let!(:rspec_test) { create(:ci_build, pipeline: pipeline, name: 'rspec', stage_idx: 1, stage: 'test') } -- cgit v1.2.1 From 6185ce0c84b5674abf4f54576fec44fece571565 Mon Sep 17 00:00:00 2001 From: Jacopo Date: Thu, 21 Dec 2017 22:15:50 +0100 Subject: Rendering of emoji's in Group-Overview Allows rendering of emoji's in the Group Overview. --- app/assets/javascripts/groups/components/group_item.vue | 3 ++- app/assets/javascripts/groups/store/groups_store.js | 2 +- app/serializers/group_child_entity.rb | 9 +++++++++ .../unreleased/40549-render-emoj-in-groups-overview.yml | 5 +++++ spec/features/groups/show_spec.rb | 16 ++++++++++++++++ spec/serializers/group_child_entity_spec.rb | 12 +++++++++++- 6 files changed, 44 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/40549-render-emoj-in-groups-overview.yml diff --git a/app/assets/javascripts/groups/components/group_item.vue b/app/assets/javascripts/groups/components/group_item.vue index 6421547bbde..ad01c261b7f 100644 --- a/app/assets/javascripts/groups/components/group_item.vue +++ b/app/assets/javascripts/groups/components/group_item.vue @@ -139,7 +139,8 @@ export default {
- {{group.description}} + +
😄

') + end + end + it_behaves_like 'group child json' end end -- cgit v1.2.1 From 7b52a3482ec696320e4a101a80537e4e61118b5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Sun, 24 Dec 2017 22:35:18 +0100 Subject: Add cache_index to list of safe Project attributes --- spec/lib/gitlab/import_export/safe_model_attributes.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index ec8fa99e0da..7e09f486854 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -459,6 +459,7 @@ Project: - delete_error - merge_requests_ff_only_enabled - merge_requests_rebase_enabled +- cache_index Author: - name ProjectFeature: -- cgit v1.2.1 From 40264b87af1c8a228a6b943367d6cd06d9f8d812 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Sun, 24 Dec 2017 17:03:38 +0500 Subject: Move invites spinach test to Rspec https://gitlab.com/gitlab-org/gitlab-ce/issues/23036 --- features/invites.feature | 45 -------------------- features/steps/invites.rb | 80 ----------------------------------- spec/features/invites_spec.rb | 97 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+), 125 deletions(-) delete mode 100644 features/invites.feature delete mode 100644 features/steps/invites.rb create mode 100644 spec/features/invites_spec.rb diff --git a/features/invites.feature b/features/invites.feature deleted file mode 100644 index dc8eefaeaed..00000000000 --- a/features/invites.feature +++ /dev/null @@ -1,45 +0,0 @@ -Feature: Invites - Background: - Given "John Doe" is owner of group "Owned" - And "John Doe" has invited "user@example.com" to group "Owned" - - Scenario: Viewing invitation when signed out - When I visit the invitation page - Then I should be redirected to the sign in page - And I should see a notice telling me to sign in - - Scenario: Signing in to view invitation - When I visit the invitation page - And I sign in as "Mary Jane" - Then I should be redirected to the invitation page - - Scenario: Viewing invitation when signed in - Given I sign in as "Mary Jane" - And I visit the invitation page - Then I should see the invitation details - And I should see an "Accept invitation" button - And I should see a "Decline" button - - Scenario: Viewing invitation as an existing member - Given I sign in as "John Doe" - And I visit the invitation page - Then I should see a message telling me I'm already a member - - Scenario: Accepting the invitation - Given I sign in as "Mary Jane" - And I visit the invitation page - And I click the "Accept invitation" button - Then I should be redirected to the group page - And I should see a notice telling me I have access - - Scenario: Declining the application when signed in - Given I sign in as "Mary Jane" - And I visit the invitation page - And I click the "Decline" button - Then I should be redirected to the dashboard - And I should see a notice telling me I have declined - - Scenario: Declining the application when signed out - When I visit the invitation's decline page - Then I should be redirected to the sign in page - And I should see a notice telling me I have declined diff --git a/features/steps/invites.rb b/features/steps/invites.rb deleted file mode 100644 index dac972172aa..00000000000 --- a/features/steps/invites.rb +++ /dev/null @@ -1,80 +0,0 @@ -class Spinach::Features::Invites < Spinach::FeatureSteps - include SharedAuthentication - include SharedUser - include SharedGroup - - step '"John Doe" has invited "user@example.com" to group "Owned"' do - user = User.find_by(name: "John Doe") - group = Group.find_by(name: "Owned") - group.add_developer("user@example.com", user) - end - - step 'I visit the invitation page' do - group = Group.find_by(name: "Owned") - invite = group.group_members.invite.last - invite.generate_invite_token! - @raw_invite_token = invite.raw_invite_token - visit invite_path(@raw_invite_token) - end - - step 'I should be redirected to the sign in page' do - expect(current_path).to eq(new_user_session_path) - end - - step 'I should see a notice telling me to sign in' do - expect(page).to have_content "To accept this invitation, sign in" - end - - step 'I should be redirected to the invitation page' do - expect(current_path).to eq(invite_path(@raw_invite_token)) - end - - step 'I should see the invitation details' do - expect(page).to have_content("You have been invited by John Doe to join group Owned as Developer.") - end - - step "I should see a message telling me I'm already a member" do - expect(page).to have_content("However, you are already a member of this group.") - end - - step 'I should see an "Accept invitation" button' do - expect(page).to have_link("Accept invitation") - end - - step 'I should see a "Decline" button' do - expect(page).to have_link("Decline") - end - - step 'I click the "Accept invitation" button' do - page.click_link "Accept invitation" - end - - step 'I should be redirected to the group page' do - group = Group.find_by(name: "Owned") - expect(current_path).to eq(group_path(group)) - end - - step 'I should see a notice telling me I have access' do - expect(page).to have_content("You have been granted Developer access to group Owned.") - end - - step 'I click the "Decline" button' do - page.click_link "Decline" - end - - step 'I should be redirected to the dashboard' do - expect(current_path).to eq(dashboard_projects_path) - end - - step 'I should see a notice telling me I have declined' do - expect(page).to have_content("You have declined the invitation to join group Owned.") - end - - step "I visit the invitation's decline page" do - group = Group.find_by(name: "Owned") - invite = group.group_members.invite.last - invite.generate_invite_token! - @raw_invite_token = invite.raw_invite_token - visit decline_invite_path(@raw_invite_token) - end -end diff --git a/spec/features/invites_spec.rb b/spec/features/invites_spec.rb new file mode 100644 index 00000000000..e4be6193b8b --- /dev/null +++ b/spec/features/invites_spec.rb @@ -0,0 +1,97 @@ +require 'spec_helper' + +describe 'Invites' do + let(:user) { create(:user) } + let(:owner) { create(:user, name: 'John Doe') } + let(:group) { create(:group, name: 'Owned') } + let(:project) { create(:project, :repository, namespace: group) } + let(:invite) { group.group_members.invite.last } + + before do + project.add_master(owner) + group.add_user(owner, Gitlab::Access::OWNER) + group.add_developer('user@example.com', owner) + invite.generate_invite_token! + end + + context 'when signed out' do + before do + visit invite_path(invite.raw_invite_token) + end + + it 'renders sign in page with sign in notice' do + expect(current_path).to eq(new_user_session_path) + expect(page).to have_content('To accept this invitation, sign in') + end + + it 'sign in and redirects to invitation page' do + fill_in 'user_login', with: user.email + fill_in 'user_password', with: user.password + check 'user_remember_me' + click_button 'Sign in' + + expect(current_path).to eq(invite_path(invite.raw_invite_token)) + expect(page).to have_content( + 'You have been invited by John Doe to join group Owned as Developer.' + ) + expect(page).to have_link('Accept invitation') + expect(page).to have_link('Decline') + end + end + + context 'when signed in as an exists member' do + before do + sign_in(owner) + end + + it 'shows message user already a member' do + visit invite_path(invite.raw_invite_token) + expect(page).to have_content('However, you are already a member of this group.') + end + end + + describe 'accepting the invitation' do + before do + sign_in(user) + visit invite_path(invite.raw_invite_token) + end + + it 'grants access and redirects to group page' do + page.click_link 'Accept invitation' + expect(current_path).to eq(group_path(group)) + expect(page).to have_content( + 'You have been granted Developer access to group Owned.' + ) + end + end + + describe 'declining the application' do + context 'when signed in' do + before do + sign_in(user) + visit invite_path(invite.raw_invite_token) + end + + it 'declines application and redirects to dashboard' do + page.click_link 'Decline' + expect(current_path).to eq(dashboard_projects_path) + expect(page).to have_content( + 'You have declined the invitation to join group Owned.' + ) + end + end + + context 'when signed out' do + before do + visit decline_invite_path(invite.raw_invite_token) + end + + it 'declines application and redirects to sign in page' do + expect(current_path).to eq(new_user_session_path) + expect(page).to have_content( + 'You have declined the invitation to join group Owned.' + ) + end + end + end +end -- cgit v1.2.1 From 7a815d7585a8a433359297f37b349604bfbcf2c8 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Sun, 24 Dec 2017 22:46:30 +0500 Subject: Move explore groups spinach test to RSpec https://gitlab.com/gitlab-org/gitlab-ce/issues/23036 --- features/explore/groups.feature | 105 ----------------------------------- features/steps/explore/groups.rb | 88 ----------------------------- spec/features/explore/groups_spec.rb | 87 +++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 193 deletions(-) delete mode 100644 features/explore/groups.feature delete mode 100644 features/steps/explore/groups.rb create mode 100644 spec/features/explore/groups_spec.rb diff --git a/features/explore/groups.feature b/features/explore/groups.feature deleted file mode 100644 index 830810615e0..00000000000 --- a/features/explore/groups.feature +++ /dev/null @@ -1,105 +0,0 @@ -@public -Feature: Explore Groups - Background: - Given group "TestGroup" has private project "Enterprise" - - @javascript - Scenario: I should see group with private and internal projects as user - Given group "TestGroup" has internal project "Internal" - When I sign in as a user - And I visit group "TestGroup" page - Then I should see project "Internal" items - And I should not see project "Enterprise" items - - @javascript - Scenario: I should see group issues for internal project as user - Given group "TestGroup" has internal project "Internal" - When I sign in as a user - And I visit group "TestGroup" issues page - Then I should see project "Internal" items - And I should not see project "Enterprise" items - - @javascript - Scenario: I should see group merge requests for internal project as user - Given group "TestGroup" has internal project "Internal" - When I sign in as a user - And I visit group "TestGroup" merge requests page - Then I should see project "Internal" items - And I should not see project "Enterprise" items - - @javascript - Scenario: I should see group with private, internal and public projects as visitor - Given group "TestGroup" has internal project "Internal" - Given group "TestGroup" has public project "Community" - When I visit group "TestGroup" page - Then I should see project "Community" items - And I should not see project "Internal" items - And I should not see project "Enterprise" items - - @javascript - Scenario: I should see group issues for public project as visitor - Given group "TestGroup" has internal project "Internal" - Given group "TestGroup" has public project "Community" - When I visit group "TestGroup" issues page - Then I should see project "Community" items - And I should not see project "Internal" items - And I should not see project "Enterprise" items - - @javascript - Scenario: I should see group merge requests for public project as visitor - Given group "TestGroup" has internal project "Internal" - Given group "TestGroup" has public project "Community" - When I visit group "TestGroup" merge requests page - Then I should see project "Community" items - And I should not see project "Internal" items - And I should not see project "Enterprise" items - - @javascript - Scenario: I should see group with private, internal and public projects as user - Given group "TestGroup" has internal project "Internal" - Given group "TestGroup" has public project "Community" - When I sign in as a user - And I visit group "TestGroup" page - Then I should see project "Community" items - And I should see project "Internal" items - And I should not see project "Enterprise" items - - @javascript - Scenario: I should see group issues for internal and public projects as user - Given group "TestGroup" has internal project "Internal" - Given group "TestGroup" has public project "Community" - When I sign in as a user - And I visit group "TestGroup" issues page - Then I should see project "Community" items - And I should see project "Internal" items - And I should not see project "Enterprise" items - - @javascript - Scenario: I should see group merge requests for internal and public projects as user - Given group "TestGroup" has internal project "Internal" - Given group "TestGroup" has public project "Community" - When I sign in as a user - And I visit group "TestGroup" merge requests page - Then I should see project "Community" items - And I should see project "Internal" items - And I should not see project "Enterprise" items - - @javascript - Scenario: I should see group with public project in public groups area - Given group "TestGroup" has public project "Community" - When I visit the public groups area - Then I should see group "TestGroup" - - @javascript - Scenario: I should see group with public project in public groups area as user - Given group "TestGroup" has public project "Community" - When I sign in as a user - And I visit the public groups area - Then I should see group "TestGroup" - - @javascript - Scenario: I should see group with internal project in public groups area as user - Given group "TestGroup" has internal project "Internal" - When I sign in as a user - And I visit the public groups area - Then I should see group "TestGroup" diff --git a/features/steps/explore/groups.rb b/features/steps/explore/groups.rb deleted file mode 100644 index 409bf0cb416..00000000000 --- a/features/steps/explore/groups.rb +++ /dev/null @@ -1,88 +0,0 @@ -class Spinach::Features::ExploreGroups < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - include SharedGroup - include SharedProject - - step 'group "TestGroup" has private project "Enterprise"' do - group_has_project("TestGroup", "Enterprise", Gitlab::VisibilityLevel::PRIVATE) - end - - step 'group "TestGroup" has internal project "Internal"' do - group_has_project("TestGroup", "Internal", Gitlab::VisibilityLevel::INTERNAL) - end - - step 'group "TestGroup" has public project "Community"' do - group_has_project("TestGroup", "Community", Gitlab::VisibilityLevel::PUBLIC) - end - - step '"John Doe" is owner of group "TestGroup"' do - group = Group.find_by(name: "TestGroup") || create(:group, name: "TestGroup") - user = create(:user, name: "John Doe") - group.add_owner(user) - end - - step 'I visit group "TestGroup" page' do - visit group_path(Group.find_by(name: "TestGroup")) - end - - step 'I visit group "TestGroup" issues page' do - visit issues_group_path(Group.find_by(name: "TestGroup")) - end - - step 'I visit group "TestGroup" merge requests page' do - visit merge_requests_group_path(Group.find_by(name: "TestGroup")) - end - - step 'I visit group "TestGroup" members page' do - visit group_group_members_path(Group.find_by(name: "TestGroup")) - end - - step 'I should not see project "Enterprise" items' do - expect(page).not_to have_content "Enterprise" - end - - step 'I should see project "Internal" items' do - expect(page).to have_content "Internal" - end - - step 'I should not see project "Internal" items' do - expect(page).not_to have_content "Internal" - end - - step 'I should see project "Community" items' do - expect(page).to have_content "Community" - end - - step 'I change filter to Everyone\'s' do - click_link "Everyone's" - end - - step 'I should see group member "John Doe"' do - expect(page).to have_content "John Doe" - end - - protected - - def group_has_project(groupname, projectname, visibility_level) - group = Group.find_by(name: groupname) || create(:group, name: groupname) - project = create(:project, - namespace: group, - name: projectname, - path: "#{groupname}-#{projectname}", - visibility_level: visibility_level - ) - create(:issue, - title: "#{projectname} feature", - project: project - ) - create(:merge_request, - title: "#{projectname} feature implemented", - source_project: project, - target_project: project - ) - create(:closed_issue_event, - project: project - ) - end -end diff --git a/spec/features/explore/groups_spec.rb b/spec/features/explore/groups_spec.rb new file mode 100644 index 00000000000..e4ef47d88dd --- /dev/null +++ b/spec/features/explore/groups_spec.rb @@ -0,0 +1,87 @@ +require 'spec_helper' + +describe 'Explore Groups', :js do + let(:user) { create :user } + let(:group) { create :group } + let!(:private_project) do + create :project, :private, namespace: group do |project| + create(:issue, project: internal_project) + create(:merge_request, source_project: project, target_project: project) + end + end + + let!(:internal_project) do + create :project, :internal, namespace: group do |project| + create(:issue, project: project) + create(:merge_request, source_project: project, target_project: project) + end + end + + let!(:public_project) do + create(:project, :public, namespace: group) do |project| + create(:issue, project: project) + create(:merge_request, source_project: project, target_project: project) + end + end + + shared_examples 'renders public and internal projects' do + it do + visit_page + expect(page).to have_content(public_project.name) + expect(page).to have_content(internal_project.name) + expect(page).not_to have_content(private_project.name) + end + end + + shared_examples 'renders only public project' do + it do + visit_page + expect(page).to have_content(public_project.name) + expect(page).not_to have_content(internal_project.name) + expect(page).not_to have_content(private_project.name) + end + end + + shared_examples 'renders group in public groups area' do + it do + visit explore_groups_path + expect(page).to have_content(group.name) + end + end + + context 'when signed in' do + before do + sign_in(user) + end + + it_behaves_like 'renders public and internal projects' do + subject(:visit_page) { visit group_path(group) } + end + + it_behaves_like 'renders public and internal projects' do + subject(:visit_page) { visit issues_group_path(group) } + end + + it_behaves_like 'renders public and internal projects' do + subject(:visit_page) { visit merge_requests_group_path(group) } + end + + it_behaves_like 'renders group in public groups area' + end + + context 'when signed out' do + it_behaves_like 'renders only public project' do + subject(:visit_page) { visit group_path(group) } + end + + it_behaves_like 'renders only public project' do + subject(:visit_page) { visit issues_group_path(group) } + end + + it_behaves_like 'renders only public project' do + subject(:visit_page) { visit merge_requests_group_path(group) } + end + + it_behaves_like 'renders group in public groups area' + end +end -- cgit v1.2.1 From a83c41f6c6e0035c40916b3cbdda7fdd4f7e925f Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Sun, 24 Dec 2017 09:35:30 -0800 Subject: Fix Error 500s with anonymous clones for a project that has moved Closes #41457 --- .../unreleased/sh-handle-anonymous-clones-project-moved.yml | 5 +++++ lib/gitlab/checks/project_moved.rb | 13 ++++++++++--- spec/lib/gitlab/checks/project_moved_spec.rb | 7 +++++++ 3 files changed, 22 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/sh-handle-anonymous-clones-project-moved.yml diff --git a/changelogs/unreleased/sh-handle-anonymous-clones-project-moved.yml b/changelogs/unreleased/sh-handle-anonymous-clones-project-moved.yml new file mode 100644 index 00000000000..a0860871152 --- /dev/null +++ b/changelogs/unreleased/sh-handle-anonymous-clones-project-moved.yml @@ -0,0 +1,5 @@ +--- +title: Fix Error 500s with anonymous clones for a project that has moved +merge_request: +author: +type: fixed diff --git a/lib/gitlab/checks/project_moved.rb b/lib/gitlab/checks/project_moved.rb index 3a1c0a3455e..c1da2471b54 100644 --- a/lib/gitlab/checks/project_moved.rb +++ b/lib/gitlab/checks/project_moved.rb @@ -2,6 +2,7 @@ module Gitlab module Checks class ProjectMoved REDIRECT_NAMESPACE = "redirect_namespace".freeze + ANONYMOUS_ID_KEY = 'anonymous'.freeze def initialize(project, user, redirected_path, protocol) @project = project @@ -22,7 +23,7 @@ module Gitlab def add_redirect_message Gitlab::Redis::SharedState.with do |redis| - key = self.class.redirect_message_key(user.id, project.id) + key = self.class.redirect_message_key(user_identifier, project.id) redis.setex(key, 5.minutes, redirect_message) end end @@ -45,8 +46,14 @@ module Gitlab attr_reader :project, :redirected_path, :protocol, :user - def self.redirect_message_key(user_id, project_id) - "#{REDIRECT_NAMESPACE}:#{user_id}:#{project_id}" + def self.redirect_message_key(user_identifier, project_id) + "#{REDIRECT_NAMESPACE}:#{user_identifier}:#{project_id}" + end + + def user_identifier + return ANONYMOUS_ID_KEY unless user.present? + + user.id end def remote_url_message(rejected) diff --git a/spec/lib/gitlab/checks/project_moved_spec.rb b/spec/lib/gitlab/checks/project_moved_spec.rb index fa1575e2177..3d72e78332d 100644 --- a/spec/lib/gitlab/checks/project_moved_spec.rb +++ b/spec/lib/gitlab/checks/project_moved_spec.rb @@ -35,6 +35,13 @@ describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do project_moved = described_class.new(project, user, 'foo/bar', 'http') expect(project_moved.add_redirect_message).to eq("OK") end + + it 'should handle anonymous clones' do + project_moved = described_class.new(project, nil, 'foo/bar', 'http') + + expect(project_moved.add_redirect_message).to eq("OK") + expect(Gitlab::Redis::SharedState.with { |redis| redis.get("redirect_namespace:anonymous:#{project.id}") }).not_to be_nil + end end describe '#redirect_message' do -- cgit v1.2.1 From b6c711fd38f65d78bbd02ad9ad05f22bcb5033c5 Mon Sep 17 00:00:00 2001 From: Stan Hu Date: Mon, 25 Dec 2017 05:33:32 -0800 Subject: Disable redirect messages for anonymous clones --- lib/gitlab/checks/project_moved.rb | 17 +++++++---------- spec/lib/gitlab/checks/project_moved_spec.rb | 3 +-- 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/lib/gitlab/checks/project_moved.rb b/lib/gitlab/checks/project_moved.rb index c1da2471b54..dfb2f4d4054 100644 --- a/lib/gitlab/checks/project_moved.rb +++ b/lib/gitlab/checks/project_moved.rb @@ -2,7 +2,6 @@ module Gitlab module Checks class ProjectMoved REDIRECT_NAMESPACE = "redirect_namespace".freeze - ANONYMOUS_ID_KEY = 'anonymous'.freeze def initialize(project, user, redirected_path, protocol) @project = project @@ -22,8 +21,12 @@ module Gitlab end def add_redirect_message + # Don't bother with sending a redirect message for anonymous clones + # because they never see it via the `/internal/post_receive` endpoint + return unless user.present? && project.present? + Gitlab::Redis::SharedState.with do |redis| - key = self.class.redirect_message_key(user_identifier, project.id) + key = self.class.redirect_message_key(user.id, project.id) redis.setex(key, 5.minutes, redirect_message) end end @@ -46,14 +49,8 @@ module Gitlab attr_reader :project, :redirected_path, :protocol, :user - def self.redirect_message_key(user_identifier, project_id) - "#{REDIRECT_NAMESPACE}:#{user_identifier}:#{project_id}" - end - - def user_identifier - return ANONYMOUS_ID_KEY unless user.present? - - user.id + def self.redirect_message_key(user_id, project_id) + "#{REDIRECT_NAMESPACE}:#{user_id}:#{project_id}" end def remote_url_message(rejected) diff --git a/spec/lib/gitlab/checks/project_moved_spec.rb b/spec/lib/gitlab/checks/project_moved_spec.rb index 3d72e78332d..f90c2d6aded 100644 --- a/spec/lib/gitlab/checks/project_moved_spec.rb +++ b/spec/lib/gitlab/checks/project_moved_spec.rb @@ -39,8 +39,7 @@ describe Gitlab::Checks::ProjectMoved, :clean_gitlab_redis_shared_state do it 'should handle anonymous clones' do project_moved = described_class.new(project, nil, 'foo/bar', 'http') - expect(project_moved.add_redirect_message).to eq("OK") - expect(Gitlab::Redis::SharedState.with { |redis| redis.get("redirect_namespace:anonymous:#{project.id}") }).not_to be_nil + expect(project_moved.add_redirect_message).to eq(nil) end end -- cgit v1.2.1 From 8ae129954ffb5850d1804b5bd6a907696f6d77ee Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Fri, 22 Dec 2017 12:28:44 +0530 Subject: Reduce font size for 24px identicon --- app/assets/stylesheets/framework/avatar.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/stylesheets/framework/avatar.scss b/app/assets/stylesheets/framework/avatar.scss index 26db2386879..077d0424093 100644 --- a/app/assets/stylesheets/framework/avatar.scss +++ b/app/assets/stylesheets/framework/avatar.scss @@ -71,7 +71,7 @@ vertical-align: top; &.s16 { font-size: 12px; line-height: 1.33; } - &.s24 { font-size: 14px; line-height: 1.8; } + &.s24 { font-size: 13px; line-height: 1.8; } &.s26 { font-size: 20px; line-height: 1.33; } &.s32 { font-size: 20px; line-height: 30px; } &.s40 { font-size: 16px; line-height: 38px; } -- cgit v1.2.1 From 86e0d931abc34600ea65f86e92d8d2423664150a Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Fri, 22 Dec 2017 12:29:09 +0530 Subject: Add `updatedAt` prop for Projects --- app/assets/javascripts/groups/store/groups_store.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/assets/javascripts/groups/store/groups_store.js b/app/assets/javascripts/groups/store/groups_store.js index a1689f4c5cc..ffc86175548 100644 --- a/app/assets/javascripts/groups/store/groups_store.js +++ b/app/assets/javascripts/groups/store/groups_store.js @@ -91,6 +91,7 @@ export default class GroupsStore { subgroupCount: rawGroupItem.subgroup_count, memberCount: rawGroupItem.number_users_with_delimiter, starCount: rawGroupItem.star_count, + updatedAt: rawGroupItem.updated_at, }; } -- cgit v1.2.1 From 95ff461314a23a21a52f4dc240fa91873b91226c Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Fri, 22 Dec 2017 12:30:07 +0530 Subject: Use SVG sprite icons --- .../javascripts/groups/components/item_actions.vue | 18 +++++++----------- .../javascripts/groups/components/item_type_icon.vue | 13 +++++++------ 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/app/assets/javascripts/groups/components/item_actions.vue b/app/assets/javascripts/groups/components/item_actions.vue index 58ba5aff7cf..d3817cae6dc 100644 --- a/app/assets/javascripts/groups/components/item_actions.vue +++ b/app/assets/javascripts/groups/components/item_actions.vue @@ -1,14 +1,14 @@ + + diff --git a/spec/javascripts/groups/components/item_stats_value_spec.js b/spec/javascripts/groups/components/item_stats_value_spec.js new file mode 100644 index 00000000000..e990870aaa6 --- /dev/null +++ b/spec/javascripts/groups/components/item_stats_value_spec.js @@ -0,0 +1,81 @@ +import Vue from 'vue'; + +import itemStatsValueComponent from '~/groups/components/item_stats_value.vue'; + +import mountComponent from '../../helpers/vue_mount_component_helper'; + +const createComponent = ({ title, cssClass, iconName, tooltipPlacement, value }) => { + const Component = Vue.extend(itemStatsValueComponent); + + return mountComponent(Component, { + title, + cssClass, + iconName, + tooltipPlacement, + value, + }); +}; + +describe('ItemStatsValueComponent', () => { + describe('computed', () => { + let vm; + const itemConfig = { + title: 'Subgroups', + cssClass: 'number-subgroups', + iconName: 'folder', + tooltipPlacement: 'left', + }; + + describe('isValuePresent', () => { + it('returns true if non-empty `value` is present', () => { + vm = createComponent(Object.assign({}, itemConfig, { value: 10 })); + expect(vm.isValuePresent).toBeTruthy(); + }); + + it('returns false if empty `value` is present', () => { + vm = createComponent(itemConfig); + expect(vm.isValuePresent).toBeFalsy(); + }); + + afterEach(() => { + vm.$destroy(); + }); + }); + }); + + describe('template', () => { + let vm; + beforeEach(() => { + vm = createComponent({ + title: 'Subgroups', + cssClass: 'number-subgroups', + iconName: 'folder', + tooltipPlacement: 'left', + value: 10, + }); + }); + + it('renders component element correctly', () => { + expect(vm.$el.classList.contains('number-subgroups')).toBeTruthy(); + expect(vm.$el.querySelectorAll('svg').length > 0).toBeTruthy(); + expect(vm.$el.querySelectorAll('.stat-value').length > 0).toBeTruthy(); + }); + + it('renders element tooltip correctly', () => { + expect(vm.$el.dataset.originalTitle).toBe('Subgroups'); + expect(vm.$el.dataset.placement).toBe('left'); + }); + + it('renders element icon correctly', () => { + expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('folder'); + }); + + it('renders value count correctly', () => { + expect(vm.$el.querySelector('.stat-value').innerText.trim()).toContain('10'); + }); + + afterEach(() => { + vm.$destroy(); + }); + }); +}); -- cgit v1.2.1 From f39f5d2f230d0b99852a6f5e40c2150a51022856 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Fri, 22 Dec 2017 13:05:10 +0530 Subject: Use ItemStatsValue Component, add `updatedAt` info for projects --- .../javascripts/groups/components/item_stats.vue | 88 +++++++++------------- 1 file changed, 37 insertions(+), 51 deletions(-) diff --git a/app/assets/javascripts/groups/components/item_stats.vue b/app/assets/javascripts/groups/components/item_stats.vue index 9f8ac138fc3..fbe129913fe 100644 --- a/app/assets/javascripts/groups/components/item_stats.vue +++ b/app/assets/javascripts/groups/components/item_stats.vue @@ -1,10 +1,14 @@ + diff --git a/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js new file mode 100644 index 00000000000..9ffbaae3ea5 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/file_icon/file_icon_map.js @@ -0,0 +1,589 @@ +const fileExtensionIcons = { + html: 'html', + htm: 'html', + html_vm: 'html', + asp: 'html', + jade: 'pug', + pug: 'pug', + md: 'markdown', + 'md.rendered': 'markdown', + markdown: 'markdown', + 'markdown.rendered': 'markdown', + rst: 'markdown', + blink: 'blink', + css: 'css', + scss: 'sass', + sass: 'sass', + less: 'less', + json: 'json', + yaml: 'yaml', + 'YAML-tmLanguage': 'yaml', + yml: 'yaml', + xml: 'xml', + plist: 'xml', + xsd: 'xml', + dtd: 'xml', + xsl: 'xml', + xslt: 'xml', + resx: 'xml', + iml: 'xml', + xquery: 'xml', + tmLanguage: 'xml', + manifest: 'xml', + project: 'xml', + png: 'image', + jpeg: 'image', + jpg: 'image', + gif: 'image', + svg: 'image', + ico: 'image', + tif: 'image', + tiff: 'image', + psd: 'image', + psb: 'image', + ami: 'image', + apx: 'image', + bmp: 'image', + bpg: 'image', + brk: 'image', + cur: 'image', + dds: 'image', + dng: 'image', + exr: 'image', + fpx: 'image', + gbr: 'image', + img: 'image', + jbig2: 'image', + jb2: 'image', + jng: 'image', + jxr: 'image', + pbm: 'image', + pgf: 'image', + pic: 'image', + raw: 'image', + webp: 'image', + js: 'javascript', + ejs: 'javascript', + esx: 'javascript', + jsx: 'react', + tsx: 'react', + ini: 'settings', + dlc: 'settings', + dll: 'settings', + config: 'settings', + conf: 'settings', + properties: 'settings', + prop: 'settings', + settings: 'settings', + option: 'settings', + props: 'settings', + toml: 'settings', + prefs: 'settings', + 'sln.dotsettings': 'settings', + 'sln.dotsettings.user': 'settings', + ts: 'typescript', + 'd.ts': 'typescript-def', + marko: 'markojs', + pdf: 'pdf', + xlsx: 'table', + xls: 'table', + csv: 'table', + tsv: 'table', + vscodeignore: 'vscode', + vsixmanifest: 'vscode', + vsix: 'vscode', + 'code-workplace': 'vscode', + suo: 'visualstudio', + sln: 'visualstudio', + csproj: 'visualstudio', + vb: 'visualstudio', + pdb: 'database', + sql: 'database', + pks: 'database', + pkb: 'database', + accdb: 'database', + mdb: 'database', + sqlite: 'database', + cs: 'csharp', + zip: 'zip', + tar: 'zip', + gz: 'zip', + xz: 'zip', + bzip2: 'zip', + gzip: 'zip', + '7z': 'zip', + rar: 'zip', + tgz: 'zip', + exe: 'exe', + msi: 'exe', + java: 'java', + jar: 'java', + jsp: 'java', + c: 'c', + m: 'c', + h: 'h', + cc: 'cpp', + cpp: 'cpp', + mm: 'cpp', + cxx: 'cpp', + hpp: 'hpp', + go: 'go', + py: 'python', + url: 'url', + sh: 'console', + ksh: 'console', + csh: 'console', + tcsh: 'console', + zsh: 'console', + bash: 'console', + bat: 'console', + cmd: 'console', + ps1: 'powershell', + psm1: 'powershell', + psd1: 'powershell', + ps1xml: 'powershell', + psc1: 'powershell', + pssc: 'powershell', + gradle: 'gradle', + doc: 'word', + docx: 'word', + rtf: 'word', + cer: 'certificate', + cert: 'certificate', + crt: 'certificate', + pub: 'key', + key: 'key', + pem: 'key', + asc: 'key', + gpg: 'key', + woff: 'font', + woff2: 'font', + ttf: 'font', + eot: 'font', + suit: 'font', + otf: 'font', + bmap: 'font', + fnt: 'font', + odttf: 'font', + ttc: 'font', + font: 'font', + fonts: 'font', + sui: 'font', + ntf: 'font', + mrf: 'font', + lib: 'lib', + bib: 'lib', + rb: 'ruby', + erb: 'ruby', + fs: 'fsharp', + fsx: 'fsharp', + fsi: 'fsharp', + fsproj: 'fsharp', + swift: 'swift', + ino: 'arduino', + dockerignore: 'docker', + dockerfile: 'docker', + tex: 'tex', + cls: 'tex', + sty: 'tex', + pptx: 'powerpoint', + ppt: 'powerpoint', + pptm: 'powerpoint', + potx: 'powerpoint', + pot: 'powerpoint', + potm: 'powerpoint', + ppsx: 'powerpoint', + ppsm: 'powerpoint', + pps: 'powerpoint', + ppam: 'powerpoint', + ppa: 'powerpoint', + webm: 'movie', + mkv: 'movie', + flv: 'movie', + vob: 'movie', + ogv: 'movie', + ogg: 'movie', + gifv: 'movie', + avi: 'movie', + mov: 'movie', + qt: 'movie', + wmv: 'movie', + yuv: 'movie', + rm: 'movie', + rmvb: 'movie', + mp4: 'movie', + m4v: 'movie', + mpg: 'movie', + mp2: 'movie', + mpeg: 'movie', + mpe: 'movie', + mpv: 'movie', + m2v: 'movie', + vdi: 'virtual', + vbox: 'virtual', + 'vbox-prev': 'virtual', + ics: 'email', + mp3: 'music', + flac: 'music', + m4a: 'music', + wma: 'music', + aiff: 'music', + coffee: 'coffee', + txt: 'document', + graphql: 'graphql', + rs: 'rust', + raml: 'raml', + xaml: 'xaml', + hs: 'haskell', + kt: 'kotlin', + kts: 'kotlin', + patch: 'git', + lua: 'lua', + clj: 'clojure', + cljs: 'clojure', + groovy: 'groovy', + r: 'r', + rmd: 'r', + dart: 'dart', + as: 'actionscript', + mxml: 'mxml', + ahk: 'autohotkey', + swf: 'flash', + swc: 'swc', + cmake: 'cmake', + asm: 'assembly', + a51: 'assembly', + inc: 'assembly', + nasm: 'assembly', + s: 'assembly', + ms: 'assembly', + agc: 'assembly', + ags: 'assembly', + aea: 'assembly', + argus: 'assembly', + mitigus: 'assembly', + binsource: 'assembly', + vue: 'vue', + ml: 'ocaml', + mli: 'ocaml', + cmx: 'ocaml', + 'js.map': 'javascript-map', + 'css.map': 'css-map', + lock: 'lock', + hbs: 'handlebars', + mustache: 'handlebars', + pl: 'perl', + pm: 'perl', + hx: 'haxe', + 'spec.ts': 'test-ts', + 'test.ts': 'test-ts', + 'ts.snap': 'test-ts', + 'spec.tsx': 'test-jsx', + 'test.tsx': 'test-jsx', + 'tsx.snap': 'test-jsx', + 'spec.jsx': 'test-jsx', + 'test.jsx': 'test-jsx', + 'jsx.snap': 'test-jsx', + 'spec.js': 'test-js', + 'test.js': 'test-js', + 'js.snap': 'test-js', + 'routing.ts': 'angular-routing', + 'routing.js': 'angular-routing', + 'module.ts': 'angular', + 'module.js': 'angular', + 'ng-template': 'angular', + 'component.ts': 'angular-component', + 'component.js': 'angular-component', + 'guard.ts': 'angular-guard', + 'guard.js': 'angular-guard', + 'service.ts': 'angular-service', + 'service.js': 'angular-service', + 'pipe.ts': 'angular-pipe', + 'pipe.js': 'angular-pipe', + 'filter.js': 'angular-pipe', + 'directive.ts': 'angular-directive', + 'directive.js': 'angular-directive', + 'resolver.ts': 'angular-resolver', + 'resolver.js': 'angular-resolver', + pp: 'puppet', + ex: 'elixir', + exs: 'elixir', + ls: 'livescript', + erl: 'erlang', + twig: 'twig', + jl: 'julia', + elm: 'elm', + pure: 'purescript', + tpl: 'smarty', + styl: 'stylus', + re: 'reason', + rei: 'reason', + cmj: 'bucklescript', + merlin: 'merlin', + v: 'verilog', + vhd: 'verilog', + sv: 'verilog', + svh: 'verilog', + nb: 'mathematica', + wl: 'wolframlanguage', + wls: 'wolframlanguage', + njk: 'nunjucks', + nunjucks: 'nunjucks', + robot: 'robot', + sol: 'solidity', + au3: 'autoit', + haml: 'haml', + yang: 'yang', + tf: 'terraform', + 'tf.json': 'terraform', + tfvars: 'terraform', + tfstate: 'terraform', + 'blade.php': 'laravel', + 'inky.php': 'laravel', + applescript: 'applescript', + cake: 'cake', + feature: 'cucumber', + nim: 'nim', + nimble: 'nim', + apib: 'apiblueprint', + apiblueprint: 'apiblueprint', + tag: 'riot', + vfl: 'vfl', + kl: 'kl', + pcss: 'postcss', + sss: 'postcss', + todo: 'todo', + cfml: 'coldfusion', + cfc: 'coldfusion', + lucee: 'coldfusion', + cabal: 'cabal', + nix: 'nix', + slim: 'slim', + http: 'http', + rest: 'http', + rql: 'restql', + restql: 'restql', + kv: 'kivy', + graphcool: 'graphcool', + sbt: 'sbt', + 'reducer.ts': 'ngrx-reducer', + 'rootReducer.ts': 'ngrx-reducer', + 'state.ts': 'ngrx-state', + 'actions.ts': 'ngrx-actions', + 'effects.ts': 'ngrx-effects', + cr: 'crystal', + 'drone.yml': 'drone', + cu: 'cuda', + cuh: 'cuda', + log: 'log', +}; + +const fileNameIcons = { + '.jscsrc': 'json', + '.jshintrc': 'json', + 'tsconfig.json': 'json', + 'tslint.json': 'json', + 'composer.lock': 'json', + '.jsbeautifyrc': 'json', + '.esformatter': 'json', + 'cdp.pid': 'json', + '.htaccess': 'xml', + '.jshintignore': 'settings', + '.buildignore': 'settings', + makefile: 'settings', + '.mrconfig': 'settings', + '.yardopts': 'settings', + 'gradle.properties': 'gradle', + gradlew: 'gradle', + 'gradle-wrapper.properties': 'gradle', + license: 'certificate', + 'license.md': 'certificate', + 'license.md.rendered': 'certificate', + 'license.txt': 'certificate', + licence: 'certificate', + 'licence.md': 'certificate', + 'licence.md.rendered': 'certificate', + 'licence.txt': 'certificate', + dockerfile: 'docker', + 'docker-compose.yml': 'docker', + '.mailmap': 'email', + '.gitignore': 'git', + '.gitconfig': 'git', + '.gitattributes': 'git', + '.gitmodules': 'git', + '.gitkeep': 'git', + 'git-history': 'git', + '.Rhistory': 'r', + 'cmakelists.txt': 'cmake', + 'cmakecache.txt': 'cmake', + 'angular-cli.json': 'angular', + '.angular-cli.json': 'angular', + '.vfl': 'vfl', + '.kl': 'kl', + 'postcss.config.js': 'postcss', + '.postcssrc.js': 'postcss', + 'project.graphcool': 'graphcool', + 'webpack.js': 'webpack', + 'webpack.ts': 'webpack', + 'webpack.base.js': 'webpack', + 'webpack.base.ts': 'webpack', + 'webpack.config.js': 'webpack', + 'webpack.config.ts': 'webpack', + 'webpack.common.js': 'webpack', + 'webpack.common.ts': 'webpack', + 'webpack.config.common.js': 'webpack', + 'webpack.config.common.ts': 'webpack', + 'webpack.config.common.babel.js': 'webpack', + 'webpack.config.common.babel.ts': 'webpack', + 'webpack.dev.js': 'webpack', + 'webpack.dev.ts': 'webpack', + 'webpack.config.dev.js': 'webpack', + 'webpack.config.dev.ts': 'webpack', + 'webpack.config.dev.babel.js': 'webpack', + 'webpack.config.dev.babel.ts': 'webpack', + 'webpack.prod.js': 'webpack', + 'webpack.prod.ts': 'webpack', + 'webpack.server.js': 'webpack', + 'webpack.server.ts': 'webpack', + 'webpack.client.js': 'webpack', + 'webpack.client.ts': 'webpack', + 'webpack.config.server.js': 'webpack', + 'webpack.config.server.ts': 'webpack', + 'webpack.config.client.js': 'webpack', + 'webpack.config.client.ts': 'webpack', + 'webpack.config.production.babel.js': 'webpack', + 'webpack.config.production.babel.ts': 'webpack', + 'webpack.config.prod.babel.js': 'webpack', + 'webpack.config.prod.babel.ts': 'webpack', + 'webpack.config.prod.js': 'webpack', + 'webpack.config.prod.ts': 'webpack', + 'webpack.config.production.js': 'webpack', + 'webpack.config.production.ts': 'webpack', + 'webpack.config.staging.js': 'webpack', + 'webpack.config.staging.ts': 'webpack', + 'webpack.config.babel.js': 'webpack', + 'webpack.config.babel.ts': 'webpack', + 'webpack.config.base.babel.js': 'webpack', + 'webpack.config.base.babel.ts': 'webpack', + 'webpack.config.base.js': 'webpack', + 'webpack.config.base.ts': 'webpack', + 'webpack.config.staging.babel.js': 'webpack', + 'webpack.config.staging.babel.ts': 'webpack', + 'webpack.config.coffee': 'webpack', + 'webpack.config.test.js': 'webpack', + 'webpack.config.test.ts': 'webpack', + 'webpack.config.vendor.js': 'webpack', + 'webpack.config.vendor.ts': 'webpack', + 'webpack.config.vendor.production.js': 'webpack', + 'webpack.config.vendor.production.ts': 'webpack', + 'webpack.test.js': 'webpack', + 'webpack.test.ts': 'webpack', + 'webpack.dist.js': 'webpack', + 'webpack.dist.ts': 'webpack', + 'webpackfile.js': 'webpack', + 'webpackfile.ts': 'webpack', + 'ionic.config.json': 'ionic', + '.io-config.json': 'ionic', + 'gulpfile.js': 'gulp', + 'gulpfile.ts': 'gulp', + 'gulpfile.babel.js': 'gulp', + 'package.json': 'nodejs', + 'package-lock.json': 'nodejs', + '.nvmrc': 'nodejs', + '.npmignore': 'npm', + '.npmrc': 'npm', + '.yarnrc': 'yarn', + 'yarn.lock': 'yarn', + '.yarnclean': 'yarn', + '.yarn-integrity': 'yarn', + 'yarn-error.log': 'yarn', + 'androidmanifest.xml': 'android', + '.env': 'tune', + '.env.example': 'tune', + '.babelrc': 'babel', + 'contributing.md': 'contributing', + 'contributing.md.rendered': 'contributing', + 'readme.md': 'readme', + 'readme.md.rendered': 'readme', + changelog: 'changelog', + 'changelog.md': 'changelog', + 'changelog.md.rendered': 'changelog', + CREDITS: 'credits', + 'credits.txt': 'credits', + 'credits.md': 'credits', + 'credits.md.rendered': 'credits', + '.flowconfig': 'flow', + 'favicon.ico': 'favicon', + 'karma.conf.js': 'karma', + 'karma.conf.ts': 'karma', + 'karma.conf.coffee': 'karma', + 'karma.config.js': 'karma', + 'karma.config.ts': 'karma', + 'karma-main.js': 'karma', + 'karma-main.ts': 'karma', + '.bithoundrc': 'bithound', + 'appveyor.yml': 'appveyor', + '.travis.yml': 'travis', + 'protractor.conf.js': 'protractor', + 'protractor.conf.ts': 'protractor', + 'protractor.conf.coffee': 'protractor', + 'protractor.config.js': 'protractor', + 'protractor.config.ts': 'protractor', + 'fuse.js': 'fusebox', + procfile: 'heroku', + '.editorconfig': 'editorconfig', + '.gitlab-ci.yml': 'gitlab', + '.bowerrc': 'bower', + 'bower.json': 'bower', + '.eslintrc.js': 'eslint', + '.eslintrc.yaml': 'eslint', + '.eslintrc.yml': 'eslint', + '.eslintrc.json': 'eslint', + '.eslintrc': 'eslint', + '.eslintignore': 'eslint', + 'code_of_conduct.md': 'conduct', + 'code_of_conduct.md.rendered': 'conduct', + '.watchmanconfig': 'watchman', + 'aurelia.json': 'aurelia', + 'mocha.opts': 'mocha', + jenkinsfile: 'jenkins', + 'firebase.json': 'firebase', + '.firebaserc': 'firebase', + 'rollup.config.js': 'rollup', + 'rollup.config.ts': 'rollup', + 'rollup-config.js': 'rollup', + 'rollup-config.ts': 'rollup', + 'rollup.config.prod.js': 'rollup', + 'rollup.config.prod.ts': 'rollup', + 'rollup.config.dev.js': 'rollup', + 'rollup.config.dev.ts': 'rollup', + 'rollup.config.prod.vendor.js': 'rollup', + 'rollup.config.prod.vendor.ts': 'rollup', + '.hhconfig': 'hack', + '.stylelintrc': 'stylelint', + 'stylelint.config.js': 'stylelint', + '.stylelintrc.json': 'stylelint', + '.stylelintrc.yaml': 'stylelint', + '.stylelintrc.yml': 'stylelint', + '.stylelintrc.js': 'stylelint', + '.stylelintignore': 'stylelint', + '.codeclimate.yml': 'code-climate', + '.prettierrc': 'prettier', + 'prettier.config.js': 'prettier', + '.prettierrc.js': 'prettier', + '.prettierrc.json': 'prettier', + '.prettierrc.yaml': 'prettier', + '.prettierrc.yml': 'prettier', + 'nodemon.json': 'nodemon', + '.sonarrc': 'sonar', + browserslist: 'browserlist', + '.browserslistrc': 'browserlist', + '.snyk': 'snyk', + '.drone.yml': 'drone', +}; + +export default function getIconForFile(name) { + return fileNameIcons[name] || + fileExtensionIcons[name ? name.split('.').pop() : ''] || + ''; +} diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index da3c2d7fa5d..51cc1729d9a 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -96,8 +96,14 @@ padding: 6px 12px; } -.multi-file-table-name { +table.table tr td.multi-file-table-name { width: 350px; + padding: 6px 12px; + + svg { + vertical-align: middle; + margin-right: 2px; + } } .multi-file-table-col-commit-message { @@ -132,6 +138,10 @@ border-bottom: 1px solid $white-dark; cursor: pointer; + svg { + vertical-align: middle; + } + &.active { background-color: $white-light; border-bottom-color: $white-light; diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index c6a83f21ceb..c5522ff7a69 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -30,6 +30,13 @@ module IconsHelper ActionController::Base.helpers.image_path('icons.svg', host: sprite_base_url) end + def sprite_file_icons_path + # SVG Sprites currently don't work across domains, so in the case of a CDN + # we have to set the current path deliberately to prevent addition of asset_host + sprite_base_url = Gitlab.config.gitlab.url if ActionController::Base.asset_host + ActionController::Base.helpers.image_path('file_icons.svg', host: sprite_base_url) + end + def sprite_icon(icon_name, size: nil, css_class: nil) css_classes = size ? "s#{size}" : "" css_classes << " #{css_class}" unless css_class.blank? diff --git a/lib/gitlab/gon_helper.rb b/lib/gitlab/gon_helper.rb index dfcdfc307b6..9148d7571f2 100644 --- a/lib/gitlab/gon_helper.rb +++ b/lib/gitlab/gon_helper.rb @@ -21,6 +21,7 @@ module Gitlab gon.revision = Gitlab::REVISION gon.gitlab_logo = ActionController::Base.helpers.asset_path('gitlab_logo.png') gon.sprite_icons = IconsHelper.sprite_icon_path + gon.sprite_file_icons = IconsHelper.sprite_file_icons_path if current_user gon.current_user_id = current_user.id diff --git a/spec/javascripts/repo/components/repo_file_spec.js b/spec/javascripts/repo/components/repo_file_spec.js index e8b370f97b4..0810da87e80 100644 --- a/spec/javascripts/repo/components/repo_file_spec.js +++ b/spec/javascripts/repo/components/repo_file_spec.js @@ -32,13 +32,9 @@ describe('RepoFile', () => { vm.$mount(); const name = vm.$el.querySelector('.repo-file-name'); - const fileIcon = vm.$el.querySelector('.file-icon'); - expect(vm.$el.querySelector(`.${vm.file.icon}`).style.marginLeft).toEqual('0px'); expect(name.href).toMatch(''); expect(name.textContent.trim()).toEqual(vm.file.name); - expect(fileIcon.classList.contains(vm.file.icon)).toBeTruthy(); - expect(fileIcon.style.marginLeft).toEqual(`${vm.file.level * 10}px`); }); it('does render if hasFiles is true and is loading tree', () => { @@ -49,17 +45,6 @@ describe('RepoFile', () => { expect(vm.$el.querySelector('.fa-spin.fa-spinner')).toBeFalsy(); }); - it('renders a spinner if the file is loading', () => { - const f = file(); - f.loading = true; - vm = createComponent({ - file: f, - }); - - expect(vm.$el.querySelector('.fa-spin.fa-spinner')).not.toBeNull(); - expect(vm.$el.querySelector('.fa-spin.fa-spinner').style.marginLeft).toEqual(`${vm.file.level * 16}px`); - }); - it('does not render commit message and datetime if mini', (done) => { vm = createComponent({ file: file(), diff --git a/spec/javascripts/vue_shared/components/file_icon_spec.js b/spec/javascripts/vue_shared/components/file_icon_spec.js new file mode 100644 index 00000000000..d99b17bdc79 --- /dev/null +++ b/spec/javascripts/vue_shared/components/file_icon_spec.js @@ -0,0 +1,83 @@ +import Vue from 'vue'; +import fileIcon from '~/vue_shared/components/file_icon.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('File Icon component', () => { + let vm; + let FileIcon; + + beforeEach(() => { + FileIcon = Vue.extend(fileIcon); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render a span element with an svg', () => { + vm = mountComponent(FileIcon, { + fileName: 'test.js', + }); + + expect(vm.$el.tagName).toEqual('SPAN'); + expect(vm.$el.querySelector('span > svg')).toBeDefined(); + }); + + it('should render a javascript icon based on file ending', () => { + vm = mountComponent(FileIcon, { + fileName: 'test.js', + }); + + expect(vm.$el.firstChild.firstChild.getAttribute('xlink:href')).toBe(`${gon.sprite_file_icons}#javascript`); + }); + + it('should render a image icon based on file ending', () => { + vm = mountComponent(FileIcon, { + fileName: 'test.png', + }); + + expect(vm.$el.firstChild.firstChild.getAttribute('xlink:href')).toBe(`${gon.sprite_file_icons}#image`); + }); + + it('should render a webpack icon based on file namer', () => { + vm = mountComponent(FileIcon, { + fileName: 'webpack.js', + }); + + expect(vm.$el.firstChild.firstChild.getAttribute('xlink:href')).toBe(`${gon.sprite_file_icons}#webpack`); + }); + + it('should render a standard folder icon', () => { + vm = mountComponent(FileIcon, { + fileName: 'js', + folder: true, + }); + + expect(vm.$el.querySelector('span > svg > use').getAttribute('xlink:href')).toBe(`${gon.sprite_file_icons}#folder`); + }); + + it('should render a loading icon', () => { + vm = mountComponent(FileIcon, { + fileName: 'test.js', + loading: true, + }); + + expect( + vm.$el.querySelector('i').getAttribute('class'), + ).toEqual('fa fa-spin fa-spinner fa-1x'); + }); + + it('should add a special class and a size class', () => { + vm = mountComponent(FileIcon, { + fileName: 'test.js', + cssClasses: 'extraclasses', + size: 120, + }); + + const classList = vm.$el.firstChild.classList; + const containsSizeClass = classList.contains('s120'); + const containsCustomClass = classList.contains('extraclasses'); + expect(containsSizeClass).toBe(true); + expect(containsCustomClass).toBe(true); + }); +}); -- cgit v1.2.1 From 8cf0ea4469290815daa1d64c4f3e16cbba8c00c1 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Fri, 22 Dec 2017 16:58:05 +0100 Subject: Handle Gitaly aborted merge due to branch update --- lib/gitlab/gitaly_client/operation_service.rb | 1 + spec/lib/gitlab/git/repository_spec.rb | 14 ++++++++++++++ 2 files changed, 15 insertions(+) diff --git a/lib/gitlab/gitaly_client/operation_service.rb b/lib/gitlab/gitaly_client/operation_service.rb index c7732764880..ae1753ff0ae 100644 --- a/lib/gitlab/gitaly_client/operation_service.rb +++ b/lib/gitlab/gitaly_client/operation_service.rb @@ -101,6 +101,7 @@ module Gitlab request_enum.push(Gitaly::UserMergeBranchRequest.new(apply: true)) branch_update = response_enum.next.branch_update + return if branch_update.nil? raise Gitlab::Git::CommitError.new('failed to apply merge to branch') unless branch_update.commit_id.present? Gitlab::Git::OperationService::BranchUpdate.from_gitaly(branch_update) diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index 0e4292026df..c8ed0494f68 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -1719,6 +1719,20 @@ describe Gitlab::Git::Repository, seed_helper: true do expect(result.repo_created).to eq(false) expect(result.branch_created).to eq(false) end + + it 'returns nil if there was a concurrent branch update' do + concurrent_update_id = '33f3729a45c02fc67d00adb1b8bca394b0e761d9' + result = repository.merge(user, source_sha, target_branch, 'Test merge') do + # This ref update should make the merge fail + repository.write_ref(Gitlab::Git::BRANCH_REF_PREFIX + target_branch, concurrent_update_id) + end + + # This 'nil' signals that the merge was not applied + expect(result).to be_nil + + # Our concurrent ref update should not have been reversed + expect(repository.find_branch(target_branch).target).to eq(concurrent_update_id) + end end context 'with gitaly' do -- cgit v1.2.1 From 78d22fb20db14c90861318b9f316466fbf002114 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Thu, 21 Dec 2017 16:44:07 +0100 Subject: Use a background migration for issues.closed_at In a previous attempt (rolled back in https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/16021) we tried to migrate `issues.closed_at` from timestamp to timestamptz using a regular migration. This has a bad impact on GitLab.com and as such was rolled back. This commit re-implements the original migrations using generic background migrations, allowing us to still migrate the data in a single release but without a negative impact on availability. To ensure the database schema is up to date the background migrations are performed inline in development and test environments. We also make sure to not migrate that that doesn't need migrating in the first place or has already been migrated. --- ...hange-issues-closed-at-background-migration.yml | 5 + ...140220_schedule_issues_closed_at_type_change.rb | 45 ++++++++ db/schema.rb | 2 +- doc/development/what_requires_downtime.md | 57 ++++++++++ .../cleanup_concurrent_type_change.rb | 54 +++++++++ lib/gitlab/background_migration/copy_column.rb | 39 +++++++ lib/gitlab/database/migration_helpers.rb | 121 +++++++++++++++++++-- spec/lib/gitlab/database/migration_helpers_spec.rb | 91 +++++++++++++++- 8 files changed, 402 insertions(+), 12 deletions(-) create mode 100644 changelogs/unreleased/change-issues-closed-at-background-migration.yml create mode 100644 db/post_migrate/20171221140220_schedule_issues_closed_at_type_change.rb create mode 100644 lib/gitlab/background_migration/cleanup_concurrent_type_change.rb create mode 100644 lib/gitlab/background_migration/copy_column.rb diff --git a/changelogs/unreleased/change-issues-closed-at-background-migration.yml b/changelogs/unreleased/change-issues-closed-at-background-migration.yml new file mode 100644 index 00000000000..1c81c6a889e --- /dev/null +++ b/changelogs/unreleased/change-issues-closed-at-background-migration.yml @@ -0,0 +1,5 @@ +--- +title: Use a background migration for issues.closed_at +merge_request: +author: +type: other diff --git a/db/post_migrate/20171221140220_schedule_issues_closed_at_type_change.rb b/db/post_migrate/20171221140220_schedule_issues_closed_at_type_change.rb new file mode 100644 index 00000000000..be18c5866ae --- /dev/null +++ b/db/post_migrate/20171221140220_schedule_issues_closed_at_type_change.rb @@ -0,0 +1,45 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class ScheduleIssuesClosedAtTypeChange < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + class Issue < ActiveRecord::Base + self.table_name = 'issues' + include EachBatch + + def self.to_migrate + where('closed_at IS NOT NULL') + end + end + + def up + return unless migrate_column_type? + + change_column_type_using_background_migration( + Issue.to_migrate, + :closed_at, + :datetime_with_timezone + ) + end + + def down + return if migrate_column_type? + + change_column_type_using_background_migration( + Issue.to_migrate, + :closed_at, + :datetime + ) + end + + def migrate_column_type? + # Some environments may have already executed the previous version of this + # migration, thus we don't need to migrate those environments again. + column_for('issues', 'closed_at').type == :datetime + end +end diff --git a/db/schema.rb b/db/schema.rb index 42715d5e1e8..cd1f5d12fef 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: 20171220191323) do +ActiveRecord::Schema.define(version: 20171221140220) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" diff --git a/doc/development/what_requires_downtime.md b/doc/development/what_requires_downtime.md index 05e0a64af18..9d0c62ecc35 100644 --- a/doc/development/what_requires_downtime.md +++ b/doc/development/what_requires_downtime.md @@ -195,6 +195,63 @@ end And that's it, we're done! +## Changing Column Types For Large Tables + +While `change_column_type_concurrently` can be used for changing the type of a +column without downtime it doesn't work very well for large tables. Because all +of the work happens in sequence the migration can take a very long time to +complete, preventing a deployment from proceeding. +`change_column_type_concurrently` can also produce a lot of pressure on the +database due to it rapidly updating many rows in sequence. + +To reduce database pressure you should instead use +`change_column_type_using_background_migration` when migrating a column in a +large table (e.g. `issues`). This method works similar to +`change_column_type_concurrently` but uses background migration to spread the +work / load over a longer time period, without slowing down deployments. + +Usage of this method is fairly simple: + +```ruby +class ExampleMigration < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + disable_ddl_transaction! + + class Issue < ActiveRecord::Base + self.table_name = 'issues' + + include EachBatch + + def self.to_migrate + where('closed_at IS NOT NULL') + end + end + + def up + change_column_type_using_background_migration( + Issue.to_migrate, + :closed_at, + :datetime_with_timezone + ) + end + + def down + change_column_type_using_background_migration( + Issue.to_migrate, + :closed_at, + :datetime + ) + end +end +``` + +This would change the type of `issues.closed_at` to `timestamp with time zone`. + +Keep in mind that the relation passed to +`change_column_type_using_background_migration` _must_ include `EachBatch`, +otherwise it will raise a `TypeError`. + ## Adding Indexes Adding indexes is an expensive process that blocks INSERT and UPDATE queries for diff --git a/lib/gitlab/background_migration/cleanup_concurrent_type_change.rb b/lib/gitlab/background_migration/cleanup_concurrent_type_change.rb new file mode 100644 index 00000000000..de622f657b2 --- /dev/null +++ b/lib/gitlab/background_migration/cleanup_concurrent_type_change.rb @@ -0,0 +1,54 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # Background migration for cleaning up a concurrent column rename. + class CleanupConcurrentTypeChange + include Database::MigrationHelpers + + RESCHEDULE_DELAY = 10.minutes + + # table - The name of the table the migration is performed for. + # old_column - The name of the old (to drop) column. + # new_column - The name of the new column. + def perform(table, old_column, new_column) + return unless column_exists?(:issues, new_column) + + rows_to_migrate = define_model_for(table) + .where(new_column => nil) + .where + .not(old_column => nil) + + if rows_to_migrate.any? + BackgroundMigrationWorker.perform_in( + RESCHEDULE_DELAY, + 'CleanupConcurrentTypeChange', + [table, old_column, new_column] + ) + else + cleanup_concurrent_column_type_change(table, old_column) + end + end + + # These methods are necessary so we can re-use the migration helpers in + # this class. + def connection + ActiveRecord::Base.connection + end + + def method_missing(name, *args, &block) + connection.__send__(name, *args, &block) # rubocop: disable GitlabSecurity/PublicSend + end + + def respond_to_missing?(*args) + connection.respond_to?(*args) || super + end + + def define_model_for(table) + Class.new(ActiveRecord::Base) do + self.table_name = table + end + end + end + end +end diff --git a/lib/gitlab/background_migration/copy_column.rb b/lib/gitlab/background_migration/copy_column.rb new file mode 100644 index 00000000000..a2cb215c230 --- /dev/null +++ b/lib/gitlab/background_migration/copy_column.rb @@ -0,0 +1,39 @@ +# frozen_string_literal: true + +module Gitlab + module BackgroundMigration + # CopyColumn is a simple (reusable) background migration that can be used to + # update the value of a column based on the value of another column in the + # same table. + # + # For this background migration to work the table that is migrated _has_ to + # have an `id` column as the primary key. + class CopyColumn + # table - The name of the table that contains the columns. + # copy_from - The column containing the data to copy. + # copy_to - The column to copy the data to. + # start_id - The start ID of the range of rows to update. + # end_id - The end ID of the range of rows to update. + def perform(table, copy_from, copy_to, start_id, end_id) + return unless connection.column_exists?(table, copy_to) + + quoted_table = connection.quote_table_name(table) + quoted_copy_from = connection.quote_column_name(copy_from) + quoted_copy_to = connection.quote_column_name(copy_to) + + # We're using raw SQL here since this job may be frequently executed. As + # a result dynamically defining models would lead to many unnecessary + # schema information queries. + connection.execute <<-SQL.strip_heredoc + UPDATE #{quoted_table} + SET #{quoted_copy_to} = #{quoted_copy_from} + WHERE id BETWEEN #{start_id} AND #{end_id} + SQL + end + + def connection + ActiveRecord::Base.connection + end + end + end +end diff --git a/lib/gitlab/database/migration_helpers.rb b/lib/gitlab/database/migration_helpers.rb index 3f65bc912de..33171f83692 100644 --- a/lib/gitlab/database/migration_helpers.rb +++ b/lib/gitlab/database/migration_helpers.rb @@ -385,10 +385,27 @@ module Gitlab # necessary since we copy over old values further down. change_column_default(table, new, old_col.default) if old_col.default - trigger_name = rename_trigger_name(table, old, new) + install_rename_triggers(table, old, new) + + update_column_in_batches(table, new, Arel::Table.new(table)[old]) + + change_column_null(table, new, false) unless old_col.null + + copy_indexes(table, old, new) + copy_foreign_keys(table, old, new) + end + + # Installs triggers in a table that keep a new column in sync with an old + # one. + # + # table - The name of the table to install the trigger in. + # old_column - The name of the old column. + # new_column - The name of the new column. + def install_rename_triggers(table, old_column, new_column) + trigger_name = rename_trigger_name(table, old_column, new_column) quoted_table = quote_table_name(table) - quoted_old = quote_column_name(old) - quoted_new = quote_column_name(new) + quoted_old = quote_column_name(old_column) + quoted_new = quote_column_name(new_column) if Database.postgresql? install_rename_triggers_for_postgresql(trigger_name, quoted_table, @@ -397,13 +414,6 @@ module Gitlab install_rename_triggers_for_mysql(trigger_name, quoted_table, quoted_old, quoted_new) end - - update_column_in_batches(table, new, Arel::Table.new(table)[old]) - - change_column_null(table, new, false) unless old_col.null - - copy_indexes(table, old, new) - copy_foreign_keys(table, old, new) end # Changes the type of a column concurrently. @@ -455,6 +465,97 @@ module Gitlab remove_column(table, old) end + # Changes the column type of a table using a background migration. + # + # Because this method uses a background migration it's more suitable for + # large tables. For small tables it's better to use + # `change_column_type_concurrently` since it can complete its work in a + # much shorter amount of time and doesn't rely on Sidekiq. + # + # Example usage: + # + # class Issue < ActiveRecord::Base + # self.table_name = 'issues' + # + # include EachBatch + # + # def self.to_migrate + # where('closed_at IS NOT NULL') + # end + # end + # + # change_column_type_using_background_migration( + # Issue.to_migrate, + # :closed_at, + # :datetime_with_timezone + # ) + # + # Reverting a migration like this is done exactly the same way, just with + # a different type to migrate to (e.g. `:datetime` in the above example). + # + # relation - An ActiveRecord relation to use for scheduling jobs and + # figuring out what table we're modifying. This relation _must_ + # have the EachBatch module included. + # + # column - The name of the column for which the type will be changed. + # + # new_type - The new type of the column. + # + # batch_size - The number of rows to schedule in a single background + # migration. + # + # interval - The time interval between every background migration. + def change_column_type_using_background_migration( + relation, + column, + new_type, + batch_size: 10_000, + interval: 10.minutes + ) + unless relation.model < EachBatch + raise TypeError, 'The relation must include the EachBatch module' + end + + temp_column = "#{column}_for_type_change" + table = relation.table_name + max_index = 0 + + add_column(table, temp_column, new_type) + install_rename_triggers(table, column, temp_column) + + # Schedule the jobs that will copy the data from the old column to the + # new one. + relation.each_batch(of: batch_size) do |batch, index| + start_id, end_id = batch.pluck('MIN(id), MAX(id)').first + max_index = index + + BackgroundMigrationWorker.perform_in( + index * interval, + 'CopyColumn', + [table, column, temp_column, start_id, end_id] + ) + end + + # Schedule the renaming of the column to happen (initially) 1 hour after + # the last batch finished. + BackgroundMigrationWorker.perform_in( + (max_index * interval) + 1.hour, + 'CleanupConcurrentTypeChange', + [table, column, temp_column] + ) + + if perform_background_migration_inline? + # To ensure the schema is up to date immediately we perform the + # migration inline in dev / test environments. + Gitlab::BackgroundMigration.steal('CopyColumn') + Gitlab::BackgroundMigration.steal('CleanupConcurrentTypeChange') + end + end + + def perform_background_migration_inline? + Rails.env.test? || Rails.env.development? + end + # Performs a concurrent column rename when using PostgreSQL. def install_rename_triggers_for_postgresql(trigger, table, old, new) execute <<-EOF.strip_heredoc diff --git a/spec/lib/gitlab/database/migration_helpers_spec.rb b/spec/lib/gitlab/database/migration_helpers_spec.rb index 664ba0f7234..7727a1d81b1 100644 --- a/spec/lib/gitlab/database/migration_helpers_spec.rb +++ b/spec/lib/gitlab/database/migration_helpers_spec.rb @@ -902,7 +902,7 @@ describe Gitlab::Database::MigrationHelpers do describe '#check_trigger_permissions!' do it 'does nothing when the user has the correct permissions' do expect { model.check_trigger_permissions!('users') } - .not_to raise_error(RuntimeError) + .not_to raise_error end it 'raises RuntimeError when the user does not have the correct permissions' do @@ -1036,4 +1036,93 @@ describe Gitlab::Database::MigrationHelpers do end end end + + describe '#change_column_type_using_background_migration' do + let!(:issue) { create(:issue) } + + let(:issue_model) do + Class.new(ActiveRecord::Base) do + self.table_name = 'issues' + include EachBatch + end + end + + it 'changes the type of a column using a background migration' do + expect(model) + .to receive(:add_column) + .with('issues', 'closed_at_for_type_change', :datetime_with_timezone) + + expect(model) + .to receive(:install_rename_triggers) + .with('issues', :closed_at, 'closed_at_for_type_change') + + expect(BackgroundMigrationWorker) + .to receive(:perform_in) + .ordered + .with( + 10.minutes, + 'CopyColumn', + ['issues', :closed_at, 'closed_at_for_type_change', issue.id, issue.id] + ) + + expect(BackgroundMigrationWorker) + .to receive(:perform_in) + .ordered + .with( + 1.hour + 10.minutes, + 'CleanupConcurrentTypeChange', + ['issues', :closed_at, 'closed_at_for_type_change'] + ) + + expect(Gitlab::BackgroundMigration) + .to receive(:steal) + .ordered + .with('CopyColumn') + + expect(Gitlab::BackgroundMigration) + .to receive(:steal) + .ordered + .with('CleanupConcurrentTypeChange') + + model.change_column_type_using_background_migration( + issue_model.all, + :closed_at, + :datetime_with_timezone + ) + end + end + + describe '#perform_background_migration_inline?' do + it 'returns true in a test environment' do + allow(Rails.env) + .to receive(:test?) + .and_return(true) + + expect(model.perform_background_migration_inline?).to eq(true) + end + + it 'returns true in a development environment' do + allow(Rails.env) + .to receive(:test?) + .and_return(false) + + allow(Rails.env) + .to receive(:development?) + .and_return(true) + + expect(model.perform_background_migration_inline?).to eq(true) + end + + it 'returns false in a production environment' do + allow(Rails.env) + .to receive(:test?) + .and_return(false) + + allow(Rails.env) + .to receive(:development?) + .and_return(false) + + expect(model.perform_background_migration_inline?).to eq(false) + end + end end -- cgit v1.2.1 From 8d8550c78ba0b7d6ac4a26b77b9b2def203d4eec Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 3 Jan 2018 12:44:57 +0100 Subject: Fix method lookup --- lib/gitlab/gitaly_client/conflicts_service.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/gitaly_client/conflicts_service.rb b/lib/gitlab/gitaly_client/conflicts_service.rb index 1e631e4bd3d..40f032cf873 100644 --- a/lib/gitlab/gitaly_client/conflicts_service.rb +++ b/lib/gitlab/gitaly_client/conflicts_service.rb @@ -1,6 +1,8 @@ module Gitlab module GitalyClient class ConflictsService + include Gitlab::EncodingHelper + MAX_MSG_SIZE = 128.kilobytes.freeze def initialize(repository, our_commit_oid, their_commit_oid) @@ -22,7 +24,7 @@ module Gitlab end def resolve_conflicts(target_repository, resolution, source_branch, target_branch) - reader = GitalyClient.binary_stringio(resolution.files.to_json) + reader = binary_stringio(resolution.files.to_json) req_enum = Enumerator.new do |y| header = resolve_conflicts_request_header(target_repository, resolution, source_branch, target_branch) -- cgit v1.2.1 From ce09dc31827e36fa0bd26c939a81bdb710e1fb93 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 3 Jan 2018 12:44:57 +0100 Subject: Fix method lookup --- lib/gitlab/gitaly_client/conflicts_service.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/gitaly_client/conflicts_service.rb b/lib/gitlab/gitaly_client/conflicts_service.rb index 1e631e4bd3d..40f032cf873 100644 --- a/lib/gitlab/gitaly_client/conflicts_service.rb +++ b/lib/gitlab/gitaly_client/conflicts_service.rb @@ -1,6 +1,8 @@ module Gitlab module GitalyClient class ConflictsService + include Gitlab::EncodingHelper + MAX_MSG_SIZE = 128.kilobytes.freeze def initialize(repository, our_commit_oid, their_commit_oid) @@ -22,7 +24,7 @@ module Gitlab end def resolve_conflicts(target_repository, resolution, source_branch, target_branch) - reader = GitalyClient.binary_stringio(resolution.files.to_json) + reader = binary_stringio(resolution.files.to_json) req_enum = Enumerator.new do |y| header = resolve_conflicts_request_header(target_repository, resolution, source_branch, target_branch) -- cgit v1.2.1 From 449b59ec69f7da2be9e75fdaf282592f048b9853 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Wed, 3 Jan 2018 12:54:47 +0100 Subject: Better English --- spec/lib/gitlab/git/repository_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/gitlab/git/repository_spec.rb b/spec/lib/gitlab/git/repository_spec.rb index c8ed0494f68..21379ae0f45 100644 --- a/spec/lib/gitlab/git/repository_spec.rb +++ b/spec/lib/gitlab/git/repository_spec.rb @@ -1730,7 +1730,7 @@ describe Gitlab::Git::Repository, seed_helper: true do # This 'nil' signals that the merge was not applied expect(result).to be_nil - # Our concurrent ref update should not have been reversed + # Our concurrent ref update should not have been undone expect(repository.find_branch(target_branch).target).to eq(concurrent_update_id) end end -- cgit v1.2.1 From 6c8daa6827353564dac45f495b5953cb3b4b273b Mon Sep 17 00:00:00 2001 From: James Ramsay Date: Wed, 3 Jan 2018 08:46:06 -0500 Subject: Remove superfluous i18n namespaces --- app/views/projects/compare/_form.html.haml | 8 ++++---- app/views/projects/compare/index.html.haml | 4 ++-- app/views/projects/compare/show.html.haml | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index ebeeeff5e76..d0c8a699608 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -9,7 +9,7 @@ = s_("CompareBranches|Source") = hidden_field_tag :to, params[:to] = button_tag type: 'button', title: params[:to], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-to-dropdown", selected: params[:to], field_name: :to } do - .dropdown-toggle-text.str-truncated= params[:to] || s_("CompareBranches|Select branch/tag") + .dropdown-toggle-text.str-truncated= params[:to] || _("Select branch/tag") = render 'shared/ref_dropdown' .compare-ellipsis.inline ... .form-group.dropdown.compare-form-group.from.js-compare-from-dropdown @@ -18,11 +18,11 @@ = s_("CompareBranches|Target") = hidden_field_tag :from, params[:from] = button_tag type: 'button', title: params[:from], class: "form-control compare-dropdown-toggle js-compare-dropdown has-tooltip git-revision-dropdown-toggle", required: true, data: { refs_url: refs_project_path(@project), toggle: "dropdown", target: ".js-compare-from-dropdown", selected: params[:from], field_name: :from } do - .dropdown-toggle-text.str-truncated= params[:from] || s_("CompareBranches|Select branch/tag") + .dropdown-toggle-text.str-truncated= params[:from] || _("Select branch/tag") = render 'shared/ref_dropdown'   = button_tag s_("CompareBranches|Compare"), class: "btn btn-create commits-compare-btn" - if @merge_request.present? - = link_to s_("CompareBranches|View open merge request"), project_merge_request_path(@project, @merge_request), class: 'prepend-left-10 btn' + = link_to _("View open merge request"), project_merge_request_path(@project, @merge_request), class: 'prepend-left-10 btn' - elsif create_mr_button? - = link_to s_("CompareBranches|Create merge request"), create_mr_path, class: 'prepend-left-10 btn' + = link_to _("Create merge request"), create_mr_path, class: 'prepend-left-10 btn' diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index b3b9aa55b7c..9b0095f5f0f 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -10,9 +10,9 @@ %code.ref-name master - example_sha = capture_haml do %code.ref-name 4eedf23 - = (s_("CompareBranches|Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to what's changed or to create a merge request.") % { master: example_master, sha: example_sha }).html_safe + = (_("Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request.") % { master: example_master, sha: example_sha }).html_safe %br - = (s_("ComparBranches|Changes are shown as if the source revision was being merged into the target revision.")).html_safe + = (_("Changes are shown as if the source revision was being merged into the target revision.")).html_safe .prepend-top-20 = render "form" diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index b892d4d8ba5..0939b2a3b07 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -22,4 +22,4 @@ %span.ref-name= params[:to] = (s_("CompareBranches|%{source_branch} and %{target_branch} are the same.") % { source_branch: sourceBranch, target_branch: targetBranch }).html_safe - else - = s_("CompareBranches|You'll need to use different branch names to get a valid comparison.") + = _("You'll need to use different branch names to get a valid comparison.") -- cgit v1.2.1 From 5e0143a84bca7fd8b2dccd175e0f50c87dea4b98 Mon Sep 17 00:00:00 2001 From: Alessio Caiazza Date: Sat, 27 May 2017 15:23:27 +0200 Subject: Add online attribute to runner api entity --- .../unreleased/feature-api_runners_online.yml | 4 +++ doc/api/runners.md | 29 +++++++++++++++------- lib/api/entities.rb | 1 + 3 files changed, 25 insertions(+), 9 deletions(-) create mode 100644 changelogs/unreleased/feature-api_runners_online.yml diff --git a/changelogs/unreleased/feature-api_runners_online.yml b/changelogs/unreleased/feature-api_runners_online.yml new file mode 100644 index 00000000000..f5077507e5b --- /dev/null +++ b/changelogs/unreleased/feature-api_runners_online.yml @@ -0,0 +1,4 @@ +--- +title: Add online attribute to runner api entity +merge_request: 11750 +author: Alessio Caiazza diff --git a/doc/api/runners.md b/doc/api/runners.md index 015b09a745e..50981ed96bc 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -30,14 +30,16 @@ Example response: "description": "test-1-20150125", "id": 6, "is_shared": false, - "name": null + "name": null, + "online": true }, { "active": true, "description": "test-2-20150125", "id": 8, "is_shared": false, - "name": null + "name": null, + "online": false } ] ``` @@ -69,28 +71,32 @@ Example response: "description": "shared-runner-1", "id": 1, "is_shared": true, - "name": null + "name": null, + "online": true }, { "active": true, "description": "shared-runner-2", "id": 3, "is_shared": true, - "name": null + "name": null, + "online": false }, { "active": true, "description": "test-1-20150125", "id": 6, "is_shared": false, - "name": null + "name": null, + "online": true }, { "active": true, "description": "test-2-20150125", "id": 8, "is_shared": false, - "name": null + "name": null, + "online": false } ] ``` @@ -122,6 +128,7 @@ Example response: "is_shared": false, "contacted_at": "2016-01-25T16:39:48.066Z", "name": null, + "online": true, "platform": null, "projects": [ { @@ -176,6 +183,7 @@ Example response: "is_shared": false, "contacted_at": "2016-01-25T16:39:48.066Z", "name": null, + "online": true, "platform": null, "projects": [ { @@ -327,14 +335,16 @@ Example response: "description": "test-2-20150125", "id": 8, "is_shared": false, - "name": null + "name": null, + "online": false }, { "active": true, "description": "development_runner", "id": 5, "is_shared": true, - "name": null + "name": null, + "online": true } ] ``` @@ -364,7 +374,8 @@ Example response: "description": "test-2016-02-01", "id": 9, "is_shared": false, - "name": null + "name": null, + "online": true } ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 4ad4a1f7867..c612dde7f73 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -862,6 +862,7 @@ module API expose :active expose :is_shared expose :name + expose :online?, as: :online end class RunnerDetails < Runner -- cgit v1.2.1 From 87a437995e4bec0c9b84c1ae2833cf7186709911 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Wed, 3 Jan 2018 11:57:07 -0200 Subject: Simplify metrics fetching for closed/merged MRs --- app/serializers/merge_request_widget_entity.rb | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/app/serializers/merge_request_widget_entity.rb b/app/serializers/merge_request_widget_entity.rb index 83ea36adcc1..e905e6876c2 100644 --- a/app/serializers/merge_request_widget_entity.rb +++ b/app/serializers/merge_request_widget_entity.rb @@ -187,12 +187,11 @@ class MergeRequestWidgetEntity < IssuableEntity def build_metrics(merge_request) # There's no need to query and serialize metrics data for merge requests that are not # merged or closed. - case merge_request.state - when 'merged' - merge_request.metrics&.merged_by_id ? merge_request.metrics : build_metrics_from_events(merge_request) - when 'closed' - merge_request.metrics&.latest_closed_by_id ? merge_request.metrics : build_metrics_from_events(merge_request) - end + return unless merge_request.merged? || merge_request.closed? + return merge_request.metrics if merge_request.merged? && merge_request.metrics&.merged_by_id + return merge_request.metrics if merge_request.closed? && merge_request.metrics&.latest_closed_by_id + + build_metrics_from_events(merge_request) end def build_metrics_from_events(merge_request) -- cgit v1.2.1 From e24e0c90a30c37917555baa0bfa0849458ce93d5 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 2 Jan 2018 22:40:07 +0800 Subject: Use heredoc for long strings so it's easier to read --- qa/qa/runtime/user.rb | 14 +++++------ spec/factories/keys.rb | 63 ++++++++++++++++++++++++++++---------------------- 2 files changed, 43 insertions(+), 34 deletions(-) diff --git a/qa/qa/runtime/user.rb b/qa/qa/runtime/user.rb index 7bd50023561..2832439d9e0 100644 --- a/qa/qa/runtime/user.rb +++ b/qa/qa/runtime/user.rb @@ -12,13 +12,13 @@ module QA end def ssh_key - <<~KEY.tr("\n", '') - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFf6RYK3qu/RKF/3ndJmL5xgMLp3O9 - 6x8lTay+QGZ0+9FnnAXMdUqBq/ZU6d/gyMB4IaW3nHzM1w049++yAB6UPCzMB8Uo27K5 - /jyZCtj7Vm9PFNjF/8am1kp46c/SeYicQgQaSBdzIW3UDEa1Ef68qroOlvpi9PYZ/tA7 - M0YP0K5PXX+E36zaIRnJVMPT3f2k+GnrxtjafZrwFdpOP/Fol5BQLBgcsyiU+LM1SuaC - rzd8c9vyaTA1CxrkxaZh+buAi0PmdDtaDrHd42gqZkXCKavyvgM5o2CkQ5LJHCgzpXy0 - 5qNFzmThBSkb+XtoxbyagBiGbVZtSVow6Xa7qewz= dummy@gitlab.com + <<~KEY.delete("\n") + ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFf6RYK3qu/RKF/3ndJmL5xgMLp3O9 + 6x8lTay+QGZ0+9FnnAXMdUqBq/ZU6d/gyMB4IaW3nHzM1w049++yAB6UPCzMB8Uo27K5 + /jyZCtj7Vm9PFNjF/8am1kp46c/SeYicQgQaSBdzIW3UDEa1Ef68qroOlvpi9PYZ/tA7 + M0YP0K5PXX+E36zaIRnJVMPT3f2k+GnrxtjafZrwFdpOP/Fol5BQLBgcsyiU+LM1SuaC + rzd8c9vyaTA1CxrkxaZh+buAi0PmdDtaDrHd42gqZkXCKavyvgM5o2CkQ5LJHCgzpXy0 + 5qNFzmThBSkb+XtoxbyagBiGbVZtSVow6Xa7qewz= dummy@gitlab.com KEY end end diff --git a/spec/factories/keys.rb b/spec/factories/keys.rb index e6eb76f71d3..552b4b7e06e 100644 --- a/spec/factories/keys.rb +++ b/spec/factories/keys.rb @@ -21,12 +21,14 @@ FactoryBot.define do factory :rsa_key_2048 do key do - 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFf6RYK3qu/RKF/3ndJmL5xgMLp3O9' \ - '6x8lTay+QGZ0+9FnnAXMdUqBq/ZU6d/gyMB4IaW3nHzM1w049++yAB6UPCzMB8Uo27K5' \ - '/jyZCtj7Vm9PFNjF/8am1kp46c/SeYicQgQaSBdzIW3UDEa1Ef68qroOlvpi9PYZ/tA7' \ - 'M0YP0K5PXX+E36zaIRnJVMPT3f2k+GnrxtjafZrwFdpOP/Fol5BQLBgcsyiU+LM1SuaC' \ - 'rzd8c9vyaTA1CxrkxaZh+buAi0PmdDtaDrHd42gqZkXCKavyvgM5o2CkQ5LJHCgzpXy0' \ - '5qNFzmThBSkb+XtoxbyagBiGbVZtSVow6Xa7qewz= dummy@gitlab.com' + <<~KEY.delete("\n") + ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDFf6RYK3qu/RKF/3ndJmL5xgMLp3O9 + 6x8lTay+QGZ0+9FnnAXMdUqBq/ZU6d/gyMB4IaW3nHzM1w049++yAB6UPCzMB8Uo27K5 + /jyZCtj7Vm9PFNjF/8am1kp46c/SeYicQgQaSBdzIW3UDEa1Ef68qroOlvpi9PYZ/tA7 + M0YP0K5PXX+E36zaIRnJVMPT3f2k+GnrxtjafZrwFdpOP/Fol5BQLBgcsyiU+LM1SuaC + rzd8c9vyaTA1CxrkxaZh+buAi0PmdDtaDrHd42gqZkXCKavyvgM5o2CkQ5LJHCgzpXy0 + 5qNFzmThBSkb+XtoxbyagBiGbVZtSVow6Xa7qewz= dummy@gitlab.com + KEY end factory :rsa_deploy_key_2048, class: 'DeployKey' @@ -34,37 +36,44 @@ FactoryBot.define do factory :dsa_key_2048 do key do - 'ssh-dss AAAAB3NzaC1kc3MAAAEBAO/3/NPLA/zSFkMOCaTtGo+uos1flfQ5f038Uk+G' \ - 'Y9AeLGzX+Srhw59GdVXmOQLYBrOt5HdGwqYcmLnE2VurUGmhtfeO5H+3p5pGJbkS0Gxp' \ - 'YH1HRO9lWsncF3Hh1w4lYsDjkclDiSTdfTuN8F4Kb3DXNnVSCieeonp+B25F/CXagyTQ' \ - '/pvNmHFeYgGCVdnBtFdi+xfxaZ8NKdPrGggzokbKHElDZQ4Xo5EpdcyLajgM7nB2r2Rz' \ - 'OrmeaevKi5lV68ehRa9Yyrb7vxvwiwBwOgqR/mnN7Gnaq1jUdmJY+ct04Qwx37f5jvhv' \ - '5gA4U40SGMoiHM8RFIN7Ksz0jsyX73MAAAAVALRWOfjfzHpK7KLz4iqDvvTUAevJAAAB' \ - 'AEa9NZ+6y9iQ5erGsdfLTXFrhSefTG0NhghoO/5IFkSGfd8V7kzTvCHaFrcfpEA5kP8t' \ - 'poeOG0TASB6tgGOxm1Bq4Wncry5RORBPJlAVpDGRcvZ931ddH7IgltEInS6za2uH6F/1' \ - 'M1QfKePSLr6xJ1ZLYfP0Og5KTp1x6yMQvfwV0a+XdA+EPgaJWLWp/pWwKWa0oLUgjsIH' \ - 'MYzuOGh5c708uZrmkzqvgtW2NgXhcIroRgynT3IfI2lP2rqqb3uuuE/qH5UCUFO+Dc3H' \ - 'nAFNeQDT/M25AERdPYBAY5a+iPjIgO+jT7BfmfByT+AZTqZySrCyc7nNZL3YgGLK0l6A' \ - '1GgAAAEBAN9FpFOdIXE+YEZhKl1vPmbcn+b1y5zOl6N4x1B7Q8pD/pLMziWROIS8uLzb' \ - 'aZ0sMIWezHIkxuo1iROMeT+jtCubn7ragaN6AX7nMpxYUH9+mYZZs/fyElt6wCviVhTI' \ - 'zM+u7VdQsnZttOOlQfogHdL+SpeAft0DsfJjlcgQnsLlHQKv6aPqCPYUST2nE7RyW/Ex' \ - 'PrMxLtOWt0/j8RYHbwwqvyeZqBz3ESBgrS9c5tBdBfauwYUV/E7gPLOU3OZFw9ue7o+z' \ - 'wzoTZqW6Xouy5wtWvSLQSLT5XwOslmQz8QMBxD0AQyDfEFGsBCWzmbTgKv9uqrBjubsS' \ - 'Taja+Cf9kMo== dummy@gitlab.com' + <<~KEY.delete("\n") + ssh-dss AAAAB3NzaC1kc3MAAAEBAO/3/NPLA/zSFkMOCaTtGo+uos1flfQ5f038Uk+G + Y9AeLGzX+Srhw59GdVXmOQLYBrOt5HdGwqYcmLnE2VurUGmhtfeO5H+3p5pGJbkS0Gxp + YH1HRO9lWsncF3Hh1w4lYsDjkclDiSTdfTuN8F4Kb3DXNnVSCieeonp+B25F/CXagyTQ + /pvNmHFeYgGCVdnBtFdi+xfxaZ8NKdPrGggzokbKHElDZQ4Xo5EpdcyLajgM7nB2r2Rz + OrmeaevKi5lV68ehRa9Yyrb7vxvwiwBwOgqR/mnN7Gnaq1jUdmJY+ct04Qwx37f5jvhv + 5gA4U40SGMoiHM8RFIN7Ksz0jsyX73MAAAAVALRWOfjfzHpK7KLz4iqDvvTUAevJAAAB + AEa9NZ+6y9iQ5erGsdfLTXFrhSefTG0NhghoO/5IFkSGfd8V7kzTvCHaFrcfpEA5kP8t + poeOG0TASB6tgGOxm1Bq4Wncry5RORBPJlAVpDGRcvZ931ddH7IgltEInS6za2uH6F/1 + M1QfKePSLr6xJ1ZLYfP0Og5KTp1x6yMQvfwV0a+XdA+EPgaJWLWp/pWwKWa0oLUgjsIH + MYzuOGh5c708uZrmkzqvgtW2NgXhcIroRgynT3IfI2lP2rqqb3uuuE/qH5UCUFO+Dc3H + nAFNeQDT/M25AERdPYBAY5a+iPjIgO+jT7BfmfByT+AZTqZySrCyc7nNZL3YgGLK0l6A + 1GgAAAEBAN9FpFOdIXE+YEZhKl1vPmbcn+b1y5zOl6N4x1B7Q8pD/pLMziWROIS8uLzb + aZ0sMIWezHIkxuo1iROMeT+jtCubn7ragaN6AX7nMpxYUH9+mYZZs/fyElt6wCviVhTI + zM+u7VdQsnZttOOlQfogHdL+SpeAft0DsfJjlcgQnsLlHQKv6aPqCPYUST2nE7RyW/Ex + PrMxLtOWt0/j8RYHbwwqvyeZqBz3ESBgrS9c5tBdBfauwYUV/E7gPLOU3OZFw9ue7o+z + wzoTZqW6Xouy5wtWvSLQSLT5XwOslmQz8QMBxD0AQyDfEFGsBCWzmbTgKv9uqrBjubsS + Taja+Cf9kMo== dummy@gitlab.com + KEY end end factory :ecdsa_key_256 do key do - 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYA' \ - 'AABBBJZmkzTgY0fiCQ+DVReyH/fFwTFz0XoR3RUO0u+199H19KFw7mNPxRSMOVS7tEtO' \ - 'Nj3Q7FcZXfqthHvgAzDiHsc= dummy@gitlab.com' + <<~KEY.delete("\n") + ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYA + AABBBJZmkzTgY0fiCQ+DVReyH/fFwTFz0XoR3RUO0u+199H19KFw7mNPxRSMOVS7tEtO + Nj3Q7FcZXfqthHvgAzDiHsc= dummy@gitlab.com + KEY end end factory :ed25519_key_256 do key do - 'ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETnVTgzqC1gatgSlC4zH6aYt2CAQzgJOhDRvf59ohL6 dummy@gitlab.com' + <<~KEY.delete("\n") + ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIETnVTgzqC1gatgSlC4zH6aYt2CAQzgJ + OhDRvf59ohL6 dummy@gitlab.com + KEY end end end -- cgit v1.2.1 From 86257cf7138a6d28c055071219142722787b6546 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 3 Jan 2018 16:17:16 +0100 Subject: refactor project create service --- app/models/project.rb | 2 +- app/services/projects/create_service.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/project.rb b/app/models/project.rb index e37eae9f176..6ebb083aeb4 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -639,7 +639,7 @@ class Project < ActiveRecord::Base end def import? - external_import? || forked? || gitlab_project_import? + external_import? || forked? || gitlab_project_import? || bare_repository_import? end def no_import? diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 52b90cdf135..dc7b1f1f5cc 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -120,7 +120,7 @@ module Projects Project.transaction do @project.create_or_update_import_data(data: import_data[:data], credentials: import_data[:credentials]) if import_data - if @project.save && !@project.import? && !@project.bare_repository_import? + if @project.save && !@project.import? raise 'Failed to create repository' unless @project.create_repository end end @@ -165,7 +165,7 @@ module Projects def import_schedule if @project.errors.empty? - @project.import_schedule if @project.import? + @project.import_schedule if @project.import? && !@project.bare_repository_import? else fail(error: @project.errors.full_messages.join(', ')) end -- cgit v1.2.1 From 274bde5d8bbf17f1cbc71ff6a38d45d6fe606245 Mon Sep 17 00:00:00 2001 From: James Ramsay Date: Wed, 3 Jan 2018 10:32:03 -0500 Subject: Fix incorrect case of ruby vars --- app/views/projects/compare/show.html.haml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index 0939b2a3b07..0d59d800d1e 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -16,10 +16,10 @@ = s_("CompareBranches|There isn't anything to compare.") %p.slead - if params[:to] == params[:from] - - sourceBranch = capture_haml do + - source_branch = capture_haml do %span.ref-name= params[:from] - - targetBranch = capture_haml do + - target_branch = capture_haml do %span.ref-name= params[:to] - = (s_("CompareBranches|%{source_branch} and %{target_branch} are the same.") % { source_branch: sourceBranch, target_branch: targetBranch }).html_safe + = (s_("CompareBranches|%{source_branch} and %{target_branch} are the same.") % { source_branch: source_branch, target_branch: target_branch }).html_safe - else = _("You'll need to use different branch names to get a valid comparison.") -- cgit v1.2.1 From 592e81503abee7087dca0c4994a9dbeff5bf6945 Mon Sep 17 00:00:00 2001 From: James Ramsay Date: Wed, 3 Jan 2018 10:32:46 -0500 Subject: Add changelog entry --- changelogs/unreleased/jramsay-4012-i18n-compare.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/jramsay-4012-i18n-compare.yml diff --git a/changelogs/unreleased/jramsay-4012-i18n-compare.yml b/changelogs/unreleased/jramsay-4012-i18n-compare.yml new file mode 100644 index 00000000000..ff15724be39 --- /dev/null +++ b/changelogs/unreleased/jramsay-4012-i18n-compare.yml @@ -0,0 +1,5 @@ +--- +title: Add i18n helpers to branch comparison view +merge_request: 16031 +author: James Ramsay +type: added -- cgit v1.2.1 From 0d6b9e30cb5c3b76ee97cd14dea1dae12a74e8d6 Mon Sep 17 00:00:00 2001 From: Jose Ivan Vargas Date: Fri, 22 Dec 2017 12:25:24 -0600 Subject: Fix import project url not updating project name --- app/assets/javascripts/projects/project_new.js | 7 +++-- .../unreleased/jivl-fix-import-project-url-bug.yml | 5 ++++ spec/javascripts/projects/project_new_spec.js | 32 ++++++++++++---------- 3 files changed, 27 insertions(+), 17 deletions(-) create mode 100644 changelogs/unreleased/jivl-fix-import-project-url-bug.yml diff --git a/app/assets/javascripts/projects/project_new.js b/app/assets/javascripts/projects/project_new.js index 3ecc0c2a6e5..4710e70d619 100644 --- a/app/assets/javascripts/projects/project_new.js +++ b/app/assets/javascripts/projects/project_new.js @@ -1,6 +1,7 @@ let hasUserDefinedProjectPath = false; -const deriveProjectPathFromUrl = ($projectImportUrl, $projectPath) => { +const deriveProjectPathFromUrl = ($projectImportUrl) => { + const $currentProjectPath = $projectImportUrl.parents('.toggle-import-form').find('#project_path'); if (hasUserDefinedProjectPath) { return; } @@ -21,7 +22,7 @@ const deriveProjectPathFromUrl = ($projectImportUrl, $projectPath) => { // extract everything after the last slash const pathMatch = /\/([^/]+)$/.exec(importUrl); if (pathMatch) { - $projectPath.val(pathMatch[1]); + $currentProjectPath.val(pathMatch[1]); } }; @@ -96,7 +97,7 @@ const bindEvents = () => { hasUserDefinedProjectPath = $projectPath.val().trim().length > 0; }); - $projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl, $projectPath)); + $projectImportUrl.keyup(() => deriveProjectPathFromUrl($projectImportUrl)); }; document.addEventListener('DOMContentLoaded', bindEvents); diff --git a/changelogs/unreleased/jivl-fix-import-project-url-bug.yml b/changelogs/unreleased/jivl-fix-import-project-url-bug.yml new file mode 100644 index 00000000000..0d97b9c9a53 --- /dev/null +++ b/changelogs/unreleased/jivl-fix-import-project-url-bug.yml @@ -0,0 +1,5 @@ +--- +title: Fix import project url not updating project name +merge_request: 16120 +author: +type: fixed diff --git a/spec/javascripts/projects/project_new_spec.js b/spec/javascripts/projects/project_new_spec.js index 850768f0e4f..c314ca8ab72 100644 --- a/spec/javascripts/projects/project_new_spec.js +++ b/spec/javascripts/projects/project_new_spec.js @@ -6,8 +6,12 @@ describe('New Project', () => { beforeEach(() => { setFixtures(` - - +
+
+ + +
+
`); $projectImportUrl = $('#project_import_url'); @@ -25,7 +29,7 @@ describe('New Project', () => { it('does not change project path for disabled $projectImportUrl', () => { $projectImportUrl.attr('disabled', true); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual(dummyImportUrl); }); @@ -38,7 +42,7 @@ describe('New Project', () => { it('does not change project path if it is set by user', () => { $projectPath.keyup(); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual(dummyImportUrl); }); @@ -46,7 +50,7 @@ describe('New Project', () => { it('does not change project path for empty $projectImportUrl', () => { $projectImportUrl.val(''); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual(dummyImportUrl); }); @@ -54,7 +58,7 @@ describe('New Project', () => { it('does not change project path for whitespace $projectImportUrl', () => { $projectImportUrl.val(' '); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual(dummyImportUrl); }); @@ -62,7 +66,7 @@ describe('New Project', () => { it('does not change project path for $projectImportUrl without slashes', () => { $projectImportUrl.val('has-no-slash'); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual(dummyImportUrl); }); @@ -70,7 +74,7 @@ describe('New Project', () => { it('changes project path to last $projectImportUrl component', () => { $projectImportUrl.val('/this/is/last'); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual('last'); }); @@ -78,7 +82,7 @@ describe('New Project', () => { it('ignores trailing slashes in $projectImportUrl', () => { $projectImportUrl.val('/has/trailing/slash/'); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual('slash'); }); @@ -86,7 +90,7 @@ describe('New Project', () => { it('ignores fragment identifier in $projectImportUrl', () => { $projectImportUrl.val('/this/has/a#fragment-identifier/'); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual('a'); }); @@ -94,7 +98,7 @@ describe('New Project', () => { it('ignores query string in $projectImportUrl', () => { $projectImportUrl.val('/url/with?query=string'); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual('with'); }); @@ -102,7 +106,7 @@ describe('New Project', () => { it('ignores trailing .git in $projectImportUrl', () => { $projectImportUrl.val('/repository.git'); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual('repository'); }); @@ -110,7 +114,7 @@ describe('New Project', () => { it('changes project path for HTTPS URL in $projectImportUrl', () => { $projectImportUrl.val('https://username:password@gitlab.company.com/group/project.git'); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual('project'); }); @@ -118,7 +122,7 @@ describe('New Project', () => { it('changes project path for SSH URL in $projectImportUrl', () => { $projectImportUrl.val('git@gitlab.com:gitlab-org/gitlab-ce.git'); - projectNew.deriveProjectPathFromUrl($projectImportUrl, $projectPath); + projectNew.deriveProjectPathFromUrl($projectImportUrl); expect($projectPath.val()).toEqual('gitlab-ce'); }); -- cgit v1.2.1 From 7c721e7bba4359b60f20090d850c672d06023072 Mon Sep 17 00:00:00 2001 From: James Ramsay Date: Wed, 3 Jan 2018 11:43:01 -0500 Subject: Replace use of capture_haml with capture --- app/views/projects/compare/index.html.haml | 4 ++-- app/views/projects/compare/show.html.haml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/projects/compare/index.html.haml b/app/views/projects/compare/index.html.haml index 9b0095f5f0f..14c64b3534a 100644 --- a/app/views/projects/compare/index.html.haml +++ b/app/views/projects/compare/index.html.haml @@ -6,9 +6,9 @@ %h3.page-title = _("Compare Git revisions") .sub-header-block - - example_master = capture_haml do + - example_master = capture do %code.ref-name master - - example_sha = capture_haml do + - example_sha = capture do %code.ref-name 4eedf23 = (_("Choose a branch/tag (e.g. %{master}) or enter a commit (e.g. %{sha}) to see what's changed or to create a merge request.") % { master: example_master, sha: example_sha }).html_safe %br diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index 0d59d800d1e..8da55664878 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -16,9 +16,9 @@ = s_("CompareBranches|There isn't anything to compare.") %p.slead - if params[:to] == params[:from] - - source_branch = capture_haml do + - source_branch = capture do %span.ref-name= params[:from] - - target_branch = capture_haml do + - target_branch = capture do %span.ref-name= params[:to] = (s_("CompareBranches|%{source_branch} and %{target_branch} are the same.") % { source_branch: source_branch, target_branch: target_branch }).html_safe - else -- cgit v1.2.1 From 9504a529b758b0352b9c60d67fda8b4ee2a5fec0 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 19 Dec 2017 14:36:33 -0200 Subject: Write project full path to .git/config when creating projects We'd need to keep track of project full path otherwise directory tree created with hashed storage enabled cannot be usefully imported using the import rake task. --- app/models/project.rb | 5 +++++ app/services/projects/create_service.rb | 5 +++++ spec/services/projects/create_service_spec.rb | 6 ++++++ 3 files changed, 16 insertions(+) diff --git a/app/models/project.rb b/app/models/project.rb index 6ebb083aeb4..eac78de1ac9 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1432,6 +1432,11 @@ class Project < ActiveRecord::Base Gitlab::PagesTransfer.new.rename_project(path_before_change, self.path, namespace.full_path) end + def write_repository_config(key, value, prefix: :gitlab) + key = [prefix, key].compact.join('.') + repo.config[key] = value + end + def rename_repo_notify! send_move_instructions(full_path_was) expires_full_path_cache diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index dc7b1f1f5cc..4c7e5370bbe 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -87,6 +87,11 @@ module Projects def after_create_actions log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"") + # We'd need to keep track of project full path otherwise directory tree + # created with hashed storage enabled cannot be usefully imported using + # the import rake task. + @project.write_repository_config(:fullpath, @project.full_path) + unless @project.gitlab_project_import? @project.create_wiki unless skip_wiki? create_services_from_active_templates(@project) diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index dc89fdebce7..1833078f37c 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -252,6 +252,12 @@ describe Projects::CreateService, '#execute' do end end + it 'writes project full path to .git/config' do + project = create_project(user, opts) + + expect(project.repo.config['gitlab.fullpath']).to eq project.full_path + end + def create_project(user, opts) Projects::CreateService.new(user, opts).execute end -- cgit v1.2.1 From 64fe954dcebaadd6f686f30eb4ff0be5ebcf172d Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 19 Dec 2017 14:53:59 -0200 Subject: Update project full path in .git/config when renaming a repository --- app/models/project.rb | 5 +++++ spec/models/project_spec.rb | 14 ++++++++++++++ spec/services/projects/update_service_spec.rb | 2 +- 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/app/models/project.rb b/app/models/project.rb index eac78de1ac9..1182dbda0c0 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1420,6 +1420,11 @@ class Project < ActiveRecord::Base end def after_rename_repo + # We'd need to keep track of project full path otherwise directory tree + # created with hashed storage enabled cannot be usefully imported using + # the import rake task. + write_repository_config(:fullpath, full_path) + path_before_change = previous_changes['path'].first # We need to check if project had been rolled out to move resource to hashed storage or not and decide diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 7338e341359..10634d22b39 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2626,6 +2626,14 @@ describe Project do project.rename_repo end end + + it 'updates project full path in .git/config' do + allow(project_storage).to receive(:rename_repo).and_return(true) + + expect(project).to receive(:write_repository_config).with(:fullpath, project.full_path) + + project.rename_repo + end end describe '#pages_path' do @@ -2781,6 +2789,12 @@ describe Project do end end end + + it 'updates project full path in .git/config' do + expect(project).to receive(:write_repository_config).with(:fullpath, project.full_path) + + project.rename_repo + end end describe '#pages_path' do diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index d887f70efae..fc6aa713d6f 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -61,7 +61,7 @@ describe Projects::UpdateService do end end - context 'When project visibility is higher than parent group' do + context 'when project visibility is higher than parent group' do let(:group) { create(:group, visibility_level: Gitlab::VisibilityLevel::INTERNAL) } before do -- cgit v1.2.1 From bd90330740e0ea5c0ce0672fd605a463fcdfc898 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 19 Dec 2017 17:18:26 -0200 Subject: Update project full path in .git/config when transfering a project --- app/services/projects/transfer_service.rb | 10 ++++++++++ spec/services/projects/transfer_service_spec.rb | 12 ++++++++++++ 2 files changed, 22 insertions(+) diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index e5cd6fcdfe3..e742df5f696 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -75,6 +75,8 @@ module Projects project.old_path_with_namespace = @old_path project.expires_full_path_cache + write_repository_config(@new_path) + execute_system_hooks end rescue Exception # rubocop:disable Lint/RescueException @@ -98,6 +100,13 @@ module Projects project.save! end + def write_repository_config(full_path) + # We'd need to keep track of project full path otherwise directory tree + # created with hashed storage enabled cannot be usefully imported using + # the import rake task. + project.write_repository_config(:fullpath, full_path) + end + def refresh_permissions # This ensures we only schedule 1 job for every user that has access to # the namespaces. @@ -110,6 +119,7 @@ module Projects def rollback_side_effects rollback_folder_move update_namespace_and_visibility(@old_namespace) + write_repository_config(@old_path) end def rollback_folder_move diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 2b1337bee7e..7377c748698 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -54,6 +54,12 @@ describe Projects::TransferService do expect(project.disk_path).not_to eq(old_path) expect(project.disk_path).to start_with(group.path) end + + it 'updates project full path in .git/config' do + transfer_project(project, user, group) + + expect(project.repo.config['gitlab.fullpath']).to eq "#{group.full_path}/#{project.path}" + end end context 'when transfer fails' do @@ -86,6 +92,12 @@ describe Projects::TransferService do expect(original_path).to eq current_path end + it 'rolls back project full path in .git/config' do + attempt_project_transfer + + expect(project.repo.config['gitlab.fullpath']).to eq project.full_path + end + it "doesn't send move notifications" do expect_any_instance_of(NotificationService).not_to receive(:project_was_moved) -- cgit v1.2.1 From ca089f59687fb8616bcbd3d5501fbc6006893e8f Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 19 Dec 2017 17:42:51 -0200 Subject: Update project full path in .git/config when renaming namespace --- app/models/concerns/storage/legacy_namespace.rb | 2 ++ app/models/namespace.rb | 7 +++++++ spec/models/namespace_spec.rb | 14 ++++++++++++++ 3 files changed, 23 insertions(+) diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb index b3020484738..22b9ef4e338 100644 --- a/app/models/concerns/storage/legacy_namespace.rb +++ b/app/models/concerns/storage/legacy_namespace.rb @@ -34,6 +34,8 @@ module Storage # So we basically we mute exceptions in next actions begin send_update_instructions + write_projects_full_path_config + true rescue # Returning false does not rollback after_* transaction but gives diff --git a/app/models/namespace.rb b/app/models/namespace.rb index 0ff169d4531..d983b2f106b 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -268,4 +268,11 @@ class Namespace < ActiveRecord::Base def namespace_previously_created_with_same_path? RedirectRoute.permanent.exists?(path: path) end + + def write_projects_full_path_config + all_projects.each do |project| + project.expires_full_path_cache # we need to clear cache to validate renames correctly + project.write_repository_config(:fullpath, project.full_path) + end + end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index b7c6286fd83..0a99485ec8e 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -240,6 +240,20 @@ describe Namespace do end end end + + it 'updates project full path in .git/config for each project inside namespace' do + parent = create(:group, name: 'mygroup', path: 'mygroup') + subgroup = create(:group, name: 'mysubgroup', path: 'mysubgroup', parent: parent) + project_in_parent_group = create(:project, :repository, namespace: parent, name: 'foo1') + hashed_project_in_subgroup = create(:project, :repository, :hashed, namespace: subgroup, name: 'foo2') + legacy_project_in_subgroup = create(:project, :repository, namespace: subgroup, name: 'foo3') + + parent.update(path: 'mygroup_new') + + expect(project_in_parent_group.repo.config['gitlab.fullpath']).to eq "mygroup_new/#{project_in_parent_group.path}" + expect(hashed_project_in_subgroup.repo.config['gitlab.fullpath']).to eq "mygroup_new/mysubgroup/#{hashed_project_in_subgroup.path}" + expect(legacy_project_in_subgroup.repo.config['gitlab.fullpath']).to eq "mygroup_new/mysubgroup/#{legacy_project_in_subgroup.path}" + end end describe '#rm_dir', 'callback' do -- cgit v1.2.1 From 2f2233774c3d6416f5571a2e83b367d34bad3f5f Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 19 Dec 2017 18:04:48 -0200 Subject: Write project full path to .git/config when migrating legacy storage --- .../projects/hashed_storage/migrate_repository_service.rb | 7 +++++++ .../projects/hashed_storage/migrate_repository_service_spec.rb | 8 +++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/app/services/projects/hashed_storage/migrate_repository_service.rb b/app/services/projects/hashed_storage/migrate_repository_service.rb index 7212e7524ab..c076ce06278 100644 --- a/app/services/projects/hashed_storage/migrate_repository_service.rb +++ b/app/services/projects/hashed_storage/migrate_repository_service.rb @@ -39,6 +39,13 @@ module Projects yield end + # We'd need to keep track of project full path otherwise directory tree + # created with hashed storage enabled cannot be usefully imported using + # the import rake task. + if result + project.write_repository_config(:fullpath, project.full_path) + end + result end diff --git a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb index 3a3e47fd9c0..ded864beb1d 100644 --- a/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb +++ b/spec/services/projects/hashed_storage/migrate_repository_service_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' describe Projects::HashedStorage::MigrateRepositoryService do let(:gitlab_shell) { Gitlab::Shell.new } - let(:project) { create(:project, :empty_repo, :wiki_repo) } + let(:project) { create(:project, :repository, :wiki_repo) } let(:service) { described_class.new(project) } let(:legacy_storage) { Storage::LegacyProject.new(project) } let(:hashed_storage) { Storage::HashedProject.new(project) } @@ -33,6 +33,12 @@ describe Projects::HashedStorage::MigrateRepositoryService do service.execute end + + it 'writes project full path to .git/config' do + service.execute + + expect(project.repo.config['gitlab.fullpath']).to eq project.full_path + end end context 'when one move fails' do -- cgit v1.2.1 From d3d617354e0f0da7c8930dd9c089f437603dea20 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 19 Dec 2017 19:54:04 -0200 Subject: Does not write to .git/config when importing bare repositories --- app/services/projects/create_service.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 4c7e5370bbe..24ae50f8dc4 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -87,12 +87,12 @@ module Projects def after_create_actions log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"") - # We'd need to keep track of project full path otherwise directory tree - # created with hashed storage enabled cannot be usefully imported using - # the import rake task. - @project.write_repository_config(:fullpath, @project.full_path) - unless @project.gitlab_project_import? + # We'd need to keep track of project full path otherwise directory tree + # created with hashed storage enabled cannot be usefully imported using + # the import rake task. + @project.write_repository_config(:fullpath, @project.full_path) + @project.create_wiki unless skip_wiki? create_services_from_active_templates(@project) -- cgit v1.2.1 From 582678b5f5e1399603610b20149acf1d305309d3 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 19 Dec 2017 23:58:54 -0200 Subject: Import directory tree created with hashed storage using import rake task --- lib/gitlab/bare_repository_import/importer.rb | 9 +- lib/gitlab/bare_repository_import/repository.rb | 36 +++++-- .../gitlab/bare_repository_import/importer_spec.rb | 14 ++- .../bare_repository_import/repository_spec.rb | 119 +++++++++++++++------ 4 files changed, 129 insertions(+), 49 deletions(-) diff --git a/lib/gitlab/bare_repository_import/importer.rb b/lib/gitlab/bare_repository_import/importer.rb index 64e41d42709..1f0fdc6685e 100644 --- a/lib/gitlab/bare_repository_import/importer.rb +++ b/lib/gitlab/bare_repository_import/importer.rb @@ -14,13 +14,13 @@ module Gitlab repos_to_import.each do |repo_path| bare_repo = Gitlab::BareRepositoryImport::Repository.new(import_path, repo_path) - if bare_repo.hashed? || bare_repo.wiki? + unless bare_repo.processable? log " * Skipping repo #{bare_repo.repo_path}".color(:yellow) next end - log "Processing #{repo_path}".color(:yellow) + log "Processing #{repo_path} -> #{bare_repo.project_full_path}".color(:yellow) new(user, bare_repo).create_project_if_needed end @@ -62,6 +62,11 @@ module Gitlab if project.persisted? && mv_repo(project) log " * Created #{project.name} (#{project_full_path})".color(:green) + # We'd need to keep track of project full path otherwise directory tree + # created with hashed storage enabled cannot be usefully imported using + # the import rake task. + project.write_repository_config(:fullpath, project.full_path) + ProjectCacheWorker.perform_async(project.id) else log " * Failed trying to create #{project.name} (#{project_full_path})".color(:red) diff --git a/lib/gitlab/bare_repository_import/repository.rb b/lib/gitlab/bare_repository_import/repository.rb index fa7891c8dcc..b5907875460 100644 --- a/lib/gitlab/bare_repository_import/repository.rb +++ b/lib/gitlab/bare_repository_import/repository.rb @@ -6,39 +6,55 @@ module Gitlab def initialize(root_path, repo_path) @root_path = root_path @repo_path = repo_path - @root_path << '/' unless root_path.ends_with?('/') + full_path = + if hashed? && !wiki? + repository.config.get('gitlab.fullpath') + else + repo_relative_path + end + # Split path into 'all/the/namespaces' and 'project_name' - @group_path, _, @project_name = repo_relative_path.rpartition('/') + @group_path, _, @project_name = full_path.to_s.rpartition('/') end def wiki_exists? File.exist?(wiki_path) end - def wiki? - @wiki ||= repo_path.end_with?('.wiki.git') - end - def wiki_path @wiki_path ||= repo_path.sub(/\.git$/, '.wiki.git') end - def hashed? - @hashed ||= group_path.start_with?('@hashed') - end - def project_full_path @project_full_path ||= "#{group_path}/#{project_name}" end + def processable? + return false if wiki? + + group_path.present? && project_name.present? + end + private + def wiki? + @wiki ||= repo_path.end_with?('.wiki.git') + end + + def hashed? + @hashed ||= repo_relative_path.include?('@hashed') + end + def repo_relative_path # Remove root path and `.git` at the end repo_path[@root_path.size...-4] end + + def repository + @repository ||= Rugged::Repository.new(repo_path) + end end end end diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb index 82ac3c424d4..99cc9c4bd41 100644 --- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb @@ -1,6 +1,7 @@ require 'spec_helper' describe Gitlab::BareRepositoryImport::Importer, repository: true do + let(:gitlab_shell) { Gitlab::Shell.new } let!(:admin) { create(:admin) } let!(:base_dir) { Dir.mktmpdir + '/' } let(:bare_repository) { Gitlab::BareRepositoryImport::Repository.new(base_dir, File.join(base_dir, "#{project_path}.git")) } @@ -75,7 +76,7 @@ describe Gitlab::BareRepositoryImport::Importer, repository: true do end it 'creates the Git repo in disk' do - FileUtils.mkdir_p(File.join(base_dir, "#{project_path}.git")) + create_bare_repository("#{project_path}.git") importer.create_project_if_needed @@ -130,7 +131,7 @@ describe Gitlab::BareRepositoryImport::Importer, repository: true do end it 'creates the Git repo in disk' do - FileUtils.mkdir_p(File.join(base_dir, "#{project_path}.git")) + create_bare_repository("#{project_path}.git") importer.create_project_if_needed @@ -165,8 +166,8 @@ describe Gitlab::BareRepositoryImport::Importer, repository: true do it_behaves_like 'importing a repository' it 'creates the Wiki git repo in disk' do - FileUtils.mkdir_p(File.join(base_dir, "#{project_path}.git")) - FileUtils.mkdir_p(File.join(base_dir, "#{project_path}.wiki.git")) + create_bare_repository("#{project_path}.git") + create_bare_repository("#{project_path}.wiki.git") expect(Projects::CreateService).to receive(:new).with(admin, hash_including(skip_wiki: true, import_type: 'bare_repository')).and_call_original @@ -192,4 +193,9 @@ describe Gitlab::BareRepositoryImport::Importer, repository: true do end end end + + def create_bare_repository(project_path) + repo_path = File.join(base_dir, project_path) + Gitlab::Git::Repository.create(repo_path, bare: true) + end end diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb index 61b73abcba4..770e26bbf6a 100644 --- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb @@ -1,58 +1,111 @@ require 'spec_helper' describe ::Gitlab::BareRepositoryImport::Repository do - let(:project_repo_path) { described_class.new('/full/path/', '/full/path/to/repo.git') } + context 'legacy storage' do + subject { described_class.new('/full/path/', '/full/path/to/repo.git') } - it 'stores the repo path' do - expect(project_repo_path.repo_path).to eq('/full/path/to/repo.git') - end + it 'stores the repo path' do + expect(subject.repo_path).to eq('/full/path/to/repo.git') + end - it 'stores the group path' do - expect(project_repo_path.group_path).to eq('to') - end + it 'stores the group path' do + expect(subject.group_path).to eq('to') + end - it 'stores the project name' do - expect(project_repo_path.project_name).to eq('repo') - end + it 'stores the project name' do + expect(subject.project_name).to eq('repo') + end - it 'stores the wiki path' do - expect(project_repo_path.wiki_path).to eq('/full/path/to/repo.wiki.git') - end + it 'stores the wiki path' do + expect(subject.wiki_path).to eq('/full/path/to/repo.wiki.git') + end + + describe '#processable?' do + it 'returns false if it is a wiki' do + subject = described_class.new('/full/path/', '/full/path/to/a/b/my.wiki.git') - describe '#wiki?' do - it 'returns true if it is a wiki' do - wiki_path = described_class.new('/full/path/', '/full/path/to/a/b/my.wiki.git') + expect(subject.processable?).to eq(false) + end - expect(wiki_path.wiki?).to eq(true) + it 'returns true when group and project name are present' do + expect(subject.processable?).to eq(true) + end end - it 'returns false if it is not a wiki' do - expect(project_repo_path.wiki?).to eq(false) + describe '#project_full_path' do + it 'returns the project full path' do + expect(subject.repo_path).to eq('/full/path/to/repo.git') + expect(subject.project_full_path).to eq('to/repo') + end + + it 'with no trailing slash in the root path' do + repo_path = described_class.new('/full/path', '/full/path/to/repo.git') + + expect(repo_path.project_full_path).to eq('to/repo') + end end end - describe '#hashed?' do - it 'returns true if it is a hashed folder' do - path = described_class.new('/full/path/', '/full/path/@hashed/my.repo.git') + context 'hashed storage' do + let(:gitlab_shell) { Gitlab::Shell.new } + let(:repository_storage) { 'default' } + let(:root_path) { Gitlab.config.repositories.storages[repository_storage]['path'] } + let(:hash) { '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b' } + let(:hashed_path) { "@hashed/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b" } + let(:repo_path) { File.join(root_path, "#{hashed_path}.git") } + let(:wiki_path) { File.join(root_path, "#{hashed_path}.wiki.git") } - expect(path.hashed?).to eq(true) + before do + gitlab_shell.add_repository(repository_storage, hashed_path) + repository = Rugged::Repository.new(repo_path) + repository.config['gitlab.fullpath'] = 'to/repo' end - it 'returns false if it is not a hashed folder' do - expect(project_repo_path.hashed?).to eq(false) + after do + gitlab_shell.remove_repository(root_path, hashed_path) end - end - describe '#project_full_path' do - it 'returns the project full path' do - expect(project_repo_path.repo_path).to eq('/full/path/to/repo.git') - expect(project_repo_path.project_full_path).to eq('to/repo') + subject { described_class.new(root_path, repo_path) } + + it 'stores the repo path' do + expect(subject.repo_path).to eq(repo_path) + end + + it 'stores the wiki path' do + expect(subject.wiki_path).to eq(wiki_path) + end + + it 'reads the group path from .git/config' do + expect(subject.group_path).to eq('to') + end + + it 'reads the project name from .git/config' do + expect(subject.project_name).to eq('repo') end - it 'with no trailing slash in the root path' do - repo_path = described_class.new('/full/path', '/full/path/to/repo.git') + describe '#processable?' do + it 'returns false if it is a wiki' do + subject = described_class.new(root_path, wiki_path) + + expect(subject.processable?).to eq(false) + end + + it 'returns false when group and project name are missing' do + repository = Rugged::Repository.new(repo_path) + repository.config.delete('gitlab.fullpath') + + expect(subject.processable?).to eq(false) + end + + it 'returns true when group and project name are present' do + expect(subject.processable?).to eq(true) + end + end - expect(repo_path.project_full_path).to eq('to/repo') + describe '#project_full_path' do + it 'returns the project full path' do + expect(subject.project_full_path).to eq('to/repo') + end end end end -- cgit v1.2.1 From c328a7db75b601bed449ad04100e12779f5bdb36 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 20 Dec 2017 14:22:13 -0200 Subject: Handle exceptions when writing to .git/config --- app/models/project.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/models/project.rb b/app/models/project.rb index 1182dbda0c0..47ca62aa5bb 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1440,6 +1440,9 @@ class Project < ActiveRecord::Base def write_repository_config(key, value, prefix: :gitlab) key = [prefix, key].compact.join('.') repo.config[key] = value + rescue Gitlab::Git::Repository::NoRepository => e + Rails.logger.error("Error writing key #{key} to .git/config for project #{full_path} (#{id}): #{e.message}.") + nil end def rename_repo_notify! -- cgit v1.2.1 From 603fcb6b9ca0aec1a5f8d75fa16d626dd8058269 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 20 Dec 2017 15:40:59 -0200 Subject: Add CHANGELOG --- .../da-handle-hashed-storage-repos-using-repo-import-task.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/da-handle-hashed-storage-repos-using-repo-import-task.yml diff --git a/changelogs/unreleased/da-handle-hashed-storage-repos-using-repo-import-task.yml b/changelogs/unreleased/da-handle-hashed-storage-repos-using-repo-import-task.yml new file mode 100644 index 00000000000..74a00d49ab3 --- /dev/null +++ b/changelogs/unreleased/da-handle-hashed-storage-repos-using-repo-import-task.yml @@ -0,0 +1,5 @@ +--- +title: Handle GitLab hashed storage repositories using the repo import task +merge_request: +author: +type: added -- cgit v1.2.1 From 9d575acc5b46be7e0b76ccc763997412cd278ef0 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Tue, 19 Dec 2017 17:06:38 -0200 Subject: Fix TestEnv.copy_repo to use disk_path instead of full_path --- spec/models/namespace_spec.rb | 2 +- spec/models/project_spec.rb | 2 +- spec/support/test_env.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 0a99485ec8e..0678cae9b93 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -203,7 +203,7 @@ describe Namespace do context 'with subgroups' do let(:parent) { create(:group, name: 'parent', path: 'parent') } let(:child) { create(:group, name: 'child', path: 'child', parent: parent) } - let!(:project) { create(:project_empty_repo, path: 'the-project', namespace: child) } + let!(:project) { create(:project_empty_repo, path: 'the-project', namespace: child, skip_disk_validation: true) } let(:uploads_dir) { File.join(CarrierWave.root, FileUploader.base_dir) } let(:pages_dir) { File.join(TestEnv.pages_path) } diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 10634d22b39..62029f385a2 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2676,7 +2676,7 @@ describe Project do end context 'hashed storage' do - let(:project) { create(:project, :repository) } + let(:project) { create(:project, :repository, skip_disk_validation: true) } let(:gitlab_shell) { Gitlab::Shell.new } let(:hash) { '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b' } diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb index ffc051a3fff..1d99746b09f 100644 --- a/spec/support/test_env.rb +++ b/spec/support/test_env.rb @@ -215,7 +215,7 @@ module TestEnv end def copy_repo(project, bare_repo:, refs:) - target_repo_path = File.expand_path(project.repository_storage_path + "/#{project.full_path}.git") + target_repo_path = File.expand_path(project.repository_storage_path + "/#{project.disk_path}.git") FileUtils.mkdir_p(target_repo_path) FileUtils.cp_r("#{File.expand_path(bare_repo)}/.", target_repo_path) FileUtils.chmod_R 0755, target_repo_path -- cgit v1.2.1 From 93eba91df9af083ea80b3b8ab01986efdeec43a0 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 21 Dec 2017 13:58:36 -0200 Subject: Refactoring Project#write_repository_config --- app/models/namespace.rb | 2 +- app/models/project.rb | 15 +++++++-------- app/services/projects/create_service.rb | 6 +----- .../hashed_storage/migrate_repository_service.rb | 9 ++------- app/services/projects/transfer_service.rb | 5 +---- lib/gitlab/bare_repository_import/importer.rb | 7 ++----- spec/models/project_spec.rb | 22 +++++++++++----------- 7 files changed, 25 insertions(+), 41 deletions(-) diff --git a/app/models/namespace.rb b/app/models/namespace.rb index d983b2f106b..efbfc607040 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -272,7 +272,7 @@ class Namespace < ActiveRecord::Base def write_projects_full_path_config all_projects.each do |project| project.expires_full_path_cache # we need to clear cache to validate renames correctly - project.write_repository_config(:fullpath, project.full_path) + project.write_repository_config end end end diff --git a/app/models/project.rb b/app/models/project.rb index 47ca62aa5bb..9c0bbf697e2 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -1420,10 +1420,7 @@ class Project < ActiveRecord::Base end def after_rename_repo - # We'd need to keep track of project full path otherwise directory tree - # created with hashed storage enabled cannot be usefully imported using - # the import rake task. - write_repository_config(:fullpath, full_path) + write_repository_config path_before_change = previous_changes['path'].first @@ -1437,11 +1434,13 @@ class Project < ActiveRecord::Base Gitlab::PagesTransfer.new.rename_project(path_before_change, self.path, namespace.full_path) end - def write_repository_config(key, value, prefix: :gitlab) - key = [prefix, key].compact.join('.') - repo.config[key] = value + def write_repository_config(gl_full_path: full_path) + # We'd need to keep track of project full path otherwise directory tree + # created with hashed storage enabled cannot be usefully imported using + # the import rake task. + repo.config['gitlab.fullpath'] = gl_full_path rescue Gitlab::Git::Repository::NoRepository => e - Rails.logger.error("Error writing key #{key} to .git/config for project #{full_path} (#{id}): #{e.message}.") + Rails.logger.error("Error writing to .git/config for project #{full_path} (#{id}): #{e.message}.") nil end diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 24ae50f8dc4..01838ec6b5d 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -88,11 +88,7 @@ module Projects log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"") unless @project.gitlab_project_import? - # We'd need to keep track of project full path otherwise directory tree - # created with hashed storage enabled cannot be usefully imported using - # the import rake task. - @project.write_repository_config(:fullpath, @project.full_path) - + @project.write_repository_config @project.create_wiki unless skip_wiki? create_services_from_active_templates(@project) diff --git a/app/services/projects/hashed_storage/migrate_repository_service.rb b/app/services/projects/hashed_storage/migrate_repository_service.rb index c076ce06278..b6763c9436f 100644 --- a/app/services/projects/hashed_storage/migrate_repository_service.rb +++ b/app/services/projects/hashed_storage/migrate_repository_service.rb @@ -30,6 +30,8 @@ module Projects unless result rollback_folder_move project.storage_version = nil + else + project.write_repository_config end project.repository_read_only = false @@ -39,13 +41,6 @@ module Projects yield end - # We'd need to keep track of project full path otherwise directory tree - # created with hashed storage enabled cannot be usefully imported using - # the import rake task. - if result - project.write_repository_config(:fullpath, project.full_path) - end - result end diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index e742df5f696..14cf9f82bdb 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -101,10 +101,7 @@ module Projects end def write_repository_config(full_path) - # We'd need to keep track of project full path otherwise directory tree - # created with hashed storage enabled cannot be usefully imported using - # the import rake task. - project.write_repository_config(:fullpath, full_path) + project.write_repository_config(:gl_fullpath, full_path) end def refresh_permissions diff --git a/lib/gitlab/bare_repository_import/importer.rb b/lib/gitlab/bare_repository_import/importer.rb index 1f0fdc6685e..709a901aa77 100644 --- a/lib/gitlab/bare_repository_import/importer.rb +++ b/lib/gitlab/bare_repository_import/importer.rb @@ -20,7 +20,7 @@ module Gitlab next end - log "Processing #{repo_path} -> #{bare_repo.project_full_path}".color(:yellow) + log "Processing #{repo_path}".color(:yellow) new(user, bare_repo).create_project_if_needed end @@ -62,10 +62,7 @@ module Gitlab if project.persisted? && mv_repo(project) log " * Created #{project.name} (#{project_full_path})".color(:green) - # We'd need to keep track of project full path otherwise directory tree - # created with hashed storage enabled cannot be usefully imported using - # the import rake task. - project.write_repository_config(:fullpath, project.full_path) + project.write_repository_config ProjectCacheWorker.perform_async(project.id) else diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 62029f385a2..1d4b68bdf8d 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -2630,9 +2630,9 @@ describe Project do it 'updates project full path in .git/config' do allow(project_storage).to receive(:rename_repo).and_return(true) - expect(project).to receive(:write_repository_config).with(:fullpath, project.full_path) - project.rename_repo + + expect(project.repo.config['gitlab.fullpath']).to eq(project.full_path) end end @@ -2678,12 +2678,10 @@ describe Project do context 'hashed storage' do let(:project) { create(:project, :repository, skip_disk_validation: true) } let(:gitlab_shell) { Gitlab::Shell.new } - let(:hash) { '6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b' } + let(:hash) { Digest::SHA2.hexdigest(project.id.to_s) } before do stub_application_setting(hashed_storage_enabled: true) - allow(Digest::SHA2).to receive(:hexdigest) { hash } - allow(project).to receive(:gitlab_shell).and_return(gitlab_shell) end describe '#legacy_storage?' do @@ -2706,13 +2704,13 @@ describe Project do describe '#base_dir' do it 'returns base_dir based on hash of project id' do - expect(project.base_dir).to eq('@hashed/6b/86') + expect(project.base_dir).to eq("@hashed/#{hash[0..1]}/#{hash[2..3]}") end end describe '#disk_path' do it 'returns disk_path based on hash of project id' do - hashed_path = '@hashed/6b/86/6b86b273ff34fce19d6b804eff5a3f5747ada4eaa22f1d49c01e52ddb7875b4b' + hashed_path = "@hashed/#{hash[0..1]}/#{hash[2..3]}/#{hash}" expect(project.disk_path).to eq(hashed_path) end @@ -2720,7 +2718,9 @@ describe Project do describe '#ensure_storage_path_exists' do it 'delegates to gitlab_shell to ensure namespace is created' do - expect(gitlab_shell).to receive(:add_namespace).with(project.repository_storage_path, '@hashed/6b/86') + allow(project).to receive(:gitlab_shell).and_return(gitlab_shell) + + expect(gitlab_shell).to receive(:add_namespace).with(project.repository_storage_path, "@hashed/#{hash[0..1]}/#{hash[2..3]}") project.ensure_storage_path_exists end @@ -2780,7 +2780,7 @@ describe Project do end context 'when not rolled out' do - let(:project) { create(:project, :repository, storage_version: 1) } + let(:project) { create(:project, :repository, storage_version: 1, skip_disk_validation: true) } it 'moves pages folder to new location' do expect_any_instance_of(Gitlab::UploadsTransfer).to receive(:rename_project) @@ -2791,9 +2791,9 @@ describe Project do end it 'updates project full path in .git/config' do - expect(project).to receive(:write_repository_config).with(:fullpath, project.full_path) - project.rename_repo + + expect(project.repo.config['gitlab.fullpath']).to eq(project.full_path) end end -- cgit v1.2.1 From fcb967ac672e224737f6e170693e45331eb4d636 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 21 Dec 2017 15:32:08 -0200 Subject: Write projects config to all projects inside namespace in batches --- app/models/concerns/storage/legacy_namespace.rb | 2 +- app/models/namespace.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/concerns/storage/legacy_namespace.rb b/app/models/concerns/storage/legacy_namespace.rb index 22b9ef4e338..99dbd4fbacf 100644 --- a/app/models/concerns/storage/legacy_namespace.rb +++ b/app/models/concerns/storage/legacy_namespace.rb @@ -34,7 +34,7 @@ module Storage # So we basically we mute exceptions in next actions begin send_update_instructions - write_projects_full_path_config + write_projects_repository_config true rescue diff --git a/app/models/namespace.rb b/app/models/namespace.rb index efbfc607040..bdcc9159d26 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -269,8 +269,8 @@ class Namespace < ActiveRecord::Base RedirectRoute.permanent.exists?(path: path) end - def write_projects_full_path_config - all_projects.each do |project| + def write_projects_repository_config + all_projects.find_each do |project| project.expires_full_path_cache # we need to clear cache to validate renames correctly project.write_repository_config end -- cgit v1.2.1 From 2af3400c4eeb0227ca6f38117323a18e9fbd7d9b Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 21 Dec 2017 16:04:58 -0200 Subject: Add spec for Project#write_repository_config --- spec/models/project_spec.rb | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 1d4b68bdf8d..cea22bbd184 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -3155,4 +3155,26 @@ describe Project do it { is_expected.to eq(platform_kubernetes) } end end + + describe '#write_repository_config' do + set(:project) { create(:project, :repository) } + + it 'writes full path in .git/config when key is missing' do + project.write_repository_config + + expect(project.repo.config['gitlab.fullpath']).to eq project.full_path + end + + it 'updates full path in .git/config when key is present' do + project.write_repository_config(gl_full_path: 'old/path') + + expect { project.write_repository_config }.to change { project.repo.config['gitlab.fullpath'] }.from('old/path').to(project.full_path) + end + + it 'does not raise an error with an empty repository' do + project = create(:project_empty_repo) + + expect { project.write_repository_config }.not_to raise_error + end + end end -- cgit v1.2.1 From 58c2f3b5796cd8349dbd90404ed6ad0ca3ee6caf Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 21 Dec 2017 16:49:32 -0200 Subject: Fix Repository#processable? to allow .git repos in the root folder --- lib/gitlab/bare_repository_import/repository.rb | 3 ++- spec/lib/gitlab/bare_repository_import/repository_spec.rb | 10 ++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/bare_repository_import/repository.rb b/lib/gitlab/bare_repository_import/repository.rb index b5907875460..85b79362196 100644 --- a/lib/gitlab/bare_repository_import/repository.rb +++ b/lib/gitlab/bare_repository_import/repository.rb @@ -33,8 +33,9 @@ module Gitlab def processable? return false if wiki? + return false if hashed? && (group_path.blank? || project_name.blank?) - group_path.present? && project_name.present? + true end private diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb index 770e26bbf6a..e0b7d16ebb7 100644 --- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb @@ -27,7 +27,13 @@ describe ::Gitlab::BareRepositoryImport::Repository do expect(subject.processable?).to eq(false) end - it 'returns true when group and project name are present' do + it 'returns true if group path is missing' do + subject = described_class.new('/full/path/', '/full/path/repo.git') + + expect(subject.processable?).to eq(true) + end + + it 'returns true when group path and project name are present' do expect(subject.processable?).to eq(true) end end @@ -97,7 +103,7 @@ describe ::Gitlab::BareRepositoryImport::Repository do expect(subject.processable?).to eq(false) end - it 'returns true when group and project name are present' do + it 'returns true when group path and project name are present' do expect(subject.processable?).to eq(true) end end -- cgit v1.2.1 From 6c95c771fb9a3c71d176aa996b82096a8e0a3f7a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Thu, 21 Dec 2017 17:08:00 -0200 Subject: Fix Projects::TransferService#write_repository_config method --- app/services/projects/transfer_service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index 14cf9f82bdb..26765e5c3f3 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -101,7 +101,7 @@ module Projects end def write_repository_config(full_path) - project.write_repository_config(:gl_fullpath, full_path) + project.write_repository_config(gl_full_path: full_path) end def refresh_permissions -- cgit v1.2.1 From 62ee2ccfcc1d765cf2b80ba8f7a226855f2f8a2f Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Fri, 22 Dec 2017 11:28:46 -0200 Subject: Refactoring spec for Gitlab::BareRepositoryImport::Repository --- .../gitlab/bare_repository_import/repository_spec.rb | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb index e0b7d16ebb7..39f2cfe6175 100644 --- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb @@ -39,15 +39,14 @@ describe ::Gitlab::BareRepositoryImport::Repository do end describe '#project_full_path' do - it 'returns the project full path' do - expect(subject.repo_path).to eq('/full/path/to/repo.git') + it 'returns the project full path with trailing slash in the root path' do expect(subject.project_full_path).to eq('to/repo') end - it 'with no trailing slash in the root path' do - repo_path = described_class.new('/full/path', '/full/path/to/repo.git') + it 'returns the project full path with no trailing slash in the root path' do + subject = described_class.new('/full/path', '/full/path/to/repo.git') - expect(repo_path.project_full_path).to eq('to/repo') + expect(subject.project_full_path).to eq('to/repo') end end end @@ -109,7 +108,13 @@ describe ::Gitlab::BareRepositoryImport::Repository do end describe '#project_full_path' do - it 'returns the project full path' do + it 'returns the project full path with trailing slash in the root path' do + expect(subject.project_full_path).to eq('to/repo') + end + + it 'returns the project full path with no trailing slash in the root path' do + subject = described_class.new(root_path[0...-1], repo_path) + expect(subject.project_full_path).to eq('to/repo') end end -- cgit v1.2.1 From 3f5403ab74b2f60c1a306a2f617d1cd323854c7a Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 3 Jan 2018 16:17:11 -0200 Subject: Remove unused variable from bare repository importer spec --- spec/lib/gitlab/bare_repository_import/importer_spec.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/spec/lib/gitlab/bare_repository_import/importer_spec.rb b/spec/lib/gitlab/bare_repository_import/importer_spec.rb index 99cc9c4bd41..b5d86df09d2 100644 --- a/spec/lib/gitlab/bare_repository_import/importer_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/importer_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Gitlab::BareRepositoryImport::Importer, repository: true do - let(:gitlab_shell) { Gitlab::Shell.new } let!(:admin) { create(:admin) } let!(:base_dir) { Dir.mktmpdir + '/' } let(:bare_repository) { Gitlab::BareRepositoryImport::Repository.new(base_dir, File.join(base_dir, "#{project_path}.git")) } -- cgit v1.2.1 From 08de4746dc03e7f090546063711153e99de344ae Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 3 Jan 2018 16:22:00 -0200 Subject: Refactoring Gitlab::BareRepositoryImport::Repository --- spec/lib/gitlab/bare_repository_import/repository_spec.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/lib/gitlab/bare_repository_import/repository_spec.rb b/spec/lib/gitlab/bare_repository_import/repository_spec.rb index 39f2cfe6175..9f42cf1dfca 100644 --- a/spec/lib/gitlab/bare_repository_import/repository_spec.rb +++ b/spec/lib/gitlab/bare_repository_import/repository_spec.rb @@ -24,17 +24,17 @@ describe ::Gitlab::BareRepositoryImport::Repository do it 'returns false if it is a wiki' do subject = described_class.new('/full/path/', '/full/path/to/a/b/my.wiki.git') - expect(subject.processable?).to eq(false) + expect(subject).not_to be_processable end it 'returns true if group path is missing' do subject = described_class.new('/full/path/', '/full/path/repo.git') - expect(subject.processable?).to eq(true) + expect(subject).to be_processable end it 'returns true when group path and project name are present' do - expect(subject.processable?).to eq(true) + expect(subject).to be_processable end end @@ -92,18 +92,18 @@ describe ::Gitlab::BareRepositoryImport::Repository do it 'returns false if it is a wiki' do subject = described_class.new(root_path, wiki_path) - expect(subject.processable?).to eq(false) + expect(subject).not_to be_processable end it 'returns false when group and project name are missing' do repository = Rugged::Repository.new(repo_path) repository.config.delete('gitlab.fullpath') - expect(subject.processable?).to eq(false) + expect(subject).not_to be_processable end it 'returns true when group path and project name are present' do - expect(subject.processable?).to eq(true) + expect(subject).to be_processable end end -- cgit v1.2.1 From 540a2b67098782842cfdd98b4177a29ac3c81ebf Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Tue, 2 Jan 2018 15:07:13 +0200 Subject: Move 2FA disable button - Removed disable button from /profile/account - Added disable button to /profile/two_factor_auth - Changed 2FA breadcrumb from 'User Settings > Account > Account' to 'User Settings > Account > Two-Factor Authentication' --- app/views/profiles/accounts/show.html.haml | 4 ---- app/views/profiles/two_factor_auths/show.html.haml | 9 +++++++-- changelogs/unreleased/fix-move-2fa-disable-button.yml | 5 +++++ spec/features/u2f_spec.rb | 2 +- 4 files changed, 13 insertions(+), 7 deletions(-) create mode 100644 changelogs/unreleased/fix-move-2fa-disable-button.yml diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index ced58dffcdc..f1313b79589 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -17,10 +17,6 @@ Status: #{current_user.two_factor_enabled? ? 'Enabled' : 'Disabled'} - if current_user.two_factor_enabled? = link_to 'Manage two-factor authentication', profile_two_factor_auth_path, class: 'btn btn-info' - = link_to 'Disable', profile_two_factor_auth_path, - method: :delete, - data: { confirm: "Are you sure? This will invalidate your registered applications and U2F devices." }, - class: 'btn btn-danger' - else .append-bottom-10 = link_to 'Enable two-factor authentication', profile_two_factor_auth_path, class: 'btn btn-success' diff --git a/app/views/profiles/two_factor_auths/show.html.haml b/app/views/profiles/two_factor_auths/show.html.haml index 0b03276efcc..5207dac3ac2 100644 --- a/app/views/profiles/two_factor_auths/show.html.haml +++ b/app/views/profiles/two_factor_auths/show.html.haml @@ -1,5 +1,5 @@ - page_title 'Two-Factor Authentication', 'Account' -- add_to_breadcrumbs("Account", profile_account_path) +- add_to_breadcrumbs("Two-Factor Authentication", profile_account_path) - @content_class = "limit-container-width" unless fluid_layout = render 'profiles/head' @@ -18,7 +18,12 @@ Use an app on your mobile device to enable two-factor authentication (2FA). .col-lg-8 - if current_user.two_factor_otp_enabled? - = icon "check inverse", base: "circle", class: "text-success", text: "You've already enabled two-factor authentication using mobile authenticator applications. You can disable it from your account settings page." + %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. + = link_to 'Disable two-factor authentication', profile_two_factor_auth_path, + method: :delete, + data: { confirm: "Are you sure? This will invalidate your registered applications and U2F devices." }, + class: 'btn btn-danger' - else %p Download the Google Authenticator application from App Store or Google Play Store and scan this code. diff --git a/changelogs/unreleased/fix-move-2fa-disable-button.yml b/changelogs/unreleased/fix-move-2fa-disable-button.yml new file mode 100644 index 00000000000..bac98ad5148 --- /dev/null +++ b/changelogs/unreleased/fix-move-2fa-disable-button.yml @@ -0,0 +1,5 @@ +--- +title: Move 2FA disable button +merge_request: 16177 +author: George Tsiolis +type: fixed diff --git a/spec/features/u2f_spec.rb b/spec/features/u2f_spec.rb index c9afef2a8de..50ee1656e10 100644 --- a/spec/features/u2f_spec.rb +++ b/spec/features/u2f_spec.rb @@ -264,7 +264,7 @@ feature 'Using U2F (Universal 2nd Factor) Devices for Authentication', :js do end it "deletes u2f registrations" do - visit profile_account_path + visit profile_two_factor_auth_path expect do accept_confirm { click_on "Disable" } end.to change { U2fRegistration.count }.by(-1) -- cgit v1.2.1 From 14336c0bda493a870ffeed86379b274c522fe804 Mon Sep 17 00:00:00 2001 From: Douglas Barbosa Alexandre Date: Wed, 3 Jan 2018 16:26:59 -0200 Subject: Use if instead of unless on Projects::HashedStorage::MigrateRepositoryService --- app/services/projects/hashed_storage/migrate_repository_service.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/services/projects/hashed_storage/migrate_repository_service.rb b/app/services/projects/hashed_storage/migrate_repository_service.rb index b6763c9436f..67178de75de 100644 --- a/app/services/projects/hashed_storage/migrate_repository_service.rb +++ b/app/services/projects/hashed_storage/migrate_repository_service.rb @@ -27,11 +27,11 @@ module Projects result &&= move_repository("#{@old_wiki_disk_path}", "#{@new_disk_path}.wiki") end - unless result + if result + project.write_repository_config + else rollback_folder_move project.storage_version = nil - else - project.write_repository_config end project.repository_read_only = false -- cgit v1.2.1 From fd8e284dde6a263e7f0364b36c642c92f3ad31a4 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Wed, 3 Jan 2018 11:36:18 -0700 Subject: Add left margin to definition elements --- app/assets/stylesheets/framework/typography.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/assets/stylesheets/framework/typography.scss b/app/assets/stylesheets/framework/typography.scss index 11c1aeea871..d0999e60e65 100644 --- a/app/assets/stylesheets/framework/typography.scss +++ b/app/assets/stylesheets/framework/typography.scss @@ -178,6 +178,10 @@ font-weight: inherit; } + dd { + margin-left: $gl-padding; + } + ul, ol { padding: 0; -- cgit v1.2.1 From 78cdac8401375cc85be54ae68e5d94d02a90233c Mon Sep 17 00:00:00 2001 From: Luc Didry Date: Wed, 3 Jan 2018 17:32:34 +0100 Subject: Expose project_id on /api/v4/pages/domains --- changelogs/unreleased/api-domains-expose-project_id.yml | 5 +++++ doc/api/pages_domains.md | 1 + lib/api/entities.rb | 1 + spec/fixtures/api/schemas/public_api/v4/pages_domain/basic.json | 3 ++- spec/requests/api/pages_domains_spec.rb | 1 + 5 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/api-domains-expose-project_id.yml diff --git a/changelogs/unreleased/api-domains-expose-project_id.yml b/changelogs/unreleased/api-domains-expose-project_id.yml new file mode 100644 index 00000000000..22617ffe9b5 --- /dev/null +++ b/changelogs/unreleased/api-domains-expose-project_id.yml @@ -0,0 +1,5 @@ +--- +title: Expose project_id on /api/v4/pages/domains +merge_request: 16200 +author: Luc Didry +type: changed diff --git a/doc/api/pages_domains.md b/doc/api/pages_domains.md index 50685f335f7..20275b902c6 100644 --- a/doc/api/pages_domains.md +++ b/doc/api/pages_domains.md @@ -21,6 +21,7 @@ curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/a { "domain": "ssl.domain.example", "url": "https://ssl.domain.example", + "project_id": 1337, "certificate": { "expired": false, "expiration": "2020-04-12T14:32:00.000Z" diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 4ad4a1f7867..270b456597d 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1133,6 +1133,7 @@ module API class PagesDomainBasic < Grape::Entity expose :domain expose :url + expose :project_id expose :certificate, as: :certificate_expiration, if: ->(pages_domain, _) { pages_domain.certificate? }, diff --git a/spec/fixtures/api/schemas/public_api/v4/pages_domain/basic.json b/spec/fixtures/api/schemas/public_api/v4/pages_domain/basic.json index 4ba6422406c..e8c17298b43 100644 --- a/spec/fixtures/api/schemas/public_api/v4/pages_domain/basic.json +++ b/spec/fixtures/api/schemas/public_api/v4/pages_domain/basic.json @@ -3,6 +3,7 @@ "properties": { "domain": { "type": "string" }, "url": { "type": "uri" }, + "project_id": { "type": "integer" }, "certificate_expiration": { "type": "object", "properties": { @@ -13,6 +14,6 @@ "additionalProperties": false } }, - "required": ["domain", "url"], + "required": ["domain", "url", "project_id"], "additionalProperties": false } diff --git a/spec/requests/api/pages_domains_spec.rb b/spec/requests/api/pages_domains_spec.rb index d412b045e9f..5d01dc37f0e 100644 --- a/spec/requests/api/pages_domains_spec.rb +++ b/spec/requests/api/pages_domains_spec.rb @@ -46,6 +46,7 @@ describe API::PagesDomains do expect(json_response).to be_an Array expect(json_response.size).to eq(3) expect(json_response.last).to have_key('domain') + expect(json_response.last).to have_key('project_id') expect(json_response.last).to have_key('certificate_expiration') expect(json_response.last['certificate_expiration']['expired']).to be true expect(json_response.first).not_to have_key('certificate_expiration') -- cgit v1.2.1 From 4963f39141208b91be52f7ae6dbdc2d885b4db3c Mon Sep 17 00:00:00 2001 From: George Tsiolis Date: Wed, 3 Jan 2018 21:04:42 +0200 Subject: Fix dashboard projects nav links height --- app/views/dashboard/projects/_nav.html.haml | 2 +- changelogs/unreleased/fix-dashboard-projects-nav-links-height.yml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/fix-dashboard-projects-nav-links-height.yml diff --git a/app/views/dashboard/projects/_nav.html.haml b/app/views/dashboard/projects/_nav.html.haml index 3701e1c0578..c18077bc66f 100644 --- a/app/views/dashboard/projects/_nav.html.haml +++ b/app/views/dashboard/projects/_nav.html.haml @@ -1,4 +1,4 @@ -.top-area +.nav-block %ul.nav-links = nav_link(html_options: { class: ("active" unless params[:personal].present?) }) do = link_to s_('DashboardProjects|All'), dashboard_projects_path diff --git a/changelogs/unreleased/fix-dashboard-projects-nav-links-height.yml b/changelogs/unreleased/fix-dashboard-projects-nav-links-height.yml new file mode 100644 index 00000000000..2f6a07bb234 --- /dev/null +++ b/changelogs/unreleased/fix-dashboard-projects-nav-links-height.yml @@ -0,0 +1,5 @@ +--- +title: Fix dashboard projects nav links height +merge_request: 16204 +author: George Tsiolis +type: fixed -- cgit v1.2.1 From f635277228c4ac90bd7215db741392df1998ddfc Mon Sep 17 00:00:00 2001 From: Michael Kozono Date: Wed, 3 Jan 2018 12:03:52 -0800 Subject: Make DeleteConflictingRedirectRoutes no-op Both the post-deploy and background migration. --- .../mk-no-op-delete-conflicting-redirects.yml | 6 ++++ ...907170235_delete_conflicting_redirect_routes.rb | 28 ++--------------- .../delete_conflicting_redirect_routes_range.rb | 36 ++-------------------- ...elete_conflicting_redirect_routes_range_spec.rb | 13 +++----- .../delete_conflicting_redirect_routes_spec.rb | 22 ++----------- 5 files changed, 17 insertions(+), 88 deletions(-) create mode 100644 changelogs/unreleased/mk-no-op-delete-conflicting-redirects.yml diff --git a/changelogs/unreleased/mk-no-op-delete-conflicting-redirects.yml b/changelogs/unreleased/mk-no-op-delete-conflicting-redirects.yml new file mode 100644 index 00000000000..37fdb1df6df --- /dev/null +++ b/changelogs/unreleased/mk-no-op-delete-conflicting-redirects.yml @@ -0,0 +1,6 @@ +--- +title: Prevent excessive DB load due to faulty DeleteConflictingRedirectRoutes background + migration +merge_request: 16205 +author: +type: fixed diff --git a/db/post_migrate/20170907170235_delete_conflicting_redirect_routes.rb b/db/post_migrate/20170907170235_delete_conflicting_redirect_routes.rb index 3e84b295be4..033019c398e 100644 --- a/db/post_migrate/20170907170235_delete_conflicting_redirect_routes.rb +++ b/db/post_migrate/20170907170235_delete_conflicting_redirect_routes.rb @@ -2,36 +2,12 @@ # for more information on how to write migrations for GitLab. class DeleteConflictingRedirectRoutes < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - MIGRATION = 'DeleteConflictingRedirectRoutesRange'.freeze - BATCH_SIZE = 200 # At 200, I expect under 20s per batch, which is under our query timeout of 60s. - DELAY_INTERVAL = 12.seconds - - disable_ddl_transaction! - - class Route < ActiveRecord::Base - include EachBatch - - self.table_name = 'routes' - end - def up - say opening_message - - queue_background_migration_jobs_by_range_at_intervals(Route, MIGRATION, DELAY_INTERVAL, batch_size: BATCH_SIZE) + # No-op. + # See https://gitlab.com/gitlab-com/infrastructure/issues/3460#note_53223252 end def down # nothing end - - def opening_message - <<~MSG - Clean up redirect routes that conflict with regular routes. - See initial bug fix: - https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/13357 - MSG - end end diff --git a/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range.rb b/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range.rb index a1af045a71f..21b626dde56 100644 --- a/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range.rb +++ b/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range.rb @@ -1,44 +1,12 @@ # frozen_string_literal: true -# rubocop:disable Metrics/LineLength # rubocop:disable Style/Documentation module Gitlab module BackgroundMigration class DeleteConflictingRedirectRoutesRange - class Route < ActiveRecord::Base - self.table_name = 'routes' - end - - class RedirectRoute < ActiveRecord::Base - self.table_name = 'redirect_routes' - end - - # start_id - The start ID of the range of events to process - # end_id - The end ID of the range to process. def perform(start_id, end_id) - return unless migrate? - - conflicts = RedirectRoute.where(routes_match_redirects_clause(start_id, end_id)) - num_rows = conflicts.delete_all - - Rails.logger.info("Gitlab::BackgroundMigration::DeleteConflictingRedirectRoutesRange [#{start_id}, #{end_id}] - Deleted #{num_rows} redirect routes that were conflicting with routes.") - end - - def migrate? - Route.table_exists? && RedirectRoute.table_exists? - end - - def routes_match_redirects_clause(start_id, end_id) - <<~ROUTES_MATCH_REDIRECTS - EXISTS ( - SELECT 1 FROM routes - WHERE ( - LOWER(redirect_routes.path) = LOWER(routes.path) - OR LOWER(redirect_routes.path) LIKE LOWER(CONCAT(routes.path, '/%')) - ) - AND routes.id BETWEEN #{start_id} AND #{end_id} - ) - ROUTES_MATCH_REDIRECTS + # No-op. + # See https://gitlab.com/gitlab-com/infrastructure/issues/3460#note_53223252 end end end diff --git a/spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb b/spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb index 5c471cbdeda..9bae7e53b71 100644 --- a/spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb +++ b/spec/lib/gitlab/background_migration/delete_conflicting_redirect_routes_range_spec.rb @@ -24,17 +24,12 @@ describe Gitlab::BackgroundMigration::DeleteConflictingRedirectRoutesRange, :mig redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo5') end - it 'deletes the conflicting redirect_routes in the range' do + # No-op. See https://gitlab.com/gitlab-com/infrastructure/issues/3460#note_53223252 + it 'NO-OP: does not delete any redirect_routes' do expect(redirect_routes.count).to eq(8) - expect do - described_class.new.perform(1, 3) - end.to change { redirect_routes.where("path like 'foo%'").count }.from(5).to(2) + described_class.new.perform(1, 5) - expect do - described_class.new.perform(4, 5) - end.to change { redirect_routes.where("path like 'foo%'").count }.from(2).to(0) - - expect(redirect_routes.count).to eq(3) + expect(redirect_routes.count).to eq(8) end end diff --git a/spec/migrations/delete_conflicting_redirect_routes_spec.rb b/spec/migrations/delete_conflicting_redirect_routes_spec.rb index 1df2477da51..8a191bd7139 100644 --- a/spec/migrations/delete_conflicting_redirect_routes_spec.rb +++ b/spec/migrations/delete_conflicting_redirect_routes_spec.rb @@ -10,9 +10,6 @@ describe DeleteConflictingRedirectRoutes, :migration, :sidekiq do end before do - stub_const("DeleteConflictingRedirectRoutes::BATCH_SIZE", 2) - stub_const("Gitlab::Database::MigrationHelpers::BACKGROUND_MIGRATION_JOB_BUFFER_SIZE", 2) - routes.create!(id: 1, source_id: 1, source_type: 'Namespace', path: 'foo1') routes.create!(id: 2, source_id: 2, source_type: 'Namespace', path: 'foo2') routes.create!(id: 3, source_id: 3, source_type: 'Namespace', path: 'foo3') @@ -32,27 +29,14 @@ describe DeleteConflictingRedirectRoutes, :migration, :sidekiq do redirect_routes.create!(source_id: 1, source_type: 'Namespace', path: 'foo5') end - it 'correctly schedules background migrations' do + # No-op. See https://gitlab.com/gitlab-com/infrastructure/issues/3460#note_53223252 + it 'NO-OP: does not schedule any background migrations' do Sidekiq::Testing.fake! do Timecop.freeze do migrate! - expect(BackgroundMigrationWorker.jobs[0]['args']).to eq([described_class::MIGRATION, [1, 2]]) - expect(BackgroundMigrationWorker.jobs[0]['at']).to eq(12.seconds.from_now.to_f) - expect(BackgroundMigrationWorker.jobs[1]['args']).to eq([described_class::MIGRATION, [3, 4]]) - expect(BackgroundMigrationWorker.jobs[1]['at']).to eq(24.seconds.from_now.to_f) - expect(BackgroundMigrationWorker.jobs[2]['args']).to eq([described_class::MIGRATION, [5, 5]]) - expect(BackgroundMigrationWorker.jobs[2]['at']).to eq(36.seconds.from_now.to_f) - expect(BackgroundMigrationWorker.jobs.size).to eq 3 + expect(BackgroundMigrationWorker.jobs.size).to eq 0 end end end - - it 'schedules background migrations' do - Sidekiq::Testing.inline! do - expect do - migrate! - end.to change { redirect_routes.count }.from(8).to(3) - end - end end -- cgit v1.2.1 From 57a490ec192d91ec2fd9dc9e0511af1709001fe6 Mon Sep 17 00:00:00 2001 From: Danny Date: Wed, 3 Jan 2018 20:57:41 +0000 Subject: fix issue #37843 --- ...-due-wrongly-ansi2html-light-color-variant-conversion-feature.yml | 5 +++++ lib/gitlab/ci/ansi2html.rb | 2 +- spec/lib/gitlab/ci/ansi2html_spec.rb | 4 ++++ 3 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/37843-ci-trace-ansi-colours-256-bold-have-no-css-due-wrongly-ansi2html-light-color-variant-conversion-feature.yml diff --git a/changelogs/unreleased/37843-ci-trace-ansi-colours-256-bold-have-no-css-due-wrongly-ansi2html-light-color-variant-conversion-feature.yml b/changelogs/unreleased/37843-ci-trace-ansi-colours-256-bold-have-no-css-due-wrongly-ansi2html-light-color-variant-conversion-feature.yml new file mode 100644 index 00000000000..abf98cd2af4 --- /dev/null +++ b/changelogs/unreleased/37843-ci-trace-ansi-colours-256-bold-have-no-css-due-wrongly-ansi2html-light-color-variant-conversion-feature.yml @@ -0,0 +1,5 @@ +--- +title: Fix ANSI 256 bold colors in pipelines job output +merge_request: +author: +type: fixed diff --git a/lib/gitlab/ci/ansi2html.rb b/lib/gitlab/ci/ansi2html.rb index 72b75791bbb..e25916528f4 100644 --- a/lib/gitlab/ci/ansi2html.rb +++ b/lib/gitlab/ci/ansi2html.rb @@ -234,7 +234,7 @@ module Gitlab # Most terminals show bold colored text in the light color variant # Let's mimic that here if @style_mask & STYLE_SWITCHES[:bold] != 0 - fg_color.sub!(/fg-(\w{2,}+)/, 'fg-l-\1') + fg_color.sub!(/fg-([a-z]{2,}+)/, 'fg-l-\1') end css_classes << fg_color end diff --git a/spec/lib/gitlab/ci/ansi2html_spec.rb b/spec/lib/gitlab/ci/ansi2html_spec.rb index 33540eab5d6..05e2d94cbd6 100644 --- a/spec/lib/gitlab/ci/ansi2html_spec.rb +++ b/spec/lib/gitlab/ci/ansi2html_spec.rb @@ -120,6 +120,10 @@ describe Gitlab::Ci::Ansi2html do expect(convert_html("\e[48;5;240mHello")).to eq('Hello') end + it "can print 256 xterm fg bold colors" do + expect(convert_html("\e[38;5;16;1mHello")).to eq('Hello') + end + it "can print 256 xterm bg colors on normal magenta foreground" do expect(convert_html("\e[48;5;16;35mHello")).to eq('Hello') end -- cgit v1.2.1 From f6e339141d527fe50f61d9204ccf16b8ccc6d861 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 3 Jan 2018 21:03:39 +0000 Subject: Backport of methods and components added in EBackport of methods and components added in EEE --- app/assets/javascripts/lib/utils/text_utility.js | 9 +++++ .../vue_shared/components/expand_button.vue | 46 ++++++++++++++++++++++ spec/javascripts/lib/utils/text_utility_spec.js | 10 +++++ .../vue_shared/components/expand_button_spec.js | 32 +++++++++++++++ 4 files changed, 97 insertions(+) create mode 100644 app/assets/javascripts/vue_shared/components/expand_button.vue create mode 100644 spec/javascripts/vue_shared/components/expand_button_spec.js diff --git a/app/assets/javascripts/lib/utils/text_utility.js b/app/assets/javascripts/lib/utils/text_utility.js index 9280b7f150c..cb6e06ea584 100644 --- a/app/assets/javascripts/lib/utils/text_utility.js +++ b/app/assets/javascripts/lib/utils/text_utility.js @@ -64,3 +64,12 @@ export const truncate = (string, maxLength) => `${string.substr(0, (maxLength - export function capitalizeFirstCharacter(text) { return `${text[0].toUpperCase()}${text.slice(1)}`; } + +/** + * Replaces all html tags from a string with the given replacement. + * + * @param {String} string + * @param {*} replace + * @returns {String} + */ +export const stripeHtml = (string, replace = '') => string.replace(/<[^>]*>/g, replace); diff --git a/app/assets/javascripts/vue_shared/components/expand_button.vue b/app/assets/javascripts/vue_shared/components/expand_button.vue new file mode 100644 index 00000000000..96991c4e268 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/expand_button.vue @@ -0,0 +1,46 @@ + + diff --git a/spec/javascripts/lib/utils/text_utility_spec.js b/spec/javascripts/lib/utils/text_utility_spec.js index 1f46c225071..6f8dad6b835 100644 --- a/spec/javascripts/lib/utils/text_utility_spec.js +++ b/spec/javascripts/lib/utils/text_utility_spec.js @@ -62,4 +62,14 @@ describe('text_utility', () => { expect(textUtils.slugify('João')).toEqual('joão'); }); }); + + describe('stripeHtml', () => { + it('replaces html tag with the default replacement', () => { + expect(textUtils.stripeHtml('This is a text with

html

.')).toEqual('This is a text with html.'); + }); + + it('replaces html tags with the provided replacement', () => { + expect(textUtils.stripeHtml('This is a text with

html

.', ' ')).toEqual('This is a text with html .'); + }); + }); }); diff --git a/spec/javascripts/vue_shared/components/expand_button_spec.js b/spec/javascripts/vue_shared/components/expand_button_spec.js new file mode 100644 index 00000000000..a33ab689dd1 --- /dev/null +++ b/spec/javascripts/vue_shared/components/expand_button_spec.js @@ -0,0 +1,32 @@ +import Vue from 'vue'; +import expandButton from '~/vue_shared/components/expand_button.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('expand button', () => { + let vm; + + beforeEach(() => { + const Component = Vue.extend(expandButton); + vm = mountComponent(Component, { + slots: { + expanded: '

Expanded!

', + }, + }); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('renders a collpased button', () => { + expect(vm.$el.textContent.trim()).toEqual('...'); + }); + + it('hides expander on click', (done) => { + vm.$el.querySelector('button').click(); + vm.$nextTick(() => { + expect(vm.$el.querySelector('button').getAttribute('style')).toEqual('display: none;'); + done(); + }); + }); +}); -- cgit v1.2.1 From b299198e1eb5e1f26d5267f4a64944e600086d6b Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Wed, 3 Jan 2018 23:14:55 +0000 Subject: Adds `eslint-plugin-vue`, fixes linter errors and adds docs --- .eslintrc | 9 +- app/assets/javascripts/blob/notebook/index.js | 76 ++++---- app/assets/javascripts/blob/pdf/index.js | 6 +- app/assets/javascripts/boards/boards_bundle.js | 18 +- .../clusters/components/applications.vue | 32 ++-- app/assets/javascripts/commit/image_file.js | 194 ++++++++++----------- .../cycle_analytics/cycle_analytics_bundle.js | 20 +-- app/assets/javascripts/deploy_keys/index.js | 6 +- .../environments/components/environment_item.vue | 3 +- .../filtered_search/recent_searches_root.js | 6 +- .../javascripts/groups/components/group_folder.vue | 6 +- .../javascripts/groups/components/item_stats.vue | 7 +- .../ide/components/repo_commit_section.vue | 4 +- app/assets/javascripts/ide/index.js | 10 +- .../issue_show/components/description.vue | 6 +- app/assets/javascripts/jobs/job_details_bundle.js | 12 +- .../merge_conflicts/merge_conflicts_bundle.js | 2 +- .../monitoring/components/empty_state.vue | 6 +- .../javascripts/notes/components/comment_form.vue | 14 +- .../notes/components/noteable_discussion.vue | 3 +- .../components/graph/dropdown_job_component.vue | 3 +- .../pipelines/components/pipeline_url.vue | 12 +- .../javascripts/pipelines/components/stage.vue | 4 +- .../pipelines/pipeline_details_bundle.js | 12 +- .../javascripts/pipelines/pipelines_bundle.js | 6 +- .../account/components/delete_account_modal.vue | 3 +- .../confidential/confidential_issue_sidebar.vue | 5 +- .../sidebar/components/lock/lock_issue_sidebar.vue | 3 +- doc/development/fe_guide/style_guide_js.md | 14 +- package.json | 1 + spec/javascripts/boards/issue_card_spec.js | 6 +- .../vue_shared/components/markdown/field_spec.js | 6 +- yarn.lock | 44 +++++ 33 files changed, 333 insertions(+), 226 deletions(-) diff --git a/.eslintrc b/.eslintrc index 44ad6a4896c..a419dc521e8 100644 --- a/.eslintrc +++ b/.eslintrc @@ -4,7 +4,10 @@ "browser": true, "es6": true }, - "extends": "airbnb-base", + "extends": [ + "airbnb-base", + "plugin:vue/recommended" + ], "globals": { "__webpack_public_path__": true, "_": false, @@ -12,7 +15,9 @@ "gon": false, "localStorage": false }, - "parser": "babel-eslint", + "parserOptions": { + "parser": "babel-eslint" + }, "plugins": [ "filenames", "import", diff --git a/app/assets/javascripts/blob/notebook/index.js b/app/assets/javascripts/blob/notebook/index.js index 57b031956e8..6f1350e80fc 100644 --- a/app/assets/javascripts/blob/notebook/index.js +++ b/app/assets/javascripts/blob/notebook/index.js @@ -8,6 +8,9 @@ export default () => { new Vue({ el, + components: { + notebookLab, + }, data() { return { error: false, @@ -16,8 +19,41 @@ export default () => { json: {}, }; }, - components: { - notebookLab, + mounted() { + if (gon.katex_css_url) { + const katexStyles = document.createElement('link'); + katexStyles.setAttribute('rel', 'stylesheet'); + katexStyles.setAttribute('href', gon.katex_css_url); + document.head.appendChild(katexStyles); + } + + if (gon.katex_js_url) { + const katexScript = document.createElement('script'); + katexScript.addEventListener('load', () => { + this.loadFile(); + }); + katexScript.setAttribute('src', gon.katex_js_url); + document.head.appendChild(katexScript); + } else { + this.loadFile(); + } + }, + methods: { + loadFile() { + axios.get(el.dataset.endpoint) + .then(res => res.data) + .then((data) => { + this.json = data; + this.loading = false; + }) + .catch((e) => { + if (e.status !== 200) { + this.loadError = true; + } + + this.error = true; + }); + }, }, template: `
@@ -46,41 +82,5 @@ export default () => {

`, - methods: { - loadFile() { - axios.get(el.dataset.endpoint) - .then(res => res.data) - .then((data) => { - this.json = data; - this.loading = false; - }) - .catch((e) => { - if (e.status !== 200) { - this.loadError = true; - } - - this.error = true; - }); - }, - }, - mounted() { - if (gon.katex_css_url) { - const katexStyles = document.createElement('link'); - katexStyles.setAttribute('rel', 'stylesheet'); - katexStyles.setAttribute('href', gon.katex_css_url); - document.head.appendChild(katexStyles); - } - - if (gon.katex_js_url) { - const katexScript = document.createElement('script'); - katexScript.addEventListener('load', () => { - this.loadFile(); - }); - katexScript.setAttribute('src', gon.katex_js_url); - document.head.appendChild(katexScript); - } else { - this.loadFile(); - } - }, }); }; diff --git a/app/assets/javascripts/blob/pdf/index.js b/app/assets/javascripts/blob/pdf/index.js index 7109f356540..70136cc4087 100644 --- a/app/assets/javascripts/blob/pdf/index.js +++ b/app/assets/javascripts/blob/pdf/index.js @@ -7,6 +7,9 @@ export default () => { return new Vue({ el, + components: { + pdfLab, + }, data() { return { error: false, @@ -15,9 +18,6 @@ export default () => { pdf: el.dataset.endpoint, }; }, - components: { - pdfLab, - }, methods: { onLoad() { this.loading = false; diff --git a/app/assets/javascripts/boards/boards_bundle.js b/app/assets/javascripts/boards/boards_bundle.js index 679c883cdcf..90166b3d3d1 100644 --- a/app/assets/javascripts/boards/boards_bundle.js +++ b/app/assets/javascripts/boards/boards_bundle.js @@ -171,19 +171,14 @@ $(() => { }); gl.IssueBoardsModalAddBtn = new Vue({ - mixins: [gl.issueBoards.ModalMixins], el: document.getElementById('js-add-issues-btn'), + mixins: [gl.issueBoards.ModalMixins], data() { return { modal: ModalStore.store, store: Store.state, }; }, - watch: { - disabled() { - this.updateTooltip(); - }, - }, computed: { disabled() { if (!this.store) { @@ -199,6 +194,14 @@ $(() => { return ''; }, }, + watch: { + disabled() { + this.updateTooltip(); + }, + }, + mounted() { + this.updateTooltip(); + }, methods: { updateTooltip() { const $tooltip = $(this.$refs.addIssuesButton); @@ -217,9 +220,6 @@ $(() => { } }, }, - mounted() { - this.updateTooltip(); - }, template: `
diff --git a/app/assets/javascripts/ide/components/ide_side_bar.vue b/app/assets/javascripts/ide/components/ide_side_bar.vue index 535398d98c2..269f300a04d 100644 --- a/app/assets/javascripts/ide/components/ide_side_bar.vue +++ b/app/assets/javascripts/ide/components/ide_side_bar.vue @@ -2,11 +2,18 @@ import { mapState, mapActions } from 'vuex'; import projectTree from './ide_project_tree.vue'; import icon from '../../vue_shared/components/icon.vue'; +import panelResizer from '../../vue_shared/components/panel_resizer.vue'; export default { + data() { + return { + width: 290, + }; + }, components: { projectTree, icon, + panelResizer, }, computed: { ...mapState([ @@ -16,10 +23,20 @@ export default { currentIcon() { return this.leftPanelCollapsed ? 'angle-double-right' : 'angle-double-left'; }, + maxSize() { + return window.innerWidth / 2; + }, + panelStyle() { + if (!this.leftPanelCollapsed) { + return { width: `${this.width}px` }; + } + return {}; + }, }, methods: { ...mapActions([ 'setPanelCollapsedStatus', + 'setResizingStatus', ]), toggleCollapsed() { this.setPanelCollapsedStatus({ @@ -27,6 +44,12 @@ export default { collapsed: !this.leftPanelCollapsed, }); }, + resizingStarted() { + this.setResizingStatus(true); + }, + resizingEnded() { + this.setResizingStatus(false); + }, }, }; @@ -37,6 +60,7 @@ export default { :class="{ 'is-collapsed': leftPanelCollapsed, }" + :style="panelStyle" >
Collapse sidebar +
diff --git a/app/assets/javascripts/ide/components/repo_editor.vue b/app/assets/javascripts/ide/components/repo_editor.vue index 221be4b9074..343fd0a5300 100644 --- a/app/assets/javascripts/ide/components/repo_editor.vue +++ b/app/assets/javascripts/ide/components/repo_editor.vue @@ -90,6 +90,11 @@ export default { rightPanelCollapsed() { this.editor.updateDimensions(); }, + panelResizing(isResizing) { + if (isResizing === false) { + this.editor.updateDimensions(); + } + }, }, computed: { ...mapGetters([ @@ -99,6 +104,7 @@ export default { ...mapState([ 'leftPanelCollapsed', 'rightPanelCollapsed', + 'panelResizing', ]), shouldHideEditor() { return this.activeFile.binary && !this.activeFile.raw; diff --git a/app/assets/javascripts/ide/stores/actions.js b/app/assets/javascripts/ide/stores/actions.js index c01046c8c76..335882bb6d7 100644 --- a/app/assets/javascripts/ide/stores/actions.js +++ b/app/assets/javascripts/ide/stores/actions.js @@ -63,6 +63,10 @@ export const setPanelCollapsedStatus = ({ commit }, { side, collapsed }) => { } }; +export const setResizingStatus = ({ commit }, resizing) => { + commit(types.SET_RESIZING_STATUS, resizing); +}; + export const checkCommitStatus = ({ state }) => service .getBranchData(state.currentProjectId, state.currentBranchId) diff --git a/app/assets/javascripts/ide/stores/mutation_types.js b/app/assets/javascripts/ide/stores/mutation_types.js index 4e3c10972ba..69b218a5e7d 100644 --- a/app/assets/javascripts/ide/stores/mutation_types.js +++ b/app/assets/javascripts/ide/stores/mutation_types.js @@ -5,6 +5,7 @@ export const SET_ROOT = 'SET_ROOT'; export const SET_LAST_COMMIT_DATA = 'SET_LAST_COMMIT_DATA'; export const SET_LEFT_PANEL_COLLAPSED = 'SET_LEFT_PANEL_COLLAPSED'; export const SET_RIGHT_PANEL_COLLAPSED = 'SET_RIGHT_PANEL_COLLAPSED'; +export const SET_RESIZING_STATUS = 'SET_RESIZING_STATUS'; // Project Mutation Types export const SET_PROJECT = 'SET_PROJECT'; diff --git a/app/assets/javascripts/ide/stores/mutations.js b/app/assets/javascripts/ide/stores/mutations.js index 2fed9019cb6..03d81be10a1 100644 --- a/app/assets/javascripts/ide/stores/mutations.js +++ b/app/assets/javascripts/ide/stores/mutations.js @@ -49,6 +49,11 @@ export default { rightPanelCollapsed: collapsed, }); }, + [types.SET_RESIZING_STATUS](state, resizing) { + Object.assign(state, { + panelResizing: resizing, + }); + }, [types.SET_LAST_COMMIT_DATA](state, { entry, lastCommit }) { Object.assign(entry.lastCommit, { id: lastCommit.commit.id, diff --git a/app/assets/javascripts/ide/stores/state.js b/app/assets/javascripts/ide/stores/state.js index 539e382830f..61d12096946 100644 --- a/app/assets/javascripts/ide/stores/state.js +++ b/app/assets/javascripts/ide/stores/state.js @@ -19,4 +19,5 @@ export default () => ({ projects: {}, leftPanelCollapsed: false, rightPanelCollapsed: true, + panelResizing: false, }); diff --git a/app/assets/javascripts/vue_shared/components/panel_resizer.vue b/app/assets/javascripts/vue_shared/components/panel_resizer.vue new file mode 100644 index 00000000000..4371534d345 --- /dev/null +++ b/app/assets/javascripts/vue_shared/components/panel_resizer.vue @@ -0,0 +1,91 @@ + + + diff --git a/app/assets/stylesheets/pages/repo.scss b/app/assets/stylesheets/pages/repo.scss index 51cc1729d9a..d01cbadebcc 100644 --- a/app/assets/stylesheets/pages/repo.scss +++ b/app/assets/stylesheets/pages/repo.scss @@ -36,10 +36,6 @@ } } -.with-performance-bar .ide-view { - height: calc(100vh - #{$header-height}); -} - .ide-file-list { flex: 1; @@ -242,12 +238,13 @@ table.table tr td.multi-file-table-name { .multi-file-commit-panel { display: flex; + position: relative; flex-direction: column; height: 100%; width: 290px; padding: 0; background-color: $gray-light; - border-left: 1px solid $white-dark; + padding-right: 3px; .projects-sidebar { display: flex; @@ -496,3 +493,30 @@ table.table tr td.multi-file-table-name { margin-top: $header-height; margin-bottom: 0; } + +.with-performance-bar { + .ide-flash-container.flash-container { + margin-top: $header-height + $performance-bar-height; + } + + .ide-view { + height: calc(100vh - #{$header-height + $performance-bar-height}); + } +} + + +.dragHandle { + position: absolute; + top: 0; + bottom: 0; + width: 3px; + background-color: $white-dark; + + &.dragright { + right: 0; + } + + &.dragleft { + left: 0; + } +} diff --git a/spec/javascripts/vue_shared/components/panel_resizer_spec.js b/spec/javascripts/vue_shared/components/panel_resizer_spec.js new file mode 100644 index 00000000000..70ce3dffaba --- /dev/null +++ b/spec/javascripts/vue_shared/components/panel_resizer_spec.js @@ -0,0 +1,59 @@ +import Vue from 'vue'; +import panelResizer from '~/vue_shared/components/panel_resizer.vue'; +import mountComponent from '../../helpers/vue_mount_component_helper'; + +describe('Panel Resizer component', () => { + let vm; + let PanelResizer; + + const triggerEvent = (eventName, el = vm.$el, clientX = 0) => { + const event = document.createEvent('MouseEvents'); + event.initMouseEvent(eventName, true, true, window, 1, clientX, 0, clientX, 0, false, false, + false, false, 0, null); + + el.dispatchEvent(event); + }; + + beforeEach(() => { + PanelResizer = Vue.extend(panelResizer); + }); + + afterEach(() => { + vm.$destroy(); + }); + + it('should render a div element with the correct classes and styles', () => { + vm = mountComponent(PanelResizer, { + startSize: 100, + side: 'left', + }); + + expect(vm.$el.tagName).toEqual('DIV'); + expect(vm.$el.getAttribute('class')).toBe('dragHandle dragleft'); + expect(vm.$el.getAttribute('style')).toBe('cursor: ew-resize;'); + }); + + it('should render a div element with the correct classes for a right side panel', () => { + vm = mountComponent(PanelResizer, { + startSize: 100, + side: 'right', + }); + + expect(vm.$el.tagName).toEqual('DIV'); + expect(vm.$el.getAttribute('class')).toBe('dragHandle dragright'); + }); + + it('drag the resizer', () => { + vm = mountComponent(PanelResizer, { + startSize: 100, + side: 'left', + }); + + spyOn(vm, '$emit'); + triggerEvent('mousedown', vm.$el); + triggerEvent('mousemove', document); + triggerEvent('mouseup', document); + expect(vm.$emit.calls.allArgs()).toEqual([['resize-start', 100], ['update:size', 100], ['resize-end', 100]]); + expect(vm.size).toBe(100); + }); +}); -- cgit v1.2.1 From e028d795c484dcd1030b4f6bba8f53d4e677f0b3 Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Thu, 4 Jan 2018 09:33:51 +0000 Subject: 41054-Disallow creation of new Kubernetes integrations --- app/assets/stylesheets/pages/settings.scss | 4 + app/helpers/services_helper.rb | 11 +++ app/models/project_services/kubernetes_service.rb | 28 ++++++ app/models/service.rb | 8 ++ .../services/_deprecated_message.html.haml | 3 + app/views/projects/services/_form.html.haml | 5 +- app/views/projects/services/edit.html.haml | 2 + app/views/shared/_field.html.haml | 11 ++- app/views/shared/_service_settings.html.haml | 2 +- ...ble-creation-of-new-kubernetes-integrations.yml | 6 ++ .../projects/services_controller_spec.rb | 36 ++++++++ spec/factories/services.rb | 1 + .../projects/clusters/interchangeability_spec.rb | 2 +- .../project_services/kubernetes_service_spec.rb | 101 +++++++++++++++++++++ spec/models/service_spec.rb | 18 ++++ spec/requests/api/services_spec.rb | 8 +- spec/requests/api/v3/services_spec.rb | 4 + spec/support/services_shared_context.rb | 8 ++ 18 files changed, 244 insertions(+), 14 deletions(-) create mode 100644 app/views/projects/services/_deprecated_message.html.haml create mode 100644 changelogs/unreleased/41054-disable-creation-of-new-kubernetes-integrations.yml diff --git a/app/assets/stylesheets/pages/settings.scss b/app/assets/stylesheets/pages/settings.scss index 5d630c7d61e..6353482ede7 100644 --- a/app/assets/stylesheets/pages/settings.scss +++ b/app/assets/stylesheets/pages/settings.scss @@ -268,3 +268,7 @@ margin: 0 0 5px 17px; } } + +.deprecated-service { + cursor: default; +} diff --git a/app/helpers/services_helper.rb b/app/helpers/services_helper.rb index 3707bb5ba36..240783bc7fd 100644 --- a/app/helpers/services_helper.rb +++ b/app/helpers/services_helper.rb @@ -27,5 +27,16 @@ module ServicesHelper "#{event}_events" end + def service_save_button(service) + button_tag(class: 'btn btn-save', type: 'submit', disabled: service.deprecated?) do + icon('spinner spin', class: 'hidden js-btn-spinner') + + content_tag(:span, 'Save changes', class: 'js-btn-label') + end + end + + def disable_fields_service?(service) + !current_controller?("admin/services") && service.deprecated? + end + extend self end diff --git a/app/models/project_services/kubernetes_service.rb b/app/models/project_services/kubernetes_service.rb index b82567ce2b3..c72b01b64af 100644 --- a/app/models/project_services/kubernetes_service.rb +++ b/app/models/project_services/kubernetes_service.rb @@ -31,6 +31,7 @@ class KubernetesService < DeploymentService before_validation :enforce_namespace_to_lower_case + validate :deprecation_validation, unless: :template? validates :namespace, allow_blank: true, length: 1..63, @@ -145,6 +146,17 @@ class KubernetesService < DeploymentService @kubeclient ||= build_kubeclient! end + def deprecated? + !active + end + + def deprecation_message + content = <<-MESSAGE.strip_heredoc + Kubernetes service integration has been deprecated. #{deprecated_message_content} your clusters using the new Clusters page + MESSAGE + content.html_safe + end + TEMPLATE_PLACEHOLDER = 'Kubernetes namespace'.freeze private @@ -226,4 +238,20 @@ class KubernetesService < DeploymentService def enforce_namespace_to_lower_case self.namespace = self.namespace&.downcase end + + def deprecation_validation + return if active_changed?(from: true, to: false) + + if deprecated? + errors[:base] << deprecation_message + end + end + + def deprecated_message_content + if active? + "Your cluster information on this page is still editable, but you are advised to disable and reconfigure" + else + "Fields on this page are now uneditable, you can configure" + end + end end diff --git a/app/models/service.rb b/app/models/service.rb index 3c4f1885dd0..176b472e724 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -263,6 +263,14 @@ class Service < ActiveRecord::Base service end + def deprecated? + false + end + + def deprecation_message + nil + end + private def cache_project_has_external_issue_tracker diff --git a/app/views/projects/services/_deprecated_message.html.haml b/app/views/projects/services/_deprecated_message.html.haml new file mode 100644 index 00000000000..fea9506a4bb --- /dev/null +++ b/app/views/projects/services/_deprecated_message.html.haml @@ -0,0 +1,3 @@ +.flash-container.flash-container-page + .flash-alert.deprecated-service + %span= @service.deprecation_message diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index c0b1c62e8ef..21acd857ce7 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/services/_form.html.haml @@ -13,10 +13,7 @@ = render 'shared/service_settings', form: form, subject: @service - if @service.editable? .footer-block.row-content-block - %button.btn.btn-save{ type: 'submit' } - = icon('spinner spin', class: 'hidden js-btn-spinner') - %span.js-btn-label - Save changes + = service_save_button(@service)   - if @service.valid? && @service.activated? - unless @service.can_test? diff --git a/app/views/projects/services/edit.html.haml b/app/views/projects/services/edit.html.haml index 25770df1c90..df1fd583670 100644 --- a/app/views/projects/services/edit.html.haml +++ b/app/views/projects/services/edit.html.haml @@ -2,4 +2,6 @@ - page_title @service.title, "Services" - add_to_breadcrumbs("Settings", edit_project_path(@project)) += render 'deprecated_message' if @service.deprecation_message + = render 'form' diff --git a/app/views/shared/_field.html.haml b/app/views/shared/_field.html.haml index 795447a9ca6..aea0a8fd8e0 100644 --- a/app/views/shared/_field.html.haml +++ b/app/views/shared/_field.html.haml @@ -7,6 +7,7 @@ - choices = field[:choices] - default_choice = field[:default_choice] - help = field[:help] +- disabled = disable_fields_service?(@service) .form-group - if type == "password" && value.present? @@ -15,14 +16,14 @@ = form.label name, title, class: "control-label" .col-sm-10 - if type == 'text' - = form.text_field name, class: "form-control", placeholder: placeholder, required: required + = form.text_field name, class: "form-control", placeholder: placeholder, required: required, disabled: disabled - elsif type == 'textarea' - = form.text_area name, rows: 5, class: "form-control", placeholder: placeholder, required: required + = form.text_area name, rows: 5, class: "form-control", placeholder: placeholder, required: required, disabled: disabled - elsif type == 'checkbox' - = form.check_box name + = form.check_box name, disabled: disabled - elsif type == 'select' - = form.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" } + = form.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control", disabled: disabled} - elsif type == 'password' - = form.password_field name, autocomplete: "new-password", class: "form-control", required: value.blank? && :required + = form.password_field name, autocomplete: "new-password", class: "form-control", required: value.blank? && required, disabled: disabled - if help %span.help-block= help diff --git a/app/views/shared/_service_settings.html.haml b/app/views/shared/_service_settings.html.haml index 7ca14ac93cc..61b39afb5d4 100644 --- a/app/views/shared/_service_settings.html.haml +++ b/app/views/shared/_service_settings.html.haml @@ -11,7 +11,7 @@ .form-group = form.label :active, "Active", class: "control-label" .col-sm-10 - = form.check_box :active + = form.check_box :active, disabled: disable_fields_service?(@service) - if @service.supported_events.present? .form-group diff --git a/changelogs/unreleased/41054-disable-creation-of-new-kubernetes-integrations.yml b/changelogs/unreleased/41054-disable-creation-of-new-kubernetes-integrations.yml new file mode 100644 index 00000000000..b960b14624c --- /dev/null +++ b/changelogs/unreleased/41054-disable-creation-of-new-kubernetes-integrations.yml @@ -0,0 +1,6 @@ +--- +title: Disable creation of new Kubernetes Integrations unless they're active or created + from template +merge_request: 41054 +author: +type: added diff --git a/spec/controllers/projects/services_controller_spec.rb b/spec/controllers/projects/services_controller_spec.rb index 2c6ad00515e..847ac6f2be0 100644 --- a/spec/controllers/projects/services_controller_spec.rb +++ b/spec/controllers/projects/services_controller_spec.rb @@ -114,5 +114,41 @@ describe Projects::ServicesController do expect(flash[:notice]).to eq 'HipChat settings saved, but not activated.' end end + + context 'with a deprecated service' do + let(:service) { create(:kubernetes_service, project: project) } + + before do + put :update, + namespace_id: project.namespace, project_id: project, id: service.to_param, service: { namespace: 'updated_namespace' } + end + + it 'should not update the service' do + service.reload + expect(service.namespace).not_to eq('updated_namespace') + end + end + end + + describe "GET #edit" do + before do + get :edit, namespace_id: project.namespace, project_id: project, id: service_id + end + + context 'with approved services' do + let(:service_id) { 'jira' } + + it 'should render edit page' do + expect(response).to be_success + end + end + + context 'with a deprecated service' do + let(:service_id) { 'kubernetes' } + + it 'should render edit page' do + expect(response).to be_success + end + end end end diff --git a/spec/factories/services.rb b/spec/factories/services.rb index 4b0377967c7..110ef33c6f7 100644 --- a/spec/factories/services.rb +++ b/spec/factories/services.rb @@ -18,6 +18,7 @@ FactoryBot.define do factory :kubernetes_service do project + type 'KubernetesService' active true properties({ api_url: 'https://kubernetes.example.com', diff --git a/spec/features/projects/clusters/interchangeability_spec.rb b/spec/features/projects/clusters/interchangeability_spec.rb index 01f9526608f..3ddb35c755c 100644 --- a/spec/features/projects/clusters/interchangeability_spec.rb +++ b/spec/features/projects/clusters/interchangeability_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' feature 'Interchangeability between KubernetesService and Platform::Kubernetes' do - EXCEPT_METHODS = %i[test title description help fields initialize_properties namespace namespace= api_url api_url=].freeze + EXCEPT_METHODS = %i[test title description help fields initialize_properties namespace namespace= api_url api_url= deprecated? deprecation_message].freeze EXCEPT_METHODS_GREP_V = %w[_touched? _changed? _was].freeze it 'Clusters::Platform::Kubernetes covers core interfaces in KubernetesService' do diff --git a/spec/models/project_services/kubernetes_service_spec.rb b/spec/models/project_services/kubernetes_service_spec.rb index f037ee77a94..6980ba335b8 100644 --- a/spec/models/project_services/kubernetes_service_spec.rb +++ b/spec/models/project_services/kubernetes_service_spec.rb @@ -52,12 +52,75 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do context 'when service is inactive' do before do + subject.project = project subject.active = false end it { is_expected.not_to validate_presence_of(:api_url) } it { is_expected.not_to validate_presence_of(:token) } end + + context 'with a deprecated service' do + let(:kubernetes_service) { create(:kubernetes_service) } + + before do + kubernetes_service.update_attribute(:active, false) + kubernetes_service.properties[:namespace] = "foo" + end + + it 'should not update attributes' do + expect(kubernetes_service.save).to be_falsy + end + + it 'should include an error with a deprecation message' do + kubernetes_service.valid? + expect(kubernetes_service.errors[:base].first).to match(/Kubernetes service integration has been deprecated/) + end + end + + context 'with a non-deprecated service' do + let(:kubernetes_service) { create(:kubernetes_service) } + + it 'should update attributes' do + kubernetes_service.properties[:namespace] = 'foo' + expect(kubernetes_service.save).to be_truthy + end + end + + context 'with an active and deprecated service' do + let(:kubernetes_service) { create(:kubernetes_service) } + + before do + kubernetes_service.active = false + kubernetes_service.properties[:namespace] = 'foo' + kubernetes_service.save + end + + it 'should deactive the service' do + expect(kubernetes_service.active?).to be_falsy + end + + it 'should not include a deprecation message as error' do + expect(kubernetes_service.errors.messages.count).to eq(0) + end + + it 'should update attributes' do + expect(kubernetes_service.properties[:namespace]).to eq("foo") + end + end + + context 'with a template service' do + let(:kubernetes_service) { create(:kubernetes_service, template: true, active: false) } + + before do + kubernetes_service.properties[:namespace] = 'foo' + end + + it 'should update attributes' do + expect(kubernetes_service.save).to be_truthy + expect(kubernetes_service.properties[:namespace]).to eq('foo') + end + end end describe '#initialize_properties' do @@ -318,4 +381,42 @@ describe KubernetesService, :use_clean_rails_memory_store_caching do it { is_expected.to eq(pods: []) } end end + + describe "#deprecated?" do + let(:kubernetes_service) { create(:kubernetes_service) } + + context 'with an active kubernetes service' do + it 'should return false' do + expect(kubernetes_service.deprecated?).to be_falsy + end + end + + context 'with a inactive kubernetes service' do + it 'should return true' do + kubernetes_service.update_attribute(:active, false) + expect(kubernetes_service.deprecated?).to be_truthy + end + end + end + + describe "#deprecation_message" do + let(:kubernetes_service) { create(:kubernetes_service) } + + it 'should indicate the service is deprecated' do + expect(kubernetes_service.deprecation_message).to match(/Kubernetes service integration has been deprecated/) + end + + context 'if the services is active' do + it 'should return a message' do + expect(kubernetes_service.deprecation_message).to match(/Your cluster information on this page is still editable/) + end + end + + context 'if the service is not active' do + it 'should return a message' do + kubernetes_service.update_attribute(:active, false) + expect(kubernetes_service.deprecation_message).to match(/Fields on this page are now uneditable/) + end + end + end end diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 0f2f906c667..540615de117 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -254,4 +254,22 @@ describe Service do end end end + + describe "#deprecated?" do + let(:project) { create(:project, :repository) } + + it 'should return false by default' do + service = create(:service, project: project) + expect(service.deprecated?).to be_falsy + end + end + + describe "#deprecation_message" do + let(:project) { create(:project, :repository) } + + it 'should be empty by default' do + service = create(:service, project: project) + expect(service.deprecation_message).to be_nil + end + end end diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index ceafa0e2058..26d56c04862 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -53,6 +53,10 @@ describe API::Services do describe "DELETE /projects/:id/services/#{service.dasherize}" do include_context service + before do + initialize_service(service) + end + it "deletes #{service}" do delete api("/projects/#{project.id}/services/#{dashed_service}", user) @@ -67,9 +71,7 @@ describe API::Services do # inject some properties into the service before do - service_object = project.find_or_initialize_service(service) - service_object.properties = service_attrs - service_object.save + initialize_service(service) end it 'returns authentication error when unauthenticated' do diff --git a/spec/requests/api/v3/services_spec.rb b/spec/requests/api/v3/services_spec.rb index 8f212ab6be6..c69a7d58ca6 100644 --- a/spec/requests/api/v3/services_spec.rb +++ b/spec/requests/api/v3/services_spec.rb @@ -10,6 +10,10 @@ describe API::V3::Services do describe "DELETE /projects/:id/services/#{service.dasherize}" do include_context service + before do + initialize_service(service) + end + it "deletes #{service}" do delete v3_api("/projects/#{project.id}/services/#{dashed_service}", user) diff --git a/spec/support/services_shared_context.rb b/spec/support/services_shared_context.rb index 7457484a932..3f1fd169b72 100644 --- a/spec/support/services_shared_context.rb +++ b/spec/support/services_shared_context.rb @@ -29,5 +29,13 @@ Service.available_services_names.each do |service| end end end + + def initialize_service(service) + service_item = project.find_or_initialize_service(service) + service_item.properties = service_attrs + service_item.active = true if service == "kubernetes" + service_item.save + service_item + end end end -- cgit v1.2.1 From 20f79920e584f70218c78ce7a2c9c42328020031 Mon Sep 17 00:00:00 2001 From: Alessio Caiazza Date: Thu, 4 Jan 2018 10:29:16 +0100 Subject: Backport gitlab-org/gitlab-ci-yml!128 - Fix kubectl version to 1.8.6 This commit extracts `kubectl`, `helm` and `codeclimate` versions as CI variables. `kubectl` changes from latest stable version to `1.8.6`, the other two are just extracted in order to be easily updated; now we can also test tool upgrades overriding CI secret variables. --- changelogs/unreleased/ac-autodevopfix-kubectl-version.yml | 5 +++++ vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml | 12 ++++++++---- 2 files changed, 13 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/ac-autodevopfix-kubectl-version.yml diff --git a/changelogs/unreleased/ac-autodevopfix-kubectl-version.yml b/changelogs/unreleased/ac-autodevopfix-kubectl-version.yml new file mode 100644 index 00000000000..0ceeb7ccee1 --- /dev/null +++ b/changelogs/unreleased/ac-autodevopfix-kubectl-version.yml @@ -0,0 +1,5 @@ +--- +title: Force Auto DevOps kubectl version to 1.8.6 +merge_request: 16218 +author: +type: fixed diff --git a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml index 18910a46d11..06473fba8e1 100644 --- a/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml +++ b/vendor/gitlab-ci-yml/Auto-DevOps.gitlab-ci.yml @@ -34,6 +34,10 @@ variables: POSTGRES_ENABLED: "true" POSTGRES_DB: $CI_ENVIRONMENT_SLUG + KUBERNETES_VERSION: 1.8.6 + HELM_VERSION: 2.6.1 + CODECLIMATE_VERSION: 0.69.0 + stages: - build - test @@ -250,8 +254,8 @@ production: --volume /var/run/docker.sock:/var/run/docker.sock \ --volume /tmp/cc:/tmp/cc" - docker run ${cc_opts} codeclimate/codeclimate:0.69.0 init - docker run ${cc_opts} codeclimate/codeclimate:0.69.0 analyze -f json > codeclimate.json + docker run ${cc_opts} "codeclimate/codeclimate:${CODECLIMATE_VERSION}" init + docker run ${cc_opts} "codeclimate/codeclimate:${CODECLIMATE_VERSION}" analyze -f json > codeclimate.json } function sast() { @@ -323,11 +327,11 @@ production: apk add glibc-2.23-r3.apk rm glibc-2.23-r3.apk - curl https://kubernetes-helm.storage.googleapis.com/helm-v2.6.1-linux-amd64.tar.gz | tar zx + curl "https://kubernetes-helm.storage.googleapis.com/helm-v${HELM_VERSION}-linux-amd64.tar.gz" | tar zx mv linux-amd64/helm /usr/bin/ helm version --client - curl -L -o /usr/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl + 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 } -- cgit v1.2.1 From 260935868acfb7c0cb720088d4f8c4c1c1088ddb Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 14 Dec 2017 11:49:35 +0100 Subject: add new git fsck rake task and spec --- lib/tasks/gitlab/git.rake | 10 ++++++++++ spec/tasks/gitlab/git_rake_spec.rb | 27 +++++++++++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 spec/tasks/gitlab/git_rake_spec.rb diff --git a/lib/tasks/gitlab/git.rake b/lib/tasks/gitlab/git.rake index cf82134d97e..f3ffff43726 100644 --- a/lib/tasks/gitlab/git.rake +++ b/lib/tasks/gitlab/git.rake @@ -30,6 +30,16 @@ namespace :gitlab do end end + desc 'GitLab | Git | Check all repos integrity' + task fsck: :environment do + failures = perform_git_cmd(%W(#{Gitlab.config.git.bin_path} fsck --name-objects --no-progress), "Checking integrity") + if failures.empty? + puts "Done".color(:green) + else + output_failures(failures) + end + end + def perform_git_cmd(cmd, message) puts "Starting #{message} on all repositories" diff --git a/spec/tasks/gitlab/git_rake_spec.rb b/spec/tasks/gitlab/git_rake_spec.rb new file mode 100644 index 00000000000..63a7f7efe73 --- /dev/null +++ b/spec/tasks/gitlab/git_rake_spec.rb @@ -0,0 +1,27 @@ +require 'rake_helper' + +describe 'gitlab:git rake tasks' do + before do + Rake.application.rake_require 'tasks/gitlab/git' + + stub_warn_user_is_not_gitlab + + FileUtils.mkdir(Settings.absolute('tmp/tests/default_storage')) + end + + after do + FileUtils.rm_rf(Settings.absolute('tmp/tests/default_storage')) + end + + describe 'fsck' do + let(:storages) do + { 'default' => { 'path' => Settings.absolute('tmp/tests/default_storage') } } + end + + it 'outputs the right git command' do + expect(Kernel).to receive(:system).with('').and_return(true) + + run_rake_task('gitlab:git:fsck') + end + end +end -- cgit v1.2.1 From 7721e8dfca9d272376f58dcb03ff277aef0a9c31 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 14 Dec 2017 14:53:34 +0100 Subject: fix spec --- spec/tasks/gitlab/git_rake_spec.rb | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/spec/tasks/gitlab/git_rake_spec.rb b/spec/tasks/gitlab/git_rake_spec.rb index 63a7f7efe73..60b51186ceb 100644 --- a/spec/tasks/gitlab/git_rake_spec.rb +++ b/spec/tasks/gitlab/git_rake_spec.rb @@ -4,9 +4,11 @@ describe 'gitlab:git rake tasks' do before do Rake.application.rake_require 'tasks/gitlab/git' - stub_warn_user_is_not_gitlab + storages = { 'default' => { 'path' => Settings.absolute('tmp/tests/default_storage') } } - FileUtils.mkdir(Settings.absolute('tmp/tests/default_storage')) + FileUtils.mkdir_p(Settings.absolute('tmp/tests/default_storage/repo/test.git')) + allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) + stub_warn_user_is_not_gitlab end after do @@ -14,14 +16,8 @@ describe 'gitlab:git rake tasks' do end describe 'fsck' do - let(:storages) do - { 'default' => { 'path' => Settings.absolute('tmp/tests/default_storage') } } - end - it 'outputs the right git command' do - expect(Kernel).to receive(:system).with('').and_return(true) - - run_rake_task('gitlab:git:fsck') + expect { run_rake_task('gitlab:git:fsck') }.to output(/Performed Checking integrity/).to_stdout end end end -- cgit v1.2.1 From bc46c822fc94cfa54a190cfb0e89afeae799f57a Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 21 Dec 2017 15:42:25 +0100 Subject: remove max-depth flag so it works with subgroups --- lib/tasks/gitlab/task_helpers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tasks/gitlab/task_helpers.rb b/lib/tasks/gitlab/task_helpers.rb index 6723662703c..c1182af1014 100644 --- a/lib/tasks/gitlab/task_helpers.rb +++ b/lib/tasks/gitlab/task_helpers.rb @@ -130,7 +130,7 @@ module Gitlab def all_repos Gitlab.config.repositories.storages.each_value do |repository_storage| - IO.popen(%W(find #{repository_storage['path']} -mindepth 2 -maxdepth 2 -type d -name *.git)) do |find| + IO.popen(%W(find #{repository_storage['path']} -mindepth 2 -type d -name *.git)) do |find| find.each_line do |path| yield path.chomp end -- cgit v1.2.1 From f8e1b44dc5d2a78676672dfc7d44c17e6defeda6 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 3 Jan 2018 14:51:04 +0100 Subject: add locks chek --- lib/tasks/gitlab/git.rake | 26 +++++++++++++++++++++++++- spec/tasks/gitlab/git_rake_spec.rb | 4 +++- 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/lib/tasks/gitlab/git.rake b/lib/tasks/gitlab/git.rake index f3ffff43726..5c1b19860f0 100644 --- a/lib/tasks/gitlab/git.rake +++ b/lib/tasks/gitlab/git.rake @@ -32,7 +32,10 @@ namespace :gitlab do desc 'GitLab | Git | Check all repos integrity' task fsck: :environment do - failures = perform_git_cmd(%W(#{Gitlab.config.git.bin_path} fsck --name-objects --no-progress), "Checking integrity") + failures = perform_git_cmd(%W(#{Gitlab.config.git.bin_path} fsck --name-objects --no-progress), "Checking integrity") do |repo| + check_config_lock(repo) + check_ref_locks(repo) + end if failures.empty? puts "Done".color(:green) else @@ -50,6 +53,8 @@ namespace :gitlab do else failures << repo end + + yield(repo) if block_given? end failures @@ -59,5 +64,24 @@ namespace :gitlab do puts "The following repositories reported errors:".color(:red) failures.each { |f| puts "- #{f}" } end + + def check_config_lock(repo_dir) + config_exists = File.exist?(File.join(repo_dir, 'config.lock')) + config_output = config_exists ? 'yes'.color(:red) : 'no'.color(:green) + + puts "'config.lock' file exists?".color(:yellow) + " ... #{config_output}" + end + + def check_ref_locks(repo_dir) + lock_files = Dir.glob(File.join(repo_dir, 'refs/heads/*.lock')) + + if lock_files.present? + puts "Ref lock files exist:".color(:red) + + lock_files.each { |lock_file| puts " #{lock_file}" } + else + puts "No ref lock files exist".color(:green) + end + end end end diff --git a/spec/tasks/gitlab/git_rake_spec.rb b/spec/tasks/gitlab/git_rake_spec.rb index 60b51186ceb..19d298fb36d 100644 --- a/spec/tasks/gitlab/git_rake_spec.rb +++ b/spec/tasks/gitlab/git_rake_spec.rb @@ -1,3 +1,5 @@ + + require 'rake_helper' describe 'gitlab:git rake tasks' do @@ -6,7 +8,7 @@ describe 'gitlab:git rake tasks' do storages = { 'default' => { 'path' => Settings.absolute('tmp/tests/default_storage') } } - FileUtils.mkdir_p(Settings.absolute('tmp/tests/default_storage/repo/test.git')) + FileUtils.mkdir_p(Settings.absolute('tmp/tests/default_storage/@repo/1/2/test.git')) allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) stub_warn_user_is_not_gitlab end -- cgit v1.2.1 From 5b9e7773766eebbe73bb400025de002962532a7c Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 3 Jan 2018 15:32:16 +0100 Subject: add lock specs --- spec/tasks/gitlab/git_rake_spec.rb | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/spec/tasks/gitlab/git_rake_spec.rb b/spec/tasks/gitlab/git_rake_spec.rb index 19d298fb36d..44a2607bea2 100644 --- a/spec/tasks/gitlab/git_rake_spec.rb +++ b/spec/tasks/gitlab/git_rake_spec.rb @@ -21,5 +21,18 @@ describe 'gitlab:git rake tasks' do it 'outputs the right git command' do expect { run_rake_task('gitlab:git:fsck') }.to output(/Performed Checking integrity/).to_stdout end + + it 'errors out about config.lock issues' do + FileUtils.touch(Settings.absolute('tmp/tests/default_storage/@repo/1/2/test.git/config.lock')) + + expect { run_rake_task('gitlab:git:fsck') }.to output(/file exists\? ... yes/).to_stdout + end + + it 'errors out about ref lock issues' do + FileUtils.mkdir_p(Settings.absolute('tmp/tests/default_storage/@repo/1/2/test.git/refs/heads')) + FileUtils.touch(Settings.absolute('tmp/tests/default_storage/@repo/1/2/test.git/refs/heads/blah.lock')) + + expect { run_rake_task('gitlab:git:fsck') }.to output(/Ref lock files exist:/).to_stdout + end end end -- cgit v1.2.1 From 6ee122c04ee8263dc1cb9dfddd010c5c0b587e8e Mon Sep 17 00:00:00 2001 From: James Lopez Date: Wed, 3 Jan 2018 16:11:17 +0100 Subject: deprecate check integrity task --- lib/tasks/gitlab/check.rake | 41 ++--------------------------------------- 1 file changed, 2 insertions(+), 39 deletions(-) diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index dfade1f3885..903e84359cd 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -387,14 +387,8 @@ namespace :gitlab do namespace :repo do desc "GitLab | Check the integrity of the repositories managed by GitLab" task check: :environment do - Gitlab.config.repositories.storages.each do |name, repository_storage| - namespace_dirs = Dir.glob(File.join(repository_storage['path'], '*')) - - namespace_dirs.each do |namespace_dir| - repo_dirs = Dir.glob(File.join(namespace_dir, '*')) - repo_dirs.each { |repo_dir| check_repo_integrity(repo_dir) } - end - end + puts "This task is deprecated. Please use gitlab:git:fsck instead".color(:red) + Rake::Task["gitlab:git:fsck"].execute end end @@ -461,35 +455,4 @@ namespace :gitlab do puts "FAIL. Please update gitlab-shell to #{required_version} from #{current_version}".color(:red) end end - - def check_repo_integrity(repo_dir) - puts "\nChecking repo at #{repo_dir.color(:yellow)}" - - git_fsck(repo_dir) - check_config_lock(repo_dir) - check_ref_locks(repo_dir) - end - - def git_fsck(repo_dir) - puts "Running `git fsck`".color(:yellow) - system(*%W(#{Gitlab.config.git.bin_path} fsck), chdir: repo_dir) - end - - def check_config_lock(repo_dir) - config_exists = File.exist?(File.join(repo_dir, 'config.lock')) - config_output = config_exists ? 'yes'.color(:red) : 'no'.color(:green) - puts "'config.lock' file exists?".color(:yellow) + " ... #{config_output}" - end - - def check_ref_locks(repo_dir) - lock_files = Dir.glob(File.join(repo_dir, 'refs/heads/*.lock')) - if lock_files.present? - puts "Ref lock files exist:".color(:red) - lock_files.each do |lock_file| - puts " #{lock_file}" - end - else - puts "No ref lock files exist".color(:green) - end - end end -- cgit v1.2.1 From de36a8e27961d4c2af43d0ac2d700a391c245353 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 4 Jan 2018 11:02:43 +0100 Subject: refactor spec, add docs --- doc/administration/raketasks/check.md | 4 ++-- lib/tasks/gitlab/git.rake | 1 + spec/tasks/gitlab/git_rake_spec.rb | 16 ++++++++-------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/doc/administration/raketasks/check.md b/doc/administration/raketasks/check.md index c8b5434c068..7dabc014bad 100644 --- a/doc/administration/raketasks/check.md +++ b/doc/administration/raketasks/check.md @@ -34,13 +34,13 @@ This task loops through all repositories on the GitLab server and runs the **Omnibus Installation** ``` -sudo gitlab-rake gitlab:repo:check +sudo gitlab-rake gitlab:git:fsck ``` **Source Installation** ```bash -sudo -u git -H bundle exec rake gitlab:repo:check RAILS_ENV=production +sudo -u git -H bundle exec rake gitlab:git:fsck RAILS_ENV=production ``` ### Check repositories for a specific user diff --git a/lib/tasks/gitlab/git.rake b/lib/tasks/gitlab/git.rake index 5c1b19860f0..3f5dd2ae3b3 100644 --- a/lib/tasks/gitlab/git.rake +++ b/lib/tasks/gitlab/git.rake @@ -36,6 +36,7 @@ namespace :gitlab do check_config_lock(repo) check_ref_locks(repo) end + if failures.empty? puts "Done".color(:green) else diff --git a/spec/tasks/gitlab/git_rake_spec.rb b/spec/tasks/gitlab/git_rake_spec.rb index 44a2607bea2..dacc5dc5ae7 100644 --- a/spec/tasks/gitlab/git_rake_spec.rb +++ b/spec/tasks/gitlab/git_rake_spec.rb @@ -1,5 +1,3 @@ - - require 'rake_helper' describe 'gitlab:git rake tasks' do @@ -8,8 +6,10 @@ describe 'gitlab:git rake tasks' do storages = { 'default' => { 'path' => Settings.absolute('tmp/tests/default_storage') } } - FileUtils.mkdir_p(Settings.absolute('tmp/tests/default_storage/@repo/1/2/test.git')) + FileUtils.mkdir_p(Settings.absolute('tmp/tests/default_storage/@hashed/1/2/test.git')) allow(Gitlab.config.repositories).to receive(:storages).and_return(storages) + allow_any_instance_of(String).to receive(:color) { |string, _color| string } + stub_warn_user_is_not_gitlab end @@ -18,19 +18,19 @@ describe 'gitlab:git rake tasks' do end describe 'fsck' do - it 'outputs the right git command' do - expect { run_rake_task('gitlab:git:fsck') }.to output(/Performed Checking integrity/).to_stdout + it 'outputs the integrity check for a repo' do + expect { run_rake_task('gitlab:git:fsck') }.to output(/Performed Checking integrity at .*@hashed\/1\/2\/test.git/).to_stdout end it 'errors out about config.lock issues' do - FileUtils.touch(Settings.absolute('tmp/tests/default_storage/@repo/1/2/test.git/config.lock')) + FileUtils.touch(Settings.absolute('tmp/tests/default_storage/@hashed/1/2/test.git/config.lock')) expect { run_rake_task('gitlab:git:fsck') }.to output(/file exists\? ... yes/).to_stdout end it 'errors out about ref lock issues' do - FileUtils.mkdir_p(Settings.absolute('tmp/tests/default_storage/@repo/1/2/test.git/refs/heads')) - FileUtils.touch(Settings.absolute('tmp/tests/default_storage/@repo/1/2/test.git/refs/heads/blah.lock')) + FileUtils.mkdir_p(Settings.absolute('tmp/tests/default_storage/@hashed/1/2/test.git/refs/heads')) + FileUtils.touch(Settings.absolute('tmp/tests/default_storage/@hashed/1/2/test.git/refs/heads/blah.lock')) expect { run_rake_task('gitlab:git:fsck') }.to output(/Ref lock files exist:/).to_stdout end -- cgit v1.2.1 From 21d0a3a6c4ee78724e084f355da9e40c4243b036 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Thu, 4 Jan 2018 11:19:11 +0100 Subject: add missing changelog --- changelogs/unreleased/40228-verify-integrity-of-repositories.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/40228-verify-integrity-of-repositories.yml diff --git a/changelogs/unreleased/40228-verify-integrity-of-repositories.yml b/changelogs/unreleased/40228-verify-integrity-of-repositories.yml new file mode 100644 index 00000000000..261d48652db --- /dev/null +++ b/changelogs/unreleased/40228-verify-integrity-of-repositories.yml @@ -0,0 +1,5 @@ +--- +title: Fix gitlab-rake gitlab:import:repos import schedule +merge_request: 15931 +author: +type: fixed -- cgit v1.2.1 From 3b8cee95a7d62bce3b3890ff8618073958dc2fb0 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 4 Jan 2018 11:28:18 +0100 Subject: Fix cycle analytics specs --- spec/lib/gitlab/cycle_analytics/events_spec.rb | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/spec/lib/gitlab/cycle_analytics/events_spec.rb b/spec/lib/gitlab/cycle_analytics/events_spec.rb index 28ea7d4c303..38a47a159e1 100644 --- a/spec/lib/gitlab/cycle_analytics/events_spec.rb +++ b/spec/lib/gitlab/cycle_analytics/events_spec.rb @@ -122,17 +122,18 @@ describe 'cycle analytics events' do let(:stage) { :test } let(:merge_request) { MergeRequest.first } + let!(:pipeline) do create(:ci_pipeline, ref: merge_request.source_branch, sha: merge_request.diff_head_sha, - project: context.project, + project: project, head_pipeline_of: merge_request) end before do - create(:ci_build, pipeline: pipeline, status: :success, author: user) - create(:ci_build, pipeline: pipeline, status: :success, author: user) + create(:ci_build, :success, pipeline: pipeline, author: user) + create(:ci_build, :success, pipeline: pipeline, author: user) pipeline.run! pipeline.succeed! @@ -219,17 +220,18 @@ describe 'cycle analytics events' do describe '#staging_events' do let(:stage) { :staging } let(:merge_request) { MergeRequest.first } + let!(:pipeline) do create(:ci_pipeline, ref: merge_request.source_branch, sha: merge_request.diff_head_sha, - project: context.project, + project: project, head_pipeline_of: merge_request) end before do - create(:ci_build, pipeline: pipeline, status: :success, author: user) - create(:ci_build, pipeline: pipeline, status: :success, author: user) + create(:ci_build, :success, pipeline: pipeline, author: user) + create(:ci_build, :success, pipeline: pipeline, author: user) pipeline.run! pipeline.succeed! -- cgit v1.2.1 From 55137e200d9f9f8cdbdadc96cf098a088873e3f1 Mon Sep 17 00:00:00 2001 From: Joshua Lambert Date: Thu, 4 Jan 2018 10:59:08 +0000 Subject: Include integration tests in CE/EE testing documentation --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 01d4a546b97..2b79f0825e2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -553,7 +553,7 @@ the feature you contribute through all of these steps. 1. Description explaining the relevancy (see following item) 1. Working and clean code that is commented where needed -1. [Unit and system tests][testing] that pass on the CI server +1. [Unit, integration, and system tests][testing] that pass on the CI server 1. Performance/scalability implications have been considered, addressed, and tested 1. [Documented][doc-styleguide] in the `/doc` directory 1. [Changelog entry added][changelog], if necessary -- cgit v1.2.1 From e8acb3f11755811fca28d38bb0cbba44add7b0af Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 4 Jan 2018 12:09:14 +0100 Subject: Copy-edit end-to-end testing guide documentation --- doc/development/testing_guide/end_to_end_tests.md | 34 ++++++++++++----------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/doc/development/testing_guide/end_to_end_tests.md b/doc/development/testing_guide/end_to_end_tests.md index 561547e1581..30efe3e3b76 100644 --- a/doc/development/testing_guide/end_to_end_tests.md +++ b/doc/development/testing_guide/end_to_end_tests.md @@ -23,24 +23,25 @@ You can find these nightly pipelines at [GitLab QA pipelines page][gitlab-qa-pip It is also possible to trigger build of GitLab packages and then pass these package to GitLab QA to run tests in a [pipeline][gitlab-qa-pipelines]. -Developers can trigger a `package-qa` manual action, that should be present in -the merge request widget in your merge request. +Developers can trigger the `package-qa` manual action, that should be present in +the merge request widget. -It is possible to trigger Gitlab QA pipeline from merge requests in GitLab CE -and GitLab EE, but QA triggering manual action is also available in the Omnibus -GitLab project as well. +It is also possible to trigger Gitlab QA pipeline from merge requests in +Omnibus GitLab project. You can find a manual action that is similar to +`package-qa`, mentioned above, in your Omnibus-related merge requests as well. Below you can read more about how to use it and how does it work. #### How does it work? -Currently, we are _multi-project pipeline_-like approach to run QA pipelines. +Currently, we are using _multi-project pipeline_-like approach to run QA +pipelines. 1. Developer triggers a manual action, that can be found in CE and EE merge -requests, what starts a chain of pipelines. +requests. This starts a chain of pipelines in multiple projects. -1. The script, that is being executed, triggers a pipeline in GitLab Omnibus -projects, and waits for the resulting status. We call this a _status attribution_. +1. The script being executed triggers a pipeline in GitLab Omnibus and waits +for the resulting status. We call this a _status attribution_. 1. GitLab packages are being built in Omnibus pipeline. Packages are going to be pushed to Container Registry. @@ -50,24 +51,25 @@ pipeline, that is now running in Omnibus, triggers a new pipeline in the GitLab QA project. It also waits for a resulting status. 1. GitLab QA pulls images from the registry, spins-up containers and runs tests -against a test environment that has been just orchestrated by `gitlab-qa` tool. +against a test environment that has been just orchestrated by the `gitlab-qa` +tool. -1. The result of GitLab QA pipeline is being propagated upstream, through +1. The result of the GitLab QA pipeline is being propagated upstream, through Omnibus, back to CE / EE merge request. #### How do I write tests? In order to write new tests, you first need to learn more about GitLab QA -architecture. There is some documentation about it in GitLab QA project -[here][gitlab-qa-architecture]. +architecture. See the [documentation about it][gitlab-qa-architecture] in +GitLab QA project. -Once you decided were to put test environment orchestration scenarios and -instance specs, take a looks at [relevant documentation][instance-qa-readme] +Once you decided where to put test environment orchestration scenarios and +instance specs, take a look at the [relevant documentation][instance-qa-readme] and examples in [the `qa/` directory][instance-qa-examples]. ## Where can I ask for help? -You can ask question in `#qa` channel on Slack (GitLab internal) or you can +You can ask question in the `#qa` channel on Slack (GitLab internal) or you can find an issue you would like to work on in [the issue tracker][gitlab-qa-issues] and start a new discussion there. -- cgit v1.2.1 From 0ba0f9de08eb3d5113f4557b925506167484950a Mon Sep 17 00:00:00 2001 From: Ahmad Sherif Date: Wed, 3 Jan 2018 13:31:06 +0100 Subject: Prepare Gitlab::Git::Repository#rebase for Gitaly migration --- lib/gitlab/git/operation_service.rb | 5 +++++ lib/gitlab/git/repository.rb | 9 ++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/git/operation_service.rb b/lib/gitlab/git/operation_service.rb index ef5bdbaf819..3fb0e2eed93 100644 --- a/lib/gitlab/git/operation_service.rb +++ b/lib/gitlab/git/operation_service.rb @@ -97,6 +97,11 @@ module Gitlab end end + def update_branch(branch_name, newrev, oldrev) + ref = Gitlab::Git::BRANCH_REF_PREFIX + branch_name + update_ref_in_hooks(ref, newrev, oldrev) + end + private # Returns [newrev, should_run_after_create, should_run_after_create_branch] diff --git a/lib/gitlab/git/repository.rb b/lib/gitlab/git/repository.rb index 176bd953ca1..7c6349f4e84 100644 --- a/lib/gitlab/git/repository.rb +++ b/lib/gitlab/git/repository.rb @@ -1212,9 +1212,16 @@ module Gitlab rebase_path = worktree_path(REBASE_WORKTREE_PREFIX, rebase_id) env = git_env_for_user(user) + if remote_repository.is_a?(RemoteRepository) + env.merge!(remote_repository.fetch_env) + remote_repo_path = GITALY_INTERNAL_URL + else + remote_repo_path = remote_repository.path + end + with_worktree(rebase_path, branch, env: env) do run_git!( - %W(pull --rebase #{remote_repository.path} #{remote_branch}), + %W(pull --rebase #{remote_repo_path} #{remote_branch}), chdir: rebase_path, env: env ) -- cgit v1.2.1 From 88cc49fa17d212e087f6e8a5aec0b2a680cec6e3 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 4 Jan 2018 13:00:02 +0100 Subject: Add test for restoring associations with import/export --- spec/lib/gitlab/import_export/project.json | 26 ++++++++++++++++++++-- .../import_export/project_tree_restorer_spec.rb | 12 ++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index f0752649121..6778b23ee7f 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -6465,6 +6465,26 @@ } } ], + "stages": [ + { + "id": 11, + "project_id": 5, + "pipeline_id": 36, + "name": "test", + "status": 1, + "created_at": "2016-03-22T15:44:44.772Z", + "updated_at": "2016-03-29T06:44:44.634Z" + }, + { + "id": 12, + "project_id": 5, + "pipeline_id": 36, + "name": "deploy", + "status": 2, + "created_at": "2016-03-22T15:45:45.772Z", + "updated_at": "2016-03-29T06:45:45.634Z" + } + ], "statuses": [ { "id": 71, @@ -6487,6 +6507,7 @@ "stage": "test", "trigger_request_id": null, "stage_idx": 1, + "stage_id": 11, "tag": null, "ref": "master", "user_id": null, @@ -6515,15 +6536,16 @@ "runner_id": null, "coverage": null, "commit_id": 36, - "commands": "$ build command", + "commands": "$ deploy command", "job_id": null, "name": "test build 2", "deploy": false, "options": null, "allow_failure": false, - "stage": "test", + "stage": "deploy", "trigger_request_id": null, "stage_idx": 1, + "stage_id": 12, "tag": null, "ref": "master", "user_id": null, diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index 0ab3afd0074..d4342f2b1a8 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -179,6 +179,18 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end end end + + context 'when restoring hierarchy of pipeline, stages and jobs' do + let(:pipeline) { Ci::Pipeline.first } + + it 'restores pipeline stages' do + expect(pipeline.stages.count).to be 2 + end + + it 'correctly restores association between a stage and a job' do + expect(pipeline.statuses).to all(have_attributes(stage_id: a_value > 10)) + end + end end end -- cgit v1.2.1 From 099a59e8fd461a01b1b4f84583b46b4c7d6888e9 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 4 Jan 2018 13:11:24 +0100 Subject: Check if stage_id relation has been assigned only --- spec/lib/gitlab/import_export/project_tree_restorer_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb index d4342f2b1a8..70a6d1a3c6a 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -188,7 +188,7 @@ describe Gitlab::ImportExport::ProjectTreeRestorer do end it 'correctly restores association between a stage and a job' do - expect(pipeline.statuses).to all(have_attributes(stage_id: a_value > 10)) + expect(pipeline.statuses).to all(have_attributes(stage_id: a_value > 0)) end end end -- cgit v1.2.1 From d6bd608b354928196142f5bec65f29b80960cd30 Mon Sep 17 00:00:00 2001 From: Mark Fletcher Date: Thu, 4 Jan 2018 12:50:34 +0000 Subject: Specifies the accepted refs for downloading an archive via the API --- doc/api/repositories.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/repositories.md b/doc/api/repositories.md index 03b32577872..5fb25e40ed7 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -113,7 +113,7 @@ GET /projects/:id/repository/archive Parameters: - `id` (required) - The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user -- `sha` (optional) - The commit SHA to download defaults to the tip of the default branch +- `sha` (optional) - The commit SHA to download. A tag, branch reference or sha can be used. This defaults to the tip of the default branch if not specified ## Compare branches, tags or commits -- cgit v1.2.1 From ebdcbd4552b16b8aacaaaf0bdc1c600685d4e696 Mon Sep 17 00:00:00 2001 From: Alessio Caiazza Date: Wed, 3 Jan 2018 09:57:48 +0100 Subject: Do not run ee_compat_check on security branches Branches started from `security-X-Y` will likely fail on `ee_compat_check`, the check tries to merge against EE `master` which may likely fail for MR that are not targetted on `master`, like security fixes. This commit disables `ee_compat_check` on branches starting with `security-`. --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e98ac200332..038eeb2bf61 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -431,6 +431,7 @@ ee_compat_check: - master - tags - /^[\d-]+-stable(-ee)?/ + - /^security-/ - branches@gitlab-org/gitlab-ee - branches@gitlab/gitlab-ee retry: 0 -- cgit v1.2.1 From dac51ace521d7b2b2a5a5bb19167a8690ead242e Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Wed, 3 Jan 2018 17:44:29 +0100 Subject: Eager load event target authors whenever possible This ensures that the "author" association of an event's "target" association is eager loaded whenever the "target" association defines an "author" association. This in turn solves the N+1 query problem we first tried to solve in https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/15788 but caused problems when displaying milestones as those don't define an "author" association. The approach in this commit does mean that the authors are _always_ eager loaded since this takes place in the "belongs_to" block. This however shouldn't pose too much of a problem, and as far as I can tell there's no real way around this unfortunately. --- app/models/event.rb | 13 ++++++++++++- .../conditionally-eager-load-event-target-authors.yml | 5 +++++ spec/features/dashboard/activity_spec.rb | 7 +++++++ spec/models/event_spec.rb | 16 ++++++++++++++++ 4 files changed, 40 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/conditionally-eager-load-event-target-authors.yml diff --git a/app/models/event.rb b/app/models/event.rb index 0997b056c6a..8a79100de5a 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -48,7 +48,18 @@ class Event < ActiveRecord::Base belongs_to :author, class_name: "User" belongs_to :project - belongs_to :target, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations + + belongs_to :target, -> { + # If the association for "target" defines an "author" association we want to + # eager-load this so Banzai & friends don't end up performing N+1 queries to + # get the authors of notes, issues, etc. + if reflections['events'].active_record.reflect_on_association(:author) + includes(:author) + else + self + end + }, polymorphic: true # rubocop:disable Cop/PolymorphicAssociations + has_one :push_event_payload # Callbacks diff --git a/changelogs/unreleased/conditionally-eager-load-event-target-authors.yml b/changelogs/unreleased/conditionally-eager-load-event-target-authors.yml new file mode 100644 index 00000000000..a5f1a958fa8 --- /dev/null +++ b/changelogs/unreleased/conditionally-eager-load-event-target-authors.yml @@ -0,0 +1,5 @@ +--- +title: Eager load event target authors whenever possible +merge_request: +author: +type: performance diff --git a/spec/features/dashboard/activity_spec.rb b/spec/features/dashboard/activity_spec.rb index bd115785646..a74a8aac2b2 100644 --- a/spec/features/dashboard/activity_spec.rb +++ b/spec/features/dashboard/activity_spec.rb @@ -24,6 +24,7 @@ feature 'Dashboard > Activity' do end let(:note) { create(:note, project: project, noteable: merge_request) } + let(:milestone) { create(:milestone, :active, project: project, title: '1.0') } let!(:push_event) do event = create(:push_event, project: project, author: user) @@ -54,6 +55,10 @@ feature 'Dashboard > Activity' do create(:event, :commented, project: project, target: note, author: user) end + let!(:milestone_event) do + create(:event, :closed, project: project, target: milestone, author: user) + end + before do project.add_master(user) @@ -68,6 +73,7 @@ feature 'Dashboard > Activity' do expect(page).to have_content('accepted') expect(page).to have_content('closed') expect(page).to have_content('commented on') + expect(page).to have_content('closed milestone') end end @@ -107,6 +113,7 @@ feature 'Dashboard > Activity' do expect(page).not_to have_content('accepted') expect(page).to have_content('closed') expect(page).not_to have_content('commented on') + expect(page).to have_content('closed milestone') end end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index e999192940c..67f49348acb 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -347,6 +347,22 @@ describe Event do end end + describe '#target' do + it 'eager loads the author of an event target' do + create(:closed_issue_event) + + events = described_class.preload(:target).all.to_a + count = ActiveRecord::QueryRecorder + .new { events.first.target.author }.count + + # This expectation exists to make sure the test doesn't pass when the + # author is for some reason not loaded at all. + expect(events.first.target.author).to be_an_instance_of(User) + + expect(count).to be_zero + end + end + def create_push_event(project, user) event = create(:push_event, project: project, author: user) -- cgit v1.2.1 From e1f0d23c898d9133e36afec868abdcef4fb4e8e8 Mon Sep 17 00:00:00 2001 From: Jacob Schatz Date: Thu, 4 Jan 2018 08:44:27 -0500 Subject: Moves prettier to dev dependency --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 8c3932dccfd..3587b015fb3 100644 --- a/package.json +++ b/package.json @@ -61,7 +61,6 @@ "mousetrap": "^1.4.6", "name-all-modules-plugin": "^1.0.1", "pikaday": "^1.6.1", - "prettier": "^1.9.2", "prismjs": "^1.6.0", "raphael": "^2.2.7", "raven-js": "^3.14.0", @@ -109,6 +108,7 @@ "karma-sourcemap-loader": "^0.3.7", "karma-webpack": "^2.0.4", "nodemon": "^1.11.0", + "prettier": "1.9.2", "webpack-dev-server": "^2.6.1" } } diff --git a/yarn.lock b/yarn.lock index 381b1a243f8..b29fc022bde 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5146,14 +5146,14 @@ preserve@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" +prettier@1.9.2: + version "1.9.2" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.9.2.tgz#96bc2132f7a32338e6078aeb29727178c6335827" + prettier@^1.7.0: version "1.8.2" resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.8.2.tgz#bff83e7fd573933c607875e5ba3abbdffb96aeb8" -prettier@^1.9.2: - version "1.9.2" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.9.2.tgz#96bc2132f7a32338e6078aeb29727178c6335827" - 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 528b5eeb761f627bb1972fa8a25a09dc1cea4556 Mon Sep 17 00:00:00 2001 From: Sean McGivern Date: Wed, 3 Jan 2018 14:18:13 +0000 Subject: Fix error when viewing diffs without blobs Old merge requests can have diffs without corresponding blobs. (This also may be possible for commit diffs in corrupt repositories.) We can't use the `&.` operator on the blobs, because the blob objects are never nil, but `BatchLoader` instances that delegate to `Blob`. We can't use `Object#try`, because `Blob` doesn't inherit from `Object`. `BatchLoader` provides a `__sync` method that returns the delegated object, but using `itself` also works because it's forwarded, and will work for non-`BatchLoader` instances too. So the simplest solution is to just use that with the `&.` operator. --- ...on-undefined-method-binary-for-nil-nilclass.yml | 5 ++++ lib/gitlab/diff/file.rb | 31 ++++++++++++++++------ spec/lib/gitlab/diff/file_spec.rb | 25 +++++++++++++++++ 3 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 changelogs/unreleased/41468-error-500-trying-to-view-a-merge-request-json-undefined-method-binary-for-nil-nilclass.yml diff --git a/changelogs/unreleased/41468-error-500-trying-to-view-a-merge-request-json-undefined-method-binary-for-nil-nilclass.yml b/changelogs/unreleased/41468-error-500-trying-to-view-a-merge-request-json-undefined-method-binary-for-nil-nilclass.yml new file mode 100644 index 00000000000..f69116382f0 --- /dev/null +++ b/changelogs/unreleased/41468-error-500-trying-to-view-a-merge-request-json-undefined-method-binary-for-nil-nilclass.yml @@ -0,0 +1,5 @@ +--- +title: Fix viewing merge request diffs where the underlying blobs are unavailable +merge_request: +author: +type: fixed diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb index cd490aaa291..34b070dd375 100644 --- a/lib/gitlab/diff/file.rb +++ b/lib/gitlab/diff/file.rb @@ -116,8 +116,10 @@ module Gitlab new_content_sha || old_content_sha end + # Use #itself to check the value wrapped by a BatchLoader instance, rather + # than if the BatchLoader instance itself is falsey. def blob - new_blob || old_blob + new_blob&.itself || old_blob&.itself end attr_writer :highlighted_diff_lines @@ -173,7 +175,7 @@ module Gitlab end def binary? - has_binary_notice? || old_blob&.binary? || new_blob&.binary? + has_binary_notice? || try_blobs(:binary?) end def text? @@ -181,15 +183,15 @@ module Gitlab end def external_storage_error? - old_blob&.external_storage_error? || new_blob&.external_storage_error? + try_blobs(:external_storage_error?) end def stored_externally? - old_blob&.stored_externally? || new_blob&.stored_externally? + try_blobs(:stored_externally?) end def external_storage - old_blob&.external_storage || new_blob&.external_storage + try_blobs(:external_storage) end def content_changed? @@ -204,15 +206,15 @@ module Gitlab end def size - [old_blob&.size, new_blob&.size].compact.sum + valid_blobs.map(&:size).sum end def raw_size - [old_blob&.raw_size, new_blob&.raw_size].compact.sum + valid_blobs.map(&:raw_size).sum end def raw_binary? - old_blob&.raw_binary? || new_blob&.raw_binary? + try_blobs(:raw_binary?) end def raw_text? @@ -235,6 +237,19 @@ module Gitlab private + # The blob instances are instances of BatchLoader, which means calling + # &. directly on them won't work. Object#try also won't work, because Blob + # doesn't inherit from Object, but from BasicObject (via SimpleDelegator). + def try_blobs(meth) + old_blob&.itself&.public_send(meth) || new_blob&.itself&.public_send(meth) + end + + # We can't use #compact for the same reason we can't use &., but calling + # #nil? explicitly does work because it is proxied to the blob itself. + def valid_blobs + [old_blob, new_blob].reject(&:nil?) + end + def text_position_properties(line) { old_line: line.old_line, new_line: line.new_line } end diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb index ff9acfd08b9..9204ea37963 100644 --- a/spec/lib/gitlab/diff/file_spec.rb +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -431,4 +431,29 @@ describe Gitlab::Diff::File do end end end + + context 'when neither blob exists' do + let(:blank_diff_refs) { Gitlab::Diff::DiffRefs.new(base_sha: Gitlab::Git::BLANK_SHA, head_sha: Gitlab::Git::BLANK_SHA) } + let(:diff_file) { described_class.new(diff, diff_refs: blank_diff_refs, repository: project.repository) } + + describe '#blob' do + it 'returns a concrete nil so it can be used in boolean expressions' do + actual = diff_file.blob && true + + expect(actual).to be_nil + end + end + + describe '#binary?' do + it 'returns false' do + expect(diff_file).not_to be_binary + end + end + + describe '#size' do + it 'returns zero' do + expect(diff_file.size).to be_zero + end + end + end end -- cgit v1.2.1 From b5fe3916752aafd5c79b2aa7a770fbd51f1b4bef Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 4 Jan 2018 15:44:00 +0100 Subject: Update some Gitaly annotations in Gitlab::Shell --- lib/gitlab/shell.rb | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index 9cdd3d22f18..18da242e1cb 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -71,7 +71,6 @@ module Gitlab # Ex. # add_repository("/path/to/storage", "gitlab/gitlab-ci") # - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 def add_repository(storage, name) relative_path = name.dup relative_path << '.git' unless relative_path.end_with?('.git') @@ -100,7 +99,7 @@ module Gitlab # Ex. # import_repository("/path/to/storage", "gitlab/gitlab-ci", "https://gitlab.com/gitlab-org/gitlab-test.git") # - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/874 def import_repository(storage, name, url) # The timeout ensures the subprocess won't hang forever cmd = gitlab_projects(storage, "#{name}.git") @@ -122,7 +121,6 @@ module Gitlab # Ex. # fetch_remote(my_repo, "upstream") # - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 def fetch_remote(repository, remote, ssh_auth: nil, forced: false, no_tags: false) gitaly_migrate(:fetch_remote) do |is_enabled| if is_enabled @@ -142,7 +140,7 @@ module Gitlab # Ex. # mv_repository("/path/to/storage", "gitlab/gitlab-ci", "randx/gitlab-ci-new") # - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/873 def mv_repository(storage, path, new_path) gitlab_projects(storage, "#{path}.git").mv_project("#{new_path}.git") end @@ -156,7 +154,7 @@ module Gitlab # Ex. # fork_repository("/path/to/forked_from/storage", "gitlab/gitlab-ci", "/path/to/forked_to/storage", "new-namespace/gitlab-ci") # - # Gitaly note: JV: not easy to migrate because this involves two Gitaly servers, not one. + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/817 def fork_repository(forked_from_storage, forked_from_disk_path, forked_to_storage, forked_to_disk_path) gitlab_projects(forked_from_storage, "#{forked_from_disk_path}.git") .fork_repository(forked_to_storage, "#{forked_to_disk_path}.git") @@ -170,7 +168,7 @@ module Gitlab # Ex. # remove_repository("/path/to/storage", "gitlab/gitlab-ci") # - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 + # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/873 def remove_repository(storage, name) gitlab_projects(storage, "#{name}.git").rm_project end @@ -221,7 +219,6 @@ module Gitlab # Ex. # add_namespace("/path/to/storage", "gitlab") # - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/385 def add_namespace(storage, name) Gitlab::GitalyClient.migrate(:add_namespace) do |enabled| if enabled @@ -243,7 +240,6 @@ module Gitlab # Ex. # rm_namespace("/path/to/storage", "gitlab") # - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/385 def rm_namespace(storage, name) Gitlab::GitalyClient.migrate(:remove_namespace) do |enabled| if enabled @@ -261,7 +257,6 @@ module Gitlab # Ex. # mv_namespace("/path/to/storage", "gitlab", "gitlabhq") # - # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/385 def mv_namespace(storage, old_name, new_name) Gitlab::GitalyClient.migrate(:rename_namespace) do |enabled| if enabled -- cgit v1.2.1 From 44d15e414348ab7befaa22636b85d1c0d9064d08 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 4 Jan 2018 16:34:37 +0100 Subject: get it working --- lib/gitlab/import_export/command_line_util.rb | 4 ++++ lib/gitlab/import_export/repo_restorer.rb | 4 +++- lib/gitlab/shell.rb | 4 ++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb index 0135b3c6f22..349f17cf0f8 100644 --- a/lib/gitlab/import_export/command_line_util.rb +++ b/lib/gitlab/import_export/command_line_util.rb @@ -15,6 +15,10 @@ module Gitlab execute(%W(#{git_bin_path} --git-dir=#{repo_path} bundle create #{bundle_path} --all)) end + def git_clone_bundle(repo_path:, bundle_path:) + execute(%W(#{git_bin_path} clone --bare -- #{bundle_path} #{repo_path})) + end + def mkdir_p(path) FileUtils.mkdir_p(path, mode: DEFAULT_MODE) FileUtils.chmod(DEFAULT_MODE, path) diff --git a/lib/gitlab/import_export/repo_restorer.rb b/lib/gitlab/import_export/repo_restorer.rb index 32ca2809b2f..a7e00c71990 100644 --- a/lib/gitlab/import_export/repo_restorer.rb +++ b/lib/gitlab/import_export/repo_restorer.rb @@ -13,7 +13,9 @@ module Gitlab def restore return true unless File.exist?(@path_to_bundle) - gitlab_shell.import_repository(@project.repository_storage_path, @project.disk_path, @path_to_bundle) + repo_path = @project.repository.path_to_repo + git_clone_bundle(repo_path: repo_path, bundle_path: @path_to_bundle) + Gitlab::Git::Repository.create_hooks(repo_path, File.expand_path(Gitlab.config.gitlab_shell.hooks_path)) rescue => e @shared.error(e) false diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index 9cdd3d22f18..1d0eae28f82 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -102,6 +102,10 @@ module Gitlab # # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 def import_repository(storage, name, url) + if url.start_with?('.') || url.start_with?('/') + raise Error.new("don't use disk paths with import_repository: #{url.inspect}") + end + # The timeout ensures the subprocess won't hang forever cmd = gitlab_projects(storage, "#{name}.git") success = cmd.import_project(url, git_timeout) -- cgit v1.2.1 From 80242f246b69a803120bc06527b80601fafc526c Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 4 Jan 2018 16:37:18 +0100 Subject: Hide hooks stuff --- lib/gitlab/import_export/command_line_util.rb | 1 + lib/gitlab/import_export/repo_restorer.rb | 4 +--- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/import_export/command_line_util.rb b/lib/gitlab/import_export/command_line_util.rb index 349f17cf0f8..dd5d35feab9 100644 --- a/lib/gitlab/import_export/command_line_util.rb +++ b/lib/gitlab/import_export/command_line_util.rb @@ -17,6 +17,7 @@ module Gitlab def git_clone_bundle(repo_path:, bundle_path:) execute(%W(#{git_bin_path} clone --bare -- #{bundle_path} #{repo_path})) + Gitlab::Git::Repository.create_hooks(repo_path, File.expand_path(Gitlab.config.gitlab_shell.hooks_path)) end def mkdir_p(path) diff --git a/lib/gitlab/import_export/repo_restorer.rb b/lib/gitlab/import_export/repo_restorer.rb index a7e00c71990..d0e5cfcfd3e 100644 --- a/lib/gitlab/import_export/repo_restorer.rb +++ b/lib/gitlab/import_export/repo_restorer.rb @@ -13,9 +13,7 @@ module Gitlab def restore return true unless File.exist?(@path_to_bundle) - repo_path = @project.repository.path_to_repo - git_clone_bundle(repo_path: repo_path, bundle_path: @path_to_bundle) - Gitlab::Git::Repository.create_hooks(repo_path, File.expand_path(Gitlab.config.gitlab_shell.hooks_path)) + git_clone_bundle(repo_path: @project.repository.path_to_repo, bundle_path: @path_to_bundle) rescue => e @shared.error(e) false -- cgit v1.2.1 From e28bf81e9aa2b00452e71e9e8dd5f485c541e7d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 4 Jan 2018 16:37:21 +0100 Subject: Rename db:seed_fu-{pg,mysql} to gitlab:setup-{pg,mysql} MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .gitlab-ci.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index e98ac200332..82bc62bc0ab 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -508,7 +508,7 @@ db:rollback-mysql: <<: *db-rollback <<: *use-mysql -.db-seed_fu: &db-seed_fu +.gitlab-setup: &gitlab-setup <<: *dedicated-runner <<: *except-docs-and-qa <<: *pull-cache @@ -529,12 +529,12 @@ db:rollback-mysql: paths: - log/development.log -db:seed_fu-pg: - <<: *db-seed_fu +gitlab:setup-pg: + <<: *gitlab-setup <<: *use-pg -db:seed_fu-mysql: - <<: *db-seed_fu +gitlab:setup-mysql: + <<: *gitlab-setup <<: *use-mysql # Frontend-related jobs -- cgit v1.2.1 From 3df6fa6c05ad86f1bc861a8f38d8096098110e37 Mon Sep 17 00:00:00 2001 From: Kushal Pandya Date: Thu, 4 Jan 2018 21:33:25 +0530 Subject: Enclose props in quotes --- app/assets/javascripts/groups/components/item_stats.vue | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/assets/javascripts/groups/components/item_stats.vue b/app/assets/javascripts/groups/components/item_stats.vue index 803dc63d39c..3a94e57a028 100644 --- a/app/assets/javascripts/groups/components/item_stats.vue +++ b/app/assets/javascripts/groups/components/item_stats.vue @@ -43,27 +43,27 @@ export default { css-class="number-subgroups" icon-name="folder" :title="s__('Subgroups')" - :value=item.subgroupCount + :value="item.subgroupCount" /> Date: Thu, 4 Jan 2018 21:47:40 +0530 Subject: Use `__` instead of `s__` when context is not required --- app/assets/javascripts/groups/components/item_stats.vue | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/groups/components/item_stats.vue b/app/assets/javascripts/groups/components/item_stats.vue index 3a94e57a028..2e42fb6c9a6 100644 --- a/app/assets/javascripts/groups/components/item_stats.vue +++ b/app/assets/javascripts/groups/components/item_stats.vue @@ -42,21 +42,21 @@ export default { v-if="isGroup" css-class="number-subgroups" icon-name="folder" - :title="s__('Subgroups')" + :title="__('Subgroups')" :value="item.subgroupCount" /> Date: Tue, 2 Jan 2018 12:57:28 -0500 Subject: Remove downcase from special path helper --- app/helpers/projects_helper.rb | 2 +- changelogs/unreleased/jramsay-41590-add-readme-case.yml | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/jramsay-41590-add-readme-case.yml diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 4a6b22b5ff6..f7bdcc6fd9c 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -389,7 +389,7 @@ module ProjectsHelper end def add_special_file_path(project, file_name:, commit_message: nil, branch_name: nil, context: nil) - commit_message ||= s_("CommitMessage|Add %{file_name}") % { file_name: file_name.downcase } + commit_message ||= s_("CommitMessage|Add %{file_name}") % { file_name: file_name } project_new_blob_path( project, project.default_branch || 'master', diff --git a/changelogs/unreleased/jramsay-41590-add-readme-case.yml b/changelogs/unreleased/jramsay-41590-add-readme-case.yml new file mode 100644 index 00000000000..37b2bd44e0e --- /dev/null +++ b/changelogs/unreleased/jramsay-41590-add-readme-case.yml @@ -0,0 +1,5 @@ +--- +title: Fix inconsistent downcase of filenames in prefilled `Add` commit messages +merge_request: 16232 +author: James Ramsay +type: fixed -- cgit v1.2.1 From 0a02b0d8eada41b72d19bee3f963d7cee5b19c57 Mon Sep 17 00:00:00 2001 From: Gauthier Wallet Date: Thu, 4 Jan 2018 16:26:14 +0000 Subject: Update settings.md --- doc/api/settings.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api/settings.md b/doc/api/settings.md index 0e4758cda2d..0b5b1f0c134 100644 --- a/doc/api/settings.md +++ b/doc/api/settings.md @@ -69,7 +69,7 @@ PUT /application/settings | `after_sign_up_text` | string | no | Text shown to the user after signing up | | `akismet_api_key` | string | no | API key for akismet spam protection | | `akismet_enabled` | boolean | no | Enable or disable akismet spam protection | -| `circuitbreaker_access_retries | integer | no | The number of attempts GitLab will make to access a storage. | +| `circuitbreaker_access_retries` | integer | no | The number of attempts GitLab will make to access a storage. | | `circuitbreaker_check_interval` | integer | no | Number of seconds in between storage checks. | | `circuitbreaker_failure_count_threshold` | integer | no | The number of failures of after which GitLab will completely prevent access to the storage. | | `circuitbreaker_failure_reset_time` | integer | no | Time in seconds GitLab will keep storage failure information. When no failures occur during this time, the failure information is reset. | -- cgit v1.2.1 From ac2cb65ab3dc2688b3a1db9de661dc01ed196177 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 4 Jan 2018 18:00:28 +0100 Subject: Remove the Commit#tree method --- app/models/commit.rb | 2 +- lib/gitlab/git/blob.rb | 4 ++-- lib/gitlab/git/commit.rb | 7 +++++-- spec/lib/gitlab/git/blob_spec.rb | 4 ++-- spec/lib/gitlab/git/commit_spec.rb | 1 - spec/models/commit_spec.rb | 1 - 6 files changed, 10 insertions(+), 9 deletions(-) diff --git a/app/models/commit.rb b/app/models/commit.rb index 2be07ca7d3c..39d7f5b159d 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -371,7 +371,7 @@ class Commit # # Returns a symbol def uri_type(path) - entry = @raw.tree.path(path) + entry = @raw.rugged_tree_entry(path) if entry[:type] == :blob blob = ::Blob.decorate(Gitlab::Git::Blob.new(name: entry[:name]), @project) blob.image? || blob.video? ? :raw : :blob diff --git a/lib/gitlab/git/blob.rb b/lib/gitlab/git/blob.rb index 228d97a87ab..c4caa306b5d 100644 --- a/lib/gitlab/git/blob.rb +++ b/lib/gitlab/git/blob.rb @@ -154,8 +154,8 @@ module Gitlab end def find_by_rugged(repository, sha, path, limit:) - commit = repository.lookup(sha) - root_tree = commit.tree + rugged_commit = repository.lookup(sha) + root_tree = rugged_commit.tree blob_entry = find_entry_by_path(repository, root_tree.oid, path) diff --git a/lib/gitlab/git/commit.rb b/lib/gitlab/git/commit.rb index 145721dea76..016437b2419 100644 --- a/lib/gitlab/git/commit.rb +++ b/lib/gitlab/git/commit.rb @@ -15,8 +15,6 @@ module Gitlab attr_accessor *SERIALIZE_KEYS # rubocop:disable Lint/AmbiguousOperator - delegate :tree, to: :rugged_commit - def ==(other) return false unless other.is_a?(Gitlab::Git::Commit) @@ -452,6 +450,11 @@ module Gitlab ) end + # Is this the same as Blob.find_entry_by_path ? + def rugged_tree_entry(path) + rugged_commit.tree.path(path) + end + private def init_from_hash(hash) diff --git a/spec/lib/gitlab/git/blob_spec.rb b/spec/lib/gitlab/git/blob_spec.rb index c04a9688503..67838163b05 100644 --- a/spec/lib/gitlab/git/blob_spec.rb +++ b/spec/lib/gitlab/git/blob_spec.rb @@ -146,7 +146,7 @@ describe Gitlab::Git::Blob, seed_helper: true do context 'when sha references a tree' do it 'returns nil' do - tree = Gitlab::Git::Commit.find(repository, 'master').tree + tree = repository.rugged.rev_parse('master^{tree}') blob = Gitlab::Git::Blob.raw(repository, tree.oid) @@ -237,7 +237,7 @@ describe Gitlab::Git::Blob, seed_helper: true do end describe '.batch_lfs_pointers' do - let(:tree_object) { Gitlab::Git::Commit.find(repository, 'master').tree } + let(:tree_object) { repository.rugged.rev_parse('master^{tree}') } let(:non_lfs_blob) do Gitlab::Git::Blob.find( diff --git a/spec/lib/gitlab/git/commit_spec.rb b/spec/lib/gitlab/git/commit_spec.rb index 6d35734d306..6a07a3ca8b8 100644 --- a/spec/lib/gitlab/git/commit_spec.rb +++ b/spec/lib/gitlab/git/commit_spec.rb @@ -55,7 +55,6 @@ describe Gitlab::Git::Commit, seed_helper: true do it { expect(@commit.parents).to eq(@gitlab_parents) } it { expect(@commit.parent_id).to eq(@parents.first.oid) } it { expect(@commit.no_commit_message).to eq("--no commit message") } - it { expect(@commit.tree).to eq(@tree) } after do # Erase the new commit so other tests get the original repo diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 4f02dc33cd8..817254c7d1e 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -181,7 +181,6 @@ eos it { is_expected.to respond_to(:parents) } it { is_expected.to respond_to(:date) } it { is_expected.to respond_to(:diffs) } - it { is_expected.to respond_to(:tree) } it { is_expected.to respond_to(:id) } it { is_expected.to respond_to(:to_patch) } end -- cgit v1.2.1 From dcebe1494e35fcd8870b38f311c5176eab6b2a2f Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 4 Jan 2018 18:05:49 +0100 Subject: rubocop --- lib/gitlab/shell.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/shell.rb b/lib/gitlab/shell.rb index 1d0eae28f82..bef944ef1f9 100644 --- a/lib/gitlab/shell.rb +++ b/lib/gitlab/shell.rb @@ -102,7 +102,7 @@ module Gitlab # # Gitaly migration: https://gitlab.com/gitlab-org/gitaly/issues/387 def import_repository(storage, name, url) - if url.start_with?('.') || url.start_with?('/') + if url.start_with?('.', '/') raise Error.new("don't use disk paths with import_repository: #{url.inspect}") end -- cgit v1.2.1 From 176b60d11055999d56e30b6fe0581fbede2740c4 Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Thu, 4 Jan 2018 18:38:39 +0100 Subject: Remove the Project#repo method --- app/controllers/projects_controller.rb | 2 +- app/models/project.rb | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 6f609348402..6f229b08c0c 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -353,7 +353,7 @@ class ProjectsController < Projects::ApplicationController end def repo_exists? - project.repository_exists? && !project.empty_repo? && project.repo + project.repository_exists? && !project.empty_repo? rescue Gitlab::Git::Repository::NoRepository project.repository.expire_exists_cache diff --git a/app/models/project.rb b/app/models/project.rb index 9c0bbf697e2..4784bbc8a44 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -992,10 +992,6 @@ class Project < ActiveRecord::Base false end - def repo - repository.rugged - end - def url_to_repo gitlab_shell.url_to_repo(full_path) end @@ -1438,7 +1434,7 @@ class Project < ActiveRecord::Base # We'd need to keep track of project full path otherwise directory tree # created with hashed storage enabled cannot be usefully imported using # the import rake task. - repo.config['gitlab.fullpath'] = gl_full_path + repository.rugged.config['gitlab.fullpath'] = gl_full_path rescue Gitlab::Git::Repository::NoRepository => e Rails.logger.error("Error writing to .git/config for project #{full_path} (#{id}): #{e.message}.") nil -- cgit v1.2.1 From e3f215f676488d74ff8ed61dea8e715e73db2934 Mon Sep 17 00:00:00 2001 From: Mike Greiling Date: Thu, 4 Jan 2018 12:25:48 -0600 Subject: fix missing import of timeWeek which would cause errors in prometheus graphs with deployments --- .../javascripts/monitoring/utils/date_time_formatters.js | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/monitoring/utils/date_time_formatters.js b/app/assets/javascripts/monitoring/utils/date_time_formatters.js index 48bdec1e030..068813ddee6 100644 --- a/app/assets/javascripts/monitoring/utils/date_time_formatters.js +++ b/app/assets/javascripts/monitoring/utils/date_time_formatters.js @@ -1,8 +1,18 @@ import { timeFormat as time } from 'd3-time-format'; -import { timeSecond, timeMinute, timeHour, timeDay, timeMonth, timeYear } from 'd3-time'; +import { timeSecond, timeMinute, timeHour, timeDay, timeWeek, timeMonth, timeYear } from 'd3-time'; import { bisector } from 'd3-array'; -const d3 = { time, bisector, timeSecond, timeMinute, timeHour, timeDay, timeMonth, timeYear }; +const d3 = { + time, + bisector, + timeSecond, + timeMinute, + timeHour, + timeDay, + timeWeek, + timeMonth, + timeYear, +}; export const dateFormat = d3.time('%b %-d, %Y'); export const timeFormat = d3.time('%-I:%M%p'); -- cgit v1.2.1 From 0a35f372d232a0ac6b9355d27a9fb9e95b1ee959 Mon Sep 17 00:00:00 2001 From: Jose Ivan Vargas Date: Thu, 4 Jan 2018 18:31:05 +0000 Subject: Added multi editor setting on the profile preferences page --- app/assets/images/multi-editor-off.png | Bin 0 -> 4884 bytes app/assets/images/multi-editor-on.png | Bin 0 -> 5464 bytes app/assets/javascripts/profile/profile.js | 21 +++++++++++++++++++++ app/assets/stylesheets/framework/header.scss | 2 +- app/assets/stylesheets/framework/variables.scss | 5 +++++ .../stylesheets/pages/profiles/preferences.scss | 16 ++++++++++++++++ app/views/layouts/header/_default.html.haml | 2 ++ app/views/profiles/preferences/show.html.haml | 17 +++++++++++++++++ .../jivl-activate-repo-cookie-preferences.yml | 5 +++++ .../user_visits_profile_preferences_page_spec.rb | 12 ++++++++++++ spec/features/projects/user_edits_files_spec.rb | 4 +++- spec/support/cookie_helper.rb | 4 ++++ 12 files changed, 86 insertions(+), 2 deletions(-) create mode 100644 app/assets/images/multi-editor-off.png create mode 100644 app/assets/images/multi-editor-on.png create mode 100644 changelogs/unreleased/jivl-activate-repo-cookie-preferences.yml diff --git a/app/assets/images/multi-editor-off.png b/app/assets/images/multi-editor-off.png new file mode 100644 index 00000000000..82a6127f853 Binary files /dev/null and b/app/assets/images/multi-editor-off.png differ diff --git a/app/assets/images/multi-editor-on.png b/app/assets/images/multi-editor-on.png new file mode 100644 index 00000000000..2bcd29abf13 Binary files /dev/null and b/app/assets/images/multi-editor-on.png differ diff --git a/app/assets/javascripts/profile/profile.js b/app/assets/javascripts/profile/profile.js index 0dc02f012e4..ba4ac850346 100644 --- a/app/assets/javascripts/profile/profile.js +++ b/app/assets/javascripts/profile/profile.js @@ -1,4 +1,5 @@ /* eslint-disable comma-dangle, no-unused-vars, class-methods-use-this, quotes, consistent-return, func-names, prefer-arrow-callback, space-before-function-paren, max-len */ +import Cookies from 'js-cookie'; import Flash from '../flash'; import { getPagePath } from '../lib/utils/common_utils'; @@ -7,6 +8,8 @@ import { getPagePath } from '../lib/utils/common_utils'; constructor({ form } = {}) { this.onSubmitForm = this.onSubmitForm.bind(this); this.form = form || $('.edit-user'); + this.newRepoActivated = Cookies.get('new_repo'); + this.setRepoRadio(); this.bindEvents(); this.initAvatarGlCrop(); } @@ -25,6 +28,7 @@ import { getPagePath } from '../lib/utils/common_utils'; bindEvents() { $('.js-preferences-form').on('change.preference', 'input[type=radio]', this.submitForm); + $('input[name="user[multi_file]"]').on('change', this.setNewRepoCookie); $('#user_notification_email').on('change', this.submitForm); $('#user_notified_of_own_activity').on('change', this.submitForm); $('.update-username').on('ajax:before', this.beforeUpdateUsername); @@ -82,6 +86,23 @@ import { getPagePath } from '../lib/utils/common_utils'; } }); } + + setNewRepoCookie() { + if (this.value === 'off') { + Cookies.remove('new_repo'); + } else { + Cookies.set('new_repo', true, { expires_in: 365 }); + } + } + + setRepoRadio() { + const multiEditRadios = $('input[name="user[multi_file]"]'); + if (this.newRepoActivated || this.newRepoActivated === 'true') { + multiEditRadios.filter('[value=on]').prop('checked', true); + } else { + multiEditRadios.filter('[value=off]').prop('checked', true); + } + } } $(function() { diff --git a/app/assets/stylesheets/framework/header.scss b/app/assets/stylesheets/framework/header.scss index 29714e348a0..ad160f37641 100644 --- a/app/assets/stylesheets/framework/header.scss +++ b/app/assets/stylesheets/framework/header.scss @@ -516,7 +516,7 @@ .header-user { .dropdown-menu-nav { width: auto; - min-width: 140px; + min-width: 160px; margin-top: 4px; color: $gl-text-color; left: auto; diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index 1d6c7a5c472..f7853909f56 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -727,3 +727,8 @@ Popup $popup-triangle-size: 15px; $popup-triangle-border-size: 1px; $popup-box-shadow-color: rgba(90, 90, 90, 0.05); + +/* +Multi file editor +*/ +$border-color-settings: #e1e1e1; diff --git a/app/assets/stylesheets/pages/profiles/preferences.scss b/app/assets/stylesheets/pages/profiles/preferences.scss index c197494b152..68d40b56133 100644 --- a/app/assets/stylesheets/pages/profiles/preferences.scss +++ b/app/assets/stylesheets/pages/profiles/preferences.scss @@ -20,6 +20,22 @@ } } +.multi-file-editor-options { + label { + margin-right: 20px; + text-align: center; + } + + .preview { + font-size: 0; + + img { + border: 1px solid $border-color-settings; + border-radius: 4px; + } + } +} + .application-theme { label { margin-right: 20px; diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml index 99e7f3b568d..39eb71c2bac 100644 --- a/app/views/layouts/header/_default.html.haml +++ b/app/views/layouts/header/_default.html.haml @@ -56,6 +56,8 @@ = link_to "Profile", current_user, class: 'profile-link', data: { user: current_user.username } %li = link_to "Settings", profile_path + %li + = link_to "Turn on multi edit", profile_preferences_path - if current_user %li = link_to "Help", help_path diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml index 66d1d1e8d44..65328791ce5 100644 --- a/app/views/profiles/preferences/show.html.haml +++ b/app/views/profiles/preferences/show.html.haml @@ -3,6 +3,23 @@ = render 'profiles/head' = form_for @user, url: profile_preferences_path, remote: true, method: :put, html: { class: 'row prepend-top-default js-preferences-form' } do |f| + .col-lg-4 + %h4.prepend-top-0 + GitLab multi file editor + %p Unlock an additional editing experience which makes it possible to edit and commit multiple files + .col-lg-8.multi-file-editor-options + = label_tag do + .preview.append-bottom-10= image_tag "multi-editor-off.png" + = f.radio_button :multi_file, "off", checked: true + Off + = label_tag do + .preview.append-bottom-10= image_tag "multi-editor-on.png" + = f.radio_button :multi_file, "on", checked: false + On + + .col-sm-12 + %hr + .col-lg-4.application-theme %h4.prepend-top-0 GitLab navigation theme diff --git a/changelogs/unreleased/jivl-activate-repo-cookie-preferences.yml b/changelogs/unreleased/jivl-activate-repo-cookie-preferences.yml new file mode 100644 index 00000000000..778eaa84381 --- /dev/null +++ b/changelogs/unreleased/jivl-activate-repo-cookie-preferences.yml @@ -0,0 +1,5 @@ +--- +title: Added option to user preferences to enable the multi file editor +merge_request: 16056 +author: +type: added diff --git a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb index 90d6841af0e..266af8f4e3d 100644 --- a/spec/features/profiles/user_visits_profile_preferences_page_spec.rb +++ b/spec/features/profiles/user_visits_profile_preferences_page_spec.rb @@ -32,6 +32,18 @@ describe 'User visits the profile preferences page' do end end + describe 'User changes their multi file editor preferences', :js do + it 'set the new_repo cookie when the option is ON' do + choose 'user_multi_file_on' + expect(get_cookie('new_repo')).not_to be_nil + end + + it 'deletes the new_repo cookie when the option is OFF' do + choose 'user_multi_file_off' + expect(get_cookie('new_repo')).to be_nil + end + end + describe 'User changes their default dashboard', :js do it 'creates a flash message' do select 'Starred Projects', from: 'user_dashboard' diff --git a/spec/features/projects/user_edits_files_spec.rb b/spec/features/projects/user_edits_files_spec.rb index 5c5c6a398f6..05c2be473da 100644 --- a/spec/features/projects/user_edits_files_spec.rb +++ b/spec/features/projects/user_edits_files_spec.rb @@ -33,7 +33,9 @@ describe 'User edits files' do binary_file = File.join(project.repository.root_ref, 'files/images/logo-black.png') visit(project_blob_path(project, binary_file)) - expect(page).not_to have_link('edit') + page.within '.content' do + expect(page).not_to have_link('edit') + end end it 'commits an edited file', :js do diff --git a/spec/support/cookie_helper.rb b/spec/support/cookie_helper.rb index 224619c899c..d72925e1838 100644 --- a/spec/support/cookie_helper.rb +++ b/spec/support/cookie_helper.rb @@ -8,6 +8,10 @@ module CookieHelper page.driver.browser.manage.add_cookie(name: name, value: value, **options) end + def get_cookie(name) + page.driver.browser.manage.cookie_named(name) + end + private def on_a_page? -- cgit v1.2.1 From 615f1927bb44fd04fac3f12ab6f1f7197ea5a69a Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 4 Jan 2018 19:07:28 +0000 Subject: [ci skip] Fix some rules --- .eslintrc | 2 +- .../javascripts/boards/components/board_card.vue | 39 ++++- app/assets/javascripts/commit/image_file.js | 194 ++++++++++----------- .../vue_shared/components/time_ago_tooltip.vue | 16 +- .../vue_shared/components/toggle_button.vue | 20 +-- .../components/user_avatar/user_avatar_image.vue | 8 +- .../components/user_avatar/user_avatar_link.vue | 8 +- .../components/user_avatar/user_avatar_svg.vue | 4 +- 8 files changed, 156 insertions(+), 135 deletions(-) diff --git a/.eslintrc b/.eslintrc index a419dc521e8..2c284ec6d94 100644 --- a/.eslintrc +++ b/.eslintrc @@ -25,7 +25,7 @@ "promise" ], "settings": { - "html/html-extensions": [".html", ".html.raw", ".vue"], + "html/html-extensions": [".html", ".html.raw"], "import/resolver": { "webpack": { "config": "./config/webpack.config.js" diff --git a/app/assets/javascripts/boards/components/board_card.vue b/app/assets/javascripts/boards/components/board_card.vue index 0b220a56e0b..7e0e0a13a46 100644 --- a/app/assets/javascripts/boards/components/board_card.vue +++ b/app/assets/javascripts/boards/components/board_card.vue @@ -10,12 +10,30 @@ export default { 'issue-card-inner': gl.issueBoards.IssueCardInner, }, props: { - list: Object, - issue: Object, - issueLinkBase: String, - disabled: Boolean, - index: Number, - rootPath: String, + list: { + type: Object, + default: () => ({}), + }, + issue: { + type: Object, + default: () => ({}), + }, + issueLinkBase: { + type: String, + default: '', + }, + disabled: { + type: Boolean, + default: false, + }, + index: { + type: Number, + default: 0, + }, + rootPath: { + type: String, + default: '', + }, }, data() { return { @@ -54,8 +72,13 @@ export default { diff --git a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue index d2ff2ac006e..a775ae03ffa 100644 --- a/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue +++ b/app/assets/javascripts/vue_shared/components/user_avatar/user_avatar_svg.vue @@ -39,7 +39,7 @@ export default { :class="avatarSizeClass" :height="size" :width="size" - v-html="svg"> - + v-html="svg" + /> -- cgit v1.2.1 From 5e148d4e931792733400f59864e1aa886ef55953 Mon Sep 17 00:00:00 2001 From: Felipe Artur Date: Wed, 6 Dec 2017 17:07:47 -0200 Subject: EE-BACKPORT group boards --- app/finders/labels_finder.rb | 19 +- app/policies/group_policy.rb | 8 +- doc/api/boards.md | 200 +++++++++++++++++++-- lib/api/api.rb | 4 +- lib/api/boards.rb | 84 ++++----- lib/api/boards_responses.rb | 50 ++++++ lib/api/entities.rb | 2 + lib/api/helpers.rb | 15 +- lib/api/labels.rb | 4 +- lib/api/v3/labels.rb | 2 +- spec/finders/labels_finder_spec.rb | 10 ++ spec/fixtures/api/schemas/public_api/v4/board.json | 86 +++++++++ .../fixtures/api/schemas/public_api/v4/boards.json | 4 + .../api/schemas/public_api/v4/user/basic.json | 2 +- spec/requests/api/boards_spec.rb | 179 ++---------------- spec/support/api/boards_shared_examples.rb | 180 +++++++++++++++++++ 16 files changed, 601 insertions(+), 248 deletions(-) create mode 100644 lib/api/boards_responses.rb create mode 100644 spec/fixtures/api/schemas/public_api/v4/board.json create mode 100644 spec/fixtures/api/schemas/public_api/v4/boards.json create mode 100644 spec/support/api/boards_shared_examples.rb diff --git a/app/finders/labels_finder.rb b/app/finders/labels_finder.rb index ce432ddbfe6..6de9eb89468 100644 --- a/app/finders/labels_finder.rb +++ b/app/finders/labels_finder.rb @@ -1,4 +1,6 @@ class LabelsFinder < UnionFinder + include Gitlab::Utils::StrongMemoize + def initialize(current_user, params = {}) @current_user = current_user @params = params @@ -32,6 +34,8 @@ class LabelsFinder < UnionFinder label_ids << project.labels end end + elsif only_group_labels? + label_ids << Label.where(group_id: group.id) else label_ids << Label.where(group_id: projects.group_ids) label_ids << Label.where(project_id: projects.select(:id)) @@ -51,6 +55,13 @@ class LabelsFinder < UnionFinder items.where(title: title) end + def group + strong_memoize(:group) do + group = Group.find(params[:group_id]) + authorized_to_read_labels?(group) && group + end + end + def group? params[:group_id].present? end @@ -63,6 +74,10 @@ class LabelsFinder < UnionFinder params[:project_ids].present? end + def only_group_labels? + params[:only_group_labels] + end + def title params[:title] || params[:name] end @@ -96,9 +111,9 @@ class LabelsFinder < UnionFinder @projects end - def authorized_to_read_labels?(project) + def authorized_to_read_labels?(label_parent) return true if skip_authorization - Ability.allowed?(current_user, :read_label, project) + Ability.allowed?(current_user, :read_label, label_parent) end end diff --git a/app/policies/group_policy.rb b/app/policies/group_policy.rb index d2d45e402b0..f0bcba588a2 100644 --- a/app/policies/group_policy.rb +++ b/app/policies/group_policy.rb @@ -28,12 +28,18 @@ class GroupPolicy < BasePolicy with_options scope: :subject, score: 0 condition(:request_access_enabled) { @subject.request_access_enabled } - rule { public_group } .enable :read_group + rule { public_group }.policy do + enable :read_group + enable :read_list + enable :read_label + end + rule { logged_in_viewable }.enable :read_group rule { guest }.policy do enable :read_group enable :upload_file + enable :read_label end rule { admin } .enable :read_group diff --git a/doc/api/boards.md b/doc/api/boards.md index 69c47abc806..a5f455e1c43 100644 --- a/doc/api/boards.md +++ b/doc/api/boards.md @@ -15,10 +15,10 @@ GET /projects/:id/boards | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | ```bash -curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/:id/boards +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards ``` Example response: @@ -27,6 +27,19 @@ Example response: [ { "id" : 1, + "project": { + "id": 5, + "name": "Diaspora Project Site", + "name_with_namespace": "Diaspora / Diaspora Project Site", + "path": "diaspora-project-site", + "path_with_namespace": "diaspora/diaspora-project-site", + "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", + "web_url": "http://example.com/diaspora/diaspora-project-site" + }, + "milestone": { + "id": 12 + "title": "10.0" + }, "lists" : [ { "id" : 1, @@ -60,6 +73,159 @@ Example response: ] ``` +## Single board + +Get a single board. + +``` +GET /projects/:id/boards/:board_id +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `board_id` | integer | yes | The ID of a board | + +```bash +curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards/1 +``` + +Example response: + +```json + { + "id": 1, + "name:": "project issue board", + "project": { + "id": 5, + "name": "Diaspora Project Site", + "name_with_namespace": "Diaspora / Diaspora Project Site", + "path": "diaspora-project-site", + "path_with_namespace": "diaspora/diaspora-project-site", + "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", + "web_url": "http://example.com/diaspora/diaspora-project-site" + }, + "milestone": { + "id": 12 + "title": "10.0" + }, + "lists" : [ + { + "id" : 1, + "label" : { + "name" : "Testing", + "color" : "#F0AD4E", + "description" : null + }, + "position" : 1 + }, + { + "id" : 2, + "label" : { + "name" : "Ready", + "color" : "#FF0000", + "description" : null + }, + "position" : 2 + }, + { + "id" : 3, + "label" : { + "name" : "Production", + "color" : "#FF5F00", + "description" : null + }, + "position" : 3 + } + ] + } +``` + +## Create a board (EES-Only) + +Creates a board. + +``` +POST /projects/:id/boards +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `name` | string | yes | The name of the new board | + +```bash +curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards?name=newboard +``` + +Example response: + +```json + { + "id": 1, + "project": { + "id": 5, + "name": "Diaspora Project Site", + "name_with_namespace": "Diaspora / Diaspora Project Site", + "path": "diaspora-project-site", + "path_with_namespace": "diaspora/diaspora-project-site", + "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", + "web_url": "http://example.com/diaspora/diaspora-project-site" + }, + "name": "newboard", + "milestone": { + "id": 12 + "title": "10.0" + }, + "lists" : [ + { + "id" : 1, + "label" : { + "name" : "Testing", + "color" : "#F0AD4E", + "description" : null + }, + "position" : 1 + }, + { + "id" : 2, + "label" : { + "name" : "Ready", + "color" : "#FF0000", + "description" : null + }, + "position" : 2 + }, + { + "id" : 3, + "label" : { + "name" : "Production", + "color" : "#FF5F00", + "description" : null + }, + "position" : 3 + } + ] + } +``` + +## Delete a board (EES-Only) + +Deletes a board. + +``` +DELETE /projects/:id/boards/:board_id +``` + +| Attribute | Type | Required | Description | +| --------- | ---- | -------- | ----------- | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `board_id` | integer | yes | The ID of a board | + +```bash +curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards/1 +``` + ## List board lists Get a list of the board's lists. @@ -71,8 +237,8 @@ GET /projects/:id/boards/:board_id/lists | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `board_id` | integer | yes | The ID of a board | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `board_id` | integer | yes | The ID of a board | ```bash curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards/1/lists @@ -122,9 +288,9 @@ GET /projects/:id/boards/:board_id/lists/:list_id | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `board_id` | integer | yes | The ID of a board | -| `list_id`| integer | yes | The ID of a board's list | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `board_id` | integer | yes | The ID of a board | +| `list_id`| integer | yes | The ID of a board's list | ```bash curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards/1/lists/1 @@ -154,9 +320,9 @@ POST /projects/:id/boards/:board_id/lists | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `board_id` | integer | yes | The ID of a board | -| `label_id` | integer | yes | The ID of a label | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `board_id` | integer | yes | The ID of a board | +| `label_id` | integer | yes | The ID of a label | ```bash curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards/1/lists?label_id=5 @@ -186,10 +352,10 @@ PUT /projects/:id/boards/:board_id/lists/:list_id | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `board_id` | integer | yes | The ID of a board | -| `list_id` | integer | yes | The ID of a board's list | -| `position` | integer | yes | The position of the list | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `board_id` | integer | yes | The ID of a board | +| `list_id` | integer | yes | The ID of a board's list | +| `position` | integer | yes | The position of the list | ```bash curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards/1/lists/1?position=2 @@ -219,9 +385,9 @@ DELETE /projects/:id/boards/:board_id/lists/:list_id | Attribute | Type | Required | Description | | --------- | ---- | -------- | ----------- | -| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | -| `board_id` | integer | yes | The ID of a board | -| `list_id` | integer | yes | The ID of a board's list | +| `id` | integer/string | yes | The ID or [URL-encoded path of the project](README.md#namespaced-path-encoding) owned by the authenticated user | +| `board_id` | integer | yes | The ID of a board | +| `list_id` | integer | yes | The ID of a board's list | ```bash curl --request DELETE --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" https://gitlab.example.com/api/v4/projects/5/boards/1/lists/1 diff --git a/lib/api/api.rb b/lib/api/api.rb index 8094597d238..e0d14281c96 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -119,6 +119,7 @@ module API mount ::API::Features mount ::API::Files mount ::API::Groups + mount ::API::GroupMilestones mount ::API::Internal mount ::API::Issues mount ::API::Jobs @@ -129,8 +130,6 @@ module API mount ::API::Members mount ::API::MergeRequestDiffs mount ::API::MergeRequests - mount ::API::ProjectMilestones - mount ::API::GroupMilestones mount ::API::Namespaces mount ::API::Notes mount ::API::NotificationSettings @@ -139,6 +138,7 @@ module API mount ::API::PipelineSchedules mount ::API::ProjectHooks mount ::API::Projects + mount ::API::ProjectMilestones mount ::API::ProjectSnippets mount ::API::ProtectedBranches mount ::API::Repositories diff --git a/lib/api/boards.rb b/lib/api/boards.rb index 366b0dc9a6f..6c706b2b4e1 100644 --- a/lib/api/boards.rb +++ b/lib/api/boards.rb @@ -1,45 +1,46 @@ module API class Boards < Grape::API + include BoardsResponses include PaginationParams before { authenticate! } + helpers do + def board_parent + user_project + end + end + params do requires :id, type: String, desc: 'The ID of a project' end resource :projects, requirements: API::PROJECT_ENDPOINT_REQUIREMENTS do - desc 'Get all project boards' do - detail 'This feature was introduced in 8.13' - success Entities::Board - end - params do - use :pagination - end - get ':id/boards' do - authorize!(:read_board, user_project) - present paginate(user_project.boards), with: Entities::Board + segment ':id/boards' do + desc 'Get all project boards' do + detail 'This feature was introduced in 8.13' + success Entities::Board + end + params do + use :pagination + end + get '/' do + authorize!(:read_board, user_project) + present paginate(board_parent.boards), with: Entities::Board + end + + desc 'Find a project board' do + detail 'This feature was introduced in 10.4' + success Entities::Board + end + get '/:board_id' do + present board, with: Entities::Board + end end params do requires :board_id, type: Integer, desc: 'The ID of a board' end segment ':id/boards/:board_id' do - helpers do - def project_board - board = user_project.boards.first - - if params[:board_id] == board.id - board - else - not_found!('Board') - end - end - - def board_lists - project_board.lists.destroyable - end - end - desc 'Get the lists of a project board' do detail 'Does not include `done` list. This feature was introduced in 8.13' success Entities::List @@ -72,22 +73,13 @@ module API requires :label_id, type: Integer, desc: 'The ID of an existing label' end post '/lists' do - unless available_labels.exists?(params[:label_id]) + unless available_labels_for(user_project).exists?(params[:label_id]) render_api_error!({ error: 'Label not found!' }, 400) end authorize!(:admin_list, user_project) - service = ::Boards::Lists::CreateService.new(user_project, current_user, - { label_id: params[:label_id] }) - - list = service.execute(project_board) - - if list.valid? - present list, with: Entities::List - else - render_validation_error!(list) - end + create_list end desc 'Moves a board list to a new position' do @@ -99,18 +91,11 @@ module API requires :position, type: Integer, desc: 'The position of the list' end put '/lists/:list_id' do - list = project_board.lists.movable.find(params[:list_id]) + list = board_lists.find(params[:list_id]) authorize!(:admin_list, user_project) - service = ::Boards::Lists::MoveService.new(user_project, current_user, - { position: params[:position] }) - - if service.execute(list) - present list, with: Entities::List - else - render_api_error!({ error: "List could not be moved!" }, 400) - end + move_list(list) end desc 'Delete a board list' do @@ -124,12 +109,7 @@ module API authorize!(:admin_list, user_project) list = board_lists.find(params[:list_id]) - destroy_conditionally!(list) do |list| - service = ::Boards::Lists::DestroyService.new(user_project, current_user) - unless service.execute(list) - render_api_error!({ error: 'List could not be deleted!' }, 400) - end - end + destroy_list(list) end end end diff --git a/lib/api/boards_responses.rb b/lib/api/boards_responses.rb new file mode 100644 index 00000000000..ead0943a74d --- /dev/null +++ b/lib/api/boards_responses.rb @@ -0,0 +1,50 @@ +module API + module BoardsResponses + extend ActiveSupport::Concern + + included do + helpers do + def board + board_parent.boards.find(params[:board_id]) + end + + def board_lists + board.lists.destroyable + end + + def create_list + create_list_service = + ::Boards::Lists::CreateService.new(board_parent, current_user, { label_id: params[:label_id] }) + + list = create_list_service.execute(board) + + if list.valid? + present list, with: Entities::List + else + render_validation_error!(list) + end + end + + def move_list(list) + move_list_service = + ::Boards::Lists::MoveService.new(board_parent, current_user, { position: params[:position].to_i }) + + if move_list_service.execute(list) + present list, with: Entities::List + else + render_api_error!({ error: "List could not be moved!" }, 400) + end + end + + def destroy_list(list) + destroy_conditionally!(list) do |list| + service = ::Boards::Lists::DestroyService.new(board_parent, current_user) + unless service.execute(list) + render_api_error!({ error: 'List could not be deleted!' }, 400) + end + end + end + end + end + end +end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 4ad4a1f7867..86ac10c39d0 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -791,6 +791,8 @@ module API class Board < Grape::Entity expose :id + expose :project, using: Entities::BasicProjectDetails + expose :lists, using: Entities::List do |board| board.lists.destroyable end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 9ba15893f55..c1f5ec2ab14 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -74,8 +74,15 @@ module API page || not_found!('Wiki Page') end - def available_labels - @available_labels ||= LabelsFinder.new(current_user, project_id: user_project.id).execute + def available_labels_for(label_parent) + search_params = + if label_parent.is_a?(Project) + { project_id: label_parent.id } + else + { group_id: label_parent.id, only_group_labels: true } + end + + LabelsFinder.new(current_user, search_params).execute end def find_user(id) @@ -141,7 +148,9 @@ module API end def find_project_label(id) - label = available_labels.find_by_id(id) || available_labels.find_by_title(id) + labels = available_labels_for(user_project) + label = labels.find_by_id(id) || labels.find_by_title(id) + label || not_found!('Label') end diff --git a/lib/api/labels.rb b/lib/api/labels.rb index e41a1720ac1..81eaf56e48e 100644 --- a/lib/api/labels.rb +++ b/lib/api/labels.rb @@ -15,7 +15,7 @@ module API use :pagination end get ':id/labels' do - present paginate(available_labels), with: Entities::Label, current_user: current_user, project: user_project + present paginate(available_labels_for(user_project)), with: Entities::Label, current_user: current_user, project: user_project end desc 'Create a new label' do @@ -30,7 +30,7 @@ module API post ':id/labels' do authorize! :admin_label, user_project - label = available_labels.find_by(title: params[:name]) + label = available_labels_for(user_project).find_by(title: params[:name]) conflict!('Label already exists') if label priority = params.delete(:priority) diff --git a/lib/api/v3/labels.rb b/lib/api/v3/labels.rb index bd5eb2175e8..4157462ec2a 100644 --- a/lib/api/v3/labels.rb +++ b/lib/api/v3/labels.rb @@ -11,7 +11,7 @@ module API success ::API::Entities::Label end get ':id/labels' do - present available_labels, with: ::API::Entities::Label, current_user: current_user, project: user_project + present available_labels_for(user_project), with: ::API::Entities::Label, current_user: current_user, project: user_project end desc 'Delete an existing label' do diff --git a/spec/finders/labels_finder_spec.rb b/spec/finders/labels_finder_spec.rb index d507af3fd3d..06031aee217 100644 --- a/spec/finders/labels_finder_spec.rb +++ b/spec/finders/labels_finder_spec.rb @@ -56,6 +56,16 @@ describe LabelsFinder do expect(finder.execute).to eq [group_label_2, group_label_1, project_label_5] end + + context 'when only_group_labels is true' do + it 'returns only group labels' do + group_1.add_developer(user) + + finder = described_class.new(user, group_id: group_1.id, only_group_labels: true) + + expect(finder.execute).to eq [group_label_2, group_label_1] + end + end end context 'filtering by project_id' do diff --git a/spec/fixtures/api/schemas/public_api/v4/board.json b/spec/fixtures/api/schemas/public_api/v4/board.json new file mode 100644 index 00000000000..d667f1d631c --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/board.json @@ -0,0 +1,86 @@ +{ + "type": "object", + "required" : [ + "id", + "project", + "lists" + ], + "properties" : { + "id": { "type": "integer" }, + "project": { + "type": ["object", "null"], + "required": [ + "id", + "avatar_url", + "description", + "default_branch", + "tag_list", + "ssh_url_to_repo", + "http_url_to_repo", + "web_url", + "name", + "name_with_namespace", + "path", + "path_with_namespace", + "star_count", + "forks_count", + "created_at", + "last_activity_at" + ], + "properties": { + "id": { "type": "integer" }, + "avatar_url": { "type": ["string", "null"] }, + "description": { "type": ["string", "null"] }, + "default_branch": { "type": ["string", "null"] }, + "tag_list": { "type": "array" }, + "ssh_url_to_repo": { "type": "string" }, + "http_url_to_repo": { "type": "string" }, + "web_url": { "type": "string" }, + "name": { "type": "string" }, + "name_with_namespace": { "type": "string" }, + "path": { "type": "string" }, + "path_with_namespace": { "type": "string" }, + "star_count": { "type": "integer" }, + "forks_count": { "type": "integer" }, + "created_at": { "type": "date" }, + "last_activity_at": { "type": "date" } + }, + "additionalProperties": false + }, + "lists": { + "type": "array", + "items": { + "type": "object", + "required" : [ + "id", + "label", + "position" + ], + "properties" : { + "id": { "type": "integer" }, + "label": { + "type": ["object", "null"], + "required": [ + "id", + "color", + "description", + "name" + ], + "properties": { + "id": { "type": "integer" }, + "color": { + "type": "string", + "pattern": "^#[0-9A-Fa-f]{3}{1,2}+$" + }, + "description": { "type": ["string", "null"] }, + "name": { "type": "string" } + } + }, + "position": { "type": ["integer", "null"] } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": true +} diff --git a/spec/fixtures/api/schemas/public_api/v4/boards.json b/spec/fixtures/api/schemas/public_api/v4/boards.json new file mode 100644 index 00000000000..117564ef77a --- /dev/null +++ b/spec/fixtures/api/schemas/public_api/v4/boards.json @@ -0,0 +1,4 @@ +{ + "type": "array", + "items": { "$ref": "board.json" } +} diff --git a/spec/fixtures/api/schemas/public_api/v4/user/basic.json b/spec/fixtures/api/schemas/public_api/v4/user/basic.json index 9f69d31971c..bf330d8278c 100644 --- a/spec/fixtures/api/schemas/public_api/v4/user/basic.json +++ b/spec/fixtures/api/schemas/public_api/v4/user/basic.json @@ -1,5 +1,5 @@ { - "type": "object", + "type": ["object", "null"], "required": [ "id", "state", diff --git a/spec/requests/api/boards_spec.rb b/spec/requests/api/boards_spec.rb index f65af69dc7f..c6c10025f7f 100644 --- a/spec/requests/api/boards_spec.rb +++ b/spec/requests/api/boards_spec.rb @@ -6,18 +6,18 @@ describe API::Boards do set(:non_member) { create(:user) } set(:guest) { create(:user) } set(:admin) { create(:user, :admin) } - set(:project) { create(:project, :public, creator_id: user.id, namespace: user.namespace ) } + set(:board_parent) { create(:project, :public, creator_id: user.id, namespace: user.namespace ) } set(:dev_label) do - create(:label, title: 'Development', color: '#FFAABB', project: project) + create(:label, title: 'Development', color: '#FFAABB', project: board_parent) end set(:test_label) do - create(:label, title: 'Testing', color: '#FFAACC', project: project) + create(:label, title: 'Testing', color: '#FFAACC', project: board_parent) end set(:ux_label) do - create(:label, title: 'UX', color: '#FF0000', project: project) + create(:label, title: 'UX', color: '#FF0000', project: board_parent) end set(:dev_list) do @@ -28,180 +28,25 @@ describe API::Boards do create(:list, label: test_label, position: 2) end - set(:board) do - create(:board, project: project, lists: [dev_list, test_list]) - end - - before do - project.add_reporter(user) - project.add_guest(guest) - end + set(:milestone) { create(:milestone, project: board_parent) } + set(:board_label) { create(:label, project: board_parent) } + set(:board) { create(:board, project: board_parent, lists: [dev_list, test_list]) } - describe "GET /projects/:id/boards" do - let(:base_url) { "/projects/#{project.id}/boards" } + it_behaves_like 'group and project boards', "/projects/:id/boards" - context "when unauthenticated" do - it "returns authentication error" do - get api(base_url) - - expect(response).to have_gitlab_http_status(401) - end - end - - context "when authenticated" do - it "returns the project issue board" do - get api(base_url, user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(1) - expect(json_response.first['id']).to eq(board.id) - expect(json_response.first['lists']).to be_an Array - expect(json_response.first['lists'].length).to eq(2) - expect(json_response.first['lists'].last).to have_key('position') - end - end - end - - describe "GET /projects/:id/boards/:board_id/lists" do - let(:base_url) { "/projects/#{project.id}/boards/#{board.id}/lists" } - - it 'returns issue board lists' do - get api(base_url, user) - - expect(response).to have_gitlab_http_status(200) - expect(response).to include_pagination_headers - expect(json_response).to be_an Array - expect(json_response.length).to eq(2) - expect(json_response.first['label']['name']).to eq(dev_label.title) - end - - it 'returns 404 if board not found' do - get api("/projects/#{project.id}/boards/22343/lists", user) - - expect(response).to have_gitlab_http_status(404) - end - end - - describe "GET /projects/:id/boards/:board_id/lists/:list_id" do - let(:base_url) { "/projects/#{project.id}/boards/#{board.id}/lists" } - - it 'returns a list' do - get api("#{base_url}/#{dev_list.id}", user) - - expect(response).to have_gitlab_http_status(200) - expect(json_response['id']).to eq(dev_list.id) - expect(json_response['label']['name']).to eq(dev_label.title) - expect(json_response['position']).to eq(1) - end - - it 'returns 404 if list not found' do - get api("#{base_url}/5324", user) - - expect(response).to have_gitlab_http_status(404) - end - end - - describe "POST /projects/:id/board/lists" do - let(:base_url) { "/projects/#{project.id}/boards/#{board.id}/lists" } + describe "POST /projects/:id/boards/lists" do + let(:url) { "/projects/#{board_parent.id}/boards/#{board.id}/lists" } it 'creates a new issue board list for group labels' do group = create(:group) group_label = create(:group_label, group: group) - project.update(group: group) + board_parent.update(group: group) - post api(base_url, user), label_id: group_label.id + post api(url, user), label_id: group_label.id expect(response).to have_gitlab_http_status(201) expect(json_response['label']['name']).to eq(group_label.title) expect(json_response['position']).to eq(3) end - - it 'creates a new issue board list for project labels' do - post api(base_url, user), label_id: ux_label.id - - expect(response).to have_gitlab_http_status(201) - expect(json_response['label']['name']).to eq(ux_label.title) - expect(json_response['position']).to eq(3) - end - - it 'returns 400 when creating a new list if label_id is invalid' do - post api(base_url, user), label_id: 23423 - - expect(response).to have_gitlab_http_status(400) - end - - it 'returns 403 for project members with guest role' do - put api("#{base_url}/#{test_list.id}", guest), position: 1 - - expect(response).to have_gitlab_http_status(403) - end - end - - describe "PUT /projects/:id/boards/:board_id/lists/:list_id to update only position" do - let(:base_url) { "/projects/#{project.id}/boards/#{board.id}/lists" } - - it "updates a list" do - put api("#{base_url}/#{test_list.id}", user), - position: 1 - - expect(response).to have_gitlab_http_status(200) - expect(json_response['position']).to eq(1) - end - - it "returns 404 error if list id not found" do - put api("#{base_url}/44444", user), - position: 1 - - expect(response).to have_gitlab_http_status(404) - end - - it "returns 403 for project members with guest role" do - put api("#{base_url}/#{test_list.id}", guest), - position: 1 - - expect(response).to have_gitlab_http_status(403) - end - end - - describe "DELETE /projects/:id/board/lists/:list_id" do - let(:base_url) { "/projects/#{project.id}/boards/#{board.id}/lists" } - - it "rejects a non member from deleting a list" do - delete api("#{base_url}/#{dev_list.id}", non_member) - - expect(response).to have_gitlab_http_status(403) - end - - it "rejects a user with guest role from deleting a list" do - delete api("#{base_url}/#{dev_list.id}", guest) - - expect(response).to have_gitlab_http_status(403) - end - - it "returns 404 error if list id not found" do - delete api("#{base_url}/44444", user) - - expect(response).to have_gitlab_http_status(404) - end - - context "when the user is project owner" do - set(:owner) { create(:user) } - - before do - project.update(namespace: owner.namespace) - end - - it "deletes the list if an admin requests it" do - delete api("#{base_url}/#{dev_list.id}", owner) - - expect(response).to have_gitlab_http_status(204) - end - - it_behaves_like '412 response' do - let(:request) { api("#{base_url}/#{dev_list.id}", owner) } - end - end end end diff --git a/spec/support/api/boards_shared_examples.rb b/spec/support/api/boards_shared_examples.rb new file mode 100644 index 00000000000..943c1f6ffd7 --- /dev/null +++ b/spec/support/api/boards_shared_examples.rb @@ -0,0 +1,180 @@ +shared_examples_for 'group and project boards' do |route_definition, ee = false| + let(:root_url) { route_definition.gsub(":id", board_parent.id.to_s) } + + before do + board_parent.add_reporter(user) + board_parent.add_guest(guest) + end + + def expect_schema_match_for(response, schema_file, ee) + if ee + expect(response).to match_response_schema(schema_file, dir: "ee") + else + expect(response).to match_response_schema(schema_file) + end + end + + describe "GET #{route_definition}" do + context "when unauthenticated" do + it "returns authentication error" do + get api(root_url) + + expect(response).to have_gitlab_http_status(401) + end + end + + context "when authenticated" do + it "returns the issue boards" do + get api(root_url, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + + expect_schema_match_for(response, 'public_api/v4/boards', ee) + end + + describe "GET #{route_definition}/:board_id" do + let(:url) { "#{root_url}/#{board.id}" } + + it 'get a single board by id' do + get api(url, user) + + expect_schema_match_for(response, 'public_api/v4/board', ee) + end + end + end + end + + describe "GET #{route_definition}/:board_id/lists" do + let(:url) { "#{root_url}/#{board.id}/lists" } + + it 'returns issue board lists' do + get api(url, user) + + expect(response).to have_gitlab_http_status(200) + expect(response).to include_pagination_headers + expect(json_response).to be_an Array + expect(json_response.length).to eq(2) + expect(json_response.first['label']['name']).to eq(dev_label.title) + end + + it 'returns 404 if board not found' do + get api("#{root_url}/22343/lists", user) + + expect(response).to have_gitlab_http_status(404) + end + end + + describe "GET #{route_definition}/:board_id/lists/:list_id" do + let(:url) { "#{root_url}/#{board.id}/lists" } + + it 'returns a list' do + get api("#{url}/#{dev_list.id}", user) + + expect(response).to have_gitlab_http_status(200) + expect(json_response['id']).to eq(dev_list.id) + expect(json_response['label']['name']).to eq(dev_label.title) + expect(json_response['position']).to eq(1) + end + + it 'returns 404 if list not found' do + get api("#{url}/5324", user) + + expect(response).to have_gitlab_http_status(404) + end + end + + describe "POST #{route_definition}/lists" do + let(:url) { "#{root_url}/#{board.id}/lists" } + + it 'creates a new issue board list for labels' do + post api(url, user), label_id: ux_label.id + + expect(response).to have_gitlab_http_status(201) + expect(json_response['label']['name']).to eq(ux_label.title) + expect(json_response['position']).to eq(3) + end + + it 'returns 400 when creating a new list if label_id is invalid' do + post api(url, user), label_id: 23423 + + expect(response).to have_gitlab_http_status(400) + end + + it 'returns 403 for members with guest role' do + put api("#{url}/#{test_list.id}", guest), position: 1 + + expect(response).to have_gitlab_http_status(403) + end + end + + describe "PUT #{route_definition}/:board_id/lists/:list_id to update only position" do + let(:url) { "#{root_url}/#{board.id}/lists" } + + it "updates a list" do + put api("#{url}/#{test_list.id}", user), + position: 1 + + expect(response).to have_gitlab_http_status(200) + expect(json_response['position']).to eq(1) + end + + it "returns 404 error if list id not found" do + put api("#{url}/44444", user), + position: 1 + + expect(response).to have_gitlab_http_status(404) + end + + it "returns 403 for members with guest role" do + put api("#{url}/#{test_list.id}", guest), + position: 1 + + expect(response).to have_gitlab_http_status(403) + end + end + + describe "DELETE #{route_definition}/lists/:list_id" do + let(:url) { "#{root_url}/#{board.id}/lists" } + + it "rejects a non member from deleting a list" do + delete api("#{url}/#{dev_list.id}", non_member) + + expect(response).to have_gitlab_http_status(403) + end + + it "rejects a user with guest role from deleting a list" do + delete api("#{url}/#{dev_list.id}", guest) + + expect(response).to have_gitlab_http_status(403) + end + + it "returns 404 error if list id not found" do + delete api("#{url}/44444", user) + + expect(response).to have_gitlab_http_status(404) + end + + context "when the user is parent owner" do + set(:owner) { create(:user) } + + before do + if board_parent.try(:namespace) + board_parent.update(namespace: owner.namespace) + else + board.parent.add_owner(owner) + end + end + + it "deletes the list if an admin requests it" do + delete api("#{url}/#{dev_list.id}", owner) + + expect(response).to have_gitlab_http_status(204) + end + + it_behaves_like '412 response' do + let(:request) { api("#{url}/#{dev_list.id}", owner) } + end + end + end +end -- cgit v1.2.1 From 18bd8b3bd00ac664c7043e14b02a1fadc8605c16 Mon Sep 17 00:00:00 2001 From: Fatih Acet Date: Thu, 4 Jan 2018 22:16:11 +0100 Subject: Fix issue boards scroll config. --- app/assets/javascripts/boards/components/board_list.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/javascripts/boards/components/board_list.js b/app/assets/javascripts/boards/components/board_list.js index 29aeb8e84aa..84b76a6f1b1 100644 --- a/app/assets/javascripts/boards/components/board_list.js +++ b/app/assets/javascripts/boards/components/board_list.js @@ -115,7 +115,7 @@ export default { }, mounted() { const options = gl.issueBoards.getBoardSortableDefaultOptions({ - scroll: document.querySelectorAll('.boards-list')[0], + scroll: true, group: 'issues', disabled: this.disabled, filter: '.board-list-count, .is-disabled', -- cgit v1.2.1 From f834e2907d5111f3e2bcd8d0dd126f9e0dd0be7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 4 Jan 2018 19:13:30 +0100 Subject: Move cache reset to ci_cd_settings controller --- .../projects/settings/ci_cd_controller.rb | 9 +++++ app/controllers/projects_controller.rb | 9 ----- app/views/projects/pipelines/index.html.haml | 2 +- config/routes/project.rb | 5 ++- .../projects/settings/ci_cd_controller_spec.rb | 47 ++++++++++++++++++++++ spec/controllers/projects_controller_spec.rb | 47 ---------------------- 6 files changed, 60 insertions(+), 59 deletions(-) diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb index b029b31f9af..1dcebcb15a6 100644 --- a/app/controllers/projects/settings/ci_cd_controller.rb +++ b/app/controllers/projects/settings/ci_cd_controller.rb @@ -11,6 +11,15 @@ module Projects define_auto_devops_variables end + def reset_cache + if ResetProjectCacheService.new(@project, current_user).execute + flash[:notice] = _("Project cache successfully reset.") + else + flash[:error] = _("Unable to reset project cache.") + end + redirect_to project_pipelines_path(@project) + end + private def define_runners_variables diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 928555c200b..6f609348402 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -175,15 +175,6 @@ class ProjectsController < Projects::ApplicationController ) end - def reset_cache - if ResetProjectCacheService.new(@project, current_user).execute - flash[:notice] = _("Project cache successfully reset.") - else - flash[:error] = _("Unable to reset project cache.") - end - redirect_to project_pipelines_path(@project) - end - def export @project.add_export_job(current_user: current_user) diff --git a/app/views/projects/pipelines/index.html.haml b/app/views/projects/pipelines/index.html.haml index d23cb626312..f8555f11aab 100644 --- a/app/views/projects/pipelines/index.html.haml +++ b/app/views/projects/pipelines/index.html.haml @@ -11,7 +11,7 @@ "can-create-pipeline" => can?(current_user, :create_pipeline, @project).to_s, "has-ci" => @repository.gitlab_ci_yml, "ci-lint-path" => ci_lint_path, - "reset-cache-path" => reset_cache_project_path(@project) } } + "reset-cache-path" => reset_cache_project_settings_ci_cd_path(@project) } } = page_specific_javascript_bundle_tag('common_vue') = page_specific_javascript_bundle_tag('pipelines') diff --git a/config/routes/project.rb b/config/routes/project.rb index d79c6e141c8..905c906b194 100644 --- a/config/routes/project.rb +++ b/config/routes/project.rb @@ -407,7 +407,9 @@ constraints(ProjectUrlConstrainer.new) do end namespace :settings do get :members, to: redirect("%{namespace_id}/%{project_id}/project_members") - resource :ci_cd, only: [:show], controller: 'ci_cd' + resource :ci_cd, only: [:show], controller: 'ci_cd' do + get :reset_cache + end resource :integrations, only: [:show] resource :repository, only: [:show], controller: :repository end @@ -436,7 +438,6 @@ constraints(ProjectUrlConstrainer.new) do get :download_export get :activity get :refs - get :reset_cache put :new_issuable_address 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 b8fe0f46f57..acd40f4a305 100644 --- a/spec/controllers/projects/settings/ci_cd_controller_spec.rb +++ b/spec/controllers/projects/settings/ci_cd_controller_spec.rb @@ -17,4 +17,51 @@ describe Projects::Settings::CiCdController do expect(response).to render_template(:show) end end + + describe '#reset_cache' do + before do + sign_in(user) + + project.add_master(user) + + allow(ResetProjectCacheService).to receive_message_chain(:new, :execute).and_return(true) + end + + subject { get :reset_cache, namespace_id: project.namespace, project_id: project } + + it 'calls reset project cache service' do + expect(ResetProjectCacheService).to receive_message_chain(:new, :execute) + + subject + end + + it 'redirects to project pipelines path' do + subject + + expect(response).to have_gitlab_http_status(:redirect) + expect(response).to redirect_to(project_pipelines_path(project)) + end + + context 'when service returns successfully' do + it 'sets the flash notice variable' do + subject + + expect(controller).to set_flash[:notice] + expect(controller).not_to set_flash[:error] + end + end + + context 'when service does not return successfully' do + before do + allow(ResetProjectCacheService).to receive_message_chain(:new, :execute).and_return(false) + end + + it 'sets the flash error variable' do + subject + + expect(controller).not_to set_flash[:notice] + expect(controller).to set_flash[:error] + end + end + end end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 2fc827742fe..e61187fb518 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -686,53 +686,6 @@ describe ProjectsController do end end - describe '#reset_cache' do - before do - sign_in(user) - - project.add_master(user) - - allow(ResetProjectCacheService).to receive_message_chain(:new, :execute).and_return(true) - end - - subject { get :reset_cache, namespace_id: project.namespace, id: project } - - it 'calls reset project cache service' do - expect(ResetProjectCacheService).to receive_message_chain(:new, :execute) - - subject - end - - it 'redirects to project pipelines path' do - subject - - expect(response).to have_gitlab_http_status(:redirect) - expect(response).to redirect_to(project_pipelines_path(project)) - end - - context 'when service returns successfully' do - it 'sets the flash notice variable' do - subject - - expect(controller).to set_flash[:notice] - expect(controller).not_to set_flash[:error] - end - end - - context 'when service does not return successfully' do - before do - allow(ResetProjectCacheService).to receive_message_chain(:new, :execute).and_return(false) - end - - it 'sets the flash error variable' do - subject - - expect(controller).not_to set_flash[:notice] - expect(controller).to set_flash[:error] - end - end - end - describe '#export' do before do sign_in(user) -- cgit v1.2.1 From 7d7d289b159fba332c68dc66b6f9f3b17428c3a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 4 Jan 2018 19:14:37 +0100 Subject: Add missing empty line in #reset_cache --- app/controllers/projects/settings/ci_cd_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/projects/settings/ci_cd_controller.rb b/app/controllers/projects/settings/ci_cd_controller.rb index 1dcebcb15a6..86717bb7242 100644 --- a/app/controllers/projects/settings/ci_cd_controller.rb +++ b/app/controllers/projects/settings/ci_cd_controller.rb @@ -17,6 +17,7 @@ module Projects else flash[:error] = _("Unable to reset project cache.") end + redirect_to project_pipelines_path(@project) end -- cgit v1.2.1 From 8cc14dd5371c33f389211fcee39dbb28686b2021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 4 Jan 2018 19:40:07 +0100 Subject: Rename Project#cache_index to jobs_cache_index --- app/models/ci/build.rb | 4 ++-- app/services/reset_project_cache_service.rb | 2 +- db/migrate/20171222183504_add_cache_index_to_project.rb | 13 ------------- .../20171222183504_add_jobs_cache_index_to_project.rb | 13 +++++++++++++ db/schema.rb | 2 +- spec/models/ci/build_spec.rb | 8 ++++---- spec/services/reset_project_cache_service_spec.rb | 8 ++++---- 7 files changed, 25 insertions(+), 25 deletions(-) delete mode 100644 db/migrate/20171222183504_add_cache_index_to_project.rb create mode 100644 db/migrate/20171222183504_add_jobs_cache_index_to_project.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index e4ca74f87f2..ff903a63c54 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -461,8 +461,8 @@ module Ci end def cache - if options[:cache] && project.cache_index - options[:cache].merge(key: "#{options[:cache][:key]}:#{project.cache_index}") + if options[:cache] && project.jobs_cache_index + options[:cache].merge(key: "#{options[:cache][:key]}:#{project.jobs_cache_index}") else [options[:cache]] end diff --git a/app/services/reset_project_cache_service.rb b/app/services/reset_project_cache_service.rb index 0886c6b8315..a162a6eedb9 100644 --- a/app/services/reset_project_cache_service.rb +++ b/app/services/reset_project_cache_service.rb @@ -1,5 +1,5 @@ class ResetProjectCacheService < BaseService def execute - @project.increment!(:cache_index) + @project.increment!(:jobs_cache_index) end end diff --git a/db/migrate/20171222183504_add_cache_index_to_project.rb b/db/migrate/20171222183504_add_cache_index_to_project.rb deleted file mode 100644 index e1d73db1ab0..00000000000 --- a/db/migrate/20171222183504_add_cache_index_to_project.rb +++ /dev/null @@ -1,13 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class AddCacheIndexToProject < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - # Set this constant to true if this migration requires downtime. - DOWNTIME = false - - def change - add_column :projects, :cache_index, :integer - end -end diff --git a/db/migrate/20171222183504_add_jobs_cache_index_to_project.rb b/db/migrate/20171222183504_add_jobs_cache_index_to_project.rb new file mode 100644 index 00000000000..58ac0177420 --- /dev/null +++ b/db/migrate/20171222183504_add_jobs_cache_index_to_project.rb @@ -0,0 +1,13 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddCacheIndexToProject < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + def change + add_column :projects, :jobs_cache_index, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index b7512f293a6..cd3f87062ab 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1447,7 +1447,7 @@ ActiveRecord::Schema.define(version: 20171222183504) do t.boolean "repository_read_only" t.boolean "merge_requests_ff_only_enabled", default: false t.boolean "merge_requests_rebase_enabled", default: false, null: false - t.integer "cache_index" + t.integer "jobs_cache_index" end add_index "projects", ["ci_id"], name: "index_projects_on_ci_id", using: :btree diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 96513281994..8cecaf16fdf 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -265,17 +265,17 @@ describe Ci::Build do allow(build).to receive(:options).and_return(options) end - context 'when project has cache_index' do + context 'when project has jobs_cache_index' do before do - allow_any_instance_of(Project).to receive(:cache_index).and_return(1) + allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(1) end it { is_expected.to include(key: "key:1") } end - context 'when project does not have cache_index' do + context 'when project does not have jobs_cache_index' do before do - allow_any_instance_of(Project).to receive(:cache_index).and_return(nil) + allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(nil) end it { is_expected.to eq([options[:cache]]) } diff --git a/spec/services/reset_project_cache_service_spec.rb b/spec/services/reset_project_cache_service_spec.rb index df969d08f39..de475d16586 100644 --- a/spec/services/reset_project_cache_service_spec.rb +++ b/spec/services/reset_project_cache_service_spec.rb @@ -8,21 +8,21 @@ describe ResetProjectCacheService do context 'when project cache_index is nil' do before do - project.cache_index = nil + project.jobs_cache_index = nil end it 'sets project cache_index to one' do - expect { subject }.to change { project.reload.cache_index }.from(nil).to(1) + expect { subject }.to change { project.reload.jobs_cache_index }.from(nil).to(1) end end context 'when project cache_index is a numeric value' do before do - project.update_attributes(cache_index: 1) + project.update_attributes(jobs_cache_index: 1) end it 'increments project cache index' do - expect { subject }.to change { project.reload.cache_index }.by(1) + expect { subject }.to change { project.reload.jobs_cache_index }.by(1) end end end -- cgit v1.2.1 From 2682966829b49b7fbeee02e5c070c8c14630ea6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 4 Jan 2018 19:43:31 +0100 Subject: Refactor Ci::Build#cache --- app/models/ci/build.rb | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index ff903a63c54..d0ee08ab086 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -461,11 +461,14 @@ module Ci end def cache - if options[:cache] && project.jobs_cache_index - options[:cache].merge(key: "#{options[:cache][:key]}:#{project.jobs_cache_index}") - else - [options[:cache]] + cache = options[:cache] + + if cache && project.jobs_cache_index + cache = cache.merge( + key: "#{cache[:key]}:#{project.jobs_cache_index}") end + + [cache] end def credentials -- cgit v1.2.1 From 0f137d8e9cc4b44ab11c549860bf27e7244ad09d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 4 Jan 2018 21:22:23 +0100 Subject: Fix faulty Ci::Build#cache spec --- spec/models/ci/build_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 8cecaf16fdf..3eaeeebf97d 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -270,7 +270,7 @@ describe Ci::Build do allow_any_instance_of(Project).to receive(:jobs_cache_index).and_return(1) end - it { is_expected.to include(key: "key:1") } + it { is_expected.to be_an(Array).and all(include(key: "key:1")) } end context 'when project does not have jobs_cache_index' do -- cgit v1.2.1 From 6d1548f86922d2489fd601c725a9748e3e563216 Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Wed, 3 Jan 2018 14:51:32 +0100 Subject: Fix Webpack config for ConcatenatedModule --- config/webpack.config.js | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/config/webpack.config.js b/config/webpack.config.js index 5f95255334c..95fa79990e2 100644 --- a/config/webpack.config.js +++ b/config/webpack.config.js @@ -1,5 +1,6 @@ 'use strict'; +var crypto = require('crypto'); var fs = require('fs'); var path = require('path'); var webpack = require('webpack'); @@ -179,15 +180,34 @@ var config = { if (chunk.name) { return chunk.name; } - return chunk.mapModules((m) => { + + const moduleNames = []; + + function collectModuleNames(m) { + // handle ConcatenatedModule which does not have resource nor context set + if (m.modules) { + m.modules.forEach(collectModuleNames); + return; + } + const pagesBase = path.join(ROOT_PATH, 'app/assets/javascripts/pages'); + if (m.resource.indexOf(pagesBase) === 0) { - return path.relative(pagesBase, m.resource) + moduleNames.push(path.relative(pagesBase, m.resource) .replace(/\/index\.[a-z]+$/, '') - .replace(/\//g, '__'); + .replace(/\//g, '__')); + } else { + moduleNames.push(path.relative(m.context, m.resource)); } - return path.relative(m.context, m.resource); - }).join('_'); + } + + chunk.forEachModule(collectModuleNames); + + const hash = crypto.createHash('sha256') + .update(moduleNames.join('_')) + .digest('hex'); + + return `${moduleNames[0]}-${hash.substr(0, 6)}`; }), // create cacheable common library bundle for all vue chunks -- cgit v1.2.1 From f01295a651ae8172823ce58031492d0b6d5220e7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Thu, 4 Jan 2018 22:36:11 +0100 Subject: Ignore the Migration/Datetime cop in a migration that fix a column type to datetime_with_timezone MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- db/post_migrate/20171221140220_schedule_issues_closed_at_type_change.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/db/post_migrate/20171221140220_schedule_issues_closed_at_type_change.rb b/db/post_migrate/20171221140220_schedule_issues_closed_at_type_change.rb index be18c5866ae..eeecc7b1de0 100644 --- a/db/post_migrate/20171221140220_schedule_issues_closed_at_type_change.rb +++ b/db/post_migrate/20171221140220_schedule_issues_closed_at_type_change.rb @@ -1,6 +1,6 @@ # See http://doc.gitlab.com/ce/development/migration_style_guide.html # for more information on how to write migrations for GitLab. - +# rubocop:disable Migration/Datetime class ScheduleIssuesClosedAtTypeChange < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers -- cgit v1.2.1 From 5152cc3bfb8d60814063e86c3776030aa8891e0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Thu, 4 Jan 2018 19:27:37 -0300 Subject: Fix a bug where charlock_holmes was used needlessly to encode strings --- lib/gitlab/encoding_helper.rb | 26 ++++++++++++++++---------- lib/gitlab/git.rb | 2 +- spec/lib/gitlab/encoding_helper_spec.rb | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+), 11 deletions(-) diff --git a/lib/gitlab/encoding_helper.rb b/lib/gitlab/encoding_helper.rb index 6b53eb4533d..c0edcabc6fd 100644 --- a/lib/gitlab/encoding_helper.rb +++ b/lib/gitlab/encoding_helper.rb @@ -14,14 +14,7 @@ module Gitlab ENCODING_CONFIDENCE_THRESHOLD = 50 def encode!(message) - return nil unless message.respond_to?(:force_encoding) - return message if message.encoding == Encoding::UTF_8 && message.valid_encoding? - - if message.respond_to?(:frozen?) && message.frozen? - message = message.dup - end - - message.force_encoding("UTF-8") + message = force_encode_utf8(message) return message if message.valid_encoding? # return message if message type is binary @@ -35,6 +28,8 @@ module Gitlab # encode and clean the bad chars message.replace clean(message) + rescue ArgumentError + return nil rescue encoding = detect ? detect[:encoding] : "unknown" "--broken encoding: #{encoding}" @@ -54,8 +49,8 @@ module Gitlab end def encode_utf8(message) - return nil unless message.is_a?(String) - return message if message.encoding == Encoding::UTF_8 && message.valid_encoding? + message = force_encode_utf8(message) + return message if message.valid_encoding? detect = CharlockHolmes::EncodingDetector.detect(message) if detect && detect[:encoding] @@ -69,6 +64,8 @@ module Gitlab else clean(message) end + rescue ArgumentError + return nil end def encode_binary(s) @@ -83,6 +80,15 @@ module Gitlab private + def force_encode_utf8(message) + raise ArgumentError unless message.respond_to?(:force_encoding) + return message if message.encoding == Encoding::UTF_8 && message.valid_encoding? + + message = message.dup if message.respond_to?(:frozen?) && message.frozen? + + message.force_encoding("UTF-8") + end + def clean(message) message.encode("UTF-16BE", undef: :replace, invalid: :replace, replace: "") .encode("UTF-8") diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb index 1f7c35cafaa..71647099f83 100644 --- a/lib/gitlab/git.rb +++ b/lib/gitlab/git.rb @@ -11,7 +11,7 @@ module Gitlab include Gitlab::EncodingHelper def ref_name(ref) - encode_utf8(ref).sub(/\Arefs\/(tags|heads|remotes)\//, '') + encode!(ref).sub(/\Arefs\/(tags|heads|remotes)\//, '') end def branch_name(ref) diff --git a/spec/lib/gitlab/encoding_helper_spec.rb b/spec/lib/gitlab/encoding_helper_spec.rb index 87ec2698fc1..4e9367323cb 100644 --- a/spec/lib/gitlab/encoding_helper_spec.rb +++ b/spec/lib/gitlab/encoding_helper_spec.rb @@ -120,6 +120,24 @@ describe Gitlab::EncodingHelper do it 'returns empty string on conversion errors' do expect { ext_class.encode_utf8('') }.not_to raise_error(ArgumentError) end + + context 'with strings that can be forcefully encoded into utf8' do + let(:test_string) do + "refs/heads/FixSymbolsTitleDropdown".encode("ASCII-8BIT") + end + let(:expected_string) do + "refs/heads/FixSymbolsTitleDropdown".encode("UTF-8") + end + + subject { ext_class.encode_utf8(test_string) } + + it "doesn't use CharlockHolmes if the encoding can be forced into utf_8" do + expect(CharlockHolmes::EncodingDetector).not_to receive(:detect) + + expect(subject).to eq(expected_string) + expect(subject.encoding.name).to eq('UTF-8') + end + end end describe '#clean' do -- cgit v1.2.1 From 6fb4a533b74c861a1e533604da462efb6d309de0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matija=20=C4=8Cupi=C4=87?= Date: Thu, 4 Jan 2018 23:33:17 +0100 Subject: Add feature test for resetting runner caches --- spec/features/projects/pipelines/pipelines_spec.rb | 36 ++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index b87b47d0e1a..69a836292fc 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -545,6 +545,42 @@ describe 'Pipelines', :js do end end end + + describe 'Reset runner caches' do + let(:project) { create(:project, :repository) } + + before do + create(:ci_empty_pipeline, status: 'success', project: project, sha: project.commit.id, ref: 'master') + project.team << [user, :master] + visit project_pipelines_path(project) + end + + it 'has a clear caches button' do + expect(page).to have_link 'Clear runner caches' + end + + describe 'user clicks the button' do + subject { click_link 'Clear runner caches' } + + context 'when project already has jobs_cache_index' do + before do + project.update_attributes(jobs_cache_index: 1) + end + + it 'increments jobs_cache_index' do + expect { subject }.to change { project.reload.jobs_cache_index }.by(1) + expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.' + end + end + + context 'when project does not have jobs_cache_index' do + it 'sets jobs_cache_index to 1' do + expect { subject }.to change { project.reload.jobs_cache_index }.from(nil).to(1) + expect(page.find('.flash-notice')).to have_content 'Project cache successfully reset.' + end + end + end + end end context 'when user is not logged in' do -- cgit v1.2.1 From 93e9793ce38bb9b5d519f5ca86cb56201549ef19 Mon Sep 17 00:00:00 2001 From: Mayra Cabrera Date: Thu, 4 Jan 2018 22:35:41 +0000 Subject: Create Kubernetes based on Application Templates --- app/models/concerns/deployment_platform.rb | 47 ++++++++++++++ app/models/project.rb | 7 +-- app/models/service.rb | 5 ++ ...kubernetes-integration-application-template.yml | 5 ++ .../projects/import_export/export_file_spec.rb | 2 +- spec/models/concerns/deployment_platform_spec.rb | 73 ++++++++++++++++++++++ spec/models/project_spec.rb | 19 ------ spec/models/service_spec.rb | 8 +++ 8 files changed, 140 insertions(+), 26 deletions(-) create mode 100644 app/models/concerns/deployment_platform.rb create mode 100644 changelogs/unreleased/41056-create-cluster-from-kubernetes-integration-application-template.yml create mode 100644 spec/models/concerns/deployment_platform_spec.rb diff --git a/app/models/concerns/deployment_platform.rb b/app/models/concerns/deployment_platform.rb new file mode 100644 index 00000000000..e1373455e98 --- /dev/null +++ b/app/models/concerns/deployment_platform.rb @@ -0,0 +1,47 @@ +module DeploymentPlatform + def deployment_platform + @deployment_platform ||= find_cluster_platform_kubernetes + @deployment_platform ||= find_kubernetes_service_integration + @deployment_platform ||= build_cluster_and_deployment_platform + end + + private + + def find_cluster_platform_kubernetes + clusters.find_by(enabled: true)&.platform_kubernetes + end + + def find_kubernetes_service_integration + services.deployment.reorder(nil).find_by(active: true) + end + + def build_cluster_and_deployment_platform + return unless kubernetes_service_template + + cluster = ::Clusters::Cluster.create(cluster_attributes_from_service_template) + cluster.platform_kubernetes if cluster.persisted? + end + + def kubernetes_service_template + @kubernetes_service_template ||= KubernetesService.active.find_by_template + end + + def cluster_attributes_from_service_template + { + name: 'kubernetes-template', + projects: [self], + provider_type: :user, + platform_type: :kubernetes, + platform_kubernetes_attributes: platform_kubernetes_attributes_from_service_template + } + end + + def platform_kubernetes_attributes_from_service_template + { + api_url: kubernetes_service_template.api_url, + ca_pem: kubernetes_service_template.ca_pem, + token: kubernetes_service_template.token, + namespace: kubernetes_service_template.namespace + } + end +end diff --git a/app/models/project.rb b/app/models/project.rb index 9c0bbf697e2..5d6c1b30587 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -19,6 +19,7 @@ class Project < ActiveRecord::Base include Routable include GroupDescendant include Gitlab::SQL::Pattern + include DeploymentPlatform extend Gitlab::ConfigHelper extend Gitlab::CurrentSettings @@ -904,12 +905,6 @@ class Project < ActiveRecord::Base @ci_service ||= ci_services.reorder(nil).find_by(active: true) end - # TODO: This will be extended for multiple enviroment clusters - def deployment_platform - @deployment_platform ||= clusters.find_by(enabled: true)&.platform_kubernetes - @deployment_platform ||= services.where(category: :deployment).reorder(nil).find_by(active: true) - end - def monitoring_services services.where(category: :monitoring) end diff --git a/app/models/service.rb b/app/models/service.rb index 176b472e724..24ba3039707 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -44,6 +44,7 @@ class Service < ActiveRecord::Base scope :pipeline_hooks, -> { where(pipeline_events: true, active: true) } scope :wiki_page_hooks, -> { where(wiki_page_events: true, active: true) } scope :external_issue_trackers, -> { issue_trackers.active.without_defaults } + scope :deployment, -> { where(category: 'deployment') } default_value_for :category, 'common' @@ -271,6 +272,10 @@ class Service < ActiveRecord::Base nil end + def self.find_by_template + find_by(template: true) + end + private def cache_project_has_external_issue_tracker diff --git a/changelogs/unreleased/41056-create-cluster-from-kubernetes-integration-application-template.yml b/changelogs/unreleased/41056-create-cluster-from-kubernetes-integration-application-template.yml new file mode 100644 index 00000000000..2dd6fc5f1b5 --- /dev/null +++ b/changelogs/unreleased/41056-create-cluster-from-kubernetes-integration-application-template.yml @@ -0,0 +1,5 @@ +--- +title: Allow automatic creation of Kubernetes Integration from template +merge_request: 16104 +author: +type: added diff --git a/spec/features/projects/import_export/export_file_spec.rb b/spec/features/projects/import_export/export_file_spec.rb index 461aa39d0ad..6732cf61767 100644 --- a/spec/features/projects/import_export/export_file_spec.rb +++ b/spec/features/projects/import_export/export_file_spec.rb @@ -2,7 +2,7 @@ require 'spec_helper' # Integration test that exports a file using the Import/Export feature # It looks up for any sensitive word inside the JSON, so if a sensitive word is found -# we''l have to either include it adding the model that includes it to the +safe_list+ +# we'll have to either include it adding the model that includes it to the +safe_list+ # or make sure the attribute is blacklisted in the +import_export.yml+ configuration feature 'Import/Export - project export integration test', :js do include Select2Helper diff --git a/spec/models/concerns/deployment_platform_spec.rb b/spec/models/concerns/deployment_platform_spec.rb new file mode 100644 index 00000000000..7bb89fe41dc --- /dev/null +++ b/spec/models/concerns/deployment_platform_spec.rb @@ -0,0 +1,73 @@ +require 'rails_helper' + +describe DeploymentPlatform do + let(:project) { create(:project) } + + describe '#deployment_platform' do + subject { project.deployment_platform } + + context 'with no Kubernetes configuration on CI/CD, no Kubernetes Service and a Kubernetes template configured' do + let!(:kubernetes_service) { create(:kubernetes_service, template: true) } + + it 'returns a platform kubernetes' do + expect(subject).to be_a_kind_of(Clusters::Platforms::Kubernetes) + end + + it 'creates a cluster and a platform kubernetes' do + expect { subject } + .to change { Clusters::Cluster.count }.by(1) + .and change { Clusters::Platforms::Kubernetes.count }.by(1) + end + + it 'includes appropriate attributes for Cluster' do + cluster = subject.cluster + expect(cluster.name).to eq('kubernetes-template') + expect(cluster.project).to eq(project) + expect(cluster.provider_type).to eq('user') + expect(cluster.platform_type).to eq('kubernetes') + end + + it 'creates a platform kubernetes' do + expect { subject }.to change { Clusters::Platforms::Kubernetes.count }.by(1) + end + + it 'copies attributes from Clusters::Platform::Kubernetes template into the new Cluster::Platforms::Kubernetes' do + expect(subject.api_url).to eq(kubernetes_service.api_url) + expect(subject.ca_pem).to eq(kubernetes_service.ca_pem) + expect(subject.token).to eq(kubernetes_service.token) + expect(subject.namespace).to eq(kubernetes_service.namespace) + end + end + + context 'with no Kubernetes configuration on CI/CD, no Kubernetes Service and no Kubernetes template configured' do + it { is_expected.to be_nil } + end + + context 'when user configured kubernetes from CI/CD > Clusters' do + let!(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } + let(:platform_kubernetes) { cluster.platform_kubernetes } + + it 'returns the Kubernetes platform' do + expect(subject).to eq(platform_kubernetes) + end + end + + context 'when user configured kubernetes integration from project services' do + let!(:kubernetes_service) { create(:kubernetes_service, project: project) } + + it 'returns the Kubernetes service' do + expect(subject).to eq(kubernetes_service) + end + end + + context 'when the cluster creation fails' do + let!(:kubernetes_service) { create(:kubernetes_service, template: true) } + + before do + allow_any_instance_of(Clusters::Cluster).to receive(:persisted?).and_return(false) + end + + it { is_expected.to be_nil } + end + end +end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index cea22bbd184..3c2ed043b82 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -3137,25 +3137,6 @@ describe Project do end end - describe '#deployment_platform' do - subject { project.deployment_platform } - - let(:project) { create(:project) } - - context 'when user configured kubernetes from Integration > Kubernetes' do - let!(:kubernetes_service) { create(:kubernetes_service, project: project) } - - it { is_expected.to eq(kubernetes_service) } - end - - context 'when user configured kubernetes from CI/CD > Clusters' do - let!(:cluster) { create(:cluster, :provided_by_gcp, projects: [project]) } - let(:platform_kubernetes) { cluster.platform_kubernetes } - - it { is_expected.to eq(platform_kubernetes) } - end - end - describe '#write_repository_config' do set(:project) { create(:project, :repository) } diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index 540615de117..ab6678cab38 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -272,4 +272,12 @@ describe Service do expect(service.deprecation_message).to be_nil end end + + describe '.find_by_template' do + let!(:kubernetes_service) { create(:kubernetes_service, template: true) } + + it 'returns service template' do + expect(KubernetesService.find_by_template).to eq(kubernetes_service) + end + end end -- cgit v1.2.1 From 318d6f449e26dd3ac05e9556fcd8f427e9388134 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 4 Jan 2018 19:50:06 +0000 Subject: [ci skip] Fix more rules --- .eslintrc | 12 +- .../javascripts/vue_shared/components/modal.vue | 228 +++++++++---------- .../vue_shared/components/navigation_tabs.vue | 11 +- .../components/notes/placeholder_note.vue | 16 +- .../components/notes/placeholder_system_note.vue | 12 +- .../vue_shared/components/notes/system_note.vue | 8 +- .../vue_shared/components/panel_resizer.vue | 148 ++++++------ .../javascripts/vue_shared/components/pikaday.vue | 20 +- .../vue_shared/components/project_avatar/image.vue | 146 ++++++------ .../vue_shared/components/recaptcha_modal.vue | 62 +++-- .../components/sidebar/collapsed_calendar_icon.vue | 2 +- .../sidebar/collapsed_grouped_date_picker.vue | 10 +- .../vue_shared/components/sidebar/date_picker.vue | 14 +- .../components/sidebar/toggle_sidebar.vue | 4 +- .../vue_shared/components/table_pagination.vue | 250 ++++++++++----------- .../components/user_avatar/user_avatar_svg.vue | 2 +- package.json | 4 +- yarn.lock | 12 +- 18 files changed, 483 insertions(+), 478 deletions(-) diff --git a/.eslintrc b/.eslintrc index 2c284ec6d94..5b1e5bfd530 100644 --- a/.eslintrc +++ b/.eslintrc @@ -37,6 +37,16 @@ "import/no-commonjs": "error", "no-multiple-empty-lines": ["error", { "max": 1 }], "promise/catch-or-return": "error", - "no-underscore-dangle": ["error", { "allow": ["__"]}] + "no-underscore-dangle": ["error", { "allow": ["__"]}], + "vue/html-self-closing": ["error", { + "html": { + "void": "always", + "normal": "any", + "component": "always" + }, + "svg": "always", + "math": "any" + }] + } } diff --git a/app/assets/javascripts/vue_shared/components/modal.vue b/app/assets/javascripts/vue_shared/components/modal.vue index 55f466b7b41..b2573c2c9a4 100644 --- a/app/assets/javascripts/vue_shared/components/modal.vue +++ b/app/assets/javascripts/vue_shared/components/modal.vue @@ -1,131 +1,133 @@ - diff --git a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue index a2ddd565170..cb8e6072a9b 100644 --- a/app/assets/javascripts/vue_shared/components/navigation_tabs.vue +++ b/app/assets/javascripts/vue_shared/components/navigation_tabs.vue @@ -45,7 +45,7 @@ this.$emit('onChangeTab', tab.scope); }, }, -}; + }; diff --git a/app/assets/javascripts/monitoring/components/graph/deployment.vue b/app/assets/javascripts/monitoring/components/graph/deployment.vue index 026e2fd0c49..8d6393d4ce5 100644 --- a/app/assets/javascripts/monitoring/components/graph/deployment.vue +++ b/app/assets/javascripts/monitoring/components/graph/deployment.vue @@ -1,13 +1,6 @@ diff --git a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js index cbca14ede02..6cc67ba57ee 100644 --- a/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js +++ b/app/assets/javascripts/monitoring/mixins/monitoring_mixins.js @@ -29,15 +29,18 @@ const mixins = { time.setSeconds(this.timeSeries[0].values[0].time.getSeconds()); if (xPos >= 0) { + const seriesIndex = bisectDate(this.timeSeries[0].values, time, 1); + deploymentDataArray.push({ id: deployment.id, time, sha: deployment.sha, commitUrl: `${this.projectPath}/commit/${deployment.sha}`, tag: deployment.tag, - tagUrl: `${this.tagsPath}/${deployment.tag}`, + tagUrl: deployment.tag ? `${this.tagsPath}/${deployment.ref.name}` : null, ref: deployment.ref.name, xPos, + seriesIndex, showDeploymentFlag: false, }); } diff --git a/app/assets/javascripts/monitoring/utils/date_time_formatters.js b/app/assets/javascripts/monitoring/utils/date_time_formatters.js index 068813ddee6..f3c9acdd93e 100644 --- a/app/assets/javascripts/monitoring/utils/date_time_formatters.js +++ b/app/assets/javascripts/monitoring/utils/date_time_formatters.js @@ -14,7 +14,7 @@ const d3 = { timeYear, }; -export const dateFormat = d3.time('%b %-d, %Y'); +export const dateFormat = d3.time('%a, %b %-d'); export const timeFormat = d3.time('%-I:%M%p'); export const dateFormatWithName = d3.time('%a, %b %-d'); export const bisectDate = d3.bisector(d => d.time).left; diff --git a/app/assets/stylesheets/pages/environments.scss b/app/assets/stylesheets/pages/environments.scss index f4882305c57..794bc668562 100644 --- a/app/assets/stylesheets/pages/environments.scss +++ b/app/assets/stylesheets/pages/environments.scss @@ -248,6 +248,73 @@ } } +.prometheus-graph-cursor { + position: absolute; + background: $theme-gray-600; + width: 1px; +} + +.prometheus-graph-flag { + display: block; + min-width: 160px; + + h5 { + padding: 0; + margin: 0; + font-size: 14px; + line-height: 1.2; + } + + table { + border-collapse: collapse; + padding: 0; + margin: 0; + } + + td { + vertical-align: middle; + + + td { + padding-left: 5px; + vertical-align: top; + } + } + + .deploy-meta-content { + border-bottom: 1px solid $white-dark; + + svg { + height: 15px; + vertical-align: bottom; + } + } + + &.popover { + &.left { + left: auto; + right: 0; + margin-right: 10px; + } + + &.right { + left: 0; + right: auto; + margin-left: 10px; + } + + > .arrow { + top: 40px; + } + + > .popover-title, + > .popover-content { + padding: 5px 8px; + font-size: 12px; + white-space: nowrap; + } + } +} + .prometheus-svg-container { position: relative; height: 0; diff --git a/changelogs/unreleased/38030-add-graph-value-to-hover.yml b/changelogs/unreleased/38030-add-graph-value-to-hover.yml new file mode 100644 index 00000000000..233db2b19c9 --- /dev/null +++ b/changelogs/unreleased/38030-add-graph-value-to-hover.yml @@ -0,0 +1,5 @@ +--- +title: Display graph values on hover within monitoring page +merge_request: 16261 +author: +type: changed diff --git a/spec/javascripts/monitoring/graph/deployment_spec.js b/spec/javascripts/monitoring/graph/deployment_spec.js index bf6ada8185e..d07db871d69 100644 --- a/spec/javascripts/monitoring/graph/deployment_spec.js +++ b/spec/javascripts/monitoring/graph/deployment_spec.js @@ -11,168 +11,38 @@ const createComponent = (propsData) => { }; describe('MonitoringDeployment', () => { - const reducedDeploymentData = [deploymentData[0]]; - reducedDeploymentData[0].ref = reducedDeploymentData[0].ref.name; - reducedDeploymentData[0].xPos = 10; - reducedDeploymentData[0].time = new Date(reducedDeploymentData[0].created_at); describe('Methods', () => { - it('refText shows the ref when a tag is available', () => { - reducedDeploymentData[0].tag = '1.0'; - const component = createComponent({ - showDeployInfo: false, - deploymentData: reducedDeploymentData, - graphWidth: 440, - graphHeight: 300, - graphHeightOffset: 120, - }); - - expect( - component.refText(reducedDeploymentData[0]), - ).toEqual(reducedDeploymentData[0].ref); - }); - - it('refText shows the sha when no tag is available', () => { - reducedDeploymentData[0].tag = null; - const component = createComponent({ - showDeployInfo: false, - deploymentData: reducedDeploymentData, - graphHeight: 300, - graphWidth: 440, - graphHeightOffset: 120, - }); - - expect( - component.refText(reducedDeploymentData[0]), - ).toContain('f5bcd1'); - }); - - it('nameDeploymentClass creates a class with the prefix deploy-info-', () => { + it('should contain a hidden gradient', () => { const component = createComponent({ - showDeployInfo: false, - deploymentData: reducedDeploymentData, + showDeployInfo: true, + deploymentData, graphHeight: 300, graphWidth: 440, graphHeightOffset: 120, }); - expect( - component.nameDeploymentClass(reducedDeploymentData[0]), - ).toContain('deploy-info'); + expect(component.$el.querySelector('#shadow-gradient')).not.toBeNull(); }); it('transformDeploymentGroup translates an available deployment', () => { const component = createComponent({ showDeployInfo: false, - deploymentData: reducedDeploymentData, + deploymentData, graphHeight: 300, graphWidth: 440, graphHeightOffset: 120, }); expect( - component.transformDeploymentGroup(reducedDeploymentData[0]), + component.transformDeploymentGroup({ xPos: 16 }), ).toContain('translate(11, 20)'); }); - it('hides the deployment flag', () => { - reducedDeploymentData[0].showDeploymentFlag = false; - const component = createComponent({ - showDeployInfo: true, - deploymentData: reducedDeploymentData, - graphWidth: 440, - graphHeight: 300, - graphHeightOffset: 120, - }); - - expect(component.$el.querySelector('.js-deploy-info-box')).toBeNull(); - }); - - it('positions the flag to the left when the xPos is too far right', () => { - reducedDeploymentData[0].showDeploymentFlag = false; - reducedDeploymentData[0].xPos = 250; - const component = createComponent({ - showDeployInfo: true, - deploymentData: reducedDeploymentData, - graphWidth: 440, - graphHeight: 300, - graphHeightOffset: 120, - }); - - expect( - component.positionFlag(reducedDeploymentData[0]), - ).toBeLessThan(0); - }); - - it('shows the deployment flag', () => { - reducedDeploymentData[0].showDeploymentFlag = true; - const component = createComponent({ - showDeployInfo: true, - deploymentData: reducedDeploymentData, - graphHeight: 300, - graphWidth: 440, - graphHeightOffset: 120, - }); - - expect( - component.$el.querySelector('.js-deploy-info-box').style.display, - ).not.toEqual('display: none;'); - }); - - it('contains date, refs and the "deployed" text', () => { - reducedDeploymentData[0].showDeploymentFlag = true; - const component = createComponent({ - showDeployInfo: true, - deploymentData: reducedDeploymentData, - graphHeight: 300, - graphWidth: 440, - graphHeightOffset: 120, - }); - - expect( - component.$el.querySelectorAll('.deploy-info-text'), - ).toContainText('Deployed'); - - expect( - component.$el.querySelectorAll('.deploy-info-text'), - ).toContainText('Wed, May 31'); - - expect( - component.$el.querySelectorAll('.deploy-info-text'), - ).toContainText(component.refText(reducedDeploymentData[0])); - }); - - it('contains a link to the commit contents', () => { - reducedDeploymentData[0].showDeploymentFlag = true; - const component = createComponent({ - showDeployInfo: true, - deploymentData: reducedDeploymentData, - graphHeight: 300, - graphWidth: 440, - graphHeightOffset: 120, - }); - - expect( - component.$el.querySelectorAll('.deploy-info-text-link')[0].parentElement.getAttribute('xlink:href'), - ).not.toEqual(''); - }); - - it('should contain a hidden gradient', () => { - const component = createComponent({ - showDeployInfo: true, - deploymentData: reducedDeploymentData, - graphHeight: 300, - graphWidth: 440, - graphHeightOffset: 120, - }); - - expect(component.$el.querySelector('#shadow-gradient')).not.toBeNull(); - }); - describe('Computed props', () => { it('calculatedHeight', () => { const component = createComponent({ showDeployInfo: true, - deploymentData: reducedDeploymentData, + deploymentData, graphHeight: 300, graphWidth: 440, graphHeightOffset: 120, diff --git a/spec/javascripts/monitoring/graph/flag_spec.js b/spec/javascripts/monitoring/graph/flag_spec.js index 8ee1171419d..2d474e9092f 100644 --- a/spec/javascripts/monitoring/graph/flag_spec.js +++ b/spec/javascripts/monitoring/graph/flag_spec.js @@ -1,5 +1,6 @@ import Vue from 'vue'; import GraphFlag from '~/monitoring/components/graph/flag.vue'; +import { deploymentData } from '../mock_data'; const createComponent = (propsData) => { const Component = Vue.extend(GraphFlag); @@ -9,11 +10,6 @@ const createComponent = (propsData) => { }).$mount(); }; -function getCoordinate(component, selector, coordinate) { - const coordinateVal = component.$el.querySelector(selector).getAttribute(coordinate); - return parseInt(coordinateVal, 10); -} - const defaultValuesComponent = { currentXCoordinate: 200, currentYCoordinate: 100, @@ -25,31 +21,111 @@ const defaultValuesComponent = { graphHeight: 300, graphHeightOffset: 120, showFlagContent: true, + realPixelRatio: 1, + timeSeries: [{ + values: [{ + time: new Date('2017-06-04T18:17:33.501Z'), + value: '1.49609375', + }], + }], + unitOfDisplay: 'ms', + currentDataIndex: 0, + legendTitle: 'Average', +}; + +const deploymentFlagData = { + ...deploymentData[0], + ref: deploymentData[0].ref.name, + xPos: 10, + time: new Date(deploymentData[0].created_at), }; describe('GraphFlag', () => { - it('has a line and a circle located at the currentXCoordinate and currentYCoordinate', () => { - const component = createComponent(defaultValuesComponent); + let component; - expect(getCoordinate(component, '.selected-metric-line', 'x1')) - .toEqual(component.currentXCoordinate); - expect(getCoordinate(component, '.selected-metric-line', 'x2')) - .toEqual(component.currentXCoordinate); + it('has a line at the currentXCoordinate', () => { + component = createComponent(defaultValuesComponent); + + expect(component.$el.style.left) + .toEqual(`${70 + component.currentXCoordinate}px`); }); - it('has a SVG with the class rect-text-metric at the currentFlagPosition', () => { - const component = createComponent(defaultValuesComponent); + describe('Deployment flag', () => { + it('shows a deployment flag when deployment data provided', () => { + const deploymentFlagComponent = createComponent({ + ...defaultValuesComponent, + deploymentFlagData, + }); + + expect( + deploymentFlagComponent.$el.querySelector('.popover-title'), + ).toContainText('Deployed'); + }); + + it('contains the ref when a tag is available', () => { + const deploymentFlagComponent = createComponent({ + ...defaultValuesComponent, + deploymentFlagData: { + ...deploymentFlagData, + sha: 'f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187', + tag: true, + ref: '1.0', + }, + }); + + expect( + deploymentFlagComponent.$el.querySelector('.deploy-meta-content'), + ).toContainText('f5bcd1d9'); + + expect( + deploymentFlagComponent.$el.querySelector('.deploy-meta-content'), + ).toContainText('1.0'); + }); + + it('does not contain the ref when a tag is unavailable', () => { + const deploymentFlagComponent = createComponent({ + ...defaultValuesComponent, + deploymentFlagData: { + ...deploymentFlagData, + sha: 'f5bcd1d9dac6fa4137e2510b9ccd134ef2e84187', + tag: false, + ref: '1.0', + }, + }); + + expect( + deploymentFlagComponent.$el.querySelector('.deploy-meta-content'), + ).toContainText('f5bcd1d9'); - const svg = component.$el.querySelector('.rect-text-metric'); - expect(svg.tagName).toEqual('svg'); - expect(parseInt(svg.getAttribute('x'), 10)).toEqual(component.currentFlagPosition); + expect( + deploymentFlagComponent.$el.querySelector('.deploy-meta-content'), + ).not.toContainText('1.0'); + }); }); describe('Computed props', () => { - it('calculatedHeight', () => { - const component = createComponent(defaultValuesComponent); + beforeEach(() => { + component = createComponent(defaultValuesComponent); + }); + + it('formatTime', () => { + expect(component.formatTime).toMatch(/\d:17PM/); + }); + + it('formatDate', () => { + expect(component.formatDate).toEqual('Sun, Jun 4'); + }); + + it('cursorStyle', () => { + expect(component.cursorStyle).toEqual({ + top: '20px', + left: '270px', + height: '180px', + }); + }); - expect(component.calculatedHeight).toEqual(180); + it('flagOrientation', () => { + expect(component.flagOrientation).toEqual('left'); }); }); }); -- cgit v1.2.1 From 3f54abb26c48885cb022a1a86ac7dc947579869c Mon Sep 17 00:00:00 2001 From: Winnie Hellmann Date: Mon, 8 Jan 2018 13:33:30 +0100 Subject: Make tasklist:changed test in merge_request_spec.js async --- spec/javascripts/merge_request_spec.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/spec/javascripts/merge_request_spec.js b/spec/javascripts/merge_request_spec.js index 2f02c11482f..9d6ea3781bc 100644 --- a/spec/javascripts/merge_request_spec.js +++ b/spec/javascripts/merge_request_spec.js @@ -19,17 +19,24 @@ import IssuablesHelper from '~/helpers/issuables_helper'; $('input[type=checkbox]').attr('checked', true)[0].dispatchEvent(changeEvent); return expect($('.js-task-list-field').val()).toBe('- [x] Task List Item'); }); - return it('submits an ajax request on tasklist:changed', function() { - spyOn(jQuery, 'ajax').and.callFake(function(req) { + + it('submits an ajax request on tasklist:changed', (done) => { + spyOn(jQuery, 'ajax').and.callFake((req) => { expect(req.type).toBe('PATCH'); expect(req.url).toBe(`${gl.TEST_HOST}/frontend-fixtures/merge-requests-project/merge_requests/1.json`); - return expect(req.data.merge_request.description).not.toBe(null); + expect(req.data.merge_request.description).not.toBe(null); + done(); }); - return $('.js-task-list-field').trigger('tasklist:changed'); + + $('.js-task-list-field').trigger('tasklist:changed'); }); }); describe('class constructor', () => { + beforeEach(() => { + spyOn(jQuery, 'ajax').and.stub(); + }); + it('calls .initCloseReopenReport', () => { spyOn(IssuablesHelper, 'initCloseReopenReport'); -- cgit v1.2.1 From 6762d2875e979e1e2692062b2353265f5d743fc8 Mon Sep 17 00:00:00 2001 From: Brett Walker Date: Mon, 8 Jan 2018 12:44:32 +0000 Subject: Resolve "Allow QA tests to run with `CHROME_HEADLESS=false`" --- qa/qa.rb | 1 + qa/qa/runtime/browser.rb | 41 ++++++++++++++++++++++++----- qa/qa/runtime/env.rb | 15 +++++++++++ qa/spec/runtime/env_spec.rb | 64 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 qa/qa/runtime/env.rb create mode 100644 qa/spec/runtime/env_spec.rb diff --git a/qa/qa.rb b/qa/qa.rb index 453e4e9e164..71b80a6adcb 100644 --- a/qa/qa.rb +++ b/qa/qa.rb @@ -10,6 +10,7 @@ module QA autoload :Namespace, 'qa/runtime/namespace' autoload :Scenario, 'qa/runtime/scenario' autoload :Browser, 'qa/runtime/browser' + autoload :Env, 'qa/runtime/env' end ## diff --git a/qa/qa/runtime/browser.rb b/qa/qa/runtime/browser.rb index 220bb45741b..14b2a488760 100644 --- a/qa/qa/runtime/browser.rb +++ b/qa/qa/runtime/browser.rb @@ -38,22 +38,49 @@ module QA Capybara.register_driver :chrome do |app| capabilities = Selenium::WebDriver::Remote::Capabilities.chrome( - 'chromeOptions' => { - 'args' => %w[headless no-sandbox disable-gpu window-size=1280,1680] + # This enables access to logs with `page.driver.manage.get_log(:browser)` + loggingPrefs: { + browser: "ALL", + client: "ALL", + driver: "ALL", + server: "ALL" } ) - Capybara::Selenium::Driver - .new(app, browser: :chrome, desired_capabilities: capabilities) - end + options = Selenium::WebDriver::Chrome::Options.new + options.add_argument("window-size=1240,1680") - Capybara::Screenshot.register_driver(:chrome) do |driver, path| - driver.browser.save_screenshot(path) + # Chrome won't work properly in a Docker container in sandbox mode + options.add_argument("no-sandbox") + + # Run headless by default unless CHROME_HEADLESS is false + if QA::Runtime::Env.chrome_headless? + options.add_argument("headless") + + # Chrome documentation says this flag is needed for now + # https://developers.google.com/web/updates/2017/04/headless-chrome#cli + options.add_argument("disable-gpu") + end + + # Disable /dev/shm use in CI. See https://gitlab.com/gitlab-org/gitlab-ee/issues/4252 + options.add_argument("disable-dev-shm-usage") if QA::Runtime::Env.running_in_ci? + + Capybara::Selenium::Driver.new( + app, + browser: :chrome, + desired_capabilities: capabilities, + options: options + ) end # Keep only the screenshots generated from the last failing test suite Capybara::Screenshot.prune_strategy = :keep_last_run + # From https://github.com/mattheworiordan/capybara-screenshot/issues/84#issuecomment-41219326 + Capybara::Screenshot.register_driver(:chrome) do |driver, path| + driver.browser.save_screenshot(path) + end + Capybara.configure do |config| config.default_driver = :chrome config.javascript_driver = :chrome diff --git a/qa/qa/runtime/env.rb b/qa/qa/runtime/env.rb new file mode 100644 index 00000000000..d5c28e9a7db --- /dev/null +++ b/qa/qa/runtime/env.rb @@ -0,0 +1,15 @@ +module QA + module Runtime + module Env + extend self + + def chrome_headless? + (ENV['CHROME_HEADLESS'] =~ /^(false|no|0)$/i) != 0 + end + + def running_in_ci? + ENV['CI'] || ENV['CI_SERVER'] + end + end + end +end diff --git a/qa/spec/runtime/env_spec.rb b/qa/spec/runtime/env_spec.rb new file mode 100644 index 00000000000..57a72a04507 --- /dev/null +++ b/qa/spec/runtime/env_spec.rb @@ -0,0 +1,64 @@ +describe QA::Runtime::Env do + before do + allow(ENV).to receive(:[]).and_call_original + end + + describe '.chrome_headless?' do + context 'when there is an env variable set' do + it 'returns false when falsey values specified' do + stub_env('CHROME_HEADLESS', 'false') + expect(described_class.chrome_headless?).to be_falsey + + stub_env('CHROME_HEADLESS', 'no') + expect(described_class.chrome_headless?).to be_falsey + + stub_env('CHROME_HEADLESS', '0') + expect(described_class.chrome_headless?).to be_falsey + end + + it 'returns true when anything else specified' do + stub_env('CHROME_HEADLESS', 'true') + expect(described_class.chrome_headless?).to be_truthy + + stub_env('CHROME_HEADLESS', '1') + expect(described_class.chrome_headless?).to be_truthy + + stub_env('CHROME_HEADLESS', 'anything') + expect(described_class.chrome_headless?).to be_truthy + end + end + + context 'when there is no env variable set' do + it 'returns the default, true' do + stub_env('CHROME_HEADLESS', nil) + expect(described_class.chrome_headless?).to be_truthy + end + end + end + + describe '.running_in_ci?' do + context 'when there is an env variable set' do + it 'returns true if CI' do + stub_env('CI', 'anything') + expect(described_class.running_in_ci?).to be_truthy + end + + it 'returns true if CI_SERVER' do + stub_env('CI_SERVER', 'anything') + expect(described_class.running_in_ci?).to be_truthy + end + end + + context 'when there is no env variable set' do + it 'returns true' do + stub_env('CI', nil) + stub_env('CI_SERVER', nil) + expect(described_class.running_in_ci?).to be_falsey + end + end + end + + def stub_env(name, value) + allow(ENV).to receive(:[]).with(name).and_return(value) + end +end -- cgit v1.2.1 From 6732795231dd71f2d5cd8a851372db1894ba0a3f Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Mon, 8 Jan 2018 22:24:23 +0900 Subject: Add memoization for properties --- ...ate_kubernetes_service_to_new_clusters_architectures.rb | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/db/post_migrate/20171124104327_migrate_kubernetes_service_to_new_clusters_architectures.rb b/db/post_migrate/20171124104327_migrate_kubernetes_service_to_new_clusters_architectures.rb index 3fe0a4941d5..11b581e4b57 100644 --- a/db/post_migrate/20171124104327_migrate_kubernetes_service_to_new_clusters_architectures.rb +++ b/db/post_migrate/20171124104327_migrate_kubernetes_service_to_new_clusters_architectures.rb @@ -76,19 +76,25 @@ class MigrateKubernetesServiceToNewClustersArchitectures < ActiveRecord::Migrati end def api_url - JSON.parse(self.properties)['api_url'] + parsed_properties['api_url'] end def ca_pem - JSON.parse(self.properties)['ca_pem'] + parsed_properties['ca_pem'] end def namespace - JSON.parse(self.properties)['namespace'] + parsed_properties['namespace'] end def token - JSON.parse(self.properties)['token'] + parsed_properties['token'] + end + + private + + def parsed_properties + @parsed_properties ||= JSON.parse(self.properties) end end -- cgit v1.2.1 From a32fcf33ee56ebd7e2d07e377610cfcce53d5a88 Mon Sep 17 00:00:00 2001 From: Onuwa Nnachi Isaac Date: Mon, 8 Jan 2018 15:18:13 +0100 Subject: chore: remove symbolic link --- doc/doc | 1 - 1 file changed, 1 deletion(-) delete mode 120000 doc/doc diff --git a/doc/doc b/doc/doc deleted file mode 120000 index e78a24e7fe6..00000000000 --- a/doc/doc +++ /dev/null @@ -1 +0,0 @@ -/home/isaac/apps/opensource/GitLab/gitlab-ce/doc \ No newline at end of file -- cgit v1.2.1 From dca85d4bad39e4c2e5a9b3fec792ccc259f81cff Mon Sep 17 00:00:00 2001 From: Jacob Vosmaer Date: Mon, 8 Jan 2018 15:38:24 +0100 Subject: Use workhorse 3.4.0 --- GITLAB_WORKHORSE_VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GITLAB_WORKHORSE_VERSION b/GITLAB_WORKHORSE_VERSION index bea438e9ade..18091983f59 100644 --- a/GITLAB_WORKHORSE_VERSION +++ b/GITLAB_WORKHORSE_VERSION @@ -1 +1 @@ -3.3.1 +3.4.0 -- cgit v1.2.1 From b95815e5f808c5a4005c2eb5b5f7194684f79f6b Mon Sep 17 00:00:00 2001 From: Josh Unger Date: Mon, 8 Jan 2018 16:02:40 +0000 Subject: Add Gitter room link to I want to contribute since you always have questions --- CONTRIBUTING.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 2b79f0825e2..057e2d6e0dc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -104,9 +104,13 @@ the remaining issues on the GitHub issue tracker. ## I want to contribute! -If you want to contribute to GitLab, [issues with the label `Accepting Merge Requests` and small weight][accepting-mrs-weight] is a great place to start. Issues with a lower weight (1 or 2) are deemed suitable for beginners. -These issues will be of reasonable size and challenge, for anyone to start -contributing to GitLab. +If you want to contribute to GitLab [issues with the label `Accepting Merge Requests` and small weight][accepting-mrs-weight] +is a great place to start. Issues with a lower weight (1 or 2) are deemed +suitable for beginners. These issues will be of reasonable size and challenge, +for anyone to start contributing to GitLab. If you have any questions or need help visit [Getting Help](https://about.gitlab.com/getting-help/#discussion) to +learn how to communicate with GitLab. If you're looking for a Gitter or Slack channel +please consider we favor +[asynchronous communication](https://about.gitlab.com/handbook/communication/#internal-communication) over real time communication. Thanks for your contribution! ## Workflow labels -- cgit v1.2.1 From d0b8f536a1865af3741fc3255325b7e211ed1d42 Mon Sep 17 00:00:00 2001 From: Yorick Peterse Date: Tue, 2 Jan 2018 17:21:28 +0100 Subject: Remove soft removals related code This removes all usage of soft removals except for the "pending delete" system implemented for projects. This in turn simplifies all the query plans of the models that used soft removals. Since we don't really use soft removals for anything useful there's no point in keeping it around. This _does_ mean that hard removals of issues (which only admins can do if I'm not mistaken) can influence the "iid" values, but that code is broken to begin with. More on this (and how to fix it) can be found in https://gitlab.com/gitlab-org/gitlab-ce/issues/31114. Fixes https://gitlab.com/gitlab-org/gitlab-ce/issues/37447 --- Gemfile | 3 - Gemfile.lock | 3 - app/models/ci/pipeline_schedule.rb | 3 +- app/models/ci/trigger.rb | 3 +- app/models/concerns/internal_id.rb | 1 - app/models/issue.rb | 4 +- app/models/merge_request.rb | 5 +- app/models/namespace.rb | 11 +- app/serializers/issue_entity.rb | 1 - app/services/groups/destroy_service.rb | 3 +- app/services/users/destroy_service.rb | 2 +- app/workers/group_destroy_worker.rb | 2 +- changelogs/unreleased/remove-soft-removals.yml | 5 + .../20171207150343_remove_soft_removed_objects.rb | 210 +++++++++++++++++++++ .../20171207150344_remove_deleted_at_columns.rb | 31 +++ db/schema.rb | 8 - doc/api/pipeline_triggers.md | 5 - lib/api/entities.rb | 2 +- lib/api/v3/entities.rb | 2 +- lib/gitlab/cycle_analytics/base_query.rb | 1 - lib/gitlab/hook_data/issue_builder.rb | 1 - lib/gitlab/hook_data/merge_request_builder.rb | 1 - spec/fixtures/api/schemas/entities/issue.json | 1 - .../api/schemas/entities/merge_request_widget.json | 1 - spec/javascripts/notes/mock_data.js | 2 - spec/javascripts/sidebar/mock_data.js | 2 - spec/javascripts/vue_mr_widget/mock_data.js | 1 - spec/lib/gitlab/hook_data/issue_builder_spec.rb | 1 - .../gitlab/hook_data/merge_request_builder_spec.rb | 1 - spec/lib/gitlab/import_export/project.group.json | 2 - spec/lib/gitlab/import_export/project.json | 20 -- spec/lib/gitlab/import_export/project.light.json | 1 - .../gitlab/import_export/safe_model_attributes.yml | 4 - spec/models/ci/pipeline_schedule_spec.rb | 1 - spec/models/issue_spec.rb | 5 - spec/models/merge_request_spec.rb | 5 - spec/models/namespace_spec.rb | 11 -- spec/services/users/destroy_service_spec.rb | 2 +- 38 files changed, 262 insertions(+), 105 deletions(-) create mode 100644 changelogs/unreleased/remove-soft-removals.yml create mode 100644 db/post_migrate/20171207150343_remove_soft_removed_objects.rb create mode 100644 db/post_migrate/20171207150344_remove_deleted_at_columns.rb diff --git a/Gemfile b/Gemfile index 38381d34b6b..da482ad3b7a 100644 --- a/Gemfile +++ b/Gemfile @@ -381,9 +381,6 @@ gem 'ruby-prof', '~> 0.16.2' # OAuth gem 'oauth2', '~> 1.4' -# Soft deletion -gem 'paranoia', '~> 2.3.1' - # Health check gem 'health_check', '~> 2.6.0' diff --git a/Gemfile.lock b/Gemfile.lock index 2a81c81b0f8..f35bccdf070 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -580,8 +580,6 @@ GEM orm_adapter (0.5.0) os (0.9.6) parallel (1.12.0) - paranoia (2.3.1) - activerecord (>= 4.0, < 5.2) parser (2.4.0.2) ast (~> 2.3) parslet (1.5.0) @@ -1110,7 +1108,6 @@ DEPENDENCIES omniauth-twitter (~> 1.2.0) omniauth_crowd (~> 2.2.0) org-ruby (~> 0.9.12) - paranoia (~> 2.3.1) peek (~> 1.0.1) peek-gc (~> 0.0.2) peek-host (~> 1.0.0) diff --git a/app/models/ci/pipeline_schedule.rb b/app/models/ci/pipeline_schedule.rb index 10ead6b6d3b..b6abc3d7681 100644 --- a/app/models/ci/pipeline_schedule.rb +++ b/app/models/ci/pipeline_schedule.rb @@ -2,8 +2,9 @@ module Ci class PipelineSchedule < ActiveRecord::Base extend Gitlab::Ci::Model include Importable + include IgnorableColumn - acts_as_paranoid + ignore_column :deleted_at belongs_to :project belongs_to :owner, class_name: 'User' diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb index b5290bcaf53..aa065e33739 100644 --- a/app/models/ci/trigger.rb +++ b/app/models/ci/trigger.rb @@ -1,8 +1,9 @@ module Ci class Trigger < ActiveRecord::Base extend Gitlab::Ci::Model + include IgnorableColumn - acts_as_paranoid + ignore_column :deleted_at belongs_to :project belongs_to :owner, class_name: "User" diff --git a/app/models/concerns/internal_id.rb b/app/models/concerns/internal_id.rb index a3d0ac8d862..01079fb8bd6 100644 --- a/app/models/concerns/internal_id.rb +++ b/app/models/concerns/internal_id.rb @@ -10,7 +10,6 @@ module InternalId if iid.blank? parent = project || group records = parent.public_send(self.class.name.tableize) # rubocop:disable GitlabSecurity/PublicSend - records = records.with_deleted if self.paranoid? max_iid = records.maximum(:iid) self.iid = max_iid.to_i + 1 diff --git a/app/models/issue.rb b/app/models/issue.rb index ad4a3c737ff..93628b456f2 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -12,7 +12,7 @@ class Issue < ActiveRecord::Base include ThrottledTouch include IgnorableColumn - ignore_column :assignee_id, :branch_name + ignore_column :assignee_id, :branch_name, :deleted_at DueDateStruct = Struct.new(:title, :name).freeze NoDueDate = DueDateStruct.new('No Due Date', '0').freeze @@ -78,8 +78,6 @@ class Issue < ActiveRecord::Base end end - acts_as_paranoid - class << self alias_method :in_parents, :in_projects end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index ef58816937c..8fdeddf1ed1 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -11,7 +11,8 @@ class MergeRequest < ActiveRecord::Base include Gitlab::Utils::StrongMemoize ignore_column :locked_at, - :ref_fetched + :ref_fetched, + :deleted_at belongs_to :target_project, class_name: "Project" belongs_to :source_project, class_name: "Project" @@ -150,8 +151,6 @@ class MergeRequest < ActiveRecord::Base after_save :keep_around_commit - acts_as_paranoid - def self.reference_prefix '!' end diff --git a/app/models/namespace.rb b/app/models/namespace.rb index bdcc9159d26..37a7417cafc 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -1,6 +1,4 @@ class Namespace < ActiveRecord::Base - acts_as_paranoid without_default_scope: true - include CacheMarkdownField include Sortable include Gitlab::ShellAdapter @@ -10,6 +8,9 @@ class Namespace < ActiveRecord::Base include AfterCommitQueue include Storage::LegacyNamespace include Gitlab::SQL::Pattern + include IgnorableColumn + + ignore_column :deleted_at # Prevent users from creating unreasonably deep level of nesting. # The number 20 was taken based on maximum nesting level of @@ -221,12 +222,6 @@ class Namespace < ActiveRecord::Base has_parent? end - def soft_delete_without_removing_associations - # We can't use paranoia's `#destroy` since this will hard-delete projects. - # Project uses `pending_delete` instead of the acts_as_paranoia gem. - self.deleted_at = Time.now - end - private def refresh_access_of_projects_invited_groups diff --git a/app/serializers/issue_entity.rb b/app/serializers/issue_entity.rb index 0bdd4d7a272..b5e2334b6e3 100644 --- a/app/serializers/issue_entity.rb +++ b/app/serializers/issue_entity.rb @@ -6,7 +6,6 @@ class IssueEntity < IssuableEntity expose :updated_by_id expose :created_at expose :updated_at - expose :deleted_at expose :milestone, using: API::Entities::Milestone expose :labels, using: LabelEntity expose :lock_version diff --git a/app/services/groups/destroy_service.rb b/app/services/groups/destroy_service.rb index e3f9d9ee95d..58e88688dfa 100644 --- a/app/services/groups/destroy_service.rb +++ b/app/services/groups/destroy_service.rb @@ -1,7 +1,6 @@ module Groups class DestroyService < Groups::BaseService def async_execute - group.soft_delete_without_removing_associations job_id = GroupDestroyWorker.perform_async(group.id, current_user.id) Rails.logger.info("User #{current_user.id} scheduled a deletion of group ID #{group.id} with job ID #{job_id}") end @@ -23,7 +22,7 @@ module Groups group.chat_team&.remove_mattermost_team(current_user) - group.really_destroy! + group.destroy end end end diff --git a/app/services/users/destroy_service.rb b/app/services/users/destroy_service.rb index 00db8a2c434..b71002433d6 100644 --- a/app/services/users/destroy_service.rb +++ b/app/services/users/destroy_service.rb @@ -53,7 +53,7 @@ module Users # Destroy the namespace after destroying the user since certain methods may depend on the namespace existing user_data = user.destroy - namespace.really_destroy! + namespace.destroy user_data end diff --git a/app/workers/group_destroy_worker.rb b/app/workers/group_destroy_worker.rb index f577b310b20..509bd09dc2e 100644 --- a/app/workers/group_destroy_worker.rb +++ b/app/workers/group_destroy_worker.rb @@ -4,7 +4,7 @@ class GroupDestroyWorker def perform(group_id, user_id) begin - group = Group.with_deleted.find(group_id) + group = Group.find(group_id) rescue ActiveRecord::RecordNotFound return end diff --git a/changelogs/unreleased/remove-soft-removals.yml b/changelogs/unreleased/remove-soft-removals.yml new file mode 100644 index 00000000000..aa53d33e502 --- /dev/null +++ b/changelogs/unreleased/remove-soft-removals.yml @@ -0,0 +1,5 @@ +--- +title: Remove soft removals related code +merge_request: 15789 +author: +type: changed diff --git a/db/post_migrate/20171207150343_remove_soft_removed_objects.rb b/db/post_migrate/20171207150343_remove_soft_removed_objects.rb new file mode 100644 index 00000000000..542cfb42fdc --- /dev/null +++ b/db/post_migrate/20171207150343_remove_soft_removed_objects.rb @@ -0,0 +1,210 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class RemoveSoftRemovedObjects < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + disable_ddl_transaction! + + module SoftRemoved + extend ActiveSupport::Concern + + included do + scope :soft_removed, -> { where('deleted_at IS NOT NULL') } + end + end + + class User < ActiveRecord::Base + self.table_name = 'users' + + include EachBatch + end + + class Issue < ActiveRecord::Base + self.table_name = 'issues' + + include EachBatch + include SoftRemoved + end + + class MergeRequest < ActiveRecord::Base + self.table_name = 'merge_requests' + + include EachBatch + include SoftRemoved + end + + class Namespace < ActiveRecord::Base + self.table_name = 'namespaces' + + include EachBatch + include SoftRemoved + + scope :soft_removed_personal, -> { soft_removed.where(type: nil) } + scope :soft_removed_group, -> { soft_removed.where(type: 'Group') } + end + + class Route < ActiveRecord::Base + self.table_name = 'routes' + + include EachBatch + include SoftRemoved + end + + class Project < ActiveRecord::Base + self.table_name = 'projects' + + include EachBatch + include SoftRemoved + end + + class CiPipelineSchedule < ActiveRecord::Base + self.table_name = 'ci_pipeline_schedules' + + include EachBatch + include SoftRemoved + end + + class CiTrigger < ActiveRecord::Base + self.table_name = 'ci_triggers' + + include EachBatch + include SoftRemoved + end + + MODELS = [Issue, MergeRequest, CiPipelineSchedule, CiTrigger].freeze + + def up + disable_statement_timeout + + remove_personal_routes + remove_personal_namespaces + remove_group_namespaces + remove_simple_soft_removed_rows + end + + def down + # The data removed by this migration can't be restored in an automated way. + end + + def remove_simple_soft_removed_rows + create_temporary_indexes + + MODELS.each do |model| + say_with_time("Removing soft removed rows from #{model.table_name}") do + model.soft_removed.each_batch do |batch, index| + batch.delete_all + end + end + end + ensure + remove_temporary_indexes + end + + def create_temporary_indexes + MODELS.each do |model| + index_name = temporary_index_name_for(model) + + # Without this index the removal process can take a very long time. For + # example, getting the next ID of a batch for the `issues` table in + # staging would take between 15 and 20 seconds. + next if temporary_index_exists?(model) + + say_with_time("Creating temporary index #{index_name}") do + add_concurrent_index( + model.table_name, + [:deleted_at, :id], + name: index_name, + where: 'deleted_at IS NOT NULL' + ) + end + end + end + + def remove_temporary_indexes + MODELS.each do |model| + index_name = temporary_index_name_for(model) + + next unless temporary_index_exists?(model) + + say_with_time("Removing temporary index #{index_name}") do + remove_concurrent_index_by_name(model.table_name, index_name) + end + end + end + + def temporary_index_name_for(model) + "index_on_#{model.table_name}_tmp" + end + + def temporary_index_exists?(model) + index_name = temporary_index_name_for(model) + + index_exists?(model.table_name, [:deleted_at, :id], name: index_name) + end + + def remove_personal_namespaces + # Some personal namespaces are left behind in case of GitLab.com. In these + # cases the associated data such as the projects and users has already been + # removed. + Namespace.soft_removed_personal.each_batch do |batch| + batch.delete_all + end + end + + def remove_group_namespaces + # Left over groups can't be easily removed because we may also need to + # remove memberships, repositories, and other associated data. As a result + # we'll just schedule a Sidekiq job to remove these. + # + # As of January 5th, 2018 there are 36 groups that will be removed using + # this code. + Namespace.select(:id).soft_removed_group.each_batch(of: 10) do |batch, index| + # We need the ID of an admin user as the owners of the group may no longer + # exist (or might not even be set in `namespaces.owner_id`). + admin_id = id_for_admin_user + + batch.each do |ns| + schedule_group_removal(index * 5.minutes, ns.id, admin_id) + end + end + end + + def schedule_group_removal(delay, group_id, user_id) + if migrate_inline? + GroupDestroyWorker.new.perform(group_id, user_id) + else + GroupDestroyWorker.perform_in(delay, group_id, user_id) + end + end + + def remove_personal_routes + namespaces = Namespace.select(1) + .soft_removed + .where('namespaces.type IS NULL') + .where('routes.source_type = ?', 'Namespace') + .where('routes.source_id = namespaces.id') + + Route.where('EXISTS (?)', namespaces).each_batch do |batch| + batch.delete_all + end + end + + def id_for_admin_user + return @id_for_admin_user if @id_for_admin_user + + if (admin_id = User.where(admin: true).limit(1).pluck(:id).first) + @id_for_admin_user = admin_id + else + raise 'Can not remove soft removed groups as no admin user exists. ' \ + 'Please make sure at least one user with `admin` set to TRUE exists before proceeding.' + end + end + + def migrate_inline? + Rails.env.test? || Rails.env.development? + end +end diff --git a/db/post_migrate/20171207150344_remove_deleted_at_columns.rb b/db/post_migrate/20171207150344_remove_deleted_at_columns.rb new file mode 100644 index 00000000000..154d7a1b926 --- /dev/null +++ b/db/post_migrate/20171207150344_remove_deleted_at_columns.rb @@ -0,0 +1,31 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class RemoveDeletedAtColumns < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + TABLES = %i[issues merge_requests namespaces ci_pipeline_schedules ci_triggers].freeze + COLUMN = :deleted_at + + def up + TABLES.each do |table| + remove_column(table, COLUMN) if column_exists?(table, COLUMN) + end + end + + def down + TABLES.each do |table| + unless column_exists?(table, COLUMN) + add_column(table, COLUMN, :datetime_with_timezone) + end + + unless index_exists?(table, COLUMN) + add_concurrent_index(table, COLUMN) + end + end + end +end diff --git a/db/schema.rb b/db/schema.rb index e6a2ea4c862..544a1bcc439 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -356,7 +356,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do t.integer "project_id" t.integer "owner_id" t.boolean "active", default: true - t.datetime "deleted_at" t.datetime "created_at" t.datetime "updated_at" end @@ -466,7 +465,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do create_table "ci_triggers", force: :cascade do |t| t.string "token" - t.datetime "deleted_at" t.datetime "created_at" t.datetime "updated_at" t.integer "project_id" @@ -860,7 +858,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do t.integer "iid" t.integer "updated_by_id" t.boolean "confidential", default: false, null: false - t.datetime "deleted_at" t.date "due_date" t.integer "moved_to_id" t.integer "lock_version" @@ -877,7 +874,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree add_index "issues", ["confidential"], name: "index_issues_on_confidential", using: :btree - add_index "issues", ["deleted_at"], name: "index_issues_on_deleted_at", using: :btree add_index "issues", ["description"], name: "index_issues_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree add_index "issues", ["moved_to_id"], name: "index_issues_on_moved_to_id", where: "(moved_to_id IS NOT NULL)", using: :btree @@ -1086,7 +1082,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do t.boolean "merge_when_pipeline_succeeds", default: false, null: false t.integer "merge_user_id" t.string "merge_commit_sha" - t.datetime "deleted_at" t.string "in_progress_merge_commit_sha" t.integer "lock_version" t.text "title_html" @@ -1105,7 +1100,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree - add_index "merge_requests", ["deleted_at"], name: "index_merge_requests_on_deleted_at", using: :btree add_index "merge_requests", ["description"], name: "index_merge_requests_on_description_trigram", using: :gin, opclasses: {"description"=>"gin_trgm_ops"} add_index "merge_requests", ["head_pipeline_id"], name: "index_merge_requests_on_head_pipeline_id", using: :btree add_index "merge_requests", ["latest_merge_request_diff_id"], name: "index_merge_requests_on_latest_merge_request_diff_id", using: :btree @@ -1165,7 +1159,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do t.boolean "share_with_group_lock", default: false t.integer "visibility_level", default: 20, null: false t.boolean "request_access_enabled", default: false, null: false - t.datetime "deleted_at" t.text "description_html" t.boolean "lfs_enabled" t.integer "parent_id" @@ -1175,7 +1168,6 @@ ActiveRecord::Schema.define(version: 20171230123729) do end add_index "namespaces", ["created_at"], name: "index_namespaces_on_created_at", using: :btree - add_index "namespaces", ["deleted_at"], name: "index_namespaces_on_deleted_at", using: :btree add_index "namespaces", ["name", "parent_id"], name: "index_namespaces_on_name_and_parent_id", unique: true, using: :btree add_index "namespaces", ["name"], name: "index_namespaces_on_name_trigram", using: :gin, opclasses: {"name"=>"gin_trgm_ops"} add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree diff --git a/doc/api/pipeline_triggers.md b/doc/api/pipeline_triggers.md index 9030ae32d17..e881e61d4ef 100644 --- a/doc/api/pipeline_triggers.md +++ b/doc/api/pipeline_triggers.md @@ -24,7 +24,6 @@ curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/ "id": 10, "description": "my trigger", "created_at": "2016-01-07T09:53:58.235Z", - "deleted_at": null, "last_used": null, "token": "6d056f63e50fe6f8c5f8f4aa10edb7", "updated_at": "2016-01-07T09:53:58.235Z", @@ -55,7 +54,6 @@ curl --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/ "id": 10, "description": "my trigger", "created_at": "2016-01-07T09:53:58.235Z", - "deleted_at": null, "last_used": null, "token": "6d056f63e50fe6f8c5f8f4aa10edb7", "updated_at": "2016-01-07T09:53:58.235Z", @@ -85,7 +83,6 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form descri "id": 10, "description": "my trigger", "created_at": "2016-01-07T09:53:58.235Z", - "deleted_at": null, "last_used": null, "token": "6d056f63e50fe6f8c5f8f4aa10edb7", "updated_at": "2016-01-07T09:53:58.235Z", @@ -116,7 +113,6 @@ curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" --form descrip "id": 10, "description": "my trigger", "created_at": "2016-01-07T09:53:58.235Z", - "deleted_at": null, "last_used": null, "token": "6d056f63e50fe6f8c5f8f4aa10edb7", "updated_at": "2016-01-07T09:53:58.235Z", @@ -146,7 +142,6 @@ curl --request POST --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitl "id": 10, "description": "my trigger", "created_at": "2016-01-07T09:53:58.235Z", - "deleted_at": null, "last_used": null, "token": "6d056f63e50fe6f8c5f8f4aa10edb7", "updated_at": "2016-01-07T09:53:58.235Z", diff --git a/lib/api/entities.rb b/lib/api/entities.rb index bd0c54a1b04..4b3c26d7c02 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -918,7 +918,7 @@ module API class Trigger < Grape::Entity expose :id expose :token, :description - expose :created_at, :updated_at, :deleted_at, :last_used + expose :created_at, :updated_at, :last_used expose :owner, using: Entities::UserBasic end diff --git a/lib/api/v3/entities.rb b/lib/api/v3/entities.rb index c17b6f45ed8..64758dae7d3 100644 --- a/lib/api/v3/entities.rb +++ b/lib/api/v3/entities.rb @@ -207,7 +207,7 @@ module API end class Trigger < Grape::Entity - expose :token, :created_at, :updated_at, :deleted_at, :last_used + expose :token, :created_at, :updated_at, :last_used expose :owner, using: ::API::Entities::UserBasic end diff --git a/lib/gitlab/cycle_analytics/base_query.rb b/lib/gitlab/cycle_analytics/base_query.rb index dcbdf9a64b0..8b3bc3e440d 100644 --- a/lib/gitlab/cycle_analytics/base_query.rb +++ b/lib/gitlab/cycle_analytics/base_query.rb @@ -15,7 +15,6 @@ module Gitlab query = mr_closing_issues_table.join(issue_table).on(issue_table[:id].eq(mr_closing_issues_table[:issue_id])) .join(issue_metrics_table).on(issue_table[:id].eq(issue_metrics_table[:issue_id])) .where(issue_table[:project_id].eq(@project.id)) # rubocop:disable Gitlab/ModuleWithInstanceVariables - .where(issue_table[:deleted_at].eq(nil)) .where(issue_table[:created_at].gteq(@options[:from])) # rubocop:disable Gitlab/ModuleWithInstanceVariables # Load merge_requests diff --git a/lib/gitlab/hook_data/issue_builder.rb b/lib/gitlab/hook_data/issue_builder.rb index e29dd0d5b0e..f9b1a3caf5e 100644 --- a/lib/gitlab/hook_data/issue_builder.rb +++ b/lib/gitlab/hook_data/issue_builder.rb @@ -7,7 +7,6 @@ module Gitlab closed_at confidential created_at - deleted_at description due_date id diff --git a/lib/gitlab/hook_data/merge_request_builder.rb b/lib/gitlab/hook_data/merge_request_builder.rb index ae9b68eb648..aff786864f2 100644 --- a/lib/gitlab/hook_data/merge_request_builder.rb +++ b/lib/gitlab/hook_data/merge_request_builder.rb @@ -5,7 +5,6 @@ module Gitlab assignee_id author_id created_at - deleted_at description head_pipeline_id id diff --git a/spec/fixtures/api/schemas/entities/issue.json b/spec/fixtures/api/schemas/entities/issue.json index 3d3329a3406..38467b4ca20 100644 --- a/spec/fixtures/api/schemas/entities/issue.json +++ b/spec/fixtures/api/schemas/entities/issue.json @@ -28,7 +28,6 @@ "confidential": { "type": "boolean" }, "discussion_locked": { "type": ["boolean", "null"] }, "updated_by_id": { "type": ["string", "null"] }, - "deleted_at": { "type": ["string", "null"] }, "time_estimate": { "type": "integer" }, "total_time_spent": { "type": "integer" }, "human_time_estimate": { "type": ["integer", "null"] }, diff --git a/spec/fixtures/api/schemas/entities/merge_request_widget.json b/spec/fixtures/api/schemas/entities/merge_request_widget.json index 7f662098216..05461787f06 100644 --- a/spec/fixtures/api/schemas/entities/merge_request_widget.json +++ b/spec/fixtures/api/schemas/entities/merge_request_widget.json @@ -13,7 +13,6 @@ "updated_by_id": { "type": ["string", "null"] }, "created_at": { "type": "string" }, "updated_at": { "type": "string" }, - "deleted_at": { "type": ["string", "null"] }, "time_estimate": { "type": "integer" }, "total_time_spent": { "type": "integer" }, "human_time_estimate": { "type": ["integer", "null"] }, diff --git a/spec/javascripts/notes/mock_data.js b/spec/javascripts/notes/mock_data.js index 6b608adff15..b020a1020df 100644 --- a/spec/javascripts/notes/mock_data.js +++ b/spec/javascripts/notes/mock_data.js @@ -29,7 +29,6 @@ export const noteableDataMock = { can_create_note: true, can_update: true, }, - deleted_at: null, description: '', due_date: null, human_time_estimate: null, @@ -283,7 +282,6 @@ export const loggedOutnoteableData = { "updated_by_id": 1, "created_at": "2017-02-07T10:11:18.395Z", "updated_at": "2017-08-08T10:22:51.564Z", - "deleted_at": null, "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, diff --git a/spec/javascripts/sidebar/mock_data.js b/spec/javascripts/sidebar/mock_data.js index 3b094d20838..7bc591d2d47 100644 --- a/spec/javascripts/sidebar/mock_data.js +++ b/spec/javascripts/sidebar/mock_data.js @@ -15,7 +15,6 @@ const RESPONSE_MAP = { updated_by_id: 1, created_at: '2017-02-02T21: 49: 49.664Z', updated_at: '2017-05-03T22: 26: 03.760Z', - deleted_at: null, time_estimate: 0, total_time_spent: 0, human_time_estimate: null, @@ -153,7 +152,6 @@ const RESPONSE_MAP = { updated_by_id: 1, created_at: '2017-06-27T19:54:42.437Z', updated_at: '2017-08-18T03:39:49.222Z', - deleted_at: null, time_estimate: 0, total_time_spent: 0, human_time_estimate: null, diff --git a/spec/javascripts/vue_mr_widget/mock_data.js b/spec/javascripts/vue_mr_widget/mock_data.js index ca29c9fee32..ae494267659 100644 --- a/spec/javascripts/vue_mr_widget/mock_data.js +++ b/spec/javascripts/vue_mr_widget/mock_data.js @@ -14,7 +14,6 @@ export default { "updated_by_id": null, "created_at": "2017-04-07T12:27:26.718Z", "updated_at": "2017-04-07T15:39:25.852Z", - "deleted_at": null, "time_estimate": 0, "total_time_spent": 0, "human_time_estimate": null, diff --git a/spec/lib/gitlab/hook_data/issue_builder_spec.rb b/spec/lib/gitlab/hook_data/issue_builder_spec.rb index aeacd577d18..506b2c0be20 100644 --- a/spec/lib/gitlab/hook_data/issue_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/issue_builder_spec.rb @@ -14,7 +14,6 @@ describe Gitlab::HookData::IssueBuilder do closed_at confidential created_at - deleted_at description due_date id diff --git a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb index 78475403f9e..b61614e4790 100644 --- a/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb +++ b/spec/lib/gitlab/hook_data/merge_request_builder_spec.rb @@ -12,7 +12,6 @@ describe Gitlab::HookData::MergeRequestBuilder do assignee_id author_id created_at - deleted_at description head_pipeline_id id diff --git a/spec/lib/gitlab/import_export/project.group.json b/spec/lib/gitlab/import_export/project.group.json index 82a1fbd2fc5..1a561e81e4a 100644 --- a/spec/lib/gitlab/import_export/project.group.json +++ b/spec/lib/gitlab/import_export/project.group.json @@ -54,7 +54,6 @@ "iid": 1, "updated_by_id": 1, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "lock_version": null, @@ -134,7 +133,6 @@ "iid": 2, "updated_by_id": 1, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "lock_version": null, diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 7580b62cfc0..4cf33778d15 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -56,7 +56,6 @@ "iid": 10, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "test_ee_field": "test", @@ -350,7 +349,6 @@ "iid": 9, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "milestone": { @@ -586,7 +584,6 @@ "iid": 8, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "label_links": [ @@ -820,7 +817,6 @@ "iid": 7, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -1033,7 +1029,6 @@ "iid": 6, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -1246,7 +1241,6 @@ "iid": 5, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -1459,7 +1453,6 @@ "iid": 4, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -1672,7 +1665,6 @@ "iid": 3, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -1885,7 +1877,6 @@ "iid": 2, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -2098,7 +2089,6 @@ "iid": 1, "updated_by_id": null, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "notes": [ @@ -2504,7 +2494,6 @@ "merge_when_pipeline_succeeds": true, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 671, @@ -2948,7 +2937,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 679, @@ -3228,7 +3216,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 777, @@ -3508,7 +3495,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 785, @@ -4198,7 +4184,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 793, @@ -4734,7 +4719,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 801, @@ -5223,7 +5207,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 809, @@ -5478,7 +5461,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 817, @@ -6168,7 +6150,6 @@ "merge_when_pipeline_succeeds": false, "merge_user_id": null, "merge_commit_sha": null, - "deleted_at": null, "notes": [ { "id": 825, @@ -6968,7 +6949,6 @@ "id": 123, "token": "cdbfasdf44a5958c83654733449e585", "project_id": 5, - "deleted_at": null, "created_at": "2017-01-16T15:25:28.637Z", "updated_at": "2017-01-16T15:25:28.637Z" } diff --git a/spec/lib/gitlab/import_export/project.light.json b/spec/lib/gitlab/import_export/project.light.json index 02450478a77..5dbf0ed289b 100644 --- a/spec/lib/gitlab/import_export/project.light.json +++ b/spec/lib/gitlab/import_export/project.light.json @@ -54,7 +54,6 @@ "iid": 20, "updated_by_id": 1, "confidential": false, - "deleted_at": null, "due_date": null, "moved_to_id": null, "lock_version": null, diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index ec577903eb5..c940fade6bd 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -14,7 +14,6 @@ Issue: - iid - updated_by_id - confidential -- deleted_at - closed_at - due_date - moved_to_id @@ -159,7 +158,6 @@ MergeRequest: - merge_when_pipeline_succeeds - merge_user_id - merge_commit_sha -- deleted_at - in_progress_merge_commit_sha - lock_version - milestone_id @@ -293,7 +291,6 @@ Ci::Trigger: - id - token - project_id -- deleted_at - created_at - updated_at - owner_id @@ -309,7 +306,6 @@ Ci::PipelineSchedule: - project_id - owner_id - active -- deleted_at - created_at - updated_at Clusters::Cluster: diff --git a/spec/models/ci/pipeline_schedule_spec.rb b/spec/models/ci/pipeline_schedule_spec.rb index 9a278212efc..8ee15f0e734 100644 --- a/spec/models/ci/pipeline_schedule_spec.rb +++ b/spec/models/ci/pipeline_schedule_spec.rb @@ -12,7 +12,6 @@ describe Ci::PipelineSchedule do it { is_expected.to respond_to(:cron_timezone) } it { is_expected.to respond_to(:description) } it { is_expected.to respond_to(:next_run_at) } - it { is_expected.to respond_to(:deleted_at) } describe 'validations' do it 'does not allow invalid cron patters' do diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 5ced000cdb6..f5c9f551e65 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -18,11 +18,6 @@ describe Issue do subject { create(:issue) } - describe "act_as_paranoid" do - it { is_expected.to have_db_column(:deleted_at) } - it { is_expected.to have_db_index(:deleted_at) } - end - describe 'callbacks' do describe '#ensure_metrics' do it 'creates metrics after saving' do diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index 07b3e1c1758..27e9c477d61 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -24,11 +24,6 @@ describe MergeRequest do it { is_expected.to include_module(Taskable) } end - describe "act_as_paranoid" do - it { is_expected.to have_db_column(:deleted_at) } - it { is_expected.to have_db_index(:deleted_at) } - end - describe 'validation' do it { is_expected.to validate_presence_of(:target_branch) } it { is_expected.to validate_presence_of(:source_branch) } diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index b3f160f3119..c3673a0e2a3 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -410,17 +410,6 @@ describe Namespace do end end - describe '#soft_delete_without_removing_associations' do - let(:project1) { create(:project_empty_repo, namespace: namespace) } - - it 'updates the deleted_at timestamp but preserves projects' do - namespace.soft_delete_without_removing_associations - - expect(Project.all).to include(project1) - expect(namespace.deleted_at).not_to be_nil - end - end - describe '#user_ids_for_project_authorizations' do it 'returns the user IDs for which to refresh authorizations' do expect(namespace.user_ids_for_project_authorizations) diff --git a/spec/services/users/destroy_service_spec.rb b/spec/services/users/destroy_service_spec.rb index aeba9cd60bc..bb3d73edf8e 100644 --- a/spec/services/users/destroy_service_spec.rb +++ b/spec/services/users/destroy_service_spec.rb @@ -15,7 +15,7 @@ describe Users::DestroyService do expect { user_data['email'].to eq(user.email) } expect { User.find(user.id) }.to raise_error(ActiveRecord::RecordNotFound) - expect { Namespace.with_deleted.find(namespace.id) }.to raise_error(ActiveRecord::RecordNotFound) + expect { Namespace.find(namespace.id) }.to raise_error(ActiveRecord::RecordNotFound) end it 'will delete the project' do -- cgit v1.2.1 From be8b9021f2578ced36295557a4476119270463fc Mon Sep 17 00:00:00 2001 From: Mattia Rizzolo Date: Sun, 7 Jan 2018 14:58:34 +0000 Subject: Update irker documentation to mention an irker bug with key-protected channels. Signed-off-by: Mattia Rizzolo --- doc/user/project/integrations/irker.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/doc/user/project/integrations/irker.md b/doc/user/project/integrations/irker.md index c63ea1316fe..ecdd83ce8f0 100644 --- a/doc/user/project/integrations/irker.md +++ b/doc/user/project/integrations/irker.md @@ -47,4 +47,8 @@ Irker accepts channel names of the form `chan` and `#chan`, both for the case, `Aorimn` is treated as a nick and no more as a channel name. Irker can also join password-protected channels. Users need to append -`?key=thesecretpassword` to the chan name. +`?key=thesecretpassword` to the chan name. When using this feature remember to +**not** put the `#` sign in front of the channel name; failing to do so will +result on irker joining a channel literally named `#chan?key=password` henceforth +leaking the channel key through the `/whois` IRC command (depending on IRC server +configuration). This is due to a long standing irker bug. -- cgit v1.2.1 From 97311688b5d1b33b1fa0b3958d88af19bc8089b8 Mon Sep 17 00:00:00 2001 From: Onuwa Nnachi Isaac Date: Mon, 8 Jan 2018 18:11:25 +0100 Subject: Fix: remove unnecessary line --- doc/development/architecture.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/doc/development/architecture.md b/doc/development/architecture.md index 54029e00507..d1ba7d3dfc3 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -133,8 +133,6 @@ Usage: /etc/init.d/postgresql {start|stop|restart|reload|force-reload|status} [v ### Log locations of the services -Note: `/home/git/` is shorthand for `/home/git`. - gitlabhq (includes Unicorn and Sidekiq logs) - `/home/git/gitlab/log/` contains `application.log`, `production.log`, `sidekiq.log`, `unicorn.stdout.log`, `githost.log` and `unicorn.stderr.log` normally. -- cgit v1.2.1 From 259d452dfdad2af2592527a94eda0ef2152d84c3 Mon Sep 17 00:00:00 2001 From: Oswaldo Ferreira Date: Mon, 8 Jan 2018 16:53:58 -0200 Subject: Remove unnecessary queries on Merge Request Metrics population scheduler --- ...ulate_merge_request_metrics_with_events_data.rb | 32 +--------------------- 1 file changed, 1 insertion(+), 31 deletions(-) diff --git a/db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb b/db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb index 547cc68e10e..fce1829c982 100644 --- a/db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb +++ b/db/post_migrate/20171128214150_schedule_populate_merge_request_metrics_with_events_data.rb @@ -15,8 +15,6 @@ class SchedulePopulateMergeRequestMetricsWithEventsData < ActiveRecord::Migratio end def up - merge_requests = MergeRequest.where("id IN (#{updatable_merge_requests_union_sql})").reorder(:id) - say 'Scheduling `PopulateMergeRequestMetricsWithEventsData` jobs' # It will update around 4_000_000 records in batches of 10_000 merge # requests (running between 10 minutes) and should take around 66 hours to complete. @@ -25,7 +23,7 @@ class SchedulePopulateMergeRequestMetricsWithEventsData < ActiveRecord::Migratio # # More information about the updates in `PopulateMergeRequestMetricsWithEventsData` class. # - merge_requests.each_batch(of: BATCH_SIZE) do |relation, index| + MergeRequest.all.each_batch(of: BATCH_SIZE) do |relation, index| range = relation.pluck('MIN(id)', 'MAX(id)').first BackgroundMigrationWorker.perform_in(index * 10.minutes, MIGRATION, range) @@ -37,32 +35,4 @@ class SchedulePopulateMergeRequestMetricsWithEventsData < ActiveRecord::Migratio execute "update merge_request_metrics set latest_closed_by_id = null" execute "update merge_request_metrics set merged_by_id = null" end - - private - - # On staging: - # Planning time: 0.682 ms - # Execution time: 22033.158 ms - # - def updatable_merge_requests_union_sql - metrics_not_exists_clause = - 'NOT EXISTS (SELECT 1 FROM merge_request_metrics WHERE merge_request_metrics.merge_request_id = merge_requests.id)' - - without_metrics_data = <<-SQL.strip_heredoc - merge_request_metrics.merged_by_id IS NULL OR - merge_request_metrics.latest_closed_by_id IS NULL OR - merge_request_metrics.latest_closed_at IS NULL - SQL - - mrs_without_metrics_record = MergeRequest - .where(metrics_not_exists_clause) - .select(:id) - - mrs_without_events_data = MergeRequest - .joins('INNER JOIN merge_request_metrics ON merge_requests.id = merge_request_metrics.merge_request_id') - .where(without_metrics_data) - .select(:id) - - Gitlab::SQL::Union.new([mrs_without_metrics_record, mrs_without_events_data]).to_sql - end end -- cgit v1.2.1 From 5abc1682153b0e45cc60a4762baf584228dd1f05 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 5 Jan 2018 14:39:52 -0700 Subject: Change pipeline charts colors to match legend --- app/assets/javascripts/pipelines/pipelines_charts.js | 12 ++++++------ app/assets/stylesheets/pages/pipelines.scss | 8 ++++++++ app/views/projects/pipelines/charts/_pipelines.haml | 4 ++-- 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/app/assets/javascripts/pipelines/pipelines_charts.js b/app/assets/javascripts/pipelines/pipelines_charts.js index 001faf4be33..821aa7e229f 100644 --- a/app/assets/javascripts/pipelines/pipelines_charts.js +++ b/app/assets/javascripts/pipelines/pipelines_charts.js @@ -6,16 +6,16 @@ document.addEventListener('DOMContentLoaded', () => { const data = { labels: chartScope.labels, datasets: [{ - fillColor: '#7f8fa4', - strokeColor: '#7f8fa4', - pointColor: '#7f8fa4', + fillColor: '#707070', + strokeColor: '#707070', + pointColor: '#707070', pointStrokeColor: '#EEE', data: chartScope.totalValues, }, { - fillColor: '#44aa22', - strokeColor: '#44aa22', - pointColor: '#44aa22', + fillColor: '#1aaa55', + strokeColor: '#1aaa55', + pointColor: '#1aaa55', pointStrokeColor: '#fff', data: chartScope.successValues, }, diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 05c1033c5f7..dffde736e24 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -993,3 +993,11 @@ button.mini-pipeline-graph-dropdown-toggle { font-weight: $gl-font-weight-normal; line-height: 1.5; } + +.legend-all { + color: $gl-text-color-secondary; +} + +.legend-success { + color: $green-500; +} diff --git a/app/views/projects/pipelines/charts/_pipelines.haml b/app/views/projects/pipelines/charts/_pipelines.haml index 7a100843f5e..41dc2f6cf9d 100644 --- a/app/views/projects/pipelines/charts/_pipelines.haml +++ b/app/views/projects/pipelines/charts/_pipelines.haml @@ -4,11 +4,11 @@ %h4= _("Pipelines charts") %p   - %span.cgreen + %span.legend-success = icon("circle") = s_("Pipeline|success")   - %span.cgray + %span.legend-all = icon("circle") = s_("Pipeline|all") -- cgit v1.2.1 From 349d06688fa956732390e15cefc9006a1dd1bf8c Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Mon, 8 Jan 2018 20:01:49 +0000 Subject: Fix more eslint rules --- .../clusters/components/application_row.vue | 9 +- .../cycle_analytics/components/banner.vue | 3 +- .../components/total_time_component.vue | 2 +- .../environments/components/empty_state.vue | 3 +- .../environments/components/environments_table.vue | 22 ++- .../javascripts/groups/components/group_item.vue | 12 +- app/assets/javascripts/ide/components/ide.vue | 106 ++++++------ .../javascripts/ide/components/ide_repo_tree.vue | 49 +++--- .../javascripts/ide/components/ide_side_bar.vue | 124 +++++++------- .../ide/components/repo_commit_section.vue | 3 +- .../javascripts/ide/components/repo_file.vue | 10 +- app/assets/javascripts/jobs/components/header.vue | 2 +- .../javascripts/monitoring/components/graph.vue | 124 +++++++------- .../monitoring/components/graph/deployment.vue | 22 +-- .../monitoring/components/graph/flag.vue | 45 +++--- .../javascripts/notes/components/comment_form.vue | 6 +- .../javascripts/notes/components/note_form.vue | 3 +- .../components/pipeline_schedules_callout.vue | 4 +- .../pipelines/components/empty_state.vue | 4 +- .../javascripts/pipelines/components/pipelines.vue | 5 +- .../permissions/components/settings_panel.vue | 3 + .../components/projects_list_item.vue | 4 +- app/assets/javascripts/registry/components/app.vue | 3 +- .../components/states/mr_widget_rebase.vue | 4 +- .../vue_shared/components/header_ci_component.vue | 178 +++++++++++---------- .../vue_shared/components/issue/issue_warning.vue | 3 +- .../vue_shared/components/loading_icon.vue | 2 +- 27 files changed, 402 insertions(+), 353 deletions(-) diff --git a/app/assets/javascripts/clusters/components/application_row.vue b/app/assets/javascripts/clusters/components/application_row.vue index 4deedb29ecd..32d6813e74b 100644 --- a/app/assets/javascripts/clusters/components/application_row.vue +++ b/app/assets/javascripts/clusters/components/application_row.vue @@ -71,7 +71,8 @@ // Avoid the potential for the real-time data to say APPLICATION_INSTALLABLE but // we already made a request to install and are just waiting for the real-time // to sync up. - return (this.status !== APPLICATION_INSTALLABLE && this.status !== APPLICATION_ERROR) || + return (this.status !== APPLICATION_INSTALLABLE + && this.status !== APPLICATION_ERROR) || this.requestStatus === REQUEST_LOADING || this.requestStatus === REQUEST_SUCCESS; }, @@ -83,7 +84,8 @@ this.status === APPLICATION_ERROR ) { label = s__('ClusterIntegration|Install'); - } else if (this.status === APPLICATION_SCHEDULED || this.status === APPLICATION_INSTALLING) { + } else if (this.status === APPLICATION_SCHEDULED || + this.status === APPLICATION_INSTALLING) { label = s__('ClusterIntegration|Installing'); } else if (this.status === APPLICATION_INSTALLED) { label = s__('ClusterIntegration|Installed'); @@ -92,7 +94,8 @@ return label; }, hasError() { - return this.status === APPLICATION_ERROR || this.requestStatus === REQUEST_FAILURE; + return this.status === APPLICATION_ERROR || + this.requestStatus === REQUEST_FAILURE; }, generalErrorDescription() { return sprintf( diff --git a/app/assets/javascripts/cycle_analytics/components/banner.vue b/app/assets/javascripts/cycle_analytics/components/banner.vue index 049ecab5365..3204b8dd8e7 100644 --- a/app/assets/javascripts/cycle_analytics/components/banner.vue +++ b/app/assets/javascripts/cycle_analytics/components/banner.vue @@ -43,7 +43,8 @@ {{ __('Introducing Cycle Analytics') }}

- {{ __('Cycle Analytics gives an overview of how much time it takes to go from idea to production in your project.') }} + {{ __(`Cycle Analytics gives an overview +of how much time it takes to go from idea to production in your project.`) }}

-- - + diff --git a/app/assets/javascripts/environments/components/empty_state.vue b/app/assets/javascripts/environments/components/empty_state.vue index c7ae1fe8cf0..00e63c3467a 100644 --- a/app/assets/javascripts/environments/components/empty_state.vue +++ b/app/assets/javascripts/environments/components/empty_state.vue @@ -24,7 +24,8 @@ {{ s__("Environments|You don't have any environments right now.") }}

- {{ s__("Environments|Environments are places where code gets deployed, such as staging or production.") }} + {{ s__(`Environments|Environments are places where +code gets deployed, such as staging or production.`) }}
{{ s__("Environments|Read more about environments") }} diff --git a/app/assets/javascripts/environments/components/environments_table.vue b/app/assets/javascripts/environments/components/environments_table.vue index 4c4ef31b8d1..858acf293a1 100644 --- a/app/assets/javascripts/environments/components/environments_table.vue +++ b/app/assets/javascripts/environments/components/environments_table.vue @@ -30,20 +30,16 @@ export default { default: false, }, }, - - computed: { - shouldRenderFolderContent() { - return this.model.isFolder && - this.model.isOpen && - this.model.children && - this.model.children.length > 0; - }, - }, - methods: { folderUrl(model) { return `${window.location.pathname}/folders/${model.folderName}`; }, + shouldRenderFolderContent(env) { + return env.isFolder && + env.isOpen && + env.children && + env.children.length > 0; + }, }, }; @@ -99,7 +95,7 @@ export default { />