/**
* Questmate Custom Completion Item - Add Notion Database Record
*
* Permissions:
* @UseApp {NOTION}
*
* Changelog:
* v0.1 Initial release
* v0.2 Added ability to map submitter's name to Notion record
*
* eslint-disable
*/
enableDebugMode();
const questDataToPropertyTransformerMap = [
{
outputTypes: ["rich_text"],
transformer: async ({ propertyType, value }) => {
return { rich_text: [{ text: { content: await value.toString() } }] };
},
},
{
outputTypes: ["title"],
transformer: async ({ propertyType, value }) => {
return { title: [{ text: { content: await value.toString() } }] };
},
},
];
return defineCustomItem((Questmate) => {
async function getSelectedDatabase(selectedDatabaseId) {
const databases = await databasesDataSource.retrieve();
return databases.find(
(database) => !!selectedDatabaseId && database.id === selectedDatabaseId
);
}
Questmate.registerItemRunHandler(async ({ useConfigData, useQuest }) => {
const [selectedDatabaseId] = useConfigData("databaseId");
const [propertyToQuestDataIdentifierMap] = useConfigData(
"propertyToQuestDataIdentifierMap"
);
const quest = await useQuest();
const questRun = await quest.getRun();
const newRecordData = {};
const database = await getSelectedDatabase(selectedDatabaseId);
for (const propertyId of Object.keys(propertyToQuestDataIdentifierMap)) {
const value = questRun.get(propertyToQuestDataIdentifierMap[propertyId]);
const notionPropertyType = Object.values(database.properties).find(
(property) => property.id === propertyId
).type;
const transformerConfig = questDataToPropertyTransformerMap.find(
(transformerConfig) =>
transformerConfig.outputTypes.includes(notionPropertyType)
);
if (!transformerConfig) {
continue;
}
newRecordData[propertyId] = await transformerConfig.transformer({
value,
propertyType: notionPropertyType,
});
}
console.log("NEW RECORD DATA", JSON.stringify(newRecordData));
const insertRecordRequest = await fetch(`https://api.notion.com/v1/pages`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Notion-Version": "2022-06-28",
},
body: JSON.stringify({
parent: { database_id: selectedDatabaseId },
properties: newRecordData,
}),
});
const insertRecordResponseData = await insertRecordRequest.json();
console.log(JSON.stringify(insertRecordResponseData));
});
Questmate.registerView("ITEM_CONFIG_VIEW", async ({ useConfigData }) => {
const viewComponents = [];
const [selectedDatabaseId, setSelectedDatabaseId] = useConfigData(
"databaseId",
null
);
const [
propertyToQuestDataIdentifierMap,
setPropertyToQuestDataIdentifierMap,
] = useConfigData("propertyToQuestDataIdentifierMap", {});
viewComponents.push({
id: "base",
title: "Database",
type: "dropdown",
optionNoun: "Database",
optionPluralNoun: "Databases",
value: selectedDatabaseId,
getOptions: async () =>
(await databasesDataSource.retrieve()).map((base) => ({
label: base.title[0].plain_text,
value: base.id,
})),
onSelect: async (newDatabaseId) => {
const changed = newDatabaseId !== selectedDatabaseId;
if (changed) {
const isValidDatabase =
newDatabaseId &&
(await databasesDataSource.retrieve()).some(
(base) => base.id === newDatabaseId
);
setSelectedDatabaseId(isValidDatabase ? newDatabaseId : null);
}
},
});
if (selectedDatabaseId !== null) {
const database = await getSelectedDatabase(selectedDatabaseId);
const properties = database.properties;
viewComponents.push({
id: `TextBlock1`,
type: "text",
content: "Choose which Quest data to map to each property below.",
});
const supportedNotionPageTypes = questDataToPropertyTransformerMap
.flatMap((transformer) => transformer.outputTypes)
.filter((value, index, self) => self.indexOf(value) === index);
Object.entries(properties).forEach(([, property]) => {
if (!supportedNotionPageTypes.includes(property.type)) {
viewComponents.push({
id: property.id,
optionNoun: "Compatible Quest Data",
optionPluralNoun: "Compatible Quest Data",
title: property.name,
type: "dropdown",
onSelect: () => {},
value: null,
getOptions: () => [],
});
} else {
viewComponents.push({
id: property.id,
title: property.name,
type: "QuestDataPicker",
value: propertyToQuestDataIdentifierMap[property.id],
onSelect: (questDataIdentifier) => {
if (
JSON.stringify(questDataIdentifier) !==
JSON.stringify(propertyToQuestDataIdentifierMap[property.id])
) {
setPropertyToQuestDataIdentifierMap({
...propertyToQuestDataIdentifierMap,
[property.id]: questDataIdentifier,
});
}
},
});
}
});
}
return {
components: viewComponents,
};
});
const databasesDataSource = Questmate.registerDataSource({
id: "databases",
initialData: [],
refreshInterval: 60,
aggregator: ({ results, staleResults }) => {
// combine stale pages with fresh pages to provide most up-to-date information
return (
[...results, ...staleResults]
.flatMap((result) => result.data)
// filter out duplicate row data
.filter(
(row, index, self) =>
self.findIndex(({ id }) => id === row.id) === index
)
);
},
fetcher:
({ nextPageMarker }) =>
async () => {
const databasesData = await fetch("https://api.notion.com/v1/search", {
method: "POST",
headers: {
"Content-Type": "application/json",
"Notion-Version": "2022-06-28",
},
body: JSON.stringify({
filter: {
value: "database",
property: "object",
},
sort: {
direction: "ascending",
timestamp: "last_edited_time",
},
page_size: 100,
...(nextPageMarker && { start_cursor: nextPageMarker }),
}),
});
const databases = await databasesData.json();
if (!databases.results) {
return {
error: {
message: "Failed to retrieve databases",
details: { response: databasesData },
},
};
}
return {
data: databases.results,
nextPageMarker: databases.has_more && databases.next_cursor,
};
},
});
});