techcor 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. data/Gemfile +22 -0
  2. data/Gemfile.lock +96 -0
  3. data/README.md +27 -0
  4. data/Rakefile +45 -0
  5. data/bin/tc +6 -0
  6. data/config/cucumber.yml +2 -0
  7. data/config/mongoid.yml +29 -0
  8. data/config/mongoid.yml.sample +29 -0
  9. data/db/seed.rb +37 -0
  10. data/features/add_metric.feature +9 -0
  11. data/features/add_project.feature +9 -0
  12. data/features/console_interface.feature +58 -0
  13. data/features/describe_project.feature +25 -0
  14. data/features/edit_property.feature +9 -0
  15. data/features/list_projects.feature +69 -0
  16. data/features/property_history.feature +51 -0
  17. data/features/rake.feature +11 -0
  18. data/features/seeds.feature +9 -0
  19. data/features/step_definitions/add_project.rb +22 -0
  20. data/features/step_definitions/console_interface.rb +7 -0
  21. data/features/step_definitions/list_projects.rb +3 -0
  22. data/features/step_definitions/property_history.rb +4 -0
  23. data/features/step_definitions/rake.rb +3 -0
  24. data/features/support/env.rb +9 -0
  25. data/lib/commands/add_metric.rb +17 -0
  26. data/lib/commands/add_project.rb +5 -0
  27. data/lib/commands/describe_project.rb +26 -0
  28. data/lib/commands/edit_property.rb +9 -0
  29. data/lib/commands/gli/add.rb +12 -0
  30. data/lib/commands/gli/add_metric.rb +23 -0
  31. data/lib/commands/gli/describe.rb +16 -0
  32. data/lib/commands/gli/edit_property.rb +22 -0
  33. data/lib/commands/gli/history.rb +19 -0
  34. data/lib/commands/gli/hooks.rb +8 -0
  35. data/lib/commands/gli/list.rb +14 -0
  36. data/lib/commands/gli/program.rb +5 -0
  37. data/lib/commands/list_projects.rb +32 -0
  38. data/lib/commands/view_history.rb +37 -0
  39. data/lib/console_formatter.rb +18 -0
  40. data/lib/gli_interface.rb +15 -0
  41. data/lib/metrics/boolean_metric.rb +7 -0
  42. data/lib/metrics/metric.rb +24 -0
  43. data/lib/metrics/number_metric.rb +7 -0
  44. data/lib/metrics/string_metric.rb +7 -0
  45. data/lib/project.rb +15 -0
  46. data/lib/project_catalog.rb +34 -0
  47. data/lib/property_value.rb +3 -0
  48. data/lib/storage/metric_mongo.rb +11 -0
  49. data/lib/storage/project_mongo.rb +9 -0
  50. data/lib/storage/property_value_mongo.rb +10 -0
  51. data/lib/tc/version.rb +9 -0
  52. data/lib/tc.rb +27 -0
  53. data/spec/integration/mongo_spec.rb +19 -0
  54. data/spec/lib/commands/add_metric_spec.rb +31 -0
  55. data/spec/lib/commands/add_project_spec.rb +10 -0
  56. data/spec/lib/commands/describe_project_spec.rb +42 -0
  57. data/spec/lib/commands/edit_property_spec.rb +19 -0
  58. data/spec/lib/commands/list_projects_spec.rb +47 -0
  59. data/spec/lib/commands/view_history_spec.rb +64 -0
  60. data/spec/lib/console_formatter_spec.rb +29 -0
  61. data/spec/lib/metrics/boolean_metric_spec.rb +9 -0
  62. data/spec/lib/metrics/metric_spec.rb +26 -0
  63. data/spec/lib/metrics/number_metric_spec.rb +8 -0
  64. data/spec/lib/metrics/string_metric_spec.rb +7 -0
  65. data/spec/lib/project_catalog_spec.rb +23 -0
  66. data/spec/lib/project_spec.rb +45 -0
  67. data/spec/lib/storage/metric_mongo_spec.rb +12 -0
  68. data/spec/lib/storage/project_mongo_spec.rb +11 -0
  69. data/spec/lib/storage/property_value_mongo_spec.rb +11 -0
  70. data/spec/spec_helper.rb +8 -0
  71. metadata +313 -0
