/**
* Questmate Custom Completion Item - Add Airtable Record
*
* Permissions:
* @UseApp {AIRTABLE}
*
* Changelog:
* v0.1 Initial release
* v0.2 Added email field mapping, fixed reference mapping
*
* eslint-disable
*/
enableDebugMode();
const fieldToItemTransformerMap = [
{
outputTypes: ["multipleRecordLinks"],
inputItemFilter: ["CustomV2"],
transformer: ({ itemType, fieldType, stringValue, rawValue }) => {
return [Object.values(rawValue)[0]];
},
},
{
outputTypes: ["singleSelect"],
inputItemFilter: ["SingleSelect"],
transformer: ({ itemType, fieldType, stringValue, rawValue }) => {
return rawValue.items
.filter((entry) => entry.checked)
.map((entry) => entry.text)[0];
},
},
{
outputTypes: ["singleLineText", "multilineText", "email"],
transformer: ({ stringValue }) => {
return stringValue;
},
},
];
return defineCustomItem((Questmate) => {
async function getSelectedTable(selectedBaseId, selectedTableId) {
const tables = await tablesDataSource.retrieve(selectedBaseId);
return tables.find(
(table) => !!selectedTableId && table.id === selectedTableId
);
}
Questmate.registerItemRunHandler(async ({ useConfigData, useQuest }) => {
const [selectedBaseId] = useConfigData("baseId");
const [selectedTableId] = useConfigData("tableId");
const [fieldToItemMap] = useConfigData("fieldToItemMap");
const quest = await useQuest();
const questRun = await quest.getRun();
const newRecordData = {};
const table = await getSelectedTable(selectedBaseId, selectedTableId);
for (const fieldId of Object.keys(fieldToItemMap)) {
const item = questRun.getItem(fieldToItemMap[fieldId]);
const airtableFieldType = table.fields.find(
(field) => field.id === fieldId
).type;
const transformerConfig = fieldToItemTransformerMap.find(
(transformerConfig) =>
transformerConfig.outputTypes.includes(airtableFieldType)
);
if (!transformerConfig) {
continue;
}
newRecordData[fieldId] = transformerConfig.transformer({
stringValue: await item.toString() || "",
rawValue: item.data || {},
itemType: item.type,
fieldType: airtableFieldType
});
};
console.log("NEW RECORD DATA", JSON.stringify(newRecordData));
const insertRecordRequest = await fetch(
`https://api.airtable.com/v0/${selectedBaseId}/${selectedTableId}`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
records: [
{
fields: newRecordData,
},
],
}),
}
);
const insertRecordResponseData = await insertRecordRequest.json();
console.log(JSON.stringify(insertRecordResponseData));
});
Questmate.registerView("ITEM_CONFIG_VIEW", async ({ useConfigData }) => {
const viewComponents = [];
const [selectedBaseId, setSelectedBaseId] = useConfigData("baseId", null);
const [selectedTableId, setSelectedTableId] = useConfigData(
"tableId",
null
);
const [fieldToItemMap, setFieldToItemMap] = useConfigData(
"fieldToItemMap",
{}
);
viewComponents.push({
id: "base",
title: "Airtable Base",
type: "dropdown",
optionNoun: "Base",
optionPluralNoun: "Bases",
value: selectedBaseId,
getOptions: async () =>
(await basesDataSource.retrieve()).map((base) => ({
label: base.name,
value: base.id,
})),
onSelect: async (newBaseId) => {
const changed = newBaseId !== selectedBaseId;
if (changed) {
const isValidBase =
newBaseId &&
(await basesDataSource.retrieve()).some(
(base) => base.id === newBaseId
);
setSelectedBaseId(isValidBase ? newBaseId : null);
setSelectedTableId(null);
}
},
});
if (selectedBaseId) {
viewComponents.push({
id: "table",
title: "Table",
type: "dropdown",
optionNoun: "Table",
optionPluralNoun: "Tables",
value: selectedTableId,
getOptions: async () =>
(await tablesDataSource.retrieve(selectedBaseId)).map((table) => ({
label: table.name,
value: table.id,
})),
onSelect: async (newTableId) => {
const changed = newTableId !== selectedTableId;
if (changed) {
const isValidTable =
newTableId &&
(await tablesDataSource.retrieve(selectedBaseId)).some(
(table) => table.id === newTableId
);
setSelectedTableId(isValidTable ? newTableId : null);
}
},
});
}
if (selectedTableId !== null) {
const table = await getSelectedTable(selectedBaseId, selectedTableId);
const fields = table.fields;
viewComponents.push({
id: `TextBlock1`,
type: "text",
content: "Choose the item to map to each field below.",
});
fields.forEach((field) => {
viewComponents.push({
id: field.id,
title: field.name,
type: "ItemPicker",
value: fieldToItemMap[field.id],
onSelect: (itemId) => {
if (itemId !== fieldToItemMap[field.id]) {
setFieldToItemMap({
...fieldToItemMap,
[field.id]: itemId,
});
}
},
});
});
}
return {
components: viewComponents,
};
});
const basesDataSource = Questmate.registerDataSource({
id: "bases",
initialData: [],
refreshInterval: 60,
fetcher: () => async () => {
const basesData = await fetch("https://api.airtable.com/v0/meta/bases");
const { bases } = await basesData.json();
if (!bases) {
return {
error: {
message: "Failed to retrieve bases",
details: { response: basesData },
},
};
}
return { data: bases };
},
});
const tablesDataSource = Questmate.registerDataSource({
id: "tables",
initialData: [],
refreshInterval: 60,
fetcher: () => async (baseId) => {
const tablesData = await fetch(
`https://api.airtable.com/v0/meta/bases/${baseId}/tables`
);
const { tables } = await tablesData.json();
if (!tables) {
return {
error: {
message: "Failed to retrieve tables",
details: { baseId, response: tablesData },
},
};
}
return { data: tables };
},
});
});
/**
* @UseApp {AIRTABLE}
*/
const asset = quest.getItem("asset"); // Airtable dropdown
const inspectorName = quest.getItem("inspectorName"); // Short Text
const inspectionDate = quest.getItem("inspectionDate"); // Date
const currentKilometers = quest.getItem("currentKilometers"); // Short Text
const brakesAndSteeringCheck = quest.getItem("brakesAndSteeringCheck"); // Single Choice
const photos = quest.getItem("photos"); // File upload
const getSelectionFromItem = (item) => {
return item.data.items
.filter((entry) => entry.checked)
.map((entry) => entry.text)[0];
};
const insertRowResponse = await fetch(
`https://api.airtable.com/v0/${asset.defaults.base}/___INSERT_TABLE_NAME___`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
records: [
{
fields: {
Assets: [asset.data.row],
"Inspector Name": inspectorName.data.text,
"Inspection Date": inspectionDate.data.value,
"Current Kilometers":
parseInt(currentKilometers.data.text, 10) || 0,
"Brakes & Steering Check": getSelectionFromItem(
brakesAndSteeringCheck
), // ["OK", "Faulty", "N/A"]
"Please insert photos of any reported faults":
photos.data.mediaItems.map((mediaItem) => ({
filename: mediaItem.name,
url: mediaItem.url,
})),
},
},
],
}),
}
);
console.log(JSON.stringify(insertRowResponse));