Skip to content

Commit 78e3755

Browse files
authored
add support for all airtable field types (#234)
* add 'key' and 'path' properties to table in airtable config * refactor: AirtableDataSource output query mappings to improve path handling * feat: add support for all airtable field types - Automatically assign output mapping types to airtable fields - Improved field mapping logic to filter unsupported fields and handle selected table fields more effectively. * refactor: update field type handling to use 'currency' instead of 'price'
1 parent 4e1031d commit 78e3755

File tree

12 files changed

+254
-35
lines changed

12 files changed

+254
-35
lines changed

docs/extending/query.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ The `get_input_schema` method defines the input data expected by the query. The
7474
- `image_alt`
7575
- `image_url`
7676
- `number`
77-
- `price`
77+
- `currency`
7878
- `string`
7979

8080
#### Example

inc/Config/QueryRunner/QueryRunner.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -241,7 +241,10 @@ public function execute( array $input_variables ): array|WP_Error {
241241
* @param string $field_type The field type.
242242
* @return string The field value.
243243
*/
244-
protected function get_field_value( array|string $field_value, string $default_value = '', string $field_type = 'string' ): string {
244+
protected function get_field_value( array|string $field_value, array $mapping ): string {
245+
$default_value = $mapping['default_value'] ?? '';
246+
$field_type = $mapping['type'];
247+
245248
$field_value_single = is_array( $field_value ) && count( $field_value ) > 1 ? $field_value : ( $field_value[0] ?? $default_value );
246249

247250
switch ( $field_type ) {
@@ -251,10 +254,18 @@ protected function get_field_value( array|string $field_value, string $default_v
251254
case 'html':
252255
return $field_value_single;
253256

254-
case 'price':
255-
return sprintf( '$%s', number_format( (float) $field_value_single, 2 ) );
257+
case 'currency':
258+
$currency_symbol = $mapping['prefix'] ?? '$';
259+
return sprintf( '%s%s', $currency_symbol, number_format( (float) $field_value_single, 2 ) );
256260

257261
case 'string':
262+
if ( is_array( $field_value_single ) ) {
263+
// Ensure all elements are strings and filter out non-string values
264+
$string_values = array_filter( $field_value_single, '\is_string' );
265+
if ( ! empty( $string_values ) ) {
266+
return wp_strip_all_tags( implode( ', ', $string_values ) );
267+
}
268+
}
258269
return wp_strip_all_tags( $field_value_single );
259270
}
260271

@@ -304,7 +315,7 @@ protected function map_fields( string|array|object|null $response_data, bool $is
304315

305316
// JSONPath always returns values in an array, even if there's only one value.
306317
// Because we're mostly interested in single values for field mapping, unwrap the array if it's only one item.
307-
$field_value_single = self::get_field_value( $field_value, $mapping['default_value'] ?? '', $mapping['type'] );
318+
$field_value_single = self::get_field_value( $field_value, $mapping );
308319
}
309320

310321
return array_merge( $mapping, [

inc/Editor/BlockPatterns/BlockPatterns.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ public static function register_default_block_pattern( string $block_name, strin
109109
break;
110110

111111
case 'base64':
112-
case 'price':
112+
case 'currency':
113113
$bindings['paragraphs'][] = [
114114
'content' => [ $field, $name ],
115115
];

inc/Integrations/Airtable/AirtableDataSource.php

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,23 @@ class AirtableDataSource extends HttpDataSource {
4444
'items' => [
4545
'type' => 'object',
4646
'properties' => [
47-
'name' => [ 'type' => 'string' ],
47+
'key' => [ 'type' => 'string' ],
48+
'name' => [
49+
'type' => 'string',
50+
'required' => false,
51+
],
4852
'type' => [
4953
'type' => 'string',
5054
'required' => false,
5155
],
56+
'path' => [
57+
'type' => 'string',
58+
'required' => false,
59+
],
60+
'prefix' => [
61+
'type' => 'string',
62+
'required' => false,
63+
],
5264
],
5365
],
5466
],
@@ -121,11 +133,16 @@ public function ___temp_get_query(): AirtableGetItemQuery|\WP_Error {
121133
];
122134

123135
foreach ( $this->config['tables'][0]['output_query_mappings'] as $mapping ) {
124-
$output_schema['mappings'][ ucfirst( $mapping['name'] ) ] = [
125-
'name' => $mapping['name'],
126-
'path' => '$.fields.' . $mapping['name'],
136+
$mapping_key = $mapping['key'];
137+
$output_schema['mappings'][ $mapping_key ] = [
138+
'name' => $mapping['name'] ?? $mapping_key,
139+
'path' => $mapping['path'] ?? '$.fields["' . $mapping_key . '"]',
127140
'type' => $mapping['type'] ?? 'string',
128141
];
142+
143+
if ( 'currency' === $mapping['type'] && isset( $mapping['prefix'] ) ) {
144+
$output_schema['mappings'][ $mapping_key ]['prefix'] = $mapping['prefix'];
145+
}
129146
}
130147

131148
return AirtableGetItemQuery::from_array([

inc/Integrations/Shopify/Queries/ShopifyGetProductQuery.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ public function get_output_schema(): array {
4747
'price' => [
4848
'name' => 'Item price',
4949
'path' => '$.data.product.priceRange.maxVariantPrice.amount',
50-
'type' => 'price',
50+
'type' => 'currency',
5151
],
5252
'variant_id' => [
5353
'name' => 'Variant ID',

inc/Integrations/Shopify/Queries/ShopifySearchProductsQuery.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public function get_output_schema(): array {
3131
'price' => [
3232
'name' => 'Item price',
3333
'path' => '$.node.priceRange.maxVariantPrice.amount',
34-
'type' => 'price',
34+
'type' => 'currency',
3535
],
3636
'image_url' => [
3737
'name' => 'Item image URL',

src/blocks/remote-data-container/config/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ export const REMOTE_DATA_REST_API_URL = getRestUrl();
1515
export const CONTAINER_CLASS_NAME = getClassName( 'container' );
1616

1717
export const IMAGE_FIELD_TYPES = [ 'image_alt', 'image_url' ];
18-
export const TEXT_FIELD_TYPES = [ 'number', 'base64', 'price', 'string' ];
18+
export const TEXT_FIELD_TYPES = [ 'number', 'base64', 'currency', 'string' ];
1919
export const BUTTON_FIELD_TYPES = [ 'button_url' ];

src/data-sources/airtable/AirtableSettings.tsx

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@ import { useEffect, useMemo, useState } from '@wordpress/element';
44
import { __, sprintf } from '@wordpress/i18n';
55
import { ChangeEvent } from 'react';
66

7+
import { SUPPORTED_AIRTABLE_TYPES } from '@/data-sources/airtable/constants';
78
import { AirtableFormState } from '@/data-sources/airtable/types';
9+
import { getAirtableOutputQueryMappingValue } from '@/data-sources/airtable/utils';
810
import { DataSourceForm } from '@/data-sources/components/DataSourceForm';
911
import { DataSourceFormActions } from '@/data-sources/components/DataSourceFormActions';
1012
import PasswordInputControl from '@/data-sources/components/PasswordInputControl';
@@ -15,7 +17,11 @@ import {
1517
useAirtableApiUserId,
1618
} from '@/data-sources/hooks/useAirtable';
1719
import { useDataSources } from '@/data-sources/hooks/useDataSources';
18-
import { AirtableConfig, SettingsComponentProps } from '@/data-sources/types';
20+
import {
21+
AirtableConfig,
22+
AirtableOutputQueryMappingValue,
23+
SettingsComponentProps,
24+
} from '@/data-sources/types';
1925
import { getConnectionMessage } from '@/data-sources/utils';
2026
import { useForm } from '@/hooks/useForm';
2127
import { useSettingsContext } from '@/settings/hooks/useSettingsNav';
@@ -51,7 +57,7 @@ const getInitialStateFromConfig = ( config?: AirtableConfig ): AirtableFormState
5157
name: table.name,
5258
};
5359
initialStateFromConfig.table_fields = new Set(
54-
table.output_query_mappings.map( ( { name } ) => name )
60+
table.output_query_mappings.map( ( { key } ) => key )
5561
);
5662
}
5763
}
@@ -105,6 +111,11 @@ export const AirtableSettings = ( {
105111
return;
106112
}
107113

114+
const selectedTable = tables?.find( table => table.id === state.table?.id );
115+
if ( ! selectedTable ) {
116+
return;
117+
}
118+
108119
const airtableConfig: AirtableConfig = {
109120
uuid: uuidFromProps ?? '',
110121
service: 'airtable',
@@ -114,10 +125,18 @@ export const AirtableSettings = ( {
114125
{
115126
id: state.table.id,
116127
name: state.table.name,
117-
output_query_mappings: Array.from( state.table_fields ).map( name => ( {
118-
name,
119-
type: name.endsWith( '.url' ) ? 'image_url' : 'string',
120-
} ) ),
128+
output_query_mappings: Array.from( state.table_fields )
129+
.map( key => {
130+
const field = selectedTable.fields.find( tableField => tableField.name === key );
131+
if ( field ) {
132+
return getAirtableOutputQueryMappingValue( field );
133+
}
134+
/**
135+
* Remove any fields which are not from this table or not supported.
136+
*/
137+
return null;
138+
} )
139+
.filter( Boolean ) as AirtableOutputQueryMappingValue[],
121140
},
122141
],
123142
slug: state.slug,
@@ -148,6 +167,11 @@ export const AirtableSettings = ( {
148167
} else if ( id === 'table' ) {
149168
const selectedTable = tables?.find( table => table.id === value );
150169
newValue = { id: value, name: selectedTable?.name ?? '' };
170+
171+
if ( value !== state.table?.id ) {
172+
// Reset the selected fields when the table changes.
173+
handleOnChange( 'table_fields', new Set< string >() );
174+
}
151175
}
152176
handleOnChange( id, newValue );
153177
}
@@ -284,19 +308,8 @@ export const AirtableSettings = ( {
284308

285309
if ( selectedTable ) {
286310
selectedTable.fields.forEach( field => {
287-
const simpleFieldTypes = [
288-
'singleLineText',
289-
'multilineText',
290-
'email',
291-
'phoneNumber',
292-
'url',
293-
'number',
294-
];
295-
296-
if ( simpleFieldTypes.includes( field.type ) ) {
311+
if ( SUPPORTED_AIRTABLE_TYPES.includes( field.type ) ) {
297312
newAvailableTableFields.push( field.name );
298-
} else if ( field.type === 'multipleAttachments' ) {
299-
newAvailableTableFields.push( `${ field.name }[0].url` );
300313
}
301314
} );
302315
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
export const AIRTABLE_STRING_TYPES = Object.freeze(
2+
new Set( [
3+
'singleLineText',
4+
'multilineText',
5+
'email',
6+
'phoneNumber',
7+
'richText',
8+
'barcode',
9+
'singleSelect',
10+
'date',
11+
'dateTime',
12+
'lastModifiedTime',
13+
'createdTime',
14+
'multipleRecordLinks',
15+
'rollup',
16+
'externalSyncSource',
17+
] )
18+
);
19+
20+
export const AIRTABLE_NUMBER_TYPES = Object.freeze(
21+
new Set( [ 'number', 'autoNumber', 'rating', 'duration', 'count', 'percent' ] )
22+
);
23+
24+
export const AIRTABLE_USER_TYPES = Object.freeze(
25+
new Set( [ 'createdBy', 'lastModifiedBy', 'singleCollaborator' ] )
26+
);
27+
28+
export const SUPPORTED_AIRTABLE_TYPES = Object.freeze( [
29+
// String types
30+
'singleLineText',
31+
'multilineText',
32+
'email',
33+
'phoneNumber',
34+
'richText',
35+
'barcode',
36+
'singleSelect',
37+
'multipleSelects',
38+
'date',
39+
'dateTime',
40+
'lastModifiedTime',
41+
'createdTime',
42+
'multipleRecordLinks',
43+
'rollup',
44+
'externalSyncSource',
45+
// Number types
46+
'number',
47+
'autoNumber',
48+
'rating',
49+
'duration',
50+
'count',
51+
'percent',
52+
// User types
53+
'createdBy',
54+
'lastModifiedBy',
55+
'singleCollaborator',
56+
// Other types
57+
'multipleCollaborator',
58+
'url',
59+
'button',
60+
'currency',
61+
'checkbox',
62+
'multipleAttachments',
63+
'formula',
64+
'lookup',
65+
] );

src/data-sources/airtable/types.ts

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,13 +34,35 @@ export interface AirtableTable {
3434
syncStatus: 'complete' | 'pending';
3535
}
3636

37-
interface AirtableField {
37+
/**
38+
* Represents an Airtable field configuration.
39+
* @see https://airtable.com/developers/web/api/model/table-model#fields
40+
*/
41+
export interface AirtableField {
3842
id: string;
3943
name: string;
4044
type: string;
4145
description: string | null;
4246
options?: {
43-
[ key: string ]: unknown;
47+
choices?: Array< {
48+
id: string;
49+
name: string;
50+
color?: string;
51+
} >;
52+
precision?: number;
53+
symbol?: string;
54+
format?: string;
55+
foreignTableId?: string;
56+
relationship?: 'many' | 'one';
57+
symmetricColumnId?: string;
58+
result?: {
59+
type: string;
60+
options?: {
61+
precision?: number;
62+
symbol?: string;
63+
format?: string;
64+
};
65+
};
4466
};
4567
}
4668

0 commit comments

Comments
 (0)