Plugin: how to add button on issue list?

Added by Stéphane CHÂTEAU about 3 years ago

Hi everybody.

I'm currently develop a plugin suite for a french company that add a geometry (spatial database) to issues.
This first plugin works fine.

The second one add a infos and button on issue details to be able to send issue ID to a QGis plugin via WebSocket to be able to say to QGis to display the geometry.
It's works fine too.
The init.rb:

require 'redmine'

Rails.logger.info 'Plugin d’affichage du statut de la présence d’une géométrie pour Redmine'

# This is the important line.
# It requires the file in lib/geometry_status/view_hook_listeners.rb
require_dependency 'geometry_status/view_hook_listeners'

Redmine::Plugin.register :geometry_status do
  name 'Geometry Status plugin'
  author 'xxxxxx'
  description 'Ceci est le plugin pour l’intégration du status de la géométrie dans Redmine'
  version '1.0.0'
  # Requires the redmine geometry plugin
  requires_redmine_plugin :geometry, :version_or_higher => '1.0.0'
end

view_hook_listeners.rb:

module GeometryStatus
  # Voir : http://www.redmine.org/projects/redmine/wiki/Hooks
  class Hooks < Redmine::Hook::ViewListener
    def view_layouts_base_html_head(context)
      stylesheet_link_tag("plugin", :plugin => 'geometry_status') +
      javascript_include_tag('plugin', :plugin => 'geometry_status')
    end

    # rewrite select for trackers on issue form // view_issues_show_details_bottom
    def view_issues_show_details_bottom(context={})
      issue = context[:issue]
      project = context[:project]

      html_return = ''
      if project != nil && project.id == 24 && issue != nil
        # Afficher tous les custom fields :
        #==================================
        # html_return = "<hr>" 
        # CustomField.all.each do |custom|
        #   html_return += "<br/>custom id=#{custom.id} / name=#{custom.name}" 
        #   custom.possible_values.each do |value|
        #     html_return += "<br/>    => #{value}" 
        #   end
        # end
        # return html_return

        # id == 24 => project.name == "Base des défauts" 
        pointe = Pointes.where(issue_id: issue.id)

        if pointe.first != nil
          # La géométrie a été trouvée
          html_button_click = "onClick=\"call_qgis('#{issue.id}|#{pointe.first.pointe}')\"" 
          html_button = "<button class=\"geometry_status_button\" #{html_button_click}>" + image_tag('geometry.png', :plugin => 'geometry_status', :title => "La FT #{issue.id} a une géométrie\nCliquez sur le bouton pour l'ouvrir dans le plugin Redmine sous QGis", :align => "center") + "</button>" 
          html_return = "<hr><p>#{html_button}<strong class=\"geometry_status_centered_text\">Géométrie</strong><div class=\"wiki\"><p>La FT a une géométrie => WKT = #{pointe.first.pointe}</p></div>" 
        else
          # La géométrie n'a pas été trouvée
          html_button_click = "onClick=\"call_qgis('#{issue.id}|')\"" 
          value = CustomField.select('id, name, possible_values').where(name: 'Portée du Défaut').first
          if value
            # Obligé de mettre dans "#{xxxxxx}" sinon la comparaison ne fonctionne pas !
            portee_du_defaut = "#{issue.custom_value_for(value.id)}" 

            if portee_du_defaut == "Général" 
              html_button   = "<button class=\"geometry_status_button\" #{html_button_click}>" + image_tag('geometry_none.png', :plugin => 'geometry_status', :title => "La FT #{issue.id} n'a pas de géométrie\nCliquez sur le bouton pour l'ouvrir dans le plugin Redmine sous QGis", :align => "center") + "</button>" 
              html_return = "<hr><p>#{html_button}<strong class=\"geometry_status_centered_text\">Géométrie</strong><div class=\"wiki\"><p>La FT générale ne contient pas de géométrie</p></div>" 
            elsif portee_du_defaut == "Local" 
              html_button = "<button class=\"geometry_status_button\" #{html_button_click}>" + image_tag('geometry_gray.png', :plugin => 'geometry_status', :title => "La FT #{issue.id} n'a pas de géométrie mais elle devrait en avoir une !\nCliquez sur le bouton pour l'ouvrir dans le plugin Redmine sous QGis", :align => "center") + "</button>" 
              html_return = "<hr><p>#{html_button}<strong class=\"geometry_status_centered_text\">Géométrie</strong><div class=\"wiki\"><p>La FT locale ne contient pas de géométrie mais elle devrait en avoir une !</p></div>" 
            else
              html_return = "<hr><p><strong class=\"geometry_status_centered_text\">Géométrie</strong><div class=\"wiki\"><p>La FT ne contient pas de géométrie. <strong class=\"geometry_unknown_portee_default\">Attention, la portée du défaut '#{portee_du_defaut}' est inconnue !</strong></p></div>" 
            end
          end
        end
      end

      html_return
    end 
  end
