373 lines
12 KiB
Rust
373 lines
12 KiB
Rust
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<Utc>,
|
|
pub actions: Vec<ObjectAction>,
|
|
}
|
|
|
|
#[derive(Clone, Debug, PartialEq)]
|
|
pub enum ObjectActionSubtype {
|
|
SingleAction {
|
|
timestamp: DateTime<Utc>,
|
|
processed_at_timestamp: Option<DateTime<Utc>>,
|
|
data: Option<String>,
|
|
pending: bool,
|
|
},
|
|
BundledActions {
|
|
count: i32,
|
|
oldest_timestamp: DateTime<Utc>,
|
|
latest_timestamp: DateTime<Utc>,
|
|
latest_processed_at_timestamp: DateTime<Utc>,
|
|
},
|
|
}
|
|
|
|
#[derive(Default)]
|
|
pub struct InitialLoadResponse {
|
|
pub updated_notebooks: Vec<ServerNotebook>,
|
|
pub deleted_notebooks: Vec<cloud_object_models::NotebookId>,
|
|
pub updated_workflows: Vec<ServerWorkflow>,
|
|
pub deleted_workflows: Vec<WorkflowId>,
|
|
pub updated_folders: Vec<ServerFolder>,
|
|
pub deleted_folders: Vec<FolderId>,
|
|
pub updated_generic_string_objects:
|
|
HashMap<GenericStringObjectFormat, Vec<Box<dyn ServerObject>>>,
|
|
pub deleted_generic_string_objects: Vec<GenericStringObjectId>,
|
|
pub user_profiles: Vec<UserProfileWithUID>,
|
|
pub action_histories: Vec<ObjectActionHistory>,
|
|
pub mcp_gallery: Vec<MCPGalleryTemplate>,
|
|
}
|
|
|
|
pub struct GetCloudObjectResponse {
|
|
pub object: ServerCloudObject,
|
|
pub descendants: Vec<ServerCloudObject>,
|
|
pub action_histories: Vec<ObjectActionHistory>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
#[allow(clippy::enum_variant_names)]
|
|
pub enum ObjectUpdateMessage {
|
|
ObjectMetadataChanged {
|
|
metadata: ServerMetadata,
|
|
},
|
|
ObjectPermissionsChanged,
|
|
ObjectPermissionsChangedV2 {
|
|
object_uid: ServerId,
|
|
permissions: ServerPermissions,
|
|
user_profiles: Vec<UserProfileWithUID>,
|
|
},
|
|
ObjectContentChanged {
|
|
server_object: Box<ServerCloudObject>,
|
|
last_editor: Option<UserProfileWithUID>,
|
|
},
|
|
ObjectDeleted {
|
|
object_uid: ServerId,
|
|
},
|
|
ObjectActionOccurred {
|
|
history: ObjectActionHistory,
|
|
},
|
|
TeamMembershipsChanged,
|
|
AmbientTaskUpdated {
|
|
task_id: String,
|
|
timestamp: DateTime<Utc>,
|
|
},
|
|
}
|
|
|
|
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<UserProfileWithUID>,
|
|
}
|
|
|
|
#[derive(Clone, Debug)]
|
|
pub enum ObjectMetadataUpdateResult {
|
|
Success { metadata: Box<ServerMetadata> },
|
|
Failure,
|
|
}
|
|
|
|
pub enum ObjectDeleteResult {
|
|
Success { deleted_ids: Vec<SyncId> },
|
|
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<CreateCloudObjectResult>;
|
|
|
|
/// 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<Revision>,
|
|
) -> Result<UpdateCloudObjectResult<ServerWorkflow>>;
|
|
|
|
/// 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<BulkCreateCloudObjectResult>;
|
|
|
|
async fn create_generic_string_object(
|
|
&self,
|
|
format: GenericStringObjectFormat,
|
|
uniqueness_key: Option<GenericStringObjectUniqueKey>,
|
|
request: CreateObjectRequest,
|
|
) -> Result<CreateCloudObjectResult>;
|
|
|
|
/// Creates a notebook on the server, returning the ID and revision of the object after
|
|
/// creation.
|
|
async fn create_notebook(
|
|
&self,
|
|
request: CreateObjectRequest,
|
|
) -> Result<CreateCloudObjectResult>;
|
|
|
|
/// 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<String>,
|
|
data: Option<SerializedModel>,
|
|
revision: Option<Revision>,
|
|
) -> Result<UpdateCloudObjectResult<ServerNotebook>>;
|
|
|
|
async fn create_folder(&self, request: CreateObjectRequest) -> Result<CreateCloudObjectResult>;
|
|
|
|
async fn update_folder(
|
|
&self,
|
|
folder_id: FolderId,
|
|
name: SerializedModel,
|
|
) -> Result<UpdateCloudObjectResult<ServerFolder>>;
|
|
|
|
async fn update_generic_string_object(
|
|
&self,
|
|
object_id: GenericStringObjectId,
|
|
model: SerializedModel,
|
|
revision: Option<Revision>,
|
|
) -> Result<UpdateCloudObjectResult<Box<dyn ServerObject>>>;
|
|
|
|
/// 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<ServerMetadata>;
|
|
|
|
/// 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<ServerMetadata>;
|
|
|
|
/// Gets updates for all Warp Drive actions.
|
|
async fn get_warp_drive_updates(
|
|
&self,
|
|
message_sender: Sender<ObjectUpdateMessage>,
|
|
stream_ready_sender: Sender<()>,
|
|
) -> Result<()>;
|
|
|
|
async fn fetch_changed_objects(
|
|
&self,
|
|
objects_to_update: ObjectsToUpdate,
|
|
force_refresh: bool,
|
|
) -> Result<InitialLoadResponse>;
|
|
|
|
async fn fetch_single_cloud_object(&self, id: ServerId) -> Result<GetCloudObjectResponse>;
|
|
|
|
// Transfers a notebook to the given owner
|
|
async fn transfer_notebook_owner(
|
|
&self,
|
|
notebook_id: cloud_object_models::NotebookId,
|
|
owner: Owner,
|
|
) -> Result<bool>;
|
|
|
|
async fn transfer_workflow_owner(&self, workflow_id: WorkflowId, owner: Owner) -> Result<bool>;
|
|
|
|
async fn transfer_generic_string_object_owner(
|
|
&self,
|
|
workflow_id: GenericStringObjectId,
|
|
owner: Owner,
|
|
) -> Result<bool>;
|
|
|
|
async fn trash_object(&self, id: ServerId) -> Result<bool>;
|
|
|
|
async fn untrash_object(&self, id: ServerId) -> Result<ObjectMetadataUpdateResult>;
|
|
|
|
async fn delete_object(&self, id: ServerId) -> Result<ObjectDeleteResult>;
|
|
|
|
async fn empty_trash(&self, owner: Owner) -> Result<ObjectDeleteResult>;
|
|
|
|
async fn move_object(
|
|
&self,
|
|
id: ServerId,
|
|
folder_id: Option<FolderId>,
|
|
owner: Owner,
|
|
object_type: ObjectType,
|
|
) -> Result<bool>;
|
|
|
|
async fn record_object_action(
|
|
&self,
|
|
id: ServerId,
|
|
action_type: ObjectActionType,
|
|
timestamp: DateTime<Utc>,
|
|
data: Option<String>,
|
|
) -> Result<ObjectActionHistory>;
|
|
|
|
async fn leave_object(&self, id: ServerId) -> Result<ObjectDeleteResult>;
|
|
|
|
async fn set_object_link_permissions(
|
|
&self,
|
|
object_id: ServerId,
|
|
access_level: SharingAccessLevel,
|
|
) -> Result<ObjectPermissionUpdateResult>;
|
|
|
|
async fn remove_object_link_permissions(
|
|
&self,
|
|
object_id: ServerId,
|
|
) -> Result<ObjectPermissionUpdateResult>;
|
|
|
|
async fn add_object_guests(
|
|
&self,
|
|
object_id: ServerId,
|
|
guest_emails: Vec<String>,
|
|
access_level: AccessLevel,
|
|
) -> Result<ObjectPermissionsUpdateData>;
|
|
|
|
async fn update_object_guests(
|
|
&self,
|
|
object_id: ServerId,
|
|
guest_emails: Vec<String>,
|
|
access_level: AccessLevel,
|
|
) -> Result<ServerPermissions>;
|
|
|
|
async fn remove_object_guest(
|
|
&self,
|
|
object_id: ServerId,
|
|
guest: GuestIdentifier,
|
|
) -> Result<ServerPermissions>;
|
|
|
|
/// 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<HashMap<String, DateTime<Utc>>>;
|
|
}
|