Merge remote-tracking branch 'status/develop' into sort-and-filter

This commit is contained in:
pablodip 2018-01-25 10:02:17 +01:00
commit 161cd7bd99
24 changed files with 431 additions and 49 deletions

1
.gitignore vendored
View File

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

View File

@ -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:
http://wiki.status.im/proposals/commiteth/
https://wiki.status.im/Status_Open_Bounty
Live beta version:
Live production version:
https://openbounty.status.im
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.
### 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:

View File

@ -10,7 +10,7 @@ For testing you will need:
* 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.
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
@ -60,6 +60,6 @@ To remove issue from the Bounties list you can close it in GitHub.
### 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.

View File

@ -33,7 +33,11 @@
<AppenderRef ref="FILE"/>
</logger>
<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"/>
</root>
</configuration>

View File

@ -69,7 +69,6 @@
[lein-auto "0.1.2"]
[lein-less "1.7.5"]
[lein-shell "0.5.0"]
[cider/cider-nrepl "0.15.0-SNAPSHOT"]
[lein-sha-version "0.1.1"]]
@ -144,7 +143,7 @@
:prep-tasks ["build-contracts" "javac"]
: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"]
:repl-options {:init-ns user
:nrepl-middleware [cemerick.piggieback/wrap-cljs-repl]}

File diff suppressed because one or more lines are too long

View File

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

View File

@ -53,8 +53,8 @@
new-user?)
(try
(hubspot/create-hubspot-contact (:email user)
(:name user "")
(:login user))
(:name user "")
(:login user))
(catch Throwable t
(log/error "Failed to create hubspot contact" t))))
(assoc (found (str (env :server-address) "/app"))

View File

@ -312,12 +312,13 @@
(defn wrap-in-try-catch [func]
(try
(func)
(catch Throwable t
(log/error t))))
(defn run-tasks [tasks]
(doall
(map (fn [func] (wrap-in-try-catch (func)))
(map (fn [func] (wrap-in-try-catch func))
tasks)))
(defn run-1-min-interval-tasks [time]

View File

@ -2,6 +2,7 @@
(:require [re-frame.core :as rf]
[reagent.core :as r]
[commiteth.common :refer [moment-timestamp
display-data-page
issue-url]]))
@ -56,21 +57,19 @@
(defn activity-list [activity-items]
(defn activity-list [activity-page-data]
[:div.ui.container.activity-container
(if (empty? activity-items)
(if (empty? (:items activity-page-data))
[:div.view-no-data-container
[:p "No recent activity yet"]]
(into [:div.ui.items]
(for [item activity-items]
^{:key item} [activity-item item])))] )
(display-data-page activity-page-data activity-item :set-activity-page-number))])
(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?]])]
(fn []
(if @activity-feed-loading?
[:div.view-loading-container
[:div.ui.active.inverted.dimmer
[:div.ui.text.loader.view-loading-label "Loading"]]]
[activity-list @activity-items]))))
[activity-list @activity-page-data]))))

View File

@ -2,6 +2,8 @@
(:require [reagent.core :as r]
[re-frame.core :as rf]
[commiteth.common :refer [moment-timestamp
display-data-page
items-per-page
issue-url]]
[commiteth.handlers :as handlers]
[commiteth.db :as db]
@ -203,26 +205,70 @@
(rf/dispatch [::handlers/set-open-bounties-sorting-type 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.open-bounties-header "Bounties"]
[:div.open-bounties-filter-and-sort
[bounty-filters-view]
[bounties-sort]]
(if (empty? open-bounties)
(if (empty? items)
[:div.view-no-data-container
[:p "No matching bounties found."]]
(into [:div.ui.items]
(for [bounty open-bounties]
[bounty-item bounty])))])
[: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 [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?]])]
(fn []
(if @open-bounties-loading?
[:div.view-loading-container
[:div.ui.active.inverted.dimmer
[: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]))))

View File

@ -29,3 +29,87 @@
(defn issue-url [owner repo 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]]])))

View File

