Init version of tags.
This commit is contained in:
parent
998a7f55d4
commit
4aae8cb3e2
20 changed files with 498 additions and 6 deletions
|
@ -285,6 +285,36 @@ class App.ControllerForm extends App.Controller
|
|||
else if attribute.tag is 'textarea'
|
||||
item = $( App.view('generic/textarea')( attribute: attribute ) )
|
||||
|
||||
# tag
|
||||
else if attribute.tag is 'tag'
|
||||
item = $( App.view('generic/input')( attribute: attribute ) )
|
||||
a = =>
|
||||
siteUpdate = (reorder) =>
|
||||
container = document.getElementById( attribute.id + "_tagsinput" )
|
||||
if reorder
|
||||
$('#' + attribute.id + "_tagsinput" ).height( 20 )
|
||||
height = container.scrollHeight
|
||||
$('#' + attribute.id + "_tagsinput" ).height( height - 16 )
|
||||
|
||||
onAddTag = =>
|
||||
siteUpdate()
|
||||
|
||||
onRemoveTag = =>
|
||||
siteUpdate(true)
|
||||
|
||||
w = $('#' + attribute.id).width()
|
||||
h = $('#' + attribute.id).height()
|
||||
$('#' + attribute.id).tagsInput(
|
||||
width: w + 'px'
|
||||
# height: (h + 30 )+ 'px'
|
||||
onAddTag: onAddTag
|
||||
onRemoveTag: onRemoveTag
|
||||
)
|
||||
siteUpdate(true)
|
||||
|
||||
@delay( a, 600 )
|
||||
|
||||
|
||||
# autocompletion
|
||||
else if attribute.tag is 'autocompletion'
|
||||
item = $( App.view('generic/autocompletion')( attribute: attribute ) )
|
||||
|
|
|
@ -103,6 +103,7 @@ class Index extends App.Controller
|
|||
{ name: 'customer_id', display: 'Customer', tag: 'autocompletion', type: 'text', limit: 200, null: false, relation: 'User', class: 'span7', autocapitalize: false, help: 'Select the customer of the Ticket or create one.', link: '<a href="" class="customer_new">»</a>', callback: @localUserInfo },
|
||||
{ name: 'group_id', display: 'Group', tag: 'select', multiple: false, null: false, filter: @edit_form, nulloption: true, relation: 'Group', default: defaults['group_id'], class: 'span7', },
|
||||
{ name: 'owner_id', display: 'Owner', tag: 'select', multiple: false, null: true, filter: @edit_form, nulloption: true, relation: 'User', default: defaults['owner_id'], class: 'span7', },
|
||||
{ name: 'tags', display: 'Tags', tag: 'tag', type: 'text', null: true, default: defaults['tags'], class: 'span7', },
|
||||
{ name: 'subject', display: 'Subject', tag: 'input', type: 'text', limit: 200, null: false, default: defaults['subject'], class: 'span7', },
|
||||
{ name: 'body', display: 'Text', tag: 'textarea', rows: 6, null: false, default: defaults['body'], class: 'span7', },
|
||||
{ name: 'ticket_state_id', display: 'State', tag: 'select', multiple: false, null: false, filter: @edit_form, relation: 'TicketState', default: defaults['ticket_state_id'], translate: true, class: 'medium' },
|
||||
|
|
|
@ -188,6 +188,14 @@ class Index extends App.Controller
|
|||
zoom: @
|
||||
)
|
||||
|
||||
# start tag controller
|
||||
if !@isRole('Customer')
|
||||
new App.TagWidget(
|
||||
el: @el.find('#tag_info')
|
||||
object_type: 'Ticket'
|
||||
object: @ticket
|
||||
)
|
||||
|
||||
# start link info controller
|
||||
if !@isRole('Customer')
|
||||
new App.LinkInfo(
|
||||
|
|
69
app/assets/javascripts/app/controllers/tag_widget.js.coffee
Normal file
69
app/assets/javascripts/app/controllers/tag_widget.js.coffee
Normal file
|
@ -0,0 +1,69 @@
|
|||
$ = jQuery.sub()
|
||||
|
||||
class App.TagWidget extends App.Controller
|
||||
constructor: ->
|
||||
super
|
||||
@load()
|
||||
|
||||
load: =>
|
||||
App.Com.ajax(
|
||||
id: 'tags_' + @object.id + '_' + @object_type
|
||||
type: 'GET'
|
||||
url: '/api/tags'
|
||||
data:
|
||||
object: @object_type
|
||||
o_id: @object.id
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
@render(data.tags)
|
||||
)
|
||||
|
||||
render: (tags) =>
|
||||
|
||||
# insert data
|
||||
@html App.view('tag_widget')(
|
||||
tags: tags || [],
|
||||
)
|
||||
@el.find('#tags').tagsInput(
|
||||
width: '150px'
|
||||
defaultText: App.i18n.translateContent('add a Tag')
|
||||
onAddTag: @onAddTag
|
||||
onRemoveTag: @onRemoveTag
|
||||
# height: '65px'
|
||||
)
|
||||
@delay @siteUpdate, 100
|
||||
|
||||
# @el.find('#tags').elastic()
|
||||
|
||||
onAddTag: (item) =>
|
||||
App.Com.ajax(
|
||||
type: 'GET',
|
||||
url: '/api/tags/add',
|
||||
data:
|
||||
object: @object_type,
|
||||
o_id: @object.id,
|
||||
item: item
|
||||
processData: true,
|
||||
success: (data, status, xhr) =>
|
||||
@siteUpdate()
|
||||
)
|
||||
|
||||
onRemoveTag: (item) =>
|
||||
App.Com.ajax(
|
||||
type: 'GET'
|
||||
url: '/api/tags/remove'
|
||||
data:
|
||||
object: @object_type
|
||||
o_id: @object.id
|
||||
item: item
|
||||
processData: true
|
||||
success: (data, status, xhr) =>
|
||||
@siteUpdate(true)
|
||||
)
|
||||
|
||||
siteUpdate: (reorder) =>
|
||||
container = document.getElementById("tags_tagsinput")
|
||||
if reorder
|
||||
$('#tags_tagsinput').height( 20 )
|
||||
height = container.scrollHeight
|
||||
$('#tags_tagsinput').height( height - 10 )
|
|
@ -35,7 +35,7 @@ class App.TemplateUI extends App.Controller
|
|||
template = App.Collection.find( 'Template', @template_id )
|
||||
|
||||
# insert data
|
||||
@html App.view('template')(
|
||||
@html App.view('template_widget')(
|
||||
template: template,
|
||||
)
|
||||
new App.ControllerForm(
|
||||
|
|
|
@ -132,7 +132,7 @@ class App.TextModuleUI extends App.Controller
|
|||
)
|
||||
|
||||
# insert data
|
||||
@html App.view('text_module')(
|
||||
@html App.view('text_module_widget')(
|
||||
search: @search,
|
||||
)
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class App.Ticket extends App.Model
|
||||
@configure 'Ticket', 'number', 'title', 'group_id', 'owner_id', 'customer_id', 'ticket_state_id', 'ticket_priority_id', 'article'
|
||||
@configure 'Ticket', 'number', 'title', 'group_id', 'owner_id', 'customer_id', 'ticket_state_id', 'ticket_priority_id', 'article', 'tags'
|
||||
@extend Spine.Model.Ajax
|
||||
@url: '/api/tickets'
|
||||
@configure_attributes = [
|
||||
|
|
|
@ -93,6 +93,7 @@
|
|||
<div class="span3">
|
||||
<div id="customer_info"></div>
|
||||
<div id="action_info"></div>
|
||||
<div id="tag_info"></div>
|
||||
<div id="link_info"></div>
|
||||
<div id="text_module"></div>
|
||||
</div>
|
||||
|
|
4
app/assets/javascripts/app/views/tag_widget.jst.eco
Normal file
4
app/assets/javascripts/app/views/tag_widget.jst.eco
Normal file
|
@ -0,0 +1,4 @@
|
|||
<div class="well">
|
||||
<h3><%- @T( 'Tags' ) %></h3>
|
||||
<input type="text" name="tags" id="tags" class="span2" value="<% for tag in @tags: %><%= tag %>,<% end %>"/>
|
||||
</div>
|
|
@ -5,8 +5,9 @@
|
|||
*= require_self
|
||||
*= require ./bootstrap.css
|
||||
*= require ./fileuploader.css
|
||||
*= require ./ui-lightness/jquery-ui-1.8.18.custom.css
|
||||
*= require ./ui-lightness/jquery-ui-1.8.23.custom.css
|
||||
*= require ./jquery.noty.css
|
||||
*= require ./jquery.tagsinput.css
|
||||
*= require ./noty_theme_twitter.css
|
||||
*= require ./zzz.css
|
||||
*
|
||||
|
|
57
app/controllers/tags_controller.rb
Normal file
57
app/controllers/tags_controller.rb
Normal file
|
@ -0,0 +1,57 @@
|
|||
class TagsController < ApplicationController
|
||||
before_filter :authentication_check
|
||||
|
||||
# GET /api/tags
|
||||
def index
|
||||
list = Tag.list()
|
||||
|
||||
# return result
|
||||
render :json => {
|
||||
:tags => list,
|
||||
}
|
||||
end
|
||||
|
||||
# GET /api/tags
|
||||
def list
|
||||
list = Tag.tag_list(
|
||||
:object => params[:object],
|
||||
:o_id => params[:o_id],
|
||||
)
|
||||
|
||||
# return result
|
||||
render :json => {
|
||||
:tags => list,
|
||||
}
|
||||
end
|
||||
|
||||
# POST /api/tag/add
|
||||
def add
|
||||
success = Tag.tag_add(
|
||||
:object => params[:object],
|
||||
:o_id => params[:o_id],
|
||||
:item => params[:item],
|
||||
:created_by_id => current_user.id,
|
||||
);
|
||||
if success
|
||||
render :json => success, :status => :created
|
||||
else
|
||||
render :json => success.errors, :status => :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
# DELETE /api/tag/remove
|
||||
def remove
|
||||
success = Tag.tag_remove(
|
||||
:object => params[:object],
|
||||
:o_id => params[:o_id],
|
||||
:item => params[:item],
|
||||
:created_by_id => current_user.id,
|
||||
);
|
||||
if success
|
||||
render :json => success, :status => :created
|
||||
else
|
||||
render :json => success.errors, :status => :unprocessable_entity
|
||||
end
|
||||
end
|
||||
|
||||
end
|
|
@ -36,6 +36,19 @@ class TicketsController < ApplicationController
|
|||
return
|
||||
end
|
||||
|
||||
# create tags if given
|
||||
if params[:tags] && !params[:tags].empty?
|
||||
tags = params[:tags].split /,/
|
||||
tags.each {|tag|
|
||||
Tag.tag_add(
|
||||
:object => 'Ticket',
|
||||
:o_id => @ticket.id,
|
||||
:item => tag,
|
||||
:created_by_id => current_user.id,
|
||||
)
|
||||
}
|
||||
end
|
||||
|
||||
# create article if given
|
||||
if params[:article]
|
||||
@article = Ticket::Article.new(params[:article])
|
||||
|
|
|
@ -58,7 +58,7 @@ class History < ActiveRecord::Base
|
|||
history_object = self.history_object_lookup( requested_object )
|
||||
history = History.where( :history_object_id => history_object.id ).
|
||||
where( :o_id => requested_object_id ).
|
||||
where( :history_type_id => History::Type.where( :name => ['created', 'updated', 'notification', 'email'] ) ).
|
||||
where( :history_type_id => History::Type.where( :name => ['created', 'updated', 'notification', 'email', 'added', 'removed'] ) ).
|
||||
order('created_at ASC, id ASC')
|
||||
else
|
||||
history_object_requested = self.history_object_lookup( requested_object )
|
||||
|
@ -69,7 +69,7 @@ class History < ActiveRecord::Base
|
|||
requested_object_id,
|
||||
history_object_related.id,
|
||||
requested_object_id,
|
||||
History::Type.where( :name => ['created', 'updated', 'notification', 'email'] )
|
||||
History::Type.where( :name => ['created', 'updated', 'notification', 'email', 'added', 'removed'] )
|
||||
).
|
||||
order('created_at ASC, id ASC')
|
||||
end
|
||||
|
|
37
app/models/observer/tag/ticket_history.rb
Normal file
37
app/models/observer/tag/ticket_history.rb
Normal file
|
@ -0,0 +1,37 @@
|
|||
require 'history'
|
||||
|
||||
class Observer::Tag::TicketHistory < ActiveRecord::Observer
|
||||
include UserInfo
|
||||
observe 'tag'
|
||||
|
||||
def after_create(record)
|
||||
|
||||
# just process ticket object tags
|
||||
return true if record.tag_object.name != 'Ticket';
|
||||
|
||||
# add ticket history
|
||||
History.history_create(
|
||||
:o_id => record.o_id,
|
||||
:history_type => 'added',
|
||||
:history_object => 'Ticket',
|
||||
:history_attribute => 'Tag',
|
||||
:value_from => record.tag_item.name,
|
||||
:created_by_id => current_user_id || record.created_by_id || 1
|
||||
)
|
||||
end
|
||||
def after_destroy(record)
|
||||
|
||||
# just process ticket object tags
|
||||
return true if record.tag_object.name != 'Ticket';
|
||||
|
||||
# add ticket history
|
||||
History.history_create(
|
||||
:o_id => record.o_id,
|
||||
:history_type => 'removed',
|
||||
:history_object => 'Ticket',
|
||||
:history_attribute => 'Tag',
|
||||
:value_from => record.tag_item.name,
|
||||
:created_by_id => current_user_id || record.created_by_id || 1
|
||||
)
|
||||
end
|
||||
end
|
133
app/models/tag.rb
Normal file
133
app/models/tag.rb
Normal file
|
@ -0,0 +1,133 @@
|
|||
class Tag < ApplicationModel
|
||||
belongs_to :tag_object, :class_name => 'Tag::Object'
|
||||
belongs_to :tag_item, :class_name => 'Tag::Item'
|
||||
|
||||
@@cache_item = {}
|
||||
@@cache_object = {}
|
||||
|
||||
def self.tag_add(data)
|
||||
|
||||
# lookups
|
||||
if data[:object]
|
||||
tag_object_id = self.tag_object_lookup( data[:object] )
|
||||
end
|
||||
if data[:item]
|
||||
tag_item_id = self.tag_item_lookup( data[:item] )
|
||||
end
|
||||
|
||||
# create history
|
||||
Tag.create(
|
||||
:tag_object_id => tag_object_id,
|
||||
:tag_item_id => tag_item_id,
|
||||
:o_id => data[:o_id],
|
||||
:created_by_id => data[:created_by_id]
|
||||
)
|
||||
return true
|
||||
end
|
||||
|
||||
def self.tag_remove(data)
|
||||
|
||||
# lookups
|
||||
if data[:object]
|
||||
tag_object_id = self.tag_object_lookup( data[:object] )
|
||||
end
|
||||
if data[:item]
|
||||
tag_item_id = self.tag_item_lookup( data[:item] )
|
||||
end
|
||||
|
||||
# create history
|
||||
result = Tag.where(
|
||||
:tag_object_id => tag_object_id,
|
||||
:tag_item_id => tag_item_id,
|
||||
:o_id => data[:o_id],
|
||||
)
|
||||
result.each { |item|
|
||||
item.destroy
|
||||
}
|
||||
return true
|
||||
end
|
||||
|
||||
def self.tag_list( data )
|
||||
tag_object_id_requested = self.tag_object_lookup( data[:object] )
|
||||
tag_search = Tag.where(
|
||||
:tag_object_id => tag_object_id_requested,
|
||||
:o_id => data[:o_id],
|
||||
)
|
||||
tags = []
|
||||
tag_search.each {|tag|
|
||||
tags.push self.tag_item_lookup_id( tag.tag_item_id )
|
||||
}
|
||||
return tags
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.tag_item_lookup_id( id )
|
||||
|
||||
# use cache
|
||||
return @@cache_item[ id ] if @@cache_item[ id ]
|
||||
|
||||
# lookup
|
||||
tag_item = Tag::Item.find(id)
|
||||
@@cache_item[ id ] = tag_item.name
|
||||
return tag_item.name
|
||||
end
|
||||
|
||||
def self.tag_item_lookup( name )
|
||||
|
||||
# use cache
|
||||
return @@cache_item[ name ] if @@cache_item[ name ]
|
||||
|
||||
# lookup
|
||||
tag_item = Tag::Item.where( :name => name ).first
|
||||
if tag_item
|
||||
@@cache_item[ name ] = tag_item.id
|
||||
return tag_item.id
|
||||
end
|
||||
|
||||
# create
|
||||
tag_item = Tag::Item.create(
|
||||
:name => name
|
||||
)
|
||||
@@cache_item[ name ] = tag_item.id
|
||||
return tag_item.id
|
||||
end
|
||||
|
||||
def self.tag_object_lookup_id( id )
|
||||
|
||||
# use cache
|
||||
return @@cache_object[ id ] if @@cache_object[ id ]
|
||||
|
||||
# lookup
|
||||
tag_object = Tag::Object.find(id)
|
||||
@@cache_object[ id ] = tag_object.name
|
||||
return tag_object.name
|
||||
end
|
||||
|
||||
def self.tag_object_lookup( name )
|
||||
|
||||
# use cache
|
||||
return @@cache_object[ name ] if @@cache_object[ name ]
|
||||
|
||||
# lookup
|
||||
tag_object = Tag::Object.where( :name => name ).first
|
||||
if tag_object
|
||||
@@cache_object[ name ] = tag_object.id
|
||||
return tag_object.id
|
||||
end
|
||||
|
||||
# create
|
||||
tag_object = Tag::Object.create(
|
||||
:name => name
|
||||
)
|
||||
@@cache_object[ name ] = tag_object.id
|
||||
return tag_object.id
|
||||
end
|
||||
|
||||
class Object < ActiveRecord::Base
|
||||
end
|
||||
|
||||
class Item < ActiveRecord::Base
|
||||
end
|
||||
|
||||
end
|
11
config/routes/tag.rb
Normal file
11
config/routes/tag.rb
Normal file
|
@ -0,0 +1,11 @@
|
|||
module ExtraRoutes
|
||||
def add(map)
|
||||
|
||||
# links
|
||||
map.match '/api/tags', :to => 'tags#list'
|
||||
map.match '/api/tags/add', :to => 'tags#add'
|
||||
map.match '/api/tags/remove', :to => 'tags#remove'
|
||||
|
||||
end
|
||||
module_function :add
|
||||
end
|
28
db/migrate/20121117121944_tags_create.rb
Normal file
28
db/migrate/20121117121944_tags_create.rb
Normal file
|
@ -0,0 +1,28 @@
|
|||
class TagsCreate < ActiveRecord::Migration
|
||||
def up
|
||||
create_table :tags do |t|
|
||||
t.references :tag_item, :null => false
|
||||
t.references :tag_object, :null => false
|
||||
t.column :o_id, :integer, :null => false
|
||||
t.column :created_by_id, :integer, :null => false
|
||||
t.timestamps
|
||||
end
|
||||
add_index :tags, [:o_id]
|
||||
add_index :tags, [:tag_object_id]
|
||||
|
||||
create_table :tag_objects do |t|
|
||||
t.column :name, :string, :limit => 250, :null => false
|
||||
t.timestamps
|
||||
end
|
||||
add_index :tag_objects, [:name], :unique => true
|
||||
|
||||
create_table :tag_items do |t|
|
||||
t.column :name, :string, :limit => 250, :null => false
|
||||
t.timestamps
|
||||
end
|
||||
add_index :tag_items, [:name], :unique => true
|
||||
end
|
||||
|
||||
def down
|
||||
end
|
||||
end
|
99
test/unit/tag_test.rb
Normal file
99
test/unit/tag_test.rb
Normal file
|
@ -0,0 +1,99 @@
|
|||
# encoding: utf-8
|
||||
require 'test_helper'
|
||||
|
||||
class TagTest < ActiveSupport::TestCase
|
||||
test 'tags' do
|
||||
tests = [
|
||||
|
||||
# test 1
|
||||
{
|
||||
:tag_add => {
|
||||
:item => 'tag1',
|
||||
:object => 'Object1',
|
||||
:o_id => 123,
|
||||
:created_by_id => 1
|
||||
},
|
||||
:verify => {
|
||||
:object => 'Object1',
|
||||
:items => {
|
||||
'tag1' => true,
|
||||
'tag2' => false,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
# test 2
|
||||
{
|
||||
:tag_add => {
|
||||
:item => 'tag2',
|
||||
:object => 'Object1',
|
||||
:o_id => 123,
|
||||
:created_by_id => 1
|
||||
},
|
||||
:verify => {
|
||||
:object => 'Object1',
|
||||
:items => {
|
||||
'tag1' => true,
|
||||
'tag2' => true,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
# test 2
|
||||
{
|
||||
:tag_add => {
|
||||
:item => 'tagöäüß1',
|
||||
:object => 'Object2',
|
||||
:o_id => 123,
|
||||
:created_by_id => 1
|
||||
},
|
||||
:verify => {
|
||||
:object => 'Object2',
|
||||
:items => {
|
||||
'tagöäüß1' => true,
|
||||
'tag2' => false,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
# test 4
|
||||
{
|
||||
:tag_add => {
|
||||
:item => 'tagöäüß2',
|
||||
:object => 'Object2',
|
||||
:o_id => 123,
|
||||
:created_by_id => 1
|
||||
},
|
||||
:verify => {
|
||||
:object => 'Object2',
|
||||
:items => {
|
||||
'tagöäüß1' => true,
|
||||
'tagöäüß2' => true,
|
||||
'tagöäüß3' => false,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
]
|
||||
tests.each { |test|
|
||||
success = Tag.tag_add( test[:tag_add] )
|
||||
assert( success, "Tag.tag_add successful")
|
||||
list = Tag.tag_list( test[:tag_add] )
|
||||
test[:verify][:items].each {|key, value|
|
||||
if value == true
|
||||
assert( list.include?( key ), "Tag verify #{ test[:tag_add][:item] }")
|
||||
else
|
||||
assert( !list.include?( key ), "Tag verify #{ test[:tag_add][:item] }")
|
||||
end
|
||||
}
|
||||
}
|
||||
|
||||
# delete tags
|
||||
tests.each { |test|
|
||||
success = Tag.tag_remove( test[:tag_add] )
|
||||
assert( success, "Tag.tag_remove successful")
|
||||
list = Tag.tag_list( test[:tag_add] )
|
||||
assert( !list.include?( test[:tag_add][:item] ), "Tag entry destroyed")
|
||||
}
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue