redmineup 1.0.2 → 1.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.gitlab-ci.yml +33 -0
- data/README.md +74 -153
- data/app/assets/images/icons.svg +8 -0
- data/app/assets/javascripts/chart.min.js +13 -0
- data/app/assets/javascripts/consumer.js +53 -0
- data/app/assets/javascripts/consumer_worker.js +219 -0
- data/app/assets/javascripts/jquery.colorPicker.min.js +26 -0
- data/{vendor → app}/assets/javascripts/select2_helpers.js +11 -1
- data/app/assets/stylesheets/calendars.css +15 -0
- data/{vendor → app}/assets/stylesheets/money.css +1 -2
- data/app/assets/stylesheets/redmineup.css +509 -0
- data/{vendor → app}/assets/stylesheets/select2.css +1 -1
- data/app/views/redmineup/_additional_assets.html.erb +1 -0
- data/config/locales/en.yml +1 -0
- data/config/locales/ru.yml +1 -0
- data/config/routes.rb +4 -0
- data/doc/CHANGELOG +116 -7
- data/doc/active-record-mixins.md +156 -0
- data/doc/assets-money-and-utilities.md +71 -0
- data/doc/tagging-and-select2.md +108 -0
- data/lib/action_cable/connection/rup_connection.rb +17 -0
- data/lib/action_cable/server/rup_configuration.rb +17 -0
- data/lib/action_cable/server/rup_server.rb +17 -0
- data/lib/application_record.rb +3 -0
- data/lib/redmineup/acts_as_taggable/tag.rb +26 -2
- data/lib/redmineup/acts_as_taggable/up_acts_as_taggable.rb +42 -2
- data/lib/redmineup/acts_as_viewed/up_acts_as_viewed.rb +1 -1
- data/lib/redmineup/acts_as_votable/votable.rb +2 -5
- data/lib/redmineup/assets_manager.rb +12 -1
- data/lib/redmineup/currency.rb +3 -1
- data/lib/redmineup/engine.rb +12 -2
- data/lib/redmineup/helpers/calendars_helper.rb +22 -0
- data/lib/redmineup/helpers/datetime_helper.rb +23 -0
- data/lib/redmineup/helpers/external_assets_helper.rb +15 -7
- data/lib/redmineup/helpers/form_tag_helper.rb +43 -0
- data/lib/redmineup/helpers/tags_helper.rb +41 -0
- data/lib/redmineup/helpers/test_helper.rb +12 -0
- data/lib/redmineup/hooks/views_layouts_hook.rb +1 -3
- data/lib/redmineup/liquid/drops/attachment_drop.rb +8 -0
- data/lib/redmineup/liquid/drops/custom_field_enumeration_drop.rb +11 -0
- data/lib/redmineup/liquid/drops/issues_drop.rb +11 -19
- data/lib/redmineup/liquid/drops/users_drop.rb +2 -3
- data/lib/redmineup/liquid/drops/version_drop.rb +38 -0
- data/lib/redmineup/liquid/filters/additional.rb +31 -0
- data/lib/redmineup/liquid/filters/base.rb +16 -1
- data/lib/redmineup/money_helper.rb +10 -2
- data/lib/redmineup/patches/action_cable_base_patch.rb +25 -0
- data/lib/redmineup/patches/action_cable_patch.rb +19 -0
- data/lib/redmineup/patches/auto_completes_controller_patch.rb +17 -0
- data/lib/redmineup/patches/compatibility/routing_mapper_patch.rb +16 -0
- data/lib/redmineup/patches/compatibility/sprite_patch.rb +13 -0
- data/lib/redmineup/patches/compatibility_patch.rb +4 -0
- data/lib/redmineup/patches/liquid_patch.rb +13 -22
- data/lib/redmineup/version.rb +1 -1
- data/lib/redmineup.rb +66 -40
- data/test/acts_as_taggable/rup_acts_as_taggable_test.rb +1 -1
- data/test/acts_as_taggable/tag_test.rb +16 -0
- data/test/acts_as_votable/votable_test.rb +9 -9
- data/test/acts_as_votable/voter_test.rb +4 -4
- data/test/database.yml +3 -3
- data/test/fixtures/tags.yml +2 -0
- data/test/liquid/drops/issues_drop_test.rb +5 -0
- data/test/liquid/filters/additional_filter_test.rb +35 -0
- data/test/liquid/filters/arrays_filter_test.rb +6 -3
- data/test/liquid/liquid_helper.rb +1 -1
- data/test/models/attachment.rb +5 -0
- data/test/models/issue.rb +5 -0
- data/test/models/journal.rb +6 -0
- data/test/schema.rb +65 -53
- data/test/tags_helper_view_test.rb +41 -0
- data/test/test_helper.rb +13 -12
- metadata +43 -14
- data/app/views/redmine_crm/redmineup_calendar/_calendar.html.erb +0 -34
- data/bitbucket-pipelines.yml +0 -50
- data/lib/redmineup/compatibility/application_controller_patch.rb +0 -33
- data/lib/redmineup/compatibility/routing_mapper_patch.rb +0 -25
- data/lib/redmineup/helpers/rup_calendar_helper.rb +0 -146
- /data/{vendor → app}/assets/images/money.png +0 -0
- /data/{vendor → app}/assets/images/vcard.png +0 -0
- /data/{vendor/assets/javascripts/Chart.bundle.min.js → app/assets/javascripts/Chart.bundle.min.js.bak} +0 -0
- /data/{vendor → app}/assets/javascripts/select2.js +0 -0
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# ActiveRecord mixins
|
|
2
|
+
|
|
3
|
+
This page documents the model mixins provided by `redmineup`.
|
|
4
|
+
|
|
5
|
+
## `up_acts_as_taggable`
|
|
6
|
+
|
|
7
|
+
Adds polymorphic tagging support.
|
|
8
|
+
|
|
9
|
+
### Migration
|
|
10
|
+
|
|
11
|
+
```ruby
|
|
12
|
+
class CreateTags < ActiveRecord::Migration[6.1]
|
|
13
|
+
def change
|
|
14
|
+
ActiveRecord::Base.create_taggable_table
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Useful migration helpers:
|
|
20
|
+
|
|
21
|
+
- `ActiveRecord::Base.create_taggable_table`
|
|
22
|
+
- `ActiveRecord::Base.drop_taggable_table`
|
|
23
|
+
- `ActiveRecord::Base.add_tags_column(:tags, name: :color, type: :integer, default: nil)`
|
|
24
|
+
- `ActiveRecord::Base.remove_tags_columns(:tags, :color)`
|
|
25
|
+
|
|
26
|
+
### Model
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
class Product < ApplicationRecord
|
|
30
|
+
up_acts_as_taggable
|
|
31
|
+
end
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Runtime API
|
|
35
|
+
|
|
36
|
+
- `record.tag_list` / `record.tag_list = 'a, b'`
|
|
37
|
+
- `record.save_tags` (called via callbacks)
|
|
38
|
+
- `Model.available_tags(project: @project, limit: 30)`
|
|
39
|
+
- `Model.tagged_with(['tag1', 'tag2'])`
|
|
40
|
+
- `Model.find_related_tags('tag1')`
|
|
41
|
+
|
|
42
|
+
## `up_acts_as_draftable`
|
|
43
|
+
|
|
44
|
+
Adds draft save/restore workflow for unsaved or changed records.
|
|
45
|
+
|
|
46
|
+
### Migration
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
class CreateDrafts < ActiveRecord::Migration[6.1]
|
|
50
|
+
def change
|
|
51
|
+
ActiveRecord::Base.create_drafts_table
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Helpers:
|
|
57
|
+
|
|
58
|
+
- `ActiveRecord::Base.create_drafts_table`
|
|
59
|
+
- `ActiveRecord::Base.drop_drafts_table`
|
|
60
|
+
|
|
61
|
+
### Model
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
class WikiPage < ApplicationRecord
|
|
65
|
+
up_acts_as_draftable parent: :wiki
|
|
66
|
+
end
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### Runtime API
|
|
70
|
+
|
|
71
|
+
- `record.save_draft`
|
|
72
|
+
- `record.update_draft(attrs)`
|
|
73
|
+
- `Model.drafts(User.current)`
|
|
74
|
+
- `Model.from_draft(draft_or_id)`
|
|
75
|
+
|
|
76
|
+
## `up_acts_as_viewed`
|
|
77
|
+
|
|
78
|
+
Tracks unique views by user (or IP) and total views.
|
|
79
|
+
|
|
80
|
+
### Migration
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
class CreateViewings < ActiveRecord::Migration[6.1]
|
|
84
|
+
def change
|
|
85
|
+
ActiveRecord::Base.create_viewings_table
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Optional helpers for models with embedded counters:
|
|
91
|
+
|
|
92
|
+
- `ActiveRecord::Base.generate_viewings_columns(table)`
|
|
93
|
+
- `Model.add_viewings_columns`
|
|
94
|
+
- `Model.remove_viewings_columns`
|
|
95
|
+
- `ActiveRecord::Base.drop_viewings_table`
|
|
96
|
+
|
|
97
|
+
### Model
|
|
98
|
+
|
|
99
|
+
```ruby
|
|
100
|
+
class Question < ApplicationRecord
|
|
101
|
+
up_acts_as_viewed
|
|
102
|
+
end
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Runtime API
|
|
106
|
+
|
|
107
|
+
- `record.view(request.remote_ip, User.current)`
|
|
108
|
+
- `record.viewed?`
|
|
109
|
+
- `record.view_count`
|
|
110
|
+
- `record.viewed_by?(request.remote_ip, User.current)`
|
|
111
|
+
|
|
112
|
+
## `up_acts_as_votable` and `up_acts_as_voter`
|
|
113
|
+
|
|
114
|
+
Adds voting capabilities for content and users.
|
|
115
|
+
|
|
116
|
+
### Migration
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
class CreateVotes < ActiveRecord::Migration[6.1]
|
|
120
|
+
def change
|
|
121
|
+
ActiveRecord::Base.create_votable_table
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Helpers:
|
|
127
|
+
|
|
128
|
+
- `ActiveRecord::Base.create_votable_table`
|
|
129
|
+
- `ActiveRecord::Base.drop_votable_table`
|
|
130
|
+
|
|
131
|
+
### Models
|
|
132
|
+
|
|
133
|
+
```ruby
|
|
134
|
+
class Question < ApplicationRecord
|
|
135
|
+
up_acts_as_votable
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
class User < ApplicationRecord
|
|
139
|
+
up_acts_as_voter
|
|
140
|
+
end
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## `up_acts_as_priceable`
|
|
144
|
+
|
|
145
|
+
Generates currency-aware `*_to_s` methods for price fields.
|
|
146
|
+
|
|
147
|
+
### Model
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
class Deal < ApplicationRecord
|
|
151
|
+
up_acts_as_priceable :amount, :tax_amount, :subtotal, :total
|
|
152
|
+
end
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
This creates methods like `amount_to_s`, `tax_amount_to_s`, etc., backed by
|
|
156
|
+
`Redmineup::MoneyHelper#object_price`.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# Assets, money, and shared utilities
|
|
2
|
+
|
|
3
|
+
## Asset helpers
|
|
4
|
+
|
|
5
|
+
Provided by `Redmineup::ExternalAssetsHelper`:
|
|
6
|
+
|
|
7
|
+
- `redmineup_assets`
|
|
8
|
+
- `select2_assets` (alias for `redmineup_assets`)
|
|
9
|
+
- `chartjs_assets`
|
|
10
|
+
|
|
11
|
+
`redmineup_assets` includes:
|
|
12
|
+
|
|
13
|
+
- `consumer.js`
|
|
14
|
+
- `select2.js`
|
|
15
|
+
- `select2_helpers.js`
|
|
16
|
+
- `redmineup.css`
|
|
17
|
+
|
|
18
|
+
The gem also hooks `view_layouts_base_html_head` and renders
|
|
19
|
+
`redmineup/_additional_assets`, so shared assets can be included globally.
|
|
20
|
+
Helpers are idempotent and safe to call from plugin views.
|
|
21
|
+
|
|
22
|
+
## Money and currency
|
|
23
|
+
|
|
24
|
+
### Settings API
|
|
25
|
+
|
|
26
|
+
`Redmineup::Settings::Money` provides access to plugin-level money settings:
|
|
27
|
+
|
|
28
|
+
- `default_currency`
|
|
29
|
+
- `major_currencies`
|
|
30
|
+
- `default_tax`
|
|
31
|
+
- `tax_type`, `tax_exclusive?`
|
|
32
|
+
- `thousands_delimiter`
|
|
33
|
+
- `decimal_separator`
|
|
34
|
+
- `disable_taxes?`
|
|
35
|
+
|
|
36
|
+
### Formatting helpers
|
|
37
|
+
|
|
38
|
+
`Redmineup::MoneyHelper` provides:
|
|
39
|
+
|
|
40
|
+
- `price_to_currency(price, currency, options = {})`
|
|
41
|
+
- `object_price(record, :price)`
|
|
42
|
+
- `prices_collection_by_currency(hash, hide_zeros: true)`
|
|
43
|
+
- `collection_for_currencies_select`
|
|
44
|
+
- `all_currencies`
|
|
45
|
+
|
|
46
|
+
`up_acts_as_priceable` uses these helpers to generate `*_to_s` methods on
|
|
47
|
+
models.
|
|
48
|
+
|
|
49
|
+
### Admin UI
|
|
50
|
+
|
|
51
|
+
`Redmineup::Currency.add_admin_money_menu` registers a Redmine admin menu item
|
|
52
|
+
that points to:
|
|
53
|
+
|
|
54
|
+
- `redmineup_settings_path(:money)`
|
|
55
|
+
|
|
56
|
+
## Misc shared helpers and patches
|
|
57
|
+
|
|
58
|
+
- `Redmineup::CalendarsHelper#calendar_day_css_classes`
|
|
59
|
+
- Liquid patch for numeric conversion in `Liquid::StandardFilters`
|
|
60
|
+
- Liquid filters/drops under `lib/redmineup/liquid/**`
|
|
61
|
+
- Compatibility and ActionCable patches loaded at gem boot
|
|
62
|
+
|
|
63
|
+
## Version requirements in plugins
|
|
64
|
+
|
|
65
|
+
Plugins typically guard compatibility in `init.rb`:
|
|
66
|
+
|
|
67
|
+
```ruby
|
|
68
|
+
requires_redmineup version_or_higher: '1.1.5'
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Use this to fail fast when a plugin depends on newer helper or API behavior.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# Tagging and Select2 helpers
|
|
2
|
+
|
|
3
|
+
This page covers view helpers used by RedmineUP plugins for tag rendering,
|
|
4
|
+
tag editing, and select2-backed filters.
|
|
5
|
+
|
|
6
|
+
## Tag rendering helpers
|
|
7
|
+
|
|
8
|
+
Provided by `Redmineup::TagsHelper`:
|
|
9
|
+
|
|
10
|
+
- `tag_link(url, tag_name, options = {})`
|
|
11
|
+
- `tag_links(tag_list, options = {}) { |tag| url }`
|
|
12
|
+
- `tag_cloud(tags, classes) { |tag, css_class| ... }`
|
|
13
|
+
- `tag_cloud_links(tags, key, options = {})`
|
|
14
|
+
- `tag_separator`
|
|
15
|
+
|
|
16
|
+
### Recommended pattern
|
|
17
|
+
|
|
18
|
+
Define `*_tag_url` per plugin context:
|
|
19
|
+
|
|
20
|
+
```ruby
|
|
21
|
+
def contact_tag_url(tag_name, options = {})
|
|
22
|
+
{
|
|
23
|
+
controller: 'contacts',
|
|
24
|
+
action: 'index',
|
|
25
|
+
set_filter: 1,
|
|
26
|
+
fields: [:tags],
|
|
27
|
+
values: { tags: [tag_name] },
|
|
28
|
+
operators: { tags: '=' }
|
|
29
|
+
}.merge(options)
|
|
30
|
+
end
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Then render the cloud via convention:
|
|
34
|
+
|
|
35
|
+
```erb
|
|
36
|
+
<%= tag_cloud_links(Contact.available_tags(project: @project), :contact) %>
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
`tag_cloud_links(tags, :contact)` expects `contact_tag_url(name)` to be defined
|
|
40
|
+
in the current view context.
|
|
41
|
+
|
|
42
|
+
## Tag editors with Select2
|
|
43
|
+
|
|
44
|
+
Provided by `Redmineup::FormTagHelper`:
|
|
45
|
+
|
|
46
|
+
- `select2_tag(name, option_tags = nil, options = {})`
|
|
47
|
+
- `select2(...)` (alias)
|
|
48
|
+
- `tag_list_field_tag(name, value = [], options = {})`
|
|
49
|
+
- `transform_to_select2(type, options = {})`
|
|
50
|
+
|
|
51
|
+
`ActionView::Helpers::FormBuilder` is extended with:
|
|
52
|
+
|
|
53
|
+
- `f.tag_list_field(:tag_list, **options)`
|
|
54
|
+
|
|
55
|
+
### Example: form object API
|
|
56
|
+
|
|
57
|
+
```erb
|
|
58
|
+
<%= f.tag_list_field :tag_list, width: '95%' %>
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Example: standalone field API
|
|
62
|
+
|
|
63
|
+
```erb
|
|
64
|
+
<%= tag_list_field_tag 'product[tag_list]', @product.tag_list, taggable_type: 'Product' %>
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Example: query/filter select2 transform
|
|
68
|
+
|
|
69
|
+
```erb
|
|
70
|
+
<%= transform_to_select2 'product_tags',
|
|
71
|
+
url: auto_complete_taggable_tags_path(project_id: @project, taggable_type: 'Product') %>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Tag autocomplete endpoint
|
|
75
|
+
|
|
76
|
+
`redmineup` adds a shared route:
|
|
77
|
+
|
|
78
|
+
- `GET /auto_completes/taggable_tags`
|
|
79
|
+
|
|
80
|
+
Named route:
|
|
81
|
+
|
|
82
|
+
- `auto_complete_taggable_tags_path`
|
|
83
|
+
|
|
84
|
+
Typical params used by plugins:
|
|
85
|
+
|
|
86
|
+
- `taggable_type`: model class name (`Contact`, `Person`, `Product`, `Issue`, `DriveEntry`)
|
|
87
|
+
- `project_id`: optional project scope
|
|
88
|
+
- `limit`: optional result size limit
|
|
89
|
+
|
|
90
|
+
Response format:
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
[
|
|
94
|
+
{ "id": "important", "text": "important" },
|
|
95
|
+
{ "id": "vip", "text": "vip" }
|
|
96
|
+
]
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Real plugin usage
|
|
100
|
+
|
|
101
|
+
Patterns above are used by:
|
|
102
|
+
|
|
103
|
+
- `redmine_contacts` (`contact_tag_url`, `contact_tag_links`, `f.tag_list_field`)
|
|
104
|
+
- `redmine_people` (`people_tag_url`, `tag_cloud_links`, bulk edit tag forms)
|
|
105
|
+
- `redmine_products` (`product_tag_url`, `tag_list_field_tag`, select2 filters)
|
|
106
|
+
- `redmine_questions` (`question_tag_url`, `tag_cloud_links`)
|
|
107
|
+
- `redmine_drive` (`drive_entry_tag_url`, select2 tag filters)
|
|
108
|
+
- `redmineup_tags` (issue tag forms with `tag_list_field_tag`)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module ActionCable
|
|
2
|
+
module Connection
|
|
3
|
+
class RupConnection < ActionCable::Connection::Base
|
|
4
|
+
identified_by :current_user
|
|
5
|
+
|
|
6
|
+
def connect
|
|
7
|
+
self.current_user = find_verified_user
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
def find_verified_user
|
|
13
|
+
User.find_by(id: @request.session[:user_id]) || User.anonymous
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module ActionCable
|
|
2
|
+
module Server
|
|
3
|
+
class RupConfiguration < ActionCable::Server::Configuration
|
|
4
|
+
def initialize(connection_klass: 'ActionCable::Connection::Base')
|
|
5
|
+
super()
|
|
6
|
+
|
|
7
|
+
@connection_class = -> { connection_klass.constantize }
|
|
8
|
+
@logger ||= ::Rails.logger
|
|
9
|
+
@disable_request_forgery_protection = true
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def cable
|
|
13
|
+
@cable ||= ActionCable.server.config.cable || { 'adapter' => 'async' }.with_indifferent_access
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
module ActionCable
|
|
2
|
+
module Server
|
|
3
|
+
class RupServer < ActionCable::Server::Base
|
|
4
|
+
attr_reader :config
|
|
5
|
+
|
|
6
|
+
def initialize(config: self.class.config)
|
|
7
|
+
@config = config
|
|
8
|
+
@mutex = Monitor.new
|
|
9
|
+
@remote_connections = @event_loop = @worker_pool = @pubsub = nil
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def setup_heartbeat_timer
|
|
13
|
+
Rails.logger.info('Heartbeat timer disabled for RupServer')
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
require 'digest/md5'
|
|
2
|
+
|
|
1
3
|
module Redmineup
|
|
2
4
|
module ActsAsTaggable #:nodoc:
|
|
3
5
|
class Tag < ActiveRecord::Base #:nodoc:
|
|
@@ -17,6 +19,10 @@ module Redmineup
|
|
|
17
19
|
where("LOWER(name) LIKE LOWER(?)", name).first || create(:name => name)
|
|
18
20
|
end
|
|
19
21
|
|
|
22
|
+
def self.color_from_name(str)
|
|
23
|
+
"##{Digest::MD5.hexdigest(str.to_s)[0..5]}"
|
|
24
|
+
end
|
|
25
|
+
|
|
20
26
|
def ==(object)
|
|
21
27
|
super || (object.is_a?(Tag) && name == object.name)
|
|
22
28
|
end
|
|
@@ -29,6 +35,18 @@ module Redmineup
|
|
|
29
35
|
read_attribute(:count).to_i
|
|
30
36
|
end
|
|
31
37
|
|
|
38
|
+
def color
|
|
39
|
+
if self.class.column_names.include?('color')
|
|
40
|
+
db_color = read_attribute(:color)
|
|
41
|
+
return ('#' + "%06x" % db_color) unless db_color.nil?
|
|
42
|
+
end
|
|
43
|
+
self.class.color_from_name(name)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def color=(color)
|
|
47
|
+
write_attribute(:color, color.sub(/\A#/, '').hex) unless color.blank?
|
|
48
|
+
end
|
|
49
|
+
|
|
32
50
|
class << self
|
|
33
51
|
# Calculate the tag counts for all tags.
|
|
34
52
|
# :start_at - Restrict the tags to those created after a certain time
|
|
@@ -66,9 +84,15 @@ module Redmineup
|
|
|
66
84
|
having = 'COUNT(*) > 0'
|
|
67
85
|
having = [having, at_least, at_most].compact.join(' AND ')
|
|
68
86
|
group_by = "#{Tag.table_name}.id, #{Tag.table_name}.name"
|
|
69
|
-
|
|
87
|
+
group_by << ", #{Tag.table_name}.color" if column_names.include?('color')
|
|
88
|
+
|
|
89
|
+
select_condition = if self.column_names.include?('color')
|
|
90
|
+
"#{Tag.table_name}.id, #{Tag.table_name}.name, #{Tag.table_name}.color, COUNT(*) AS count"
|
|
91
|
+
else
|
|
92
|
+
"#{Tag.table_name}.id, #{Tag.table_name}.name, COUNT(*) AS count"
|
|
93
|
+
end
|
|
70
94
|
|
|
71
|
-
{ :select =>
|
|
95
|
+
{ :select => select_condition,
|
|
72
96
|
:joins => joins.join(" "),
|
|
73
97
|
:conditions => conditions,
|
|
74
98
|
:group => group_by,
|
|
@@ -44,7 +44,7 @@ module Redmineup
|
|
|
44
44
|
|
|
45
45
|
# Create the taggable tables
|
|
46
46
|
# === Options hash:
|
|
47
|
-
# * <tt>:table_name</tt> - use a table name other than
|
|
47
|
+
# * <tt>:table_name</tt> - use a table name other than tags
|
|
48
48
|
# To be used during migration, but can also be used in other places
|
|
49
49
|
def create_taggable_table(options = {})
|
|
50
50
|
tag_name_table = options[:tags] || :tags
|
|
@@ -52,6 +52,7 @@ module Redmineup
|
|
|
52
52
|
if !self.connection.table_exists?(tag_name_table)
|
|
53
53
|
self.connection.create_table(tag_name_table) do |t|
|
|
54
54
|
t.column :name, :string
|
|
55
|
+
t.column :color, :integer
|
|
55
56
|
end
|
|
56
57
|
end
|
|
57
58
|
|
|
@@ -64,6 +65,7 @@ module Redmineup
|
|
|
64
65
|
# You should make sure that the column created is
|
|
65
66
|
# long enough to store the required class names.
|
|
66
67
|
t.column :taggable_type, :string
|
|
68
|
+
t.column :color, :integer, default: nil
|
|
67
69
|
|
|
68
70
|
t.column :created_at, :datetime
|
|
69
71
|
end
|
|
@@ -73,6 +75,43 @@ module Redmineup
|
|
|
73
75
|
end
|
|
74
76
|
end
|
|
75
77
|
|
|
78
|
+
# Create the needed column for up_acts_as_taggable.
|
|
79
|
+
# To be used during migration, but can also be used in other places.
|
|
80
|
+
#
|
|
81
|
+
# Options :name, :type, :default are required
|
|
82
|
+
#
|
|
83
|
+
# By default, a :color: column will be created
|
|
84
|
+
#
|
|
85
|
+
# E.g.: ActiveRecord::Base.add_tags_column(:tags, name: :color, type: :string, default: nil)
|
|
86
|
+
def add_tags_column(table_name = :tags, **options)
|
|
87
|
+
tag_column = options
|
|
88
|
+
tag_column.assert_valid_keys(:name, :type, :default)
|
|
89
|
+
|
|
90
|
+
raise ArgumentError, "Table `#{table_name}' not found" unless self.connection.table_exists?(table_name)
|
|
91
|
+
|
|
92
|
+
self.table_name = table_name
|
|
93
|
+
return if connection.column_exists?(table_name, tag_column[:name])
|
|
94
|
+
|
|
95
|
+
connection.add_column table_name, tag_column[:name], tag_column[:type], default: tag_column[:default]
|
|
96
|
+
reset_column_information
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# Remove the up_acts_as_taggable specific columns
|
|
100
|
+
# To be used during migration, but can also be used in other places
|
|
101
|
+
#
|
|
102
|
+
# E.g.: ActiveRecord::Base.remove_tags_columns(:tags, :color, :color1)
|
|
103
|
+
def remove_tags_columns(table_name = :tags, *columns)
|
|
104
|
+
raise ArgumentError, "Table `#{table_name}' not found" unless self.connection.table_exists?(table_name)
|
|
105
|
+
return if columns.blank?
|
|
106
|
+
|
|
107
|
+
columns.each do |column|
|
|
108
|
+
if self.connection.column_exists?(table_name, column)
|
|
109
|
+
self.connection.remove_column table_name, column
|
|
110
|
+
self.reset_column_information
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
76
115
|
def drop_taggable_table(options = {})
|
|
77
116
|
tag_name_table = options[:tags] || :tags
|
|
78
117
|
if self.connection.table_exists?(tag_name_table)
|
|
@@ -103,13 +142,14 @@ module Redmineup
|
|
|
103
142
|
scope = scope.where("#{table_name}.project_id IN (%s)", projects.map(&:id).join(','))
|
|
104
143
|
end
|
|
105
144
|
|
|
106
|
-
if options[:name_like]
|
|
145
|
+
if options[:name_like].present?
|
|
107
146
|
scope = scope.where("LOWER(#{Tag.table_name}.name) LIKE LOWER(?)", "%#{options[:name_like]}%")
|
|
108
147
|
end
|
|
109
148
|
|
|
110
149
|
group_fields = ''
|
|
111
150
|
group_fields << ", #{Tag.table_name}.created_at" if Tag.respond_to?(:created_at)
|
|
112
151
|
group_fields << ", #{Tag.table_name}.updated_at" if Tag.respond_to?(:updated_at)
|
|
152
|
+
group_fields << ", #{Tag.table_name}.color" if Tag.column_names.include?('color')
|
|
113
153
|
|
|
114
154
|
if base_class.respond_to?(:visible_condition)
|
|
115
155
|
visible_condition = base_class.visible_condition(User.current)
|
|
@@ -232,11 +232,8 @@ module Redmineup
|
|
|
232
232
|
end
|
|
233
233
|
end
|
|
234
234
|
self.record_timestamps = false
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
else
|
|
238
|
-
self.assign_attributes(updates) && self.save if !updates.empty?
|
|
239
|
-
end
|
|
235
|
+
|
|
236
|
+
self.update(updates) if !updates.empty?
|
|
240
237
|
end
|
|
241
238
|
|
|
242
239
|
# results
|
|
@@ -2,7 +2,8 @@ module Redmineup
|
|
|
2
2
|
class AssetsManager
|
|
3
3
|
def self.install_assets
|
|
4
4
|
return unless Gem.loaded_specs[GEM_NAME]
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
source = File.join(Gem.loaded_specs[GEM_NAME].full_gem_path, 'app', 'assets')
|
|
6
7
|
destination = File.join(Dir.pwd, 'public', 'plugin_assets', GEM_NAME)
|
|
7
8
|
return unless File.directory?(source)
|
|
8
9
|
|
|
@@ -39,5 +40,15 @@ module Redmineup
|
|
|
39
40
|
end
|
|
40
41
|
end
|
|
41
42
|
end
|
|
43
|
+
|
|
44
|
+
def self.base_path
|
|
45
|
+
Gem.loaded_specs[GEM_NAME].full_gem_path
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.assets_paths
|
|
49
|
+
return [] unless Gem.loaded_specs[GEM_NAME]
|
|
50
|
+
|
|
51
|
+
Dir[File.join(base_path, 'app', 'assets', '*')]
|
|
52
|
+
end
|
|
42
53
|
end
|
|
43
54
|
end
|
data/lib/redmineup/currency.rb
CHANGED
|
@@ -40,7 +40,9 @@ module Redmineup
|
|
|
40
40
|
Redmine::MenuManager.map(:admin_menu).push(:redmineup_money,
|
|
41
41
|
{ controller: 'redmineup', action: 'settings', id: 'money' },
|
|
42
42
|
caption: :label_redmineup_money,
|
|
43
|
-
html: { class: 'icon icon-
|
|
43
|
+
html: { class: 'icon icon-redmineup-money' },
|
|
44
|
+
icon: 'money',
|
|
45
|
+
plugin: :redmineup)
|
|
44
46
|
|
|
45
47
|
end
|
|
46
48
|
|
data/lib/redmineup/engine.rb
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
1
|
module Redmineup
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
class Engine < Rails::Engine
|
|
3
|
+
config.to_prepare do
|
|
4
|
+
next unless defined?(::Redmine::Plugin)
|
|
5
|
+
|
|
6
|
+
require 'auto_completes_controller'
|
|
7
|
+
|
|
8
|
+
require File.expand_path('../redmineup/patches/compatibility_patch', __dir__)
|
|
9
|
+
require File.expand_path('../redmineup/patches/auto_completes_controller_patch', __dir__)
|
|
10
|
+
|
|
11
|
+
require File.expand_path('../redmineup/hooks/views_layouts_hook', __dir__)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
4
14
|
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module Redmineup
|
|
2
|
+
module CalendarsHelper
|
|
3
|
+
|
|
4
|
+
def calendar_day_css_classes(calendar, day)
|
|
5
|
+
css = day.month==calendar.month ? +'even' : +'odd'
|
|
6
|
+
css << " today" if User.current.today == day
|
|
7
|
+
css << " nwday" if non_working_week_days.include?(day.cwday)
|
|
8
|
+
css
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def non_working_week_days
|
|
12
|
+
@non_working_week_days ||= begin
|
|
13
|
+
days = Setting.non_working_week_days
|
|
14
|
+
if days.is_a?(Array) && days.size < 7
|
|
15
|
+
days.map(&:to_i)
|
|
16
|
+
else
|
|
17
|
+
[]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Redmineup
|
|
2
|
+
module DatetimeHelper
|
|
3
|
+
def convert_time_to_user_timezone(time)
|
|
4
|
+
if User.current.time_zone
|
|
5
|
+
time.in_time_zone(User.current.time_zone)
|
|
6
|
+
else
|
|
7
|
+
time.utc? ? time.localtime : time
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def convert_time_to_utc(time = Time.now)
|
|
12
|
+
return Time.now.utc if time.blank?
|
|
13
|
+
return time.utc if time.respond_to?(:utc)
|
|
14
|
+
|
|
15
|
+
time_zone = User.current.time_zone
|
|
16
|
+
return Time.parse(time).utc unless time_zone
|
|
17
|
+
|
|
18
|
+
Time.use_zone(time_zone) do
|
|
19
|
+
Time.zone.parse(time).utc
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -2,19 +2,27 @@ module Redmineup
|
|
|
2
2
|
module ExternalAssetsHelper
|
|
3
3
|
include ActionView::Helpers::JavaScriptHelper
|
|
4
4
|
|
|
5
|
+
def redmineup_assets
|
|
6
|
+
return if @redmineup_assets_included
|
|
7
|
+
|
|
8
|
+
@redmineup_assets_included = true
|
|
9
|
+
javascript_include_tag('consumer', plugin: GEM_NAME) +
|
|
10
|
+
javascript_include_tag('select2', plugin: GEM_NAME) +
|
|
11
|
+
javascript_include_tag('select2_helpers', plugin: GEM_NAME) +
|
|
12
|
+
javascript_include_tag('jquery.colorPicker.min', plugin: GEM_NAME) +
|
|
13
|
+
stylesheet_link_tag('redmineup', plugin: GEM_NAME) +
|
|
14
|
+
javascript_tag("window.redmineupWorkerUrl = #{asset_path('consumer_worker.js', plugin: GEM_NAME).to_json};")
|
|
15
|
+
end
|
|
16
|
+
|
|
5
17
|
def select2_assets
|
|
6
|
-
|
|
7
|
-
@select2_tag_included = true
|
|
8
|
-
javascript_include_tag('select2', plugin: GEM_NAME) +
|
|
9
|
-
stylesheet_link_tag('select2', plugin: GEM_NAME) +
|
|
10
|
-
javascript_include_tag('select2_helpers', plugin: GEM_NAME)
|
|
18
|
+
redmineup_assets
|
|
11
19
|
end
|
|
12
20
|
|
|
13
21
|
def chartjs_assets
|
|
14
22
|
return if @chartjs_tag_included
|
|
23
|
+
|
|
15
24
|
@chartjs_tag_included = true
|
|
16
|
-
javascript_include_tag('
|
|
25
|
+
javascript_include_tag('chart.min', plugin: GEM_NAME)
|
|
17
26
|
end
|
|
18
|
-
|
|
19
27
|
end
|
|
20
28
|
end
|