Merge branch 'develop' into feature/readd-bounty-#185

This commit is contained in:
Vitaliy Vlasov 2018-03-20 14:33:27 +02:00 committed by GitHub
commit 8bb9f7caaa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 281 additions and 249 deletions

View File

@ -5,12 +5,13 @@
:exclusions [joda-time]]
[re-frame "0.10.2"]
[cljs-ajax "0.7.2"]
[secretary "1.2.3"]
[funcool/bide "1.6.0"]
[reagent-utils "0.2.1"]
[reagent "0.7.0"]
[org.clojure/clojurescript "1.9.946"]
[org.clojure/clojure "1.8.0"]
[selmer "1.11.1"]
[com.taoensso/tufte "1.3.0"]
[markdown-clj "1.0.1"]
[ring-middleware-format "0.7.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.is_open = true;
-- :name update-repo-generic :! :n
/* :require [clojure.string :as string]
[hugsql.parameters :refer [identifier-param-quote]] */
-- :name update-repo-name :! :n
UPDATE repositories
SET
/*~
(string/join ","
(for [[field _] (:updates params)]
(str (identifier-param-quote (name field) options)
" = :v:updates." (name field))))
~*/
where repo_id = :repo_id;
SET repo = :repo_name
WHERE repo_id = :repo_id
AND repo != :repo_name
-- :name update-repo-state :! :n
UPDATE repositories
SET state = :repo_state
WHERE repo_id = :repo_id
-- Issues --------------------------------------------------------------------------
@ -430,6 +427,14 @@ SELECT exists(SELECT 1
FROM issues
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 :? :*
-- :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"
/></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://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" />
<script type="text/javascript" src="/js/app.js?v={{commiteth-version}}"></script>
<script>

View File

@ -110,7 +110,14 @@
:exists
boolean)))
(defn contract-from-pool
[owner-address]
(jdbc/with-db-connection [con-db *db*]
(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*]
(db/get-enabled-repositories con-db {:user_id user-id}))))
(defn update-repo
[repo-id updates]
(defn update-repo-name
[repo-id repo-name]
(jdbc/with-db-connection [con-db *db*]
(db/update-repo-generic con-db {:repo_id repo-id
:updates updates})))
(db/update-repo-name con-db {:repo_id repo-id
: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
"Get a repo from DB given it's full name (owner/repo-name)"
[full-name]

View File

@ -4,8 +4,10 @@
[clojure.java.io :as io]
[commiteth.config :refer [env]]
[clojure.string :refer [join]]
[taoensso.tufte :as tufte :refer (defnp p profiled profile)]
[clojure.tools.logging :as log]
[clojure.string :as str]
[mount.core :as mount]
[pandect.core :as pandect]
[commiteth.util.util :refer [json-api-request]])
(:import [org.web3j
@ -26,24 +28,30 @@
(defn auto-gas-price? [] (env :auto-gas-price false))
(defn offline-signing? [] (env :offline-signing true))
(def web3j-obj (atom nil))
(def creds-obj (atom nil))
(defn wallet-file-path []
(env :eth-wallet-file))
(defn wallet-password []
(env :eth-password))
(defn creds []
(let [password (wallet-password)
file-path (wallet-file-path)]
(if (and password file-path)
(WalletUtils/loadCredentials
password
file-path)
(throw (ex-info "Make sure you provided proper credentials in appropriate resources/config.edn"
{:password password :file-path file-path})))))
(defn create-web3j []
(Web3j/build (HttpService. (eth-rpc-url))))
(or @web3j-obj
(swap! web3j-obj (constantly (Web3j/build (HttpService. (eth-rpc-url)))))))
(defn creds []
(or @creds-obj
(let [password (wallet-password)
file-path (wallet-file-path)]
(if (and password file-path)
(swap! creds-obj
(constantly (WalletUtils/loadCredentials
password
file-path)))
(throw (ex-info "Make sure you provided proper credentials in appropriate resources/config.edn"
{:password password :file-path file-path}))))))
(defn get-signed-tx [gas-price gas-limit to data]
"Create a sign a raw transaction.
@ -192,7 +200,8 @@
(defn get-balance-eth
[account digits]
(hex->eth (get-balance-hex account) digits))
(p :get-balance-eth
(hex->eth (get-balance-hex account) digits)))
(defn- format-param
[param]
@ -296,3 +305,15 @@
(filter true?)
(empty?)))
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]]
[commiteth.config :refer [env]]
[clojure.tools.logging :as log]
[taoensso.tufte :as tufte :refer (defnp p profiled profile)]
[commiteth.eth.token-data :as token-data])
(:import [org.web3j
abi.datatypes.Address
@ -147,20 +148,22 @@
"Query (internal) ERC20 token balance from bounty contract for given
token TLA."
[bounty-addr token]
(let [bounty-contract (load-bounty-contract bounty-addr)
(p :token-balance-in-bounty
(let [bounty-contract (load-bounty-contract bounty-addr)
token-address (get-token-address token)
token-addr-web3j (Address. token-address)]
(-> bounty-contract
(.tokenBalances token-addr-web3j)
.get
.getValue
(convert-token-value token))))
(convert-token-value token)))))
(defn token-balance
"Query balance of given ERC20 token TLA for given address from ERC20
contract."
[bounty-addr token]
(let [token-address (get-token-address token)]
(p :token-balance
(let [token-address (get-token-address token)]
(log/debug "token-balance" bounty-addr token token-address)
(try
(-> (eth/call token-address
@ -170,25 +173,26 @@
(convert-token-value token))
(catch Throwable t
(log/debug "Failed to query token balance " t)
0))))
0)))))
(defn token-balances
"Get a given bounty contract's token balances. Assumes contract's
internal balances have been updated."
[bounty-addr]
(let [bounty-contract (load-bounty-contract bounty-addr)
token-addresses (-> bounty-contract
(p :token-balances
(let [bounty-contract (p :load-bounty-contract (load-bounty-contract bounty-addr))
token-addresses (p :getTokenList (-> bounty-contract
(.getTokenList)
.get)]
.get))]
(if token-addresses
(let [addrs (map str
(.getValue token-addresses))]
(p :getValue (.getValue token-addresses)))]
(into {}
(map (fn [addr] (if-let [info (token-data/token-info-by-addr addr)]
(let [tla (first info)]
[tla (token-balance bounty-addr tla)]))) addrs)))
{})))
{}))))
(defn uint256 [x]
(org.web3j.abi.datatypes.generated.Uint256. x))

View File

@ -46,67 +46,6 @@
(log/debug "token" token "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]
(some #(= elem %) coll))
@ -286,9 +225,4 @@
:auth-rules authenticated?
:current-user user
(log/debug "/user/bounties")
(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)))))))
(ok (user-bounties user))))))

View File

@ -104,26 +104,17 @@
(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
[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)
(let [issue (github/get-issue owner repo bounty-issue-number)
open-or-edit? (contains? #{:opened :edited} event-type)
(let [open-or-edit? (contains? #{:opened :edited} event-type)
close? (= :closed event-type)
pr-data {:repo_id repo-id
:pr_id pr-id
:pr_number pr-number
:user_id user-id
:issue_number bounty-issue-number
:issue_id (:id issue)
:issue_number (:issue_number issue)
:issue_id (:issue_id issue)
:state event-type}]
;; TODO: in the opened case if the submitting user has no
@ -133,18 +124,18 @@
(cond
open-or-edit? (do
(log/info "PR with reference to bounty issue"
bounty-issue-number "opened")
(:issue_number issue) "opened")
(pull-requests/save (merge pr-data {:state :opened
:commit_sha head-sha})))
close? (if merged?
(do (log/info "PR with reference to bounty issue"
bounty-issue-number "merged")
(:issue_number issue) "merged")
(pull-requests/save
(merge pr-data {:state :merged
: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"
bounty-issue-number "closed with no merge")
(:issue_number issue) "closed with no merge")
(pull-requests/save
(merge pr-data {:state :closed
:commit_sha head-sha})))))))
@ -172,26 +163,27 @@
pr-number :number
pr-body :body
pr-title :title} :pull_request}]
(log/debug "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 [bounty-issue-number (->>
(extract-issue-number pr-body pr-title)
(first)
(ensure-bounty-issue owner repo))]
(do
(log/debug "Referenced bounty issue found" owner repo bounty-issue-number)
(handle-claim user-id
login name
avatar_url
owner repo
repo-id
bounty-issue-number
pr-id
pr-number
head-sha
merged?
event-type))
(log/info "handle-pull-request-event" event-type owner repo repo-id login pr-body pr-title)
(if-let [issue (some->> (extract-issue-number pr-body pr-title)
(first)
(issues/get-issue repo-id))]
(if-not (:commit_sha issue) ; no PR has been merged yet referencing this issue
(do
(log/info "Referenced bounty issue found" owner repo (:issue_number issue))
(handle-claim issue
user-id
login name
avatar_url
owner repo
repo-id
pr-id
pr-number
head-sha
merged?
event-type))
(log/info "PR for issue already merged"))
(when (= :edited event-type)
; Remove PR if it does not reference any issue
(pull-requests/remove pr-id))))
@ -202,9 +194,15 @@
new-title (:title gh-issue)]
(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
[webhook-payload]
(update-repo-name webhook-payload)
(when-let [action (:action webhook-payload)]
(log/debug "handle-issue" action)
(case action
@ -222,17 +220,17 @@
(log/debug "Not handling action '" action "'"))
(ok)))
(defn enable-repo-2 [repo-id full-repo]
(log/debug "enable-repo-2" repo-id full-repo)
(defn enable-repo [repo-id full-repo]
(log/debug "enable-repo" repo-id full-repo)
;; TODO(oskarth): Add granular permissions to enable creation of label
#_(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?)
(bounties/add-bounties-for-existing-issues full-repo)))
(defn disable-repo-2 [repo-id full-repo]
(log/debug "disable-repo-2" repo-id full-repo)
(repositories/update-repo repo-id {:state 0}))
(defn disable-repo [repo-id full-repo]
(log/debug "disable-repo" repo-id full-repo)
(repositories/update-repo-state repo-id 0))
(defn full-repo->owner [full-repo]
(try
@ -242,8 +240,6 @@
(log/error "exception when parsing repo" e)
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?]
(let [repo-id (:id repo)
repo-name (:name repo)
@ -283,14 +279,14 @@
_ (log/info "handle-add-repo db-item" db-item)
is-enabled (= 2 (:state db-item))]
(if is-enabled
(disable-repo-2 repo-id full-repo)
(enable-repo-2 repo-id full-repo))
(disable-repo repo-id full-repo)
(enable-repo repo-id full-repo))
(ok {:enabled (not is-enabled)
:id repo-id
:full_name full-repo}))
(catch Exception 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))))))
(defn handle-installation [{:keys [action installation repositories sender]}]
@ -333,12 +329,13 @@
(ok))
(defn handle-pull-request
[pull-request]
(let [action (keyword (:action pull-request))]
[webhook-payload]
(update-repo-name webhook-payload)
(let [action (keyword (:action webhook-payload))]
(when (contains? #{:opened
:edited
:closed} action)
(handle-pull-request-event action pull-request))
(handle-pull-request-event action webhook-payload))
(ok)))
@ -391,14 +388,5 @@
"pull_request" (handle-pull-request payload)
"installation" (handle-installation 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)))
(forbidden)))))

