Commit 3f67f34d by Tấn Trần Thanh

done for search month, year, team and export data to csv

parent 0dc84db9
Pipeline #1566 canceled with stages
in 0 seconds
require 'csv'
class WorkflowReportController < ApplicationController class WorkflowReportController < ApplicationController
def index def index
@thead = ['root_id', 'project', 'subject', 'target_version', ' created_on ', ' closed_on ', ' due_date ', ' status ', ' Estimated_hours (*final version) ', 'Actual time', 'Diff', 'Estimated_hours (*Initial version)', @team_options = $workflow_report_config['teams'].map { |team| team.keys() }
end
def export
thead = ['root_id', 'project', 'subject', 'target_version', ' created_on ', ' closed_on ', ' due_date ', ' status ', ' Estimated_hours (*final version) ', 'Actual time', 'Diff', 'Estimated_hours (*Initial version)',
'Number of estimation changes', 'Note of estimation changes', '1. Requirement', '2. Design', '3. Coding', '4. Testing', '5. Bug fixing', '6. Release', 'Others', '1. Requirement', '2. Design', '3. Coding', 'Number of estimation changes', 'Note of estimation changes', '1. Requirement', '2. Design', '3. Coding', '4. Testing', '5. Bug fixing', '6. Release', 'Others', '1. Requirement', '2. Design', '3. Coding',
'4. Testing', '5. Bug fixing', '6. Release', 'Others', 'testcases', 'vn STG bug', 'Jp STG bug', 'Production', 'Issue', 'Issue comment', 'PR comment', 'Review comment', 'Commits', 'File changed', 'Addtion', 'Deletetion'] '4. Testing', '5. Bug fixing', '6. Release', 'Others', 'testcases', 'vn STG bug', 'Jp STG bug', 'Production', 'Issue', 'Issue comment', 'PR comment', 'Review comment', 'Commits', 'File changed', 'Addtion', 'Deletetion']
github = Github.new oauth_token: "your github token" team = params[:team]
kuruma_project_ids = [139, 138, 94, 90, 93, 120, 128, 147, 121, 116] project_ids = $workflow_report_config['teams'].select { |hash| hash.key?(team) }[0][team]
@result = Array.new(@thead.length) result = fetch_data(project_ids, params[:year].to_i, params[:month].to_i, column_number: thead.size)
respond_to do |format|
format.html do
render partial: 'table_workflow_report', layout: false, locals: { result: result, thead: thead }
end
end
end
private
def fetch_data(project_ids, year, month, column_number: 0)
github = Github.new oauth_token: $workflow_report_config['github_token']
result = Array.new(column_number)
root_ids = Issue.select('issues.root_id') root_ids = Issue.select('issues.root_id')
.joins('INNER JOIN projects ON projects.id = issues.project_id') .joins('INNER JOIN projects ON projects.id = issues.project_id')
.joins('LEFT OUTER JOIN time_entries ON issues.id = time_entries.issue_id') .joins('LEFT OUTER JOIN time_entries ON issues.id = time_entries.issue_id')
.joins('INNER JOIN enabled_modules ON enabled_modules.project_id = projects.id') .joins('INNER JOIN enabled_modules ON enabled_modules.project_id = projects.id')
.where('projects.status <> 9') .where('projects.status <> 9')
.where("projects.id IN (#{kuruma_project_ids.join(',')})") .where("projects.id IN (#{project_ids.join(',')})")
.where(['enabled_modules.name = ? ', 'time_tracking']) .where(['enabled_modules.name = ? ', 'time_tracking'])
.where([ .where([
'((time_entries.spent_on IS NOT NULL AND time_entries.tyear = ? AND time_entries.tmonth = ?) OR (issues.closed_on BETWEEN ? AND ?))', '((time_entries.spent_on IS NOT NULL AND time_entries.tyear = ? AND time_entries.tmonth = ?) OR (issues.closed_on BETWEEN ? AND ?))',
2023, year,
3, month,
DateTime.new(2023, 3).beginning_of_day, DateTime.new(year, month).beginning_of_day,
DateTime.new(2023, 3, -1).end_of_day]) DateTime.new(year, month, -1).end_of_day])
.distinct.pluck(:root_id) .distinct.pluck(:root_id)
return unless root_ids.length.positive? return unless root_ids.length.positive?
...@@ -42,7 +61,7 @@ class WorkflowReportController < ApplicationController ...@@ -42,7 +61,7 @@ class WorkflowReportController < ApplicationController
.where("issues.root_id IN (#{root_ids.join(',')})") .where("issues.root_id IN (#{root_ids.join(',')})")
.where('issues.tracker_id <> 1') .where('issues.tracker_id <> 1')
.order('issues.root_id') .order('issues.root_id')
@result.map! { |item| item = [] } result.map! { |item| item.to_a }
raw_tasks_records.group_by(&:root_id).each do |root_id, record| raw_tasks_records.group_by(&:root_id).each do |root_id, record|
sum_hours_record = sum_hours_records.find { |hr| hr.root_id == root_id } sum_hours_record = sum_hours_records.find { |hr| hr.root_id == root_id }
...@@ -51,41 +70,42 @@ class WorkflowReportController < ApplicationController ...@@ -51,41 +70,42 @@ class WorkflowReportController < ApplicationController
INNER JOIN journal_details ON journals.id = journal_id WHERE journalized_id IN (#{issue_ids.join(',')}) INNER JOIN journal_details ON journals.id = journal_id WHERE journalized_id IN (#{issue_ids.join(',')})
AND prop_key = 'estimated_hours' ORDER BY journalized_id, journals.id").to_a AND prop_key = 'estimated_hours' ORDER BY journalized_id, journals.id").to_a
@result[0] << root_id result[0] << root_id
@result[1] << record.first[:project].gsub(/[^[:print:]]/, '') result[1] << record.first[:project].gsub(/[^[:print:]]/, '')
@result[2] << record.first[:subject].gsub(/[^[:print:]]/, '') result[2] << record.first[:subject].gsub(/[^[:print:]]/, '')
@result[3] << record.first[:target_version] result[3] << record.first[:target_version]
issue_created_on = record.min_by { |i| i[:created_on] if i[:created_on].present? } issue_created_on = record.min_by { |i| i[:created_on] if i[:created_on].present? }
@result[4].push(issue_created_on.present? ? issue_created_on[:created_on]&.strftime('%Y-%m-%d %H:%M:%S') : '') result[4].push(issue_created_on.present? ? issue_created_on[:created_on]&.strftime('%Y-%m-%d %H:%M:%S') : '')
issue_closed_on = record.max_by { |i| i[:closed_on] if i[:closed_on].present? } closed_on_issues = record.map(&:closed_on).compact
@result[5].push(issue_closed_on.present? ? issue_closed_on[:closed_on]&.strftime('%Y-%m-%d %H:%M:%S') : '') issue_closed_on = closed_on_issues.max_by { |close_on| close_on }
result[5].push(issue_closed_on.present? ? issue_closed_on.strftime('%Y-%m-%d %H:%M:%S') : '')
issue_due_dates = record.map(&:due_date).compact issue_due_dates = record.map(&:due_date).compact
issue_due_date = issue_due_dates.max_by { |a| a } issue_due_date = issue_due_dates.max_by { |due_date| due_date }
@result[6].push(issue_due_date.present? ? issue_due_date&.strftime('%Y-%m-%d %H:%M:%S') : '') result[6].push(issue_due_date.present? ? issue_due_date&.strftime('%Y-%m-%d %H:%M:%S') : '')
@result[7].push(record.first[:status].present? ? record.first[:status] : '') result[7].push(record.first[:status].present? ? record.first[:status] : '')
sum_estimated_hours = record.map { |i| i[:estimated_hours] }.compact.sum sum_estimated_hours = record.map { |i| i[:estimated_hours] }.compact.sum
@result[8] << sum_estimated_hours result[8] << sum_estimated_hours
if sum_hours_record.present? if sum_hours_record.present?
@result[9] << sum_hours_record[:hours] result[9] << sum_hours_record[:hours]
@result[10] << (sum_hours_record[:hours] - sum_estimated_hours) result[10] << (sum_hours_record[:hours] - sum_estimated_hours)
else else
@result[9] << '' result[9] << ''
@result[10] << '' result[10] << ''
end end
if journals.length.positive? if journals.length.positive?
number_of_est_changes = journals.count { |j| !j[1].nil? } || '' number_of_est_changes = journals.count { |j| !j[1].nil? } || ''
est_changes = journals.reject { |j| j[1].nil? }.select { |e| e[4].present? } est_changes = journals.reject { |j| j[1].nil? }.select { |e| e[4].present? }
notes = est_changes.inject('') { |all, i| "#{all}#{i[4]}\n" } || '' notes = est_changes.inject('') { |all, i| "#{all}#{i[4]}\n" } || ''
original_est_hours = journals.group_by(&:shift).inject(0) { |sum, i| sum + i[1][0][1].to_f unless i.nil? } || '' original_est_hours = journals.group_by(&:shift).inject(0) { |sum, i| sum + i[1][0][1].to_f unless i.nil? } || ''
@result[11] << original_est_hours result[11] << original_est_hours
@result[12] << number_of_est_changes result[12] << number_of_est_changes
@result[13] << notes result[13] << notes
else else
@result[11] << '' result[11] << ''
@result[12] << '' result[12] << ''
@result[13] << '' result[13] << ''
end end
pull_request = '' pull_request = ''
jp_request = '' jp_request = ''
...@@ -100,58 +120,58 @@ class WorkflowReportController < ApplicationController ...@@ -100,58 +120,58 @@ class WorkflowReportController < ApplicationController
case process case process
when '1. Requirement' when '1. Requirement'
@result[index_1].push(issue.estimated_hours.present? ? issue.estimated_hours : '') result[index_1].push(issue.estimated_hours.present? ? issue.estimated_hours : '')
@result[index_2].push(issue.spent_hours.positive? ? issue.spent_hours : '') result[index_2].push(issue.spent_hours.positive? ? issue.spent_hours : '')
when '2. Design' when '2. Design'
@result[index_1 + 1].push(issue.estimated_hours.present? ? issue.estimated_hours : '') result[index_1 + 1].push(issue.estimated_hours.present? ? issue.estimated_hours : '')
@result[index_2 + 1].push(issue.spent_hours.positive? ? issue.spent_hours : '') result[index_2 + 1].push(issue.spent_hours.positive? ? issue.spent_hours : '')
when '3. Coding' when '3. Coding'
@result[index_1 + 2].push(issue.estimated_hours.present? ? issue.estimated_hours : '') result[index_1 + 2].push(issue.estimated_hours.present? ? issue.estimated_hours : '')
@result[index_2 + 2].push(issue.spent_hours.positive? ? issue.spent_hours : '') result[index_2 + 2].push(issue.spent_hours.positive? ? issue.spent_hours : '')
when '4. Testing' when '4. Testing'
@result[index_1 + 3].push(issue.estimated_hours.present? ? issue.estimated_hours : '') result[index_1 + 3].push(issue.estimated_hours.present? ? issue.estimated_hours : '')
@result[index_2 + 3].push(issue.spent_hours.positive? ? issue.spent_hours : '') result[index_2 + 3].push(issue.spent_hours.positive? ? issue.spent_hours : '')
when '5. Bug fixing' when '5. Bug fixing'
@result[index_1 + 4].push(issue.estimated_hours.present? ? issue.estimated_hours : '') result[index_1 + 4].push(issue.estimated_hours.present? ? issue.estimated_hours : '')
@result[index_2 + 4].push(issue.spent_hours.positive? ? issue.spent_hours : '') result[index_2 + 4].push(issue.spent_hours.positive? ? issue.spent_hours : '')
when '6. Release' when '6. Release'
@result[index_1 + 5].push(issue.estimated_hours.present? ? issue.estimated_hours : '') result[index_1 + 5].push(issue.estimated_hours.present? ? issue.estimated_hours : '')
@result[index_2 + 5].push(issue.spent_hours.positive? ? issue.spent_hours : '') result[index_2 + 5].push(issue.spent_hours.positive? ? issue.spent_hours : '')
else else
@result[index_1 + 6].push(issue.estimated_hours.present? ? issue.estimated_hours : '') result[index_1 + 6].push(issue.estimated_hours.present? ? issue.estimated_hours : '')
@result[index_2 + 6].push(issue.spent_hours.positive? ? issue.spent_hours : '') result[index_2 + 6].push(issue.spent_hours.positive? ? issue.spent_hours : '')
end end
end end
if sum_hours_record.nil? if sum_hours_record.nil?
# testcases # testcases
@result[28] << '' result[28] << ''
# internal bug # internal bug
@result[29] << '' result[29] << ''
# stg bug # stg bug
@result[30] << '' result[30] << ''
# prod bug # prod bug
@result[31] << '' result[31] << ''
else else
# testcases # testcases
@result[28] << sum_hours_record[:testcases] if sum_hours_record[:testcases].to_i >= 0 result[28] << sum_hours_record[:testcases] if sum_hours_record[:testcases].to_i >= 0
# internal bug # internal bug
@result[29] << sum_hours_record[:bugs] if sum_hours_record[:bugs].to_i >= 0 result[29] << sum_hours_record[:bugs] if sum_hours_record[:bugs].to_i >= 0
# stg bug # stg bug
@result[30] << sum_hours_record[:stg_bugs] if sum_hours_record[:stg_bugs].to_i >= 0 result[30] << sum_hours_record[:stg_bugs] if sum_hours_record[:stg_bugs].to_i >= 0
# prod bug # prod bug
@result[31] << sum_hours_record[:prod_bugs] if sum_hours_record[:prod_bugs].to_i >= 0 result[31] << sum_hours_record[:prod_bugs] if sum_hours_record[:prod_bugs].to_i >= 0
end end
if jp_request.present? if jp_request.present?
jp_request = jp_request.strip jp_request = jp_request.strip
@result[32] << jp_request result[32] << jp_request
jp_request_arr = URI(jp_request).path.split('/').reject(&:blank?) jp_request_arr = URI(jp_request).path.split('/').reject(&:blank?)
if jp_request_arr.length == 4 && jp_request_arr[3].to_i.positive? if jp_request_arr.length == 4 && jp_request_arr[3].to_i.positive?
issue_detail = github.issues.find user: jp_request_arr[0], repo: jp_request_arr[1], number: jp_request_arr[3] issue_detail = github.issues.find user: jp_request_arr[0], repo: jp_request_arr[1], number: jp_request_arr[3]
@result[33].push(issue_detail.success? ? issue_detail.comments : '') result[33].push(issue_detail.success? ? issue_detail.comments : '')
end end
else else
@result[32] << '' result[32] << ''
@result[33] << '' result[33] << ''
end end
# pr detail # pr detail
next unless pull_request.present? next unless pull_request.present?
...@@ -177,17 +197,17 @@ class WorkflowReportController < ApplicationController ...@@ -177,17 +197,17 @@ class WorkflowReportController < ApplicationController
pr_changed_files += pr_detail.changed_files pr_changed_files += pr_detail.changed_files
end end
@result[34] << pr_comments result[34] << pr_comments
@result[35] << pr_review_comments result[35] << pr_review_comments
@result[36] << pr_commits result[36] << pr_commits
@result[37] << pr_changed_files result[37] << pr_changed_files
@result[38] << pr_additions result[38] << pr_additions
@result[39] << pr_deletions result[39] << pr_deletions
end
end end
end end
private result
end
def get_process(subject) def get_process(subject)
name = '' name = ''
......
- if result[0].size > 0
table[border="1"]
tr.header_table--visible
th.green[colspan="8"] Task information
th.blue[colspan="6"] Overall
th.purple[colspan="7"] Estimation detail *final version
th.gray[colspan="7"] Actual time detail
th.yellow[colspan="4"] Bugs
th.pink[colspan="8"] Github
tr.header_table--visible
- thead.each_with_index do |head, index|
- if (0..7).include?(index)
th.green = head
- elsif (8..10).include?(index)
th.red = head
- elsif (11..13).include?(index)
th.blue = head
- elsif (14..20).include?(index)
th.purple = head
- elsif (21..27).include?(index)
th.gray = head
- elsif (28..31).include?(index)
th.yellow = head
- else
th.pink = head
- result[0].each_with_index do |_root_id, index|
tr
- (0..(thead.length - 1)).each do |i|
- if i == 0
td.sidebar_visible = result[0][index]
- else
td = result[i][index]
- else
h1 There is no data for the 'workflow report' table.
\ No newline at end of file
= stylesheet_link_tag 'style', plugin: 'workflow_report' = stylesheet_link_tag 'style', plugin: 'workflow_report'
= javascript_include_tag(:application, :plugin => 'workflow_report')
.table-container fieldset.box.tabular
table[border="1"] legend
tr | EXPORT WORKFLOW
- @thead.each do |head| = form_tag do
th = head = select_tag :month ,options_for_select(1..12, Time.now.month),{ prompt: "Select month" }
- @result[0].each_with_index do |_root_id, index| = select_tag :year ,options_for_select((Time.now.year - 5)..(Time.now.year + 1), Time.now.year),{ prompt: "Select year" }
tr = select_tag :team, options_for_select(@team_options), { prompt: "Select team" }
- (0..(@thead.length - 1)).each do |i| = submit_tag 'export', id: 'export'
td = @result[i][index] button.export-button Download .CSV
#data_workflow
window.addEventListener('load', function () {
$('#export').click(function (event) {
event.preventDefault()
const month = $('#month').val()
const year = $('#year').val()
const team = $('#team').val()
if (year === '' || month === '' || team === '') {
alert('Please complete all the necessary fields.')
} else {
$.ajax({
url: 'workflow_report/export',
type: 'GET',
data: {month: month, year: year, team: team},
success: function (response) {
$('#data_workflow').html(response)
},
error: function (error) {
console.log(error)
},
})
}
})
const download = function (data) {
const blob = new Blob([data], {type: 'text/csv'});
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.setAttribute('href', url)
a.setAttribute('download', "download.csv");
a.click()
}
$(".export-button").on("click", function () {
exportToCSV();
});
function exportToCSV() {
const table = $("table");
const rows = table.find("tr");
let csv = [];
rows.each(function (index) {
const cols = $(this).find("td, th");
let rowText = [];
let currentColspan = 1;
cols.each(function () {
const text_report = $(this).text();
if ($(this).attr("colspan")) {
currentColspan = parseInt($(this).attr("colspan"), 10);
}
if (currentColspan > 1) {
for (let i = 0; i < currentColspan; i++) {
i === 0 ? rowText.push(text_report) : rowText.push('')
}
} else {
text_report.includes(',') ? rowText.push(`"${text_report}"`) : rowText.push(text_report);
}
currentColspan = Math.max(1, currentColspan - 1);
});
csv.push(rowText.join(","));
});
const csvContent = csv.join("\n");
download(csvContent);
}
});
.table-container { #data_workflow {
overflow-x: auto; overflow: auto;
height: 100vh;
} }
table { table {
display: inline-block;
table-layout: fixed; table-layout: fixed;
border-collapse: collapse; border-collapse: collapse;
} }
.header_table--visible {
position: sticky;
top: 0;
z-index: 2;
}
.sidebar_visible {
position: sticky;
background: #dacbcb;
left: 0;
z-index: 1;
}
.green {
background-color: #92ce92;
}
.red {
background-color: #e38585;
}
.blue {
background-color: #b2b2f0;
}
.purple {
background-color: #f7adf7;
}
.gray {
background-color: #e6e4e4;
}
.yellow {
background-color: #eaeab0;
}
.pink {
background-color: pink;
}
#date_from, #date_to, #team, #export {
margin-right: 24px;
}
\ No newline at end of file
...@@ -2,3 +2,5 @@ ...@@ -2,3 +2,5 @@
# See: http://guides.rubyonrails.org/routing.html # See: http://guides.rubyonrails.org/routing.html
get 'workflow_report', to: 'workflow_report#index' get 'workflow_report', to: 'workflow_report#index'
get 'workflow_report/export', to: 'workflow_report#export'
get '/download_csv', to: 'workflow_report#export_csv'
github_token: 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
teams:
- Kyujin:
- 9
- 142
- 88
- Sumai:
- 17
- 29
- 31
- 131
- 140
- Kuruma:
- 139
- 138
- 94
- 90
- 93
- 120
- 128
- 147
- 121
- 116
- LS:
- 134
- PS:
- 145
- 141
- 144
- 151
- Sanko:
- 91
- 149
- CORDA:
- 92
- 112
- APW-Travelist-Air:
- 133
- APW-Travelist-Hotel:
- 148
- APW-Voyager:
- 146
- APW-SUB:
- 137
...@@ -11,5 +11,7 @@ Redmine::Plugin.register :workflow_report do ...@@ -11,5 +11,7 @@ Redmine::Plugin.register :workflow_report do
version '0.0.1' version '0.0.1'
url 'http://example.com/path/to/plugin' url 'http://example.com/path/to/plugin'
author_url 'http://example.com/about' author_url 'http://example.com/about'
configfile = File.join(File.dirname(__FILE__), 'config', 'settings.yml')
$workflow_report_config = YAML::load_file(configfile)
menu :top_menu, :workflow_report, { controller: 'workflow_report', action: 'index' }, caption: 'Workflow Report' menu :top_menu, :workflow_report, { controller: 'workflow_report', action: 'index' }, caption: 'Workflow Report'
end end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment