Skip to content

Commit a85d5ec

Browse files
committed
Merge remote-tracking branch 'origin/trunk' into add/code-badge
2 parents 86384cb + 5e9a8e2 commit a85d5ec

File tree

19 files changed

+341
-171
lines changed

19 files changed

+341
-171
lines changed

example/google-sheets/westeros-houses/register.php

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,12 @@ function register_westeros_houses_block(): void {
2727
'spreadsheet' => [
2828
'id' => '1EHdQg53Doz0B-ImrGz_hTleYeSvkVIk_NSJCOM1FQk0',
2929
],
30-
'sheet' => [
31-
'id' => 1,
32-
'name' => 'Houses',
30+
'sheets' => [
31+
[
32+
'id' => '1',
33+
'name' => 'Houses',
34+
'output_query_mappings' => [],
35+
],
3336
],
3437
],
3538
] );

inc/Integrations/Google/Sheets/GoogleSheetsDataSource.php

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,20 @@ protected static function get_service_config_schema(): array {
3131
'id' => Types::id(),
3232
'name' => Types::nullable( Types::string() ),
3333
] ),
34-
'sheet' => Types::object( [
35-
'id' => Types::integer(),
36-
'name' => Types::string(),
37-
] ),
34+
'sheets' => Types::list_of(
35+
Types::object( [
36+
'id' => Types::string(),
37+
'name' => Types::string(),
38+
'output_query_mappings' => Types::list_of(
39+
Types::object( [
40+
'key' => Types::string(),
41+
'name' => Types::nullable( Types::string() ),
42+
'path' => Types::nullable( Types::json_path() ),
43+
'type' => Types::nullable( Types::string() ),
44+
] )
45+
),
46+
] )
47+
),
3848
] );
3949
}
4050

src/data-sources/DataSourceMetaTags.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ const DataSourceDescriptor = ( props: DataSourceMetaTagsProps ) => {
3333
tag = {
3434
key: 'spreadsheet',
3535
primaryValue: props.source.service_config.spreadsheet.name ?? 'Google Sheet',
36-
secondaryValue: props.source.service_config.sheet.name,
36+
secondaryValue: props.source.service_config.sheets[ 0 ]?.name,
3737
};
3838
break;
3939
}

src/data-sources/airtable/AirtableSettings.tsx

Lines changed: 37 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
1-
import { SelectControl, Spinner } from '@wordpress/components';
1+
import { SelectControl } from '@wordpress/components';
22
import { InputChangeCallback } from '@wordpress/components/build-types/input-control/types';
33
import { useState } from '@wordpress/element';
44
import { __, sprintf } from '@wordpress/i18n';
55
import { ChangeEvent } from 'react';
66

