lsa_tdx_feedback 1.0.0
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 +7 -0
- data/.rspec +3 -0
- data/CHANGELOG.md +49 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +328 -0
- data/INSTALLATION.md +244 -0
- data/LICENSE.txt +21 -0
- data/README.md +391 -0
- data/Rakefile +10 -0
- data/app/assets/javascripts/lsa_tdx_feedback.js +231 -0
- data/app/assets/stylesheets/lsa_tdx_feedback.css +279 -0
- data/app/controllers/lsa_tdx_feedback/feedback_controller.rb +71 -0
- data/app/views/lsa_tdx_feedback/shared/_feedback_modal.html.erb +95 -0
- data/config/routes.rb +3 -0
- data/example_application_tests.rb +0 -0
- data/lib/lsa_tdx_feedback/application_controller_extensions.rb +52 -0
- data/lib/lsa_tdx_feedback/configuration.rb +89 -0
- data/lib/lsa_tdx_feedback/engine.rb +65 -0
- data/lib/lsa_tdx_feedback/oauth_client.rb +81 -0
- data/lib/lsa_tdx_feedback/ticket_client.rb +106 -0
- data/lib/lsa_tdx_feedback/version.rb +3 -0
- data/lib/lsa_tdx_feedback/view_helpers.rb +37 -0
- data/lib/lsa_tdx_feedback.rb +31 -0
- metadata +223 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
require 'httparty'
|
|
2
|
+
require 'base64'
|
|
3
|
+
|
|
4
|
+
module LsaTdxFeedback
|
|
5
|
+
class OAuthClient
|
|
6
|
+
include HTTParty
|
|
7
|
+
|
|
8
|
+
def initialize(configuration = LsaTdxFeedback.configuration)
|
|
9
|
+
@configuration = configuration
|
|
10
|
+
@configuration.validate!
|
|
11
|
+
|
|
12
|
+
self.class.base_uri(@configuration.oauth_url)
|
|
13
|
+
self.class.headers({
|
|
14
|
+
'Content-Type' => 'application/x-www-form-urlencoded',
|
|
15
|
+
'Accept' => 'application/json'
|
|
16
|
+
})
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get_access_token
|
|
20
|
+
# Check cache first
|
|
21
|
+
cached_token = Rails.cache.read(cache_key)
|
|
22
|
+
return cached_token if cached_token
|
|
23
|
+
|
|
24
|
+
# Get new token
|
|
25
|
+
token_response = fetch_new_token
|
|
26
|
+
|
|
27
|
+
if token_response.success?
|
|
28
|
+
token_data = token_response.parsed_response
|
|
29
|
+
access_token = token_data['access_token']
|
|
30
|
+
expires_in = token_data['expires_in'].to_i
|
|
31
|
+
|
|
32
|
+
# Cache the token with some buffer time (subtract 5 minutes)
|
|
33
|
+
cache_duration = [expires_in - 300, 300].max
|
|
34
|
+
Rails.cache.write(cache_key, access_token, expires_in: cache_duration)
|
|
35
|
+
|
|
36
|
+
access_token
|
|
37
|
+
else
|
|
38
|
+
handle_error_response(token_response)
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def fetch_new_token
|
|
45
|
+
auth_header = Base64.strict_encode64("#{@configuration.client_id}:#{@configuration.client_secret}")
|
|
46
|
+
|
|
47
|
+
response = self.class.post('/token',
|
|
48
|
+
headers: {
|
|
49
|
+
'Authorization' => "Basic #{auth_header}"
|
|
50
|
+
},
|
|
51
|
+
body: {
|
|
52
|
+
grant_type: @configuration.grant_type,
|
|
53
|
+
scope: @configuration.oauth_scope
|
|
54
|
+
}.to_query
|
|
55
|
+
)
|
|
56
|
+
|
|
57
|
+
response
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def handle_error_response(response)
|
|
61
|
+
error_data = response.parsed_response
|
|
62
|
+
|
|
63
|
+
Rails.logger.error "LsaTdxFeedback: OAuth Error - HTTP #{response.code}"
|
|
64
|
+
Rails.logger.error "LsaTdxFeedback: OAuth Error Response: #{response.body}"
|
|
65
|
+
|
|
66
|
+
error_message = if error_data.is_a?(Hash) && error_data['errorMessage']
|
|
67
|
+
"OAuth Error: #{error_data['errorMessage']} (Code: #{error_data['errorCode']})"
|
|
68
|
+
elsif error_data.is_a?(Hash) && error_data['error']
|
|
69
|
+
"OAuth Error: #{error_data['error']} - #{error_data['error_description']}"
|
|
70
|
+
else
|
|
71
|
+
"OAuth Error: HTTP #{response.code} - #{response.message}"
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
raise Error, error_message
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def cache_key
|
|
78
|
+
"lsa_tdx_feedback:oauth_token:#{Digest::MD5.hexdigest(@configuration.client_id)}"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
require 'httparty'
|
|
2
|
+
|
|
3
|
+
module LsaTdxFeedback
|
|
4
|
+
class TicketClient
|
|
5
|
+
include HTTParty
|
|
6
|
+
|
|
7
|
+
def initialize(configuration = LsaTdxFeedback.configuration)
|
|
8
|
+
@configuration = configuration
|
|
9
|
+
@configuration.validate!
|
|
10
|
+
@oauth_client = OAuthClient.new(configuration)
|
|
11
|
+
|
|
12
|
+
self.class.base_uri(@configuration.api_base_url)
|
|
13
|
+
self.class.headers({
|
|
14
|
+
'Content-Type' => 'application/json',
|
|
15
|
+
'Accept' => 'application/json'
|
|
16
|
+
})
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def create_feedback_ticket(feedback_data)
|
|
20
|
+
ticket_payload = build_ticket_payload(feedback_data)
|
|
21
|
+
|
|
22
|
+
response = self.class.post("/#{@configuration.app_id}/tickets",
|
|
23
|
+
headers: {
|
|
24
|
+
'Authorization' => "Bearer #{@oauth_client.get_access_token}"
|
|
25
|
+
},
|
|
26
|
+
body: ticket_payload.to_json
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
if response.success?
|
|
30
|
+
response.parsed_response
|
|
31
|
+
else
|
|
32
|
+
handle_error_response(response)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def build_ticket_payload(feedback_data)
|
|
39
|
+
{
|
|
40
|
+
TypeID: @configuration.default_type_id,
|
|
41
|
+
Classification: @configuration.default_classification,
|
|
42
|
+
Title: feedback_data[:title] || 'User Feedback',
|
|
43
|
+
Description: build_description(feedback_data),
|
|
44
|
+
RequestorEmail: feedback_data[:email],
|
|
45
|
+
SourceID: @configuration.default_source_id,
|
|
46
|
+
ServiceID: @configuration.default_service_id,
|
|
47
|
+
ResponsibleGroupID: @configuration.default_responsible_group_id,
|
|
48
|
+
AccountID: @configuration.account_id
|
|
49
|
+
}.compact
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def build_description(feedback_data)
|
|
53
|
+
description_parts = []
|
|
54
|
+
|
|
55
|
+
description_parts << 'User Feedback'
|
|
56
|
+
description_parts << ''
|
|
57
|
+
description_parts << "Feedback: #{feedback_data[:feedback]}"
|
|
58
|
+
|
|
59
|
+
if feedback_data[:category].present?
|
|
60
|
+
description_parts << "Category: #{feedback_data[:category]}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
if feedback_data[:url].present?
|
|
64
|
+
description_parts << "Page URL: #{feedback_data[:url]}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
if feedback_data[:user_agent].present?
|
|
68
|
+
description_parts << "Browser: #{feedback_data[:user_agent]}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
if feedback_data[:additional_info].present?
|
|
72
|
+
description_parts << ''
|
|
73
|
+
description_parts << 'Additional Information:'
|
|
74
|
+
description_parts << feedback_data[:additional_info]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
description_parts.join('\n')
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def sanitize_html(text)
|
|
81
|
+
return '' unless text
|
|
82
|
+
|
|
83
|
+
# Basic HTML escaping
|
|
84
|
+
text.to_s
|
|
85
|
+
.gsub('&', '&')
|
|
86
|
+
.gsub('<', '<')
|
|
87
|
+
.gsub('>', '>')
|
|
88
|
+
.gsub('"', '"')
|
|
89
|
+
.gsub("'", ''')
|
|
90
|
+
.gsub('/', '/')
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def handle_error_response(response)
|
|
94
|
+
error_data = response.parsed_response
|
|
95
|
+
error_message = if error_data.is_a?(Hash) && error_data['errorMessage']
|
|
96
|
+
"TDX API Error: #{error_data['errorMessage']} (Code: #{error_data['errorCode']})"
|
|
97
|
+
elsif error_data.is_a?(Hash) && error_data['Message']
|
|
98
|
+
"TDX API Error: #{error_data['Message']}"
|
|
99
|
+
else
|
|
100
|
+
"TDX API Error: HTTP #{response.code} - #{response.message} - #{response.body}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
raise Error, error_message
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module LsaTdxFeedback
|
|
2
|
+
module ViewHelpers
|
|
3
|
+
# Simple test method to verify helper is working
|
|
4
|
+
def lsa_tdx_feedback_test
|
|
5
|
+
"HELPER IS WORKING!".html_safe
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
# Renders the feedback modal and trigger button
|
|
9
|
+
def lsa_tdx_feedback_modal
|
|
10
|
+
render(partial: 'lsa_tdx_feedback/shared/feedback_modal')
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Includes the feedback gem CSS
|
|
14
|
+
def lsa_tdx_feedback_css
|
|
15
|
+
stylesheet_link_tag 'lsa_tdx_feedback', 'data-turbo-track': 'reload', preload: false
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Includes the feedback gem JavaScript
|
|
19
|
+
def lsa_tdx_feedback_js
|
|
20
|
+
javascript_include_tag 'lsa_tdx_feedback', 'data-turbo-track': 'reload', defer: true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Includes both CSS and JavaScript assets
|
|
24
|
+
def lsa_tdx_feedback_assets
|
|
25
|
+
css = lsa_tdx_feedback_css
|
|
26
|
+
js = lsa_tdx_feedback_js
|
|
27
|
+
(css + js).html_safe
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# All-in-one helper that includes both modal and assets
|
|
31
|
+
def lsa_tdx_feedback
|
|
32
|
+
assets = lsa_tdx_feedback_assets
|
|
33
|
+
modal = lsa_tdx_feedback_modal
|
|
34
|
+
(assets + modal).html_safe
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
require_relative 'lsa_tdx_feedback/version'
|
|
2
|
+
require_relative 'lsa_tdx_feedback/configuration'
|
|
3
|
+
require_relative 'lsa_tdx_feedback/oauth_client'
|
|
4
|
+
require_relative 'lsa_tdx_feedback/ticket_client'
|
|
5
|
+
require_relative 'lsa_tdx_feedback/application_controller_extensions'
|
|
6
|
+
require_relative 'lsa_tdx_feedback/view_helpers'
|
|
7
|
+
|
|
8
|
+
# Only require the engine when Rails is available
|
|
9
|
+
if defined?(Rails)
|
|
10
|
+
require_relative 'lsa_tdx_feedback/engine'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
module LsaTdxFeedback
|
|
14
|
+
class Error < StandardError; end
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
attr_writer :configuration
|
|
18
|
+
|
|
19
|
+
def configuration
|
|
20
|
+
@configuration ||= Configuration.new
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def configure
|
|
24
|
+
yield(configuration)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def reset_configuration!
|
|
28
|
+
@configuration = Configuration.new
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: lsa_tdx_feedback
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- LSA Rails Team
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: httparty
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0.22'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0.22'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: rails
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - ">="
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '6.0'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '6.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: redis
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '4.0'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '4.0'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: factory_bot_rails
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '6.0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '6.0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rspec
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '3.0'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '3.0'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: rspec-rails
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '5.0'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '5.0'
|
|
96
|
+
- !ruby/object:Gem::Dependency
|
|
97
|
+
name: rubocop
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
99
|
+
requirements:
|
|
100
|
+
- - "~>"
|
|
101
|
+
- !ruby/object:Gem::Version
|
|
102
|
+
version: '1.21'
|
|
103
|
+
type: :development
|
|
104
|
+
prerelease: false
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - "~>"
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '1.21'
|
|
110
|
+
- !ruby/object:Gem::Dependency
|
|
111
|
+
name: rubocop-rails
|
|
112
|
+
requirement: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - "~>"
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '2.0'
|
|
117
|
+
type: :development
|
|
118
|
+
prerelease: false
|
|
119
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
120
|
+
requirements:
|
|
121
|
+
- - "~>"
|
|
122
|
+
- !ruby/object:Gem::Version
|
|
123
|
+
version: '2.0'
|
|
124
|
+
- !ruby/object:Gem::Dependency
|
|
125
|
+
name: rubocop-rspec
|
|
126
|
+
requirement: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - "~>"
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: '2.0'
|
|
131
|
+
type: :development
|
|
132
|
+
prerelease: false
|
|
133
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
134
|
+
requirements:
|
|
135
|
+
- - "~>"
|
|
136
|
+
- !ruby/object:Gem::Version
|
|
137
|
+
version: '2.0'
|
|
138
|
+
- !ruby/object:Gem::Dependency
|
|
139
|
+
name: vcr
|
|
140
|
+
requirement: !ruby/object:Gem::Requirement
|
|
141
|
+
requirements:
|
|
142
|
+
- - "~>"
|
|
143
|
+
- !ruby/object:Gem::Version
|
|
144
|
+
version: '6.0'
|
|
145
|
+
type: :development
|
|
146
|
+
prerelease: false
|
|
147
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
148
|
+
requirements:
|
|
149
|
+
- - "~>"
|
|
150
|
+
- !ruby/object:Gem::Version
|
|
151
|
+
version: '6.0'
|
|
152
|
+
- !ruby/object:Gem::Dependency
|
|
153
|
+
name: webmock
|
|
154
|
+
requirement: !ruby/object:Gem::Requirement
|
|
155
|
+
requirements:
|
|
156
|
+
- - "~>"
|
|
157
|
+
- !ruby/object:Gem::Version
|
|
158
|
+
version: '3.0'
|
|
159
|
+
type: :development
|
|
160
|
+
prerelease: false
|
|
161
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
162
|
+
requirements:
|
|
163
|
+
- - "~>"
|
|
164
|
+
- !ruby/object:Gem::Version
|
|
165
|
+
version: '3.0'
|
|
166
|
+
description: LsaTdxFeedback provides a zero-configuration solution for collecting
|
|
167
|
+
user feedback in Rails applications. It includes a modal interface that integrates
|
|
168
|
+
with TeamDynamix (TDX) API for ticket creation, requiring no additional setup or
|
|
169
|
+
configuration from the host application.
|
|
170
|
+
email:
|
|
171
|
+
- lsa-was-rails-admins@umich.edu
|
|
172
|
+
executables: []
|
|
173
|
+
extensions: []
|
|
174
|
+
extra_rdoc_files: []
|
|
175
|
+
files:
|
|
176
|
+
- ".rspec"
|
|
177
|
+
- CHANGELOG.md
|
|
178
|
+
- Gemfile
|
|
179
|
+
- Gemfile.lock
|
|
180
|
+
- INSTALLATION.md
|
|
181
|
+
- LICENSE.txt
|
|
182
|
+
- README.md
|
|
183
|
+
- Rakefile
|
|
184
|
+
- app/assets/javascripts/lsa_tdx_feedback.js
|
|
185
|
+
- app/assets/stylesheets/lsa_tdx_feedback.css
|
|
186
|
+
- app/controllers/lsa_tdx_feedback/feedback_controller.rb
|
|
187
|
+
- app/views/lsa_tdx_feedback/shared/_feedback_modal.html.erb
|
|
188
|
+
- config/routes.rb
|
|
189
|
+
- example_application_tests.rb
|
|
190
|
+
- lib/lsa_tdx_feedback.rb
|
|
191
|
+
- lib/lsa_tdx_feedback/application_controller_extensions.rb
|
|
192
|
+
- lib/lsa_tdx_feedback/configuration.rb
|
|
193
|
+
- lib/lsa_tdx_feedback/engine.rb
|
|
194
|
+
- lib/lsa_tdx_feedback/oauth_client.rb
|
|
195
|
+
- lib/lsa_tdx_feedback/ticket_client.rb
|
|
196
|
+
- lib/lsa_tdx_feedback/version.rb
|
|
197
|
+
- lib/lsa_tdx_feedback/view_helpers.rb
|
|
198
|
+
homepage: https://github.com/lsa-mis/lsa_feedback
|
|
199
|
+
licenses:
|
|
200
|
+
- MIT
|
|
201
|
+
metadata:
|
|
202
|
+
homepage_uri: https://github.com/lsa-mis/lsa_feedback
|
|
203
|
+
source_code_uri: https://github.com/lsa-mis/lsa_feedback
|
|
204
|
+
changelog_uri: https://github.com/lsa-mis/lsa_feedback/blob/main/CHANGELOG.md
|
|
205
|
+
rdoc_options: []
|
|
206
|
+
require_paths:
|
|
207
|
+
- lib
|
|
208
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
209
|
+
requirements:
|
|
210
|
+
- - ">="
|
|
211
|
+
- !ruby/object:Gem::Version
|
|
212
|
+
version: '0'
|
|
213
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
214
|
+
requirements:
|
|
215
|
+
- - ">="
|
|
216
|
+
- !ruby/object:Gem::Version
|
|
217
|
+
version: '0'
|
|
218
|
+
requirements: []
|
|
219
|
+
rubygems_version: 3.6.9
|
|
220
|
+
specification_version: 4
|
|
221
|
+
summary: A self-contained Rails gem for collecting user feedback via TDX API for LSA
|
|
222
|
+
applications
|
|
223
|
+
test_files: []
|