diff --git a/Gemfile b/Gemfile index 019f211..e889f33 100644 --- a/Gemfile +++ b/Gemfile @@ -1,10 +1,9 @@ -group :development do - gem 'webmock' +group :test do gem 'factory_bot_rails' gem 'rspec-rails' - gem 'poltergeist' - gem 'database_cleaner', '~> 1.6.1' - gem 'rspec-retry' + gem 'database_cleaner' + gem 'rails-controller-testing' end gem 'awesome_nested_set' +gem 'jbuilder' \ No newline at end of file diff --git a/app/controllers/custom_tables_controller.rb b/app/controllers/custom_tables_controller.rb index 02d1cf6..641f843 100644 --- a/app/controllers/custom_tables_controller.rb +++ b/app/controllers/custom_tables_controller.rb @@ -55,7 +55,6 @@ class CustomTablesController < ApplicationController @entity_count = scope.count @entity_pages = Paginator.new @entity_count, per_page_option, params['page'] @custom_entities ||= scope.offset(@entity_pages.offset).limit(@entity_pages.per_page).to_a - respond_to do |format| format.html {} format.api {} diff --git a/app/helpers/custom_entities_helper.rb b/app/helpers/custom_entities_helper.rb index 140e60e..0474abd 100644 --- a/app/helpers/custom_entities_helper.rb +++ b/app/helpers/custom_entities_helper.rb @@ -11,11 +11,14 @@ module CustomEntitiesHelper end def render_api_custom_entity(custom_entity, api) - return if custom_entity.custom_values.empty? || custom_value.custom_field.external_name.blank? - - custom_entity.custom_values.each do |custom_value| - api.__send__(custom_value.custom_field.external_name, custom_value.value) - end - + return if custom_entity.custom_values.empty? + api.id custom_entity.id + custom_entity.custom_field_values.each do |custom_field_value| + custom_field = custom_field_value.custom_field + external_name = custom_field.external_name + value = custom_field_value.value + next unless external_name.present? + api.__send__(external_name, value) + end end end diff --git a/app/models/custom_entity.rb b/app/models/custom_entity.rb index 9bcd496..9d4c68e 100644 --- a/app/models/custom_entity.rb +++ b/app/models/custom_entity.rb @@ -75,4 +75,24 @@ class CustomEntity < ActiveRecord::Base custom_field_values.detect {|v| v.custom_field.external_name == external_name}.try(:value) end + def external_values=(values) + custom_field_values.each do |custom_field_value| + key = custom_field_value.custom_field.external_name + next unless key.present? + if values.has_key?(key) + custom_field_value.value = values[key] + end + end + @custom_field_values_changed = true + end + + def to_h + values = {} + custom_field_values.each do |value| + values[value.custom_field.external_name] = value.value if value.custom_field.external_name.present? + end + values["id"] = id + values + end + end diff --git a/app/views/custom_tables/show.api.rsb b/app/views/custom_tables/show.api.rsb deleted file mode 100644 index 10e93c1..0000000 --- a/app/views/custom_tables/show.api.rsb +++ /dev/null @@ -1,8 +0,0 @@ -api.array :entities do - @custom_table.custom_entities.each do |custom_entity| - api.entity do - render_api_custom_entity custom_entity, api - end - end -end - diff --git a/app/views/custom_tables/show.json.jbuilder b/app/views/custom_tables/show.json.jbuilder new file mode 100644 index 0000000..79a08b6 --- /dev/null +++ b/app/views/custom_tables/show.json.jbuilder @@ -0,0 +1,3 @@ +json.array! @custom_entities do |entity| + render_api_custom_entity entity, json +end \ No newline at end of file diff --git a/app/views/issues/_custom_tables.html.erb b/app/views/issues/_custom_tables.html.erb index 641828b..9dadab1 100644 --- a/app/views/issues/_custom_tables.html.erb +++ b/app/views/issues/_custom_tables.html.erb @@ -1,15 +1,17 @@ -<% if User.current.allowed_to?(:manage_custom_tables, nil, global: true) %> +<% if User.current.allowed_to?(:manage_custom_tables, nil, global: true) || User.current.allowed_to?(:view_custom_tables, nil, global: true) %> <% issue.project.all_issue_custom_tables(issue).each do |custom_table| %> <% query = custom_table.query(totalable_all: true) %> <% query.add_short_filter('issue_id', "=#{issue.id}") %> <% back_url = issue_path(issue) %>
<%= custom_table.name %>
<%= render partial: 'query_custom_table', locals: { query: query, diff --git a/app/views/issues/_query_custom_table.html.erb b/app/views/issues/_query_custom_table.html.erb index a1d7fbc..3c16477 100644 --- a/app/views/issues/_query_custom_table.html.erb +++ b/app/views/issues/_query_custom_table.html.erb @@ -24,8 +24,10 @@ <% if User.current.admin? %> <%= link_to l(:button_show), custom_entity_path(entity), title: l(:button_show), class: 'icon-only icon-magnifier' %> <% end %> - <%= link_to l(:button_edit), edit_custom_entity_path(entity, edit_query: true, back_url: back_url), remote: true, title: l(:button_edit), class: 'icon-only icon-edit' %> - <%= link_to l(:button_delete), custom_entity_path(entity, custom_table_id: custom_table, back_url: back_url), data: {confirm: l(:text_are_you_sure)}, method: :delete, title: l(:button_delete), class: 'icon-only icon-del' %> + <% if User.current.allowed_to?(:manage_custom_tables, nil, global: true) %> + <%= link_to l(:button_edit), edit_custom_entity_path(entity, edit_query: true, back_url: back_url), remote: true, title: l(:button_edit), class: 'icon-only icon-edit' %> + <%= link_to l(:button_delete), custom_entity_path(entity, custom_table_id: custom_table, back_url: back_url), data: {confirm: l(:text_are_you_sure)}, method: :delete, title: l(:button_delete), class: 'icon-only icon-del' %> + <% end %> <% end %> diff --git a/init.rb b/init.rb index 3ce6bd7..a321f44 100644 --- a/init.rb +++ b/init.rb @@ -2,14 +2,19 @@ Redmine::Plugin.register :custom_tables do name 'Custom Tables plugin' author 'Ivan Marangoz' description 'This is a plugin for Redmine' - version '1.0.4' - requires_redmine :version_or_higher => '4.0.0' + version '1.0.5' + requires_redmine :version_or_higher => '3.4.0' url 'https://github.com/frywer/custom_tables' author_url 'https://github.com/frywer' permission :manage_custom_tables, { custom_entities: [:new, :edit, :create, :update, :destroy, :context_menu, :bulk_edit, :bulk_update], }, global: true + + permission :view_custom_tables, { + custom_entities: [:show], + }, global: true + Redmine::FieldFormat::UserFormat.customized_class_names << 'CustomEntity' end diff --git a/spec/controllers/custom_entities_controller_spec.rb b/spec/controllers/custom_entities_controller_spec.rb index c11aa49..9a07988 100644 --- a/spec/controllers/custom_entities_controller_spec.rb +++ b/spec/controllers/custom_entities_controller_spec.rb @@ -1,6 +1,7 @@ require "spec_helper" describe CustomEntitiesController, logged: true do + render_views let(:custom_table) { FactoryBot.create(:custom_table) } let(:custom_fields) { FactoryBot.create_list(:custom_field, 3, custom_table_id: custom_table.id) } @@ -11,55 +12,68 @@ describe CustomEntitiesController, logged: true do include_context 'logged as admin' - it '#show' do - custom_value - get :show, params: { id: custom_entity } - expect(response).to have_http_status(:success) + describe '#show' do + it 'return success response' do + custom_value + get :show, params: { id: custom_entity } + expect(response).to have_http_status(:success) + end end - it '#new' do - custom_value - get :new, params: { custom_table_id: custom_table.id } - expect(response).to have_http_status(:success) + describe '#new' do + it 'return success response' do + custom_value + get :new, params: { custom_table_id: custom_table.id } + expect(response).to have_http_status(:success) + end end - it '#create' do - custom_fields - cf_values = custom_fields.inject({}) {|k, v| k[v.id.to_s] = "value#{v.name}"; k} - expect { - post :create, params: { custom_entity: { custom_table_id: custom_table.id, custom_field_values: cf_values } } - }.to change { CustomEntity.count }.by(1) + describe '#create' do + it 'create new entity' do + custom_fields + cf_values = custom_fields.inject({}) {|k, v| k[v.id.to_s] = "value#{v.name}"; k} + expect { + post :create, params: { custom_entity: { custom_table_id: custom_table.id, custom_field_values: cf_values } } + }.to change { CustomEntity.count }.by(1) + end end - it '#edit' do - get :edit, params: { id: custom_entity } - expect(response).to have_http_status(:success) + describe '#edit' do + it 'return success response' do + get :edit, params: { id: custom_entity } + expect(response).to have_http_status(:success) + end end - it '#update' do - custom_value - cf_values = custom_entity.custom_fields.inject({}) {|k, v| k[v.id.to_s] = "new_value"; k} + describe '#update' do + it 'updates entity' do + custom_value + cf_values = custom_entity.custom_fields.inject({}) {|k, v| k[v.id.to_s] = "new_value"; k} - expect { - put :update, params: { id: custom_entity, custom_entity: { custom_field_values: cf_values } } - }.to change { custom_entity.reload.custom_values.last.value } + expect { + put :update, params: { id: custom_entity, custom_entity: { custom_field_values: cf_values } } + }.to change { custom_entity.reload.custom_values.last.value } + end + + it 'update notes' do + expect { + put :update, params: { id: custom_entity, custom_entity: { notes: 'notes' } } + }.to change(Journal, :count) + end end - it 'update notes' do - expect { - put :update, params: { id: custom_entity, custom_entity: { notes: 'notes' } } - }.to change(Journal, :count) + describe '#delete' do + it 'remove entity' do + custom_value + expect {delete :destroy, params: { id: custom_entity }}.to change(CustomEntity, :count).by(-1) + end end - it '#delete' do - custom_value - expect {delete :destroy, params: { id: custom_entity }}.to change(CustomEntity, :count).by(-1) + describe '#bulk_delete' do + it 'removes list of intities' do + custom_entity + custom_entity_2 + expect {delete :destroy, params: { ids: [custom_entity.id, custom_entity_2.id] }}.to change(CustomEntity, :count).by(-2) + end end - - it '#bulk_delete' do - custom_entity - custom_entity_2 - expect {delete :destroy, params: { ids: [custom_entity.id, custom_entity_2.id] }}.to change(CustomEntity, :count).by(-2) - end - end \ No newline at end of file diff --git a/spec/controllers/custom_tables_controller_spec.rb b/spec/controllers/custom_tables_controller_spec.rb index 95d2cc0..f32ae79 100644 --- a/spec/controllers/custom_tables_controller_spec.rb +++ b/spec/controllers/custom_tables_controller_spec.rb @@ -1,48 +1,76 @@ require "spec_helper" describe CustomTablesController, logged: true do + render_views include_context 'logged as admin' - let(:custom_table) { FactoryBot.create(:custom_table) } + let(:custom_table) { FactoryBot.create(:custom_table, custom_fields: [ + { external_name: 'name', field_format: 'string', is_required: true }, + { external_name: 'age', field_format: 'int' }, + { field_format: 'float' } + ] ) } - it '#index' do - get :index - expect(response).to have_http_status(:success) + let!(:custom_entity) { FactoryBot.create(:custom_entity, custom_table: custom_table, external_values: { + 'name' => 'Bob', + 'age' => '24' + } ) } + + describe '#index' do + it 'return success response' do + get :index + expect(response).to have_http_status(:success) + end end - it '#show' do - get :show, params: { id: custom_table } - expect(response).to have_http_status(:success) + describe '#show' do + it 'return success response' do + get :show, params: { id: custom_table } + expect(response).to have_http_status(:success) + end + + it 'returns json' do + get :show, params: { id: custom_table, format: 'json' } + expect(response).to have_http_status(:success) + end end - it '#new' do - get :new - expect(response).to have_http_status(:success) + describe '#new' do + it 'return success response' do + get :new + expect(response).to have_http_status(:success) + end end - it '#create' do - expect { - post :create, params: { custom_table: { name: 'Contacts'} } - }.to change { CustomTable.count }.by(1) + describe '#create' do + it 'create new entity' do + expect { + post :create, params: { custom_table: { name: 'Contacts'} } + }.to change { CustomTable.count }.by(1) + end end - it '#edit' do - get :edit, params: { id: custom_table } - expect(response).to have_http_status(:success) + describe '#edit' do + it 'return success response' do + get :edit, params: { id: custom_table } + expect(response).to have_http_status(:success) + end end - it '#update' do - expect { - put :update, params: { id: custom_table.id, custom_table: { name: 'Contacts updated'} } - }.to change { custom_table.reload.name } + describe '#update' do + it 'updates entity' do + expect { + put :update, params: { id: custom_table.id, custom_table: { name: 'Contacts updated'} } + }.to change { custom_table.reload.name } + end end - it '#destroy' do - custom_table - expect { - delete :destroy, params: { id: custom_table.id } - }.to change { CustomTable.count } + describe '#destroy' do + it 'destroy entity' do + custom_table + expect { + delete :destroy, params: { id: custom_table.id } + }.to change { CustomTable.count } + end end - end \ No newline at end of file diff --git a/spec/controllers/table_fields_controller_spec.rb b/spec/controllers/table_fields_controller_spec.rb index 8658a98..5a5bd74 100644 --- a/spec/controllers/table_fields_controller_spec.rb +++ b/spec/controllers/table_fields_controller_spec.rb @@ -1,6 +1,7 @@ require "spec_helper" describe TableFieldsController, logged: true do + render_views let(:custom_table) { FactoryBot.create(:custom_table) } let(:custom_field) { FactoryBot.create(:custom_field, custom_table_id: custom_table.id, name: 'old cf name') } @@ -9,51 +10,64 @@ describe TableFieldsController, logged: true do include_context 'logged as admin' - it '#new' do - get :new, params: { custom_table_id: custom_table.id } - expect(response).to have_http_status(:success) - end - - context '#create' do - it 'is valid' do - expect { - post :create, params: { custom_field: { field_format: 'string', name: 'some name', description: 'fdsf', is_filter: '1', is_required: '0', custom_table_id: custom_table.id }, type: "CustomEntityCustomField" } - }.to change { CustomField.count }.by(1) - end - - it 'is invalid' do - custom_field - expect { - post :create, params: { custom_field: { field_format: 'string', name: 'old cf name', description: 'fdsf', is_filter: '1', is_required: '0', custom_table_id: custom_table.id }, type: "CustomEntityCustomField", back_url: new_table_field_path } - }.to change { CustomField.count }.by(0) - end - - it 'is invalid nil name' do - custom_entity - expect { - post :create, params: { custom_field: { field_format: 'string', name: '', description: 'fdsf', is_filter: '1', is_required: '0', custom_table_id: custom_table.id }, type: "CustomEntityCustomField", back_url: new_table_field_path } - }.to change { CustomField.count }.by(0) + describe '#new' do + it 'return success response' do + get :new, params: { custom_table_id: custom_table.id } + expect(response).to have_http_status(:success) end end - it '#edit' do - get :edit, params: { id: custom_field } - expect(response).to have_http_status(:success) + describe '#create' do + context 'when valid' do + it 'creates new entity' do + expect { + post :create, params: { custom_field: { field_format: 'string', name: 'some name', description: 'fdsf', is_filter: '1', is_required: '0', custom_table_id: custom_table.id }, type: "CustomEntityCustomField" } + }.to change { CustomField.count }.by(1) + end + end + + context 'when invalid' do + it 'render new template' do + custom_field + expect { + post :create, params: { custom_field: { field_format: 'string', name: 'old cf name', description: 'fdsf', is_filter: '1', is_required: '0', custom_table_id: custom_table.id }, type: "CustomEntityCustomField", back_url: new_table_field_path } + }.to change { CustomField.count }.by(0) + + expect(response).to render_template(:new) + end + + it 'render new template' do + custom_entity + expect { + post :create, params: { custom_field: { field_format: 'string', name: '', description: 'fdsf', is_filter: '1', is_required: '0', custom_table_id: custom_table.id }, type: "CustomEntityCustomField", back_url: new_table_field_path } + }.to change { CustomField.count }.by(0) + + expect(response).to render_template(:new) + end + end end - context '#update' do + describe '#edit' do + it 'return success response' do + get :edit, params: { id: custom_field } + expect(response).to have_http_status(:success) + end + end + + describe '#update' do let(:custom_field_1) { FactoryBot.create(:custom_field, custom_table_id: custom_table.id, name: 'cf_1') } - it 'is valid' do + it 'updates entity' do expect { put :update, params: { id: custom_field, custom_field: { name: 'BlaCF'} } }.to change {custom_field.reload.name} end end - it '#delete' do - custom_value - expect {delete :destroy, params: { id: custom_field.id }}.to change(CustomField, :count).by(-1) + describe '#delete' do + it 'deletes entity' do + custom_value + expect {delete :destroy, params: { id: custom_field.id }}.to change(CustomField, :count).by(-1) + end end - end \ No newline at end of file diff --git a/spec/factories/factories.rb b/spec/factories/factories.rb index 6955749..2ee5894 100644 --- a/spec/factories/factories.rb +++ b/spec/factories/factories.rb @@ -1,5 +1,9 @@ FactoryBot.define do + sequence :name do |n| + "Name-##{n}" + end + factory :project do sequence(:name) { |n| "Project-##{n}" } sequence(:identifier) { |n| "project#{n}" } @@ -10,8 +14,19 @@ FactoryBot.define do end factory :custom_table do - sequence(:name) { |n| "Table-##{n}" } + transient do + # { external_name => field_format } + custom_fields { [] } + end + + sequence(:name) { |n| "Table-#{n}" } association :author, factory: :user + # create custom_fields + after(:create) do |table, evaluator| + evaluator.custom_fields.each do |field| + create(:custom_field, custom_table_id: table.id, **field) + end + end end factory :custom_entity do @@ -27,24 +42,14 @@ FactoryBot.define do end factory :custom_field do + name type { 'CustomEntityCustomField' } - sequence(:name) { |n| "Name-3213#{n}" } field_format { 'string' } - possible_values { nil } - regexp { '' } - min_length { nil } - max_length { nil } - is_required { false } - is_for_all { false } + sequence(:position) is_filter { true } - sequence(:position) { |n| n } - searchable { false } - default_value { '0' } editable { true } visible { true } - multiple { false } - description { '' } - parent_table_id { nil } + association :custom_table, factory: :custom_table end factory :user_custom_field, parent: :custom_field do diff --git a/spec/models/custom_entity_spec.rb b/spec/models/custom_entity_spec.rb new file mode 100644 index 0000000..07973e1 --- /dev/null +++ b/spec/models/custom_entity_spec.rb @@ -0,0 +1,26 @@ +require "spec_helper" + +describe CustomEntity, type: :model do + describe '#imported_values' do + let(:custom_table) { FactoryBot.create(:custom_table, custom_fields: [ + { external_name: 'name', field_format: 'string'}, + { external_name: 'age', field_format: 'int' }, + { external_name: 'height', field_format: 'float' } + ] ) } + + let(:custom_entity) { CustomEntity.new(custom_table: custom_table, author: User.current) } + + context 'when values are valid' do + it 'create custom_entity with external values' do + custom_entity.external_values = { 'name' => 'My name', + 'age' => '21', + 'height' => '140.5' } + + expect(custom_entity.to_h).to eq({'name' => 'My name', + 'age' => '21', + 'height' => '140.5', + 'id' => custom_entity.id }) + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 96017cb..7fb6632 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,23 +1,8 @@ # run test -# rspec -Iplugins/custom_tables/spec plugins/glad_custom_tables/spec +# rspec -Iplugins/custom_tables/spec plugins/custom_tables/spec ENV['RAILS_ENV'] ||= 'test' -#load simplecov -if ENV['COVERAGE'] - require 'simplecov' - SimpleCov.start 'rails' do - coverage_dir 'tmp/coverage' -### require "pry" - -#exclude core dirs coverage - add_filter do |file| - file.filename.include?('/lib/plugins/') || - !file.filename.include?('/plugins/') - end - end -end - #load rails/redmine require File.expand_path('../../../../config/environment', __FILE__) @@ -27,16 +12,6 @@ require 'rspec/rails' require 'rspec/mocks' require 'rspec/mocks/standalone' require 'factory_bot' -require 'capybara/rspec' - -# use phantom.js as js driver -require 'capybara/poltergeist' - -require 'webmock/rspec' -WebMock.disable_net_connect!(allow_localhost: true) -# Dir.glob(File.expand_path('./factories/*.rb', __FILE__)).each do |plugin_factory| -# require plugin_factory -# end require_relative 'factories/factories' require_relative 'support/user'