Map TFT useCase to partners need on opportunity import (#22054)
## Summary
- Accept optional `useCase` in the TFT → partners
`import-opportunity-from-tft` webhook payload
- Map it to `Opportunity.need` on create (TFT Use Case → partners Needs)
- Add unit tests for happy path and null `useCase` handling
- Bump twenty-partners to 1.1.4 (patch)
## Test plan
- [x] `yarn test:unit` (43 tests passing)
- [x] `yarn lint` (0 errors)
- [ ] Deploy to local/partners workspace with `yarn twenty dev --once`
- [ ] POST smoke test with `useCase` in body; confirm **Need** field
populated
- [ ] TFT workflow: add `"useCase": "{{record.useCase}}"` to HTTP body
(manual)
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22054?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. -->
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "twenty-partners",
|
||||
"version": "1.1.3",
|
||||
"version": "1.1.14",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
|
||||
+28
-1
@@ -78,7 +78,9 @@ describe('import-opportunity-from-tft handler', () => {
|
||||
.mockResolvedValueOnce({ createPerson: { id: 'person-1' } })
|
||||
.mockResolvedValueOnce({ createOpportunity: { id: 'opp-1' } });
|
||||
|
||||
const result = await handler(authedEvent(baseInput()));
|
||||
const result = await handler(
|
||||
authedEvent(baseInput({ useCase: 'Migrate from HubSpot, ~30 sales users' })),
|
||||
);
|
||||
|
||||
expect(result).toEqual({ ok: true, created: true, id: 'opp-1' });
|
||||
|
||||
@@ -91,6 +93,7 @@ describe('import-opportunity-from-tft handler', () => {
|
||||
amount: { amountMicros: 5000000, currencyCode: 'EUR' },
|
||||
closeDate: '2026-07-01T00:00:00.000Z',
|
||||
stage: 'MEETING',
|
||||
need: 'Migrate from HubSpot, ~30 sales users',
|
||||
companyId: 'company-1',
|
||||
pointOfContactId: 'person-1',
|
||||
});
|
||||
@@ -119,4 +122,28 @@ describe('import-opportunity-from-tft handler', () => {
|
||||
expect(data).not.toHaveProperty('amount');
|
||||
expect(data).not.toHaveProperty('closeDate');
|
||||
});
|
||||
|
||||
it('drops null useCase instead of failing validation', async () => {
|
||||
queryMock
|
||||
.mockResolvedValueOnce({ opportunities: { edges: [] } })
|
||||
.mockResolvedValueOnce({ companies: { edges: [] } })
|
||||
.mockResolvedValueOnce({ people: { edges: [] } });
|
||||
mutationMock
|
||||
.mockResolvedValueOnce({ createCompany: { id: 'company-1' } })
|
||||
.mockResolvedValueOnce({ createPerson: { id: 'person-1' } })
|
||||
.mockResolvedValueOnce({ createOpportunity: { id: 'opp-1' } });
|
||||
|
||||
const body = { ...baseInput(), useCase: null };
|
||||
const result = await handler({
|
||||
body,
|
||||
headers: { 'x-application-secret': SECRET },
|
||||
});
|
||||
|
||||
expect(result).toEqual({ ok: true, created: true, id: 'opp-1' });
|
||||
const data = mutationMock.mock.calls.find(
|
||||
([arg]) => 'createOpportunity' in arg,
|
||||
)?.[0].createOpportunity.__args.data;
|
||||
expect(data).not.toHaveProperty('need');
|
||||
expect(data).not.toHaveProperty('useCase');
|
||||
});
|
||||
});
|
||||
|
||||
+4
@@ -32,6 +32,7 @@ export const importOpportunityFromTftSchema = z.preprocess(dropNulls, z.object({
|
||||
currencyCode: z.string().optional(),
|
||||
closeDate: z.string().optional(),
|
||||
stage: z.string().optional(),
|
||||
useCase: z.string().optional(),
|
||||
company: z
|
||||
.object({
|
||||
name: z.string().optional(),
|
||||
@@ -198,6 +199,9 @@ export const handler = async (
|
||||
if (isNonEmptyString(input.stage)) {
|
||||
opportunityData.stage = input.stage as CoreSchema.OpportunityStageEnum;
|
||||
}
|
||||
if (isNonEmptyString(input.useCase)) {
|
||||
opportunityData.need = input.useCase.trim();
|
||||
}
|
||||
if (companyId !== undefined) opportunityData.companyId = companyId;
|
||||
if (pointOfContactId !== undefined) opportunityData.pointOfContactId = pointOfContactId;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user