Skip to content

Commit

Permalink
Merge pull request #35 from nkdAgility/topic/31-use-the-template-work…
Browse files Browse the repository at this point in the history
…-item-type-to-create-work-items

Updated to use the templates work item type!
  • Loading branch information
MrHinsh authored Jul 3, 2024
2 parents a958e99 + 279025d commit 0c9d3b8
Show file tree
Hide file tree
Showing 6 changed files with 61 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -73,19 +73,34 @@ await AnsiConsole.Progress()
var task5 = ctx.AddTask("[bold]Stage 5[/]: Create Output Plan Relations ", false);
var task6 = ctx.AddTask("[bold]Stage 6[/]: Create Work Items", false);
string cacheTemplateWorkItemsFile = $"{config.CachePath}\\templateCache-{config.templateOrganization}-{config.templateProject}.json";
string cacheTemplateWorkItemsFile = $"{config.CachePath}\\templateCache-{config.templateOrganization}-{config.templateProject}-{config.templateParentId}.json";
if (config.ClearCache && System.IO.File.Exists(cacheTemplateWorkItemsFile))
{
System.IO.File.Delete(cacheTemplateWorkItemsFile);
}
task1.MaxValue = 1;
List<WorkItemFull> templateWorkItems;
List<WorkItemFull> templateWorkItems = null;
task1.StartTask();
task2.StartTask();
if (System.IO.File.Exists(cacheTemplateWorkItemsFile))
{
var changedDate = System.IO.File.GetLastWriteTime(cacheTemplateWorkItemsFile).AddDays(1).Date;
//Test Cache
QueryResults fakeItemsFromTemplateQuery;
fakeItemsFromTemplateQuery = await templateApi.GetWiqlQueryResults("Select [System.Id] From WorkItems Where [System.TeamProject] = '@project' AND [System.Parent] = @id AND [System.ChangedDate] > '@changeddate' order by [System.CreatedDate] desc", new Dictionary<string, string>() { { "@id", config.templateParentId.ToString() }, { "@changeddate", changedDate.ToString("yyyy-MM-dd") } });
if (fakeItemsFromTemplateQuery.workItems.Length == 0)
{
AnsiConsole.WriteLine($"Stage 1: Checked template for changes. None Detected. Loading Cache");
// Load from Cache
task1.StartTask();
task1.Increment(1);
task1.Description = task1.Description + " (cache)";
await Task.Delay(250);
Expand All @@ -95,8 +110,10 @@ await AnsiConsole.Progress()
task2.Increment(templateWorkItems.Count);
task2.Description = task2.Description + " (cache)";
AnsiConsole.WriteLine($"Stage 2: Loaded {templateWorkItems.Count()} work items from cache.");
}
}
else
if (templateWorkItems == null)
{
// Get From Server
// --------------------------------------------------------------
Expand All @@ -105,7 +122,7 @@ await AnsiConsole.Progress()
//AnsiConsole.WriteLine("Stage 1: Executing items from Query");
QueryResults fakeItemsFromTemplateQuery;
fakeItemsFromTemplateQuery = await templateApi.GetWiqlQueryResults();
fakeItemsFromTemplateQuery = await templateApi.GetWiqlQueryResults("Select [System.Id] From WorkItems Where [System.TeamProject] = '@project' AND [System.Parent] = @id order by [System.CreatedDate] desc", new Dictionary<string, string>() { { "@id", config.templateParentId.ToString() } });
AnsiConsole.WriteLine($"Stage 1: Query returned {fakeItemsFromTemplateQuery.workItems.Count()} items id's from the template.");
task1.Increment(1);
task1.StopTask();
Expand Down Expand Up @@ -221,7 +238,7 @@ await AnsiConsole.Progress()
AnsiConsole.WriteLine($"Processing {buildItems.Count()} items");
task6.Description = $"[bold]Stage 6[/]: Create Work Items (0/{buildItems.Count()})";
task6.StartTask();
await foreach ((WorkItemToBuild witb, string status, int skipped, int failed, int created) result in CreateWorkItemsToBuild(buildItems, projectItem, targetApi, config.targetWorkItemType))
await foreach ((WorkItemToBuild witb, string status, int skipped, int failed, int created) result in CreateWorkItemsToBuild(buildItems, projectItem, targetApi))
{
//AnsiConsole.WriteLine($"Stage 6: Processing {witb.guid} for output of {witb.relations.Count - 1} relations");
task6.Increment(1);
Expand Down Expand Up @@ -266,6 +283,7 @@ private async IAsyncEnumerable<WorkItemToBuild> generateWorkItemsToBuildList(Lis
newItem.guid = Guid.NewGuid();
newItem.hasComplexRelation = false;
newItem.templateId = item.id;
newItem.workItemType = templateWorkItem.fields.SystemWorkItemType;
newItem.fields = new Dictionary<string, string>()
{
{ "System.Title", item.fields.title },
Expand Down Expand Up @@ -309,7 +327,7 @@ private async IAsyncEnumerable<WorkItemToBuild> generateWorkItemsToBuildRelation
}
}

private async IAsyncEnumerable<(WorkItemToBuild, string status, int skipped, int failed, int created)> CreateWorkItemsToBuild(List<WorkItemToBuild> workItemsToBuild, WorkItemFull projectItem, AzureDevOpsApi targetApi, string workItemTypeToCreate)
private async IAsyncEnumerable<(WorkItemToBuild, string status, int skipped, int failed, int created)> CreateWorkItemsToBuild(List<WorkItemToBuild> workItemsToBuild, WorkItemFull projectItem, AzureDevOpsApi targetApi)
{
int skipped = 0;
int failed = 0;
Expand All @@ -323,7 +341,7 @@ private async IAsyncEnumerable<WorkItemToBuild> generateWorkItemsToBuildRelation
} else
{
WorkItemAdd itemToAdd = CreateWorkItemAddOperation(item, workItemsToBuild, projectItem);
WorkItemFull newWorkItem = await targetApi.CreateWorkItem(itemToAdd, workItemTypeToCreate);
WorkItemFull newWorkItem = await targetApi.CreateWorkItem(itemToAdd, item.workItemType);
if (newWorkItem != null)
{
created++;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,6 @@ internal class WorkItemCloneCommandSettings : BaseCommandSettings
[Description("The project name for the target location")]
[CommandOption("--targetProject")]
public string? targetProject { get; set; }
[Description("The Name of the work item type to use when creating")]
[CommandOption("--targetWorkItemType|--wit")]
[DefaultValue("Deliverable")]
public string? targetWorkItemType { get; set; }
[Description("The ID of the work item in the target environment that will be the parent of all created work items.")]
[CommandOption("-p|--parentId|--targetParentId")]
public int? targetParentId { get; set; }
Expand All @@ -52,6 +48,9 @@ internal class WorkItemCloneCommandSettings : BaseCommandSettings
[Description("The project name for the template location")]
[CommandOption("--templateProject")]
public string? templateProject { get; set; }
[Description("The ID of the work item in the template environment under which we will read all the sub items.")]
[CommandOption("--templateParentId")]
public int? templateParentId { get; set; }
//------------------------------------------------
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ internal abstract class WorkItemCommandBase<TSettings> : AsyncCommand<TSettings>

internal void CombineValuesFromConfigAndSettings(WorkItemCloneCommandSettings settings, WorkItemCloneCommandSettings config)
{
config.NonInteractive = settings.NonInteractive;
config.ClearCache = settings.ClearCache;
config.RunName = settings.RunName != null ? settings.RunName : DateTime.Now.ToString("yyyyyMMddHHmmss");
config.configFile = EnsureConfigFileAskIfMissing(config.configFile = settings.configFile != null ? settings.configFile : config.configFile);
config.inputJsonFile = EnsureJsonFileAskIfMissing(config.inputJsonFile = settings.inputJsonFile != null ? settings.inputJsonFile : config.inputJsonFile);
Expand All @@ -26,29 +28,17 @@ internal void CombineValuesFromConfigAndSettings(WorkItemCloneCommandSettings se
config.templateOrganization = EnsureOrganizationAskIfMissing(config.templateOrganization = settings.templateOrganization != null ? settings.templateOrganization : config.templateOrganization);
config.templateProject = EnsureProjectAskIfMissing(config.templateProject = settings.templateProject != null ? settings.templateProject : config.templateProject, config.templateOrganization);
config.templateAccessToken = EnsureAccessTokenAskIfMissing(settings.templateAccessToken != null ? settings.templateAccessToken : config.templateAccessToken, config.templateOrganization);
config.templateParentId = EnsureParentIdAskIfMissing(config.templateParentId = settings.templateParentId != null ? settings.templateParentId : config.templateParentId);

config.targetOrganization = EnsureOrganizationAskIfMissing(config.targetOrganization = settings.targetOrganization != null ? settings.targetOrganization : config.targetOrganization);
config.targetProject = EnsureProjectAskIfMissing(config.targetProject = settings.targetProject != null ? settings.targetProject : config.targetProject, config.targetOrganization);
config.targetAccessToken = EnsureAccessTokenAskIfMissing(settings.targetAccessToken != null ? settings.targetAccessToken : config.targetAccessToken, config.targetOrganization);
config.targetParentId = EnsureParentIdAskIfMissing(config.targetParentId = settings.targetParentId != null ? settings.targetParentId : config.targetParentId);
config.targetWorkItemType = EnsureWorkItemTypeAskIfMissing(config.targetWorkItemType = settings.targetWorkItemType != null ? settings.targetWorkItemType : config.targetWorkItemType);
}

private string? EnsureWorkItemTypeAskIfMissing(string? v)
{
if (v == null)
{
v = AnsiConsole.Prompt(
new TextPrompt<string>("What is the target Work Item Type?")
.Validate(v
=> !string.IsNullOrWhiteSpace(v)
? ValidationResult.Success()
: ValidationResult.Error("[yellow]Invalid Work Item Type[/]")));
}
return v;

}



internal int EnsureParentIdAskIfMissing(int? parentId)
{
if (parentId == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"Clone": {
"commandName": "Project",
"commandLineArgs": "clone --RunName Allessandro2 --cachePath ..\\..\\..\\..\\..\\.cache\\ --configFile ..\\..\\..\\..\\..\\.cache\\configuration.json --jsonFile ..\\..\\..\\..\\..\\TestData\\new.json "
"commandLineArgs": "clone --cachePath ..\\..\\..\\..\\..\\.cache\\ --configFile ..\\..\\..\\..\\..\\.cache\\configuration-test.json --jsonFile ..\\..\\..\\..\\..\\TestData\\new.json --NonInteractive"
},
"empty": {
"commandName": "Project"
Expand Down
25 changes: 25 additions & 0 deletions AzureDevOps.WorkItemClone/AzureDevOpsApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,31 @@ public async IAsyncEnumerable<WorkItemFull> GetWorkItemsFullAsync(Workitem[] ite
}
}

public async Task<QueryResults?> GetWiqlQueryResults(string wiqlQuery, Dictionary<string, string> parameters)
{
if (parameters == null)
{
parameters = new Dictionary<string, string>();
}
if (!parameters.ContainsKey("@project"))
{
parameters.Add("@project", _project);
}
if (string.IsNullOrEmpty(wiqlQuery))
{
wiqlQuery = "Select [System.Id], [System.Title], [System.State] From WorkItems Where [System.TeamProject] = '@project' order by [System.CreatedDate] desc";
}
foreach (var param in parameters)
{
wiqlQuery = wiqlQuery.Replace(param.Key, param.Value);
}
string post = JsonConvert.SerializeObject(new
{
query = wiqlQuery
});
string apiCallUrl = $"https://dev.azure.com/{_account}/_apis/wit/wiql?api-version=7.2-preview.2";
return await GetObjectResult<QueryResults>(apiCallUrl, post);
}

public async Task<QueryResults?> GetWiqlQueryResults()
{
Expand Down
1 change: 1 addition & 0 deletions AzureDevOps.WorkItemClone/DataContracts/WorkItemToBuild.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@ public class WorkItemToBuild
public bool hasComplexRelation { get; set; }
public string targetUrl { get; set; }

Check warning on line 12 in AzureDevOps.WorkItemClone/DataContracts/WorkItemToBuild.cs

View workflow job for this annotation

GitHub Actions / Build & Test

Non-nullable property 'targetUrl' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 12 in AzureDevOps.WorkItemClone/DataContracts/WorkItemToBuild.cs

View workflow job for this annotation

GitHub Actions / Build & Test

Non-nullable property 'targetUrl' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
public int targetId { get; set; }
public string workItemType { get; set; }

Check warning on line 14 in AzureDevOps.WorkItemClone/DataContracts/WorkItemToBuild.cs

View workflow job for this annotation

GitHub Actions / Build & Test

Non-nullable property 'workItemType' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.

Check warning on line 14 in AzureDevOps.WorkItemClone/DataContracts/WorkItemToBuild.cs

View workflow job for this annotation

GitHub Actions / Build & Test

Non-nullable property 'workItemType' must contain a non-null value when exiting constructor. Consider declaring the property as nullable.
}
}

0 comments on commit 0c9d3b8

Please sign in to comment.