tfs_graph 0.1.1

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.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.ruby-gemset +1 -0
  4. data/.ruby-version +1 -0
  5. data/Gemfile +19 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +119 -0
  8. data/Rakefile +1 -0
  9. data/lib/tfs_graph/associators/branch_associator.rb +25 -0
  10. data/lib/tfs_graph/associators/changeset_tree_creator.rb +19 -0
  11. data/lib/tfs_graph/branch/branch_archive_handler.rb +23 -0
  12. data/lib/tfs_graph/branch/branch_normalizer.rb +11 -0
  13. data/lib/tfs_graph/branch/branch_store.rb +53 -0
  14. data/lib/tfs_graph/branch.rb +157 -0
  15. data/lib/tfs_graph/changeset/changeset_normalizer.rb +21 -0
  16. data/lib/tfs_graph/changeset/changeset_store.rb +65 -0
  17. data/lib/tfs_graph/changeset.rb +71 -0
  18. data/lib/tfs_graph/changeset_merge/changeset_merge_normalizer.rb +21 -0
  19. data/lib/tfs_graph/changeset_merge/changeset_merge_store.rb +24 -0
  20. data/lib/tfs_graph/changeset_merge.rb +57 -0
  21. data/lib/tfs_graph/config.rb +10 -0
  22. data/lib/tfs_graph/entity.rb +16 -0
  23. data/lib/tfs_graph/graph_populator.rb +35 -0
  24. data/lib/tfs_graph/normalizer.rb +30 -0
  25. data/lib/tfs_graph/populators/everything.rb +20 -0
  26. data/lib/tfs_graph/populators/for_project.rb +18 -0
  27. data/lib/tfs_graph/populators/since_date.rb +26 -0
  28. data/lib/tfs_graph/populators/since_last.rb +26 -0
  29. data/lib/tfs_graph/populators/utilities.rb +38 -0
  30. data/lib/tfs_graph/populators.rb +11 -0
  31. data/lib/tfs_graph/project/project_normalizer.rb +12 -0
  32. data/lib/tfs_graph/project/project_store.rb +30 -0
  33. data/lib/tfs_graph/project.rb +59 -0
  34. data/lib/tfs_graph/store_helpers.rb +31 -0
  35. data/lib/tfs_graph/tfs_client.rb +37 -0
  36. data/lib/tfs_graph/tfs_helpers.rb +42 -0
  37. data/lib/tfs_graph/version.rb +3 -0
  38. data/lib/tfs_graph.rb +19 -0
  39. data/spec/factories.rb +20 -0
  40. data/spec/helpers_spec.rb +48 -0
  41. data/spec/spec_helper.rb +43 -0
  42. data/tfs_graph.gemspec +26 -0
  43. metadata +144 -0
