Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
galaxyproject
GitHub Repository: galaxyproject/training-material
Path: blob/main/_plugins/author-page.rb
1677 views
1
# frozen_string_literal: true
2
3
require './_plugins/gtn/mod'
4
require './_plugins/gtn'
5
6
module Jekyll
7
module Generators
8
##
9
# This class generates the GTN's author pags
10
class AuthorPageGenerator < Generator
11
safe true
12
13
##
14
# This extracts the contributions and pushes them on to an existing
15
# datastructure, modifying it in the process. It's pretty gross.
16
#
17
# Params
18
# +t+:: The tutorial, slide, or news item
19
# +datastructure+:: The hash of contributors that the author information should be pushed onto
20
# +flat+:: Whether the datastructure is a flat array or a nested array
21
#
22
# Returns
23
# +datastructure+:: The modified datastructure
24
def pusher(t, datastructure, flat)
25
if t.data.key?('contributors')
26
if flat
27
t.data['contributors'].each { |c| datastructure[c].push(t) }
28
else
29
t.data['contributors'].each { |c| datastructure[c].push([t, nil]) }
30
end
31
elsif t.data.key?('contributions')
32
t.data['contributions'].each do |contribution_type, contributor|
33
contributor.each do |c|
34
if flat
35
datastructure[c].push(t)
36
else
37
datastructure[c].push([t, contribution_type])
38
end
39
end
40
end
41
end
42
43
t.data['maintainers'].each { |c| datastructure[c].push([t, 'maintainer']) } if t.data.key?('maintainers')
44
t.data['funding'].each { |c| datastructure[c].push([t, 'funding']) } if t.data.key?('funding')
45
46
datastructure
47
end
48
49
##
50
# This generates the author pages
51
# Params
52
# +site+:: The site object
53
def generate(site)
54
return unless site.layouts.key? 'contributor_index'
55
56
dir = 'hall-of-fame'
57
58
# pre-calculating this hash saves about 4.9 seconds off the previous
59
# build time of 5 seconds.
60
tutorials_by_author = Hash.new { |hash, key| hash[key] = [] }
61
learning_pathways_by_author = Hash.new { |hash, key| hash[key] = [] }
62
slides_by_author = Hash.new { |hash, key| hash[key] = [] }
63
news_by_author = Hash.new { |hash, key| hash[key] = [] }
64
events_by_author = Hash.new { |hash, key| hash[key] = [] }
65
videos_by_author = Hash.new { |hash, key| hash[key] = [] }
66
faqs_by_author = Hash.new { |hash, key| hash[key] = [] }
67
has_philosophy = Hash.new { false }
68
69
prs_by_author = Hash.new { |hash, key| hash[key] = [] }
70
reviews_by_author = Hash.new { |hash, key| hash[key] = [] }
71
72
site.data['github'].each do |num, pr|
73
prs_by_author[pr['author']['login']] << [num, pr['mergedAt']]
74
75
pr['reviews'].each do |review|
76
reviews_by_author[review['author']['login']] << [num, review['submittedAt'], review['state']]
77
end
78
end
79
80
site.pages.each do |t|
81
# Skip Symlinks
82
if t.data['symlink']
83
next
84
end
85
86
# Tutorials
87
pusher(t, tutorials_by_author, false) if t['layout'] == 'tutorial_hands_on'
88
89
# Slides
90
if !%w[base_slides introduction_slides tutorial_slides].index(t['layout']).nil?
91
pusher(t, slides_by_author, false)
92
end
93
94
pusher(t, events_by_author, false) if t['layout'] == 'event' or t['layout'] == "event-external"
95
96
pusher(t, faqs_by_author, false) if t['layout'] == 'faq'
97
98
t.data.fetch('recordings', []).each do |r|
99
r.fetch('captioners', []).each { |ent| videos_by_author[ent].push([t, 'captioner', r]) }
100
r.fetch('speakers', []).each { |ent| videos_by_author[ent].push([t, 'speaker', r]) }
101
end
102
103
pusher(t, learning_pathways_by_author, false) if t['layout'] == 'learning-pathway'
104
105
# Philosophies
106
has_philosophy[t.data['username']] = true if t['layout'] == 'training_philosophy' && !t.data['username'].nil?
107
end
108
109
site.posts.docs.each do |t|
110
# News
111
pusher(t, news_by_author, true) if t['layout'] == 'news'
112
end
113
114
Gtn::Contributors.list(site).each_key do |contributor|
115
# Using PageWithoutAFile instead of a custom class which reads files
116
# from disk each time, saves some time, but it is unclear how much
117
# due to how the previous was accounted. But assuming 0.040s per page * 193 should be about 8 seconds.
118
page2 = PageWithoutAFile.new(site, '', File.join(dir, contributor), 'index.html')
119
page2.content = nil
120
name = Gtn::Contributors.fetch_contributor(site, contributor).fetch('name', contributor)
121
122
# Their tutorials
123
page2.data['contributor'] = contributor
124
page2.data['personname'] = name
125
page2.data['title'] = "GTN Contributor: #{name}"
126
page2.data['layout'] = 'contributor_index'
127
128
page2.data['tutorials'] = tutorials_by_author[contributor].group_by{|x| x[0] }.map{|k, v| [k, v.map{|vv| vv[1]}.compact]}
129
page2.data['slides'] = slides_by_author[contributor].group_by{|x| x[0] }.map{|k, v| [k, v.map{|vv| vv[1]}.compact]}
130
page2.data['news'] = news_by_author[contributor]
131
page2.data['learning_pathways'] = learning_pathways_by_author[contributor]
132
page2.data['events'] = events_by_author[contributor].group_by{|x| x[0] }.map{|k, v| [k, v.map{|vv| vv[1]}.compact]}
133
page2.data['videos'] = videos_by_author[contributor].group_by{|x| x[0] }.map{|k, v| [k, v.map{|vv| vv[1]}.uniq.compact]}
134
page2.data['faqs'] = faqs_by_author[contributor].group_by{|x| x[0] }.map{|k, v| [k, v.map{|vv| vv[1]}.uniq.compact]}
135
136
page2.data['tutorials_count'] = tutorials_by_author[contributor].length
137
page2.data['slides_count'] = slides_by_author[contributor].length
138
page2.data['news_count'] = news_by_author[contributor].length
139
page2.data['learning_pathways_count'] = learning_pathways_by_author[contributor].length
140
page2.data['events_count'] = events_by_author[contributor].length
141
page2.data['videos_count'] = videos_by_author[contributor].length
142
page2.data['faqs_count'] = faqs_by_author[contributor].length
143
144
page2.data['editors'] = Gtn::TopicFilter.enumerate_topics(site).select do |t|
145
t.fetch('editorial_board', []).include?(contributor)
146
end
147
# Also their learning pathways
148
page2.data['editors'] += site.pages.select do |t|
149
t['layout'] == 'learning-pathway' && t.data.fetch('editorial_board', []).include?(contributor)
150
end
151
page2.data['editor_count'] = page2.data['editors'].length
152
153
page2.data['has_philosophy'] = has_philosophy[contributor]
154
155
countable_reviews = reviews_by_author[contributor]
156
.reject{|x| x[1].nil?} # Group by PRs.
157
.group_by{|x| x[0]}.map{|x, r| r.sort_by{|r1| r1[1]}.max}.sort_by{|w| w[1]}.reverse
158
159
page2.data['gh_prs_count'] = prs_by_author[contributor].count
160
page2.data['gh_reviews_count'] = countable_reviews.count
161
162
page2.data['gh_prs_recent'] = prs_by_author[contributor]
163
.reject{|x| x[1].nil?}.sort_by { |x| x[1] }.reverse.take(5)
164
.map{|x| x[0]}
165
page2.data['gh_reviews_recent'] = countable_reviews.take(5)
166
.map{|x| x[0]}
167
168
site.pages << page2
169
end
170
end
171
end
172
end
173
end
174
175
Jekyll::Hooks.register :site, :post_read do |site|
176
if Jekyll.env == 'production'
177
Jekyll.logger.info "[GTN/Reviewers] Ingesting GitHub reviewer metadata"
178
start_time = Time.now
179
# Maps a lowercase version of their name to the potential mixed-case version
180
contribs_lower = site.data['contributors'].map{|k, v| [k.downcase, k]}.to_h
181
182
# Annotate the from github metadata
183
gh_reviewers_by_path = Hash.new { |hash, key| hash[key] = [] }
184
# Hash of PRs by path
185
site.data['github'].each do |num, pr|
186
# Within a PR we have some reviews, let's get that set organised:
187
reviewers = pr['reviews'].map do |review|
188
# Just "people"
189
contribs_lower.fetch(review['author']['login'].downcase, review['author']['login'])
190
end.uniq.reject{|x| x == 'github-actions'}
191
192
pr['files'].select{|p| p['path'] =~ /.(md|html)$/}.each do |file|
193
real_path = Gtn::PublicationTimes.chase_rename(file['path'])
194
gh_reviewers_by_path[real_path] += reviewers
195
gh_reviewers_by_path[real_path].uniq!
196
end
197
end
198
199
# For all of our pages, if the path is mentioned above, then, tag it.
200
site.pages.select{|t| gh_reviewers_by_path.key?(t.path)}.each do |t|
201
if t['layout'] == 'tutorial_hands_on' or !%w[base_slides introduction_slides tutorial_slides].index(t['layout']).nil?
202
if t.data.key?('contributors')
203
# Automatically 'upgrade' to new structure
204
t.data['contributions'] = {
205
'authorship' => t.data['contributors'],
206
'reviewing' => gh_reviewers_by_path[t.path]
207
}
208
t.data.delete('contributors')
209
elsif t.data.key?('contributions')
210
if t.data['contributions'].key?('reviewing')
211
t.data['contributions']['reviewing'] += gh_reviewers_by_path[t.path]
212
else
213
t.data['contributions']['reviewing'] = gh_reviewers_by_path[t.path]
214
end
215
t.data['contributions']['reviewing'].uniq!
216
end
217
end
218
end
219
220
site.posts.docs.select{|t| gh_reviewers_by_path.key?(t.path)}.each do |t|
221
if t['layout'] == 'news'
222
if t.data.key?('contributors')
223
t.data['contributions'] = {
224
'authorship' => t.data['contributors'],
225
'reviewing' => gh_reviewers_by_path[t.path]
226
}
227
t.data.delete('contributors')
228
elsif t.data.key?('contributions')
229
if t.data['contributions'].key?('reviewing')
230
t.data['contributions']['reviewing'] += gh_reviewers_by_path[t.path]
231
else
232
t.data['contributions']['reviewing'] = gh_reviewers_by_path[t.path]
233
end
234
t.data['contributions']['reviewing'].uniq!
235
end
236
end
237
end
238
Jekyll.logger.info "[GTN/Reviewers] Complete in #{Time.now - start_time} seconds"
239
end
240
end
241
242