diff --git a/connectors.go b/connectors.go index 04de85b..6264b2e 100644 --- a/connectors.go +++ b/connectors.go @@ -29,6 +29,7 @@ import ( "github.com/wakflo/extensions/internal/connectors/flexport" "github.com/wakflo/extensions/internal/connectors/freshdesk" "github.com/wakflo/extensions/internal/connectors/freshworkscrm" + "github.com/wakflo/extensions/internal/connectors/github" googledocs "github.com/wakflo/extensions/internal/connectors/google_docs" googlesheets "github.com/wakflo/extensions/internal/connectors/google_sheets" "github.com/wakflo/extensions/internal/connectors/googlecalendar" @@ -110,6 +111,7 @@ func RegisterConnectors() []*sdk.ConnectorPlugin { monday.NewConnector, // Monday.com zoom.NewConnector, // Zoom flexport.NewConnector, // Flexport + github.NewConnector, // Github } // 🛑Do-Not-Edit diff --git a/internal/connectors/github/lib.go b/internal/connectors/github/lib.go new file mode 100644 index 0000000..1e3f21b --- /dev/null +++ b/internal/connectors/github/lib.go @@ -0,0 +1,38 @@ +// Copyright 2022-present Wakflo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package github + +import ( + sdk "github.com/wakflo/go-sdk/connector" +) + +func NewConnector() (*sdk.ConnectorPlugin, error) { + return sdk.CreateConnector(&sdk.CreateConnectorArgs{ + Name: "Github", + Description: "Developer platform that allows developers to create, store, manage and share their code", + Logo: "mdi:github", + Version: "0.0.1", + Group: sdk.ConnectorGroupApps, + Authors: []string{"Wakflo "}, + Triggers: []sdk.ITrigger{}, + Operations: []sdk.IOperation{ + NewCreateIssueOperation(), + NewGetIssueInfoOperation(), + NewCreateIssueCommentOperation(), + NewLockIssueOperation(), + NewUnlockIssueOperation(), + }, + }) +} diff --git a/internal/connectors/github/model.go b/internal/connectors/github/model.go new file mode 100644 index 0000000..2115c38 --- /dev/null +++ b/internal/connectors/github/model.go @@ -0,0 +1,26 @@ +package github + +type Response struct { + Data Data `json:"data"` +} + +// Data contains the node information +type Data struct { + Node Node `json:"node"` +} + +// Node contains assignable users information +type Node struct { + AssignableUsers AssignableUsers `json:"assignableUsers"` +} + +// AssignableUsers contains a slice of user nodes +type AssignableUsers struct { + Nodes []User `json:"nodes"` +} + +// User represents each user with login and ID +type User struct { + Login string `json:"login"` + ID string `json:"id"` +} diff --git a/internal/connectors/github/operation_create_a_comment_on_an_issue.go b/internal/connectors/github/operation_create_a_comment_on_an_issue.go new file mode 100644 index 0000000..5106c57 --- /dev/null +++ b/internal/connectors/github/operation_create_a_comment_on_an_issue.go @@ -0,0 +1,97 @@ +// Copyright 2022-present Wakflo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package github + +import ( + "errors" + "fmt" + + "github.com/wakflo/go-sdk/autoform" + sdk "github.com/wakflo/go-sdk/connector" + sdkcore "github.com/wakflo/go-sdk/core" +) + +type createIssueCommentOperationProps struct { + Repository string `json:"repository"` + Body string `json:"body"` + IssueNumber string `json:"issue_number"` +} + +type CreateIssueCommentOperation struct { + options *sdk.OperationInfo +} + +func NewCreateIssueCommentOperation() *CreateIssueCommentOperation { + return &CreateIssueCommentOperation{ + options: &sdk.OperationInfo{ + Name: "Create comment on a issue", + Description: "Adds a comment to the specified issue (also works with pull requests)", + RequireAuth: true, + Auth: sharedAuth, + Input: map[string]*sdkcore.AutoFormSchema{ + "repository": getRepositoryInput(), + "issue_number": getIssuesInput(), + "body": autoform.NewLongTextField(). + SetDisplayName("Comment"). + SetDescription("Issue comment"). + Build(), + }, + ErrorSettings: sdkcore.StepErrorSettings{ + ContinueOnError: false, + RetryOnError: false, + }, + }, + } +} + +func (c *CreateIssueCommentOperation) Run(ctx *sdk.RunContext) (sdk.JSON, error) { + input, err := sdk.InputToTypeSafely[createIssueCommentOperationProps](ctx) + if err != nil { + return nil, err + } + + mutation := fmt.Sprintf(` + mutation { + addComment(input: { subjectId: "%s", body: "%s" }) { + commentEdge { + node { + id + body + createdAt + } + } + } + }`, input.IssueNumber, input.Body) + + response, err := githubGQL(ctx.Auth.AccessToken, mutation) + if err != nil { + return nil, errors.New("error making graphQL request") + } + + issue, ok := response["data"].(map[string]interface{})["addComment"].(map[string]interface{})["commentEdge"].(map[string]interface{}) + if !ok { + return nil, errors.New("failed to extract issue from response") + } + + return issue, nil +} + +func (c *GetIssueInfoOperation) Test(ctx *sdk.RunContext) (sdk.JSON, error) { + return c.Run(ctx) +} + +func (c *GetIssueInfoOperation) GetInfo() *sdk.OperationInfo { + return c.options +} diff --git a/internal/connectors/github/operation_create_issue.go b/internal/connectors/github/operation_create_issue.go new file mode 100644 index 0000000..b332f53 --- /dev/null +++ b/internal/connectors/github/operation_create_issue.go @@ -0,0 +1,117 @@ +// Copyright 2022-present Wakflo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package github + +import ( + "fmt" + "log" + "strings" + + "github.com/wakflo/go-sdk/autoform" + sdk "github.com/wakflo/go-sdk/connector" + sdkcore "github.com/wakflo/go-sdk/core" +) + +type createIssueOperationProps struct { + Repository string `json:"repository"` + Title string `json:"title"` + Body string `json:"body"` + Labels string `json:"labels"` +} + +type CreateIssueOperation struct { + options *sdk.OperationInfo +} + +func NewCreateIssueOperation() *CreateIssueOperation { + return &CreateIssueOperation{ + options: &sdk.OperationInfo{ + Name: "Create Issue", + Description: "Create an issue", + RequireAuth: true, + Auth: sharedAuth, + Input: map[string]*sdkcore.AutoFormSchema{ + "repository": getRepositoryInput(), + "title": autoform.NewShortTextField(). + SetDisplayName("Issue Name"). + SetDescription("The issue name"). + SetRequired(true). + Build(), + "body": autoform.NewLongTextField(). + SetDisplayName("Description"). + SetDescription("Issue description"). + Build(), + "labels": getLabelInput(), + }, + ErrorSettings: sdkcore.StepErrorSettings{ + ContinueOnError: false, + RetryOnError: false, + }, + }, + } +} + +func (c *CreateIssueOperation) Run(ctx *sdk.RunContext) (sdk.JSON, error) { + input, err := sdk.InputToTypeSafely[createIssueOperationProps](ctx) + if err != nil { + return nil, err + } + + // Create a map to store fields conditionally + fields := make(map[string]string) + fields["title"] = fmt.Sprintf(`"%s"`, input.Title) + fields["repositoryId"] = fmt.Sprintf(`"%s"`, input.Repository) + + if input.Body != "" { + fields["body"] = fmt.Sprintf(`"%s"`, input.Body) + } + + if input.Labels != "" { + fields["labelIds"] = fmt.Sprintf(`"%s"`, input.Labels) + } + + fieldStrings := make([]string, 0, len(fields)) + for key, value := range fields { + fieldStrings = append(fieldStrings, fmt.Sprintf("%s: %s", key, value)) + } + + mutation := fmt.Sprintf(` + mutation CreateNewIssue { + createIssue(input: { + %s + }) { + issue { + id + title + url + } + } + }`, strings.Join(fieldStrings, "\n")) + + response, err := githubGQL(ctx.Auth.AccessToken, mutation) + if err != nil { + log.Fatalf("Error making GraphQL request: %v", err) + } + + return response, nil +} + +func (c *CreateIssueOperation) Test(ctx *sdk.RunContext) (sdk.JSON, error) { + return c.Run(ctx) +} + +func (c *CreateIssueOperation) GetInfo() *sdk.OperationInfo { + return c.options +} diff --git a/internal/connectors/github/operation_get_issue_information.go b/internal/connectors/github/operation_get_issue_information.go new file mode 100644 index 0000000..4f5723a --- /dev/null +++ b/internal/connectors/github/operation_get_issue_information.go @@ -0,0 +1,107 @@ +// Copyright 2022-present Wakflo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package github + +import ( + "errors" + "fmt" + + "github.com/wakflo/go-sdk/autoform" + sdk "github.com/wakflo/go-sdk/connector" + sdkcore "github.com/wakflo/go-sdk/core" +) + +type getIssueInfoOperationProps struct { + Repository string `json:"repository"` + IssueNumber int `json:"issue_number"` +} + +type GetIssueInfoOperation struct { + options *sdk.OperationInfo +} + +func NewGetIssueInfoOperation() *GetIssueInfoOperation { + return &GetIssueInfoOperation{ + options: &sdk.OperationInfo{ + Name: "Get Issue information", + Description: "Get information about a specific issue", + RequireAuth: true, + Auth: sharedAuth, + Input: map[string]*sdkcore.AutoFormSchema{ + "repository": getRepositoryInput(), + "issue_number": autoform.NewNumberField(). + SetDisplayName("Issue Number"). + SetDescription("The issue number"). + SetRequired(true). + Build(), + }, + ErrorSettings: sdkcore.StepErrorSettings{ + ContinueOnError: false, + RetryOnError: false, + }, + }, + } +} + +func (c *GetIssueInfoOperation) Run(ctx *sdk.RunContext) (sdk.JSON, error) { + input, err := sdk.InputToTypeSafely[getIssueInfoOperationProps](ctx) + if err != nil { + return nil, err + } + + query := fmt.Sprintf(` + query { + node(id: "%s") { + ... on Repository { + issue(number:%d ){ + title + body + createdAt + updatedAt + number + author { + login + } + assignees(first:10){ + nodes { + name + login + } + } + } + } + } +}`, input.Repository, input.IssueNumber) + + response, err := githubGQL(ctx.Auth.AccessToken, query) + if err != nil { + return nil, errors.New("error making graphQL request") + } + + issue, ok := response["data"].(map[string]interface{})["node"].(map[string]interface{})["issue"].(map[string]interface{}) + if !ok { + return nil, errors.New("failed to extract issue from response") + } + + return issue, nil +} + +func (c *CreateIssueCommentOperation) Test(ctx *sdk.RunContext) (sdk.JSON, error) { + return c.Run(ctx) +} + +func (c *CreateIssueCommentOperation) GetInfo() *sdk.OperationInfo { + return c.options +} diff --git a/internal/connectors/github/operation_lock_issue.go b/internal/connectors/github/operation_lock_issue.go new file mode 100644 index 0000000..d472bb6 --- /dev/null +++ b/internal/connectors/github/operation_lock_issue.go @@ -0,0 +1,96 @@ +// Copyright 2022-present Wakflo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package github + +import ( + "errors" + "fmt" + + "github.com/wakflo/go-sdk/autoform" + sdk "github.com/wakflo/go-sdk/connector" + sdkcore "github.com/wakflo/go-sdk/core" +) + +type lockIssueOperationProps struct { + Repository string `json:"repository"` + LockReason string `json:"lock_reason"` + IssueNumber string `json:"issue_number"` +} + +type LockIssueOperation struct { + options *sdk.OperationInfo +} + +func NewLockIssueOperation() *LockIssueOperation { + return &LockIssueOperation{ + options: &sdk.OperationInfo{ + Name: "Lock Issue", + Description: "Locks the specified issue", + RequireAuth: true, + Auth: sharedAuth, + Input: map[string]*sdkcore.AutoFormSchema{ + "repository": getRepositoryInput(), + "issue_number": getIssuesInput(), + "lock_reason": autoform.NewSelectField(). + SetDisplayName("Lock Reason"). + SetDescription("The reason for locking the issue"). + SetOptions(lockIssueReason). + SetRequired(true). + Build(), + }, + ErrorSettings: sdkcore.StepErrorSettings{ + ContinueOnError: false, + RetryOnError: false, + }, + }, + } +} + +func (c *LockIssueOperation) Run(ctx *sdk.RunContext) (sdk.JSON, error) { + input, err := sdk.InputToTypeSafely[lockIssueOperationProps](ctx) + if err != nil { + return nil, err + } + + mutation := fmt.Sprintf(` + mutation { + lockLockable(input: { lockableId: "%s", lockReason: %s }) { + lockedRecord { + locked + activeLockReason + } + } + }`, input.IssueNumber, input.LockReason) + + response, err := githubGQL(ctx.Auth.AccessToken, mutation) + if err != nil { + return nil, errors.New("error making graphQL request") + } + + issue, ok := response["data"].(map[string]interface{})["lockLockable"].(map[string]interface{})["lockedRecord"].(map[string]interface{}) + if !ok { + return nil, errors.New("failed to extract issue from response") + } + + return issue, nil +} + +func (c *LockIssueOperation) Test(ctx *sdk.RunContext) (sdk.JSON, error) { + return c.Run(ctx) +} + +func (c *LockIssueOperation) GetInfo() *sdk.OperationInfo { + return c.options +} diff --git a/internal/connectors/github/operation_unlock_issue.go b/internal/connectors/github/operation_unlock_issue.go new file mode 100644 index 0000000..e8be1ff --- /dev/null +++ b/internal/connectors/github/operation_unlock_issue.go @@ -0,0 +1,87 @@ +// Copyright 2022-present Wakflo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package github + +import ( + "errors" + "fmt" + + sdk "github.com/wakflo/go-sdk/connector" + sdkcore "github.com/wakflo/go-sdk/core" +) + +type unlockIssueOperationProps struct { + Repository string `json:"repository"` + IssueNumber string `json:"issue_number"` +} + +type UnlockIssueOperation struct { + options *sdk.OperationInfo +} + +func NewUnlockIssueOperation() *UnlockIssueOperation { + return &UnlockIssueOperation{ + options: &sdk.OperationInfo{ + Name: "Unlock Issue", + Description: "Unlocks an issue", + RequireAuth: true, + Auth: sharedAuth, + Input: map[string]*sdkcore.AutoFormSchema{ + "repository": getRepositoryInput(), + "issue_number": getIssuesInput(), + }, + ErrorSettings: sdkcore.StepErrorSettings{ + ContinueOnError: false, + RetryOnError: false, + }, + }, + } +} + +func (c *UnlockIssueOperation) Run(ctx *sdk.RunContext) (sdk.JSON, error) { + input, err := sdk.InputToTypeSafely[unlockIssueOperationProps](ctx) + if err != nil { + return nil, err + } + + mutation := fmt.Sprintf(` + mutation { + unlockLockable(input: { lockableId: "%s" }) { + unlockedRecord{ + locked + } + } + }`, input.IssueNumber) + + response, err := githubGQL(ctx.Auth.AccessToken, mutation) + if err != nil { + return nil, errors.New("error making graphQL request") + } + + issue, ok := response["data"].(map[string]interface{})["unlockLockable"].(map[string]interface{})["unlockedRecord"].(map[string]interface{}) + if !ok { + return nil, errors.New("failed to extract issue from response") + } + + return issue, nil +} + +func (c *UnlockIssueOperation) Test(ctx *sdk.RunContext) (sdk.JSON, error) { + return c.Run(ctx) +} + +func (c *UnlockIssueOperation) GetInfo() *sdk.OperationInfo { + return c.options +} diff --git a/internal/connectors/github/shared.go b/internal/connectors/github/shared.go new file mode 100644 index 0000000..1d29d88 --- /dev/null +++ b/internal/connectors/github/shared.go @@ -0,0 +1,403 @@ +// Copyright 2022-present Wakflo +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package github + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + + "github.com/gookit/goutil/arrutil" + + "github.com/wakflo/go-sdk/autoform" + sdk "github.com/wakflo/go-sdk/connector" + sdkcore "github.com/wakflo/go-sdk/core" +) + +var ( + // #nosec + tokenURL = "https://github.com/login/oauth/access_token" + sharedAuth = autoform.NewOAuthField("https://github.com/login/oauth/authorize", &tokenURL, []string{"admin:repo_hook admin:org repo"}).SetRequired(true).Build() +) + +const baseURL = "https://api.github.com/graphql" + +func githubGQL(accessToken, query string) (map[string]interface{}, error) { + payload := map[string]string{ + "query": query, + } + jsonPayload, err := json.Marshal(payload) + if err != nil { + return nil, fmt.Errorf("failed to marshal payload: %w", err) + } + + req, err := http.NewRequest(http.MethodPost, baseURL, bytes.NewBuffer(jsonPayload)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("X-Github-Api-Version", "2022-11-28") + req.Header.Add("Accept", "application/vnd.github+json") + req.Header.Add("Authorization", "Bearer "+accessToken) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to make request: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + var result map[string]interface{} + if errs := json.Unmarshal(body, &result); errs != nil { + return nil, fmt.Errorf("failed to unmarshal response: %w", errs) + } + + return result, nil +} + +func getRepositoryInput() *sdkcore.AutoFormSchema { + getRepository := func(ctx *sdkcore.DynamicFieldContext) (interface{}, error) { + query := `{ + viewer { + repositories(first: 100) { + nodes { + name + id + } + } + } + }` + + queryBody := map[string]string{ + "query": query, + } + jsonQuery, err := json.Marshal(queryBody) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, baseURL, bytes.NewBuffer(jsonQuery)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("X-Github-Api-Version", "2022-11-28") + req.Header.Add("Accept", "application/vnd.github+json") + req.Header.Add("Authorization", "Bearer "+ctx.Auth.AccessToken) + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to make request: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + var response struct { + Data struct { + Viewer struct { + Repositories struct { + Nodes []struct { + Name string `json:"name"` + ID string `json:"id"` + } `json:"nodes"` + } `json:"repositories"` + } `json:"viewer"` + } `json:"data"` + } + + err = json.Unmarshal(body, &response) + if err != nil { + return nil, err + } + + repositories := response.Data.Viewer.Repositories.Nodes + + return &repositories, nil + } + + return autoform.NewDynamicField(sdkcore.String). + SetDisplayName("Repository"). + SetDescription("Select a repository"). + SetDynamicOptions(&getRepository). + SetRequired(true).Build() +} + +func getLabelInput() *sdkcore.AutoFormSchema { + getLabels := func(ctx *sdkcore.DynamicFieldContext) (interface{}, error) { + input := sdk.DynamicInputToType[struct { + Repository string `json:"repository"` + }](ctx) + query := fmt.Sprintf(` { + node(id: "%s") { + ... on Repository { + labels(first: 100) { + nodes { + name + id + } + } + } + } + }`, input.Repository) + + queryBody := map[string]interface{}{ + "query": query, + } + jsonQuery, err := json.Marshal(queryBody) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, baseURL, bytes.NewBuffer(jsonQuery)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Authorization", "Bearer "+ctx.Auth.AccessToken) + req.Header.Add("Accept", "application/vnd.github+json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to make request: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + var response struct { + Data struct { + Node struct { + Labels struct { + Nodes []struct { + Name string `json:"name"` + ID string `json:"id"` + } `json:"nodes"` + } `json:"labels"` + } `json:"node"` + } `json:"data"` + } + + err = json.Unmarshal(body, &response) + if err != nil { + return nil, err + } + + labels := response.Data.Node.Labels.Nodes + + return &labels, nil + } + + return autoform.NewDynamicField(sdkcore.String). + SetDisplayName("Labels"). + SetDescription("Select labels for the issue"). + SetDynamicOptions(&getLabels). + SetRequired(false). + Build() +} + +func getIssuesInput() *sdkcore.AutoFormSchema { + getIssues := func(ctx *sdkcore.DynamicFieldContext) (interface{}, error) { + input := sdk.DynamicInputToType[struct { + Repository string `json:"repository"` + }](ctx) + query := fmt.Sprintf(` { + node(id: "%s") { + ... on Repository { + issues(first:100){ + nodes{ + id + title + } + } + } + } + }`, input.Repository) + + queryBody := map[string]interface{}{ + "query": query, + } + jsonQuery, err := json.Marshal(queryBody) + if err != nil { + return nil, err + } + + req, err := http.NewRequest(http.MethodPost, baseURL, bytes.NewBuffer(jsonQuery)) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Authorization", "Bearer "+ctx.Auth.AccessToken) + req.Header.Add("Accept", "application/vnd.github+json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to make request: %w", err) + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("failed to read response body: %w", err) + } + + var response struct { + Data struct { + Node struct { + Issues struct { + Nodes []struct { + Title string `json:"title"` + ID string `json:"id"` + } `json:"nodes"` + } `json:"issues"` + } `json:"node"` + } `json:"data"` + } + + err = json.Unmarshal(body, &response) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal response: %w", err) + } + + issues := arrutil.Map(response.Data.Node.Issues.Nodes, func(issue struct { + Title string `json:"title"` + ID string `json:"id"` + }, + ) (map[string]any, bool) { + return map[string]any{ + "id": issue.ID, + "name": issue.Title, + }, true + }) + + return &issues, nil + } + + return autoform.NewDynamicField(sdkcore.String). + SetDisplayName("Issues"). + SetDescription("Select issue"). + SetDynamicOptions(&getIssues). + SetRequired(true). + Build() +} + +// func getAssigneeInput() *sdkcore.AutoFormSchema { +// getAssignees := func(ctx *sdkcore.DynamicFieldContext) (interface{}, error) { +// input := sdk.DynamicInputToType[struct { +// Repository string `json:"repository"` +// }](ctx) +// query := fmt.Sprintf(` { +// node(id: "%s") { +// ... on Repository { +// assignableUsers(first: 100) { +// nodes { +// login +// id +// } +// } +// } +// } +// }`, input.Repository) +// +// queryBody := map[string]interface{}{ +// "query": query, +// } +// jsonQuery, err := json.Marshal(queryBody) +// if err != nil { +// return nil, err +// } +// +// req, err := http.NewRequest(http.MethodPost, baseURL, bytes.NewBuffer(jsonQuery)) +// if err != nil { +// return nil, fmt.Errorf("failed to create request: %w", err) +// } +// +// req.Header.Set("Authorization", "Bearer "+ctx.Auth.AccessToken) +// req.Header.Add("Accept", "application/vnd.github+json") +// +// client := &http.Client{} +// resp, err := client.Do(req) +// if err != nil { +// return nil, fmt.Errorf("failed to make request: %w", err) +// } +// defer resp.Body.Close() +// +// body, err := io.ReadAll(resp.Body) +// if err != nil { +// return nil, fmt.Errorf("failed to read response body: %w", err) +// } +// +// var response struct { +// Data struct { +// Node struct { +// AssignableUsers struct { +// Nodes []struct { +// Login string `json:"login"` +// ID string `json:"id"` +// } `json:"nodes"` +// } `json:"assignableUsers"` +// } `json:"node"` +// } `json:"data"` +// } +// +// err = json.Unmarshal(body, &response) +// if err != nil { +// return nil, err +// } +// +// assignees := arrutil.Map(response.Data.Node.AssignableUsers.Nodes, func(user struct { +// Login string `json:"login"` +// ID string `json:"id"` +// }, +// ) (map[string]any, bool) { +// return map[string]any{ +// "id": user.ID, +// "name": user.Login, +// }, true +// }) +// +// return &assignees, nil +// } +// +// return autoform.NewDynamicField(sdkcore.String). +// SetDisplayName("Assignees"). +// SetDescription("Select assignees for the issue"). +// SetDynamicOptions(&getAssignees). +// SetRequired(false). +// Build() +// } + +var lockIssueReason = []*sdkcore.AutoFormSchema{ + {Const: " OFF_TOPIC", Title: "Off topic"}, + {Const: "TOO_HEATED", Title: "Too heated"}, + {Const: "RESOLVED", Title: "resolved"}, + {Const: "SPAM", Title: "Spam"}, +} diff --git a/internal/connectors/linear/operation_create_issue.go b/internal/connectors/linear/operation_create_issue.go index f36671d..39e5f3d 100644 --- a/internal/connectors/linear/operation_create_issue.go +++ b/internal/connectors/linear/operation_create_issue.go @@ -97,7 +97,7 @@ func (c *CreateIssueOperation) Run(ctx *sdk.RunContext) (sdk.JSON, error) { if input.AssigneeID != "" { fields["assigneeId"] = fmt.Sprintf(`"%s"`, input.AssigneeID) } - if input.AssigneeID != "" { + if input.LabelID != "" { fields["labelIds"] = fmt.Sprintf(`"%s"`, input.LabelID) }