Create the example workspace in the browser build

This commit is contained in:
Gregory Schier
2026-09-15 16:32:20 -07:00
parent f827aae46d
commit 2c51fc92d6
12 changed files with 117 additions and 91 deletions
+104
View File
@@ -0,0 +1,104 @@
//! The example workspace offered during onboarding.
//!
//! Authored as JSON with `{{PLACEHOLDER}}` ids so requests can reference each
//! other (the chaining example names another request by id). Every placeholder
//! gets a fresh id per creation, so the example can be created more than once.
use crate::error::Result;
use crate::models::{Environment, Folder, HttpRequest, UpsertModelInfo, Workspace};
use crate::query_manager::QueryManager;
use crate::util::{BatchUpsertResult, UpdateSource};
const EXAMPLE_JSON: &str = include_str!("example_workspace.json");
const WORKSPACE_IDS: &[&str] = &["WORKSPACE"];
const ENVIRONMENT_IDS: &[&str] = &["ENV_BASE", "ENV_USER_2"];
const FOLDER_IDS: &[&str] = &[
"FOLDER_BASICS",
"FOLDER_VARIABLES",
"FOLDER_CHAINING",
"FOLDER_AUTH",
];
const REQUEST_IDS: &[&str] = &[
"RQ_LIST_POSTS",
"RQ_GET_POST",
"RQ_CREATE_POST",
"RQ_CURRENT_USER",
"RQ_LIST_USERS",
"RQ_POSTS_BY_FIRST_USER",
"RQ_TODOS",
"RQ_LOG_IN",
"RQ_ME",
];
pub fn create_example_workspace(
query_manager: &QueryManager,
source: &UpdateSource,
) -> Result<BatchUpsertResult> {
let resources = example_resources()?;
Ok(query_manager.with_tx(|tx| {
tx.batch_upsert(
resources.workspaces,
resources.environments,
resources.folders,
resources.http_requests,
resources.grpc_requests,
resources.websocket_requests,
source,
)
})?)
}
fn example_resources() -> Result<BatchUpsertResult> {
let mut json = EXAMPLE_JSON.to_string();
let placeholders = WORKSPACE_IDS
.iter()
.map(|p| (*p, Workspace::generate_id()))
.chain(ENVIRONMENT_IDS.iter().map(|p| (*p, Environment::generate_id())))
.chain(FOLDER_IDS.iter().map(|p| (*p, Folder::generate_id())))
.chain(REQUEST_IDS.iter().map(|p| (*p, HttpRequest::generate_id())));
for (placeholder, id) in placeholders {
json = json.replace(&format!("{{{{{placeholder}}}}}"), &id);
}
debug_assert!(!json.contains("{{"), "example workspace has an unmapped placeholder");
Ok(serde_json::from_str(&json)?)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::init_in_memory;
#[test]
fn every_placeholder_is_mapped() {
let json = serde_json::to_string(&example_resources().unwrap()).unwrap();
assert!(!json.contains("{{"), "unmapped placeholder in {json}");
}
#[test]
fn chained_request_references_a_created_request() {
let (query_manager, _blobs, _rx) = init_in_memory().unwrap();
let created = create_example_workspace(&query_manager, &UpdateSource::Background).unwrap();
assert_eq!(created.workspaces.len(), 1);
let workspace_id = &created.workspaces[0].id;
let db = query_manager.connect();
let requests = db.list_http_requests(workspace_id).unwrap();
assert_eq!(requests.len(), REQUEST_IDS.len());
assert_eq!(db.list_folders(workspace_id).unwrap().len(), FOLDER_IDS.len());
assert_eq!(db.list_environments(workspace_id).unwrap().len(), ENVIRONMENT_IDS.len());
let list_users = requests.iter().find(|r| r.name == "List users").unwrap();
let chained = requests.iter().find(|r| r.name == "Posts by the first user").unwrap();
let param = &chained.url_parameters[0].value;
assert!(param.contains(&format!("request='{}'", list_users.id)), "{param}");
}
#[test]
fn creating_twice_makes_two_workspaces() {
let (query_manager, _blobs, _rx) = init_in_memory().unwrap();
create_example_workspace(&query_manager, &UpdateSource::Background).unwrap();
create_example_workspace(&query_manager, &UpdateSource::Background).unwrap();
assert_eq!(query_manager.connect().list_workspaces().unwrap().len(), 2);
}
}
@@ -0,0 +1,181 @@
{
"workspaces": [
{
"id": "{{WORKSPACE}}",
"name": "Example Workspace",
"description": "A small tour of Yaak using the [Yaak Playground](https://yaak.run) API. Nothing here needs an account.\n\n1. Open **Getting Started / List posts** and press Send\n2. Open **Variables / Get the current user**, then switch the environment in the top-left to change which user it fetches\n3. Open **Chaining / Posts by the first user** to see how one request reads a value out of another request's response\n4. Open **Authentication / Get my profile** to see a login token used for you\n\nDelete this workspace whenever you're done with it."
}
],
"environments": [
{
"id": "{{ENV_BASE}}",
"workspaceId": "{{WORKSPACE}}",
"name": "Global Variables",
"parentModel": "workspace",
"public": true,
"variables": [
{ "name": "base_url", "value": "https://yaak.run", "enabled": true },
{ "name": "user_id", "value": "1", "enabled": true },
{ "name": "username", "value": "ada", "enabled": true },
{ "name": "password", "value": "yaak", "enabled": true }
]
},
{
"id": "{{ENV_USER_2}}",
"workspaceId": "{{WORKSPACE}}",
"name": "Another User",
"parentModel": "environment",
"public": true,
"color": "#c084fc",
"variables": [
{ "name": "user_id", "value": "2", "enabled": true },
{ "name": "username", "value": "grace", "enabled": true }
]
}
],
"folders": [
{
"id": "{{FOLDER_BASICS}}",
"workspaceId": "{{WORKSPACE}}",
"name": "Getting Started",
"sortPriority": 1,
"description": "Plain requests. Send one, then look through the response tabs: Body, Headers, Cookies, and Timeline."
},
{
"id": "{{FOLDER_VARIABLES}}",
"workspaceId": "{{WORKSPACE}}",
"name": "Variables",
"sortPriority": 2,
"description": "Anything wrapped in `${[ ... ]}` is a template. Variables come from the environment selected in the top-left, and sub-environments override the base values."
},
{
"id": "{{FOLDER_CHAINING}}",
"workspaceId": "{{WORKSPACE}}",
"name": "Chaining",
"sortPriority": 3,
"description": "The `response()` template function reads a value out of another request's response, sending that request first if it has to. Put the cursor in a URL and press Ctrl+Space to insert one."
},
{
"id": "{{FOLDER_AUTH}}",
"workspaceId": "{{WORKSPACE}}",
"name": "Authentication",
"sortPriority": 4,
"description": "This folder sets Bearer auth to the token from the **Log in** response. Every request inside inherits it, so the token lives in one place. When the last login is more than 50 minutes old, Yaak logs in again before sending.",
"authenticationType": "bearer",
"authentication": {
"token": "${[ response.body.path(request='{{RQ_LOG_IN}}', path='$.accessToken', behavior='ttl', ttl='3000') ]}"
}
}
],
"httpRequests": [
{
"id": "{{RQ_LIST_POSTS}}",
"workspaceId": "{{WORKSPACE}}",
"folderId": "{{FOLDER_BASICS}}",
"name": "List posts",
"method": "GET",
"url": "${[ base_url ]}/posts",
"sortPriority": 1,
"urlParameters": [{ "name": "limit", "value": "5", "enabled": true }],
"description": "Query parameters live in the Params tab. Try raising `limit`."
},
{
"id": "{{RQ_GET_POST}}",
"workspaceId": "{{WORKSPACE}}",
"folderId": "{{FOLDER_BASICS}}",
"name": "Get a post",
"method": "GET",
"url": "${[ base_url ]}/posts/1",
"sortPriority": 2,
"description": "Change the ID at the end of the URL and send again."
},
{
"id": "{{RQ_CREATE_POST}}",
"workspaceId": "{{WORKSPACE}}",
"folderId": "{{FOLDER_BASICS}}",
"name": "Create a post",
"method": "POST",
"url": "${[ base_url ]}/posts",
"sortPriority": 3,
"bodyType": "application/json",
"body": {
"text": "{\n \"title\": \"Hello from Yaak\",\n \"body\": \"Templates work inside bodies too\",\n \"userId\": ${[ user_id ]}\n}"
},
"headers": [{ "name": "Content-Type", "value": "application/json", "enabled": true }],
"description": "Posts you create are only visible to you, and go back to the sample data after an hour. Put the new ID into **Get a post** to read it back."
},
{
"id": "{{RQ_CURRENT_USER}}",
"workspaceId": "{{WORKSPACE}}",
"folderId": "{{FOLDER_VARIABLES}}",
"name": "Get the current user",
"method": "GET",
"url": "${[ base_url ]}/users/${[ user_id ]}",
"sortPriority": 1,
"description": "Switch to the **Another User** environment in the top-left and send again. Hover a template in the URL to see what it resolves to."
},
{
"id": "{{RQ_TODOS}}",
"workspaceId": "{{WORKSPACE}}",
"folderId": "{{FOLDER_VARIABLES}}",
"name": "List the user's todos",
"method": "GET",
"url": "${[ base_url ]}/users/${[ user_id ]}/todos",
"sortPriority": 2,
"urlParameters": [{ "name": "completed", "value": "false", "enabled": true }],
"description": "The same `user_id` variable, used in a different request. Disable the `completed` parameter to see finished todos too."
},
{
"id": "{{RQ_LIST_USERS}}",
"workspaceId": "{{WORKSPACE}}",
"folderId": "{{FOLDER_CHAINING}}",
"name": "List users",
"method": "GET",
"url": "${[ base_url ]}/users",
"sortPriority": 1
},
{
"id": "{{RQ_POSTS_BY_FIRST_USER}}",
"workspaceId": "{{WORKSPACE}}",
"folderId": "{{FOLDER_CHAINING}}",
"name": "Posts by the first user",
"method": "GET",
"url": "${[ base_url ]}/posts",
"sortPriority": 2,
"urlParameters": [
{
"name": "userId",
"value": "${[ response.body.path(request='{{RQ_LIST_USERS}}', path='$[0].id') ]}",
"enabled": true
}
],
"description": "The `userId` parameter is pulled from the **List users** response with the JSONPath `$[0].id`. If that request hasn't been sent yet, Yaak sends it first."
},
{
"id": "{{RQ_LOG_IN}}",
"workspaceId": "{{WORKSPACE}}",
"folderId": "{{FOLDER_AUTH}}",
"name": "Log in",
"method": "POST",
"url": "${[ base_url ]}/auth/token",
"sortPriority": 1,
"authenticationType": "none",
"bodyType": "application/json",
"body": {
"text": "{\n \"username\": \"${[ username ]}\",\n \"password\": \"${[ password ]}\"\n}"
},
"headers": [{ "name": "Content-Type", "value": "application/json", "enabled": true }],
"description": "Sends the username and password from the environment and returns a token. Its Auth tab is set to No Auth, so it doesn't try to use the token it's fetching."
},
{
"id": "{{RQ_ME}}",
"workspaceId": "{{WORKSPACE}}",
"folderId": "{{FOLDER_AUTH}}",
"name": "Get my profile",
"method": "GET",
"url": "${[ base_url ]}/auth/me",
"sortPriority": 2,
"description": "Open the Auth tab: it inherits Bearer auth from the folder, and there's nothing to copy and paste. Check the Timeline tab after sending to see the Authorization header that was sent. To try it as someone else, switch to **Another User** and send **Log in** again."
}
]
}
+1
View File
@@ -13,6 +13,7 @@ pub mod client_db;
mod connection_or_tx;
pub mod cookies;
pub mod error;
pub mod example;
pub mod export;
pub mod migrate;
pub mod models;