tokite 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +46 -0
  4. data/Rakefile +23 -0
  5. data/app/assets/config/tokite_manifest.js +3 -0
  6. data/app/assets/javascripts/tokite/application.js +1 -0
  7. data/app/assets/stylesheets/tokite/application.css +18 -0
  8. data/app/controllers/tokite/application_controller.rb +20 -0
  9. data/app/controllers/tokite/hooks_controller.rb +18 -0
  10. data/app/controllers/tokite/rules_controller.rb +54 -0
  11. data/app/controllers/tokite/sessions_controller.rb +30 -0
  12. data/app/controllers/tokite/sha_controller.rb +17 -0
  13. data/app/controllers/tokite/top_controller.rb +6 -0
  14. data/app/controllers/tokite/users_controller.rb +38 -0
  15. data/app/helpers/tokite/application_helper.rb +30 -0
  16. data/app/jobs/tokite/application_job.rb +4 -0
  17. data/app/jobs/tokite/notify_github_hook_event_job.rb +9 -0
  18. data/app/mailers/tokite/application_mailer.rb +6 -0
  19. data/app/models/tokite/application_record.rb +5 -0
  20. data/app/models/tokite/hook.rb +38 -0
  21. data/app/models/tokite/hook_event/base_event.rb +19 -0
  22. data/app/models/tokite/hook_event/issue_comment.rb +30 -0
  23. data/app/models/tokite/hook_event/issues.rb +33 -0
  24. data/app/models/tokite/hook_event/pull_request.rb +33 -0
  25. data/app/models/tokite/revision.rb +10 -0
  26. data/app/models/tokite/rule.rb +46 -0
  27. data/app/models/tokite/search_query.rb +67 -0
  28. data/app/models/tokite/user.rb +20 -0
  29. data/app/views/layouts/tokite/application.html.haml +20 -0
  30. data/app/views/layouts/tokite/mailer.html.erb +13 -0
  31. data/app/views/layouts/tokite/mailer.text.erb +1 -0
  32. data/app/views/tokite/rules/_form.html.haml +60 -0
  33. data/app/views/tokite/rules/_rules.html.haml +8 -0
  34. data/app/views/tokite/rules/edit.html.haml +2 -0
  35. data/app/views/tokite/rules/index.html.haml +4 -0
  36. data/app/views/tokite/rules/new.html.haml +2 -0
  37. data/app/views/tokite/sessions/new.html.haml +1 -0
  38. data/app/views/tokite/top/show.html.haml +1 -0
  39. data/app/views/tokite/users/_form.html.haml +8 -0
  40. data/app/views/tokite/users/edit.html.haml +5 -0
  41. data/app/views/tokite/users/index.html.haml +16 -0
  42. data/config/initializers/omniauth.rb +7 -0
  43. data/config/initializers/slack_notifier.rb +6 -0
  44. data/config/routes.rb +15 -0
  45. data/lib/tasks/tokite.rake +49 -0
  46. data/lib/tasks/yarn.rake +3 -0
  47. data/lib/tokite/engine.rb +14 -0
  48. data/lib/tokite/version.rb +3 -0
  49. data/lib/tokite.rb +5 -0
  50. metadata +246 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 2cf9c92ec41ae0d0e05dcaa42ebbca53d4e888d3
