Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support pull requests #31

Merged
merged 1 commit into from
Jun 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 24 additions & 5 deletions AzureDevOpsRunnerAction.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public async Task UpdateStatus(string context)
{
PipelineType.Build => await _service.GetBuildStatusImage(SettingsModel),
PipelineType.Release => await _service.GetReleaseStatusImage(SettingsModel),
PipelineType.PullRequests => await _service.GetPrStatusImage(SettingsModel),
_ => throw new ArgumentOutOfRangeException($"Unsupported pipeline type {SettingsModel.PipelineType}."),
};

Expand Down Expand Up @@ -94,7 +95,8 @@ private async Task ExecuteKeyPress(StreamDeckEventPayload args, KeyPressAction k
{
PipelineType pipelineType = (PipelineType)SettingsModel.PipelineType;
SettingsModel.ErrorMessage = string.Empty;

var organization = SettingsModel.OrganizationNameFormatted();

switch (keyPressAction)
{
case KeyPressAction.DoNothing:
Expand All @@ -113,10 +115,16 @@ private async Task ExecuteKeyPress(StreamDeckEventPayload args, KeyPressAction k
{
await _service.StartBuild(SettingsModel);
}
else
else if(pipelineType == PipelineType.Release)
{
await _service.StartRelease(SettingsModel);
}
else if(pipelineType == PipelineType.PullRequests)
{
// Open new pull request page
var newPrUrl = $"{organization}/{SettingsModel.ProjectName}/_git/{SettingsModel.DefinitionId}/pullrequestcreate?sourceRef=&targetRef=";
await Manager.OpenUrlAsync(args.context, newPrUrl);
}

await Manager.ShowOkAsync(args.context);
if ((StatusUpdateFrequency)SettingsModel.UpdateStatusEverySecond == StatusUpdateFrequency.Never)
Expand All @@ -129,9 +137,20 @@ private async Task ExecuteKeyPress(StreamDeckEventPayload args, KeyPressAction k
}
break;
case KeyPressAction.Open:
var organization = SettingsModel.OrganizationNameFormatted();
var type = pipelineType == PipelineType.Build ? "_build" : "_release";
var url = $"{organization}/{SettingsModel.ProjectName}/{type}?definitionId={SettingsModel.DefinitionId}";
string url = "";
if (pipelineType == PipelineType.Build)
{
url = $"{organization}/{SettingsModel.ProjectName}/_build?definitionId={SettingsModel.DefinitionId}";
}
else if(pipelineType == PipelineType.Release)
{
url = $"{organization}/{SettingsModel.ProjectName}/_release?definitionId={SettingsModel.DefinitionId}";
}
else if(pipelineType == PipelineType.PullRequests)
{
// Show active pull requests page
url = $"{organization}/{SettingsModel.ProjectName}/_git/{SettingsModel.DefinitionId}/pullrequests?_a=active";
}

await Manager.OpenUrlAsync(args.context, url);

Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ Install the plugin from Stream Deck Store and create Azure DevOps PAT token with
| Organization URL |The Azure DevOps URL with or without https://. For example : dev.azure.com/{your organization}.|
| Project name |Azure DevOps project name.|
| PAT |The personal access token with read and execute permissions for build and release pipelines. Dont create PAT tokens with full access!|
| Pipeline type |Build or release depending on what kind of action you want to trigger|
| Definition Id |The build or release definition ID. Open the pipeline at edit mode (in Azure DevOps) and copy the ID from URL.|
| Pipeline type |Build, release or pull requests depending on what kind of action you want to trigger|
| Definition Id |The build or release definition ID / Repository name if pipeline type is Pull Requests. To get pipeline id: Open the pipeline at edit mode (in Azure DevOps) and copy the ID from URL.|
| Branch name |Leave empty to use pipelines default branch, or specify branch name that you want to build.|
| Tap action |What happens when StreamDeck button is pressed|
| Long press action |What happens if the StreamDeck button is pressed over one second|
Expand All @@ -36,6 +36,8 @@ If Stream Deck shows red question icon on top right corner of the button, check
5. To debug the app just run it and attach debugger into StreamDeck application (you might have two so try both).
6. If you experience problems try to run the Visual Studio in Administrator mode.

If you have problems to build plugin after debugging, quick fix is to restart Visual Studio.


## References

Expand Down
37 changes: 27 additions & 10 deletions Services/AzureDevOpsService.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using Microsoft.Extensions.Logging;
using Microsoft.TeamFoundation.Build.WebApi;
using Microsoft.TeamFoundation.Core.WebApi;
using Microsoft.TeamFoundation.SourceControl.WebApi;
using Microsoft.VisualStudio.Services.Common;
using Microsoft.VisualStudio.Services.ReleaseManagement.WebApi;
using Microsoft.VisualStudio.Services.ReleaseManagement.WebApi.Clients;
Expand Down Expand Up @@ -37,9 +38,9 @@ public async Task<string> GetBuildStatusImage(AzureDevOpsSettingsModel settings)

var teamProject = await projectClient.GetProject(settings.ProjectName);

// Definition ID == 0 means that we take whatever build definitions are available.
// Definition ID == "" means that we take whatever build definitions are available.
Build build;
if (settings.DefinitionId > 0)
if (settings.DefinitionId != string.Empty)
{
// First check the latest in progress build.
// It's more useful to show in progress build than latest one because
Expand All @@ -48,7 +49,7 @@ public async Task<string> GetBuildStatusImage(AzureDevOpsSettingsModel settings)
teamProject.Id,
top: 1,
queryOrder: BuildQueryOrder.QueueTimeDescending,
definitions: new[] { settings.DefinitionId },
definitions: new[] { int.Parse(settings.DefinitionId) },
statusFilter: BuildStatus.InProgress,
branchName: settings.GetFullBranchName());

Expand Down Expand Up @@ -104,9 +105,9 @@ public async Task StartBuild(AzureDevOpsSettingsModel settings)
var teamProjectTask = projectClient.GetProject(settings.ProjectName);

List<BuildDefinitionReference> buildDefinitions;
if (settings.DefinitionId > 0)
if (settings.DefinitionId != string.Empty)
{
BuildDefinition buildDefinition = await buildClient.GetDefinitionAsync(settings.ProjectName, settings.DefinitionId);
BuildDefinition buildDefinition = await buildClient.GetDefinitionAsync(settings.ProjectName, int.Parse(settings.DefinitionId));
if (buildDefinition == null)
{
throw new ArgumentException($"Build definition {settings.DefinitionId} not found");
Expand Down Expand Up @@ -150,9 +151,7 @@ public async Task<string> GetReleaseStatusImage(AzureDevOpsSettingsModel setting

var teamProject = await projectClient.GetProject(settings.ProjectName);

int? definitionId = settings.DefinitionId > 0
? settings.DefinitionId
: null;
int? definitionId = settings.DefinitionId != string.Empty ? int.Parse(settings.DefinitionId): null;

// Prioritize in-progress deployments over waiting/completed.
List<Deployment> releases = await releaseClient.GetDeploymentsAsync(
Expand Down Expand Up @@ -188,9 +187,9 @@ public async Task StartRelease(AzureDevOpsSettingsModel settings)
var releaseClient = connection.GetClient<ReleaseHttpClient2>();

List<int> definitionIds = new List<int>();
if (settings.DefinitionId > 0)
if (settings.DefinitionId != string.Empty)
{
definitionIds.Add(settings.DefinitionId);
definitionIds.Add(int.Parse(settings.DefinitionId));
}
else
{
Expand Down Expand Up @@ -264,6 +263,24 @@ or DeploymentOperationStatus.QueuedForPipeline
};
}

public async Task<string> GetPrStatusImage(AzureDevOpsSettingsModel settingsModel)
{
var connection = GetConnection(settingsModel);
var repositoryClient = connection.GetClient<GitHttpClient>();
var pullRequests = await repositoryClient.GetPullRequestsByProjectAsync(settingsModel.ProjectName, new GitPullRequestSearchCriteria { Status = PullRequestStatus.Active });

var count = pullRequests.Where(_ => string.Equals(_.Repository.Name, settingsModel.DefinitionId, StringComparison.CurrentCultureIgnoreCase)).Count();

if (count < 10)
{
return $"images/Azure-DevOps-{count}.png";
}
else
{
return "images/Azure-DevOps-9plus.png";
}
}

private VssConnection GetConnection(AzureDevOpsSettingsModel settings)
{
var credentials = new VssBasicCredential(string.Empty, settings.PAT);
Expand Down
Binary file added images/Azure-DevOps-0.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Azure-DevOps-1.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Azure-DevOps-2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Azure-DevOps-3.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Azure-DevOps-4.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Azure-DevOps-5.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Azure-DevOps-6.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Azure-DevOps-7.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Azure-DevOps-8.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Azure-DevOps-9.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Azure-DevOps-9plus.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/Azure-DevOps-number.pdn
Binary file not shown.
7 changes: 4 additions & 3 deletions models/AzureDevOpsSettingsModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ public class AzureDevOpsSettingsModel
public int PipelineType { get; set; } = 0;

/// <summary>
/// Build or release definition unique identifier. Can be picked from Azure DevOps build/release pipeline URL
/// Build, release or reponame definition unique identifier. Can be picked from Azure DevOps build/release pipeline URL
/// </summary>
public int DefinitionId { get; set; } = 0;
public string DefinitionId { get; set; } = "";

/// <summary>
/// The branch name
Expand Down Expand Up @@ -76,7 +76,8 @@ public int GetUpdateFrequencyInSeconds()
public enum PipelineType
{
Build = 0,
Release = 1
Release = 1,
PullRequests = 2
}

public enum KeyPressAction
Expand Down
15 changes: 8 additions & 7 deletions property_inspector/property_inspector.html
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,17 @@
</div>

<div class="sdpi-item" id="select_single">
<div class="sdpi-item-label">Pipeline type</div>
<div class="sdpi-item-label">Action type</div>
<select class="sdpi-item-value select" id="pipeline_type" onchange="setSettings(event.target.value, 'PipelineType')">
<option value="0" selected="selected">Build</option>
<option value="1">Release</option>
<option value="2">Pull Requests</option>
</select>
</div>

<div class="sdpi-item" id="definition_id" type="field">
<div class="sdpi-item-label">Definition Id</div>
<input id="txtDefinitionId" class="sdpi-item-value" type="number" inputmode="numeric" placeholder="The build or release definition id (0 is for all builds/releases)" onKeyUp="setSettings(event.target.value, 'DefinitionId')" required />
<div class="sdpi-item-label">Definition Id <br />(repository if PRs)</div>
<input id="txtDefinitionId" class="sdpi-item-value" type="text" placeholder="The build or release definition id (0 is for all builds/releases). Repository name for Pull requests" onKeyUp="setSettings(event.target.value, 'DefinitionId')" required />
</div>
<div class="sdpi-item" id="branch_name" type="field">
<div class="sdpi-item-label">Branch name</div>
Expand All @@ -50,8 +51,8 @@
<select class="sdpi-item-value select" id="tap_action" onchange="setSettings(event.target.value, 'TapAction')">
<option value="0">Do nothing</option>
<option value="1" selected="selected">Refresh status</option>
<option value="2">Start build/release</option>
<option value="3">Open build/release in Browser</option>
<option value="2">Start build/release/open new pr</option>
<option value="3">Open page in Browser</option>
</select>
</div>

Expand All @@ -60,8 +61,8 @@
<select class="sdpi-item-value select" id="long_press_action" onchange="setSettings(event.target.value, 'LongPressAction')">
<option value="0">Do nothing</option>
<option value="1">Refresh status</option>
<option value="2" selected="selected">Start build/release</option>
<option value="3">Open build/release in Browser</option>
<option value="2" selected="selected">Start build/release/open new PR</option>
<option value="3">Open page in Browser</option>
</select>
</div>

Expand Down