Merge pull request #422 from status-im/develop

Deploy week 16 2018
This commit is contained in:
Martin Klepsch 2018-04-18 16:45:50 +02:00 committed by GitHub
commit c4ab1047f2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 478 additions and 159 deletions

View File

@ -0,0 +1 @@
ALTER TABLE "public"."pull_requests" DROP COLUMN "title";

View File

@ -0,0 +1 @@
ALTER TABLE "public"."pull_requests" ADD COLUMN "title" character varying(256);

View File

@ -0,0 +1,78 @@
-- restore the previous version of the view
CREATE VIEW "public"."claims_view" AS SELECT i.title AS issue_title,
i.issue_number,
r.repo AS repo_name,
r.owner AS repo_owner,
COALESCE(u.name, u.login) AS user_name,
u.avatar_url AS user_avatar_url,
i.payout_receipt,
p.updated,
i.updated AS issue_updated,
i.balance_eth,
i.tokens,
i.value_usd,
p.state AS pr_state,
i.is_open AS issue_open,
(case when u.address IS NULL THEN false ELSE true END) AS user_has_address
FROM issues i,
users u,
repositories r,
pull_requests p
WHERE r.repo_id = i.repo_id
AND p.issue_id = i.issue_id
AND p.user_id = u.id
AND i.contract_address IS NOT NULL
AND i.comment_id IS NOT NULL
ORDER BY p.updated;
CREATE OR REPLACE VIEW "public"."activity_feed_view" AS
SELECT 'open-claim'::text AS type,
claims_view.issue_title,
claims_view.repo_name,
claims_view.repo_owner,
claims_view.issue_number,
claims_view.user_name,
claims_view.user_avatar_url,
claims_view.balance_eth,
claims_view.tokens,
claims_view.value_usd,
claims_view.user_has_address,
claims_view.updated
FROM claims_view
WHERE claims_view.pr_state = 0
AND claims_view.payout_receipt IS NULL
AND claims_view.issue_open IS TRUE
UNION
SELECT 'claim-pending'::text AS type,
claims_view.issue_title,
claims_view.repo_name,
claims_view.repo_owner,
claims_view.issue_number,
claims_view.user_name,
claims_view.user_avatar_url,
claims_view.balance_eth,
claims_view.tokens,
claims_view.value_usd,
claims_view.user_has_address,
claims_view.issue_updated AS updated
FROM claims_view
WHERE claims_view.pr_state = 1
AND claims_view.payout_receipt IS NULL
UNION
SELECT 'claim-payout'::text AS type,
claims_view.issue_title,
claims_view.repo_name,
claims_view.repo_owner,
claims_view.issue_number,
claims_view.user_name,
claims_view.user_avatar_url,
claims_view.balance_eth,
claims_view.tokens,
claims_view.value_usd,
claims_view.user_has_address,
claims_view.issue_updated AS updated
FROM claims_view
WHERE claims_view.pr_state = 1
AND claims_view.payout_receipt IS NOT NULL
ORDER BY 12 DESC;

View File

