org_chart.rb 3.26 KB
Newer Older
1 2 3 4 5
require 'json'
require 'yaml'

# Loads data/team.yml and generates JSON data to plot the organization chart.
class OrgChart
6 7 8
  attr_accessor :filename, :team, :slug_to_entry_map
  attr_accessor :count_for_roles, :reports_to_for_roles

9
  def initialize(filename = 'data/team.yml')
10
    @filename = filename
11
    @team = YAML.load_file(filename)
12
    @slug_to_entry_map = {}
13 14
    @count_for_roles = Hash.new { 0 }
    @reports_to_for_roles = Hash.new { 0 }
15

16
    build_validation_data
17 18 19 20
    build_role_map
  end

  def validate!
21
    validate_member_data
22 23
    validate_roles!
    validate_reporting_structure!
24
  end
25

26
  def team_data
27 28
    validate!

29 30 31
    build_json_data
  end

32
  def team_data_tree
Mike Greiling's avatar
Mike Greiling committed
33 34
    tree = Hash.new do |h, k|
      h[k] = {
35
        slug: nil,
Mike Greiling's avatar
Mike Greiling committed
36 37 38 39 40
        name: nil,
        lead: nil,
        children: []
      }
    end
41 42

    team_data.each do |member|
43 44 45 46
      slug, lead_entry = member.values_at(:slug, :lead)
      lead = lead_entry&.fetch('slug')

      member_tree = tree[slug].merge!(member)
47 48

      tree[lead][:children].push(member_tree)
49 50 51 52 53
    end

    tree[nil][:children]
  end

54 55
  private

56
  def build_validation_data
57
    @team.each do |entry|
58
      role = entry['slug']
59 60 61 62 63
      reports_to = entry['reports_to']

      count_for_roles[role] += 1 if role
      reports_to_for_roles[reports_to] += 1 if reports_to
    end
64 65 66 67 68 69 70 71 72 73 74
  end

  def validate_roles!
    expected_roles = reports_to_for_roles.keys

    missing = expected_roles - @count_for_roles.keys

    return if missing.empty?

    raise Exception,
          "There are `reports_to` entries for #{missing}, but nobody is assigned to those roles.\n" \
75
          "Be sure to assign a person `slug:` entry in #{filename}."
76
  end
77

78 79 80 81 82 83 84 85 86
  def validate_member_data
    @team.each do |entry|
      if entry['start_date'].nil? || entry['start_date'].is_a?(Date) == false
        raise Exception,
          "In ``/data/team.yml`, the entry\n\n#{entry}\n\ndoes not have the required `start_date` field formatted like `YYYY-MM-DD`."
      end
    end
  end

87
  def validate_reporting_structure!
88 89 90 91 92
    count_for_roles.each do |role, count|
      next if count <= 1

      report_count = reports_to_for_roles[role]

Stan Hu's avatar
Stan Hu committed
93 94 95 96 97
      next unless report_count.positive?

      raise Exception,
            "Ambiguous reporting structure: #{report_count} people reports to the role #{role}, " \
            "but #{count} people have that role.\n" \
98
            "Check that the right person has `slug: #{role}` in #{@filename}\n" \
Stan Hu's avatar
Stan Hu committed
99
            "or that the lines `reports_to: #{role}` are correct for team members."
100 101 102
    end
  end

103
  def build_role_map
104
    @team.each do |entry|
105
      role = entry['slug']
106
      # Assumes titles are unique among people who have reports
107
      @slug_to_entry_map[role] = entry
108 109 110 111 112
    end
  end

  def build_json_data
    # Build only the data we need
Stan Hu's avatar
Stan Hu committed
113
    @team.map do |entry|
114
      data = {
115 116
        # This needs to match the implementation of Gitlab::Homepage::Team::Member#anchor
        anchor: entry['twitter'] || entry['gitlab'] || entry['slug'],
117
        slug: entry['slug'],
118
        name: entry['name'],
119
        link: entry['role'],
120 121
        speciality: entry.fetch('speciality', nil),
        placeholder: entry.fetch('placeholder', false)
122
      }
123 124 125 126

      reports_to = entry['reports_to']
      data[:lead] = @slug_to_entry_map.fetch(reports_to) if reports_to
      data
127 128 129
    end
  end
end