Merge branch 'feature/readd-bounty-#185' of github.com:status-im/open-bounty into feature/readd-bounty-#185

This commit is contained in:
Vitaliy Vlasov 2018-03-20 14:33:48 +02:00
commit a94d67c4a6
No known key found for this signature in database
GPG Key ID: A7D57C347F2B2964
19 changed files with 281 additions and 249 deletions

View File

@ -5,12 +5,13 @@
:exclusions [joda-time]] :exclusions [joda-time]]
[re-frame "0.10.2"] [re-frame "0.10.2"]
[cljs-ajax "0.7.2"] [cljs-ajax "0.7.2"]
[secretary "1.2.3"] [funcool/bide "1.6.0"]
[reagent-utils "0.2.1"] [reagent-utils "0.2.1"]
[reagent "0.7.0"] [reagent "0.7.0"]
[org.clojure/clojurescript "1.9.946"] [org.clojure/clojurescript "1.9.946"]
[org.clojure/clojure "1.8.0"] [org.clojure/clojure "1.8.0"]
[selmer "1.11.1"] [selmer "1.11.1"]
[com.taoensso/tufte "1.3.0"]
[markdown-clj "1.0.1"] [markdown-clj "1.0.1"]
[ring-middleware-format "0.7.2"] [ring-middleware-format "0.7.2"]
[ring/ring-core "1.6.2"] [ring/ring-core "1.6.2"]

View File

@ -103,19 +103,16 @@ WHERE i.repo_id = :repo_id
AND i.confirm_hash is null AND i.confirm_hash is null
AND i.is_open = true; AND i.is_open = true;
-- :name update-repo-generic :! :n -- :name update-repo-name :! :n
/* :require [clojure.string :as string]
[hugsql.parameters :refer [identifier-param-quote]] */
UPDATE repositories UPDATE repositories
SET SET repo = :repo_name
/*~ WHERE repo_id = :repo_id
(string/join "," AND repo != :repo_name
(for [[field _] (:updates params)]
(str (identifier-param-quote (name field) options)
" = :v:updates." (name field))))
~*/
where repo_id = :repo_id;
-- :name update-repo-state :! :n
UPDATE repositories
SET state = :repo_state
WHERE repo_id = :repo_id
-- Issues -------------------------------------------------------------------------- -- Issues --------------------------------------------------------------------------
@ -430,6 +427,14 @@ SELECT exists(SELECT 1
FROM issues FROM issues
WHERE issue_id = :issue_id); WHERE issue_id = :issue_id);
-- :name get-issue :? :1
-- :doc get issue from DB by repo-id and issue-number
SELECT issue_id, issue_number, is_open, winner_login, commit_sha
FROM issues
WHERE repo_id = :repo_id
AND issue_number = :issue_number;
-- :name open-bounties :? :* -- :name open-bounties :? :*
-- :doc all open bounty issues -- :doc all open bounty issues

View File

@ -65,6 +65,7 @@ height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
src="https://www.facebook.com/tr?id=293089407869419&ev=PageView&noscript=1" src="https://www.facebook.com/tr?id=293089407869419&ev=PageView&noscript=1"
/></noscript> /></noscript>
<link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.1.8/semantic.min.css" rel="stylesheet" type="text/css" /> <link href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/2.1.8/semantic.min.css" rel="stylesheet" type="text/css" />
<link href="https://unpkg.com/tachyons@4.9.1/css/tachyons.min.css" rel="stylesheet" type="text/css" />
<link href="/css/style.css?v={{commiteth-version}}" rel="stylesheet" type="text/css" /> <link href="/css/style.css?v={{commiteth-version}}" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="/js/app.js?v={{commiteth-version}}"></script> <script type="text/javascript" src="/js/app.js?v={{commiteth-version}}"></script>
<script> <script>

View File

@ -110,7 +110,14 @@
:exists :exists
boolean))) boolean)))
(defn contract-from-pool (defn contract-from-pool
[owner-address] [owner-address]
(jdbc/with-db-connection [con-db *db*] (jdbc/with-db-connection [con-db *db*]
(db/contract-from-pool con-db {:address owner-address}))) (db/contract-from-pool con-db {:address owner-address})))
(defn get-issue
[repo-id issue-number]
(jdbc/with-db-connection [con-db *db*]
(db/get-issue con-db {:repo_id repo-id
:issue_number issue-number})))

View File

