Làm Thế Nào Để Đếm Được Line of Code của 10 000 Pull Requests ?
Bài đăng này đã không được cập nhật trong 3 năm
Chào các bạn ,
Sau chuỗi series về lập trình Ruby on Rails dài dòng của mình không biết có bạn nào đã follow được hết từ phần một đến phần phần 8 không nhỉ, không biết trình của các chú về lập trình Ruby on Rails bây giờ đến đâu rồi, đã có thể hiểu và tự build được các dự án của mình chưa :v.
Nếu có ai đó đã follow theo series của mình và đã đạt được thành quả nhất định, thì comment bên dưới cho mình biết phát nhé =]].
Chém gió như thế đủ rồi nhỉ =]], hôm nay mình sẽ đổi không khí chút không chơi với Ruby on Rails khô khan nữa, mà sẽ chuyển qua dùng Ruby để giải quyết vấn đề nhỏ phát sinh trong dự án của mình, đúng như tiêu đề : "Làm Thế Nào Để Đếm Được Line of Code của 10 000 Pull Requests ?"
1. Vấn đề đặt ra
-
Nếu project của bạn là một project public thì việc đếm số line of code sẽ trở nên rất đơn giản, khi thằng Github đã cung cấp sắn cho chúng ta API để có thể thực hiện này một cách dễ dàng. Các bạn có thể tìm hiểu thêm ở đây : https://developer.github.com/v3/repos/statistics/
-
Tuy nhiên nếu project của bạn là một project private thì nó lại khác, lần trước mình có tìm hiểu để xem có thể dùng API statistics của Github không, thì tìm một hồi không thấy ra kết quả đâu (sad4), và lúc đấy dự án đang cần gấp nên nghĩ sang phương án khác, vì tịt với API statistics của Github với project private rồi
Thế là nghĩ sang phương án cứ ở mỗi cái Pull Request gửi lên Github lúc view detail, các bạn để ý nó sẽ show ra số Line of Addition và Line of Deletion như ở bên dưới:
Ví dụ đối với PR của gem devise này : https://github.com/plataformatec/devise/pull/4737/files
Ta có:
- Line of Addition: +3
- Line of Deletion: -1
OK như thế là Github đã giúp ta thống kê được số line of code của từng PR rồi, giờ việc của chúng ta là đếm số line of code của 10 000 Pull Request Thật ra con số 10 000 PR chỉ là ví dụ thôi mình ghi cho nó hấp dẫn, chứ thực tế các bạn sẽ phải đếm số PR theo một điều kiện nào đó, ví dụ như: trong một tháng, trong một năm, trong khoảng thời gian X -> Y,... chắc cũng không tới nổi 10k đâu =]]
Vậy chúng ta sẽ phải ngồi mở từng PR ra và đếm từng dòng, note lại số liệu sau đó ngồi cộng chúng lại à ? -> Câu trả lời là không. Đối với công việc có tính lặp đi lặp lại và nhàm chán thế này, thì chắc các bạn sẽ nghĩ tới cái tool tự động nào đấy sẽ giúp chúng ta làm việc này ? Chính xác rồi đấy, chẳng có ai rỗi hơi ngồi đếm cái đống trên cả.
Tool tự động để đếm cái này không biết là có không mình chưa search thử nhưng chắc là không có vì yêu cầu của việc này nó cụ thể qúa, nên thôi chắc tự viết lấy một cái
2. Công cụ support mình nghĩ tới là : Selenium
Một thư viện hỗ trợ rất phổ biến trong việc test tự động các Web Application. Nó hỗ trợ hầu hết các ngôn ngữ phổ biến nhất hiện nay: Java, C#, Ruby, Python, Javascript(Nodejs)
Hay quá có hỗ trợ cho Ruby luôn, vậy dùng luôn Ruby để viết. Quá tiện nhỉ (lol)
OK, vậy chung ta đã có yêu cầu và thư viện hỗ trợ, bắt tay vào làm nhỉ.
3. Tiến hành
3.1 Cài đặt Selenium
3.1.1 Cài đặt gem selenium-webdriver
Đơn giản như cài đặt các gem khác, các bạn chỉ cần chạy lệnh bên dưới
gem install selenium-webdriver
Để thằng selenium-webdriver của chúng ta có thể giao tiếp được với các browser các bạn cần cài đặt Third Party Browser Drivers, tùy vài Browser mà các bạn dự định sẽ dùng mà chúng ta sẽ download các Browser Drivers cho phù hợp.
Để download các bạn vào trang chủ của nó:
http://www.seleniumhq.org/download/
Phần Third Party Drivers, Bindings, and Plugins chọn phần Browser Drivers muốn download.
Trong bài viết này mình sẽ sử dụng FireFox, do đó mình sẽ download thằng
Mozilla GeckoDriver
Click chọn version: 0.19.0
để download cho phiên bản Ubuntu, nếu bạn nào dùng Windows
hoặc OS khác thì cần download phiên bản tương ứng.
Ở đây mình sẽ chọn down phiên bản: geckodriver-v0.19.1-linux64.tar.gz
cho Ubuntu
Sau khi download xong các bạn cần giải nén nó ra và sẽ được file geckodriver
Để thằng geckodriver
có thể chạy được các bạn cần move nó vào thư mục /usr/local/bin
sudo mv geckodriver /usr/local/bin
3.1.2 Thư viện Selenium cho Ruby
Tất tần tật về Example và API doc của selenium-webdriver
ở đây:
https://github.com/SeleniumHQ/selenium/wiki/Ruby-Bindings
Các bạn cần method hay example gì thì vào đây để search nhé, bên dưới là mình sử dụng lại các method mà nó đã cung cấp ở trên thôi.
Ngoài ra mình còn tìm thêm được 1 trang các example về các method rất rõ ràng ở đây: https://gist.github.com/huangzhichong/3284966#file-selenium-webdriver-cheatsheet-md
Các bạn có thể tham khảo
3.2 Login với Github
Đầu tiên ta cần tạo một class để chạy code. Ở đây mình sẽ đặt tên là SeleniumTest
Ta sẽ có file khởi tạo như sau:
Khởi tạo để kết nối với selenium-webdriver:
attr_reader :driver
def initialize
@driver = Selenium::WebDriver.for :firefox
@driver.navigate.to "https://github.com/login"
end
Như này đã, giờ chạy thử xem nó làm được gì nào:
ruby selenium_test.rb
nó sẽ tự động mở FireFox cho chúng ta và truy cập vào trang https://github.com/login Bước đầu có vẻ ngon lành nhỉ
Login function
Vì là đối với các Project Private chúng ta cần phải đăng nhập vào Github rồi mới truy cập vào được. Nê tiếp theo chúng ta sẽ viết lệnh login vào Github tự động nhé :v
Chúng ta đã đến được trang login của Github https://github.com/login Giờ để login thì cần thằng SeleniumTest của chúng ta thực hiện các bước bên dưới
B1: điền email address
và password
vào hai ô tương ứng
B2: nhấn button Sign in
Để tương tác được với các item trên Web browser đầu tiên chúng ta cần lấy được điều khiển của chúng
hay nôm na là chúng ta cần điều khiển được item đó bằng lệnh.
Selenium-webdriver cung cấp cho chúng ta method find_element
để làm việc này.
Chi tiết các bạn có thể xem thêm ở 3.1.2 Thư viện Selenium cho Ruby ở trên
http://seleniumhq.github.io/selenium/docs/api/rb/Selenium/WebDriver/SearchContext.html
Ở đây vì là form submit nên mình sẽ dùng find_element name:
Cách để tìm thuộc tính name
của item rất đơn giản. Với FireFox chỉ cần đưa chuột lên đối tượng cần xem, click phải chọn Inspect Element(Q)
nó sẽ hiện ra thông tin thẻ HTML của đối tượng đấy, và các bạn chỉ cần quan tâm đến thuộc tính name="xxx"
là đủ
đấy chính là giá trị của attribute name là chúng ta cần tìm.
Đối với element email address
của trang https://github.com/login thì nó
có giá trị name="login"
Tương tự với các thằng text field Password
và button Sign in
private
def login_github
email = driver.find_element name: "login"
email.send_keys "<your email>"
password = driver.find_element name: "password"
password.send_keys "<your password>"
submit = driver.find_element name: "commit"
submit.click
end
Ta sẽ có function login_github
như này
cho vào hàm execute rồi chạy thử lại phát nào, nhớ là điền thông tin để đăng nhập vào nhé
def execute
login_github
end
Sau khi chạy nó sẽ login luôn vào cho chúng ta và được như này
3.3 List các Pull Request
Bây giờ chúng ta sẽ list tất cả các Pull Request mà cần đếm line of code ra. Trong bài hướng dẫn này mình sẽ lấy tạm Repository của thằng Devise để list Pull Request nhé, ta cứ xem nó như là project private đi =]]
link repo gem devise: https://github.com/plataformatec/devise
Điều kiện để list của chúng ta là: sẽ phải list tất cả PR trên devise
- được tạo từ 01/01/2017 -> 31/12/2017
- đã được merge
- không chứa từ "Merge" hoặc "Release" trong title
Ta sẽ có query trên github như này:
is:pr is:merged NOT Merge in:title OR NOT Release in:title created:2017-01-01..2017-12-31
Để tìm hiểu về cách viết query nâng cao trên Github: các bạn có thể tham khảo thêm ở đây https://help.github.com/articles/searching-issues-and-pull-requests/
Mình nghĩ đây cũng là một kỹ năng nên có, khi muốn query một vài cái phức tạp =]]
OK bỏ query vào test thử thì ta thấy thằng devise
có tổng cộng 34 cái PR, ít hơn con số 10k nhiều lần nhỉ =]],
mà thôi vì đã chạy Automation nên con số không quan trọng lắm đâu (yaoming)
Giờ chúng ta sẽ code để con SeleniumTest của chúng ta filter được ra như trên:
attr_reader :driver, :repository_path, :pull_requests
def initialize
@driver = Selenium::WebDriver.for :firefox
@driver.navigate.to "https://github.com/login"
@repository_path = "https://github.com/plataformatec/devise/pulls"
@pull_requests = []
end
Hàm filters_pull_request_by_query
: filter pull request theo query của chúng ta ở trên
Hàm get_list_pull_requests
: get list pull request từ query ra được
def get_list_pull_requests repository_path
driver.get repository_path
filters_by_months = [
"is:pr is:merged NOT Merge in:title OR NOT Release in:title created:2017-01-01..2017-12-31"]
filters_by_months.each do |month|
filters_pull_request_by_query month
sleep 5
button_next = driver.find_element class: "next_page"
while button_next
link_pull_requests = driver.find_elements :xpath, "//a[@class='link-gray-dark no-underline h4 js-navigation-open']"
puts link_pull_requests.size
link_pull_requests.each do |link_pull_request|
pull_requests << link_pull_request.attribute("href")
puts link_pull_request.attribute("href")
end
break if button_next.tag_name == "span"
button_next.click
sleep 2
button_next = driver.find_element class: "next_page"
end
end
puts "\n"
puts pull_requests
puts pull_requests.size
end
def filters_pull_request_by_query query
filter_element = driver.find_element id: "js-issues-search"
filter_element.clear
filter_element.send_keys query
filter_element.submit
end
def execute
login_github
get_list_pull_requests repository_path
end
Kết quả chúng ta sẽ được như này
3.4 Đếm line of code Pull Request
Như vậy là chúng ta đã có được list Pull Request cần đếm line of code, giờ chỉ việc code
để con SeleniumTest
của chúng ta count được là ok
attr_reader :driver, :repository_path, :pull_requests, :wait, :data_export_to_csv
def initialize
...
@wait = Selenium::WebDriver::Wait.new timeout: 10
@data_export_to_csv = []
end
def count_total_additional
total_additional = 0
total_deletion = 0
pull_requests.each do |pull_request|
driver.get "#{pull_request}/files"
wait.until {driver.find_element :xpath, '//*[@id="files_bucket"]/div[3]/div/span/span[1]'}
additional_element = driver.find_element :xpath, '//*[@id="files_bucket"]/div[3]/div/span/span[1]'
deletion_element = driver.find_element :xpath, '//*[@id="files_bucket"]/div[3]/div/span/span[2]'
additional = additional_element.text.gsub(",", "").to_i
deletion = deletion_element.text.gsub("−", "-").to_i
data_export_to_csv << [pull_request, additional, deletion]
total_additional += additional
total_deletion += deletion
puts "#{pull_request} Line code +: #{additional} Line code -: #{deletion}"
end
puts "total_additional : #{total_additional}"
puts "total_deletion : #{total_deletion}"
end
def execute
login_github
get_list_pull_requests repository_path
count_total_additional
end
Ta sẽ được kết quả như này Có vẻ là ok rồi nhỉ, tuy nhiên dữ liệu mà nằm trên terminal thì nhìn nó chuối quá, tính toán lại mất công, ta cho nó ra file csv cho nó chuyên nghiệp :v
3.5 Xuất file CSV
def execute
login_github
get_list_pull_requests repository_path
count_total_additional
export_data_to_csv "/home/framgia/Documents/Framgia/tai_lieu/Viblo/result/result.csv"
end
def export_data_to_csv file_name
CSV.open(file_name, "wb") do |csv|
csv << csv_header
data_export_to_csv.each do |data|
csv << data
end
end
puts "export csv success !"
end
chạy lại phát và xem kết quả nào :v
Sau khi nó chạy xong thì ta có file như này, có vẻ đáp ứng được yêu cầu đưa ra rồi, giờ muốn nó chạy bao nhiêu PR thì chỉ việc nhấn lệnh và ngồi chơi xơi nước thôi :v
Full source code: https://github.com/duc11t3bk/tool_get_pull_request
Hôm nay đến đây thôi, chào các bạn và hẹn gặp vào kì tới :v
Nguồn:
All rights reserved