Merge remote-tracking branch 'status/develop' into sort-and-filter
This commit is contained in:
commit
161cd7bd99
|
@ -23,4 +23,3 @@ profiles.clj
|
||||||
.idea
|
.idea
|
||||||
resources/contracts
|
resources/contracts
|
||||||
node_modules
|
node_modules
|
||||||
.DS_Store
|
|
||||||
|
|
28
README.md
28
README.md
|
@ -1,11 +1,11 @@
|
||||||
# Commiteth
|
# Status Open Bounty
|
||||||
|
|
||||||
Allows you to set bounties for Github issues, paid out in Ether.
|
Allows you to set bounties for Github issues, paid out in Ether or any ERC-20 token.
|
||||||
|
|
||||||
More information:
|
More information:
|
||||||
http://wiki.status.im/proposals/commiteth/
|
https://wiki.status.im/Status_Open_Bounty
|
||||||
|
|
||||||
Live beta version:
|
Live production version:
|
||||||
https://openbounty.status.im
|
https://openbounty.status.im
|
||||||
The `master` branch is automatically deployed here.
|
The `master` branch is automatically deployed here.
|
||||||
|
|
||||||
|
@ -20,6 +20,26 @@ The `develop` branch is automatically deployed here.
|
||||||
You will need [Leiningen](https://github.com/technomancy/leiningen) 2.0 or above installed.
|
You will need [Leiningen](https://github.com/technomancy/leiningen) 2.0 or above installed.
|
||||||
|
|
||||||
### PostgreSQL
|
### PostgreSQL
|
||||||
|
<<<<<<< HEAD
|
||||||
|
|
||||||
|
Make sure you install [PostgreSQL](https://www.postgresql.org/) and properly set it up:
|
||||||
|
|
||||||
|
```
|
||||||
|
sudo -u postgres psql -c "CREATE USER commiteth WITH PASSWORD 'commiteth';"
|
||||||
|
sudo -u postgres createdb commiteth
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running
|
||||||
|
|
||||||
|
Launch following commands each in its own shell:
|
||||||
|
|
||||||
|
```
|
||||||
|
lein run
|
||||||
|
lein figwheel
|
||||||
|
lein less auto
|
||||||
|
```
|
||||||
|
|
||||||
|
=======
|
||||||
|
|
||||||
Make sure you install [PostgreSQL](https://www.postgresql.org/) and properly set it up:
|
Make sure you install [PostgreSQL](https://www.postgresql.org/) and properly set it up:
|
||||||
|
|
||||||
|
|
|
@ -10,7 +10,7 @@ For testing you will need:
|
||||||
* a Github account with administrative access to one or more repositories
|
* a Github account with administrative access to one or more repositories
|
||||||
* for approving bounty payouts you will additionally need access to an Ethereum wallet. So far, Mist and [MetaMask](https://metamask.io/) have been used, but anything that provides the web3 javascript interface should work.
|
* for approving bounty payouts you will additionally need access to an Ethereum wallet. So far, Mist and [MetaMask](https://metamask.io/) have been used, but anything that provides the web3 javascript interface should work.
|
||||||
|
|
||||||
The developers can be reached on the `#commiteth` channel in the [Status slack](http://slack.status.im/).
|
The developers can be reached on the `#openbounty` channel in the [Status slack](http://slack.status.im/).
|
||||||
|
|
||||||
### Signing up
|
### Signing up
|
||||||
|
|
||||||
|
@ -60,6 +60,6 @@ To remove issue from the Bounties list you can close it in GitHub.
|
||||||
|
|
||||||
### Reporting bugs
|
### Reporting bugs
|
||||||
|
|
||||||
All bugs should be reported as issues in the [CommitETH Github repository](https://github.com/status-im/commiteth/issues).
|
All bugs should be reported as issues in the [OpenBounty Github repository](https://github.com/status-im/open-bounty/issues).
|
||||||
|
|
||||||
Please first check that there is not already a duplicate issue. Issues should contain exact and minimal step-by-step instructions for reproducing the problem.
|
Please first check that there is not already a duplicate issue. Issues should contain exact and minimal step-by-step instructions for reproducing the problem.
|
||||||
|
|
|
@ -33,7 +33,11 @@
|
||||||
<AppenderRef ref="FILE"/>
|
<AppenderRef ref="FILE"/>
|
||||||
</logger>
|
</logger>
|
||||||
<root level="DEBUG">
|
<root level="DEBUG">
|
||||||
<appender-ref ref="STDOUT"/>
|
<!-- <appender-ref ref="STDOUT"/> -->
|
||||||
|
<appender-ref ref="FILE"/>
|
||||||
|
</root>
|
||||||
|
<root level="INFO">
|
||||||
|
<!-- <appender-ref ref="STDOUT"/> -->
|
||||||
<appender-ref ref="FILE"/>
|
<appender-ref ref="FILE"/>
|
||||||
</root>
|
</root>
|
||||||
</configuration>
|
</configuration>
|
||||||
|
|
|
@ -69,7 +69,6 @@
|
||||||
[lein-auto "0.1.2"]
|
[lein-auto "0.1.2"]
|
||||||
[lein-less "1.7.5"]
|
[lein-less "1.7.5"]
|
||||||
[lein-shell "0.5.0"]
|
[lein-shell "0.5.0"]
|
||||||
[cider/cider-nrepl "0.15.0-SNAPSHOT"]
|
|
||||||
[lein-sha-version "0.1.1"]]
|
[lein-sha-version "0.1.1"]]
|
||||||
|
|
||||||
|
|
||||||
|
@ -144,7 +143,7 @@
|
||||||
|
|
||||||
:prep-tasks ["build-contracts" "javac"]
|
:prep-tasks ["build-contracts" "javac"]
|
||||||
:doo {:build "test"}
|
:doo {:build "test"}
|
||||||
:source-paths ["env/dev/clj" "test/clj"]
|
:source-paths ["env/dev/clj" "test/clj" "src/cljs" "env/dev/cljs"]
|
||||||
:resource-paths ["env/dev/resources"]
|
:resource-paths ["env/dev/resources"]
|
||||||
:repl-options {:init-ns user
|
:repl-options {:init-ns user
|
||||||
:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]}
|
:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]}
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,3 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
|
||||||
|
<path fill="#57A7ED" fill-rule="evenodd" d="M7.223 11.293l7.068-7.067a.999.999 0 1 1 1.414 1.414l-6.377 6.377 6.377 6.376a.999.999 0 1 1-1.414 1.415L7.223 12.74a1.001 1.001 0 0 1-.288-.827 1 1 0 0 1 .288-.62z"/>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 307 B |
|
@ -312,12 +312,13 @@
|
||||||
|
|
||||||
(defn wrap-in-try-catch [func]
|
(defn wrap-in-try-catch [func]
|
||||||
(try
|
(try
|
||||||
|
(func)
|
||||||
(catch Throwable t
|
(catch Throwable t
|
||||||
(log/error t))))
|
(log/error t))))
|
||||||
|
|
||||||
(defn run-tasks [tasks]
|
(defn run-tasks [tasks]
|
||||||
(doall
|
(doall
|
||||||
(map (fn [func] (wrap-in-try-catch (func)))
|
(map (fn [func] (wrap-in-try-catch func))
|
||||||
tasks)))
|
tasks)))
|
||||||
|
|
||||||
(defn run-1-min-interval-tasks [time]
|
(defn run-1-min-interval-tasks [time]
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
(:require [re-frame.core :as rf]
|
(:require [re-frame.core :as rf]
|
||||||
[reagent.core :as r]
|
[reagent.core :as r]
|
||||||
[commiteth.common :refer [moment-timestamp
|
[commiteth.common :refer [moment-timestamp
|
||||||
|
display-data-page
|
||||||
issue-url]]))
|
issue-url]]))
|
||||||
|
|
||||||
|
|
||||||
|
@ -56,21 +57,19 @@
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
(defn activity-list [activity-items]
|
(defn activity-list [activity-page-data]
|
||||||
[:div.ui.container.activity-container
|
[:div.ui.container.activity-container
|
||||||
(if (empty? activity-items)
|
(if (empty? (:items activity-page-data))
|
||||||
[:div.view-no-data-container
|
[:div.view-no-data-container
|
||||||
[:p "No recent activity yet"]]
|
[:p "No recent activity yet"]]
|
||||||
(into [:div.ui.items]
|
(display-data-page activity-page-data activity-item :set-activity-page-number))])
|
||||||
(for [item activity-items]
|
|
||||||
^{:key item} [activity-item item])))] )
|
|
||||||
|
|
||||||
(defn activity-page []
|
(defn activity-page []
|
||||||
(let [activity-items (rf/subscribe [:activity-feed])
|
(let [activity-page-data (rf/subscribe [:activities-page])
|
||||||
activity-feed-loading? (rf/subscribe [:get-in [:activity-feed-loading?]])]
|
activity-feed-loading? (rf/subscribe [:get-in [:activity-feed-loading?]])]
|
||||||
(fn []
|
(fn []
|
||||||
(if @activity-feed-loading?
|
(if @activity-feed-loading?
|
||||||
[:div.view-loading-container
|
[:div.view-loading-container
|
||||||
[:div.ui.active.inverted.dimmer
|
[:div.ui.active.inverted.dimmer
|
||||||
[:div.ui.text.loader.view-loading-label "Loading"]]]
|
[:div.ui.text.loader.view-loading-label "Loading"]]]
|
||||||
[activity-list @activity-items]))))
|
[activity-list @activity-page-data]))))
|
||||||
|
|
|
@ -2,6 +2,8 @@
|
||||||
(:require [reagent.core :as r]
|
(:require [reagent.core :as r]
|
||||||
[re-frame.core :as rf]
|
[re-frame.core :as rf]
|
||||||
[commiteth.common :refer [moment-timestamp
|
[commiteth.common :refer [moment-timestamp
|
||||||
|
display-data-page
|
||||||
|
items-per-page
|
||||||
issue-url]]
|
issue-url]]
|
||||||
[commiteth.handlers :as handlers]
|
[commiteth.handlers :as handlers]
|
||||||
[commiteth.db :as db]
|
[commiteth.db :as db]
|
||||||
|
@ -203,26 +205,70 @@
|
||||||
(rf/dispatch [::handlers/set-open-bounties-sorting-type sorting-type]))}
|
(rf/dispatch [::handlers/set-open-bounties-sorting-type sorting-type]))}
|
||||||
(ui-model/bounty-sorting-type->name sorting-type)])])]))))
|
(ui-model/bounty-sorting-type->name sorting-type)])])]))))
|
||||||
|
|
||||||
(defn bounties-list [open-bounties]
|
;(defn bounties-list [open-bounties]
|
||||||
|
; [:div.ui.container.open-bounties-container
|
||||||
|
; [:div.open-bounties-header "Bounties"]
|
||||||
|
; [:div.open-bounties-filter-and-sort
|
||||||
|
; [bounty-filters-view]
|
||||||
|
; [bounties-sort]]
|
||||||
|
; (if (empty? open-bounties)
|
||||||
|
; [:div.view-no-data-container
|
||||||
|
; [:p "No matching bounties found."]]
|
||||||
|
; (into [:div.ui.items]
|
||||||
|
; (for [bounty open-bounties]
|
||||||
|
; [bounty-item bounty])))])
|
||||||
|
|
||||||
|
(defn bounties-list [{:keys [items item-count page-number total-count]
|
||||||
|
:as bounty-page-data}]
|
||||||
[:div.ui.container.open-bounties-container
|
[:div.ui.container.open-bounties-container
|
||||||
[:div.open-bounties-header "Bounties"]
|
[:div.open-bounties-header "Bounties"]
|
||||||
[:div.open-bounties-filter-and-sort
|
[:div.open-bounties-filter-and-sort
|
||||||
[bounty-filters-view]
|
[bounty-filters-view]
|
||||||
[bounties-sort]]
|
[bounties-sort]]
|
||||||
(if (empty? open-bounties)
|
(if (empty? items)
|
||||||
[:div.view-no-data-container
|
[:div.view-no-data-container
|
||||||
[:p "No matching bounties found."]]
|
[:p "No matching bounties found."]]
|
||||||
(into [:div.ui.items]
|
[:div
|
||||||
(for [bounty open-bounties]
|
(let [left (inc (* (dec page-number) items-per-page))
|
||||||
[bounty-item bounty])))])
|
right (dec (+ left item-count))]
|
||||||
|
[:div.item-counts-label
|
||||||
|
[:span (str "Showing " left "-" right " of " total-count)]])
|
||||||
|
(display-data-page bounty-page-data bounty-item :set-bounty-page-number)])])
|
||||||
|
|
||||||
(defn bounties-page []
|
(defn bounties-page []
|
||||||
(let [open-bounties (rf/subscribe [::subs/filtered-and-sorted-open-bounties])
|
(let [bounty-page-data (rf/subscribe [:open-bounties-page])
|
||||||
open-bounties-loading? (rf/subscribe [:get-in [:open-bounties-loading?]])]
|
open-bounties-loading? (rf/subscribe [:get-in [:open-bounties-loading?]])]
|
||||||
(fn []
|
(fn []
|
||||||
(if @open-bounties-loading?
|
(if @open-bounties-loading?
|
||||||
[:div.view-loading-container
|
[:div.view-loading-container
|
||||||
[:div.ui.active.inverted.dimmer
|
[:div.ui.active.inverted.dimmer
|
||||||
[:div.ui.text.loader.view-loading-label "Loading"]]]
|
[:div.ui.text.loader.view-loading-label "Loading"]]]
|
||||||
[bounties-list @open-bounties]))))
|
[bounties-list @bounty-page-data]))))
|
||||||
|
;
|
||||||
|
;(defn bounties-page []
|
||||||
|
; (let [open-bounties (rf/subscribe [::subs/filtered-and-sorted-open-bounties])
|
||||||
|
;=======
|
||||||
|
;(defn bounties-list [{:keys [items item-count page-number total-count]
|
||||||
|
; :as bounty-page-data}]
|
||||||
|
; [:div.ui.container.open-bounties-container
|
||||||
|
; [:div.open-bounties-header "Bounties"]
|
||||||
|
; (if (empty? items)
|
||||||
|
; [:div.view-no-data-container
|
||||||
|
; [:p "No recent activity yet"]]
|
||||||
|
; [:div
|
||||||
|
; (let [left (inc (* (dec page-number) items-per-page))
|
||||||
|
; right (dec (+ left item-count))]
|
||||||
|
; [:div.item-counts-label
|
||||||
|
; [:span (str "Showing " left "-" right " of " total-count)]])
|
||||||
|
; (display-data-page bounty-page-data bounty-item :set-bounty-page-number)])])
|
||||||
|
;
|
||||||
|
;(defn bounties-page []
|
||||||
|
; (let [bounty-page-data (rf/subscribe [:open-bounties-page])
|
||||||
|
;>>>>>>> status/develop
|
||||||
|
; open-bounties-loading? (rf/subscribe [:get-in [:open-bounties-loading?]])]
|
||||||
|
; (fn []
|
||||||
|
; (if @open-bounties-loading?
|
||||||
|
; [:div.view-loading-container
|
||||||
|
; [:div.ui.active.inverted.dimmer
|
||||||
|
; [:div.ui.text.loader.view-loading-label "Loading"]]]
|
||||||
|
; [bounties-list @bounty-page-data]))))
|
||||||
|
|
|
@ -29,3 +29,87 @@
|
||||||
|
|
||||||
(defn issue-url [owner repo number]
|
(defn issue-url [owner repo number]
|
||||||
(str "https://github.com/" owner "/" repo "/issues/" number))
|
(str "https://github.com/" owner "/" repo "/issues/" number))
|
||||||
|
|
||||||
|
(def items-per-page 15)
|
||||||
|
|
||||||
|
(defn draw-page-numbers [page-number page-count set-page-kw]
|
||||||
|
"Draw page numbers for the pagination component.
|
||||||
|
Inserts ellipsis when list is too long, by default
|
||||||
|
max 6 items are allowed"
|
||||||
|
(let [draw-page-num-fn (fn [current? i]
|
||||||
|
^{:key i}
|
||||||
|
[:div.rectangle-rounded
|
||||||
|
(cond-> {}
|
||||||
|
(not current?)
|
||||||
|
(assoc :class "grayed-out"
|
||||||
|
:on-click #(rf/dispatch [set-page-kw i])))
|
||||||
|
i])
|
||||||
|
max-page-nums 6]
|
||||||
|
[:div.page-nums-container
|
||||||
|
(cond (<= page-count max-page-nums)
|
||||||
|
(for [i (map inc (range page-count))]
|
||||||
|
(draw-page-num-fn (= i page-number) i))
|
||||||
|
(<= page-number (- max-page-nums 3))
|
||||||
|
(concat
|
||||||
|
(for [i (map inc (range (- max-page-nums 2)))]
|
||||||
|
(draw-page-num-fn (= i page-number) i))
|
||||||
|
[^{:key (dec max-page-nums)}
|
||||||
|
[:div.page-nav-text [:span "..."]]]
|
||||||
|
[(draw-page-num-fn false page-count)])
|
||||||
|
(>= page-number (- page-count (- max-page-nums 4)))
|
||||||
|
(concat
|
||||||
|
[(draw-page-num-fn false 1)
|
||||||
|
^{:key 2}
|
||||||
|
[:div.page-nav-text [:span "..."]]]
|
||||||
|
(for [i (map inc (range (- page-count 4) page-count))]
|
||||||
|
(draw-page-num-fn (= i page-number) i))
|
||||||
|
)
|
||||||
|
:else
|
||||||
|
(concat
|
||||||
|
[(draw-page-num-fn false 1)
|
||||||
|
^{:key 2} [:div.page-nav-text [:span "..."]]]
|
||||||
|
(for [i [(dec page-number) page-number (inc page-number)]]
|
||||||
|
(draw-page-num-fn (= i page-number) i))
|
||||||
|
[^{:key (dec page-count)} [:div.page-nav-text [:span "..."]]
|
||||||
|
(draw-page-num-fn false page-count)]))]))
|
||||||
|
|
||||||
|
(defn display-data-page [{:keys [items
|
||||||
|
item-count
|
||||||
|
total-count
|
||||||
|
page-number
|
||||||
|
page-count]}
|
||||||
|
draw-item-fn
|
||||||
|
set-page-kw]
|
||||||
|
"Draw data items along with pagination controls"
|
||||||
|
(let [draw-items (fn []
|
||||||
|
(into [:div.ui.items]
|
||||||
|
(for [item items]
|
||||||
|
^{:key item} [draw-item-fn item])))
|
||||||
|
on-direction-click (fn [forward?]
|
||||||
|
#(when (or (and (< page-number page-count)
|
||||||
|
forward?)
|
||||||
|
(and (< 1 page-number)
|
||||||
|
(not forward?)))
|
||||||
|
(rf/dispatch [set-page-kw
|
||||||
|
(if forward?
|
||||||
|
(inc page-number)
|
||||||
|
(dec page-number))])))
|
||||||
|
draw-rect (fn [direction]
|
||||||
|
(let [forward? (= direction :forward)]
|
||||||
|
[:div.rectangle-rounded
|
||||||
|
{:on-click (on-direction-click forward?)}
|
||||||
|
[:img.icon-forward-gray
|
||||||
|
(cond-> {:src "icon-forward-gray.svg"}
|
||||||
|
forward? (assoc :class "flip-horizontal"))]]))]
|
||||||
|
(cond (<= total-count items-per-page)
|
||||||
|
[draw-items]
|
||||||
|
:else
|
||||||
|
[:div
|
||||||
|
[draw-items]
|
||||||
|
[:div.page-nav-container
|
||||||
|
[draw-rect :backward]
|
||||||
|
[draw-rect :forward]
|
||||||
|
[:div.page-nav-text [:span (str "Page " page-number " of " page-count)]]
|
||||||
|
[draw-page-numbers page-number page-count set-page-kw]]])))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,8 @@
|
||||||
:activity-feed-loading? false
|
:activity-feed-loading? false
|
||||||
:open-bounties-loading? false
|
:open-bounties-loading? false
|
||||||
:open-bounties []
|
:open-bounties []
|
||||||
|
:bounty-page-number 1
|
||||||
|
:activity-page-number 1
|
||||||
::open-bounties-sorting-type ::ui-model/bounty-sorting-type|most-recent
|
::open-bounties-sorting-type ::ui-model/bounty-sorting-type|most-recent
|
||||||
::open-bounties-filters {::ui-model/bounty-filter-type|value nil
|
::open-bounties-filters {::ui-model/bounty-filter-type|value nil
|
||||||
::ui-model/bounty-filter-type|currency nil
|
::ui-model/bounty-filter-type|currency nil
|
||||||
|
|
|
@ -68,6 +68,16 @@
|
||||||
(fn [db [_ page]]
|
(fn [db [_ page]]
|
||||||
(assoc db :page page)))
|
(assoc db :page page)))
|
||||||
|
|
||||||
|
(reg-event-db
|
||||||
|
:set-bounty-page-number
|
||||||
|
(fn [db [_ page]]
|
||||||
|
(assoc db :bounty-page-number page)))
|
||||||
|
|
||||||
|
(reg-event-db
|
||||||
|
:set-activity-page-number
|
||||||
|
(fn [db [_ page]]
|
||||||
|
(assoc db :activity-page-number page)))
|
||||||
|
|
||||||
(reg-event-fx
|
(reg-event-fx
|
||||||
:set-flash-message
|
:set-flash-message
|
||||||
(fn [{:keys [db]} [_ type text]]
|
(fn [{:keys [db]} [_ type text]]
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
(ns commiteth.subscriptions
|
(ns commiteth.subscriptions
|
||||||
(:require [re-frame.core :refer [reg-sub]]
|
(:require [re-frame.core :refer [reg-sub]]
|
||||||
[commiteth.db :as db]
|
[commiteth.db :as db]
|
||||||
[commiteth.ui-model :as ui-model]))
|
[commiteth.ui-model :as ui-model]
|
||||||
|
[commiteth.common :refer [items-per-page]]))
|
||||||
|
|
||||||
(reg-sub
|
(reg-sub
|
||||||
:db
|
:db
|
||||||
|
@ -35,7 +36,28 @@
|
||||||
(reg-sub
|
(reg-sub
|
||||||
:open-bounties
|
:open-bounties
|
||||||
(fn [db _]
|
(fn [db _]
|
||||||
(:open-bounties db)))
|
(vec (:open-bounties db))))
|
||||||
|
|
||||||
|
(reg-sub
|
||||||
|
:bounty-page-number
|
||||||
|
(fn [db _]
|
||||||
|
(:bounty-page-number db)))
|
||||||
|
|
||||||
|
(reg-sub
|
||||||
|
:open-bounties-page
|
||||||
|
:<- [::filtered-and-sorted-open-bounties]
|
||||||
|
:<- [:bounty-page-number]
|
||||||
|
(fn [[open-bounties page-number] _]
|
||||||
|
(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 items
|
||||||
|
:item-count (count items)
|
||||||
|
:total-count total-count
|
||||||
|
:page-number page-number
|
||||||
|
:page-count (Math/ceil (/ total-count items-per-page))})))
|
||||||
|
|
||||||
|
|
||||||
(reg-sub
|
(reg-sub
|
||||||
:owner-bounties
|
:owner-bounties
|
||||||
|
@ -55,7 +77,28 @@
|
||||||
(reg-sub
|
(reg-sub
|
||||||
:activity-feed
|
:activity-feed
|
||||||
(fn [db _]
|
(fn [db _]
|
||||||
(:activity-feed db)))
|
(vec (:activity-feed db))))
|
||||||
|
|
||||||
|
(reg-sub
|
||||||
|
:activity-page-number
|
||||||
|
(fn [db _]
|
||||||
|
(:activity-page-number db)))
|
||||||
|
|
||||||
|
(reg-sub
|
||||||
|
:activities-page
|
||||||
|
:<- [:activity-feed]
|
||||||
|
:<- [:activity-page-number]
|
||||||
|
(fn [[activities page-number] _]
|
||||||
|
(let [total-count (count activities)
|
||||||
|
start (* (dec page-number) items-per-page)
|
||||||
|
end (min total-count (+ items-per-page start))
|
||||||
|
items (subvec activities start end)]
|
||||||
|
{: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
|
(reg-sub
|
||||||
:gh-admin-token
|
:gh-admin-token
|
||||||
|
@ -120,4 +163,5 @@
|
||||||
(fn [[open-bounties filters sorting-type] _]
|
(fn [[open-bounties filters sorting-type] _]
|
||||||
(cond->> open-bounties
|
(cond->> open-bounties
|
||||||
filters (ui-model/filter-bounties filters)
|
filters (ui-model/filter-bounties filters)
|
||||||
sorting-type (ui-model/sort-bounties-by-sorting-type sorting-type))))
|
sorting-type (ui-model/sort-bounties-by-sorting-type sorting-type)
|
||||||
|
filter vec)))
|
||||||
|
|
|
@ -1166,3 +1166,85 @@ body {
|
||||||
text-rendering: optimizeLegibility;
|
text-rendering: optimizeLegibility;
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.rectangle-rounded {
|
||||||
|
width: 45px;
|
||||||
|
height: 45px;
|
||||||
|
color: #57a7ed;
|
||||||
|
/*opacity: 0.2;*/
|
||||||
|
border-radius: 22.5px;
|
||||||
|
/*background-color: #57a7ed;*/
|
||||||
|
background-color: rgba(87,167,237,.2);
|
||||||
|
font-family: "PostGrotesk-Medium";
|
||||||
|
font-weight: 500;
|
||||||
|
font-size: 13px;
|
||||||
|
display: flex;
|
||||||
|
margin: 0 6px;
|
||||||
|
flex: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.grayed-out {
|
||||||
|
color: #8d99a4;
|
||||||
|
background-color: #f2f5f8;
|
||||||
|
opacity: 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flip-horizontal {
|
||||||
|
-moz-transform: scaleX(-1);
|
||||||
|
-webkit-transform: scaleX(-1);
|
||||||
|
-o-transform: scaleX(-1);
|
||||||
|
transform: scaleX(-1);
|
||||||
|
-ms-filter: fliph; /*IE*/
|
||||||
|
filter: fliph;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-text {
|
||||||
|
width: 83px;
|
||||||
|
height: 15px;
|
||||||
|
font-family: PostGrotesk;
|
||||||
|
font-size: 15px;
|
||||||
|
text-align: center;
|
||||||
|
color: #8d99a4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-forward-gray {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-nav-container {
|
||||||
|
display: flex;
|
||||||
|
margin: 0 -6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-nums-container {
|
||||||
|
display: flex;
|
||||||
|
margin-left: auto;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-nav-text {
|
||||||
|
font-family: PostGrotesk-Book;
|
||||||
|
font-size: 15px;
|
||||||
|
color: #8d99a4;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
margin: 0 6px;
|
||||||
|
flex: none;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.item-counts-label {
|
||||||
|
margin: auto;
|
||||||
|
font-family: "PostGrotesk-Book";
|
||||||
|
font-size: 15px;
|
||||||
|
color: #8d99a4;
|
||||||
|
padding-top: 8px;
|
||||||
|
padding-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from pages.base_page import BasePageObject
|
from pages.base_page import BasePageObject
|
||||||
from pages.base_element import *
|
from pages.base_element import *
|
||||||
|
from tests import test_data
|
||||||
|
|
||||||
|
|
||||||
class BountiesHeader(BaseText):
|
class BountiesHeader(BaseText):
|
||||||
|
@ -15,11 +16,38 @@ class TopHuntersHeader(BaseText):
|
||||||
super(TopHuntersHeader, self).__init__(driver)
|
super(TopHuntersHeader, self).__init__(driver)
|
||||||
self.locator = self.Locator.css_selector('.top-hunters-header')
|
self.locator = self.Locator.css_selector('.top-hunters-header')
|
||||||
|
|
||||||
|
class BountyTitles(BaseText):
|
||||||
|
|
||||||
|
def __init__(self, driver):
|
||||||
|
super(BountyTitles, self).__init__(driver)
|
||||||
|
self.locator = self.Locator.css_selector('.open-bounty-item-content .header')
|
||||||
|
|
||||||
|
|
||||||
|
class BountyItemRows(BaseText):
|
||||||
|
|
||||||
|
def __init__(self, driver):
|
||||||
|
super(BountyItemRows, self).__init__(driver)
|
||||||
|
self.locator = self.Locator.css_selector('.open-bounty-item-content .bounty-item-row')
|
||||||
|
|
||||||
|
class BountyFooters(BaseText):
|
||||||
|
|
||||||
|
def __init__(self, driver):
|
||||||
|
super(BountyFooters, self).__init__(driver)
|
||||||
|
self.locator = self.Locator.css_selector('.open-bounty-item-content .footer-row')
|
||||||
|
|
||||||
|
|
||||||
class BountiesPage(BasePageObject):
|
class BountiesPage(BasePageObject):
|
||||||
def __init__(self, driver):
|
def __init__(self, driver):
|
||||||
super(BountiesPage, self).__init__(driver)
|
super(BountiesPage, self).__init__(driver)
|
||||||
|
|
||||||
self.driver = driver
|
self.driver = driver
|
||||||
|
|
||||||
self.bounties_header = BountiesHeader(self.driver)
|
self.bounties_header = BountiesHeader(self.driver)
|
||||||
self.top_hunters_header = TopHuntersHeader(self.driver)
|
self.top_hunters_header = TopHuntersHeader(self.driver)
|
||||||
|
self.bounty_titles = BountyTitles(self.driver)
|
||||||
|
self.bounty_item_rows = BountyItemRows(self.driver)
|
||||||
|
self.bounty_footers = BountyFooters(self.driver)
|
||||||
|
|
||||||
|
def get_bounties_page(self):
|
||||||
|
self.driver.get(test_data.config['Common']['url'] + 'app')
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
from pages.base_page import BasePageObject
|
from pages.base_page import BasePageObject
|
||||||
from pages.base_element import *
|
from pages.base_element import *
|
||||||
|
from tests import test_data
|
||||||
|
|
||||||
|
|
||||||
class LoginButton(BaseButton):
|
class LoginButton(BaseButton):
|
||||||
|
@ -20,4 +21,4 @@ class LandingPage(BasePageObject):
|
||||||
self.login_button = LoginButton(self.driver)
|
self.login_button = LoginButton(self.driver)
|
||||||
|
|
||||||
def get_landing_page(self):
|
def get_landing_page(self):
|
||||||
self.driver.get('https://openbounty.status.im:444/')
|
self.driver.get(test_data.config['Common']['url'])
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import time, pytest
|
import time, pytest
|
||||||
from pages.base_element import *
|
from pages.base_element import *
|
||||||
from pages.base_page import BasePageObject
|
from pages.base_page import BasePageObject
|
||||||
|
from tests import test_data
|
||||||
|
|
||||||
|
|
||||||
class EmailEditbox(BaseEditBox):
|
class EmailEditbox(BaseEditBox):
|
||||||
|
@ -84,7 +85,7 @@ class LabelsButton(BaseButton):
|
||||||
class BountyLabel(BaseButton):
|
class BountyLabel(BaseButton):
|
||||||
def __init__(self, driver):
|
def __init__(self, driver):
|
||||||
super(LabelsButton.BountyLabel, self).__init__(driver)
|
super(LabelsButton.BountyLabel, self).__init__(driver)
|
||||||
self.locator = self.Locator.css_selector("[data-name='bounty']")
|
self.locator = self.Locator.css_selector("[data-name='748942015']")
|
||||||
|
|
||||||
class CrossButton(BaseButton):
|
class CrossButton(BaseButton):
|
||||||
def __init__(self, driver):
|
def __init__(self, driver):
|
||||||
|
@ -156,11 +157,14 @@ class GithubPage(BasePageObject):
|
||||||
def create_new_bounty(self):
|
def create_new_bounty(self):
|
||||||
self.get_issues_page()
|
self.get_issues_page()
|
||||||
self.new_issue_button.click()
|
self.new_issue_button.click()
|
||||||
self.issue_title_input.send_keys('auto_test_bounty_%s' % self.time_now)
|
test_data.issue = dict()
|
||||||
|
test_data.issue['title'] = 'auto_test_bounty_%s' % self.time_now
|
||||||
|
self.issue_title_input.send_keys(test_data.issue['title'])
|
||||||
self.labels_button.click()
|
self.labels_button.click()
|
||||||
self.bounty_label.click()
|
self.bounty_label.click()
|
||||||
self.cross_button.click()
|
self.cross_button.click()
|
||||||
self.submit_new_issue_button.click()
|
self.submit_new_issue_button.click()
|
||||||
|
return test_data.issue['title']
|
||||||
|
|
||||||
def get_deployed_contract(self, wait=120):
|
def get_deployed_contract(self, wait=120):
|
||||||
for i in range(wait):
|
for i in range(wait):
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
|
import configparser
|
||||||
|
|
||||||
class TestData(object):
|
class TestData(object):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.test_name = None
|
self.test_name = None
|
||||||
|
self.config = configparser.ConfigParser()
|
||||||
|
|
||||||
|
# define here path to your config.ini file
|
||||||
|
#example - config_example.ini
|
||||||
|
|
||||||
|
self.config.read('config.ini')
|
||||||
|
self.base_case_issue = dict()
|
||||||
|
self.base_case_issue['title'] = 'Very first auto_test_bounty'
|
||||||
|
|
||||||
|
|
||||||
test_data = TestData()
|
test_data = TestData()
|
||||||
|
|
|
@ -39,18 +39,29 @@ class BaseTestCase:
|
||||||
desired_caps['captureHtml'] = False
|
desired_caps['captureHtml'] = False
|
||||||
return desired_caps
|
return desired_caps
|
||||||
|
|
||||||
|
@property
|
||||||
|
def environment(self):
|
||||||
|
return pytest.config.getoption('env')
|
||||||
|
|
||||||
def setup_method(self):
|
def setup_method(self):
|
||||||
|
|
||||||
self.errors = []
|
self.errors = []
|
||||||
|
self.cleanup = None
|
||||||
|
|
||||||
# options = webdriver.ChromeOptions()
|
if self.environment == 'local':
|
||||||
# options.add_argument('--start-fullscreen')
|
options = webdriver.ChromeOptions()
|
||||||
# options.add_extension(path.abspath('/resources/metamask3_12_0.crx'))
|
options.add_argument('--start-fullscreen')
|
||||||
|
options.add_extension(
|
||||||
|
path.abspath(test_data.config['Paths']['tests_absolute'] + 'resources/metamask3_12_0.crx'))
|
||||||
|
# for chromedriver 2.35
|
||||||
|
self.driver = webdriver.Chrome(chrome_options=options)
|
||||||
|
if self.environment == 'sauce':
|
||||||
self.driver = webdriver.Remote(self.executor_sauce_lab,
|
self.driver = webdriver.Remote(self.executor_sauce_lab,
|
||||||
desired_capabilities=self.capabilities_sauce_lab)
|
desired_capabilities=self.capabilities_sauce_lab)
|
||||||
self.driver.implicitly_wait(5)
|
self.driver.implicitly_wait(5)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def verify_no_errors(self):
|
def verify_no_errors(self):
|
||||||
if self.errors:
|
if self.errors:
|
||||||
msg = ''
|
msg = ''
|
||||||
|
@ -59,7 +70,7 @@ class BaseTestCase:
|
||||||
pytest.fail(msg, pytrace=False)
|
pytest.fail(msg, pytrace=False)
|
||||||
|
|
||||||
def teardown_method(self):
|
def teardown_method(self):
|
||||||
|
if self.cleanup:
|
||||||
remove_application(self.driver)
|
remove_application(self.driver)
|
||||||
remove_installation(self.driver)
|
remove_installation(self.driver)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
[Common]
|
||||||
|
;app URL
|
||||||
|
url = https://openbounty.status.im:444/
|
||||||
|
[Paths]
|
||||||
|
;AbsolutePath to 'tests' folder
|
||||||
|
tests_absolute = /usr/dir/open-bounty/test/end-to-end/tests/
|
||||||
|
[ORG]
|
||||||
|
;GitHub credentials for organization
|
||||||
|
gh_login = login
|
||||||
|
gh_password = password
|
||||||
|
;MetaMask password for organization
|
||||||
|
mm_password = password
|
||||||
|
|
|
@ -11,6 +11,10 @@ def pytest_addoption(parser):
|
||||||
action='store',
|
action='store',
|
||||||
default=True,
|
default=True,
|
||||||
help='Display each test step in terminal as plain text: True/False')
|
help='Display each test step in terminal as plain text: True/False')
|
||||||
|
parser.addoption('--env',
|
||||||
|
action='store',
|
||||||
|
default='sauce',
|
||||||
|
help='Specify environment, sauce or local')
|
||||||
|
|
||||||
|
|
||||||
def pytest_configure(config):
|
def pytest_configure(config):
|
||||||
|
|
|
@ -1,22 +1,42 @@
|
||||||
import pytest
|
import pytest
|
||||||
from os import environ
|
from os import environ
|
||||||
from pages.openbounty.landing import LandingPage
|
from pages.openbounty.landing import LandingPage
|
||||||
|
from pages.openbounty.bounties import BountiesPage
|
||||||
from tests.basetestcase import BaseTestCase
|
from tests.basetestcase import BaseTestCase
|
||||||
|
from tests import test_data
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.sanity
|
@pytest.mark.sanity
|
||||||
class TestLogin(BaseTestCase):
|
class TestLogin(BaseTestCase):
|
||||||
|
|
||||||
def test_deploy_new_contract(self):
|
def test_deploy_new_contract(self):
|
||||||
|
self.cleanup = True
|
||||||
landing = LandingPage(self.driver)
|
landing = LandingPage(self.driver)
|
||||||
landing.get_landing_page()
|
landing.get_landing_page()
|
||||||
|
|
||||||
|
# Sign Up to SOB
|
||||||
github = landing.login_button.click()
|
github = landing.login_button.click()
|
||||||
github.sign_in('anna04test',
|
github.sign_in(test_data.config['ORG']['gh_login'],
|
||||||
'f@E23D3H15Rd')
|
test_data.config['ORG']['gh_password'])
|
||||||
assert github.permission_type.text == 'Personal user data'
|
assert github.permission_type.text == 'Personal user data'
|
||||||
bounties_page = github.authorize_sob.click()
|
bounties_page = github.authorize_sob.click()
|
||||||
|
|
||||||
|
# SOB Plugin installation and navigate to "Open bounties"
|
||||||
github.install_sob_plugin()
|
github.install_sob_plugin()
|
||||||
assert bounties_page.bounties_header.text == 'Bounties'
|
assert bounties_page.bounties_header.text == 'Bounties'
|
||||||
assert bounties_page.top_hunters_header.text == 'Top hunters'
|
assert bounties_page.top_hunters_header.text == 'Top 5 hunters'
|
||||||
|
|
||||||
|
# Waiting for deployed contract; test_data.issue created here
|
||||||
github.create_new_bounty()
|
github.create_new_bounty()
|
||||||
github.get_deployed_contract()
|
github.get_deployed_contract()
|
||||||
|
|
||||||
|
# Navigate and check top bounty in "Open bounties"
|
||||||
|
bounties_page = BountiesPage(self.driver)
|
||||||
|
bounties_page.get_bounties_page()
|
||||||
|
titles = bounties_page.bounty_titles.find_elements()
|
||||||
|
assert titles[0].text == test_data.issue['title']
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue