Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
419d5ac
Create onboarding pages
tristantr Oct 3, 2025
34fca0b
add the users/onboarding route
tristantr Oct 16, 2025
ebbfc1f
make the onboarding not available in oss
tristantr Oct 16, 2025
85766a8
Front end for onboarding form for cloud users
tristantr Oct 17, 2025
76b8266
WIP: Save current progress on first-timers onboarding feature
tristantr Oct 17, 2025
8bb5b1f
Put back the cloud.ts file like before
tristantr Oct 20, 2025
b40ed5f
Add the onboading form when cloud users connect for the first time
tristantr Oct 20, 2025
77de827
Add check to show onboarding only for first time users on cloud
tristantr Oct 20, 2025
537ea18
Add submit_onboarding_data route in the backend
tristantr Oct 20, 2025
93dc75e
Remove useless cookie code
tristantr Oct 20, 2025
6868d98
Remove useless function
tristantr Oct 20, 2025
ad0bbc8
Remove the unused onMount import
tristantr Oct 20, 2025
f8cf6a7
Add SQLx query cache for first_time_user field
tristantr Oct 20, 2025
62ad25c
Allow dead_code for OnboardingData in OSS version
tristantr Oct 20, 2025
d9b988e
Merge remote-tracking branch 'origin/main' into tl/first-timers-onboa…
wendrul Oct 21, 2025
bda81a7
Point to the latest ee hash
tristantr Oct 21, 2025
8e8f4a0
Merge branch 'main' into tl/first-timers-onboarding2
diegoimbert Oct 21, 2025
fe490e6
Add maxlength on use_case text input
tristantr Oct 21, 2025
afc1f01
Collect from the frontend only inputted data from the users - touche_…
tristantr Oct 21, 2025
fcec202
write latest ee ref
tristantr Oct 21, 2025
17adf99
Remove checkFirstTimeSetup() call if cloud instance
tristantr Oct 21, 2025
977aa6a
Remove silent error
tristantr Oct 21, 2025
85e488a
Remove magical number from onboarding screen navigation
tristantr Oct 21, 2025
7bc9de2
remove unused databse field for login query
tristantr Oct 21, 2025
2bff422
Merge branch 'main' into tl/first-timers-onboarding2
tristantr Oct 27, 2025
1f1082f
Add first_time_user check in loadUser()
tristantr Oct 27, 2025
dc59dbc
Add input for the Other answer
tristantr Oct 27, 2025
4d88007
Update ee hash
tristantr Oct 27, 2025
d4cc0b1
Remove autofocus
tristantr Oct 27, 2025
2d50d39
Improve the submit onboarding data function checks
tristantr Oct 27, 2025
3282e25
Fix feature flags
tristantr Oct 27, 2025
c9236bd
Add latest ee hash
tristantr Oct 27, 2025
7922f23
Update to latest hash
tristantr Oct 27, 2025
22a8e9e
Update to last ee hash
tristantr Oct 27, 2025
69a0dd0
Merge remote-tracking branch 'origin/main' into tl/first-timers-onboa…
hugocasa Oct 30, 2025
db20e74
nits
hugocasa Oct 30, 2025
51616d5
simplify feature flag logic
hugocasa Oct 30, 2025
8f99fe1
nit
hugocasa Oct 30, 2025
24caaac
Merge branch 'main' into tl/first-timers-onboarding2
hugocasa Oct 30, 2025
9da59fe
Update ee-repo-ref.txt
windmill-internal-app[bot] Oct 30, 2025
a0e6612
nits
hugocasa Oct 30, 2025
9d73761
update ref
hugocasa Oct 30, 2025
f11e78d
Merge branch 'main' into tl/first-timers-onboarding2
rubenfiszel Oct 30, 2025
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

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion backend/ee-repo-ref.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
c8d57987b72fd15f2e3b8d7f6501edaac8235965
e30b6ea6212d9d3f3b06b5aa8b690a7bf2940877
31 changes: 31 additions & 0 deletions backend/windmill-api/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -554,6 +554,34 @@ paths:
items:
$ref: "#/components/schemas/ExportedUser"

/users/onboarding:
post:
summary: Submit user onboarding data
operationId: submitOnboardingData
tags:
- user
requestBody:
required: true
content:
application/json:
schema:
type: object
required:
- touch_point
- use_case
properties:
touch_point:
type: string
use_case:
type: string
responses:
'200':
description: Onboarding data submitted successfully
content:
application/json:
schema:
type: string

/w/{workspace}/users/delete/{username}:
delete:
summary: delete user (require admin privilege)
Expand Down Expand Up @@ -18470,12 +18498,15 @@ components:
type: string
operator_only:
type: boolean
first_time_user:
type: boolean

required:
- email
- login_type
- super_admin
- verified
- first_time_user

Flow:
allOf:
Expand Down
42 changes: 17 additions & 25 deletions backend/windmill-api/src/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ pub fn global_service() -> Router {
.route("/leave_instance", post(leave_instance))
.route("/export", get(export_global_users))
.route("/overwrite", post(overwrite_global_users))
.route("/onboarding", post(submit_onboarding_data))

// .route("/list_invite_codes", get(list_invite_codes))
// .route("/create_invite_code", post(create_invite_code))
Expand Down Expand Up @@ -286,6 +287,7 @@ pub struct GlobalUserInfo {
username: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
operator_only: Option<bool>,
first_time_user: bool,
}

#[derive(Serialize, Debug)]
Expand Down Expand Up @@ -533,7 +535,7 @@ async fn list_users_as_super_admin(
GlobalUserInfo,
"WITH active_users AS (SELECT distinct username as email FROM audit WHERE timestamp > NOW() - INTERVAL '1 month' AND (operation = 'users.login' OR operation = 'oauth.login' OR operation = 'users.token.refresh')),
authors as (SELECT distinct email FROM usr WHERE usr.operator IS false)
SELECT email, email NOT IN (SELECT email FROM authors) as operator_only, login_type::text, verified, super_admin, devops, name, company, username
SELECT email, email NOT IN (SELECT email FROM authors) as operator_only, login_type::text, verified, super_admin, devops, name, company, username, first_time_user
FROM password
WHERE email IN (SELECT email FROM active_users)
ORDER BY super_admin DESC, devops DESC
Expand All @@ -546,7 +548,7 @@ async fn list_users_as_super_admin(
} else {
sqlx::query_as!(
GlobalUserInfo,
"SELECT email, login_type::text, verified, super_admin, devops, name, company, username, NULL::bool as operator_only FROM password ORDER BY super_admin DESC, devops DESC, email LIMIT \
"SELECT email, login_type::text, verified, super_admin, devops, name, company, username, NULL::bool as operator_only, first_time_user FROM password ORDER BY super_admin DESC, devops DESC, email LIMIT \
$1 OFFSET $2",
per_page as i32,
offset as i32
Expand Down Expand Up @@ -749,7 +751,7 @@ async fn global_whoami(
) -> JsonResult<GlobalUserInfo> {
let user = sqlx::query_as!(
GlobalUserInfo,
"SELECT email, login_type::TEXT, super_admin, devops, verified, name, company, username, NULL::bool as operator_only FROM password WHERE \
"SELECT email, login_type::TEXT, super_admin, devops, verified, name, company, username, NULL::bool as operator_only, first_time_user FROM password WHERE \
email = $1",
email
)
Expand All @@ -770,6 +772,7 @@ async fn global_whoami(
company: None,
username: None,
operator_only: None,
first_time_user: false,
}))
} else {
Err(user.unwrap_err())
Expand Down Expand Up @@ -1643,6 +1646,14 @@ async fn create_user(
crate::users_oss::create_user(authed, db, webhook, argon2, nu).await
}

async fn submit_onboarding_data(
authed: ApiAuthed,
Extension(db): Extension<DB>,
Json(data): Json<crate::users_oss::OnboardingData>,
) -> Result<String> {
crate::users_oss::submit_onboarding_data(authed, Extension(db), Json(data)).await
}

/// Internal helper for updating workspace user permissions - used by both API and system operations
pub async fn update_workspace_user_internal(
w_id: &str,
Expand Down Expand Up @@ -1858,15 +1869,15 @@ async fn login(
username_override: None,
token_prefix: None,
};
let email_w_h: Option<(String, String, bool, bool)> = sqlx::query_as(
"SELECT email, password_hash, super_admin, first_time_user FROM password WHERE email = $1 AND login_type = \
let email_w_h: Option<(String, String, bool)> = sqlx::query_as(
"SELECT email, password_hash, super_admin FROM password WHERE email = $1 AND login_type = \
'password'",
)
.bind(&email)
.fetch_optional(&mut *tx)
.await?;

if let Some((email, hash, super_admin, first_time_user)) = email_w_h {
if let Some((email, hash, super_admin)) = email_w_h {
let parsed_hash =
PasswordHash::new(&hash).map_err(|e| Error::internal_err(e.to_string()))?;
if argon2
Expand All @@ -1885,25 +1896,6 @@ async fn login(
.await?;
Err(Error::BadRequest("Invalid login".to_string()))
} else {
if first_time_user {
sqlx::query_scalar!(
"UPDATE password SET first_time_user = false WHERE email = $1",
&email
)
.execute(&mut *tx)
.await?;
let mut c = Cookie::new("first_time", "1");
if let Some(domain) = COOKIE_DOMAIN.as_ref() {
c.set_domain(domain);
}
c.set_secure(false);
c.set_expires(time::OffsetDateTime::now_utc() + time::Duration::minutes(15));
c.set_http_only(false);
c.set_path("/");

cookies.add(c);
}

let token = create_session_token(&email, super_admin, &mut tx, cookies).await?;

let audit_author = AuditAuthor {
Expand Down
Loading