Bounties view + related backend functionality & more
Changes to bounty/pull-request data model and logic * save git sha of pull request's HEAD instead of possible merge commit and save it for opened, merged and closed events * only look for bounties being accepted in pull-request closed via merge events (no need for compilicated logic with issue-closed event) * pull_request now has issue_id column to make many sql queries simpler Frontend * owner-bounties app-db structure changes * bounties view with confirm pending payout UI Other * removed lots of unused legacy code * bug fixes + refactoring * added comments + TODOs
This commit is contained in:
parent
ec5f07a578
commit
9cb48b5183
|
@ -41,7 +41,8 @@
|
|||
[crypto-equality "1.0.0"]
|
||||
[cheshire "5.7.0"]
|
||||
[mpg "1.3.0"]
|
||||
[pandect "0.6.1"]]
|
||||
[pandect "0.6.1"]
|
||||
[prismatic/plumbing "0.5.3"]]
|
||||
|
||||
:min-lein-version "2.0.0"
|
||||
:source-paths ["src/clj" "src/cljc"]
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
ALTER TABLE "public"."pull_requests"
|
||||
ADD COLUMN "updated" timestamp without time zone DEFAULT timezone('utc'::text, now()),
|
||||
ADD COLUMN "issue_id" integer,
|
||||
ADD UNIQUE ("pr_id");
|
|
@ -121,8 +121,8 @@ INSERT INTO issues (repo_id, issue_id, issue_number, title)
|
|||
FROM issues
|
||||
WHERE repo_id = :repo_id AND issue_id = :issue_id);
|
||||
|
||||
-- :name close-issue! :<! :1
|
||||
-- :doc updates issue with commit id
|
||||
-- :name update-commit-id :<! :1
|
||||
-- :doc updates issue with commit_id
|
||||
UPDATE issues
|
||||
SET commit_id = :commit_id
|
||||
WHERE issue_id = :issue_id
|
||||
|
@ -171,7 +171,7 @@ SELECT
|
|||
transaction_hash
|
||||
FROM issues
|
||||
WHERE contract_address IS NULL
|
||||
AND issues.transaction_hash IS NOT NULL;
|
||||
AND issues.transaction_hash IS NOT NULL;
|
||||
|
||||
-- Pull Requests -------------------------------------------------------------------
|
||||
|
||||
|
@ -181,6 +181,7 @@ INSERT INTO pull_requests (pr_id,
|
|||
repo_id,
|
||||
pr_number,
|
||||
issue_number,
|
||||
issue_id,
|
||||
commit_id,
|
||||
user_id,
|
||||
state)
|
||||
|
@ -188,12 +189,14 @@ VALUES(:pr_id,
|
|||
:repo_id,
|
||||
:pr_number,
|
||||
:issue_number,
|
||||
:issue_id,
|
||||
:commit_id,
|
||||
:user_id,
|
||||
:state)
|
||||
ON CONFLICT (pr_id) DO UPDATE
|
||||
SET
|
||||
state = :state,
|
||||
updated = timezone('utc'::text, now()),
|
||||
commit_id = :commit_id;
|
||||
|
||||
-- Bounties ------------------------------------------------------------------------
|
||||
|
@ -206,7 +209,7 @@ SELECT
|
|||
u.address AS payout_address
|
||||
FROM issues i, pull_requests p, users u
|
||||
WHERE
|
||||
(p.commit_id = i.commit_id OR coalesce(p.issue_number, -1) = i.issue_number)
|
||||
p.issue_id = i.issue_id
|
||||
AND p.repo_id = i.repo_id
|
||||
AND u.id = p.user_id
|
||||
AND i.execute_hash IS NULL;
|
||||
|
@ -216,10 +219,11 @@ AND i.execute_hash IS NULL;
|
|||
SELECT
|
||||
i.contract_address AS contract_address,
|
||||
i.issue_id AS issue_id,
|
||||
u.address AS payout_address
|
||||
u.address AS payout_address,
|
||||
i.execute_hash AS execute_hash
|
||||
FROM issues i, pull_requests p, users u
|
||||
WHERE
|
||||
(p.commit_id = i.commit_id OR coalesce(p.issue_number, -1) = i.issue_number)
|
||||
p.issue_id = i.issue_id
|
||||
AND p.repo_id = i.repo_id
|
||||
AND u.id = p.user_id
|
||||
AND i.confirm_hash IS NULL
|
||||
|
@ -230,10 +234,11 @@ AND i.execute_hash IS NOT NULL;
|
|||
SELECT
|
||||
i.contract_address AS contract_address,
|
||||
i.issue_id AS issue_id,
|
||||
u.address AS payout_address
|
||||
u.address AS payout_address,
|
||||
i.payout_hash AS payout_hash
|
||||
FROM issues i, pull_requests p, users u
|
||||
WHERE
|
||||
(p.commit_id = i.commit_id OR coalesce(p.issue_number, -1) = i.issue_number)
|
||||
p.issue_id = i.issue_id
|
||||
AND p.repo_id = i.repo_id
|
||||
AND u.id = p.user_id
|
||||
AND i.payout_receipt IS NULL
|
||||
|
@ -280,7 +285,7 @@ r.repo_id = i.repo_id
|
|||
AND i.commit_id IS NULL;
|
||||
|
||||
-- :name owner-bounties-list :? :*
|
||||
-- :doc lists fixed issues (bounties awaiting maintainer confirmation)
|
||||
-- :doc all bounty issues for given owner
|
||||
SELECT
|
||||
i.contract_address AS contract_address,
|
||||
i.issue_id AS issue_id,
|
||||
|
@ -291,24 +296,50 @@ SELECT
|
|||
i.confirm_hash AS confirm_hash,
|
||||
i.payout_hash AS payout_hash,
|
||||
i.payout_receipt AS payout_receipt,
|
||||
r.repo AS repo_name,
|
||||
o.address AS owner_address
|
||||
FROM issues i, users o, repositories r
|
||||
WHERE
|
||||
r.repo_id = i.repo_id
|
||||
AND r.user_id = o.id
|
||||
AND r.user_id = :owner_id;
|
||||
-- AND i.confirm_hash IS NOT NULL;
|
||||
|
||||
|
||||
-- :name bounty-claims :? :*
|
||||
-- :doc open, merged and closed PRs referencing given bounty issue
|
||||
SELECT
|
||||
i.contract_address AS contract_address,
|
||||
i.issue_id AS issue_id,
|
||||
i.issue_number AS issue_number,
|
||||
i.title AS issue_title,
|
||||
i.repo_id AS repo_id,
|
||||
i.balance AS balance,
|
||||
i.confirm_hash AS confirm_hash,
|
||||
i.payout_hash AS payout_hash,
|
||||
i.payout_receipt AS payout_receipt,
|
||||
p.state AS pr_state,
|
||||
p.pr_id AS pr_id,
|
||||
p.user_id AS user_id,
|
||||
p.pr_number AS pr_number,
|
||||
u.address AS payout_address,
|
||||
u.login AS user_login,
|
||||
u.name AS user_name,
|
||||
r.login AS owner_name,
|
||||
u.avatar_url AS user_avatar_url,
|
||||
r.login AS owner_login,
|
||||
r.repo AS repo_name,
|
||||
o.address AS owner_address
|
||||
FROM issues i, pull_requests p, users u, users o, repositories r
|
||||
WHERE
|
||||
(p.commit_id = i.commit_id OR coalesce(p.issue_number, -1) = i.issue_number)
|
||||
p.issue_id = i.issue_id
|
||||
AND p.repo_id = i.repo_id
|
||||
AND u.id = p.user_id
|
||||
AND r.repo_id = i.repo_id
|
||||
AND r.user_id = o.id
|
||||
AND r.user_id = :owner_id
|
||||
AND i.confirm_hash IS NOT NULL;
|
||||
AND i.issue_id = :issue_id;
|
||||
--AND i.confirm_hash IS NOT NULL;
|
||||
|
||||
|
||||
|
||||
-- :name owner-issues-list :? :*
|
||||
-- :doc owner's bounty issues with no merged PR
|
||||
|
@ -353,8 +384,7 @@ SELECT
|
|||
i.issue_number AS issue_number,
|
||||
i.balance AS balance,
|
||||
r.login AS login,
|
||||
r.repo AS repo,
|
||||
i.state AS state
|
||||
r.repo AS repo
|
||||
FROM issues i, repositories r
|
||||
WHERE i.issue_number = :issue_number
|
||||
AND r.repo_id = i.repo_id
|
||||
|
|
|
@ -14,6 +14,12 @@
|
|||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/owner-bounties-list con-db {:owner_id owner})))
|
||||
|
||||
(defn bounty-claims
|
||||
[issue-id]
|
||||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/bounty-claims con-db {
|
||||
:issue_id issue-id})))
|
||||
|
||||
(defn list-not-fixed-issues
|
||||
[owner-id]
|
||||
(jdbc/with-db-connection [con-db *db*]
|
||||
|
|
|
@ -12,11 +12,12 @@
|
|||
:issue_number issue-number
|
||||
:title issue-title})))
|
||||
|
||||
(defn close
|
||||
(defn update-commit-id
|
||||
"Updates issue with commit_id"
|
||||
[commit-id issue-id]
|
||||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/close-issue! con-db {:issue_id issue-id :commit_id commit-id})))
|
||||
(db/update-commit-id con-db {:issue_id issue-id
|
||||
:commit_id commit-id})))
|
||||
|
||||
(defn update-transaction-hash
|
||||
"Updates issue with transaction-hash"
|
||||
|
|
|
@ -11,7 +11,8 @@
|
|||
:opened 0
|
||||
:merged 1
|
||||
:closed 2)]
|
||||
(log/debug (assoc pull-request :state state))
|
||||
|
||||
(log/debug "save pr" (assoc pull-request :state state))
|
||||
(jdbc/with-db-connection [con-db *db*]
|
||||
(db/save-pull-request! con-db
|
||||
(assoc pull-request :state state)))))
|
||||
|
|
|
@ -90,6 +90,19 @@
|
|||
github-repos))))
|
||||
|
||||
|
||||
(defn user-bounties [user]
|
||||
(let [owner-bounties (bounties-db/list-owner-bounties (:id user))]
|
||||
owner-bounties
|
||||
(into {}
|
||||
(for [b owner-bounties]
|
||||
[(:issue_id b)
|
||||
(conj b
|
||||
(let [claims (bounties-db/bounty-claims (:issue_id b))
|
||||
balance (:balance b)]
|
||||
{:balance-eth (eth/hex->eth balance 6)
|
||||
:claims claims}))]))))
|
||||
|
||||
|
||||
(defapi service-routes
|
||||
{:swagger {:ui "/swagger-ui"
|
||||
:spec "/swagger.json"
|
||||
|
@ -134,21 +147,22 @@
|
|||
(POST "/bounty/:issue{[0-9]{1,9}}/payout" {:keys [params]}
|
||||
:auth-rules authenticated?
|
||||
:current-user user
|
||||
(do
|
||||
(log/debug "/bounty/X/payout" params)
|
||||
(let [{issue :issue
|
||||
payout-hash :payout-hash} params
|
||||
result (bounties-db/update-payout-hash
|
||||
(Integer/parseInt issue)
|
||||
payout-hash)]
|
||||
(log/debug "result" result)
|
||||
(if (= 1 result)
|
||||
(ok)
|
||||
(internal-server-error))))
|
||||
(internal-server-error)))))
|
||||
(GET "/bounties" []
|
||||
:auth-rules authenticated?
|
||||
:current-user user
|
||||
(log/debug "/user/bounties")
|
||||
(ok (map #(conj % (let [balance (:balance %)]
|
||||
{:balance-eth (eth/hex->eth balance 6)}))
|
||||
(bounties-db/list-owner-bounties (:id user)))))
|
||||
(ok (user-bounties user)))
|
||||
(POST "/repository/toggle" {:keys [params]}
|
||||
:auth-rules authenticated?
|
||||
:current-user user
|
||||
|
|
|
@ -12,7 +12,8 @@
|
|||
[commiteth.util.digest :refer [hex-hmac-sha1]]
|
||||
[compojure.core :refer [defroutes POST]]
|
||||
[crypto.equality :as crypto]
|
||||
[ring.util.http-response :refer [ok]])
|
||||
[ring.util.http-response :refer [ok]]
|
||||
[commiteth.db.bounties :as bounties-db])
|
||||
(:import java.lang.Integer))
|
||||
|
||||
(defn find-issue-event
|
||||
|
@ -51,13 +52,20 @@
|
|||
|
||||
(defn handle-issue-closed
|
||||
;; TODO: does not work in case the issue is closed on github web ui
|
||||
[{{{user :login} :owner repo :name} :repository
|
||||
[{{{owner :login} :owner repo :name} :repository
|
||||
{issue-id :id issue-number :number} :issue}]
|
||||
(log/debug "handle-issue-closed")
|
||||
(future
|
||||
(when-let [commit-id (find-commit-id user repo issue-number ["referenced" "closed"])]
|
||||
(log/debug (format "Issue %s/%s/%s closed with commit %s" user repo issue-number commit-id))
|
||||
(issues/close commit-id issue-id))))
|
||||
(log/debug "handle-issue-closed" owner repo issue-number issue-id)
|
||||
(when-let [commit-id (find-commit-id owner repo issue-number ["referenced" "closed"])]
|
||||
(log/debug (format "Issue %s/%s/%s closed with commit %s" owner repo issue-number commit-id))
|
||||
(log/info "NOT considering event as bounty winner")
|
||||
;; TODO: disabled for now since the system is meant to be used
|
||||
;; exclusively via pull requests. issue closed event without a PR
|
||||
;; closed via merge first means that the referencing commit was
|
||||
;; pushed directly to master and thus never accepted by the
|
||||
;; maintainer (could be that the bounty hunter had write access
|
||||
;; to master, but that scenario should be very rare and better
|
||||
;; not to support it)
|
||||
#_(issues/close commit-id issue-id)))
|
||||
|
||||
(def ^:const keywords
|
||||
[#"(?i)close:?\s+#(\d+)"
|
||||
|
@ -109,6 +117,8 @@
|
|||
avatar_url :avatar_url
|
||||
name :name} :user
|
||||
id :id
|
||||
merged? :merged
|
||||
{head-sha :sha} :head
|
||||
pr-number :number
|
||||
pr-body :body
|
||||
pr-title :title} :pull_request}]
|
||||
|
@ -118,34 +128,39 @@
|
|||
(extract-issue-number pr-body pr-title)
|
||||
(first)
|
||||
(ensure-bounty-issue owner repo))]
|
||||
(log/debug "Referenced bounty issue found" bounty-issue-number)
|
||||
(log/debug "Referenced bounty issue found" repo bounty-issue-number)
|
||||
(users/create-user user-id login name nil avatar_url nil)
|
||||
(let [pr-data {:repo_id repo-id
|
||||
(let [issue (github/get-issue owner repo bounty-issue-number)
|
||||
pr-data {:repo_id repo-id
|
||||
:pr_id id
|
||||
:pr_number pr-number
|
||||
:user_id user-id
|
||||
:issue_number bounty-issue-number
|
||||
:issue_id (: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
|
||||
;; merged
|
||||
(case event-type
|
||||
:opened (do
|
||||
(log/info "PR with reference to bounty issue"
|
||||
bounty-issue-number "opened")
|
||||
(pull-requests/save (merge pr-data {:state :opened
|
||||
:commit_id nil})))
|
||||
:closed (if-let [commit-id (find-commit-id owner
|
||||
repo
|
||||
pr-number
|
||||
["merged"])]
|
||||
:commit_id head-sha})))
|
||||
:closed (if merged?
|
||||
(do (log/info "PR with reference to bounty issue"
|
||||
bounty-issue-number "merged")
|
||||
(pull-requests/save
|
||||
(merge pr-data {:state :merged
|
||||
:commit_id commit-id})))
|
||||
:commit_id head-sha}))
|
||||
(issues/update-commit-id head-sha (:id issue)))
|
||||
(do (log/info "PR with reference to bounty issue"
|
||||
bounty-issue-number "closed with no merge")
|
||||
(pull-requests/save
|
||||
(merge pr-data {:state :closed
|
||||
:commit_id nil}))))))))
|
||||
:commit_id head-sha}))))))))
|
||||
|
||||
|
||||
(defn handle-issue
|
||||
|
|
|
@ -60,7 +60,7 @@
|
|||
(when-let [confirm-hash (wallet/find-confirmation-hash receipt)]
|
||||
(db-bounties/update-confirm-hash issue-id confirm-hash)))))
|
||||
|
||||
(defn update-payout-hash
|
||||
(defn update-payout-receipt
|
||||
"Gets transaction receipt for each confirmed payout and updates payout_hash"
|
||||
[]
|
||||
(doseq [{issue-id :issue_id
|
||||
|
@ -68,7 +68,9 @@
|
|||
(log/debug "confirmed payout:" payout-hash)
|
||||
(when-let [receipt (eth/get-transaction-receipt payout-hash)]
|
||||
(log/info "payout receipt for issue #" issue-id ": " receipt)
|
||||
(db-bounties/update-payout-receipt issue-id receipt))))
|
||||
;;TODO: not sure if saving the transaction-receipt clojure map as
|
||||
;; a string is a good idea
|
||||
(db-bounties/update-payout-receipt issue-id (str receipt)))))
|
||||
|
||||
(defn update-balance
|
||||
[]
|
||||
|
@ -137,7 +139,7 @@
|
|||
:start (restart-scheduler 60000
|
||||
[update-issue-contract-address
|
||||
update-confirm-hash
|
||||
update-payout-hash
|
||||
update-payout-receipt
|
||||
self-sign-bounty
|
||||
update-balance])
|
||||
:stop (stop-scheduler))
|
||||
|
|
|
@ -1,7 +1,63 @@
|
|||
(ns commiteth.bounties
|
||||
(:require [re-frame.core :as rf]))
|
||||
|
||||
|
||||
|
||||
(defn pr-url [{owner :owner_login
|
||||
pr-number :pr_number
|
||||
repo :repo_name}]
|
||||
(str "https://github.com/" owner "/" repo "/pull/" pr-number))
|
||||
|
||||
(defn claim-card [claim]
|
||||
(let [{pr-state :pr_state
|
||||
user-name :user_name
|
||||
avatar-url :user_avatar_url
|
||||
issue-id :issue_id
|
||||
issue-title :issue_title} claim
|
||||
merged? (= 1 (:pr_state claim))
|
||||
paid? ((comp not nil?) (:payout_hash claim))]
|
||||
(println "paid?" paid? "merged?" merged? (and merged? (comp not) paid?))
|
||||
[:div.activity-item
|
||||
[:div.ui.grid.container
|
||||
[:div.left-column
|
||||
[:div.ui.circular.image
|
||||
[:img {:src avatar-url}]]]
|
||||
[:div.content
|
||||
[:div.header user-name]
|
||||
[:div.description "Submitted a claim for " [:a {:href (pr-url claim)}
|
||||
issue-title]]
|
||||
[:div.description (if paid?
|
||||
"(paid)"
|
||||
(str "(" (if merged? "merged" "open") ")"))]
|
||||
[:div.time "1 day ago"] ;; TODO: claim timestamp
|
||||
[:button.ui.button
|
||||
(merge (if (and merged? (not paid?))
|
||||
{}
|
||||
{:disabled true})
|
||||
{:on-click #(rf/dispatch [:confirm-payout claim])})
|
||||
(if paid?
|
||||
"Signed off"
|
||||
"Confirm")]]]]))
|
||||
|
||||
|
||||
(defn claim-list [bounties]
|
||||
(into [:div.activity-item-container]
|
||||
(for [b (keys bounties)
|
||||
claim (:claims (get bounties b))]
|
||||
[claim-card claim])))
|
||||
|
||||
|
||||
(defn bounties-page []
|
||||
(let [owner-bounties (rf/subscribe [:owner-bounties])]
|
||||
(fn []
|
||||
(let [paid-out? #(nil? (:payout_hash %))
|
||||
paid-bounties (into {} (filter paid-out?
|
||||
@owner-bounties))
|
||||
unpaid-bounties (into {} (filter (comp not paid-out?)
|
||||
@owner-bounties))]
|
||||
(println "unpaid-bounties" unpaid-bounties)
|
||||
[:div.ui.container
|
||||
[:div.ui.text "Bounties view coming soon"]]))
|
||||
[:h3 "New claims"]
|
||||
[claim-list unpaid-bounties]
|
||||
[:h3 "Old claims"]
|
||||
[claim-list paid-bounties]]))))
|
||||
|
|
|
@ -175,11 +175,7 @@
|
|||
:token js/token}]))
|
||||
(reset! active-user nil)))
|
||||
|
||||
(defn load-issues []
|
||||
(rf/dispatch [:load-bounties]))
|
||||
|
||||
(defn load-data []
|
||||
(load-issues)
|
||||
(load-user))
|
||||
|
||||
(defonce timer-id (r/atom nil))
|
||||
|
|
|
@ -5,12 +5,7 @@
|
|||
:user nil
|
||||
:repos-loading? false
|
||||
:repos {}
|
||||
:all-bounties []
|
||||
:owner-bounties []
|
||||
:error nil
|
||||
:pagination {}
|
||||
:pagination-props {:page-size 10
|
||||
:pages-max 10}
|
||||
:owner-bounties {}
|
||||
:top-hunters [{:profile-image-url "https://randomuser.me/api/portraits/men/4.jpg"
|
||||
:display-name "Place Holder"
|
||||
:eth-earned "11 000.00"}
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
(:require [commiteth.db :as db]
|
||||
[re-frame.core :refer [dispatch reg-event-db reg-event-fx reg-fx]]
|
||||
[ajax.core :refer [GET POST]]
|
||||
[cuerdas.core :as str]))
|
||||
[cuerdas.core :as str]
|
||||
[plumbing.core :refer [dissoc-in]]))
|
||||
|
||||
(reg-fx
|
||||
:http
|
||||
|
@ -59,14 +60,6 @@
|
|||
(fn [db [_ table page]]
|
||||
(assoc-in db [:pagination table :page] page)))
|
||||
|
||||
(reg-event-db
|
||||
:init-pagination
|
||||
(fn [db [_ bounties]]
|
||||
(let [{page-size :page-size} (:pagination-props db)]
|
||||
(assoc-in db [:pagination :all-bounties]
|
||||
{:page 0
|
||||
:pages (Math/ceil (/ (count bounties) page-size))}))))
|
||||
|
||||
(reg-event-fx
|
||||
:set-active-user
|
||||
(fn [{:keys [db]} [_ user]]
|
||||
|
@ -79,13 +72,6 @@
|
|||
{:db (assoc db :user nil)
|
||||
:redirect {:path "/logout"}}))
|
||||
|
||||
(reg-event-fx
|
||||
:load-bounties
|
||||
(fn [{:keys [db]} [_]]
|
||||
{:db db
|
||||
:http {:method GET
|
||||
:url "/api/bounties/all"
|
||||
:on-success #(dispatch [:set-bounties %])}}))
|
||||
|
||||
(reg-event-fx
|
||||
:save-payout-hash
|
||||
|
@ -93,14 +79,10 @@
|
|||
{:db db
|
||||
:http {:method POST
|
||||
:url (str/format "/api/user/bounty/%s/payout" issue-id)
|
||||
:on-success #(println %)
|
||||
:on-success #(dispatch [:payout-confirmed issue-id])
|
||||
:on-error #(dispatch [:payout-confirm-failed issue-id])
|
||||
:params {:payout-hash payout-hash}}}))
|
||||
|
||||
(reg-event-fx
|
||||
:set-bounties
|
||||
(fn [{:keys [db]} [_ bounties]]
|
||||
{:db (assoc db :all-bounties bounties)
|
||||
:dispatch [:init-pagination bounties]}))
|
||||
|
||||
(reg-event-fx
|
||||
:load-owner-bounties
|
||||
|
@ -221,3 +203,54 @@
|
|||
:clear-updating-address
|
||||
(fn [db _]
|
||||
(dissoc db :updating-address)))
|
||||
|
||||
|
||||
|
||||
(defn send-transaction-callback
|
||||
[issue-id]
|
||||
(println "send-transaction-callback")
|
||||
(fn [error payout-hash]
|
||||
(println "send-transaction-callback fn")
|
||||
(when error
|
||||
(dispatch [:set-flash-message
|
||||
:error
|
||||
(str "Error sending transaction: " error)]))
|
||||
(when payout-hash
|
||||
(dispatch [:save-payout-hash issue-id payout-hash]))))
|
||||
|
||||
|
||||
(reg-event-fx
|
||||
:confirm-payout
|
||||
(fn [{:keys [db]} [_ {issue-id :issue_id
|
||||
owner-address :owner_address
|
||||
contract-address :contract_address
|
||||
confirm-hash :confirm_hash} issue]]
|
||||
(let [send-transaction-fn (aget js/web3 "eth" "sendTransaction")
|
||||
payload {:from owner-address
|
||||
:to contract-address
|
||||
:value 1
|
||||
:data (str "0x797af627" confirm-hash)}]
|
||||
(println "confirm-payout" owner-address to)
|
||||
(try
|
||||
(apply send-transaction-fn [(clj->js payload)
|
||||
(send-transaction-callback issue-id)])
|
||||
{:db (assoc-in db [:owner-bounties issue-id :confirming?] true)}
|
||||
(catch js/Error e
|
||||
{:db (assoc-in db [:owner-bounties issue-id :confirm-failed?] true)
|
||||
:dispatch [:set-flash-message
|
||||
:error
|
||||
(str "Failed to send transaction" e)]})))))
|
||||
|
||||
(reg-event-db
|
||||
:payout-confirmed
|
||||
(fn [db [_ issue-id]]
|
||||
(-> db
|
||||
(dissoc-in [:owner-bounties (:issue_id issue) :confirming?] false)
|
||||
(assoc-in [:owner-bounties (:issue_id issue) :confirmed?] true))))
|
||||
|
||||
(reg-event-db
|
||||
:payout-confirm-failed
|
||||
(fn [db [_ issue-id]]
|
||||
(-> db
|
||||
(dissoc-in [:owner-bounties (:issue_id issue) :confirming?] false)
|
||||
(assoc-in [:owner-bounties (:issue_id issue) :confirm-failed?] true))))
|
||||
|
|
|
@ -1,104 +0,0 @@
|
|||
(ns commiteth.issues
|
||||
(:require [reagent.core :as r]
|
||||
[re-frame.core :as rf]))
|
||||
|
||||
(defn- page-btn
|
||||
[table selected-page current-page]
|
||||
^{:key current-page}
|
||||
[:a {:class (when (= selected-page current-page) "current")
|
||||
:on-click #(rf/dispatch [:set-page table current-page])}
|
||||
(str (inc current-page))])
|
||||
|
||||
(defn pagination [table]
|
||||
(fn []
|
||||
(let [{page :page
|
||||
pages :pages} @(rf/subscribe [:pagination table])
|
||||
{pages-max :pages-max} @(rf/subscribe [:get-in [:pagination-props]])]
|
||||
(when (> pages 1)
|
||||
(let [last-page (dec pages)
|
||||
start-page (max 1 (min (- pages pages-max) (max 1 (- page (quot pages-max 2)))))
|
||||
end-page (min (dec pages) (+ pages-max start-page))
|
||||
gap-left (> start-page 1)
|
||||
gap-right (< end-page last-page)]
|
||||
[:div.paginate-container
|
||||
[:div.pagination
|
||||
[:span.previous-page
|
||||
(let [disabled (= page 0)]
|
||||
{:class (when disabled "disabled")
|
||||
:on-click (when-not disabled
|
||||
#(rf/dispatch [:set-page table (dec page)]))})
|
||||
"Previous"]
|
||||
(page-btn table page 0)
|
||||
(when gap-left [:span.gap "…"])
|
||||
(map #(page-btn table page %) (range start-page end-page))
|
||||
(when gap-right [:span.gap "…"])
|
||||
(page-btn table page last-page)
|
||||
[:span.next-page
|
||||
(let [disabled (= page last-page)]
|
||||
{:class (when disabled "disabled")
|
||||
:on-click (when-not disabled
|
||||
#(rf/dispatch [:set-page table (inc page)]))})
|
||||
"Next"]]])))))
|
||||
|
||||
(defn repo-link
|
||||
[owner repo]
|
||||
(let [coordinates (str owner "/" repo)
|
||||
url (str "https://github.com/" coordinates)]
|
||||
[:span
|
||||
[:svg.octicon.octicon-repo {:height "16"
|
||||
:version "1.1"
|
||||
:viewBox "0 0 16 16"
|
||||
:width "16"}
|
||||
[:path {:d "M4 9H3V8h1v1zm0-3H3v1h1V6zm0-2H3v1h1V4zm0-2H3v1h1V2zm8-1v12c0 .55-.45 1-1 1H6v2l-1.5-1.5L3 16v-2H1c-.55 0-1-.45-1-1V1c0-.55.45-1 1-1h10c.55 0 1 .45 1 1zm-1 10H1v2h2v-1h3v1h5v-2zm0-10H2v9h9V1z"}]]
|
||||
[:a.text-gray {:href url} coordinates]]))
|
||||
|
||||
(defn issue-url
|
||||
[owner repo issue-number]
|
||||
(str "https://github.com/" owner "/" repo "/issues/" issue-number))
|
||||
|
||||
(defn issue-row [{title :issue_title
|
||||
issue-id :issue_id
|
||||
issue-number :issue_number
|
||||
owner :owner_name
|
||||
repo :repo_name}]
|
||||
^{:key issue-id}
|
||||
[:li.issue
|
||||
[:div.d-table.table-fixed.width-full
|
||||
[:div.float-left.pt-3.pl-3
|
||||
[:span.tooltipped.tooltipped-n
|
||||
{:aria-label "Open issue"}
|
||||
[:svg.octicon.octicon-issue-opened.open
|
||||
{:aria-hidden "true",
|
||||
:height "16",
|
||||
:version "1.1",
|
||||
:viewBox "0 0 14 16",
|
||||
:width "14"}
|
||||
[:path
|
||||
{:d
|
||||
"M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"}]]]]
|
||||
[:div.float-left.col-9.p-3.lh-condensed
|
||||
[:a.box-row-link.h4
|
||||
{:href (issue-url owner repo issue-number)} title]
|
||||
[:span.gh-header-number
|
||||
(str " #" issue-number)]
|
||||
[:div.mt-1.text-small.text-gray
|
||||
(repo-link owner repo)]]]])
|
||||
|
||||
(defn issues-list-table
|
||||
[issues-path issue-row-fn]
|
||||
(fn []
|
||||
(let [{page :page} @(rf/subscribe (into [:pagination] issues-path))
|
||||
{page-size :page-size} @(rf/subscribe [:get-in [:pagination-props]])
|
||||
issues (rf/subscribe issues-path)
|
||||
issues-page (->> @issues
|
||||
(drop (* page page-size))
|
||||
(take page-size))]
|
||||
[:div.issues-list-table
|
||||
[:ul.issues-list
|
||||
(map issue-row-fn issues-page)]])))
|
||||
|
||||
(defn issues-page []
|
||||
(fn []
|
||||
[:div.container
|
||||
[(issues-list-table [:all-bounties] issue-row)]
|
||||
[(pagination :all-bounties)]]))
|
|
@ -1,127 +0,0 @@
|
|||
(ns commiteth.manage
|
||||
(:require [re-frame.core :as rf]
|
||||
[commiteth.common :refer [input]]
|
||||
[commiteth.issues :refer [issues-list-table issue-url]]
|
||||
[clojure.set :refer [rename-keys]]))
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
(defn repository-row [repo]
|
||||
(let [{repo-id :id
|
||||
url :html_url
|
||||
name :full_name
|
||||
description :description} repo]
|
||||
^{:key repo-id}
|
||||
[:div.d-table.width-full
|
||||
[:div.d-table.col-12.width-full.py-4.border-bottom.issue
|
||||
#_[checkbox {:value-path [:enabled-repos repo-id]
|
||||
:style {:width 32 :margin-left 10}
|
||||
:on-change #(rf/dispatch [:toggle-repo repo])}]
|
||||
[:div.d-table-cell.col-11.pr-3.v-align-top
|
||||
[:h4.f4
|
||||
[:a {:href url} name]]
|
||||
[:p.text-gray.mt-1 description]]]]))
|
||||
|
||||
(defn repos-list []
|
||||
(let [repos-loading? (rf/subscribe [:repos-loading?])
|
||||
repos (rf/subscribe [:repos])]
|
||||
(fn []
|
||||
[:div
|
||||
(if @repos-loading?
|
||||
[:i.fa.fa-spinner.fa-spin]
|
||||
(map repository-row @repos))])))
|
||||
|
||||
(defn enable-disable-button
|
||||
[button-id disable]
|
||||
(let [button (.getElementById js/document button-id)]
|
||||
(set! (.-disabled button) disable)))
|
||||
|
||||
(defn lock-button
|
||||
[issue-id]
|
||||
(enable-disable-button (str "payout" issue-id) "true"))
|
||||
|
||||
(defn unlock-button
|
||||
[issue-id]
|
||||
(enable-disable-button (str "payout" issue-id) nil))
|
||||
|
||||
(defn send-transaction-callback
|
||||
[issue-id]
|
||||
(fn [error payout-hash]
|
||||
(when error
|
||||
(unlock-button issue-id)
|
||||
(rf/dispatch [:set-error (str "Error sending transaction: " error)]))
|
||||
(when payout-hash
|
||||
(rf/dispatch [:save-payout-hash issue-id payout-hash]))))
|
||||
|
||||
(defn send-transaction
|
||||
[issue]
|
||||
(fn []
|
||||
(let [{issue-id :issue_id
|
||||
owner-address :owner_address
|
||||
contract-address :contract_address
|
||||
confirm-hash :confirm_hash} issue
|
||||
send-transaction-fn (aget js/web3 "eth" "sendTransaction")
|
||||
payload {:from owner-address
|
||||
:to contract-address
|
||||
:value 1
|
||||
:data (str "0x797af627" confirm-hash)}]
|
||||
(println "sending transaction" payload)
|
||||
(lock-button issue-id)
|
||||
(try
|
||||
(apply send-transaction-fn [(clj->js payload) (send-transaction-callback issue-id)])
|
||||
(catch js/Error e (do
|
||||
(unlock-button issue-id)
|
||||
(rf/dispatch [:set-error e])))))))
|
||||
|
||||
(defn issue-row [{title :issue_title
|
||||
issue-id :issue_id
|
||||
issue-number :issue_number
|
||||
owner :owner_name
|
||||
repo :repo_name
|
||||
balance :balance-eth
|
||||
payout-hash :payout_hash
|
||||
payout-receipt :payout_receipt
|
||||
:as issue}]
|
||||
^{:key issue-id}
|
||||
[:li.issue
|
||||
[:div.d-table.table-fixed.width-full
|
||||
[:div.float-left.pt-3.pl-3
|
||||
[:span.tooltipped.tooltipped-n
|
||||
{:aria-label "Closed issue"}
|
||||
[:svg.octicon.octicon-issue-closed.closed
|
||||
{:aria-hidden "true",
|
||||
:height "16",
|
||||
:version "1.1",
|
||||
:viewBox "0 0 14 16",
|
||||
:width "14"}
|
||||
[:path
|
||||
{:d
|
||||
"M7 10h2v2H7v-2zm2-6H7v5h2V4zm1.5 1.5l-1 1L12 9l4-4.5-1-1L12 7l-1.5-1.5zM8 13.7A5.71 5.71 0 0 1 2.3 8c0-3.14 2.56-5.7 5.7-5.7 1.83 0 3.45.88 4.5 2.2l.92-.92A6.947 6.947 0 0 0 8 1C4.14 1 1 4.14 1 8s3.14 7 7 7 7-3.14 7-7l-1.52 1.52c-.66 2.41-2.86 4.19-5.48 4.19v-.01z"}]]]]
|
||||
(if payout-receipt
|
||||
[:span.btn-sm.float-right {:disabled true
|
||||
:style {:margin-top "7px"
|
||||
:margin-right "7px"}}
|
||||
(str "Sent " balance " ETH")]
|
||||
[:button.btn.btn-sm.btn-danger.float-right
|
||||
{:id (str "payout" issue-id)
|
||||
:type "submit"
|
||||
:style {:margin-top "7px"
|
||||
:margin-right "7px"}
|
||||
:disabled (or payout-hash (not (exists? js/web3)))
|
||||
:on-click (send-transaction issue)}
|
||||
(str "Send " balance " ETH")])
|
||||
[:div.float-left.col-9.p-3.lh-condensed
|
||||
[:span.mr-2.float-right.text-gray.text-small
|
||||
(str " #" issue-number)]
|
||||
[:a.box-row-link.h4
|
||||
{:href (issue-url owner repo issue-number)} title]]]])
|
||||
|
||||
(defn manage-page []
|
||||
(fn []
|
||||
[:div.container
|
||||
[:h3 "Bounties"]
|
||||
[(issues-list-table [:owner-bounties] issue-row)]
|
||||
[:h3 "Repositories"]
|
||||
[repos-list]]))
|
|
@ -23,6 +23,11 @@
|
|||
&:active,&:focus {
|
||||
background-color: #97ebe7;
|
||||
}
|
||||
&:disabled,&:disabled:active {
|
||||
background-color: #fff;
|
||||
color: #2f3f44;
|
||||
border: #e7e7e7 solid 0.1em!important;
|
||||
}
|
||||
}
|
||||
.ui.small.button {
|
||||
font-size: 15px!important;
|
||||
|
@ -59,8 +64,8 @@
|
|||
}
|
||||
|
||||
.ui.mini.circular.image {
|
||||
height: 35px;
|
||||
width: 35px!important;
|
||||
height: 32px;
|
||||
width: 32px!important;
|
||||
}
|
||||
|
||||
.ui.header {
|
||||
|
@ -193,15 +198,50 @@ span.dropdown.icon {
|
|||
cursor: default;
|
||||
}
|
||||
|
||||
|
||||
.activity-item-container {
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
.activity-item {
|
||||
.left-column {
|
||||
margin: 0;
|
||||
padding-left: 12px;
|
||||
padding-top: 12px;
|
||||
width: 100px!important;
|
||||
.ui.image {
|
||||
position: relative;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
border: #e7e7e7 solid 0.1em!important;
|
||||
box-shadow: none!important;
|
||||
border-radius: 0.3em!important;
|
||||
padding: 0.8em 1em 1.1em!important;
|
||||
|
||||
flex-direction: row!important;
|
||||
margin: 1em;
|
||||
|
||||
.content {
|
||||
padding: .7em 0 1em;
|
||||
font-family: 'postgrotesk';
|
||||
line-height: 2em;
|
||||
min-width: 300px!important;
|
||||
.header {
|
||||
font-size: 1.2em;
|
||||
font-weight: bold;
|
||||
color: #474951;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.ui.cards>.card {
|
||||
border: #e7e7e7 solid 0.1em;
|
||||
box-shadow: none;
|
||||
|
|
Loading…
Reference in New Issue