@ -25,13 +25,17 @@
(jdbc/with-db-connection [con-db *db*] (jdbc/with-db-connection [con-db *db*]
(db/get-enabled-repositories con-db {:user_id user-id})))) (db/get-enabled-repositories con-db {:user_id user-id}))))
(defn update-repo (defn update-repo-name
[repo-id updates] [repo-id repo-name]
(jdbc/with-db-connection [con-db *db*] (jdbc/with-db-connection [con-db *db*]
(db/update-repo-generic con-db {:repo_id repo-id (db/update-repo-name con-db {:repo_id repo-id
:updates updates}))) :repo_name repo-name})))
(defn update-repo-state
[repo-id repo-state]
(jdbc/with-db-connection [con-db *db*]
(db/update-repo-name con-db {:repo_id repo-id
:repo_state repo-state})))
(defn get-repo (defn get-repo
"Get a repo from DB given it's full name (owner/repo-name)" "Get a repo from DB given it's full name (owner/repo-name)"
[full-name] [full-name]

View File

@ -4,8 +4,10 @@
[clojure.java.io :as io] [clojure.java.io :as io]
[commiteth.config :refer [env]] [commiteth.config :refer [env]]
[clojure.string :refer [join]] [clojure.string :refer [join]]
[taoensso.tufte :as tufte :refer (defnp p profiled profile)]
[clojure.tools.logging :as log] [clojure.tools.logging :as log]
[clojure.string :as str] [clojure.string :as str]
[mount.core :as mount]
[pandect.core :as pandect] [pandect.core :as pandect]
[commiteth.util.util :refer [json-api-request]]) [commiteth.util.util :refer [json-api-request]])
(:import [org.web3j (:import [org.web3j
@ -26,24 +28,30 @@
(defn auto-gas-price? [] (env :auto-gas-price false)) (defn auto-gas-price? [] (env :auto-gas-price false))
(defn offline-signing? [] (env :offline-signing true)) (defn offline-signing? [] (env :offline-signing true))
(def web3j-obj (atom nil))
(def creds-obj (atom nil))
(defn wallet-file-path [] (defn wallet-file-path []
(env :eth-wallet-file)) (env :eth-wallet-file))
(defn wallet-password [] (defn wallet-password []
(env :eth-password)) (env :eth-password))
(defn create-web3j []
(or @web3j-obj
(swap! web3j-obj (constantly (Web3j/build (HttpService. (eth-rpc-url)))))))
(defn creds [] (defn creds []
(or @creds-obj
(let [password (wallet-password) (let [password (wallet-password)
file-path (wallet-file-path)] file-path (wallet-file-path)]
(if (and password file-path) (if (and password file-path)
(WalletUtils/loadCredentials (swap! creds-obj
(constantly (WalletUtils/loadCredentials
password password
file-path) file-path)))
(throw (ex-info "Make sure you provided proper credentials in appropriate resources/config.edn" (throw (ex-info "Make sure you provided proper credentials in appropriate resources/config.edn"
{:password password :file-path file-path}))))) {:password password :file-path file-path}))))))
(defn create-web3j []
(Web3j/build (HttpService. (eth-rpc-url))))
(defn get-signed-tx [gas-price gas-limit to data] (defn get-signed-tx [gas-price gas-limit to data]
"Create a sign a raw transaction. "Create a sign a raw transaction.
@ -192,7 +200,8 @@
(defn get-balance-eth (defn get-balance-eth
[account digits] [account digits]
(hex->eth (get-balance-hex account) digits)) (p :get-balance-eth
(hex->eth (get-balance-hex account) digits)))
(defn- format-param (defn- format-param
[param] [param]
@ -296,3 +305,15 @@
(filter true?) (filter true?)
(empty?))) (empty?)))
true)))) true))))
(mount/defstate
eth-core
:start
(do
(swap! web3j-obj (constantly nil))
(swap! creds-obj (constantly nil))
(log/info "eth/core started"))
:stop
(log/info "eth/core stopped"))

View File

@ -3,6 +3,7 @@
:refer [create-web3j creds]] :refer [create-web3j creds]]
[commiteth.config :refer [env]] [commiteth.config :refer [env]]
[clojure.tools.logging :as log] [clojure.tools.logging :as log]
[taoensso.tufte :as tufte :refer (defnp p profiled profile)]
[commiteth.eth.token-data :as token-data]) [commiteth.eth.token-data :as token-data])
(:import [org.web3j (:import [org.web3j
abi.datatypes.Address abi.datatypes.Address
@ -147,6 +148,7 @@
"Query (internal) ERC20 token balance from bounty contract for given "Query (internal) ERC20 token balance from bounty contract for given
token TLA." token TLA."
[bounty-addr token] [bounty-addr token]
(p :token-balance-in-bounty
(let [bounty-contract (load-bounty-contract bounty-addr) (let [bounty-contract (load-bounty-contract bounty-addr)
token-address (get-token-address token) token-address (get-token-address token)
token-addr-web3j (Address. token-address)] token-addr-web3j (Address. token-address)]
@ -154,12 +156,13 @@
(.tokenBalances token-addr-web3j) (.tokenBalances token-addr-web3j)
.get .get
.getValue .getValue
(convert-token-value token)))) (convert-token-value token)))))
(defn token-balance (defn token-balance
"Query balance of given ERC20 token TLA for given address from ERC20 "Query balance of given ERC20 token TLA for given address from ERC20
contract." contract."
[bounty-addr token] [bounty-addr token]
(p :token-balance
(let [token-address (get-token-address token)] (let [token-address (get-token-address token)]
(log/debug "token-balance" bounty-addr token token-address) (log/debug "token-balance" bounty-addr token token-address)
(try (try
@ -170,25 +173,26 @@
(convert-token-value token)) (convert-token-value token))
(catch Throwable t (catch Throwable t
(log/debug "Failed to query token balance " t) (log/debug "Failed to query token balance " t)
0)))) 0)))))
(defn token-balances (defn token-balances
"Get a given bounty contract's token balances. Assumes contract's "Get a given bounty contract's token balances. Assumes contract's
internal balances have been updated." internal balances have been updated."
[bounty-addr] [bounty-addr]
(let [bounty-contract (load-bounty-contract bounty-addr) (p :token-balances
token-addresses (-> bounty-contract (let [bounty-contract (p :load-bounty-contract (load-bounty-contract bounty-addr))
token-addresses (p :getTokenList (-> bounty-contract
(.getTokenList) (.getTokenList)
.get)] .get))]
(if token-addresses (if token-addresses
(let [addrs (map str (let [addrs (map str
(.getValue token-addresses))] (p :getValue (.getValue token-addresses)))]
(into {} (into {}
(map (fn [addr] (if-let [info (token-data/token-info-by-addr addr)] (map (fn [addr] (if-let [info (token-data/token-info-by-addr addr)]
(let [tla (first info)] (let [tla (first info)]
[tla (token-balance bounty-addr tla)]))) addrs))) [tla (token-balance bounty-addr tla)]))) addrs)))
{}))) {}))))
(defn uint256 [x] (defn uint256 [x]
(org.web3j.abi.datatypes.generated.Uint256. x)) (org.web3j.abi.datatypes.generated.Uint256. x))

View File

