Merge pull request #241 from status-im/develop

Merge develop into master
This commit is contained in:
Vitaliy Vlasov 2018-01-30 11:51:50 +02:00 committed by GitHub
commit 0088cf89f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 286 additions and 111 deletions

1
.gitignore vendored
View File

@ -23,4 +23,3 @@ profiles.clj
.idea .idea
resources/contracts resources/contracts
node_modules node_modules
.DS_Store

View File

@ -1,15 +1,14 @@
# 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.
Live testnet (Ropsten) version: Live testnet (Ropsten) version:
https://openbounty.status.im:444 https://openbounty.status.im:444
The `develop` branch is automatically deployed here. The `develop` branch is automatically deployed here.
@ -20,7 +19,6 @@ 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: Make sure you install [PostgreSQL](https://www.postgresql.org/) and properly set it up:
@ -39,7 +37,6 @@ lein figwheel
lein less auto 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:
@ -56,9 +53,40 @@ Solidity compiler [0.4.15](https://github.com/ethereum/solidity/releases/tag/v0.
Web3j [2.3.0](https://github.com/web3j/web3j/releases/tag/v2.3.0) is required and the command line tools need to be in $PATH. Web3j [2.3.0](https://github.com/web3j/web3j/releases/tag/v2.3.0) is required and the command line tools need to be in $PATH.
## Running ## Application config
Make sure `env/dev/resources/config.edn` is correctly populated. Make sure that `env/dev/resources/config.edn` is correctly populated. Description of config fields is given below:
Key | Description
--- | ---
dev | Currently specifies whether Swagger UI endpoints should be added to routes
port | HTTP port for the Ring web app
nrepl-port | nREPL port for development
jdbc-database-url | PostgreSQL database URL. For instance, URL to local db would be `jdbc:postgresql://localhost/commiteth?user=commiteth&password=commiteth`
server-address | URL and port of local server that can be resolved from public internet. It will be used as a redirect URI during GitHub OAuth authorization process
eth-account | Ethereum account ID for the bot
eth-password | Ethereum account password for the bot
eth-rpc-url | RPC URL to Ethereum node, e.g. Geth. Either local or remote
eth-wallet-file | Location of wallet file. If Geth is run with the parameters as given below, it will reside under `$HOME/.ropsten/keystore`
tokenreg-base-format | Should be set to `:status`
github-client-id | Related to OAuth. Copied from GitHub account Settings->Developer settings->OAuth Apps
github-client-secret | Related to OAuth. Copied from GitHub account Settings->Developer settings->OAuth Apps
github-user | GitHub username for bot account. It is used for posting bounty comments
github-password | GitHub password for bot account
webhook-secret | Secret string to be used when creating a GitHub App
user-whitelist | Set of GitHub user/org IDs to be whitelisted. E.g. `#{"status-im" "your_org"}`
testnet-token-data | Token data map, useful if there are Geth connectivity problems
## GitHub integration
Open Bounty uses both OAuth App and GitHub App integration.
### OAuth App
Follow the steps [here](https://developer.github.com/apps/building-oauth-apps/creating-an-oauth-app/). Specify the value of `:server-address` as "Homepage URL", and `:server-address` + `/callback` as "Authorization callback URL". Be sure to copy Client ID and Client Secret values to `config.edn`.
### GitHub App
Follow the steps [here](https://developer.github.com/apps/building-github-apps/creating-a-github-app/). Be sure to specify `:server-address` + `/webhook-app` as "Webhook URL", and `:webhook-secret` as "Webhook Secret".
## Running
Lauch a local geth node with the bot account unlocked: Lauch a local geth node with the bot account unlocked:
@ -141,6 +169,10 @@ Landing page is static and different CSS and JS due to time constraints.
This copies over necessary artifacts to `resources` dir. This copies over necessary artifacts to `resources` dir.
### Troubleshooting
See the [Cookbook](doc/cookbook.md).
## License ## License
Licensed under the [Affero General Public License v3.0](https://github.com/status-im/commiteth/blob/master/LICENSE.md) Licensed under the [Affero General Public License v3.0](https://github.com/status-im/commiteth/blob/master/LICENSE.md)

18
doc/cookbook.md Normal file
View File

@ -0,0 +1,18 @@
# Cookbook
Here some common tasks/issues are listed along with their solutions.
## Change config and restart the service
- ssh to the host running commiteth
- go to `/opt/commiteth` for prod or `/opt/commiteth-test` for test environment.
- there edit `config.edn`
- restart the service with `sudo service commiteth-test restart`
## Manually add a GitHub repo to SOB app
Sometimes SOB will not
- connect to Postgres instance. Get DB name and username/password from respective `config.edn` (see previous answer). These are set in `:jdbc-database-url` field.
- execute the query, e.g.
```
insert into repositories(repo_id,user_id,owner,repo,hook_id,state,hook_secret,owner_avatar_url) values(116971984,447328, 'aragon', 'aragon-monthly', 0, 2, '', 'https://avatars1.githubusercontent.com/u/24612534?v=4');
```
Note that the value for repo_id field is GitHub's internal repo ID. One way of obtaining it is navigating to `api.github.com/repos/:owner/:repo_name`, e.g. `https://api.github.com/repos/aragon/aragon-monthly`.

View File

@ -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
@ -29,13 +29,9 @@ You should now see `Bounties`, `Activity`, `Repositories` and `Manage Payouts` t
### Creating bounty issues ### Creating bounty issues
Before you can create bounties, you need to have administrative access to one or more GitHub repositories. These can be either in the scope of your personal user account or in the scope of a Github orgnazation. Before you can create bounties, you need to add Open Bounty GitHub App to your account or repos. Go to https://github.com/apps/status-open-bounty-app-test (or link to another GitHub App you've created for testing, as described in the [README](README.md) and click Install. Specify whether access to all org repos or specific repos is granted. This will install webhooks for SOB in your repos.
* click the `Repositories` tab * Request for your account to be whitelisted. Contact [Riot](https://chat.status.im) for more information
* click on the button `Enable Github Account`
* If you have 1 or more Organisation repositories then grant Organisation access to each of them by clicking on the button `Grant`
* grant Status Open Bounty the needed addtional permissions for managing repository webhooks, adding and modifying comments by clicking on the button `Authorize status-open-bounty`
* now you should see your repositories on the `Repositories` tab, click `Add` on one. If your account isn't whitelisted you will see instructions how to request an access. If your account is whitelisted then new `bounty` label will become available in the GitHub repository's labels and a new webhook should now exist for the repository.
* now, add the `bounty` label to a new or an existing issue. This should cause Status Open Bounty to post a new comment for the issue containing an image with text `Deploying contract, please wait` * now, add the `bounty` label to a new or an existing issue. This should cause Status Open Bounty to post a new comment for the issue containing an image with text `Deploying contract, please wait`
* once the contract has been mined, the comment will be updated to contain the bounty contract's address and a QR code * once the contract has been mined, the comment will be updated to contain the bounty contract's address and a QR code
@ -60,6 +56,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.

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>icon_sl</title><g fill="#000"><path d="M12.632 3.316H7.893a2.365 2.365 0 0 0-2.368 2.369v11.821a2.365 2.365 0 0 0 2.368 2.369 2.366 2.366 0 0 0 2.369-2.369v-2.369h2.37c3.254 0 5.91-2.656 5.91-5.911 0-3.254-2.656-5.91-5.91-5.91z"/><path d="M7.905 19.277a1.771 1.771 0 0 1-1.771-1.771V5.673a1.77 1.77 0 0 1 1.759-1.772h4.739a5.336 5.336 0 0 1 5.325 5.325 5.334 5.334 0 0 1-5.325 5.324H9.676v2.956c0 .967-.794 1.771-1.771 1.771z"/><path d="M7.905 18.68a1.185 1.185 0 0 1-1.185-1.186V5.673c0-.645.517-1.173 1.161-1.185h4.751a4.734 4.734 0 0 1 4.726 4.727 4.74 4.74 0 0 1-4.726 4.726H9.09v3.541c0 .666-.529 1.198-1.185 1.198z"/><path d="M7.905 18.094a.596.596 0 0 1-.598-.6V5.673c0-.322.252-.587.574-.598h4.751a4.144 4.144 0 0 1 4.14 4.14 4.145 4.145 0 0 1-4.14 4.14h-4.13v4.139c0 .334-.264.6-.597.6z"/><path d="M7.905 12.768h4.727a3.556 3.556 0 0 0 3.554-3.552 3.55 3.55 0 0 0-3.554-3.554H7.905v7.106z"/><path d="M8.502 12.182V6.26h4.14a2.953 2.953 0 0 1 2.955 2.956 2.954 2.954 0 0 1-2.955 2.956h-4.14v.01z"/><path d="M9.09 11.595V6.857h3.542a2.366 2.366 0 0 1 2.369 2.369 2.366 2.366 0 0 1-2.369 2.369H9.09z"/><path d="M9.676 11.01V7.444h2.956a1.783 1.783 0 0 1 0 3.566H9.676z"/><path d="M10.262 10.411v-2.38h2.37c.654 0 1.195.53 1.195 1.195 0 .668-.528 1.196-1.195 1.196h-2.37v-.011z"/><path d="M10.861 9.825V8.617h1.771c.333 0 .599.265.599.599a.595.595 0 0 1-.599.598h-1.771v.011z"/><ellipse cx="7.827" cy="5.678" rx="2.369" ry="2.369"/><path d="M18.036 16.137a2.361 2.361 0 1 1-3.863 2.716l-3.542-5.062a2.362 2.362 0 0 1 3.865-2.713l3.54 5.059z"/></g></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>icon_sl</title><g fill="#FFF"><path d="M12.632 3.316H7.893a2.365 2.365 0 0 0-2.368 2.369v11.821a2.365 2.365 0 0 0 2.368 2.369 2.366 2.366 0 0 0 2.369-2.369v-2.369h2.37c3.254 0 5.91-2.656 5.91-5.911 0-3.254-2.656-5.91-5.91-5.91z"/><path d="M7.905 19.277a1.771 1.771 0 0 1-1.771-1.771V5.673a1.77 1.77 0 0 1 1.759-1.772h4.739a5.336 5.336 0 0 1 5.325 5.325 5.334 5.334 0 0 1-5.325 5.324H9.676v2.956c0 .967-.794 1.771-1.771 1.771z"/><path d="M7.905 18.68a1.185 1.185 0 0 1-1.185-1.186V5.673c0-.645.517-1.173 1.161-1.185h4.751a4.734 4.734 0 0 1 4.726 4.727 4.74 4.74 0 0 1-4.726 4.726H9.09v3.541c0 .666-.529 1.198-1.185 1.198z"/><path d="M7.905 18.094a.596.596 0 0 1-.598-.6V5.673c0-.322.252-.587.574-.598h4.751a4.144 4.144 0 0 1 4.14 4.14 4.145 4.145 0 0 1-4.14 4.14h-4.13v4.139c0 .334-.264.6-.597.6z"/><path d="M7.905 12.768h4.727a3.556 3.556 0 0 0 3.554-3.552 3.55 3.55 0 0 0-3.554-3.554H7.905v7.106z"/><path d="M8.502 12.182V6.26h4.14a2.953 2.953 0 0 1 2.955 2.956 2.954 2.954 0 0 1-2.955 2.956h-4.14v.01z"/><path d="M9.09 11.595V6.857h3.542a2.366 2.366 0 0 1 2.369 2.369 2.366 2.366 0 0 1-2.369 2.369H9.09z"/><path d="M9.676 11.01V7.444h2.956a1.783 1.783 0 0 1 0 3.566H9.676z"/><path d="M10.262 10.411v-2.38h2.37c.654 0 1.195.53 1.195 1.195 0 .668-.528 1.196-1.195 1.196h-2.37v-.011z"/><path d="M10.861 9.825V8.617h1.771c.333 0 .599.265.599.599a.595.595 0 0 1-.599.598h-1.771v.011z"/><ellipse cx="7.827" cy="5.678" rx="2.369" ry="2.369"/><path d="M18.036 16.137a2.361 2.361 0 1 1-3.863 2.716l-3.542-5.062a2.362 2.362 0 0 1 3.865-2.713l3.54 5.059z"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -72,7 +72,7 @@ height="0" width="0" style="display:none;visibility:hidden"></iframe></noscript>
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
ga('create', 'UA-79146816-2', 'auto'); ga('create', 'UA-79146816-1', 'auto');
ga('send', 'pageview'); ga('send', 'pageview');
</script> </script>
<script type="text/javascript" id="hs-script-loader" async defer src="//js.hs-scripts.com/3954379.js"></script> <script type="text/javascript" id="hs-script-loader" async defer src="//js.hs-scripts.com/3954379.js"></script>

View File

@ -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
items-per-page
display-data-page display-data-page
issue-url]])) issue-url]]))
@ -55,21 +56,29 @@
(str (subs (str tla) 1) " " balance)])]) (str (subs (str tla) 1) " " balance)])])
[:div.time (moment-timestamp updated)]]]]) [:div.time (moment-timestamp updated)]]]])
(defn activity-list [{:keys [items item-count page-number total-count]
:as activity-page-data}
(defn activity-list [activity-page-data] container-element]
[:div.ui.container.activity-container (if (empty? (:items activity-page-data))
(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"]] [:div
(display-data-page activity-page-data activity-item :set-activity-page-number))]) (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 activity-page-data activity-item container-element)]))
(defn activity-page [] (defn activity-page []
(let [activity-page-data (rf/subscribe [:activities-page]) (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?]])
container-element (atom nil)]
(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-page-data])))) [:div.ui.container.activity-container
{:ref #(reset! container-element %1)}
[:div.activity-header "Activities"]
[activity-list @activity-page-data container-element]]))))

