diff options
27 files changed, 226 insertions, 68 deletions
diff --git a/CHANGELOG b/CHANGELOG index a6411317774..463b07b635e 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -4,7 +4,12 @@ v 8.13.0 (unreleased) - Speed-up group milestones show page - Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison) -v 8.12.1 (unreleased) +v 8.12.2 (unreleased) + - Fix Import/Export not recognising correctly the imported services. + +v 8.12.1 + - Fix a memory leak in HTML::Pipeline::SanitizationFilter::WHITELIST + - Fix issue with search filter labels not displaying v 8.12.0 - Update the rouge gem to 2.0.6, which adds highlighting support for JSX, Prometheus, and others. !6251 @@ -87,6 +92,7 @@ v 8.12.0 - Fix markdown anchor icon interaction (ClemMakesApps) - Test migration paths from 8.5 until current release !4874 - Replace animateEmoji timeout with eventListener (ClemMakesApps) + - Show badges in Milestone tabs. !5946 (Dan Rowden) - Optimistic locking for Issues and Merge Requests (title and description overriding prevention) - Require confirmation when not logged in for unsubscribe links !6223 (Maximiliano Perez Coto) - Add `wiki_page_events` to project hook APIs (Ben Boeckel) @@ -322,10 +322,6 @@ group :test do gem 'timecop', '~> 0.8.0' end -group :production do - gem 'gitlab_meta', '7.0' -end - gem 'newrelic_rpm', '~> 3.16' gem 'octokit', '~> 4.3.0' diff --git a/Gemfile.lock b/Gemfile.lock index a5a2e5785ac..1db8c9dd8c8 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -284,7 +284,6 @@ GEM charlock_holmes (~> 0.7.3) github-linguist (~> 4.7.0) rugged (~> 0.24.0) - gitlab_meta (7.0) gitlab_omniauth-ldap (1.2.1) net-ldap (~> 0.9) omniauth (~> 1.0) @@ -864,7 +863,6 @@ DEPENDENCIES github-markup (~> 1.4) gitlab-flowdock-git-hook (~> 1.0.1) gitlab_git (~> 10.6.6) - gitlab_meta (= 7.0) gitlab_omniauth-ldap (~> 1.2.1) gollum-lib (~> 4.2) gollum-rugged_adapter (~> 0.4.2) @@ -991,4 +989,4 @@ DEPENDENCIES wikicloth (= 0.8.1) BUNDLED WITH - 1.13.0 + 1.13.1 diff --git a/README.md b/README.md index 9661a554b9f..8236f986b56 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ## Canonical source -The cannonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/). +The canonical source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/). ## Open source software to collaborate on code diff --git a/app/assets/stylesheets/framework/flash.scss b/app/assets/stylesheets/framework/flash.scss index 0c21d0240b3..7ae309ba103 100644 --- a/app/assets/stylesheets/framework/flash.scss +++ b/app/assets/stylesheets/framework/flash.scss @@ -3,7 +3,6 @@ margin: 0; margin-bottom: $gl-padding; font-size: 14px; - z-index: 100; .flash-notice { @extend .alert; @@ -41,4 +40,3 @@ } } } - diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss index b94f524b513..6b865730487 100644 --- a/app/assets/stylesheets/pages/milestone.scss +++ b/app/assets/stylesheets/pages/milestone.scss @@ -2,13 +2,17 @@ max-width: 90%; } -li.milestone { - h4 { - font-weight: bold; - } +.milestones { + .milestone { + padding: 10px 16px; + + h4 { + font-weight: bold; + } - .progress { - height: 6px; + .progress { + height: 6px; + } } } @@ -64,3 +68,14 @@ li.milestone { border-bottom: 1px solid $border-color; padding: 20px 0; } + +@media (max-width: $screen-sm-min) { + .milestone-actions { + @include clearfix(); + padding-top: $gl-vert-padding; + + .btn:first-child { + margin-left: 0; + } + } +} diff --git a/app/assets/stylesheets/pages/pipelines.scss b/app/assets/stylesheets/pages/pipelines.scss index 1b4d12d3053..b035bfc9f3c 100644 --- a/app/assets/stylesheets/pages/pipelines.scss +++ b/app/assets/stylesheets/pages/pipelines.scss @@ -177,6 +177,10 @@ border-bottom: 2px solid $border-color; } } + + a { + display: block; + } } } diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb index b3e6e468ecd..a11c313a6b8 100644 --- a/app/helpers/milestones_helper.rb +++ b/app/helpers/milestones_helper.rb @@ -35,6 +35,30 @@ module MilestonesHelper milestone.issues.with_label(label.title).send(state).size end + # Returns count of milestones for different states + # Uses explicit hash keys as the 'opened' state URL params differs from the db value + # and we need to add the total + def milestone_counts(milestones) + counts = milestones.reorder(nil).group(:state).count + + { + opened: counts['active'] || 0, + closed: counts['closed'] || 0, + all: counts.values.sum || 0 + } + end + + # Show 'active' class if provided GET param matches check + # `or_blank` allows the function to return 'active' when given an empty param + # Could be refactored to be simpler but that may make it harder to read + def milestone_class_for_state(param, check, match_blank_param = false) + if match_blank_param + 'active' if param.blank? || param == check + else + 'active' if param == check + end + end + def milestone_progress_bar(milestone) options = { class: 'progress-bar progress-bar-success', diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb index 63a5ed14484..d9fba3d4a41 100644 --- a/app/models/project_services/custom_issue_tracker_service.rb +++ b/app/models/project_services/custom_issue_tracker_service.rb @@ -9,6 +9,10 @@ class CustomIssueTrackerService < IssueTrackerService end end + def title=(value) + self.properties['title'] = value if self.properties + end + def description if self.properties && self.properties['description'].present? self.properties['description'] diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index 689cd6ed665..2ef383960f4 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -1,4 +1,4 @@ -= form_tag(user_omniauth_callback_path(server['provider_name']), id: 'new_ldap_user' ) do += form_tag(omniauth_callback_path(:user, server['provider_name']), id: 'new_ldap_user') do = text_field_tag :username, nil, {class: "form-control top", placeholder: "#{server['label']} Login", autofocus: "autofocus"} = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} - if devise_mapping.rememberable? diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 643f7c589e6..6624d5cb427 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -24,7 +24,7 @@ = succeed '.' do More examples are in the - = link_to 'documentation', help_page_path("user/project/markdown", anchor: "wiki-specific-markdown") + = link_to 'documentation', help_page_path("user/markdown", anchor: "wiki-specific-markdown") .form-group = f.label :commit_message, class: 'control-label' diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml index cf16c203f9c..73d288e2236 100644 --- a/app/views/shared/_milestones_filter.html.haml +++ b/app/views/shared/_milestones_filter.html.haml @@ -1,10 +1,19 @@ +- if @project + - counts = milestone_counts(@project.milestones) + %ul.nav-links - %li{class: ("active" if params[:state].blank? || params[:state] == 'opened')} + %li{class: milestone_class_for_state(params[:state], 'opened', true)} = link_to milestones_filter_path(state: 'opened') do Open - %li{class: ("active" if params[:state] == 'closed')} + - if @project + %span.badge #{counts[:opened]} + %li{class: milestone_class_for_state(params[:state], 'closed')} = link_to milestones_filter_path(state: 'closed') do Closed - %li{class: ("active" if params[:state] == 'all')} + - if @project + %span.badge #{counts[:closed]} + %li{class: milestone_class_for_state(params[:state], 'all')} = link_to milestones_filter_path(state: 'all') do All + - if @project + %span.badge #{counts[:all]} diff --git a/app/views/shared/milestones/_milestone.html.haml b/app/views/shared/milestones/_milestone.html.haml index acc3ccf4dcf..3dccfb147bf 100644 --- a/app/views/shared/milestones/_milestone.html.haml +++ b/app/views/shared/milestones/_milestone.html.haml @@ -33,7 +33,7 @@ - if @project .row .col-sm-6= render('shared/milestone_expired', milestone: milestone) - .col-sm-6 + .col-sm-6.milestone-actions - if can?(current_user, :admin_milestone, milestone.project) and milestone.active? = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-xs btn-grouped" do Edit diff --git a/doc/api/README.md b/doc/api/README.md index 6e3295e0e0c..8e4f7f12b4b 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -10,6 +10,7 @@ following locations: - [Award Emoji](award_emoji.md) - [Branches](branches.md) +- [Broadcast Messages](broadcast_messages.md) - [Builds](builds.md) - [Build Triggers](build_triggers.md) - [Build Variables](build_variables.md) diff --git a/doc/ci/examples/README.md b/doc/ci/examples/README.md index 71670e6247c..40f0165deef 100644 --- a/doc/ci/examples/README.md +++ b/doc/ci/examples/README.md @@ -13,7 +13,7 @@ Apart from those, here is an collection of tutorials and guides on setting up yo - [Test a Scala application](test-scala-application.md) - [Using `dpl` as deployment tool](deployment/README.md) - [Blog post about using GitLab CI for iOS projects](https://about.gitlab.com/2016/03/10/setting-up-gitlab-ci-for-ios-projects/) -- [Repo's with examples for various languages](https://gitlab.com/groups/gitlab-examples) +- [Repositories with examples for various languages](https://gitlab.com/groups/gitlab-examples) - [The .gitlab-ci.yml file for GitLab itself](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/.gitlab-ci.yml) [gitlab-ci-templates]: https://gitlab.com/gitlab-org/gitlab-ci-yml diff --git a/doc/ci/variables/README.md b/doc/ci/variables/README.md index 6a971c3ae87..22d67bd9964 100644 --- a/doc/ci/variables/README.md +++ b/doc/ci/variables/README.md @@ -44,7 +44,7 @@ The `API_TOKEN` will take the Secure Variable value: `SECURE`. | **CI_PROJECT_URL** | 8.10 | 0.5 | The HTTP address to access project | | **CI_PROJECT_DIR** | all | all | The full path where the repository is cloned and where the build is run | | **CI_REGISTRY** | 8.10 | 0.5 | If the Container Registry is enabled it returns the address of GitLab's Container Registry | -| **CI_REGISTRY_IMAGE** | 8.10 | 0.5 | If the Container Registry is enabled for the project it returnes the address of the registry tied to the specific project | +| **CI_REGISTRY_IMAGE** | 8.10 | 0.5 | If the Container Registry is enabled for the project it returns the address of the registry tied to the specific project | | **CI_RUNNER_ID** | 8.10 | 0.5 | The unique id of runner being used | | **CI_RUNNER_DESCRIPTION** | 8.10 | 0.5 | The description of the runner as saved in GitLab | | **CI_RUNNER_TAGS** | 8.10 | 0.5 | The defined runner tags | diff --git a/lib/api/api.rb b/lib/api/api.rb index 74ca4728695..cb47ec8f33f 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -28,6 +28,7 @@ module API helpers ::SentryHelper helpers ::API::Helpers + # Keep in alphabetical order mount ::API::AccessRequests mount ::API::AwardEmoji mount ::API::Branches @@ -48,6 +49,7 @@ module API mount ::API::Lint mount ::API::Members mount ::API::MergeRequests + mount ::API::MergeRequestDiffs mount ::API::Milestones mount ::API::Namespaces mount ::API::Notes @@ -70,6 +72,5 @@ module API mount ::API::Triggers mount ::API::Users mount ::API::Variables - mount ::API::MergeRequestDiffs end end diff --git a/lib/banzai/filter/sanitization_filter.rb b/lib/banzai/filter/sanitization_filter.rb index 6e13282d5f4..2470362e019 100644 --- a/lib/banzai/filter/sanitization_filter.rb +++ b/lib/banzai/filter/sanitization_filter.rb @@ -7,7 +7,7 @@ module Banzai UNSAFE_PROTOCOLS = %w(data javascript vbscript).freeze def whitelist - whitelist = super.dup + whitelist = super customize_whitelist(whitelist) @@ -42,58 +42,58 @@ module Banzai # Allow any protocol in `a` elements... whitelist[:protocols].delete('a') - whitelist[:transformers] = whitelist[:transformers].dup - # ...but then remove links with unsafe protocols - whitelist[:transformers].push(remove_unsafe_links) + whitelist[:transformers].push(self.class.remove_unsafe_links) # Remove `rel` attribute from `a` elements - whitelist[:transformers].push(remove_rel) + whitelist[:transformers].push(self.class.remove_rel) # Remove `class` attribute from non-highlight spans - whitelist[:transformers].push(clean_spans) + whitelist[:transformers].push(self.class.clean_spans) whitelist end - def remove_unsafe_links - lambda do |env| - node = env[:node] + class << self + def remove_unsafe_links + lambda do |env| + node = env[:node] - return unless node.name == 'a' - return unless node.has_attribute?('href') + return unless node.name == 'a' + return unless node.has_attribute?('href') - begin - uri = Addressable::URI.parse(node['href']) - uri.scheme = uri.scheme.strip.downcase if uri.scheme + begin + uri = Addressable::URI.parse(node['href']) + uri.scheme = uri.scheme.strip.downcase if uri.scheme - node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(uri.scheme) - rescue Addressable::URI::InvalidURIError - node.remove_attribute('href') + node.remove_attribute('href') if UNSAFE_PROTOCOLS.include?(uri.scheme) + rescue Addressable::URI::InvalidURIError + node.remove_attribute('href') + end end end - end - def remove_rel - lambda do |env| - if env[:node_name] == 'a' - env[:node].remove_attribute('rel') + def remove_rel + lambda do |env| + if env[:node_name] == 'a' + env[:node].remove_attribute('rel') + end end end - end - def clean_spans - lambda do |env| - node = env[:node] + def clean_spans + lambda do |env| + node = env[:node] - return unless node.name == 'span' - return unless node.has_attribute?('class') + return unless node.name == 'span' + return unless node.has_attribute?('class') - unless has_ancestor?(node, 'pre') - node.remove_attribute('class') - end + unless node.ancestors.any? { |n| n.name.casecmp('pre').zero? } + node.remove_attribute('class') + end - { node_whitelist: [node] } + { node_whitelist: [node] } + end end end end diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 88803d76623..1c42acab9c1 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -73,5 +73,7 @@ excluded_attributes: methods: statuses: - :type + services: + - :type merge_request_diff: - :utf8_st_diffs diff --git a/lib/gitlab/workhorse.rb b/lib/gitlab/workhorse.rb index 60aae541d46..5d33f98e89e 100644 --- a/lib/gitlab/workhorse.rb +++ b/lib/gitlab/workhorse.rb @@ -60,7 +60,7 @@ module Gitlab def send_git_diff(repository, diff_refs) params = { 'RepoPath' => repository.path_to_repo, - 'ShaFrom' => diff_refs.start_sha, + 'ShaFrom' => diff_refs.base_sha, 'ShaTo' => diff_refs.head_sha } @@ -73,7 +73,7 @@ module Gitlab def send_git_patch(repository, diff_refs) params = { 'RepoPath' => repository.path_to_repo, - 'ShaFrom' => diff_refs.start_sha, + 'ShaFrom' => diff_refs.base_sha, 'ShaTo' => diff_refs.head_sha } @@ -107,15 +107,15 @@ module Gitlab bytes end end - + def write_secret bytes = SecureRandom.random_bytes(SECRET_LENGTH) - File.open(secret_path, 'w:BINARY', 0600) do |f| + File.open(secret_path, 'w:BINARY', 0600) do |f| f.chmod(0600) f.write(Base64.strict_encode64(bytes)) end end - + def verify_api_request!(request_headers) JWT.decode( request_headers[INTERNAL_API_REQUEST_HEADER], @@ -128,7 +128,7 @@ module Gitlab def secret_path Rails.root.join('.gitlab_workhorse_secret') end - + protected def encode(hash) diff --git a/spec/factories/milestones.rb b/spec/factories/milestones.rb index e9e85962fe4..84da71ed6dc 100644 --- a/spec/factories/milestones.rb +++ b/spec/factories/milestones.rb @@ -3,10 +3,15 @@ FactoryGirl.define do title project + trait :active do + state "active" + end + trait :closed do - state :closed + state "closed" end + factory :active_milestone, traits: [:active] factory :closed_milestone, traits: [:closed] end end diff --git a/spec/helpers/milestones_helper_spec.rb b/spec/helpers/milestones_helper_spec.rb new file mode 100644 index 00000000000..28c2268f8d0 --- /dev/null +++ b/spec/helpers/milestones_helper_spec.rb @@ -0,0 +1,33 @@ +require 'spec_helper' + +describe MilestonesHelper do + describe '#milestone_counts' do + let(:project) { FactoryGirl.create(:project) } + let(:counts) { helper.milestone_counts(project.milestones) } + + context 'when there are milestones' do + let!(:milestone_1) { FactoryGirl.create(:active_milestone, project: project) } + let!(:milestone_2) { FactoryGirl.create(:active_milestone, project: project) } + let!(:milestone_3) { FactoryGirl.create(:closed_milestone, project: project) } + + it 'returns the correct counts' do + expect(counts).to eq(opened: 2, closed: 1, all: 3) + end + end + + context 'when there are only milestones of one type' do + let!(:milestone_1) { FactoryGirl.create(:active_milestone, project: project) } + let!(:milestone_2) { FactoryGirl.create(:active_milestone, project: project) } + + it 'returns the correct counts' do + expect(counts).to eq(opened: 2, closed: 0, all: 2) + end + end + + context 'when there are no milestones' do + it 'returns the correct counts' do + expect(counts).to eq(opened: 0, closed: 0, all: 0) + end + end + end +end diff --git a/spec/lib/gitlab/import_export/project.json b/spec/lib/gitlab/import_export/project.json index 281f6cf1177..056eaa2d719 100644 --- a/spec/lib/gitlab/import_export/project.json +++ b/spec/lib/gitlab/import_export/project.json @@ -6918,6 +6918,7 @@ "note_events": true, "build_events": true, "category": "issue_tracker", + "type": "CustomIssueTrackerService", "default": true, "wiki_page_events": true }, 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 feacb295231..65d0aaf53d6 100644 --- a/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_restorer_spec.rb @@ -107,6 +107,12 @@ describe Gitlab::ImportExport::ProjectTreeRestorer, services: true do expect(Label.first.label_links.first.target).not_to be_nil end + it 'restores the correct service' do + restored_project_json + + expect(CustomIssueTrackerService.first).not_to be_nil + end + context 'Merge requests' do before do restored_project_json diff --git a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb index d891c2d0cc6..cf8f2200c57 100644 --- a/spec/lib/gitlab/import_export/project_tree_saver_spec.rb +++ b/spec/lib/gitlab/import_export/project_tree_saver_spec.rb @@ -111,6 +111,10 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do expect(saved_project_json['issues'].first['label_links'].first['label']).not_to be_empty end + it 'saves the correct service type' do + expect(saved_project_json['services'].first['type']).to eq('CustomIssueTrackerService') + end + it 'has project feature' do project_feature = saved_project_json['project_feature'] expect(project_feature).not_to be_empty @@ -161,6 +165,7 @@ describe Gitlab::ImportExport::ProjectTreeSaver, services: true do commit_id: ci_pipeline.sha) create(:event, target: milestone, project: project, action: Event::CREATED, author: user) + create(:service, project: project, type: 'CustomIssueTrackerService', category: 'issue_tracker') project.project_feature.update_attribute(:issues_access_level, ProjectFeature::DISABLED) project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::ENABLED) diff --git a/spec/lib/gitlab/workhorse_spec.rb b/spec/lib/gitlab/workhorse_spec.rb index 6c7fa7e7c15..b5b685da904 100644 --- a/spec/lib/gitlab/workhorse_spec.rb +++ b/spec/lib/gitlab/workhorse_spec.rb @@ -1,8 +1,16 @@ require 'spec_helper' describe Gitlab::Workhorse, lib: true do - let(:project) { create(:project) } - let(:subject) { Gitlab::Workhorse } + let(:project) { create(:project) } + let(:repository) { project.repository } + + def decode_workhorse_header(array) + key, value = array + command, encoded_params = value.split(":") + params = JSON.parse(Base64.urlsafe_decode64(encoded_params)) + + [key, command, params] + end describe ".send_git_archive" do context "when the repository doesn't have an archive file path" do @@ -11,11 +19,37 @@ describe Gitlab::Workhorse, lib: true do end it "raises an error" do - expect { subject.send_git_archive(project.repository, ref: "master", format: "zip") }.to raise_error(RuntimeError) + expect { described_class.send_git_archive(project.repository, ref: "master", format: "zip") }.to raise_error(RuntimeError) end end end + describe '.send_git_patch' do + let(:diff_refs) { double(base_sha: "base", head_sha: "head") } + subject { described_class.send_git_patch(repository, diff_refs) } + + it 'sets the header correctly' do + key, command, params = decode_workhorse_header(subject) + + expect(key).to eq("Gitlab-Workhorse-Send-Data") + expect(command).to eq("git-format-patch") + expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head") + end + end + + describe '.send_git_diff' do + let(:diff_refs) { double(base_sha: "base", head_sha: "head") } + subject { described_class.send_git_patch(repository, diff_refs) } + + it 'sets the header correctly' do + key, command, params = decode_workhorse_header(subject) + + expect(key).to eq("Gitlab-Workhorse-Send-Data") + expect(command).to eq("git-format-patch") + expect(params).to eq("RepoPath" => repository.path_to_repo, "ShaFrom" => "base", "ShaTo" => "head") + end + end + describe ".secret" do subject { described_class.secret } diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb index 7020667ea58..63320931e76 100644 --- a/spec/models/project_services/custom_issue_tracker_service_spec.rb +++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb @@ -25,5 +25,21 @@ describe CustomIssueTrackerService, models: true do it { is_expected.not_to validate_presence_of(:issues_url) } it { is_expected.not_to validate_presence_of(:new_issue_url) } end + + context 'title' do + let(:issue_tracker) { described_class.new(properties: {}) } + + it 'sets a default title' do + issue_tracker.title = nil + + expect(issue_tracker.title).to eq('Custom Issue Tracker') + end + + it 'sets the custom title' do + issue_tracker.title = 'test title' + + expect(issue_tracker.title).to eq('test title') + end + end end end |