Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add google sheets field selection in settings #259

Merged
merged 11 commits into from
Dec 27, 2024
9 changes: 6 additions & 3 deletions example/google-sheets/westeros-houses/register.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ function register_westeros_houses_block(): void {
'spreadsheet' => [
'id' => '1EHdQg53Doz0B-ImrGz_hTleYeSvkVIk_NSJCOM1FQk0',
],
'sheet' => [
'id' => 1,
'name' => 'Houses',
'sheets' => [
[
'id' => 1,
'name' => 'Houses',
'output_query_mappings' => [],
],
],
],
] );
Expand Down
18 changes: 14 additions & 4 deletions inc/Integrations/Google/Sheets/GoogleSheetsDataSource.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,20 @@ protected static function get_service_config_schema(): array {
'id' => Types::id(),
'name' => Types::nullable( Types::string() ),
] ),
'sheet' => Types::object( [
'id' => Types::integer(),
'name' => Types::string(),
] ),
'sheets' => Types::list_of(
Types::object( [
'id' => Types::integer(),
'name' => Types::string(),
'output_query_mappings' => Types::list_of(
Types::object( [
'key' => Types::string(),
'name' => Types::nullable( Types::string() ),
'path' => Types::nullable( Types::json_path() ),
'type' => Types::nullable( Types::string() ),
] )
),
] )
),
] );
}

Expand Down
2 changes: 1 addition & 1 deletion src/data-sources/DataSourceList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ const DataSourceList = () => {
tags.push( {
key: 'spreadsheet',
primaryValue: source.service_config.spreadsheet.name ?? 'Google Sheet',
secondaryValue: source.service_config.sheet.name,
secondaryValue: source.service_config.sheets[ 0 ]?.name,
} );
break;
}
Expand Down
96 changes: 34 additions & 62 deletions src/data-sources/airtable/AirtableSettings.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import { SelectControl, Spinner } from '@wordpress/components';
import { SelectControl } from '@wordpress/components';
import { InputChangeCallback } from '@wordpress/components/build-types/input-control/types';
import { useState } from '@wordpress/element';
import { __, sprintf } from '@wordpress/i18n';
import { ChangeEvent } from 'react';

