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

[MI-2339][server]: Added support to show pipeline build link preview. #104

Merged
merged 9 commits into from
Dec 14, 2022
10 changes: 10 additions & 0 deletions mocks/mock_client.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 6 additions & 5 deletions server/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ const (
// Regex to verify pull request link
PullRequestLinkRegex = `http(s)?:\/\/dev.azure.com\/[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]*\/[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]*\/_git\/[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]*\/pullrequest\/[1-9]+`

// Regex to verify pipeline build details link
BuildDetailsLinkRegex = `http(s)?:\/\/dev.azure.com\/[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]*\/[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]*\/_build\/results\?buildId=[a-zA-Z0-9!@#$%^&*()_+\-=\[\]{};':"\\|,.<>\/?]+`

// Azure API Versions
CreateTaskAPIVersion = "7.1-preview.3"
TasksIDAPIVersion = "5.1"
Expand Down Expand Up @@ -117,18 +120,16 @@ const (
WSEventSubscriptionDeleted = "subscription_deleted"

// Colors
ReposIconColor = "#d74f27"
BoardsIconColor = "#53bba1"
IconColorRepos = "#d74f27"
IconColorBoards = "#53bba1"
IconColorPipelines = "#4275E4"

SubscriptionEventTypeDummy = "dummy"
FileNameGitBranchIcon = "git-branch-icon.svg"
FileNameProjectIcon = "project-icon.svg"
FileNameReposIcon = "repos-icon.svg"
FileNameBoardsIcon = "boards-icon.svg"
FileNamePipelinesIcon = "pipelines-icon.svg"
IconColorBoards = "#53bba1"
IconColorRepos = "#d74f27"
IconColorPipelines = "#4b68ad"

SlackAttachmentAuthorNameRepos = "Azure Repos"
SlackAttachmentAuthorNameBoards = "Azure Boards"
Expand Down
1 change: 1 addition & 0 deletions server/constants/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const (
CreatedTask = "[%s #%d](%s) (%s) was successfully created by %s."
TaskTitle = "[%s #%d: %s](%s)"
PullRequestTitle = "[#%d: %s](%s)"
BuildDetailsTitle = "[#%s](%s): %s"
AlreadyLinkedProject = "This project is already linked."
NoProjectLinked = "No project is linked, please link a project."

Expand Down
1 change: 1 addition & 0 deletions server/constants/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ const (
CreateTask = "/%s/%s/_apis/wit/workitems/$%s?api-version=7.1-preview.3"
GetTask = "%s/%s/_apis/wit/workitems/%s?api-version=7.1-preview.3"
GetPullRequest = "%s/%s/_apis/git/pullrequests/%s?api-version=6.0"
GetBuildDetails = "%s/%s/_apis/build/builds/%s?api-version=6.0"
GetGitRepositories = "%s/%s/_apis/git/repositories?api-version=6.0"
GetGitRepositoryBranches = "%s/%s/_apis/git/repositories/%s/refs?filter=heads"
GetSubscriptionFilterPossibleValues = "%s/_apis/hooks/inputValuesQuery?api-version=6.0"
Expand Down
14 changes: 14 additions & 0 deletions server/plugin/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type Client interface {
CheckIfUserIsProjectAdmin(organizationName, projectID, pluginURL, mattermostUserID string) (int, error)
GetGitRepositories(organization, projectName, mattermostUserID string) (*serializers.GitRepositoriesResponse, int, error)
GetGitRepositoryBranches(organization, projectName, repository, mattermostUserID string) (*serializers.GitBranchesResponse, int, error)
GetBuildDetails(organization, projectName, buildID, mattermostUserID string) (*serializers.BuildDetails, int, error)
GetSubscriptionFilterPossibleValues(request *serializers.GetSubscriptionFilterPossibleValuesRequestPayload, mattermostUserID string) (*serializers.SubscriptionFilterPossibleValuesResponseFromClient, int, error)
}

Expand Down Expand Up @@ -118,6 +119,19 @@ func (c *client) GetPullRequest(organization, pullRequestID, projectName, matter
return pullRequest, statusCode, nil
}

// Function to get the pipeline build details.
func (c *client) GetBuildDetails(organization, projectName, buildID, mattermostUserID string) (*serializers.BuildDetails, int, error) {
buildDetailsURL := fmt.Sprintf(constants.GetBuildDetails, organization, projectName, buildID)

var buildDetails *serializers.BuildDetails
_, statusCode, err := c.CallJSON(c.plugin.getConfiguration().AzureDevopsAPIBaseURL, buildDetailsURL, http.MethodGet, mattermostUserID, nil, &buildDetails, nil)
if err != nil {
return nil, statusCode, errors.Wrap(err, "failed to get the pipeline build details")
}

return buildDetails, statusCode, nil
}

// Function to link a project and an organization.
func (c *client) Link(body *serializers.LinkRequestPayload, mattermostUserID string) (*serializers.Project, int, error) {
projectURL := fmt.Sprintf(constants.GetProject, body.Organization, body.Project)
Expand Down
6 changes: 6 additions & 0 deletions server/plugin/plugin.go
Original file line number Diff line number Diff line change
Expand Up @@ -124,5 +124,11 @@ func (p *Plugin) MessageWillBePosted(c *plugin.Context, post *model.Post) (*mode
return newPost, msg
}

// Check if a message contains a pipeline build link.
if buildDetailsData, link, isValid := IsLinkPresent(post.Message, constants.BuildDetailsLinkRegex); isValid {
newPost, msg := p.PostBuildDetailsPreview(buildDetailsData, link, post.UserId, post.ChannelId)
return newPost, msg
}

return nil, ""
}
56 changes: 54 additions & 2 deletions server/plugin/taskLinkPreview.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
func (p *Plugin) PostTaskPreview(linkData []string, userID, channelID string) (*model.Post, string) {
task, _, err := p.Client.GetTask(linkData[3], linkData[7], linkData[4], userID)
if err != nil {
p.API.LogDebug("Error in getting task details from Azure", "Error", err.Error())
return nil, ""
}

Expand All @@ -35,7 +36,7 @@ func (p *Plugin) PostTaskPreview(linkData []string, userID, channelID string) (*
AuthorName: "Azure Boards",
AuthorIcon: fmt.Sprintf("%s/plugins/%s/static/%s", p.GetSiteURL(), constants.PluginID, constants.FileNameBoardsIcon),
Title: fmt.Sprintf(constants.TaskTitle, task.Fields.Type, task.ID, task.Fields.Title, task.Link.HTML.Href),
Color: constants.BoardsIconColor,
Color: constants.IconColorBoards,
Fields: []*model.SlackAttachmentField{
{
Title: "State",
Expand All @@ -62,6 +63,7 @@ func (p *Plugin) PostTaskPreview(linkData []string, userID, channelID string) (*
func (p *Plugin) PostPullRequestPreview(linkData []string, link, userID, channelID string) (*model.Post, string) {
pullRequest, _, err := p.Client.GetPullRequest(linkData[3], linkData[8], linkData[6], userID)
if err != nil {
p.API.LogDebug("Error in getting pull request details from Azure", "Error", err.Error())
return nil, ""
}

Expand All @@ -83,7 +85,7 @@ func (p *Plugin) PostPullRequestPreview(linkData []string, link, userID, channel
AuthorName: "Azure Repos",
AuthorIcon: fmt.Sprintf("%s/plugins/%s/static/%s", p.GetSiteURL(), constants.PluginID, constants.FileNameReposIcon),
Title: fmt.Sprintf(constants.PullRequestTitle, pullRequest.PullRequestID, pullRequest.Title, link),
Color: constants.ReposIconColor,
Color: constants.IconColorRepos,
Fields: []*model.SlackAttachmentField{
{
Title: "Target Branch",
Expand All @@ -107,3 +109,53 @@ func (p *Plugin) PostPullRequestPreview(linkData []string, link, userID, channel

return post, ""
}

func (p *Plugin) PostBuildDetailsPreview(linkData []string, link, userID, channelID string) (*model.Post, string) {
organization := linkData[3]
project := linkData[4]
buildID := strings.Split(linkData[6], "&")[0][16:]
manojmalik20 marked this conversation as resolved.
Show resolved Hide resolved
buildDetails, _, err := p.Client.GetBuildDetails(organization, project, buildID, userID)
if err != nil {
p.API.LogDebug("Error in getting build details from Azure", "Error", err.Error())
return nil, ""
}

post := &model.Post{
UserId: userID,
ChannelId: channelID,
}

attachment := &model.SlackAttachment{
AuthorName: "Azure Pipelines",
AuthorIcon: fmt.Sprintf("%s/plugins/%s/static/%s", p.GetSiteURL(), constants.PluginID, constants.FileNamePipelinesIcon), // TODO: update icon file
Title: fmt.Sprintf(constants.BuildDetailsTitle, buildDetails.BuildNumber, buildDetails.Link.Web.Href, buildDetails.Definition.Name),
Color: constants.IconColorPipelines,
Fields: []*model.SlackAttachmentField{
{
Title: "Repository",
Value: buildDetails.Repository.Name,
Short: true,
},
{
Title: "Source Branch",
Value: buildDetails.SourceBranch,
Short: true,
},
{
Title: "Requested By",
Value: buildDetails.RequestedBy.DisplayName,
Short: true,
},
{
Title: "Status",
Value: buildDetails.Status,
Short: true,
},
},
Footer: project,
FooterIcon: fmt.Sprintf("%s/plugins/%s/static/%s", p.GetSiteURL(), constants.PluginID, constants.FileNameProjectIcon),
avas27JTG marked this conversation as resolved.
Show resolved Hide resolved
}

model.ParseSlackAttachment(post, []*model.SlackAttachment{attachment})
return post, ""
}
15 changes: 15 additions & 0 deletions server/serializers/subscriptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,21 @@ func GetSubscriptionFilterPossibleValuesRequestPayloadFromJSON(data io.Reader) (
return body, nil
}

type BuildDetails struct {
BuildNumber string `json:"buildNumber"`
SourceBranch string `json:"sourceBranch"`
Repository Repository `json:"repository"`
Status string `json:"status"`
RequestedBy RequestedBy `json:"requestedBy"`
Project Project `json:"project"`
Link Link `json:"_links"`
Definition Definition `json:"definition"`
}

type RequestedBy struct {
DisplayName string `json:"displayName"`
}

func CreateSubscriptionRequestPayloadFromJSON(data io.Reader) (*CreateSubscriptionRequestPayload, error) {
var body *CreateSubscriptionRequestPayload
if err := json.NewDecoder(data).Decode(&body); err != nil {
Expand Down
1 change: 1 addition & 0 deletions server/serializers/task.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type TaskFieldValue struct {

type Link struct {
HTML Href `json:"html"`
Web Href `json:"web"`
}

type Href struct {
Expand Down