Skip to content

Initial commit for task end points #691

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

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
1 change: 1 addition & 0 deletions v2/arangodb/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ type Client interface {
ClientAdmin
ClientAsyncJob
ClientFoxx
ClientTasks
}
2 changes: 2 additions & 0 deletions v2/arangodb/client_impl.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func newClient(connection connection.Connection) *client {
c.clientAdmin = newClientAdmin(c)
c.clientAsyncJob = newClientAsyncJob(c)
c.clientFoxx = newClientFoxx(c)
c.clientTask = newClientTask(c)

c.Requests = NewRequests(connection)

Expand All @@ -56,6 +57,7 @@ type client struct {
*clientAdmin
*clientAsyncJob
*clientFoxx
*clientTask

Requests
}
Expand Down
84 changes: 84 additions & 0 deletions v2/arangodb/tasks.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
// DISCLAIMER
//
// # Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany

package arangodb

import (
"context"
)

// ClientTasks defines the interface for managing tasks in ArangoDB.
type ClientTasks interface {
// Task retrieves an existing task by its ID.
// If no task with the given ID exists, a NotFoundError is returned.
Task(ctx context.Context, id string) (Task, error)

// Tasks returns a list of all tasks on the server.
Tasks(ctx context.Context) ([]Task, error)

// CreateTask creates a new task with the specified options.
CreateTask(ctx context.Context, options *TaskOptions) (Task, error)

// If a task with the given ID already exists, a Conflict error is returned.
CreateTaskWithID(ctx context.Context, id string, options *TaskOptions) (Task, error)

// RemoveTask deletes an existing task by its ID.
RemoveTask(ctx context.Context, id string) error
}

// TaskOptions contains options for creating a new task.
type TaskOptions struct {
// ID is an optional identifier for the task.
ID string `json:"id,omitempty"`
// Name is an optional name for the task.
Name string `json:"name,omitempty"`

// Command is the JavaScript code to be executed.
Command string `json:"command"`

// Params are optional parameters passed to the command.
Params interface{} `json:"params,omitempty"`

// Period is the interval (in seconds) at which the task runs periodically.
// If zero, the task runs once after the offset.
Period int64 `json:"period,omitempty"`

// Offset is the delay (in milliseconds) before the task is first executed.
Offset float64 `json:"offset,omitempty"`
}

// Task provides access to a single task on the server.
type Task interface {
// ID returns the ID of the task.
ID() string

// Name returns the name of the task.
Name() string

// Command returns the JavaScript code of the task.
Command() string

// Params returns the parameters of the task.
Params(result interface{}) error

// Period returns the period (in seconds) of the task.
Period() int64

// Offset returns the offset (in milliseconds) of the task.
Offset() float64
}
227 changes: 227 additions & 0 deletions v2/arangodb/tasks_impl.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
// DISCLAIMER
//
// Copyright 2024 ArangoDB GmbH, Cologne, Germany
//
// 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.
//
// Copyright holder is ArangoDB GmbH, Cologne, Germany
//

package arangodb

import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"

"github.com/pkg/errors"

"github.com/arangodb/go-driver/v2/arangodb/shared"
"github.com/arangodb/go-driver/v2/connection"
)

// newClientTask initializes a new task client with the given database name.
func newClientTask(client *client) *clientTask {
return &clientTask{
client: client,
}
}

// will check all methods in ClientTasks are implemented with the clientTask struct.
var _ ClientTasks = &clientTask{}

type clientTask struct {
client *client
}

type taskResponse struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Command string `json:"command,omitempty"`
Params json.RawMessage `json:"params,omitempty"`
Period int64 `json:"period,omitempty"`
Offset float64 `json:"offset,omitempty"`
}

func newTask(client *client, resp *taskResponse) Task {
return &task{
client: client,
id: resp.ID,
name: resp.Name,
command: resp.Command,
params: resp.Params,
period: resp.Period,
offset: resp.Offset,
}
}

type task struct {
client *client
id string
name string
command string
params json.RawMessage
period int64
offset float64
}

func (t *task) ID() string {
return t.id
}