View File

@ -1,5 +1,6 @@
(ns commiteth.bounties (ns commiteth.bounties
(:require [re-frame.core :as rf] (:require [re-frame.core :as rf]
[reagent.core :as r]
[commiteth.common :refer [moment-timestamp [commiteth.common :refer [moment-timestamp
display-data-page display-data-page
items-per-page items-per-page
@ -45,25 +46,29 @@
[:img {:src avatar-url}]]]])) [:img {:src avatar-url}]]]]))
(defn bounties-list [{:keys [items item-count page-number total-count] (defn bounties-list [{:keys [items item-count page-number total-count]
:as bounty-page-data}] :as bounty-page-data}
[:div.ui.container.open-bounties-container container-element]
[:div.open-bounties-header "Bounties"] (if (empty? items)
(if (empty? items) [:div.view-no-data-container
[:div.view-no-data-container [:p "No recent activity yet"]]
[:p "No recent activity yet"]] [:div
[:div (let [left (inc (* (dec page-number) items-per-page))
(let [left (inc (* (dec page-number) items-per-page)) right (dec (+ left item-count))]
right (dec (+ left item-count))] [:div.item-counts-label
[:div.item-counts-label [:span (str "Showing " left "-" right " of " total-count)]])
[:span (str "Showing " left "-" right " of " total-count)]]) (display-data-page bounty-page-data bounty-item container-element)]))
(display-data-page bounty-page-data bounty-item :set-bounty-page-number)])])
(defn bounties-page [] (defn bounties-page []
(let [bounty-page-data (rf/subscribe [:open-bounties-page]) (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?]])
container-element (atom nil)]
(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 @bounty-page-data])))) [:div.ui.container.open-bounties-container
{:ref #(reset! container-element %1)}
[:div.open-bounties-header "Bounties"]
[bounties-list @bounty-page-data container-element]]))
))

