Skip to content

Commit 232e5ea

Browse files
committed
initial commit with basically working state
0 parents  commit 232e5ea

File tree

9 files changed

+140
-0
lines changed

9 files changed

+140
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# -*- coding: utf-8 -*-
2+
class SubjectAutocompleteController < ApplicationController
3+
4+
if Rails::VERSION::MAJOR >= 4
5+
before_action :find_project, :init
6+
else
7+
before_filter :find_project, :init
8+
end
9+
10+
def init
11+
@issue_status_closed = IssueStatus.where('is_closed=?', true).pluck :id
12+
@issue_status_open = IssueStatus.select('id').where('is_closed=?', false).pluck :id
13+
end
14+
15+
def get_matches
16+
# autocomplete for the issue subject field.
17+
limit_count = 15
18+
default_closed_past_days = 30
19+
# load closed tickets, show issue id
20+
@issues = []
21+
q = params[:term].to_s.strip
22+
if q.present?
23+
if @project.nil?
24+
scope = Issue.visible
25+
else
26+
if @project.parent
27+
project_siblings = Project.where(:parent_id => @project.parent.id).map{|e| e.id }
28+
project_siblings_children = Project.where(:parent_id => project_siblings).map{|e| e.id }
29+
projects = project_siblings + project_siblings_children << @project.parent[:id]
30+
else
31+
project_children = Project.where(:parent_id => @project.id).map{|e| e.id }
32+
projects = project_children << @project.id
33+
end
34+
scope = Issue.where(:project_id => projects).visible
35+
end
36+
if q.match(/\A#?(\d+)\z/)
37+
@issues << scope.find_by_id($1.to_i)
38+
end
39+
past_days = params[:closed_past_days] ? params[:closed_past_days].to_i : default_closed_past_days
40+
time_now = DateTime.now
41+
# use ruby regexp. not all databases support regexp in sql
42+
q = q.gsub(/[^a-zA-Z0-9# ]/, "").split(" ").map{|e|
43+
e = Issue.connection.quote("%#{e}%")
44+
"lower(#{Issue.table_name}.subject) like #{e}"}.join(" and ")
45+
@issues += scope
46+
.where("#{q} and " +
47+
"(issues.status_id in(?) or (issues.status_id in(?) and issues.updated_on between ? and ?))",
48+
@issue_status_open, @issue_status_closed, time_now - past_days, time_now)
49+
.order("#{Issue.table_name}.id desc")
50+
.limit(limit_count)
51+
@issues.compact!
52+
versions = {}
53+
Version.select("id,name").each{|e| versions[e.id] = e.name }
54+
end
55+
render :json => @issues.map {|e|
56+
label = "##{e[:id]} #{e[:subject]}"
57+
if e.fixed_version_id then label = "#{versions[e.fixed_version_id]} » #{label}" end
58+
{
59+
"label" => label,
60+
"value" => "",
61+
"issue_url" => url_for(:controller => "issues", :action => "show", :id => e[:id]),
62+
"is_closed" => e.closed?
63+
}
64+
}
65+
end
66+
67+
private
68+
69+
def find_project
70+
if params[:project_id].present?
71+
@project = Project.find(params[:project_id])
72+
end
73+
rescue ActiveRecord::RecordNotFound
74+
render_404
75+
end
76+
end

assets/javascripts/main.js

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
function autocompleteSubject() {
2+
// offer an autocomplete selection for existing issues in the subject field when creating new issues.
3+
var input = $(".new_issue #issue_subject");
4+
if (input.length == 0) return;
5+
input.attr("placeholder", subjectAutocomplete.translations.placeholder_text)
6+
input.autocomplete({
7+
source: subjectAutocomplete.get_matches_path,
8+
minLength: 2,
9+
search: function() {
10+
input.addClass("ajax-loading");
11+
},
12+
response: function() {
13+
input.removeClass("ajax-loading");
14+
},
15+
select: function(event, ui) {
16+
window.location.href = ui.item.issue_url;
17+
}
18+
}).data("ui-autocomplete")._renderItem = function(ul, item) {
19+
listItem = $("<li></li>").data("item.autocomplete", item).append("<a>" + item.label + "</a>").appendTo(ul);
20+
if (item.is_closed) listItem.css("text-decoration", "line-through");
21+
return listItem;
22+
};
23+
}
24+
25+
jQuery(function() {
26+
autocompleteSubject();
27+
});

config/locales/de.yml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
de:
2+
placeholder_text: "Kurzbeschreibung eingeben / Tickets suchen"

config/locales/en.yml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
en:
2+
placeholder_text: "Enter subject / Search existing Issues"

config/routes.rb

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
get "sbject_autocomplete/:action", controller: "subject_autocomplete"

init.rb

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
require "redmine"
2+
require_dependency "redmine_subject_autocomplete/hooks"
3+
4+
Redmine::Plugin.register :redmine_subject_autocomplete do
5+
name "redmine subject autocomplete"
6+
author "intera gmbh"
7+
author_url "https://github.com/intera"
8+
version "1.0.0"
9+
description "makes the issue subject field show an autocomplete that lists existing issues to prevent duplicate ticket creation"
10+
end
+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# -*- coding: utf-8 -*-
2+
module RedmineSubjectAutocomplete
3+
class Hooks < Redmine::Hook::ViewListener
4+
def view_issues_form_details_bottom(context)
5+
6+
path = (config.relative_url_root || '') + url_for({controller: 'subject_autocomplete', action: 'get_matches'}) + "?project_id=#{context[:project][:id]}"
7+
translations = {
8+
"placeholder_text" => I18n.translate(:placeholder_text)
9+
}
10+
javascript_tag("var subjectAutocomplete = {get_matches_path: \"#{escape_javascript path}\", translations: #{translations.to_json}}") +
11+
javascript_include_tag('main', :plugin => 'redmine_subject_autocomplete')
12+
end
13+
end
14+
end

other/subject_autocomplete.png

57.2 KB
Loading

readme.md

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# redmine subject autocomplete
2+
3+
makes the new issue subject field show an autocomplete that lists existing issues to prevent duplicate ticket creation.
4+
when one of the listed issues is selected, navigation changes to the selected issue.
5+
6+
compatible with redmine 4 and 3.
7+
8+
![screenshot-1](other/subject_autocomplete.png?raw=true)

0 commit comments

Comments
 (0)