4
+ data.tar.gz: 21d14599ea843af7f8142795e733720f9adc9c95
5
+ SHA512:
6
+ metadata.gz: 4ec749095174061c64d4365d4bdb412da665556c948bb33264c255b084bdddb75553bc4a03452b8c193395bf249414acd4a4f2e0295ec90213ae3408cdd3ef54
7
+ data.tar.gz: d0707f1204541ebdb820eb5a6fd9d2de135701f0c405bb480c3cc31540d5616420b702d5e9b7835ad0e5c418f9be6687867b13d68fca12655e7a44da7b70f700
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2017 hogelog
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # Tokite [![CircleCI](https://circleci.com/gh/hogelog/tokite.svg?style=svg)](https://circleci.com/gh/hogelog/tokite)
2
+
3
+ Tokite send GitHub event (pull-request, issue and comment) to Slack.
4
+
5
+ Notification setting are personalized and customizable by query.
6
+
7
+ ## Installation
8
+ Tokite works as rails mountable engine.
9
+
10
+ Add this line to your rails application's Gemfile:
11
+ ```ruby
12
+ gem "tokite"
13
+ ```
14
+
15
+ And mount engine.
16
+
17
+ ```ruby
18
+ Rails.application.routes.draw do
19
+ mount Tokite::Engine => "/"
20
+ end
21
+ ```
22
+
23
+ ### Setup database
24
+ ```console
25
+ $ ./bin/rails db:create
26
+ $ ./bin/rails tokite:ridgepole:install
27
+ $ ./bin/rails tokite:ridgepole:apply
28
+ ```
29
+
30
+ ### Setup yarn pkg
31
+ ```console
32
+ $ ./bin/rails tokite:yarn:install
33
+ ```
34
+
35
+ ## Configuration
36
+ <table>
37
+ <tr><th>GOOGLE_CLIENT_ID</th><td>Google+ OAuth2 client ID</td></tr>
38
+ <tr><th>GOOGLE_CLIENT_SECRET</th><td>Google+ OAuth2 client secret</td></tr>
39
+ <tr><th>GOOGLE_HOSTED_DOMAIN (optional)</th><td>Limited by G Suite domain</td></tr>
40
+ <tr><th>SECRET_KEY_BASE</th><td><code>rails secret</code> key</td></tr>
41
+ <tr><th>SLACK_WEBHOOK_URL</th><td>Slack incoming webhook url</td></tr>
42
+ <tr><th>SLACK_NAME (optional)</th><td>Slack notification user name</td></tr>
43
+ <tr><th>SLACK_ICON_EMOJI (optional)</th><td>Slack notification icon</td></tr>
44
+ <tr><th>SENTRY_DSN (optional)</th><td>Sentry DSN</td></tr>
45
+ <tr><th>APP_HOST (optional)</th><td>Application host url</td></tr>
46
+ </table>
data/Rakefile ADDED
@@ -0,0 +1,23 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Tokite'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+ require 'bundler/gem_tasks'
@@ -0,0 +1,3 @@
1
+ //= link_tree ../images/tokite
2
+ //= link_directory ../javascripts/tokite .js
3
+ //= link_directory ../stylesheets/tokite .css
@@ -0,0 +1 @@
1
+ //= require rails-ujs
@@ -0,0 +1,18 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, or any plugin's
6
+ * vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require bulma/bulma
14
+ */
15
+
16
+ .vspace-40 {
17
+ margin: 40px 0;
18
+ }
@@ -0,0 +1,20 @@
1
+ module Tokite
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery with: :exception
4
+
5
+ helper_method :current_user
6
+
7
+ before_action :require_login
8
+
9
+ private
10
+
11
+ def current_user
12
+ @current_user ||= User.find_by(id: session[:user_id]) if session[:user_id]
13
+ end
14
+
15
+ def require_login
16
+ return if current_user
17
+ redirect_to sign_in_path
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tokite
4
+ class HooksController < ActionController::API
5
+ GITHUB_EVENT_HEADER = "X-GitHub-Event"
6
+
7
+ def create
8
+ Hook.fire!(github_event, request.request_parameters)
9
+ render plain: "ok"
10
+ end
11
+
12
+ private
13
+
14
+ def github_event
15
+ request.headers[GITHUB_EVENT_HEADER]
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,54 @@
1
+ module Tokite
2
+ class RulesController < ApplicationController
3
+ def index
4
+ if params[:user_id]
5
+ @users = User.where(id: params[:user_id]).includes(:rules)
6
+ else
7
+ @users = User.all.includes(:rules)
8
+ end
9
+ end
10
+
11
+ def new
12
+ @rule = rule_user.rules.build
13
+ end
14
+
15
+ def create
16
+ @rule = rule_user.rules.new(rule_params)
17
+ if @rule.save
18
+ redirect_to edit_user_rule_path(params[:user_id], @rule)
19
+ else
20
+ render "new"
21
+ end
22
+ end
23
+
24
+ def edit
25
+ @rule = rule_user.rules.find(params[:id])
26
+ end
27
+
28
+ def update
29
+ @rule = rule_user.rules.find(params[:id])
30
+ @rule.assign_attributes(rule_params)
31
+ if @rule.save
32
+ redirect_to edit_user_rule_path(params[:user_id], @rule)
33
+ else
34
+ render "edit"
35
+ end
36
+ end
37
+
38
+ def destroy
39
+ @rule = rule_user.rules.find(params[:id])
40
+ @rule.destroy!
41
+ redirect_to user_rules_path(params[:user_id])
42
+ end
43
+
44
+ private
45
+
46
+ def rule_user
47
+ @rule_user ||= User.find(params[:user_id])
48
+ end
49
+
50
+ def rule_params
51
+ params.require(:rule).permit(:name, :query, :channel, :icon_emoji, :additional_text)
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,30 @@
1
+ module Tokite
2
+ class SessionsController < ApplicationController
3
+ skip_before_action :require_login
4
+
5
+ def new
6
+ end
7
+
8
+ def create
9
+ auth = request.env["omniauth.auth"]
10
+ user = User.find_or_initialize_by(
11
+ provider: auth[:provider],
12
+ uid: auth[:uid],
13
+ )
14
+ user.assign_attributes(
15
+ name: auth[:info][:name],
16
+ email: auth[:info][:email],
17
+ image_url: auth[:info][:image],
18
+ )
19
+ user.save! if user.changed?
20
+
21
+ session[:user_id] = user.id
22
+ redirect_to root_path
23
+ end
24
+
25
+ def destroy
26
+ session[:user_id] = nil
27
+ redirect_to root_path
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tokite
4
+ class ShaController < ActionController::API
5
+
6
+ def show
7
+ revision = Revision.take
8
+ if revision
9
+ status = 200
10
+ else
11
+ revision = "REVISION_FILE_NOT_FOUND"
12
+ status = 404
13
+ end
14
+ render plain: "#{revision}\n", status: status
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,6 @@
1
+ module Tokite
2
+ class TopController < ApplicationController
3
+ def show
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,38 @@
1
+ module Tokite
2
+ class UsersController < ApplicationController
3
+ def index
4
+ @users = User.all.order(:id)
5
+ end
6
+
7
+ def create
8
+ @user = User.create_group_user!("group name")
9
+ redirect_to edit_user_path(@user)
10
+ end
11
+
12
+ def edit
13
+ @user = User.find(params[:id])
14
+ end
15
+
16
+ def update
17
+ @user = User.find(params[:id])
18
+ @user.update!(user_params)
19
+ redirect_to edit_user_path(@user)
20
+ end
21
+
22
+ def destroy
23
+ @user = User.find(params[:id])
24
+ if @user.group_user?
25
+ @user.destroy!
26
+ redirect_to users_path
27
+ else
28
+ head 400
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def user_params
35
+ params.require(:user).permit(:name)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,30 @@
1
+ module Tokite
2
+ module ApplicationHelper
3
+ def nav_list_item(name, path, controllers)
4
+ if controllers.include?(params[:controller])
5
+ link_to(name, path, class: "nav-item is-tab is-active")
6
+ else
7
+ link_to(name, path, class: "nav-item is-tab")
8
+ end
9
+ end
10
+
11
+ def form_text_field(form, name, options)
12
+ html_class = options[:class].dup
13
+ object = form.object
14
+ content_tag("div", class: "field") do
15
+ form.label(name, class: "label") +
16
+ if object.errors[name].present?
17
+ errors = object.errors[name]
18
+ content_tag("p", class: "control") do
19
+ form.text_field name, options.merge(class: "#{html_class} is-danger")
20
+ end +
21
+ content_tag("p", errors.join("\n"), class: "help is-danger")
22
+ else
23
+ content_tag("p", class: "control") do
24
+ form.text_field name, size: 400, class: html_class
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,4 @@
1
+ module Tokite
2
+ class ApplicationJob < ActiveJob::Base
3
+ end
4
+ end
@@ -0,0 +1,9 @@
1
+ module Tokite
2
+ class NotifyGithubHookEventJob < ApplicationJob
3
+ queue_as :default
4
+
5
+ def perform(payload)
6
+ Rails.application.config.slack_notifier.ping(payload)
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,6 @@
1
+ module Tokite
2
+ class ApplicationMailer < ActionMailer::Base
3
+ default from: 'from@example.com'
4
+ layout 'mailer'
5
+ end
6
+ end
@@ -0,0 +1,5 @@
1
+ module Tokite
2
+ class ApplicationRecord < ActiveRecord::Base
3
+ self.abstract_class = true
4
+ end
5
+ end
@@ -0,0 +1,38 @@
1
+ module Tokite
2
+ class Hook
3
+ attr_reader :event
4
+
5
+ HOOK_EVENTS = {
6
+ "pull_request" => HookEvent::PullRequest,
7
+ "issues" => HookEvent::Issues,
8
+ "issue_comment" => HookEvent::IssueComment,
9
+ }.freeze
10
+
11
+ def self.fire!(github_event, hook_params)
12
+ event_class = HOOK_EVENTS[github_event]
13
+ Hook.new(event_class.new(hook_params)).fire! if event_class
14
+ end
15
+
16
+ def initialize(event)
17
+ @event = event
18
+ end
19
+
20
+ def fire!
21
+ return unless event.notify?
22
+ Rule.matched_rules(event).each do |rule|
23
+ attachment = event.slack_attachment
24
+ attachment[:fallback] += "\n\n#{rule.slack_attachment_fallback}"
25
+ attachment[:text] += "\n\n#{rule.slack_attachment_text}"
26
+ emoji = rule.icon_emoji.chomp.presence
27
+ additional_text = rule.additional_text
28
+
29
+ notify!(channel: rule.channel, text: event.slack_text, icon_emoji: emoji, attachments: [attachment])
30
+ notify!(channel: rule.channel, text: additional_text, icon_emoji: emoji, parse: "full") if additional_text.present?
31
+ end
32
+ end
33
+
34
+ def notify!(payload)
35
+ NotifyGithubHookEventJob.perform_now(payload.compact)
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,19 @@
1
+ module Tokite
2
+ module HookEvent
3
+ class BaseEvent
4
+ attr_reader :hook_params
5
+
6
+ def initialize(hook_params)
7
+ @hook_params = hook_params
8
+ end
9
+
10
+ def slack_payload(rule)
11
+ {
12
+ channel: rule.channel,
13
+ text: slack_text,
14
+ attachments: slack_attachments(rule),
15
+ }
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,30 @@
1
+ module Tokite
2
+ module HookEvent
3
+ class IssueComment < BaseEvent
4
+ def fields
5
+ {
6
+ event: "issue_comment",
7
+ repo: hook_params[:repository][:full_name],
8
+ body: hook_params[:comment][:body],
9
+ user: hook_params[:comment][:user][:login],
10
+ }
11
+ end
12
+
13
+ def notify?
14
+ %w(created).include?(hook_params[:action])
15
+ end
16
+
17
+ def slack_text
18
+ "[#{hook_params[:repository][:full_name]}] New comment by #{hook_params[:comment][:user][:login]} on issue <#{hook_params[:comment][:html_url]}|##{hook_params[:issue][:number]}: #{hook_params[:issue][:title]}>"
19
+ end
20
+
21
+ def slack_attachment
22
+ {
23
+ fallback: hook_params[:comment][:body],
24
+ text: hook_params[:comment][:body],
25
+ color: "good",
26
+ }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,33 @@
1
+ module Tokite
2
+ module HookEvent
3
+ class Issues < BaseEvent
4
+ def fields
5
+ {
6
+ event: "issues",
7
+ repo: hook_params[:repository][:full_name],
8
+ title: hook_params[:issue][:title],
9
+ body: hook_params[:issue][:body],
10
+ user: hook_params[:issue][:user][:login],
11
+ }
12
+ end
13
+
14
+ def notify?
15
+ %w(opened).include?(hook_params[:action])
16
+ end
17
+
18
+ def slack_text
19
+ "[#{hook_params[:repository][:full_name]}] Issue created by <#{hook_params[:issue][:user][:html_url]}|#{hook_params[:issue][:user][:login]}>"
20
+ end
21
+
22
+ def slack_attachment
23
+ {
24
+ title: "##{hook_params[:issue][:number]} #{hook_params[:issue][:title]}",
25
+ title_link: hook_params[:issue][:html_url],
26
+ fallback: "#{hook_params[:issue][:title]}\n#{hook_params[:issue][:body]}",
27
+ text: hook_params[:issue][:body],
28
+ color: "good",
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ module Tokite
2
+ module HookEvent
3
+ class PullRequest < BaseEvent
4
+ def fields
5
+ {
6
+ event: "pull_request",
7
+ repo: hook_params[:repository][:full_name],
8
+ title: hook_params[:pull_request][:title],
9
+ body: hook_params[:pull_request][:body],
10
+ user: hook_params[:pull_request][:user][:login],
11
+ }
12
+ end
13
+
14
+ def notify?
15
+ %w(opened).include?(hook_params[:action])
16
+ end
17
+
18
+ def slack_text
19
+ "[#{hook_params[:repository][:full_name]}] Pull request submitted by <#{hook_params[:pull_request][:user][:html_url]}|#{hook_params[:pull_request][:user][:login]}>"
20
+ end
21
+
22
+ def slack_attachment
23
+ {
24
+ title: "##{hook_params[:pull_request][:number]} #{hook_params[:pull_request][:title]}",
25
+ title_link: hook_params[:pull_request][:html_url],
26
+ fallback: "#{hook_params[:pull_request][:title]}\n#{hook_params[:pull_request][:body]}",
27
+ text: hook_params[:pull_request][:body],
28
+ color: "good",
29
+ }
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,10 @@
1
+ module Tokite
2
+ class Revision
3
+ REVISION_PATH = Rails.root.join("REVISION")
4
+
5
+ def self.take
6
+ return ENV["HEROKU_SLUG_COMMIT"] if ENV["HEROKU_SLUG_COMMIT"]
7
+ return File.read(REVISION_PATH).chomp if File.exist?(REVISION_PATH)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,46 @@
1
+ module Tokite
2
+ class Rule < ApplicationRecord
3
+ attr_reader :search_query
4
+
5
+ belongs_to :user
6
+
7
+ validate :validate_query
8
+
9
+ # TODO: Performance
10
+ def self.matched_rules(event)
11
+ Rule.all.to_a.select do |rule|
12
+ rule.match?(event)
13
+ end
14
+ end
15
+
16
+ def search_query
17
+ @search_query ||= SearchQuery.new(query)
18
+ end
19
+
20
+ def match?(event)
21
+ search_query.match?(event.fields)
22
+ end
23
+
24
+ def slack_attachment_fallback
25
+ "#{name} by #{user.name}"
26
+ end
27
+
28
+ def slack_attachment_text
29
+ "Notification from #{rule_name_link} (#{user_link}) "
30
+ end
31
+
32
+ def rule_name_link
33
+ "<#{Tokite::Engine.routes.url_helpers.edit_user_rule_url(user, self)}|#{name}>"
34
+ end
35
+
36
+ def user_link
37
+ "<#{Tokite::Engine.routes.url_helpers.user_rules_url(user)}|#{user.name}>"
38
+ end
39
+
40
+ def validate_query
41
+ SearchQuery.parse(query)
42
+ rescue SearchQuery::ParseError => e
43
+ errors.add(:query, e.message)
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,67 @@
1
+ require "parslet"
2
+
3
+ module Tokite
4
+ class SearchQuery
5
+ attr_reader :query, :tree
6
+
7
+ class ParseError < StandardError
8
+ end
9
+
10
+ class Parser < Parslet::Parser
11
+ rule(:space) { match('\s').repeat(1) }
12
+ rule(:space?) { space.maybe }
13
+
14
+ rule(:escape) { str('\\').ignore }
15
+ rule(:quot) { str('"') }
16
+ rule(:quoted_char) { escape >> any | match('[^"]') }
17
+ rule(:char) { match('[^\s]') }
18
+ rule(:slash) { str('/') }
19
+ rule(:regexp_char) { escape >> any | match('[^/]') }
20
+
21
+ rule(:regexp_word) { slash >> regexp_char.repeat(1).as(:regexp_word) >> slash }
22
+ rule(:quot_word) { quot >> quoted_char.repeat(1).as(:word) >> quot }
23
+ rule(:plain_word) { (match('[^\s/"]') >> match('[^\s]').repeat).as(:word) }
24
+ rule(:field) { match('\w').repeat(1).as(:field) }
25
+ rule(:word) { (field >> str(':')).maybe >> (regexp_word | quot_word | plain_word) }
26
+
27
+ rule(:query) { word >> (space >> word).repeat }
28
+ root :query
29
+ end
30
+
31
+ def self.parser
32
+ @parser ||= Tokite::SearchQuery::Parser.new
33
+ end
34
+
35
+ def self.parse(query)
36
+ Array.wrap(parser.parse(query))
37
+ rescue Parslet::ParseFailed => e
38
+ raise ParseError, e
39
+ end
40
+
41
+ def initialize(query)
42
+ @query = query
43
+ @tree = Array.wrap(self.class.parse(query))
44
+ end
45
+
46
+ def match?(doc)
47
+ tree.all? do |word|
48
+ field = word[:field]
49
+ if word[:regexp_word]
50
+ regexp = word[:regexp_word].to_s
51
+ if field
52
+ doc[field.to_sym]&.match(regexp)
53
+ else
54
+ doc.values.any?{|text| text.match(regexp) }
55
+ end
56
+ else
57
+ value = word[:word].to_s
58
+ if field
59
+ doc[field.to_sym]&.index(value)
60
+ else
61
+ doc.values.any?{|text| text.index(value) }
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,20 @@
1
+ module Tokite
2
+ class User < ApplicationRecord
3
+ has_many :rules, dependent: :destroy
4
+
5
+ def self.create_group_user!(name)
6
+ uuid = SecureRandom.uuid.tr("-", "")
7
+ create!(
8
+ provider: "GROUP",
9
+ uid: uuid,
10
+ email: uuid,
11
+ image_url: "",
12
+ name: name
13
+ )
14
+ end
15
+
16
+ def group_user?
17
+ provider == "GROUP"
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,20 @@
1
+ !!!
2
+ %html
3
+ %head
4
+ %title Tokite
5
+ = csrf_meta_tags
6
+
7
+ = stylesheet_link_tag 'tokite/application', media: 'all'
8
+ = javascript_include_tag 'tokite/application'
9
+
10
+ %body
11
+ - if current_user
12
+ %nav.nav.has-shadow
13
+ .container
14
+ .nav-left
15
+ = nav_list_item "Top", root_path, ["top"]
16
+ = nav_list_item "Users", users_path, ["users"]
17
+ = nav_list_item "Rules", rules_path, ["rules"]
18
+
19
+ %section.section
20
+ = yield
@@ -0,0 +1,13 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
5
+ <style>
6
+ /* Email styles need to be inline */
7
+ </style>
8
+ </head>
9
+
10
+ <body>
11
+ <%= yield %>
12
+ </body>
13
+ </html>
@@ -0,0 +1 @@
1
+ <%= yield %>
@@ -0,0 +1,60 @@
1
+ = form_for [@rule_user, @rule] do |f|
2
+ = form_text_field f, :name, size: 40, class: "input"
3
+ = form_text_field f, :query, size: 400, class: "input"
4
+ = form_text_field f, :channel, size: 40, class: "input"
5
+ = form_text_field f, :icon_emoji, size: 40, class: "input"
6
+ = form_text_field f, :additional_text, size: 40, class: "input"
7
+ .field
8
+ = f.submit class: "button is-primary"
9
+ - if @rule.persisted?
10
+ = link_to "Delete rule", user_rule_path(@rule_user, @rule), method: :delete, data: { confirm: %(Delete "#{@rule.name}"?) }, class: "button is-danger is-pulled-right"
11
+
12
+ .message.vspace-40
13
+ .message-header
14
+ Query description
15
+ .message-body
16
+ %h2.title Supported query type
17
+ %table.table.is-bordered
18
+ %tr
19
+ %th Name
20
+ %th Example
21
+ %tr
22
+ %td Plain text
23
+ %td hoge fuga moge
24
+ %tr
25
+ %td Quoted text
26
+ %td "hoge fuga moge"
27
+ %tr
28
+ %td Regular expression
29
+ %td /(hoge|fuga|moge)/
30
+
31
+ %h2 Supported query field
32
+ %table.table.is-bordered
33
+ %tr
34
+ %th Field
35
+ %th Description
36
+ %th Example
37
+ %tr
38
+ %td repo:
39
+ %td Match repository name.
40
+ %td repo:hogelog/tokite
41
+ %tr
42
+ %td title:
43
+ %td Match pull_request or issues title.
44
+ %td title:Bug
45
+ %tr
46
+ %td event:
47
+ %td Match event type pull_request, issues or issue_comment.
48
+ %td event:/(pull_request|issues)/
49
+ %tr
50
+ %td body:
51
+ %td Match body text.
52
+ %td body:"review please"
53
+ %tr
54
+ %td user:
55
+ %td Match user name.
56
+ %td user:hogelog
57
+ %tr
58
+ %td unspecified
59
+ %td Match any field.
60
+ %td hogelog
@@ -0,0 +1,8 @@
1
+ .content
2
+ %h3 Rules
3
+ %ul
4
+ - user.rules.order(:id).each do |rule|
5
+ %li= link_to rule.name, edit_user_rule_path(user, rule)
6
+
7
+ .content
8
+ = link_to "New Rule", new_user_rule_path(user), class: "button is-primary"
@@ -0,0 +1,2 @@
1
+ %h1.title Edit rule
2
+ = render "form"
@@ -0,0 +1,4 @@
1
+ - @users.each do |user|
2
+ .box
3
+ %h2.title= user.name
4
+ = render "rules", user: user
@@ -0,0 +1,2 @@
1
+ %h1.title New rule
2
+ = render "form"
@@ -0,0 +1 @@
1
+ = link_to "Login with Google+", "/auth/google_oauth2"
@@ -0,0 +1 @@
1
+ = render "tokite/rules/rules", user: current_user
@@ -0,0 +1,8 @@
1
+ = form_for [@user] do |f|
2
+ .field
3
+ = f.label :name, class: "label"
4
+ %p.control
5
+ = f.text_field :name, size: 40, class: "input"
6
+ .field
7
+ %p.control
8
+ = f.submit class: "button is-primary"
@@ -0,0 +1,5 @@
1
+ %h1.title Edit user
2
+ = render "form"
3
+
4
+ - if @user.group_user?
5
+ = link_to "Delete user", user_path(@user), method: :delete, data: { confirm: %(Delete "#{@user.name}"?) }
@@ -0,0 +1,16 @@
1
+ %table.table.is-striped
2
+ %thead
3
+ %tr
4
+ %th ID
5
+ %th Name
6
+ %tbody
7
+ - @users.each do |user|
8
+ %tr
9
+ %td= user.id
10
+ %td
11
+ -#= user.name
12
+ = link_to user.name, edit_user_path(user)
13
+ = link_to "Rules", [user, :rules], class: "button is-small"
14
+
15
+ = link_to "Create group", users_path, method: :post, data: { confirm: %(Create Group?) }, class: "button is-primary"
16
+
@@ -0,0 +1,7 @@
1
+ require "omniauth-google-oauth2"
2
+
3
+ Rails.application.config.middleware.use OmniAuth::Builder do
4
+ provider :google_oauth2, ENV["GOOGLE_CLIENT_ID"], ENV["GOOGLE_CLIENT_SECRET"], {
5
+ hd: ENV["GOOGLE_HOSTED_DOMAIN"].present? ? ENV["GOOGLE_HOSTED_DOMAIN"] : nil,
6
+ }
7
+ end
@@ -0,0 +1,6 @@
1
+ if ENV["SLACK_WEBHOOK_URL"]
2
+ Tokite::Engine.config.slack_notifier = Slack::Notifier.new ENV["SLACK_WEBHOOK_URL"] do
3
+ defaults username: ENV.fetch("SLACK_NAME", "tokite"),
4
+ icon_emoji: ENV.fetch("SLACK_ICON_EMOJI", ":sunglasses:")
5
+ end
6
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,15 @@
1
+ Tokite::Engine.routes.draw do
2
+ root to: "top#show"
3
+
4
+ resources :hooks, only: %w(create)
5
+ resources :users, only: %w(index create edit update destroy) do
6
+ resources :rules, only: %w(index new create edit update destroy)
7
+ end
8
+ resources :rules, only: %w(index)
9
+
10
+ get "sign_in", to: "sessions#new", as: "sign_in"
11
+ get "auth/google_oauth2/callback", to: "sessions#create"
12
+ delete "sign_out", to: "sessions#destroy", as: "sign_out"
13
+
14
+ get "site/sha", to: "sha#show"
15
+ end
@@ -0,0 +1,49 @@
1
+ require 'pry'
2
+
3
+ namespace :tokite do
4
+ namespace :ridgepole do
5
+ def engine_path(file)
6
+ Tokite::Engine.root.join(file).to_s
7
+ end
8
+
9
+ def app_path(file)
10
+ Rails.root.join(file).to_s
11
+ end
12
+
13
+ def ridgepole_exec(*args)
14
+ yml = Rails.root.join("config/database.yml").to_s
15
+ sh "bundle", "exec", "ridgepole", "-c", yml, "-E", Rails.env, *args
16
+ end
17
+
18
+ desc "Export current schema"
19
+ task :export do
20
+ ridgepole_exec("--export", app_path("schema/Schemafile"), "--split")
21
+ end
22
+
23
+ desc "Apply Schemafile"
24
+ task :apply do
25
+ ridgepole_exec("--file", app_path("schema/Schemafile"), "-a")
26
+ end
27
+
28
+ desc "Apply Schemafile (dry-run)"
29
+ task :"dry-run" do
30
+ ridgepole_exec("--file", app_path("schema/Schemafile"), "-a", "--dry-run")
31
+ end
32
+
33
+ desc "Install schema"
34
+ task :install do
35
+ schema_dir = app_path("schema")
36
+ mkdir(schema_dir) unless Dir.exist?(schema_dir)
37
+ Dir.glob("#{engine_path("schema")}/*").each do |f|
38
+ cp f, schema_dir
39
+ end
40
+ end
41
+ end
42
+
43
+ namespace :yarn do
44
+ desc "Install yarn packages"
45
+ task :install do
46
+ sh "yarn", "add", "bulma"
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,3 @@
1
+ namespace :tokite do
2
+
3
+ end
@@ -0,0 +1,14 @@
1
+ module Tokite
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace Tokite
4
+
5
+ config.before_configuration do
6
+ require "haml-rails"
7
+ require "slack-notifier"
8
+ end
9
+
10
+ initializer "tokite.config" do
11
+ Tokite::Engine.routes.default_url_options = { protocol: "https", host: ENV.fetch("APP_HOST", "example.com") }
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,3 @@
1
+ module Tokite
2
+ VERSION = '0.1.0'
3
+ end
data/lib/tokite.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "tokite/engine"
2
+
3
+ module Tokite
4
+ # Your code goes here...
5
+ end
metadata ADDED
@@ -0,0 +1,246 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tokite
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - hogelog
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-06-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 5.1.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 5.1.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: pg
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.18'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.18'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sass-rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '5.0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '5.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: haml
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: haml-rails
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: omniauth-google-oauth2
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: slack-notifier
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: ridgepole
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :runtime
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: parslet
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :runtime
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec-rails
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: factory_girl_rails
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ description: Customizable Slack notification from GitHub
168
+ email:
169
+ - konbu.komuro@gmail.com
170
+ executables: []
171
+ extensions: []
172
+ extra_rdoc_files: []
173
+ files:
174
+ - MIT-LICENSE
175
+ - README.md
176
+ - Rakefile
177
+ - app/assets/config/tokite_manifest.js
178
+ - app/assets/javascripts/tokite/application.js
179
+ - app/assets/stylesheets/tokite/application.css
180
+ - app/controllers/tokite/application_controller.rb
181
+ - app/controllers/tokite/hooks_controller.rb
182
+ - app/controllers/tokite/rules_controller.rb
183
+ - app/controllers/tokite/sessions_controller.rb
184
+ - app/controllers/tokite/sha_controller.rb
185
+ - app/controllers/tokite/top_controller.rb
186
+ - app/controllers/tokite/users_controller.rb
187
+ - app/helpers/tokite/application_helper.rb
188
+ - app/jobs/tokite/application_job.rb
189
+ - app/jobs/tokite/notify_github_hook_event_job.rb
190
+ - app/mailers/tokite/application_mailer.rb
191
+ - app/models/tokite/application_record.rb
192
+ - app/models/tokite/hook.rb
193
+ - app/models/tokite/hook_event/base_event.rb
194
+ - app/models/tokite/hook_event/issue_comment.rb
195
+ - app/models/tokite/hook_event/issues.rb
196
+ - app/models/tokite/hook_event/pull_request.rb
197
+ - app/models/tokite/revision.rb
198
+ - app/models/tokite/rule.rb
199
+ - app/models/tokite/search_query.rb
200
+ - app/models/tokite/user.rb
201
+ - app/views/layouts/tokite/application.html.haml
202
+ - app/views/layouts/tokite/mailer.html.erb
203
+ - app/views/layouts/tokite/mailer.text.erb
204
+ - app/views/tokite/rules/_form.html.haml
205
+ - app/views/tokite/rules/_rules.html.haml
206
+ - app/views/tokite/rules/edit.html.haml
207
+ - app/views/tokite/rules/index.html.haml
208
+ - app/views/tokite/rules/new.html.haml
209
+ - app/views/tokite/sessions/new.html.haml
210
+ - app/views/tokite/top/show.html.haml
211
+ - app/views/tokite/users/_form.html.haml
212
+ - app/views/tokite/users/edit.html.haml
213
+ - app/views/tokite/users/index.html.haml
214
+ - config/initializers/omniauth.rb
215
+ - config/initializers/slack_notifier.rb
216
+ - config/routes.rb
217
+ - lib/tasks/tokite.rake
218
+ - lib/tasks/yarn.rake
219
+ - lib/tokite.rb
220
+ - lib/tokite/engine.rb
221
+ - lib/tokite/version.rb
222
+ homepage: https://github.com/hogelog/tokite/
223
+ licenses:
224
+ - MIT
225
+ metadata: {}
226
+ post_install_message:
227
+ rdoc_options: []
228
+ require_paths:
229
+ - lib
230
+ required_ruby_version: !ruby/object:Gem::Requirement
231
+ requirements:
232
+ - - ">="
233
+ - !ruby/object:Gem::Version
234
+ version: '0'
235
+ required_rubygems_version: !ruby/object:Gem::Requirement
236
+ requirements:
237
+ - - ">="
238
+ - !ruby/object:Gem::Version
239
+ version: '0'
240
+ requirements: []
241
+ rubyforge_project:
242
+ rubygems_version: 2.6.11
243
+ signing_key:
244
+ specification_version: 4
245
+ summary: Customizable Slack notification from GitHub
246
+ test_files: []