Commit 66dfdfb089b5542aa1ed7a2d90911492b666bd82

Authored by Victor Costa
2 parents 92a1c494 fbcb1e60

Merge branch 'proposal_response' into 'master'

Add a response to a Proposal

New feature to allow create a `Response` associated with a Proposal. A new button was added to **content manager** page that redirects to new article(of type 'Response') action. The screen below shows a new arrow icon:

![reply-article](https://softwarepublico.gov.br/gitlab/uploads/noosfero-plugins/proposals_discussion/c68346ddaa/reply-article.png)

**Obs:** Before accept this merge request, please, check this another merge about [new hotspot](https://softwarepublico.gov.br/gitlab/noosferogov/noosfero/merge_requests/5)

See merge request !2
lib/cms_helper.rb
... ... @@ -10,7 +10,11 @@ module CmsHelper
10 10 image_tag(icon_for_article(article)) + link_to(article_name, article.url)
11 11 else
12 12 if "ProposalsDiscussionPlugin::Proposal".eql? article.type
13   - link_to article.abstract, article.url, :class => icon_for_article(article)
  13 + if article.children_count > 0
  14 + link_to article.abstract, {:action => 'view', :id => article.id}, :class => 'icon icon-replyied-article'
  15 + else
  16 + link_to article.abstract, article.url, :class => icon_for_article(article)
  17 + end
14 18 else
15 19 link_to article_name, article.url, :class => icon_for_article(article)
16 20 end
... ...
lib/proposals_discussion_plugin.rb
... ... @@ -23,6 +23,9 @@ class ProposalsDiscussionPlugin < Noosfero::Plugin
23 23 types << ProposalsDiscussionPlugin::Proposal
24 24 types << ProposalsDiscussionPlugin::Story
25 25 end
  26 + if parent.kind_of?(ProposalsDiscussionPlugin::Proposal)
  27 + types << ProposalsDiscussionPlugin::Response
  28 + end
26 29 types
27 30 else
28 31 [ProposalsDiscussionPlugin::Discussion,
... ... @@ -52,6 +55,14 @@ class ProposalsDiscussionPlugin &lt; Noosfero::Plugin
52 55 [ProposalsDiscussionPlugin::API]
53 56 end
54 57  
  58 + def extra_content_actions(article)
  59 + proc do
  60 + if article.kind_of? ProposalsDiscussionPlugin::Proposal
  61 + render :partial => 'proposals_discussion/view_item_buttons', :locals => {:article => article}
  62 + end
  63 + end
  64 + end
  65 +
55 66 # schedule ranking job in initialization process
56 67 ProposalsDiscussionPlugin::RankingJob.new.schedule
57 68  
... ...
lib/proposals_discussion_plugin/api_new_relic_instrumenter.rb 0 → 100644
... ... @@ -0,0 +1,46 @@
  1 +class ProposalsDiscussionPlugin::ApiNewRelicInstrumenter < Grape::Middleware::Base
  2 + include NewRelic::Agent::Instrumentation::ControllerInstrumentation
  3 +
  4 + def call_with_newrelic(&block)
  5 + trace_options = {
  6 + :category => :rack,
  7 + :path => "#{route_path}\##{route_method}",
  8 + :request => Grape::Request.new(@env)
  9 + }
  10 +
  11 + perform_action_with_newrelic_trace(trace_options) do
  12 + result = yield
  13 + MetricFrame.abort_transaction! if result.first == 404 # ignore cascaded calls
  14 + result
  15 + end
  16 + end
  17 +
  18 + def call(env)
  19 + @env = env
  20 + if ENV['NEW_RELIC_ID']
  21 + call_with_newrelic do
  22 + super
  23 + end
  24 + else
  25 + super
  26 + end
  27 + end
  28 +
  29 + def env
  30 + @env
  31 + end
  32 +
  33 + def route
  34 + env['api.endpoint'].routes.first
  35 + end
  36 +
  37 + def route_method
  38 + route.route_method.downcase
  39 + end
  40 +
  41 + def route_path
  42 + path = route.route_path.gsub(/^.+:version\/|^\/|:|\(.+\)/, '').tr('/', '-')
  43 + "api.#{route.route_version}.#{path}"
  44 + end
  45 +
  46 +end
... ...
lib/proposals_discussion_plugin/response.rb 0 → 100644
... ... @@ -0,0 +1,27 @@
  1 +class ProposalsDiscussionPlugin::Response < TinyMceArticle
  2 +
  3 + validates_presence_of :body
  4 +
  5 + validate :check_parent_type
  6 +
  7 + def self.short_description
  8 + _("Proposal Response")
  9 + end
  10 +
  11 + def self.description
  12 + _("The response of a Proposal")
  13 + end
  14 +
  15 + def icon_name
  16 + 'response'
  17 + end
  18 +
  19 + protected
  20 +
  21 + def check_parent_type
  22 + unless parent.is_a? ProposalsDiscussionPlugin::Proposal
  23 + errors.add(:parent, N_('of Response needs be a Proposal'))
  24 + end
  25 + end
  26 +
  27 +end
... ...
public/images/reply.png 0 → 100644

196 Bytes

public/images/replyied-article.png 0 → 100644

743 Bytes

public/images/response-article.png 0 → 100644

291 Bytes

public/style.css
... ... @@ -402,3 +402,15 @@ div.confirm_evaluation_button a.disabled {
402 402 margin-top: 15px;
403 403 margin-left: 10px;
404 404 }
  405 +
  406 +.icon-child-article {
  407 + background-image: url('/plugins/proposals_discussion/images/reply.png');
  408 +}
  409 +
  410 +.icon-response {
  411 + background: url('/plugins/proposals_discussion/images/response-article.png') no-repeat;
  412 +}
  413 +
  414 +.icon-replyied-article {
  415 + background: url('/plugins/proposals_discussion/images/replyied-article.png') no-repeat;
  416 +}
... ...
test/unit/proposal_test.rb
... ... @@ -124,4 +124,52 @@ class ProposalTest &lt; ActiveSupport::TestCase
124 124 assert_equal [location], proposal.locations
125 125 end
126 126  
  127 + should 'add a response to a proposal' do
  128 + proposal.save!
  129 + data = {
  130 + :name => 'Response',
  131 + :body => 'A response test',
  132 + :abstract => 'Test',
  133 + :parent => proposal,
  134 + :profile => profile
  135 + }
  136 +
  137 + response = create(ProposalsDiscussionPlugin::Response, data)
  138 + response.save!
  139 +
  140 + assert_equal proposal.id, response.parent.id
  141 + end
  142 +
  143 + should 'not add a response to a proposal without a body' do
  144 + proposal.save!
  145 + data = {
  146 + :name => 'Response',
  147 + :abstract => 'Test',
  148 + :parent => proposal,
  149 + :profile => profile
  150 + }
  151 +
  152 + err = assert_raises ActiveRecord::RecordInvalid do
  153 + response = create(ProposalsDiscussionPlugin::Response, data)
  154 + end
  155 +
  156 + assert_match "Body can't be blank", err.message
  157 + end
  158 +
  159 + should 'not add a response to a article that isnt a proposal' do
  160 + article = create(Article, :name => 'test article', :profile => @profile)
  161 + data = {
  162 + :name => 'Response',
  163 + :abstract => 'Test',
  164 + :parent => article,
  165 + :profile => profile
  166 + }
  167 +
  168 + err = assert_raises ActiveRecord::RecordInvalid do
  169 + response = create(ProposalsDiscussionPlugin::Response, data)
  170 + end
  171 +
  172 + assert_match "Parent of Response needs be a Proposal", err.message
  173 + end
  174 +
127 175 end
... ...
views/cms/edit.html.erb 0 → 100644
... ... @@ -0,0 +1,79 @@
  1 +<%= error_messages_for 'article' %>
  2 +
  3 +<% if @article.archived? %>
  4 + <%= render :partial => 'archived_warning', :locals => {:article => @article} %>
  5 +<% end %>
  6 +<div class='<%= (@article.display_media_panel? ? 'with_media_panel' : 'no_media_panel') %>'>
  7 +<%= labelled_form_for 'article', :html => { :multipart => true, :class => @type } do |f| %>
  8 +
  9 + <%= hidden_field_tag("type", @type) if @type %>
  10 +
  11 + <%= hidden_field_tag('back_to', @back_to) %>
  12 +
  13 + <%= hidden_field_tag('success_back_to', @success_back_to) %>
  14 +
  15 +
  16 + <%= render :partial => partial_for_class(@article.class), :locals => { :f => f } %>
  17 +
  18 + <% if environment.is_portal_community?(profile) %>
  19 + <div>
  20 + <%= check_box(:article, :highlighted) %>
  21 + <label for="article_highlighted"><%= _('Highlight this article')%></label>
  22 + </div>
  23 + <% end %>
  24 +
  25 + <% button_bar do %>
  26 + <%= submit_button :save, _('Save') %>
  27 + <%= submit_button :save, _('Save and continue'), :name => "continue" %>
  28 + <% end %>
  29 +
  30 + <% unless @article.kind_of?(ProposalsDiscussionPlugin::Response) %>
  31 + <div style='float: right'>
  32 + <%= modal_button :help, _('Why categorize?'), :action => 'why_categorize' %>
  33 + </div>
  34 +
  35 + <%= select_categories(:article, _('Categorize your article')) %>
  36 +
  37 + <br />
  38 +
  39 + <%= f.text_field('tag_list', :size => 64) %>
  40 + <%= content_tag( 'small', _('Separate tags with commas') ) %>
  41 +
  42 + <script>
  43 + jQuery('#article_tag_list').inputosaurus({
  44 + autoCompleteSource: <%= "'/myprofile/#{profile.identifier}/cms/search_tags'," %>
  45 + activateFinalResult : true
  46 + })
  47 + </script>
  48 +
  49 + <div id='edit-article-options'>
  50 + <%= options_for_article(@article, @tokenized_children) %>
  51 + </div>
  52 +
  53 + <% button_bar do %>
  54 + <%= submit_button :save, _('Save') %>
  55 +
  56 + <% if @back_to %>
  57 + <%= button :cancel, _('Cancel'), @back_to %>
  58 + <% elsif @parent_id || @article.parent %>
  59 + <%= button :cancel, _('Cancel'), :action => 'view', :id => @parent_id || @article.parent %>
  60 + <% else %>
  61 + <%= button :cancel, _('Cancel'), :action => 'index' %>
  62 + <% end %>
  63 +
  64 + <% unless @article.new_record? %>
  65 + <%= button :delete, _('Delete'), {:controller => :cms, :action => :destroy, :id => @article},
  66 + :method => :post, :confirm => delete_article_message(@article) %>
  67 + <% end %>
  68 + <% end %>
  69 + <% end %>
  70 +<% end %>
  71 +</div>
  72 +
  73 +<% if @article.display_media_panel? %>
  74 + <%= render :partial => 'text_editor_sidebar' %>
  75 +<% end %>
  76 +
  77 +<br style='clear: both'/>
  78 +
  79 +<%= javascript_include_tag "article.js" %>
... ...
views/cms/proposals_discussion_plugin/_response.html.erb 0 → 100644
... ... @@ -0,0 +1,30 @@
  1 +<%= required_fields_message %>
  2 +
  3 +<%= render :file => 'shared/tiny_mce' %>
  4 +
  5 +<% title_limit = 70 %>
  6 +<% abstract_limit = 140 %>
  7 +
  8 +<div class="proposals-discussion-plugin">
  9 +
  10 + <div class="title">
  11 + <%= required labelled_form_field _('Title'), limited_text_area(:article, :name, title_limit, 'title_textarea', :rows => 1) %>
  12 + </div>
  13 + <% if @article.parent_id.nil? %>
  14 + <%= select_profile_folder(_('Parent folder:'), 'article[parent_id]', profile, @article.parent_id, {},{},{},:include_articles => true) %>
  15 + <% else %>
  16 + <%= hidden_field(:article, :parent_id) %>
  17 + <% end %>
  18 +
  19 + <div class="body">
  20 + <%= labelled_form_field(_('Response'), text_area(:article, :body, :class => 'mceEditor')) %>
  21 + </div>
  22 +
  23 +</div>
  24 +
  25 +<script>
  26 +jQuery( document ).ready(function( $ ) {
  27 + limited_text_area('title_textarea', <%= title_limit %>);
  28 + limited_text_area('abstract_textarea', <%= abstract_limit %>);
  29 +});
  30 +</script>
... ...
views/proposals_discussion/_view_item_buttons.html.erb 0 → 100644
... ... @@ -0,0 +1,6 @@
  1 +<%= link_to(
  2 + content_tag('span', _('Reply') ),
  3 + {:action => 'new', :cms => true, :parent_id => article.id, :type => 'ProposalsDiscussionPlugin::Response', :back_to => request.original_url},
  4 + {:class => 'button icon-child-article', :title => _('Reply') }
  5 + )
  6 +%>
... ...