From 1e62a13968cc4351684f919630cd426e20fc022a Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 28 Nov 2016 16:55:31 +0100 Subject: [PATCH 01/75] Improve pipeline fixtures --- db/fixtures/development/14_pipelines.rb | 63 +++++++++++++++++-------- 1 file changed, 44 insertions(+), 19 deletions(-) diff --git a/db/fixtures/development/14_pipelines.rb b/db/fixtures/development/14_pipelines.rb index 08ad3097d34..a019660e5f2 100644 --- a/db/fixtures/development/14_pipelines.rb +++ b/db/fixtures/development/14_pipelines.rb @@ -1,26 +1,50 @@ class Gitlab::Seeder::Pipelines STAGES = %w[build test deploy notify] BUILDS = [ - { name: 'build:linux', stage: 'build', status: :success }, - { name: 'build:osx', stage: 'build', status: :success }, - { name: 'rspec:linux 0 3', stage: 'test', status: :success }, - { name: 'rspec:linux 1 3', stage: 'test', status: :success }, - { name: 'rspec:linux 2 3', stage: 'test', status: :success }, - { name: 'rspec:windows 0 3', stage: 'test', status: :success }, - { name: 'rspec:windows 1 3', stage: 'test', status: :success }, - { name: 'rspec:windows 2 3', stage: 'test', status: :success }, - { name: 'rspec:windows 2 3', stage: 'test', status: :success }, - { name: 'rspec:osx', stage: 'test', status_event: :success }, - { name: 'spinach:linux', stage: 'test', status: :success }, - { name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true}, - { name: 'env:alpha', stage: 'deploy', environment: 'alpha', status: :pending }, - { name: 'env:beta', stage: 'deploy', environment: 'beta', status: :running }, - { name: 'env:gamma', stage: 'deploy', environment: 'gamma', status: :canceled }, - { name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success, options: { environment: { on_stop: 'stop staging' } } }, - { name: 'stop staging', stage: 'deploy', environment: 'staging', when: 'manual', status: :skipped }, - { name: 'production', stage: 'deploy', environment: 'production', when: 'manual', status: :skipped }, + # build stage + { name: 'build:linux', stage: 'build', status: :success, + queued_at: 10.hour.ago, started_at: 9.hour.ago, finished_at: 8.hour.ago }, + { name: 'build:osx', stage: 'build', status: :success, + queued_at: 10.hour.ago, started_at: 10.hour.ago, finished_at: 9.hour.ago }, + + # test stage + { name: 'rspec:linux 0 3', stage: 'test', status: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'rspec:linux 1 3', stage: 'test', status: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'rspec:linux 2 3', stage: 'test', status: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'rspec:windows 0 3', stage: 'test', status: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'rspec:windows 1 3', stage: 'test', status: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'rspec:windows 2 3', stage: 'test', status: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'rspec:windows 2 3', stage: 'test', status: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'rspec:osx', stage: 'test', status_event: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'spinach:linux', stage: 'test', status: :success, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + { name: 'spinach:osx', stage: 'test', status: :failed, allow_failure: true, + queued_at: 8.hour.ago, started_at: 8.hour.ago, finished_at: 7.hour.ago }, + + # deploy stage + { name: 'staging', stage: 'deploy', environment: 'staging', status_event: :success, + options: { environment: { action: 'start', on_stop: 'stop staging' } }, + queued_at: 7.hour.ago, started_at: 6.hour.ago, finished_at: 4.hour.ago }, + { name: 'stop staging', stage: 'deploy', environment: 'staging', + when: 'manual', status: :skipped }, + { name: 'production', stage: 'deploy', environment: 'production', + when: 'manual', status: :skipped }, + + # notify stage { name: 'slack', stage: 'notify', when: 'manual', status: :created }, ] + EXTERNAL_JOBS = [ + { name: 'jenkins', stage: 'test', status: :success, + queued_at: 7.hour.ago, started_at: 6.hour.ago, finished_at: 4.hour.ago }, + ] def initialize(project) @project = project @@ -30,11 +54,12 @@ class Gitlab::Seeder::Pipelines pipelines.each do |pipeline| begin BUILDS.each { |opts| build_create!(pipeline, opts) } - commit_status_create!(pipeline, name: 'jenkins', stage: 'test', status: :success) + EXTERNAL_JOBS.each { |opts| commit_status_create!(pipeline, opts) } print '.' rescue ActiveRecord::RecordInvalid print 'F' ensure + pipeline.update_duration pipeline.update_status end end From e485b3f6ad3c220655e4aa909d93bca7a4ae6afc Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 15 Dec 2016 00:28:55 +0800 Subject: [PATCH 02/75] Give forbidden if project for the build was deleted Closes #25309 --- lib/ci/api/builds.rb | 19 ++++++------------- lib/ci/api/helpers.rb | 7 +++++-- spec/requests/ci/api/builds_spec.rb | 19 +++++++++++++++++++ 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index ed87a2603e8..3c4cfccb19a 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -41,7 +41,7 @@ module Ci put ":id" do authenticate_runner! build = Ci::Build.where(runner_id: current_runner.id).running.find(params[:id]) - forbidden!('Build has been erased!') if build.erased? + authenticate_build!(build, verify_token: false) update_runner_info @@ -71,9 +71,7 @@ module Ci # PATCH /builds/:id/trace.txt patch ":id/trace.txt" do build = Ci::Build.find_by_id(params[:id]) - not_found! unless build - authenticate_build_token!(build) - forbidden!('Build has been erased!') if build.erased? + authenticate_build!(build) error!('400 Missing header Content-Range', 400) unless request.headers.has_key?('Content-Range') content_range = request.headers['Content-Range'] @@ -104,8 +102,7 @@ module Ci Gitlab::Workhorse.verify_api_request!(headers) not_allowed! unless Gitlab.config.artifacts.enabled build = Ci::Build.find_by_id(params[:id]) - not_found! unless build - authenticate_build_token!(build) + authenticate_build!(build) forbidden!('build is not running') unless build.running? if params[:filesize] @@ -142,10 +139,8 @@ module Ci require_gitlab_workhorse! not_allowed! unless Gitlab.config.artifacts.enabled build = Ci::Build.find_by_id(params[:id]) - not_found! unless build - authenticate_build_token!(build) + authenticate_build!(build) forbidden!('Build is not running!') unless build.running? - forbidden!('Build has been erased!') if build.erased? artifacts_upload_path = ArtifactUploader.artifacts_upload_path artifacts = uploaded_file(:file, artifacts_upload_path) @@ -176,8 +171,7 @@ module Ci # GET /builds/:id/artifacts get ":id/artifacts" do build = Ci::Build.find_by_id(params[:id]) - not_found! unless build - authenticate_build_token!(build) + authenticate_build!(build) artifacts_file = build.artifacts_file unless artifacts_file.file_storage? @@ -202,8 +196,7 @@ module Ci # DELETE /builds/:id/artifacts delete ":id/artifacts" do build = Ci::Build.find_by_id(params[:id]) - not_found! unless build - authenticate_build_token!(build) + authenticate_build!(build) build.erase_artifacts! end diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb index e608f5f6cad..0202b3cf8a3 100644 --- a/lib/ci/api/helpers.rb +++ b/lib/ci/api/helpers.rb @@ -13,8 +13,11 @@ module Ci forbidden! unless current_runner end - def authenticate_build_token!(build) - forbidden! unless build_token_valid?(build) + def authenticate_build!(build, verify_token: true) + not_found! unless build + forbidden! if verify_token && !build_token_valid?(build) + forbidden!('Project has been deleted!') unless build.project + forbidden!('Build has been erased!') if build.erased? end def runner_registration_token_valid? diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 80652129928..d61a9afd12e 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -329,6 +329,25 @@ describe Ci::API::Builds do end end end + + context 'when project for the build has been deleted' do + let(:build) do + create(:ci_build, + :pending, + :trace, + runner_id: runner.id, + pipeline: pipeline) + end + + it 'responds with forbidden' do + expect(response.status).to eq 403 + end + + def initial_patch_the_trace + build.project.update(pending_delete: true) + super + end + end end context 'when Runner makes a force-patch' do From 2044f43dc2564f99178b674ede31159a94b35805 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Wed, 14 Dec 2016 17:00:57 -0300 Subject: [PATCH 03/75] Update CHANGELOG.md for 8.14.5 [ci skip] --- CHANGELOG.md | 15 +++++++++++++++ ...-project-and-leave-group-should-be-buttons.yml | 5 ----- ...s-settings-hidden-when-builds-are-disabled.yml | 4 ---- changelogs/unreleased/fix-milestone-summary.yml | 4 ---- .../group-members-in-project-members-view.yml | 4 ---- changelogs/unreleased/issue_24020.yml | 4 ---- changelogs/unreleased/issue_25030.yml | 4 ---- changelogs/unreleased/timeago-perf-fix.yml | 4 ---- changelogs/unreleased/unescape-relative-path.yml | 4 ---- 9 files changed, 15 insertions(+), 33 deletions(-) delete mode 100644 changelogs/unreleased/23305-leave-project-and-leave-group-should-be-buttons.yml delete mode 100644 changelogs/unreleased/25171-fix-mr-features-settings-hidden-when-builds-are-disabled.yml delete mode 100644 changelogs/unreleased/fix-milestone-summary.yml delete mode 100644 changelogs/unreleased/group-members-in-project-members-view.yml delete mode 100644 changelogs/unreleased/issue_24020.yml delete mode 100644 changelogs/unreleased/issue_25030.yml delete mode 100644 changelogs/unreleased/timeago-perf-fix.yml delete mode 100644 changelogs/unreleased/unescape-relative-path.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index fb13db4dd1c..f1d2f09e34f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 8.14.5 (2016-12-14) + +- Moved Leave Project and Leave Group buttons to access_request_buttons from the settings dropdown. !7600 +- fix display hook error message. !7775 (basyura) +- Remove wrong '.builds-feature' class from the MR settings fieldset. !7930 +- Avoid escaping relative links in Markdown twice. !7940 (winniehell) +- API: Memoize the current_user so that sudo can work properly. !8017 +- Displays milestone remaining days only when it's present. +- Allow branch names with dots on API endpoint. +- Issue#visible_to_user moved to IssuesFinder to prevent accidental use. +- Shows group members in project members list. +- Encode input when migrating ProcessCommitWorker jobs to prevent migration errors. +- Fixed timeago re-rendering every timeago. +- Fix missing Note access checks by moving Note#search to updated NoteFinder. + ## 8.14.4 (2016-12-08) - Fix diff view permalink highlighting. !7090 diff --git a/changelogs/unreleased/23305-leave-project-and-leave-group-should-be-buttons.yml b/changelogs/unreleased/23305-leave-project-and-leave-group-should-be-buttons.yml deleted file mode 100644 index 99dbe4a32a0..00000000000 --- a/changelogs/unreleased/23305-leave-project-and-leave-group-should-be-buttons.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Moved Leave Project and Leave Group buttons to access_request_buttons from - the settings dropdown -merge_request: 7600 -author: diff --git a/changelogs/unreleased/25171-fix-mr-features-settings-hidden-when-builds-are-disabled.yml b/changelogs/unreleased/25171-fix-mr-features-settings-hidden-when-builds-are-disabled.yml deleted file mode 100644 index a7576e2cbdb..00000000000 --- a/changelogs/unreleased/25171-fix-mr-features-settings-hidden-when-builds-are-disabled.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove wrong '.builds-feature' class from the MR settings fieldset -merge_request: 7930 -author: diff --git a/changelogs/unreleased/fix-milestone-summary.yml b/changelogs/unreleased/fix-milestone-summary.yml deleted file mode 100644 index 3045a15054c..00000000000 --- a/changelogs/unreleased/fix-milestone-summary.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Displays milestone remaining days only when it's present -merge_request: -author: diff --git a/changelogs/unreleased/group-members-in-project-members-view.yml b/changelogs/unreleased/group-members-in-project-members-view.yml deleted file mode 100644 index 415e2b6b1e2..00000000000 --- a/changelogs/unreleased/group-members-in-project-members-view.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Shows group members in project members list -merge_request: -author: diff --git a/changelogs/unreleased/issue_24020.yml b/changelogs/unreleased/issue_24020.yml deleted file mode 100644 index 87310b75296..00000000000 --- a/changelogs/unreleased/issue_24020.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: "fix display hook error message" -merge_request: 7775 -author: basyura diff --git a/changelogs/unreleased/issue_25030.yml b/changelogs/unreleased/issue_25030.yml deleted file mode 100644 index e18b8d6a79b..00000000000 --- a/changelogs/unreleased/issue_25030.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Allow branch names with dots on API endpoint -merge_request: -author: diff --git a/changelogs/unreleased/timeago-perf-fix.yml b/changelogs/unreleased/timeago-perf-fix.yml deleted file mode 100644 index 265e7db29a9..00000000000 --- a/changelogs/unreleased/timeago-perf-fix.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixed timeago re-rendering every timeago -merge_request: -author: diff --git a/changelogs/unreleased/unescape-relative-path.yml b/changelogs/unreleased/unescape-relative-path.yml deleted file mode 100644 index 755b0379a16..00000000000 --- a/changelogs/unreleased/unescape-relative-path.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Avoid escaping relative links in Markdown twice -merge_request: 7940 -author: winniehell From 1df85aed4baf85531d6a1e47a518ef3f04c14908 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Wed, 14 Dec 2016 17:04:12 -0300 Subject: [PATCH 04/75] Update CHANGELOG.md for 8.13.10 [ci skip] --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1d2f09e34f..786b0128aac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -279,6 +279,13 @@ entry. - Fix "Without projects" filter. !6611 (Ben Bodenmiller) - Fix 404 when visit /projects page +## 8.13.10 (2016-12-14) + +- API: Memoize the current_user so that sudo can work properly. !8017 +- Filter `authentication_token`, `incoming_email_token` and `runners_token` parameters. +- Issue#visible_to_user moved to IssuesFinder to prevent accidental use. +- Fix missing Note access checks by moving Note#search to updated NoteFinder. + ## 8.13.9 (2016-12-08) - Reenables /user API request to return private-token if user is admin and request is made with sudo. !7615 From 05bc71c856c735fa364e15db62ba5d40cfd7c5ef Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Wed, 14 Dec 2016 22:38:40 +0800 Subject: [PATCH 05/75] Convert CI YAML variables keys into strings So that this would be more consistent with the other variables, which all of them are string based. Closes #25554 --- app/models/ci/build.rb | 2 +- changelogs/unreleased/fix-yaml-variables.yml | 4 +++ lib/ci/gitlab_ci_yaml_processor.rb | 2 +- lib/gitlab/serialize/yaml_variables.rb | 30 +++++++++++++++++++ spec/factories/ci/builds.rb | 2 +- spec/lib/ci/gitlab_ci_yaml_processor_spec.rb | 24 +++++++-------- .../gitlab/serialize/yaml_variables_spec.rb | 19 ++++++++++++ spec/models/build_spec.rb | 2 +- 8 files changed, 69 insertions(+), 16 deletions(-) create mode 100644 changelogs/unreleased/fix-yaml-variables.yml create mode 100644 lib/gitlab/serialize/yaml_variables.rb create mode 100644 spec/lib/gitlab/serialize/yaml_variables_spec.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index e7cf606a7ae..99f3f4711aa 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -10,7 +10,7 @@ module Ci has_many :deployments, as: :deployable serialize :options - serialize :yaml_variables + serialize :yaml_variables, Gitlab::Serialize::YamlVariables validates :coverage, numericality: true, allow_blank: true validates_presence_of :ref diff --git a/changelogs/unreleased/fix-yaml-variables.yml b/changelogs/unreleased/fix-yaml-variables.yml new file mode 100644 index 00000000000..3abff1e3b08 --- /dev/null +++ b/changelogs/unreleased/fix-yaml-variables.yml @@ -0,0 +1,4 @@ +--- +title: Convert CI YAML variables keys into strings +merge_request: 8088 +author: diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index fef652cb975..8806a506ffa 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -118,7 +118,7 @@ module Ci .merge(job_variables(name)) variables.map do |key, value| - { key: key, value: value, public: true } + { key: key.to_s, value: value.to_s, public: true } end end diff --git a/lib/gitlab/serialize/yaml_variables.rb b/lib/gitlab/serialize/yaml_variables.rb new file mode 100644 index 00000000000..ca44acbd906 --- /dev/null +++ b/lib/gitlab/serialize/yaml_variables.rb @@ -0,0 +1,30 @@ + +module Gitlab + module Serialize + # This serializer could make sure our YAML variables' keys and values + # are always strings. This is more for legacy build data because + # from now on we convert them into strings before saving to database. + module YamlVariables + extend self + + def load(string) + return unless string + + YAML.load(string). + map(&YamlVariables.method(:convert_key_value_to_string)) + end + + def dump(object) + YAML.dump(object) + end + + private + + def convert_key_value_to_string(variable) + variable[:key] = variable[:key].to_s + variable[:value] = variable[:value].to_s + variable + end + end + end +end diff --git a/spec/factories/ci/builds.rb b/spec/factories/ci/builds.rb index 62466c06194..0397d5d4001 100644 --- a/spec/factories/ci/builds.rb +++ b/spec/factories/ci/builds.rb @@ -22,7 +22,7 @@ FactoryGirl.define do yaml_variables do [ - { key: :DB_NAME, value: 'postgres', public: true } + { key: 'DB_NAME', value: 'postgres', public: true } ] end diff --git a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb index ff5dcc06ab3..62d68721574 100644 --- a/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb +++ b/spec/lib/ci/gitlab_ci_yaml_processor_spec.rb @@ -483,7 +483,7 @@ module Ci context 'when global variables are defined' do let(:variables) do - { VAR1: 'value1', VAR2: 'value2' } + { 'VAR1' => 'value1', 'VAR2' => 'value2' } end let(:config) do { @@ -495,18 +495,18 @@ module Ci it 'returns global variables' do expect(subject).to contain_exactly( - { key: :VAR1, value: 'value1', public: true }, - { key: :VAR2, value: 'value2', public: true } + { key: 'VAR1', value: 'value1', public: true }, + { key: 'VAR2', value: 'value2', public: true } ) end end context 'when job and global variables are defined' do let(:global_variables) do - { VAR1: 'global1', VAR3: 'global3' } + { 'VAR1' => 'global1', 'VAR3' => 'global3' } end let(:job_variables) do - { VAR1: 'value1', VAR2: 'value2' } + { 'VAR1' => 'value1', 'VAR2' => 'value2' } end let(:config) do { @@ -518,9 +518,9 @@ module Ci it 'returns all unique variables' do expect(subject).to contain_exactly( - { key: :VAR3, value: 'global3', public: true }, - { key: :VAR1, value: 'value1', public: true }, - { key: :VAR2, value: 'value2', public: true } + { key: 'VAR3', value: 'global3', public: true }, + { key: 'VAR1', value: 'value1', public: true }, + { key: 'VAR2', value: 'value2', public: true } ) end end @@ -535,13 +535,13 @@ module Ci context 'when syntax is correct' do let(:variables) do - { VAR1: 'value1', VAR2: 'value2' } + { 'VAR1' => 'value1', 'VAR2' => 'value2' } end it 'returns job variables' do expect(subject).to contain_exactly( - { key: :VAR1, value: 'value1', public: true }, - { key: :VAR2, value: 'value2', public: true } + { key: 'VAR1', value: 'value1', public: true }, + { key: 'VAR2', value: 'value2', public: true } ) end end @@ -549,7 +549,7 @@ module Ci context 'when syntax is incorrect' do context 'when variables defined but invalid' do let(:variables) do - [ :VAR1, 'value1', :VAR2, 'value2' ] + [ 'VAR1', 'value1', 'VAR2', 'value2' ] end it 'raises error' do diff --git a/spec/lib/gitlab/serialize/yaml_variables_spec.rb b/spec/lib/gitlab/serialize/yaml_variables_spec.rb new file mode 100644 index 00000000000..6d74f8c44d6 --- /dev/null +++ b/spec/lib/gitlab/serialize/yaml_variables_spec.rb @@ -0,0 +1,19 @@ +require 'spec_helper' + +describe Gitlab::Serialize::YamlVariables do + subject do + Gitlab::Serialize::YamlVariables.load( + Gitlab::Serialize::YamlVariables.dump(object)) + end + + let(:object) do + [{ key: :key, value: 'value', public: true }, + { key: 'wee', value: 1, public: false }] + end + + it 'converts key and values into strings' do + is_expected.to eq([ + { key: 'key', value: 'value', public: true }, + { key: 'wee', value: '1', public: false }]) + end +end diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 7f39aff7639..13928695ccb 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -403,7 +403,7 @@ describe Ci::Build, models: true do }) end let(:variables) do - [{ key: :KEY, value: 'value', public: true }] + [{ key: 'KEY', value: 'value', public: true }] end it { is_expected.to eq(predefined_variables + variables) } From e682e2f888fd84deefc7b4b028d00a55bdd1c3a5 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 15 Dec 2016 22:55:23 +0800 Subject: [PATCH 06/75] Strictly check the type loaded from YAML Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8088#note_20062470 Eventually we should move to SafeYAML, but requiring that would impact all other `YAML.load` which is bad. For this particular case, I think we could just check it strictly. --- lib/gitlab/serialize/yaml_variables.rb | 27 ++++++++++++++++--- .../gitlab/serialize/yaml_variables_spec.rb | 21 +++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/lib/gitlab/serialize/yaml_variables.rb b/lib/gitlab/serialize/yaml_variables.rb index ca44acbd906..68ca50ed60e 100644 --- a/lib/gitlab/serialize/yaml_variables.rb +++ b/lib/gitlab/serialize/yaml_variables.rb @@ -10,15 +10,36 @@ module Gitlab def load(string) return unless string - YAML.load(string). - map(&YamlVariables.method(:convert_key_value_to_string)) + object = YAML.load(string) + + # We don't need to verify the object once we're using SafeYAML + if YamlVariables.verify_object(object) + YamlVariables.convert_object(object) + else + [] + end end def dump(object) YAML.dump(object) end - private + def verify_object(object) + YamlVariables.verify_type(object, Array) && + object.all? { |obj| YamlVariables.verify_type(obj, Hash) } + end + + # We use three ways to check if the class is exactly the one we want, + # rather than some subclass or duck typing class. + def verify_type(object, klass) + object.kind_of?(klass) && + object.class == klass && + klass === object + end + + def convert_object(object) + object.map(&YamlVariables.method(:convert_key_value_to_string)) + end def convert_key_value_to_string(variable) variable[:key] = variable[:key].to_s diff --git a/spec/lib/gitlab/serialize/yaml_variables_spec.rb b/spec/lib/gitlab/serialize/yaml_variables_spec.rb index 6d74f8c44d6..41aea95dfdb 100644 --- a/spec/lib/gitlab/serialize/yaml_variables_spec.rb +++ b/spec/lib/gitlab/serialize/yaml_variables_spec.rb @@ -16,4 +16,25 @@ describe Gitlab::Serialize::YamlVariables do { key: 'key', value: 'value', public: true }, { key: 'wee', value: '1', public: false }]) end + + context 'with a subclass of Array' do + let(:object) do + Kaminari::PaginatableArray.new << 'I am evil' + end + + it 'ignores it' do + is_expected.to eq([]) + end + end + + context 'with the array containing subclasses of Hash' do + let(:object) do + [ActiveSupport::OrderedOptions.new( + key: 'key', value: 'value', public: true)] + end + + it 'ignores it' do + is_expected.to eq([]) + end + end end From bcc09ca76098e8e12b0a42454920e1d4df6434c2 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Thu, 15 Dec 2016 23:37:22 +0800 Subject: [PATCH 07/75] Just use YAML.safe_load and assume the format should be correct since it's already passing the validation anyway. Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8088#note_20076187 --- lib/gitlab/serialize/yaml_variables.rb | 26 +++---------------- .../gitlab/serialize/yaml_variables_spec.rb | 21 --------------- 2 files changed, 3 insertions(+), 44 deletions(-) diff --git a/lib/gitlab/serialize/yaml_variables.rb b/lib/gitlab/serialize/yaml_variables.rb index 68ca50ed60e..db1e7641c74 100644 --- a/lib/gitlab/serialize/yaml_variables.rb +++ b/lib/gitlab/serialize/yaml_variables.rb @@ -10,36 +10,16 @@ module Gitlab def load(string) return unless string - object = YAML.load(string) + object = YAML.safe_load(string, [Symbol]) - # We don't need to verify the object once we're using SafeYAML - if YamlVariables.verify_object(object) - YamlVariables.convert_object(object) - else - [] - end + object.map(&YamlVariables.method(:convert_key_value_to_string)) end def dump(object) YAML.dump(object) end - def verify_object(object) - YamlVariables.verify_type(object, Array) && - object.all? { |obj| YamlVariables.verify_type(obj, Hash) } - end - - # We use three ways to check if the class is exactly the one we want, - # rather than some subclass or duck typing class. - def verify_type(object, klass) - object.kind_of?(klass) && - object.class == klass && - klass === object - end - - def convert_object(object) - object.map(&YamlVariables.method(:convert_key_value_to_string)) - end + private def convert_key_value_to_string(variable) variable[:key] = variable[:key].to_s diff --git a/spec/lib/gitlab/serialize/yaml_variables_spec.rb b/spec/lib/gitlab/serialize/yaml_variables_spec.rb index 41aea95dfdb..6d74f8c44d6 100644 --- a/spec/lib/gitlab/serialize/yaml_variables_spec.rb +++ b/spec/lib/gitlab/serialize/yaml_variables_spec.rb @@ -16,25 +16,4 @@ describe Gitlab::Serialize::YamlVariables do { key: 'key', value: 'value', public: true }, { key: 'wee', value: '1', public: false }]) end - - context 'with a subclass of Array' do - let(:object) do - Kaminari::PaginatableArray.new << 'I am evil' - end - - it 'ignores it' do - is_expected.to eq([]) - end - end - - context 'with the array containing subclasses of Hash' do - let(:object) do - [ActiveSupport::OrderedOptions.new( - key: 'key', value: 'value', public: true)] - end - - it 'ignores it' do - is_expected.to eq([]) - end - end end From 5dcd01aa96f26ff98311d0415d659d0b14e6c632 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Fri, 16 Dec 2016 14:31:07 +0100 Subject: [PATCH 08/75] Bump gitlab-shell to 4.1.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes a compatibility issue with Git 2.11: - Issue: gitlab-org/gitlab-ce#25301 - gitlab-shell MR: gitlab-org/gitlab-shell!112 - CE MR: gitlab-org/gitlab-ce!7967 - EE MR: gitlab-org/gitlab-ee!964 Signed-off-by: Rémy Coutable --- GITLAB_SHELL_VERSION | 2 +- doc/update/8.14-to-8.15.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index ee74734aa22..627a3f43a64 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -4.1.0 +4.1.1 diff --git a/doc/update/8.14-to-8.15.md b/doc/update/8.14-to-8.15.md index 4eacab0c890..8d4bfd913bd 100644 --- a/doc/update/8.14-to-8.15.md +++ b/doc/update/8.14-to-8.15.md @@ -72,7 +72,7 @@ sudo -u git -H git checkout 8-15-stable-ee ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch --all --tags -sudo -u git -H git checkout v4.1.0 +sudo -u git -H git checkout v4.1.1 ``` ### 6. Update gitlab-workhorse From cc1eb7fec5a225342992377495e4969fdf8adf45 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 16 Dec 2016 22:49:33 +0800 Subject: [PATCH 09/75] Use described_class Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8088#note_20133505 --- spec/lib/gitlab/serialize/yaml_variables_spec.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/spec/lib/gitlab/serialize/yaml_variables_spec.rb b/spec/lib/gitlab/serialize/yaml_variables_spec.rb index 6d74f8c44d6..2691c3dc22f 100644 --- a/spec/lib/gitlab/serialize/yaml_variables_spec.rb +++ b/spec/lib/gitlab/serialize/yaml_variables_spec.rb @@ -2,8 +2,7 @@ require 'spec_helper' describe Gitlab::Serialize::YamlVariables do subject do - Gitlab::Serialize::YamlVariables.load( - Gitlab::Serialize::YamlVariables.dump(object)) + described_class.load(described_class.dump(object)) end let(:object) do From 1b4a244dbc8d1e5b79feb4f111ec6183afa1b413 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Fri, 16 Dec 2016 23:04:01 +0800 Subject: [PATCH 10/75] Rename to Gitlab::Serialize::Ci::Variables Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8088#note_20133176 --- app/models/ci/build.rb | 2 +- lib/gitlab/serialize/ci/variables.rb | 32 +++++++++++++++++++ lib/gitlab/serialize/yaml_variables.rb | 31 ------------------ .../variables_spec.rb} | 2 +- 4 files changed, 34 insertions(+), 33 deletions(-) create mode 100644 lib/gitlab/serialize/ci/variables.rb delete mode 100644 lib/gitlab/serialize/yaml_variables.rb rename spec/lib/gitlab/serialize/{yaml_variables_spec.rb => ci/variables_spec.rb} (89%) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 99f3f4711aa..0e30755b870 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -10,7 +10,7 @@ module Ci has_many :deployments, as: :deployable serialize :options - serialize :yaml_variables, Gitlab::Serialize::YamlVariables + serialize :yaml_variables, Gitlab::Serialize::Ci::Variables validates :coverage, numericality: true, allow_blank: true validates_presence_of :ref diff --git a/lib/gitlab/serialize/ci/variables.rb b/lib/gitlab/serialize/ci/variables.rb new file mode 100644 index 00000000000..0ca060cd95e --- /dev/null +++ b/lib/gitlab/serialize/ci/variables.rb @@ -0,0 +1,32 @@ +module Gitlab + module Serialize + module Ci + # This serializer could make sure our YAML variables' keys and values + # are always strings. This is more for legacy build data because + # from now on we convert them into strings before saving to database. + module Variables + extend self + + def load(string) + return unless string + + object = YAML.safe_load(string, [Symbol]) + + object.map(&Variables.method(:convert_key_value_to_string)) + end + + def dump(object) + YAML.dump(object) + end + + private + + def convert_key_value_to_string(variable) + variable[:key] = variable[:key].to_s + variable[:value] = variable[:value].to_s + variable + end + end + end + end +end diff --git a/lib/gitlab/serialize/yaml_variables.rb b/lib/gitlab/serialize/yaml_variables.rb deleted file mode 100644 index db1e7641c74..00000000000 --- a/lib/gitlab/serialize/yaml_variables.rb +++ /dev/null @@ -1,31 +0,0 @@ - -module Gitlab - module Serialize - # This serializer could make sure our YAML variables' keys and values - # are always strings. This is more for legacy build data because - # from now on we convert them into strings before saving to database. - module YamlVariables - extend self - - def load(string) - return unless string - - object = YAML.safe_load(string, [Symbol]) - - object.map(&YamlVariables.method(:convert_key_value_to_string)) - end - - def dump(object) - YAML.dump(object) - end - - private - - def convert_key_value_to_string(variable) - variable[:key] = variable[:key].to_s - variable[:value] = variable[:value].to_s - variable - end - end - end -end diff --git a/spec/lib/gitlab/serialize/yaml_variables_spec.rb b/spec/lib/gitlab/serialize/ci/variables_spec.rb similarity index 89% rename from spec/lib/gitlab/serialize/yaml_variables_spec.rb rename to spec/lib/gitlab/serialize/ci/variables_spec.rb index 2691c3dc22f..797baef640c 100644 --- a/spec/lib/gitlab/serialize/yaml_variables_spec.rb +++ b/spec/lib/gitlab/serialize/ci/variables_spec.rb @@ -1,6 +1,6 @@ require 'spec_helper' -describe Gitlab::Serialize::YamlVariables do +describe Gitlab::Serialize::Ci::Variables do subject do described_class.load(described_class.dump(object)) end From e0eb86ee809aaad86be4ca1d985a5e67c0657a6f Mon Sep 17 00:00:00 2001 From: Drew Blessing Date: Tue, 22 Dec 2015 15:09:35 -0600 Subject: [PATCH 11/75] Add LDAP task to rename a provider Sometimes admins will change the LDAP configuration, not realizing that problems will occur if the user's LDAP identities are not also updated to use the new provider name. This task will give admins a single command to run to update identities and will prevent having to run multiple Rails console queries. --- changelogs/unreleased/ldap_maint_task.yml | 4 + doc/administration/raketasks/check.md | 23 +---- doc/administration/raketasks/ldap.md | 120 ++++++++++++++++++++++ doc/raketasks/README.md | 3 +- lib/tasks/gitlab/ldap.rake | 40 ++++++++ spec/tasks/gitlab/ldap_rake_spec.rb | 13 +++ 6 files changed, 181 insertions(+), 22 deletions(-) create mode 100644 changelogs/unreleased/ldap_maint_task.yml create mode 100644 doc/administration/raketasks/ldap.md create mode 100644 lib/tasks/gitlab/ldap.rake create mode 100644 spec/tasks/gitlab/ldap_rake_spec.rb diff --git a/changelogs/unreleased/ldap_maint_task.yml b/changelogs/unreleased/ldap_maint_task.yml new file mode 100644 index 00000000000..8acffba0ce5 --- /dev/null +++ b/changelogs/unreleased/ldap_maint_task.yml @@ -0,0 +1,4 @@ +--- +title: Add LDAP Rake task to rename a provider +merge_request: 2181 +author: diff --git a/doc/administration/raketasks/check.md b/doc/administration/raketasks/check.md index d1d2fed4861..c8b5434c068 100644 --- a/doc/administration/raketasks/check.md +++ b/doc/administration/raketasks/check.md @@ -74,24 +74,5 @@ Example output: The LDAP check Rake task will test the bind_dn and password credentials (if configured) and will list a sample of LDAP users. This task is also -executed as part of the `gitlab:check` task, but can run independently -using the command below. - -**Omnibus Installation** - -``` -sudo gitlab-rake gitlab:ldap:check -``` - -**Source Installation** - -```bash -sudo -u git -H bundle exec rake gitlab:ldap:check RAILS_ENV=production -``` - -By default, the task will return a sample of 100 LDAP users. Change this -limit by passing a number to the check task: - -```bash -rake gitlab:ldap:check[50] -``` +executed as part of the `gitlab:check` task, but can run independently. +See [LDAP Rake Tasks - LDAP Check](ldap.md#check) for details. diff --git a/doc/administration/raketasks/ldap.md b/doc/administration/raketasks/ldap.md new file mode 100644 index 00000000000..91fc0133d56 --- /dev/null +++ b/doc/administration/raketasks/ldap.md @@ -0,0 +1,120 @@ +# LDAP Rake Tasks + +## Check + +The LDAP check Rake task will test the `bind_dn` and `password` credentials +(if configured) and will list a sample of LDAP users. This task is also +executed as part of the `gitlab:check` task, but can run independently +using the command below. + +**Omnibus Installation** + +``` +sudo gitlab-rake gitlab:ldap:check +``` + +**Source Installation** + +```bash +sudo -u git -H bundle exec rake gitlab:ldap:check RAILS_ENV=production +``` + +------ + +By default, the task will return a sample of 100 LDAP users. Change this +limit by passing a number to the check task: + +```bash +rake gitlab:ldap:check[50] +``` + +## Rename a provider + +If you change the LDAP server ID in `gitlab.yml` or `gitlab.rb` you will need +to update all user identities or users will be unable to sign in. Input the +old and new provider and this task will update all matching identities in the +database. + +`old_provider` and `new_provider` are derived from the prefix `ldap` plus the +LDAP server ID from the configuration file. For example, in `gitlab.yml` or +`gitlab.rb` you may see LDAP configuration like this: + +```yaml +main: + label: 'LDAP' + host: '_your_ldap_server' + port: 389 + uid: 'sAMAccountName' + ... +``` + +`main` is the LDAP server ID. Together, the unique provider is `ldapmain`. + +> **Warning**: If you input an incorrect new provider users will be unable +to sign in. If this happens, run the task again with the incorrect provider +as the `old_provider` and the correct provider as the `new_provider`. + +**Omnibus Installation** + +```bash +sudo gitlab-rake gitlab:ldap:rename_provider[old_provider,new_provider] +``` + +**Source Installation** + +```bash +bundle exec rake gitlab:ldap:rename_provider[old_provider,new_provider] RAILS_ENV=production +``` + +### Example + +Consider beginning with the default server ID `main` (full provider `ldapmain`). +If we change `main` to `mycompany`, the `new_provider` is `ldapmycompany`. +To rename all user identities run the following command: + +```bash +sudo gitlab-rake gitlab:ldap:rename_provider[ldapmain,ldapmycompany] +``` + +Example output: + +``` +100 users with provider 'ldapmain' will be updated to 'ldapmycompany'. +If the new provider is incorrect, users will be unable to sign in. +Do you want to continue (yes/no)? yes + +User identities were successfully updated +``` + +### Other options + +If you do not specify an `old_provider` and `new_provider` you will be prompted +for them: + +**Omnibus Installation** + +```bash +sudo gitlab-rake gitlab:ldap:rename_provider +``` + +**Source Installation** + +```bash +bundle exec rake gitlab:ldap:rename_provider RAILS_ENV=production +``` + +**Example output:** + +``` +What is the old provider? Ex. 'ldapmain': ldapmain +What is the new provider? Ex. 'ldapcustom': ldapmycompany +``` + +------ + +This tasks also accepts the `force` environment variable which will skip the +confirmation dialog: + +```bash +sudo gitlab-rake gitlab:ldap:rename_provider[old_provider,new_provider] force=yes +``` diff --git a/doc/raketasks/README.md b/doc/raketasks/README.md index a49c43b8ef2..2b81ebc9c59 100644 --- a/doc/raketasks/README.md +++ b/doc/raketasks/README.md @@ -4,7 +4,8 @@ - [Check](check.md) - [Cleanup](cleanup.md) - [Features](features.md) -- [Maintenance](maintenance.md) and self-checks +- [LDAP Maintenance](../administration/raketasks/ldap.md) +- [General Maintenance](maintenance.md) and self-checks - [User management](user_management.md) - [Webhooks](web_hooks.md) - [Import](import.md) of git repositories in bulk diff --git a/lib/tasks/gitlab/ldap.rake b/lib/tasks/gitlab/ldap.rake new file mode 100644 index 00000000000..c66a2a263dc --- /dev/null +++ b/lib/tasks/gitlab/ldap.rake @@ -0,0 +1,40 @@ +namespace :gitlab do + namespace :ldap do + desc 'GitLab | LDAP | Rename provider' + task :rename_provider, [:old_provider, :new_provider] => :environment do |_, args| + old_provider = args[:old_provider] || + prompt('What is the old provider? Ex. \'ldapmain\': '.color(:blue)) + new_provider = args[:new_provider] || + prompt('What is the new provider ID? Ex. \'ldapcustom\': '.color(:blue)) + puts '' # Add some separation in the output + + identities = Identity.where(provider: old_provider) + identity_count = identities.count + + if identities.empty? + puts "Found no user identities with '#{old_provider}' provider." + puts 'Please check the provider name and try again.' + exit 1 + end + + plural_id_count = ActionController::Base.helpers.pluralize(identity_count, 'user') + + unless ENV['force'] == 'yes' + puts "#{plural_id_count} with provider '#{old_provider}' will be updated to '#{new_provider}'" + puts 'If the new provider is incorrect, users will be unable to sign in' + ask_to_continue + puts '' + end + + updated_count = identities.update_all(provider: new_provider) + + if updated_count == identity_count + puts 'User identities were successfully updated'.color(:green) + else + plural_updated_count = ActionController::Base.helpers.pluralize(updated_count, 'user') + puts 'Some user identities could not be updated'.color(:red) + puts "Successfully updated #{plural_updated_count} out of #{plural_id_count} total" + end + end + end +end diff --git a/spec/tasks/gitlab/ldap_rake_spec.rb b/spec/tasks/gitlab/ldap_rake_spec.rb new file mode 100644 index 00000000000..12d442b9820 --- /dev/null +++ b/spec/tasks/gitlab/ldap_rake_spec.rb @@ -0,0 +1,13 @@ +require 'rake_helper' + +describe 'gitlab:ldap:rename_provider rake task' do + it 'completes without error' do + Rake.application.rake_require 'tasks/gitlab/ldap' + stub_warn_user_is_not_gitlab + ENV['force'] = 'yes' + + create(:identity) # Necessary to prevent `exit 1` from the task. + + run_rake_task('gitlab:ldap:rename_provider', 'ldapmain', 'ldapfoo') + end +end From 0d3e24358b88ce41848c97f3ac37bc813074f260 Mon Sep 17 00:00:00 2001 From: "Z.J. van de Weg" Date: Wed, 30 Nov 2016 15:51:48 +0100 Subject: [PATCH 12/75] Create Slack Slash command service --- Gemfile | 2 +- Gemfile.lock | 4 +- app/models/project.rb | 1 + .../slack_slash_commands_service.rb | 49 +++++++++++++++++++ app/models/service.rb | 3 +- .../mattermost_slash_commands/_help.html.haml | 2 +- .../unreleased/zj-slack-slash-commands.yml | 4 ++ lib/gitlab/chat_commands/deploy.rb | 2 +- lib/gitlab/chat_commands/help.rb | 28 +++++++++++ .../chat_commands/presenters/mattermost.rb} | 0 spec/lib/gitlab/chat_commands/command_spec.rb | 26 +++++++++- .../chat_message/build_message_spec.rb | 8 +-- .../chat_message/issue_message_spec.rb | 10 ++-- .../chat_message/merge_message_spec.rb | 12 ++--- .../chat_message/note_message_spec.rb | 22 ++++----- .../chat_message/push_message_spec.rb | 26 +++++----- .../chat_message/wiki_page_message_spec.rb | 8 +-- 17 files changed, 156 insertions(+), 51 deletions(-) create mode 100644 app/models/project_services/slack_slash_commands_service.rb create mode 100644 changelogs/unreleased/zj-slack-slash-commands.yml create mode 100644 lib/gitlab/chat_commands/help.rb rename lib/{mattermost/presenter.rb => gitlab/chat_commands/presenters/mattermost.rb} (100%) diff --git a/Gemfile b/Gemfile index 5eb8c32b168..a59b874248b 100644 --- a/Gemfile +++ b/Gemfile @@ -170,7 +170,7 @@ gem 'gitlab-flowdock-git-hook', '~> 1.0.1' gem 'gemnasium-gitlab-service', '~> 0.2' # Slack integration -gem 'slack-notifier', '~> 1.2.0' +gem 'slack-notifier', '~> 1.5.1' # Asana integration gem 'asana', '~> 0.4.0' diff --git a/Gemfile.lock b/Gemfile.lock index 23e45ddc16f..f33b171e1d2 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -687,7 +687,7 @@ GEM json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.0) - slack-notifier (1.2.1) + slack-notifier (1.5.1) slop (3.6.0) spinach (0.8.10) colorize @@ -957,7 +957,7 @@ DEPENDENCIES sidekiq-cron (~> 0.4.4) sidekiq-limit_fetch (~> 3.4) simplecov (= 0.12.0) - slack-notifier (~> 1.2.0) + slack-notifier (~> 1.5.1) spinach-rails (~> 0.2.1) spinach-rerun-reporter (~> 0.0.2) spring (~> 1.7.0) diff --git a/app/models/project.rb b/app/models/project.rb index 5d092ca42c2..7f3a4debfc6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -96,6 +96,7 @@ class Project < ActiveRecord::Base has_one :gemnasium_service, dependent: :destroy has_one :mattermost_slash_commands_service, dependent: :destroy has_one :mattermost_notification_service, dependent: :destroy + has_one :slack_slash_commands_service, dependent: :destroy has_one :slack_notification_service, dependent: :destroy has_one :buildkite_service, dependent: :destroy has_one :bamboo_service, dependent: :destroy diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb new file mode 100644 index 00000000000..9c66f95eef9 --- /dev/null +++ b/app/models/project_services/slack_slash_commands_service.rb @@ -0,0 +1,49 @@ +class SlackSlashCommandsService < ChatService + include TriggersHelper + + prop_accessor :token + + def can_test? + false + end + + def title + 'Slack Slash Command' + end + + def description + "Perform common operations on GitLab in Slack" + end + + def to_param + 'slack_slash_commands' + end + + def fields + [ + { type: 'text', name: 'token', placeholder: '' } + ] + end + + def trigger(params) + return nil unless valid_token?(params[:token]) + + user = find_chat_user(params) + unless user + url = authorize_chat_name_url(params) + return Gitlab::ChatCommands::Presenters::Access.new(url).authorize + end + + Gitlab::ChatCommands::Command.new(project, user, params).execute + end + + private + + def find_chat_user(params) + ChatNames::FindUserService.new(self, params).execute + end + + def authorize_chat_name_url(params) + ChatNames::AuthorizeUserService.new(self, params).execute + end +end diff --git a/app/models/service.rb b/app/models/service.rb index 0bbab078cf6..8abd8e73e43 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -216,11 +216,12 @@ class Service < ActiveRecord::Base jira kubernetes mattermost_slash_commands + mattermost_notification pipelines_email pivotaltracker pushover redmine - mattermost_notification + slack_slash_commands slack_notification teamcity ] diff --git a/app/views/projects/services/mattermost_slash_commands/_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_help.html.haml index a676c0290a0..2bdd4cd148c 100644 --- a/app/views/projects/services/mattermost_slash_commands/_help.html.haml +++ b/app/views/projects/services/mattermost_slash_commands/_help.html.haml @@ -1,4 +1,4 @@ -- pretty_path_with_namespace = "#{@project ? @project.namespace.name : 'namespace'} / #{@project ? @project.name : 'name'}" +- pretty_path_with_namespace = "#{@project.namespace ? @project.namespace.name : 'namespace'} / #{@project ? @project.name : 'name'}" - run_actions_text = "Perform common operations on this project: #{pretty_path_with_namespace}" .well diff --git a/changelogs/unreleased/zj-slack-slash-commands.yml b/changelogs/unreleased/zj-slack-slash-commands.yml new file mode 100644 index 00000000000..9f4c8681ad0 --- /dev/null +++ b/changelogs/unreleased/zj-slack-slash-commands.yml @@ -0,0 +1,4 @@ +--- +title: Refactor presenters ChatCommands +merge_request: 7846 +author: diff --git a/lib/gitlab/chat_commands/deploy.rb b/lib/gitlab/chat_commands/deploy.rb index 0eed1fce0dc..6bb854dc080 100644 --- a/lib/gitlab/chat_commands/deploy.rb +++ b/lib/gitlab/chat_commands/deploy.rb @@ -4,7 +4,7 @@ module Gitlab include Gitlab::Routing.url_helpers def self.match(text) - /\Adeploy\s+(?.*)\s+to+\s+(?.*)\z/.match(text) + /\Adeploy\s+(?\S+.*)\s+to+\s+(?\S+.*)\z/.match(text) end def self.help_message diff --git a/lib/gitlab/chat_commands/help.rb b/lib/gitlab/chat_commands/help.rb new file mode 100644 index 00000000000..e76733f5445 --- /dev/null +++ b/lib/gitlab/chat_commands/help.rb @@ -0,0 +1,28 @@ +module Gitlab + module ChatCommands + class Help < BaseCommand + # This class has to be used last, as it always matches. It has to match + # because other commands were not triggered and we want to show the help + # command + def self.match(_text) + true + end + + def self.help_message + 'help' + end + + def self.allowed?(_project, _user) + true + end + + def execute(commands) + Gitlab::ChatCommands::Presenters::Help.new(commands).present(trigger) + end + + def trigger + params[:command] + end + end + end +end diff --git a/lib/mattermost/presenter.rb b/lib/gitlab/chat_commands/presenters/mattermost.rb similarity index 100% rename from lib/mattermost/presenter.rb rename to lib/gitlab/chat_commands/presenters/mattermost.rb diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb index bfc6818ac08..ed8d25a526a 100644 --- a/spec/lib/gitlab/chat_commands/command_spec.rb +++ b/spec/lib/gitlab/chat_commands/command_spec.rb @@ -64,7 +64,7 @@ describe Gitlab::ChatCommands::Command, service: true do context 'and user can not create deployment' do it 'returns action' do expect(subject[:response_type]).to be(:ephemeral) - expect(subject[:text]).to start_with('Whoops! That action is not allowed') + expect(subject[:text]).to start_with('Whoops! This action is not allowed') end end @@ -74,7 +74,7 @@ describe Gitlab::ChatCommands::Command, service: true do end it 'returns action' do - expect(subject[:text]).to include('Deployment from staging to production started') + expect(subject[:text]).to include('Deployment started from staging to production') expect(subject[:response_type]).to be(:in_channel) end @@ -91,4 +91,26 @@ describe Gitlab::ChatCommands::Command, service: true do end end end + + describe '#match_command' do + subject { described_class.new(project, user, params).match_command.first } + + context 'IssueShow is triggered' do + let(:params) { { text: 'issue show 123' } } + + it { is_expected.to eq(Gitlab::ChatCommands::IssueShow) } + end + + context 'IssueCreate is triggered' do + let(:params) { { text: 'issue create my title' } } + + it { is_expected.to eq(Gitlab::ChatCommands::IssueCreate) } + end + + context 'IssueSearch is triggered' do + let(:params) { { text: 'issue search my query' } } + + it { is_expected.to eq(Gitlab::ChatCommands::IssueSearch) } + end + end end diff --git a/spec/models/project_services/chat_message/build_message_spec.rb b/spec/models/project_services/chat_message/build_message_spec.rb index b71d153f814..50ad5013df9 100644 --- a/spec/models/project_services/chat_message/build_message_spec.rb +++ b/spec/models/project_services/chat_message/build_message_spec.rb @@ -10,7 +10,7 @@ describe ChatMessage::BuildMessage do tag: false, project_name: 'project_name', - project_url: 'example.gitlab.com', + project_url: 'http://example.gitlab.com', commit: { status: status, @@ -48,10 +48,10 @@ describe ChatMessage::BuildMessage do end def build_message(status_text = status) - ":" \ - " Commit :" \ + " Commit " \ - " of branch" \ + " of branch" \ " by hacker #{status_text} in #{duration} #{'second'.pluralize(duration)}" end end diff --git a/spec/models/project_services/chat_message/issue_message_spec.rb b/spec/models/project_services/chat_message/issue_message_spec.rb index ebe0ead4408..190ff4c535d 100644 --- a/spec/models/project_services/chat_message/issue_message_spec.rb +++ b/spec/models/project_services/chat_message/issue_message_spec.rb @@ -10,14 +10,14 @@ describe ChatMessage::IssueMessage, models: true do username: 'test.user' }, project_name: 'project_name', - project_url: 'somewhere.com', + project_url: 'http://somewhere.com', object_attributes: { title: 'Issue title', id: 10, iid: 100, assignee_id: 1, - url: 'url', + url: 'http://url.com', action: 'open', state: 'opened', description: 'issue description' @@ -40,11 +40,11 @@ describe ChatMessage::IssueMessage, models: true do context 'open' do it 'returns a message regarding opening of issues' do expect(subject.pretext).to eq( - '] Issue opened by test.user') + '[] Issue opened by test.user') expect(subject.attachments).to eq([ { title: "#100 Issue title", - title_link: "url", + title_link: "http://url.com", text: "issue description", color: color, } @@ -60,7 +60,7 @@ describe ChatMessage::IssueMessage, models: true do it 'returns a message regarding closing of issues' do expect(subject.pretext). to eq( - '] Issue closed by test.user') + '[] Issue closed by test.user') expect(subject.attachments).to be_empty end end diff --git a/spec/models/project_services/chat_message/merge_message_spec.rb b/spec/models/project_services/chat_message/merge_message_spec.rb index 07c414c6ca4..cc154112e90 100644 --- a/spec/models/project_services/chat_message/merge_message_spec.rb +++ b/spec/models/project_services/chat_message/merge_message_spec.rb @@ -10,14 +10,14 @@ describe ChatMessage::MergeMessage, models: true do username: 'test.user' }, project_name: 'project_name', - project_url: 'somewhere.com', + project_url: 'http://somewhere.com', object_attributes: { title: "Issue title\nSecond line", id: 10, iid: 100, assignee_id: 1, - url: 'url', + url: 'http://url.com', state: 'opened', description: 'issue description', source_branch: 'source_branch', @@ -31,8 +31,8 @@ describe ChatMessage::MergeMessage, models: true do context 'open' do it 'returns a message regarding opening of merge requests' do expect(subject.pretext).to eq( - 'test.user opened '\ - 'in : *Issue title*') + 'test.user opened '\ + 'in : *Issue title*') expect(subject.attachments).to be_empty end end @@ -43,8 +43,8 @@ describe ChatMessage::MergeMessage, models: true do end it 'returns a message regarding closing of merge requests' do expect(subject.pretext).to eq( - 'test.user closed '\ - 'in : *Issue title*') + 'test.user closed '\ + 'in : *Issue title*') expect(subject.attachments).to be_empty end end diff --git a/spec/models/project_services/chat_message/note_message_spec.rb b/spec/models/project_services/chat_message/note_message_spec.rb index 31936da40a2..da700a08e57 100644 --- a/spec/models/project_services/chat_message/note_message_spec.rb +++ b/spec/models/project_services/chat_message/note_message_spec.rb @@ -11,15 +11,15 @@ describe ChatMessage::NoteMessage, models: true do avatar_url: 'http://fakeavatar' }, project_name: 'project_name', - project_url: 'somewhere.com', + project_url: 'http://somewhere.com', repository: { name: 'project_name', - url: 'somewhere.com', + url: 'http://somewhere.com', }, object_attributes: { id: 10, note: 'comment on a commit', - url: 'url', + url: 'http://url.com', noteable_type: 'Commit' } } @@ -37,8 +37,8 @@ describe ChatMessage::NoteMessage, models: true do it 'returns a message regarding notes on commits' do message = described_class.new(@args) - expect(message.pretext).to eq("test.user in : " \ + expect(message.pretext).to eq("test.user in : " \ "*Added a commit message*") expected_attachments = [ { @@ -63,8 +63,8 @@ describe ChatMessage::NoteMessage, models: true do it 'returns a message regarding notes on a merge request' do message = described_class.new(@args) - expect(message.pretext).to eq("test.user in : " \ + expect(message.pretext).to eq("test.user in : " \ "*merge request title*") expected_attachments = [ { @@ -90,8 +90,8 @@ describe ChatMessage::NoteMessage, models: true do it 'returns a message regarding notes on an issue' do message = described_class.new(@args) expect(message.pretext).to eq( - "test.user in : " \ + "test.user in : " \ "*issue title*") expected_attachments = [ { @@ -115,8 +115,8 @@ describe ChatMessage::NoteMessage, models: true do it 'returns a message regarding notes on a project snippet' do message = described_class.new(@args) - expect(message.pretext).to eq("test.user in : " \ + expect(message.pretext).to eq("test.user in : " \ "*snippet title*") expected_attachments = [ { diff --git a/spec/models/project_services/chat_message/push_message_spec.rb b/spec/models/project_services/chat_message/push_message_spec.rb index b781c4505db..24928873bad 100644 --- a/spec/models/project_services/chat_message/push_message_spec.rb +++ b/spec/models/project_services/chat_message/push_message_spec.rb @@ -10,7 +10,7 @@ describe ChatMessage::PushMessage, models: true do project_name: 'project_name', ref: 'refs/heads/master', user_name: 'test.user', - project_url: 'url' + project_url: 'http://url.com' } end @@ -19,20 +19,20 @@ describe ChatMessage::PushMessage, models: true do context 'push' do before do args[:commits] = [ - { message: 'message1', url: 'url1', id: 'abcdefghijkl', author: { name: 'author1' } }, - { message: 'message2', url: 'url2', id: '123456789012', author: { name: 'author2' } }, + { message: 'message1', url: 'http://url1.com', id: 'abcdefghijkl', author: { name: 'author1' } }, + { message: 'message2', url: 'http://url2.com', id: '123456789012', author: { name: 'author2' } }, ] end it 'returns a message regarding pushes' do expect(subject.pretext).to eq( - 'test.user pushed to branch of '\ - ' ()' + 'test.user pushed to branch of '\ + ' ()' ) expect(subject.attachments).to eq([ { - text: ": message1 - author1\n"\ - ": message2 - author2", + text: ": message1 - author1\n"\ + ": message2 - author2", color: color, } ]) @@ -47,14 +47,14 @@ describe ChatMessage::PushMessage, models: true do project_name: 'project_name', ref: 'refs/tags/new_tag', user_name: 'test.user', - project_url: 'url' + project_url: 'http://url.com' } end it 'returns a message regarding pushes' do expect(subject.pretext).to eq('test.user pushed new tag ' \ - ' to ' \ - '') + ' to ' \ + '') expect(subject.attachments).to be_empty end end @@ -66,8 +66,8 @@ describe ChatMessage::PushMessage, models: true do it 'returns a message regarding a new branch' do expect(subject.pretext).to eq( - 'test.user pushed new branch to '\ - '' + 'test.user pushed new branch to '\ + '' ) expect(subject.attachments).to be_empty end @@ -80,7 +80,7 @@ describe ChatMessage::PushMessage, models: true do it 'returns a message regarding a removed branch' do expect(subject.pretext).to eq( - 'test.user removed branch master from ' + 'test.user removed branch master from ' ) expect(subject.attachments).to be_empty end diff --git a/spec/models/project_services/chat_message/wiki_page_message_spec.rb b/spec/models/project_services/chat_message/wiki_page_message_spec.rb index 94c04dc0865..a2ad61e38e7 100644 --- a/spec/models/project_services/chat_message/wiki_page_message_spec.rb +++ b/spec/models/project_services/chat_message/wiki_page_message_spec.rb @@ -10,10 +10,10 @@ describe ChatMessage::WikiPageMessage, models: true do username: 'test.user' }, project_name: 'project_name', - project_url: 'somewhere.com', + project_url: 'http://somewhere.com', object_attributes: { title: 'Wiki page title', - url: 'url', + url: 'http://url.com', content: 'Wiki page description' } } @@ -25,7 +25,7 @@ describe ChatMessage::WikiPageMessage, models: true do it 'returns a message that a new wiki page was created' do expect(subject.pretext).to eq( - 'test.user created in : '\ + 'test.user created in : '\ '*Wiki page title*') end end @@ -35,7 +35,7 @@ describe ChatMessage::WikiPageMessage, models: true do it 'returns a message that a wiki page was updated' do expect(subject.pretext).to eq( - 'test.user edited in : '\ + 'test.user edited in : '\ '*Wiki page title*') end end From ed880e4954803f9753cfafe0dd8106852245d10d Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Dec 2016 23:45:10 +0100 Subject: [PATCH 13/75] Rename ChatService into ChatSlashCommandsService --- app/models/project.rb | 1 - app/models/project_services/chat_service.rb | 21 ------- .../chat_slash_commands_service.rb | 55 +++++++++++++++++++ .../mattermost_slash_commands_service.rb | 28 +--------- .../slack_slash_commands_service.rb | 36 +----------- 5 files changed, 60 insertions(+), 81 deletions(-) delete mode 100644 app/models/project_services/chat_service.rb create mode 100644 app/models/project_services/chat_slash_commands_service.rb diff --git a/app/models/project.rb b/app/models/project.rb index 7f3a4debfc6..9d8351399f9 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -79,7 +79,6 @@ class Project < ActiveRecord::Base has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event' has_many :boards, before_add: :validate_board_limit, dependent: :destroy - has_many :chat_services # Project services has_one :campfire_service, dependent: :destroy diff --git a/app/models/project_services/chat_service.rb b/app/models/project_services/chat_service.rb deleted file mode 100644 index 574788462de..00000000000 --- a/app/models/project_services/chat_service.rb +++ /dev/null @@ -1,21 +0,0 @@ -# Base class for Chat services -# This class is not meant to be used directly, but only to inherit from. -class ChatService < Service - default_value_for :category, 'chat' - - has_many :chat_names, foreign_key: :service_id - - def valid_token?(token) - self.respond_to?(:token) && - self.token.present? && - ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token) - end - - def supported_events - [] - end - - def trigger(params) - raise NotImplementedError - end -end diff --git a/app/models/project_services/chat_slash_commands_service.rb b/app/models/project_services/chat_slash_commands_service.rb new file mode 100644 index 00000000000..b21a1730285 --- /dev/null +++ b/app/models/project_services/chat_slash_commands_service.rb @@ -0,0 +1,55 @@ +# Base class for Chat services +# This class is not meant to be used directly, but only to inherrit from. +class ChatSlashCommandsService < Service + default_value_for :category, 'chat' + + prop_accessor :token + + has_many :chat_names, foreign_key: :service_id + + def valid_token?(token) + self.respond_to?(:token) && + self.token.present? && + ActiveSupport::SecurityUtils.variable_size_secure_compare(token, self.token) + end + + def supported_events + [] + end + + def can_test? + false + end + + def fields + [ + { type: 'text', name: 'token', placeholder: '' } + ] + end + + def trigger(params) + return nil unless valid_token?(params[:token]) + + user = find_chat_user(params) + unless user + url = authorize_chat_name_url(params) + return presenter.authorize_chat_name(url) + end + + Gitlab::ChatCommands::Command.new(presenter, project, user, params).execute + end + + private + + def find_chat_user(params) + ChatNames::FindUserService.new(self, params).execute + end + + def authorize_chat_name_url(params) + ChatNames::AuthorizeUserService.new(self, params).execute + end + + def presenter + throw NotImplementedError + end +end diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb index 33431f41dc2..4c1e27cafbb 100644 --- a/app/models/project_services/mattermost_slash_commands_service.rb +++ b/app/models/project_services/mattermost_slash_commands_service.rb @@ -19,31 +19,7 @@ class MattermostSlashCommandsService < ChatService 'mattermost_slash_commands' end - def fields - [ - { type: 'text', name: 'token', placeholder: '' } - ] - end - - def trigger(params) - return nil unless valid_token?(params[:token]) - - user = find_chat_user(params) - unless user - url = authorize_chat_name_url(params) - return Mattermost::Presenter.authorize_chat_name(url) - end - - Gitlab::ChatCommands::Command.new(project, user, params).execute - end - - private - - def find_chat_user(params) - ChatNames::FindUserService.new(self, params).execute - end - - def authorize_chat_name_url(params) - ChatNames::AuthorizeUserService.new(self, params).execute + def presenter + Gitlab::ChatCommands::Presenters::Mattermost.new end end diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb index 9c66f95eef9..978d48e8c18 100644 --- a/app/models/project_services/slack_slash_commands_service.rb +++ b/app/models/project_services/slack_slash_commands_service.rb @@ -1,12 +1,6 @@ -class SlackSlashCommandsService < ChatService +class SlackSlashCommandsService < ChatSlashCommandsService include TriggersHelper - prop_accessor :token - - def can_test? - false - end - def title 'Slack Slash Command' end @@ -19,31 +13,7 @@ class SlackSlashCommandsService < ChatService 'slack_slash_commands' end - def fields - [ - { type: 'text', name: 'token', placeholder: '' } - ] - end - - def trigger(params) - return nil unless valid_token?(params[:token]) - - user = find_chat_user(params) - unless user - url = authorize_chat_name_url(params) - return Gitlab::ChatCommands::Presenters::Access.new(url).authorize - end - - Gitlab::ChatCommands::Command.new(project, user, params).execute - end - - private - - def find_chat_user(params) - ChatNames::FindUserService.new(self, params).execute - end - - def authorize_chat_name_url(params) - ChatNames::AuthorizeUserService.new(self, params).execute + def presenter + Gitlab::ChatCommands::Presenters::Mattermost.new end end From 37057870a6b4bf4bf42cc7210a6ae17d68ae5448 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Dec 2016 23:45:46 +0100 Subject: [PATCH 14/75] Rename Mattermost::Presenter to Presenter --- lib/gitlab/chat_commands/presenter.rb | 133 ++++++++++++++++++ .../chat_commands/presenters/mattermost.rb | 131 ----------------- 2 files changed, 133 insertions(+), 131 deletions(-) create mode 100644 lib/gitlab/chat_commands/presenter.rb delete mode 100644 lib/gitlab/chat_commands/presenters/mattermost.rb diff --git a/lib/gitlab/chat_commands/presenter.rb b/lib/gitlab/chat_commands/presenter.rb new file mode 100644 index 00000000000..3143dc092a1 --- /dev/null +++ b/lib/gitlab/chat_commands/presenter.rb @@ -0,0 +1,133 @@ +module Gitlab + class ChatCommands + class Presenter + class << self + include Gitlab::Routing.url_helpers + + def authorize_chat_name(url) + message = if url + ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{url})." + else + ":sweat_smile: Couldn't identify you, nor can I autorize you!" + end + + ephemeral_response(message) + end + + def help(commands, trigger) + if commands.none? + ephemeral_response("No commands configured") + else + commands.map! { |command| "#{trigger} #{command}" } + message = header_with_list("Available commands", commands) + + ephemeral_response(message) + end + end + + def present(subject) + return not_found unless subject + + if subject.is_a?(Gitlab::ChatCommands::Result) + show_result(subject) + elsif subject.respond_to?(:count) + if subject.many? + multiple_resources(subject) + elsif subject.none? + not_found + else + single_resource(subject) + end + else + single_resource(subject) + end + end + + def access_denied + ephemeral_response("Whoops! That action is not allowed. This incident will be [reported](https://xkcd.com/838/).") + end + + private + + def show_result(result) + case result.type + when :success + in_channel_response(result.message) + else + ephemeral_response(result.message) + end + end + + def not_found + ephemeral_response("404 not found! GitLab couldn't find what you were looking for! :boom:") + end + + def single_resource(resource) + return error(resource) if resource.errors.any? || !resource.persisted? + + message = "### #{title(resource)}" + message << "\n\n#{resource.description}" if resource.try(:description) + + in_channel_response(message) + end + + def multiple_resources(resources) + resources.map! { |resource| title(resource) } + + message = header_with_list("Multiple results were found:", resources) + + ephemeral_response(message) + end + + def error(resource) + message = header_with_list("The action was not successful, because:", resource.errors.messages) + + ephemeral_response(message) + end + + def title(resource) + reference = resource.try(:to_reference) || resource.try(:id) + title = resource.try(:title) || resource.try(:name) + + "[#{reference} #{title}](#{url(resource)})" + end + + def header_with_list(header, items) + message = [header] + + items.each do |item| + message << "- #{item}" + end + + message.join("\n") + end + + def url(resource) + url_for( + [ + resource.project.namespace.becomes(Namespace), + resource.project, + resource + ] + ) + end + + def ephemeral_response(message) + { + response_type: :ephemeral, + text: message, + status: 200 + } + end + + def in_channel_response(message) + { + response_type: :in_channel, + text: message, + status: 200 + } + end + end + end + end +end diff --git a/lib/gitlab/chat_commands/presenters/mattermost.rb b/lib/gitlab/chat_commands/presenters/mattermost.rb deleted file mode 100644 index 67eda983a74..00000000000 --- a/lib/gitlab/chat_commands/presenters/mattermost.rb +++ /dev/null @@ -1,131 +0,0 @@ -module Mattermost - class Presenter - class << self - include Gitlab::Routing.url_helpers - - def authorize_chat_name(url) - message = if url - ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{url})." - else - ":sweat_smile: Couldn't identify you, nor can I autorize you!" - end - - ephemeral_response(message) - end - - def help(commands, trigger) - if commands.none? - ephemeral_response("No commands configured") - else - commands.map! { |command| "#{trigger} #{command}" } - message = header_with_list("Available commands", commands) - - ephemeral_response(message) - end - end - - def present(subject) - return not_found unless subject - - if subject.is_a?(Gitlab::ChatCommands::Result) - show_result(subject) - elsif subject.respond_to?(:count) - if subject.many? - multiple_resources(subject) - elsif subject.none? - not_found - else - single_resource(subject) - end - else - single_resource(subject) - end - end - - def access_denied - ephemeral_response("Whoops! That action is not allowed. This incident will be [reported](https://xkcd.com/838/).") - end - - private - - def show_result(result) - case result.type - when :success - in_channel_response(result.message) - else - ephemeral_response(result.message) - end - end - - def not_found - ephemeral_response("404 not found! GitLab couldn't find what you were looking for! :boom:") - end - - def single_resource(resource) - return error(resource) if resource.errors.any? || !resource.persisted? - - message = "### #{title(resource)}" - message << "\n\n#{resource.description}" if resource.try(:description) - - in_channel_response(message) - end - - def multiple_resources(resources) - resources.map! { |resource| title(resource) } - - message = header_with_list("Multiple results were found:", resources) - - ephemeral_response(message) - end - - def error(resource) - message = header_with_list("The action was not successful, because:", resource.errors.messages) - - ephemeral_response(message) - end - - def title(resource) - reference = resource.try(:to_reference) || resource.try(:id) - title = resource.try(:title) || resource.try(:name) - - "[#{reference} #{title}](#{url(resource)})" - end - - def header_with_list(header, items) - message = [header] - - items.each do |item| - message << "- #{item}" - end - - message.join("\n") - end - - def url(resource) - url_for( - [ - resource.project.namespace.becomes(Namespace), - resource.project, - resource - ] - ) - end - - def ephemeral_response(message) - { - response_type: :ephemeral, - text: message, - status: 200 - } - end - - def in_channel_response(message) - { - response_type: :in_channel, - text: message, - status: 200 - } - end - end - end -end From 8ca5fef9d472e11b4a9ff5d5ab47bbba3e7e670d Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Dec 2016 23:48:30 +0100 Subject: [PATCH 15/75] Use single presenter for everything --- .../project_services/chat_slash_commands_service.rb | 8 ++------ .../project_services/mattermost_slash_commands_service.rb | 4 ---- .../project_services/slack_slash_commands_service.rb | 4 ---- 3 files changed, 2 insertions(+), 14 deletions(-) diff --git a/app/models/project_services/chat_slash_commands_service.rb b/app/models/project_services/chat_slash_commands_service.rb index b21a1730285..7ff80447a1c 100644 --- a/app/models/project_services/chat_slash_commands_service.rb +++ b/app/models/project_services/chat_slash_commands_service.rb @@ -33,10 +33,10 @@ class ChatSlashCommandsService < Service user = find_chat_user(params) unless user url = authorize_chat_name_url(params) - return presenter.authorize_chat_name(url) + return Gitlab::ChatCommands::Presenter.authorize_chat_name(url) end - Gitlab::ChatCommands::Command.new(presenter, project, user, params).execute + Gitlab::ChatCommands::Command.new(project, user, params).execute end private @@ -48,8 +48,4 @@ class ChatSlashCommandsService < Service def authorize_chat_name_url(params) ChatNames::AuthorizeUserService.new(self, params).execute end - - def presenter - throw NotImplementedError - end end diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb index 4c1e27cafbb..6aac7c2788b 100644 --- a/app/models/project_services/mattermost_slash_commands_service.rb +++ b/app/models/project_services/mattermost_slash_commands_service.rb @@ -18,8 +18,4 @@ class MattermostSlashCommandsService < ChatService def to_param 'mattermost_slash_commands' end - - def presenter - Gitlab::ChatCommands::Presenters::Mattermost.new - end end diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb index 978d48e8c18..197d8eb7bca 100644 --- a/app/models/project_services/slack_slash_commands_service.rb +++ b/app/models/project_services/slack_slash_commands_service.rb @@ -12,8 +12,4 @@ class SlackSlashCommandsService < ChatSlashCommandsService def to_param 'slack_slash_commands' end - - def presenter - Gitlab::ChatCommands::Presenters::Mattermost.new - end end From dc995daf90e67b5531dd7fbb8a958507f46e4eb6 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Thu, 15 Dec 2016 23:51:57 +0100 Subject: [PATCH 16/75] Use Slack compatible syntax --- lib/gitlab/chat_commands/presenter.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/gitlab/chat_commands/presenter.rb b/lib/gitlab/chat_commands/presenter.rb index 3143dc092a1..14c78ac39aa 100644 --- a/lib/gitlab/chat_commands/presenter.rb +++ b/lib/gitlab/chat_commands/presenter.rb @@ -6,7 +6,7 @@ module Gitlab def authorize_chat_name(url) message = if url - ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{url})." + ":wave: Hi there! Before I do anything for you, please <#{url}|connect your GitLab account>." else ":sweat_smile: Couldn't identify you, nor can I autorize you!" end @@ -44,7 +44,7 @@ module Gitlab end def access_denied - ephemeral_response("Whoops! That action is not allowed. This incident will be [reported](https://xkcd.com/838/).") + ephemeral_response("Whoops! That action is not allowed. This incident will be .") end private @@ -65,7 +65,7 @@ module Gitlab def single_resource(resource) return error(resource) if resource.errors.any? || !resource.persisted? - message = "### #{title(resource)}" + message = "#{title(resource)}:" message << "\n\n#{resource.description}" if resource.try(:description) in_channel_response(message) @@ -89,7 +89,7 @@ module Gitlab reference = resource.try(:to_reference) || resource.try(:id) title = resource.try(:title) || resource.try(:name) - "[#{reference} #{title}](#{url(resource)})" + "<#{url(resource)}|#{reference} #{title}>" end def header_with_list(header, items) From cc83aded33471a3e2f943bd9a760f689d30f901e Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 16 Dec 2016 00:00:54 +0100 Subject: [PATCH 17/75] Render format dependent links --- .../chat_slash_commands_service.rb | 13 +- .../mattermost_slash_commands_service.rb | 4 + .../slack_slash_commands_service.rb | 4 + lib/gitlab/chat_commands/base_command.rb | 4 + lib/gitlab/chat_commands/command.rb | 6 +- lib/gitlab/chat_commands/presenter.rb | 232 +++++++++--------- 6 files changed, 148 insertions(+), 115 deletions(-) diff --git a/app/models/project_services/chat_slash_commands_service.rb b/app/models/project_services/chat_slash_commands_service.rb index 7ff80447a1c..f11d257e6c1 100644 --- a/app/models/project_services/chat_slash_commands_service.rb +++ b/app/models/project_services/chat_slash_commands_service.rb @@ -33,10 +33,11 @@ class ChatSlashCommandsService < Service user = find_chat_user(params) unless user url = authorize_chat_name_url(params) - return Gitlab::ChatCommands::Presenter.authorize_chat_name(url) + return presenter.authorize_chat_name(url) end - Gitlab::ChatCommands::Command.new(project, user, params).execute + Gitlab::ChatCommands::Command.new(project, user, + params.merge(presenter_format: presenter_format)).execute end private @@ -48,4 +49,12 @@ class ChatSlashCommandsService < Service def authorize_chat_name_url(params) ChatNames::AuthorizeUserService.new(self, params).execute end + + def presenter + Gitlab::ChatCommands::Presenter.new(presenter_format) + end + + def presenter_format + throw NotImplementedError + end end diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb index 6aac7c2788b..f9d4b29f4ea 100644 --- a/app/models/project_services/mattermost_slash_commands_service.rb +++ b/app/models/project_services/mattermost_slash_commands_service.rb @@ -18,4 +18,8 @@ class MattermostSlashCommandsService < ChatService def to_param 'mattermost_slash_commands' end + + def presenter_format + 'mattermost' + end end diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb index 197d8eb7bca..6bf10ff6572 100644 --- a/app/models/project_services/slack_slash_commands_service.rb +++ b/app/models/project_services/slack_slash_commands_service.rb @@ -12,4 +12,8 @@ class SlackSlashCommandsService < ChatSlashCommandsService def to_param 'slack_slash_commands' end + + def presenter_format + 'slack' + end end diff --git a/lib/gitlab/chat_commands/base_command.rb b/lib/gitlab/chat_commands/base_command.rb index 25da8474e95..156bb826f86 100644 --- a/lib/gitlab/chat_commands/base_command.rb +++ b/lib/gitlab/chat_commands/base_command.rb @@ -42,6 +42,10 @@ module Gitlab def find_by_iid(iid) collection.find_by(iid: iid) end + + def presenter + Gitlab::ChatCommands::Presenter.new(params[:presenter_format]) + end end end end diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb index b0d3fdbc48a..c5c54cf7cfc 100644 --- a/lib/gitlab/chat_commands/command.rb +++ b/lib/gitlab/chat_commands/command.rb @@ -48,15 +48,15 @@ module Gitlab end def help(messages) - Mattermost::Presenter.help(messages, params[:command]) + presenter.help(messages, params[:command]) end def access_denied - Mattermost::Presenter.access_denied + presenter.access_denied end def present(resource) - Mattermost::Presenter.present(resource) + presenter.present(resource) end end end diff --git a/lib/gitlab/chat_commands/presenter.rb b/lib/gitlab/chat_commands/presenter.rb index 14c78ac39aa..e151513cbd5 100644 --- a/lib/gitlab/chat_commands/presenter.rb +++ b/lib/gitlab/chat_commands/presenter.rb @@ -1,131 +1,143 @@ module Gitlab class ChatCommands class Presenter - class << self - include Gitlab::Routing.url_helpers + include Gitlab::Routing.url_helpers - def authorize_chat_name(url) - message = if url - ":wave: Hi there! Before I do anything for you, please <#{url}|connect your GitLab account>." - else - ":sweat_smile: Couldn't identify you, nor can I autorize you!" - end + attr_reader :format + + def initialize(format) + @format = format + end + + def authorize_chat_name(url) + message = if url + ":wave: Hi there! Before I do anything for you, please #{link(url, 'connect your GitLab account')}." + else + ":sweat_smile: Couldn't identify you, nor can I autorize you!" + end + + ephemeral_response(message) + end + + def help(commands, trigger) + if commands.none? + ephemeral_response("No commands configured") + else + commands.map! { |command| "#{trigger} #{command}" } + message = header_with_list("Available commands", commands) ephemeral_response(message) end + end - def help(commands, trigger) - if commands.none? - ephemeral_response("No commands configured") - else - commands.map! { |command| "#{trigger} #{command}" } - message = header_with_list("Available commands", commands) + def present(subject) + return not_found unless subject - ephemeral_response(message) - end - end - - def present(subject) - return not_found unless subject - - if subject.is_a?(Gitlab::ChatCommands::Result) - show_result(subject) - elsif subject.respond_to?(:count) - if subject.many? - multiple_resources(subject) - elsif subject.none? - not_found - else - single_resource(subject) - end + if subject.is_a?(Gitlab::ChatCommands::Result) + show_result(subject) + elsif subject.respond_to?(:count) + if subject.many? + multiple_resources(subject) + elsif subject.none? + not_found else single_resource(subject) end + else + single_resource(subject) + end + end + + def access_denied + ephemeral_response("Whoops! That action is not allowed. This incident will be #{link('https://xkcd.com/838/', 'reported')}.") + end + + private + + def show_result(result) + case result.type + when :success + in_channel_response(result.message) + else + ephemeral_response(result.message) + end + end + + def not_found + ephemeral_response("404 not found! GitLab couldn't find what you were looking for! :boom:") + end + + def single_resource(resource) + return error(resource) if resource.errors.any? || !resource.persisted? + + message = "#{title(resource)}:" + message << "\n\n#{resource.description}" if resource.try(:description) + + in_channel_response(message) + end + + def multiple_resources(resources) + resources.map! { |resource| title(resource) } + + message = header_with_list("Multiple results were found:", resources) + + ephemeral_response(message) + end + + def error(resource) + message = header_with_list("The action was not successful, because:", resource.errors.messages) + + ephemeral_response(message) + end + + def title(resource) + reference = resource.try(:to_reference) || resource.try(:id) + title = resource.try(:title) || resource.try(:name) + + link(url(resource), "#{reference} #{title}") + end + + def header_with_list(header, items) + message = [header] + + items.each do |item| + message << "- #{item}" end - def access_denied - ephemeral_response("Whoops! That action is not allowed. This incident will be .") - end + message.join("\n") + end - private + def url(resource) + url_for( + [ + resource.project.namespace.becomes(Namespace), + resource.project, + resource + ] + ) + end - def show_result(result) - case result.type - when :success - in_channel_response(result.message) - else - ephemeral_response(result.message) - end - end + def ephemeral_response(message) + { + response_type: :ephemeral, + text: message, + status: 200 + } + end - def not_found - ephemeral_response("404 not found! GitLab couldn't find what you were looking for! :boom:") - end + def in_channel_response(message) + { + response_type: :in_channel, + text: message, + status: 200 + } + end - def single_resource(resource) - return error(resource) if resource.errors.any? || !resource.persisted? - - message = "#{title(resource)}:" - message << "\n\n#{resource.description}" if resource.try(:description) - - in_channel_response(message) - end - - def multiple_resources(resources) - resources.map! { |resource| title(resource) } - - message = header_with_list("Multiple results were found:", resources) - - ephemeral_response(message) - end - - def error(resource) - message = header_with_list("The action was not successful, because:", resource.errors.messages) - - ephemeral_response(message) - end - - def title(resource) - reference = resource.try(:to_reference) || resource.try(:id) - title = resource.try(:title) || resource.try(:name) - - "<#{url(resource)}|#{reference} #{title}>" - end - - def header_with_list(header, items) - message = [header] - - items.each do |item| - message << "- #{item}" - end - - message.join("\n") - end - - def url(resource) - url_for( - [ - resource.project.namespace.becomes(Namespace), - resource.project, - resource - ] - ) - end - - def ephemeral_response(message) - { - response_type: :ephemeral, - text: message, - status: 200 - } - end - - def in_channel_response(message) - { - response_type: :in_channel, - text: message, - status: 200 - } + def link(url, title) + case format + when 'slack' then "<#{url}|#{title}>" + when 'mattermost' then "[#{title}](#{url})" + else then title end end end From ebc3f62be52e386b4550e01ce589744e880ad443 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 16 Dec 2016 13:32:05 +0100 Subject: [PATCH 18/75] Fix specs --- .../mattermost_slash_commands/_help.html.haml | 5 ++--- lib/gitlab/chat_commands/command.rb | 4 ++-- lib/gitlab/chat_commands/presenter.rb | 11 ++++++++--- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/app/views/projects/services/mattermost_slash_commands/_help.html.haml b/app/views/projects/services/mattermost_slash_commands/_help.html.haml index 2bdd4cd148c..01a77a952d1 100644 --- a/app/views/projects/services/mattermost_slash_commands/_help.html.haml +++ b/app/views/projects/services/mattermost_slash_commands/_help.html.haml @@ -1,5 +1,4 @@ -- pretty_path_with_namespace = "#{@project.namespace ? @project.namespace.name : 'namespace'} / #{@project ? @project.name : 'name'}" -- run_actions_text = "Perform common operations on this project: #{pretty_path_with_namespace}" +- run_actions_text = "Perform common operations on this project: #{@project.name_with_namespace}" .well This service allows GitLab users to perform common operations on this @@ -27,7 +26,7 @@ .form-group = label_tag :display_name, 'Display name', class: 'col-sm-2 col-xs-12 control-label' .col-sm-10.col-xs-12.input-group - = text_field_tag :display_name, "GitLab / #{pretty_path_with_namespace}", class: 'form-control input-sm', readonly: 'readonly' + = text_field_tag :display_name, "GitLab / #{@project.name_with_namespace}", class: 'form-control input-sm', readonly: 'readonly' .input-group-btn = clipboard_button(clipboard_target: '#display_name') diff --git a/lib/gitlab/chat_commands/command.rb b/lib/gitlab/chat_commands/command.rb index c5c54cf7cfc..145086755e4 100644 --- a/lib/gitlab/chat_commands/command.rb +++ b/lib/gitlab/chat_commands/command.rb @@ -22,8 +22,6 @@ module Gitlab end end - private - def match_command match = nil service = available_commands.find do |klass| @@ -33,6 +31,8 @@ module Gitlab [service, match] end + private + def help_messages available_commands.map(&:help_message) end diff --git a/lib/gitlab/chat_commands/presenter.rb b/lib/gitlab/chat_commands/presenter.rb index e151513cbd5..c86efc0c3f8 100644 --- a/lib/gitlab/chat_commands/presenter.rb +++ b/lib/gitlab/chat_commands/presenter.rb @@ -135,9 +135,14 @@ module Gitlab def link(url, title) case format - when 'slack' then "<#{url}|#{title}>" - when 'mattermost' then "[#{title}](#{url})" - else then title + when 'slack' + "<#{url}|#{title}>" + + when 'mattermost' + "[#{title}](#{url})" + + else + title end end end From f9f1a508c6a4bdb2fcee98d18394e28e1cc663c3 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 16 Dec 2016 14:21:06 +0100 Subject: [PATCH 19/75] Fix SlackSlashCommands tests --- .../chat_slash_commands_service.rb | 2 +- .../mattermost_slash_commands_service.rb | 2 +- lib/gitlab/chat_commands/presenter.rb | 2 +- spec/lib/gitlab/chat_commands/command_spec.rb | 20 +++- .../project_services/chat_service_spec.rb | 15 --- .../chat_slash_commands_service_spec.rb | 103 ++++++++++++++++++ .../mattermost_slash_commands_service_spec.rb | 96 +--------------- .../slack_slash_commands_service.rb | 5 + 8 files changed, 127 insertions(+), 118 deletions(-) delete mode 100644 spec/models/project_services/chat_service_spec.rb create mode 100644 spec/models/project_services/chat_slash_commands_service_spec.rb create mode 100644 spec/models/project_services/slack_slash_commands_service.rb diff --git a/app/models/project_services/chat_slash_commands_service.rb b/app/models/project_services/chat_slash_commands_service.rb index f11d257e6c1..e58c96f5094 100644 --- a/app/models/project_services/chat_slash_commands_service.rb +++ b/app/models/project_services/chat_slash_commands_service.rb @@ -5,7 +5,7 @@ class ChatSlashCommandsService < Service prop_accessor :token - has_many :chat_names, foreign_key: :service_id + has_many :chat_names, foreign_key: :service_id, dependent: :destroy def valid_token?(token) self.respond_to?(:token) && diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb index f9d4b29f4ea..72a7b9c8f3a 100644 --- a/app/models/project_services/mattermost_slash_commands_service.rb +++ b/app/models/project_services/mattermost_slash_commands_service.rb @@ -1,4 +1,4 @@ -class MattermostSlashCommandsService < ChatService +class MattermostSlashCommandsService < ChatSlashCommandsService include TriggersHelper prop_accessor :token diff --git a/lib/gitlab/chat_commands/presenter.rb b/lib/gitlab/chat_commands/presenter.rb index c86efc0c3f8..98356ebebb3 100644 --- a/lib/gitlab/chat_commands/presenter.rb +++ b/lib/gitlab/chat_commands/presenter.rb @@ -1,5 +1,5 @@ module Gitlab - class ChatCommands + module ChatCommands class Presenter include Gitlab::Routing.url_helpers diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb index ed8d25a526a..dec98d990b2 100644 --- a/spec/lib/gitlab/chat_commands/command_spec.rb +++ b/spec/lib/gitlab/chat_commands/command_spec.rb @@ -3,9 +3,13 @@ require 'spec_helper' describe Gitlab::ChatCommands::Command, service: true do let(:project) { create(:empty_project) } let(:user) { create(:user) } + let(:format) { nil } describe '#execute' do - subject { described_class.new(project, user, params).execute } + subject do + described_class.new(project, user, + params.merge(presenter_format: format)).execute + end context 'when no command is available' do let(:params) { { text: 'issue show 1' } } @@ -47,8 +51,14 @@ describe Gitlab::ChatCommands::Command, service: true do expect(subject[:text]).to match("my new issue") end - it 'shows a link to the new issue' do - expect(subject[:text]).to match(/\/issues\/\d+/) + %w(slack mattermost).each do |format| + context "for #{format}" do + let(:format) { format } + + it 'shows a link to the new issue' do + expect(subject[:text]).to match(/\/issues\/\d+/) + end + end end end @@ -64,7 +74,7 @@ describe Gitlab::ChatCommands::Command, service: true do context 'and user can not create deployment' do it 'returns action' do expect(subject[:response_type]).to be(:ephemeral) - expect(subject[:text]).to start_with('Whoops! This action is not allowed') + expect(subject[:text]).to start_with('Whoops! That action is not allowed') end end @@ -74,7 +84,7 @@ describe Gitlab::ChatCommands::Command, service: true do end it 'returns action' do - expect(subject[:text]).to include('Deployment started from staging to production') + expect(subject[:text]).to include('Deployment from staging to production started.') expect(subject[:response_type]).to be(:in_channel) end diff --git a/spec/models/project_services/chat_service_spec.rb b/spec/models/project_services/chat_service_spec.rb deleted file mode 100644 index c6a45a3e1be..00000000000 --- a/spec/models/project_services/chat_service_spec.rb +++ /dev/null @@ -1,15 +0,0 @@ -require 'spec_helper' - -describe ChatService, models: true do - describe "Associations" do - it { is_expected.to have_many :chat_names } - end - - describe '#valid_token?' do - subject { described_class.new } - - it 'is false as it has no token' do - expect(subject.valid_token?('wer')).to be_falsey - end - end -end diff --git a/spec/models/project_services/chat_slash_commands_service_spec.rb b/spec/models/project_services/chat_slash_commands_service_spec.rb new file mode 100644 index 00000000000..64fdd4d570b --- /dev/null +++ b/spec/models/project_services/chat_slash_commands_service_spec.rb @@ -0,0 +1,103 @@ +require 'spec_helper' + +describe ChatSlashCommandsService, models: true do + describe "Associations" do + it { is_expected.to respond_to :token } + it { is_expected.to have_many :chat_names } + end + + describe '#valid_token?' do + subject { described_class.new } + + context 'when the token is empty' do + it 'is false' do + expect(subject.valid_token?('wer')).to be_falsey + end + end + + context 'when there is a token' do + before do + subject.token = '123' + end + + it 'accepts equal tokens' do + expect(subject.valid_token?('123')).to be_truthy + end + end + end + + describe '#trigger' do + subject { described_class.new } + + before do + allow(subject).to receive(:presenter_format).and_return('unknown') + end + + context 'no token is passed' do + let(:params) { Hash.new } + + it 'returns nil' do + expect(subject.trigger(params)).to be_nil + end + end + + context 'with a token passed' do + let(:project) { create(:empty_project) } + let(:params) { { token: 'token' } } + + before do + allow(subject).to receive(:token).and_return('token') + end + + context 'no user can be found' do + context 'when no url can be generated' do + it 'responds with the authorize url' do + response = subject.trigger(params) + + expect(response[:response_type]).to eq :ephemeral + expect(response[:text]).to start_with ":sweat_smile: Couldn't identify you" + end + end + + context 'when an auth url can be generated' do + let(:params) do + { + team_domain: 'http://domain.tld', + team_id: 'T3423423', + user_id: 'U234234', + user_name: 'mepmep', + token: 'token' + } + end + + let(:service) do + project.create_mattermost_slash_commands_service( + properties: { token: 'token' } + ) + end + + it 'generates the url' do + response = service.trigger(params) + + expect(response[:text]).to start_with(':wave: Hi there!') + end + end + end + + context 'when the user is authenticated' do + let!(:chat_name) { create(:chat_name, service: subject) } + let(:params) { { token: 'token', team_id: chat_name.team_id, user_id: chat_name.chat_id } } + + subject do + described_class.create(project: project, properties: { token: 'token' }) + end + + it 'triggers the command' do + expect_any_instance_of(Gitlab::ChatCommands::Command).to receive(:execute) + + subject.trigger(params) + end + end + end + end +end diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb index 4a1037e950b..b9deb0201e1 100644 --- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb +++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb @@ -1,99 +1,5 @@ require 'spec_helper' describe MattermostSlashCommandsService, models: true do - describe "Associations" do - it { is_expected.to respond_to :token } - end - - describe '#valid_token?' do - subject { described_class.new } - - context 'when the token is empty' do - it 'is false' do - expect(subject.valid_token?('wer')).to be_falsey - end - end - - context 'when there is a token' do - before do - subject.token = '123' - end - - it 'accepts equal tokens' do - expect(subject.valid_token?('123')).to be_truthy - end - end - end - - describe '#trigger' do - subject { described_class.new } - - context 'no token is passed' do - let(:params) { Hash.new } - - it 'returns nil' do - expect(subject.trigger(params)).to be_nil - end - end - - context 'with a token passed' do - let(:project) { create(:empty_project) } - let(:params) { { token: 'token' } } - - before do - allow(subject).to receive(:token).and_return('token') - end - - context 'no user can be found' do - context 'when no url can be generated' do - it 'responds with the authorize url' do - response = subject.trigger(params) - - expect(response[:response_type]).to eq :ephemeral - expect(response[:text]).to start_with ":sweat_smile: Couldn't identify you" - end - end - - context 'when an auth url can be generated' do - let(:params) do - { - team_domain: 'http://domain.tld', - team_id: 'T3423423', - user_id: 'U234234', - user_name: 'mepmep', - token: 'token' - } - end - - let(:service) do - project.create_mattermost_slash_commands_service( - properties: { token: 'token' } - ) - end - - it 'generates the url' do - response = service.trigger(params) - - expect(response[:text]).to start_with(':wave: Hi there!') - end - end - end - - context 'when the user is authenticated' do - let!(:chat_name) { create(:chat_name, service: service) } - let(:service) do - project.create_mattermost_slash_commands_service( - properties: { token: 'token' } - ) - end - let(:params) { { token: 'token', team_id: chat_name.team_id, user_id: chat_name.chat_id } } - - it 'triggers the command' do - expect_any_instance_of(Gitlab::ChatCommands::Command).to receive(:execute) - - service.trigger(params) - end - end - end - end + it { is_expected.to respond_to :presenter_format } end diff --git a/spec/models/project_services/slack_slash_commands_service.rb b/spec/models/project_services/slack_slash_commands_service.rb new file mode 100644 index 00000000000..5ef97b9a2ed --- /dev/null +++ b/spec/models/project_services/slack_slash_commands_service.rb @@ -0,0 +1,5 @@ +require 'spec_helper' + +describe SlackSlashCommandsService, models: true do + it { is_expected.to respond_to :presenter_format } +end From 0f2776287a7d9b0fde9ff54ef8d9f74e2f844a09 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Fri, 16 Dec 2016 15:08:10 +0100 Subject: [PATCH 20/75] Use Slack::Notifier::LinkFormatter to convert markdown links to slack compat --- .../chat_slash_commands_service.rb | 8 ++--- .../mattermost_slash_commands_service.rb | 4 --- .../slack_slash_commands_service.rb | 11 ++++-- lib/gitlab/chat_commands/base_command.rb | 2 +- lib/gitlab/chat_commands/presenter.rb | 25 ++----------- spec/lib/gitlab/chat_commands/command_spec.rb | 14 ++------ .../mattermost_notification_service_spec.rb | 2 +- .../mattermost_slash_commands_service_spec.rb | 2 +- .../slack_notification_service_spec.rb | 2 +- .../slack_slash_commands_service.rb | 35 ++++++++++++++++++- .../chat_slash_commands_shared_examples.rb} | 8 +---- ...ttermost_notifications_shared_examples.rb} | 2 +- 12 files changed, 57 insertions(+), 58 deletions(-) rename spec/{models/project_services/chat_slash_commands_service_spec.rb => support/chat_slash_commands_shared_examples.rb} (93%) rename spec/support/{slack_mattermost_shared_examples.rb => slack_mattermost_notifications_shared_examples.rb} (99%) diff --git a/app/models/project_services/chat_slash_commands_service.rb b/app/models/project_services/chat_slash_commands_service.rb index e58c96f5094..12261e9821e 100644 --- a/app/models/project_services/chat_slash_commands_service.rb +++ b/app/models/project_services/chat_slash_commands_service.rb @@ -37,7 +37,7 @@ class ChatSlashCommandsService < Service end Gitlab::ChatCommands::Command.new(project, user, - params.merge(presenter_format: presenter_format)).execute + params).execute end private @@ -51,10 +51,6 @@ class ChatSlashCommandsService < Service end def presenter - Gitlab::ChatCommands::Presenter.new(presenter_format) - end - - def presenter_format - throw NotImplementedError + Gitlab::ChatCommands::Presenter.new end end diff --git a/app/models/project_services/mattermost_slash_commands_service.rb b/app/models/project_services/mattermost_slash_commands_service.rb index 72a7b9c8f3a..10740275669 100644 --- a/app/models/project_services/mattermost_slash_commands_service.rb +++ b/app/models/project_services/mattermost_slash_commands_service.rb @@ -18,8 +18,4 @@ class MattermostSlashCommandsService < ChatSlashCommandsService def to_param 'mattermost_slash_commands' end - - def presenter_format - 'mattermost' - end end diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb index 6bf10ff6572..8413c657099 100644 --- a/app/models/project_services/slack_slash_commands_service.rb +++ b/app/models/project_services/slack_slash_commands_service.rb @@ -13,7 +13,14 @@ class SlackSlashCommandsService < ChatSlashCommandsService 'slack_slash_commands' end - def presenter_format - 'slack' + def trigger(params) + result = super + + # Format messages to be Slack-compatible + if result && result[:text] + result[:text] = Slack::Notifier::LinkFormatter.format(result[:text]) + end + + result end end diff --git a/lib/gitlab/chat_commands/base_command.rb b/lib/gitlab/chat_commands/base_command.rb index 156bb826f86..4fe53ce93a9 100644 --- a/lib/gitlab/chat_commands/base_command.rb +++ b/lib/gitlab/chat_commands/base_command.rb @@ -44,7 +44,7 @@ module Gitlab end def presenter - Gitlab::ChatCommands::Presenter.new(params[:presenter_format]) + Gitlab::ChatCommands::Presenter.new end end end diff --git a/lib/gitlab/chat_commands/presenter.rb b/lib/gitlab/chat_commands/presenter.rb index 98356ebebb3..e94d0ce2470 100644 --- a/lib/gitlab/chat_commands/presenter.rb +++ b/lib/gitlab/chat_commands/presenter.rb @@ -3,15 +3,9 @@ module Gitlab class Presenter include Gitlab::Routing.url_helpers - attr_reader :format - - def initialize(format) - @format = format - end - def authorize_chat_name(url) message = if url - ":wave: Hi there! Before I do anything for you, please #{link(url, 'connect your GitLab account')}." + ":wave: Hi there! Before I do anything for you, please [connect your GitLab account](#{url})." else ":sweat_smile: Couldn't identify you, nor can I autorize you!" end @@ -49,7 +43,7 @@ module Gitlab end def access_denied - ephemeral_response("Whoops! That action is not allowed. This incident will be #{link('https://xkcd.com/838/', 'reported')}.") + ephemeral_response("Whoops! That action is not allowed. This incident will be [reported](https://xkcd.com/838/).") end private @@ -94,7 +88,7 @@ module Gitlab reference = resource.try(:to_reference) || resource.try(:id) title = resource.try(:title) || resource.try(:name) - link(url(resource), "#{reference} #{title}") + "[#{reference} #{title}](#{url(resource)})" end def header_with_list(header, items) @@ -132,19 +126,6 @@ module Gitlab status: 200 } end - - def link(url, title) - case format - when 'slack' - "<#{url}|#{title}>" - - when 'mattermost' - "[#{title}](#{url})" - - else - title - end - end end end end diff --git a/spec/lib/gitlab/chat_commands/command_spec.rb b/spec/lib/gitlab/chat_commands/command_spec.rb index dec98d990b2..a0ec8884635 100644 --- a/spec/lib/gitlab/chat_commands/command_spec.rb +++ b/spec/lib/gitlab/chat_commands/command_spec.rb @@ -3,12 +3,10 @@ require 'spec_helper' describe Gitlab::ChatCommands::Command, service: true do let(:project) { create(:empty_project) } let(:user) { create(:user) } - let(:format) { nil } describe '#execute' do subject do - described_class.new(project, user, - params.merge(presenter_format: format)).execute + described_class.new(project, user, params).execute end context 'when no command is available' do @@ -51,14 +49,8 @@ describe Gitlab::ChatCommands::Command, service: true do expect(subject[:text]).to match("my new issue") end - %w(slack mattermost).each do |format| - context "for #{format}" do - let(:format) { format } - - it 'shows a link to the new issue' do - expect(subject[:text]).to match(/\/issues\/\d+/) - end - end + it 'shows a link to the new issue' do + expect(subject[:text]).to match(/\/issues\/\d+/) end end diff --git a/spec/models/project_services/mattermost_notification_service_spec.rb b/spec/models/project_services/mattermost_notification_service_spec.rb index c01e64b4c8e..7832d6f50cf 100644 --- a/spec/models/project_services/mattermost_notification_service_spec.rb +++ b/spec/models/project_services/mattermost_notification_service_spec.rb @@ -1,5 +1,5 @@ require 'spec_helper' describe MattermostNotificationService, models: true do - it_behaves_like "slack or mattermost" + it_behaves_like "slack or mattermost notifications" end diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb index b9deb0201e1..5c34cb6b4cf 100644 --- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb +++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb @@ -1,5 +1,5 @@ require 'spec_helper' describe MattermostSlashCommandsService, models: true do - it { is_expected.to respond_to :presenter_format } + it_behaves_like "chat slash commands" end diff --git a/spec/models/project_services/slack_notification_service_spec.rb b/spec/models/project_services/slack_notification_service_spec.rb index 59ddddf7454..110b5bf2115 100644 --- a/spec/models/project_services/slack_notification_service_spec.rb +++ b/spec/models/project_services/slack_notification_service_spec.rb @@ -1,5 +1,5 @@ require 'spec_helper' describe SlackNotificationService, models: true do - it_behaves_like "slack or mattermost" + it_behaves_like "slack or mattermost notifications" end diff --git a/spec/models/project_services/slack_slash_commands_service.rb b/spec/models/project_services/slack_slash_commands_service.rb index 5ef97b9a2ed..c3fa80caebe 100644 --- a/spec/models/project_services/slack_slash_commands_service.rb +++ b/spec/models/project_services/slack_slash_commands_service.rb @@ -1,5 +1,38 @@ require 'spec_helper' describe SlackSlashCommandsService, models: true do - it { is_expected.to respond_to :presenter_format } + it_behaves_like "chat slash commands" + + describe '#trigger' do + context 'when an auth url is generated' do + let(:project) { create(:empty_project) } + let(:params) do + { + team_domain: 'http://domain.tld', + team_id: 'T3423423', + user_id: 'U234234', + user_name: 'mepmep', + token: 'token' + } + end + let(:service) do + project.create_slack_slash_commands_service( + properties: { token: 'token' } + ) + end + let(:authorize_url) do + 'http://authorize.example.com/' + end + + before do + allow(service).to receive(:authorize_chat_name_url).and_return(authorize_url) + end + + it 'uses slack compatible links' do + response = service.trigger(params) + + expect(response[:text]).to include("<#{authorize_url}|connect your GitLab account>") + end + end + end end diff --git a/spec/models/project_services/chat_slash_commands_service_spec.rb b/spec/support/chat_slash_commands_shared_examples.rb similarity index 93% rename from spec/models/project_services/chat_slash_commands_service_spec.rb rename to spec/support/chat_slash_commands_shared_examples.rb index 64fdd4d570b..96130b45235 100644 --- a/spec/models/project_services/chat_slash_commands_service_spec.rb +++ b/spec/support/chat_slash_commands_shared_examples.rb @@ -1,6 +1,4 @@ -require 'spec_helper' - -describe ChatSlashCommandsService, models: true do +RSpec.shared_examples 'chat slash commands' do describe "Associations" do it { is_expected.to respond_to :token } it { is_expected.to have_many :chat_names } @@ -29,10 +27,6 @@ describe ChatSlashCommandsService, models: true do describe '#trigger' do subject { described_class.new } - before do - allow(subject).to receive(:presenter_format).and_return('unknown') - end - context 'no token is passed' do let(:params) { Hash.new } diff --git a/spec/support/slack_mattermost_shared_examples.rb b/spec/support/slack_mattermost_notifications_shared_examples.rb similarity index 99% rename from spec/support/slack_mattermost_shared_examples.rb rename to spec/support/slack_mattermost_notifications_shared_examples.rb index 56d4965f74d..8582aea5fe5 100644 --- a/spec/support/slack_mattermost_shared_examples.rb +++ b/spec/support/slack_mattermost_notifications_shared_examples.rb @@ -1,6 +1,6 @@ Dir[Rails.root.join("app/models/project_services/chat_message/*.rb")].each { |f| require f } -RSpec.shared_examples 'slack or mattermost' do +RSpec.shared_examples 'slack or mattermost notifications' do let(:chat_service) { described_class.new } let(:webhook_url) { 'https://example.gitlab.com/' } From 1b313e8db8513f41808923d9d429305a68bcee3a Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Thu, 15 Dec 2016 12:55:20 +0100 Subject: [PATCH 21/75] Make CI/CD detailed status group concept explicit --- app/views/ci/status/_badge.html.haml | 5 +++-- lib/gitlab/ci/status/build/play.rb | 4 ++++ lib/gitlab/ci/status/build/stop.rb | 4 ++++ lib/gitlab/ci/status/core.rb | 9 +-------- spec/lib/gitlab/ci/status/build/cancelable_spec.rb | 8 ++++++++ spec/lib/gitlab/ci/status/build/play_spec.rb | 4 ++++ spec/lib/gitlab/ci/status/build/retryable_spec.rb | 8 ++++++++ spec/lib/gitlab/ci/status/build/stop_spec.rb | 4 ++++ spec/lib/gitlab/ci/status/canceled_spec.rb | 4 ++++ spec/lib/gitlab/ci/status/created_spec.rb | 4 ++++ spec/lib/gitlab/ci/status/failed_spec.rb | 4 ++++ spec/lib/gitlab/ci/status/pending_spec.rb | 4 ++++ spec/lib/gitlab/ci/status/running_spec.rb | 4 ++++ spec/lib/gitlab/ci/status/skipped_spec.rb | 4 ++++ spec/lib/gitlab/ci/status/success_spec.rb | 4 ++++ 15 files changed, 64 insertions(+), 10 deletions(-) diff --git a/app/views/ci/status/_badge.html.haml b/app/views/ci/status/_badge.html.haml index f2135af2686..601fb7f0f3f 100644 --- a/app/views/ci/status/_badge.html.haml +++ b/app/views/ci/status/_badge.html.haml @@ -1,10 +1,11 @@ - status = local_assigns.fetch(:status) +- css_classes = "ci-status ci-#{status.group}" - if status.has_details? - = link_to status.details_path, class: "ci-status ci-#{status}" do + = link_to status.details_path, class: css_classes do = custom_icon(status.icon) = status.text - else - %span{ class: "ci-status ci-#{status}" } + %span{ class: css_classes } = custom_icon(status.icon) = status.text diff --git a/lib/gitlab/ci/status/build/play.rb b/lib/gitlab/ci/status/build/play.rb index 5c506e6d59f..1bf949c96dd 100644 --- a/lib/gitlab/ci/status/build/play.rb +++ b/lib/gitlab/ci/status/build/play.rb @@ -17,6 +17,10 @@ module Gitlab 'icon_status_manual' end + def group + 'manual' + end + def has_action? can?(user, :update_build, subject) end diff --git a/lib/gitlab/ci/status/build/stop.rb b/lib/gitlab/ci/status/build/stop.rb index f8ffa95cde4..e1dfdb76d41 100644 --- a/lib/gitlab/ci/status/build/stop.rb +++ b/lib/gitlab/ci/status/build/stop.rb @@ -17,6 +17,10 @@ module Gitlab 'icon_status_manual' end + def group + 'manual' + end + def has_action? can?(user, :update_build, subject) end diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb index 46fef8262c1..43d2b1b40d4 100644 --- a/lib/gitlab/ci/status/core.rb +++ b/lib/gitlab/ci/status/core.rb @@ -22,14 +22,7 @@ module Gitlab raise NotImplementedError end - # Deprecation warning: this method is here because we need to maintain - # backwards compatibility with legacy statuses. We often do something - # like "ci-status ci-status-#{status}" to set CSS class. - # - # `to_s` method should be renamed to `group` at some point, after - # phasing legacy satuses out. - # - def to_s + def group self.class.name.demodulize.downcase.underscore end diff --git a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb index 9376bce17a1..b3c07347de1 100644 --- a/spec/lib/gitlab/ci/status/build/cancelable_spec.rb +++ b/spec/lib/gitlab/ci/status/build/cancelable_spec.rb @@ -32,6 +32,14 @@ describe Gitlab::Ci::Status::Build::Cancelable do end end + describe '#group' do + it 'does not override status group' do + expect(status).to receive(:group) + + subject.group + end + end + describe 'action details' do let(:user) { create(:user) } let(:build) { create(:ci_build) } diff --git a/spec/lib/gitlab/ci/status/build/play_spec.rb b/spec/lib/gitlab/ci/status/build/play_spec.rb index 4ddf04a8e11..f1b50a59ce6 100644 --- a/spec/lib/gitlab/ci/status/build/play_spec.rb +++ b/spec/lib/gitlab/ci/status/build/play_spec.rb @@ -18,6 +18,10 @@ describe Gitlab::Ci::Status::Build::Play do it { expect(subject.icon).to eq 'icon_status_manual' } end + describe '#group' do + it { expect(subject.group).to eq 'manual' } + end + describe 'action details' do let(:user) { create(:user) } let(:build) { create(:ci_build) } diff --git a/spec/lib/gitlab/ci/status/build/retryable_spec.rb b/spec/lib/gitlab/ci/status/build/retryable_spec.rb index d61e5bbaa6b..62036f8ec5d 100644 --- a/spec/lib/gitlab/ci/status/build/retryable_spec.rb +++ b/spec/lib/gitlab/ci/status/build/retryable_spec.rb @@ -32,6 +32,14 @@ describe Gitlab::Ci::Status::Build::Retryable do end end + describe '#group' do + it 'does not override status group' do + expect(status).to receive(:group) + + subject.group + end + end + describe 'action details' do let(:user) { create(:user) } let(:build) { create(:ci_build) } diff --git a/spec/lib/gitlab/ci/status/build/stop_spec.rb b/spec/lib/gitlab/ci/status/build/stop_spec.rb index 59a85b55f90..597e02e86e4 100644 --- a/spec/lib/gitlab/ci/status/build/stop_spec.rb +++ b/spec/lib/gitlab/ci/status/build/stop_spec.rb @@ -20,6 +20,10 @@ describe Gitlab::Ci::Status::Build::Stop do it { expect(subject.icon).to eq 'icon_status_manual' } end + describe '#group' do + it { expect(subject.group).to eq 'manual' } + end + describe 'action details' do let(:user) { create(:user) } let(:build) { create(:ci_build) } diff --git a/spec/lib/gitlab/ci/status/canceled_spec.rb b/spec/lib/gitlab/ci/status/canceled_spec.rb index 4639278ad45..38412fe2e4f 100644 --- a/spec/lib/gitlab/ci/status/canceled_spec.rb +++ b/spec/lib/gitlab/ci/status/canceled_spec.rb @@ -16,4 +16,8 @@ describe Gitlab::Ci::Status::Canceled do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_canceled' } end + + describe '#group' do + it { expect(subject.group).to eq 'canceled' } + end end diff --git a/spec/lib/gitlab/ci/status/created_spec.rb b/spec/lib/gitlab/ci/status/created_spec.rb index 2ce176a29d6..6d847484693 100644 --- a/spec/lib/gitlab/ci/status/created_spec.rb +++ b/spec/lib/gitlab/ci/status/created_spec.rb @@ -16,4 +16,8 @@ describe Gitlab::Ci::Status::Created do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_created' } end + + describe '#group' do + it { expect(subject.group).to eq 'created' } + end end diff --git a/spec/lib/gitlab/ci/status/failed_spec.rb b/spec/lib/gitlab/ci/status/failed_spec.rb index 9d527e6a7ef..990d686d22c 100644 --- a/spec/lib/gitlab/ci/status/failed_spec.rb +++ b/spec/lib/gitlab/ci/status/failed_spec.rb @@ -16,4 +16,8 @@ describe Gitlab::Ci::Status::Failed do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_failed' } end + + describe '#group' do + it { expect(subject.group).to eq 'failed' } + end end diff --git a/spec/lib/gitlab/ci/status/pending_spec.rb b/spec/lib/gitlab/ci/status/pending_spec.rb index d03f595d3c7..7bb6579c317 100644 --- a/spec/lib/gitlab/ci/status/pending_spec.rb +++ b/spec/lib/gitlab/ci/status/pending_spec.rb @@ -16,4 +16,8 @@ describe Gitlab::Ci::Status::Pending do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_pending' } end + + describe '#group' do + it { expect(subject.group).to eq 'pending' } + end end diff --git a/spec/lib/gitlab/ci/status/running_spec.rb b/spec/lib/gitlab/ci/status/running_spec.rb index 9f47090d396..852d6c06baf 100644 --- a/spec/lib/gitlab/ci/status/running_spec.rb +++ b/spec/lib/gitlab/ci/status/running_spec.rb @@ -16,4 +16,8 @@ describe Gitlab::Ci::Status::Running do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_running' } end + + describe '#group' do + it { expect(subject.group).to eq 'running' } + end end diff --git a/spec/lib/gitlab/ci/status/skipped_spec.rb b/spec/lib/gitlab/ci/status/skipped_spec.rb index 94601648a8d..e00b356a24b 100644 --- a/spec/lib/gitlab/ci/status/skipped_spec.rb +++ b/spec/lib/gitlab/ci/status/skipped_spec.rb @@ -16,4 +16,8 @@ describe Gitlab::Ci::Status::Skipped do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_skipped' } end + + describe '#group' do + it { expect(subject.group).to eq 'skipped' } + end end diff --git a/spec/lib/gitlab/ci/status/success_spec.rb b/spec/lib/gitlab/ci/status/success_spec.rb index 90f9f615e0d..4a89e1faf40 100644 --- a/spec/lib/gitlab/ci/status/success_spec.rb +++ b/spec/lib/gitlab/ci/status/success_spec.rb @@ -16,4 +16,8 @@ describe Gitlab::Ci::Status::Success do describe '#icon' do it { expect(subject.icon).to eq 'icon_status_success' } end + + describe '#group' do + it { expect(subject.group).to eq 'success' } + end end From cce41cb2a6574de609211e4e2284f684e2d236d8 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Thu, 15 Dec 2016 18:08:11 +0000 Subject: [PATCH 22/75] Adds CSS --- app/assets/stylesheets/framework/icons.scss | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss index 226bd2ead31..b37847e3d96 100644 --- a/app/assets/stylesheets/framework/icons.scss +++ b/app/assets/stylesheets/framework/icons.scss @@ -49,3 +49,11 @@ fill: $gray-darkest; } } + +.ci-status-icon-manual { + color: $gl-text-color; + + svg { + fill: $gray-darkest; + } +} From b18897ac40ffa9f68cf189f2a011ea6eca5ac74f Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 16 Dec 2016 15:28:24 +0100 Subject: [PATCH 23/75] Update status group name for success with warnings --- lib/gitlab/ci/status/pipeline/success_with_warnings.rb | 2 +- .../gitlab/ci/status/pipeline/success_with_warnings_spec.rb | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/ci/status/pipeline/success_with_warnings.rb b/lib/gitlab/ci/status/pipeline/success_with_warnings.rb index a7c98f9e909..24bf8b869e0 100644 --- a/lib/gitlab/ci/status/pipeline/success_with_warnings.rb +++ b/lib/gitlab/ci/status/pipeline/success_with_warnings.rb @@ -17,7 +17,7 @@ module Gitlab 'icon_status_warning' end - def to_s + def group 'success_with_warnings' end diff --git a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb b/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb index 7e3383c307f..979160eb9c4 100644 --- a/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb +++ b/spec/lib/gitlab/ci/status/pipeline/success_with_warnings_spec.rb @@ -17,6 +17,10 @@ describe Gitlab::Ci::Status::Pipeline::SuccessWithWarnings do it { expect(subject.icon).to eq 'icon_status_warning' } end + describe '#group' do + it { expect(subject.group).to eq 'success_with_warnings' } + end + describe '.matches?' do context 'when pipeline is successful' do let(:pipeline) do From 1476bb2c54d8195ea673777c2d5c873c2a3becf7 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Fri, 16 Dec 2016 15:32:54 +0100 Subject: [PATCH 24/75] Improve how we calculate detailed status group name --- lib/gitlab/ci/status/core.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/ci/status/core.rb b/lib/gitlab/ci/status/core.rb index 43d2b1b40d4..73b6ab5a635 100644 --- a/lib/gitlab/ci/status/core.rb +++ b/lib/gitlab/ci/status/core.rb @@ -23,7 +23,7 @@ module Gitlab end def group - self.class.name.demodulize.downcase.underscore + self.class.name.demodulize.underscore end def has_details? From bea9795531534a2ce060d14aef4f99577c17f2c3 Mon Sep 17 00:00:00 2001 From: James Edwards-Jones Date: Sat, 17 Dec 2016 06:19:12 +0000 Subject: [PATCH 25/75] Fix link from doc/development/performance.md to 'Performance Monitoring' --- doc/development/performance.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/development/performance.md b/doc/development/performance.md index 5c43ae7b79a..f936a49a2aa 100644 --- a/doc/development/performance.md +++ b/doc/development/performance.md @@ -37,7 +37,7 @@ graphs/dashboards. GitLab provides built-in tools to aid the process of improving performance: * [Sherlock](profiling.md#sherlock) -* [GitLab Performance Monitoring](../administration/monitoring/performance/monitoring.md) +* [GitLab Performance Monitoring](../administration/monitoring/performance/introduction.md) * [Request Profiling](../administration/monitoring/performance/request_profiling.md) GitLab employees can use GitLab.com's performance monitoring systems located at From dc68a91c444d3da85bbcb87e4ef4e841be9f3887 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 17 Dec 2016 13:34:35 +0100 Subject: [PATCH 26/75] Fix CI/CD statuses actions' CSS on pipeline graphs --- app/views/ci/status/_graph_badge.html.haml | 2 +- spec/features/projects/pipelines/pipeline_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/views/ci/status/_graph_badge.html.haml b/app/views/ci/status/_graph_badge.html.haml index c7d04ab61e9..9f3a9c0c6b2 100644 --- a/app/views/ci/status/_graph_badge.html.haml +++ b/app/views/ci/status/_graph_badge.html.haml @@ -2,7 +2,7 @@ - subject = local_assigns.fetch(:subject) - status = subject.detailed_status(current_user) -- klass = "ci-status-icon ci-status-icon-#{status}" +- klass = "ci-status-icon ci-status-icon-#{status.group}" - if status.has_details? = link_to status.details_path, data: { toggle: 'tooltip', title: "#{subject.name} - #{status.label}" } do diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 0a77eaa123c..57f1e75ea2c 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -99,7 +99,7 @@ describe "Pipelines", feature: true, js: true do context 'when pipeline has manual builds' do it 'shows the skipped icon and a play action for the manual build' do page.within('a[data-title="manual build - manual play action"]') do - expect(page).to have_selector('.ci-status-icon-skipped') + expect(page).to have_selector('.ci-status-icon-manual') expect(page).to have_content('manual') end From 27fd32613dac9c093d538e576131e7fda3f7d8e3 Mon Sep 17 00:00:00 2001 From: Grzegorz Bizon Date: Sat, 17 Dec 2016 13:46:16 +0100 Subject: [PATCH 27/75] Add `ci-manual` status CSS with darkest gray color --- app/assets/stylesheets/pages/status.scss | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index f3b0608e545..637df7e349e 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -113,6 +113,19 @@ fill: $gl-gray-light; } } + + &.ci-manual { + color: $gl-gray-dark; + border-color: $gl-gray-dark; + + &:not(span):hover { + background-color: rgba( $gl-gray-dark, .07); + } + + svg { + fill: $gl-gray-dark; + } + } } } From 3932c20ee61d404196022258d7b7e83c137e4519 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Sat, 17 Dec 2016 13:38:18 +0000 Subject: [PATCH 28/75] Improve spacing and fixes manual status color --- app/assets/stylesheets/framework/icons.scss | 2 +- app/assets/stylesheets/pages/status.scss | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/assets/stylesheets/framework/icons.scss b/app/assets/stylesheets/framework/icons.scss index b37847e3d96..8624a25c052 100644 --- a/app/assets/stylesheets/framework/icons.scss +++ b/app/assets/stylesheets/framework/icons.scss @@ -54,6 +54,6 @@ color: $gl-text-color; svg { - fill: $gray-darkest; + fill: $gl-text-color; } } diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index 637df7e349e..7ce8f7757f3 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -1,5 +1,6 @@ .container-fluid { .ci-status { + display: inline-block; padding: 2px 7px; margin-right: 10px; border: 1px solid $gray-darker; @@ -15,8 +16,7 @@ height: 13px; width: 13px; position: relative; - top: 1px; - margin-right: 3px; + top: 2px; overflow: visible; } @@ -115,15 +115,15 @@ } &.ci-manual { - color: $gl-gray-dark; - border-color: $gl-gray-dark; + color: $gl-text-color; + border-color: $gl-text-color; &:not(span):hover { - background-color: rgba( $gl-gray-dark, .07); + background-color: rgba( $gl-text-color, .07); } svg { - fill: $gl-gray-dark; + fill: $gl-text-color; } } } From 25b84b2f849d10005d214cc537ed833503d8dc89 Mon Sep 17 00:00:00 2001 From: Filipa Lacerda Date: Sat, 17 Dec 2016 14:09:38 +0000 Subject: [PATCH 29/75] Fix extra spacing in all rgba methods in status file --- app/assets/stylesheets/pages/status.scss | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index 7ce8f7757f3..055dacd81f4 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -25,7 +25,7 @@ border-color: $gl-danger; &:not(span):hover { - background-color: rgba( $gl-danger, .07); + background-color: rgba($gl-danger, .07); } svg { @@ -39,7 +39,7 @@ border-color: $gl-success; &:not(span):hover { - background-color: rgba( $gl-success, .07); + background-color: rgba($gl-success, .07); } svg { @@ -52,7 +52,7 @@ border-color: $gl-info; &:not(span):hover { - background-color: rgba( $gl-info, .07); + background-color: rgba($gl-info, .07); } svg { @@ -66,7 +66,7 @@ border-color: $gl-gray; &:not(span):hover { - background-color: rgba( $gl-gray, .07); + background-color: rgba($gl-gray, .07); } svg { @@ -79,7 +79,7 @@ border-color: $gl-warning; &:not(span):hover { - background-color: rgba( $gl-warning, .07); + background-color: rgba($gl-warning, .07); } svg { @@ -92,7 +92,7 @@ border-color: $blue-normal; &:not(span):hover { - background-color: rgba( $blue-normal, .07); + background-color: rgba($blue-normal, .07); } svg { @@ -106,7 +106,7 @@ border-color: $gl-gray-light; &:not(span):hover { - background-color: rgba( $gl-gray-light, .07); + background-color: rgba($gl-gray-light, .07); } svg { @@ -119,7 +119,7 @@ border-color: $gl-text-color; &:not(span):hover { - background-color: rgba( $gl-text-color, .07); + background-color: rgba($gl-text-color, .07); } svg { From 8c2297f00b96cafea2adf7918086b3ef5fd049fc Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Wed, 14 Dec 2016 15:51:25 -0600 Subject: [PATCH 30/75] Move access request buttons to project header --- app/assets/stylesheets/pages/projects.scss | 8 +++-- app/views/projects/_home_panel.html.haml | 3 ++ app/views/projects/show.html.haml | 1 - .../members/_access_request_buttons.html.haml | 29 ++++++++++--------- changelogs/unreleased/leave-project-btn.yml | 4 +++ 5 files changed, 27 insertions(+), 18 deletions(-) create mode 100644 changelogs/unreleased/leave-project-btn.yml diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 3b1b375133d..29a8f5b3995 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -178,8 +178,10 @@ .download-button, .dropdown-toggle, .notification-dropdown, - .project-dropdown { + .project-dropdown, + .access-button { margin-left: 10px; + vertical-align: top; } .notification-dropdown .dropdown-menu { @@ -201,7 +203,7 @@ display: inline-block; input { - height: 29px; + height: 28px; } } @@ -255,7 +257,7 @@ line-height: 13px; padding: $gl-vert-padding $gl-padding; letter-spacing: .4px; - padding: 7px 14px; + padding: 6px 14px; text-align: center; vertical-align: middle; touch-action: manipulation; diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 5a04c3318cf..d13177f6776 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -26,3 +26,6 @@ - if @project.feature_available?(:repository, current_user) .project-clone-holder = render "shared/clone_panel" + + - if current_user + = render 'shared/members/access_request_buttons', source: @project diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index c50093cf47c..20ea8600a40 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -69,7 +69,6 @@ %li.project-repo-buttons.right .project-right-buttons - if current_user - = render 'shared/members/access_request_buttons', source: @project = render "projects/buttons/koding" .btn-group.project-repo-btn-group diff --git a/app/views/shared/members/_access_request_buttons.html.haml b/app/views/shared/members/_access_request_buttons.html.haml index e166dfab710..e9b9ed554c1 100644 --- a/app/views/shared/members/_access_request_buttons.html.haml +++ b/app/views/shared/members/_access_request_buttons.html.haml @@ -1,16 +1,17 @@ - model_name = source.model_name.to_s.downcase -- if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id)) - = link_to "Leave #{model_name}", polymorphic_path([:leave, source, :members]), - method: :delete, - data: { confirm: leave_confirmation_message(source) }, - class: 'btn' -- elsif requester = source.requesters.find_by(user_id: current_user.id) - = link_to 'Withdraw Access Request', polymorphic_path([:leave, source, :members]), - method: :delete, - data: { confirm: remove_member_message(requester) }, - class: 'btn' -- elsif source.request_access_enabled && can?(current_user, :request_access, source) - = link_to 'Request Access', polymorphic_path([:request_access, source, :members]), - method: :post, - class: 'btn' +.access-button.inline.hidden-sm.hidden-xs + - if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id)) + = link_to "Leave #{model_name}", polymorphic_path([:leave, source, :members]), + method: :delete, + data: { confirm: leave_confirmation_message(source) }, + class: 'btn' + - elsif requester = source.requesters.find_by(user_id: current_user.id) + = link_to 'Withdraw Access Request', polymorphic_path([:leave, source, :members]), + method: :delete, + data: { confirm: remove_member_message(requester) }, + class: 'btn' + - elsif source.request_access_enabled && can?(current_user, :request_access, source) + = link_to 'Request Access', polymorphic_path([:request_access, source, :members]), + method: :post, + class: 'btn' diff --git a/changelogs/unreleased/leave-project-btn.yml b/changelogs/unreleased/leave-project-btn.yml new file mode 100644 index 00000000000..ff07dfeed17 --- /dev/null +++ b/changelogs/unreleased/leave-project-btn.yml @@ -0,0 +1,4 @@ +--- +title: Move access project button to header +merge_request: +author: From 2f87703cfff38a8c88f44310031c35dbe7f017ff Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 16 Dec 2016 09:46:24 -0600 Subject: [PATCH 31/75] Move all project buttons to header --- app/assets/stylesheets/framework/mobile.scss | 2 +- app/assets/stylesheets/pages/projects.scss | 15 ++++++++------- app/views/projects/_home_panel.html.haml | 18 ++++++++++++------ app/views/projects/buttons/_download.html.haml | 2 +- app/views/projects/buttons/_dropdown.html.haml | 2 +- app/views/projects/buttons/_koding.html.haml | 6 +++--- app/views/projects/show.html.haml | 13 +------------ .../members/_access_request_buttons.html.haml | 2 +- .../shared/notifications/_button.html.haml | 2 +- 9 files changed, 29 insertions(+), 33 deletions(-) diff --git a/app/assets/stylesheets/framework/mobile.scss b/app/assets/stylesheets/framework/mobile.scss index abfdd7a759d..7eb9962ba33 100644 --- a/app/assets/stylesheets/framework/mobile.scss +++ b/app/assets/stylesheets/framework/mobile.scss @@ -54,7 +54,7 @@ } // Display Star and Fork buttons without counters on mobile. - .project-action-buttons { + .project-repo-buttons { display: block; .count-buttons .btn { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index 29a8f5b3995..acfcb6c0e24 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -143,10 +143,12 @@ margin-top: 0; } -.project-repo-buttons, .group-buttons { margin-top: 15px; +} +.project-repo-buttons, +.group-buttons { .btn { @include btn-gray; padding: 3px 10px; @@ -175,12 +177,8 @@ } } - .download-button, - .dropdown-toggle, - .notification-dropdown, - .project-dropdown, - .access-button { - margin-left: 10px; + .project-action-button { + margin: 15px 5px 0 5px; vertical-align: top; } @@ -197,10 +195,12 @@ .count-buttons { display: inline-block; vertical-align: top; + margin-top: 15px; } .project-clone-holder { display: inline-block; + margin: 15px 5px 0 0; input { height: 28px; @@ -494,6 +494,7 @@ a.deploy-project-label { .project-stats { font-size: 0; + text-align: center; border-bottom: 1px solid $border-color; .nav { diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index d13177f6776..b7e0c7f7128 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -18,14 +18,20 @@ = link_to project_path(forked_from_project) do = forked_from_project.namespace.try(:name) - .project-repo-buttons.project-action-buttons + .project-repo-buttons .count-buttons = render 'projects/buttons/star' = render 'projects/buttons/fork' - - if @project.feature_available?(:repository, current_user) - .project-clone-holder - = render "shared/clone_panel" + %span.hidden-xs + - if @project.feature_available?(:repository, current_user) + .project-clone-holder + = render "shared/clone_panel" - - if current_user - = render 'shared/members/access_request_buttons', source: @project + = render 'projects/buttons/download', project: @project, ref: @ref + = render 'projects/buttons/dropdown' + = render 'shared/notifications/button', notification_setting: @notification_setting + + - if current_user + = render "projects/buttons/koding" + = render 'shared/members/access_request_buttons', source: @project diff --git a/app/views/projects/buttons/_download.html.haml b/app/views/projects/buttons/_download.html.haml index 40bfa01a45a..324a7f8cd3f 100644 --- a/app/views/projects/buttons/_download.html.haml +++ b/app/views/projects/buttons/_download.html.haml @@ -1,5 +1,5 @@ - if !project.empty_repo? && can?(current_user, :download_code, project) - .dropdown.inline.download-button + .project-action-button.dropdown.inline %button.btn{ 'data-toggle' => 'dropdown' } = icon('download') = icon("caret-down") diff --git a/app/views/projects/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml index d3ccebbe290..f5659be25f0 100644 --- a/app/views/projects/buttons/_dropdown.html.haml +++ b/app/views/projects/buttons/_dropdown.html.haml @@ -1,5 +1,5 @@ - if current_user - .dropdown.inline + .project-action-button.dropdown.inline %a.btn.dropdown-toggle{href: '#', "data-toggle" => "dropdown"} = icon('plus') = icon("caret-down") diff --git a/app/views/projects/buttons/_koding.html.haml b/app/views/projects/buttons/_koding.html.haml index fdc80d44253..f8cad1605f6 100644 --- a/app/views/projects/buttons/_koding.html.haml +++ b/app/views/projects/buttons/_koding.html.haml @@ -1,7 +1,7 @@ - if koding_enabled? && current_user && can_push_branch?(@project, @project.default_branch) - if @repository.koding_yml - = link_to koding_project_url(@project), class: 'btn', target: '_blank' do + = link_to koding_project_url(@project), class: 'btn project-action-button inline', target: '_blank' do Run in IDE (Koding) - else - = link_to add_koding_stack_path(@project), class: 'btn' do - Set Up Koding + = link_to add_koding_stack_path(@project), class: 'btn project-action-button inline' do + Set up Koding diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 20ea8600a40..5a60ea58a9e 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -64,19 +64,8 @@ - unless @repository.gitlab_ci_yml %li.missing = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do - Set Up CI + Set up CI - %li.project-repo-buttons.right - .project-right-buttons - - if current_user - = render "projects/buttons/koding" - - .btn-group.project-repo-btn-group - = render 'projects/buttons/download', project: @project, ref: @ref - = render 'projects/buttons/dropdown' - - .pull-right - = render 'shared/notifications/button', notification_setting: @notification_setting - if @repository.commit .project-last-commit{ class: container_class } = render 'projects/last_commit', commit: @repository.commit, ref: current_ref, project: @project diff --git a/app/views/shared/members/_access_request_buttons.html.haml b/app/views/shared/members/_access_request_buttons.html.haml index e9b9ed554c1..fb795ad1c72 100644 --- a/app/views/shared/members/_access_request_buttons.html.haml +++ b/app/views/shared/members/_access_request_buttons.html.haml @@ -1,6 +1,6 @@ - model_name = source.model_name.to_s.downcase -.access-button.inline.hidden-sm.hidden-xs +.project-action-button.inline - if can?(current_user, :"destroy_#{model_name}_member", source.members.find_by(user_id: current_user.id)) = link_to "Leave #{model_name}", polymorphic_path([:leave, source, :members]), method: :delete, diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml index fbad0d05de3..8e4fa5d48c3 100644 --- a/app/views/shared/notifications/_button.html.haml +++ b/app/views/shared/notifications/_button.html.haml @@ -1,5 +1,5 @@ - if notification_setting - .dropdown.notification-dropdown + .project-action-button.dropdown.notification-dropdown.inline = form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f| = hidden_setting_source_input(notification_setting) = f.hidden_field :level, class: "notification_setting_level" From 30a7a76f3bbc0572a151fc6612c46591f7de7ec0 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 16 Dec 2016 10:11:51 -0600 Subject: [PATCH 32/75] Add empty koding state; check permissions for project buttons --- app/views/projects/_home_panel.html.haml | 11 +++++------ app/views/projects/buttons/_koding.html.haml | 10 +++------- app/views/projects/show.html.haml | 3 +++ changelogs/unreleased/leave-project-btn.yml | 6 +++--- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index b7e0c7f7128..0d1f2b70018 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -28,10 +28,9 @@ .project-clone-holder = render "shared/clone_panel" - = render 'projects/buttons/download', project: @project, ref: @ref - = render 'projects/buttons/dropdown' - = render 'shared/notifications/button', notification_setting: @notification_setting - - - if current_user - = render "projects/buttons/koding" + - if current_user && can?(current_user, :download_code, @project) + = render 'projects/buttons/download', project: @project, ref: @ref + = render 'projects/buttons/dropdown' + = render 'shared/notifications/button', notification_setting: @notification_setting + = render 'projects/buttons/koding' = render 'shared/members/access_request_buttons', source: @project diff --git a/app/views/projects/buttons/_koding.html.haml b/app/views/projects/buttons/_koding.html.haml index f8cad1605f6..5d9a776da89 100644 --- a/app/views/projects/buttons/_koding.html.haml +++ b/app/views/projects/buttons/_koding.html.haml @@ -1,7 +1,3 @@ -- if koding_enabled? && current_user && can_push_branch?(@project, @project.default_branch) - - if @repository.koding_yml - = link_to koding_project_url(@project), class: 'btn project-action-button inline', target: '_blank' do - Run in IDE (Koding) - - else - = link_to add_koding_stack_path(@project), class: 'btn project-action-button inline' do - Set up Koding +- if koding_enabled? && current_user && @repository.koding_yml && can_push_branch?(@project, @project.default_branch) + = link_to koding_project_url(@project), class: 'btn project-action-button inline', target: '_blank' do + Run in IDE (Koding) diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 5a60ea58a9e..097dd224140 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -65,6 +65,9 @@ %li.missing = link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do Set up CI + - if koding_enabled? && @repository.koding_yml.blank? + %li.missing + = link_to 'Set Up Koding', add_koding_stack_path(@project) - if @repository.commit .project-last-commit{ class: container_class } diff --git a/changelogs/unreleased/leave-project-btn.yml b/changelogs/unreleased/leave-project-btn.yml index ff07dfeed17..2aa553d7b97 100644 --- a/changelogs/unreleased/leave-project-btn.yml +++ b/changelogs/unreleased/leave-project-btn.yml @@ -1,4 +1,4 @@ --- -title: Move access project button to header -merge_request: -author: +title: Move all action buttons to project header +merge_request: +author: From 78e8ac5da1fce5dab13667b9eafe7c91550e7210 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Fri, 16 Dec 2016 11:13:17 -0600 Subject: [PATCH 33/75] Add sentence casing, fix groups page buttons, fix dark gray variable --- app/assets/stylesheets/framework/variables.scss | 1 + app/assets/stylesheets/pages/groups.scss | 6 ------ app/assets/stylesheets/pages/projects.scss | 6 +----- app/assets/stylesheets/pages/tree.scss | 2 +- app/views/projects/show.html.haml | 2 +- app/views/shared/notifications/_button.html.haml | 2 +- 6 files changed, 5 insertions(+), 14 deletions(-) diff --git a/app/assets/stylesheets/framework/variables.scss b/app/assets/stylesheets/framework/variables.scss index d0c27d64239..460c5d995be 100644 --- a/app/assets/stylesheets/framework/variables.scss +++ b/app/assets/stylesheets/framework/variables.scss @@ -24,6 +24,7 @@ $gray-lightest: #fdfdfd; $gray-light: #fafafa; $gray-lighter: #f9f9f9; $gray-normal: #f5f5f5; +$gray-dark: darken($gray-light, $darken-dark-factor); $gray-darker: #eee; $gray-darkest: #c4c4c4; diff --git a/app/assets/stylesheets/pages/groups.scss b/app/assets/stylesheets/pages/groups.scss index a9af7af59e2..16bff5f1e03 100644 --- a/app/assets/stylesheets/pages/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -27,12 +27,6 @@ } } -.group-buttons { - .notification-dropdown { - display: inline-block; - } -} - .groups-header { @media (min-width: $screen-sm-min) { .nav-links { diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss index acfcb6c0e24..a443b6a37b3 100644 --- a/app/assets/stylesheets/pages/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -143,10 +143,6 @@ margin-top: 0; } -.group-buttons { - margin-top: 15px; -} - .project-repo-buttons, .group-buttons { .btn { @@ -178,7 +174,7 @@ } .project-action-button { - margin: 15px 5px 0 5px; + margin: 15px 5px 0; vertical-align: top; } diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index c0341db7289..05c0a4c29f4 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -172,7 +172,7 @@ position: relative; z-index: 2; - .download-button { + .project-action-button { margin-left: $btn-side-margin; } } diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 097dd224140..8a214e1de58 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -67,7 +67,7 @@ Set up CI - if koding_enabled? && @repository.koding_yml.blank? %li.missing - = link_to 'Set Up Koding', add_koding_stack_path(@project) + = link_to 'Set up Koding', add_koding_stack_path(@project) - if @repository.commit .project-last-commit{ class: container_class } diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml index 8e4fa5d48c3..ce33d1497ba 100644 --- a/app/views/shared/notifications/_button.html.haml +++ b/app/views/shared/notifications/_button.html.haml @@ -1,5 +1,5 @@ - if notification_setting - .project-action-button.dropdown.notification-dropdown.inline + .project-action-button.dropdown.inline = form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f| = hidden_setting_source_input(notification_setting) = f.hidden_field :level, class: "notification_setting_level" From 26c8e0b3f472c2567ae4d7eed16f19cd40132b70 Mon Sep 17 00:00:00 2001 From: Jeff Stubler Date: Fri, 16 Dec 2016 09:10:04 -0600 Subject: [PATCH 34/75] Fix 500 error for invalid path when visiting blame page Closes #25761. --- app/controllers/projects/blame_controller.rb | 3 +++ changelogs/unreleased/fix-blame-500.yml | 4 ++++ spec/controllers/projects/blame_controller_spec.rb | 5 +++++ 3 files changed, 12 insertions(+) create mode 100644 changelogs/unreleased/fix-blame-500.yml diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb index f576d0be1fc..863a766a255 100644 --- a/app/controllers/projects/blame_controller.rb +++ b/app/controllers/projects/blame_controller.rb @@ -8,6 +8,9 @@ class Projects::BlameController < Projects::ApplicationController def show @blob = @repository.blob_at(@commit.id, @path) + + return render_404 unless @blob + @blame_groups = Gitlab::Blame.new(@blob, @commit).groups end end diff --git a/changelogs/unreleased/fix-blame-500.yml b/changelogs/unreleased/fix-blame-500.yml new file mode 100644 index 00000000000..379d81aaa44 --- /dev/null +++ b/changelogs/unreleased/fix-blame-500.yml @@ -0,0 +1,4 @@ +--- +title: Fix blame 500 error on invalid path. +merge_request: 25761 +author: Jeff Stubler diff --git a/spec/controllers/projects/blame_controller_spec.rb b/spec/controllers/projects/blame_controller_spec.rb index 25f06299a29..4402ca43c65 100644 --- a/spec/controllers/projects/blame_controller_spec.rb +++ b/spec/controllers/projects/blame_controller_spec.rb @@ -25,5 +25,10 @@ describe Projects::BlameController do let(:id) { 'master/files/ruby/popen.rb' } it { is_expected.to respond_with(:success) } end + + context "invalid file" do + let(:id) { 'master/files/ruby/missing_file.rb'} + it { expect(response).to have_http_status(404) } + end end end From e7d019c6997fbfed56a415de6dfe4754a5c966d4 Mon Sep 17 00:00:00 2001 From: Adam Niedzielski Date: Sun, 18 Dec 2016 21:07:06 +0100 Subject: [PATCH 35/75] Bring back "notification-dropdown" class for styling and use "js-notification-dropdown" for JavaScript --- app/assets/javascripts/notifications_dropdown.js | 2 +- app/views/shared/notifications/_button.html.haml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/assets/javascripts/notifications_dropdown.js b/app/assets/javascripts/notifications_dropdown.js index 324b68a7efc..5d0d594073d 100644 --- a/app/assets/javascripts/notifications_dropdown.js +++ b/app/assets/javascripts/notifications_dropdown.js @@ -19,7 +19,7 @@ }); $(document).off('ajax:success', '.notification-form').on('ajax:success', '.notification-form', function(e, data) { if (data.saved) { - return $(e.currentTarget).closest('.notification-dropdown').replaceWith(data.html); + return $(e.currentTarget).closest('.js-notification-dropdown').replaceWith(data.html); } else { return new Flash('Failed to save new settings', 'alert'); } diff --git a/app/views/shared/notifications/_button.html.haml b/app/views/shared/notifications/_button.html.haml index ce33d1497ba..1d072c16b32 100644 --- a/app/views/shared/notifications/_button.html.haml +++ b/app/views/shared/notifications/_button.html.haml @@ -1,5 +1,5 @@ - if notification_setting - .project-action-button.dropdown.inline + .js-notification-dropdown.notification-dropdown.project-action-button.dropdown.inline = form_for notification_setting, remote: true, html: { class: "inline notification-form" } do |f| = hidden_setting_source_input(notification_setting) = f.hidden_field :level, class: "notification_setting_level" From 3f60a276fc36fc7d1c5323c38b33fdbc774cfbbf Mon Sep 17 00:00:00 2001 From: "Luke \"Jared\" Bennett" Date: Tue, 13 Dec 2016 16:53:01 +0000 Subject: [PATCH 36/75] Added slack slash commands frontend help well Added tests --- app/assets/stylesheets/framework/forms.scss | 4 + .../slack_slash_commands/_help.html.haml | 93 +++++++++++++++++++ .../services/slack_slash_command_spec.rb | 48 ++++++++++ 3 files changed, 145 insertions(+) create mode 100644 app/views/projects/services/slack_slash_commands/_help.html.haml create mode 100644 spec/features/projects/services/slack_slash_command_spec.rb diff --git a/app/assets/stylesheets/framework/forms.scss b/app/assets/stylesheets/framework/forms.scss index 940807fc399..8726a69867b 100644 --- a/app/assets/stylesheets/framework/forms.scss +++ b/app/assets/stylesheets/framework/forms.scss @@ -96,6 +96,10 @@ label { code { line-height: 1.8; } + + img { + margin-right: $gl-padding; + } } @media(max-width: $screen-xs-max) { diff --git a/app/views/projects/services/slack_slash_commands/_help.html.haml b/app/views/projects/services/slack_slash_commands/_help.html.haml new file mode 100644 index 00000000000..c45052e3954 --- /dev/null +++ b/app/views/projects/services/slack_slash_commands/_help.html.haml @@ -0,0 +1,93 @@ +- run_actions_text = "Perform common operations on this project: #{@project.name_with_namespace}" + +.well + This service allows GitLab users to perform common operations on this + project by entering slash commands in Slack. + %br + See list of available commands in Slack after setting up this service, + by entering + %code /<command> help + %br + %br + To setup this service: + %ul.list-unstyled + %li + 1. + = link_to 'Add a slash command', 'https://my.slack.com/services/new/slash-commands' + in your Slack team with these options: + + %hr + + .help-form + .form-group + = label_tag nil, 'Command', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.text-block + %p Fill in the word that works best for your team. + %p + Suggestions: + %code= 'gitlab' + %code= @project.path # Path contains no spaces, but dashes + %code= @project.path_with_namespace + + .form-group + = label_tag :url, 'URL', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.input-group + = text_field_tag :url, service_trigger_url(subject), class: 'form-control input-sm', readonly: 'readonly' + .input-group-btn + = clipboard_button(clipboard_target: '#url') + + .form-group + = label_tag nil, 'Method', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.text-block POST + + .form-group + = label_tag :customize_name, 'Customize name', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.input-group + = text_field_tag :customize_name, 'GitLab', class: 'form-control input-sm', readonly: 'readonly' + .input-group-btn + = clipboard_button(clipboard_target: '#customize_name') + + .form-group + = label_tag nil, 'Customize icon', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.text-block + = image_tag(asset_url('gitlab_logo.png'), width: 36, height: 36) + = link_to('Download image', asset_url('gitlab_logo.png'), class: 'btn btn-sm', target: '_blank') + + .form-group + = label_tag nil, 'Autocomplete', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.text-block Show this command in the autocomplete list + + .form-group + = label_tag :autocomplete_description, 'Autocomplete description', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.input-group + = text_field_tag :autocomplete_description, run_actions_text, class: 'form-control input-sm', readonly: 'readonly' + .input-group-btn + = clipboard_button(clipboard_target: '#autocomplete_description') + + .form-group + = label_tag :autocomplete_usage_hint, 'Autocomplete usage hint', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.input-group + = text_field_tag :autocomplete_usage_hint, '[help]', class: 'form-control input-sm', readonly: 'readonly' + .input-group-btn + = clipboard_button(clipboard_target: '#autocomplete_usage_hint') + + .form-group + = label_tag :descriptive_label, 'Descriptive label', class: 'col-sm-2 col-xs-12 control-label' + .col-sm-10.col-xs-12.input-group + = text_field_tag :descriptive_label, 'Perform common operations on GitLab project', class: 'form-control input-sm', readonly: 'readonly' + .input-group-btn + = clipboard_button(clipboard_target: '#descriptive_label') + + %hr + + %ul.list-unstyled + %li + 2. Paste the + %strong Token + into the field below + %li + 3. Select the + %strong Active + checkbox, press + %strong Save changes + and start using GitLab inside Slack! diff --git a/spec/features/projects/services/slack_slash_command_spec.rb b/spec/features/projects/services/slack_slash_command_spec.rb new file mode 100644 index 00000000000..dee43d69895 --- /dev/null +++ b/spec/features/projects/services/slack_slash_command_spec.rb @@ -0,0 +1,48 @@ +require 'spec_helper' + +feature 'Setup Slack slash commands', feature: true do + include WaitForAjax + + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:service) { project.create_slack_slash_commands_service } + + before do + project.team << [user, :master] + login_as(user) + end + + describe 'user visits the slack slash command config page', js: true do + it 'shows a help message' do + visit edit_namespace_project_service_path(project.namespace, project, service) + + wait_for_ajax + + expect(page).to have_content('This service allows GitLab users to perform common') + end + end + + describe 'saving a token' do + let(:token) { ('a'..'z').to_a.join } + + it 'shows the token after saving' do + visit edit_namespace_project_service_path(project.namespace, project, service) + + fill_in 'service_token', with: token + click_on 'Save' + + value = find_field('service_token').value + + expect(value).to eq(token) + end + end + + describe 'the trigger url' do + it 'shows the correct url' do + visit edit_namespace_project_service_path(project.namespace, project, service) + + value = find_field('url').value + expect(value).to match("api/v3/projects/#{project.id}/services/slack_slash_commands/trigger") + end + end +end From 676b79b94d33e007b34d70c00900a52983ce1106 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 18 Dec 2016 23:30:37 +0100 Subject: [PATCH 37/75] Fix Rubocop --- lib/gitlab/chat_commands/presenter.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/chat_commands/presenter.rb b/lib/gitlab/chat_commands/presenter.rb index e94d0ce2470..b4c4dc252ca 100644 --- a/lib/gitlab/chat_commands/presenter.rb +++ b/lib/gitlab/chat_commands/presenter.rb @@ -43,7 +43,7 @@ module Gitlab end def access_denied - ephemeral_response("Whoops! That action is not allowed. This incident will be [reported](https://xkcd.com/838/).") + ephemeral_response("Whoops! That action is not allowed. This incident will be [reported](https://xkcd.com/838/).") end private @@ -88,7 +88,7 @@ module Gitlab reference = resource.try(:to_reference) || resource.try(:id) title = resource.try(:title) || resource.try(:name) - "[#{reference} #{title}](#{url(resource)})" + "[#{reference} #{title}](#{url(resource)})" end def header_with_list(header, items) From 9e3153dbf556b9b9397806bedcf0a195e8ee3fa4 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 18 Dec 2016 23:30:48 +0100 Subject: [PATCH 38/75] Remove not related spec changes --- .../chat_message/build_message_spec.rb | 8 +++--- .../chat_message/issue_message_spec.rb | 10 +++---- .../chat_message/merge_message_spec.rb | 12 ++++----- .../chat_message/note_message_spec.rb | 22 ++++++++-------- .../chat_message/push_message_spec.rb | 26 +++++++++---------- .../chat_message/wiki_page_message_spec.rb | 8 +++--- 6 files changed, 43 insertions(+), 43 deletions(-) diff --git a/spec/models/project_services/chat_message/build_message_spec.rb b/spec/models/project_services/chat_message/build_message_spec.rb index 50ad5013df9..b71d153f814 100644 --- a/spec/models/project_services/chat_message/build_message_spec.rb +++ b/spec/models/project_services/chat_message/build_message_spec.rb @@ -10,7 +10,7 @@ describe ChatMessage::BuildMessage do tag: false, project_name: 'project_name', - project_url: 'http://example.gitlab.com', + project_url: 'example.gitlab.com', commit: { status: status, @@ -48,10 +48,10 @@ describe ChatMessage::BuildMessage do end def build_message(status_text = status) - ":" \ - " Commit :" \ + " Commit " \ - " of branch" \ + " of branch" \ " by hacker #{status_text} in #{duration} #{'second'.pluralize(duration)}" end end diff --git a/spec/models/project_services/chat_message/issue_message_spec.rb b/spec/models/project_services/chat_message/issue_message_spec.rb index 190ff4c535d..ebe0ead4408 100644 --- a/spec/models/project_services/chat_message/issue_message_spec.rb +++ b/spec/models/project_services/chat_message/issue_message_spec.rb @@ -10,14 +10,14 @@ describe ChatMessage::IssueMessage, models: true do username: 'test.user' }, project_name: 'project_name', - project_url: 'http://somewhere.com', + project_url: 'somewhere.com', object_attributes: { title: 'Issue title', id: 10, iid: 100, assignee_id: 1, - url: 'http://url.com', + url: 'url', action: 'open', state: 'opened', description: 'issue description' @@ -40,11 +40,11 @@ describe ChatMessage::IssueMessage, models: true do context 'open' do it 'returns a message regarding opening of issues' do expect(subject.pretext).to eq( - '[] Issue opened by test.user') + '] Issue opened by test.user') expect(subject.attachments).to eq([ { title: "#100 Issue title", - title_link: "http://url.com", + title_link: "url", text: "issue description", color: color, } @@ -60,7 +60,7 @@ describe ChatMessage::IssueMessage, models: true do it 'returns a message regarding closing of issues' do expect(subject.pretext). to eq( - '[] Issue closed by test.user') + '] Issue closed by test.user') expect(subject.attachments).to be_empty end end diff --git a/spec/models/project_services/chat_message/merge_message_spec.rb b/spec/models/project_services/chat_message/merge_message_spec.rb index cc154112e90..07c414c6ca4 100644 --- a/spec/models/project_services/chat_message/merge_message_spec.rb +++ b/spec/models/project_services/chat_message/merge_message_spec.rb @@ -10,14 +10,14 @@ describe ChatMessage::MergeMessage, models: true do username: 'test.user' }, project_name: 'project_name', - project_url: 'http://somewhere.com', + project_url: 'somewhere.com', object_attributes: { title: "Issue title\nSecond line", id: 10, iid: 100, assignee_id: 1, - url: 'http://url.com', + url: 'url', state: 'opened', description: 'issue description', source_branch: 'source_branch', @@ -31,8 +31,8 @@ describe ChatMessage::MergeMessage, models: true do context 'open' do it 'returns a message regarding opening of merge requests' do expect(subject.pretext).to eq( - 'test.user opened '\ - 'in : *Issue title*') + 'test.user opened '\ + 'in : *Issue title*') expect(subject.attachments).to be_empty end end @@ -43,8 +43,8 @@ describe ChatMessage::MergeMessage, models: true do end it 'returns a message regarding closing of merge requests' do expect(subject.pretext).to eq( - 'test.user closed '\ - 'in : *Issue title*') + 'test.user closed '\ + 'in : *Issue title*') expect(subject.attachments).to be_empty end end diff --git a/spec/models/project_services/chat_message/note_message_spec.rb b/spec/models/project_services/chat_message/note_message_spec.rb index da700a08e57..31936da40a2 100644 --- a/spec/models/project_services/chat_message/note_message_spec.rb +++ b/spec/models/project_services/chat_message/note_message_spec.rb @@ -11,15 +11,15 @@ describe ChatMessage::NoteMessage, models: true do avatar_url: 'http://fakeavatar' }, project_name: 'project_name', - project_url: 'http://somewhere.com', + project_url: 'somewhere.com', repository: { name: 'project_name', - url: 'http://somewhere.com', + url: 'somewhere.com', }, object_attributes: { id: 10, note: 'comment on a commit', - url: 'http://url.com', + url: 'url', noteable_type: 'Commit' } } @@ -37,8 +37,8 @@ describe ChatMessage::NoteMessage, models: true do it 'returns a message regarding notes on commits' do message = described_class.new(@args) - expect(message.pretext).to eq("test.user in : " \ + expect(message.pretext).to eq("test.user in : " \ "*Added a commit message*") expected_attachments = [ { @@ -63,8 +63,8 @@ describe ChatMessage::NoteMessage, models: true do it 'returns a message regarding notes on a merge request' do message = described_class.new(@args) - expect(message.pretext).to eq("test.user in : " \ + expect(message.pretext).to eq("test.user in : " \ "*merge request title*") expected_attachments = [ { @@ -90,8 +90,8 @@ describe ChatMessage::NoteMessage, models: true do it 'returns a message regarding notes on an issue' do message = described_class.new(@args) expect(message.pretext).to eq( - "test.user in : " \ + "test.user in : " \ "*issue title*") expected_attachments = [ { @@ -115,8 +115,8 @@ describe ChatMessage::NoteMessage, models: true do it 'returns a message regarding notes on a project snippet' do message = described_class.new(@args) - expect(message.pretext).to eq("test.user in : " \ + expect(message.pretext).to eq("test.user in : " \ "*snippet title*") expected_attachments = [ { diff --git a/spec/models/project_services/chat_message/push_message_spec.rb b/spec/models/project_services/chat_message/push_message_spec.rb index 24928873bad..b781c4505db 100644 --- a/spec/models/project_services/chat_message/push_message_spec.rb +++ b/spec/models/project_services/chat_message/push_message_spec.rb @@ -10,7 +10,7 @@ describe ChatMessage::PushMessage, models: true do project_name: 'project_name', ref: 'refs/heads/master', user_name: 'test.user', - project_url: 'http://url.com' + project_url: 'url' } end @@ -19,20 +19,20 @@ describe ChatMessage::PushMessage, models: true do context 'push' do before do args[:commits] = [ - { message: 'message1', url: 'http://url1.com', id: 'abcdefghijkl', author: { name: 'author1' } }, - { message: 'message2', url: 'http://url2.com', id: '123456789012', author: { name: 'author2' } }, + { message: 'message1', url: 'url1', id: 'abcdefghijkl', author: { name: 'author1' } }, + { message: 'message2', url: 'url2', id: '123456789012', author: { name: 'author2' } }, ] end it 'returns a message regarding pushes' do expect(subject.pretext).to eq( - 'test.user pushed to branch of '\ - ' ()' + 'test.user pushed to branch of '\ + ' ()' ) expect(subject.attachments).to eq([ { - text: ": message1 - author1\n"\ - ": message2 - author2", + text: ": message1 - author1\n"\ + ": message2 - author2", color: color, } ]) @@ -47,14 +47,14 @@ describe ChatMessage::PushMessage, models: true do project_name: 'project_name', ref: 'refs/tags/new_tag', user_name: 'test.user', - project_url: 'http://url.com' + project_url: 'url' } end it 'returns a message regarding pushes' do expect(subject.pretext).to eq('test.user pushed new tag ' \ - ' to ' \ - '') + ' to ' \ + '') expect(subject.attachments).to be_empty end end @@ -66,8 +66,8 @@ describe ChatMessage::PushMessage, models: true do it 'returns a message regarding a new branch' do expect(subject.pretext).to eq( - 'test.user pushed new branch to '\ - '' + 'test.user pushed new branch to '\ + '' ) expect(subject.attachments).to be_empty end @@ -80,7 +80,7 @@ describe ChatMessage::PushMessage, models: true do it 'returns a message regarding a removed branch' do expect(subject.pretext).to eq( - 'test.user removed branch master from ' + 'test.user removed branch master from ' ) expect(subject.attachments).to be_empty end diff --git a/spec/models/project_services/chat_message/wiki_page_message_spec.rb b/spec/models/project_services/chat_message/wiki_page_message_spec.rb index a2ad61e38e7..94c04dc0865 100644 --- a/spec/models/project_services/chat_message/wiki_page_message_spec.rb +++ b/spec/models/project_services/chat_message/wiki_page_message_spec.rb @@ -10,10 +10,10 @@ describe ChatMessage::WikiPageMessage, models: true do username: 'test.user' }, project_name: 'project_name', - project_url: 'http://somewhere.com', + project_url: 'somewhere.com', object_attributes: { title: 'Wiki page title', - url: 'http://url.com', + url: 'url', content: 'Wiki page description' } } @@ -25,7 +25,7 @@ describe ChatMessage::WikiPageMessage, models: true do it 'returns a message that a new wiki page was created' do expect(subject.pretext).to eq( - 'test.user created in : '\ + 'test.user created in : '\ '*Wiki page title*') end end @@ -35,7 +35,7 @@ describe ChatMessage::WikiPageMessage, models: true do it 'returns a message that a wiki page was updated' do expect(subject.pretext).to eq( - 'test.user edited in : '\ + 'test.user edited in : '\ '*Wiki page title*') end end From f9023adbad4939ba597d509e319105659e61734b Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 18 Dec 2016 23:32:53 +0100 Subject: [PATCH 39/75] Fix spec failures --- app/models/project_services/slack_slash_commands_service.rb | 2 +- spec/features/admin/admin_settings_spec.rb | 4 ++-- spec/lib/gitlab/import_export/all_models.yml | 1 + spec/models/project_spec.rb | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb index 8413c657099..6782ba5ad0a 100644 --- a/app/models/project_services/slack_slash_commands_service.rb +++ b/app/models/project_services/slack_slash_commands_service.rb @@ -2,7 +2,7 @@ class SlackSlashCommandsService < ChatSlashCommandsService include TriggersHelper def title - 'Slack Slash Command' + 'Slack Command' end def description diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index 8cd66f189be..e7a23746244 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -17,9 +17,9 @@ feature 'Admin updates settings', feature: true do expect(page).to have_content "Application settings saved successfully" end - scenario 'Change Slack Service template settings' do + scenario 'Change Slack Notifications Service template settings' do click_link 'Service Templates' - click_link 'Slack' + click_link 'Slack notifications' fill_in 'Webhook', with: 'http://localhost' fill_in 'Username', with: 'test_user' fill_in 'service_push_channel', with: '#test_channel' diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 9b49d6837c3..7e618e2fcf5 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -129,6 +129,7 @@ project: - builds_email_service - pipelines_email_service - mattermost_slash_commands_service +- slack_slash_commands_service - irker_service - pivotaltracker_service - hipchat_service diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index bab3c3dbb02..4b39dc77f29 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -20,7 +20,6 @@ describe Project, models: true do it { is_expected.to have_many(:deploy_keys) } it { is_expected.to have_many(:hooks).dependent(:destroy) } it { is_expected.to have_many(:protected_branches).dependent(:destroy) } - it { is_expected.to have_many(:chat_services) } it { is_expected.to have_one(:forked_project_link).dependent(:destroy) } it { is_expected.to have_one(:slack_notification_service).dependent(:destroy) } it { is_expected.to have_one(:mattermost_notification_service).dependent(:destroy) } @@ -37,6 +36,7 @@ describe Project, models: true do it { is_expected.to have_one(:hipchat_service).dependent(:destroy) } it { is_expected.to have_one(:flowdock_service).dependent(:destroy) } it { is_expected.to have_one(:assembla_service).dependent(:destroy) } + it { is_expected.to have_one(:slack_slash_commands_service).dependent(:destroy) } it { is_expected.to have_one(:mattermost_slash_commands_service).dependent(:destroy) } it { is_expected.to have_one(:gemnasium_service).dependent(:destroy) } it { is_expected.to have_one(:buildkite_service).dependent(:destroy) } From f5ff372140d066d7bcedc0ad0799c723a9012bb0 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Sun, 18 Dec 2016 23:34:40 +0100 Subject: [PATCH 40/75] Fix failures --- spec/features/admin/admin_settings_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/features/admin/admin_settings_spec.rb b/spec/features/admin/admin_settings_spec.rb index e7a23746244..47fa2f14307 100644 --- a/spec/features/admin/admin_settings_spec.rb +++ b/spec/features/admin/admin_settings_spec.rb @@ -30,7 +30,7 @@ feature 'Admin updates settings', feature: true do expect(page).to have_content 'Application settings saved successfully' - click_link 'Slack' + click_link 'Slack notifications' page.all('input[type=checkbox]').each do |checkbox| expect(checkbox).to be_checked From cfd1c6a54e36a25282afb61dea5f1be37e9b3500 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 19 Dec 2016 07:58:10 +0000 Subject: [PATCH 41/75] Fix typo --- doc/ci/yaml/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/ci/yaml/README.md b/doc/ci/yaml/README.md index dd8e1078c60..7158b2e7895 100644 --- a/doc/ci/yaml/README.md +++ b/doc/ci/yaml/README.md @@ -1056,7 +1056,7 @@ variables: GET_SOURCES_ATTEMPTS: "3" ``` -You can set the them in the global [`variables`](#variables) section or the [`variables`](#job-variables) +You can set them in the global [`variables`](#variables) section or the [`variables`](#job-variables) section for individual jobs. ## Shallow cloning From 561ed7b9f91a1d1d08c4a1c1f15bdbcf44f973f1 Mon Sep 17 00:00:00 2001 From: Semyon Pupkov Date: Sat, 17 Dec 2016 19:22:16 +0500 Subject: [PATCH 42/75] Move admin labels spinach test to rspec https://gitlab.com/gitlab-org/gitlab-ce/issues/23036 --- features/admin/labels.feature | 38 -------- features/steps/admin/labels.rb | 117 ----------------------- spec/features/admin/admin_labels_spec.rb | 99 +++++++++++++++++++ 3 files changed, 99 insertions(+), 155 deletions(-) delete mode 100644 features/admin/labels.feature delete mode 100644 features/steps/admin/labels.rb create mode 100644 spec/features/admin/admin_labels_spec.rb diff --git a/features/admin/labels.feature b/features/admin/labels.feature deleted file mode 100644 index 1af0e700bd4..00000000000 --- a/features/admin/labels.feature +++ /dev/null @@ -1,38 +0,0 @@ -Feature: Admin Issues Labels - Background: - Given I sign in as an admin - And I have labels: "bug", "feature", "enhancement" - Given I visit admin labels page - - Scenario: I should see labels list - Then I should see label 'bug' - And I should see label 'feature' - - Scenario: I create new label - Given I submit new label 'support' - Then I should see label 'support' - - Scenario: I edit label - Given I visit 'bug' label edit page - When I change label 'bug' to 'fix' - Then I should not see label 'bug' - Then I should see label 'fix' - - Scenario: I remove label - When I remove label 'bug' - Then I should not see label 'bug' - - @javascript - Scenario: I delete all labels - When I delete all labels - Then I should see labels help message - - Scenario: I create a label with invalid color - Given I visit admin new label page - When I submit new label with invalid color - Then I should see label color error message - - Scenario: I create a label that already exists - Given I visit admin new label page - When I submit new label 'bug' - Then I should see label exist error message diff --git a/features/steps/admin/labels.rb b/features/steps/admin/labels.rb deleted file mode 100644 index 55ddcc25085..00000000000 --- a/features/steps/admin/labels.rb +++ /dev/null @@ -1,117 +0,0 @@ -class Spinach::Features::AdminIssuesLabels < Spinach::FeatureSteps - include SharedAuthentication - include SharedProject - include SharedPaths - - step 'I visit \'bug\' label edit page' do - visit edit_admin_label_path(bug_label) - end - - step 'I visit admin new label page' do - visit new_admin_label_path - end - - step 'I visit admin labels page' do - visit admin_labels_path - end - - step 'I remove label \'bug\'' do - page.within "#label_#{bug_label.id}" do - click_link 'Delete' - end - end - - step 'I have labels: "bug", "feature", "enhancement"' do - ["bug", "feature", "enhancement"].each do |title| - Label.create(title: title, template: true) - end - end - - step 'I delete all labels' do - page.within '.labels' do - page.all('.btn-remove').each do |remove| - remove.click - sleep 0.05 - end - end - end - - step 'I should see labels help message' do - page.within '.labels' do - expect(page).to have_content 'There are no labels yet' - end - end - - step 'I submit new label \'support\'' do - visit new_admin_label_path - fill_in 'Title', with: 'support' - fill_in 'Background color', with: '#F95610' - click_button 'Save' - end - - step 'I submit new label \'bug\'' do - visit new_admin_label_path - fill_in 'Title', with: 'bug' - fill_in 'Background color', with: '#F95610' - click_button 'Save' - end - - step 'I submit new label with invalid color' do - visit new_admin_label_path - fill_in 'Title', with: 'support' - fill_in 'Background color', with: '#12' - click_button 'Save' - end - - step 'I should see label exist error message' do - page.within '.label-form' do - expect(page).to have_content 'Title has already been taken' - end - end - - step 'I should see label color error message' do - page.within '.label-form' do - expect(page).to have_content 'Color must be a valid color code' - end - end - - step 'I should see label \'feature\'' do - page.within '.manage-labels-list' do - expect(page).to have_content 'feature' - end - end - - step 'I should see label \'bug\'' do - page.within '.manage-labels-list' do - expect(page).to have_content 'bug' - end - end - - step 'I should not see label \'bug\'' do - page.within '.manage-labels-list' do - expect(page).not_to have_content 'bug' - end - end - - step 'I should see label \'support\'' do - page.within '.manage-labels-list' do - expect(page).to have_content 'support' - end - end - - step 'I change label \'bug\' to \'fix\'' do - fill_in 'Title', with: 'fix' - fill_in 'Background color', with: '#F15610' - click_button 'Save' - end - - step 'I should see label \'fix\'' do - page.within '.manage-labels-list' do - expect(page).to have_content 'fix' - end - end - - def bug_label - Label.templates.find_or_create_by(title: 'bug') - end -end diff --git a/spec/features/admin/admin_labels_spec.rb b/spec/features/admin/admin_labels_spec.rb new file mode 100644 index 00000000000..eaa42aad0a7 --- /dev/null +++ b/spec/features/admin/admin_labels_spec.rb @@ -0,0 +1,99 @@ +require 'spec_helper' + +RSpec.describe 'admin issues labels' do + include WaitForAjax + + let!(:bug_label) { Label.create(title: 'bug', template: true) } + let!(:feature_label) { Label.create(title: 'feature', template: true) } + + before do + login_as :admin + end + + describe 'list' do + before do + visit admin_labels_path + end + + it 'renders labels list' do + page.within '.manage-labels-list' do + expect(page).to have_content('bug') + expect(page).to have_content('feature') + end + end + + it 'deletes label' do + page.within "#label_#{bug_label.id}" do + click_link 'Delete' + end + + page.within '.manage-labels-list' do + expect(page).not_to have_content('bug') + end + end + + it 'deletes all labels', js: true do + page.within '.labels' do + page.all('.btn-remove').each do |remove| + wait_for_ajax + remove.click + end + end + + page.within '.manage-labels-list' do + expect(page).not_to have_content('bug') + expect(page).not_to have_content('feature_label') + end + end + end + + describe 'create' do + before do + visit new_admin_label_path + end + + it 'creates new label' do + fill_in 'Title', with: 'support' + fill_in 'Background color', with: '#F95610' + click_button 'Save' + + page.within '.manage-labels-list' do + expect(page).to have_content('support') + end + end + + it 'does not creates label with invalid color' do + fill_in 'Title', with: 'support' + fill_in 'Background color', with: '#12' + click_button 'Save' + + page.within '.label-form' do + expect(page).to have_content('Color must be a valid color code') + end + end + + it 'does not creates label if label already exists' do + fill_in 'Title', with: 'bug' + fill_in 'Background color', with: '#F95610' + click_button 'Save' + + page.within '.label-form' do + expect(page).to have_content 'Title has already been taken' + end + end + end + + describe 'edit' do + it 'changes bug label' do + visit edit_admin_label_path(bug_label) + + fill_in 'Title', with: 'fix' + fill_in 'Background color', with: '#F15610' + click_button 'Save' + + page.within '.manage-labels-list' do + expect(page).to have_content('fix') + end + end + end +end From 8607383e1dcdde62a2143890f02a72141dd44af1 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 19 Dec 2016 18:11:18 +0800 Subject: [PATCH 43/75] Just implement it in the block Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8088#note_20223109 --- lib/gitlab/serialize/ci/variables.rb | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/lib/gitlab/serialize/ci/variables.rb b/lib/gitlab/serialize/ci/variables.rb index 0ca060cd95e..8919f0ccd00 100644 --- a/lib/gitlab/serialize/ci/variables.rb +++ b/lib/gitlab/serialize/ci/variables.rb @@ -12,20 +12,16 @@ module Gitlab object = YAML.safe_load(string, [Symbol]) - object.map(&Variables.method(:convert_key_value_to_string)) + object.map do |variable| + variable[:key] = variable[:key].to_s + variable[:value] = variable[:value].to_s + variable + end end def dump(object) YAML.dump(object) end - - private - - def convert_key_value_to_string(variable) - variable[:key] = variable[:key].to_s - variable[:value] = variable[:value].to_s - variable - end end end end From d6edecdeec9b41b4509775ae5b2e01ee15b6d9dc Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 19 Dec 2016 11:26:57 +0100 Subject: [PATCH 44/75] Fix duplicated build token problem and added relevant spec --- changelogs/unreleased/fix-import-export-build-token.yml | 4 ++++ lib/gitlab/import_export/relation_factory.rb | 2 ++ spec/lib/gitlab/import_export/project.json | 4 +++- .../gitlab/import_export/project_tree_restorer_spec.rb | 8 ++++++++ 4 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/fix-import-export-build-token.yml diff --git a/changelogs/unreleased/fix-import-export-build-token.yml b/changelogs/unreleased/fix-import-export-build-token.yml new file mode 100644 index 00000000000..622487e6829 --- /dev/null +++ b/changelogs/unreleased/fix-import-export-build-token.yml @@ -0,0 +1,4 @@ +--- +title: Fix Import/Export duplicated builds error +merge_request: +author: diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index a0e80fccad9..9b590bcee8d 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -99,6 +99,8 @@ module Gitlab def generate_imported_object if BUILD_MODELS.include?(@relation_name) # call #trace= method after assigning the other attributes trace = @relation_hash.delete('trace') + @relation_hash.delete('token') + imported_object do |object| object.trace = trace object.commit_id = nil diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index ed9df468ced..1e5901651ae 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -6548,7 +6548,9 @@ "url": null }, "erased_by_id": null, - "erased_at": null + "erased_at": null, + "type": "Ci::Build", + "token": "abcd" }, { "id": 72, 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 3038ab53ad8..13f593fb2db 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -189,6 +189,14 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do end end end + + context 'existing builds' do + it 'do not throw errors' do + create(:ci_build, token: 'abcd') + + expect(restored_project_json).to be true + end + end end end end From 64d7772b6f0594896eb1ac67d5d3f4c33c813fe3 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 19 Dec 2016 18:43:06 +0800 Subject: [PATCH 45/75] Use a separate method to skip validation Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8091#note_20222666 --- lib/ci/api/builds.rb | 2 +- lib/ci/api/helpers.rb | 9 +++++++-- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/ci/api/builds.rb b/lib/ci/api/builds.rb index 3c4cfccb19a..142bce82286 100644 --- a/lib/ci/api/builds.rb +++ b/lib/ci/api/builds.rb @@ -41,7 +41,7 @@ module Ci put ":id" do authenticate_runner! build = Ci::Build.where(runner_id: current_runner.id).running.find(params[:id]) - authenticate_build!(build, verify_token: false) + validate_build!(build) update_runner_info diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb index 0202b3cf8a3..51b05aa0cb6 100644 --- a/lib/ci/api/helpers.rb +++ b/lib/ci/api/helpers.rb @@ -13,9 +13,14 @@ module Ci forbidden! unless current_runner end - def authenticate_build!(build, verify_token: true) + def authenticate_build!(build) + not_found! unless build + forbidden! if !build_token_valid?(build) + validate_build!(build) + end + + def validate_build!(build) not_found! unless build - forbidden! if verify_token && !build_token_valid?(build) forbidden!('Project has been deleted!') unless build.project forbidden!('Build has been erased!') if build.erased? end From fb23153343f274a29cba1023759f675aaf64251a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 19 Dec 2016 18:50:01 +0800 Subject: [PATCH 46/75] Delete the project when building the build Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8091#note_20222756 --- spec/requests/ci/api/builds_spec.rb | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index d61a9afd12e..2963fe85478 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -332,21 +332,18 @@ describe Ci::API::Builds do context 'when project for the build has been deleted' do let(:build) do - create(:ci_build, - :pending, - :trace, - runner_id: runner.id, - pipeline: pipeline) + result = create(:ci_build, + :pending, + :trace, + runner_id: runner.id, + pipeline: pipeline) + result.project.update(pending_delete: true) + result end it 'responds with forbidden' do expect(response.status).to eq 403 end - - def initial_patch_the_trace - build.project.update(pending_delete: true) - super - end end end From ec003d9eb338f9172696f5540637e759a93f9fcf Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 19 Dec 2016 19:14:21 +0800 Subject: [PATCH 47/75] Prefer unless over if not Feedback: https://gitlab.com/gitlab-org/gitlab-ce/builds/7606797 --- lib/ci/api/helpers.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb index 51b05aa0cb6..62c10c3b753 100644 --- a/lib/ci/api/helpers.rb +++ b/lib/ci/api/helpers.rb @@ -15,7 +15,7 @@ module Ci def authenticate_build!(build) not_found! unless build - forbidden! if !build_token_valid?(build) + forbidden! unless build_token_valid?(build) validate_build!(build) end From e6842ff5643c7fc88a65af24b56682f47f4be2de Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 19 Dec 2016 13:32:37 +0100 Subject: [PATCH 48/75] Improve code design --- .../project_services/chat_slash_commands_service.rb | 2 +- .../project_services/slack_slash_commands_service.rb | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/app/models/project_services/chat_slash_commands_service.rb b/app/models/project_services/chat_slash_commands_service.rb index 12261e9821e..0bc160af604 100644 --- a/app/models/project_services/chat_slash_commands_service.rb +++ b/app/models/project_services/chat_slash_commands_service.rb @@ -28,7 +28,7 @@ class ChatSlashCommandsService < Service end def trigger(params) - return nil unless valid_token?(params[:token]) + return unless valid_token?(params[:token]) user = find_chat_user(params) unless user diff --git a/app/models/project_services/slack_slash_commands_service.rb b/app/models/project_services/slack_slash_commands_service.rb index 6782ba5ad0a..cb19ebf4cad 100644 --- a/app/models/project_services/slack_slash_commands_service.rb +++ b/app/models/project_services/slack_slash_commands_service.rb @@ -14,13 +14,15 @@ class SlackSlashCommandsService < ChatSlashCommandsService end def trigger(params) - result = super - # Format messages to be Slack-compatible - if result && result[:text] - result[:text] = Slack::Notifier::LinkFormatter.format(result[:text]) + super.tap do |result| + result[:text] = format(result[:text]) end + end - result + private + + def format(text) + Slack::Notifier::LinkFormatter.format(text) if text end end From b1ccf99e87605216f7d5733d6a4ffb4530d4cfc9 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 19 Dec 2016 13:34:03 +0100 Subject: [PATCH 49/75] Fix previously reverted spec failures --- .../chat_message/build_message_spec.rb | 8 +++--- .../chat_message/issue_message_spec.rb | 10 +++---- .../chat_message/merge_message_spec.rb | 12 ++++----- .../chat_message/note_message_spec.rb | 22 ++++++++-------- .../chat_message/pipeline_message_spec.rb | 8 +++--- .../chat_message/push_message_spec.rb | 26 +++++++++---------- .../chat_message/wiki_page_message_spec.rb | 8 +++--- 7 files changed, 47 insertions(+), 47 deletions(-) diff --git a/spec/models/project_services/chat_message/build_message_spec.rb b/spec/models/project_services/chat_message/build_message_spec.rb index b71d153f814..50ad5013df9 100644 --- a/spec/models/project_services/chat_message/build_message_spec.rb +++ b/spec/models/project_services/chat_message/build_message_spec.rb @@ -10,7 +10,7 @@ describe ChatMessage::BuildMessage do tag: false, project_name: 'project_name', - project_url: 'example.gitlab.com', + project_url: 'http://example.gitlab.com', commit: { status: status, @@ -48,10 +48,10 @@ describe ChatMessage::BuildMessage do end def build_message(status_text = status) - ":" \ - " Commit :" \ + " Commit " \ - " of branch" \ + " of branch" \ " by hacker #{status_text} in #{duration} #{'second'.pluralize(duration)}" end end diff --git a/spec/models/project_services/chat_message/issue_message_spec.rb b/spec/models/project_services/chat_message/issue_message_spec.rb index ebe0ead4408..190ff4c535d 100644 --- a/spec/models/project_services/chat_message/issue_message_spec.rb +++ b/spec/models/project_services/chat_message/issue_message_spec.rb @@ -10,14 +10,14 @@ describe ChatMessage::IssueMessage, models: true do username: 'test.user' }, project_name: 'project_name', - project_url: 'somewhere.com', + project_url: 'http://somewhere.com', object_attributes: { title: 'Issue title', id: 10, iid: 100, assignee_id: 1, - url: 'url', + url: 'http://url.com', action: 'open', state: 'opened', description: 'issue description' @@ -40,11 +40,11 @@ describe ChatMessage::IssueMessage, models: true do context 'open' do it 'returns a message regarding opening of issues' do expect(subject.pretext).to eq( - '] Issue opened by test.user') + '[] Issue opened by test.user') expect(subject.attachments).to eq([ { title: "#100 Issue title", - title_link: "url", + title_link: "http://url.com", text: "issue description", color: color, } @@ -60,7 +60,7 @@ describe ChatMessage::IssueMessage, models: true do it 'returns a message regarding closing of issues' do expect(subject.pretext). to eq( - '] Issue closed by test.user') + '[] Issue closed by test.user') expect(subject.attachments).to be_empty end end diff --git a/spec/models/project_services/chat_message/merge_message_spec.rb b/spec/models/project_services/chat_message/merge_message_spec.rb index 07c414c6ca4..cc154112e90 100644 --- a/spec/models/project_services/chat_message/merge_message_spec.rb +++ b/spec/models/project_services/chat_message/merge_message_spec.rb @@ -10,14 +10,14 @@ describe ChatMessage::MergeMessage, models: true do username: 'test.user' }, project_name: 'project_name', - project_url: 'somewhere.com', + project_url: 'http://somewhere.com', object_attributes: { title: "Issue title\nSecond line", id: 10, iid: 100, assignee_id: 1, - url: 'url', + url: 'http://url.com', state: 'opened', description: 'issue description', source_branch: 'source_branch', @@ -31,8 +31,8 @@ describe ChatMessage::MergeMessage, models: true do context 'open' do it 'returns a message regarding opening of merge requests' do expect(subject.pretext).to eq( - 'test.user opened '\ - 'in : *Issue title*') + 'test.user opened '\ + 'in : *Issue title*') expect(subject.attachments).to be_empty end end @@ -43,8 +43,8 @@ describe ChatMessage::MergeMessage, models: true do end it 'returns a message regarding closing of merge requests' do expect(subject.pretext).to eq( - 'test.user closed '\ - 'in : *Issue title*') + 'test.user closed '\ + 'in : *Issue title*') expect(subject.attachments).to be_empty end end diff --git a/spec/models/project_services/chat_message/note_message_spec.rb b/spec/models/project_services/chat_message/note_message_spec.rb index 31936da40a2..da700a08e57 100644 --- a/spec/models/project_services/chat_message/note_message_spec.rb +++ b/spec/models/project_services/chat_message/note_message_spec.rb @@ -11,15 +11,15 @@ describe ChatMessage::NoteMessage, models: true do avatar_url: 'http://fakeavatar' }, project_name: 'project_name', - project_url: 'somewhere.com', + project_url: 'http://somewhere.com', repository: { name: 'project_name', - url: 'somewhere.com', + url: 'http://somewhere.com', }, object_attributes: { id: 10, note: 'comment on a commit', - url: 'url', + url: 'http://url.com', noteable_type: 'Commit' } } @@ -37,8 +37,8 @@ describe ChatMessage::NoteMessage, models: true do it 'returns a message regarding notes on commits' do message = described_class.new(@args) - expect(message.pretext).to eq("test.user in : " \ + expect(message.pretext).to eq("test.user in : " \ "*Added a commit message*") expected_attachments = [ { @@ -63,8 +63,8 @@ describe ChatMessage::NoteMessage, models: true do it 'returns a message regarding notes on a merge request' do message = described_class.new(@args) - expect(message.pretext).to eq("test.user in : " \ + expect(message.pretext).to eq("test.user in : " \ "*merge request title*") expected_attachments = [ { @@ -90,8 +90,8 @@ describe ChatMessage::NoteMessage, models: true do it 'returns a message regarding notes on an issue' do message = described_class.new(@args) expect(message.pretext).to eq( - "test.user in : " \ + "test.user in : " \ "*issue title*") expected_attachments = [ { @@ -115,8 +115,8 @@ describe ChatMessage::NoteMessage, models: true do it 'returns a message regarding notes on a project snippet' do message = described_class.new(@args) - expect(message.pretext).to eq("test.user in : " \ + expect(message.pretext).to eq("test.user in : " \ "*snippet title*") expected_attachments = [ { diff --git a/spec/models/project_services/chat_message/pipeline_message_spec.rb b/spec/models/project_services/chat_message/pipeline_message_spec.rb index eca71db07b6..bf2a9616455 100644 --- a/spec/models/project_services/chat_message/pipeline_message_spec.rb +++ b/spec/models/project_services/chat_message/pipeline_message_spec.rb @@ -15,7 +15,7 @@ describe ChatMessage::PipelineMessage do duration: duration }, project: { path_with_namespace: 'project_name', - web_url: 'example.gitlab.com' }, + web_url: 'http://example.gitlab.com' }, user: user } end @@ -59,9 +59,9 @@ describe ChatMessage::PipelineMessage do end def build_message(status_text = status, name = user[:name]) - ":" \ - " Pipeline " \ - " of branch" \ + ":" \ + " Pipeline " \ + " of branch" \ " by #{name} #{status_text} in #{duration} #{'second'.pluralize(duration)}" end end diff --git a/spec/models/project_services/chat_message/push_message_spec.rb b/spec/models/project_services/chat_message/push_message_spec.rb index b781c4505db..24928873bad 100644 --- a/spec/models/project_services/chat_message/push_message_spec.rb +++ b/spec/models/project_services/chat_message/push_message_spec.rb @@ -10,7 +10,7 @@ describe ChatMessage::PushMessage, models: true do project_name: 'project_name', ref: 'refs/heads/master', user_name: 'test.user', - project_url: 'url' + project_url: 'http://url.com' } end @@ -19,20 +19,20 @@ describe ChatMessage::PushMessage, models: true do context 'push' do before do args[:commits] = [ - { message: 'message1', url: 'url1', id: 'abcdefghijkl', author: { name: 'author1' } }, - { message: 'message2', url: 'url2', id: '123456789012', author: { name: 'author2' } }, + { message: 'message1', url: 'http://url1.com', id: 'abcdefghijkl', author: { name: 'author1' } }, + { message: 'message2', url: 'http://url2.com', id: '123456789012', author: { name: 'author2' } }, ] end it 'returns a message regarding pushes' do expect(subject.pretext).to eq( - 'test.user pushed to branch of '\ - ' ()' + 'test.user pushed to branch of '\ + ' ()' ) expect(subject.attachments).to eq([ { - text: ": message1 - author1\n"\ - ": message2 - author2", + text: ": message1 - author1\n"\ + ": message2 - author2", color: color, } ]) @@ -47,14 +47,14 @@ describe ChatMessage::PushMessage, models: true do project_name: 'project_name', ref: 'refs/tags/new_tag', user_name: 'test.user', - project_url: 'url' + project_url: 'http://url.com' } end it 'returns a message regarding pushes' do expect(subject.pretext).to eq('test.user pushed new tag ' \ - ' to ' \ - '') + ' to ' \ + '') expect(subject.attachments).to be_empty end end @@ -66,8 +66,8 @@ describe ChatMessage::PushMessage, models: true do it 'returns a message regarding a new branch' do expect(subject.pretext).to eq( - 'test.user pushed new branch to '\ - '' + 'test.user pushed new branch to '\ + '' ) expect(subject.attachments).to be_empty end @@ -80,7 +80,7 @@ describe ChatMessage::PushMessage, models: true do it 'returns a message regarding a removed branch' do expect(subject.pretext).to eq( - 'test.user removed branch master from ' + 'test.user removed branch master from ' ) expect(subject.attachments).to be_empty end diff --git a/spec/models/project_services/chat_message/wiki_page_message_spec.rb b/spec/models/project_services/chat_message/wiki_page_message_spec.rb index 94c04dc0865..a2ad61e38e7 100644 --- a/spec/models/project_services/chat_message/wiki_page_message_spec.rb +++ b/spec/models/project_services/chat_message/wiki_page_message_spec.rb @@ -10,10 +10,10 @@ describe ChatMessage::WikiPageMessage, models: true do username: 'test.user' }, project_name: 'project_name', - project_url: 'somewhere.com', + project_url: 'http://somewhere.com', object_attributes: { title: 'Wiki page title', - url: 'url', + url: 'http://url.com', content: 'Wiki page description' } } @@ -25,7 +25,7 @@ describe ChatMessage::WikiPageMessage, models: true do it 'returns a message that a new wiki page was created' do expect(subject.pretext).to eq( - 'test.user created in : '\ + 'test.user created in : '\ '*Wiki page title*') end end @@ -35,7 +35,7 @@ describe ChatMessage::WikiPageMessage, models: true do it 'returns a message that a wiki page was updated' do expect(subject.pretext).to eq( - 'test.user edited in : '\ + 'test.user edited in : '\ '*Wiki page title*') end end From f2afdc92b8029f628f6f8ed2d5b9dc1323fd3295 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 19 Dec 2016 21:09:40 +0800 Subject: [PATCH 50/75] Test if expanded_environment_name could expand var with symbols. Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8088#note_20234245 --- spec/models/build_spec.rb | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index 5a47e7ddf0d..e97f6ae3cea 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -1306,11 +1306,25 @@ describe Ci::Build, models: true do describe '#expanded_environment_name' do subject { build.expanded_environment_name } - context 'when environment uses variables' do - let(:build) { create(:ci_build, ref: 'master', environment: 'review/$CI_BUILD_REF_NAME') } + context 'when environment uses $CI_BUILD_REF_NAME' do + let(:build) do + create(:ci_build, + ref: 'master', + environment: 'review/$CI_BUILD_REF_NAME') + end it { is_expected.to eq('review/master') } end + + context 'when environment uses yaml_variables containing symbol keys' do + let(:build) do + create(:ci_build, + yaml_variables: [{key: :APP_HOST, value: 'host'}], + environment: 'review/$APP_HOST') + end + + it { is_expected.to eq('review/host') } + end end describe '#detailed_status' do From 3ea8d983adc467c64c91b2cad91486555678c958 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 19 Dec 2016 21:15:47 +0800 Subject: [PATCH 51/75] Keep the value type for YAML variables Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8088#note_20235080 --- lib/ci/gitlab_ci_yaml_processor.rb | 2 +- lib/gitlab/serialize/ci/variables.rb | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index 8806a506ffa..7463bd719d5 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -118,7 +118,7 @@ module Ci .merge(job_variables(name)) variables.map do |key, value| - { key: key.to_s, value: value.to_s, public: true } + { key: key.to_s, value: value, public: true } end end diff --git a/lib/gitlab/serialize/ci/variables.rb b/lib/gitlab/serialize/ci/variables.rb index 8919f0ccd00..3a9443bfcd9 100644 --- a/lib/gitlab/serialize/ci/variables.rb +++ b/lib/gitlab/serialize/ci/variables.rb @@ -14,7 +14,6 @@ module Gitlab object.map do |variable| variable[:key] = variable[:key].to_s - variable[:value] = variable[:value].to_s variable end end From 527428a78ed4834b936b0aa7a6da430bf66baa2d Mon Sep 17 00:00:00 2001 From: James Lopez Date: Mon, 19 Dec 2016 14:38:23 +0100 Subject: [PATCH 52/75] updated spec --- spec/lib/gitlab/import_export/project_tree_restorer_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 13f593fb2db..4b07fa53bf5 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -190,8 +190,8 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do end end - context 'existing builds' do - it 'do not throw errors' do + context 'when there is an existing build with build token' do + it 'restores project json correctly' do create(:ci_build, token: 'abcd') expect(restored_project_json).to be true From 1e2e0de9441a2f9777bb989e8a8c275c2b103ca7 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 19 Dec 2016 22:01:37 +0800 Subject: [PATCH 53/75] Define actions in let so that it could be overridden Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8091/diffs#note_20236356 --- spec/requests/ci/api/builds_spec.rb | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 2963fe85478..fdb2234d32b 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -249,7 +249,13 @@ describe Ci::API::Builds do end describe 'PATCH /builds/:id/trace.txt' do - let(:build) { create(:ci_build, :pending, :trace, runner_id: runner.id) } + let(:build) do + attributes = {runner_id: runner.id, pipeline: pipeline} + create(:ci_build, :pending, :trace, attributes) do |build| + build.run + end + end + let(:headers) { { Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token, 'Content-Type' => 'text/plain' } } let(:headers_with_range) { headers.merge({ 'Content-Range' => '11-20' }) } let(:update_interval) { 10.seconds.to_i } @@ -276,7 +282,6 @@ describe Ci::API::Builds do end before do - build.run! initial_patch_the_trace end @@ -332,17 +337,15 @@ describe Ci::API::Builds do context 'when project for the build has been deleted' do let(:build) do - result = create(:ci_build, - :pending, - :trace, - runner_id: runner.id, - pipeline: pipeline) - result.project.update(pending_delete: true) - result + attributes = {runner_id: runner.id, pipeline: pipeline} + create(:ci_build, :pending, :trace, attributes) do |build| + build.run + build.project.update(pending_delete: true) + end end it 'responds with forbidden' do - expect(response.status).to eq 403 + expect(response.status).to eq(403) end end end From e6c83b1c1f3db5cea9730ae320f3a6525fd6fe2a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 19 Dec 2016 22:19:03 +0800 Subject: [PATCH 54/75] Just set the status rather than calling event Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8091#note_20239558 --- spec/requests/ci/api/builds_spec.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index fdb2234d32b..5acda0fd729 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -251,9 +251,7 @@ describe Ci::API::Builds do describe 'PATCH /builds/:id/trace.txt' do let(:build) do attributes = {runner_id: runner.id, pipeline: pipeline} - create(:ci_build, :pending, :trace, attributes) do |build| - build.run - end + create(:ci_build, :running, :trace, attributes) end let(:headers) { { Ci::API::Helpers::BUILD_TOKEN_HEADER => build.token, 'Content-Type' => 'text/plain' } } @@ -338,8 +336,7 @@ describe Ci::API::Builds do context 'when project for the build has been deleted' do let(:build) do attributes = {runner_id: runner.id, pipeline: pipeline} - create(:ci_build, :pending, :trace, attributes) do |build| - build.run + create(:ci_build, :running, :trace, attributes) do |build| build.project.update(pending_delete: true) end end From 8e2ea26cc1fff5787c0cf0c4b0160e1815ee1344 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 19 Dec 2016 22:22:57 +0800 Subject: [PATCH 55/75] Spaces for literal hash --- spec/models/build_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/build_spec.rb b/spec/models/build_spec.rb index e97f6ae3cea..cd3b6d51545 100644 --- a/spec/models/build_spec.rb +++ b/spec/models/build_spec.rb @@ -1319,7 +1319,7 @@ describe Ci::Build, models: true do context 'when environment uses yaml_variables containing symbol keys' do let(:build) do create(:ci_build, - yaml_variables: [{key: :APP_HOST, value: 'host'}], + yaml_variables: [{ key: :APP_HOST, value: 'host' }], environment: 'review/$APP_HOST') end From 298d05a5c3cc3c2f1daa4d77c45f9c90b53248df Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 19 Dec 2016 15:40:06 +0100 Subject: [PATCH 56/75] Improve after feedback --- features/project/service.feature | 6 ++-- features/steps/project/services.rb | 8 +++--- lib/api/services.rb | 9 +++++- lib/gitlab/chat_commands/help.rb | 28 ------------------- lib/gitlab/chat_commands/presenter.rb | 2 +- .../services/slack_slash_command_spec.rb | 18 ++++++------ .../mattermost_slash_commands_service_spec.rb | 4 +-- .../slack_slash_commands_service.rb | 6 ++-- .../chat_slash_commands_shared_examples.rb | 2 +- 9 files changed, 32 insertions(+), 51 deletions(-) delete mode 100644 lib/gitlab/chat_commands/help.rb diff --git a/features/project/service.feature b/features/project/service.feature index 3a7b8308524..892db48d785 100644 --- a/features/project/service.feature +++ b/features/project/service.feature @@ -39,9 +39,9 @@ Feature: Project Services Scenario: Activate Slack service When I visit project "Shop" services page - And I click Slack service link - And I fill Slack settings - Then I should see Slack service settings saved + And I click Slack Notifications service link + And I fill Slack Notifications settings + Then I should see Slack Notifications service settings saved Scenario: Activate Pushover service When I visit project "Shop" services page diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb index bd6466f3686..06a1afedbd9 100644 --- a/features/steps/project/services.rb +++ b/features/steps/project/services.rb @@ -137,17 +137,17 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps expect(find_field('Colorize messages').value).to eq '1' end - step 'I click Slack service link' do - click_link 'Slack' + step 'I click Slack Notifications service link' do + click_link 'Slack Notifications' end - step 'I fill Slack settings' do + step 'I fill Slack Notifications settings' do check 'Active' fill_in 'Webhook', with: 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' click_button 'Save' end - step 'I should see Slack service settings saved' do + step 'I should see Slack Notifications service settings saved' do expect(find_field('Webhook').value).to eq 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' end diff --git a/lib/api/services.rb b/lib/api/services.rb index 59232c84c24..aa97f6af0b2 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -378,7 +378,6 @@ module API desc: 'A custom certificate authority bundle to verify the Kubernetes cluster with (PEM format)' }, ], - 'mattermost-slash-commands' => [ { required: true, @@ -387,6 +386,14 @@ module API desc: 'The Mattermost token' } ], + 'slack-slash-commands' => [ + { + required: true, + name: :token, + type: String, + desc: 'The Slack token' + } + ], 'pipelines-email' => [ { required: true, diff --git a/lib/gitlab/chat_commands/help.rb b/lib/gitlab/chat_commands/help.rb deleted file mode 100644 index e76733f5445..00000000000 --- a/lib/gitlab/chat_commands/help.rb +++ /dev/null @@ -1,28 +0,0 @@ -module Gitlab - module ChatCommands - class Help < BaseCommand - # This class has to be used last, as it always matches. It has to match - # because other commands were not triggered and we want to show the help - # command - def self.match(_text) - true - end - - def self.help_message - 'help' - end - - def self.allowed?(_project, _user) - true - end - - def execute(commands) - Gitlab::ChatCommands::Presenters::Help.new(commands).present(trigger) - end - - def trigger - params[:command] - end - end - end -end diff --git a/lib/gitlab/chat_commands/presenter.rb b/lib/gitlab/chat_commands/presenter.rb index b4c4dc252ca..caceaa25391 100644 --- a/lib/gitlab/chat_commands/presenter.rb +++ b/lib/gitlab/chat_commands/presenter.rb @@ -1,7 +1,7 @@ module Gitlab module ChatCommands class Presenter - include Gitlab::Routing.url_helpers + include Gitlab::Routing def authorize_chat_name(url) message = if url diff --git a/spec/features/projects/services/slack_slash_command_spec.rb b/spec/features/projects/services/slack_slash_command_spec.rb index dee43d69895..70e203efcf5 100644 --- a/spec/features/projects/services/slack_slash_command_spec.rb +++ b/spec/features/projects/services/slack_slash_command_spec.rb @@ -1,18 +1,18 @@ require 'spec_helper' -feature 'Setup Slack slash commands', feature: true do +feature 'Slack slash commands', feature: true do include WaitForAjax - let(:user) { create(:user) } - let(:project) { create(:project) } - let(:service) { project.create_slack_slash_commands_service } + given(:user) { create(:user) } + given(:project) { create(:project) } + given(:service) { project.create_slack_slash_commands_service } - before do + background do project.team << [user, :master] login_as(user) end - describe 'user visits the slack slash command config page', js: true do + scenario 'user visits the slack slash command config page', js: true do it 'shows a help message' do visit edit_namespace_project_service_path(project.namespace, project, service) @@ -22,8 +22,8 @@ feature 'Setup Slack slash commands', feature: true do end end - describe 'saving a token' do - let(:token) { ('a'..'z').to_a.join } + scenario 'saving a token' do + given(:token) { ('a'..'z').to_a.join } it 'shows the token after saving' do visit edit_namespace_project_service_path(project.namespace, project, service) @@ -37,7 +37,7 @@ feature 'Setup Slack slash commands', feature: true do end end - describe 'the trigger url' do + scenario 'the trigger url' do it 'shows the correct url' do visit edit_namespace_project_service_path(project.namespace, project, service) diff --git a/spec/models/project_services/mattermost_slash_commands_service_spec.rb b/spec/models/project_services/mattermost_slash_commands_service_spec.rb index 5c34cb6b4cf..1ae1483e2a4 100644 --- a/spec/models/project_services/mattermost_slash_commands_service_spec.rb +++ b/spec/models/project_services/mattermost_slash_commands_service_spec.rb @@ -1,5 +1,5 @@ require 'spec_helper' -describe MattermostSlashCommandsService, models: true do - it_behaves_like "chat slash commands" +describe MattermostSlashCommandsService, :models do + it_behaves_like "chat slash commands service" end diff --git a/spec/models/project_services/slack_slash_commands_service.rb b/spec/models/project_services/slack_slash_commands_service.rb index c3fa80caebe..5775e439906 100644 --- a/spec/models/project_services/slack_slash_commands_service.rb +++ b/spec/models/project_services/slack_slash_commands_service.rb @@ -1,7 +1,7 @@ require 'spec_helper' -describe SlackSlashCommandsService, models: true do - it_behaves_like "chat slash commands" +describe SlackSlashCommandsService, :models do + it_behaves_like "chat slash commands service" describe '#trigger' do context 'when an auth url is generated' do @@ -15,11 +15,13 @@ describe SlackSlashCommandsService, models: true do token: 'token' } end + let(:service) do project.create_slack_slash_commands_service( properties: { token: 'token' } ) end + let(:authorize_url) do 'http://authorize.example.com/' end diff --git a/spec/support/chat_slash_commands_shared_examples.rb b/spec/support/chat_slash_commands_shared_examples.rb index 96130b45235..4dfa29849ee 100644 --- a/spec/support/chat_slash_commands_shared_examples.rb +++ b/spec/support/chat_slash_commands_shared_examples.rb @@ -1,4 +1,4 @@ -RSpec.shared_examples 'chat slash commands' do +RSpec.shared_examples 'chat slash commands service' do describe "Associations" do it { is_expected.to respond_to :token } it { is_expected.to have_many :chat_names } From a0690c4c01fceccef9efca70a9256f5790fba9c7 Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 19 Dec 2016 23:20:22 +0800 Subject: [PATCH 57/75] Spaces around literal hash Feedback: https://gitlab.com/gitlab-org/gitlab-ce/builds/7617209 --- spec/requests/ci/api/builds_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/requests/ci/api/builds_spec.rb b/spec/requests/ci/api/builds_spec.rb index 5acda0fd729..79f12ace999 100644 --- a/spec/requests/ci/api/builds_spec.rb +++ b/spec/requests/ci/api/builds_spec.rb @@ -250,7 +250,7 @@ describe Ci::API::Builds do describe 'PATCH /builds/:id/trace.txt' do let(:build) do - attributes = {runner_id: runner.id, pipeline: pipeline} + attributes = { runner_id: runner.id, pipeline: pipeline } create(:ci_build, :running, :trace, attributes) end @@ -335,7 +335,7 @@ describe Ci::API::Builds do context 'when project for the build has been deleted' do let(:build) do - attributes = {runner_id: runner.id, pipeline: pipeline} + attributes = { runner_id: runner.id, pipeline: pipeline } create(:ci_build, :running, :trace, attributes) do |build| build.project.update(pending_delete: true) end From 4d8a2bc987dbdd417f7ae971c884b084a8982f0a Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Mon, 19 Dec 2016 23:32:09 +0800 Subject: [PATCH 58/75] Fix tests because now we don't convert values --- spec/lib/gitlab/serialize/ci/variables_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/lib/gitlab/serialize/ci/variables_spec.rb b/spec/lib/gitlab/serialize/ci/variables_spec.rb index 797baef640c..7ea74da5252 100644 --- a/spec/lib/gitlab/serialize/ci/variables_spec.rb +++ b/spec/lib/gitlab/serialize/ci/variables_spec.rb @@ -10,9 +10,9 @@ describe Gitlab::Serialize::Ci::Variables do { key: 'wee', value: 1, public: false }] end - it 'converts key and values into strings' do + it 'converts keys into strings' do is_expected.to eq([ { key: 'key', value: 'value', public: true }, - { key: 'wee', value: '1', public: false }]) + { key: 'wee', value: 1, public: false }]) end end From 786457ca3e77e6af02ecabcfd6d5eae6a31837c1 Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 16 Dec 2016 16:13:46 +0100 Subject: [PATCH 59/75] Fix for missing service when importing from EE to CE MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- .../fix-import-export-ee-services.yml | 4 ++++ .../import_export/project_tree_restorer.rb | 2 +- lib/gitlab/import_export/relation_factory.rb | 7 ++++++ spec/lib/gitlab/import_export/project.json | 22 +++++++++++++++++++ 4 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/fix-import-export-ee-services.yml diff --git a/changelogs/unreleased/fix-import-export-ee-services.yml b/changelogs/unreleased/fix-import-export-ee-services.yml new file mode 100644 index 00000000000..c0aacbc96f8 --- /dev/null +++ b/changelogs/unreleased/fix-import-export-ee-services.yml @@ -0,0 +1,4 @@ +--- +title: Fix missing service error importing from EE to CE +merge_request: 8144 +author: diff --git a/lib/gitlab/import_export/project_tree_restorer.rb b/lib/gitlab/import_export/project_tree_restorer.rb index c551321c18d..cda6ddf0443 100644 --- a/lib/gitlab/import_export/project_tree_restorer.rb +++ b/lib/gitlab/import_export/project_tree_restorer.rb @@ -120,7 +120,7 @@ module Gitlab members_mapper: members_mapper, user: @user, project_id: restored_project.id) - end + end.compact relation_hash_list.is_a?(Array) ? relation_array : relation_array.first end diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index 9b590bcee8d..b4f0f761add 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -40,6 +40,8 @@ module Gitlab # the relation_hash, updating references with new object IDs, mapping users using # the "members_mapper" object, also updating notes if required. def create + return nil if unknown_service? + setup_models generate_imported_object @@ -217,6 +219,11 @@ module Gitlab existing_object end end + + def unknown_service? + @relation_name == :services && parsed_relation_hash['type'] && + !Object.const_defined?(parsed_relation_hash['type']) + end end end end diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 1e5901651ae..400fc2a1f91 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -7411,6 +7411,28 @@ "category": "common", "default": false, "wiki_page_events": true + }, + { + "id": 101, + "title": "JenkinsDeprecated", + "project_id": 5, + "created_at": "2016-06-14T15:01:51.031Z", + "updated_at": "2016-06-14T15:01:51.031Z", + "active": false, + "properties": { + + }, + "template": false, + "push_events": true, + "issues_events": true, + "merge_requests_events": true, + "tag_push_events": true, + "note_events": true, + "build_events": true, + "category": "common", + "default": false, + "wiki_page_events": true, + "type": "JenkinsDeprecatedService" } ], "hooks": [ From d80b2677319e5ba5570be4b691a07cbbaea2fd47 Mon Sep 17 00:00:00 2001 From: Alfredo Sumaran Date: Mon, 19 Dec 2016 00:18:06 -0500 Subject: [PATCH 60/75] Check if selected object is valid before passing to calback --- app/assets/javascripts/gl_dropdown.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/gl_dropdown.js b/app/assets/javascripts/gl_dropdown.js index 57dabfe05e4..bb516b3d2df 100644 --- a/app/assets/javascripts/gl_dropdown.js +++ b/app/assets/javascripts/gl_dropdown.js @@ -343,16 +343,18 @@ selector = ".dropdown-page-one .dropdown-content a"; } this.dropdown.on("click", selector, function(e) { - var $el, selected; + var $el, selected, selectedObj, isMarking; $el = $(this); selected = self.rowClicked($el); + selectedObj = selected ? selected[0] : null; + isMarking = selected ? selected[1] : null; if (self.options.clicked) { - self.options.clicked(selected[0], $el, e, selected[1]); + self.options.clicked(selectedObj, $el, e, isMarking); } // Update label right after all modifications in dropdown has been done if (self.options.toggleLabel) { - self.updateLabel(selected[0], $el, self); + self.updateLabel(selectedObj, $el, self); } $el.trigger('blur'); From 0631c7bcdce5dcb2aac94b1b755e63022a62469f Mon Sep 17 00:00:00 2001 From: James Lopez Date: Fri, 16 Dec 2016 09:13:58 +0100 Subject: [PATCH 61/75] Fix MR issue to do with merge user --- app/models/merge_request.rb | 2 +- changelogs/unreleased/fix-import-export-mr-error.yml | 4 ++++ lib/gitlab/import_export/relation_factory.rb | 2 +- spec/lib/gitlab/import_export/project.json | 2 +- 4 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 changelogs/unreleased/fix-import-export-mr-error.yml diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index b73d7acefea..b1914df3020 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -97,7 +97,7 @@ class MergeRequest < ActiveRecord::Base validates :source_branch, presence: true validates :target_project, presence: true validates :target_branch, presence: true - validates :merge_user, presence: true, if: :merge_when_build_succeeds? + validates :merge_user, presence: true, if: :merge_when_build_succeeds?, unless: :importing? validate :validate_branches, unless: [:allow_broken, :importing?, :closed_without_fork?] validate :validate_fork, unless: :closed_without_fork? diff --git a/changelogs/unreleased/fix-import-export-mr-error.yml b/changelogs/unreleased/fix-import-export-mr-error.yml new file mode 100644 index 00000000000..e1137bca131 --- /dev/null +++ b/changelogs/unreleased/fix-import-export-mr-error.yml @@ -0,0 +1,4 @@ +--- +title: Fix Import/Export merge requests error while importing +merge_request: +author: diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index a0e80fccad9..66e54e86ea9 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -14,7 +14,7 @@ module Gitlab priorities: :label_priorities, label: :project_label }.freeze - USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id created_by_id].freeze + USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id created_by_id merge_user_id].freeze PROJECT_REFERENCES = %w[project_id source_project_id gl_project_id target_project_id].freeze diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index ed9df468ced..19bd15bffbc 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -2517,7 +2517,7 @@ "merge_params": { "force_remove_source_branch": null }, - "merge_when_build_succeeds": false, + "merge_when_build_succeeds": true, "merge_user_id": null, "merge_commit_sha": null, "deleted_at": null, From a0c48ec62b64f3b836d8af04c46938d6951911ef Mon Sep 17 00:00:00 2001 From: jurre Date: Tue, 13 Dec 2016 16:22:18 +0100 Subject: [PATCH 62/75] Move Admin Appearance spinach feature to rspec --- features/admin/appearance.feature | 37 ---------- features/steps/admin/appearance.rb | 72 ------------------- features/steps/shared/paths.rb | 4 -- spec/features/admin/admin_appearance_spec.rb | 76 ++++++++++++++++++++ 4 files changed, 76 insertions(+), 113 deletions(-) delete mode 100644 features/admin/appearance.feature delete mode 100644 features/steps/admin/appearance.rb create mode 100644 spec/features/admin/admin_appearance_spec.rb diff --git a/features/admin/appearance.feature b/features/admin/appearance.feature deleted file mode 100644 index 5c1dd7531c1..00000000000 --- a/features/admin/appearance.feature +++ /dev/null @@ -1,37 +0,0 @@ -Feature: Admin Appearance - Scenario: Create new appearance - Given I sign in as an admin - And I visit admin appearance page - When submit form with new appearance - Then I should be redirected to admin appearance page - And I should see newly created appearance - - Scenario: Preview appearance - Given application has custom appearance - And I sign in as an admin - When I visit admin appearance page - And I click preview button - Then I should see a customized appearance - - Scenario: Custom sign-in page - Given application has custom appearance - When I visit login page - Then I should see a customized appearance - - Scenario: Appearance logo - Given application has custom appearance - And I sign in as an admin - And I visit admin appearance page - When I attach a logo - Then I should see a logo - And I remove the logo - Then I should see logo removed - - Scenario: Header logos - Given application has custom appearance - And I sign in as an admin - And I visit admin appearance page - When I attach header logos - Then I should see header logos - And I remove the header logos - Then I should see header logos removed diff --git a/features/steps/admin/appearance.rb b/features/steps/admin/appearance.rb deleted file mode 100644 index 0d1be46d11d..00000000000 --- a/features/steps/admin/appearance.rb +++ /dev/null @@ -1,72 +0,0 @@ -class Spinach::Features::AdminAppearance < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - - step 'submit form with new appearance' do - fill_in 'appearance_title', with: 'MyCompany' - fill_in 'appearance_description', with: 'dev server' - click_button 'Save' - end - - step 'I should be redirected to admin appearance page' do - expect(current_path).to eq admin_appearances_path - expect(page).to have_content 'Appearance settings' - end - - step 'I should see newly created appearance' do - expect(page).to have_field('appearance_title', with: 'MyCompany') - expect(page).to have_field('appearance_description', with: 'dev server') - expect(page).to have_content 'Last edit' - end - - step 'I click preview button' do - click_link "Preview" - end - - step 'application has custom appearance' do - create(:appearance) - end - - step 'I should see a customized appearance' do - expect(page).to have_content appearance.title - expect(page).to have_content appearance.description - end - - step 'I attach a logo' do - attach_file(:appearance_logo, Rails.root.join('spec', 'fixtures', 'dk.png')) - click_button 'Save' - end - - step 'I attach header logos' do - attach_file(:appearance_header_logo, Rails.root.join('spec', 'fixtures', 'dk.png')) - click_button 'Save' - end - - step 'I should see a logo' do - expect(page).to have_xpath('//img[@src="/uploads/appearance/logo/1/dk.png"]') - end - - step 'I should see header logos' do - expect(page).to have_xpath('//img[@src="/uploads/appearance/header_logo/1/dk.png"]') - end - - step 'I remove the logo' do - click_link 'Remove logo' - end - - step 'I remove the header logos' do - click_link 'Remove header logo' - end - - step 'I should see logo removed' do - expect(page).not_to have_xpath('//img[@src="/uploads/appearance/logo/1/gitlab_logo.png"]') - end - - step 'I should see header logos removed' do - expect(page).not_to have_xpath('//img[@src="/uploads/appearance/header_logo/1/header_logo_light.png"]') - end - - def appearance - Appearance.last - end -end diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index a78d0a775ba..15b81fa529b 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -195,10 +195,6 @@ module SharedPaths visit admin_groups_path end - step 'I visit admin appearance page' do - visit admin_appearances_path - end - step 'I visit admin teams page' do visit admin_teams_path end diff --git a/spec/features/admin/admin_appearance_spec.rb b/spec/features/admin/admin_appearance_spec.rb new file mode 100644 index 00000000000..96d715ef383 --- /dev/null +++ b/spec/features/admin/admin_appearance_spec.rb @@ -0,0 +1,76 @@ +require 'spec_helper' + +feature 'Admin Appearance', feature: true do + let!(:appearance) { create(:appearance) } + + scenario 'Create new appearance' do + login_as :admin + visit admin_appearances_path + + fill_in 'appearance_title', with: 'MyCompany' + fill_in 'appearance_description', with: 'dev server' + click_button 'Save' + + expect(current_path).to eq admin_appearances_path + expect(page).to have_content 'Appearance settings' + + expect(page).to have_field('appearance_title', with: 'MyCompany') + expect(page).to have_field('appearance_description', with: 'dev server') + expect(page).to have_content 'Last edit' + end + + scenario 'Preview appearance' do + login_as :admin + + visit admin_appearances_path + click_link "Preview" + + expect_page_has_custom_appearance(appearance) + end + + scenario 'Custom sign-in page' do + visit new_user_session_path + expect_page_has_custom_appearance(appearance) + end + + scenario 'Appearance logo' do + login_as :admin + visit admin_appearances_path + + attach_file(:appearance_logo, logo_fixture) + click_button 'Save' + expect(page).to have_css(logo_selector) + + click_link 'Remove logo' + expect(page).not_to have_css(logo_selector) + end + + scenario 'Header logos' do + login_as :admin + visit admin_appearances_path + + attach_file(:appearance_header_logo, logo_fixture) + click_button 'Save' + expect(page).to have_css(header_logo_selector) + + click_link 'Remove header logo' + expect(page).not_to have_css(header_logo_selector) + end + + def expect_page_has_custom_appearance(appearance) + expect(page).to have_content appearance.title + expect(page).to have_content appearance.description + end + + def logo_selector + '//img[@src^="/uploads/appearance/logo"]' + end + + def header_logo_selector + '//img[@src^="/uploads/appearance/header_logo"]' + end + + def logo_fixture + Rails.root.join('spec', 'fixtures', 'dk.png') + end +end From bf6b1078521b6461caeffb3a0ba003c5c5ac2a9e Mon Sep 17 00:00:00 2001 From: jurre Date: Tue, 13 Dec 2016 17:52:58 +0100 Subject: [PATCH 63/75] Move admin broadcast messages spinach feature to rspec --- features/admin/broadcast_messages.feature | 33 ---------- features/steps/admin/broadcast_messages.rb | 66 ------------------- .../admin/admin_broadcast_messages_spec.rb | 51 ++++++++++++++ 3 files changed, 51 insertions(+), 99 deletions(-) delete mode 100644 features/admin/broadcast_messages.feature delete mode 100644 features/steps/admin/broadcast_messages.rb create mode 100644 spec/features/admin/admin_broadcast_messages_spec.rb diff --git a/features/admin/broadcast_messages.feature b/features/admin/broadcast_messages.feature deleted file mode 100644 index 4f9c651561e..00000000000 --- a/features/admin/broadcast_messages.feature +++ /dev/null @@ -1,33 +0,0 @@ -@admin -Feature: Admin Broadcast Messages - Background: - Given I sign in as an admin - And application already has a broadcast message - And I visit admin messages page - - Scenario: See broadcast messages list - Then I should see all broadcast messages - - Scenario: Create a customized broadcast message - When submit form with new customized broadcast message - Then I should be redirected to admin messages page - And I should see newly created broadcast message - Then I visit dashboard page - And I should see a customized broadcast message - - Scenario: Edit an existing broadcast message - When I edit an existing broadcast message - And I change the broadcast message text - Then I should be redirected to admin messages page - And I should see the updated broadcast message - - Scenario: Remove an existing broadcast message - When I remove an existing broadcast message - Then I should be redirected to admin messages page - And I should not see the removed broadcast message - - @javascript - Scenario: Live preview a customized broadcast message - When I visit admin messages page - And I enter a broadcast message with Markdown - Then I should see a live preview of the rendered broadcast message diff --git a/features/steps/admin/broadcast_messages.rb b/features/steps/admin/broadcast_messages.rb deleted file mode 100644 index af2b4a29313..00000000000 --- a/features/steps/admin/broadcast_messages.rb +++ /dev/null @@ -1,66 +0,0 @@ -class Spinach::Features::AdminBroadcastMessages < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - - step 'application already has a broadcast message' do - FactoryGirl.create(:broadcast_message, :expired, message: "Migration to new server") - end - - step 'I should see all broadcast messages' do - expect(page).to have_content "Migration to new server" - end - - step 'I should be redirected to admin messages page' do - expect(current_path).to eq admin_broadcast_messages_path - end - - step 'I should see newly created broadcast message' do - expect(page).to have_content 'Application update from 4:00 CST to 5:00 CST' - end - - step 'submit form with new customized broadcast message' do - fill_in 'broadcast_message_message', with: 'Application update from **4:00 CST to 5:00 CST**' - fill_in 'broadcast_message_color', with: '#f2dede' - fill_in 'broadcast_message_font', with: '#b94a48' - select Date.today.next_year.year, from: "broadcast_message_ends_at_1i" - click_button "Add broadcast message" - end - - step 'I should see a customized broadcast message' do - expect(page).to have_content 'Application update from 4:00 CST to 5:00 CST' - expect(page).to have_selector 'strong', text: '4:00 CST to 5:00 CST' - expect(page).to have_selector %(div[style="background-color: #f2dede; color: #b94a48"]) - end - - step 'I edit an existing broadcast message' do - click_link 'Edit' - end - - step 'I change the broadcast message text' do - fill_in 'broadcast_message_message', with: 'Application update RIGHT NOW' - click_button 'Update broadcast message' - end - - step 'I should see the updated broadcast message' do - expect(page).to have_content "Application update RIGHT NOW" - end - - step 'I remove an existing broadcast message' do - click_link 'Remove' - end - - step 'I should not see the removed broadcast message' do - expect(page).not_to have_content 'Migration to new server' - end - - step 'I enter a broadcast message with Markdown' do - fill_in 'broadcast_message_message', with: "Live **Markdown** previews. :tada:" - end - - step 'I should see a live preview of the rendered broadcast message' do - page.within('.broadcast-message-preview') do - expect(page).to have_selector('strong', text: 'Markdown') - expect(page).to have_selector('img.emoji') - end - end -end diff --git a/spec/features/admin/admin_broadcast_messages_spec.rb b/spec/features/admin/admin_broadcast_messages_spec.rb new file mode 100644 index 00000000000..bc957ec72e1 --- /dev/null +++ b/spec/features/admin/admin_broadcast_messages_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +feature 'Admin Broadcast Messages', feature: true do + before do + login_as :admin + create(:broadcast_message, :expired, message: 'Migration to new server') + visit admin_broadcast_messages_path + end + + scenario 'See broadcast messages list' do + expect(page).to have_content 'Migration to new server' + end + + scenario 'Create a customized broadcast message' do + fill_in 'broadcast_message_message', with: 'Application update from **4:00 CST to 5:00 CST**' + fill_in 'broadcast_message_color', with: '#f2dede' + fill_in 'broadcast_message_font', with: '#b94a48' + select Date.today.next_year.year, from: 'broadcast_message_ends_at_1i' + click_button 'Add broadcast message' + + expect(current_path).to eq admin_broadcast_messages_path + expect(page).to have_content 'Application update from 4:00 CST to 5:00 CST' + expect(page).to have_selector 'strong', text: '4:00 CST to 5:00 CST' + expect(page).to have_selector %(div[style="background-color: #f2dede; color: #b94a48"]) + end + + scenario 'Edit an existing broadcast message' do + click_link 'Edit' + fill_in 'broadcast_message_message', with: 'Application update RIGHT NOW' + click_button 'Update broadcast message' + + expect(current_path).to eq admin_broadcast_messages_path + expect(page).to have_content 'Application update RIGHT NOW' + end + + scenario 'Remove an existing broadcast message' do + click_link 'Remove' + + expect(current_path).to eq admin_broadcast_messages_path + expect(page).not_to have_content 'Migration to new server' + end + + scenario 'Live preview a customized broadcast message', js: true do + fill_in 'broadcast_message_message', with: "Live **Markdown** previews. :tada:" + + page.within('.broadcast-message-preview') do + expect(page).to have_selector('strong', text: 'Markdown') + expect(page).to have_selector('img.emoji') + end + end +end From 66ff2dede173c9918c72d381468f2d9f5b39ab86 Mon Sep 17 00:00:00 2001 From: Mitchell Hentges Date: Mon, 19 Dec 2016 01:34:45 -0800 Subject: [PATCH 64/75] Make CI badge hitboxes better match container --- app/assets/stylesheets/pages/pipelines.scss | 74 +++++++++---------- app/views/ci/status/_graph_badge.html.haml | 9 ++- app/views/projects/stage/_graph.html.haml | 11 ++- .../projects/stage/_in_stage_group.html.haml | 4 +- .../unreleased/pipeline-build-hitbox.yml | 4 + .../projects/pipelines/pipeline_spec.rb | 33 ++++----- .../fixtures/pipeline_graph.html.haml | 9 +-- 7 files changed, 68 insertions(+), 76 deletions(-) create mode 100644 changelogs/unreleased/pipeline-build-hitbox.yml diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index be22e7bdc79..243c9153ded 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -428,7 +428,7 @@ width: 21px; height: 25px; position: absolute; - top: -32px; + top: -31px; border-top: 2px solid $border-color; } @@ -456,32 +456,31 @@ } .build { - border: 1px solid $border-color; - border-radius: 30px; - background-color: $white-light; position: relative; - padding: 8px 4px 9px 10px; width: 186px; margin-bottom: 10px; white-space: normal; + color: $gl-text-color-light; - &:hover { - background-color: $stage-hover-bg; - border: 1px solid $stage-hover-border; + > .build-content { + display: inline-block; + padding: 8px 10px 9px; + width: 100%; + border: 1px solid $border-color; + border-radius: 30px; + background-color: $white-light; - a, - .dropdown-counter-badge, - .dropdown-menu-toggle { + &:hover { + background-color: $stage-hover-bg; + border: 1px solid $stage-hover-border; color: $gl-text-color; } + } - .grouped-pipeline-dropdown a { - color: $gl-text-color-light; - - &:hover { - color: $gl-text-color; - } - } + > .ci-action-icon-container { + position: absolute; + right: 4px; + top: 5px; } .ci-status-icon { @@ -621,8 +620,8 @@ padding: 0; width: 191px; left: auto; - right: -206px; - top: -11px; + right: -195px; + top: -4px; box-shadow: 0 1px 5px $black-transparent; a { @@ -650,17 +649,25 @@ .dropdown-build { color: $gl-text-color-light; - a.ci-action-icon-container { - padding: 0; + .build-content { + width: 100%; + } + + .ci-action-icon-container { font-size: 11px; - float: right; - margin-top: 4px; - display: inline-block; - position: relative; + position: absolute; + right: 4px; i { + width: 25px; + height: 25px; font-size: 11px; margin-top: 0; + + &::before { + top: 1px; + left: 1px; + } } } @@ -670,18 +677,6 @@ color: $gl-text-color; } - .ci-action-icon-container { - i { - width: 25px; - height: 25px; - - &::before { - top: 1px; - left: 1px; - } - } - } - .stage { max-width: 100px; width: 100px; @@ -704,9 +699,6 @@ // Action Icons .ci-action-icon-container .ci-action-icon-wrapper { - float: right; - margin-top: -4px; - i { color: $border-color; border-radius: 100%; diff --git a/app/views/ci/status/_graph_badge.html.haml b/app/views/ci/status/_graph_badge.html.haml index 9f3a9c0c6b2..52b4d77d074 100644 --- a/app/views/ci/status/_graph_badge.html.haml +++ b/app/views/ci/status/_graph_badge.html.haml @@ -5,12 +5,13 @@ - klass = "ci-status-icon ci-status-icon-#{status.group}" - if status.has_details? - = link_to status.details_path, data: { toggle: 'tooltip', title: "#{subject.name} - #{status.label}" } do + = link_to status.details_path, class: 'build-content' do %span{ class: klass }= custom_icon(status.icon) - .ci-status-text= subject.name + .ci-status-text{ 'data-toggle' => 'tooltip', 'data-title' => "#{subject.name} - #{status.label}" }= subject.name - else - %span{ class: klass }= custom_icon(status.icon) - .ci-status-text= subject.name + .build-content + %span{ class: klass }= custom_icon(status.icon) + .ci-status-text{ 'data-toggle' => 'tooltip', 'data-title' => "#{subject.name} - #{status.label}" }= subject.name - if status.has_action? = link_to status.action_path, method: status.action_method, diff --git a/app/views/projects/stage/_graph.html.haml b/app/views/projects/stage/_graph.html.haml index b70b574e687..6919b210a00 100644 --- a/app/views/projects/stage/_graph.html.haml +++ b/app/views/projects/stage/_graph.html.haml @@ -10,12 +10,11 @@ - status_groups.each do |group_name, grouped_statuses| - if grouped_statuses.one? - status = grouped_statuses.first - %li.build + %li.build{ 'id' => "ci-badge-#{group_name}" } .curve - .build-content - = render 'ci/status/graph_badge', subject: status + = render 'ci/status/graph_badge', subject: status - else - %li.build + %li.build{ 'id' => "ci-badge-#{group_name}" } .curve - .dropdown.inline.build-content - = render 'projects/stage/in_stage_group', name: group_name, subject: grouped_statuses + = render 'projects/stage/in_stage_group', name: group_name, subject: grouped_statuses + diff --git a/app/views/projects/stage/_in_stage_group.html.haml b/app/views/projects/stage/_in_stage_group.html.haml index b03837d1211..b15f7eaeab2 100644 --- a/app/views/projects/stage/_in_stage_group.html.haml +++ b/app/views/projects/stage/_in_stage_group.html.haml @@ -1,8 +1,8 @@ - group_status = CommitStatus.where(id: subject).status -%button.dropdown-menu-toggle.has-tooltip{ type: 'button', data: { toggle: 'dropdown', title: "#{name} - #{group_status}" } } +%button.dropdown-menu-toggle.build-content.has-tooltip{ type: 'button', data: { toggle: 'dropdown'} } %span{class: "ci-status-icon ci-status-icon-#{group_status}"} = ci_icon_for_status(group_status) - %span.ci-status-text + %span.ci-status-text{ 'data-toggle' => 'tooltip', 'data-title' => "#{name} - #{group_status}" } = name %span.dropdown-counter-badge= subject.size .dropdown-menu.grouped-pipeline-dropdown diff --git a/changelogs/unreleased/pipeline-build-hitbox.yml b/changelogs/unreleased/pipeline-build-hitbox.yml new file mode 100644 index 00000000000..051b538a9a3 --- /dev/null +++ b/changelogs/unreleased/pipeline-build-hitbox.yml @@ -0,0 +1,4 @@ +--- +title: Make CI badge hitboxes match parent +merge_request: +author: diff --git a/spec/features/projects/pipelines/pipeline_spec.rb b/spec/features/projects/pipelines/pipeline_spec.rb index 57f1e75ea2c..1210e2745db 100644 --- a/spec/features/projects/pipelines/pipeline_spec.rb +++ b/spec/features/projects/pipelines/pipeline_spec.rb @@ -19,7 +19,7 @@ describe "Pipelines", feature: true, js: true do @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build') @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test') @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy') - @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build') + @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual-build') @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external') end @@ -41,37 +41,34 @@ describe "Pipelines", feature: true, js: true do describe 'pipeline graph' do context 'when pipeline has running builds' do it 'shows a running icon and a cancel action for the running build' do - page.within('a[data-title="deploy - running"]') do + page.within('#ci-badge-deploy') do expect(page).to have_selector('.ci-status-icon-running') - expect(page).to have_content('deploy') - end - - page.within('a[data-title="deploy - running"] + .ci-action-icon-container') do expect(page).to have_selector('.ci-action-icon-container .fa-ban') + expect(page).to have_content('deploy') end end it 'should be possible to cancel the running build' do - find('a[data-title="deploy - running"] + .ci-action-icon-container').trigger('click') + find('#ci-badge-deploy .ci-action-icon-container').trigger('click') expect(page).not_to have_content('Cancel running') end end context 'when pipeline has successful builds' do - it 'shows the success icon and a retry action for the successfull build' do - page.within('a[data-title="build - passed"]') do + it 'shows the success icon and a retry action for the successful build' do + page.within('#ci-badge-build') do expect(page).to have_selector('.ci-status-icon-success') expect(page).to have_content('build') end - page.within('a[data-title="build - passed"] + .ci-action-icon-container') do + page.within('#ci-badge-build .ci-action-icon-container') do expect(page).to have_selector('.ci-action-icon-container .fa-refresh') end end it 'should be possible to retry the success build' do - find('a[data-title="build - passed"] + .ci-action-icon-container').trigger('click') + find('#ci-badge-build .ci-action-icon-container').trigger('click') expect(page).not_to have_content('Retry build') end @@ -79,18 +76,18 @@ describe "Pipelines", feature: true, js: true do context 'when pipeline has failed builds' do it 'shows the failed icon and a retry action for the failed build' do - page.within('a[data-title="test - failed"]') do + page.within('#ci-badge-test') do expect(page).to have_selector('.ci-status-icon-failed') expect(page).to have_content('test') end - page.within('a[data-title="test - failed"] + .ci-action-icon-container') do + page.within('#ci-badge-test .ci-action-icon-container') do expect(page).to have_selector('.ci-action-icon-container .fa-refresh') end end it 'should be possible to retry the failed build' do - find('a[data-title="test - failed"] + .ci-action-icon-container').trigger('click') + find('#ci-badge-test .ci-action-icon-container').trigger('click') expect(page).not_to have_content('Retry build') end @@ -98,18 +95,18 @@ describe "Pipelines", feature: true, js: true do context 'when pipeline has manual builds' do it 'shows the skipped icon and a play action for the manual build' do - page.within('a[data-title="manual build - manual play action"]') do + page.within('#ci-badge-manual-build') do expect(page).to have_selector('.ci-status-icon-manual') expect(page).to have_content('manual') end - page.within('a[data-title="manual build - manual play action"] + .ci-action-icon-container') do + page.within('#ci-badge-manual-build .ci-action-icon-container') do expect(page).to have_selector('.ci-action-icon-container .fa-play') end end it 'should be possible to play the manual build' do - find('a[data-title="manual build - manual play action"] + .ci-action-icon-container').trigger('click') + find('#ci-badge-manual-build .ci-action-icon-container').trigger('click') expect(page).not_to have_content('Play build') end @@ -167,7 +164,7 @@ describe "Pipelines", feature: true, js: true do @success = create(:ci_build, :success, pipeline: pipeline, stage: 'build', name: 'build') @failed = create(:ci_build, :failed, pipeline: pipeline, stage: 'test', name: 'test', commands: 'test') @running = create(:ci_build, :running, pipeline: pipeline, stage: 'deploy', name: 'deploy') - @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual build') + @manual = create(:ci_build, :manual, pipeline: pipeline, stage: 'deploy', name: 'manual-build') @external = create(:generic_commit_status, status: 'success', pipeline: pipeline, name: 'jenkins', stage: 'external') end diff --git a/spec/javascripts/fixtures/pipeline_graph.html.haml b/spec/javascripts/fixtures/pipeline_graph.html.haml index deca50ceaa7..c0b5ab4411e 100644 --- a/spec/javascripts/fixtures/pipeline_graph.html.haml +++ b/spec/javascripts/fixtures/pipeline_graph.html.haml @@ -8,8 +8,7 @@ %ul %li.build .curve - .build-content - %a - %svg - .ci-status-text - stop_review + %a + %svg + .ci-status-text + stop_review From 5b0ebbe5b4c18c136aad2a53898c1a6441d39a9d Mon Sep 17 00:00:00 2001 From: Valery Sizov Date: Mon, 19 Dec 2016 20:44:57 +0200 Subject: [PATCH 65/75] Add Wiki import to BB importer --- doc/integration/bitbucket.md | 3 +- .../img/bitbucket_oauth_settings_page.png | Bin 5607 -> 28719 bytes .../import_projects_from_bitbucket.md | 1 + lib/bitbucket/representation/repo.rb | 4 ++ lib/gitlab/bitbucket_import/importer.rb | 13 +++++ .../bitbucket_import/project_creator.rb | 9 +++- .../lib/bitbucket/representation/repo_spec.rb | 49 ++++++++++++++++++ .../gitlab/bitbucket_import/importer_spec.rb | 12 +++-- .../bitbucket_import/project_creator_spec.rb | 3 +- 9 files changed, 88 insertions(+), 6 deletions(-) create mode 100644 spec/lib/bitbucket/representation/repo_spec.rb diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md index 5df6e103f42..1dfc985eaea 100644 --- a/doc/integration/bitbucket.md +++ b/doc/integration/bitbucket.md @@ -5,7 +5,7 @@ Bitbucket.org account. ## Overview -You can set up Bitbucket.org as an OAuth provider so that you can use your +You can set up Bitbucket.org as an OAuth2 provider so that you can use your credentials to authenticate into GitLab or import your projects from Bitbucket.org. @@ -50,6 +50,7 @@ you to use. Repositories: Read Pull Requests: Read Issues: Read + Wiki: Read and Write ``` ![Bitbucket OAuth settings page](img/bitbucket_oauth_settings_page.png) diff --git a/doc/integration/img/bitbucket_oauth_settings_page.png b/doc/integration/img/bitbucket_oauth_settings_page.png index 21ce82a6074e2a5b1d52331a5567634a8c5c7914..3e6dea6cfe9abfe51777318f07f916e6d2806996 100644 GIT binary patch literal 28719 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 literal 5607 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 diff --git a/doc/workflow/importing/import_projects_from_bitbucket.md b/doc/workflow/importing/import_projects_from_bitbucket.md index b6d47e5afa2..97380bce172 100644 --- a/doc/workflow/importing/import_projects_from_bitbucket.md +++ b/doc/workflow/importing/import_projects_from_bitbucket.md @@ -17,6 +17,7 @@ to enable this if not already. - the pull requests (GitLab 8.4+) - the pull request comments (GitLab 8.15+) - the milestones (GitLab 8.15+) + - the wiki (GitLab 8.15+) - References to pull requests and issues are preserved (GitLab 8.7+) - Repository public access is retained. If a repository is private in Bitbucket it will be created as private in GitLab as well. diff --git a/lib/bitbucket/representation/repo.rb b/lib/bitbucket/representation/repo.rb index 8969ecd1c19..423eff8f2a5 100644 --- a/lib/bitbucket/representation/repo.rb +++ b/lib/bitbucket/representation/repo.rb @@ -51,6 +51,10 @@ module Bitbucket raw['scm'] == 'git' end + def has_wiki? + raw['has_wiki'] + end + def visibility_level if raw['is_private'] Gitlab::VisibilityLevel::PRIVATE diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb index 7d2f92d577a..44323b47dca 100644 --- a/lib/gitlab/bitbucket_import/importer.rb +++ b/lib/gitlab/bitbucket_import/importer.rb @@ -1,6 +1,8 @@ module Gitlab module BitbucketImport class Importer + include Gitlab::ShellAdapter + LABELS = [{ title: 'bug', color: '#FF0000' }, { title: 'enhancement', color: '#428BCA' }, { title: 'proposal', color: '#69D100' }, @@ -18,6 +20,7 @@ module Gitlab end def execute + import_wiki import_issues import_pull_requests handle_errors @@ -55,6 +58,16 @@ module Gitlab @repo ||= client.repo(project.import_source) end + def import_wiki + return if project.wiki.repository_exists? + + path_with_namespace = "#{project.path_with_namespace}.wiki" + import_url = project.import_url.sub(/\.git\z/, ".git/wiki") + gitlab_shell.import_repository(project.repository_storage_path, path_with_namespace, import_url) + rescue StandardError => e + errors << { type: :wiki, errors: e.message } + end + def import_issues return unless repo.issues_enabled? diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb index eb03882ab26..d94f70fd1fb 100644 --- a/lib/gitlab/bitbucket_import/project_creator.rb +++ b/lib/gitlab/bitbucket_import/project_creator.rb @@ -22,9 +22,16 @@ module Gitlab import_type: 'bitbucket', import_source: repo.full_name, import_url: repo.clone_url(session_data[:token]), - import_data: { credentials: session_data } + import_data: { credentials: session_data }, + skip_wiki: skip_wiki ).execute end + + private + + def skip_wiki + repo.has_wiki? + end end end end diff --git a/spec/lib/bitbucket/representation/repo_spec.rb b/spec/lib/bitbucket/representation/repo_spec.rb new file mode 100644 index 00000000000..adcd978e1b3 --- /dev/null +++ b/spec/lib/bitbucket/representation/repo_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe Bitbucket::Representation::Repo do + describe '#has_wiki?' do + it { expect(described_class.new({ 'has_wiki' => false }).has_wiki?).to be_falsey } + it { expect(described_class.new({ 'has_wiki' => true }).has_wiki?).to be_truthy } + end + + describe '#name' do + it { expect(described_class.new({ 'name' => 'test' }).name).to eq('test') } + end + + describe '#valid?' do + it { expect(described_class.new({ 'scm' => 'hg' }).valid?).to be_falsey } + it { expect(described_class.new({ 'scm' => 'git' }).valid?).to be_truthy } + end + + describe '#full_name' do + it { expect(described_class.new({ 'full_name' => 'test_full' }).full_name).to eq('test_full') } + end + + describe '#description' do + it { expect(described_class.new({ 'description' => 'desc' }).description).to eq('desc') } + end + + describe '#issues_enabled?' do + it { expect(described_class.new({ 'has_issues' => false }).issues_enabled?).to be_falsey } + it { expect(described_class.new({ 'has_issues' => true }).issues_enabled?).to be_truthy } + end + + describe '#owner_and_slug' do + it { expect(described_class.new({ 'full_name' => 'ben/test' }).owner_and_slug).to eq(['ben', 'test']) } + end + + describe '#owner' do + it { expect(described_class.new({ 'full_name' => 'ben/test' }).owner).to eq('ben') } + end + + describe '#slug' do + it { expect(described_class.new({ 'full_name' => 'ben/test' }).slug).to eq('test') } + end + + describe '#clone_url' do + it 'builds url' do + data = { 'links' => { 'clone' => [ { 'name' => 'https', 'href' => 'https://bibucket.org/test/test.git' }] } } + expect(described_class.new(data).clone_url('abc')).to eq('https://x-token-auth:abc@bibucket.org/test/test.git') + end + end +end diff --git a/spec/lib/gitlab/bitbucket_import/importer_spec.rb b/spec/lib/gitlab/bitbucket_import/importer_spec.rb index 53f3c73ade4..72b1ba36b58 100644 --- a/spec/lib/gitlab/bitbucket_import/importer_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/importer_spec.rb @@ -69,6 +69,9 @@ describe Gitlab::BitbucketImport::Importer, lib: true do context 'issues statuses' do before do + # HACK: Bitbucket::Representation.const_get('Issue') seems to return ::Issue without this + Bitbucket::Representation::Issue.new({}) + stub_request( :get, "https://api.bitbucket.org/2.0/repositories/#{project_identifier}" @@ -108,13 +111,16 @@ describe Gitlab::BitbucketImport::Importer, lib: true do body: {}.to_json) end - it 'map statuses to open or closed' do - # HACK: Bitbucket::Representation.const_get('Issue') seems to return ::Issue without this - Bitbucket::Representation::Issue.new({}) + it 'maps statuses to open or closed' do importer.execute expect(project.issues.where(state: "closed").size).to eq(5) expect(project.issues.where(state: "opened").size).to eq(2) end + + it 'calls import_wiki' do + expect(importer).to receive(:import_wiki) + importer.execute + end end end diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb index b6d052a4612..773d0d4d288 100644 --- a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb +++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb @@ -11,7 +11,8 @@ describe Gitlab::BitbucketImport::ProjectCreator, lib: true do owner: "asd", full_name: 'Vim repo', visibility_level: Gitlab::VisibilityLevel::PRIVATE, - clone_url: 'ssh://git@bitbucket.org/asd/vim.git') + clone_url: 'ssh://git@bitbucket.org/asd/vim.git', + has_wiki?: false) end let(:namespace){ create(:group, owner: user) } From e7c56dd1f63a0065b5930accb3ff24314430f82c Mon Sep 17 00:00:00 2001 From: Sam Rose Date: Mon, 19 Dec 2016 10:25:46 -0500 Subject: [PATCH 66/75] Align milestone column header with count number --- app/assets/stylesheets/framework/panels.scss | 14 ++++++++++++++ app/views/shared/milestones/_issuables.html.haml | 8 +++++--- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/app/assets/stylesheets/framework/panels.scss b/app/assets/stylesheets/framework/panels.scss index 5ba0486177f..9d8d08dff88 100644 --- a/app/assets/stylesheets/framework/panels.scss +++ b/app/assets/stylesheets/framework/panels.scss @@ -18,6 +18,20 @@ margin-top: -2px; margin-left: 5px; } + + &.split { + display: flex; + align-items: center; + } + + .left { + flex: 1 1 auto; + } + + .right { + flex: 0 0 auto; + text-align: right; + } } .panel-body { diff --git a/app/views/shared/milestones/_issuables.html.haml b/app/views/shared/milestones/_issuables.html.haml index 8619939dde7..15ff5b8a27e 100644 --- a/app/views/shared/milestones/_issuables.html.haml +++ b/app/views/shared/milestones/_issuables.html.haml @@ -3,10 +3,12 @@ - panel_class = primary ? 'panel-primary' : 'panel-default' .panel{ class: panel_class } - .panel-heading - = title + .panel-heading.split + .left + = title - if show_counter - .pull-right= issuables.size + .right + = issuables.size - class_prefix = dom_class(issuables).pluralize %ul{ class: "well-list #{class_prefix}-sortable-list", id: "#{class_prefix}-list-#{id}", "data-state" => id } From 18c9fc42249a08ff28cf9d5b9159b7bada168bcf Mon Sep 17 00:00:00 2001 From: Lin Jen-Shin Date: Tue, 20 Dec 2016 03:24:38 +0800 Subject: [PATCH 67/75] Use a block to insert extra check for authenticate_build! Feedback: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/8091#note_20253762 --- lib/ci/api/helpers.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb index 62c10c3b753..31fbd1da108 100644 --- a/lib/ci/api/helpers.rb +++ b/lib/ci/api/helpers.rb @@ -14,13 +14,16 @@ module Ci end def authenticate_build!(build) - not_found! unless build - forbidden! unless build_token_valid?(build) - validate_build!(build) + validate_build!(build) do + forbidden! unless build_token_valid?(build) + end end def validate_build!(build) not_found! unless build + + yield if block_given? + forbidden!('Project has been deleted!') unless build.project forbidden!('Build has been erased!') if build.erased? end From f347d5fcadb4b05f367ed571b27e73040f625520 Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 19 Dec 2016 14:13:06 -0600 Subject: [PATCH 68/75] Remove inline-block styling from status --- app/assets/stylesheets/framework/page-header.scss | 1 - app/assets/stylesheets/pages/status.scss | 1 - 2 files changed, 2 deletions(-) diff --git a/app/assets/stylesheets/framework/page-header.scss b/app/assets/stylesheets/framework/page-header.scss index fff7d7f7524..625bea96aaa 100644 --- a/app/assets/stylesheets/framework/page-header.scss +++ b/app/assets/stylesheets/framework/page-header.scss @@ -57,7 +57,6 @@ } .ci-status-link { - svg { position: relative; top: 2px; diff --git a/app/assets/stylesheets/pages/status.scss b/app/assets/stylesheets/pages/status.scss index 055dacd81f4..a810ed32327 100644 --- a/app/assets/stylesheets/pages/status.scss +++ b/app/assets/stylesheets/pages/status.scss @@ -1,6 +1,5 @@ .container-fluid { .ci-status { - display: inline-block; padding: 2px 7px; margin-right: 10px; border: 1px solid $gray-darker; From 95e0fac59ae8174d11873e95a3ef579af476f215 Mon Sep 17 00:00:00 2001 From: Dmitriy Zaporozhets Date: Mon, 19 Dec 2016 22:36:47 +0200 Subject: [PATCH 69/75] Fix Route#rename_children behavior Given group `gitlab` and `gitlab-org` exists. When rename `gitlab` it will rename `gitlab-org` group route too. This commit fixes it Signed-off-by: Dmitriy Zaporozhets --- app/models/route.rb | 2 +- changelogs/unreleased/dz-fix-route-rename.yml | 4 ++++ spec/models/route_spec.rb | 8 +++++--- 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 changelogs/unreleased/dz-fix-route-rename.yml diff --git a/app/models/route.rb b/app/models/route.rb index d40214b9da6..caf596efa79 100644 --- a/app/models/route.rb +++ b/app/models/route.rb @@ -13,7 +13,7 @@ class Route < ActiveRecord::Base def rename_children # We update each row separately because MySQL does not have regexp_replace. # rubocop:disable Rails/FindEach - Route.where('path LIKE ?', "#{path_was}%").each do |route| + Route.where('path LIKE ?', "#{path_was}/%").each do |route| # Note that update column skips validation and callbacks. # We need this to avoid recursive call of rename_children method route.update_column(:path, route.path.sub(path_was, path)) diff --git a/changelogs/unreleased/dz-fix-route-rename.yml b/changelogs/unreleased/dz-fix-route-rename.yml new file mode 100644 index 00000000000..a649fb169a5 --- /dev/null +++ b/changelogs/unreleased/dz-fix-route-rename.yml @@ -0,0 +1,4 @@ +--- +title: Fix Route#rename_children behavior +merge_request: +author: diff --git a/spec/models/route_spec.rb b/spec/models/route_spec.rb index 6f491fdf9a0..8481a9bef16 100644 --- a/spec/models/route_spec.rb +++ b/spec/models/route_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Route, models: true do - let!(:group) { create(:group) } + let!(:group) { create(:group, path: 'gitlab') } let!(:route) { group.route } describe 'relationships' do @@ -17,13 +17,15 @@ describe Route, models: true do describe '#rename_children' do let!(:nested_group) { create(:group, path: "test", parent: group) } let!(:deep_nested_group) { create(:group, path: "foo", parent: nested_group) } + let!(:similar_group) { create(:group, path: 'gitlab-org') } + + before { route.update_attributes(path: 'bar') } it "updates children routes with new path" do - route.update_attributes(path: 'bar') - expect(described_class.exists?(path: 'bar')).to be_truthy expect(described_class.exists?(path: 'bar/test')).to be_truthy expect(described_class.exists?(path: 'bar/test/foo')).to be_truthy + expect(described_class.exists?(path: 'gitlab-org')).to be_truthy end end end From 62aa468d1967e83febd840b4a95f066084aee197 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Wed, 14 Dec 2016 17:00:57 -0300 Subject: [PATCH 70/75] Update CHANGELOG.md for 8.14.5 [ci skip] --- CHANGELOG.md | 15 +++++++++++++++ ...-project-and-leave-group-should-be-buttons.yml | 5 ----- ...s-settings-hidden-when-builds-are-disabled.yml | 4 ---- changelogs/unreleased/fix-milestone-summary.yml | 4 ---- .../group-members-in-project-members-view.yml | 4 ---- changelogs/unreleased/issue_24020.yml | 4 ---- changelogs/unreleased/issue_25030.yml | 4 ---- changelogs/unreleased/timeago-perf-fix.yml | 4 ---- changelogs/unreleased/unescape-relative-path.yml | 4 ---- 9 files changed, 15 insertions(+), 33 deletions(-) delete mode 100644 changelogs/unreleased/23305-leave-project-and-leave-group-should-be-buttons.yml delete mode 100644 changelogs/unreleased/25171-fix-mr-features-settings-hidden-when-builds-are-disabled.yml delete mode 100644 changelogs/unreleased/fix-milestone-summary.yml delete mode 100644 changelogs/unreleased/group-members-in-project-members-view.yml delete mode 100644 changelogs/unreleased/issue_24020.yml delete mode 100644 changelogs/unreleased/issue_25030.yml delete mode 100644 changelogs/unreleased/timeago-perf-fix.yml delete mode 100644 changelogs/unreleased/unescape-relative-path.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index fb13db4dd1c..f1d2f09e34f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,21 @@ documentation](doc/development/changelog.md) for instructions on adding your own entry. +## 8.14.5 (2016-12-14) + +- Moved Leave Project and Leave Group buttons to access_request_buttons from the settings dropdown. !7600 +- fix display hook error message. !7775 (basyura) +- Remove wrong '.builds-feature' class from the MR settings fieldset. !7930 +- Avoid escaping relative links in Markdown twice. !7940 (winniehell) +- API: Memoize the current_user so that sudo can work properly. !8017 +- Displays milestone remaining days only when it's present. +- Allow branch names with dots on API endpoint. +- Issue#visible_to_user moved to IssuesFinder to prevent accidental use. +- Shows group members in project members list. +- Encode input when migrating ProcessCommitWorker jobs to prevent migration errors. +- Fixed timeago re-rendering every timeago. +- Fix missing Note access checks by moving Note#search to updated NoteFinder. + ## 8.14.4 (2016-12-08) - Fix diff view permalink highlighting. !7090 diff --git a/changelogs/unreleased/23305-leave-project-and-leave-group-should-be-buttons.yml b/changelogs/unreleased/23305-leave-project-and-leave-group-should-be-buttons.yml deleted file mode 100644 index 99dbe4a32a0..00000000000 --- a/changelogs/unreleased/23305-leave-project-and-leave-group-should-be-buttons.yml +++ /dev/null @@ -1,5 +0,0 @@ ---- -title: Moved Leave Project and Leave Group buttons to access_request_buttons from - the settings dropdown -merge_request: 7600 -author: diff --git a/changelogs/unreleased/25171-fix-mr-features-settings-hidden-when-builds-are-disabled.yml b/changelogs/unreleased/25171-fix-mr-features-settings-hidden-when-builds-are-disabled.yml deleted file mode 100644 index a7576e2cbdb..00000000000 --- a/changelogs/unreleased/25171-fix-mr-features-settings-hidden-when-builds-are-disabled.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Remove wrong '.builds-feature' class from the MR settings fieldset -merge_request: 7930 -author: diff --git a/changelogs/unreleased/fix-milestone-summary.yml b/changelogs/unreleased/fix-milestone-summary.yml deleted file mode 100644 index 3045a15054c..00000000000 --- a/changelogs/unreleased/fix-milestone-summary.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Displays milestone remaining days only when it's present -merge_request: -author: diff --git a/changelogs/unreleased/group-members-in-project-members-view.yml b/changelogs/unreleased/group-members-in-project-members-view.yml deleted file mode 100644 index 415e2b6b1e2..00000000000 --- a/changelogs/unreleased/group-members-in-project-members-view.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Shows group members in project members list -merge_request: -author: diff --git a/changelogs/unreleased/issue_24020.yml b/changelogs/unreleased/issue_24020.yml deleted file mode 100644 index 87310b75296..00000000000 --- a/changelogs/unreleased/issue_24020.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: "fix display hook error message" -merge_request: 7775 -author: basyura diff --git a/changelogs/unreleased/issue_25030.yml b/changelogs/unreleased/issue_25030.yml deleted file mode 100644 index e18b8d6a79b..00000000000 --- a/changelogs/unreleased/issue_25030.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Allow branch names with dots on API endpoint -merge_request: -author: diff --git a/changelogs/unreleased/timeago-perf-fix.yml b/changelogs/unreleased/timeago-perf-fix.yml deleted file mode 100644 index 265e7db29a9..00000000000 --- a/changelogs/unreleased/timeago-perf-fix.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Fixed timeago re-rendering every timeago -merge_request: -author: diff --git a/changelogs/unreleased/unescape-relative-path.yml b/changelogs/unreleased/unescape-relative-path.yml deleted file mode 100644 index 755b0379a16..00000000000 --- a/changelogs/unreleased/unescape-relative-path.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -title: Avoid escaping relative links in Markdown twice -merge_request: 7940 -author: winniehell From 5f1739638882dd630cd814c4501c4147737f4663 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Rodr=C3=ADguez?= Date: Wed, 14 Dec 2016 17:04:12 -0300 Subject: [PATCH 71/75] Update CHANGELOG.md for 8.13.10 [ci skip] --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f1d2f09e34f..786b0128aac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -279,6 +279,13 @@ entry. - Fix "Without projects" filter. !6611 (Ben Bodenmiller) - Fix 404 when visit /projects page +## 8.13.10 (2016-12-14) + +- API: Memoize the current_user so that sudo can work properly. !8017 +- Filter `authentication_token`, `incoming_email_token` and `runners_token` parameters. +- Issue#visible_to_user moved to IssuesFinder to prevent accidental use. +- Fix missing Note access checks by moving Note#search to updated NoteFinder. + ## 8.13.9 (2016-12-08) - Reenables /user API request to return private-token if user is admin and request is made with sudo. !7615 From 00f076d71b3ba831051d627271e8b0cec6f5324b Mon Sep 17 00:00:00 2001 From: Annabel Dunstone Gray Date: Mon, 19 Dec 2016 14:54:07 -0600 Subject: [PATCH 72/75] Update font size of detail page header to 14px --- app/assets/stylesheets/pages/detail_page.scss | 1 - 1 file changed, 1 deletion(-) diff --git a/app/assets/stylesheets/pages/detail_page.scss b/app/assets/stylesheets/pages/detail_page.scss index 80baebd5ea3..9b28df1afc5 100644 --- a/app/assets/stylesheets/pages/detail_page.scss +++ b/app/assets/stylesheets/pages/detail_page.scss @@ -2,7 +2,6 @@ padding: $gl-padding-top 0; border-bottom: 1px solid $border-color; color: $gl-text-color-dark; - font-size: 16px; line-height: 34px; .author { From d41c8f48143acf865183a324e3c18dd286d423ba Mon Sep 17 00:00:00 2001 From: Ryan Harris Date: Fri, 16 Dec 2016 20:03:57 -0500 Subject: [PATCH 73/75] Even out padding on plus button in breadcrumb menu Changed plus button padding to 6px 10px --- app/assets/stylesheets/pages/tree.scss | 1 + changelogs/unreleased/25740-fix-new-branch-button-padding.yml | 4 ++++ 2 files changed, 5 insertions(+) create mode 100644 changelogs/unreleased/25740-fix-new-branch-button-padding.yml diff --git a/app/assets/stylesheets/pages/tree.scss b/app/assets/stylesheets/pages/tree.scss index c0341db7289..13be08104e6 100644 --- a/app/assets/stylesheets/pages/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -14,6 +14,7 @@ .add-to-tree { vertical-align: top; + padding: 6px 10px; } .tree-table { diff --git a/changelogs/unreleased/25740-fix-new-branch-button-padding.yml b/changelogs/unreleased/25740-fix-new-branch-button-padding.yml new file mode 100644 index 00000000000..7da8f9357a7 --- /dev/null +++ b/changelogs/unreleased/25740-fix-new-branch-button-padding.yml @@ -0,0 +1,4 @@ +--- +title: Made the padding on the plus button in the breadcrumb menu even +merge_request: +author: Ryan Harris From e06f88effa842c73d3827593f8d28846207bfca0 Mon Sep 17 00:00:00 2001 From: Kamil Trzcinski Date: Mon, 19 Dec 2016 22:12:30 +0100 Subject: [PATCH 74/75] Fix specs --- features/project/service.feature | 6 ++-- features/steps/project/services.rb | 6 ++-- .../services/slack_slash_command_spec.rb | 36 ++++++++----------- 3 files changed, 20 insertions(+), 28 deletions(-) diff --git a/features/project/service.feature b/features/project/service.feature index 892db48d785..cce5f58adec 100644 --- a/features/project/service.feature +++ b/features/project/service.feature @@ -37,10 +37,10 @@ Feature: Project Services And I fill Assembla settings Then I should see Assembla service settings saved - Scenario: Activate Slack service + Scenario: Activate Slack notifications service When I visit project "Shop" services page - And I click Slack Notifications service link - And I fill Slack Notifications settings + And I click Slack notifications service link + And I fill Slack notifications settings Then I should see Slack Notifications service settings saved Scenario: Activate Pushover service diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb index 06a1afedbd9..a4d29770922 100644 --- a/features/steps/project/services.rb +++ b/features/steps/project/services.rb @@ -137,11 +137,11 @@ class Spinach::Features::ProjectServices < Spinach::FeatureSteps expect(find_field('Colorize messages').value).to eq '1' end - step 'I click Slack Notifications service link' do - click_link 'Slack Notifications' + step 'I click Slack notifications service link' do + click_link 'Slack notifications' end - step 'I fill Slack Notifications settings' do + step 'I fill Slack notifications settings' do check 'Active' fill_in 'Webhook', with: 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' click_button 'Save' diff --git a/spec/features/projects/services/slack_slash_command_spec.rb b/spec/features/projects/services/slack_slash_command_spec.rb index 70e203efcf5..32b32f7ae8e 100644 --- a/spec/features/projects/services/slack_slash_command_spec.rb +++ b/spec/features/projects/services/slack_slash_command_spec.rb @@ -12,37 +12,29 @@ feature 'Slack slash commands', feature: true do login_as(user) end - scenario 'user visits the slack slash command config page', js: true do - it 'shows a help message' do - visit edit_namespace_project_service_path(project.namespace, project, service) + scenario 'user visits the slack slash command config page and shows a help message', js: true do + visit edit_namespace_project_service_path(project.namespace, project, service) - wait_for_ajax + wait_for_ajax - expect(page).to have_content('This service allows GitLab users to perform common') - end + expect(page).to have_content('This service allows GitLab users to perform common') end - scenario 'saving a token' do - given(:token) { ('a'..'z').to_a.join } + scenario 'shows the token after saving' do + visit edit_namespace_project_service_path(project.namespace, project, service) - it 'shows the token after saving' do - visit edit_namespace_project_service_path(project.namespace, project, service) + fill_in 'service_token', with: 'token' + click_on 'Save' - fill_in 'service_token', with: token - click_on 'Save' + value = find_field('service_token').value - value = find_field('service_token').value - - expect(value).to eq(token) - end + expect(value).to eq('token') end - scenario 'the trigger url' do - it 'shows the correct url' do - visit edit_namespace_project_service_path(project.namespace, project, service) + scenario 'shows the correct trigger url' do + visit edit_namespace_project_service_path(project.namespace, project, service) - value = find_field('url').value - expect(value).to match("api/v3/projects/#{project.id}/services/slack_slash_commands/trigger") - end + value = find_field('url').value + expect(value).to match("api/v3/projects/#{project.id}/services/slack_slash_commands/trigger") end end From 212967aefb55e0792a36e67a881b66c8bd871e9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9my=20Coutable?= Date: Tue, 20 Dec 2016 09:45:37 +0100 Subject: [PATCH 75/75] Reject blank environment vcariables in Gitlab::Git::RevList MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Rémy Coutable --- lib/gitlab/git/rev_list.rb | 4 ++-- spec/lib/gitlab/git/rev_list_spec.rb | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/git/rev_list.rb b/lib/gitlab/git/rev_list.rb index 25e9d619697..79dd0cf7df2 100644 --- a/lib/gitlab/git/rev_list.rb +++ b/lib/gitlab/git/rev_list.rb @@ -22,7 +22,7 @@ module Gitlab def valid? environment_variables.all? do |(name, value)| - value.start_with?(project.repository.path_to_repo) + value.to_s.start_with?(project.repository.path_to_repo) end end @@ -35,7 +35,7 @@ module Gitlab end def environment_variables - @environment_variables ||= env.slice(*ALLOWED_VARIABLES) + @environment_variables ||= env.slice(*ALLOWED_VARIABLES).compact end end end diff --git a/spec/lib/gitlab/git/rev_list_spec.rb b/spec/lib/gitlab/git/rev_list_spec.rb index 444639acbaa..1f9c987be0b 100644 --- a/spec/lib/gitlab/git/rev_list_spec.rb +++ b/spec/lib/gitlab/git/rev_list_spec.rb @@ -26,6 +26,13 @@ describe Gitlab::Git::RevList, lib: true do expect(rev_list).not_to be_valid end + + it "ignores nil values" do + env = { var => nil } + rev_list = described_class.new('oldrev', 'newrev', project: project, env: env) + + expect(rev_list).to be_valid + end end end end