View File

@ -1,6 +1,7 @@
(ns commiteth.common (ns commiteth.common
(:require [reagent.core :as r] (:require [reagent.core :as r]
[re-frame.core :as rf] [re-frame.core :as rf]
[clojure.string :as str]
[cljsjs.moment])) [cljsjs.moment]))
(defn input [val-ratom props] (defn input [val-ratom props]
@ -32,17 +33,20 @@
(def items-per-page 15) (def items-per-page 15)
(defn draw-page-numbers [page-number page-count set-page-kw] (defn draw-page-numbers [page-number page-count container-element]
"Draw page numbers for the pagination component. "Draw page numbers for the pagination component.
Inserts ellipsis when list is too long, by default Inserts ellipsis when list is too long, by default
max 6 items are allowed" max 6 items are allowed"
(let [draw-page-num-fn (fn [current? i] (let [draw-page-num-fn (fn [current? i]
^{:key i} ^{:key i}
[:div.rectangle-rounded [:div.rectangle-rounded
(cond-> {} (if current?
(not current?) {:class "page-num-active"}
(assoc :class "grayed-out" {:class "grayed-out-page-num"
:on-click #(rf/dispatch [set-page-kw i]))) :on-click #(do
(rf/dispatch [:set-page-number i])
(when @container-element
(.scrollIntoView @container-element)))})
i]) i])
max-page-nums 6] max-page-nums 6]
[:div.page-nums-container [:div.page-nums-container
@ -79,7 +83,7 @@
page-number page-number
page-count]} page-count]}
draw-item-fn draw-item-fn
set-page-kw] container-element]
"Draw data items along with pagination controls" "Draw data items along with pagination controls"
(let [draw-items (fn [] (let [draw-items (fn []
(into [:div.ui.items] (into [:div.ui.items]
@ -90,14 +94,19 @@
forward?) forward?)
(and (< 1 page-number) (and (< 1 page-number)
(not forward?))) (not forward?)))
(rf/dispatch [set-page-kw (rf/dispatch [:set-page-number
(if forward? (if forward?
(inc page-number) (inc page-number)
(dec page-number))]))) (dec page-number))])
(when @container-element
(.scrollIntoView @container-element))))
draw-rect (fn [direction] draw-rect (fn [direction]
(let [forward? (= direction :forward)] (let [forward? (= direction :forward)
gray-out? (or (and forward? (= page-number page-count))
(and (not forward?) (= page-number 1)))]
[:div.rectangle-rounded [:div.rectangle-rounded
{:on-click (on-direction-click forward?)} (cond-> {:on-click (on-direction-click forward?)}
gray-out? (assoc :class "grayed-out-direction"))
[:img.icon-forward-gray [:img.icon-forward-gray
(cond-> {:src "icon-forward-gray.svg"} (cond-> {:src "icon-forward-gray.svg"}
forward? (assoc :class "flip-horizontal"))]]))] forward? (assoc :class "flip-horizontal"))]]))]
@ -107,9 +116,10 @@
[:div [:div
[draw-items] [draw-items]
[:div.page-nav-container [:div.page-nav-container
[draw-rect :backward] [:div.page-direction-container
[draw-rect :forward] [draw-rect :backward]
[draw-rect :forward]]
[:div.page-nav-text [:span (str "Page " page-number " of " page-count)]] [:div.page-nav-text [:span (str "Page " page-number " of " page-count)]]
[draw-page-numbers page-number page-count set-page-kw]]]))) [draw-page-numbers page-number page-count container-element]]])))