View File

@ -4,6 +4,7 @@
[commiteth.eth.token-data :as token-data]
[commiteth.github.core :as github]
[commiteth.db.issues :as issues]
[taoensso.tufte :as tufte :refer (defnp p profiled profile)]
[commiteth.db.bounties :as db-bounties]
[commiteth.bounties :as bounties]
[commiteth.util.crypto-fiat-value :as fiat-util]
@ -15,13 +16,35 @@
[clj-time.periodic :refer [periodic-seq]]
[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
"For each pending deployment: gets transaction receipt, updates db
state (contract-address, comment-id) and posts github comment"
[]
(log/info "In update-issue-contract-address")
(doseq [{issue-id :issue_id
(p :update-issue-contract-address
(doseq [{issue-id :issue_id
transaction-hash :transaction_hash} (issues/list-pending-deployments)]
(log/info "pending deployment:" transaction-hash)
(try
@ -57,7 +80,7 @@
(log/error "Failed to find contract address in tx logs")))
(catch Throwable 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"))
@ -84,16 +107,18 @@
(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."
[]
(doseq [{issue-id :issue_id
(p :deploy-pending-contracts
(doseq [{issue-id :issue_id
owner-address :owner_address} (db-bounties/pending-contracts)]
(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
"Walks through all issues eligible for bounty payout and signs corresponding transaction"
[]
(log/info "In self-sign-bounty")
(doseq [{contract-address :contract_address
(p :self-sign-bounty
(doseq [{contract-address :contract_address
issue-id :issue_id
payout-address :payout_address
repo :repo
@ -130,7 +155,7 @@
false))))
(catch Throwable 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")
)
@ -138,25 +163,27 @@
"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")
(doseq [{issue-id :issue_id
(p :update-confirm-hash
(doseq [{issue-id :issue_id
execute-hash :execute_hash} (db-bounties/pending-payouts)]
(log/info "pending payout:" execute-hash)
(when-let [receipt (eth/get-transaction-receipt execute-hash)]
(log/info "execution receipt for issue #" issue-id ": " receipt)
(when-let [confirm-hash (multisig/find-confirmation-tx-id receipt)]
(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"))
(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"
[]
(doseq [{issue-id :issue_id
(p :update-watch-hash
(doseq [{issue-id :issue_id
watch-hash :watch_hash} (db-bounties/pending-watch-calls)]
(log/info "pending watch call" 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?
@ -171,7 +198,8 @@
"Gets transaction receipt for each confirmed payout and updates payout_hash"
[]
(log/info "In update-payout-receipt")
(doseq [{issue-id :issue_id
(p :update-payout-receipt
(doseq [{issue-id :issue_id
payout-hash :payout_hash
contract-address :contract_address
repo :repo
@ -213,7 +241,7 @@
(db-bounties/reset-payout-hash issue-id)))
(catch Throwable 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")
)
@ -252,12 +280,14 @@
(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."
[]
(doseq [{issue-id :issue_id
bounty-address :contract_address
watch-hash :watch_hash}
(db-bounties/open-bounty-contracts)]
(update-bounty-token-balances issue-id bounty-address watch-hash)))
(log/info "In update-contract-internal-balances")
(p :update-contract-internal-balances
(doseq [{issue-id :issue_id
bounty-address :contract_address
watch-hash :watch_hash}
(db-bounties/open-bounty-contracts)]
(update-bounty-token-balances issue-id bounty-address watch-hash)))
(log/info "Exit update-contract-internal-balances"))
(defn get-bounty-funds
"Get funds in given bounty contract.
@ -281,9 +311,10 @@
(defn update-open-issue-usd-values
"Sum up current USD values of all crypto assets in a bounty and store to DB"
[]
(doseq [{bounty-addr :contract_address}
(p :update-open-issue-usd-values
(doseq [{bounty-addr :contract_address}
(db-bounties/open-bounty-contracts)]
(update-issue-usd-value bounty-addr)))
(update-issue-usd-value bounty-addr))))
(defn float=
([x y] (float= x y 0.0000001))
@ -299,7 +330,8 @@
(defn update-balances
[]
(log/info "In update-balances")
(doseq [{contract-address :contract_address
(p :update-balances
(doseq [{contract-address :contract_address
owner :owner
repo :repo
comment-id :comment_id
@ -347,7 +379,7 @@
(update-issue-usd-value contract-address))))
(catch Throwable 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"))
@ -375,17 +407,18 @@
update-payout-receipt
update-watch-hash
self-sign-bounty
update-contract-internal-balances
update-balances])
(log/debug "run-1-min-interval-tasks done")))
])
(log/info "run-1-min-interval-tasks done")))
(defn run-10-min-interval-tasks [time]
(do
(log/debug "run-1-min-interval-tasks" time)
(log/info "run-10-min-interval-tasks" time)
(run-tasks
[update-open-issue-usd-values])
(log/debug "run-10-min-interval-tasks done")))
[update-contract-internal-balances
update-balances
update-open-issue-usd-values])
(log/info "run-10-min-interval-tasks done")))
(mount/defstate scheduler

View File

@ -135,4 +135,10 @@
[:div.page-nav-text [:span (str "Page " page-number " of " page-count)]]
[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
(:require [reagent.core :as r]
[re-frame.core :as rf]
[secretary.core :as secretary]
[goog.events :as events]
[goog.history.EventType :as HistoryEventType]
[commiteth.ajax :refer [load-interceptors!]]
[commiteth.routes]
[commiteth.handlers]
[commiteth.subscriptions]
[commiteth.activity :refer [activity-page]]
@ -53,7 +53,7 @@
[:a
(merge props
(if (keyword? target)
{:on-click #(rf/dispatch [target])}
{:on-click #(commiteth.routes/nav! target)}
{:href target}))
caption]]))]]))
@ -66,7 +66,7 @@
[:div.ui.container.user-component
[user-dropdown
@user
[[:update-address "My Payment Details" {}]
[[:settings "My Payment Details" {}]
["/logout" "Sign Out" {:class "logout-link"}]]
mobile?]]
[:a.ui.button.small.login-button {:href js/authorizeUrl} (str "LOG IN"
@ -89,7 +89,7 @@
(for [[page caption] tabs]
(let [props {:class (str "ui item"
(when (= @current-page page) " active"))
:on-click #(rf/dispatch [:set-active-page page])}]
:on-click #(commiteth.routes/nav! page)}]
^{:key page} [:div props caption])))))))
@ -124,15 +124,6 @@
[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 []
(let [top-hunters (rf/subscribe [:top-hunters])]
(fn []
@ -211,7 +202,13 @@
[:div {:class (str (if (show-top-hunters?) "eleven" "sixteen")
" wide computer sixteen wide tablet column")}
[: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?)
[:div.five.wide.column.computer.only
[:div.ui.container.top-hunters
@ -220,28 +217,6 @@
[top-hunters]]])]]]
[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 []
(r/render [#'page] (.getElementById js/document "app")))
@ -286,7 +261,7 @@
(when config/debug?
(enable-re-frisk!))
(load-interceptors!)
(hook-browser-navigation!)
(commiteth.routes/setup-nav!)
(load-data true)
(.addEventListener js/window "click" #(rf/dispatch [:clear-flash-message]))
(on-js-load))

View File

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

View File

@ -1,6 +1,6 @@
(ns commiteth.manage-payouts
(:require [re-frame.core :as rf]
[commiteth.common :refer [human-time]]))
[commiteth.common :as common :refer [human-time]]))
@ -57,9 +57,21 @@
(:claims bounty))]
[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 []
(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?]])]
(fn []
(if @owner-bounties-loading?
@ -67,17 +79,14 @@
[:div.ui.active.inverted.dimmer
[:div.ui.text.loader "Loading"]]]
(let [web3 (.-web3 js/window)
bounties (vals @owner-bounties)
unpaid? #(empty? (:payout_hash %))
paid? #(not-empty (:payout_hash %))
unpaid-bounties (filter unpaid? bounties)
paid-bounties (filter paid? bounties)]
bounties (vals @owner-bounties)]
[:div.ui.container
(when (nil? web3)
[:div.ui.warning.message
[:i.warning.icon]
"To sign off claims, please view Status Open Bounty in Status, Mist or Metamask"])
[bounty-stats @bounty-stats-data]
[:h3 "New claims"]
[claim-list unpaid-bounties]
[claim-list (filter (complement :paid?) bounties)]
[: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
:owner-bounties
(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
:pagination

3
test/Jenkinsfile vendored
View File

@ -1,7 +1,8 @@
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']]])
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 {
saucePublisher()

View File

@ -1,4 +1,5 @@
import configparser
import os
class TestData(object):
@ -9,7 +10,7 @@ class TestData(object):
# define here path to your config.ini file
# 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['id'] is set in GithubPage::create_new_bounty

View File

@ -18,6 +18,8 @@ class BaseTestCase:
sauce_lab_cap = dict()
sauce_lab_cap['name'] = test_data.test_name
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['browserName'] = 'Chrome'
sauce_lab_cap['screenResolution'] = '2048x1536'
@ -67,8 +69,9 @@ class BaseTestCase:
if cls.environment == 'sauce':
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.update(remote)
driver = webdriver.Remote(cls.executor_sauce_lab,
desired_capabilities=new_caps)
drivers.append(driver)
@ -79,6 +82,7 @@ class BaseTestCase:
cls.driver_dev = drivers[0]
cls.driver_org = drivers[1]
for driver in drivers:
driver.implicitly_wait(10)
@ -132,9 +136,9 @@ class BaseTestCase:
remove_installation(cls.driver_org)
######DEV
cls.github_dev.delete_fork()
cls.github_dev.clean_repo_local_folder()
cls.github_dev.delete_fork()
try:
cls.driver_dev.quit()
cls.driver_org.quit()