@ -9,6 +9,8 @@
:activity-feed-loading? false
:open-bounties-loading? false
:open-bounties []
:bounty-page-number 1
:activity-page-number 1
::open-bounties-sorting-type ::ui-model/bounty-sorting-type|most-recent
::open-bounties-filters {::ui-model/bounty-filter-type|value nil
::ui-model/bounty-filter-type|currency nil

View File

@ -68,6 +68,16 @@
(fn [db [_ 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
:set-flash-message
(fn [{:keys [db]} [_ type text]]

View File

@ -1,7 +1,8 @@
(ns commiteth.subscriptions
(:require [re-frame.core :refer [reg-sub]]
[commiteth.db :as db]
[commiteth.ui-model :as ui-model]))
[commiteth.ui-model :as ui-model]
[commiteth.common :refer [items-per-page]]))
(reg-sub
:db
@ -35,7 +36,28 @@
(reg-sub
:open-bounties
(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
:owner-bounties
@ -55,7 +77,28 @@
(reg-sub
:activity-feed
(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
:gh-admin-token
@ -120,4 +163,5 @@
(fn [[open-bounties filters sorting-type] _]
(cond->> open-bounties
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)))

View File

@ -1166,3 +1166,85 @@ body {
text-rendering: optimizeLegibility;
-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;
}

View File

@ -1,5 +1,6 @@
from pages.base_page import BasePageObject
from pages.base_element import *
from tests import test_data
class BountiesHeader(BaseText):
@ -15,11 +16,38 @@ class TopHuntersHeader(BaseText):
super(TopHuntersHeader, self).__init__(driver)
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):
def __init__(self, driver):
super(BountiesPage, self).__init__(driver)
self.driver = driver
self.bounties_header = BountiesHeader(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_element import *
from tests import test_data
class LoginButton(BaseButton):
@ -20,4 +21,4 @@ class LandingPage(BasePageObject):
self.login_button = LoginButton(self.driver)
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
from pages.base_element import *
from pages.base_page import BasePageObject
from tests import test_data
class EmailEditbox(BaseEditBox):
@ -84,7 +85,7 @@ class LabelsButton(BaseButton):
class BountyLabel(BaseButton):
def __init__(self, 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):
def __init__(self, driver):
@ -156,11 +157,14 @@ class GithubPage(BasePageObject):
def create_new_bounty(self):
self.get_issues_page()
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.bounty_label.click()
self.cross_button.click()
self.submit_new_issue_button.click()
return test_data.issue['title']
def get_deployed_contract(self, wait=120):
for i in range(wait):

View File

@ -1,8 +1,17 @@
import configparser
class TestData(object):
def __init__(self):
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()

View File

@ -39,18 +39,29 @@ class BaseTestCase:
desired_caps['captureHtml'] = False
return desired_caps
@property
def environment(self):
return pytest.config.getoption('env')
def setup_method(self):
self.errors = []
self.cleanup = None
# options = webdriver.ChromeOptions()
# options.add_argument('--start-fullscreen')
# options.add_extension(path.abspath('/resources/metamask3_12_0.crx'))
self.driver = webdriver.Remote(self.executor_sauce_lab,
desired_capabilities=self.capabilities_sauce_lab)
if self.environment == 'local':
options = webdriver.ChromeOptions()
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,
desired_capabilities=self.capabilities_sauce_lab)
self.driver.implicitly_wait(5)
def verify_no_errors(self):
if self.errors:
msg = ''
@ -59,9 +70,9 @@ class BaseTestCase:
pytest.fail(msg, pytrace=False)
def teardown_method(self):
remove_application(self.driver)
remove_installation(self.driver)
if self.cleanup:
remove_application(self.driver)
remove_installation(self.driver)
try:
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',
default=True,
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):

View File

@ -1,22 +1,42 @@
import pytest
from os import environ
from pages.openbounty.landing import LandingPage
from pages.openbounty.bounties import BountiesPage
from tests.basetestcase import BaseTestCase
from tests import test_data
@pytest.mark.sanity
class TestLogin(BaseTestCase):
def test_deploy_new_contract(self):
self.cleanup = True
landing = LandingPage(self.driver)
landing.get_landing_page()
# Sign Up to SOB
github = landing.login_button.click()
github.sign_in('anna04test',
'f@E23D3H15Rd')
github.sign_in(test_data.config['ORG']['gh_login'],
test_data.config['ORG']['gh_password'])
assert github.permission_type.text == 'Personal user data'
bounties_page = github.authorize_sob.click()
# SOB Plugin installation and navigate to "Open bounties"
github.install_sob_plugin()
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.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']