mingle-macro-development-toolkit 1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE.txt +21 -0
- data/README.rdoc +66 -0
- data/Rakefile +36 -0
- data/bin/new_mingle_macro +107 -0
- data/example/Rakefile +3 -0
- data/example/deploy.rake +10 -0
- data/example/init.rb +10 -0
- data/example/integration_test.rb +13 -0
- data/example/integration_test_helper.rb +19 -0
- data/example/macro.rb +18 -0
- data/example/unit_test.rb +13 -0
- data/example/unit_test_helper.rb +12 -0
- data/getting_started.txt +253 -0
- data/lib/macro_development_toolkit.rb +22 -0
- data/lib/macro_development_toolkit/mingle/card_type.rb +41 -0
- data/lib/macro_development_toolkit/mingle/card_type_property_definition.rb +26 -0
- data/lib/macro_development_toolkit/mingle/project.rb +80 -0
- data/lib/macro_development_toolkit/mingle/project_variable.rb +23 -0
- data/lib/macro_development_toolkit/mingle/property_definition.rb +94 -0
- data/lib/macro_development_toolkit/mingle/property_value.rb +68 -0
- data/lib/macro_development_toolkit/mingle/user.rb +29 -0
- data/lib/macro_development_toolkit/mingle_model_loader.rb +169 -0
- data/tasks/test.rake +15 -0
- data/test/fixtures/sample/card_types.yml +29 -0
- data/test/fixtures/sample/card_types_property_definitions.yml +81 -0
- data/test/fixtures/sample/project_variables.yml +5 -0
- data/test/fixtures/sample/projects.yml +4 -0
- data/test/fixtures/sample/property_definitions.yml +41 -0
- data/test/fixtures/sample/users.yml +16 -0
- data/test/integration/integration_test_helper.rb +20 -0
- data/test/integration/rest_loader.rb +302 -0
- data/test/integration/rest_loader_test.rb +86 -0
- data/test/unit/fixture_loader.rb +180 -0
- data/test/unit/fixture_loader_test.rb +46 -0
- data/test/unit/unit_test_helper.rb +13 -0
- metadata +110 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2008 ThoughtWorks, Inc. All rights reserved.
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,66 @@
|
|
1
|
+
= Macro Development Toolkit - supporting development of custom macros for Mingle
|
2
|
+
|
3
|
+
This toolkit provides support for developing, testing and deploying custom Mingle macros.
|
4
|
+
|
5
|
+
Use the built in generator to create a skeleton of a plugin which you can then deploy to an instance of Mingle running
|
6
|
+
version 2.2 or later of the software.
|
7
|
+
|
8
|
+
This allows you to take advantage of free charting utilities such as the Google Charts API, to create
|
9
|
+
new visualizations, specific to your project or organization.
|
10
|
+
|
11
|
+
== FEATURES:
|
12
|
+
|
13
|
+
* A command line tool to generate the skeleton of a custom Mingle macro
|
14
|
+
* A unit test helper that uses locally available YAML fixture files to facilitate unit testing of the macros
|
15
|
+
* An integration test helper that can
|
16
|
+
* obtain data from a remote instance of Mingle
|
17
|
+
* test MQL execution against a remote instance of Mingle
|
18
|
+
* Rake task to deploy the custom macro as a plugin to a locally deployed Mingle instance
|
19
|
+
|
20
|
+
== INSTALL:
|
21
|
+
|
22
|
+
The preferred method of installing the Macro Development Toolkit is through its GEM file. You'll need to have
|
23
|
+
RubyGems[http://www.rubygems.org/] installed for that, though. If you have it,
|
24
|
+
then use:
|
25
|
+
|
26
|
+
% [sudo] gem install macro_development_toolkit-1.0.gem
|
27
|
+
|
28
|
+
== GETTING STARTED:
|
29
|
+
|
30
|
+
To get started with this gem after you install it, use the new_mingle_macro script to generate a skeleton for your
|
31
|
+
macro, along with test helpers. Say you wanted to create a new macro called "risk_meter" start out by creating
|
32
|
+
the macro skeleton as follows
|
33
|
+
|
34
|
+
% new_mingle_macro risk_meter
|
35
|
+
|
36
|
+
The skeleton project will also contain a file, called "getting_started.txt" that will walk you through the steps
|
37
|
+
of fleshing out your macro.
|
38
|
+
|
39
|
+
== SUPPORT:
|
40
|
+
|
41
|
+
For any issues/clarifications/comments with installation or using this gem, contact us at the Mingle forums[http://studios.thoughtworks.com/discussion/forums/]. You can also get in touch with the ThoughtWorks Studios support
|
42
|
+
team over email at support@thoughtworks.com
|
43
|
+
|
44
|
+
== LICENSE:
|
45
|
+
|
46
|
+
The MIT License
|
47
|
+
|
48
|
+
Copyright (c) 2008 ThoughtWorks, Inc. All rights reserved.
|
49
|
+
|
50
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
51
|
+
of this software and associated documentation files (the "Software"), to deal
|
52
|
+
in the Software without restriction, including without limitation the rights
|
53
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
54
|
+
copies of the Software, and to permit persons to whom the Software is
|
55
|
+
furnished to do so, subject to the following conditions:
|
56
|
+
|
57
|
+
The above copyright notice and this permission notice shall be included in
|
58
|
+
all copies or substantial portions of the Software.
|
59
|
+
|
60
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
61
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
62
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
63
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
64
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
65
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
66
|
+
THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#Copyright 2008 ThoughtWorks, Inc. All rights reserved.
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'rubygems'
|
5
|
+
|
6
|
+
unless Gem::RubyGemsVersion >= '1.2.0'
|
7
|
+
$stderr.puts %(Rails requires RubyGems >= 1.2.0 (you have #{rubygems_version}). Please `gem update --system` and try again.)
|
8
|
+
exit 1
|
9
|
+
end
|
10
|
+
|
11
|
+
rescue LoadError
|
12
|
+
$stderr.puts %(Rails requires RubyGems >= 1.2.0. Please install RubyGems and try again: http://rubygems.rubyforge.org)
|
13
|
+
exit 1
|
14
|
+
end
|
15
|
+
|
16
|
+
%w[rake rake/clean fileutils newgem rubigen].each { |f| require f }
|
17
|
+
require File.dirname(__FILE__) + '/lib/macro_development_toolkit'
|
18
|
+
|
19
|
+
# Generate all the Rake tasks
|
20
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
21
|
+
$hoe = Hoe.new('mingle-macro-development-toolkit', MacroDevelopmentToolkit::VERSION) do |p|
|
22
|
+
p.developer('ThoughtWorks Inc', 'support@thoughtworks.com')
|
23
|
+
p.post_install_message = 'getting_started.txt'
|
24
|
+
p.rubyforge_name = 'mingle-macros'
|
25
|
+
p.extra_deps = [
|
26
|
+
['activesupport','>= 2.0.2'],
|
27
|
+
]
|
28
|
+
p.rdoc_pattern = /README|(lib\/macro_development_toolkit\/mingle(?!_))/
|
29
|
+
p.clean_globs |= %w[**/.DS_Store tmp *.log]
|
30
|
+
path = (p.rubyforge_name == p.name) ? p.rubyforge_name : "\#{p.rubyforge_name}/\#{p.name}"
|
31
|
+
p.remote_rdoc_dir = ''
|
32
|
+
p.rsync_args = '-av --delete --ignore-errors'
|
33
|
+
end
|
34
|
+
|
35
|
+
require 'newgem/tasks'
|
36
|
+
Dir['tasks/**/*.rake'].each { |t| load t }
|
@@ -0,0 +1,107 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#Copyright 2008 ThoughtWorks, Inc. All rights reserved.
|
3
|
+
|
4
|
+
require 'erb'
|
5
|
+
begin
|
6
|
+
require 'activesupport'
|
7
|
+
rescue LoadError
|
8
|
+
require 'rubygems'
|
9
|
+
require 'activesupport'
|
10
|
+
end
|
11
|
+
|
12
|
+
def print_usage
|
13
|
+
puts <<-EOS
|
14
|
+
Macro name cannot be blank.
|
15
|
+
|
16
|
+
Usage: new_mingle_macro <macro_name> [macro_class_name]
|
17
|
+
|
18
|
+
macro_name : The name of your new macro. Should be aplphabetic, possibly including underscores(_)
|
19
|
+
macro_class_name: (optional) The name of the class that implements the macro. Should be a CamelCased alphabetic string. Defaults to CamelCased version of macro_name.
|
20
|
+
|
21
|
+
EOS
|
22
|
+
end
|
23
|
+
|
24
|
+
if ARGV[0].blank?
|
25
|
+
print_usage
|
26
|
+
exit!
|
27
|
+
end
|
28
|
+
|
29
|
+
macro_name = ARGV[0].downcase.gsub(/\s/, '_')
|
30
|
+
|
31
|
+
if macro_name !~ /^[_A-Za-z]+$/
|
32
|
+
print "Your macro name contains special characters that we will replace with underscore (_). Is this OK? [Yes/No]"
|
33
|
+
exit if STDIN.gets.downcase =~ /^n/
|
34
|
+
macro_name = macro_name.gsub(/[^_A-Za-z]/, '_').squeeze('_')
|
35
|
+
end
|
36
|
+
|
37
|
+
macro_class_name = ARGV[1] || macro_name.gsub(/\W/, '_').classify
|
38
|
+
if macro_class_name.classify != macro_class_name
|
39
|
+
print "Your macro class name is not a valid class name. Continue with the default class name of #{macro_name.classify}? [Yes/No]"
|
40
|
+
exit if STDIN.gets.downcase =~ /^n/
|
41
|
+
macro_class_name = macro_name.classify
|
42
|
+
end
|
43
|
+
|
44
|
+
if File.exists?(macro_name)
|
45
|
+
print "Folder #{macro_name} already exists in your current directory. Either rename the macro or delete the folder and then execute this command to continue."
|
46
|
+
exit
|
47
|
+
end
|
48
|
+
|
49
|
+
puts "macro name is #{macro_name}"
|
50
|
+
|
51
|
+
class Path
|
52
|
+
def initialize(location=:gem)
|
53
|
+
@base_location = if location.to_s == 'gem'
|
54
|
+
File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
55
|
+
else
|
56
|
+
File.expand_path(location)
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
def /(another_fragment)
|
62
|
+
self.class.new(File.join(@base_location, another_fragment))
|
63
|
+
end
|
64
|
+
|
65
|
+
def cp(options = {:to => nil, :r => true})
|
66
|
+
if options[:r]
|
67
|
+
FileUtils.cp(@base_location, options[:to].to_s, :verbose => true)
|
68
|
+
else
|
69
|
+
FileUtils.cp_r(@base_location, options[:to].to_s, :verbose => true)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def mkdir_p
|
74
|
+
FileUtils.mkdir_p(@base_location)
|
75
|
+
end
|
76
|
+
|
77
|
+
def to_s
|
78
|
+
@base_location
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
gem_dir = Path.new(:gem)
|
83
|
+
macro_dir = Path.new(macro_name)
|
84
|
+
|
85
|
+
[macro_dir/'lib', macro_dir/'test'/'unit', macro_dir/'test'/'fixtures', macro_dir/'test'/'integration', macro_dir/'tasks'].each(&:mkdir_p)
|
86
|
+
|
87
|
+
(gem_dir/'test'/'fixtures'/'sample').cp :to => macro_dir/'test'/'fixtures'
|
88
|
+
(gem_dir/'test'/'unit'/'fixture_loader.rb').cp :to => macro_dir/'test'/'unit'/'fixture_loader.rb'
|
89
|
+
(gem_dir/'test'/'integration'/'rest_loader.rb').cp :to => macro_dir/'test'/'integration'/'rest_loader.rb'
|
90
|
+
(gem_dir/'tasks'/'test.rake').cp :to => macro_dir/'tasks'/'test.rake'
|
91
|
+
(gem_dir/'example'/'deploy.rake').cp :to => macro_dir/'tasks'/'deploy.rake'
|
92
|
+
(gem_dir/'getting_started.txt').cp :to => macro_dir/'getting_started.txt'
|
93
|
+
|
94
|
+
templates = {
|
95
|
+
gem_dir/'example'/'Rakefile' => macro_dir/'Rakefile',
|
96
|
+
gem_dir/'example'/'init.rb' => macro_dir/'init.rb',
|
97
|
+
gem_dir/'example'/'macro.rb' => macro_dir/'lib'/"#{macro_name}.rb",
|
98
|
+
gem_dir/'example'/'unit_test.rb' => macro_dir/'test'/'unit'/"#{macro_name}_test.rb",
|
99
|
+
gem_dir/'example'/'unit_test_helper.rb' => macro_dir/'test'/'unit'/'unit_test_helper.rb',
|
100
|
+
gem_dir/'example'/'integration_test.rb' => macro_dir/'test'/'integration'/"#{macro_name}_integration_test.rb",
|
101
|
+
gem_dir/'example'/'integration_test_helper.rb' => macro_dir/'test'/'integration'/'integration_test_helper.rb'
|
102
|
+
}
|
103
|
+
|
104
|
+
templates.each do |template, output|
|
105
|
+
template_file = File.open(template.to_s)
|
106
|
+
File.open(output.to_s, 'w').write(ERB.new(template_file.read, nil, '-').result(binding))
|
107
|
+
end
|
data/example/Rakefile
ADDED
data/example/deploy.rake
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
namespace :macro do |ns|
|
2
|
+
|
3
|
+
task :deploy do
|
4
|
+
macro_folder = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
5
|
+
mingle_plugins_folder = File.join(ENV['MINGLE_LOCATION'], 'vendor', 'plugins')
|
6
|
+
FileUtils.cp_r(macro_folder, mingle_plugins_folder)
|
7
|
+
puts "#{macro_folder} successfully copied over to #{mingle_plugins_folder}. Restart the Mingle server to start using the macro."
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
data/example/init.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
begin
|
2
|
+
require 'macro_development_toolkit'
|
3
|
+
rescue LoadError
|
4
|
+
require 'rubygems'
|
5
|
+
require 'macro_development_toolkit'
|
6
|
+
end
|
7
|
+
|
8
|
+
if defined?(RAILS_ENV) && RAILS_ENV == 'production' && defined?(MinglePlugins)
|
9
|
+
MinglePlugins::Macros.register(<%= macro_class_name %>, '<%= macro_name %>')
|
10
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/integration_test_helper.rb'
|
2
|
+
|
3
|
+
class <%= macro_class_name %>IntegrationTest < Test::Unit::TestCase
|
4
|
+
|
5
|
+
PROJECT_RESOURCE = 'http://username:password@your.mingle.server:port/lightweight_projects/your_project_identifier.xml'
|
6
|
+
|
7
|
+
def test_macro_contents
|
8
|
+
<%= macro_name %> = <%= macro_class_name %>.new(nil, project(PROJECT_RESOURCE), nil)
|
9
|
+
result = <%= macro_name %>.execute
|
10
|
+
assert result
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'init.rb')
|
3
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'lib', '<%= macro_name %>')
|
4
|
+
require File.join(File.dirname(__FILE__), 'rest_loader')
|
5
|
+
|
6
|
+
class Test::Unit::TestCase
|
7
|
+
|
8
|
+
def project(name)
|
9
|
+
@project ||= RESTfulLoaders::ProjectLoader.new(name, nil, self).project
|
10
|
+
end
|
11
|
+
|
12
|
+
def errors
|
13
|
+
@errors ||= []
|
14
|
+
end
|
15
|
+
|
16
|
+
def alert(message)
|
17
|
+
errors << message
|
18
|
+
end
|
19
|
+
end
|
data/example/macro.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
class <%= macro_class_name %>
|
2
|
+
|
3
|
+
def initialize(parameters, project, current_user)
|
4
|
+
@parameters = parameters
|
5
|
+
@project = project
|
6
|
+
@current_user = current_user
|
7
|
+
end
|
8
|
+
|
9
|
+
def execute
|
10
|
+
"Customize me and return some HTML"
|
11
|
+
end
|
12
|
+
|
13
|
+
def can_be_cached?
|
14
|
+
false # if appropriate, switch to true once you move your macro to production
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'unit_test_helper')
|
2
|
+
|
3
|
+
class <%= macro_class_name %>Test < Test::Unit::TestCase
|
4
|
+
|
5
|
+
FIXTURE = 'sample'
|
6
|
+
|
7
|
+
def test_macro_contents
|
8
|
+
<%= macro_name %> = <%= macro_class_name %>.new(nil, project(FIXTURE), nil)
|
9
|
+
result = <%= macro_name %>.execute
|
10
|
+
assert result
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'test/unit'
|
2
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'init.rb')
|
3
|
+
require File.join(File.dirname(__FILE__), '..', '..', 'lib', '<%= macro_name %>')
|
4
|
+
require File.join(File.dirname(__FILE__), 'fixture_loader')
|
5
|
+
|
6
|
+
class Test::Unit::TestCase
|
7
|
+
|
8
|
+
def project(name)
|
9
|
+
@project ||= FixtureLoaders::ProjectLoader.new(name).project
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
data/getting_started.txt
ADDED
@@ -0,0 +1,253 @@
|
|
1
|
+
A background on macros in Mingle
|
2
|
+
---------------------------------
|
3
|
+
|
4
|
+
Macros are a special kind of markup in the Mingle wiki. A macro is identified by the following markup generic syntax
|
5
|
+
|
6
|
+
{{
|
7
|
+
macro_name
|
8
|
+
parameter1: value1
|
9
|
+
parameter2: value2
|
10
|
+
...
|
11
|
+
}}
|
12
|
+
|
13
|
+
The markup has to be valid YAML syntax. Specifically, this means that the markup is sensitive to spacing and indentation. For more help around YAML and what constitutes valid YAML markup, you can refer to http://yaml.org/spec/current.html
|
14
|
+
|
15
|
+
Specific examples of this include the pre-written macros, such as the value, average & table macros and the macros for all the charts.
|
16
|
+
|
17
|
+
When Mingle encounters a macro while rendering the markup, it delegates handling of the macro to a custom class behind the scenes that is registered to handle it. e.g., if Mingle encountered the following markup,
|
18
|
+
|
19
|
+
{{
|
20
|
+
average
|
21
|
+
query: SELECT 'Pre-release Estimate' WHERE Release = (current release)
|
22
|
+
}}
|
23
|
+
|
24
|
+
it would parse the content between the opening and closing double braces, and identify the following.
|
25
|
+
|
26
|
+
Macro Name: average
|
27
|
+
Macro Parameters: {query => "SELECT 'Pre-release Estimate' WHERE Release = (current release)"}
|
28
|
+
|
29
|
+
It then scans a registry of known macros, for a class that is configured to handle a macro with name average. This is the AverageMacro class. You can find this class under the vendor/plugins/average_macro directory of your installation of Mingle.
|
30
|
+
|
31
|
+
class AverageMacro
|
32
|
+
|
33
|
+
def initialize(parameters, project, current_user)
|
34
|
+
@parameters = parameters
|
35
|
+
@project = project
|
36
|
+
raise "Parameter <b>query</b> is required" unless query
|
37
|
+
end
|
38
|
+
|
39
|
+
def execute
|
40
|
+
first_values = @project.execute_mql(query).collect { |record| record.values.first }
|
41
|
+
data = first_values.reject(&:blank?).collect(&:to_f)
|
42
|
+
data.empty? ? 'no values found' : @project.format_number_with_project_precision(data.sum.to_f/data.size.to_f)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def query
|
48
|
+
@parameters['query']
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
All data that is required to execute the macro is injected into the macro through the constructor. The parameters that are interpreted from the markup are passed in as a hash. The project that is passed in is a lightweight representation of the project in the Mingle model, and is documented at http://mingle-macros.rubyforge.org/rdoc
|
53
|
+
|
54
|
+
The execute method uses the MQL execution facility that the project class provides, to execute the MQL string that is passed into through the parameters hash. It then formats the results to be a number, and provides that result of the execute command.
|
55
|
+
|
56
|
+
For more help on what constitutes valid MQL, you can refer to our help documentation at http://studios.thoughtworks.com/mingle-agile-project-management/2.2/help/mql_reference.html
|
57
|
+
|
58
|
+
Writing your own macro
|
59
|
+
----------------------
|
60
|
+
|
61
|
+
To write your own macro, you can start with the generated skeleton for the macro. To generate your own macro skeleton, use the new_mingle_macro script that installed with your gem.
|
62
|
+
|
63
|
+
% new_mingle_macro your_new_macro
|
64
|
+
|
65
|
+
This should generate a folder structure as follows
|
66
|
+
|
67
|
+
your_new_macro
|
68
|
+
|
|
69
|
+
|----lib
|
70
|
+
| |
|
71
|
+
| your_new_macro.rb
|
72
|
+
|
|
73
|
+
|
|
74
|
+
|----test
|
75
|
+
|
|
76
|
+
|----fixtures
|
77
|
+
| |
|
78
|
+
| sample
|
79
|
+
| |
|
80
|
+
| projects.yml, card_types.yml...
|
81
|
+
|
|
82
|
+
|---- unit
|
83
|
+
| |
|
84
|
+
| your_new_macro_test.rb,...
|
85
|
+
|
|
86
|
+
|---- integration
|
87
|
+
|
|
88
|
+
your_new_macro_integration_test.rb, ...
|
89
|
+
|
90
|
+
|
91
|
+
The lib directory contains the actual macro, and the test folders give you the option to run the tests either against local YAML based fixtures, or using REST to test against a deployed mingle instance.
|
92
|
+
|
93
|
+
When this macro is deployed to Mingle, all wiki markup of the form
|
94
|
+
|
95
|
+
{{
|
96
|
+
your_new_macro
|
97
|
+
parameter1: value1
|
98
|
+
parameter2: <some_mql_statement>
|
99
|
+
...
|
100
|
+
}}
|
101
|
+
|
102
|
+
will be parsed as YAML and handling will be delegated to an instance of your macro class, YourNewMacro. The parameters will be parsed into a Ruby hash, of the following structure:
|
103
|
+
|
104
|
+
{'parameter1' => 'value1', 'parameter2' => '<some_mql_snippet>'}
|
105
|
+
|
106
|
+
and will be passed into the constructor of the class, along with an instance of a Mingle::Project, that represents the project that this macro is being rendered on.
|
107
|
+
|
108
|
+
As an example of what you can do with this information is the following macro, which uses the Google Charting API to render a Google-o-meter style chart to represent work completed in a fuel gauge style meter.
|
109
|
+
|
110
|
+
class WorkGauge
|
111
|
+
|
112
|
+
def initialize(parameters, project, current_user)
|
113
|
+
@parameters = parameters
|
114
|
+
@project = project
|
115
|
+
@current_user = current_user
|
116
|
+
end
|
117
|
+
|
118
|
+
def execute
|
119
|
+
completed_work = @project.execute_mql(@parameters['completed_work']).first.values.sum
|
120
|
+
total_work = @project.execute_mql(@parameters['total_work']).first.values.sum
|
121
|
+
completion_percentage = (completed_work.to_f / total_work.to_f) * 100
|
122
|
+
|
123
|
+
%Q{ <img src='http://chart.apis.google.com/chart?cht=gom&chs=350&chd=t:#{completion_percentage}&chds=0,100' /> }
|
124
|
+
end
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
You can find both simpler and more complex examples in the vendor/plugins/sample_macros directory.
|
129
|
+
|
130
|
+
Unit testing your macro
|
131
|
+
------------------------
|
132
|
+
|
133
|
+
The macro development toolkit comes with a built in unit testing framework, that borrows the familiar idea of YAML based fixtures. The one small difference we have made to it is that each project that you are providing fixtures for gets its own subfolder within the fixtures directory. We hope that this makes it easier to identify relationships between the objects set up in the YAML files.
|
134
|
+
|
135
|
+
If you are using the skeleton project set up by the new_mingle_macro script, the fixtures directory provides you with a sample project fixture. The data in that should give you a sense of the relationships between the various objects.
|
136
|
+
|
137
|
+
The skeleton project also has a sample unit test set up for you, which uses the sample fixture data. Note the helper method project(...) which takes the name of a sample project to load information from. This method loads a web of objects from the directory named the same as the argument, in the fixtures folder.
|
138
|
+
|
139
|
+
class YourNewMacroTest < Test::Unit::TestCase
|
140
|
+
|
141
|
+
FIXTURE = 'sample'
|
142
|
+
|
143
|
+
def test_macro_contents
|
144
|
+
macro = YourNewMacro.new(nil, project(FIXTURE), nil)
|
145
|
+
result = macro.execute
|
146
|
+
assert result
|
147
|
+
end
|
148
|
+
|
149
|
+
end
|
150
|
+
|
151
|
+
Once loaded, you can test things like parameter checking and validations using this style of test. While you cannot execute MQL in this style of test, you can use your favorite mocking library to test how results get handled.
|
152
|
+
|
153
|
+
You can see examples of unit tests in the average macro that is packaged with Mingle in the vendor/plugins/average_macro directory.
|
154
|
+
|
155
|
+
To run your unit tests, run
|
156
|
+
|
157
|
+
% rake test:units
|
158
|
+
|
159
|
+
from the root of your custom macro
|
160
|
+
|
161
|
+
Integration testing your macro
|
162
|
+
-------------------------------
|
163
|
+
|
164
|
+
########################################################NOTE###############################################################
|
165
|
+
# #
|
166
|
+
# IN ORDER TO RUN THE INTEGRATION TESTS, YOU WILL NEED TO TURN ON BASIC AUTHENTICATION FOR THE MINGLE 2.2 SERVER THAT YOU #
|
167
|
+
# ARE GOING TO BE TESTING AGAINST. #
|
168
|
+
# #
|
169
|
+
#########################################################NOTE##############################################################
|
170
|
+
|
171
|
+
The integration tests look very similar to the unit tests, the primary difference being that they actually communicate with a deployed Mingle instance over REST. The helper methods populate a web of objects representing a project, that look and behave in a manner identical to how they will in production.
|
172
|
+
|
173
|
+
The one significant difference about these style of tests is that you can actually execute MQL remotely on the Mingle instance instead of mocking out the MQL execution. This will give you a good idea of what results and errors you may expect to see in production, without having to deploy the macro every time.
|
174
|
+
|
175
|
+
There are tradeoffs, of course. Some of these are as follows
|
176
|
+
|
177
|
+
* Should you decide to add these tests to a Continuous Integration build, like Cruise, you will hit the production Mingle sever with every test run. Not hot.
|
178
|
+
|
179
|
+
* Given that each test makes a call to a production server, there is no guarantee (unless you set it up in such a way) - that multiple calls to fetch the same resource will give the same result.
|
180
|
+
|
181
|
+
* Also, while not slow, these tests are definitely much slower than the unit tests. So while it is certainly possible to write only integration tests, we would encourage as a judicious mix of both styles.
|
182
|
+
|
183
|
+
class YourNewMacroIntegrationTest < Test::Unit::TestCase
|
184
|
+
|
185
|
+
PROJECT_RESOURCE = 'http://yourname:password@your.mingle.server:port/lightweight_project/project_identifier.xml'
|
186
|
+
|
187
|
+
def test_macro_contents
|
188
|
+
macro = YourNewMacro.new(nil, project(PROJECT_RESOURCE), nil)
|
189
|
+
result = macro.execute
|
190
|
+
assert result
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
194
|
+
|
195
|
+
The skeleton project also has a sample integration test set up for you, which points to a bogus Mingle server, and uses bad credentials. Replace this resource URL with the URL for a deployed instance within your organization. The helper method project(...) which takes the resource URL, loads the data from the XML data obtained from the live instance of Mingle.
|
196
|
+
|
197
|
+
You can see examples of integration tests in the average macro that is packaged with Mingle in the vendor/plugins/average_macro directory. These tests run against a standard template that ships with Mingle2.2, and so you should be able to run them within your organization too, without a problem.
|
198
|
+
|
199
|
+
To run your integration tests, run
|
200
|
+
|
201
|
+
% rake test:integration
|
202
|
+
|
203
|
+
from the root of your custom macro
|
204
|
+
|
205
|
+
Deploying your macro
|
206
|
+
---------------------
|
207
|
+
|
208
|
+
####################################################### CAUTION ###########################################################
|
209
|
+
# #
|
210
|
+
# BEFORE YOU DEPLOY ANYTHING TO YOUR MINGLE INSTANCE, PLEASE MAKE SURE THAT IT IS COMPLETELY SAFE. THIS IS ESPECIALLY #
|
211
|
+
# IMPORTANT IF THE MACRO WAS DEVELOPED BY A THIRD PARTY. #
|
212
|
+
# HERE IS A LIST OF THINGS THAT YOU SHOULD LOOK OUT FOR. THIS LIST SHOULD NOT BE CONSIDERED COMPLETE, IT IS JUST A #
|
213
|
+
# REPRESENTATIVE SAMPLE. #
|
214
|
+
# * IF MINGLE RUNS AS A PRIVILEGED USER, THE MACRO COULD END UP DAMAGING THE HOST MACHINE #
|
215
|
+
# * THROUGH DIRECT SQL CALLS, RATHER THAN USING SUPPLIED MQL EXECUTION MECHANISM, THE MACRO COULD GAIN ACCESS TO #
|
216
|
+
# DATA THAT PEOPLE WOULD NORMALLY NOT BE AUTHORIZED TO SEE #
|
217
|
+
# * LENGTHY CALLS TO EXTERNAL SYSTEMS COULD TIE UP MINGLE RESOURCES AND LEAVE THE APP UNRESPONSIVE #
|
218
|
+
# * SYSTEM CALLS, IF USED, MUST BE INSPECTED AND WELL-UNDERSTOOD PRIOR TO DEPLOYMENT #
|
219
|
+
# * OTHER DATABASE ACTIVITY, SUCH AS TRANSACTION COMMITS, SHOULD BE MONITORED AND AVOIDED #
|
220
|
+
# #
|
221
|
+
######################################################## CAUTION ##########################################################
|
222
|
+
|
223
|
+
|
224
|
+
To deploy your macro to a locally deployed instance of Mingle, which is running at mingle_root
|
225
|
+
|
226
|
+
% rake macro:deploy MINGLE_LOCATION=/path/to/mingle_root
|
227
|
+
|
228
|
+
where /path/to/mingle_root is the location where Mingle2.2 is installed.
|
229
|
+
|
230
|
+
* On Windows, this is the location that the installer installed Mingle at
|
231
|
+
* On OSX, this will be within the app bundle, at <mingle_application_bundle>/Contents/Resources/app
|
232
|
+
* On *NIX, this is the expanded archive
|
233
|
+
|
234
|
+
The entire macro folder and its contents will be copied over into the vendor/plugins directory of that Mingle installation. Once deployed, the server will need to be restarted in order for the macro to become available for use.
|
235
|
+
|
236
|
+
Alternatively, you could also copy the folder by hand into the same location.
|
237
|
+
|
238
|
+
########################################################NOTE################################################################
|
239
|
+
# #
|
240
|
+
# LEGAL NOTICES AND INFORMATION #
|
241
|
+
# #
|
242
|
+
#########################################################NOTE###############################################################
|
243
|
+
|
244
|
+
This Getting Started file and the mingle-macro-development-toolkit-1.0.gem are owned exclusively by ThoughtWorks, Inc.,
|
245
|
+
and ThoughtWorks reserves all rights therein.
|
246
|
+
|
247
|
+
We believe that it is a sound practice from legal, business and software development perspectives to always provide copyright
|
248
|
+
information and license information with any software that you make available to others. We have provided this information
|
249
|
+
for the Mingle Macro Development Toolkit in the LICENSE.txt file distributed with the Toolkit. We encourage you to use that
|
250
|
+
as an example to follow when marking your software with a copyright notice, as well as providing users with a license to your
|
251
|
+
software. We have chosen to use the MIT License, an Open Source License, you may choose to use the same or a different
|
252
|
+
license. If you are going to use an Open Source License we strongly encourage you to use a license approved by the Open Source
|
253
|
+
Initiative, available here: http://www.opensource.org/licenses.
|