ZReviewTender 1.3.5 → 1.3.7
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/.version +1 -1
- data/config/android.example.yml +29 -2
- data/config/apple.example.yml +30 -2
- data/lib/Models/Processor.rb +15 -0
- data/lib/Models/ReviewFetcher.rb +9 -1
- data/lib/Processors/AsanaProcessor.rb +131 -0
- data/lib/Processors/GoogleSheetProcessor.rb +76 -18
- metadata +3 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 77c4a5c74b6076acbf3ec3ea98046ab703f1df3485c4590c9f1de62bda77fad9
|
4
|
+
data.tar.gz: 1b8bc52b07eac4add166d360b648fa5991264d501b72a39c1dc7a412da51413c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 8bba8ffe64bb4342689ade1b868a4a369e7879641c927466792cfef62f1860b3508efb1d289c5fea8c0bb6741780e2ce491c063814b327e4b85c2b699851dff7
|
7
|
+
data.tar.gz: 5cea45b9203f52e95ff67169fec881981e501e68b8e7cbc2298798e483963a44684d17e0337a7536b99ed2ba6f08f99bf531b762cf0848d8326c4864ba5bdc99
|
data/.version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.3.
|
1
|
+
1.3.7
|
data/config/android.example.yml
CHANGED
@@ -29,8 +29,11 @@ processors:
|
|
29
29
|
enable: false # enable
|
30
30
|
googleSheetAPIKeyFilePath: "" # Google Translate API Service Account Credential .json File Path
|
31
31
|
googleSheetTimeZoneOffset: "+08:00" # Review Created Date TimeZone
|
32
|
-
|
33
|
-
|
32
|
+
googleSheetInsertStyle:
|
33
|
+
- type: "append" # Google Sheet Insert type, append or insert
|
34
|
+
- at: 0 # required if type is insert, where index should insert at
|
35
|
+
- sheetID: null # required if type is insert, the sheet ID, you can get it on google sheet url: e.g. https://docs.google.com/spreadsheets/d/googleSpreadsheetID/edit#gid=sheetID
|
36
|
+
- sheetName: "Sheet1" # required if type is append, a.k.a google sheet tab name
|
34
37
|
values: ["%RATING%","%TITLE%\n%BODY%","%APPVERSION%","%CREATEDDATE%"] # Columns Data, you can uses magic variable below to compose string.
|
35
38
|
# %TITLE% for review's title
|
36
39
|
# %BODY% for review's content
|
@@ -43,6 +46,30 @@ processors:
|
|
43
46
|
# %APPVERSION% for review's reviewer app version
|
44
47
|
# %CREATEDDATE% for review's created date
|
45
48
|
|
49
|
+
keywordsInclude: [] # keywords you want to filter out
|
50
|
+
ratingsInclude: [] # ratings you want to filter out
|
51
|
+
territoriesInclude: [] # territories you want to filter out(language for android e.g. zh-Hant, en)
|
52
|
+
- AsanaProcessor:
|
53
|
+
class: "AsanaProcessor"
|
54
|
+
enable: false # enable
|
55
|
+
asanaTimeZoneOffset: "+08:00"
|
56
|
+
asanaToken: "" # Asana Personal Access Token, get it here -> https://app.asana.com/0/my-apps
|
57
|
+
asanaProjectID: "" # Asana Project ID, get it in project url -> https://app.asana.com/0/asanaProjectID/list
|
58
|
+
asanaSectionName: "" # Task Target Project - Section Name, optional
|
59
|
+
asanaTaskTitleTemplate: "%PLATFORM% - %RATING% ⭐️ - %TITLE%" # Asana Task Title Template
|
60
|
+
asanaTaskBodyTemplate: "Title: %TITLE%\n---\nBody:\n%BODY%\n---\n- UserName: %USERNAME%\n- App Version: %APPVERSION%\n- Date:%CREATEDDATE%" # Asana Task Body Template
|
61
|
+
# you can uses magic variable below to compose string.
|
62
|
+
# %TITLE% for review's title
|
63
|
+
# %BODY% for review's content
|
64
|
+
# %RATING% for review's rating 1~5
|
65
|
+
# %PLATFORM% for review's platform Apple or Android
|
66
|
+
# %ID% for review's ID
|
67
|
+
# %USERNAME% for review's reviewer username
|
68
|
+
# %URL% for link to review
|
69
|
+
# %TERRITORY% for review's territory (territory for Apple e.g. TWN)
|
70
|
+
# %APPVERSION% for review's reviewer app version
|
71
|
+
# %CREATEDDATE% for review's created date
|
72
|
+
|
46
73
|
keywordsInclude: [] # keywords you want to filter out
|
47
74
|
ratingsInclude: [] # ratings you want to filter out
|
48
75
|
territoriesInclude: [] # territories you want to filter out(language for android e.g. zh-Hant, en)
|
data/config/apple.example.yml
CHANGED
@@ -29,8 +29,12 @@ processors:
|
|
29
29
|
enable: false # enable
|
30
30
|
googleSheetAPIKeyFilePath: "" # Google Translate API Service Account Credential .json File Path
|
31
31
|
googleSheetTimeZoneOffset: "+08:00" # Review Created Date TimeZone
|
32
|
-
|
33
|
-
|
32
|
+
googleSpreadsheetID: "1_lc82p-epecVKwpUWlXh1yyNWaSrygzDb8I0QH2xkTI" # Google Sheet SpreadSheet ID, you can get it on google sheet url: e.g. https://docs.google.com/spreadsheets/d/googleSpreadsheetID/
|
33
|
+
googleSheetInsertStyle:
|
34
|
+
- type: "append" # Google Sheet Insert type, append or insert
|
35
|
+
- at: 0 # required if type is insert, where index should insert at
|
36
|
+
- sheetID: null # required if type is insert, the sheet ID, you can get it on google sheet url: e.g. https://docs.google.com/spreadsheets/d/googleSpreadsheetID/edit#gid=sheetID
|
37
|
+
- sheetName: "Sheet1" # required if type is append, a.k.a google sheet tab name
|
34
38
|
values: ["%RATING%","%TITLE%\n%BODY%","%APPVERSION%","%CREATEDDATE%"] # Columns Data, you can uses magic variable below to compose string.
|
35
39
|
# %TITLE% for review's title
|
36
40
|
# %BODY% for review's content
|
@@ -43,6 +47,30 @@ processors:
|
|
43
47
|
# %APPVERSION% for review's reviewer app version
|
44
48
|
# %CREATEDDATE% for review's created date
|
45
49
|
|
50
|
+
keywordsInclude: [] # keywords you want to filter out
|
51
|
+
ratingsInclude: [] # ratings you want to filter out
|
52
|
+
territoriesInclude: [] # territories you want to filter out (territory for Apple e.g. TWN)
|
53
|
+
- AsanaProcessor:
|
54
|
+
class: "AsanaProcessor"
|
55
|
+
enable: false # enable
|
56
|
+
asanaTimeZoneOffset: "+08:00"
|
57
|
+
asanaToken: "" # Asana Personal Access Token, get it here -> https://app.asana.com/0/my-apps
|
58
|
+
asanaProjectID: "" # Asana Project ID, get it in project url -> https://app.asana.com/0/asanaProjectID/list
|
59
|
+
asanaSectionName: "" # Task Target Project - Section Name, optional
|
60
|
+
asanaTaskTitleTemplate: "%PLATFORM% - %RATING% ⭐️ - %TITLE%" # Asana Task Title Template
|
61
|
+
asanaTaskBodyTemplate: "Title: %TITLE%\n---\nBody:\n%BODY%\n---\n- UserName: %USERNAME%\n- App Version: %APPVERSION%\n- Date:%CREATEDDATE%" # Asana Task Body Template
|
62
|
+
# you can uses magic variable below to compose string.
|
63
|
+
# %TITLE% for review's title
|
64
|
+
# %BODY% for review's content
|
65
|
+
# %RATING% for review's rating 1~5
|
66
|
+
# %PLATFORM% for review's platform Apple or Android
|
67
|
+
# %ID% for review's ID
|
68
|
+
# %USERNAME% for review's reviewer username
|
69
|
+
# %URL% for link to review
|
70
|
+
# %TERRITORY% for review's territory (territory for Apple e.g. TWN)
|
71
|
+
# %APPVERSION% for review's reviewer app version
|
72
|
+
# %CREATEDDATE% for review's created date
|
73
|
+
|
46
74
|
keywordsInclude: [] # keywords you want to filter out
|
47
75
|
ratingsInclude: [] # ratings you want to filter out
|
48
76
|
territoriesInclude: [] # territories you want to filter out (territory for Apple e.g. TWN)
|
data/lib/Models/Processor.rb
CHANGED
@@ -13,4 +13,19 @@ class Processor
|
|
13
13
|
def processReviews(reviews, platform)
|
14
14
|
|
15
15
|
end
|
16
|
+
|
17
|
+
def renderReview(templateText, review, timeZoneOffset)
|
18
|
+
templateText = templateText.gsub("%TITLE%", review.title || "")
|
19
|
+
templateText = templateText.gsub("%BODY%", review.body || "")
|
20
|
+
templateText = templateText.gsub("%RATING%", review.rating.nil? ? "" :review.rating.to_s)
|
21
|
+
templateText = templateText.gsub("%PLATFORM%", review.platform || "")
|
22
|
+
templateText = templateText.gsub("%ID%", review.id || "")
|
23
|
+
templateText = templateText.gsub("%USERNAME%", review.userName || "")
|
24
|
+
templateText = templateText.gsub("%URL%", review.url || "")
|
25
|
+
templateText = templateText.gsub("%TERRITORY%", review.territory || "")
|
26
|
+
templateText = templateText.gsub("%APPVERSION%", review.appVersion || "")
|
27
|
+
templateText = templateText.gsub("%CREATEDDATE%", review.createdDateTimestamp.nil? ? "" : Time.at(review.createdDateTimestamp).getlocal(timeZoneOffset).to_s)
|
28
|
+
|
29
|
+
return templateText
|
30
|
+
end
|
16
31
|
end
|
data/lib/Models/ReviewFetcher.rb
CHANGED
@@ -20,7 +20,15 @@ class ReviewFetcher
|
|
20
20
|
|
21
21
|
def processReviews(reviews, platform)
|
22
22
|
processors.each do |processor|
|
23
|
-
|
23
|
+
begin
|
24
|
+
reviews = processor.processReviews(reviews, platform)
|
25
|
+
rescue => e
|
26
|
+
errorMessage = "# Processor Error"
|
27
|
+
errorMessage += "#Error Message: #{e.message}\n"
|
28
|
+
errorMessage += "#Error Class: #{e.class}\n"
|
29
|
+
errorMessage += "#Backtrace Start#\n#{e.backtrace.join("\n")}\n#Backtrace End#\n"
|
30
|
+
logger.logError(errorMessage)
|
31
|
+
end
|
24
32
|
end
|
25
33
|
end
|
26
34
|
|
@@ -0,0 +1,131 @@
|
|
1
|
+
$lib = File.expand_path('../lib', File.dirname(__FILE__))
|
2
|
+
|
3
|
+
require "Models/Review"
|
4
|
+
require "Models/Processor"
|
5
|
+
require "Helper"
|
6
|
+
require "pathname"
|
7
|
+
require "GoogleAPI"
|
8
|
+
|
9
|
+
class AsanaProcessor < Processor
|
10
|
+
|
11
|
+
attr_accessor :keywordsInclude, :ratingsInclude, :territoriesInclude, :logger, :timeZoneOffset, :token, :projectID, :sectionName, :taskTitleTemplate, :taskBodyTemplate, :asanaAPIURL
|
12
|
+
|
13
|
+
def initialize(config, configFilePath, baseExecutePath)
|
14
|
+
@config = config
|
15
|
+
@configFilePath = configFilePath
|
16
|
+
@baseExecutePath = baseExecutePath
|
17
|
+
@logger = ZLogger.new(baseExecutePath)
|
18
|
+
|
19
|
+
@asanaAPIURL = "https://app.asana.com/api/1.0"
|
20
|
+
@token = Helper.unwrapRequiredParameter(config, "asanaToken")
|
21
|
+
@projectID = Helper.unwrapRequiredParameter(config, "asanaProjectID")
|
22
|
+
@taskTitleTemplate = Helper.unwrapRequiredParameter(config, "asanaTaskTitleTemplate")
|
23
|
+
@taskBodyTemplate = Helper.unwrapRequiredParameter(config, "asanaTaskBodyTemplate")
|
24
|
+
@timeZoneOffset = Helper.unwrapRequiredParameter(config, "asanaTimeZoneOffset")
|
25
|
+
@sectionName = nil
|
26
|
+
if !config["asanaSectionName"].nil? && config["asanaSectionName"] != ""
|
27
|
+
@sectionName = config["asanaSectionName"].strip
|
28
|
+
end
|
29
|
+
|
30
|
+
@keywordsInclude = []
|
31
|
+
if !config["keywordsInclude"].nil?
|
32
|
+
@keywordsInclude = config["keywordsInclude"]
|
33
|
+
end
|
34
|
+
|
35
|
+
@ratingsInclude = []
|
36
|
+
if !config["ratingsInclude"].nil?
|
37
|
+
@ratingsInclude = config["ratingsInclude"]
|
38
|
+
end
|
39
|
+
|
40
|
+
@territoriesInclude = []
|
41
|
+
if !config["territoriesInclude"].nil?
|
42
|
+
@territoriesInclude = config["territoriesInclude"]
|
43
|
+
end
|
44
|
+
|
45
|
+
puts "[AsanaProcessor] Init Success."
|
46
|
+
end
|
47
|
+
|
48
|
+
def processReviews(reviews, platform)
|
49
|
+
if reviews.length < 1
|
50
|
+
return reviews
|
51
|
+
end
|
52
|
+
|
53
|
+
sectionID = findSectionIDFromSectionName(sectionName)
|
54
|
+
|
55
|
+
filterReviews = reviews
|
56
|
+
|
57
|
+
if ratingsInclude.length > 0
|
58
|
+
filterReviews = filterReviews.select{ |review| ratingsInclude.map{ |rating| rating.to_i }.include? review.rating }
|
59
|
+
end
|
60
|
+
|
61
|
+
if territoriesInclude.length > 0
|
62
|
+
filterReviews = filterReviews.select{ |review| territoriesInclude.map{ |territory| territory.upcase }.include? review.territory.upcase }
|
63
|
+
end
|
64
|
+
|
65
|
+
if keywordsInclude.length > 0
|
66
|
+
keywordsInclude.select{ |keywordsInclude| keywordsInclude != "" }.each do |keywordInclude|
|
67
|
+
filterReviews = filterReviews.select{ |review| review.body.include? keywordInclude }
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
filterReviews.each do |review|
|
72
|
+
title = renderReview(taskTitleTemplate, review, timeZoneOffset)
|
73
|
+
body = renderReview(taskBodyTemplate, review, timeZoneOffset)
|
74
|
+
|
75
|
+
requestTaskData = {
|
76
|
+
"name": title,
|
77
|
+
"notes": body,
|
78
|
+
"projects": [projectID.to_s]
|
79
|
+
}
|
80
|
+
|
81
|
+
if !review.createdDateTimestamp.nil?
|
82
|
+
requestTaskData['due_at'] = Time.at(review.createdDateTimestamp).iso8601
|
83
|
+
end
|
84
|
+
|
85
|
+
taskData = asanaAPI("/tasks", "POST", requestTaskData)
|
86
|
+
taskData = taskData["data"]
|
87
|
+
|
88
|
+
if !sectionID.nil? && !taskData.nil?
|
89
|
+
asanaAPI("/sections/#{sectionID}/addTask", "POST", {"task": taskData["gid"]})
|
90
|
+
end
|
91
|
+
|
92
|
+
puts "[AsanaProcessor] Insert Review #{title} as a task to asana project."
|
93
|
+
end
|
94
|
+
|
95
|
+
return reviews
|
96
|
+
end
|
97
|
+
|
98
|
+
private
|
99
|
+
def findSectionIDFromSectionName(sectionName)
|
100
|
+
if !sectionName.nil?
|
101
|
+
sections = asanaAPI("/projects/#{projectID}/sections")["data"]
|
102
|
+
section = sections.find{ |section| section["name"].strip == sectionName }
|
103
|
+
if !section.nil?
|
104
|
+
return section["gid"]
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
return nil
|
109
|
+
end
|
110
|
+
|
111
|
+
private
|
112
|
+
def asanaAPI(path, method = "GET", data = nil)
|
113
|
+
uri = URI(asanaAPIURL+path)
|
114
|
+
https = Net::HTTP.new(uri.host, uri.port)
|
115
|
+
https.use_ssl = true
|
116
|
+
|
117
|
+
request = Net::HTTP::Get.new(uri)
|
118
|
+
if method.upcase == "POST"
|
119
|
+
request = Net::HTTP::Post.new(uri)
|
120
|
+
if !data.nil?
|
121
|
+
request['Content-Type'] = 'application/json'
|
122
|
+
request.body = JSON.dump({"data": data})
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
request['Authorization'] = "Bearer #{token}";
|
127
|
+
|
128
|
+
response = https.request(request).read_body
|
129
|
+
result = JSON.parse(response)
|
130
|
+
end
|
131
|
+
end
|
@@ -8,7 +8,7 @@ require "GoogleAPI"
|
|
8
8
|
|
9
9
|
class GoogleSheetProcessor < Processor
|
10
10
|
|
11
|
-
attr_accessor :keywordsInclude, :ratingsInclude, :territoriesInclude, :logger, :googleAPI, :
|
11
|
+
attr_accessor :keywordsInclude, :ratingsInclude, :territoriesInclude, :logger, :googleAPI, :spreadsheetID, :sheetInsertType, :sheetId, :sheetName, :sheetInsertAt, :formatValues, :timeZoneOffset
|
12
12
|
|
13
13
|
def initialize(config, configFilePath, baseExecutePath)
|
14
14
|
@config = config
|
@@ -41,8 +41,31 @@ class GoogleSheetProcessor < Processor
|
|
41
41
|
end
|
42
42
|
|
43
43
|
@timeZoneOffset = Helper.unwrapRequiredParameter(config, "googleSheetTimeZoneOffset")
|
44
|
-
@
|
45
|
-
|
44
|
+
@spreadsheetID = Helper.unwrapRequiredParameter(config, "googleSpreadsheetID")
|
45
|
+
|
46
|
+
sheetInsertStyle = Helper.unwrapRequiredParameter(config, "googleSheetInsertStyle")
|
47
|
+
if !sheetInsertStyle.is_a? Array
|
48
|
+
raise "googleSheetInsertStyle must specify as Array in GoogleSheetProcessor."
|
49
|
+
end
|
50
|
+
|
51
|
+
sheetInsertStyles = {}
|
52
|
+
sheetInsertStyle.each do |value|
|
53
|
+
value.keys.each do |key|
|
54
|
+
sheetInsertStyles[key] = value[key]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
@sheetInsertType = Helper.unwrapRequiredParameter(sheetInsertStyles, "type")
|
59
|
+
|
60
|
+
if sheetInsertType != "insert" && sheetInsertType != "append"
|
61
|
+
raise "googleSheetInsertStyle.type only accept insert or append in GoogleSheetProcessor."
|
62
|
+
elsif sheetInsertType == "insert"
|
63
|
+
@sheetInsertAt = Helper.unwrapRequiredParameter(sheetInsertStyles, "at").to_i
|
64
|
+
@sheetId = Helper.unwrapRequiredParameter(sheetInsertStyles, "sheetID")
|
65
|
+
elsif sheetInsertType == "append"
|
66
|
+
@sheetName = Helper.unwrapRequiredParameter(sheetInsertStyles, "sheetName")
|
67
|
+
end
|
68
|
+
|
46
69
|
@formatValues = []
|
47
70
|
if !config["values"].nil?
|
48
71
|
@formatValues = config["values"]
|
@@ -74,33 +97,68 @@ class GoogleSheetProcessor < Processor
|
|
74
97
|
end
|
75
98
|
|
76
99
|
values = []
|
77
|
-
|
100
|
+
sortedFilterReviews = filterReviews
|
101
|
+
if sheetInsertType == "insert"
|
102
|
+
sortedFilterReviews = sortedFilterReviews.sort! { |a, b| b.createdDateTimestamp <=> a.createdDateTimestamp }
|
103
|
+
end
|
104
|
+
|
105
|
+
sortedFilterReviews.each do |review|
|
78
106
|
cols = []
|
79
107
|
formatValues.each do |formatValue|
|
80
|
-
formatValue = formatValue
|
81
|
-
formatValue = formatValue.gsub("%BODY%", review.body || "")
|
82
|
-
formatValue = formatValue.gsub("%RATING%", review.rating.nil? ? "" :review.rating.to_s)
|
83
|
-
formatValue = formatValue.gsub("%PLATFORM%", review.platform || "")
|
84
|
-
formatValue = formatValue.gsub("%ID%", review.id || "")
|
85
|
-
formatValue = formatValue.gsub("%USERNAME%", review.userName || "")
|
86
|
-
formatValue = formatValue.gsub("%URL%", review.url || "")
|
87
|
-
formatValue = formatValue.gsub("%TERRITORY%", review.territory || "")
|
88
|
-
formatValue = formatValue.gsub("%APPVERSION%", review.appVersion || "")
|
89
|
-
formatValue = formatValue.gsub("%CREATEDDATE%", review.createdDateTimestamp.nil? ? "" : Time.at(review.createdDateTimestamp).getlocal(timeZoneOffset).to_s)
|
90
|
-
|
108
|
+
formatValue = renderReview(formatValue, review, timeZoneOffset)
|
91
109
|
cols.append(formatValue)
|
92
110
|
end
|
93
111
|
values.append(cols)
|
94
112
|
end
|
95
113
|
|
96
114
|
page = 1
|
97
|
-
limit =
|
115
|
+
limit = 100
|
98
116
|
values.each_slice(limit) do |value|
|
99
|
-
puts "[GoogleSheetProcessor] Insert rows(#{page}/#{(values.length/limit).ceil + 1}) to #{
|
117
|
+
puts "[GoogleSheetProcessor] Insert rows page:(#{page}/#{(values.length/limit).ceil + 1}) #{sheetInsertType} to #{spreadsheetID}"
|
100
118
|
page += 1
|
101
|
-
|
119
|
+
if sheetInsertType == "insert"
|
120
|
+
googleAPI.request("https://sheets.googleapis.com/v4/spreadsheets/#{spreadsheetID}:batchUpdate", "POST", {
|
121
|
+
"requests": [
|
122
|
+
{
|
123
|
+
"insertRange": {
|
124
|
+
"range": {
|
125
|
+
"sheetId": sheetId,
|
126
|
+
"startRowIndex": sheetInsertAt,
|
127
|
+
"endRowIndex": sheetInsertAt + value.length
|
128
|
+
},
|
129
|
+
"shiftDimension": "ROWS"
|
130
|
+
}
|
131
|
+
},
|
132
|
+
{
|
133
|
+
"pasteData": {
|
134
|
+
"data": rowsToString(value),
|
135
|
+
"type": "PASTE_NORMAL",
|
136
|
+
"delimiter": ",",
|
137
|
+
"coordinate": {
|
138
|
+
"sheetId": sheetId,
|
139
|
+
"rowIndex": sheetInsertAt,
|
140
|
+
}
|
141
|
+
}
|
142
|
+
}
|
143
|
+
]
|
144
|
+
})
|
145
|
+
elsif sheetInsertType == "append"
|
146
|
+
googleAPI.request("https://sheets.googleapis.com/v4/spreadsheets/#{spreadsheetID}/values/#{sheetName}!A1:append?valueInputOption=RAW", "POST", {:values => value})
|
147
|
+
end
|
102
148
|
end
|
103
149
|
|
104
150
|
return reviews
|
105
151
|
end
|
152
|
+
|
153
|
+
private
|
154
|
+
def rowsToString(rows)
|
155
|
+
string = ""
|
156
|
+
rows.each do |row|
|
157
|
+
if string != ""
|
158
|
+
string += "\n"
|
159
|
+
end
|
160
|
+
string += "\"#{row.map{ |v| v.gsub('"','\"') }.join('","')}\""
|
161
|
+
end
|
162
|
+
return string
|
163
|
+
end
|
106
164
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ZReviewTender
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.3.
|
4
|
+
version: 1.3.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- ZhgChgLi
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-12-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: net-http
|
@@ -59,6 +59,7 @@ files:
|
|
59
59
|
- lib/Models/Review.rb
|
60
60
|
- lib/Models/ReviewFetcher.rb
|
61
61
|
- lib/Models/Version.rb
|
62
|
+
- lib/Processors/AsanaProcessor.rb
|
62
63
|
- lib/Processors/FilterProcessor.rb
|
63
64
|
- lib/Processors/GoogleSheetProcessor.rb
|
64
65
|
- lib/Processors/GoogleTranslateProcessor.rb
|