@ -46,67 +46,6 @@
(log/debug "token" token "member?" member?) (log/debug "token" token "member?" member?)
member?)) member?))
(defn enable-repo [repo-id repo full-repo token]
(log/debug "enable-repo" repo-id repo)
(when (github/webhook-exists? full-repo token)
(github/remove-our-webhooks full-repo token))
(let [hook-secret (random/base64 32)]
(repositories/update-repo repo-id {:state 1
:hook_secret hook-secret})
(let [created-hook (github/add-webhook full-repo token hook-secret)]
(log/debug "Created webhook:" created-hook)
(repositories/update-repo repo-id {:hook_id (:id created-hook)})))
(github/create-label full-repo token)
(repositories/update-repo repo-id {:state 2})
(when (add-bounties-for-existing-issues?)
(bounties/add-bounties-for-existing-issues full-repo)))
(defn disable-repo [repo-id full-repo hook-id token]
(log/debug "disable-repo" repo-id full-repo)
(github/remove-webhook full-repo hook-id token)
(repositories/update-repo repo-id {:hook_secret ""
:state 0
:hook_id nil}))
;; NOTE(oskarth): This and above two functions about to be deprecated with Github App
(defn handle-toggle-repo [user params can-create?]
(log/info "XXX handle-toggle-repo" (pr-str user) (pr-str params))
(let [{user-id :id} user
{repo-id :id
full-repo :full_name
owner-avatar-url :owner-avatar-url
token :token
repo :name} params
[owner _] (str/split full-repo #"/")
db-user (users/get-user (:id user))]
(cond (not can-create?)
{:status 400
:body "Please join our Riot - chat.status.im/#/register and request
access in our #openbounty room to have your account whitelisted"}
(empty? (:address db-user))
{:status 400
:body "Please add your ethereum address to your profile first"}
:else
(try
(let [_ (println "CREATING")
db-item (repositories/create (merge params {:user_id user-id
:owner owner}))
is-enabled (= 2 (:state db-item))]
(if is-enabled
(disable-repo repo-id full-repo (:hook_id db-item) token)
(enable-repo repo-id repo full-repo token))
(ok (merge
{:enabled (not is-enabled)}
(select-keys params [:id :full_name]))))
(catch Exception e
(log/error "exception when enabling repo" e)
(repositories/update-repo repo-id {:state -1})
(internal-server-error))))))
(defn in? [coll elem] (defn in? [coll elem]
(some #(= elem %) coll)) (some #(= elem %) coll))
@ -286,9 +225,4 @@
:auth-rules authenticated? :auth-rules authenticated?
:current-user user :current-user user
(log/debug "/user/bounties") (log/debug "/user/bounties")
(ok (user-bounties user))) (ok (user-bounties user))))))
(POST "/repository/toggle" {:keys [params]}
;; NOTE: Don't allow anyone to create repos; manual add
:auth-rules authenticated?
:current-user user
(handle-toggle-repo user params (user-whitelisted? (:login user)))))))

View File