func (t *task) Name() string {
return t.name
}

func (t *task) Command() string {
return t.command
}

func (t *task) Params(result interface{}) error {
if t.params == nil {
return nil
}
return json.Unmarshal(t.params, result)
}

func (t *task) Period() int64 {
return t.period
}

func (t *task) Offset() float64 {
return t.offset
}

func (c clientTask) Tasks(ctx context.Context) ([]Task, error) {
urlEndpoint := connection.NewUrl("_api", "tasks") // Note: This should include database context, see below
response := make([]taskResponse, 0) // Direct array response
resp, err := connection.CallGet(ctx, c.client.connection, urlEndpoint, &response)
if err != nil {
return nil, errors.WithStack(err)
}
switch code := resp.Code(); code {
case http.StatusOK:
result := make([]Task, len(response))
for i, task := range response {
fmt.Printf("Task %d: %+v\n", i, task)
result[i] = newTask(c.client, &task)
}
return result, nil
default:
// Attempt to get error details from response headers or body
return nil, shared.NewResponseStruct().AsArangoErrorWithCode(code)
}
}

func (c clientTask) Task(ctx context.Context, id string) (Task, error) {
urlEndpoint := connection.NewUrl("_api", "tasks", url.PathEscape(id))

response := struct {
taskResponse `json:",inline"`
shared.ResponseStruct `json:",inline"`
}{}

resp, err := connection.CallGet(ctx, c.client.connection, urlEndpoint, &response)
if err != nil {
return nil, errors.WithStack(err)
}
switch code := resp.Code(); code {
case http.StatusOK:
return newTask(c.client, &response.taskResponse), nil
default:
return nil, response.AsArangoError()
}
}

func (c clientTask) CreateTask(ctx context.Context, options *TaskOptions) (Task, error) {
var urlEndpoint string
if options.ID != "" {
urlEndpoint = connection.NewUrl("_api", "tasks", url.PathEscape(options.ID))
} else {
urlEndpoint = connection.NewUrl("_api", "tasks")
}
// Prepare the request body
createRequest := struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Command string `json:"command,omitempty"`
Params json.RawMessage `json:"params,omitempty"`
Period int64 `json:"period,omitempty"`
Offset float64 `json:"offset,omitempty"`
}{
ID: options.ID,
Name: options.Name,
Command: options.Command,
Period: options.Period,
Offset: options.Offset,
}

if options.Params != nil {
raw, err := json.Marshal(options.Params)
if err != nil {
return nil, errors.WithStack(err)
}
createRequest.Params = raw
}

response := struct {
shared.ResponseStruct `json:",inline"`
taskResponse `json:",inline"`
}{}

resp, err := connection.CallPost(ctx, c.client.connection, urlEndpoint, &response, &createRequest)
if err != nil {
return nil, errors.WithStack(err)
}

switch code := resp.Code(); code {
case http.StatusCreated, http.StatusOK:
return newTask(c.client, &response.taskResponse), nil
default:
return nil, response.AsArangoError()
}
}

func (c clientTask) RemoveTask(ctx context.Context, id string) error {
urlEndpoint := connection.NewUrl("_api", "tasks", url.PathEscape(id))

resp, err := connection.CallDelete(ctx, c.client.connection, urlEndpoint, nil)
if err != nil {
return err
}

switch code := resp.Code(); code {
case http.StatusAccepted, http.StatusOK:
return nil
default:
return shared.NewResponseStruct().AsArangoErrorWithCode(code)
}
}

func (c clientTask) CreateTaskWithID(ctx context.Context, id string, options *TaskOptions) (Task, error) {
// Check if task already exists
existingTask, err := c.Task(ctx, id)
fmt.Printf("Checking existing task with ID: %s, existingTask: %v, Error:%v", id, existingTask, err)
if err == nil && existingTask != nil {
return nil, &shared.ArangoError{
Code: http.StatusConflict,
ErrorMessage: fmt.Sprintf("Task with ID %s already exists", id),
}
}

// Set the ID and call CreateTask
options.ID = id
return c.CreateTask(ctx, options)
}
Loading