@@ -0,0 +1,26 @@
1
+ class DescribeProject < Struct.new :project_name, :date_format
2
+ def call formatter = formatter, records = records
3
+ formatter.present records
4
+ end
5
+
6
+ def project project_name = project_name
7
+ Project.find_by name: project_name
8
+ end
9
+
10
+ def records project = project
11
+ project.metrics
12
+ end
13
+
14
+ def formatter format = format
15
+ ConsoleFormatter.new format
16
+ end
17
+
18
+ def format date_format = date_format
19
+ {
20
+ 'Metric' => 'name',
21
+ 'Value' => 'value',
22
+ 'Changed at' => "last_updated_at.strftime('#{date_format}')",
23
+ 'Changed by' => "last_updated_by"
24
+ }
25
+ end
26
+ end
@@ -0,0 +1,9 @@
1
+ class EditProperty < Struct.new :project_name, :metric_name, :value, :author
2
+ def call project = project, metric_name = metric_name, value = value, author = author
3
+ project.edit_property(metric_name, value, author)
4
+ end
5
+
6
+ def project project_name = project_name
7
+ Project.find_by(name: project_name)
8
+ end
9
+ end
@@ -0,0 +1,12 @@
1
+ class TcCli
2
+ desc "Adds new project"
3
+ long_desc "Allows to create new project"
4
+ arg_name '{project_name}'
5
+ command :add do |command|
6
+ command.action do |global, options, args|
7
+ name = args.first
8
+ puts "Project #{name} was successfully added." if AddProject.new(name).call
9
+ end
10
+ end
11
+
12
+ end
@@ -0,0 +1,23 @@
1
+ class TcCli
2
+ desc "Adds new metric to the project"
3
+ long_desc "Allows to add new metric to the project"
4
+ arg_name '{metric_name}'
5
+ command :add_metric do |command|
6
+ command.arg_name 'project name'
7
+ command.desc 'name of the project, to which you want to add metric'
8
+ command.flag :pn, :project_name
9
+
10
+ command.arg_name 'metric type'
11
+ command.desc 'type of the metric you want to add (string, number, boolean)'
12
+ command.default_value "string"
13
+ command.flag :mt, :metric_type
14
+
15
+ command.action do |global, options, args|
16
+ project_name = options[:project_name]
17
+ metric_type = options[:metric_type]
18
+ metric_name = args.first
19
+ puts "Metric #{metric_name} with type #{metric_type} was successfully added to project #{project_name}." if AddMetric.new(project_name, metric_type, metric_name).call
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,16 @@
1
+ class TcCli
2
+ desc "Describes current state of the project"
3
+ long_desc "Displays current metrics of the project, date and author of last modification"
4
+ arg_name '{project_name}'
5
+ command :describe do |command|
6
+ command.arg_name 'date format'
7
+ command.desc 'ruby date format to display time of change'
8
+ command.default_value "%d-%m-%Y"
9
+ command.flag :df, :date_format
10
+
11
+ command.action do |global, options, args|
12
+ puts DescribeProject.new(args.first, options[:date_format]).call
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,22 @@
1
+ class TcCli
2
+ desc "Edits one metric of the project"
3
+ long_desc "Allows to edit one of the project's metrics"
4
+ arg_name '{value}'
5
+ command :edit_property do |command|
6
+ command.arg_name 'project name'
7
+ command.desc 'name of the project, property of which you want to edit'
8
+ command.flag :pn, :project_name
9
+
10
+ command.arg_name 'metric name'
11
+ command.desc 'name of the metric, which you want to edit'
12
+ command.flag :mn, :metric_name
13
+
14
+ command.action do |global, options, args|
15
+ project_name = options[:project_name]
16
+ metric_name = options[:metric_name]
17
+ value = args.first
18
+ puts "Metric #{metric_name} in project #{project_name} now has value #{value}." if EditProperty.new(project_name, metric_name, value, `whoami`).call
19
+ end
20
+ end
21
+
22
+ end
@@ -0,0 +1,19 @@
1
+ class TcCli
2
+ desc "Displays property history"
3
+ long_desc "Displays one or more properties history (list of values over time)"
4
+ arg_name '[{property}],[{another_property}],[...]'
5
+ command :history do |command|
6
+ command.arg_name 'project name'
7
+ command.desc 'name of the project, property of which you want to edit'
8
+ command.flag :pn, :project_name
9
+
10
+ command.arg_name 'date format'
11
+ command.desc 'ruby date format to display time of change'
12
+ command.default_value "%d-%m-%Y"
13
+ command.flag :df, :date_format
14
+
15
+ command.action do |global, options, args|
16
+ puts ViewHistory.new(options[:project_name], options[:date_format], args.first.try(:split, ',')).call
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,8 @@
1
+ class TcCli
2
+ pre do
3
+ ENV['RACK_ENV'] ||= 'production'
4
+ require 'hirb'
5
+ require 'tc'
6
+ true
7
+ end
8
+ end
@@ -0,0 +1,14 @@
1
+ class TcCli
2
+ desc "List projects from catalog"
3
+ long_desc "Lists projects from catalog matching specified criteria"
4
+ arg_name '{criteria}'
5
+ command :list do |command|
6
+ command.arg_name 'table format'
7
+ command.desc 'ruby code for table format'
8
+ command.flag :fm, :format
9
+
10
+ command.action do |global, options, args|
11
+ puts ListProjects.new(options[:format], args.first).call
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ class TcCli
2
+ program_desc 'Command Line Interface for Technical Coordination'
3
+ version '0.0.1'
4
+
5
+ end
@@ -0,0 +1,32 @@
1
+ class ListProjects < Struct.new :format, :criteria
2
+ def call formatter = formatter, projects = projects
3
+ formatter.present projects
4
+ end
5
+
6
+ def projects catalog = ProjectCatalog.load, criteria = criteria
7
+ catalog.projects criteria
8
+ end
9
+
10
+ def formatter(format = format.present? ? eval(format) : default_format)
11
+ ConsoleFormatter.new format
12
+ end
13
+
14
+ def default_format projects = projects
15
+ {'Name' => 'name'}.merge list_properties_format properties
16
+ end
17
+
18
+ private
19
+
20
+ def list_properties_format properties
21
+ Hash[properties.map { |p| [p, "property('#{p}').try(:value)"] }]
22
+ end
23
+
24
+ def properties
25
+ metrics.collect(&:name).flatten
26
+ end
27
+
28
+ def metrics
29
+ projects.collect(&:metrics).flatten
30
+ end
31
+
32
+ end
@@ -0,0 +1,37 @@
1
+ class ViewHistory < Struct.new :project_name, :date_format, :properties
2
+ def call formatter = formatter, records = records
3
+ formatter.present records
4
+ end
5
+
6
+ def project project_name = project_name
7
+ Project.find_by name: project_name
8
+ end
9
+
10
+ def records metrics = metrics
11
+ metrics.collect(&:values).flatten
12
+ end
13
+
14
+ def formatter format = format
15
+ ConsoleFormatter.new format
16
+ end
17
+
18
+ def properties project = project
19
+ self[:properties].present? ? self[:properties] : project.metrics.collect(&:name)
20
+ end
21
+
22
+ def metrics project = project, properties = properties
23
+ properties.map do |property|
24
+ project.property property
25
+ end.compact
26
+ end
27
+
28
+ def format date_format = date_format, metrics = metrics
29
+ {'Date' => "created_at.strftime('#{date_format}')"}.merge list_metrics_format(metrics)
30
+ end
31
+
32
+ private
33
+
34
+ def list_metrics_format metrics
35
+ Hash[metrics.map { |m| [m.name, "metric.name == '#{m.name}' ? value : ''"] }]
36
+ end
37
+ end
@@ -0,0 +1,18 @@
1
+ class ConsoleFormatter < Struct.new :format
2
+
3
+ def present records, format = format
4
+ Hirb::Helpers::AutoTable.render(render_each(records), fields: format.keys, resize: false)
5
+ end
6
+
7
+ def render_each records
8
+ records.map { |record|
9
+ render_record record
10
+ }
11
+ end
12
+
13
+ def render_record record, format = format
14
+ format.merge(format) do |_, expression|
15
+ record.instance_eval expression
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,15 @@
1
+ require 'gli'
2
+ ENV["COLUMNS"] = "1000"
3
+ ENV["LINES"] = "1000"
4
+
5
+ $:.unshift File.expand_path(File.dirname(File.realpath(__FILE__)))
6
+
7
+ class TcCli
8
+ extend GLI::App
9
+ commands_from 'commands/gli'
10
+
11
+ def self.exec argv
12
+ commands[:help] = GLI::Commands::Help.new(self)
13
+ run(argv)
14
+ end
15
+ end
@@ -0,0 +1,7 @@
1
+ class BooleanMetric < Metric
2
+
3
+ def convert value
4
+ !!value
5
+ end
6
+
7
+ end
@@ -0,0 +1,24 @@
1
+ class Metric
2
+
3
+ def edit value, user
4
+ values << PropertyValue.new(value: convert(value), created_by: user)
5
+ self
6
+ end
7
+
8
+ def value
9
+ values.last.try :value
10
+ end
11
+
12
+ def last_updated_by
13
+ values.last.try :created_by
14
+ end
15
+
16
+ def last_updated_at
17
+ values.last.try :created_at
18
+ end
19
+
20
+ def convert value
21
+ value
22
+ end
23
+
24
+ end
@@ -0,0 +1,7 @@
1
+ class NumberMetric < Metric
2
+
3
+ def convert value
4
+ value.to_f
5
+ end
6
+
7
+ end
@@ -0,0 +1,7 @@
1
+ class StringMetric < Metric
2
+
3
+ def convert value
4
+ value.to_s
5
+ end
6
+
7
+ end
data/lib/project.rb ADDED
@@ -0,0 +1,15 @@
1
+ class Project
2
+ def add_metric metric
3
+ metrics << metric
4
+ self
5
+ end
6
+
7
+ def edit_property name, value, user
8
+ property(name).edit value, user
9
+ self
10
+ end
11
+
12
+ def property name
13
+ metrics.find_by(name: name)
14
+ end
15
+ end
@@ -0,0 +1,34 @@
1
+ class ProjectCatalog
2
+
3
+ def initialize
4
+ @projects = []
5
+ end
6
+
7
+ def projects criteria = nil
8
+ result = @projects.sort_by(&:name)
9
+
10
+ return result if criteria.blank?
11
+ result.find_all { |project|
12
+ project.instance_eval criteria
13
+ }
14
+ end
15
+
16
+ def add_project project
17
+ @projects << project
18
+ self
19
+ end
20
+
21
+ def save
22
+ @projects.all? &:save
23
+ self
24
+ end
25
+
26
+ def load
27
+ @projects = Project.all
28
+ self
29
+ end
30
+
31
+ def self.load
32
+ self.new.load
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ class PropertyValue
2
+
3
+ end
@@ -0,0 +1,11 @@
1
+ class Metric
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+
5
+ field :name
6
+ validates :name, uniqueness: true
7
+
8
+ embedded_in :project
9
+
10
+ embeds_many :values, class_name: 'PropertyValue', cascade_callbacks: true
11
+ end
@@ -0,0 +1,9 @@
1
+ class Project
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+
5
+ field :name
6
+ validates :name, uniqueness: true
7
+
8
+ embeds_many :metrics, cascade_callbacks: true
9
+ end
@@ -0,0 +1,10 @@
1
+ class PropertyValue
2
+ include Mongoid::Document
3
+ include Mongoid::Timestamps
4
+
5
+ field :value
6
+ field :created_by
7
+ validates :created_by, presence: true
8
+
9
+ embedded_in :metric
10
+ end
data/lib/tc/version.rb ADDED
@@ -0,0 +1,9 @@
1
+ class TC
2
+ module Version
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ PATCH = 1
6
+
7
+ STRING = [MAJOR, MINOR, PATCH].compact.join('.')
8
+ end
9
+ end
data/lib/tc.rb ADDED
@@ -0,0 +1,27 @@
1
+ require 'hirb'
2
+ require 'mongoid'
3
+
4
+ config_folder = File.expand_path(File.dirname(File.realpath(__FILE__)) + '/../config')
5
+ `cp #{config_folder}/mongoid.yml.sample #{config_folder}/mongoid.yml` unless File.exists? "#{config_folder}/mongoid.yml"
6
+
7
+ Mongoid.load!("#{config_folder}/mongoid.yml")
8
+
9
+ $: << File.expand_path(File.dirname(File.realpath(__FILE__)) + '/../lib')
10
+
11
+ require 'project'
12
+ require 'property_value'
13
+ require 'project_catalog'
14
+ require 'console_formatter'
15
+ require 'metrics/metric'
16
+ require 'metrics/number_metric'
17
+ require 'metrics/string_metric'
18
+ require 'metrics/boolean_metric'
19
+ require 'commands/list_projects'
20
+ require 'commands/add_project'
21
+ require 'commands/add_metric'
22
+ require 'commands/edit_property'
23
+ require 'commands/view_history'
24
+ require 'commands/describe_project'
25
+ require 'storage/metric_mongo'
26
+ require 'storage/project_mongo'
27
+ require 'storage/property_value_mongo'
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe ProjectCatalog do
4
+
5
+ before { Project.delete_all }
6
+
7
+ subject {
8
+ ProjectCatalog.new.
9
+ add_project(Project.new(name: '1').add_metric(NumberMetric.new(name: 'length')).edit_property('length', 3, 'user')).
10
+ add_project(Project.new(name: '2').add_metric(NumberMetric.new(name: 'length')).edit_property('length', 2, 'user')).
11
+ add_project(Project.new(name: '3').add_metric(NumberMetric.new(name: 'length')).edit_property('length', 1, 'user'))
12
+ }
13
+
14
+ it 'loads list of saved projects' do
15
+ subject.save
16
+
17
+ ProjectCatalog.load.projects.should have_exactly(3).items
18
+ end
19
+ end
@@ -0,0 +1,31 @@
1
+ require 'spec_helper'
2
+
3
+ describe AddMetric do
4
+ it 'adds metric to the project' do
5
+ result = stub(:result)
6
+ metric = stub(:metric)
7
+ project = stub(:project).tap { |p| p.should_receive(:add_metric).with(metric) { result } }
8
+ subject.call(project, metric)
9
+ end
10
+
11
+ it 'find project' do
12
+ result = stub(:result)
13
+ project_name = stub(:project_name)
14
+ Project.should_receive(:find_by).with(name: project_name) { result }
15
+ subject.project(project_name).should == result
16
+ end
17
+
18
+ it 'creates appropriate metric class' do
19
+ subject.metric_class(:string).should == StringMetric
20
+ subject.metric_class(:number).should == NumberMetric
21
+ subject.metric_class(nil).should == Metric
22
+ end
23
+
24
+ it 'creates metric' do
25
+ result = stub(:result)
26
+ name = stub(:name)
27
+ metric_class = stub(:metric_class).tap { |mc| mc.should_receive(:new).with(name: name) { result } }
28
+
29
+ subject.metric(metric_class, name).should == result
30
+ end
31
+ end
@@ -0,0 +1,10 @@
1
+ require 'spec_helper'
2
+
3
+ describe AddProject do
4
+ it 'adds new project to the database' do
5
+ name = stub(:name)
6
+ project = stub(:project)
7
+ Project.should_receive(:create).with(name: name) { project }
8
+ subject.call(name).should == project
9
+ end
10
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe DescribeProject do
4
+ it 'presents properties to table view' do
5
+ result = stub(:result)
6
+
7
+ records = stub(:records)
8
+ formatter = stub(:formatter).tap { |f| f.should_receive(:present).with(records) { result } }
9
+
10
+ subject.call(formatter, records).should == result
11
+ end
12
+
13
+ it 'passes format to console formatter' do
14
+ format = stub(:format)
15
+ ConsoleFormatter.should_receive(:new).with(format)
16
+
17
+ subject.formatter(format)
18
+ end
19
+
20
+ it 'collects metrics from project' do
21
+ metrics = stub(:metrics)
22
+ project = stub(:project, :metrics => metrics)
23
+ subject.records(project).should == metrics
24
+ end
25
+
26
+ it 'constructs format for console formatter' do
27
+ subject.format("DATE_FORMAT").should == {
28
+ 'Metric' => 'name',
29
+ 'Value' => 'value',
30
+ 'Changed at' => "last_updated_at.strftime('DATE_FORMAT')",
31
+ 'Changed by' => "last_updated_by"
32
+ }
33
+ end
34
+
35
+ it 'finds project by its name' do
36
+ name, result = stub(:name), stub(:result)
37
+ Project.should_receive(:find_by).with(name: name) { result }
38
+
39
+ subject.project(name).should == result
40
+ end
41
+
42
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+
3
+ describe EditProperty do
4
+ it 'edits property of the project' do
5
+ result = stub(:result)
6
+ metric_name = stub(:metric_name)
7
+ value = stub(:value)
8
+ author = stub(:author)
9
+ project = stub(:project).tap { |p| p.should_receive(:edit_property).with(metric_name, value, author) { result } }
10
+ subject.call(project, metric_name, value, author).should == result
11
+ end
12
+
13
+ it 'find project' do
14
+ result = stub(:result)
15
+ project_name = stub(:project_name)
16
+ Project.should_receive(:find_by).with(name: project_name) { result }
17
+ subject.project(project_name).should == result
18
+ end
19
+ end
@@ -0,0 +1,47 @@
1
+ require 'spec_helper'
2
+
3
+ describe ListProjects do
4
+ it 'passes format to console formatter' do
5
+ format = '42'
6
+ ConsoleFormatter.should_receive(:new).with(eval(format))
7
+
8
+ ListProjects.new(format).formatter
9
+ end
10
+
11
+ it 'gets list of projects from catalog according to criteria' do
12
+ result = stub(:result)
13
+
14
+ criteria = stub(:criteria)
15
+ catalog = stub(:catalog).tap { |c| c.should_receive(:projects).with(criteria) { result } }
16
+
17
+ subject.projects(catalog, criteria).should == result
18
+ end
19
+
20
+ it 'presents projects to table view' do
21
+ result = stub(:result)
22
+
23
+ projects = stub(:projects)
24
+ formatter = stub(:formatter).tap { |f| f.should_receive(:present).with(projects) { result } }
25
+
26
+ subject.call(formatter, projects).should == result
27
+ end
28
+
29
+ it 'uses default format if no format string was specified' do
30
+ result = stub(:result)
31
+
32
+ command = ListProjects.new(nil)
33
+ command.should_receive(:default_format) { result }
34
+
35
+ command.formatter
36
+ end
37
+
38
+ it 'builds default format as list of all metrics for all projects' do
39
+ subject.stub(:projects => [
40
+ stub(:metrics => [stub(:name => 'metric1'), stub(:name => 'metric2')]),
41
+ stub(:metrics => [stub(:name => 'metric3')])])
42
+ subject.default_format.should == {'Name' => 'name',
43
+ 'metric1' => "property('metric1').try(:value)",
44
+ 'metric2' => "property('metric2').try(:value)",
45
+ 'metric3' => "property('metric3').try(:value)"}
46
+ end
47
+ end