In diesem Video zeige ich eine von vielen maßgeschneiderten Funktionen unseres Release Prozesses. Das Ziel dabei war es die Anwender noch mehr in die Produktentwicklung einzubinden.
Die langfristige Vision: Anwender bringen selbst die Anforderungen ein. Wie das genau gemeint ist und wie wir das umgesetzt haben, seht ihr im Video. Unter anderem kommen YouTrack und TeamCity zum Einsatz.
Über ein Rake-Skript, welches auf ein bereits existierendes YouTrack-Gem zurückgreift, sprechen wir die Rest-API an. Vielen Dank an Alexander Groß von GROSSWEBER für die Hilfe bei der Umsetzung unserer Vision.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'youtrack' | |
require 'ostruct' | |
module Build | |
class YouTrack | |
class View | |
attr_reader :issues, :by_department, :by_relevance | |
def initialize(issues) | |
@issues = issues | |
@by_department = department(issues) | |
@by_relevance = relevance(issues) | |
end | |
private | |
def department(issues) | |
create_view(issues, proc { |issue| issue.departments }) | |
end | |
def relevance(issues) | |
create_view(issues, proc { |issue| issue.relevant_to }) | |
end | |
def create_view(issues, group_by) | |
view = issues.inject({}) do |memo, issue| | |
group_by.call(issue).each do |rel| | |
previous_value = memo.fetch(rel, []) | |
new_value = previous_value << issue | |
memo[rel] = new_value.sort_by(&:issue_id) | |
end | |
memo | |
end | |
Hash[view.sort] | |
end | |
end | |
class << self | |
NOT_FOUND = proc { { 'value' => nil } } | |
def fixed_issues(from, to) | |
resource = client.issues | |
maybe_patch_unreleased_methods(resource) | |
query = query(from, to) | |
puts "Getting issue list from YouTrack using query: #{query}" | |
issues = resource.list(filter: query, max: 1000) | |
issues = force_list(issues) | |
transformed = transform(issues) | |
View.new(transformed) | |
end | |
private | |
def client | |
@client ||= Youtrack::Client.new do |c| | |
c.url = 'your-url' | |
c.login = 'your-user' | |
c.password = 'your-pw' | |
c.connect! | |
end | |
end | |
def maybe_patch_unreleased_methods(issues) | |
return if issues.respond_to?(:list) | |
warn 'Patching #list method' | |
# Get a list of issues for a search query. | |
# | |
# attributes | |
# filter string A query to search for issues. | |
# with string List of fields that should be included in the result. | |
# max integer Maximum number of issues to get. If not provided, only 10 issues will be returned by default. | |
# after integer A number of issues to skip before getting a list of issues. | |
# | |
def issues.list(attributes = {}) | |
attributes[:max] ||= 10 | |
get("issue?#{URI.encode_www_form(attributes)}") | |
end | |
end | |
def query(from, to) | |
"project: HECO_comWORK Fixed in build: #{from} .. #{to} Department: -KEINE #{excluded_subsystems}" | |
end | |
def excluded_subsystems | |
spec = ENV['RELEASE_NOTES_EXCLUDE_SUBSYSTEMS'] | |
return unless spec | |
spec = spec.split(',') | |
return if spec.empty? | |
"Subsystem: #{spec.map { |s| "-{#{s}}" }.join(' ')}" | |
end | |
def force_list(issues) | |
issues = issues.to_hash['issueCompacts']['issue'] rescue [] | |
([] << issues).flatten(1) | |
end | |
def transform(issues) | |
issues.map do |x| | |
field = x['field'] | |
project = field.find { |f| f['name'] == 'projectShortName' }['value'] | |
number = field.find { |f| f['name'] == 'numberInProject' }['value'] | |
issue_id = "#{project}-#{number}" | |
subsystem = subsystem(field) | |
summary = field.find { |f| f['name'] == 'summary' }['value'] | |
info = field.find(NOT_FOUND) { |f| f['name'] == 'Release Infotext' }['value'] | |
departments = arrayify(field, 'Department') | |
relevant_to = arrayify(field, 'Relevant to') | |
OpenStruct.new(issue_id: issue_id, | |
subsystem: subsystem, | |
summary: summary, | |
info: info, | |
departments: departments, | |
relevant_to: relevant_to) | |
end | |
end | |
def subsystem(field) | |
value = field.find { |f| f['name'] == 'Subsystem' }['value'] | |
return nil if value == 'No Subsystem' | |
value | |
end | |
def arrayify(field, key) | |
value = field.find(NOT_FOUND) { |f| f['name'] == key }['value'] | |
return [] if value.nil? | |
([value]).flatten.sort | |
end | |
end | |
end | |
end |
Wer weitere Einblicke in unseren Prozess bekommen möchte, der kann mir dazu einen Kommentar hinterlassen.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'youtrack' | |
require 'ostruct' | |
module Build | |
class YouTrack | |
class View | |
attr_reader :issues, :by_department, :by_relevance | |
def initialize(issues) | |
@issues = issues | |
@by_department = department(issues) | |
@by_relevance = relevance(issues) | |
end | |
private | |
def department(issues) | |
create_view(issues, proc { |issue| issue.departments }) | |
end | |
def relevance(issues) | |
create_view(issues, proc { |issue| issue.relevant_to }) | |
end | |
def create_view(issues, group_by) | |
view = issues.inject({}) do |memo, issue| | |
group_by.call(issue).each do |rel| | |
previous_value = memo.fetch(rel, []) | |
new_value = previous_value << issue | |
memo[rel] = new_value.sort_by(&:issue_id) | |
end | |
memo | |
end | |
Hash[view.sort] | |
end | |
end | |
class << self | |
NOT_FOUND = proc { { 'value' => nil } } | |
def fixed_issues(from, to) | |
resource = client.issues | |
maybe_patch_unreleased_methods(resource) | |
query = query(from, to) | |
puts "Getting issue list from YouTrack using query: #{query}" | |
issues = resource.list(filter: query, max: 1000) | |
issues = force_list(issues) | |
transformed = transform(issues) | |
View.new(transformed) | |
end | |
private | |
def client | |
@client ||= Youtrack::Client.new do |c| | |
c.url = 'your-url' | |
c.login = 'your-user' | |
c.password = 'your-pw' | |
c.connect! | |
end | |
end | |
def maybe_patch_unreleased_methods(issues) | |
return if issues.respond_to?(:list) | |
warn 'Patching #list method' | |
# Get a list of issues for a search query. | |
# | |
# attributes | |
# filter string A query to search for issues. | |
# with string List of fields that should be included in the result. | |
# max integer Maximum number of issues to get. If not provided, only 10 issues will be returned by default. | |
# after integer A number of issues to skip before getting a list of issues. | |
# | |
def issues.list(attributes = {}) | |
attributes[:max] ||= 10 | |
get("issue?#{URI.encode_www_form(attributes)}") | |
end | |
end | |
def query(from, to) | |
"project: HECO_comWORK Fixed in build: #{from} .. #{to} Department: -KEINE #{excluded_subsystems}" | |
end | |
def excluded_subsystems | |
spec = ENV['RELEASE_NOTES_EXCLUDE_SUBSYSTEMS'] | |
return unless spec | |
spec = spec.split(',') | |
return if spec.empty? | |
"Subsystem: #{spec.map { |s| "-{#{s}}" }.join(' ')}" | |
end | |
def force_list(issues) | |
issues = issues.to_hash['issueCompacts']['issue'] rescue [] | |
([] << issues).flatten(1) | |
end | |
def transform(issues) | |
issues.map do |x| | |
field = x['field'] | |
project = field.find { |f| f['name'] == 'projectShortName' }['value'] | |
number = field.find { |f| f['name'] == 'numberInProject' }['value'] | |
issue_id = "#{project}–#{number}" | |
subsystem = subsystem(field) | |
summary = field.find { |f| f['name'] == 'summary' }['value'] | |
info = field.find(NOT_FOUND) { |f| f['name'] == 'Release Infotext' }['value'] | |
departments = arrayify(field, 'Department') | |
relevant_to = arrayify(field, 'Relevant to') | |
OpenStruct.new(issue_id: issue_id, | |
subsystem: subsystem, | |
summary: summary, | |
info: info, | |
departments: departments, | |
relevant_to: relevant_to) | |
end | |
end | |
def subsystem(field) | |
value = field.find { |f| f['name'] == 'Subsystem' }['value'] | |
return nil if value == 'No Subsystem' | |
value | |
end | |
def arrayify(field, key) | |
value = field.find(NOT_FOUND) { |f| f['name'] == key }['value'] | |
return [] if value.nil? | |
([value]).flatten.sort | |
end | |
end | |
end | |
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'youtrack' | |
require 'ostruct' | |
module Build | |
class YouTrack | |
class View | |
attr_reader :issues, :by_department, :by_relevance | |
def initialize(issues) | |
@issues = issues | |
@by_department = department(issues) | |
@by_relevance = relevance(issues) | |
end | |
private | |
def department(issues) | |
create_view(issues, proc { |issue| issue.departments }) | |
end | |
def relevance(issues) | |
create_view(issues, proc { |issue| issue.relevant_to }) | |
end | |
def create_view(issues, group_by) | |
view = issues.inject({}) do |memo, issue| | |
group_by.call(issue).each do |rel| | |
previous_value = memo.fetch(rel, []) | |
new_value = previous_value << issue | |
memo[rel] = new_value.sort_by(&:issue_id) | |
end | |
memo | |
end | |
Hash[view.sort] | |
end | |
end | |
class << self | |
NOT_FOUND = proc { { 'value' => nil } } | |
def fixed_issues(from, to) | |
resource = client.issues | |
maybe_patch_unreleased_methods(resource) | |
query = query(from, to) | |
puts "Getting issue list from YouTrack using query: #{query}" | |
issues = resource.list(filter: query, max: 1000) | |
issues = force_list(issues) | |
transformed = transform(issues) | |
View.new(transformed) | |
end | |
private | |
def client | |
@client ||= Youtrack::Client.new do |c| | |
c.url = 'your-url' | |
c.login = 'your-user' | |
c.password = 'your-pw' | |
c.connect! | |
end | |
end | |
def maybe_patch_unreleased_methods(issues) | |
return if issues.respond_to?(:list) | |
warn 'Patching #list method' | |
# Get a list of issues for a search query. | |
# | |
# attributes | |
# filter string A query to search for issues. | |
# with string List of fields that should be included in the result. | |
# max integer Maximum number of issues to get. If not provided, only 10 issues will be returned by default. | |
# after integer A number of issues to skip before getting a list of issues. | |
# | |
def issues.list(attributes = {}) | |
attributes[:max] ||= 10 | |
get("issue?#{URI.encode_www_form(attributes)}") | |
end | |
end | |
def query(from, to) | |
"project: HECO_comWORK Fixed in build: #{from} .. #{to} Department: -KEINE #{excluded_subsystems}" | |
end | |
def excluded_subsystems | |
spec = ENV['RELEASE_NOTES_EXCLUDE_SUBSYSTEMS'] | |
return unless spec | |
spec = spec.split(',') | |
return if spec.empty? | |
"Subsystem: #{spec.map { |s| "-{#{s}}" }.join(' ')}" | |
end | |
def force_list(issues) | |
issues = issues.to_hash['issueCompacts']['issue'] rescue [] | |
([] << issues).flatten(1) | |
end | |
def transform(issues) | |
issues.map do |x| | |
field = x['field'] | |
project = field.find { |f| f['name'] == 'projectShortName' }['value'] | |
number = field.find { |f| f['name'] == 'numberInProject' }['value'] | |
issue_id = "#{project}–#{number}" | |
subsystem = subsystem(field) | |
summary = field.find { |f| f['name'] == 'summary' }['value'] | |
info = field.find(NOT_FOUND) { |f| f['name'] == 'Release Infotext' }['value'] | |
departments = arrayify(field, 'Department') | |
relevant_to = arrayify(field, 'Relevant to') | |
OpenStruct.new(issue_id: issue_id, | |
subsystem: subsystem, | |
summary: summary, | |
info: info, | |
departments: departments, | |
relevant_to: relevant_to) | |
end | |
end | |
def subsystem(field) | |
value = field.find { |f| f['name'] == 'Subsystem' }['value'] | |
return nil if value == 'No Subsystem' | |
value | |
end | |
def arrayify(field, key) | |
value = field.find(NOT_FOUND) { |f| f['name'] == key }['value'] | |
return [] if value.nil? | |
([value]).flatten.sort | |
end | |
end | |
end | |
end |