From 983eaba866c4249845ba7fa25937d50f389d9149 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Tue, 30 Sep 2025 18:02:28 -0400 Subject: [PATCH 1/5] Add support for GCP tasks --- pkgs/gcloud/lib/src/tasks_impl.dart | 15 +++++++ pkgs/gcloud/lib/tasks.dart | 54 ++++++++++++++++++++++++++ pkgs/gcloud/test/tasks/tasks_test.dart | 18 +++++++++ 3 files changed, 87 insertions(+) create mode 100644 pkgs/gcloud/lib/src/tasks_impl.dart create mode 100644 pkgs/gcloud/lib/tasks.dart create mode 100644 pkgs/gcloud/test/tasks/tasks_test.dart diff --git a/pkgs/gcloud/lib/src/tasks_impl.dart b/pkgs/gcloud/lib/src/tasks_impl.dart new file mode 100644 index 00000000..a5227488 --- /dev/null +++ b/pkgs/gcloud/lib/src/tasks_impl.dart @@ -0,0 +1,15 @@ +import 'package:googleapis/tasks/v1.dart' as api; +import 'package:http/http.dart' as http; + +import '../tasks.dart' as tasks; + +class TasksImpl implements tasks.Tasks { + final api.TasksApi _api; + + TasksImpl(http.Client client) : _api = api.TasksApi(client); + + @override + Future createTask(String taskList, api.Task task) { + return _api.tasks.insert(task, taskList); + } +} diff --git a/pkgs/gcloud/lib/tasks.dart b/pkgs/gcloud/lib/tasks.dart new file mode 100644 index 00000000..70dc5a4c --- /dev/null +++ b/pkgs/gcloud/lib/tasks.dart @@ -0,0 +1,54 @@ +/// This library provides a low-level API for accessing Google's Cloud +/// Tasks. +/// +/// For more information on Cloud Tasks, please refer to the following +/// developers page: https://cloud.google.com/tasks/docs +library; + +import 'package:googleapis/tasks/v1.dart' as tasks; +import 'package:http/http.dart' as http; + +import 'service_scope.dart' as ss; + +import 'src/tasks_impl.dart' show TasksImpl; + +const Symbol _tasksKey = #gcloud.tasks; + +/// Access the [Tasks] object available in the current service scope. +/// +/// The returned object will be the one which was previously registered with +/// [registerTasksService] within the current (or a parent) service scope. +/// +/// Accessing this getter outside of a service scope will result in an error. +/// See the `package:gcloud/service_scope.dart` library for more information. +Tasks get tasksService => ss.lookup(_tasksKey) as Tasks; + +/// Registers the [Tasks] object within the current service scope. +/// +/// The provided `tasks` object will be available via the top-level +/// `tasksService` getter. +/// +/// Calling this function outside of a service scope will result in an error. +/// Calling this function more than once inside the same service scope is not +/// allowed. +void registerTasksService(Tasks tasks) { + ss.register(_tasksKey, tasks); +} + +/// Interface used to talk to the Google Cloud Tasks service. +abstract class Tasks { + /// List of required OAuth2 scopes for Tasks operations. + // ignore: constant_identifier_names + static const Scopes = [tasks.TasksApi.tasksScope]; + + /// Access Tasks using an authenticated client. + /// + /// The [client] is an authenticated HTTP client. This client must + /// provide access to at least the scopes in [Tasks.Scopes]. + factory Tasks(http.Client client) { + return TasksImpl(client); + } + + /// Creates a new task on the specified task list. + Future createTask(String taskList, tasks.Task task); +} diff --git a/pkgs/gcloud/test/tasks/tasks_test.dart b/pkgs/gcloud/test/tasks/tasks_test.dart new file mode 100644 index 00000000..9004a3b7 --- /dev/null +++ b/pkgs/gcloud/test/tasks/tasks_test.dart @@ -0,0 +1,18 @@ +import 'package:googleapis/tasks/v1.dart' as tasks; +import 'package:test/test.dart'; + +import '../common.dart'; + +const _hostName = 'tasks.googleapis.com'; +const _rootPath = '/v1/'; + +MockClient mockClient() => MockClient(_hostName, _rootPath); + +void main() { + group('task', () { + test('create', () { + final task = tasks.Task(); + // expect(task.name, 'test-task'); + }); + }); +} From 349c7a6dc5dd8806953c4dc0e87daa99e82838a8 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Tue, 7 Oct 2025 11:49:13 -0400 Subject: [PATCH 2/5] add test --- pkgs/gcloud/lib/cloud_tasks.dart | 68 +++++++++++++++++++ pkgs/gcloud/lib/src/cloud_tasks_impl.dart | 47 +++++++++++++ pkgs/gcloud/lib/src/tasks_impl.dart | 15 ---- pkgs/gcloud/lib/tasks.dart | 54 --------------- .../test/cloud_tasks/cloud_tasks_test.dart | 65 ++++++++++++++++++ pkgs/gcloud/test/tasks/tasks_test.dart | 18 ----- 6 files changed, 180 insertions(+), 87 deletions(-) create mode 100644 pkgs/gcloud/lib/cloud_tasks.dart create mode 100644 pkgs/gcloud/lib/src/cloud_tasks_impl.dart delete mode 100644 pkgs/gcloud/lib/src/tasks_impl.dart delete mode 100644 pkgs/gcloud/lib/tasks.dart create mode 100644 pkgs/gcloud/test/cloud_tasks/cloud_tasks_test.dart delete mode 100644 pkgs/gcloud/test/tasks/tasks_test.dart diff --git a/pkgs/gcloud/lib/cloud_tasks.dart b/pkgs/gcloud/lib/cloud_tasks.dart new file mode 100644 index 00000000..04d17563 --- /dev/null +++ b/pkgs/gcloud/lib/cloud_tasks.dart @@ -0,0 +1,68 @@ +/// This library provides a low-level API for accessing Google's Cloud +/// Tasks. +/// +/// For more information on Cloud Tasks, please refer to the following +/// developers page: https://cloud.google.com/tasks/docs +library; + +import 'package:googleapis/cloudtasks/v2.dart' as tasks; +import 'package:http/http.dart' as http; + +import 'service_scope.dart' as ss; + +import 'src/cloud_tasks_impl.dart' show CloudTasksImpl; + +const Symbol _tasksKey = #gcloud.tasks; + +/// Access the [CloudTasks] object available in the current service scope. +/// +/// The returned object will be the one which was previously registered with +/// [registerTasksService] within the current (or a parent) service scope. +/// +/// Accessing this getter outside of a service scope will result in an error. +/// See the `package:gcloud/service_scope.dart` library for more information. +CloudTasks get tasksService => ss.lookup(_tasksKey) as CloudTasks; + +/// Registers the [CloudTasks] object within the current service scope. +/// +/// The provided `tasks` object will be available via the top-level +/// `tasksService` getter. +/// +/// Calling this function outside of a service scope will result in an error. +/// Calling this function more than once inside the same service scope is not +/// allowed. +void registerTasksService(CloudTasks tasks) { + ss.register(_tasksKey, tasks); +} + +/// Interface used to talk to the Google Cloud Tasks service. +abstract class CloudTasks { + /// List of required OAuth2 scopes for Cloud Tasks operations. + // ignore: constant_identifier_names + static const Scopes = [tasks.CloudTasksApi.cloudTasksScope]; + + /// Access Cloud Tasks using an authenticated client. + /// + /// The [client] is an authenticated HTTP client. This client must + /// provide access to at least the scopes in [CloudTasks.Scopes]. + factory CloudTasks(http.Client client, String project, String location) { + return CloudTasksImpl(client, project, location); + } + + /// Creates a new task on the specified [queue]. When the task is run, + /// [request] will be executed. + /// + /// If [scheduleTime] is provided, the task will be scheduled to run at the + /// specified time. If not provided, the task will be scheduled to run + /// immediately. + /// + /// If [name] is provided, the task will be given that name. + /// + /// Returns a [Future] which completes with the newly created task. + Future createTask( + String queue, + http.Request request, { + String? name, + DateTime? scheduleTime, + }); +} diff --git a/pkgs/gcloud/lib/src/cloud_tasks_impl.dart b/pkgs/gcloud/lib/src/cloud_tasks_impl.dart new file mode 100644 index 00000000..a5ae68dc --- /dev/null +++ b/pkgs/gcloud/lib/src/cloud_tasks_impl.dart @@ -0,0 +1,47 @@ +import 'dart:convert'; + +import 'package:googleapis/cloudtasks/v2.dart' as api; +import 'package:http/http.dart' as http; + +import '../cloud_tasks.dart' as tasks; + +class CloudTasksImpl implements tasks.CloudTasks { + final api.CloudTasksApi _api; + final String _project; + final String _location; + + CloudTasksImpl(http.Client client, String project, String location) + : _api = api.CloudTasksApi(client), + _project = project, + _location = location; + + @override + Future createTask( + String queue, + http.Request request, { + DateTime? scheduleTime, + String? name, + }) async { + final task = api.Task( + name: name, + scheduleTime: scheduleTime?.toUtc().toIso8601String(), + httpRequest: _fromHttpRequest(request), + ); + final createTaskRequest = api.CreateTaskRequest(task: task); + return _api.projects.locations.queues.tasks + .create(createTaskRequest, _fullQueueName(queue)); + } + + api.HttpRequest _fromHttpRequest(http.Request request) { + return api.HttpRequest( + headers: request.headers, + body: base64.encode(utf8.encode(request.body)), + httpMethod: request.method, + url: request.url.toString(), + ); + } + + String _fullQueueName(String name) { + return 'projects/$_project/locations/$_location/queues/$name'; + } +} diff --git a/pkgs/gcloud/lib/src/tasks_impl.dart b/pkgs/gcloud/lib/src/tasks_impl.dart deleted file mode 100644 index a5227488..00000000 --- a/pkgs/gcloud/lib/src/tasks_impl.dart +++ /dev/null @@ -1,15 +0,0 @@ -import 'package:googleapis/tasks/v1.dart' as api; -import 'package:http/http.dart' as http; - -import '../tasks.dart' as tasks; - -class TasksImpl implements tasks.Tasks { - final api.TasksApi _api; - - TasksImpl(http.Client client) : _api = api.TasksApi(client); - - @override - Future createTask(String taskList, api.Task task) { - return _api.tasks.insert(task, taskList); - } -} diff --git a/pkgs/gcloud/lib/tasks.dart b/pkgs/gcloud/lib/tasks.dart deleted file mode 100644 index 70dc5a4c..00000000 --- a/pkgs/gcloud/lib/tasks.dart +++ /dev/null @@ -1,54 +0,0 @@ -/// This library provides a low-level API for accessing Google's Cloud -/// Tasks. -/// -/// For more information on Cloud Tasks, please refer to the following -/// developers page: https://cloud.google.com/tasks/docs -library; - -import 'package:googleapis/tasks/v1.dart' as tasks; -import 'package:http/http.dart' as http; - -import 'service_scope.dart' as ss; - -import 'src/tasks_impl.dart' show TasksImpl; - -const Symbol _tasksKey = #gcloud.tasks; - -/// Access the [Tasks] object available in the current service scope. -/// -/// The returned object will be the one which was previously registered with -/// [registerTasksService] within the current (or a parent) service scope. -/// -/// Accessing this getter outside of a service scope will result in an error. -/// See the `package:gcloud/service_scope.dart` library for more information. -Tasks get tasksService => ss.lookup(_tasksKey) as Tasks; - -/// Registers the [Tasks] object within the current service scope. -/// -/// The provided `tasks` object will be available via the top-level -/// `tasksService` getter. -/// -/// Calling this function outside of a service scope will result in an error. -/// Calling this function more than once inside the same service scope is not -/// allowed. -void registerTasksService(Tasks tasks) { - ss.register(_tasksKey, tasks); -} - -/// Interface used to talk to the Google Cloud Tasks service. -abstract class Tasks { - /// List of required OAuth2 scopes for Tasks operations. - // ignore: constant_identifier_names - static const Scopes = [tasks.TasksApi.tasksScope]; - - /// Access Tasks using an authenticated client. - /// - /// The [client] is an authenticated HTTP client. This client must - /// provide access to at least the scopes in [Tasks.Scopes]. - factory Tasks(http.Client client) { - return TasksImpl(client); - } - - /// Creates a new task on the specified task list. - Future createTask(String taskList, tasks.Task task); -} diff --git a/pkgs/gcloud/test/cloud_tasks/cloud_tasks_test.dart b/pkgs/gcloud/test/cloud_tasks/cloud_tasks_test.dart new file mode 100644 index 00000000..9a8f2995 --- /dev/null +++ b/pkgs/gcloud/test/cloud_tasks/cloud_tasks_test.dart @@ -0,0 +1,65 @@ +import 'dart:convert'; + +import 'package:gcloud/cloud_tasks.dart'; +import 'package:http/http.dart' as http; +import 'package:test/test.dart'; + +import '../common.dart'; +import '../common_e2e.dart'; + +const _hostName = 'cloudtasks.googleapis.com'; +const _rootPath = '/v2/'; +const _defaultLocation = 'us-central1'; + +MockClient mockClient() => MockClient(_hostName, _rootPath); + +Future withMockClientAsync( + Future Function(MockClient client, CloudTasks tasks) function) async { + var mock = mockClient(); + await function(mock, CloudTasks(mock, testProject, _defaultLocation)); +} + +void main() { + group('task', () { + test('create with httpRequest', () async { + const taskName = 'my-task'; + const requestBody = 'hello world'; + const requestHeaders = { + 'content-type': 'application/json', + 'authorization': 'Bearer ....' + }; + final expectedTaskJson = { + 'name': taskName, + 'httpRequest': { + 'headers': requestHeaders, + 'body': base64.encode(utf8.encode(requestBody)), + 'httpMethod': 'GET', + 'url': 'https://www.google.com', + }, + }; + final responseJson = {'task': expectedTaskJson}; + + await withMockClientAsync((mock, tasks) async { + mock.register( + 'POST', + 'projects/test-project/locations/us-central1/queues/test-queue/tasks', + expectAsync1((request) { + final json = jsonDecode(request.body) as Map; + expect(json, responseJson); + return mock.respond(expectedTaskJson); + }), + ); + + final request = http.Request('GET', Uri.parse('https://www.google.com')) + ..body = requestBody + ..headers.addAll(requestHeaders); + + final task = + await tasks.createTask('test-queue', request, name: taskName); + expect(task.name, taskName); + final actualRequestJson = task.httpRequest!.toJson(); + expect(actualRequestJson, expectedTaskJson['httpRequest']); + }); + }); + }); +} diff --git a/pkgs/gcloud/test/tasks/tasks_test.dart b/pkgs/gcloud/test/tasks/tasks_test.dart deleted file mode 100644 index 9004a3b7..00000000 --- a/pkgs/gcloud/test/tasks/tasks_test.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:googleapis/tasks/v1.dart' as tasks; -import 'package:test/test.dart'; - -import '../common.dart'; - -const _hostName = 'tasks.googleapis.com'; -const _rootPath = '/v1/'; - -MockClient mockClient() => MockClient(_hostName, _rootPath); - -void main() { - group('task', () { - test('create', () { - final task = tasks.Task(); - // expect(task.name, 'test-task'); - }); - }); -} From 3916ec5ad363dab34d91a1bbf63ae5189136a9d0 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Tue, 7 Oct 2025 11:55:25 -0400 Subject: [PATCH 3/5] update changelog --- pkgs/gcloud/CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkgs/gcloud/CHANGELOG.md b/pkgs/gcloud/CHANGELOG.md index 5d4ab7e8..162953c5 100644 --- a/pkgs/gcloud/CHANGELOG.md +++ b/pkgs/gcloud/CHANGELOG.md @@ -1,3 +1,7 @@ +## 0.10.0 + +- Add support for Cloud Tasks + ## 0.9.0 - Support `orderingKey` on pub/sub's `Message` type. From 390747d7c14f000fdc408966c2c343c75a109bc6 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Tue, 7 Oct 2025 11:55:37 -0400 Subject: [PATCH 4/5] update version --- pkgs/gcloud/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/gcloud/pubspec.yaml b/pkgs/gcloud/pubspec.yaml index 818774ed..6e21d6de 100644 --- a/pkgs/gcloud/pubspec.yaml +++ b/pkgs/gcloud/pubspec.yaml @@ -1,5 +1,5 @@ name: gcloud -version: 0.9.0 +version: 0.10.0 description: >- High level idiomatic Dart API for Google Cloud Storage, Pub-Sub and Datastore. repository: https://github.com/dart-lang/labs/tree/main/pkgs/gcloud From b952d04c4fd04e9e78a32ae2811b8531e40ca921 Mon Sep 17 00:00:00 2001 From: Bryan Oltman Date: Wed, 8 Oct 2025 16:42:35 -0400 Subject: [PATCH 5/5] use correct scope, as per discovery doc --- pkgs/gcloud/lib/cloud_tasks.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/gcloud/lib/cloud_tasks.dart b/pkgs/gcloud/lib/cloud_tasks.dart index 04d17563..5a49b370 100644 --- a/pkgs/gcloud/lib/cloud_tasks.dart +++ b/pkgs/gcloud/lib/cloud_tasks.dart @@ -39,7 +39,7 @@ void registerTasksService(CloudTasks tasks) { abstract class CloudTasks { /// List of required OAuth2 scopes for Cloud Tasks operations. // ignore: constant_identifier_names - static const Scopes = [tasks.CloudTasksApi.cloudTasksScope]; + static const Scopes = [tasks.CloudTasksApi.cloudPlatformScope]; /// Access Cloud Tasks using an authenticated client. ///