collavre_github 0.4.0 → 0.5.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 +4 -4
- data/app/controllers/collavre_github/creatives/integrations_controller.rb +66 -23
- data/app/controllers/collavre_github/webhooks_controller.rb +25 -4
- data/app/jobs/collavre_github/initial_markdown_sync_job.rb +19 -0
- data/app/jobs/collavre_github/markdown_sync_job.rb +16 -0
- data/app/models/collavre_github/repository_link.rb +7 -0
- data/app/services/collavre_github/client.rb +50 -0
- data/app/services/collavre_github/markdown_sync/content_processor.rb +113 -0
- data/app/services/collavre_github/markdown_sync/incremental_sync_service.rb +231 -0
- data/app/services/collavre_github/markdown_sync/initial_import_service.rb +190 -0
- data/app/services/collavre_github/tools/concerns/github_client_finder.rb +17 -0
- data/app/services/collavre_github/tools/github_pr_commits_service.rb +7 -10
- data/app/services/collavre_github/tools/github_pr_details_service.rb +21 -24
- data/app/services/collavre_github/tools/github_pr_diff_service.rb +13 -16
- data/app/services/collavre_github/webhook_provisioner.rb +18 -3
- data/app/views/collavre_github/auth/setup.html.erb +48 -3
- data/app/views/collavre_github/integrations/_modal.html.erb +18 -3
- data/config/locales/en.yml +9 -0
- data/config/locales/ko.yml +9 -0
- data/config/routes.rb +3 -1
- data/db/migrate/20260412000000_add_markdown_sync_to_repository_links.rb +10 -0
- data/db/migrate/20260416000000_add_markdown_root_creative_index.rb +5 -0
- data/lib/collavre_github/version.rb +1 -1
- data/lib/tasks/mock_import.rake +98 -0
- metadata +9 -1
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
module CollavreGithub
|
|
2
|
+
module MarkdownSync
|
|
3
|
+
class InitialImportService
|
|
4
|
+
MAX_FILES = 200
|
|
5
|
+
|
|
6
|
+
def initialize(repository_link:, user:)
|
|
7
|
+
@link = repository_link
|
|
8
|
+
@user = user
|
|
9
|
+
@client = CollavreGithub::Client.new(repository_link.github_account)
|
|
10
|
+
@repo = repository_link.repository_full_name
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
branch = resolve_branch
|
|
15
|
+
tree_entries = @client.tree(@repo, branch)
|
|
16
|
+
md_entries = tree_entries.select { |e| e.type == "blob" && e.path.end_with?(".md") }
|
|
17
|
+
|
|
18
|
+
if md_entries.size > MAX_FILES
|
|
19
|
+
Rails.logger.warn("[MarkdownSync] #{@repo}: #{md_entries.size} .md files found, limiting to #{MAX_FILES}")
|
|
20
|
+
md_entries = md_entries.first(MAX_FILES)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Archive existing root tree before reimport (pause/resume flow)
|
|
24
|
+
root = @link.markdown_root_creative
|
|
25
|
+
if root && root.archived_at.nil?
|
|
26
|
+
root.archive!
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
parent_creative = @link.creative
|
|
30
|
+
root_creative = create_root_creative(parent_creative)
|
|
31
|
+
@link.update!(markdown_root_creative_id: root_creative.id, last_synced_at: Time.current)
|
|
32
|
+
|
|
33
|
+
dir_map = { "" => root_creative }
|
|
34
|
+
created = [ root_creative ]
|
|
35
|
+
file_creatives = []
|
|
36
|
+
|
|
37
|
+
if md_entries.empty?
|
|
38
|
+
Collavre::Creative::RealtimeBroadcastable.broadcast_batch_created(created) if created.size > 1
|
|
39
|
+
return created
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
md_entries.sort_by(&:path).each do |entry|
|
|
43
|
+
parts = entry.path.split("/")
|
|
44
|
+
filename = parts.pop
|
|
45
|
+
|
|
46
|
+
parent = ensure_directories(parts, dir_map, root_creative, created)
|
|
47
|
+
|
|
48
|
+
content = @client.file_content(@repo, entry.path, ref: branch)
|
|
49
|
+
next if content.blank?
|
|
50
|
+
|
|
51
|
+
creative = Collavre::Creative.new(
|
|
52
|
+
description: filename,
|
|
53
|
+
parent: parent,
|
|
54
|
+
user: @user,
|
|
55
|
+
data: {
|
|
56
|
+
"source" => {
|
|
57
|
+
"type" => "github_markdown",
|
|
58
|
+
"repo" => @repo,
|
|
59
|
+
"path" => entry.path,
|
|
60
|
+
"sha" => entry.sha,
|
|
61
|
+
"markdown" => content,
|
|
62
|
+
"repository_link_id" => @link.id
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
)
|
|
66
|
+
creative.skip_github_validation = true
|
|
67
|
+
creative.save!
|
|
68
|
+
file_creatives << creative
|
|
69
|
+
created << creative
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Second pass: resolve relative links and images now that all creatives exist
|
|
73
|
+
path_map = build_path_map(created)
|
|
74
|
+
processor = ContentProcessor.new(client: @client, repo: @repo, branch: branch, path_to_creative_map: path_map)
|
|
75
|
+
|
|
76
|
+
file_creatives.each do |creative|
|
|
77
|
+
raw_md = creative.data.dig("source", "markdown")
|
|
78
|
+
next if raw_md.blank?
|
|
79
|
+
|
|
80
|
+
processed, blobs = processor.process(raw_md, creative.data.dig("source", "path"))
|
|
81
|
+
comment = create_content_comment(creative, processed)
|
|
82
|
+
attach_blobs(comment, blobs) if blobs.any?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
resequence_directories_first(created)
|
|
86
|
+
Collavre::Creative::RealtimeBroadcastable.broadcast_batch_created(created) if created.size > 1
|
|
87
|
+
created
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
private
|
|
91
|
+
|
|
92
|
+
def resolve_branch
|
|
93
|
+
if @link.sync_branch.present?
|
|
94
|
+
@link.sync_branch
|
|
95
|
+
else
|
|
96
|
+
branch = @client.default_branch(@repo)
|
|
97
|
+
@link.update_column(:sync_branch, branch)
|
|
98
|
+
branch
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def create_root_creative(parent)
|
|
103
|
+
repo_name = @repo.split("/").last
|
|
104
|
+
creative = Collavre::Creative.new(
|
|
105
|
+
description: repo_name,
|
|
106
|
+
parent: parent,
|
|
107
|
+
user: @user,
|
|
108
|
+
data: {
|
|
109
|
+
"source" => {
|
|
110
|
+
"type" => "github_markdown",
|
|
111
|
+
"repo" => @repo,
|
|
112
|
+
"path" => "",
|
|
113
|
+
"repository_link_id" => @link.id
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
)
|
|
117
|
+
creative.skip_github_validation = true
|
|
118
|
+
creative.save!
|
|
119
|
+
creative
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def build_path_map(creatives)
|
|
123
|
+
creatives.each_with_object({}) do |c, map|
|
|
124
|
+
path = c.data&.dig("source", "path")
|
|
125
|
+
map[path] = c if path.present?
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def create_content_comment(creative, markdown_content)
|
|
130
|
+
topic = creative.content_topic(fallback_user: @user)
|
|
131
|
+
creative.comments.create!(
|
|
132
|
+
content: markdown_content,
|
|
133
|
+
topic: topic,
|
|
134
|
+
user: @user,
|
|
135
|
+
skip_dispatch: true
|
|
136
|
+
)
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def attach_blobs(comment, blobs)
|
|
140
|
+
blobs.each { |blob| comment.images.attach(blob) }
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def resequence_directories_first(creatives)
|
|
144
|
+
Collavre::Creative.transaction do
|
|
145
|
+
creatives.group_by(&:parent_id).each do |_parent_id, siblings|
|
|
146
|
+
sorted = siblings.sort_by do |c|
|
|
147
|
+
path = c.data&.dig("source", "path") || ""
|
|
148
|
+
is_dir = path.end_with?("/") || path == ""
|
|
149
|
+
[ is_dir ? 0 : 1, c.description.to_s.downcase ]
|
|
150
|
+
end
|
|
151
|
+
sorted.each_with_index do |c, idx|
|
|
152
|
+
c.update_column(:sequence, idx)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def ensure_directories(parts, dir_map, root, created)
|
|
159
|
+
current_path = ""
|
|
160
|
+
parent = root
|
|
161
|
+
|
|
162
|
+
parts.each do |part|
|
|
163
|
+
current_path = current_path.empty? ? part : "#{current_path}/#{part}"
|
|
164
|
+
unless dir_map[current_path]
|
|
165
|
+
dir_creative = Collavre::Creative.new(
|
|
166
|
+
description: part,
|
|
167
|
+
parent: parent,
|
|
168
|
+
user: @user,
|
|
169
|
+
data: {
|
|
170
|
+
"source" => {
|
|
171
|
+
"type" => "github_markdown",
|
|
172
|
+
"repo" => @repo,
|
|
173
|
+
"path" => "#{current_path}/",
|
|
174
|
+
"repository_link_id" => @link.id
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
)
|
|
178
|
+
dir_creative.skip_github_validation = true
|
|
179
|
+
dir_creative.save!
|
|
180
|
+
dir_map[current_path] = dir_creative
|
|
181
|
+
created << dir_creative
|
|
182
|
+
end
|
|
183
|
+
parent = dir_map[current_path]
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
parent
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
@@ -30,6 +30,23 @@ module CollavreGithub
|
|
|
30
30
|
|
|
31
31
|
CollavreGithub::Client.new(link.github_account)
|
|
32
32
|
end
|
|
33
|
+
|
|
34
|
+
# Find client or return error hash. Yields the client to the block.
|
|
35
|
+
# Wraps the block with a StandardError rescue that returns { error: ... }.
|
|
36
|
+
#
|
|
37
|
+
# @param creative_id [Integer]
|
|
38
|
+
# @param repo [String]
|
|
39
|
+
# @param error_context [String] human-readable label for error messages
|
|
40
|
+
# @yield [client] the GitHub client
|
|
41
|
+
# @return [Hash] the block's result or an error hash
|
|
42
|
+
def with_github_client(creative_id:, repo:, error_context:, &block)
|
|
43
|
+
client = find_github_client(creative_id, repo)
|
|
44
|
+
return { error: "GitHub account not found for this creative and repository" } unless client
|
|
45
|
+
|
|
46
|
+
yield client
|
|
47
|
+
rescue StandardError => e
|
|
48
|
+
{ error: "Failed to #{error_context}: #{e.message}" }
|
|
49
|
+
end
|
|
33
50
|
end
|
|
34
51
|
end
|
|
35
52
|
end
|
|
@@ -23,17 +23,14 @@ module CollavreGithub
|
|
|
23
23
|
|
|
24
24
|
sig { params(creative_id: Integer, repo: String, pr_number: Integer).returns(T::Hash[Symbol, T.untyped]) }
|
|
25
25
|
def call(creative_id:, repo:, pr_number:)
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
with_github_client(creative_id: creative_id, repo: repo, error_context: "fetch PR commits") do |client|
|
|
27
|
+
messages = client.pull_request_commit_messages(repo, pr_number)
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
}
|
|
35
|
-
rescue StandardError => e
|
|
36
|
-
{ error: "Failed to fetch PR commits: #{e.message}" }
|
|
29
|
+
{
|
|
30
|
+
commits: messages.map.with_index(1) { |msg, i| { index: i, message: msg } },
|
|
31
|
+
count: messages.size
|
|
32
|
+
}
|
|
33
|
+
end
|
|
37
34
|
end
|
|
38
35
|
end
|
|
39
36
|
end
|
|
@@ -22,31 +22,28 @@ module CollavreGithub
|
|
|
22
22
|
|
|
23
23
|
sig { params(creative_id: Integer, repo: String, pr_number: Integer).returns(T::Hash[Symbol, T.untyped]) }
|
|
24
24
|
def call(creative_id:, repo:, pr_number:)
|
|
25
|
-
|
|
26
|
-
|
|
25
|
+
with_github_client(creative_id: creative_id, repo: repo, error_context: "fetch PR details") do |client|
|
|
26
|
+
pr = client.pull_request_details(repo, pr_number)
|
|
27
|
+
return { error: "Pull request not found" } unless pr
|
|
27
28
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
head_branch: pr.head&.ref
|
|
47
|
-
}
|
|
48
|
-
rescue StandardError => e
|
|
49
|
-
{ error: "Failed to fetch PR details: #{e.message}" }
|
|
29
|
+
{
|
|
30
|
+
number: pr.number,
|
|
31
|
+
title: pr.title,
|
|
32
|
+
body: pr.body.to_s.truncate(2000),
|
|
33
|
+
state: pr.state,
|
|
34
|
+
merged: pr.merged,
|
|
35
|
+
author: pr.user&.login,
|
|
36
|
+
created_at: pr.created_at&.iso8601,
|
|
37
|
+
updated_at: pr.updated_at&.iso8601,
|
|
38
|
+
merged_at: pr.merged_at&.iso8601,
|
|
39
|
+
additions: pr.additions,
|
|
40
|
+
deletions: pr.deletions,
|
|
41
|
+
changed_files: pr.changed_files,
|
|
42
|
+
html_url: pr.html_url,
|
|
43
|
+
base_branch: pr.base&.ref,
|
|
44
|
+
head_branch: pr.head&.ref
|
|
45
|
+
}
|
|
46
|
+
end
|
|
50
47
|
end
|
|
51
48
|
end
|
|
52
49
|
end
|
|
@@ -35,22 +35,19 @@ module CollavreGithub
|
|
|
35
35
|
def call(creative_id:, repo:, pr_number:, max_length: nil)
|
|
36
36
|
max_length ||= DIFF_MAX_LENGTH
|
|
37
37
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
rescue StandardError => e
|
|
53
|
-
{ error: "Failed to fetch PR diff: #{e.message}" }
|
|
38
|
+
with_github_client(creative_id: creative_id, repo: repo, error_context: "fetch PR diff") do |client|
|
|
39
|
+
diff = client.pull_request_diff(repo, pr_number)
|
|
40
|
+
return { error: "Could not fetch diff", diff: nil } unless diff
|
|
41
|
+
|
|
42
|
+
truncated = diff.length > max_length
|
|
43
|
+
diff_text = truncated ? "#{diff[0, max_length]}\n...\n[Diff truncated to #{max_length} characters]" : diff
|
|
44
|
+
|
|
45
|
+
{
|
|
46
|
+
diff: diff_text,
|
|
47
|
+
truncated: truncated,
|
|
48
|
+
original_length: diff.length
|
|
49
|
+
}
|
|
50
|
+
end
|
|
54
51
|
end
|
|
55
52
|
end
|
|
56
53
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
module CollavreGithub
|
|
2
2
|
class WebhookProvisioner
|
|
3
3
|
EVENTS = %w[pull_request].freeze
|
|
4
|
+
EVENTS_WITH_PUSH = %w[pull_request push].freeze
|
|
4
5
|
CONTENT_TYPE = "json".freeze
|
|
5
6
|
|
|
6
7
|
def self.ensure_for_links(account:, links:, webhook_url:)
|
|
@@ -23,8 +24,15 @@ module CollavreGithub
|
|
|
23
24
|
end
|
|
24
25
|
|
|
25
26
|
def remove_for_repositories(repositories)
|
|
27
|
+
# Batch-query to avoid N+1: find all repo names that still have links
|
|
28
|
+
linked_names = CollavreGithub::RepositoryLink
|
|
29
|
+
.where(repository_full_name: repositories)
|
|
30
|
+
.distinct
|
|
31
|
+
.pluck(:repository_full_name)
|
|
32
|
+
.to_set
|
|
33
|
+
|
|
26
34
|
repositories.each do |repository_full_name|
|
|
27
|
-
next if
|
|
35
|
+
next if linked_names.include?(repository_full_name)
|
|
28
36
|
|
|
29
37
|
remove_webhook(repository_full_name)
|
|
30
38
|
end
|
|
@@ -84,7 +92,7 @@ module CollavreGithub
|
|
|
84
92
|
repository_full_name,
|
|
85
93
|
url: webhook_url,
|
|
86
94
|
secret: secret,
|
|
87
|
-
events:
|
|
95
|
+
events: events_for(repository_full_name),
|
|
88
96
|
content_type: CONTENT_TYPE
|
|
89
97
|
)
|
|
90
98
|
end
|
|
@@ -95,11 +103,18 @@ module CollavreGithub
|
|
|
95
103
|
hook_id,
|
|
96
104
|
url: webhook_url,
|
|
97
105
|
secret: secret,
|
|
98
|
-
events:
|
|
106
|
+
events: events_for(repository_full_name),
|
|
99
107
|
content_type: CONTENT_TYPE
|
|
100
108
|
)
|
|
101
109
|
end
|
|
102
110
|
|
|
111
|
+
def events_for(repository_full_name)
|
|
112
|
+
has_markdown_sync = CollavreGithub::RepositoryLink
|
|
113
|
+
.where(repository_full_name: repository_full_name, markdown_sync_enabled: true)
|
|
114
|
+
.exists?
|
|
115
|
+
has_markdown_sync ? EVENTS_WITH_PUSH : EVENTS
|
|
116
|
+
end
|
|
117
|
+
|
|
103
118
|
def primary_link_for(repository_full_name)
|
|
104
119
|
CollavreGithub::RepositoryLink
|
|
105
120
|
.where(repository_full_name: repository_full_name)
|
|
@@ -101,7 +101,13 @@
|
|
|
101
101
|
</div>
|
|
102
102
|
</div>
|
|
103
103
|
|
|
104
|
-
<!-- Step 3:
|
|
104
|
+
<!-- Step 3: Markdown Sync -->
|
|
105
|
+
<div class="step" id="step-markdown-sync">
|
|
106
|
+
<p class="step-subtext"><%= t('collavre_github.markdown_sync.step_description', default: 'Enable Markdown Sync to import .md files as read-only creatives. Auto-synced on push.') %></p>
|
|
107
|
+
<div id="markdown-sync-list" class="list-box"></div>
|
|
108
|
+
</div>
|
|
109
|
+
|
|
110
|
+
<!-- Step 4: Summary -->
|
|
105
111
|
<div class="step" id="step-summary">
|
|
106
112
|
<p class="step-subtext"><%= t('collavre_github.integration.summary') %></p>
|
|
107
113
|
<p id="webhook-instructions" class="step-subtext" style="display:none;"><%= t('collavre_github.integration.webhook_instructions') %></p>
|
|
@@ -139,10 +145,11 @@
|
|
|
139
145
|
var loadReposError = wizard.dataset.loadReposError;
|
|
140
146
|
var saveErrorMsg = wizard.dataset.saveError;
|
|
141
147
|
|
|
142
|
-
var steps = ['organization', 'repositories', 'summary', 'done'];
|
|
148
|
+
var steps = ['organization', 'repositories', 'markdown-sync', 'summary', 'done'];
|
|
143
149
|
var currentStepIndex = 0;
|
|
144
150
|
var selectedOrg = null;
|
|
145
151
|
var selectedRepos = new Set();
|
|
152
|
+
var markdownSyncRepos = new Set();
|
|
146
153
|
var webhookDetails = {};
|
|
147
154
|
|
|
148
155
|
var prevBtn = document.getElementById('prev-btn');
|
|
@@ -278,6 +285,39 @@
|
|
|
278
285
|
});
|
|
279
286
|
}
|
|
280
287
|
|
|
288
|
+
function populateMarkdownSync() {
|
|
289
|
+
var syncList = document.getElementById('markdown-sync-list');
|
|
290
|
+
syncList.innerHTML = '';
|
|
291
|
+
var repos = Array.from(selectedRepos);
|
|
292
|
+
if (!repos.length) {
|
|
293
|
+
syncList.innerHTML = '<p style="padding:0.5em;color:#999;">No repositories selected.</p>';
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
repos.forEach(function(fullName) {
|
|
297
|
+
var label = document.createElement('label');
|
|
298
|
+
label.className = 'list-item';
|
|
299
|
+
|
|
300
|
+
var input = document.createElement('input');
|
|
301
|
+
input.type = 'checkbox';
|
|
302
|
+
input.value = fullName;
|
|
303
|
+
input.checked = markdownSyncRepos.has(fullName);
|
|
304
|
+
input.addEventListener('change', function() {
|
|
305
|
+
if (input.checked) {
|
|
306
|
+
markdownSyncRepos.add(fullName);
|
|
307
|
+
} else {
|
|
308
|
+
markdownSyncRepos.delete(fullName);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
var span = document.createElement('span');
|
|
313
|
+
span.textContent = fullName + ' — Markdown Sync';
|
|
314
|
+
|
|
315
|
+
label.appendChild(input);
|
|
316
|
+
label.appendChild(span);
|
|
317
|
+
syncList.appendChild(label);
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
|
|
281
321
|
function updateSummary() {
|
|
282
322
|
var summaryList = document.getElementById('summary-repos');
|
|
283
323
|
var summaryEmpty = document.getElementById('summary-empty');
|
|
@@ -320,7 +360,11 @@
|
|
|
320
360
|
|
|
321
361
|
function saveSelection() {
|
|
322
362
|
clearError();
|
|
323
|
-
var
|
|
363
|
+
var markdownSync = {};
|
|
364
|
+
Array.from(selectedRepos).forEach(function(repo) {
|
|
365
|
+
markdownSync[repo] = markdownSyncRepos.has(repo);
|
|
366
|
+
});
|
|
367
|
+
var payload = { repositories: Array.from(selectedRepos), markdown_sync: markdownSync };
|
|
324
368
|
|
|
325
369
|
fetch('/github/creatives/' + creativeId + '/integration', {
|
|
326
370
|
method: 'PATCH',
|
|
@@ -371,6 +415,7 @@
|
|
|
371
415
|
var nextIndex = currentStepIndex + 1;
|
|
372
416
|
showStep(nextIndex);
|
|
373
417
|
if (steps[nextIndex] === 'repositories') loadRepositories();
|
|
418
|
+
if (steps[nextIndex] === 'markdown-sync') populateMarkdownSync();
|
|
374
419
|
});
|
|
375
420
|
|
|
376
421
|
finishBtn.addEventListener('click', function() {
|
|
@@ -10,6 +10,11 @@
|
|
|
10
10
|
data-delete-error="<%= t('collavre_github.integration.delete_error', default: 'Failed to remove the Github integration.') %>"
|
|
11
11
|
data-delete-button-label="<%= t('collavre_github.integration.delete_button', default: 'Remove integration') %>"
|
|
12
12
|
data-delete-select-warning="<%= t('collavre_github.integration.delete_select_warning', default: 'Select repositories to delete.') %>"
|
|
13
|
+
data-resync-button-label="<%= t('collavre_github.integration.resync_button', default: 'Resync all') %>"
|
|
14
|
+
data-resync-confirm="<%= t('collavre_github.integration.resync_confirm', default: 'Re-import all markdown files from the selected repositories?') %>"
|
|
15
|
+
data-resync-success="<%= t('collavre_github.integration.resync_success', default: 'Re-sync started. The tree will update shortly.') %>"
|
|
16
|
+
data-resync-error="<%= t('collavre_github.integration.resync_error', default: 'Failed to start re-sync.') %>"
|
|
17
|
+
data-resync-select-warning="<%= t('collavre_github.integration.resync_select_warning', default: 'Select repositories to resync.') %>"
|
|
13
18
|
style="display:none;position:fixed;top:0;left:0;width:100vw;height:100vh;z-index:10000;align-items:center;justify-content:center;">
|
|
14
19
|
<div class="popup-box" style="min-width:360px;max-width:90vw;">
|
|
15
20
|
<button type="button" id="close-github-modal" class="popup-close-btn">×</button>
|
|
@@ -27,9 +32,14 @@
|
|
|
27
32
|
<div id="github-existing-connections" style="display:none;margin-top:1.25em;">
|
|
28
33
|
<p style="margin-bottom:0.5em;"><%= t('collavre_github.integration.existing_intro', default: 'Connected repositories:') %></p>
|
|
29
34
|
<ul id="github-existing-repo-list" style="padding-left:1.2em;margin-bottom:0.75em;color:var(--color-text);"></ul>
|
|
30
|
-
<
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
<div style="display:flex;gap:0.5em;flex-wrap:wrap;">
|
|
36
|
+
<button type="button" id="github-delete-btn" class="btn btn-danger" style="display:none;">
|
|
37
|
+
<%= t('collavre_github.integration.delete_button', default: 'Remove integration') %>
|
|
38
|
+
</button>
|
|
39
|
+
<button type="button" id="github-resync-btn" class="btn btn-secondary" style="display:none;">
|
|
40
|
+
<%= t('collavre_github.integration.resync_button', default: 'Resync all') %>
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
33
43
|
</div>
|
|
34
44
|
</div>
|
|
35
45
|
|
|
@@ -43,6 +53,11 @@
|
|
|
43
53
|
<div id="github-repository-list" class="github-list github-modal-list-box" style="max-height:240px;overflow:auto;"></div>
|
|
44
54
|
</div>
|
|
45
55
|
|
|
56
|
+
<div class="github-wizard-step" id="github-step-markdown-sync" style="display:none;">
|
|
57
|
+
<p class="github-modal-subtext"><%= t('collavre_github.markdown_sync.step_description', default: 'Enable Markdown Sync to import .md files as read-only creatives. Changes are auto-synced on push to the default branch.') %></p>
|
|
58
|
+
<div id="github-markdown-sync-list" class="github-list github-modal-list-box" style="max-height:240px;overflow:auto;"></div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
46
61
|
<div class="github-wizard-step" id="github-step-summary" style="display:none;">
|
|
47
62
|
<p class="github-modal-subtext"><%= t('collavre_github.integration.summary', default: 'The following repositories will be linked to this Creative:') %></p>
|
|
48
63
|
<p id="github-webhook-instructions" class="github-modal-subtext" style="display:none;margin-bottom:0.5em;"><%= t('collavre_github.integration.webhook_instructions', default: 'Configure each repository with the webhook details below.') %></p>
|
data/config/locales/en.yml
CHANGED
|
@@ -16,6 +16,11 @@ en:
|
|
|
16
16
|
delete_error: "Failed to remove the Github integration."
|
|
17
17
|
delete_button: "Remove integration"
|
|
18
18
|
delete_select_warning: "Select repositories to delete."
|
|
19
|
+
resync_button: "Resync all"
|
|
20
|
+
resync_confirm: "Re-import all markdown files from the selected repositories?"
|
|
21
|
+
resync_success: "Re-sync started. The tree will update shortly."
|
|
22
|
+
resync_error: "Failed to start re-sync."
|
|
23
|
+
resync_select_warning: "Select repositories to resync."
|
|
19
24
|
connect: "Sign in with your Github account to start linking."
|
|
20
25
|
login_button: "Sign in with Github"
|
|
21
26
|
choose_org: "Select the organization that owns the repositories."
|
|
@@ -34,6 +39,10 @@ en:
|
|
|
34
39
|
auth:
|
|
35
40
|
login_first: "Please log in first to connect your GitHub account"
|
|
36
41
|
connected: "GitHub account connected successfully"
|
|
42
|
+
markdown_sync:
|
|
43
|
+
step_description: "Enable Markdown Sync to import .md files as read-only creatives. Changes are auto-synced on push to the default branch."
|
|
44
|
+
not_enabled: "Markdown sync is not enabled for this repository."
|
|
45
|
+
resync_started: "Re-sync started. The tree will update shortly."
|
|
37
46
|
errors:
|
|
38
47
|
not_connected: "GitHub account not connected"
|
|
39
48
|
forbidden: "You don't have permission to perform this action"
|
data/config/locales/ko.yml
CHANGED
|
@@ -16,6 +16,11 @@ ko:
|
|
|
16
16
|
delete_error: "Github 연동 삭제에 실패했습니다."
|
|
17
17
|
delete_button: "연동 삭제"
|
|
18
18
|
delete_select_warning: "삭제할 Repository를 선택하세요."
|
|
19
|
+
resync_button: "전체 다시 싱크"
|
|
20
|
+
resync_confirm: "선택한 저장소의 마크다운 파일을 전체 다시 가져오시겠습니까?"
|
|
21
|
+
resync_success: "재동기화가 시작되었습니다. 잠시 후 트리가 업데이트됩니다."
|
|
22
|
+
resync_error: "재동기화를 시작하지 못했습니다."
|
|
23
|
+
resync_select_warning: "재동기화할 저장소를 선택하세요."
|
|
19
24
|
connect: "Github 계정으로 로그인하여 연동을 시작하세요."
|
|
20
25
|
login_button: "Github으로 로그인"
|
|
21
26
|
choose_org: "저장소가 속한 조직을 선택하세요."
|
|
@@ -34,6 +39,10 @@ ko:
|
|
|
34
39
|
auth:
|
|
35
40
|
login_first: "GitHub 계정을 연결하려면 먼저 로그인하세요"
|
|
36
41
|
connected: "GitHub 계정이 연결되었습니다"
|
|
42
|
+
markdown_sync:
|
|
43
|
+
step_description: "Markdown Sync를 활성화하면 .md 파일이 읽기전용 크리에이티브로 가져와집니다. 기본 브랜치에 push하면 자동으로 동기화됩니다."
|
|
44
|
+
not_enabled: "이 저장소에 Markdown Sync가 활성화되어 있지 않습니다."
|
|
45
|
+
resync_started: "재동기화가 시작되었습니다. 곧 트리가 업데이트됩니다."
|
|
37
46
|
errors:
|
|
38
47
|
not_connected: "GitHub 계정이 연결되지 않았습니다"
|
|
39
48
|
forbidden: "이 작업을 수행할 권한이 없습니다"
|
data/config/routes.rb
CHANGED
|
@@ -16,6 +16,8 @@ CollavreGithub::Engine.routes.draw do
|
|
|
16
16
|
|
|
17
17
|
# Creative integration endpoints
|
|
18
18
|
resources :creatives, only: [] do
|
|
19
|
-
resource :integration, module: :creatives, only: [ :show, :update, :destroy ]
|
|
19
|
+
resource :integration, module: :creatives, only: [ :show, :update, :destroy ] do
|
|
20
|
+
post :resync, on: :member
|
|
21
|
+
end
|
|
20
22
|
end
|
|
21
23
|
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
class AddMarkdownSyncToRepositoryLinks < ActiveRecord::Migration[8.0]
|
|
2
|
+
def change
|
|
3
|
+
add_column :github_repository_links, :markdown_sync_enabled, :boolean, default: false, null: false
|
|
4
|
+
add_column :github_repository_links, :markdown_root_creative_id, :integer
|
|
5
|
+
add_column :github_repository_links, :last_synced_at, :datetime
|
|
6
|
+
add_column :github_repository_links, :sync_branch, :string
|
|
7
|
+
|
|
8
|
+
add_index :github_repository_links, :markdown_sync_enabled
|
|
9
|
+
end
|
|
10
|
+
end
|