import { CustomFormFieldToken } from '../components/CustomFormFieldToken';
import { SUPPORTED_AIRTABLE_TYPES } from '@/data-sources/airtable/constants';
import { getAirtableOutputQueryMappingValue } from '@/data-sources/airtable/utils';
import { DataSourceForm } from '@/data-sources/components/DataSourceForm';
import { FieldsSelection } from '@/data-sources/components/FieldsSelection';
import PasswordInputControl from '@/data-sources/components/PasswordInputControl';
import {
useAirtableApiBases,
Expand All @@ -17,8 +17,8 @@ import {
import { useDataSources } from '@/data-sources/hooks/useDataSources';
import {
AirtableConfig,
AirtableOutputQueryMappingValue,
AirtableServiceConfig,
DataSourceQueryMappingValue,
SettingsComponentProps,
} from '@/data-sources/types';
import { getConnectionMessage } from '@/data-sources/utils';
Expand Down Expand Up @@ -240,65 +240,37 @@ export const AirtableSettings = ( {
__nextHasNoMarginBottom
/>

{ selectedTable && availableTableFields.length ? (
<CustomFormFieldToken
label={ __( 'Fields', 'remote-data-blocks' ) }
onChange={ selection => {
let newTableFields: string[];
if ( selection.includes( 'Select All' ) ) {
newTableFields = Array.from( new Set( availableTableFields ) );
} else if ( selection.includes( 'Deselect All' ) ) {
newTableFields = [];
} else {
newTableFields = Array.from(
new Set(
selection
.filter( item => item !== 'Select All' && item !== 'Deselect All' )
.map( item => ( 'object' === typeof item ? item.value : item ) )
)
);
}
setTableFields( newTableFields );
handleOnChange( 'tables', [
{
id: selectedTable.id,
name: selectedTable.name,
output_query_mappings: newTableFields
.map( key => {
const field = selectedTable.fields.find(
tableField => tableField.name === key
);
if ( field ) {
return getAirtableOutputQueryMappingValue( field );
}
/**
* Remove any fields which are not from this table or not supported.
*/
return null;
} )
.filter( Boolean ) as AirtableOutputQueryMappingValue[],
},
] );
} }
suggestions={ [
...( tableFields.length === availableTableFields.length
? [ 'Deselect All' ]
: [ 'Select All' ] ),
...availableTableFields,
] }
value={ tableFields }
__experimentalValidateInput={ input =>
availableTableFields.includes( input ) ||
input === 'Select All' ||
input === 'Deselect All'
}
__nextHasNoMarginBottom
__experimentalExpandOnFocus
__next40pxDefaultSize
/>
) : (
selectedTable && <Spinner />
) }
<FieldsSelection
selectedFields={ tableFields }
availableFields={ availableTableFields }
disabled={ ! selectedTable }
customHelpText={
! selectedTable ? __( 'Please select a table first.', 'remote-data-blocks' ) : null
}
onFieldsChange={ newFields => {
setTableFields( newFields );
handleOnChange( 'tables', [
{
id: selectedTable?.id,
name: selectedTable?.name,
output_query_mappings: newFields
.map( key => {
const field = selectedTable?.fields.find(
tableField => tableField.name === key
);
if ( field ) {
return getAirtableOutputQueryMappingValue( field );
}
/**
* Remove any fields which are not from this table or not supported.
*/
return null;
} )
.filter( Boolean ) as DataSourceQueryMappingValue[],
},
] );
} }
/>
</DataSourceForm.Scope>
</DataSourceForm>
</>
Expand Down
4 changes: 2 additions & 2 deletions src/data-sources/airtable/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import {
AIRTABLE_USER_TYPES,
} from '@/data-sources/airtable/constants';
import { AirtableField } from '@/data-sources/airtable/types';
import { AirtableOutputQueryMappingValue } from '@/data-sources/types';
import { DataSourceQueryMappingValue } from '@/data-sources/types';

export const getAirtableOutputQueryMappingValue = (
field: AirtableField
): AirtableOutputQueryMappingValue => {
): DataSourceQueryMappingValue => {
const baseField = {
path: `$.fields["${ field.name }"]`,
name: field.name,
Expand Down
22 changes: 21 additions & 1 deletion src/data-sources/api-clients/google.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { __, sprintf } from '@wordpress/i18n';

import { GoogleSpreadsheet, GoogleDriveFileList, GoogleDriveFile } from '@/types/google';
import {
GoogleSpreadsheet,
GoogleDriveFileList,
GoogleDriveFile,
GoogleSheetsValueRange,
} from '@/types/google';
import { SelectOption } from '@/types/input';

export class GoogleApi {
Expand Down Expand Up @@ -66,4 +71,19 @@ export class GoogleApi {
value: sheet.properties.sheetId.toString(),
} ) );
}

private async getSheetValues(
spreadsheetId: string,
sheetTitle: string,
cellRange: string
): Promise< GoogleSheetsValueRange > {
const url = `${ GoogleApi.SHEETS_BASE_URL }/spreadsheets/${ spreadsheetId }/values/${ sheetTitle }!${ cellRange }`;
const result = await this.fetchApi< GoogleSheetsValueRange >( url );
return result;
}

public async getSheetFields( spreadsheetId: string, sheetTitle: string ): Promise< string[] > {
const values = await this.getSheetValues( spreadsheetId, sheetTitle, 'A1:Z1' );
return values.values[ 0 ] ?? [];
}
}
8 changes: 6 additions & 2 deletions src/data-sources/components/CustomFormFieldToken.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@ import { FormTokenField } from '@wordpress/components';
import { FormTokenFieldProps } from '@wordpress/components/build-types/form-token-field/types';
import { useEffect, useRef, useState } from '@wordpress/element';

type CustomFormFieldTokenProps = FormTokenFieldProps;
type CustomFormFieldTokenProps = FormTokenFieldProps & {
customHelpText?: string | null;
};

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

const handlePosition = () => {
if ( ! inputRef.current ) return;
Expand Down Expand Up @@ -36,7 +39,8 @@ export const CustomFormFieldToken = ( props: CustomFormFieldTokenProps ) => {
className={ `form-token-field-wrapper ${ isAbove ? 'above' : 'below' }` }
style={ { position: 'relative' } }
>
<FormTokenField { ...props } />
<FormTokenField { ...formTokenFieldProps } __experimentalShowHowTo={ ! customHelpText } />
{ customHelpText && <p className="input-help-text">{ customHelpText }</p> }
</div>
);
};
57 changes: 57 additions & 0 deletions src/data-sources/components/FieldsSelection.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { __ } from '@wordpress/i18n';

import { CustomFormFieldToken } from '@/data-sources/components/CustomFormFieldToken';

interface FieldsSelectionProps {
selectedFields: string[];
availableFields: string[];
onFieldsChange: ( fields: string[] ) => void;
disabled?: boolean;
customHelpText?: string | null;
}

export const FieldsSelection = ( {
selectedFields,
availableFields,
onFieldsChange,
customHelpText,
disabled = false,
}: FieldsSelectionProps ) => {
return (
<CustomFormFieldToken
label={ __( 'Fields', 'remote-data-blocks' ) }
onChange={ selection => {
let newFields: string[];
if ( selection.includes( 'Select All' ) ) {
newFields = Array.from( new Set( availableFields ) );
} else if ( selection.includes( 'Deselect All' ) ) {
newFields = [];
} else {
newFields = Array.from(
new Set(
selection
.filter( item => item !== 'Select All' && item !== 'Deselect All' )
.map( item => ( 'object' === typeof item ? item.value : item ) )
)
);
}
onFieldsChange( newFields );
} }
suggestions={ [
...( selectedFields.length === availableFields.length
? [ 'Deselect All' ]
: [ 'Select All' ] ),
...availableFields,
] }
value={ selectedFields }
__experimentalValidateInput={ input =>
availableFields.includes( input ) || input === 'Select All' || input === 'Deselect All'
}
__nextHasNoMarginBottom
__experimentalExpandOnFocus
__next40pxDefaultSize
disabled={ disabled }
customHelpText={ customHelpText }
/>
);
};
Loading
Loading