Skip to content

Commit

Permalink
Epic changes (all code merged here are already reviewed) (#24)
Browse files Browse the repository at this point in the history
* [MI-1846]: Added nvmrc file

* Modular folder structure

* Modular folder structure

* [MI-1846]: Base setup

* [MI-1846]: Refactored server base setup

* [MI-1854]: Implement OAuth to access Azure DevOps services

* working

* [MI-1846]: Review fixes

* [MI-1931] Add API to get projects and get tasks

* [MI-1931] Self review fixes

* [MI-1931] Review fixes 1

* [MI-1931] Self review fix

* [MI-1945] Add function to make protected routes

* [MI-1948] Add API to create task

* [MI-1931] Review fixes 2

* [MI-1931] Add json error handling

* [MI-1948] Add error in json format

* [MI-1948_1] Resolve build error

* [MI-1964]: Make changes to make POST API request from the webapp.

* [MI-1846]: Review fixes

* [MI-1854]: Review fixes

* [MI-1854]: Removed unused config

* [MI-1854]: Added logic to verify state

* [MI-1953] Add create task modal (#6)

* [MI-1953] Add create task modal

* [MI-1953] Run fmt

* [MI-1953] Correct validations

* [MI-1953] Send new work item link in DM

* [MI-1953] Correct error handling and reusable components

* [MI-1953] Correct operator in modal footer

* [MI-1953] Disable button on API request

* [MI-1953] Correct loading state

* [MI-1953] Review fixes

Co-authored-by: Abhishek Verma <abhishek.verma@brightscout.com>

* [MI-1950] Add feature to preview work items (#7)

* [MI-1953] Add create task modal

* [MI-1953] Run fmt

* [MI-1950] Add feature to preview work items

* [MI-1950] Self review fix

* [MI-1953] Correct validations

* [MI-1950] Self review fix

* [MI-1953] Send new work item link in DM

* [MI-1950] Add a comment

* [MI-1950] Review fixes 1

* [MI-1950] Review fix

* [MI-1953] Correct error handling and reusable components

* [MI-1950] Update variable names

* [MI-1953] Correct operator in modal footer

* [MI-1953] Disable button on API request

* [MI-1953] Correct loading state

* [MI-1950] Using slack attachment to show preview

* [MI-1950] Correct comments

* [MI-1950]: Sync

Co-authored-by: Abhishek Verma <abhishek.verma@brightscout.com>

* [MI-1979]: Created project list RHS view and related components (#8)

* [MI-1953] Add create task modal

* [MI-1953] Run fmt

* [MI-1950] Add feature to preview work items

* [MI-1950] Self review fix

* [MI-1953] Correct validations

* [MI-1950] Self review fix

* [MI-1953] Send new work item link in DM

* [MI-1950] Add a comment

* [MI-1979]: Created project list RHS view and realted components

* [MI-1979]: Fixed lints

* [MI-1979]: Review fixes

* [MI-1979]: Review fixes

* [MI-1979]: Removed unused code

Co-authored-by: ayusht2810 <ayush.thakur@joshtechnologygroup.com>
Co-authored-by: Abhishek Verma <abhishek.verma@brightscout.com>

* [MI-1983]: Create subscription details with related components and ad… (#9)

* [MI-1953] Add create task modal

* [MI-1953] Run fmt

* [MI-1950] Add feature to preview work items

* [MI-1950] Self review fix

* [MI-1953] Correct validations

* [MI-1950] Self review fix

* [MI-1953] Send new work item link in DM

* [MI-1950] Add a comment

* [MI-1979]: Created project list RHS view and realted components

* [MI-1979]: Fixed lints

* [MI-1983]: Create subscription details with related components and add redux logic to navigate.

* [MI-1983]: Changed hover state

* [MI-1983]: Review fixes

Co-authored-by: ayusht2810 <ayush.thakur@joshtechnologygroup.com>
Co-authored-by: Abhishek Verma <abhishek.verma@brightscout.com>

* [MI-1974] Add modal to link a project (#11)

* [MI-1953] Add create task modal

* [MI-1953] Run fmt

* [MI-1953] Correct validations

* [MI-1953] Send new work item link in DM

* [MI-1974] Add feature to link a project

* [MI-1953] Correct error handling and reusable components

* [MI-1953] Correct operator in modal footer

* [MI-1974] Refactor KV store

* [MI-1953] Disable button on API request

* [MI-1974] Run fmt and correct loading

* [MI-1974] Add error field in modal

* [MI-1974] Review fixes

* [MI-1974] Add function to check if project is already linked

Co-authored-by: Abhishek Verma <abhishek.verma@brightscout.com>

* [MI-1989] Add middleware to check oAuth (#12)

* [MI-1953] Add create task modal

* [MI-1953] Run fmt

* [MI-1953] Correct validations

* [MI-1953] Send new work item link in DM

* [MI-1953] Correct error handling and reusable components

* [MI-1953] Correct operator in modal footer

* [MI-1953] Disable button on API request

* [MI-1989] Add middleware to check oAuth

* [MI-1989] Correct function call

Co-authored-by: Abhishek Verma <abhishek.verma@brightscout.com>

* [MI-1993] Correct failing CI (#13)

* [MI-1953] Add create task modal

* [MI-1953] Run fmt

* [MI-1953] Correct validations

* [MI-1953] Send new work item link in DM

* [MI-1953] Correct error handling and reusable components

* [MI-1953] Correct operator in modal footer

* [MI-1953] Disable button on API request

* [MI-1953] Correct loading state

* [MI-1993] Correct ci

* [MI-1993] Correct if else chain

* [MI-1993] Correct make test

* [MI-1993] Correct error handling

* [MI-1993]: Review fixes

* [MI-1993]: Review fixes

Co-authored-by: Abhishek Verma <abhishek.verma@brightscout.com>

Co-authored-by: ayusht2810 <ayush.thakur@joshtechnologygroup.com>
Co-authored-by: Abhishek Verma <abhishek.verma@brightscout.com>
Co-authored-by: ayusht2810 <100013900+ayusht2810@users.noreply.github.com>
  • Loading branch information
4 people authored Aug 10, 2022
1 parent d22b29b commit adf462f
Show file tree
Hide file tree
Showing 100 changed files with 3,516 additions and 536 deletions.
16 changes: 15 additions & 1 deletion build/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,22 @@ go 1.12

require (
github.com/go-git/go-git/v5 v5.4.2
github.com/mattermost/mattermost-server/v6 v6.2.1
github.com/google/uuid v1.3.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.0.9 // indirect
github.com/kr/pretty v0.3.0 // indirect
github.com/mattermost/go-i18n v1.11.1-0.20211013152124-5c415071e404 // indirect
github.com/mattermost/mattermost-server/v5 v5.39.3
github.com/minio/minio-go/v7 v7.0.14 // indirect
github.com/pelletier/go-toml v1.9.4 // indirect
github.com/pkg/errors v0.9.1
github.com/rogpeppe/go-internal v1.8.0 // indirect
github.com/stretchr/testify v1.7.0
go.uber.org/atomic v1.9.0 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
golang.org/x/net v0.0.0-20211006190231-62292e806868 // indirect
golang.org/x/sys v0.0.0-20211006225509-1a26e0398eed // indirect
golang.org/x/text v0.3.7 // indirect
gopkg.in/ini.v1 v1.63.2 // indirect
sigs.k8s.io/yaml v1.3.0
)
543 changes: 162 additions & 381 deletions build/go.sum

Large diffs are not rendered by default.

35 changes: 17 additions & 18 deletions build/pluginctl/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ func pluginctl() error {
func getClient() (*model.Client4, error) {
socketPath := os.Getenv("MM_LOCALSOCKETPATH")
if socketPath == "" {
socketPath = model.LocalModeSocketPath
socketPath = model.LOCAL_MODE_SOCKET_PATH
}

client, connected := getUnixClient(socketPath)
Expand Down Expand Up @@ -91,11 +91,10 @@ func getClient() (*model.Client4, error) {
if adminUsername != "" && adminPassword != "" {
client := model.NewAPIv4Client(siteURL)
log.Printf("Authenticating as %s against %s.", adminUsername, siteURL)
_, _, err := client.Login(adminUsername, adminPassword)
if err != nil {
return nil, fmt.Errorf("failed to login as %s: %w", adminUsername, err)
_, resp := client.Login(adminUsername, adminPassword)
if resp.Error != nil {
return nil, fmt.Errorf("failed to login as %s: %w", adminUsername, resp.Error)
}

return client, nil
}

Expand All @@ -121,15 +120,15 @@ func deploy(client *model.Client4, pluginID, bundlePath string) error {
defer pluginBundle.Close()

log.Print("Uploading plugin via API.")
_, _, err = client.UploadPluginForced(pluginBundle)
if err != nil {
return fmt.Errorf("failed to upload plugin bundle: %s", err.Error())
_, resp := client.UploadPluginForced(pluginBundle)
if resp.Error != nil {
return fmt.Errorf("failed to upload plugin bundle: %s", resp.Error.Error())
}

log.Print("Enabling plugin.")
_, err = client.EnablePlugin(pluginID)
if err != nil {
return fmt.Errorf("failed to enable plugin: %s", err.Error())
_, resp = client.EnablePlugin(pluginID)
if resp.Error != nil {
return fmt.Errorf("failed to enable plugin: %s", resp.Error.Error())
}

return nil
Expand All @@ -138,9 +137,9 @@ func deploy(client *model.Client4, pluginID, bundlePath string) error {
// disablePlugin attempts to disable the plugin via the Client4 API.
func disablePlugin(client *model.Client4, pluginID string) error {
log.Print("Disabling plugin.")
_, err := client.DisablePlugin(pluginID)
if err != nil {
return fmt.Errorf("failed to disable plugin: %w", err)
_, resp := client.DisablePlugin(pluginID)
if resp.Error != nil {
return fmt.Errorf("failed to disable plugin: %w", resp.Error)
}

return nil
Expand All @@ -149,9 +148,9 @@ func disablePlugin(client *model.Client4, pluginID string) error {
// enablePlugin attempts to enable the plugin via the Client4 API.
func enablePlugin(client *model.Client4, pluginID string) error {
log.Print("Enabling plugin.")
_, err := client.EnablePlugin(pluginID)
if err != nil {
return fmt.Errorf("failed to enable plugin: %w", err)
_, resp := client.EnablePlugin(pluginID)
if resp.Error != nil {
return fmt.Errorf("failed to enable plugin: %w", resp.Error)
}

return nil
Expand All @@ -170,4 +169,4 @@ func resetPlugin(client *model.Client4, pluginID string) error {
}

return nil
}
}
3 changes: 3 additions & 0 deletions package-lock.json

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

20 changes: 19 additions & 1 deletion server/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,30 @@ const (
InvalidCommand = "Invalid command parameters. Please use `/azuredevops help` for more information."

// Azure API Routes
CreateTask = "/%s/%s/_apis/wit/workitems/$%s?api-version=" + CreateTaskAPIVersion
CreateTask = "/%s/%s/_apis/wit/workitems/$%s?api-version=7.1-preview.3"
GetTask = "%s/_apis/wit/workitems/%s?api-version=7.1-preview.3"
GetProject = "/%s/_apis/projects/%s?api-version=7.1-preview.4"

// Get task link preview constants
HTTPS = "https:"
HTTP = "http:"
AzureDevopsBaseURL = "dev.azure.com"
Workitems = "_workitems"
Edit = "edit"

// Azure API Versions
CreateTaskAPIVersion = "7.1-preview.3"
TasksIDAPIVersion = "5.1"
TasksAPIVersion = "6.0"

// Authorization constants
Bearer = "Bearer"
Authorization = "Authorization"

GetTasksID = "/%s/_apis/wit/wiql"
GetTasks = "/%s/_apis/wit/workitems"

PageQueryParam = "$top"
APIVersionQueryParam = "api-version"
IDsQueryParam = "ids"
)
6 changes: 6 additions & 0 deletions server/constants/messages.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@ const (
UnableToStoreOauthState = "Unable to store oAuth state for the userID %s"
AuthAttemptExpired = "Authentication attempt expired, please try again"
InvalidAuthState = "Invalid oauth state, please try again"
CreatedTask = "Link for newly created task: %s"
TaskTitle = "[%s #%d: %s](%s)"
TaskPreviewMessage = "**State:** %s\n**Assigned To:** %s\n**Description:** %s"
AlreadyLinkedProject = "This project is already linked."
GetProjectListError = "Error getting Project List"
ErrorFetchProjectList = "Error in fetching project list"
)
3 changes: 2 additions & 1 deletion server/constants/oauth_config.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const (
BaseOauthURL = "https://app.vssps.visualstudio.com"

// Paths
PathAuth = "/oauth2/authorize"
PathAuth = "/oauth2/authorize"
// #nosec G101 -- This is a false positive
PathToken = "/oauth2/token"
)
5 changes: 5 additions & 0 deletions server/constants/store.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,9 @@ const (
AtomicRetryLimit = 5
AtomicRetryWait = 30 * time.Millisecond
TTLSecondsForOAuthState int64 = 60

// KV store prefix keys
OAuthPrefix = "oAuth_%s"
ProjectKey = "%s_%s"
ProjectPrefix = "project_list"
)
9 changes: 9 additions & 0 deletions server/constants/taskQuery.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package constants

// TODO: WIP.
// const (
// // TaskQuery
// TaskQuery = "Select [System.Id] From WorkItems Where [System.TeamProject] = '%s'"
// TaskQueryStatusFilter = " and [System.State] = '%s'"
// TaskQueryAssignedToFilter = " and [System.AssignedTo] = @me"
// )
76 changes: 58 additions & 18 deletions server/plugin/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package plugin

import (
"encoding/json"
"fmt"
"net/http"
"path/filepath"
"runtime/debug"
Expand Down Expand Up @@ -31,9 +32,10 @@ func (p *Plugin) InitRoutes() {
// OAuth
s.HandleFunc(constants.PathOAuthConnect, p.OAuthConnect).Methods(http.MethodGet)
s.HandleFunc(constants.PathOAuthCallback, p.OAuthComplete).Methods(http.MethodGet)
// TODO: for testing purpose, remove later
s.HandleFunc("/test", p.testAPI).Methods(http.MethodGet)

// Plugin APIs
s.HandleFunc("/tasks", p.handleAuthRequired(p.handleCreateTask)).Methods(http.MethodPost)
s.HandleFunc("/link", p.handleAuthRequired(p.handleLink)).Methods(http.MethodPost)
}

// API to create task of a project in an organization.
Expand All @@ -57,7 +59,6 @@ func (p *Plugin) handleCreateTask(w http.ResponseWriter, r *http.Request) {
p.handleError(w, r, &serializers.Error{Code: statusCode, Message: err.Error()})
return
}

response, err := json.Marshal(task)
if err != nil {
p.handleError(w, r, &serializers.Error{Code: http.StatusInternalServerError, Message: err.Error()})
Expand All @@ -68,12 +69,65 @@ func (p *Plugin) handleCreateTask(w http.ResponseWriter, r *http.Request) {
if _, err := w.Write(response); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}

message := fmt.Sprintf(constants.CreatedTask, task.Link.HTML.Href)

// Send message to DM.
p.DM(mattermostUserID, message)
}

// API to link a project and an organization to a user.
func (p *Plugin) handleLink(w http.ResponseWriter, r *http.Request) {
mattermostUserID := r.Header.Get(constants.HeaderMattermostUserIDAPI)
var body *serializers.LinkRequestPayload
decoder := json.NewDecoder(r.Body)
if err := decoder.Decode(&body); err != nil {
p.API.LogError("Error in decoding body", "Error", err.Error())
p.handleError(w, r, &serializers.Error{Code: http.StatusInternalServerError, Message: err.Error()})
return
}

if err := body.IsLinkPayloadValid(); err != "" {
error := serializers.Error{Code: http.StatusBadRequest, Message: err}
p.handleError(w, r, &error)
return
}

projectList, err := p.Store.GetAllProjects(mattermostUserID)
if err != nil {
p.API.LogError(constants.ErrorFetchProjectList, "Error", err.Error())
p.handleError(w, r, &serializers.Error{Code: http.StatusInternalServerError, Message: err.Error()})
return
}

if p.IsProjectLinked(projectList, serializers.ProjectDetails{OrganizationName: body.Organization, ProjectName: body.Project}) {
p.DM(mattermostUserID, constants.AlreadyLinkedProject)
return
}

response, err := p.Client.Link(body, mattermostUserID)
if err != nil {
p.handleError(w, r, &serializers.Error{Code: http.StatusInternalServerError, Message: err.Error()})
return
}

project := serializers.ProjectDetails{
MattermostUserID: mattermostUserID,
ProjectID: response.ID,
ProjectName: response.Name,
OrganizationName: body.Organization,
}

p.Store.StoreProject(&project)

w.WriteHeader(http.StatusOK)
w.Header().Add("Content-Type", "application/json")
}

// handleAuthRequired verifies if the provided request is performed by an authorized source.
func (p *Plugin) handleAuthRequired(handleFunc http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
mattermostUserID := r.Header.Get(constants.HeaderMattermostUserID)
mattermostUserID := r.Header.Get(constants.HeaderMattermostUserIDAPI)
if mattermostUserID == "" {
error := serializers.Error{Code: http.StatusUnauthorized, Message: constants.NotAuthorized}
p.handleError(w, r, &error)
Expand Down Expand Up @@ -112,20 +166,6 @@ func (p *Plugin) WithRecovery(next http.Handler) http.Handler {
})
}

// TODO: for testing purpose, remove later
func (p *Plugin) testAPI(w http.ResponseWriter, r *http.Request) {
// TODO: remove later
response, err := p.Client.TestApi()
if err != nil {
w.WriteHeader(http.StatusInternalServerError)
w.Write([]byte(err.Error()))
return
}
res, _ := json.Marshal(response)
w.Header().Add("Content-Type", "application/json")
w.Write(res)
}

// Handles the static files under the assets directory.
func (p *Plugin) HandleStaticFiles() {
bundlePath, err := p.API.GetBundlePath()
Expand Down
Loading

0 comments on commit adf462f

Please sign in to comment.