@ -104,26 +104,17 @@
(extract pr-title)))) (extract pr-title))))
(defn ensure-bounty-issue
"Checks if an issue has a bounty label attached and returns its number"
[user repo issue-number]
(when-let [issue (github/get-issue user repo issue-number)]
(when (bounties/has-bounty-label? issue)
issue-number)))
(defn handle-claim (defn handle-claim
[user-id login name avatar_url owner repo repo-id bounty-issue-number pr-id pr-number head-sha merged? event-type] [issue user-id login name avatar_url owner repo repo-id pr-id pr-number head-sha merged? event-type]
(users/create-user user-id login name nil avatar_url) (users/create-user user-id login name nil avatar_url)
(let [issue (github/get-issue owner repo bounty-issue-number) (let [open-or-edit? (contains? #{:opened :edited} event-type)
open-or-edit? (contains? #{:opened :edited} event-type)
close? (= :closed event-type) close? (= :closed event-type)
pr-data {:repo_id repo-id pr-data {:repo_id repo-id
:pr_id pr-id :pr_id pr-id
:pr_number pr-number :pr_number pr-number
:user_id user-id :user_id user-id
:issue_number bounty-issue-number :issue_number (:issue_number issue)
:issue_id (:id issue) :issue_id (:issue_id issue)
:state event-type}] :state event-type}]
;; TODO: in the opened case if the submitting user has no ;; TODO: in the opened case if the submitting user has no
@ -133,18 +124,18 @@
(cond (cond
open-or-edit? (do open-or-edit? (do
(log/info "PR with reference to bounty issue" (log/info "PR with reference to bounty issue"
bounty-issue-number "opened") (:issue_number issue) "opened")
(pull-requests/save (merge pr-data {:state :opened (pull-requests/save (merge pr-data {:state :opened
:commit_sha head-sha}))) :commit_sha head-sha})))
close? (if merged? close? (if merged?
(do (log/info "PR with reference to bounty issue" (do (log/info "PR with reference to bounty issue"
bounty-issue-number "merged") (:issue_number issue) "merged")
(pull-requests/save (pull-requests/save
(merge pr-data {:state :merged (merge pr-data {:state :merged
:commit_sha head-sha})) :commit_sha head-sha}))
(issues/update-commit-sha (:id issue) head-sha)) (issues/update-commit-sha (:issue_id issue) head-sha))
(do (log/info "PR with reference to bounty issue" (do (log/info "PR with reference to bounty issue"
bounty-issue-number "closed with no merge") (:issue_number issue) "closed with no merge")
(pull-requests/save (pull-requests/save
(merge pr-data {:state :closed (merge pr-data {:state :closed
:commit_sha head-sha}))))))) :commit_sha head-sha})))))))
@ -172,26 +163,27 @@
pr-number :number pr-number :number
pr-body :body pr-body :body
pr-title :title} :pull_request}] pr-title :title} :pull_request}]
(log/debug "handle-pull-request-event" event-type owner repo repo-id login pr-body pr-title) (log/info "handle-pull-request-event" event-type owner repo repo-id login pr-body pr-title)
(log/debug (extract-issue-number pr-body pr-title)) (if-let [issue (some->> (extract-issue-number pr-body pr-title)
(if-let [bounty-issue-number (->>
(extract-issue-number pr-body pr-title)
(first) (first)
(ensure-bounty-issue owner repo))] (issues/get-issue repo-id))]
(if-not (:commit_sha issue) ; no PR has been merged yet referencing this issue
(do (do
(log/debug "Referenced bounty issue found" owner repo bounty-issue-number) (log/info "Referenced bounty issue found" owner repo (:issue_number issue))
(handle-claim user-id (handle-claim issue
user-id
login name login name
avatar_url avatar_url
owner repo owner repo
repo-id repo-id
bounty-issue-number
pr-id pr-id
pr-number pr-number
head-sha head-sha
merged? merged?
event-type)) event-type))
(log/info "PR for issue already merged"))
(when (= :edited event-type) (when (= :edited event-type)
; Remove PR if it does not reference any issue
(pull-requests/remove pr-id)))) (pull-requests/remove pr-id))))
@ -202,9 +194,15 @@
new-title (:title gh-issue)] new-title (:title gh-issue)]
(issues/update-issue-title issue-id new-title))) (issues/update-issue-title issue-id new-title)))
(defn update-repo-name [webhook-payload]
"Update repo name in DB if changed"
(let [{repo-id :id
repo-name :name} (:repository webhook-payload)]
(repositories/update-repo-name repo-id repo-name)))
(defn handle-issue (defn handle-issue
[webhook-payload] [webhook-payload]
(update-repo-name webhook-payload)
(when-let [action (:action webhook-payload)] (when-let [action (:action webhook-payload)]
(log/debug "handle-issue" action) (log/debug "handle-issue" action)
(case action (case action
@ -222,17 +220,17 @@
(log/debug "Not handling action '" action "'")) (log/debug "Not handling action '" action "'"))
(ok))) (ok)))
(defn enable-repo-2 [repo-id full-repo] (defn enable-repo [repo-id full-repo]
(log/debug "enable-repo-2" repo-id full-repo) (log/debug "enable-repo" repo-id full-repo)
;; TODO(oskarth): Add granular permissions to enable creation of label ;; TODO(oskarth): Add granular permissions to enable creation of label
#_(github/create-label full-repo) #_(github/create-label full-repo)
(repositories/update-repo repo-id {:state 2}) (repositories/update-repo-state repo-id 2)
(when (add-bounties-for-existing-issues?) (when (add-bounties-for-existing-issues?)
(bounties/add-bounties-for-existing-issues full-repo))) (bounties/add-bounties-for-existing-issues full-repo)))
(defn disable-repo-2 [repo-id full-repo] (defn disable-repo [repo-id full-repo]
(log/debug "disable-repo-2" repo-id full-repo) (log/debug "disable-repo" repo-id full-repo)
(repositories/update-repo repo-id {:state 0})) (repositories/update-repo-state repo-id 0))
(defn full-repo->owner [full-repo] (defn full-repo->owner [full-repo]
(try (try
@ -242,8 +240,6 @@
(log/error "exception when parsing repo" e) (log/error "exception when parsing repo" e)
nil))) nil)))
;; NOTE(oskarth): Together with {enable,disable}-repo-2 above, this replaces
;; handle-toggle-repo for Github App.
(defn handle-add-repo [user-id username owner-avatar-url repo can-create?] (defn handle-add-repo [user-id username owner-avatar-url repo can-create?]
(let [repo-id (:id repo) (let [repo-id (:id repo)
repo-name (:name repo) repo-name (:name repo)
@ -283,14 +279,14 @@
_ (log/info "handle-add-repo db-item" db-item) _ (log/info "handle-add-repo db-item" db-item)
is-enabled (= 2 (:state db-item))] is-enabled (= 2 (:state db-item))]
(if is-enabled (if is-enabled
(disable-repo-2 repo-id full-repo) (disable-repo repo-id full-repo)
(enable-repo-2 repo-id full-repo)) (enable-repo repo-id full-repo))
(ok {:enabled (not is-enabled) (ok {:enabled (not is-enabled)
:id repo-id :id repo-id
:full_name full-repo})) :full_name full-repo}))
(catch Exception e (catch Exception e
(log/error "exception when enabling repo" e) (log/error "exception when enabling repo" e)
(repositories/update-repo repo-id {:state -1}) (repositories/update-repo-state repo-id -1)
(internal-server-error)))))) (internal-server-error))))))
(defn handle-installation [{:keys [action installation repositories sender]}] (defn handle-installation [{:keys [action installation repositories sender]}]
@ -333,12 +329,13 @@
(ok)) (ok))
(defn handle-pull-request (defn handle-pull-request
[pull-request] [webhook-payload]
(let [action (keyword (:action pull-request))] (update-repo-name webhook-payload)
(let [action (keyword (:action webhook-payload))]
(when (contains? #{:opened (when (contains? #{:opened
:edited :edited
:closed} action) :closed} action)
(handle-pull-request-event action pull-request)) (handle-pull-request-event action webhook-payload))
(ok))) (ok)))
@ -391,14 +388,5 @@
"pull_request" (handle-pull-request payload) "pull_request" (handle-pull-request payload)
"installation" (handle-installation payload) "installation" (handle-installation payload)
"installation_repositories" (handle-installation-repositories payload) "installation_repositories" (handle-installation-repositories payload)
;; NOTE(oskarth): These two webhooks are / will be deprecated on
;; November 22, 2017 but they keep being called. According to
;; documentation they should contain same format.
;; https://developer.github.com/webhooks/
"integration_installation" (handle-installation payload)
"integration_installation_repositories" (handle-installation-repositories payload)
(ok))) (ok)))
(forbidden))))) (forbidden)))))

View File

