Rally APIs: How to Move A Test Folder - ruby

I've worked with the script outlined in the following answer:
Rally APIs: How to copy Test Folder and member Test Cases
and it's handy, but what I really want to do is to move an entire Test Folder into a different project. This is next to impossible through the Rally User Interface. According to Rally Support, the only way to do this in the UI is:
Un-assign the Test Cases from their current Test Folder
Setup a Custom Grid app on your dashboard
Use the Custom Grid bulk edit to update the Project of the Test Cases
Lastly use the Custom Grid bulk edit to update the Test Folder - now that you're in the target Project, of the Test Cases
Even though the above process is clunky, it is easier now than it used to be before the advent of the bulk edit within the Custom Grids. Before you had to go through and edit each Test Case one-by-one which was very manual and slow.
However, we have several thousand Test Cases we need to move, and the Custom Grid has a fatal flaw for us. It will only show the first 200 records in a query. So we would have to manually change our grid query in a step wise manner to accomplish the move we need. This is barely better than editing Test Cases one-by-one. Is there a way to move a Test Folder with Test Cases from one Project to another, using a script? Please tell me there is.

The following script will perform this task - it will move all Test Cases from a Source Test Folder identified by FormattedID, to a Target Test Folder, also identified by FormattedID. The Source Test Folder and Target Test Folder can be in different Projects (although they must be within the same Workspace). Like the Copy script referenced in the question, the Target Test Folder must exist, i.e. the script will not create a Test Folder for you if the Target is not found.
For those needing to install and configure the Ruby REST Toolkit, links are here:
Developer Portal: Rally REST API for Ruby
Github
# Copyright 2002-2012 Rally Software Development Corp. All Rights Reserved.
require 'rally_api'
$my_base_url = "https://rally1.rallydev.com/slm"
$my_username = "user#company.com"
$my_password = "password"
$my_workspace = "My Workspace"
$my_project = "My Project"
$wsapi_version = "1.39"
# Test Folders
$source_test_folder_formatted_id = "TF8"
$target_test_folder_formatted_id = "TF11"
#==================== Make a connection to Rally ====================
config = {:base_url => $my_base_url}
config[:username] = $my_username
config[:password] = $my_password
config[:workspace] = $my_workspace
config[:project] = $my_project
config[:version] = $wsapi_version
#rally = RallyAPI::RallyRestJson.new(config)
begin
# Lookup source Test Folder
source_test_folder_query = RallyAPI::RallyQuery.new()
source_test_folder_query.type = :testfolder
source_test_folder_query.fetch = true
source_test_folder_query.query_string = "(FormattedID = \"" + $source_test_folder_formatted_id + "\")"
source_test_folder_result = #rally.find(source_test_folder_query)
# Lookup Target Test Folder
target_test_folder_query = RallyAPI::RallyQuery.new()
target_test_folder_query.type = :testfolder
target_test_folder_query.fetch = true
target_test_folder_query.query_string = "(FormattedID = \"" + $target_test_folder_formatted_id + "\")"
target_test_folder_result = #rally.find(target_test_folder_query)
if source_test_folder_result.total_result_count == 0
puts "Source Test Folder: " + $source_test_folder_formatted_id + "not found. Exiting."
exit
end
if target_test_folder_result.total_result_count == 0
puts "Target Test Folder: " + $target_test_folder_formatted_id + "not found. Target must exist before moving."
exit
end
source_test_folder = source_test_folder_result.first()
target_test_folder = target_test_folder_result.first()
# Populate full object for both Source and Target Test Folders
full_source_test_folder = source_test_folder.read
full_target_test_folder = target_test_folder.read
# Grab collection of Source Test Cases
source_test_cases = source_test_folder["TestCases"]
# Loop through Source Test Cases and Move to Target
source_test_cases.each do |source_test_case|
begin
test_case_to_update = source_test_case.read
source_test_case_formatted_id = test_case_to_update["FormattedID"]
target_project = full_target_test_folder["Project"]
target_project_full_object = target_project.read
target_project_name = target_project_full_object["Name"]
source_project = full_source_test_folder["Project"]
source_project_full_object = source_project.read
source_project_name = source_project_full_object["Name"]
puts "Source Project Name: #{source_project_name}"
puts "Target Project Name: #{target_project_name}"
# Test if the source project and target project are the same
source_target_proj_match = source_project_name.eql?(target_project_name)
# If the target Test Folder is in a different Project, we have to do some homework first:
# "un-Test Folder" the project
# Assign the Test Case to the Target Project
# Assign the Test Case to the Target Test Folder
if !source_target_proj_match then
fields = {}
fields["TestFolder"] = ""
test_case_updated = #rally.update(:testcase, test_case_to_update.ObjectID, fields) #by ObjectID
puts "Test Case #{source_test_case_formatted_id} successfully dissociated from: #{$source_test_folder_formatted_id}"
# Get full object on Target Project and assign Test Case to Target Project
fields = {}
fields["Project"] = target_project_full_object
test_case_updated = #rally.update(:testcase, test_case_to_update.ObjectID, fields) #by ObjectID
puts "Test Case #{source_test_case_formatted_id} successfully assigned to Project: #{target_project_name}"
end
# Change the Test Folder attribute on the Test Case
fields = {}
fields["TestFolder"] = target_test_folder
test_case_updated = #rally.update(:testcase, test_case_to_update.ObjectID, fields) #by ObjectID
puts "Test Case #{source_test_case_formatted_id} successfully moved to #{$target_test_folder_formatted_id}"
rescue => ex
puts "Test Case #{source_test_case_formatted_id} not updated due to error"
puts ex
end
end
end

