The onboarding scan started on its own, discarded what it found, claimed to read email and calendars, simulated progress for everyone who could not scan, and its project creation always failed (wrong Space fields and a space lookup by title across tenants). Discovery now asks first and creates nothing without a choice: - The scan step appears only for the deployment owner when ONBOARDING_SCAN_HOME is set; everyone else skips it. The simulated scan is gone. - It shows the folder and asks. Only directory listings are read: git projects up to four levels deep, their languages and a few tools (Docker, GitHub Actions, Terraform). Symlinks are never followed and each chosen folder is resolved again before use, so nothing outside the folder is reached. Results stay with the account that scanned. - Found projects are a checklist (none preselected); found languages and tools are facts (preselected). "Add" creates one project space per chosen project with createSpaceForMember and wireProjectWorkspace in the user's own organization, reusing the user's own space already linked to that folder, and saves the chosen facts as onboarding facts under the profile lock and fact limit. - When a connection becomes connected (direct connect, authorization round trip, or catalog reconciliation), "Uses <App>" is remembered as a tools fact from the connector. Revoking keeps it. The E2E harness now points ONBOARDING_SCAN_HOME at a fixture folder and starts without a deployment owner, so owner-only onboarding steps appear only in specs that take the seat through /__e2e/deployment-owner. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MRtGG9ARfqqhma3t85Ro2u
51 lines
1.9 KiB
TypeScript
51 lines
1.9 KiB
TypeScript
import { FEATURED_CONNECTOR_LABELS, matchFeaturedConnectorId } from "@rakazo/core";
|
|
import type { PrismaClient } from "@rakazo/db";
|
|
import { updateUserProfile } from "@rakazo/db";
|
|
import { getLogger } from "@rakazo/logging";
|
|
|
|
/**
|
|
* The app's own name: a featured app's label, otherwise the label of the user's first account for
|
|
* it (later accounts may be named "Gmail 2" or renamed).
|
|
*/
|
|
async function connectedAppName(
|
|
prisma: PrismaClient,
|
|
connection: { userId: string; connectorId: string; provider: string },
|
|
): Promise<string | null> {
|
|
const featured = matchFeaturedConnectorId(connection.provider);
|
|
if (featured) return FEATURED_CONNECTOR_LABELS[featured];
|
|
const first = await prisma.connection.findFirst({
|
|
where: {
|
|
userId: connection.userId,
|
|
connectorId: connection.connectorId,
|
|
provider: connection.provider,
|
|
},
|
|
orderBy: [{ createdAt: "asc" }, { id: "asc" }],
|
|
select: { displayName: true },
|
|
});
|
|
const name = first?.displayName.trim().slice(0, 80);
|
|
return name || null;
|
|
}
|
|
|
|
/**
|
|
* Remembers "Uses <App>" in the user's profile when one of their connections becomes connected.
|
|
* Revoking the connection keeps the fact; the user removes it in About you. Best effort: the
|
|
* connection itself has already succeeded, so a full profile or a database error only logs.
|
|
*/
|
|
export async function recordConnectedApp(
|
|
prisma: PrismaClient,
|
|
connection: { userId: string; connectorId: string; provider: string },
|
|
): Promise<void> {
|
|
try {
|
|
const name = await connectedAppName(prisma, connection);
|
|
if (!name) return;
|
|
await updateUserProfile(prisma, connection.userId, {
|
|
addFacts: [{ category: "tools", value: `Uses ${name}`, source: "connector" }],
|
|
});
|
|
} catch (error) {
|
|
getLogger().error("connected app fact was not recorded", error, {
|
|
connectorId: connection.connectorId,
|
|
provider: connection.provider,
|
|
});
|
|
}
|
|
}
|