diff --git a/Makefile b/Makefile index 81da9515..928baef1 100644 --- a/Makefile +++ b/Makefile @@ -35,6 +35,9 @@ PROGS = helloworld \ pageflow \ signalcounter \ sideeffect \ + sleepworkflow \ + dataconverter \ + TEST_ARG ?= -race -v -timeout 5m BUILD := ./build @@ -68,6 +71,8 @@ TEST_DIRS=./cmd/samples/cron \ ./cmd/samples/recipes/searchattributes \ ./cmd/samples/recipes/sideeffect \ ./cmd/samples/recipes/signalcounter \ + ./cmd/samples/recipes/sleepworkflow \ + ./cmd/samples/recipes/dataconverter \ ./cmd/samples/recovery \ ./cmd/samples/pso \ @@ -176,6 +181,9 @@ sideeffect: versioning: go build -o bin/versioning cmd/samples/recipes/versioning/*.go +dataconverter: + go build -o bin/dataconverter cmd/samples/recipes/dataconverter/*.go + bins: helloworld \ versioning \ delaystart \ @@ -207,6 +215,8 @@ bins: helloworld \ pageflow \ signalcounter \ sideeffect \ + sleepworkflow \ + dataconverter \ test: bins @rm -f test diff --git a/cmd/samples/jiraToGithub/README.md b/cmd/samples/jiraToGithub/README.md new file mode 100644 index 00000000..4169201a --- /dev/null +++ b/cmd/samples/jiraToGithub/README.md @@ -0,0 +1,29 @@ +# Expense +This sample workflow process an expense request. The key part of this sample is to show how to complete an activity asynchronously. + +# Sample Description +* Create a new expense report. +* Wait for the expense report to be approved. This could take an arbitrary amount of time. So the activity's Execute method has to return before it is actually approved. This is done by returning a special error so the framework knows the activity is not completed yet. + * When the expense is approved (or rejected), somewhere in the world needs to be notified, and it will need to call WorkflowClient.CompleteActivity() to tell cadence service that that activity is now completed. In this sample case, the dummy server do this job. In real world, you will need to register some listener to the expense system or you will need to have your own pulling agent to check for the expense status periodic. +* After the wait activity is completed, it did the payment for the expense. (dummy step in this sample case) + +This sample rely on an a dummy expense server to work. + +# Steps To Run Sample +* You need a cadence service running. See https://github.com/uber/cadence/blob/master/README.md for more details. +* Start the dummy server +``` +./bin/expense_dummy +``` +If dummy is not found, run make to build it. +* Start workflow and activity workers +``` +./bin/expense -m worker +``` +* Start expanse workflow execution +``` +./bin/expense -m trigger +``` +* When you see the console print out the expense is created, go to [localhost:8099/list](http://localhost:8099/list) to approve the expense. +* You should see the workflow complete after you approve the expense. You can also reject the expense. +* If you see the workflow failed, try to change to a different port number in dummy.go and workflow.go. Then rebuild everything. diff --git a/cmd/samples/jiraToGithub/main.go b/cmd/samples/jiraToGithub/main.go new file mode 100644 index 00000000..23a3b8d7 --- /dev/null +++ b/cmd/samples/jiraToGithub/main.go @@ -0,0 +1,57 @@ +package main + +import ( + "flag" + "time" + + "github.com/pborman/uuid" + + "go.uber.org/cadence/client" + "go.uber.org/cadence/worker" + + "github.com/uber-common/cadence-samples/cmd/samples/common" +) + +// This needs to be done as part of a bootstrap step when the process starts. +// The workers are supposed to be long running. +func startWorkers(h *common.SampleHelper) { + // Configure worker options. + workerOptions := worker.Options{ + MetricsScope: h.WorkerMetricScope, + Logger: h.Logger, + } + h.StartWorkers(h.Config.DomainName, ApplicationName, workerOptions) +} + +func startWorkflow(h *common.SampleHelper) { + workflowOptions := client.StartWorkflowOptions{ + ID: "jiraToGithub_" + uuid.New(), + TaskList: ApplicationName, + ExecutionStartToCloseTimeout: time.Minute * 12, + DecisionTaskStartToCloseTimeout: time.Minute * 12, + } + h.StartWorkflow(workflowOptions, jiraToGithubWorkflow) +} + +func main() { + var mode string + flag.StringVar(&mode, "m", "trigger", "Mode is worker or trigger.") + flag.Parse() + + var h common.SampleHelper + h.SetupServiceConfig() + + switch mode { + case "worker": + h.RegisterWorkflow(jiraToGithubWorkflow) + h.RegisterActivity(getJiraTasksActivity) + h.RegisterActivity(createGithubIssuesActivity) + startWorkers(&h) + + // The workers are supposed to be long running process that should not exit. + // Use select{} to block indefinitely for samples, you can quit by CMD+C. + select {} + case "trigger": + startWorkflow(&h) + } +} diff --git a/cmd/samples/jiraToGithub/workflow.go b/cmd/samples/jiraToGithub/workflow.go new file mode 100644 index 00000000..ebcb4f61 --- /dev/null +++ b/cmd/samples/jiraToGithub/workflow.go @@ -0,0 +1,265 @@ +package main + +import ( + "context" + "fmt" + "log" + "os/exec" + "strings" + "time" + + // "code.uber.internal/devexp/utils/jirawithretry" + // "github.com/andygrunwald/go-jira" + jira "github.com/andygrunwald/go-jira" + "github.com/google/go-github/github" + "golang.org/x/oauth2" + + "go.uber.org/cadence/activity" + "go.uber.org/cadence/workflow" + "go.uber.org/zap" +) + +const ( + // ApplicationName is the task list for this sample + ApplicationName = "jiraToGithubGroup" + jiraURL = "https://t3.uberinternal.com" + jiraUsername = "svc-cadence-jira@uber.com" +) + +// type Activity struct { +// jiraClient jirawithretry.IssueClient +// } + +func jiraToGithubWorkflow(ctx workflow.Context) (result string, err error) { + // step 1, get JIRA tasks from cadence project + ao := workflow.ActivityOptions{ + ScheduleToStartTimeout: time.Minute, + StartToCloseTimeout: time.Minute * 4, + HeartbeatTimeout: time.Second * 20, + } + ctx1 := workflow.WithActivityOptions(ctx, ao) + logger := workflow.GetLogger(ctx) + logger.Info("Jira to Github workflow started") + + var issues []jira.Issue + err = workflow.ExecuteActivity(ctx1, getJiraTasksActivity).Get(ctx1, &issues) + if err != nil { + return "", err + } + + // step 2, create issues in github + ao = workflow.ActivityOptions{ + ScheduleToStartTimeout: time.Minute, + StartToCloseTimeout: time.Minute * 8, + } + ctx2 := workflow.WithActivityOptions(ctx, ao) + + err = workflow.ExecuteActivity(ctx2, createGithubIssuesActivity, issues).Get(ctx2, nil) + if err != nil { + return "", err + } + + logger.Info("Workflow completed with Github issues created.") + return "COMPLETED", nil +} + +// func New() (*Activity, error) { +// client, err := jirawithretry.NewIssueClient(&http.Client{}, jiraURL) +// if err != nil { +// return nil, err +// } +// envVars, err := getEnvVars() +// if err != nil { +// return nil, err +// } +// client.Authentication().SetBasicAuth(jiraUsername, envVars["JIRA_API_TOKEN"]) +// return &Activity{jiraClient: client}, nil +// } + +func getJiraTasksActivity(ctx context.Context) ([]jira.Issue, error) { + jiraClient, err := jira.NewClient(nil, jiraURL) + if err != nil { + return nil, err + } + + jql := "project = Cadence AND created >= -365d AND issuetype = Task AND labels = opensourceable ORDER BY created DESC" //AND labels = opensourceable AND description IS NOT EMPTY + options := &jira.SearchOptions{ + MaxResults: 10, + } + issues, _, err := jiraClient.Issue.Search(jql, options) + if err != nil { + return nil, err + } + + activity.GetLogger(ctx).Info("JIRA tasks with label opensourceable fetched", zap.Int("Count", len(issues))) + return issues, err +} + +func createGithubIssuesActivity(ctx context.Context, issues []jira.Issue) error { + // Replace with your actual token + token := "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" + + // GitHub org and repo + // org := "cadence-workflow" + // repo := "cadence-java-samples" + client := github.NewClient(nil) + + org := "vishwa-test-2" + repo := "sample" + + ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token}) + tc := oauth2.NewClient(ctx, ts) + client2 := github.NewClient(tc) + for _, issue := range issues { + title := issue.Fields.Summary + // key := issue.Key + body := issue.Fields.Description + + // Check if the issue already exists in GitHub + searchOpts := &github.SearchOptions{ + TextMatch: true, + } + query := fmt.Sprintf("repo:vishwa-test-2/sample in:title %s state:open", title) + result, _, err := client.Search.Issues(ctx, query, searchOpts) + if err != nil { + return err + } + + if len(result.Issues) == 0 { + // Create issue request + issueRequest := &github.IssueRequest{ + Title: github.String(title), + Body: github.String(body), + } + + // Create issue + issue, _, err := client2.Issues.Create(ctx, org, repo, issueRequest) + if err != nil { + log.Fatalf("Error creating issue: %v", err) + } + + // req := &github.IssueRequest{ + // Title: github.String(fmt.Sprintf("[JIRA %s] %s", key, title)), + // Body: github.String(body), + // } + // _, _, err := client.Issues.Create(ctx, "vishwa-test-2", "sample", req) + // if err != nil { + // return err + // } + activity.GetLogger(ctx).Info("Created an issue in GitHub", zap.String("title", issue.GetHTMLURL())) + } else { + activity.GetLogger(ctx).Info("GitHub issue already exists", zap.String("title", title)) + } + } + return nil +} + +// type IssueRequest struct { +// Title string `json:"title"` +// Body string `json:"body"` +// } + +// func createGithubIssuesActivity(ctx context.Context, issues []jira.Issue) error { +// for _, issue := range issues { +// title := issue.Fields.Summary +// key := issue.Key +// body := issue.Fields.Description + +// // Check if the issue already exists in GitHub +// searchArgs := []string{ +// "gh", +// "issue", +// "list", +// "--repo", +// "cadence-workflow/cadence-java-samples", +// "--search", +// fmt.Sprintf("[JIRA issue] %s in:title", title), +// } + +// searchOutput, err := exec.Command(searchArgs[0], searchArgs[1:]...).Output() +// if err != nil { +// return err +// } + +// if len(searchOutput) == 0 { +// Create a new issue in GitHub + +// issueData, err := json.Marshal(IssueRequest{ +// Title: title, +// Body: body, +// }) +// if err != nil { +// fmt.Println("Error marshaling JSON:", err) +// } + +// url := fmt.Sprintf("https://api.github.com/repos/%s/%s/issues", "vishwa2-uber", "cadence-java-samples") +// req, err := http.NewRequest("POST", url, bytes.NewBuffer(issueData)) +// if err != nil { +// fmt.Println("Error creating request:", err) +// } + +// req.Header.Set("Content-Type", "application/json") + +// client := &http.Client{} +// resp, err := client.Do(req) +// if err != nil { +// fmt.Println("Error sending request:", err) +// } +// defer resp.Body.Close() + +// if resp.StatusCode != http.StatusCreated { +// fmt.Println("Failed to create issue. Status code:", resp.StatusCode) +// buf := new(bytes.Buffer) +// buf.ReadFrom(resp.Body) +// newStr := buf.String() +// fmt.Println(newStr) +// os.Exit(1) +// // return +// } + +// fmt.Println("Issue created successfully!") + +// createArgs := []string{ +// "gh", +// "issue", +// "create", +// "--repo", +// "cadence-workflow/cadence-java-samples", +// "--title", +// fmt.Sprintf("[JIRA %s] %s", key, title), +// "--body", +// body, +// } + +// _, err := exec.Command(createArgs[0], createArgs[1:]...).Output() +// if err != nil { +// return err +// } +// activity.GetLogger(ctx).Info("Created an issue in GitHub", zap.String("title", title)) +// } else { +// activity.GetLogger(ctx).Info("GitHub issue already exists", zap.String("title", title)) +// } +// } +// return nil +// } + +func getEnvVars() (map[string]string, error) { + jiraArgs := []string{"usso", "-ussh", "t3", "-print"} + output, err := exec.Command(jiraArgs[0], jiraArgs[1:]...).Output() + if err != nil { + return nil, err + } + + githubArgs := []string{"usso", "-ussh", "git", "-print"} + githubTokenOutput, err := exec.Command(githubArgs[0], githubArgs[1:]...).Output() + if err != nil { + return nil, err + } + + envVars := map[string]string{ + "JIRA_API_TOKEN": string(output), + "GITHUB_TOKEN": strings.TrimSuffix(string(githubTokenOutput), "\n"), + } + + return envVars, nil +} diff --git a/cmd/samples/jiraToGithub/workflow_test.go b/cmd/samples/jiraToGithub/workflow_test.go new file mode 100644 index 00000000..18080a61 --- /dev/null +++ b/cmd/samples/jiraToGithub/workflow_test.go @@ -0,0 +1,256 @@ +package main + +import ( + "fmt" + "testing" + "time" + + jira "github.com/andygrunwald/go-jira" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" + "go.uber.org/cadence/testsuite" + "go.uber.org/cadence/workflow" +) + +type UnitTestSuite struct { + suite.Suite + testsuite.WorkflowTestSuite + + env *testsuite.TestWorkflowEnvironment +} + +func TestUnitTestSuite(t *testing.T) { + suite.Run(t, new(UnitTestSuite)) +} +func (s *UnitTestSuite) Test_getJiraTasksActivity_Success() { + mockEnvVars := map[string]string{ + "JIRA_API_TOKEN": "mockToken", + } + + s.env.OnActivity(getEnvVars).Return(mockEnvVars, nil).Once() + + mockJiraClient := new(mockJiraClient) + mockIssues := []jira.Issue{ + { + Key: "TEST-1", + Fields: &jira.IssueFields{ + Summary: "Test issue 1", + Description: "Description for test issue 1", + }, + }, + { + Key: "TEST-2", + Fields: &jira.IssueFields{ + Summary: "Test issue 2", + Description: "Description for test issue 2", + }, + }, + } + + s.env.OnActivity(createGithubIssuesActivity, mock.Anything, mock.Anything).Return(nil).Once() + mockJiraClient.On("Search", mock.Anything, mock.Anything).Return(mockIssues, &jira.Response{}, nil) + + // Replace the actual jiraClient with the mock client + jiraClient = mockJiraClient + + // Execute the activity + s.env.ExecuteWorkflow(func(ctx workflow.Context) error { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + ScheduleToStartTimeout: time.Minute, + StartToCloseTimeout: time.Minute, + }) + var issues []jira.Issue + err := workflow.ExecuteActivity(ctx, getJiraTasksActivity).Get(ctx, &issues) + s.NoError(err) + s.Equal(2, len(issues)) + s.Equal("TEST-1", issues[0].Key) + s.Equal("TEST-2", issues[1].Key) + return nil + }) + + s.True(s.env.IsWorkflowCompleted()) + s.NoError(s.env.GetWorkflowError()) +} + +func (s *UnitTestSuite) Test_getJiraTasksActivity_AuthFailure() { + mockEnvVars := map[string]string{ + "JIRA_API_TOKEN": "mockToken", + } + + s.env.OnActivity(getEnvVars).Return(mockEnvVars, nil).Once() + + mockJiraClient := new(mockJiraClient) + mockJiraClient.On("Search", mock.Anything, mock.Anything).Return(nil, nil, fmt.Errorf("authentication failed")) + + // Replace the actual jiraClient with the mock client + jiraClient = mockJiraClient + + // Execute the activity + s.env.ExecuteWorkflow(func(ctx workflow.Context) error { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + ScheduleToStartTimeout: time.Minute, + StartToCloseTimeout: time.Minute, + }) + var issues []jira.Issue + err := workflow.ExecuteActivity(ctx, getJiraTasksActivity).Get(ctx, &issues) + s.Error(err) + s.Contains(err.Error(), "authentication failed") + s.Nil(issues) + return nil + }) + + s.True(s.env.IsWorkflowCompleted()) + s.Error(s.env.GetWorkflowError()) +} + +type mockJiraClient struct { + mock.Mock +} + +func (m *mockJiraClient) Search(jql string, options *jira.SearchOptions) ([]jira.Issue, *jira.Response, error) { + args := m.Called(jql, options) + return args.Get(0).([]jira.Issue), args.Get(1).(*jira.Response), args.Error(2) +} + +var jiraClient *mockJiraClient + +func (s *UnitTestSuite) Test_GetJiraTasksActivity() { + mockEnvVars := map[string]string{ + "JIRA_API_TOKEN": "mockToken", + } + + s.env.OnActivity(getEnvVars).Return(mockEnvVars, nil).Once() + + mockJiraClient := new(mockJiraClient) + mockIssues := []jira.Issue{ + { + Key: "TEST-1", + Fields: &jira.IssueFields{ + Summary: "Test issue 1", + Description: "Description for test issue 1", + }, + }, + { + Key: "TEST-2", + Fields: &jira.IssueFields{ + Summary: "Test issue 2", + Description: "Description for test issue 2", + }, + }, + } + + mockJiraClient.On("Search", mock.Anything, mock.Anything).Return(mockIssues, &jira.Response{}, nil) + + // Replace the actual jiraClient with the mock client + jiraClient = mockJiraClient + + // Execute the activity + // ctx := context.Background() + + // issues, err := getJiraTasksActivity(ctx) + // var issues []jira.Issue + s.env.ExecuteWorkflow(jiraToGithubWorkflow) + // s.Error(err) + + // Assertions + // s.NoError(err) + // s.Equal(2, len(issues)) + // s.Equal("TEST-1", issues[0].Key) + // s.Equal("TEST-2", issues[1].Key) +} + +func (s *UnitTestSuite) Test_getJiraTasksActivity_Failure() { + // envVars := map[string]string{ + // "JIRA_API_TOKEN": "dummy_token", + // } + + // s.env.OnActivity(getEnvVars).Return(envVars, nil).Once() + s.env.OnActivity(getJiraTasksActivity, mock.Anything).Return(nil, fmt.Errorf("failed to fetch JIRA tasks")).Once() + + s.env.ExecuteWorkflow(func(ctx workflow.Context) error { + ctx = workflow.WithActivityOptions(ctx, workflow.ActivityOptions{ + ScheduleToStartTimeout: time.Minute, + StartToCloseTimeout: time.Minute, + }) + var issues []jira.Issue + err := workflow.ExecuteActivity(ctx, getJiraTasksActivity).Get(ctx, &issues) + s.Error(err) + s.Contains(err.Error(), "failed to fetch JIRA tasks") + s.Nil(issues) + return nil + }) + + s.True(s.env.IsWorkflowCompleted()) + s.Error(s.env.GetWorkflowError()) +} +func (s *UnitTestSuite) SetupTest() { + s.env = s.NewTestWorkflowEnvironment() + s.env.RegisterWorkflow(jiraToGithubWorkflow) + s.env.RegisterActivity(createGithubIssuesActivity) + s.env.RegisterActivity(getJiraTasksActivity) +} + +func (s *UnitTestSuite) TearDownTest() { + s.env.AssertExpectations(s.T()) +} + +func (s *UnitTestSuite) Test_WorkflowWithMockActivities() { + mockIssues := []jira.Issue{ + { + Key: "TEST-1", + Fields: &jira.IssueFields{ + Summary: "Test issue 1", + Description: "Description for test issue 1", + }, + }, + { + Key: "TEST-2", + Fields: &jira.IssueFields{ + Summary: "Test issue 2", + Description: "Description for test issue 2", + }, + }, + } + + s.env.OnActivity(getJiraTasksActivity, mock.Anything).Return(mockIssues, nil).Once() + // s.env.OnActivity(createGithubIssuesActivity, mock.Anything, mock.Anything).Return(nil).Once() + + s.env.ExecuteWorkflow(jiraToGithubWorkflow) + + s.True(s.env.IsWorkflowCompleted()) + s.NoError(s.env.GetWorkflowError()) + var workflowResult string + err := s.env.GetWorkflowResult(&workflowResult) + s.NoError(err) + s.Equal("COMPLETED", workflowResult) +} + +func (s *UnitTestSuite) Test_WorkflowWithTimeout() { + mockIssues := []jira.Issue{ + { + Key: "TEST-1", + Fields: &jira.IssueFields{ + Summary: "Test issue 1", + Description: "Description for test issue 1", + }, + }, + { + Key: "TEST-2", + Fields: &jira.IssueFields{ + Summary: "Test issue 2", + Description: "Description for test issue 2", + }, + }, + } + s.env.OnActivity(getJiraTasksActivity, mock.Anything).Return(mockIssues, nil).Once() + + s.env.SetWorkflowTimeout(time.Millisecond * 2) + s.env.SetTestTimeout(time.Minute * 10) + + s.env.ExecuteWorkflow(jiraToGithubWorkflow) + + var workflowResult string + err := s.env.GetWorkflowResult(&workflowResult) + s.Equal("TimeoutType: SCHEDULE_TO_CLOSE", err.Error()) + s.Empty(workflowResult) +} diff --git a/cmd/samples/recipes/dataconverter/dataconverter.go b/cmd/samples/recipes/dataconverter/dataconverter.go new file mode 100644 index 00000000..217ea14d --- /dev/null +++ b/cmd/samples/recipes/dataconverter/dataconverter.go @@ -0,0 +1,39 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "reflect" + + "go.uber.org/cadence/encoded" +) + +type jsonDataConverter struct{} + +func NewJSONDataConverter() encoded.DataConverter { + return &jsonDataConverter{} +} + +func (dc *jsonDataConverter) ToData(value ...interface{}) ([]byte, error) { + var buf bytes.Buffer + enc := json.NewEncoder(&buf) + for i, obj := range value { + err := enc.Encode(obj) + if err != nil { + return nil, fmt.Errorf("unable to encode argument: %d, %v, with error: %v", i, reflect.TypeOf(obj), err) + } + } + return buf.Bytes(), nil +} + +func (dc *jsonDataConverter) FromData(input []byte, valuePtr ...interface{}) error { + dec := json.NewDecoder(bytes.NewBuffer(input)) + for i, obj := range valuePtr { + err := dec.Decode(obj) + if err != nil { + return fmt.Errorf("unable to decode argument: %d, %v, with error: %v", i, reflect.TypeOf(obj), err) + } + } + return nil +} diff --git a/cmd/samples/recipes/dataconverter/main.go b/cmd/samples/recipes/dataconverter/main.go new file mode 100644 index 00000000..3870db94 --- /dev/null +++ b/cmd/samples/recipes/dataconverter/main.go @@ -0,0 +1,63 @@ +package main + +import ( + "flag" + "time" + + "github.com/pborman/uuid" + "go.uber.org/cadence/client" + "go.uber.org/cadence/worker" + + "github.com/uber-common/cadence-samples/cmd/samples/common" +) + +const ( + ApplicationName = "dataConverterTaskList" +) + +func startWorkers(h *common.SampleHelper) { + workerOptions := worker.Options{ + MetricsScope: h.WorkerMetricScope, + Logger: h.Logger, + FeatureFlags: client.FeatureFlags{ + WorkflowExecutionAlreadyCompletedErrorEnabled: true, + }, + DataConverter: NewJSONDataConverter(), + } + h.StartWorkers(h.Config.DomainName, ApplicationName, workerOptions) +} + +func startWorkflow(h *common.SampleHelper) { + input := MyPayload{Msg: "hello", Count: 1} + workflowOptions := client.StartWorkflowOptions{ + ID: "dataconverter_" + uuid.New(), + TaskList: ApplicationName, + ExecutionStartToCloseTimeout: time.Minute, + DecisionTaskStartToCloseTimeout: time.Minute, + } + h.StartWorkflow(workflowOptions, DataConverterWorkflowName, input) +} + +func registerWorkflowAndActivity(h *common.SampleHelper) { + h.RegisterWorkflowWithAlias(dataConverterWorkflow, DataConverterWorkflowName) + h.RegisterActivity(dataConverterActivity) +} + +func main() { + var mode string + flag.StringVar(&mode, "m", "trigger", "Mode is worker or trigger.") + flag.Parse() + + var h common.SampleHelper + h.DataConverter = NewJSONDataConverter() + h.SetupServiceConfig() + + switch mode { + case "worker": + registerWorkflowAndActivity(&h) + startWorkers(&h) + select {} + case "trigger": + startWorkflow(&h) + } +} diff --git a/cmd/samples/recipes/dataconverter/workflow.go b/cmd/samples/recipes/dataconverter/workflow.go new file mode 100644 index 00000000..2ea49300 --- /dev/null +++ b/cmd/samples/recipes/dataconverter/workflow.go @@ -0,0 +1,46 @@ +package main + +import ( + "context" + "time" + + "go.uber.org/cadence/activity" + "go.uber.org/cadence/workflow" + "go.uber.org/zap" +) + +type MyPayload struct { + Msg string + Count int +} + +const DataConverterWorkflowName = "dataConverterWorkflow" + +func dataConverterWorkflow(ctx workflow.Context, input MyPayload) (MyPayload, error) { + logger := workflow.GetLogger(ctx) + logger.Info("Workflow started", zap.Any("input", input)) + + activityOptions := workflow.ActivityOptions{ + ScheduleToStartTimeout: time.Minute, + StartToCloseTimeout: time.Minute, + } + ctx = workflow.WithActivityOptions(ctx, activityOptions) + + var result MyPayload + err := workflow.ExecuteActivity(ctx, dataConverterActivity, input).Get(ctx, &result) + if err != nil { + logger.Error("Activity failed", zap.Error(err)) + return MyPayload{}, err + } + logger.Info("Workflow completed", zap.Any("result", result)) + return result, nil +} + +func dataConverterActivity(ctx context.Context, input MyPayload) (MyPayload, error) { + logger := activity.GetLogger(ctx) + logger.Info("Activity received input", zap.Any("input", input)) + input.Msg = input.Msg + " processed" + input.Count++ + logger.Info("Activity returning", zap.Any("output", input)) + return input, nil +} diff --git a/cmd/samples/recipes/dataconverter/workflow_test.go b/cmd/samples/recipes/dataconverter/workflow_test.go new file mode 100644 index 00000000..c0a3f76d --- /dev/null +++ b/cmd/samples/recipes/dataconverter/workflow_test.go @@ -0,0 +1,38 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/require" + "go.uber.org/cadence/activity" + "go.uber.org/cadence/encoded" + "go.uber.org/cadence/testsuite" + "go.uber.org/cadence/worker" +) + +func Test_DataConverterWorkflow(t *testing.T) { + testSuite := &testsuite.WorkflowTestSuite{} + env := testSuite.NewTestWorkflowEnvironment() + env.RegisterWorkflow(dataConverterWorkflow) + env.RegisterActivity(dataConverterActivity) + + dataConverter := NewJSONDataConverter() + workerOptions := worker.Options{ + DataConverter: dataConverter, + } + env.SetWorkerOptions(workerOptions) + + input := MyPayload{Msg: "test", Count: 42} + + var activityResult MyPayload + env.SetOnActivityCompletedListener(func(activityInfo *activity.Info, result encoded.Value, err error) { + result.Get(&activityResult) + }) + + env.ExecuteWorkflow(dataConverterWorkflow, input) + + require.True(t, env.IsWorkflowCompleted()) + require.NoError(t, env.GetWorkflowError()) + require.Equal(t, "test processed", activityResult.Msg) + require.Equal(t, 43, activityResult.Count) +} diff --git a/main.go b/main.go new file mode 100644 index 00000000..188ddbd1 --- /dev/null +++ b/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "context" + "fmt" + "log" + + "github.com/google/go-github/github" + "golang.org/x/oauth2" +) + +func main() { + // Replace with your actual token + token := "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" + + // GitHub org and repo + // org := "cadence-workflow" + // repo := "cadence-java-samples" + org := "vishwa-test-2" + repo := "sample" + + // Issue details + title := "Sample Issue Title" + body := "This is a test issue created via the GitHub API." + + // Authenticate using the token + ctx := context.Background() + ts := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: token}) + tc := oauth2.NewClient(ctx, ts) + client := github.NewClient(tc) + + // Create issue request + issueRequest := &github.IssueRequest{ + Title: github.String(title), + Body: github.String(body), + } + + // Create issue + issue, _, err := client.Issues.Create(ctx, org, repo, issueRequest) + if err != nil { + log.Fatalf("Error creating issue: %v", err) + } + + fmt.Printf("Issue created: %s\n", issue.GetHTMLURL()) +} diff --git a/main2.go b/main2.go new file mode 100644 index 00000000..aff13ce8 --- /dev/null +++ b/main2.go @@ -0,0 +1,66 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" +) + +const ( + baseURL = "https://api.github.com" + org = "vishwa-test-2" + repo = "sample" + authToken = "eyJhbGciOiJFUzI1NiIsImVudiI6InByb2QiLCJraWQiOiJnX0hsUlpnWGRzMlNFcUVFUGVzZmdiUDBaRWxIV2tiaFJ5SUd4alRKMWNrIiwidHlwIjoiSldUIiwidmVyIjoiMS4wIn0.eyJjbGllbnRfaWQiOiJnaXQudWJlcmludGVybmFsLmNvbSIsImVtYWlsIjoidnBhdGlsMTZAZXh0LnViZXIuY29tIiwiZXhwIjoxNzQxMjc1MTAyLCJpYXQiOjE3NDEyMDI4MDIsImlzcyI6InNwaWZmZTovL3Vzc28udXBraS5jYSIsImp0aSI6IjQwY2Q2MDI1LWNhYmMtNGQ5ZS1iMjZmLTIzMDJjMWQ4MmQ4ZiIsInBsY3kiOiJ0K1U5TkFITWJuTENRUmxWSFZOdS9WdktlaHlkdXJSblhYMWpqZGt4bzRUQVFPcVJPQkVVQkQyUGk3V2VTVVlJeUdQcndDa0JWTGJWZ05ZTkYwU0YvZjF0Ui9EYlhhUWNycCs2YXpwSTRDSGQrNlhMTlMzY09XY3JQRFFKSHNCY1I5YW9FdEZXOUxxeVdKV0YzWmtKUFpHcEJmT2l6Qmd3RlpiT0M1WT0iLCJwbGN5X2tleSI6ImtleS11c3NvLXBsY3ktMTEwOTE4LnBlbSIsInN1YiI6InNwaWZmZTovL3BlcnNvbm5lbC51cGtpLmNhL2VpZC85OTkwMDA1MDYzMTAiLCJ0ZW5hbmN5IjoidWJlci9wcm9kdWN0aW9uIiwidHlwZSI6Im9mZmxpbmUiLCJ1dWlkIjoiNjg2YzBmOWYtMzRmMy00OTgwLWE0NGYtMmU5ODVmMzY0MTg0In0.mhW0U3fW9J9HVSLjgfTfCgmgB4WF7E-C32IJ78jK5hzEK6iEE9Ng-V4-iKT0yKNq2n8-g6uG2T17-oUndeQW9w" +) + +type Issue struct { + Title string `json:"title"` + Body string `json:"body"` +} + +func createIssue(title, body string) error { + url := fmt.Sprintf("%s/repos/%s/%s/issues", baseURL, org, repo) + + issue := Issue{ + Title: title, + Body: body, + } + + jsonData, err := json.Marshal(issue) + if err != nil { + return err + } + + req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData)) + if err != nil { + return err + } + + req.Header.Set("Authorization", "Bearer "+authToken) + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusCreated { + return fmt.Errorf("failed to create issue: %s", resp.Status) + } + + fmt.Println("Issue created successfully") + return nil +} + +func main2() { + title := "Sample Issue Title" + body := "This is a sample issue body." + + err := createIssue(title, body) + if err != nil { + fmt.Printf("Error creating issue: %v\n", err) + } +}