Related

Problem Generating Html Report Using DbUp during Octopus Deployment

Using Octopus Deploy to deploy a simple API.
The first step of our deployment process is to generate an HTML report with the delta of the scripts run vs the scripts required to run. I used this tutorial to create the step.
The relevant code in my console application is:
var reportLocationSection = appConfiguration.GetSection(previewReportCmdLineFlag);
if (reportLocationSection.Value is not null)
{
// Generate a preview file so Octopus Deploy can generate an artifact for approvals
try
{
var report = reportLocationSection.Value;
var fullReportPath = Path.Combine(report, deltaReportName);
Console.WriteLine($"Generating upgrade report at {fullReportPath}");
upgrader.GenerateUpgradeHtmlReport(fullReportPath);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return operationError;
}
}
The Powershell which I am using in the script step is:
# Get the extracted path for the package
$packagePath = $OctopusParameters["Octopus.Action.Package[DatabaseUpdater].ExtractedPath"]
$connectionString = $OctopusParameters["Project.Database.ConnectionString"]
$reportPath = $OctopusParameters["Project.HtmlReport.Location"]
Write-Host "Report Path: $($reportPath)"
$exeToRun = "$($packagePath)\DatabaseUpdater.exe"
$generatedReport = "$($reportPath)\UpgradeReport.html"
Write-Host "Generated Report: $($generatedReport)"
if ((test-path $reportPath) -eq $false){
New-Item "Creating new directory..."
} else {
New-Item "Directory already exists."
}
# Run this .NET app, passing in the Connection String and a flag
# which tells the app to create a report, but not update the database
& $exeToRun --connectionString="$($connectionString)" --previewReportPath="$($reportPath)"
New-OctopusArtifact -Path "$($generatedReport)"
The error reported by Octopus is:
'Could not find file 'C:\DeltaReports\Some API\2.9.15-DbUp-Test-9\UpgradeReport.html'.'
I'm guessing that is being thrown when this powershell line is hit: New-OctopusArtifact ...
And that seems to indicate that the report was never created.
I've used a bit of logging to log out certain variables and the values look sound:
Report Path: C:\DeltaReports\Some API\2.9.15-DbUp-Test-9
Generated Report: C:\DeltaReports\Some API\2.9.15-DbUp-Test-9\UpgradeReport.html
Generating upgrade report at C:\DeltaReports\Some API\2.9.15-DbUp-Test-9\UpgradeReport.html
As you can see in the C#, the relevant code is wrapped in a try/catch block, but I'm not sure whether the error is being written out there or at a later point by Octopus (I'd need to do a pull request to add a marker in the code).
Can anyone see a way forward win resolving this? Has anyone else encountered this?
Cheers
I recently redid some of the work from that article for this video up on YouTube. I did run into some issues with the .SQL files not being included in the assembly. I think it was after I upgraded to .NET 6. But that might be a coincidence.
Anyway, because the files weren't being included in the assembly, when I ran the command line app via Octopus, it wouldn't properly generate the file for me. I ended up configuring the project to copy the .SQL files to a folder in the output directory instead of embedding them in the assembly. You can view a sample package here.
One thing that helped me is running the app in a debugger with the same parameters just to make sure it was actually generating the file. I'm sure you already thought of that, but I'd be remiss if I forgot to include it in my answer. :)
FWIW, this is my updated scripts.
First, the Octopus Script:
$packagePath = $OctopusParameters["Octopus.Action.Package[Trident.Database].ExtractedPath"]
$connectionString = $OctopusParameters["Project.Connection.String"]
$environmentName = $OctopusParameters["Octopus.Environment.Name"]
$reportPath = $OctopusParameters["Project.Database.Report.Path"]
cd $packagePath
$appToRun = ".\Octopus.Trident.Database.DbUp"
$generatedReport = "$reportPath\UpgradeReport.html"
& $appToRun --ConnectionString="$connectionString" --PreviewReportPath="$reportPath"
New-OctopusArtifact -Path "$generatedReport" -Name "$environmentName.UpgradeReport.html"
My C# code can be found here but for ease of use, you can see it all here (I'm not proud of how I parse the parameters).
static void Main(string[] args)
{
var connectionString = args.FirstOrDefault(x => x.StartsWith("--ConnectionString", StringComparison.OrdinalIgnoreCase));
connectionString = connectionString.Substring(connectionString.IndexOf("=") + 1).Replace(#"""", string.Empty);
var executingPath = Assembly.GetExecutingAssembly().Location.Replace("Octopus.Trident.Database.DbUp", "").Replace(".dll", "").Replace(".exe", "");
Console.WriteLine($"The execution location is {executingPath}");
var deploymentScriptPath = Path.Combine(executingPath, "DeploymentScripts");
Console.WriteLine($"The deployment script path is located at {deploymentScriptPath}");
var postDeploymentScriptsPath = Path.Combine(executingPath, "PostDeploymentScripts");
Console.WriteLine($"The deployment script path is located at {postDeploymentScriptsPath}");
var upgradeEngineBuilder = DeployChanges.To
.SqlDatabase(connectionString, null)
.WithScriptsFromFileSystem(deploymentScriptPath, new SqlScriptOptions { ScriptType = ScriptType.RunOnce, RunGroupOrder = 1 })
.WithScriptsFromFileSystem(postDeploymentScriptsPath, new SqlScriptOptions { ScriptType = ScriptType.RunAlways, RunGroupOrder = 2 })
.WithTransactionPerScript()
.LogToConsole();
var upgrader = upgradeEngineBuilder.Build();
Console.WriteLine("Is upgrade required: " + upgrader.IsUpgradeRequired());
if (args.Any(a => a.StartsWith("--PreviewReportPath", StringComparison.InvariantCultureIgnoreCase)))
{
// Generate a preview file so Octopus Deploy can generate an artifact for approvals
var report = args.FirstOrDefault(x => x.StartsWith("--PreviewReportPath", StringComparison.OrdinalIgnoreCase));
report = report.Substring(report.IndexOf("=") + 1).Replace(#"""", string.Empty);
if (Directory.Exists(report) == false)
{
Directory.CreateDirectory(report);
}
var fullReportPath = Path.Combine(report, "UpgradeReport.html");
if (File.Exists(fullReportPath) == true)
{
File.Delete(fullReportPath);
}
Console.WriteLine($"Generating the report at {fullReportPath}");
upgrader.GenerateUpgradeHtmlReport(fullReportPath);
}
else
{
var result = upgrader.PerformUpgrade();
// Display the result
if (result.Successful)
{
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("Success!");
}
else
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine(result.Error);
Console.WriteLine("Failed!");
}
}
}
I hope that helps!
After long and detailed investigation, we discovered the answer was quite obvious.
We assumed the existing deploy process configuration was sound. Because we never had a problem with it (until now). As it transpires, there was a problem which led to the Development deployments being deployed twice.
Hence, the errors like the one above and others which talked about file handles being held by another process.
It was actually obvious in hindsight, but we were blind to it as we thought the existing process was sound 😣

Not able to update the tester name in a Test Set from vbscript

I am been trying to search online for a solution but due to lack of knowledge in HP ALM I am not able to search proper hit tags
Set RunFactory = tsTest.RunFactory
Set obj_theRun = RunFactory.AddItem(CStr(testrunname))
obj_theRun.Status = sExecutionStatus '"Passed" '-- Status to be updated
obj_theRun.Tester = strTesterName
Getting error in this line object does not support obj_theRun.Tester
I just want to update the Tester column(Not Responsible tester) in Test set via vbscript. Please refer to the attached image at the very last column (TesterAny help is appreciated. Thank you in advance.
The documentation for ALM says the Tester Name can be specified by passing an array as the argument to AddItem.
https://admhelp.microfocus.com/alm/api_refs/ota/Content/ota/topic8805.html?Highlight=tester
An array consisting of the following elements:
Name - The name of the run (string. required).
Tester - The name of the user responsible (string. optional)
Location - The host name (string. optional). The default is the host name of the current machine
So change your code to this:
Set runFactory = tsTest.RunFactory
Dim newRunArgs(3)
newRunArgs(0) = testrunname ' `testrunname` is already a string so you don't need CStr.
newRunArgs(1) = "tester name goes here"
Set newRunArgs(2) = Nothing
Set newRun = RunFactory.AddItem( newRunArgs )
newRun.Status = sExecutionStatus '"Passed" '-- Status to be updated

Trying to write a custom rule

I'm trying to write a custom rule for gqlgen. The idea is to run it to generate Go code from a GraphQL schema.
My intended usage is:
gqlgen(
name = "gql-gen-foo",
schemas = ["schemas/schema.graphql"],
visibility = ["//visibility:public"],
)
"name" is the name of the rule, on which I want other rules to depend; "schemas" is the set of input files.
So far I have:
load(
"#io_bazel_rules_go//go:def.bzl",
_go_context = "go_context",
_go_rule = "go_rule",
)
def _gqlgen_impl(ctx):
go = _go_context(ctx)
args = ["run github.com/99designs/gqlgen --config"] + [ctx.attr.config]
ctx.actions.run(
inputs = ctx.attr.schemas,
outputs = [ctx.actions.declare_file(ctx.attr.name)],
arguments = args,
progress_message = "Generating GraphQL models and runtime from %s" % ctx.attr.config,
executable = go.go,
)
_gqlgen = _go_rule(
implementation = _gqlgen_impl,
attrs = {
"config": attr.string(
default = "gqlgen.yml",
doc = "The gqlgen filename",
),
"schemas": attr.label_list(
allow_files = [".graphql"],
doc = "The schema file location",
),
},
executable = True,
)
def gqlgen(**kwargs):
tags = kwargs.get("tags", [])
if "manual" not in tags:
tags.append("manual")
kwargs["tags"] = tags
_gqlgen(**kwargs)
My immediate issue is that Bazel complains that the schemas are not Files:
expected type 'File' for 'inputs' element but got type 'Target' instead
What's the right approach to specify the input files?
Is this the right approach to generate a rule that executes a command?
Finally, is it okay to have the output file not exist in the filesystem, but rather be a label on which other rules can depend?
Instead of:
ctx.actions.run(
inputs = ctx.attr.schemas,
Use:
ctx.actions.run(
inputs = ctx.files.schemas,
Is this the right approach to generate a rule that executes a command?
This looks right, as long as gqlgen creates the file with the correct output name (outputs = [ctx.actions.declare_file(ctx.attr.name)]).
generated_go_file = ctx.actions.declare_file(ctx.attr.name + ".go")
# ..
ctx.actions.run(
outputs = [generated_go_file],
args = ["run", "...", "--output", generated_go_file.short_path],
# ..
)
Finally, is it okay to have the output file not exist in the filesystem, but rather be a label on which other rules can depend?
The output file needs to be created, and as long as it's returned at the end of the rule implementation in a DefaultInfo provider, other rules will be able to depend on the file label (e.g. //my/package:foo-gqlgen.go).

Generate Filename Before Downloading

I'm trying to download the latest backup of data during my chef run but it's trying to download the file before the filename is generated. What's the best approach for doing this. All I want to do is generate a filename based on the time and download it.
The below code gives the error undefined method 'latest_backup' for Custom resource aws_s3_file from cookbook aws.
ruby_block "generate file name" do
block do
require 'time'
latest_backup = "NOT-SET"
utc_now = Time.now.utc
utc_midday = Time.new(Time.new.year, Time.new.month, Time.new.day, 22, 00, 1 ).utc
utc_midnight = Time.new(Time.new.year, Time.new.month, Time.new.day, 10, 00, 1 ).utc
if (utc_now < utc_midday) && (utc_now > utc_midnight )
latest_backup = "data_" + Time.now.strftime("%Y%m%d") + "-00001.tgz"
elsif (utc_now > utc_midday ) && (utc_now < utc_midnight)
latest_backup = "data_" + Time.now.strftime("%Y%m%d") + "-120001.tgz"
end
end
action :create
end
aws_s3_file "/root/backup.tgz" do
remote_path "backup-dir/#{latest_backup}"
bucket "my-backups-bucket"
region "ap-southeast-2"
end
You can't set a local variable across contexts like that. Since nothing in that code requires waiting until converge time, you can just run the code outside of a ruby_block and have it be a normal local variable.

Ruby shoes4 package over 1.5GB

I've made a small CLI script in ruby to manage a small shop for a friend, but then he wanted me to make a GUI for him, so I looked around and found shoes4.
So, I went and download it, created a small test, and run:
./bin/shoes -p swt:jar ./path/to/app.rb
and left it to create the package, then I got a warning from system that I'm running low on disc space, so I went to check the jar file, and it was over 1.5GB and still not done packaging... and the code is very small and basic:
require 'yaml'
Shoes.app do
button "Add client" do
filename = ask_open_file
para File.read(filename)
clients = YAML.load_file(filename)
id = clients[clients.length - 1][0].to_i + 1
name = ask("Enter the client's full name: ")
items = ask("Enter list of items.")
patients[id] = ["ID = "+ id.to_s,"Name = "+ pname,"list of items:\n"+ items]
File.open(filename, 'w') { |f| YAML.dump(clients, f) }
alert ("Added new patient.")
end
button "Exit" do
exit()
end
end
any idea why this small app is more than 1.5GB?? or did I try to package it wrong way??
The packager will include everything in the directory of your shoes script and below.

Resources