Merge pull request #19 from frywer/fix_api

Fix & extend api, new permission
This commit is contained in:
Ivan Marangoz 2020-05-16 12:51:39 +01:00 committed by GitHub
commit 4923fd1488
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 255 additions and 168 deletions

View File

@ -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'

View File

@ -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 {}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -0,0 +1,3 @@
json.array! @custom_entities do |entity|
render_api_custom_entity entity, json
end

View File

@ -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) %>
<hr />
<div class="contextual">
<%= link_to l(:button_add), new_custom_entity_path(
custom_table_id: custom_table.id,
back_url: back_url,
issue_id: issue.id), remote: true %>
</div>
<% if User.current.allowed_to?(:manage_custom_tables, nil, global: true) %>
<div class="contextual">
<%= link_to l(:button_add), new_custom_entity_path(
custom_table_id: custom_table.id,
back_url: back_url,
issue_id: issue.id), remote: true %>
</div>
<% end %>
<p><strong><%= custom_table.name %></strong></p>
<%= render partial: 'query_custom_table', locals: {
query: query,

View File

@ -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 %>
</td>
</tr>
<% end %>

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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'