7-
import { CustomFormFieldToken } from '../components/CustomFormFieldToken';
87
import { SUPPORTED_AIRTABLE_TYPES } from '@/data-sources/airtable/constants';
98
import { getAirtableOutputQueryMappingValue } from '@/data-sources/airtable/utils';
109
import { DataSourceForm } from '@/data-sources/components/DataSourceForm';
10+
import { FieldsSelection } from '@/data-sources/components/FieldsSelection';
1111
import PasswordInputControl from '@/data-sources/components/PasswordInputControl';
1212
import {
1313
useAirtableApiBases,
@@ -17,8 +17,8 @@ import {
1717
import { useDataSources } from '@/data-sources/hooks/useDataSources';
1818
import {
1919
AirtableConfig,
20-
AirtableOutputQueryMappingValue,
2120
AirtableServiceConfig,
21+
DataSourceQueryMappingValue,
2222
SettingsComponentProps,
2323
} from '@/data-sources/types';
2424
import { getConnectionMessage } from '@/data-sources/utils';
@@ -240,65 +240,41 @@ export const AirtableSettings = ( {
240240
__nextHasNoMarginBottom
241241
/>
242242

243-
{ selectedTable && availableTableFields.length ? (
244-
<CustomFormFieldToken
245-
label={ __( 'Fields', 'remote-data-blocks' ) }
246-
onChange={ selection => {
247-
let newTableFields: string[];
248-
if ( selection.includes( 'Select All' ) ) {
249-
newTableFields = Array.from( new Set( availableTableFields ) );
250-
} else if ( selection.includes( 'Deselect All' ) ) {
251-
newTableFields = [];
252-
} else {
253-
newTableFields = Array.from(
254-
new Set(
255-
selection
256-
.filter( item => item !== 'Select All' && item !== 'Deselect All' )
257-
.map( item => ( 'object' === typeof item ? item.value : item ) )
258-
)
259-
);
260-
}
261-
setTableFields( newTableFields );
262-
handleOnChange( 'tables', [
263-
{
264-
id: selectedTable.id,
265-
name: selectedTable.name,
266-
output_query_mappings: newTableFields
267-
.map( key => {
268-
const field = selectedTable.fields.find(
269-
tableField => tableField.name === key
270-
);
271-
if ( field ) {
272-
return getAirtableOutputQueryMappingValue( field );
273-
}
274-
/**
275-
* Remove any fields which are not from this table or not supported.
276-
*/
277-
return null;
278-
} )
279-
.filter( Boolean ) as AirtableOutputQueryMappingValue[],
280-
},
281-
] );
282-
} }
283-
suggestions={ [
284-
...( tableFields.length === availableTableFields.length
285-
? [ 'Deselect All' ]
286-
: [ 'Select All' ] ),
287-
...availableTableFields,
288-
] }
289-
value={ tableFields }
290-
__experimentalValidateInput={ input =>
291-
availableTableFields.includes( input ) ||
292-
input === 'Select All' ||
293-
input === 'Deselect All'
243+
<FieldsSelection
244+
selectedFields={ tableFields }
245+
availableFields={ availableTableFields }
246+
disabled={ ! selectedTable }
247+
customHelpText={
248+
! selectedTable ? __( 'Please select a table first.', 'remote-data-blocks' ) : null
249+
}
250+
onFieldsChange={ newFields => {
251+
if ( ! selectedTable ) {
252+
return;
294253
}
295-
__nextHasNoMarginBottom
296-
__experimentalExpandOnFocus
297-
__next40pxDefaultSize
298-
/>
299-
) : (
300-
selectedTable && <Spinner />
301-
) }
254+
255+
setTableFields( newFields );
256+
handleOnChange( 'tables', [
257+
{
258+
id: selectedTable.id,
259+
name: selectedTable.name,
260+
output_query_mappings: newFields
261+
.map( key => {
262+
const field = selectedTable?.fields.find(
263+
tableField => tableField.name === key
264+
);
265+
if ( field ) {
266+
return getAirtableOutputQueryMappingValue( field );
267+
}
268+
/**
269+
* Remove any fields which are not from this table or not supported.
270+
*/
271+
return null;
272+
} )
273+
.filter( ( value ): value is DataSourceQueryMappingValue => value !== null ),
274+
},
275+
] );
276+
} }
277+
/>
302278
</DataSourceForm.Scope>
303279
</DataSourceForm>
304280
</>

src/data-sources/airtable/utils.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,11 @@ import {
44
AIRTABLE_USER_TYPES,
55
} from '@/data-sources/airtable/constants';
66
import { AirtableField } from '@/data-sources/airtable/types';
7-
import { AirtableOutputQueryMappingValue } from '@/data-sources/types';
7+
import { DataSourceQueryMappingValue } from '@/data-sources/types';
88

99
export const getAirtableOutputQueryMappingValue = (
1010
field: AirtableField
11-
): AirtableOutputQueryMappingValue => {
11+
): DataSourceQueryMappingValue => {
1212
const baseField = {
1313
path: `$.fields["${ field.name }"]`,
1414
name: field.name,

src/data-sources/api-clients/google.ts

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
import { __, sprintf } from '@wordpress/i18n';
22

3-
import { GoogleSpreadsheet, GoogleDriveFileList, GoogleDriveFile } from '@/types/google';
3+
import {
4+
GoogleSpreadsheet,
5+
GoogleDriveFileList,
6+
GoogleDriveFile,
7+
GoogleSheetsValueRange,
8+
} from '@/types/google';
49
import { SelectOption } from '@/types/input';
510

611
export class GoogleApi {
@@ -66,4 +71,19 @@ export class GoogleApi {
6671
value: sheet.properties.sheetId.toString(),
6772
} ) );
6873
}
74+
75+
private async getSheetValues(
76+
spreadsheetId: string,
77+
sheetTitle: string,
78+
cellRange: string
79+
): Promise< GoogleSheetsValueRange > {
80+
const url = `${ GoogleApi.SHEETS_BASE_URL }/spreadsheets/${ spreadsheetId }/values/${ sheetTitle }!${ cellRange }`;
81+
const result = await this.fetchApi< GoogleSheetsValueRange >( url );
82+
return result;
83+
}
84+
85+
public async getSheetFields( spreadsheetId: string, sheetTitle: string ): Promise< string[] > {
86+
const values = await this.getSheetValues( spreadsheetId, sheetTitle, 'A1:Z1' );
87+
return values.values[ 0 ] ?? [];
88+
}
6989
}

src/data-sources/components/CustomFormFieldToken.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@ import { FormTokenField } from '@wordpress/components';
22
import { FormTokenFieldProps } from '@wordpress/components/build-types/form-token-field/types';
33
import { useEffect, useRef, useState } from '@wordpress/element';
44

5-
type CustomFormFieldTokenProps = FormTokenFieldProps;
5+
type CustomFormFieldTokenProps = FormTokenFieldProps & {
6+
customHelpText?: string | null;
7+
};
68

79
export const CustomFormFieldToken = ( props: CustomFormFieldTokenProps ) => {
810
const [ isAbove, setIsAbove ] = useState( false );
911
const inputRef = useRef( null );
12+
const { customHelpText, ...formTokenFieldProps } = props;
1013

1114
const handlePosition = () => {
1215
if ( ! inputRef.current ) return;
@@ -36,7 +39,8 @@ export const CustomFormFieldToken = ( props: CustomFormFieldTokenProps ) => {
3639
className={ `form-token-field-wrapper ${ isAbove ? 'above' : 'below' }` }
3740
style={ { position: 'relative' } }
3841
>
39-
<FormTokenField { ...props } />
42+
<FormTokenField { ...formTokenFieldProps } __experimentalShowHowTo={ ! customHelpText } />
43+
{ customHelpText && <p className="input-help-text">{ customHelpText }</p> }
4044
</div>
4145
);
4246
};
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import { __ } from '@wordpress/i18n';
2+
3+
import { CustomFormFieldToken } from '@/data-sources/components/CustomFormFieldToken';
4+
5+
interface FieldsSelectionProps {
6+
selectedFields: string[];
7+
availableFields: string[];
8+
onFieldsChange: ( fields: string[] ) => void;
9+
disabled?: boolean;
10+
customHelpText?: string | null;
11+
}
12+
13+
export const FieldsSelection = ( {
14+
selectedFields,
15+
availableFields,
16+
onFieldsChange,
17+
customHelpText,
18+
disabled = false,
19+
}: FieldsSelectionProps ) => {
20+
return (
21+
<CustomFormFieldToken
22+
label={ __( 'Fields', 'remote-data-blocks' ) }
23+
onChange={ selection => {
24+
let newFields: string[];
25+
if ( selection.includes( 'Select All' ) ) {
26+
newFields = Array.from( new Set( availableFields ) );
27+
} else if ( selection.includes( 'Deselect All' ) ) {
28+
newFields = [];
29+
} else {
30+
newFields = Array.from(
31+
new Set(
32+
selection
33+
.filter( item => item !== 'Select All' && item !== 'Deselect All' )
34+
.map( item => ( 'object' === typeof item ? item.value : item ) )
35+
)
36+
);
37+
}
38+
onFieldsChange( newFields );
39+
} }
40+
suggestions={ [
41+
...( selectedFields.length === availableFields.length
42+
? [ 'Deselect All' ]
43+
: [ 'Select All' ] ),
44+
...availableFields,
45+
] }
46+
value={ selectedFields }
47+
__experimentalValidateInput={ input =>
48+
availableFields.includes( input ) || input === 'Select All' || input === 'Deselect All'
49+
}
50+
__nextHasNoMarginBottom
51+
__experimentalExpandOnFocus
52+
__next40pxDefaultSize
53+
disabled={ disabled }
54+
customHelpText={ customHelpText }
55+
/>
56+
);
57+
};

0 commit comments

Comments
 (0)