diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 00000000..4b55f9e0
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,6 @@
+language: ruby
+
+branches:
+ only:
+ - enterprise
+ - master
diff --git a/Gemfile b/Gemfile
index 5a482325..76b198e5 100644
--- a/Gemfile
+++ b/Gemfile
@@ -1,7 +1,7 @@
source 'https://rubygems.org'
-ruby '2.2.3' if ENV['DYNO']
+ruby '2.3.1' if ENV['DYNO']
-gem 'rails', '4.2.4'
+gem 'rails', '4.2.6'
gem 'dotenv-rails', '~> 2.0.0', :require => 'dotenv/rails-now'
# gem 'pg'
gem 'sass-rails', '~> 5.0'
@@ -10,15 +10,14 @@ gem 'coffee-rails', '~> 4.1.0'
# gem 'therubyracer', platforms: :ruby
gem "ember-cli-rails", '0.1.13', require: nil
gem 'ember-rails-assets'
-#gem "warden-github-rails" , github: "huboard/warden-github-rails"
-gem 'warden-github'
-# TODO: replace this
-gem 'sinatra_auth_github'
gem 'rails_12factor', group: :production
gem 'puma'
gem 'foreman'
gem 'sprockets-rails','3.0.0.beta1', :require => 'sprockets/railtie'
+# warden
+gem 'warden-github', '1.4.0', github: 'huboard/warden-github'
+
gem 'jquery-rails'
gem 'jbuilder', '~> 2.0'
gem 'sdoc', '~> 0.4.0', group: :doc
@@ -46,6 +45,7 @@ gem 'redcarpet'
gem 'pdfkit'
gem 'wkhtmltopdf-heroku'
gem 'private_pub', '1.0.3'
+gem 'sinatra', :require => nil
gem 'sidekiq', "~> 3.0"
# BUNDLE_WITHOUT="development:test:saas"
diff --git a/Gemfile.lock b/Gemfile.lock
index 5be344d2..b4e355d3 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -13,6 +13,15 @@ PATH
enterprise (0.0.1)
rails (~> 4.2.0)
+GIT
+ remote: git://github.com/huboard/warden-github.git
+ revision: 459903681fa00820065348c27639b9f27db9fa55
+ specs:
+ warden-github (1.4.0)
+ activesupport (> 3.0)
+ octokit (> 2.1.0)
+ warden (> 1.0)
+
PATH
remote: vendor/engines/saas
specs:
@@ -30,42 +39,42 @@ GEM
remote: https://rubygems.org/
specs:
CFPropertyList (2.3.2)
- actionmailer (4.2.4)
- actionpack (= 4.2.4)
- actionview (= 4.2.4)
- activejob (= 4.2.4)
+ actionmailer (4.2.6)
+ actionpack (= 4.2.6)
+ actionview (= 4.2.6)
+ activejob (= 4.2.6)
mail (~> 2.5, >= 2.5.4)
rails-dom-testing (~> 1.0, >= 1.0.5)
- actionpack (4.2.4)
- actionview (= 4.2.4)
- activesupport (= 4.2.4)
+ actionpack (4.2.6)
+ actionview (= 4.2.6)
+ activesupport (= 4.2.6)
rack (~> 1.6)
rack-test (~> 0.6.2)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- actionview (4.2.4)
- activesupport (= 4.2.4)
+ actionview (4.2.6)
+ activesupport (= 4.2.6)
builder (~> 3.1)
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
- activejob (4.2.4)
- activesupport (= 4.2.4)
+ activejob (4.2.6)
+ activesupport (= 4.2.6)
globalid (>= 0.3.0)
- activemodel (4.2.4)
- activesupport (= 4.2.4)
+ activemodel (4.2.6)
+ activesupport (= 4.2.6)
builder (~> 3.1)
- activerecord (4.2.4)
- activemodel (= 4.2.4)
- activesupport (= 4.2.4)
+ activerecord (4.2.6)
+ activemodel (= 4.2.6)
+ activesupport (= 4.2.6)
arel (~> 6.0)
- activesupport (4.2.4)
+ activesupport (4.2.6)
i18n (~> 0.7)
json (~> 1.7, >= 1.7.7)
minitest (~> 5.1)
thread_safe (~> 0.3, >= 0.3.4)
tzinfo (~> 1.1)
- addressable (2.3.7)
+ addressable (2.4.0)
analytics-ruby (2.0.13)
annotate (2.6.8)
activerecord (>= 3.2, <= 4.3)
@@ -126,7 +135,7 @@ GEM
sprockets (>= 2.0)
ember-rails-assets (0.1.1)
erubis (2.7.0)
- ethon (0.8.0)
+ ethon (0.9.0)
ffi (>= 1.3.0)
eventmachine (1.0.7)
excon (0.45.4)
@@ -267,7 +276,7 @@ GEM
foreman (0.78.0)
thor (~> 0.19.1)
formatador (0.2.5)
- ghee (0.14.19)
+ ghee (0.14.22)
faraday (~> 0.9)
faraday_middleware (~> 0.9)
hashie (~> 3.3.2)
@@ -301,8 +310,8 @@ GEM
kgio (2.9.3)
loofah (2.0.3)
nokogiri (>= 1.5.9)
- mail (2.6.3)
- mime-types (>= 1.16, < 3)
+ mail (2.6.4)
+ mime-types (>= 1.16, < 4)
memcachier (0.0.2)
meta_request (0.3.4)
callsite (~> 0.0, >= 0.0.11)
@@ -315,14 +324,14 @@ GEM
minitest (5.8.4)
mocha (1.1.0)
metaclass (~> 0.0.1)
- multi_json (1.11.2)
+ multi_json (1.12.1)
multi_xml (0.5.5)
multipart-post (2.0.0)
netrc (0.10.3)
nokogiri (1.6.7.2)
mini_portile2 (~> 2.0.0.rc2)
- octokit (3.8.0)
- sawyer (~> 0.6.0, >= 0.5.3)
+ octokit (4.3.0)
+ sawyer (~> 0.7.0, >= 0.5.3)
pdfkit (0.6.2)
private_pub (1.0.3)
faye
@@ -333,8 +342,7 @@ GEM
pry-byebug (3.2.0)
byebug (~> 5.0)
pry (~> 0.10)
- puma (2.11.1)
- rack (>= 1.1, < 2.0)
+ puma (3.4.0)
rack (1.6.4)
rack-attack (4.2.0)
rack
@@ -344,16 +352,16 @@ GEM
rack
rack-test (0.6.3)
rack (>= 1.0)
- rails (4.2.4)
- actionmailer (= 4.2.4)
- actionpack (= 4.2.4)
- actionview (= 4.2.4)
- activejob (= 4.2.4)
- activemodel (= 4.2.4)
- activerecord (= 4.2.4)
- activesupport (= 4.2.4)
+ rails (4.2.6)
+ actionmailer (= 4.2.6)
+ actionpack (= 4.2.6)
+ actionview (= 4.2.6)
+ activejob (= 4.2.6)
+ activemodel (= 4.2.6)
+ activerecord (= 4.2.6)
+ activesupport (= 4.2.6)
bundler (>= 1.3.0, < 2.0)
- railties (= 4.2.4)
+ railties (= 4.2.6)
sprockets-rails
rails-deprecated_sanitizer (1.0.3)
activesupport (>= 4.2.0.alpha)
@@ -368,9 +376,9 @@ GEM
rails_stdout_logging
rails_serve_static_assets (0.0.4)
rails_stdout_logging (0.0.3)
- railties (4.2.4)
- actionpack (= 4.2.4)
- activesupport (= 4.2.4)
+ railties (4.2.6)
+ actionpack (= 4.2.6)
+ activesupport (= 4.2.6)
rake (>= 0.8.7)
thor (>= 0.18.1, < 2.0)
rake (10.5.0)
@@ -413,8 +421,8 @@ GEM
sprockets (>= 2.8, < 4.0)
sprockets-rails (>= 2.0, < 4.0)
tilt (~> 1.1)
- sawyer (0.6.0)
- addressable (~> 2.3.5)
+ sawyer (0.7.0)
+ addressable (>= 2.3.5, < 2.5)
faraday (~> 0.8, < 0.10)
sdoc (0.4.1)
json (~> 1.7, >= 1.7.7)
@@ -425,13 +433,10 @@ GEM
json
redis (>= 3.0.6)
redis-namespace (>= 1.3.1)
- sinatra (1.4.5)
- rack (~> 1.4)
+ sinatra (1.4.7)
+ rack (~> 1.5)
rack-protection (~> 1.4)
- tilt (~> 1.3, >= 1.3.4)
- sinatra_auth_github (1.2.0)
- sinatra (~> 1.0)
- warden-github (~> 1.2.0)
+ tilt (>= 1.3, < 3)
slop (3.6.0)
solid_use_case (2.1.1)
deterministic (~> 0.6.0)
@@ -468,12 +473,8 @@ GEM
execjs (>= 0.3.0)
json (>= 1.8.0)
uuidtools (2.1.5)
- warden (1.2.3)
+ warden (1.2.6)
rack (>= 1.0)
- warden-github (1.2.0)
- activesupport (> 3.0)
- octokit (> 2.1.0)
- warden (> 1.0)
websocket-driver (0.5.3)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.2)
@@ -518,7 +519,7 @@ DEPENDENCIES
pry-byebug
puma
rack-attack
- rails (= 4.2.4)
+ rails (= 4.2.6)
rails_12factor
raygun4ruby
redcarpet
@@ -529,12 +530,12 @@ DEPENDENCIES
sass-rails (~> 5.0)
sdoc (~> 0.4.0)
sidekiq (~> 3.0)
- sinatra_auth_github
+ sinatra
solid_use_case
spring
sprockets-rails (= 3.0.0.beta1)
stripe-rails
sucker_punch
uglifier (>= 1.3.0)
- warden-github
+ warden-github (= 1.4.0)!
wkhtmltopdf-heroku
diff --git a/README.md b/README.md
index 86af1176..1acb0440 100644
--- a/README.md
+++ b/README.md
@@ -2,6 +2,7 @@
[](https://huboard.com)
+[](https://travis-ci.org/huboard/huboard-web)
Check out our [parent repository](https://github.com/huboard/huboard#github-issues-made-awesome) for details!
## License
diff --git a/app/assets/images/GitHub-Mark-Light-32px.png b/app/assets/images/GitHub-Mark-Light-32px.png
new file mode 100644
index 00000000..628da97c
Binary files /dev/null and b/app/assets/images/GitHub-Mark-Light-32px.png differ
diff --git a/app/assets/images/marketing/header-bg--github.png b/app/assets/images/marketing/header-bg--github.png
index 119844fe..28927a55 100644
Binary files a/app/assets/images/marketing/header-bg--github.png and b/app/assets/images/marketing/header-bg--github.png differ
diff --git a/app/assets/stylesheets/_buttons.scss b/app/assets/stylesheets/_buttons.scss
index 2d97afad..e7b6d379 100644
--- a/app/assets/stylesheets/_buttons.scss
+++ b/app/assets/stylesheets/_buttons.scss
@@ -37,7 +37,14 @@
left: 10px;
}
}
-
+
+ &.hb-octo-button {
+ img {
+ position: relative;
+ right: 7px;
+ bottom: 1px;
+ }
+ }
}
.hb-button-group {
diff --git a/app/assets/stylesheets/_colors.scss b/app/assets/stylesheets/_colors.scss
index 2e897247..89f4bf7a 100644
--- a/app/assets/stylesheets/_colors.scss
+++ b/app/assets/stylesheets/_colors.scss
@@ -2,6 +2,7 @@ $lighterGrey: #CCC;
$lightGrey: #999;
$grey: #666;
$darkGrey: #444;
+$navGrey: #F3F3F3;
$red: red;
diff --git a/app/assets/stylesheets/_repos.scss b/app/assets/stylesheets/_repos.scss
index 344e3619..aec9f02e 100644
--- a/app/assets/stylesheets/_repos.scss
+++ b/app/assets/stylesheets/_repos.scss
@@ -24,6 +24,7 @@
color:rgba(#ee9c49, 0.7);
}
}
+
}
ul.repos, ul.repos li {
@@ -66,4 +67,56 @@ ul.repos li a.btn {
margin-top:7px;
}
+.repo-list {
+ .label {
+ &.label-success {
+ padding: 4px;
+ }
+ &.label-warning {
+ padding: 4px;
+ }
+ &.label-warning-grey {
+ padding: 4px;
+ background-color: $lightGrey;
+ }
+ }
+
+ .nav-tabs {
+ .disabled {
+ cursor: not-allowed;
+ a {
+ pointer-events: none;
+ }
+ }
+ }
+}
+
+.dashboard--alert {
+ h5 {
+ color: black;
+ i.ui-icon-alert {
+ position: relative;
+ top: 1px;
+ color: #ee9c49;
+ }
+ }
+ &.public-only-hero {
+ position: relative;
+ bottom: 10px;
+ text-align: center;
+ }
+ &.public-only-hero:before {
+ content: '';
+ width: 0px; height: 5px;
+ margin: -19px 25px 0px auto;
+ border-bottom: 15px solid rgb(251, 238, 213);
+ border-left: 15px solid transparent;
+ border-right: 15px solid transparent;
+ float: right;
+ }
+ &.alert {
+ display: block;
+ margin-bottom: 0px;
+ }
+}
diff --git a/app/assets/stylesheets/components/_flash-message.scss b/app/assets/stylesheets/components/_flash-message.scss
index e6abce7c..f38d59d8 100644
--- a/app/assets/stylesheets/components/_flash-message.scss
+++ b/app/assets/stylesheets/components/_flash-message.scss
@@ -8,12 +8,41 @@
.message {
position: relative;
- background: $hb-purple-dark;
color: white;
font-size: 13px;
text-align: center;
padding: 8px;
margin-bottom: 4px;
box-shadow: -1px 2px 2px rgba(0,0,0,0.5);
+
+ .ui-icon-x-thin {
+ position: relative;
+ bottom: 5px;
+ float: right;
+ font-size: 8px;
+ margin-left: -16px;
+ margin-right: -8px;
+ }
+ }
+
+ .info {
+ background: $hb-purple-dark;
+ }
+
+ .warning {
+ background: $hb-red;
+ }
+
+ .progress.message {
+ background: $navGrey;
+ box-shadow: -1px 2px 2px rgba($navGrey, 0.5);
+ color: $hb-purple-dark;
+ }
+
+ .hb-spinner {
+ position: relative;
+ float: right;
+ right: 28px;
+ bottom: 4px;
}
}
diff --git a/app/assets/stylesheets/marketing/main.scss b/app/assets/stylesheets/marketing/main.scss
index cd0b3325..92a21dab 100644
--- a/app/assets/stylesheets/marketing/main.scss
+++ b/app/assets/stylesheets/marketing/main.scss
@@ -49,6 +49,7 @@
@import "partials/faq";
@import "partials/integrations";
@import "partials/login";
+@import "partials/permissions";
/**
diff --git a/app/assets/stylesheets/marketing/modules/_variables.scss b/app/assets/stylesheets/marketing/modules/_variables.scss
index f4df86cd..a26640ee 100644
--- a/app/assets/stylesheets/marketing/modules/_variables.scss
+++ b/app/assets/stylesheets/marketing/modules/_variables.scss
@@ -15,7 +15,7 @@ $max-width: 1001px;
* Breakpoints
*/
$short: new-breakpoint(max-width 22.5em 4); // 360px
-$tall: new-breakpoint(max-width 47.8em 12); // 768px
+$tall: new-breakpoint(max-width 52.94em 12); // 847px
$grande: new-breakpoint(max-width 65.06em 12); // 1041px
$venti: new-breakpoint(min-width 65.06em 12); // 1260px
diff --git a/app/assets/stylesheets/marketing/partials/_footer.scss b/app/assets/stylesheets/marketing/partials/_footer.scss
index 3d2bbf7b..f14f10ef 100644
--- a/app/assets/stylesheets/marketing/partials/_footer.scss
+++ b/app/assets/stylesheets/marketing/partials/_footer.scss
@@ -160,7 +160,7 @@
@include e(btn) {
@include span-columns(7);
- margin-top: 10%;
+ margin-top: 5%;
text-align: center;
}
@@ -188,7 +188,6 @@
.footer-nav {
background: url(asset-path('marketing/separator.png')) no-repeat bottom center;
margin: 0 auto;
- text-align: left;
@include row();
@include rem((
max-width: 500px,
diff --git a/app/assets/stylesheets/marketing/partials/_header.scss b/app/assets/stylesheets/marketing/partials/_header.scss
index abe71066..39db30e1 100644
--- a/app/assets/stylesheets/marketing/partials/_header.scss
+++ b/app/assets/stylesheets/marketing/partials/_header.scss
@@ -44,7 +44,7 @@
@include rem((
padding-top: 80px,
padding-bottom: 80px,
- max-width: $slim-container-width
+ max-width: 885px
));
// Breakpoints
@@ -57,6 +57,14 @@
));
}
}
+
+ .nav {
+ .btn-octocat {
+ position: relative;
+ right: 6px;
+ top: 2px;
+ }
+ }
}
diff --git a/app/assets/stylesheets/marketing/partials/_hero.scss b/app/assets/stylesheets/marketing/partials/_hero.scss
index c6d1af31..5a3e92fb 100644
--- a/app/assets/stylesheets/marketing/partials/_hero.scss
+++ b/app/assets/stylesheets/marketing/partials/_hero.scss
@@ -38,6 +38,19 @@
color: white;
font-family: $alt-font-family;
font-weight: 300;
+ .nav__btn {
+ display: inline;
+ &:before {
+ display: none;
+ }
+ }
+ .learn-more {
+ font-size: 26%;
+ color: white;
+ padding-left: 2px;
+ vertical-align: middle;
+ }
+
@include rem((
font-size: 52px,
margin-bottom: 17px
diff --git a/app/assets/stylesheets/marketing/partials/_login.scss b/app/assets/stylesheets/marketing/partials/_login.scss
index 65c43cdb..db840ced 100644
--- a/app/assets/stylesheets/marketing/partials/_login.scss
+++ b/app/assets/stylesheets/marketing/partials/_login.scss
@@ -10,6 +10,14 @@
background-size: 100% auto;
color: white;
+ .permissions {
+ .btn-octocat {
+ position: relative;
+ top: 2px;
+ margin-right: 4px;
+ }
+ }
+
.top {
background: none;
}
@@ -211,146 +219,3 @@
}
-/**
- * Permissions
- */
-.permissions {
- @include row();
- @include rem((
- margin: 0 auto 20px,
- max-width: 900px,
- ));
-
- .btn {
- display: block;
- text-align: center;
- }
-
- /**
- * Opening copy
- */
- @include e(copy) {
- text-align: center;
- position: relative;
- @include rem((
- font-size: 20px,
- margin-bottom: 100px
- ));
-
- &:before {
- background: rgba(white, 0.1);
- content: " ";
- display: block;
- height: 3px;
- width: 30%;
- @include rem((
- margin: 32px auto
- ));
- }
-
- // Breakpoints
- @include media($tall) {
- @include rem((
- margin-bottom: 40px
- ));
- }
- }
-
- /**
- * Column containers
- */
- @include e(col) {
- @include span-columns(6);
- @include rem((
- margin-bottom: 40px
- ));
-
- // Breakpoints
- @include media($tall) {
- @include span-columns(12);
- }
- }
- @include e(heading) {
- line-height: 1;
- text-align: center;
- @include rem((
- font-size: 32px
- ));
-
- img {
- display: inline-block;
- vertical-align: middle;
- @include rem((
- margin-right: 10px
- ));
- }
- }
-
- /**
- * Permissions details
- */
- @include e(list) {
- background: white;
- border-radius: 5px;
- color: #3C3C3C;
- @include rem((
- padding: 30px
- ));
-
- dl, dt, dd {
- margin: 0;
- padding: 0;
- }
- dl {
- border-bottom: 1px solid #E8E8E8;
- @include rem((
- padding-bottom: 12px,
- margin-bottom: 24px
- ));
- }
- dt {
- @include rem((
- line-height: 18px,
- margin-bottom: 12px
- ));
- }
- dd {
- float: left;
- @include rem((
- margin-right: 10px
- ));
- }
- img {
- display: inline-block;
- @include rem((
- margin-right: 10px
- ));
- }
-
- // Breakpoints
- @include media($tall) {
- dl {
- text-align: center;
- @include rem((
- padding-bottom: 1px
- ));
- }
- dt {
- line-height: 1.5;
- @include rem((
- margin-bottom: 24px
- ));
- }
- dd {
- float: none;
- margin-right: 0;
- }
- img {
- @include rem((
- margin: 0 10px
- ));
- }
- }
- }
-
-}
diff --git a/app/assets/stylesheets/marketing/partials/_permissions.scss b/app/assets/stylesheets/marketing/partials/_permissions.scss
new file mode 100644
index 00000000..d7fd427b
--- /dev/null
+++ b/app/assets/stylesheets/marketing/partials/_permissions.scss
@@ -0,0 +1,145 @@
+/**
+ * Permissions
+ */
+.permissions {
+ @include row();
+ @include rem((
+ margin: 0 auto 20px,
+ max-width: 850px,
+ ));
+
+ .btn {
+ display: block;
+ text-align: center;
+ margin-bottom: 30px;
+ }
+
+ /**
+ * Opening copy
+ */
+ @include e(copy) {
+ text-align: center;
+ position: relative;
+ @include rem((
+ font-size: 20px,
+ margin-bottom: 100px
+ ));
+
+ &:before {
+ background: rgba(white, 0.1);
+ content: " ";
+ display: block;
+ height: 3px;
+ width: 30%;
+ @include rem((
+ margin: 32px auto
+ ));
+ }
+
+ // Breakpoints
+ @include media($tall) {
+ @include rem((
+ margin-bottom: 40px
+ ));
+ }
+ }
+
+ /**
+ * Column containers
+ */
+ @include e(col) {
+ @include span-columns(6);
+ @include rem((
+ margin-bottom: 40px
+ ));
+
+ // Breakpoints
+ @include media($tall) {
+ @include span-columns(12);
+ }
+ }
+ @include e(heading) {
+ line-height: 1;
+ text-align: center;
+ @include rem((
+ font-size: 32px
+ ));
+
+ img {
+ display: inline-block;
+ vertical-align: middle;
+ @include rem((
+ margin-right: 10px
+ ));
+ }
+ }
+
+ /**
+ * Permissions details
+ */
+ @include e(list) {
+ background: white;
+ border-radius: 5px;
+ color: #3C3C3C;
+ @include rem((
+ padding: 30px
+ ));
+
+ dl, dt, dd {
+ margin: 0;
+ padding: 0;
+ }
+ dl {
+ border-bottom: 1px solid #E8E8E8;
+ @include rem((
+ padding-bottom: 12px,
+ margin-bottom: 24px
+ ));
+ }
+ dt {
+ @include rem((
+ line-height: 18px,
+ margin-bottom: 12px
+ ));
+ }
+ dd {
+ float: left;
+ @include rem((
+ margin-right: 10px
+ ));
+ }
+ img {
+ display: inline-block;
+ @include rem((
+ margin-right: 10px
+ ));
+ }
+
+ // Breakpoints
+ @include media($tall) {
+ dl {
+ text-align: center;
+ @include rem((
+ padding-bottom: 1px
+ ));
+ }
+ dt {
+ line-height: 1.5;
+ @include rem((
+ margin-bottom: 24px
+ ));
+ }
+ dd {
+ float: none;
+ margin-right: 0;
+ }
+ img {
+ @include rem((
+ margin: 0 10px
+ ));
+ }
+ }
+ }
+
+}
+
diff --git a/app/controllers/api/issues_controller.rb b/app/controllers/api/issues_controller.rb
index 3de72da0..227409c1 100644
--- a/app/controllers/api/issues_controller.rb
+++ b/app/controllers/api/issues_controller.rb
@@ -6,6 +6,11 @@ def issue
render json: api.issue(params[:number])
end
+ def issues
+ api = huboard.board(params[:user], params[:repo])
+ render json: api.issues(params[:label], params[:options])
+ end
+
def details
api = huboard.board(params[:user], params[:repo])
render json: api.issue(params[:number]).activities
@@ -25,6 +30,14 @@ def update_issue
def label_issue
api = huboard.board(params[:user], params[:repo])
@issue = api.issue(params[:number]).update(params)
+ @label = params["selectedLabel"]
+ render json: @issue
+ end
+
+ def unlabel_issue
+ api = huboard.board(params[:user], params[:repo])
+ @issue = api.issue(params[:number]).update(params)
+ @label = params["selectedLabel"]
render json: @issue
end
diff --git a/app/controllers/api/webhooks_controller.rb b/app/controllers/api/webhooks_controller.rb
index 01f85be1..5a231933 100644
--- a/app/controllers/api/webhooks_controller.rb
+++ b/app/controllers/api/webhooks_controller.rb
@@ -12,6 +12,42 @@ def legacy
render json: { message: "Webhook received" }
end
+ def publish_pull_request_event
+ return render json: { message: "pong" } if request.env["HTTP_X_GITHUB_EVENT"] == "ping"
+
+ payload = HashWithIndifferentAccess.new JSON.parse(params[:payload])
+ return render json: { message: "Fail to parse message" } if payload[:pull_request].nil?
+
+ #guard against the syncronize action
+ return render json: { message: "Not implemented: `synchronize` event" } if payload[:action] == "synchronize"
+
+ repo = {
+ repo: {
+ owner: { login: payload[:repository][:owner][:login] },
+ name: payload[:repository][:name],
+ full_name: payload[:repository][:full_name]
+ }
+ }
+ payload[:pull_request].extend(Huboard::Issues::Card).merge!(repo)
+
+ message = HashWithIndifferentAccess.new(
+ :pull_request => true,
+ :issue => payload[:pull_request],
+ :label => payload[:label],
+ "action_controller.params" => {},
+ :current_user => payload[:sender]
+ )
+
+ is_column = Huboard.column_pattern
+ if payload[:label] && !!payload[:label][:name].match(is_column)
+ return render json: { message: "Webhook received" }
+ end
+
+ generate_issue_event(payload[:action], message)
+ render json: { message: "Webhook received" }
+ end
+
+
def publish_issue_event
return render json: { message: "pong" } if request.env["HTTP_X_GITHUB_EVENT"] == "ping"
@@ -29,6 +65,7 @@ def publish_issue_event
message = HashWithIndifferentAccess.new(
:issue => payload[:issue],
+ :label => payload[:label],
"action_controller.params" => {},
:current_user => payload[:sender]
)
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 9c5d54cf..aae4d8c9 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -4,17 +4,25 @@ class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
rescue_from ActionController::InvalidAuthenticityToken, :with => :csrf_failed
rescue_from Ghee::Unauthorized, :with => :ghee_unauthorized
+ rescue_from Ghee::NotFound, :with => :ghee_not_found
include ApplicationHelper
after_action :queue_job
protected
+ def ghee_not_found
+ render({ json: {error: 'Not Found'}, status: 404 })
+ end
def ghee_unauthorized
- request.env['warden'].logout
respond_to do |format|
format.json { render json: {error: 'GitHub token is expired'}, status: 422}
- format.html { redirect_to '/login' }
+ format.html do
+ #only logout if html
+ request.env['warden'].logout
+
+ redirect_to '/login/github'
+ end
end
end
def csrf_failed
diff --git a/app/controllers/board_controller.rb b/app/controllers/board_controller.rb
index a03ce60e..48c95524 100644
--- a/app/controllers/board_controller.rb
+++ b/app/controllers/board_controller.rb
@@ -4,7 +4,6 @@ def index
success do
@repo = huboard.repo(params[:user],params[:repo]).fetch
@repo[:repo].merge!(is_collaborator: is_collaborator?(@repo[:repo]))
- @auth_level = github_authenticated?(:private) ? "private" : "public"
render :index, layout: "ember"
end
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 90ee93c0..91ef0210 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -1,10 +1,15 @@
class DashboardController < ApplicationController
before_action :login, only: :private
+ before_action :determine_auth_scope
+
def index
+ github_authenticate!(:default) unless github_authenticated?(:default)
+ return redirect_to(welcome_path) unless logged_in? && current_user.has_scope?('read:org')
@private = nil
@user = gh.users(current_user.login)
@repos = huboard.all_repos
end
+
def user
user = gh.users(params[:user]).raw
not_found unless user.status == 200
@@ -47,4 +52,8 @@ def login
github_authenticate! :private
end
+ def determine_auth_scope
+ @auth_scope_private = logged_in? && current_user.has_scope?(:repo)
+ end
+
end
diff --git a/app/controllers/login_controller.rb b/app/controllers/login_controller.rb
index 34036efd..9450ca23 100644
--- a/app/controllers/login_controller.rb
+++ b/app/controllers/login_controller.rb
@@ -1,19 +1,37 @@
class LoginController < ApplicationController
layout false
+
def logout
- request.env['warden'].logout
+ warden.logout
redirect_to "/"
end
+
def public
- github_authenticate! :default
+ github_authenticate! :public
@user = gh.user
@emails = @user.emails.all
- redirect_to params[:redirect_to] || "/"
+ redirect_to params[:redirect_to] || "/dashboard"
end
+
def private
github_authenticate! :private
@user = gh.user
@emails = @user.emails.all
- redirect_to params[:redirect_to] || "/"
+ redirect_to params[:redirect_to] || "/dashboard"
+ end
+
+ def github
+ github_authenticate! :default
+ redirect_to params[:redirect_to] || "/dashboard"
+ end
+
+ def github_callback
+ warden.authenticate!(scope: session[:scope].to_sym)
+ end
+
+ private
+ def warden
+ env['warden']
end
+
end
diff --git a/app/controllers/welcome_controller.rb b/app/controllers/welcome_controller.rb
new file mode 100644
index 00000000..3366068d
--- /dev/null
+++ b/app/controllers/welcome_controller.rb
@@ -0,0 +1,6 @@
+class WelcomeController < ApplicationController
+ layout false
+ def index;
+ redirect_to(dashboard_path) if logged_in? && current_user.has_scope?("read:org")
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 8d75f3c8..aa65088c 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -1,16 +1,21 @@
require 'bridge/huboard'
module ApplicationHelper
+
def logged_in?
- github_authenticated?(:private) || github_authenticated?(:default)
+ is_default = github_authenticated?(:default)
+ is_private = github_authenticated?(:private)
+ is_public = github_authenticated?(:public)
+ return is_default || is_private || is_public
end
+
def current_user
- github_user(:private) || github_user(:default) || OpenStruct.new
+ github_user(:default) || github_user(:private) || github_user(:public) || User.new(OpenStruct.new())
end
def controller? *controller
(controller.include?(params[:controller]) || controller.include?(params[:action])) ? "nav__btn--active nav__item--current": ''
end
def user_token
- current_user ? current_user.token : nil
+ current_user.token
end
def github_config
{
@@ -34,7 +39,9 @@ def couch
# Initiates the OAuth flow if not already authenticated for the
# # specified scope.
def github_authenticate!(scope=:default)
+ request.session[:scope] = scope
request.env['warden'].authenticate!(scope: scope)
+ request.env['warden'].set_user(request.env['warden'].user(scope), scope: :default)
end
# Logs out a user if currently logged in for the specified scope.
@@ -46,8 +53,9 @@ def github_authenticated?(scope=:default)
end
def github_user(scope=:default)
- request.env['warden'].user(scope)
+ User.new(request.env['warden'].user(scope)) if request.env['warden'].user(scope)
end
+
def github_session(scope=:default)
request.env['warden'].session(scope) if github_authenticated?(scope)
end
@@ -68,6 +76,11 @@ def markdown(text)
def generate_issue_event(action, message)
verb = action.present_tense
constant = "Api::Issues#{verb.capitalize}IssueJob".constantize
- constant.perform_later message
+
+ if Rails.configuration.active_job.queue_adapter == :sucker_punch # sucker_punch doesn't support enqueue
+ constant.perform_later message
+ else
+ constant.set(wait: 1.second).perform_later message
+ end
end
end
diff --git a/app/jobs/api/issues_assign_issue_job.rb b/app/jobs/api/issues_assign_issue_job.rb
index 09b93e5e..bbecb7ea 100644
--- a/app/jobs/api/issues_assign_issue_job.rb
+++ b/app/jobs/api/issues_assign_issue_job.rb
@@ -2,7 +2,11 @@ module Api
class IssuesAssignIssueJob < IssueEventJob
include IsPublishable
action 'assigned'
- timestamp Proc.new { Time.now.utc.iso8601}
+ timestamp ->(params) { params[:issue]['updated_at'] }
+ cache_key ->(message) {
+ "assigned.#{message[:meta][:user]["login"]}.#{message[:meta][:identifier]}.#{message[:meta][:timestamp]}"
+ }
+
def payload(params)
{
issue: params[:issue],
diff --git a/app/jobs/api/issues_assign_milestone_job.rb b/app/jobs/api/issues_assign_milestone_job.rb
index 778385a2..c679c7c9 100644
--- a/app/jobs/api/issues_assign_milestone_job.rb
+++ b/app/jobs/api/issues_assign_milestone_job.rb
@@ -2,7 +2,7 @@ module Api
class IssuesAssignMilestoneJob < IssueEventJob
include IsPublishable
action 'milestone_changed'
- timestamp Proc.new { Time.now.utc.iso8601}
+ timestamp ->(params) { params[:issue]['updated_at'] }
## suppress if the milestone didn't change
#
diff --git a/app/jobs/api/issues_label_issue_job.rb b/app/jobs/api/issues_label_issue_job.rb
index 6dc98c5e..5a330914 100644
--- a/app/jobs/api/issues_label_issue_job.rb
+++ b/app/jobs/api/issues_label_issue_job.rb
@@ -1,11 +1,18 @@
module Api
class IssuesLabelIssueJob < IssueEventJob
- include IsPublishable
timestamp ->(params) { params[:issue]['updated_at'] }
action "issue_labeled"
+ cache_key ->(message) {
+ label_name = message[:payload][:label] ? message[:payload][:label]['name'] : ""
+
+ "#{message[:meta][:action]}.#{message[:meta][:user]["login"]}.#{message[:meta][:identifier]}.#{message[:meta][:timestamp]}.#{ label_name }"
+ }
def payload(params)
- { issue: params[:issue] }
+ {
+ issue: params[:issue],
+ label: params[:label]
+ }
end
end
end
diff --git a/app/jobs/api/issues_open_issue_job.rb b/app/jobs/api/issues_open_issue_job.rb
index 629f6623..d5c84595 100644
--- a/app/jobs/api/issues_open_issue_job.rb
+++ b/app/jobs/api/issues_open_issue_job.rb
@@ -6,6 +6,7 @@ class IssuesOpenIssueJob < IssueEventJob
def payload(params)
{
+ pull_request: params[:pull_request],
issue: params[:issue]
}
end
diff --git a/app/jobs/api/issues_unassign_issue_job.rb b/app/jobs/api/issues_unassign_issue_job.rb
index 054052c1..868a1954 100644
--- a/app/jobs/api/issues_unassign_issue_job.rb
+++ b/app/jobs/api/issues_unassign_issue_job.rb
@@ -2,7 +2,11 @@ module Api
class IssuesUnassignIssueJob < IssueEventJob
include IsPublishable
action 'unassigned'
- timestamp Proc.new { Time.now.utc.iso8601}
+ timestamp ->(params) { params[:issue]['updated_at'] }
+ cache_key ->(message) {
+ "assigned.#{message[:meta][:user]["login"]}.#{message[:meta][:identifier]}.#{message[:meta][:timestamp]}"
+ }
+
def payload(params)
{
issue: params[:issue]
diff --git a/app/jobs/api/issues_unlabel_issue_job.rb b/app/jobs/api/issues_unlabel_issue_job.rb
index 879ef56e..f0b13589 100644
--- a/app/jobs/api/issues_unlabel_issue_job.rb
+++ b/app/jobs/api/issues_unlabel_issue_job.rb
@@ -2,9 +2,17 @@ module Api
class IssuesUnlabelIssueJob < IssueEventJob
timestamp ->(params) { params[:issue]['updated_at'] }
action "issue_unlabeled"
+ cache_key ->(message) {
+ label_name = message[:payload][:label] ? message[:payload][:label]['name'] : ""
+
+ "#{message[:meta][:action]}.#{message[:meta][:user]["login"]}.#{message[:meta][:identifier]}.#{message[:meta][:timestamp]}.#{ label_name }"
+ }
def payload(params)
- { issue: params[:issue] }
+ {
+ issue: params[:issue],
+ label: params[:label]
+ }
end
end
end
diff --git a/app/jobs/cached_job.rb b/app/jobs/cached_job.rb
new file mode 100644
index 00000000..b6a0d550
--- /dev/null
+++ b/app/jobs/cached_job.rb
@@ -0,0 +1,27 @@
+class CachedJob < ActiveJob::Base
+ def perform(params)
+ Rails.cache.with do |dalli|
+ uuid = SecureRandom.uuid
+ if params[:cache_key] && !dalli.get(params[:cache_key])
+ dalli.set(params[:cache_key], uuid)
+ end
+
+ if dalli.get(params[:cache_key]) === uuid
+ message = deliver params
+
+ return if params[:suppress]
+ PublishWebhookJob.perform_later message if params[:webhook_publishable]
+ end
+ end
+ end
+
+ :private
+
+ def deliver(message)
+ client = ::Faye::Redis::Publisher.new({})
+ Rails.logger.debug ["/" + message[:meta][:repo_full_name], message]
+ channel = message[:meta][:repo_full_name].downcase
+ client.publish "/" + channel, message
+ return message
+ end
+end
diff --git a/app/jobs/issue_event_job.rb b/app/jobs/issue_event_job.rb
index bfca753b..5cc7c554 100644
--- a/app/jobs/issue_event_job.rb
+++ b/app/jobs/issue_event_job.rb
@@ -1,67 +1,66 @@
class IssueEventJob < ActiveJob::Base
+ # TODO: DRY this up relative to MilestoneEventJob
- around_perform :guard_against_double_events
+ class_attribute :_action
+ class_attribute :_cache_key
+ class_attribute :_timestamp
+ class_attribute :_identifier
def self.action(action)
- @_action = action
+ self._action = action
end
- @_timestamp = Proc.new { Time.now.utc.iso8601 }
- def self.timestamp(override=nil)
- if override
- @_timestamp = override
- else
- @_timestamp
- end
+ self._timestamp = Proc.new { Time.now.utc.iso8601 }
+
+ def self.timestamp(override)
+ self._timestamp = override
end
+
+ self._identifier = ->(p){ p['issue']['number'] }
+
def self.identifier(override=nil)
- @_identifier = override
+ self._identifier = override
+ end
+
+
+ self._cache_key = ->(message) { "#{message[:meta][:action]}.#{message[:meta][:user]["login"]}.#{message[:meta][:identifier]}.#{message[:meta][:timestamp]}" }
+
+ def self.cache_key(override)
+ self._cache_key = override
end
def self.build_meta(params)
#TODO Fix this hack on remapping params to HWIA
params = HashWithIndifferentAccess.new(params)
issue = HashWithIndifferentAccess.new(params['issue'])
- action = @_action.is_a?(String) ? @_action : @_action.call(params)
+ action = self._action.is_a?(String) ? self._action : self._action.call(params)
+
HashWithIndifferentAccess.new(
action: action,
- timestamp: (@_timestamp || Proc.new{Time.now.utc.iso8601}).call(params),
+ timestamp: self._timestamp.call(params),
correlationId: params['action_controller.params']['correlationId'],
user: params['current_user'],
- identifier: (@_identifier || ->(p){ p['issue']['number']}).call(params),
+ identifier: self._identifier.call(params),
repo_full_name: "#{issue[:repo][:owner][:login]}/#{issue[:repo][:name]}"
)
end
- def guard_against_double_events
- payload = { meta: self.class.build_meta(self.arguments.first) }
- willPublish = true
- Rails.cache.with do |dalli|
- key = "#{payload[:meta][:action]}.#{payload[:meta][:user]["login"]}.#{payload[:meta][:identifier]}.#{payload[:meta][:timestamp]}"
- willPublish = false if dalli.get(key)
- dalli.set(key, payload.to_s)
- end
- yield if willPublish
+ def build_message(params)
+ payload = payload(params)
+ payload['actor'] = self.arguments.first['current_user']
+
+ message = HashWithIndifferentAccess.new({
+ meta: self.class.build_meta(arguments.first),
+ payload: payload
+ })
end
def perform(params)
- message = deliver payload(params)
-
- return if params[:suppress]
-
- PublishWebhookJob.perform_later message if self.class.included_modules.include? IsPublishable
- end
+ message = build_message(params)
+ message[:cache_key] = self._cache_key.call(message)
+ Rails.logger.debug [self.class, 'ActiveJob:cache_key', message[:cache_key]]
- def deliver(payload)
- payload['actor'] = self.arguments.first['current_user']
- message = {
- meta: self.class.build_meta(arguments.first),
- payload: payload
- }
- client = ::Faye::Redis::Publisher.new({})
- Rails.logger.debug ["/" + message[:meta][:repo_full_name], message]
- channel = message[:meta][:repo_full_name].downcase
- client.publish "/" + channel, message
- return message
+ message[:webhook_publishable] = self.class.included_modules.include? IsPublishable
+ CachedJob.perform_later(message)
end
end
diff --git a/app/jobs/milestone_event_job.rb b/app/jobs/milestone_event_job.rb
index 6c2c76f7..28b32284 100644
--- a/app/jobs/milestone_event_job.rb
+++ b/app/jobs/milestone_event_job.rb
@@ -1,62 +1,66 @@
class MilestoneEventJob < ActiveJob::Base
+ # TODO: DRY this up relative to IssueEventJob
- around_perform :guard_against_double_events
+ class_attribute :_action
+ class_attribute :_cache_key
+ class_attribute :_timestamp
+ class_attribute :_identifier
def self.action(action)
- @_action = action
+ self._action = action
end
- @_timestamp = Proc.new { Time.now.utc.iso8601 }
- def self.timestamp(override=nil)
- if override
- @_timestamp = override
- else
- @_timestamp
- end
+ self._timestamp = Proc.new { Time.now.utc.iso8601 }
+
+ def self.timestamp(override)
+ self._timestamp = override
end
+
+ self._identifier = ->(p){ p['milestone']['number'] }
+
def self.identifier(override=nil)
- @_identifier = override
+ self._identifier = override
+ end
+
+
+ self._cache_key = ->(message) { "#{message[:meta][:action]}.#{message[:meta][:user]["login"]}.#{message[:meta][:identifier]}.#{message[:meta][:timestamp]}" }
+
+ def self.cache_key(override)
+ self._cache_key = override
end
def self.build_meta(params)
- milestone = params['milestone']
- action = @_action.is_a?(String) ? @_action : @_action.call(params)
+ #TODO Fix this hack on remapping params to HWIA
+ params = HashWithIndifferentAccess.new(params)
+ milestone = HashWithIndifferentAccess.new(params['milestone'])
+ action = self._action.is_a?(String) ? self._action : self._action.call(params)
+
HashWithIndifferentAccess.new(
action: action,
- timestamp: (@_timestamp || Proc.new{Time.now.utc.iso8601}).call(params),
+ timestamp: self._timestamp.call(params),
correlationId: params['action_controller.params']['correlationId'],
user: params['current_user'],
- identifier: (@_identifier || ->(p){ p['milestone']['number']}).call(params),
+ identifier: self._identifier.call(params),
repo_full_name: "#{milestone[:repo][:owner][:login]}/#{milestone[:repo][:name]}"
)
end
- def guard_against_double_events
- payload = { meta: self.class.build_meta(self.arguments.first) }
- willPublish = true
- Rails.cache.with do |dalli|
- key = "#{payload[:meta][:action]}.#{payload[:meta][:user]["login"]}.#{payload[:meta][:identifier]}.#{payload[:meta][:timestamp]}"
- willPublish = false if dalli.get(key)
- dalli.set(key, payload.to_s)
- end
- yield if willPublish
+ def build_message(params)
+ payload = payload(params)
+ payload['actor'] = self.arguments.first['current_user']
+
+ message = HashWithIndifferentAccess.new({
+ meta: self.class.build_meta(arguments.first),
+ payload: payload
+ })
end
def perform(params)
- message = deliver payload(params)
+ message = build_message(params)
+ message[:cache_key] = self._cache_key.call(message)
+ Rails.logger.debug [self.class, 'ActiveJob:cache_key', message[:cache_key]]
- PublishWebhookJob.perform_later message if self.class.included_modules.include? IsPublishable
- end
-
- def deliver(payload)
- message = {
- meta: self.class.build_meta(arguments.first),
- payload: payload
- }
- client = ::Faye::Redis::Publisher.new({})
- Rails.logger.debug ["/" + message[:meta][:repo_full_name], message]
- channel = message[:meta][:repo_full_name].downcase
- client.publish "/" + channel, message
- return message
+ message[:webhook_publishable] = self.class.included_modules.include? IsPublishable
+ CachedJob.perform_later(message)
end
end
diff --git a/app/models/user.rb b/app/models/user.rb
new file mode 100644
index 00000000..b1671095
--- /dev/null
+++ b/app/models/user.rb
@@ -0,0 +1,22 @@
+class User
+ ATTRIBUTES = %w[id login name gravatar_id avatar_url email company site_admin].freeze
+
+ extend Forwardable
+
+ attr_reader :warden
+
+ def initialize(warden_user)
+ @warden = warden_user
+ end
+
+ def has_scope?(scope)
+ (self.scope || "").empty? ? false : self.scope.split(',').include?(scope.to_s)
+ end
+
+ def_delegators :@warden, :token, :scope, :attribs, :safe_avatar_url
+
+ ATTRIBUTES.each do |name|
+ def_delegator :@warden, name.to_sym
+ end
+
+end
diff --git a/app/views/board/index.html.erb b/app/views/board/index.html.erb
index 151a409f..9fd502ea 100644
--- a/app/views/board/index.html.erb
+++ b/app/views/board/index.html.erb
@@ -41,7 +41,6 @@
application.set("repo", Ember.Object.create(<%= @repo.to_hash.to_json.html_safe %>));
<% if logged_in? %>
application.set("loggedIn", true);
- application.set("authLevel", "<%= @auth_level %>")
application.set("currentUser",<%= current_user.attribs.to_json.html_safe %>);
<% if Rails.application.config.sockets.socket_backend %>
application.set("socketBackend", "<%= Rails.application.config.sockets.socket_backend %>");
diff --git a/app/views/dashboard/_repo_filters.html.erb b/app/views/dashboard/_repo_filters.html.erb
index 86eb6731..93ecb051 100644
--- a/app/views/dashboard/_repo_filters.html.erb
+++ b/app/views/dashboard/_repo_filters.html.erb
@@ -7,9 +7,9 @@
end
%>
- -
+
- '>
-
+
Private
diff --git a/app/views/dashboard/index.html.erb b/app/views/dashboard/index.html.erb
index 83684f27..189de1a2 100644
--- a/app/views/dashboard/index.html.erb
+++ b/app/views/dashboard/index.html.erb
@@ -9,10 +9,25 @@