tokite 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: []