@ -0,0 +1,95 @@
DROP VIEW "public"."claims_view" CASCADE;
CREATE VIEW "public"."claims_view" AS SELECT i.title AS issue_title,
i.issue_number,
r.repo AS repo_name,
r.owner AS repo_owner,
p.title AS pr_title, -- added
p.pr_number AS pr_number, -- added
p.pr_id AS pr_id, -- added
i.issue_id AS issue_id, -- added
COALESCE(u.name, u.login) AS user_name,
u.avatar_url AS user_avatar_url,
i.payout_receipt,
p.updated,
i.updated AS issue_updated,
i.balance_eth,
i.tokens,
i.value_usd,
p.state AS pr_state,
i.is_open AS issue_open,
(case when u.address IS NULL THEN false ELSE true END) AS user_has_address
FROM issues i,
users u,
repositories r,
pull_requests p
WHERE r.repo_id = i.repo_id
AND p.issue_id = i.issue_id
AND p.user_id = u.id
AND i.contract_address IS NOT NULL
AND i.comment_id IS NOT NULL
ORDER BY p.updated;
CREATE VIEW "public"."activity_feed_view" AS -- altered
SELECT 'open-claim'::text AS type,
claims_view.issue_title,
claims_view.repo_name,
claims_view.repo_owner,
claims_view.pr_title, -- added
claims_view.pr_number, -- added
claims_view.pr_id, -- added
claims_view.issue_number,
claims_view.issue_id, -- added
claims_view.user_name,
claims_view.user_avatar_url,
claims_view.balance_eth,
claims_view.tokens,
claims_view.value_usd,
claims_view.user_has_address,
claims_view.updated
FROM claims_view
WHERE claims_view.pr_state = 0
AND claims_view.payout_receipt IS NULL
AND claims_view.issue_open IS TRUE
UNION
SELECT 'claim-pending'::text AS type,
claims_view.issue_title,
claims_view.repo_name,
claims_view.repo_owner,
claims_view.pr_title, -- added
claims_view.pr_number, -- added
claims_view.pr_id, -- added
claims_view.issue_number,
claims_view.issue_id, -- added
claims_view.user_name,
claims_view.user_avatar_url,
claims_view.balance_eth,
claims_view.tokens,
claims_view.value_usd,
claims_view.user_has_address,
claims_view.issue_updated AS updated
FROM claims_view
WHERE claims_view.pr_state = 1
AND claims_view.payout_receipt IS NULL
UNION
SELECT 'claim-payout'::text AS type,
claims_view.issue_title,
claims_view.repo_name,
claims_view.repo_owner,
claims_view.pr_title, -- added
claims_view.pr_number, -- added
claims_view.pr_id, -- added
claims_view.issue_number,
claims_view.issue_id, -- added
claims_view.user_name,
claims_view.user_avatar_url,
claims_view.balance_eth,
claims_view.tokens,
claims_view.value_usd,
claims_view.user_has_address,
claims_view.issue_updated AS updated
FROM claims_view
WHERE claims_view.pr_state = 1
AND claims_view.payout_receipt IS NOT NULL
ORDER BY 12 DESC;

Binary file not shown.

After

Width:  |  Height:  |  Size: 266 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 620 B

View File