@ -4,6 +4,7 @@
[commiteth.eth.token-data :as token-data] [commiteth.eth.token-data :as token-data]
[commiteth.github.core :as github] [commiteth.github.core :as github]
[commiteth.db.issues :as issues] [commiteth.db.issues :as issues]
[taoensso.tufte :as tufte :refer (defnp p profiled profile)]
[commiteth.db.bounties :as db-bounties] [commiteth.db.bounties :as db-bounties]
[commiteth.bounties :as bounties] [commiteth.bounties :as bounties]
[commiteth.util.crypto-fiat-value :as fiat-util] [commiteth.util.crypto-fiat-value :as fiat-util]
@ -15,12 +16,34 @@
[clj-time.periodic :refer [periodic-seq]] [clj-time.periodic :refer [periodic-seq]]
[chime :refer [chime-at]])) [chime :refer [chime-at]]))
(tufte/add-basic-println-handler! {})
(tufte/add-handler! :file (fn [{stats :stats}]
(log/info "Profiling stats:" stats)))
(comment
(profile {} (update-issue-contract-address))
(profile {} (deploy-pending-contracts))
(profile {} (self-sign-bounty))
(profile {} (update-confirm-hash))
(profile {} (update-watch-hash))
(profile {} (update-payout-receipt))
(profile {} (update-contract-internal-balances))
(profile {} (update-open-issue-usd-values))
(profile {} (update-balances))
(profile {}
(doseq [i (range 5)]
(update-contract-internal-balances)
(update-open-issue-usd-values)
(update-balances)))
)
(defn update-issue-contract-address (defn update-issue-contract-address
"For each pending deployment: gets transaction receipt, updates db "For each pending deployment: gets transaction receipt, updates db
state (contract-address, comment-id) and posts github comment" state (contract-address, comment-id) and posts github comment"
[] []
(log/info "In update-issue-contract-address") (log/info "In update-issue-contract-address")
(p :update-issue-contract-address
(doseq [{issue-id :issue_id (doseq [{issue-id :issue_id
transaction-hash :transaction_hash} (issues/list-pending-deployments)] transaction-hash :transaction_hash} (issues/list-pending-deployments)]
(log/info "pending deployment:" transaction-hash) (log/info "pending deployment:" transaction-hash)
@ -57,7 +80,7 @@
(log/error "Failed to find contract address in tx logs"))) (log/error "Failed to find contract address in tx logs")))
(catch Throwable ex (catch Throwable ex
(do (log/error "update-issue-contract-address exception:" ex) (do (log/error "update-issue-contract-address exception:" ex)
(clojure.stacktrace/print-stack-trace ex))))) (clojure.stacktrace/print-stack-trace ex))))))
(log/info "Exit update-issue-contract-address")) (log/info "Exit update-issue-contract-address"))
@ -84,15 +107,17 @@
(defn deploy-pending-contracts (defn deploy-pending-contracts
"Under high-concurrency circumstances or in case geth is in defunct state, a bounty contract may not deploy successfully when the bounty label is addded to an issue. This function deploys such contracts." "Under high-concurrency circumstances or in case geth is in defunct state, a bounty contract may not deploy successfully when the bounty label is addded to an issue. This function deploys such contracts."
[] []
(p :deploy-pending-contracts
(doseq [{issue-id :issue_id (doseq [{issue-id :issue_id
owner-address :owner_address} (db-bounties/pending-contracts)] owner-address :owner_address} (db-bounties/pending-contracts)]
(log/debug "Trying to re-deploy failed bounty contract deployment, issue-id:" issue-id) (log/debug "Trying to re-deploy failed bounty contract deployment, issue-id:" issue-id)
(deploy-contract owner-address issue-id))) (deploy-contract owner-address issue-id))))
(defn self-sign-bounty (defn self-sign-bounty
"Walks through all issues eligible for bounty payout and signs corresponding transaction" "Walks through all issues eligible for bounty payout and signs corresponding transaction"
[] []
(log/info "In self-sign-bounty") (log/info "In self-sign-bounty")
(p :self-sign-bounty
(doseq [{contract-address :contract_address (doseq [{contract-address :contract_address
issue-id :issue_id issue-id :issue_id
payout-address :payout_address payout-address :payout_address
@ -130,7 +155,7 @@
false)))) false))))
(catch Throwable ex (catch Throwable ex
(do (log/error "self-sign-bounty exception:" ex) (do (log/error "self-sign-bounty exception:" ex)
(clojure.stacktrace/print-stack-trace ex))))) (clojure.stacktrace/print-stack-trace ex))))))
(log/info "Exit self-sign-bounty") (log/info "Exit self-sign-bounty")
) )
@ -138,6 +163,7 @@
"Gets transaction receipt for each pending payout and updates DB confirm_hash with tranaction ID of commiteth bot account's confirmation." "Gets transaction receipt for each pending payout and updates DB confirm_hash with tranaction ID of commiteth bot account's confirmation."
[] []
(log/info "In update-confirm-hash") (log/info "In update-confirm-hash")
(p :update-confirm-hash
(doseq [{issue-id :issue_id (doseq [{issue-id :issue_id
execute-hash :execute_hash} (db-bounties/pending-payouts)] execute-hash :execute_hash} (db-bounties/pending-payouts)]
(log/info "pending payout:" execute-hash) (log/info "pending payout:" execute-hash)
@ -145,18 +171,19 @@
(log/info "execution receipt for issue #" issue-id ": " receipt) (log/info "execution receipt for issue #" issue-id ": " receipt)
(when-let [confirm-hash (multisig/find-confirmation-tx-id receipt)] (when-let [confirm-hash (multisig/find-confirmation-tx-id receipt)]
(log/info "confirm hash:" confirm-hash) (log/info "confirm hash:" confirm-hash)
(db-bounties/update-confirm-hash issue-id confirm-hash)))) (db-bounties/update-confirm-hash issue-id confirm-hash)))))
(log/info "Exit update-confirm-hash")) (log/info "Exit update-confirm-hash"))
(defn update-watch-hash (defn update-watch-hash
"Sets watch-hash to NULL for bounties where watch tx has been mined. Used to avoid unneeded watch transactions in update-bounty-token-balances" "Sets watch-hash to NULL for bounties where watch tx has been mined. Used to avoid unneeded watch transactions in update-bounty-token-balances"
[] []
(p :update-watch-hash
(doseq [{issue-id :issue_id (doseq [{issue-id :issue_id
watch-hash :watch_hash} (db-bounties/pending-watch-calls)] watch-hash :watch_hash} (db-bounties/pending-watch-calls)]
(log/info "pending watch call" watch-hash) (log/info "pending watch call" watch-hash)
(when-let [receipt (eth/get-transaction-receipt watch-hash)] (when-let [receipt (eth/get-transaction-receipt watch-hash)]
(db-bounties/update-watch-hash issue-id nil)))) (db-bounties/update-watch-hash issue-id nil)))))
(defn older-than-3h? (defn older-than-3h?
@ -171,6 +198,7 @@
"Gets transaction receipt for each confirmed payout and updates payout_hash" "Gets transaction receipt for each confirmed payout and updates payout_hash"
[] []
(log/info "In update-payout-receipt") (log/info "In update-payout-receipt")
(p :update-payout-receipt
(doseq [{issue-id :issue_id (doseq [{issue-id :issue_id
payout-hash :payout_hash payout-hash :payout_hash
contract-address :contract_address contract-address :contract_address
@ -213,7 +241,7 @@
(db-bounties/reset-payout-hash issue-id))) (db-bounties/reset-payout-hash issue-id)))
(catch Throwable ex (catch Throwable ex
(do (log/error "update-payout-receipt exception:" ex) (do (log/error "update-payout-receipt exception:" ex)
(clojure.stacktrace/print-stack-trace ex))))) (clojure.stacktrace/print-stack-trace ex))))))
(log/info "Exit update-payout-receipt") (log/info "Exit update-payout-receipt")
) )
@ -252,12 +280,14 @@
(defn update-contract-internal-balances (defn update-contract-internal-balances
"It is required in our current smart contract to manually update it's internal balance when some tokens have been added." "It is required in our current smart contract to manually update it's internal balance when some tokens have been added."
[] []
(log/info "In update-contract-internal-balances")
(p :update-contract-internal-balances
(doseq [{issue-id :issue_id (doseq [{issue-id :issue_id
bounty-address :contract_address bounty-address :contract_address
watch-hash :watch_hash} watch-hash :watch_hash}
(db-bounties/open-bounty-contracts)] (db-bounties/open-bounty-contracts)]
(update-bounty-token-balances issue-id bounty-address watch-hash))) (update-bounty-token-balances issue-id bounty-address watch-hash)))
(log/info "Exit update-contract-internal-balances"))
(defn get-bounty-funds (defn get-bounty-funds
"Get funds in given bounty contract. "Get funds in given bounty contract.
@ -281,9 +311,10 @@
(defn update-open-issue-usd-values (defn update-open-issue-usd-values
"Sum up current USD values of all crypto assets in a bounty and store to DB" "Sum up current USD values of all crypto assets in a bounty and store to DB"
[] []
(p :update-open-issue-usd-values
(doseq [{bounty-addr :contract_address} (doseq [{bounty-addr :contract_address}
(db-bounties/open-bounty-contracts)] (db-bounties/open-bounty-contracts)]
(update-issue-usd-value bounty-addr))) (update-issue-usd-value bounty-addr))))
(defn float= (defn float=
([x y] (float= x y 0.0000001)) ([x y] (float= x y 0.0000001))
@ -299,6 +330,7 @@
(defn update-balances (defn update-balances
[] []
(log/info "In update-balances") (log/info "In update-balances")
(p :update-balances
(doseq [{contract-address :contract_address (doseq [{contract-address :contract_address
owner :owner owner :owner
repo :repo repo :repo
@ -347,7 +379,7 @@
(update-issue-usd-value contract-address)))) (update-issue-usd-value contract-address))))
(catch Throwable ex (catch Throwable ex
(do (log/error "update-balances exception:" ex) (do (log/error "update-balances exception:" ex)
(clojure.stacktrace/print-stack-trace ex))))) (clojure.stacktrace/print-stack-trace ex))))))
(log/info "Exit update-balances")) (log/info "Exit update-balances"))
@ -375,17 +407,18 @@
update-payout-receipt update-payout-receipt
update-watch-hash update-watch-hash
self-sign-bounty self-sign-bounty
update-contract-internal-balances ])
update-balances]) (log/info "run-1-min-interval-tasks done")))
(log/debug "run-1-min-interval-tasks done")))
(defn run-10-min-interval-tasks [time] (defn run-10-min-interval-tasks [time]
(do (do
(log/debug "run-1-min-interval-tasks" time) (log/info "run-10-min-interval-tasks" time)
(run-tasks (run-tasks
[update-open-issue-usd-values]) [update-contract-internal-balances
(log/debug "run-10-min-interval-tasks done"))) update-balances
update-open-issue-usd-values])
(log/info "run-10-min-interval-tasks done")))
(mount/defstate scheduler (mount/defstate scheduler

View File

@ -135,4 +135,10 @@
[:div.page-nav-text [:span (str "Page " page-number " of " page-count)]] [:div.page-nav-text [:span (str "Page " page-number " of " page-count)]]
[draw-page-numbers page-number page-count container-element]]]))) [draw-page-numbers page-number page-count container-element]]])))
(defn usd-string
"Turn a given float into a USD currency string based on the browsers locale setting.
A more complex and customizable approach can be found in goog.i18n.NumberFormat:
https://google.github.io/closure-library/api/goog.i18n.NumberFormat.html"
[usd-float]
(.toLocaleString usd-float js/navigator.language #js {:style "currency" :currency "USD"}))

View File

@ -1,10 +1,10 @@
(ns commiteth.core (ns commiteth.core
(:require [reagent.core :as r] (:require [reagent.core :as r]
[re-frame.core :as rf] [re-frame.core :as rf]
[secretary.core :as secretary]
[goog.events :as events] [goog.events :as events]
[goog.history.EventType :as HistoryEventType] [goog.history.EventType :as HistoryEventType]
[commiteth.ajax :refer [load-interceptors!]] [commiteth.ajax :refer [load-interceptors!]]
[commiteth.routes]
[commiteth.handlers] [commiteth.handlers]
[commiteth.subscriptions] [commiteth.subscriptions]
[commiteth.activity :refer [activity-page]] [commiteth.activity :refer [activity-page]]
@ -53,7 +53,7 @@
[:a [:a
(merge props (merge props
(if (keyword? target) (if (keyword? target)
{:on-click #(rf/dispatch [target])} {:on-click #(commiteth.routes/nav! target)}
{:href target})) {:href target}))
caption]]))]])) caption]]))]]))
@ -66,7 +66,7 @@
[:div.ui.container.user-component [:div.ui.container.user-component
[user-dropdown [user-dropdown
@user @user
[[:update-address "My Payment Details" {}] [[:settings "My Payment Details" {}]
["/logout" "Sign Out" {:class "logout-link"}]] ["/logout" "Sign Out" {:class "logout-link"}]]
mobile?]] mobile?]]
[:a.ui.button.small.login-button {:href js/authorizeUrl} (str "LOG IN" [:a.ui.button.small.login-button {:href js/authorizeUrl} (str "LOG IN"
@ -89,7 +89,7 @@
(for [[page caption] tabs] (for [[page caption] tabs]
(let [props {:class (str "ui item" (let [props {:class (str "ui item"
(when (= @current-page page) " active")) (when (= @current-page page) " active"))
:on-click #(rf/dispatch [:set-active-page page])}] :on-click #(commiteth.routes/nav! page)}]
^{:key page} [:div props caption]))))))) ^{:key page} [:div props caption])))))))
@ -124,15 +124,6 @@
[flash-message-pane])]))) [flash-message-pane])])))
(def pages
{:activity #'activity-page
:bounties #'bounties-page
:repos #'repos-page
:manage-payouts #'manage-payouts-page
:update-address #'update-address-page
:usage-metrics #'usage-metrics-page})
(defn top-hunters [] (defn top-hunters []
(let [top-hunters (rf/subscribe [:top-hunters])] (let [top-hunters (rf/subscribe [:top-hunters])]
(fn [] (fn []
@ -211,7 +202,13 @@
[:div {:class (str (if (show-top-hunters?) "eleven" "sixteen") [:div {:class (str (if (show-top-hunters?) "eleven" "sixteen")
" wide computer sixteen wide tablet column")} " wide computer sixteen wide tablet column")}
[:div.ui.container [:div.ui.container
[(pages @current-page)]]] (case @current-page
:activity [activity-page]
:bounties [bounties-page]
:repos [repos-page]
:manage-payouts [manage-payouts-page]
:settings [update-address-page]
:usage-metrics [usage-metrics-page])]]
(when (show-top-hunters?) (when (show-top-hunters?)
[:div.five.wide.column.computer.only [:div.five.wide.column.computer.only
[:div.ui.container.top-hunters [:div.ui.container.top-hunters
@ -220,28 +217,6 @@
[top-hunters]]])]]] [top-hunters]]])]]]
[footer]]))) [footer]])))
(secretary/set-config! :prefix "#")
(secretary/defroute "/" []
(rf/dispatch [:set-active-page :bounties]))
(secretary/defroute "/activity" []
(rf/dispatch [:set-active-page :activity]))
(secretary/defroute "/repos" []
(if js/user
(rf/dispatch [:set-active-page :repos])
(secretary/dispatch! "/")))
(defn hook-browser-navigation! []
(doto (History.)
(events/listen
HistoryEventType/NAVIGATE
(fn [event]
(secretary/dispatch! (.-token event))))
(.setEnabled true)))
(defn mount-components [] (defn mount-components []
(r/render [#'page] (.getElementById js/document "app"))) (r/render [#'page] (.getElementById js/document "app")))
@ -286,7 +261,7 @@
(when config/debug? (when config/debug?
(enable-re-frisk!)) (enable-re-frisk!))
(load-interceptors!) (load-interceptors!)
(hook-browser-navigation!) (commiteth.routes/setup-nav!)
(load-data true) (load-data true)
(.addEventListener js/window "click" #(rf/dispatch [:clear-flash-message])) (.addEventListener js/window "click" #(rf/dispatch [:clear-flash-message]))
(on-js-load)) (on-js-load))

View File

@ -312,12 +312,6 @@
(:status-text response)))]})) (:status-text response)))]}))
(reg-event-fx
:update-address
(fn [{:keys [db]} [_]]
{:db db
:dispatch [:set-active-page :update-address]}))
(reg-event-db (reg-event-db
:update-user :update-user
(fn [db [_ fields]] (fn [db [_ fields]]

View File

@ -1,6 +1,6 @@
(ns commiteth.manage-payouts (ns commiteth.manage-payouts
(:require [re-frame.core :as rf] (:require [re-frame.core :as rf]
[commiteth.common :refer [human-time]])) [commiteth.common :as common :refer [human-time]]))
@ -57,9 +57,21 @@
(:claims bounty))] (:claims bounty))]
[claim-card bounty claim])))) [claim-card bounty claim]))))
(defn bounty-stats [{:keys [paid unpaid]}]
[:div.cf
[:div.fl-ns.w-50-ns.tc.pv4
[:div.ttu.tracked "Open"]
[:div.f2.pa2 (common/usd-string (:combined-usd-value unpaid))]
[:div (:count unpaid) " bounties"]]
[:div.fl-ns.w-50-ns.tc.pv4
[:div.ttu.tracked "Paid"]
[:div.f2.pa2 (common/usd-string (:combined-usd-value paid))]
[:div (:count paid) " bounties"]]])
(defn manage-payouts-page [] (defn manage-payouts-page []
(let [owner-bounties (rf/subscribe [:owner-bounties]) (let [owner-bounties (rf/subscribe [:owner-bounties])
bounty-stats-data (rf/subscribe [:owner-bounties-stats])
owner-bounties-loading? (rf/subscribe [:get-in [:owner-bounties-loading?]])] owner-bounties-loading? (rf/subscribe [:get-in [:owner-bounties-loading?]])]
(fn [] (fn []
(if @owner-bounties-loading? (if @owner-bounties-loading?
@ -67,17 +79,14 @@
[:div.ui.active.inverted.dimmer [:div.ui.active.inverted.dimmer
[:div.ui.text.loader "Loading"]]] [:div.ui.text.loader "Loading"]]]
(let [web3 (.-web3 js/window) (let [web3 (.-web3 js/window)
bounties (vals @owner-bounties) bounties (vals @owner-bounties)]
unpaid? #(empty? (:payout_hash %))
paid? #(not-empty (:payout_hash %))
unpaid-bounties (filter unpaid? bounties)
paid-bounties (filter paid? bounties)]
[:div.ui.container [:div.ui.container
(when (nil? web3) (when (nil? web3)
[:div.ui.warning.message [:div.ui.warning.message
[:i.warning.icon] [:i.warning.icon]
"To sign off claims, please view Status Open Bounty in Status, Mist or Metamask"]) "To sign off claims, please view Status Open Bounty in Status, Mist or Metamask"])
[bounty-stats @bounty-stats-data]
[:h3 "New claims"] [:h3 "New claims"]
[claim-list unpaid-bounties] [claim-list (filter (complement :paid?) bounties)]
[:h3 "Old claims"] [:h3 "Old claims"]
[claim-list paid-bounties]]))))) [claim-list (filter :paid? bounties)]])))))

