import { EditorLanguage, ExtendedIntegrationPluginId, FormComponentType, GoogleSheetsAuthType, Plugin, FormItem, PluginResponseType, PluginType } from '../../types/index.js'; import { PUBLIC_INTEGRATIONS_LOGO_URL, SERVICE_ACCOUNT_GHOST_TEXT } from './constants.js'; import { authSections } from './shared/auth.js'; import { markAsDeprecated, sortByDisplayName } from './utils.js'; const makeDropdownItem = (value: string, displayName?: string, subText?: string) => { return { key: value, value: value, displayName: displayName ?? value, subText: subText ?? '' }; }; const GoogleSheetsPluginVersions = { V1: '0.0.1', V2: '0.0.2', V3: '0.0.3', V4: '0.0.4', V5: '0.0.5', V6: '0.0.6', V7: '0.0.7', V14: '0.0.14', V15: '0.0.15', V18: '0.0.18', V19: '0.0.19' }; export enum GoogleSheetsFormatType { EFFECTIVE_VALUE = 'EFFECTIVE_VALUE', USER_ENTERED_VALUE = 'USER_ENTERED_VALUE', FORMATTED_VALUE = 'FORMATTED_VALUE' } export enum GoogleSheetsActionType { CREATE_WORKSHEET = 'CREATE_WORKSHEET', READ_SPREADSHEET = 'READ_SPREADSHEET', READ_SPREADSHEET_RANGE = 'READ_SPREADSHEET_RANGE', APPEND_SPREADSHEET = 'APPEND_SPREADSHEET', CREATE_SPREADSHEET_ROWS = 'CREATE_SPREADSHEET_ROWS', CLEAR_SPREADSHEET = 'CLEAR_SPREADSHEET' } const GOOGLE_SHEETS_ACTION_DISPLAY_NAMES_V15: Record = { [GoogleSheetsActionType.READ_SPREADSHEET]: 'Read the whole spreadsheet', [GoogleSheetsActionType.READ_SPREADSHEET_RANGE]: 'Read from a range (e.g. A1:D100)', [GoogleSheetsActionType.APPEND_SPREADSHEET]: markAsDeprecated('Append rows to the spreadsheet'), [GoogleSheetsActionType.CREATE_SPREADSHEET_ROWS]: 'Create spreadsheet row(s)', [GoogleSheetsActionType.CLEAR_SPREADSHEET]: 'Clear the spreadsheet' }; const GOOGLE_SHEETS_ACTION_DISPLAY_NAMES_V19: Record = { [GoogleSheetsActionType.CREATE_WORKSHEET]: 'Add a worksheet to a spreadsheet', ...GOOGLE_SHEETS_ACTION_DISPLAY_NAMES_V15 }; export const GOOGLE_SHEETS_PLUGIN_ID = 'gsheets'; enum GOOGLE_SHEETS_SCOPE { READ = 'email https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/spreadsheets.readonly https://www.googleapis.com/auth/userinfo.email openid', WRITE = 'email https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/spreadsheets https://www.googleapis.com/auth/userinfo.email openid' } const getGsheetsAuthDisplayName = (authType: GoogleSheetsAuthType): string => { switch (authType) { case GoogleSheetsAuthType.OAUTH2_CODE: return 'Connect using Google Account'; case GoogleSheetsAuthType.SERVICE_ACCOUNT: return 'Connect using Service Account'; default: return 'None'; } }; export enum GoogleSheetsDestinationType { ROW_NUMBER = 'ROW_NUMBER', APPEND = 'APPEND' } const BASE_HEADER_ROW_NUMBER = { label: 'Header row number', name: 'headerRowNumber', startVersion: GoogleSheetsPluginVersions.V15, componentType: FormComponentType.DYNAMIC_INPUT_TEXT, initialValue: '1' }; export const GoogleSheetsPlugin = (googleSheetsClientId: string): Plugin => { return { id: GOOGLE_SHEETS_PLUGIN_ID, name: 'Google Sheets', moduleName: 'GoogleSheetsPlugin', modulePath: 'plugins/gsheets/GoogleSheetsPlugin', iconLocation: `${PUBLIC_INTEGRATIONS_LOGO_URL}/gsheets.png`, // TODO(docs-v2): no google-sheets page exists in docs-v2; add one or remove this line docsUrl: undefined, type: PluginType.API, responseType: PluginResponseType.TABLE, responseTypeOverride: { action: { values: [GoogleSheetsActionType.CREATE_WORKSHEET], responseType: PluginResponseType.JSON } }, hasRawRequest: false, hasTest: true, hasMetadata: true, datasourceTemplate: { sections: [ { name: 'main', items: [ { label: 'Display name', name: 'name', startVersion: GoogleSheetsPluginVersions.V1, componentType: FormComponentType.INPUT_TEXT, rules: [{ required: true, message: 'Display name is required' }] } ] }, ...authSections({ startVersion: GoogleSheetsPluginVersions.V1, pluginId: ExtendedIntegrationPluginId.GOOGLE_SHEETS_PLUGIN_ID, pluginName: 'Google Sheets', enabledMethods: { oauth2SuperblocksClient: { tokenUrl: 'https://accounts.google.com/o/oauth2/token', authorizationUrl: 'https://accounts.google.com/o/oauth2/auth', revokeTokenUrl: 'https://oauth2.googleapis.com/revoke', userInfoUrl: 'https://openidconnect.googleapis.com/v1/userinfo', clientId: googleSheetsClientId, iconUrl: 'https://superblocks.s3.us-west-2.amazonaws.com/img/integrations/google-login.svg', authorizationExtraParams: { responseType: 'code', accessType: 'offline' } } }, defaultMethod: 'oauth2SuperblocksClient', allowNone: false, fieldNamesToHide: [ 'oauth-callback-alert', 'authConfig.clientId', 'authConfig.audience', 'authConfig.scope', 'authConfig.tokenScope', 'oauth-revoke-shared-tokens-button' ], prependItems: [ { label: 'Authentication', name: 'authType', startVersion: GoogleSheetsPluginVersions.V4, componentType: FormComponentType.DROPDOWN, initialValue: GoogleSheetsAuthType.OAUTH2_CODE, options: Object.values(GoogleSheetsAuthType).map((authType) => ({ displayName: getGsheetsAuthDisplayName(authType), value: authType, key: authType })), immutable: true }, { label: 'Access level', name: 'authConfig.scope', startVersion: GoogleSheetsPluginVersions.V1, componentType: FormComponentType.RADIO, initialValue: GOOGLE_SHEETS_SCOPE.WRITE, options: [ { key: GOOGLE_SHEETS_SCOPE.WRITE, value: GOOGLE_SHEETS_SCOPE.WRITE, displayName: 'Read and write' }, { key: GOOGLE_SHEETS_SCOPE.READ, value: GOOGLE_SHEETS_SCOPE.READ, displayName: 'Read only' } ], rules: [{ required: true, message: 'Access level is required' }], immutable: true }, { label: 'Service account key', name: 'authConfig.googleServiceAccount.value', startVersion: GoogleSheetsPluginVersions.V4, componentType: FormComponentType.CODE_EDITOR, language: EditorLanguage.JSON, placeholder: SERVICE_ACCOUNT_GHOST_TEXT, rules: [{ required: true, message: 'Service account key is required' }], display: { show: { authType: [GoogleSheetsAuthType.SERVICE_ACCOUNT] } } } ] }) ] }, actionTemplate: { sections: [ { name: 'main', items: [ // ACTION DROPDOWN { label: 'Action', name: 'action', startVersion: GoogleSheetsPluginVersions.V15, endVersion: GoogleSheetsPluginVersions.V18, componentType: FormComponentType.DROPDOWN, initialValue: GoogleSheetsActionType.READ_SPREADSHEET, rules: [{ required: true }], options: sortByDisplayName( Object.entries(GOOGLE_SHEETS_ACTION_DISPLAY_NAMES_V15).map(([value, displayName]) => makeDropdownItem(value, displayName)) ) }, { label: 'Action', name: 'action', startVersion: GoogleSheetsPluginVersions.V19, componentType: FormComponentType.DROPDOWN, initialValue: GoogleSheetsActionType.READ_SPREADSHEET, rules: [{ required: true }], options: sortByDisplayName( Object.entries(GOOGLE_SHEETS_ACTION_DISPLAY_NAMES_V19).map(([value, displayName]) => makeDropdownItem(value, displayName)) ) } ] }, { name: 'Spreadsheet information', borderThreshold: 0, items: [ { label: 'Spreadsheet', name: 'spreadsheetId', startVersion: GoogleSheetsPluginVersions.V1, componentType: FormComponentType.METADATA_DROPDOWN, keyAccessor: 'id', valueAccessor: 'id', displayNameAccessor: 'name', triggerGetMetadata: true, gSheetsPagination: true, display: { show: { action: [ GoogleSheetsActionType.READ_SPREADSHEET, GoogleSheetsActionType.READ_SPREADSHEET_RANGE, GoogleSheetsActionType.APPEND_SPREADSHEET, GoogleSheetsActionType.CREATE_SPREADSHEET_ROWS, GoogleSheetsActionType.CLEAR_SPREADSHEET, GoogleSheetsActionType.CREATE_WORKSHEET ] } }, rules: [{ required: true, message: 'Spreadsheet is required' }], showSearch: true, optionFilterProp: 'label' }, // SHEET NAME { label: 'Sheet name', name: 'sheetTitle', startVersion: GoogleSheetsPluginVersions.V1, componentType: FormComponentType.METADATA_DROPDOWN, dependencyFieldName: 'spreadsheetId', childIteratorAccessor: 'columns', keyAccessor: 'name', valueAccessor: 'name', displayNameAccessor: 'name', display: { show: { action: [ GoogleSheetsActionType.READ_SPREADSHEET, GoogleSheetsActionType.READ_SPREADSHEET_RANGE, GoogleSheetsActionType.APPEND_SPREADSHEET, GoogleSheetsActionType.CREATE_SPREADSHEET_ROWS, GoogleSheetsActionType.CLEAR_SPREADSHEET ] } }, rules: [{ required: true, message: 'Sheet name is required' }], showSearch: true, optionFilterProp: 'label' }, { label: 'Sheet name', name: 'addSheet.sheetTitle', startVersion: GoogleSheetsPluginVersions.V19, componentType: FormComponentType.DYNAMIC_INPUT_TEXT, display: { show: { action: [GoogleSheetsActionType.CREATE_WORKSHEET] } }, rules: [{ required: true, message: 'Sheet name is required' }] }, { label: 'Rows (optional)', name: 'addSheet.rowCount', startVersion: GoogleSheetsPluginVersions.V19, componentType: FormComponentType.DYNAMIC_INPUT_TEXT, display: { show: { action: [GoogleSheetsActionType.CREATE_WORKSHEET] } }, rules: [{ required: false }], tooltip: { markdownText: 'The number of rows in the grid' } }, { label: 'Columns (optional)', name: 'addSheet.columnCount', startVersion: GoogleSheetsPluginVersions.V19, componentType: FormComponentType.DYNAMIC_INPUT_TEXT, display: { show: { action: [GoogleSheetsActionType.CREATE_WORKSHEET] } }, rules: [{ required: false }], tooltip: { markdownText: 'The number of columns in the grid' } }, { label: 'Use first row of the sheet as the header', name: 'extractFirstRowHeader', startVersion: GoogleSheetsPluginVersions.V1, componentType: FormComponentType.CHECKBOX, initialValue: true, display: { show: { action: [GoogleSheetsActionType.READ_SPREADSHEET, GoogleSheetsActionType.READ_SPREADSHEET_RANGE] } }, tooltip: { markdownText: 'Use first row of the sheet as the header' } }, { label: 'Include a header row in this sheet', name: 'includeHeaderRow', startVersion: GoogleSheetsPluginVersions.V15, componentType: FormComponentType.SWITCH, initialValue: true, display: { show: { action: [GoogleSheetsActionType.CREATE_SPREADSHEET_ROWS] } } }, { label: 'Preserve table header', name: 'preserveHeaderRow', startVersion: GoogleSheetsPluginVersions.V15, componentType: FormComponentType.SWITCH, initialValue: true, display: { show: { action: [GoogleSheetsActionType.CLEAR_SPREADSHEET] } } }, { ...BASE_HEADER_ROW_NUMBER, display: { show: { action: [GoogleSheetsActionType.CLEAR_SPREADSHEET], preserveHeaderRow: ['true'] } } } as FormItem, { ...BASE_HEADER_ROW_NUMBER, display: { show: { action: [GoogleSheetsActionType.CREATE_SPREADSHEET_ROWS], includeHeaderRow: ['true'] } } } as FormItem ] }, { name: 'Read options', borderThreshold: 0, items: [ { label: 'Data range', name: 'range', startVersion: GoogleSheetsPluginVersions.V1, componentType: FormComponentType.DYNAMIC_INPUT_TEXT, initialValue: 'A1:D100', display: { show: { action: [GoogleSheetsActionType.READ_SPREADSHEET_RANGE] } }, tooltip: { markdownText: 'Range of cells to read from' } } ] }, { name: 'Write options', borderThreshold: 0, items: [ { label: 'Write location', name: 'writeToDestinationType', startVersion: GoogleSheetsPluginVersions.V15, componentType: FormComponentType.DROPDOWN, display: { show: { action: [GoogleSheetsActionType.CREATE_SPREADSHEET_ROWS] } }, options: [ { key: GoogleSheetsDestinationType.APPEND, value: GoogleSheetsDestinationType.APPEND, displayName: 'Append data starting at the first empty row' }, { key: GoogleSheetsDestinationType.ROW_NUMBER, value: GoogleSheetsDestinationType.ROW_NUMBER, displayName: 'Replace data starting at a specific row' } ], initialValue: GoogleSheetsDestinationType.APPEND, rules: [{ required: true, message: 'Write location is required' }] }, { label: 'Starting row number', name: 'rowNumber', startVersion: GoogleSheetsPluginVersions.V15, componentType: FormComponentType.DYNAMIC_INPUT_TEXT, placeholder: '2', rules: [{ required: true, message: 'Starting row number is required' }], display: { show: { action: [GoogleSheetsActionType.CREATE_SPREADSHEET_ROWS], writeToDestinationType: [GoogleSheetsDestinationType.ROW_NUMBER] } } } ] }, { name: 'Data to write', items: [ { label: 'Rows to append', name: 'data', startVersion: GoogleSheetsPluginVersions.V15, componentType: FormComponentType.CODE_EDITOR, language: EditorLanguage.JSON, placeholder: `[ { "name": "Billie Eilish", "email": "bad_guy@gmail.com", "date_joined": "2019-01-06" }, { "name": "Katy Perry", "email": "kaycat@hotmail.com", "date_joined": "2019-01-06" } ]`, style: { minHeight: '350px' }, display: { show: { action: [GoogleSheetsActionType.APPEND_SPREADSHEET, GoogleSheetsActionType.CREATE_SPREADSHEET_ROWS] } }, tooltip: { markdownText: `Write rows either at the end of the spreadsheet or at the specified row number.` }, rules: [{ required: true, message: 'An array of one or more row objects is required' }] } ] } ] } }; };