@@ -0,0 +1,57 @@
1
+ require 'tfs_graph/entity'
2
+ require 'tfs_graph/changeset'
3
+
4
+ module TFSGraph
5
+ class ChangesetMerge < Entity
6
+
7
+ SCHEMA = {
8
+ target_version: {key: "TargetVersion", type: Integer},
9
+ source_version: {key: "SourceVersion", type: Integer},
10
+ branch: {default: nil, type: String}
11
+ }
12
+
13
+ act_as_entity
14
+
15
+ # overwrite the creator, and create a relationship between the
16
+ # two changesets requested instead of a distinct object
17
+ def self.create(attrs)
18
+ begin
19
+ merge = new(attrs)
20
+
21
+ # this will throw an error if one of the relations is not found
22
+ # this is the desired condition as it will throw out the merge if there aren't two endpoints found
23
+ target, source = merge.get_relations
24
+
25
+ Related::Relationship.create :merges, target, source
26
+
27
+ # relate the branches as well
28
+ Related::Relationship.create :related, source.branch, target.branch
29
+
30
+ Related::Relationship.create :included, source.branch, target
31
+ Related::Relationship.create :included, target.branch, source
32
+
33
+ merge
34
+ rescue Related::NotFound => ex
35
+ # puts "Could not find a changeset to merge with: #{ex.message}"
36
+ rescue Related::ValidationsFailed => ex
37
+ # puts "Couldn't create relationship for #{merge.source_version} to #{merge.target_version}"
38
+ end
39
+ end
40
+
41
+ def save
42
+ # nothing, no need to save
43
+ end
44
+
45
+ def get_relations
46
+ return get_target, get_source
47
+ end
48
+
49
+ def get_source
50
+ Changeset.find(source_version)
51
+ end
52
+
53
+ def get_target
54
+ Changeset.find(target_version)
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,10 @@
1
+ module TFSGraph
2
+ class Config
3
+ attr_accessor :tfs
4
+ attr_reader :redis
5
+
6
+ def redis=(server)
7
+ @redis = Related.redis = server
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,16 @@
1
+ module TFSGraph
2
+ class Entity < Related::Node
3
+ def self.inherited(klass)
4
+ define_singleton_method :act_as_entity do
5
+ klass::SCHEMA.each do |key, details|
6
+ property key, details[:type]
7
+ end
8
+ end
9
+ end
10
+
11
+ private
12
+ def schema
13
+ self.class::SCHEMA
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,35 @@
1
+ require 'tfs_graph/project/project_store'
2
+ require 'tfs_graph/branch/branch_store'
3
+ require 'tfs_graph/changeset/changeset_store'
4
+ require 'tfs_graph/changeset_merge/changeset_merge_store'
5
+
6
+ require 'tfs_graph/associators/changeset_tree_creator'
7
+ require 'tfs_graph/associators/branch_associator'
8
+ require 'tfs_graph/branch/branch_archive_handler'
9
+
10
+ require 'tfs_graph/populators'
11
+
12
+ BranchNotFound = Class.new(Exception)
13
+
14
+ module TFSGraph
15
+ class GraphPopulator
16
+ include Populators
17
+
18
+ class << self
19
+ include StoreHelpers
20
+
21
+ def populate_graph(type=Everything, *args)
22
+ populator = type.new *args
23
+ populator.populate
24
+ end
25
+
26
+ def incrementally_update_all
27
+ populate_graph(Populators::SinceLast)
28
+ end
29
+
30
+ def populate_all_from_time(time)
31
+ populate_graph(Populators::SinceDate, time)
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,30 @@
1
+ module TFSGraph
2
+ class Normalizer
3
+ class << self
4
+ def normalize_many(data)
5
+ data.map {|item| normalize item }
6
+ end
7
+
8
+ def normalize(item)
9
+ representation = {}
10
+ schema.each do |key, lookup|
11
+
12
+ if lookup[:key].present? # for keys that pull data from other sources
13
+ value = item.send lookup[:key]
14
+ value = lookup[:converter].call(value) if lookup[:converter].present?
15
+ else
16
+ value = lookup[:default]
17
+ end
18
+
19
+ representation[key] = value
20
+ end
21
+ representation
22
+ end
23
+
24
+ private
25
+ def schema
26
+ raise NoMethodError, "please define the schema in the Normalizer subclass"
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,20 @@
1
+ module TFSGraph
2
+ module Populators
3
+ class Everything
4
+ include Utilities
5
+
6
+ def populate
7
+ clean
8
+
9
+ collect_projects.map do |project|
10
+ branches = collect_branches(project)
11
+ branches.map {|branch| collect_changesets(branch) }
12
+
13
+ branches.each {|branch| collect_merges(branch) }
14
+ end
15
+
16
+ finalize
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,18 @@
1
+ module TFSGraph
2
+ module Populators
3
+ class ForProject
4
+ include Utilities
5
+
6
+ def initialize(project)
7
+ @project = project
8
+ end
9
+
10
+ def populate
11
+ branches = collect_branches(@project)
12
+ branches.map {|branch| collect_changesets(branch) }
13
+
14
+ branches.each {|branch| collect_merges(branch) }
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,26 @@
1
+ module TFSGraph
2
+ module Populators
3
+ class SinceDate
4
+ include Utilities
5
+
6
+ def initialize(since)
7
+ @since = since
8
+ end
9
+
10
+ def populate
11
+ clean
12
+
13
+ collect_projects.map do |project|
14
+ branches = collect_branches(project)
15
+ branches.map do |branch|
16
+ collect_changesets(branch, :cache_since_date, @since)
17
+ end
18
+
19
+ branches.each {|branch| collect_merges(branch) }
20
+ end
21
+
22
+ finalize
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ module TFSGraph
2
+ module Populators
3
+ class SinceLast
4
+ include Utilities
5
+
6
+ def populate
7
+ ProjectStore.all_cached.map do |project|
8
+ new_changesets = project.active_branches.map {|branch| collect_changesets branch, :cache_since_last_update}
9
+ new_branches = BranchStore.new(project).cache_since_last_update
10
+
11
+ new_changesets.concat new_branches.map {|branch| collect_changesets branch }
12
+
13
+ # recache and reassociate all merges for all branches.
14
+ # should not lead to dupes thanks to Related
15
+ new_branches.concat(project.branches).each do |branch|
16
+ collect_merges(branch)
17
+ BranchAssociator.associate(branch.changesets)
18
+ end
19
+ end
20
+
21
+ finalize
22
+ end
23
+
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,38 @@
1
+ module TFSGraph
2
+ module Populators
3
+ module Utilities
4
+ include StoreHelpers
5
+
6
+ def clean
7
+ flush_all
8
+ end
9
+
10
+ def finalize
11
+ BranchArchiveHandler.hide_all_archives
12
+ mark_as_updated
13
+ end
14
+
15
+ def collect_projects
16
+ ProjectStore.cache
17
+ end
18
+
19
+ def collect_branches(project)
20
+ BranchStore.new(project).cache_all
21
+ end
22
+
23
+ def collect_changesets(branch, method=:cache_all, *args)
24
+ changesets = ChangesetStore.new(branch).send(method, *args)
25
+ generate_branch_tree(branch)
26
+ changesets.compact
27
+ end
28
+
29
+ def generate_branch_tree(branch)
30
+ ChangesetTreeCreator.to_tree branch
31
+ end
32
+
33
+ def collect_merges(branch)
34
+ ChangesetMergeStore.new(branch).cache
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'populators/utilities'
2
+
3
+ require_relative 'populators/everything'
4
+ require_relative 'populators/since_last'
5
+ require_relative 'populators/since_date'
6
+ require_relative 'populators/for_project'
7
+
8
+ module TFSGraph
9
+ module Populators
10
+ end
11
+ end
@@ -0,0 +1,12 @@
1
+ require 'tfs_graph/project'
2
+ require 'tfs_graph/normalizer'
3
+
4
+ module TFSGraph
5
+ class ProjectNormalizer < Normalizer
6
+ class << self
7
+ def schema
8
+ Project::SCHEMA
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,30 @@
1
+ require 'tfs_graph/tfs_client'
2
+ require 'tfs_graph/project/project_normalizer'
3
+
4
+ module TFSGraph
5
+ class ProjectStore
6
+ extend TFSClient
7
+
8
+ class << self
9
+ def cache
10
+ projects = tfs.projects.run
11
+ normalized = ProjectNormalizer.normalize_many projects
12
+
13
+ normalized.map do |project_attrs|
14
+ project = Project.create project_attrs
15
+ Related::Relationship.create(:projects, Related.root, project)
16
+
17
+ project
18
+ end
19
+ end
20
+
21
+ def all_cached
22
+ Related.root.outgoing(:projects).options(model: Project).nodes.to_a
23
+ end
24
+
25
+ def find_cached(name)
26
+ all_cached.detect {|p| p.name == name }
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,59 @@
1
+ require 'tfs_graph/entity'
2
+
3
+ module TFSGraph
4
+ class Project < Entity
5
+ extend Comparable
6
+ SCHEMA = {
7
+ name: {key: "Name"}
8
+ }
9
+
10
+ act_as_entity
11
+
12
+ def <=>(other)
13
+ name <=> other.name
14
+ end
15
+
16
+ def last_change
17
+ branches.map {|b| b.last_changeset }
18
+ end
19
+
20
+ def all_activity
21
+ branches.map {|b| b.changesets }.flatten
22
+ end
23
+
24
+ def all_activity_by_date(limiter=nil)
25
+ raise InvalidArgument("parameter must be a Date") unless limiter.nil? || limiter.is_a?(Time)
26
+
27
+ activity = all_activity
28
+ activity = activity.select {|c| c.created > limiter } unless limiter.nil?
29
+
30
+ activity.group_by(&:formatted_created)
31
+ end
32
+
33
+ def active_branches
34
+ branches.reject(&:archived?)
35
+ end
36
+
37
+ def branches
38
+ branches_with_hidden.reject(&:hidden?)
39
+ end
40
+
41
+ def branches_with_hidden
42
+ outgoing(:branches).options(model: Branch).nodes.to_a
43
+ end
44
+
45
+ %w(master release feature).each do |type|
46
+ define_method "#{type}s" do
47
+ branches.select {|b| b.send "#{type}?" }
48
+ end
49
+
50
+ define_method "#{type}s_with_hidden" do
51
+ branches_with_hidden.select {|b| b.send "#{type}?" }
52
+ end
53
+
54
+ define_method "archived_#{type}s" do
55
+ branches.select {|b| b.send("#{type}?") && b.archived? }
56
+ end
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,31 @@
1
+ require 'time'
2
+
3
+ module TFSGraph
4
+ module StoreHelpers
5
+ UPDATED_KEY = "LAST_UPDATED_ON"
6
+
7
+ # flush by key so that we only disturbe our namespace
8
+ def flush_all
9
+ redis.keys("*").each do |k|
10
+ redis.del k
11
+ end
12
+ end
13
+
14
+ def mark_as_updated(time=nil)
15
+ time ||= Time.now
16
+ redis.set UPDATED_KEY, time.utc
17
+ end
18
+
19
+ def last_updated_on
20
+ date = redis.get(UPDATED_KEY)
21
+ return Time.now unless date
22
+
23
+ Time.parse(date).localtime
24
+ end
25
+
26
+ private
27
+ def redis
28
+ Related.redis
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,37 @@
1
+ # Wraps TFS OData domain knowledge
2
+ require 'tfs'
3
+
4
+ module TFSGraph
5
+ module TFSClient
6
+ InvalidConfig = Class.new(RuntimeError)
7
+
8
+ REQUIRED_KEYS = [:endpoint, :collection, :username, :password]
9
+
10
+ # Requires a hash of settings
11
+ def setup(settings=TFSGraph.config.tfs)
12
+ raise InvalidConfig unless REQUIRED_KEYS.all? {|key| settings.keys.include? key }
13
+
14
+ TFS.configure do |c|
15
+ c.endpoint = endpoint(settings)
16
+ c.username = settings[:username]
17
+ c.password = settings[:password]
18
+ c.namespace = settings[:namespace] || "TFS"
19
+ end
20
+ end
21
+
22
+ def tfs
23
+ @tfs ||= begin
24
+ setup
25
+ TFS.client
26
+ end
27
+ end
28
+
29
+ def tfs=(client)
30
+ @tfs = client
31
+ end
32
+
33
+ def endpoint(settings)
34
+ "#{settings[:endpoint]}/#{settings[:collection]}"
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,42 @@
1
+ module TFSGraph
2
+ module TFSHelpers
3
+ def branch_base(path)
4
+ branch_path_to_name(path).split('-').first
5
+ end
6
+
7
+ # handles OData paths: $>RJR>Project>Path
8
+ def branch_path_to_name(path)
9
+ path_parts(path).last
10
+ end
11
+
12
+ # handles TFS server paths: $/RJR/Project/Path
13
+ def server_path_to_odata_path(path)
14
+ path.gsub "/", ">"
15
+ end
16
+
17
+ def odata_path_to_server_path(path)
18
+ path.gsub ">", "/"
19
+ end
20
+
21
+ def branch_project(path)
22
+ path_parts(path)[1]
23
+ end
24
+
25
+ def base_username(name)
26
+ name.split(/\/|\\/).last
27
+ end
28
+
29
+ def scrub_changeset(version)
30
+ version.gsub /\D/, "" unless version.nil?
31
+ end
32
+
33
+ private
34
+ def path_parts(path)
35
+ path.split(">")
36
+ end
37
+
38
+ def server_path_parts(path)
39
+ path.split("/")
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,3 @@
1
+ module TFSGraph
2
+ VERSION = "0.1.1"
3
+ end
data/lib/tfs_graph.rb ADDED
@@ -0,0 +1,19 @@
1
+ require "related"
2
+
3
+ require "tfs_graph/version"
4
+ require "tfs_graph/config"
5
+ require 'tfs_graph/graph_populator'
6
+
7
+ module TFSGraph
8
+ class << self
9
+ def config
10
+ return @config unless block_given?
11
+
12
+ @config ||= begin
13
+ config = Config.new
14
+ yield config
15
+ config
16
+ end
17
+ end
18
+ end
19
+ end
data/spec/factories.rb ADDED
@@ -0,0 +1,20 @@
1
+ require 'tfs_graph/changeset'
2
+
3
+ FactoryGirl.define do
4
+ factory :changeset, class: TFSGraph::Changeset do
5
+ comment "Doing fun things"
6
+ committer "John Doe"
7
+ created { Time.now }
8
+ sequence :id
9
+ end
10
+
11
+ factory :branch do
12
+ original_path "$>DefaultCollection>Project"
13
+ path "$>DefaultCollection>Project"
14
+ project "BFG"
15
+ name "Project"
16
+ root "$>DefaultCollection>Project"
17
+ created { Time.now }
18
+ type "master"
19
+ end
20
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+ require 'tfs_graph/tfs_helpers'
3
+
4
+ class DemoClass; include TFSGraph::TFSHelpers; end
5
+
6
+ describe TFSGraph::TFSHelpers do
7
+ Given(:demo) { DemoClass.new }
8
+ Given(:archived_path) { "$>RJR>_Branches>FAQ>RJRLibraries-FAQ" }
9
+ Given(:normal_path) { "$>RJR>Grizzly" }
10
+
11
+ context "can parse name of branch" do
12
+ When(:archived_result) { demo.branch_path_to_name(archived_path) }
13
+ Then { archived_result.should eq("RJRLibraries-FAQ") }
14
+
15
+ When(:normal_result) { demo.branch_path_to_name(normal_path) }
16
+ Then { normal_result.should eq("Grizzly") }
17
+ end
18
+
19
+ context "can parse down base name from a path" do
20
+ When(:archived_result) { demo.branch_base(archived_path) }
21
+ Then { archived_result.should eq("RJRLibraries") }
22
+
23
+ When(:normal_result) { demo.branch_base(normal_path) }
24
+ Then { normal_result.should eq("Grizzly") }
25
+ end
26
+
27
+ context "can parse base username from tfs" do
28
+ When(:fwd) { demo.base_username("BFGCOM/tmoe") }
29
+ Then { fwd.should eq("tmoe") }
30
+
31
+ When(:bck) { demo.base_username("BFGCOM\\tmoe") }
32
+ Then { bck.should eq("tmoe") }
33
+ end
34
+
35
+ context "can scrub out letters from changeset version" do
36
+ When(:version_1) { demo.scrub_changeset("C1234") }
37
+ Then { version_1.should eq("1234") }
38
+
39
+ When(:version_2) { demo.scrub_changeset("1234") }
40
+ Then { version_2.should eq("1234") }
41
+
42
+ context "does nothing if no changeset given" do
43
+ When(:version) { demo.scrub_changeset(nil) }
44
+ Then { version.should_not have_failed }
45
+ And { version.should == nil }
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,43 @@
1
+ $LOAD_PATH.unshift "../lib"
2
+
3
+ require 'related'
4
+ require 'rspec/given'
5
+ require 'vcr'
6
+ require 'factory_girl'
7
+ require 'pry'
8
+
9
+ FactoryGirl.definition_file_paths = %w{./factories ./spec/factories}
10
+ FactoryGirl.find_definitions
11
+
12
+ require 'tfs_graph'
13
+
14
+ VCR.configure do |c|
15
+ c.cassette_library_dir = 'spec/fixtures/vcr_cassettes'
16
+ c.hook_into :webmock
17
+ c.allow_http_connections_when_no_cassette = true
18
+ c.default_cassette_options = { record: :new_episodes }
19
+ c.configure_rspec_metadata!
20
+ end
21
+
22
+ RSpec.configure do |config|
23
+ config.mock_with :rspec
24
+ config.include FactoryGirl::Syntax::Methods
25
+
26
+ config.before(:each) do
27
+ TFSGraph.config do |c|
28
+ c.tfs = {
29
+ username: 'BFGCOM\apiservice',
30
+ password: "BFGservice123",
31
+ endpoint: "https://tfs-dev-01.bfgdev.inside/RAI"
32
+ }
33
+ c.redis = "localhost:6379/test"
34
+ end
35
+ end
36
+
37
+ config.after(:each) do
38
+ r = Related.redis
39
+ r.keys.each do |key|
40
+ r.del key
41
+ end
42
+ end
43
+ end
data/tfs_graph.gemspec ADDED
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'tfs_graph/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "tfs_graph"
8
+ spec.version = TFSGraph::VERSION
9
+ spec.authors = ["Luke van der Hoeven"]
10
+ spec.email = ["hungerandthirst@gmail.com"]
11
+ spec.description = %q{A library to help cache and fetch TFS data}
12
+ spec.summary = %q{Simple graph db wrapper for TFS data for Redis caching}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "ruby_tfs"
22
+ spec.add_dependency "related"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "rake"
26
+ end