Commit Graph

6 Commits

Author SHA1 Message Date
Raphaël Bosi f15fabb5d9 Enrich workspace company via People Data Labs during onboarding (#23199)
https://github.com/user-attachments/assets/fb9001c4-195d-4735-898b-07ccbab01677


During onboarding, the workspace creator's work-email domain is enriched
through People Data Labs and stored client-side. The stacked
workspace-setup PR folds it into the invisible prompt that kicks off the
setup chat, so the assistant knows the company from its first reply.

- New `enrichWorkspaceCompany` mutation: throttled, creator-only, work
domains only. Off by default: requires the
`IS_WORKSPACE_COMPANY_ENRICHMENT_ENABLED` instance config variable
(default false), a `PEOPLE_DATA_LABS_API_KEY`, and the
`IS_ONBOARDING_AI_CHAT_ENABLED` workspace feature flag (the enrichment
only feeds the AI-chat workspace setup). Every attempt past the throttle
is recorded per workspace in a `keyValuePair`.
- The frontend fetches once during onboarding and stores a matched
result in localStorage. This PR does not deliver it to the model: the
hidden-message plumbing it adds (`isHidden` on `agentMessage`, excluded
from the chat UI, thread ranking and the admin transcript, included in
the model conversation) is what the stacked workspace-setup PR uses to
send the context and the setup prompt as one invisible first message.
- The PDL wire protocol (base URL, wire types, envelope parsing, error
extraction) is kept as a small self-contained copy inside the server
`company-enrichment` module. The standalone people-data-labs app keeps
its own copy; the two are intentionally not shared, since the app and
the core-engine usage are expected to evolve independently.
- `WorkspaceCompanyEnrichment` lives in `twenty-shared/workspace` so
server and front share one shape.

## Flow

```mermaid
flowchart LR
  effect[Onboarding effect] -- enrichWorkspaceCompany --> checks{creator + work domain?}
  checks -- no --> unavailable[unavailable]
  checks -- yes --> throttle{throttle 10/h/workspace}
  throttle -- limited --> transient[transientError]
  throttle -- ok --> pdl[PDL GET /company/enrich]
  pdl --> log[(keyValuePair attempt log)]
  pdl --> matched[matched]
  matched --> storage[(localStorage)]
  storage -- consumed by the stacked workspace-setup PR --> kickoff[hidden kickoff prompt]
```

1. **Onboarding effect** — mounted app-wide, fires once per session
while onboarding is in progress (before workspace activation), guarded
by a sessionStorage attempt flag and the cached value.
2. **enrichWorkspaceCompany** — metadata-schema mutation returning a
typed `WorkspaceCompanyEnrichmentResult` (`outcome` enum
`matched`/`unavailable`/`transientError` + `enrichment` JSON).
3. **Creator + work domain checks** — only the workspace's earliest
user, only non-consumer email domains, only when the config flag, API
key and `IS_ONBOARDING_AI_CHAT_ENABLED` workspace flag are all on;
anything else returns `unavailable` without consuming throttle quota.
4. **Throttle** — token bucket, 10 requests/hour per workspace, the sole
cost bound on PDL calls; when limited the mutation returns
`transientError` instead of surfacing an error.
5. **PDL call** — `GET /v5/company/enrich` with `website` +
`min_likelihood` per the PDL spec; body-level statuses win over HTTP
ones, 408/429/5xx map to `transientError`, other failures to
`unavailable`. Every attempt past the throttle is recorded (`domain`,
the pre-collapse PDL `outcome`, `httpStatus`/`message` when present,
`attemptedAt`) in a workspace-scoped `keyValuePair`.
6. **matched** — the PDL payload is mapped to
`WorkspaceCompanyEnrichment` through the same sanitizer as client input
(all fields length-capped and control-character-stripped; summary 600
chars, 8 tags max) and returned.
7. **localStorage** — the frontend stores only a matched enrichment and
never refetches it, making it the only cache; cleared on sign-out.
Non-matched outcomes are not persisted; a sessionStorage flag caps
retries at one attempt per browser session.
8. **Delivery** — out of scope here. The stacked workspace-setup PR
reads the stored enrichment and combines it with the data-model proposal
prompt into a single hidden `USER` message when the setup chat starts;
it is never injected into the system prompt.

Reviewer notes: sending the creator's email domain to a third party at
signup is not yet disclosed in onboarding copy.

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/23199?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
2026-07-28 13:29:43 +00:00
Charles Bochet 8e03921372 Add CREATED workspace activation status (read path + enum migration) (#22904)
## Context

Since v2 onboarding (#22303), workspaces are activated **before** the
billing plan step (now the last onboarding step). Users abandoning at
the plan step leave ACTIVE workspaces with a Stripe customer but no
subscription (~60–110/day on cloud, 935+ so far), and no cleanup
mechanism ever touches them: billing webhooks never fire (no
subscription), the suspended-workspaces cron only handles SUSPENDED, the
onboarding cron only handles PENDING_CREATION/ONGOING_CREATION.

Target lifecycle (across two PRs): `PENDING_CREATION → ONGOING_CREATION
→ CREATED → ACTIVE → SUSPENDED → deleted`.

**`CREATED`** = the workspace schema is provisioned but onboarding is
not complete — no billing subscription yet. It is **not** considered
active:

| Concern | CREATED behavior |
|---|---|
| Sign-in / invited teammates joining | allowed (invite-team step
precedes the plan step) |
| Member + metadata loading (app shell) | allowed (user must finish
onboarding) |
| Permissions | real permission checks (no PENDING-style bypass) |
| Version upgrades / workspace migrations | **included** (schema must
not drift) |
| Messaging/calendar/workflow/etc. crons | **excluded** — no background
processing until a plan is chosen |
| PLAN_REQUIRED onboarding lock | unchanged (still derived from
subscription existence) |

## What this PR does (read path only)

The enum addition ships as a **slow** instance command, which can run
after deploy — so nothing in this PR ever **writes** `CREATED`. The
write path (setting it at activation, the cleanup sweep, the backfill of
the existing zombie cohort) is a follow-up PR that ships once this
migration has run everywhere.

- **twenty-shared**: `CREATED` enum value;
`PROVISIONED_WORKSPACE_ACTIVATION_STATUSES` + `isWorkspaceProvisioned`
("schema exists": CREATED | ACTIVE | SUSPENDED), replacing
`isWorkspaceActiveOrSuspended` — all call sites (server member loading,
access-token workspace-member lookup, front metadata-store gates) meant
"has schema/members".
- **Slow instance command** (2.22.0): swaps
`core.workspace_activationStatus_enum` using the
rename→recreate→alter-column idiom. The CHECK constraints on
`core.workspace` embed casts to the enum type and would break the swap —
the command captures them from `pg_constraint`, drops them, swaps the
type, and restores them.
- **Pre-migration-safe queries**: Postgres rejects `IN ('CREATED', ...)`
when the enum value does not exist yet — even for reads, and the
instance-command runner itself queries provisioned workspaces before
migrating (a fresh database could never initialize). All
provisioned-status filters go through a new `activationStatusIn` util
comparing on `"activationStatus"::text`, valid before and after the
migration.
- **Upgrade path**: workspace iterator, command runner, upgrade-status
and workspace-version services iterate CREATED workspaces. Since they
now cover more than ACTIVE/SUSPENDED, the stale names were renamed to
`ProvisionedWorkspaceCommandRunner`, `hasProvisionedWorkspaces`,
`getProvisionedWorkspaceIds`, `loadProvisionedWorkspaces` (the
mechanical import rename in old version-command dirs is why this PR
carries the `ci:allow-previous-version-upgrade-mutation` label).
- **Sign-in**: `throwIfWorkspaceIsNotReadyForSignInUp` accepts CREATED
so invited members can join during onboarding (join authorization itself
is unchanged — enforced upstream in `checkAccessForSignIn`);
`activateWorkspace` idempotent-retry accepts CREATED as a terminal
state.
- **Transitions out of CREATED** (only write ACTIVE — safe to ship now,
dead until the write path lands): the Stripe webhook reactivation branch
also promotes CREATED, and `syncSubscriptionToDatabase` promotes
synchronously; both gated on
`WORKSPACE_ACTIVATING_SUBSCRIPTION_STATUSES` (Active/Trialing —
extracted from `shouldReactivateWorkspace`, behavior-preserving) so an
`incomplete` subscription created by the payment-intent flow before
payment never promotes the workspace.
- Deliberately untouched: all background crons, permission guards, JWT
strategy, PLAN_REQUIRED logic, admin panel (renders the raw status
string).

## Follow-up PR (after this migration has run)
1. `activateWorkspace` sets `hasWorkspaceAnySubscription ? ACTIVE :
CREATED` (billing disabled → always ACTIVE, self-hosted unchanged).
2. Cleanup: suspend CREATED workspaces older than N days (config var),
handing them to the existing suspended pipeline (warn → soft-delete →
destroy).
3. Backfill: cloud-only slow command moving ACTIVE workspaces with no
billingSubscription row (created since Jul 1) to CREATED.

## Verification
- Migration exercised against a real database via the command class: up
→ down → up; `enum_range` and `pg_get_constraintdef` checked after each
step (constraints restored against the new type, `DEFAULT 'INACTIVE'`
preserved).
- Pre-migration safety exercised for real: with the migration rolled
back (enum without CREATED), `run-instance-commands` — the exact
fresh-database CI path that failed before the `::text` fix — completes
cleanly.
- End-to-end with a workspace manually set to CREATED and the branch
server+front running: sign-in issues tokens, `currentUser` loads
workspaceMember(s), the full app loads with no console errors; GraphQL
returns `activationStatus: CREATED`.
- Workspace creation ran end-to-end locally in **both billing modes** on
this branch:
- billing disabled: signup → workspace creation → ACTIVE immediately →
onboarding completes with no plan step → app loads (unchanged behavior);
- billing enabled (Stripe test mode): signup creates the Stripe customer
eagerly → activation ends ACTIVE → subscription-less workspace is pinned
to the plan-required page → no-card trial checkout creates a `trialing`
subscription via `createDirectSubscription`/`syncSubscriptionToDatabase`
→ app loads.
- `twenty-shared` unit tests, server specs on touched services,
`lint:diff-with-main` and `typecheck` for shared/server/front all green;
full CI green.
2026-07-15 17:03:17 +02:00
Paul Rastoin 8b2a90dea1 [FEAT] Generate barrel export named modules and types (#11110)
# Introduction
In this PR using the Ts AST dynamically compute what to export,
gathering non-runtime types and interface in an `export type`

[Export type TypeScript
documentation](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html)

From
```ts
// index.ts
export * from "submodule"
```
To
```ts
export type { SomeType } from "submodule";
export { SomeFunction, SomeConst } from "submodule";
```


close https://github.com/twentyhq/core-team-issues/issues/644

## Motivations
- Most explicit and maintainable
- Best for tree-shaking
- Clear dependency tracking
- Prevents name collisions

## Important note
Please keep in mind that I will create, very soon, a dedicated
`generate-barrel` package in our yarn workspaces in order to:
- Make it reusable for twenty-ui
- Split in several files
- Setup lint + tsconfig
- Add tests

## Conclusion
As usual any suggestions are more than welcomed !
2025-03-24 15:06:16 +01:00
Paul Rastoin 9ad8287dbc [REFACTOR] twenty-shared multi barrel and CJS/ESM build with preconstruct (#11083)
# Introduction

In this PR we've migrated `twenty-shared` from a `vite` app
[libary-mode](https://vite.dev/guide/build#library-mode) to a
[preconstruct](https://preconstruct.tools/) "atomic" application ( in
the future would like to introduce preconstruct to handle of all our
atomic dependencies such as `twenty-emails` `twenty-ui` etc it will be
integrated at the monorepo's root directly, would be to invasive in the
first, starting incremental via `twenty-shared`)

For more information regarding the motivations please refer to nor:
- https://github.com/twentyhq/core-team-issues/issues/587
-
https://github.com/twentyhq/core-team-issues/issues/281#issuecomment-2630949682

close https://github.com/twentyhq/core-team-issues/issues/589
close https://github.com/twentyhq/core-team-issues/issues/590

## How to test
In order to ease the review this PR will ship all the codegen at the
very end, the actual meaning full diff is `+2,411 −114`
In order to migrate existing dependent packages to `twenty-shared` multi
barrel new arch you need to run in local:
```sh
yarn tsx packages/twenty-shared/scripts/migrateFromSingleToMultiBarrelImport.ts && \
npx nx run-many -t lint --fix -p twenty-front twenty-ui twenty-server twenty-emails twenty-shared twenty-zapier
```
Note that `migrateFromSingleToMultiBarrelImport` is idempotent, it's atm
included in the PR but should not be merged. ( such as codegen will be
added before merging this script will be removed )

## Misc
- related opened issue preconstruct
https://github.com/preconstruct/preconstruct/issues/617

## Closed related PR
- https://github.com/twentyhq/twenty/pull/11028
- https://github.com/twentyhq/twenty/pull/10993
- https://github.com/twentyhq/twenty/pull/10960

## Upcoming enhancement: ( in others dedicated PRs )
- 1/ refactor generate barrel to export atomic module instead of `*`
- 2/ generate barrel own package with several files and tests
- 3/ Migration twenty-ui the same way
- 4/ Use `preconstruct` at monorepo global level

## Conclusion
As always any suggestions are welcomed !
2025-03-22 19:16:06 +01:00
martmull 736b845c98 335 workflow implement workflow cron triggers backend (#9988)
[Backend side] Add cron triggers to workflow
Closes https://github.com/twentyhq/core-team-issues/issues/335
2025-02-05 11:02:49 +00:00
Charles Bochet f545bd1c40 Treat suspended workspace as workspaces that need to be synced (#9669)
In this PR:
- migrate WorkspaceActivationStatus to twenty-shared (and update case to
make FE and BE consistent)
- introduce isWorkspaceActiveOrSuspended in twenty-shared
- refactor the code to use it (when we fetch data on the FE, we want to
keep SUSPENDED workspace working + when we sync workspaces we want it
too)
2025-01-16 15:01:04 +01:00