Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
18 changes: 9 additions & 9 deletions apps/docs/components/icons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1489,15 +1489,15 @@ export function InstagramIcon(props: SVGProps<SVGSVGElement>) {

export function CrunchbaseIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
{...props}
fill='currentColor'
width='24'
height='24'
viewBox='0 0 24 24'
xmlns='http://www.w3.org/2000/svg'
>
<path d='M21.6 0H2.4A2.41 2.41 0 0 0 0 2.4v19.2A2.41 2.41 0 0 0 2.4 24h19.2a2.41 2.41 0 0 0 2.4-2.4V2.4A2.41 2.41 0 0 0 21.6 0zM7.05 14.47A2.11 2.11 0 0 0 9.84 13.42h1.66a3.69 3.69 0 1 1 0-1.75H9.84a2.11 2.11 0 1 0-2.79 2.8zm11.35.845a3.55 3.55 0 0 1-1.06.63 3.68 3.68 0 0 1-3.39-.38v.38h-1.51V5.37h1.5v4.11a3.74 3.74 0 0 1 1.8-.63H16a3.67 3.67 0 0 1 2.39 6.46zm-.223-2.77a2.1 2.1 0 1 1-4.21 0 2.1 2.1 0 0 1 4.21 0z' />
<svg {...props} viewBox='0 0 32 32' fill='none' xmlns='http://www.w3.org/2000/svg'>
<path
d='M28.802 0h-25.604c-1.76 0.005-3.193 1.438-3.198 3.198v25.604c0.005 1.76 1.438 3.193 3.198 3.198h25.604c1.76-0.005 3.193-1.438 3.198-3.198v-25.604c-0.005-1.76-1.438-3.193-3.198-3.198z'
fill='#ffffff'
/>
<path
d='M28.802 0h-25.604c-1.76 0.005-3.193 1.438-3.198 3.198v25.604c0.005 1.76 1.438 3.193 3.198 3.198h25.604c1.76-0.005 3.193-1.438 3.198-3.198v-25.604c-0.005-1.76-1.438-3.193-3.198-3.198zM9.396 19.286c1.411 0.646 3.078 0.021 3.724-1.391h2.214c-1.38 5.651-9.698 4.651-9.698-1.167 0-5.823 8.318-6.823 9.698-1.167h-2.214c-0.813-1.786-3.161-2.214-4.547-0.823-1.391 1.385-0.964 3.734 0.823 4.547zM24.521 20.411c-0.422 0.365-0.896 0.646-1.417 0.844-1.495 0.578-3.182 0.391-4.516-0.51v0.51h-2.016v-14.094h2v5.479c0.714-0.484 1.542-0.771 2.401-0.839h0.359c4.552-0.010 6.646 5.656 3.188 8.609zM24.224 16.724c0.031 1.573-1.234 2.87-2.807 2.87s-2.839-1.297-2.802-2.87c0.078-3.656 5.526-3.656 5.609 0z'
fill='#0287d1'
/>
</svg>
)
}
Expand Down
2 changes: 2 additions & 0 deletions apps/docs/components/ui/icon-mapping.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {
ContextDevIcon,
ConvexIcon,
CrowdStrikeIcon,
CrunchbaseIcon,
CursorIcon,
DagsterIcon,
DatabricksIcon,
Expand Down Expand Up @@ -307,6 +308,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
context_dev: ContextDevIcon,
convex: ConvexIcon,
crowdstrike: CrowdStrikeIcon,
crunchbase: CrunchbaseIcon,
cursor: CursorIcon,
cursor_v2: CursorIcon,
dagster: DagsterIcon,
Expand Down
369 changes: 369 additions & 0 deletions apps/docs/content/docs/en/integrations/crunchbase.mdx

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions apps/docs/content/docs/en/integrations/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"context_dev",
"convex",
"crowdstrike",
"crunchbase",
"cursor",
"dagster",
"databricks",
Expand Down
175 changes: 175 additions & 0 deletions apps/sim/blocks/blocks/crunchbase.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
/**
* @vitest-environment node
*/
import { describe, expect, it } from 'vitest'
import { CrunchbaseBlock } from '@/blocks/blocks/crunchbase'

/**
* Every assertion here runs against `{ ...inputs, ...buildParams(inputs) }`, the
* shape the generic tool handler actually forwards. A key the mapper omits is
* *not* dropped by that merge — the raw subBlock value survives — so asserting
* on the mapper's return alone would prove nothing about what the tool receives.
*/
describe('CrunchbaseBlock', () => {
const buildParams = CrunchbaseBlock.tools.config!.params!
const selectTool = CrunchbaseBlock.tools.config!.tool!

const resolve = (inputs: Record<string, unknown>) => ({ ...inputs, ...buildParams(inputs) })

const operationIds =
CrunchbaseBlock.subBlocks
.find((subBlock) => subBlock.id === 'operation')
?.options?.map((option) => (option as { id: string }).id) ?? []

it('maps every dropdown operation onto a registered tool', () => {
expect(operationIds).toHaveLength(14)
expect(new Set(operationIds.map((id) => selectTool({ operation: id })))).toEqual(
new Set(CrunchbaseBlock.tools.access)
)
})

it('rejects an operation the dropdown does not offer', () => {
expect(() => selectTool({ operation: 'search_unicorns' })).toThrow(
/Invalid Crunchbase operation/
)
})

it('gives every subblock a unique id', () => {
const ids = CrunchbaseBlock.subBlocks.map((subBlock) => subBlock.id)
expect(ids).toHaveLength(new Set(ids).size)
})

it('shows a subblock for every operation that owns it', () => {
const conditionsFor = (id: string) => {
const condition = CrunchbaseBlock.subBlocks.find((subBlock) => subBlock.id === id)?.condition
const value = (condition as { value?: unknown } | undefined)?.value
return new Set(Array.isArray(value) ? value.map(String) : [String(value)])
}

expect(conditionsFor('searchQuery')).toEqual(
new Set([
'search_organizations',
'search_people',
'search_funding_rounds',
'search_acquisitions',
'search_entities',
])
)
expect(conditionsFor('entityId')).toEqual(
new Set([
'get_organization',
'get_person',
'get_funding_round',
'get_acquisition',
'get_entity',
'get_entity_card',
])
)
})

it('never hides a required field behind advanced mode', () => {
const advancedRequired = CrunchbaseBlock.subBlocks
.filter((subBlock) => subBlock.mode === 'advanced' && subBlock.required)
.map((subBlock) => subBlock.id)

expect(advancedRequired).toEqual([])
})

/*
* The mapper's whole job is dropping keys that belong to another operation.
* `shouldSerializeSubBlock` short-circuits on `mode: 'advanced'` before it
* evaluates a condition, so a hidden advanced field still reaches `inputs` —
* only an explicit `undefined` removes it from what goes out on the wire.
*/
it('drops a previous operation’s leftovers when the operation changes', () => {
const params = resolve({
operation: 'autocomplete',
apiKey: 'key',
autocompleteQuery: 'airbnb',
searchQuery: '[{"type":"predicate","field_id":"categories"}]',
searchFieldIds: '["identifier"]',
entityId: 'tesla-motors',
fieldIds: '["identifier","name"]',
cardIds: '["founders"]',
order: '[{"field_id":"rank_org","sort":"asc"}]',
afterId: 'stale-cursor',
collection: 'organizations',
})

expect(params.query).toBe('airbnb')
expect(params.entityId).toBeUndefined()
expect(params.fieldIds).toBeUndefined()
expect(params.cardIds).toBeUndefined()
expect(params.order).toBeUndefined()
expect(params.afterId).toBeUndefined()
expect(params.collection).toBeUndefined()
expect(params.searchQuery).toBeUndefined()
expect(params.autocompleteQuery).toBeUndefined()
})

it('routes the predicate query to a search and the text query to autocomplete', () => {
const search = resolve({
operation: 'search_organizations',
apiKey: 'key',
searchQuery: '[{"type":"predicate","field_id":"categories","operator_id":"includes"}]',
autocompleteQuery: 'airbnb',
})
expect(search.query).toBe(
'[{"type":"predicate","field_id":"categories","operator_id":"includes"}]'
)

const autocomplete = resolve({
operation: 'autocomplete',
apiKey: 'key',
searchQuery: '[{"type":"predicate","field_id":"categories","operator_id":"includes"}]',
autocompleteQuery: 'airbnb',
})
expect(autocomplete.query).toBe('airbnb')
})

it('feeds the generic search its own required field list', () => {
const generic = resolve({
operation: 'search_entities',
apiKey: 'key',
collection: 'events',
searchQuery: '[]',
searchFieldIds: '["identifier","short_description"]',
fieldIds: '["identifier","name"]',
})
expect(generic.fieldIds).toBe('["identifier","short_description"]')
expect(generic.searchFieldIds).toBeUndefined()

const specific = resolve({
operation: 'search_organizations',
apiKey: 'key',
searchQuery: '[]',
searchFieldIds: '["identifier","short_description"]',
fieldIds: '["identifier","name"]',
})
expect(specific.fieldIds).toBe('["identifier","name"]')
})

it('picks the collection each operation actually asks for', () => {
const card = resolve({
operation: 'get_entity_card',
apiKey: 'key',
entityId: 'sequoia-capital',
cardId: 'participated_investments',
cardCollection: 'organizations',
collection: 'events',
deletedCollection: 'people',
})
expect(card.collection).toBe('organizations')
expect(card.cardCollection).toBeUndefined()

const deleted = resolve({
operation: 'list_deleted_entities',
apiKey: 'key',
cardCollection: 'organizations',
collection: 'events',
deletedCollection: 'people',
})
expect(deleted.collection).toBe('people')
expect(deleted.deletedCollection).toBeUndefined()
})
})
Loading
Loading