lib/team_hub/joiner.rb
# team_hub - Components for creating a team Hub using Jekyll
#
# Written in 2014 by Mike Bland (michael.bland@gsa.gov)
# on behalf of the 18F team, part of the US General Services Administration:
# https://18f.gsa.gov/
#
# To the extent possible under law, the author(s) have dedicated all copyright
# and related and neighboring rights to this software to the public domain
# worldwide. This software is distributed without any warranty.
#
# You should have received a copy of the CC0 Public Domain Dedication along
# with this software. If not, see
# <https://creativecommons.org/publicdomain/zero/1.0/>.
#
# @author Mike Bland (michael.bland@gsa.gov)
require 'hash-joiner'
require 'team_hub/private_assets'
require 'weekly_snippets/version'
module TeamHub
# Joins the data from +_data+, +_data/public+, and +_data/private+ into
# +site.data+, making the data look as though it came from a single source.
# Also filters out private data when +site.config[+'public'] is +true+ (aka
# "public mode").
class Joiner
attr_reader :site, :data, :public_mode, :team_by_email, :source
# Used to standardize snippet data of different versions before joining
# and publishing.
SNIPPET_VERSIONS = {
'v1' => ::WeeklySnippets::Version.new(
version_name:'v1',
field_map:{
'Username' => 'username',
'Timestamp' => 'timestamp',
'Name' => 'full_name',
'Snippets' => 'last-week',
'No This Week' => 'this-week',
}
),
'v2' => ::WeeklySnippets::Version.new(
version_name:'v2',
field_map:{
'Timestamp' => 'timestamp',
'Public vs. Private' => 'public',
'Last Week' => 'last-week',
'This Week' => 'this-week',
'Username' => 'username',
},
markdown_supported: true
),
'v3' => ::WeeklySnippets::Version.new(
version_name:'v3',
field_map:{
'Timestamp' => 'timestamp',
'Public' => 'public',
'Username' => 'username',
'Last week' => 'last-week',
'This week' => 'this-week',
},
public_field: 'public',
public_value: 'Public',
markdown_supported: true
),
}
# +site+:: Jekyll site data object
def initialize(site)
@site = site
@data = site.data
@public_mode = site.config['public']
if (site.data['private'] || {}).empty?
@source = 'public'
@join_source = @data
else
@source = 'private'
@join_source = site.data['private']
end
# We'll always need a 'team' property.
@join_source['team'] ||= []
['team', 'projects', 'departments', 'working_groups'].each do |c|
i = @join_source[c]
@join_source[c] = Joiner.flatten_index(i) if i.instance_of? Hash
end
create_team_by_email_index
end
# Takes Hash<string, Array<Hash>> collections and flattens them into an
# Array<Hash>.
def self.flatten_index(index)
private_data = index['private']
index['private'] = {'private' => private_data.values} if private_data
index.values
end
# Joins public and private project data.
def join_project_data
promote_private_data 'projects'
if @public_mode
@data['projects'].delete_if {|p| p['status'] == 'Hold'}
end
end
# Creates +self.team_by_email+, a hash of email address => username to use
# as an index into +site.data[+'team'] when joining snippet data.
#
# MUST be called before remove_data, or else private email addresses will
# be inaccessible and snippets will not be joined.
def create_team_by_email_index
@team_by_email = self.class.create_team_by_email_index(
@join_source['team'])
end
# Creates an index of team member information keyed by email address.
# @param team [Array<Hash>] contains individual team member information
# @return [Hash<String, Hash>] email address => team member
def self.create_team_by_email_index(team)
team_by_email = {}
team.each do |i|
# A Hash containing only a 'private' property is a list of team
# members whose information is completely private.
if i.keys == ['private']
i['private'].each do |private_member|
email = private_member['email']
team_by_email[email] = private_member['name'] if email
end
else
email = i['email']
email = i['private']['email'] if !email and i.member? 'private'
team_by_email[email] = i['name'] if email
end
end
team_by_email
end
# Prepares +site.data[@source]+ prior to joining its data with
# +site.data+. All data nested within +'private'+ attributes will be
# stripped when @public_mode is +true+, and will be promoted to the same
# level as its parent when @public_mode is +false+.
#
# If a block is given, +site.data[@source]+ will be passed to the block
# for other initialization/setup.
def setup_join_source
if @public_mode
HashJoiner.remove_data @join_source, 'private'
else
HashJoiner.promote_data @join_source, 'private'
end
yield @join_source if block_given?
end
# Promote data from +site.data['private']+ into +site.data+, if
# +site.data['private']+ exists.
# +category+:: key into +site.data['private']+ specifying data collection
def promote_private_data(category)
@data[category] = @join_source[category] if @join_source != @data
end
# Assigns the +image+ property of each team member based on the team
# member's username and whether or not an image asset exists for that team
# member. +site.config[+'missing_team_member_img'] is used as the default
# when no image asset is available.
def assign_team_member_images
base = @site.source
img_dir = site.config['team_img_dir']
missing = File.join(img_dir, site.config['missing_team_member_img'])
site.data['team'].each do |member|
img = File.join(img_dir, "#{member['name']}.jpg")
if (File.exists? File.join(base, img) or
::TeamHub::PrivateAssets.exists?(site, img))
member['image'] = img
else
member['image'] = missing
end
end
end
# Joins snippet data into +site.data[+'snippets'] and filters out snippets
# from team members not appearing in +site.data[+'team'] or
# +team_by_email+.
#
# Snippet data is expected to be stored in files matching the pattern:
# +_data/+@source/snippets/[version]/[YYYYMMDD].csv
#
# resulting in the initial structure:
# +site.data[@source][snippets][version][YYYYMMDD] = Array<Hash>
#
# After this function returns, the new structure will be:
# +site.data[snippets][YYYYMMDD] = Array<Hash>
#
# and each individual snippet will have been converted to a standardized
# format defined by ::WeeklySnippets::Version.
def join_snippet_data(snippet_versions)
standardized = ::WeeklySnippets::Version.standardize_versions(
@join_source['snippets'], snippet_versions)
team = {}
@data['team'].each {|i| team[i['name']] = i}
result = {}
standardized.each do |timestamp, snippets|
joined = []
snippets.each do |snippet|
username = snippet['username']
member = team[username] || team[@team_by_email[username]]
if member
snippet['name'] = member['name']
snippet['full_name'] = member['full_name']
joined << snippet
end
end
result[timestamp] = joined unless joined.empty?
end
@data['snippets'] = result
end
# Imports the guest_users list into the top-level site.data object.
def import_guest_users
@data['guest_users'] = @join_source['hub']['guest_users']
end
# Filters out private pages when generating the public Hub.
def filter_private_pages
if @public_mode
private_pages_path = "/#{@site.config['private_pages_path']}"
@site.pages.delete_if do |p|
p.relative_path.start_with? private_pages_path
end
end
end
end
end