use std::collections::HashMap; use anyhow::Result; use async_channel::Sender; use async_trait::async_trait; use chrono::{DateTime, Utc}; pub use cloud_object_models::*; pub use cloud_objects::cloud_object::*; use cloud_objects::drive::sharing::SharingAccessLevel; use cloud_objects::ids::{ FolderId, GenericStringObjectId, HashedSqliteId, ObjectUid, ServerId, SyncId, }; #[cfg(any(test, feature = "test-util"))] use mockall::automock; use warp_graphql::mcp_gallery_template::MCPGalleryTemplate; use warp_graphql::object_permissions::AccessLevel; /// Identifies a guest to remove from an object. #[derive(Clone, Debug)] pub enum GuestIdentifier { /// Remove a user guest by their email address. Email(String), /// Remove a team guest by their team UID. TeamUid(ServerId), } /// The type of action that occurred on an object, such as an execution, selection, so on /// and so forth. #[derive(Clone, Debug, PartialEq)] pub enum ObjectActionType { Execute, } // In order to convert from a graphql type and from a SQLite read, the action type // implements to_string(). // // Temporarily suppress clippy warnings about the `ToString` impl until we // move `ObjectType` away from using `std::fmt::Display` for serialization. #[allow(clippy::to_string_trait_impl)] impl ToString for ObjectActionType { fn to_string(&self) -> String { match self { ObjectActionType::Execute => String::from("EXECUTE"), } } } impl ObjectActionType { pub fn singular(&self) -> String { match self { ObjectActionType::Execute => "run".to_string(), } } pub fn plural(&self) -> String { match self { ObjectActionType::Execute => "runs".to_string(), } } } /// We track object actions, both those that have been sent to the server and not, through this /// type. A single ObjectAction represents an object_id, action pair and a subtype that contains data /// about the action(s). Each ObjectAction either represents one action or a summary of identical actions /// that occurred at different times. We summarize old actions in order to save memory footprint on the client. #[derive(Clone, Debug, PartialEq)] pub struct ObjectAction { pub action_type: ObjectActionType, pub uid: ObjectUid, pub hashed_sqlite_id: HashedSqliteId, // This action either represents one action or a consolidation of multiple actions. pub action_subtype: ObjectActionSubtype, } impl ObjectAction { pub fn is_pending(&self) -> bool { match self.action_subtype { ObjectActionSubtype::SingleAction { pending, .. } => pending, ObjectActionSubtype::BundledActions { .. } => false, } } } #[derive(Clone, Debug, PartialEq)] pub struct ObjectActionHistory { pub uid: ObjectUid, pub hashed_sqlite_id: HashedSqliteId, pub latest_processed_at_timestamp: DateTime, pub actions: Vec, } #[derive(Clone, Debug, PartialEq)] pub enum ObjectActionSubtype { SingleAction { timestamp: DateTime, processed_at_timestamp: Option>, data: Option, pending: bool, }, BundledActions { count: i32, oldest_timestamp: DateTime, latest_timestamp: DateTime, latest_processed_at_timestamp: DateTime, }, } #[derive(Default)] pub struct InitialLoadResponse { pub updated_notebooks: Vec, pub deleted_notebooks: Vec, pub updated_workflows: Vec, pub deleted_workflows: Vec, pub updated_folders: Vec, pub deleted_folders: Vec, pub updated_generic_string_objects: HashMap>>, pub deleted_generic_string_objects: Vec, pub user_profiles: Vec, pub action_histories: Vec, pub mcp_gallery: Vec, } pub struct GetCloudObjectResponse { pub object: ServerCloudObject, pub descendants: Vec, pub action_histories: Vec, } #[derive(Debug, Clone)] #[allow(clippy::enum_variant_names)] pub enum ObjectUpdateMessage { ObjectMetadataChanged { metadata: ServerMetadata, }, ObjectPermissionsChanged, ObjectPermissionsChangedV2 { object_uid: ServerId, permissions: ServerPermissions, user_profiles: Vec, }, ObjectContentChanged { server_object: Box, last_editor: Option, }, ObjectDeleted { object_uid: ServerId, }, ObjectActionOccurred { history: ObjectActionHistory, }, TeamMembershipsChanged, AmbientTaskUpdated { task_id: String, timestamp: DateTime, }, } impl ObjectUpdateMessage { pub fn as_str(&self) -> &'static str { use ObjectUpdateMessage::*; match self { ObjectMetadataChanged { .. } => "ObjectMetadataChanged", ObjectPermissionsChanged => "ObjectPermissionsChanged", ObjectPermissionsChangedV2 { .. } => "ObjectPermissionsChanged (V2)", ObjectContentChanged { .. } => "ObjectContentChanged", ObjectDeleted { .. } => "ObjectDeleted", ObjectActionOccurred { .. } => "ObjectActionOccurred", TeamMembershipsChanged => "TeamMembershipsChanged", AmbientTaskUpdated { .. } => "AmbientTaskUpdated", } } } #[derive(Clone, Debug)] pub enum ObjectPermissionUpdateResult { Success, Failure, } #[derive(Clone, Debug)] pub struct ObjectPermissionsUpdateData { pub permissions: ServerPermissions, pub profiles: Vec, } #[derive(Clone, Debug)] pub enum ObjectMetadataUpdateResult { Success { metadata: Box }, Failure, } pub enum ObjectDeleteResult { Success { deleted_ids: Vec }, Failure, } #[cfg_attr(any(test, feature = "test-util"), automock)] #[cfg_attr(not(target_family = "wasm"), async_trait)] #[cfg_attr(target_family = "wasm", async_trait(?Send))] pub trait ObjectClient: 'static + Send + Sync { /// This method saves a workflow for a given owner and returns it on success. async fn create_workflow( &self, request: CreateObjectRequest, ) -> Result; /// Updates a workflow with the new data. The update may be rejected if a revision /// is specified _and_ that revision is not the current revision of the object in storage. async fn update_workflow( &self, workflow_id: WorkflowId, data: SerializedModel, revision: Option, ) -> Result>; /// Creates n generic string objects in a single graphql request. Use /// this rather than calling create_generic_string_object multiple times /// in a loop. async fn bulk_create_generic_string_objects( &self, owner: Owner, objects: &[BulkCreateGenericStringObjectsRequest], ) -> Result; async fn create_generic_string_object( &self, format: GenericStringObjectFormat, uniqueness_key: Option, request: CreateObjectRequest, ) -> Result; /// Creates a notebook on the server, returning the ID and revision of the object after /// creation. async fn create_notebook( &self, request: CreateObjectRequest, ) -> Result; /// Updates a notebook with the new title and data. The update may be rejected if a revision /// is specified _and_ that revision is not the current revision of the object in storage. async fn update_notebook( &self, notebook_id: cloud_object_models::NotebookId, title: Option, data: Option, revision: Option, ) -> Result>; async fn create_folder(&self, request: CreateObjectRequest) -> Result; async fn update_folder( &self, folder_id: FolderId, name: SerializedModel, ) -> Result>; async fn update_generic_string_object( &self, object_id: GenericStringObjectId, model: SerializedModel, revision: Option, ) -> Result>>; /// Sets the current editor of the notebook to be the logged in user async fn grab_notebook_edit_access( &self, notebook_id: cloud_object_models::NotebookId, ) -> Result; /// Sets the current editor of the notebook to be null async fn give_up_notebook_edit_access( &self, notebook_id: cloud_object_models::NotebookId, ) -> Result; /// Gets updates for all Warp Drive actions. async fn get_warp_drive_updates( &self, message_sender: Sender, stream_ready_sender: Sender<()>, ) -> Result<()>; async fn fetch_changed_objects( &self, objects_to_update: ObjectsToUpdate, force_refresh: bool, ) -> Result; async fn fetch_single_cloud_object(&self, id: ServerId) -> Result; // Transfers a notebook to the given owner async fn transfer_notebook_owner( &self, notebook_id: cloud_object_models::NotebookId, owner: Owner, ) -> Result; async fn transfer_workflow_owner(&self, workflow_id: WorkflowId, owner: Owner) -> Result; async fn transfer_generic_string_object_owner( &self, workflow_id: GenericStringObjectId, owner: Owner, ) -> Result; async fn trash_object(&self, id: ServerId) -> Result; async fn untrash_object(&self, id: ServerId) -> Result; async fn delete_object(&self, id: ServerId) -> Result; async fn empty_trash(&self, owner: Owner) -> Result; async fn move_object( &self, id: ServerId, folder_id: Option, owner: Owner, object_type: ObjectType, ) -> Result; async fn record_object_action( &self, id: ServerId, action_type: ObjectActionType, timestamp: DateTime, data: Option, ) -> Result; async fn leave_object(&self, id: ServerId) -> Result; async fn set_object_link_permissions( &self, object_id: ServerId, access_level: SharingAccessLevel, ) -> Result; async fn remove_object_link_permissions( &self, object_id: ServerId, ) -> Result; async fn add_object_guests( &self, object_id: ServerId, guest_emails: Vec, access_level: AccessLevel, ) -> Result; async fn update_object_guests( &self, object_id: ServerId, guest_emails: Vec, access_level: AccessLevel, ) -> Result; async fn remove_object_guest( &self, object_id: ServerId, guest: GuestIdentifier, ) -> Result; /// Fetches the last-used timestamps for all cloud environments. /// /// This is derived from `CloudEnvironment.lastTaskCreated.createdAt`, not `lastTaskRunTimestamp`, so that "Last used" reflects the most recently created task. /// /// Returns a map from environment UID to timestamp. async fn fetch_environment_last_task_run_timestamps( &self, ) -> Result>>; }