@ -207,6 +207,7 @@ AND i.transaction_hash IS NOT NULL;
INSERT INTO pull_requests (pr_id,
repo_id,
pr_number,
title,
issue_number,
issue_id,
commit_sha,
@ -215,6 +216,7 @@ INSERT INTO pull_requests (pr_id,
VALUES(:pr_id,
:repo_id,
:pr_number,
:title,
:issue_number,
:issue_id,
:commit_sha,
@ -225,6 +227,7 @@ SET
state = :state,
issue_number = :issue_number,
issue_id = :issue_id,
title = :title,
updated = timezone('utc'::text, now()),
commit_sha = :commit_sha;
@ -592,7 +595,11 @@ SELECT
issue_title,
repo_name,
repo_owner,
pr_number,
pr_title,
pr_id,
issue_number,
issue_id,
user_name,
user_avatar_url,
balance_eth,

View File

@ -23,9 +23,10 @@
(defn deploy-contract [owner owner-address repo issue-id issue-number]
(if (empty? owner-address)
(log/errorf "issue %s: Unable to deploy bounty contract because repo owner has no Ethereum addres" issue-id)
(do
(try
(log/infof "issue %s: Deploying contract to %s" issue-id owner-address)
(if-let [transaction-hash (multisig/deploy-multisig owner-address)]
(if-let [transaction-hash (multisig/deploy-multisig {:owner owner-address
:internal-tx-id (str "contract-github-issue-" issue-id)})]
(do
(log/infof "issue %s: Contract deployed, transaction-hash: %s" issue-id transaction-hash)
(let [resp (github/post-deploying-comment owner
@ -36,7 +37,8 @@
(log/infof "issue %s: post-deploying-comment response: %s" issue-id resp)
(issues/update-comment-id issue-id comment-id))
(issues/update-transaction-hash issue-id transaction-hash))
(log/errorf "issue %s Failed to deploy contract to %s" issue-id owner-address)))))
(log/errorf "issue %s Failed to deploy contract to %s" issue-id owner-address))
(catch Exception ex (log/errorf ex "issue %s: deploy-contract exception" issue-id)))))
(defn add-bounty-for-issue [repo repo-id issue]
(let [{issue-id :id

View File

@ -34,7 +34,7 @@
(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
(db/update-repo-state 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)"

View File

@ -28,7 +28,9 @@
(defn auto-gas-price? [] (env :auto-gas-price false))
(defn offline-signing? [] (env :offline-signing true))
(def web3j-obj (atom nil))
(def web3j-obj
(delay (Web3j/build (HttpService. (eth-rpc-url)))))
(def creds-obj (atom nil))
(defn wallet-file-path []
@ -37,10 +39,6 @@
(defn wallet-password []
(env :eth-password))
(defn create-web3j []
(or @web3j-obj
(swap! web3j-obj (constantly (Web3j/build (HttpService. (eth-rpc-url)))))))
(defn creds []
(or @creds-obj
(let [password (wallet-password)
@ -53,29 +51,59 @@
(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.
'From' argument is not needed as it's already
encoded in credentials.
See https://web3j.readthedocs.io/en/latest/transactions.html#offline-transaction-signing"
(let [web3j (create-web3j)
nonce (.. (.ethGetTransactionCount web3j
(env :eth-account)
DefaultBlockParameterName/LATEST)
(defn get-web3j-nonce [web3j-instance]
(.. (.ethGetTransactionCount web3j-instance (env :eth-account) DefaultBlockParameterName/LATEST)
sendAsync
get
getTransactionCount)
tx (RawTransaction/createTransaction
nonce
gas-price
gas-limit
to
data)
signed (TransactionEncoder/signMessage tx (creds))
hex-string (Numeric/toHexString signed)]
(log/infof "Signing TX: nonce: %s, gas-price: %s, gas-limit: %s, data: %s"
nonce gas-price gas-limit data)
hex-string))
getTransactionCount))
(defprotocol INonceTracker
"The reason we need this is that we might send mutliple identical
transactions (e.g. bounty contract deploy with same owner) shortly
after another. In this case web3j's TX counting only increases once
a transaction has been confirmed and so we send multiple identical
transactions with the same nonce.
Web3j also provides a TransactionManager that increases nonces but it
does not track nonces related to transactions and so as far as I understand
it might cause transactions to be executed twice if they are retried.
https://github.com/web3j/web3j/blob/d19855475aa6620a7e93523bd9ede26ca50ed042/core/src/main/java/org/web3j/tx/RawTransactionManager.java"
(get-nonce [this internal-tx-id]
"Return the to be used nonce for an OpenBounty Ethereum
transaction identified by `internal-tx-id`. As these IDs are stable
we can use them to use consistent nonces for the same transaction."))
(defrecord NonceTracker [state]
INonceTracker
(get-nonce [this internal-tx-id]
(let [prev-nonce (get @state internal-tx-id)
web3j-nonce (get-web3j-nonce @web3j-obj)
nonces (set (vals @state))
nonce (if (seq nonces)
(inc (apply max nonces))
web3j-nonce)]
(when prev-nonce
(log/warnf "%s: tx will be retried (prev-nonce: %s, new-nonce: %s, web3j-nonce: %s)"
internal-tx-id prev-nonce nonce web3j-nonce))
;; TODO this is a memory leak since tracking state is never pruned
;; Since we're not doing 1000s of transactions every day yet we can
;; probably defer worrying about this until a bit later
(swap! state assoc internal-tx-id nonce)
nonce)))
(def nonce-tracker
(->NonceTracker (atom {})))
(defn get-signed-tx [{:keys [gas-price gas-limit to data internal-tx-id]}]
"Create a sign a raw transaction. 'From' argument is not needed as it's already encoded in credentials.
See https://web3j.readthedocs.io/en/latest/transactions.html#offline-transaction-signing"
(let [nonce (get-nonce nonce-tracker internal-tx-id)]
(log/infof "%s: Signing nonce: %s, gas-price: %s, gas-limit: %s"
internal-tx-id nonce gas-price gas-limit)
(-> (RawTransaction/createTransaction (biginteger nonce) gas-price gas-limit to data)
(TransactionEncoder/signMessage (creds))
(Numeric/toHexString))))
(defn eth-gasstation-gas-price
"Get max of average and average_calc from gas station and use that
@ -130,7 +158,8 @@
(atom 0))
(defn eth-rpc
[method params]
[{:keys [method params internal-tx-id]}]
{:pre [(string? method) (some? params)]}
(let [request-id (swap! req-id-tracker inc)
body {:jsonrpc "2.0"
:method method
@ -140,9 +169,10 @@
:body (json/write-str body)}
response @(post (eth-rpc-url) options)
result (safe-read-str (:body response))]
(log/infof "eth-rpc req(%s) body: %s\neth-rpc req(%s) result: %s"
request-id body request-id result)
(when internal-tx-id
(log/infof "%s: eth-rpc %s" internal-tx-id method))
(log/debugf "%s: eth-rpc req(%s) body: %s" internal-tx-id request-id body)
(log/debugf "%s: eth-rpc req(%s) result: %s" internal-tx-id request-id result)
(cond
;; Ignore any responses that have mismatching request ID
(not= (:id result) request-id)
@ -150,7 +180,10 @@
;; If request ID matches but contains error, throw
(:error result)
(throw (ex-info "Error submitting transaction via eth-rpc" (:error result)))
(throw
(ex-info (format "%s: Error submitting transaction via eth-rpc %s"
(or internal-tx-id "(no-tx-id)") (:error result))
(:error result)))
:else
(:result result))))
@ -187,9 +220,10 @@
(defn estimate-gas
[from to value & [params]]
(let [geth-estimate (eth-rpc
"eth_estimateGas" [(merge params {:from from
{:method "eth_estimateGas"
:params [(merge params {:from from
:to to
:value value})])
:value value})]})
adjusted-gas (adjust-gas-estimate geth-estimate)]
(log/debug "estimated gas (geth):" geth-estimate)
@ -212,7 +246,8 @@
(defn get-balance-hex
[account]
(eth-rpc "eth_getBalance" [account "latest"]))
(eth-rpc {:method "eth_getBalance"
:params [account "latest"]}))
(defn get-balance-wei
[account]
@ -231,7 +266,8 @@
(defn get-transaction-receipt
[hash]
(eth-rpc "eth_getTransactionReceipt" [hash]))
(eth-rpc {:method "eth_getTransactionReceipt"
:params [hash]}))
(defn format-call-params
[method-id & params]
@ -241,10 +277,12 @@
(defn call
[contract method-id & params]
(let [data (apply format-call-params method-id params)]
(eth-rpc "eth_call" [{:to contract :data data} "latest"])))
(eth-rpc {:method "eth_call"
:params [{:to contract :data data} "latest"]})))
(defn execute
[from contract method-id gas-limit & params]
[{:keys [from contract method-id gas-limit params internal-tx-id]}]
{:pre [(string? method-id)]}
(let [data (apply format-call-params method-id params)
gas-price (gas-price)
value (format "0x%x" 0)
@ -255,21 +293,23 @@
(merge {:gasPrice (integer->hex gas-price)})
contract
(merge {:to contract}))
gas (if gas-limit gas-limit
(estimate-gas from contract value params))
gas (or gas-limit (estimate-gas from contract value params))
params (if (offline-signing?)
(get-signed-tx (biginteger gas-price)
(hex->big-integer gas)
contract
data)
(get-signed-tx {:internal-tx-id internal-tx-id
:gas-price (biginteger gas-price)
:gas-limit (hex->big-integer gas)
:to contract
:data data})
(assoc params :gas gas))]
(if (offline-signing?)
(eth-rpc
"eth_sendRawTransaction"
[params])
{:method "eth_sendRawTransaction"
:params [params]
:internal-tx-id internal-tx-id})
(eth-rpc
"personal_sendTransaction"
[params (eth-password)]))))
{:method "personal_sendTransaction"
:params [params (eth-password)]
:internal-tx-id internal-tx-id}))))
(defn hex-ch->num
[ch]
@ -330,7 +370,6 @@
eth-core
:start
(do
(swap! web3j-obj (constantly nil))
(swap! creds-obj (constantly nil))
(log/info "eth/core started"))
:stop

View File

@ -1,6 +1,5 @@
(ns commiteth.eth.multisig-wallet
(:require [commiteth.eth.core :as eth
:refer [create-web3j creds]]
(:require [commiteth.eth.core :as eth]
[commiteth.config :refer [env]]
[clojure.tools.logging :as log]
[taoensso.tufte :as tufte :refer (defnp p profiled profile)]
@ -36,24 +35,19 @@
[]
(env :tokenreg-base-format :status))
(defn create-new
[owner1 owner2 required]
(eth/execute (eth/eth-account)
(factory-contract-addr)
(:create method-ids)
(eth/integer->hex 865000) ;; gas-limit
0x40
0x2
required
owner1
owner2))
(defn deploy-multisig
"Deploy a new multisig contract to the blockchain with commiteth bot
and given owner as owners."
[owner]
(create-new (eth/eth-account) owner 2))
and given owner as owners.
`internal-tx-id` is used to identify what issue this multisig is deployed
for and manage nonces at a later point in time."
[{:keys [owner internal-tx-id]}]
{:pre [(string? owner) (string? internal-tx-id)]}
(eth/execute {:internal-tx-id internal-tx-id
:from (eth/eth-account)
:contract (factory-contract-addr)
:method-id (:create method-ids)
:gas-limit (eth/integer->hex 865000)
:params [0x40 0x2 2 (eth/eth-account) owner]}))
(defn find-event-in-tx-receipt [tx-receipt topic-id]
(let [logs (:logs tx-receipt)
@ -92,20 +86,18 @@
(defn send-all
[contract to]
(log/debug "multisig/send-all " contract to)
[{:keys [contract payout-address internal-tx-id]}]
{:pre [(string? contract) (string? payout-address) (string? internal-tx-id)]}
(log/debug "multisig/send-all " contract payout-address internal-tx-id)
(let [params (eth/format-call-params
(:withdraw-everything method-ids)
to)]
(eth/execute (eth/eth-account)
contract
(:submit-transaction method-ids)
nil
contract
0
"0x60" ;; TODO: document these
"0x24" ;; or refactor out
params)))
payout-address)]
(eth/execute {:internal-tx-id internal-tx-id
:from (eth/eth-account)
:contract contract
:method-id (:submit-transaction method-ids)
:gas-limit nil
:params [contract 0 "0x60" "0x24" params]})))
(defn get-token-address [token]
@ -118,17 +110,17 @@
(log/debug "multisig/watch-token" bounty-addr token)
(let [token-address (get-token-address token)]
(assert token-address)
(eth/execute (eth/eth-account)
bounty-addr
(:watch method-ids)
nil
token-address)))
(eth/execute {:internal-tx-id (str "watch-token-" (System/currentTimeMillis) "-" bounty-addr)
:from (eth/eth-account)
:contract bounty-addr
:method-id (:watch method-ids)
:gas-limit nil
:params [token-address]})))
(defn load-bounty-contract [addr]
(MultiSigTokenWallet/load addr
(create-web3j)
(creds)
@eth/web3j-obj
(eth/creds)
(eth/gas-price)
(BigInteger/valueOf 500000)))

View File

@ -1,6 +1,5 @@
(ns commiteth.eth.token-registry
(:require [commiteth.eth.core :as eth
:refer [create-web3j creds]]
(:require [commiteth.eth.core :as eth]
[commiteth.config :refer [env]]
[clojure.tools.logging :as log])
(:import [org.web3j
@ -23,8 +22,8 @@
(defn- load-tokenreg-contract [addr]
(TokenReg/load addr
(create-web3j)
(creds)
@eth/web3j-obj
(eth/creds)
(eth/gas-price)
(BigInteger/valueOf 21000)))
@ -59,9 +58,8 @@
(defn deploy-parity-tokenreg
"Deploy an instance of parity token-registry to current network"
[]
(let [web3j (create-web3j)]
(TokenReg/deploy web3j
(creds)
(TokenReg/deploy @eth/web3j-obj
(eth/creds)
(eth/gas-price)
(BigInteger/valueOf 4000000) ;; gas limit
BigInteger/ZERO)))
BigInteger/ZERO))

View File

@ -109,10 +109,14 @@
renames {:user_name :display-name
:user_avatar_url :avatar-url
:issue_title :issue-title
:pr_title :pr-title
:pr_number :pr-number
:pr_id :pr-id
:type :item-type
:repo_name :repo-name
:repo_owner :repo-owner
:issue_number :issue-number
:issue_id :issue-id
:value_usd :value-usd
:claim_count :claim-count
:balance_eth :balance-eth

View File

@ -115,18 +115,18 @@
(defn handle-claim
[issue user-id login name avatar_url owner repo repo-id pr-id pr-number head-sha merged? event-type]
[issue user-id login name avatar_url owner repo repo-id pr-id pr-number pr-title head-sha merged? event-type]
(users/create-user user-id login name nil avatar_url)
(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
:title pr-title
:user_id user-id
:issue_number (:issue_number issue)
:issue_id (:issue_id issue)
:state event-type}]
;; TODO: in the opened case if the submitting user has no
;; Ethereum address stored, we could post a comment to the
;; Github PR explaining that payout is not possible if the PR is
@ -187,6 +187,7 @@
repo-id
pr-id
pr-number
pr-title
head-sha
merged?
event-type))

View File

@ -126,7 +126,9 @@
tokens
winner-login
true))
(let [execute-hash (multisig/send-all contract-address payout-address)]
(let [execute-hash (multisig/send-all {:contract contract-address
:payout-address payout-address
:internal-tx-id (str "payout-github-issue-" issue-id)})]
(log/infof "issue %s: Payout self-signed, called sign-all(%s) tx: %s" issue-id contract-address payout-address execute-hash)
(db-bounties/update-execute-hash issue-id execute-hash)
(db-bounties/update-winner-login issue-id winner-login)

View File

@ -4,38 +4,73 @@
[commiteth.common :refer [human-time
display-data-page
items-per-page
issue-url]]
issue-url
pull-request-url]]
[commiteth.handlers :as handlers]
[commiteth.db :as db]
[commiteth.ui-model :as ui-model]
[commiteth.subscriptions :as subs]
[commiteth.util :as util]))
(defn display-bounty-claims [claims]
[:div.bounty-claims-container.ph4.pv3
(for [claim claims]
(let [{:keys [avatar-url
pr-title
pr-id
updated
repo-owner
repo-name
pr-number]} claim]
^{:key pr-id}
[:div.bounty-claims-row.open-bounty-item-content.flex
[:div.bounty-claims-icon.pl2
[:div.ui.tiny.circular.image
[:img {:src avatar-url}]]]
[:span.bounty-claims-text.pt2.pl2
[:a.fw5
{:href (pull-request-url repo-owner repo-name pr-number)}
(if pr-title
pr-title
(str "#" pr-number " by " repo-owner " in " repo-name))]
[:span.time.pl2 (human-time updated)]]]))])
(defn blue-arrow-box [image-src]
"generates the appropriate container for a blue arrow"
[:span.blue-arrow-box.pa1
[:img.blue-arrow-image.v-mid {:src image-src}]])
(defn bounty-item [bounty]
(let [open-bounty-claims (rf/subscribe [::subs/open-bounty-claims])]
(fn [bounty]
(let [{avatar-url :repo_owner_avatar_url
owner :repo-owner
repo-name :repo-name
issue-title :issue-title
issue-number :issue-number
issue-id :issue-id
updated :updated
tokens :tokens
balance-eth :balance-eth
value-usd :value-usd
claim-count :claim-count} bounty
claim-count :claim-count
claims :claims} bounty
full-repo (str owner "/" repo-name)
repo-url (str "https://github.com/" full-repo)
repo-link [:a {:href repo-url} full-repo]
issue-link [:a
{:href (issue-url owner repo-name issue-number)}
issue-title]]
[:div.open-bounty-item
issue-title]
open-claims-click #(rf/dispatch [::handlers/open-bounty-claim issue-id])
close-claims-click #(rf/dispatch [::handlers/close-bounty-claim issue-id])
matches-current-issue? (some #{issue-id} @open-bounty-claims)]
[:div
[:div.open-bounty-item.ph4
[:div.open-bounty-item-content
[:div.header issue-link]
[:div.bounty-item-row
[:div.time (human-time updated)]
[:span.bounty-repo-label repo-link]]
[:div.footer-row
[:div.balance-badge "ETH " balance-eth]
(for [[tla balance] tokens]
@ -44,11 +79,20 @@
(str (subs (str tla) 1) " " balance)])
[:span.usd-value-label "Value "] [:span.usd-balance-label (str "$" value-usd)]
(when (> claim-count 0)
[:span.open-claims-label (str claim-count " open claim"
(when (> claim-count 1) "s"))])]]
[:span.open-claims-label
{:on-click (if matches-current-issue?
#(close-claims-click)
#(open-claims-click))}
(str claim-count " open claim"
(when (> claim-count 1) "s"))
(if matches-current-issue?
[blue-arrow-box "blue-arrow-up.png"]
[blue-arrow-box "blue-arrow-down.png"])])]]
[:div.open-bounty-item-icon
[:div.ui.tiny.circular.image
[:img {:src avatar-url}]]]]))
[:img {:src avatar-url}]]]]
(when matches-current-issue?
[display-bounty-claims claims])]))))
(defn bounties-filter-tooltip-value-input-view [label tooltip-open? opts]
[:div.open-bounties-filter-element-tooltip-value-input-container
@ -210,7 +254,7 @@
[:div
(let [left (inc (* (dec page-number) items-per-page))
right (dec (+ left item-count))]
[:div.item-counts-label-and-sorting-container
[:div.item-counts-label-and-sorting-container.ph4
[:div.item-counts-label
[:span (str "Showing " left "-" right " of " total-count)]]
[bounties-sort-view]])
@ -227,7 +271,7 @@
[:div.ui.text.loader.view-loading-label "Loading"]]]
[:div.ui.container.open-bounties-container
{:ref #(reset! container-element %1)}
[:div.open-bounties-header "Bounties"]
[:div.open-bounties-filter-and-sort
[:div.open-bounties-header.ph4.pt4 "Bounties"]
[:div.open-bounties-filter-and-sort.ph4
[bounty-filters-view]]
[bounties-list @bounty-page-data container-element]]))))

