shipit-engine 0.27.1 → 0.28.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +30 -1
- data/app/assets/stylesheets/_pages/_commits.scss +2 -0
- data/app/assets/stylesheets/_pages/_deploy.scss +6 -0
- data/app/controllers/shipit/api/release_statuses_controller.rb +22 -0
- data/app/controllers/shipit/api/stacks_controller.rb +6 -1
- data/app/controllers/shipit/commits_controller.rb +12 -1
- data/app/controllers/shipit/deploys_controller.rb +11 -0
- data/app/controllers/shipit/stacks_controller.rb +29 -1
- data/app/controllers/shipit/tasks_controller.rb +13 -1
- data/app/helpers/shipit/merge_status_helper.rb +2 -2
- data/app/helpers/shipit/stacks_helper.rb +10 -4
- data/app/jobs/shipit/perform_task_job.rb +1 -0
- data/app/jobs/shipit/update_github_last_deployed_ref_job.rb +41 -0
- data/app/models/shipit/command_line_user.rb +58 -0
- data/app/models/shipit/commit.rb +42 -2
- data/app/models/shipit/deploy.rb +31 -2
- data/app/models/shipit/deploy_spec/lerna_discovery.rb +43 -19
- data/app/models/shipit/deploy_spec/pypi_discovery.rb +5 -1
- data/app/models/shipit/rollback.rb +4 -2
- data/app/models/shipit/stack.rb +72 -15
- data/app/models/shipit/task.rb +30 -0
- data/app/models/shipit/undeployed_commit.rb +10 -1
- data/app/serializers/shipit/command_line_user_serializer.rb +4 -0
- data/app/views/layouts/shipit.html.erb +5 -1
- data/app/views/shipit/commits/_commit.html.erb +7 -2
- data/app/views/shipit/merge_status/_commit_count_warning.html.erb +1 -5
- data/app/views/shipit/merge_status/backlogged.html.erb +1 -1
- data/app/views/shipit/merge_status/failure.html.erb +1 -1
- data/app/views/shipit/merge_status/locked.html.erb +1 -1
- data/app/views/shipit/merge_status/success.html.erb +1 -1
- data/app/views/shipit/stacks/show.html.erb +10 -1
- data/app/views/shipit/tasks/_task_output.html.erb +2 -2
- data/config/locales/en.yml +2 -1
- data/config/routes.rb +8 -1
- data/db/migrate/20190502020249_add_lock_author_id_to_commits.rb +5 -0
- data/lib/shipit.rb +14 -2
- data/lib/shipit/cast_value.rb +9 -0
- data/lib/shipit/command.rb +62 -16
- data/lib/shipit/line_buffer.rb +42 -0
- data/lib/shipit/version.rb +1 -1
- data/lib/tasks/shipit.rake +27 -0
- data/test/controllers/api/release_statuses_controller_test.rb +66 -0
- data/test/controllers/api/stacks_controller_test.rb +19 -0
- data/test/controllers/commits_controller_test.rb +30 -6
- data/test/controllers/deploys_controller_test.rb +51 -2
- data/test/controllers/tasks_controller_test.rb +24 -0
- data/test/dummy/db/schema.rb +2 -1
- data/test/dummy/db/seeds.rb +2 -0
- data/test/fixtures/shipit/check_runs.yml +11 -0
- data/test/fixtures/shipit/commits.yml +104 -0
- data/test/fixtures/shipit/stacks.yml +98 -3
- data/test/fixtures/shipit/tasks.yml +42 -0
- data/test/jobs/update_github_last_deployed_ref_job_test.rb +88 -0
- data/test/models/commits_test.rb +88 -1
- data/test/models/deploy_spec_test.rb +34 -6
- data/test/models/deploys_test.rb +308 -6
- data/test/models/rollbacks_test.rb +17 -11
- data/test/models/stacks_test.rb +217 -4
- data/test/models/tasks_test.rb +13 -0
- data/test/models/undeployed_commits_test.rb +62 -3
- data/test/test_helper.rb +0 -1
- data/test/unit/command_test.rb +55 -0
- data/test/unit/line_buffer_test.rb +20 -0
- metadata +142 -128
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bdb3736fb9999c0409f7b81081a52467f33a39ee419f2a40e14e6aca73866263
|
4
|
+
data.tar.gz: f58bfcbbc8084bb18cf2234ed0a75658a5c6bc9e2b593bc8a2b90e5f89d030b5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 19ffafe44e93c6f02f277c5208160a605286557f5080cf3569355fcfa85b2a8583eace47a89c1ac223490ad157de451f0837343d5b818e891c9794b27ca20554
|
7
|
+
data.tar.gz: 57be6895de1d5757a48b603cfb4796316f477b791feef7cfc876dc57b5226d869e65b2332e920e695e3edbba3bd99b39dc75e0e3ceeab2b6aee851eaeea15c2e
|
data/README.md
CHANGED
@@ -35,6 +35,11 @@ This guide aims to help you [set up](#installation-and-setup), [use](#using-ship
|
|
35
35
|
* [Configuring providers](#configuring-providers)
|
36
36
|
* [Free samples](/examples/shipit.yml)
|
37
37
|
|
38
|
+
**IV. CONTRIBUTING**
|
39
|
+
|
40
|
+
* [Instructions](#contributing-instructions)
|
41
|
+
* [Local development](#contributing-local-dev)
|
42
|
+
|
38
43
|
* * *
|
39
44
|
|
40
45
|
<h2 id="installation-and-setup">I. INSTALLATION & SETUP</h2>
|
@@ -112,7 +117,7 @@ The settings in the `shipit.yml` file relate to the different things you can do
|
|
112
117
|
* [CI](#ci) (`ci.require`, `ci.hide`, `ci.allow_failures`)
|
113
118
|
* [Merge Queue](#merge-queue) (`merge.revalidate_after`, `merge.require`, `merge.ignore`, `merge.max_divergence`)
|
114
119
|
* [Custom Tasks](#custom-tasks) (`tasks`)
|
115
|
-
* [Custom links](#custom-links) (`links`)
|
120
|
+
* [Custom links](#custom-links) (`links`)
|
116
121
|
* [Review Process](#review-process) (`review.checklist`, `review.monitoring`, `review.checks`)
|
117
122
|
|
118
123
|
All the settings in `shipit.yml` are optional. Most applications can be deployed from Shipit without any configuration.
|
@@ -628,3 +633,27 @@ For Kubernetes, you have to provision Shipit environment with the following tool
|
|
628
633
|
|
629
634
|
* `kubectl`
|
630
635
|
* `kubernetes-deploy` [gem](https://github.com/Shopify/kubernetes-deploy)
|
636
|
+
|
637
|
+
<h2 id="contributing">IV. CONTRIBUTING</h2>
|
638
|
+
|
639
|
+
<h3 id="contributing-instructions">Instructions</h3>
|
640
|
+
|
641
|
+
1. Fork it ( https://github.com/shopify/shipit-engine/fork )
|
642
|
+
1. Create your feature branch (git checkout -b my-new-feature)
|
643
|
+
1. Commit your changes (git commit -am 'Add some feature')
|
644
|
+
1. Push to the branch (git push origin my-new-feature)
|
645
|
+
1. Create a new Pull Request
|
646
|
+
|
647
|
+
<h3 id="contributing-local-dev">Local development</h3>
|
648
|
+
|
649
|
+
This repository has a [test/dummy](/test/dummy) app in it which can be used for local development without having to setup a new rails application.
|
650
|
+
|
651
|
+
Run `./bin/bootstrap` in order to bootstrap the dummy application. The bootstrap script is going to:
|
652
|
+
|
653
|
+
- Copy `config/secrets.development.example.yml` to `config/secrets.development.yml`;
|
654
|
+
- Make sure all dependencies are installed;
|
655
|
+
- Create and seed database (recreate database if already available);
|
656
|
+
|
657
|
+
Run `./test/dummy/bin/rails server` to run the rails dummy application.
|
658
|
+
|
659
|
+
Set the environment variable `SHIPIT_DISABLE_AUTH=1` in order to disable authentication.
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Shipit
|
2
|
+
module Api
|
3
|
+
class ReleaseStatusesController < BaseController
|
4
|
+
require_permission :deploy, :stack
|
5
|
+
|
6
|
+
params do
|
7
|
+
requires :status, String
|
8
|
+
validates :status, inclusion: {in: %w(success failure)}
|
9
|
+
end
|
10
|
+
def create
|
11
|
+
deploy = stack.deploys_and_rollbacks.find(params[:deploy_id])
|
12
|
+
case params[:status]
|
13
|
+
when 'success'
|
14
|
+
deploy.report_healthy!(user: current_user)
|
15
|
+
when 'failure'
|
16
|
+
deploy.report_faulty!(user: current_user)
|
17
|
+
end
|
18
|
+
render_resource deploy, status: :created
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -2,7 +2,7 @@ module Shipit
|
|
2
2
|
module Api
|
3
3
|
class StacksController < BaseController
|
4
4
|
require_permission :read, :stack, only: %i(index show)
|
5
|
-
require_permission :write, :stack, only: %i(create)
|
5
|
+
require_permission :write, :stack, only: %i(create destroy)
|
6
6
|
|
7
7
|
def index
|
8
8
|
render_resources stacks
|
@@ -25,6 +25,11 @@ module Shipit
|
|
25
25
|
render_resource stack
|
26
26
|
end
|
27
27
|
|
28
|
+
def destroy
|
29
|
+
stack.schedule_for_destroy!
|
30
|
+
head :accepted
|
31
|
+
end
|
32
|
+
|
28
33
|
private
|
29
34
|
|
30
35
|
def stack
|
@@ -1,7 +1,14 @@
|
|
1
1
|
module Shipit
|
2
2
|
class CommitsController < ShipitController
|
3
3
|
def update
|
4
|
-
|
4
|
+
if update_params[:locked].present?
|
5
|
+
if Shipit::CastValue.to_boolean(update_params[:locked])
|
6
|
+
commit.lock(current_user)
|
7
|
+
else
|
8
|
+
commit.unlock
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
5
12
|
head :ok
|
6
13
|
end
|
7
14
|
|
@@ -14,5 +21,9 @@ module Shipit
|
|
14
21
|
def stack
|
15
22
|
@stack ||= Stack.from_param!(params[:stack_id])
|
16
23
|
end
|
24
|
+
|
25
|
+
def update_params
|
26
|
+
@update_params ||= params.require(:commit).permit(:locked)
|
27
|
+
end
|
17
28
|
end
|
18
29
|
end
|
@@ -5,6 +5,7 @@ module Shipit
|
|
5
5
|
before_action :load_stack
|
6
6
|
before_action :load_deploy, only: %i(show rollback revert)
|
7
7
|
before_action :load_until_commit, only: :create
|
8
|
+
helper_method :short_commit_sha
|
8
9
|
|
9
10
|
def new
|
10
11
|
@commit = @stack.commits.by_sha!(params[:sha])
|
@@ -40,6 +41,12 @@ module Shipit
|
|
40
41
|
redirect_to rollback_stack_deploy_path(@stack, previous_deploy)
|
41
42
|
end
|
42
43
|
|
44
|
+
def short_commit_sha(task)
|
45
|
+
if previous_successful_deploy_commit(task)
|
46
|
+
@short_commit_sha ||= @previous_successful_deploy_commit&.short_sha
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
43
50
|
private
|
44
51
|
|
45
52
|
def load_deploy
|
@@ -57,5 +64,9 @@ module Shipit
|
|
57
64
|
def deploy_params
|
58
65
|
@deploy_params ||= params.require(:deploy).permit(:until_commit_id, env: @stack.deploy_variables.map(&:name))
|
59
66
|
end
|
67
|
+
|
68
|
+
def previous_successful_deploy_commit(task)
|
69
|
+
@previous_successful_deploy_commit ||= task.commit_to_rollback_to
|
70
|
+
end
|
60
71
|
end
|
61
72
|
end
|
@@ -17,7 +17,28 @@ module Shipit
|
|
17
17
|
return if flash.empty? && !stale?(last_modified: @stack.updated_at)
|
18
18
|
|
19
19
|
@tasks = @stack.tasks.order(id: :desc).preload(:since_commit, :until_commit, :user).limit(10)
|
20
|
-
|
20
|
+
|
21
|
+
commits = @stack.undeployed_commits do |scope|
|
22
|
+
scope.preload(:author, :statuses, :check_runs, :lock_author)
|
23
|
+
end
|
24
|
+
|
25
|
+
next_expected_commit_to_deploy = @stack.next_expected_commit_to_deploy(commits: commits)
|
26
|
+
|
27
|
+
@active_commits = []
|
28
|
+
@undeployed_commits = []
|
29
|
+
|
30
|
+
commits.each do |commit|
|
31
|
+
(commit.active? ? @active_commits : @undeployed_commits) << commit
|
32
|
+
end
|
33
|
+
|
34
|
+
@active_commits = map_to_undeployed_commit(
|
35
|
+
@active_commits,
|
36
|
+
next_expected_commit_to_deploy: next_expected_commit_to_deploy,
|
37
|
+
)
|
38
|
+
@undeployed_commits = map_to_undeployed_commit(
|
39
|
+
@undeployed_commits,
|
40
|
+
next_expected_commit_to_deploy: next_expected_commit_to_deploy,
|
41
|
+
)
|
21
42
|
end
|
22
43
|
|
23
44
|
def lookup
|
@@ -73,6 +94,13 @@ module Shipit
|
|
73
94
|
|
74
95
|
private
|
75
96
|
|
97
|
+
def map_to_undeployed_commit(commits, next_expected_commit_to_deploy:)
|
98
|
+
commits.map.with_index do |c, i|
|
99
|
+
index = commits.size - i - 1
|
100
|
+
UndeployedCommit.new(c, index: index, next_expected_commit_to_deploy: next_expected_commit_to_deploy)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
76
104
|
def load_stack
|
77
105
|
@stack = Stack.from_param!(params[:id])
|
78
106
|
end
|
@@ -2,7 +2,7 @@ module Shipit
|
|
2
2
|
class TasksController < ShipitController
|
3
3
|
include Pagination
|
4
4
|
|
5
|
-
before_action :stack
|
5
|
+
before_action :stack, except: [:lookup]
|
6
6
|
|
7
7
|
self.default_page_size = 20
|
8
8
|
|
@@ -50,8 +50,20 @@ module Shipit
|
|
50
50
|
render json: TailTaskSerializer.new(task, context: params)
|
51
51
|
end
|
52
52
|
|
53
|
+
def lookup
|
54
|
+
@task = Task.find(params[:id])
|
55
|
+
|
56
|
+
redirect_to url_for_task
|
57
|
+
end
|
58
|
+
|
53
59
|
private
|
54
60
|
|
61
|
+
def url_for_task
|
62
|
+
base_task = @task.is_a?(Deploy) ? @task.becomes(Deploy) : @task
|
63
|
+
|
64
|
+
url_for([base_task.stack, base_task])
|
65
|
+
end
|
66
|
+
|
55
67
|
def task
|
56
68
|
@task ||= stack.tasks.find(params[:id])
|
57
69
|
end
|
@@ -1,9 +1,7 @@
|
|
1
1
|
module Shipit
|
2
2
|
module StacksHelper
|
3
|
-
COMMIT_TITLE_LENGTH = 79
|
4
|
-
|
5
3
|
def redeploy_button(deployed_commit)
|
6
|
-
commit = UndeployedCommit.new(deployed_commit, 0)
|
4
|
+
commit = UndeployedCommit.new(deployed_commit, index: 0)
|
7
5
|
url = new_stack_deploy_path(commit.stack, sha: commit.sha)
|
8
6
|
classes = %W(btn btn--primary deploy-action #{commit.state})
|
9
7
|
|
@@ -46,7 +44,7 @@ module Shipit
|
|
46
44
|
end
|
47
45
|
|
48
46
|
def render_commit_message(pull_request_or_commit)
|
49
|
-
message = pull_request_or_commit.title.to_s
|
47
|
+
message = pull_request_or_commit.title.to_s
|
50
48
|
content_tag(:span, emojify(message), class: 'event-message')
|
51
49
|
end
|
52
50
|
|
@@ -80,5 +78,13 @@ module Shipit
|
|
80
78
|
def render_raw_commit_id_link(commit)
|
81
79
|
link_to(commit.short_sha, github_commit_url(commit), target: '_blank', class: 'number')
|
82
80
|
end
|
81
|
+
|
82
|
+
def unlock_commit_tooltip(commit)
|
83
|
+
if commit.lock_author.present?
|
84
|
+
t('commit.unlock_with_author', author: commit.lock_author.name)
|
85
|
+
else
|
86
|
+
t('commit.unlock')
|
87
|
+
end
|
88
|
+
end
|
83
89
|
end
|
84
90
|
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Shipit
|
2
|
+
class UpdateGithubLastDeployedRefJob < BackgroundJob
|
3
|
+
queue_as :default
|
4
|
+
|
5
|
+
DEPLOY_PREFIX = 'shipit-deploy'.freeze
|
6
|
+
|
7
|
+
def perform(stack)
|
8
|
+
stack_sha = stack.last_successful_deploy_commit&.sha
|
9
|
+
return unless stack_sha
|
10
|
+
|
11
|
+
environment = stack.environment
|
12
|
+
stack_ref = create_full_ref(environment)
|
13
|
+
client = Shipit.github.api
|
14
|
+
|
15
|
+
full_repo_name = stack.github_repo_name
|
16
|
+
|
17
|
+
update_or_create_ref(client: client, repo_name: full_repo_name, ref: stack_ref, new_sha: stack_sha)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def create_full_ref(stack_environment)
|
23
|
+
[DEPLOY_PREFIX, stack_environment].join("/")
|
24
|
+
end
|
25
|
+
|
26
|
+
def create_ref(client:, repo_name:, ref:, sha:)
|
27
|
+
client.create_ref(repo_name, ref, sha)
|
28
|
+
end
|
29
|
+
|
30
|
+
def update_or_create_ref(client:, repo_name:, ref:, new_sha:)
|
31
|
+
client.update_ref(repo_name, ref, new_sha)
|
32
|
+
rescue Octokit::UnprocessableEntity => e
|
33
|
+
error_msg = e.message
|
34
|
+
if error_msg.include? "Reference does not exist"
|
35
|
+
create_ref(client: client, repo_name: repo_name, ref: ref, sha: new_sha)
|
36
|
+
else
|
37
|
+
raise
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Shipit
|
2
|
+
class CommandLineUser
|
3
|
+
def present?
|
4
|
+
false
|
5
|
+
end
|
6
|
+
|
7
|
+
def email
|
8
|
+
'command_line@example.com'
|
9
|
+
end
|
10
|
+
|
11
|
+
def login
|
12
|
+
'command_line'
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
'CommandLine'
|
17
|
+
end
|
18
|
+
|
19
|
+
def avatar_url
|
20
|
+
'https://github.com/images/error/octocat_happy.gif'
|
21
|
+
end
|
22
|
+
|
23
|
+
def id
|
24
|
+
end
|
25
|
+
|
26
|
+
def github_id
|
27
|
+
end
|
28
|
+
|
29
|
+
def logged_in?
|
30
|
+
false
|
31
|
+
end
|
32
|
+
|
33
|
+
def authorized?
|
34
|
+
Shipit.authentication_disabled?
|
35
|
+
end
|
36
|
+
|
37
|
+
def stacks_contributed_to
|
38
|
+
[]
|
39
|
+
end
|
40
|
+
|
41
|
+
def avatar_uri
|
42
|
+
User::DEFAULT_AVATAR.dup
|
43
|
+
end
|
44
|
+
|
45
|
+
def created_at
|
46
|
+
Time.at(0).utc
|
47
|
+
end
|
48
|
+
alias_method :updated_at, :created_at
|
49
|
+
|
50
|
+
def read_attribute_for_serialization(attr)
|
51
|
+
public_send(attr)
|
52
|
+
end
|
53
|
+
|
54
|
+
def github_api
|
55
|
+
Shipit.github.api
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/app/models/shipit/commit.rb
CHANGED
@@ -7,7 +7,7 @@ module Shipit
|
|
7
7
|
belongs_to :stack
|
8
8
|
has_many :deploys
|
9
9
|
has_many :statuses, -> { order(created_at: :desc) }, dependent: :destroy, inverse_of: :commit
|
10
|
-
has_many :check_runs, dependent: :destroy
|
10
|
+
has_many :check_runs, -> { order(created_at: :desc) }, dependent: :destroy, inverse_of: :commit
|
11
11
|
has_many :commit_deployments, dependent: :destroy
|
12
12
|
has_many :release_statuses, dependent: :destroy
|
13
13
|
belongs_to :pull_request, inverse_of: :merge_commit, optional: true
|
@@ -23,6 +23,7 @@ module Shipit
|
|
23
23
|
|
24
24
|
belongs_to :author, class_name: 'User', inverse_of: :authored_commits
|
25
25
|
belongs_to :committer, class_name: 'User', inverse_of: :commits
|
26
|
+
belongs_to :lock_author, class_name: :User, optional: true, inverse_of: false
|
26
27
|
|
27
28
|
def author
|
28
29
|
super || AnonymousUser.new
|
@@ -32,6 +33,10 @@ module Shipit
|
|
32
33
|
super || AnonymousUser.new
|
33
34
|
end
|
34
35
|
|
36
|
+
def lock_author
|
37
|
+
super || AnonymousUser.new
|
38
|
+
end
|
39
|
+
|
35
40
|
scope :reachable, -> { where(detached: false) }
|
36
41
|
|
37
42
|
delegate :broadcast_update, :github_repo_name, :hidden_statuses, :required_statuses, :blocking_statuses,
|
@@ -47,6 +52,11 @@ module Shipit
|
|
47
52
|
where('id < ?', commit.try(:id) || commit)
|
48
53
|
end
|
49
54
|
|
55
|
+
def self.since(commit)
|
56
|
+
return all unless commit
|
57
|
+
where('id >= ?', commit.try(:id) || commit)
|
58
|
+
end
|
59
|
+
|
50
60
|
def self.until(commit)
|
51
61
|
return all unless commit
|
52
62
|
where('id <= ?', commit.try(:id) || commit)
|
@@ -164,6 +174,18 @@ module Shipit
|
|
164
174
|
|
165
175
|
delegate :pending?, :success?, :error?, :failure?, :blocking?, :state, to: :status
|
166
176
|
|
177
|
+
def active?
|
178
|
+
return false unless stack.active_task?
|
179
|
+
|
180
|
+
active_task = stack.active_task
|
181
|
+
|
182
|
+
if active_task.since_commit == active_task.until_commit
|
183
|
+
id == active_task.since_commit.id
|
184
|
+
else
|
185
|
+
id > active_task.since_commit.id && id <= active_task.until_commit.id
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
167
189
|
def deployable?
|
168
190
|
!locked? && (stack.ignore_ci? || (success? && !blocked?))
|
169
191
|
end
|
@@ -198,7 +220,7 @@ module Shipit
|
|
198
220
|
end
|
199
221
|
|
200
222
|
def message_header
|
201
|
-
message.lines.first.strip
|
223
|
+
message.lines.first.to_s.strip
|
202
224
|
end
|
203
225
|
|
204
226
|
# TODO: remove in a few versions when it is assumed the commits table was backfilled
|
@@ -271,6 +293,24 @@ module Shipit
|
|
271
293
|
end
|
272
294
|
end
|
273
295
|
|
296
|
+
def lock(user)
|
297
|
+
update!(
|
298
|
+
locked: true,
|
299
|
+
lock_author_id: user.id,
|
300
|
+
)
|
301
|
+
end
|
302
|
+
|
303
|
+
def self.lock_all(user)
|
304
|
+
update_all(
|
305
|
+
locked: true,
|
306
|
+
lock_author_id: user.id,
|
307
|
+
)
|
308
|
+
end
|
309
|
+
|
310
|
+
def unlock
|
311
|
+
update!(locked: false, lock_author: nil)
|
312
|
+
end
|
313
|
+
|
274
314
|
private
|
275
315
|
|
276
316
|
def message_parser
|