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.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +46 -0
- data/Rakefile +23 -0
- data/app/assets/config/tokite_manifest.js +3 -0
- data/app/assets/javascripts/tokite/application.js +1 -0
- data/app/assets/stylesheets/tokite/application.css +18 -0
- data/app/controllers/tokite/application_controller.rb +20 -0
- data/app/controllers/tokite/hooks_controller.rb +18 -0
- data/app/controllers/tokite/rules_controller.rb +54 -0
- data/app/controllers/tokite/sessions_controller.rb +30 -0
- data/app/controllers/tokite/sha_controller.rb +17 -0
- data/app/controllers/tokite/top_controller.rb +6 -0
- data/app/controllers/tokite/users_controller.rb +38 -0
- data/app/helpers/tokite/application_helper.rb +30 -0
- data/app/jobs/tokite/application_job.rb +4 -0
- data/app/jobs/tokite/notify_github_hook_event_job.rb +9 -0
- data/app/mailers/tokite/application_mailer.rb +6 -0
- data/app/models/tokite/application_record.rb +5 -0
- data/app/models/tokite/hook.rb +38 -0
- data/app/models/tokite/hook_event/base_event.rb +19 -0
- data/app/models/tokite/hook_event/issue_comment.rb +30 -0
- data/app/models/tokite/hook_event/issues.rb +33 -0
- data/app/models/tokite/hook_event/pull_request.rb +33 -0
- data/app/models/tokite/revision.rb +10 -0
- data/app/models/tokite/rule.rb +46 -0
- data/app/models/tokite/search_query.rb +67 -0
- data/app/models/tokite/user.rb +20 -0
- data/app/views/layouts/tokite/application.html.haml +20 -0
- data/app/views/layouts/tokite/mailer.html.erb +13 -0
- data/app/views/layouts/tokite/mailer.text.erb +1 -0
- data/app/views/tokite/rules/_form.html.haml +60 -0
- data/app/views/tokite/rules/_rules.html.haml +8 -0
- data/app/views/tokite/rules/edit.html.haml +2 -0
- data/app/views/tokite/rules/index.html.haml +4 -0
- data/app/views/tokite/rules/new.html.haml +2 -0
- data/app/views/tokite/sessions/new.html.haml +1 -0
- data/app/views/tokite/top/show.html.haml +1 -0
- data/app/views/tokite/users/_form.html.haml +8 -0
- data/app/views/tokite/users/edit.html.haml +5 -0
- data/app/views/tokite/users/index.html.haml +16 -0
- data/config/initializers/omniauth.rb +7 -0
- data/config/initializers/slack_notifier.rb +6 -0
- data/config/routes.rb +15 -0
- data/lib/tasks/tokite.rake +49 -0
- data/lib/tasks/yarn.rake +3 -0
- data/lib/tokite/engine.rb +14 -0
- data/lib/tokite/version.rb +3 -0
- data/lib/tokite.rb +5 -0
- 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 [](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 @@
|
|
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,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,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,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 @@
|
|
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 @@
|
|
1
|
+
= link_to "Login with Google+", "/auth/google_oauth2"
|
@@ -0,0 +1 @@
|
|
1
|
+
= render "tokite/rules/rules", user: current_user
|
@@ -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
|
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
|
data/lib/tasks/yarn.rake
ADDED
@@ -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
|
data/lib/tokite.rb
ADDED
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: []
|