View File

@ -0,0 +1,25 @@
(ns commiteth.routes
(:require [bide.core :as bide]
[re-frame.core :as rf]))
(defonce router
(bide/router [["/" :bounties]
["/activity" :activity]
["/repos" :repos]
["/manage-payouts" :manage-payouts]
["/settings" :settings]
["/usage-metrics" :usage-metrics]]))
(defn on-navigate
"A function which will be called on each route change."
[name params query]
(println "Route change to: " name params query)
(rf/dispatch [:set-active-page name]))
(defn setup-nav! []
(bide/start! router {:default :bounties
:on-navigate on-navigate}))
(defn nav! [route-id]
(bide/navigate! router route-id {}))

View File

@ -68,7 +68,26 @@
(reg-sub (reg-sub
:owner-bounties :owner-bounties
(fn [db _] (fn [db _]
(:owner-bounties db))) (->> (for [[id bounty] (:owner-bounties db)]
;; TODO(martinklepsch) we might want to consider using a
;; special prefix or namespace for derived properties that
;; are added to domain records like this
;; e.g. `derived/paid?`
[id (assoc bounty :paid? (boolean (:payout_hash bounty)))])
(into {}))))
(reg-sub
:owner-bounties-stats
:<- [:owner-bounties]
(fn [owner-bounties _]
(let [sum-dollars (fn sum-dollars [bounties]
(reduce + (map #(js/parseFloat (:value_usd %)) bounties)))
{:keys [paid unpaid]} (group-by #(if (:paid? %) :paid :unpaid)
(vals owner-bounties))]
{:paid {:count (count paid)
:combined-usd-value (sum-dollars paid)}
:unpaid {:count (count unpaid)
:combined-usd-value (sum-dollars unpaid)}})))
(reg-sub (reg-sub
:pagination :pagination

3
test/Jenkinsfile vendored
View File

@ -1,7 +1,8 @@
node ('linux1') {sauce('1be1b688-e0e7-4314-92a0-db11f52d3c00') { node ('linux1') {sauce('1be1b688-e0e7-4314-92a0-db11f52d3c00') {
checkout([$class: 'GitSCM', branches: [[name: '*/develop']], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'CleanBeforeCheckout']], submoduleCfg: [], userRemoteConfigs: [[url: 'https://github.com/status-im/open-bounty.git']]]) checkout([$class: 'GitSCM', branches: [[name: '*/develop']], doGenerateSubmoduleConfigurations: false, extensions: [[$class: 'CleanBeforeCheckout']], submoduleCfg: [], userRemoteConfigs: [[url: 'https://github.com/status-im/open-bounty.git']]])
configFileProvider([configFile(fileId: 'sob_automation_test_config', targetLocation: 'test/end-to-end/tests')]) { configFileProvider([configFile(fileId: 'sob_automation_test_config', targetLocation: 'test/end-to-end/tests')]) {
try {sh 'cd test/end-to-end/tests && python3 -m pytest -m sanity --build=$BUILD_NAME -v -n 1' try {withCredentials([string(credentialsId: 'SOB_SAUCE_ACCESS_KEY', variable: 'SAUCE_ACCESS_KEY'), string(credentialsId: 'SOB_SAUCE_USERNAME', variable: 'SAUCE_USERNAME')])
{sh 'cd test && docker build -t end2end . && docker run --rm -e "SAUCE_USERNAME="${SAUCE_USERNAME} -e "SAUCE_ACCESS_KEY="${SAUCE_ACCESS_KEY} --name end2end-container end2end -m pytest -m sanity --build=$BUILD_NAME -v -n 1'}
} }
finally { finally {
saucePublisher() saucePublisher()

View File

@ -1,4 +1,5 @@
import configparser import configparser
import os
class TestData(object): class TestData(object):
@ -9,7 +10,7 @@ class TestData(object):
# define here path to your config.ini file # define here path to your config.ini file
# example - config_example.ini # example - config_example.ini
self.config.read("tests/config.ini") self.config.read(os.path.join(os.path.abspath(os.path.dirname(__file__)), 'config.ini'))
# self.issue['title'] is set in GithubPage::create_new_bounty # self.issue['title'] is set in GithubPage::create_new_bounty
# self.issue['id'] is set in GithubPage::create_new_bounty # self.issue['id'] is set in GithubPage::create_new_bounty

View File

@ -18,6 +18,8 @@ class BaseTestCase:
sauce_lab_cap = dict() sauce_lab_cap = dict()
sauce_lab_cap['name'] = test_data.test_name sauce_lab_cap['name'] = test_data.test_name
sauce_lab_cap['build'] = pytest.config.getoption('build') sauce_lab_cap['build'] = pytest.config.getoption('build')
sauce_lab_cap['idleTimeout'] = 900
sauce_lab_cap['commandTimeout'] = 500
sauce_lab_cap['platform'] = "MAC" sauce_lab_cap['platform'] = "MAC"
sauce_lab_cap['browserName'] = 'Chrome' sauce_lab_cap['browserName'] = 'Chrome'
sauce_lab_cap['screenResolution'] = '2048x1536' sauce_lab_cap['screenResolution'] = '2048x1536'
@ -67,8 +69,9 @@ class BaseTestCase:
if cls.environment == 'sauce': if cls.environment == 'sauce':
for caps in cls.capabilities_dev, cls.capabilities_org: for caps in cls.capabilities_dev, cls.capabilities_org:
cls.get_remote_caps(cls) remote = cls.get_remote_caps(cls)
new_caps = caps.to_capabilities() new_caps = caps.to_capabilities()
new_caps.update(remote)
driver = webdriver.Remote(cls.executor_sauce_lab, driver = webdriver.Remote(cls.executor_sauce_lab,
desired_capabilities=new_caps) desired_capabilities=new_caps)
drivers.append(driver) drivers.append(driver)
@ -79,6 +82,7 @@ class BaseTestCase:
cls.driver_dev = drivers[0] cls.driver_dev = drivers[0]
cls.driver_org = drivers[1] cls.driver_org = drivers[1]
for driver in drivers: for driver in drivers:
driver.implicitly_wait(10) driver.implicitly_wait(10)
@ -132,9 +136,9 @@ class BaseTestCase:
remove_installation(cls.driver_org) remove_installation(cls.driver_org)
######DEV ######DEV
cls.github_dev.delete_fork()
cls.github_dev.clean_repo_local_folder() cls.github_dev.clean_repo_local_folder()
cls.github_dev.delete_fork()
try: try:
cls.driver_dev.quit() cls.driver_dev.quit()
cls.driver_org.quit() cls.driver_org.quit()