import {initializeBlock, useBase, useRecords, Loader, Button, Box} from '@airtable/blocks/ui'; import React, {Fragment, useState} from 'react'; // These values match the base for this example: https://airtable.com/shrBJH7LLUMD6ONIf const TABLE_NAME = 'Articles'; const TITLE_FIELD_NAME = 'Title'; const EXTRACT_FIELD_NAME = 'Extract'; const IMAGE_FIELD_NAME = 'Image'; // Airtable SDK limit: we can only update 50 records at a time. For more details, see // https://github.com/Airtable/blocks/blob/master/packages/sdk/docs/guide_writes.md#size-limits--rate-limits const MAX_RECORDS_PER_UPDATE = 50; // The API endpoint we're going to hit. For more details, see // https://en.wikipedia.org/api/rest_v1/#/Page%20content/get_page_summary__title_ const API_ENDPOINT = 'https://en.wikipedia.org/api/rest_v1/page/summary'; function WikipediaEnrichmentApp() { const base = useBase(); const table = base.getTableByName(TABLE_NAME); const titleField = table.getFieldByName(TITLE_FIELD_NAME); // load the records ready to be updated // we only need to load the word field - the others don't get read, only written to. const records = useRecords(table, {fields: [titleField]}); // keep track of whether we have up update currently in progress - if there is, we want to hide // the update button so you can't have two updates running at once. const [isUpdateInProgress, setIsUpdateInProgress] = useState(false); // check whether we have permission to update our records or not. Any time we do a permissions // check like this, we can pass in undefined for values we don't yet know. Here, as we want to // make sure we can update the summary and image fields, we make sure to include them even // though we don't know the values we want to use for them yet. const permissionCheck = table.checkPermissionsForUpdateRecord(undefined, { [EXTRACT_FIELD_NAME]: undefined, [IMAGE_FIELD_NAME]: undefined, }); async function onButtonClick() { setIsUpdateInProgress(true); const recordUpdates = await getExtractAndImageUpdatesAsync(table, titleField, records); await updateRecordsInBatchesAsync(table, recordUpdates); setIsUpdateInProgress(false); } return ( <Box // center the button/loading spinner horizontally and vertically. position="absolute" top="0" bottom="0" left="0" right="0" display="flex" flexDirection="column" justifyContent="center" alignItems="center" > {isUpdateInProgress ? ( <Loader /> ) : ( <Fragment> <Button variant="primary" onClick={onButtonClick} disabled={!permissionCheck.hasPermission} marginBottom={3} > Update summaries and images </Button> {!permissionCheck.hasPermission && // when we don't have permission to perform the update, we want to tell the // user why. `reasonDisplayString` is a human-readable string that will // explain why the button is disabled. permissionCheck.reasonDisplayString} </Fragment> )} </Box> ); } async function getExtractAndImageUpdatesAsync(table, titleField, records) { const recordUpdates = []; for (const record of records) { // for each record, we take the article title and make an API request: const articleTitle = record.getCellValueAsString(titleField); const requestUrl = `${API_ENDPOINT}/${encodeURIComponent(articleTitle)}?redirect=true`; const response = await fetch(requestUrl, {cors: true}); const pageSummary = await response.json(); // then, we can use the result of that API request to decide how we want to update our // record. To update an attachment, you need an array of objects with a `url` property. recordUpdates.push({ id: record.id, fields: { [EXTRACT_FIELD_NAME]: pageSummary.extract, [IMAGE_FIELD_NAME]: pageSummary.originalimage ? [{url: pageSummary.originalimage.source}] : undefined, }, }); // out of respect for the wikipedia API, a free public resource, we wait a short time // between making requests. If you change this example to use a different API, you might // not need this. await delayAsync(50); } return recordUpdates; } async function updateRecordsInBatchesAsync(table, recordUpdates) { // Fetches & saves the updates in batches of MAX_RECORDS_PER_UPDATE to stay under size limits. let i = 0; while (i < recordUpdates.length) { const updateBatch = recordUpdates.slice(i, i + MAX_RECORDS_PER_UPDATE); // await is used to wait for the update to finish saving to Airtable servers before // continuing. This means we'll stay under the rate limit for writes. await table.updateRecordsAsync(updateBatch); i += MAX_RECORDS_PER_UPDATE; } } function delayAsync(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } initializeBlock(() => <WikipediaEnrichmentApp />);