View File

@ -48,6 +48,9 @@
(defn issue-url [owner repo number]
(str "https://github.com/" owner "/" repo "/issues/" number))
(defn pull-request-url [owner repo number]
(str "https://github.com/" owner "/" repo "/pull/" number))
(def items-per-page 15)
(defn draw-page-numbers [page-number page-count container-element]
@ -132,7 +135,7 @@
:else
[:div
[draw-items]
[:div.page-nav-container
[:div.page-nav-container.ph4.pb4
[:div.page-direction-container
[draw-rect :backward]
[draw-rect :forward]]

View File

@ -18,6 +18,7 @@
::ui-model/bounty-filter-type|currency nil
::ui-model/bounty-filter-type|date nil
::ui-model/bounty-filter-type|owner nil}
::open-bounty-claims #{}
:owner-bounties {}
:top-hunters []
:activity-feed []})

View File

@ -484,6 +484,16 @@
(.removeEventListener js/window "click" close-dropdown)
(assoc db :user-dropdown-open? false)))
(reg-event-db
::open-bounty-claim
(fn [db [_ opening-issue-id]]
(update db ::db/open-bounty-claims #(conj % opening-issue-id))))
(reg-event-db
::close-bounty-claim
(fn [db [_ closing-issue-id]]
(update db ::db/open-bounty-claims #(disj % closing-issue-id))))
(reg-event-db
::set-open-bounties-sorting-type
(fn [db [_ sorting-type]]

View File

@ -53,18 +53,25 @@
:open-bounties-page
:<- [::filtered-and-sorted-open-bounties]
:<- [:page-number]
(fn [[open-bounties page-number] _]
:<- [:activity-feed]
(fn [[open-bounties page-number activity-feed] _]
(let [total-count (count open-bounties)
start (* (dec page-number) items-per-page)
end (min total-count (+ items-per-page start))
items (subvec open-bounties start end)]
items (->> (subvec open-bounties start end)
(map (fn [bounty]
(let [matching-claims (filter
(fn [claim]
(= (:issue-id claim)
(:issue-id bounty)))
activity-feed)]
(assoc bounty :claims matching-claims)))))]
{:items items
:item-count (count items)
:total-count total-count
:page-number page-number
:page-count (Math/ceil (/ total-count items-per-page))})))
(reg-sub
:owner-bounties
(fn [db _]
@ -146,6 +153,11 @@
(fn [db _]
(:user-dropdown-open? db)))
(reg-sub
::open-bounty-claims
(fn [db _]
(::db/open-bounty-claims db)))
(reg-sub
::open-bounties-sorting-type
(fn [db _]

View File

@ -415,7 +415,6 @@ label[for="input-hidden"] {
background-color: #fff;
border-radius: 10px;
transform: translateY(-45px);
padding: 24px;
.open-bounties-header {
font-family: "PostGrotesk-Medium";
font-size: 21px;
@ -714,6 +713,17 @@ label[for="input-hidden"] {
padding: 4px;
text-align: center;
}
.blue-arrow-box {
width: 24px;
height: 24px;
}
.blue-arrow-image {
width: 13.5px;
height: 6px;
}
}
.open-bounty-item:first-child {
@ -748,8 +758,6 @@ label[for="input-hidden"] {
font-size: 15px;
}
border-bottom: #eaecee 1px solid !important;
.open-bounty-item-content {
width: 80%;
padding: .7em 0 1em;
@ -780,8 +788,28 @@ label[for="input-hidden"] {
flex-direction: row !important;
justify-content: space-between;
width: 80%;
}
.bounty-claims-row {
padding-top: 6px;
padding-bottom: 6px;
}
.bounty-claims-container {
background-color: #FBFBFB;
}
.bounty-claims-icon {
width: 40px;
}
.bounty-claims-text {
a {
text-decoration: none;
color: #42505c;
}
}
.bounty-repo-label a {
margin: auto;
font-family: "PostGrotesk-Book";
@ -804,6 +832,7 @@ label[for="input-hidden"] {
padding-left: 15px;
font-size: 15px;
color: #57a7ed;
cursor: pointer;
}
.activity-item-container {