View File

@ -8,8 +8,7 @@
:activity-feed-loading? false :activity-feed-loading? false
:open-bounties-loading? false :open-bounties-loading? false
:open-bounties [] :open-bounties []
:bounty-page-number 1 :page-number 1
:activity-page-number 1
:owner-bounties {} :owner-bounties {}
:top-hunters [] :top-hunters []
:activity-feed []}) :activity-feed []})

View File

@ -38,7 +38,6 @@
(println "redirecting to" path) (println "redirecting to" path)
(set! (.-pathname js/location) path))) (set! (.-pathname js/location) path)))
(reg-event-fx (reg-event-fx
:initialize-db :initialize-db
[(inject-cofx :store)] [(inject-cofx :store)]
@ -64,19 +63,15 @@
(reg-event-db (reg-event-db
:set-active-page :set-active-page
(fn [db [_ page]] (fn [db [_ page]]
(assoc db :page page))) (assoc db :page page
:page-number 1)))
(reg-event-db (reg-event-db
:set-bounty-page-number :set-page-number
(fn [db [_ page]] (fn [db [_ page]]
(assoc db :bounty-page-number page))) (assoc db :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
@ -91,7 +86,6 @@
(fn [db _] (fn [db _]
(dissoc db :flash-message))) (dissoc db :flash-message)))
(defn assoc-in-if-not-empty [m path val] (defn assoc-in-if-not-empty [m path val]
(if (seq val) (if (seq val)
(assoc-in m path val) (assoc-in m path val)

View File

@ -37,14 +37,14 @@
(vec (:open-bounties db)))) (vec (:open-bounties db))))
(reg-sub (reg-sub
:bounty-page-number :page-number
(fn [db _] (fn [db _]
(:bounty-page-number db))) (:page-number db)))
(reg-sub (reg-sub
:open-bounties-page :open-bounties-page
:<- [:open-bounties] :<- [:open-bounties]
:<- [:bounty-page-number] :<- [:page-number]
(fn [[open-bounties page-number] _] (fn [[open-bounties page-number] _]
(let [total-count (count open-bounties) (let [total-count (count open-bounties)
start (* (dec page-number) items-per-page) start (* (dec page-number) items-per-page)
@ -77,15 +77,10 @@
(fn [db _] (fn [db _]
(vec (:activity-feed db)))) (vec (:activity-feed db))))
(reg-sub
:activity-page-number
(fn [db _]
(:activity-page-number db)))
(reg-sub (reg-sub
:activities-page :activities-page
:<- [:activity-feed] :<- [:activity-feed]
:<- [:activity-page-number] :<- [:page-number]
(fn [[activities page-number] _] (fn [[activities page-number] _]
(let [total-count (count activities) (let [total-count (count activities)
start (* (dec page-number) items-per-page) start (* (dec page-number) items-per-page)

View File

@ -523,7 +523,7 @@
} }
} }
border: #e7e7e7 solid 0.1em!important; border-bottom: #eaecee 1px solid !important;
box-shadow: none!important; box-shadow: none!important;
border-radius: 0.3em!important; border-radius: 0.3em!important;
padding: 0.8em 1em 1.1em!important; padding: 0.8em 1em 1.1em!important;
@ -588,9 +588,16 @@
} }
.activity-container { .activity-container {
background-color: #fff;
transform: translate(0, -45px); transform: translate(0, -45px);
border-radius: 10px; border-radius: 10px;
padding: 24px;
.activity-header {
font-family: "PostGrotesk-Medium";
font-size: 21px;
font-weight: 500;
color: #42505c;
}
} }
.footer-row { .footer-row {
@ -887,14 +894,26 @@ body {
flex: none; flex: none;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
cursor: pointer;
} }
.grayed-out { .page-num-active {
cursor: auto;
}
.grayed-out-page-num {
color: #8d99a4; color: #8d99a4;
background-color: #f2f5f8; background-color: #f2f5f8;
opacity: 1.0; opacity: 1.0;
} }
.grayed-out-direction {
color: #8d99a4;
background-color: #f2f5f8;
opacity: 0.4;
cursor: auto
}
.flip-horizontal { .flip-horizontal {
-moz-transform: scaleX(-1); -moz-transform: scaleX(-1);
-webkit-transform: scaleX(-1); -webkit-transform: scaleX(-1);
@ -904,15 +923,6 @@ body {
filter: fliph; filter: fliph;
} }
.pagination-text {
width: 83px;
height: 15px;
font-family: PostGrotesk;
font-size: 15px;
text-align: center;
color: #8d99a4;
}
.icon-forward-gray { .icon-forward-gray {
width: 24px; width: 24px;
height: 24px; height: 24px;
@ -922,10 +932,24 @@ body {
.page-nav-container { .page-nav-container {
display: flex; display: flex;
margin: 0 -6px; margin: 0 -6px;
@media (max-width: 767px) {
align-items: center;
flex-direction: column;
}
}
.page-direction-container {
display: flex;
@media (max-width: 767px) {
flex-direction: row;
}
} }
.page-nums-container { .page-nums-container {
display: flex; display: flex;
@media (max-width: 767px) {
display: none;
}
margin-left: auto; margin-left: auto;
justify-content: space-between; justify-content: space-between;
} }
@ -940,6 +964,10 @@ body {
flex: none; flex: none;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@media (max-width: 767px) {
margin-top: 12px;
}
} }
.item-counts-label { .item-counts-label {

View File

@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>icon_sl</title><g fill="#000"><path d="M12.632 3.316H7.893a2.365 2.365 0 0 0-2.368 2.369v11.821a2.365 2.365 0 0 0 2.368 2.369 2.366 2.366 0 0 0 2.369-2.369v-2.369h2.37c3.254 0 5.91-2.656 5.91-5.911 0-3.254-2.656-5.91-5.91-5.91z"/><path d="M7.905 19.277a1.771 1.771 0 0 1-1.771-1.771V5.673a1.77 1.77 0 0 1 1.759-1.772h4.739a5.336 5.336 0 0 1 5.325 5.325 5.334 5.334 0 0 1-5.325 5.324H9.676v2.956c0 .967-.794 1.771-1.771 1.771z"/><path d="M7.905 18.68a1.185 1.185 0 0 1-1.185-1.186V5.673c0-.645.517-1.173 1.161-1.185h4.751a4.734 4.734 0 0 1 4.726 4.727 4.74 4.74 0 0 1-4.726 4.726H9.09v3.541c0 .666-.529 1.198-1.185 1.198z"/><path d="M7.905 18.094a.596.596 0 0 1-.598-.6V5.673c0-.322.252-.587.574-.598h4.751a4.144 4.144 0 0 1 4.14 4.14 4.145 4.145 0 0 1-4.14 4.14h-4.13v4.139c0 .334-.264.6-.597.6z"/><path d="M7.905 12.768h4.727a3.556 3.556 0 0 0 3.554-3.552 3.55 3.55 0 0 0-3.554-3.554H7.905v7.106z"/><path d="M8.502 12.182V6.26h4.14a2.953 2.953 0 0 1 2.955 2.956 2.954 2.954 0 0 1-2.955 2.956h-4.14v.01z"/><path d="M9.09 11.595V6.857h3.542a2.366 2.366 0 0 1 2.369 2.369 2.366 2.366 0 0 1-2.369 2.369H9.09z"/><path d="M9.676 11.01V7.444h2.956a1.783 1.783 0 0 1 0 3.566H9.676z"/><path d="M10.262 10.411v-2.38h2.37c.654 0 1.195.53 1.195 1.195 0 .668-.528 1.196-1.195 1.196h-2.37v-.011z"/><path d="M10.861 9.825V8.617h1.771c.333 0 .599.265.599.599a.595.595 0 0 1-.599.598h-1.771v.011z"/><ellipse cx="7.827" cy="5.678" rx="2.369" ry="2.369"/><path d="M18.036 16.137a2.361 2.361 0 1 1-3.863 2.716l-3.542-5.062a2.362 2.362 0 0 1 3.865-2.713l3.54 5.059z"/></g></svg> <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><title>icon_sl</title><g fill="#FFF"><path d="M12.632 3.316H7.893a2.365 2.365 0 0 0-2.368 2.369v11.821a2.365 2.365 0 0 0 2.368 2.369 2.366 2.366 0 0 0 2.369-2.369v-2.369h2.37c3.254 0 5.91-2.656 5.91-5.911 0-3.254-2.656-5.91-5.91-5.91z"/><path d="M7.905 19.277a1.771 1.771 0 0 1-1.771-1.771V5.673a1.77 1.77 0 0 1 1.759-1.772h4.739a5.336 5.336 0 0 1 5.325 5.325 5.334 5.334 0 0 1-5.325 5.324H9.676v2.956c0 .967-.794 1.771-1.771 1.771z"/><path d="M7.905 18.68a1.185 1.185 0 0 1-1.185-1.186V5.673c0-.645.517-1.173 1.161-1.185h4.751a4.734 4.734 0 0 1 4.726 4.727 4.74 4.74 0 0 1-4.726 4.726H9.09v3.541c0 .666-.529 1.198-1.185 1.198z"/><path d="M7.905 18.094a.596.596 0 0 1-.598-.6V5.673c0-.322.252-.587.574-.598h4.751a4.144 4.144 0 0 1 4.14 4.14 4.145 4.145 0 0 1-4.14 4.14h-4.13v4.139c0 .334-.264.6-.597.6z"/><path d="M7.905 12.768h4.727a3.556 3.556 0 0 0 3.554-3.552 3.55 3.55 0 0 0-3.554-3.554H7.905v7.106z"/><path d="M8.502 12.182V6.26h4.14a2.953 2.953 0 0 1 2.955 2.956 2.954 2.954 0 0 1-2.955 2.956h-4.14v.01z"/><path d="M9.09 11.595V6.857h3.542a2.366 2.366 0 0 1 2.369 2.369 2.366 2.366 0 0 1-2.369 2.369H9.09z"/><path d="M9.676 11.01V7.444h2.956a1.783 1.783 0 0 1 0 3.566H9.676z"/><path d="M10.262 10.411v-2.38h2.37c.654 0 1.195.53 1.195 1.195 0 .668-.528 1.196-1.195 1.196h-2.37v-.011z"/><path d="M10.861 9.825V8.617h1.771c.333 0 .599.265.599.599a.595.595 0 0 1-.599.598h-1.771v.011z"/><ellipse cx="7.827" cy="5.678" rx="2.369" ry="2.369"/><path d="M18.036 16.137a2.361 2.361 0 1 1-3.863 2.716l-3.542-5.062a2.362 2.362 0 0 1 3.865-2.713l3.54 5.059z"/></g></svg>

Before

Width:  |  Height:  |  Size: 1.6 KiB

After

Width:  |  Height:  |  Size: 1.6 KiB

View File

@ -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')

View File

@ -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'])

View File

@ -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):

View File

@ -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()

View File

@ -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(
self.driver = webdriver.Remote(self.executor_sauce_lab, path.abspath(test_data.config['Paths']['tests_absolute'] + 'resources/metamask3_12_0.crx'))
desired_capabilities=self.capabilities_sauce_lab) # for chromedriver 2.35
self.driver = webdriver.Chrome(chrome_options=options)
if self.environment == 'sauce':
self.driver = webdriver.Remote(self.executor_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,9 +70,9 @@ 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)
try: try:
self.print_sauce_lab_info(self.driver) self.print_sauce_lab_info(self.driver)

View File

@ -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

View File

@ -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):

View File

@ -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']