call-rota 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3cde12e911d3263bf5173b3682108d4275a65baf
4
+ data.tar.gz: 75026298ff90a1eb6b8edfd16672143c46ac5181
5
+ SHA512:
6
+ metadata.gz: ebc7f858f79ab4ab805dd25735c5c1bede161aae185cf8d0b7110bd039c96d16aac957c98bba5f8a2c87882d913d83813fee471cde1257169c169fb934653db4
7
+ data.tar.gz: 4d31b2ff54b713fd0cf5a7eb2e7e1d25145178d800917450e66392683b869b33c64b7b00a7a55e13809540b9c40de8b36d933f2cf7187916221c78025842f43c
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2014 GOV.UK
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
22
+
@@ -0,0 +1,3 @@
1
+ # Call rota
2
+
3
+ Gem that provides a script to generate on-call rotas
@@ -0,0 +1,3 @@
1
+ module CallRota
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,32 @@
1
+ require "ostruct"
2
+
3
+ class PeopleCollectionFactory
4
+ def initialize(people_inputs, deploy_access_inputs)
5
+ @people_inputs = people_inputs
6
+ @people_with_deploy_access = production_access_use_names(deploy_access_inputs)
7
+ end
8
+
9
+ def call
10
+ @people_inputs.map do |person_hash|
11
+ build_person(
12
+ person_hash,
13
+ deploy_access?(person_hash[:use_name]),
14
+ )
15
+ end
16
+ end
17
+
18
+ private
19
+ def build_person(person_hash, has_production_access)
20
+ OpenStruct.new(
21
+ person_hash.merge(production_access: has_production_access)
22
+ )
23
+ end
24
+
25
+ def production_access_use_names(raw_deploy_access_inputs)
26
+ raw_deploy_access_inputs.map { |p| p[:use_name] }
27
+ end
28
+
29
+ def deploy_access?(person_use_name)
30
+ @people_with_deploy_access.include? person_use_name
31
+ end
32
+ end
@@ -0,0 +1,69 @@
1
+ class RotaWeekBuilder
2
+ def initialize(people)
3
+ @people = people
4
+ end
5
+
6
+ def call
7
+ RotaWeek.new(
8
+ :web_ops => pick_person("webops"),
9
+ :dev => pick_person("developer", true),
10
+ :supplemental_dev => pick_person("developer"),
11
+ )
12
+ end
13
+
14
+ private
15
+
16
+ def pick_person(role, production_access=false)
17
+ eligible_people = available_people(role, production_access)
18
+ if eligible_people.empty?
19
+ raise "No more people to pick for role #{role}
20
+ #{production_access ? "with production access" : ""}!"
21
+ end
22
+ eligible_people.sample.tap do |chosen_person|
23
+ make_ineligible(team_members(chosen_person.team))
24
+ end
25
+ end
26
+
27
+ def team_members(team_name)
28
+ @people.select { |p| p.team == team_name }
29
+ end
30
+
31
+ def available_people(role, production_access)
32
+ people_with_role_and_access(role, production_access) - ineligible_people
33
+ end
34
+
35
+ def ineligible_people
36
+ @ineligible_people ||= []
37
+ end
38
+
39
+ def make_ineligible(people)
40
+ @ineligible_people = ineligible_people + people
41
+ end
42
+
43
+ def people_with_role_and_access(role, production_access)
44
+ result = @people.dup
45
+ if production_access
46
+ result = result.select { |p| p.production_access == true }
47
+ end
48
+ result.select { |p| p.rota_skill_group == role }
49
+ end
50
+
51
+ end
52
+
53
+ class RotaWeek
54
+ include Enumerable
55
+
56
+ attr_reader :web_ops, :dev, :supplemental_dev
57
+
58
+ def initialize(web_ops:, dev:, supplemental_dev:)
59
+ @web_ops = web_ops
60
+ @dev = dev
61
+ @supplemental_dev = supplemental_dev
62
+ end
63
+
64
+ def each
65
+ yield web_ops
66
+ yield dev
67
+ yield supplemental_dev
68
+ end
69
+ end
@@ -0,0 +1,73 @@
1
+ module TestPenguinFactories
2
+ class ColonyFactory
3
+ include RSpec::Mocks
4
+
5
+ def initialize(generator)
6
+ @generator = generator
7
+ end
8
+
9
+ def call(teams:, test_penguin_team_factory:)
10
+ result = []
11
+
12
+ teams.times do |i|
13
+ result += test_penguin_team_factory.call("team_#{i}")
14
+ end
15
+
16
+ result
17
+ end
18
+ end
19
+
20
+ class TeamFactory
21
+ include RSpec::Mocks
22
+
23
+ def initialize(generator, webops:, developers_with_prod:, developers_without_prod:)
24
+ @generator = generator
25
+ @webops = webops
26
+ @developers_with_prod = developers_with_prod
27
+ @developers_without_prod = developers_without_prod
28
+ end
29
+
30
+ def call(team_name = "team_name")
31
+ result = []
32
+
33
+ @webops.times do
34
+ result << webops_penguin_factory.call(team_name)
35
+ end
36
+
37
+ @developers_with_prod.times do
38
+ result << developers_penguin_factory.call(team_name, true)
39
+ end
40
+
41
+ @developers_without_prod.times do
42
+ result << developers_penguin_factory.call(team_name, false)
43
+ end
44
+
45
+ result
46
+ end
47
+
48
+ private
49
+ def webops_penguin_factory
50
+ ->(team_name) {
51
+ double(:webops_penguin,
52
+ team: team_name,
53
+ rota_skill_group: "webops",
54
+ production_access: true,
55
+ )
56
+ }
57
+ end
58
+
59
+ def developers_penguin_factory
60
+ ->(team_name, production_access) {
61
+ double(:developer_penguin,
62
+ team: team_name,
63
+ rota_skill_group: "developer",
64
+ production_access: production_access,
65
+ )
66
+ }
67
+ end
68
+
69
+ def double(symbol, *args)
70
+ @generator.call(symbol, *args)
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,61 @@
1
+ require 'people_collection_factory'
2
+
3
+ describe PeopleCollectionFactory do
4
+ let(:names) { ["Blue Penguin", "Red Penguin"] }
5
+ let(:use_names) { ["B Penguin", "R Penguin"] }
6
+ let(:blue_penguin_hash) {
7
+ {
8
+ :full_name => names.first,
9
+ :use_name => use_names.first,
10
+ :rota_skill_group => "developer",
11
+ :team => "User Formats",
12
+ }
13
+ }
14
+ let(:red_penguin_hash) {
15
+ {
16
+ :full_name => names[1],
17
+ :use_name => use_names[1],
18
+ :rota_skill_group => "webops",
19
+ :team => "Product Gaps",
20
+ }
21
+ }
22
+
23
+ #When this data is available, this will contain
24
+ #information about whether person is tech lead or not
25
+ let(:people_input_data) {
26
+ [
27
+ blue_penguin_hash,
28
+ red_penguin_hash,
29
+ ]
30
+ }
31
+
32
+ let(:deploy_access_input_data) {
33
+ [
34
+ red_penguin_hash,
35
+ ]
36
+ }
37
+
38
+ subject(:factory) {
39
+ described_class.new(
40
+ people_input_data,
41
+ deploy_access_input_data
42
+ )
43
+ }
44
+
45
+ it "returns a collection of objects representing the input data" do
46
+ result = factory.call
47
+ first_result = result[0]
48
+
49
+ expect(result).to respond_to :[]
50
+ expect(result.size).to eq(2)
51
+ expect(names).to include(first_result.full_name)
52
+ end
53
+
54
+ it "correctly determines production access" do
55
+ result = factory.call
56
+ with_production_access = result.select { |p| p.production_access == true }
57
+
58
+ expect(with_production_access.size).to eq(1)
59
+ expect(with_production_access.first.use_name).to eq("R Penguin")
60
+ end
61
+ end
@@ -0,0 +1,64 @@
1
+ require "rota_week_builder"
2
+ require "helpers/test_penguin_factories"
3
+
4
+ describe RotaWeekBuilder do
5
+ let(:penguins) {
6
+ test_penguin_team_factory = TestPenguinFactories::TeamFactory.new(
7
+ method(:double),
8
+ webops: 2,
9
+ developers_with_prod: 2,
10
+ developers_without_prod: 10,
11
+ )
12
+ TestPenguinFactories::ColonyFactory.new(method(:double)).call(
13
+ teams: 3,
14
+ test_penguin_team_factory: test_penguin_team_factory,
15
+ )
16
+ }
17
+
18
+ subject(:builder) { RotaWeekBuilder.new(penguins) }
19
+
20
+ let(:result) { subject.call }
21
+
22
+ it "returns a collection for the next week" do
23
+ expect(result.web_ops).not_to be(nil)
24
+ expect(result.dev).not_to be(nil)
25
+ expect(result.supplemental_dev).not_to be(nil)
26
+ expect(result.count).to eq(3)
27
+ end
28
+
29
+ it "returns three different people" do
30
+ people = result.map.to_a
31
+ expect(people.uniq.size).to eq(3)
32
+ end
33
+
34
+ it "returns people of the right role for their allocated job" do
35
+ expect(result.web_ops.rota_skill_group).to eq("webops")
36
+ expect(result.dev.rota_skill_group).to eq("developer")
37
+ expect(result.supplemental_dev.rota_skill_group).to eq("developer")
38
+ end
39
+
40
+ it "returns a primary dev with production access" do
41
+ expect(result.dev.production_access).to be true
42
+ end
43
+
44
+ it "returns people from different teams" do
45
+ people = [result.dev, result.supplemental_dev, result.web_ops]
46
+
47
+ expect(people.map(&:team).uniq.size).to eq 3
48
+ end
49
+
50
+ context "with two people" do
51
+ let(:penguins) {
52
+ TestPenguinFactories::TeamFactory.new(
53
+ method(:double),
54
+ webops: 1,
55
+ developers_with_prod: 0,
56
+ developers_without_prod: 1,
57
+ ).call
58
+ }
59
+
60
+ it "raises if not provided with enough people" do
61
+ expect { subject.call }.to raise_error
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,78 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # The generated `.rspec` file contains `--require spec_helper` which will cause this
4
+ # file to always be loaded, without a need to explicitly require it in any files.
5
+ #
6
+ # Given that it is always loaded, you are encouraged to keep this file as
7
+ # light-weight as possible. Requiring heavyweight dependencies from this file
8
+ # will add to the boot time of your test suite on EVERY test run, even for an
9
+ # individual file that may not need all of that loaded. Instead, make a
10
+ # separate helper file that requires this one and then use it only in the specs
11
+ # that actually need it.
12
+ #
13
+ # The `.rspec` file also contains a few flags that are not defaults but that
14
+ # users commonly want.
15
+ #
16
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
17
+ RSpec.configure do |config|
18
+ # The settings below are suggested to provide a good initial experience
19
+ # with RSpec, but feel free to customize to your heart's content.
20
+ =begin
21
+ # These two settings work together to allow you to limit a spec run
22
+ # to individual examples or groups you care about by tagging them with
23
+ # `:focus` metadata. When nothing is tagged with `:focus`, all examples
24
+ # get run.
25
+ config.filter_run :focus
26
+ config.run_all_when_everything_filtered = true
27
+
28
+ # Many RSpec users commonly either run the entire suite or an individual
29
+ # file, and it's useful to allow more verbose output when running an
30
+ # individual spec file.
31
+ if config.files_to_run.one?
32
+ # Use the documentation formatter for detailed output,
33
+ # unless a formatter has already been configured
34
+ # (e.g. via a command-line flag).
35
+ config.default_formatter = 'doc'
36
+ end
37
+
38
+ # Print the 10 slowest examples and example groups at the
39
+ # end of the spec run, to help surface which specs are running
40
+ # particularly slow.
41
+ config.profile_examples = 10
42
+
43
+ # Run specs in random order to surface order dependencies. If you find an
44
+ # order dependency and want to debug it, you can fix the order by providing
45
+ # the seed, which is printed after each run.
46
+ # --seed 1234
47
+ config.order = :random
48
+
49
+ # Seed global randomization in this process using the `--seed` CLI option.
50
+ # Setting this allows you to use `--seed` to deterministically reproduce
51
+ # test failures related to randomization by passing the same `--seed` value
52
+ # as the one that triggered the failure.
53
+ Kernel.srand config.seed
54
+
55
+ # rspec-expectations config goes here. You can use an alternate
56
+ # assertion/expectation library such as wrong or the stdlib/minitest
57
+ # assertions if you prefer.
58
+ config.expect_with :rspec do |expectations|
59
+ # Enable only the newer, non-monkey-patching expect syntax.
60
+ # For more details, see:
61
+ # - http://myronmars.to/n/dev-blog/2012/06/rspecs-new-expectation-syntax
62
+ expectations.syntax = :expect
63
+ end
64
+
65
+ # rspec-mocks config goes here. You can use an alternate test double
66
+ # library (such as bogus or mocha) by changing the `mock_with` option here.
67
+ config.mock_with :rspec do |mocks|
68
+ # Enable only the newer, non-monkey-patching expect syntax.
69
+ # For more details, see:
70
+ # - http://teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/
71
+ mocks.syntax = :expect
72
+
73
+ # Prevents you from mocking or stubbing a method that does not exist on
74
+ # a real object. This is generally recommended.
75
+ mocks.verify_partial_doubles = true
76
+ end
77
+ =end
78
+ end
metadata ADDED
@@ -0,0 +1,113 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: call-rota
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Tom Russell
8
+ - Camille Baldock
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2014-10-13 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: gem_publisher
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: pry
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ description:
71
+ email: bradley.wright@digital.cabinet-office.gov.uk
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - LICENSE
77
+ - README.md
78
+ - lib/call_rota/version.rb
79
+ - lib/people_collection_factory.rb
80
+ - lib/rota_week_builder.rb
81
+ - spec/helpers/test_penguin_factories.rb
82
+ - spec/people_collection_factory_spec.rb
83
+ - spec/rota_week_builder_spec.rb
84
+ - spec/spec_helper.rb
85
+ homepage: https://github.com/alphagov/call-rota
86
+ licenses:
87
+ - MIT
88
+ metadata: {}
89
+ post_install_message:
90
+ rdoc_options: []
91
+ require_paths:
92
+ - lib
93
+ required_ruby_version: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - ">="
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ required_rubygems_version: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - ">="
101
+ - !ruby/object:Gem::Version
102
+ version: '0'
103
+ requirements: []
104
+ rubyforge_project:
105
+ rubygems_version: 2.2.2
106
+ signing_key:
107
+ specification_version: 4
108
+ summary: Gem that provides a script to generate on-call rotas
109
+ test_files:
110
+ - spec/helpers/test_penguin_factories.rb
111
+ - spec/people_collection_factory_spec.rb
112
+ - spec/spec_helper.rb
113
+ - spec/rota_week_builder_spec.rb