end

The websocket is implemented into the plugin.js.
All of this is working fine.

Now, I would like to add a global button on issue list page to be able to send all displayed (and filtered) issue's ID to the QGis plugin.
I try to use hooks:
view_layouts_base_body_top
view_layouts_base_content
view_layouts_base_body_bottom
view_projects_show_right

But none of them is placed where I want: at the top of the list, behind the apply/clear filters line.
And another problem is: i have not the issue's ids list into the context passsed into the view hooks.

I'm sure there is another way (may be by controller hooks?) but I don't know how to use it.
I hope some peaple here can show me the way to do these expected things.

Thanks for all,
Kind regards,
Stéphane.

Replies (2)

RE: Plugin: how to add button on issue list? - Added by kumar abhinav about 3 years ago

Use 'view_issues_index_bottom' hook.
It renders at the bottom of issue list page but you can use js to move your element around as desired.
The hook is present in app/views/issues/index.html.erb.

The hook also provides the list of issues in @issues.

RE: Plugin: how to add button on issue list? - Added by Stéphane CHÂTEAU about 3 years ago

Wonderfull, thanks a lot !!!!!!!!

In my javascript I have add:

function geometry_status_move_geometry_button()
{
  // Move list_geometry_button (if exist) in the correct place
  var eltDestination = document.getElementById('query_form_with_buttons');

  if (eltDestination != null)
  {
    var allSelect = eltDestination.getElementsByClassName("buttons");

    if (allSelect.length > 1)
    {
      eltDestination = allSelect[allSelect.length - 1]
      if (eltDestination != null)
      {
        var eltSource = document.getElementById('id_list_geometry_button');
        if (eltSource != null)
        {
          eltDestination.appendChild(eltSource);
        }
      }
    }
  }
}

$(document).ready(function()
{
    geometry_status_move_geometry_button();
});

It do the job. Just some think that take me a lot of time to understand:
Moving the element where I put it => it seem to be is into a <form> and so clicking on button make the page reload making WenbSocket.send never call.
I shoud modify my js script like :

function call_qgis(evt, issues_ids_and_wtk)
{
    // Very important!
    // If not these lines, clicking on button after moving it into a form will send the form and
    // force the page to be reloaded.
    evt = evt || window.event;
    evt.preventDefault();

    try
    {
        if ("WebSocket" in window)
        {
            var bConnected = false;

            // Let us open a web socket
            ws = new WebSocket("wss://localhost:8025/xxxxxxxx");
            ws.onopen = function()
            {
                bConnected = true;
                // Web Socket is connected, send data using send()
                var jsonArr = [];
                issues_ids_and_wtk.split(",").forEach(function (item)
                {
                    array_ids_wkf = item.split("|");
                    if (array_ids_wkf.length == 2)
                    {
                        jsonArr.push({ issue_id: array_ids_wkf[0], wkt: array_ids_wkf[1]});
                    }
                });

                ws.send(JSON.stringify(jsonArr));
            };

            ws.onmessage = function (evt) 
            { 
                if (evt.data != "OK")
                {
                    alert("Error return by QGis plugin:\n" + evt.data);
                }
                ws.close();
            };

            ws.onclose = function(evt)
            { // websocket is closed.
                if (!bConnected)
                {
                    alert("Not working, server is offline\n");
                    // Pour Chrome activer : chrome://flags/#allow-insecure-localhost
                }
            };

            window.onbeforeunload = function(event)
            {
                ws.close();
            };
        }
        else
        {
            // The browser doesn't support WebSocket
            alert("WebSocket NOT supported by your Browser!\n");
        }
    }
    catch(error)
    {
        alert(error);
    }
}

(1-2/2)