There are a couple scenarios where you may want to pop your users right back into mapping. You may run into a situation where users have mapped their data and realize they accidentally mapped a column to the wrong header, or need to make modifications to their column mapping that they didn't realize on import. While they could start a new session, or delete everything in the table and start over manually, you could also set up a "back to mapping" button for them that will delete the data in the review table for them and get them back in the mapping experience with their file.
You could also have users importing a multi-sheet file, and want to enable them to get back into the mapping flow without clicking into the "Files" tab and re-initiating the mapping process.
The code examples below will demonstrate how to do this in your listener. This will require a minimum @flatfile/listener version of 0.3.18.
Re-Map Previously Mapped Columns
/**
* When the mapping job completes, let's cache the last config so we can restore it during the back
* operation and let's add the back action.
*/
listener.on("job:completed", { operation: "map" }, async ({ context }) => {
const { data: job } = await api.jobs.get(context.jobId);
// @ts-ignore
const { data: sheet } = await api.sheets.get(job.config.destinationSheetId);
const { data: workbook } = await api.workbooks.get(sheet.workbookId);
await api.workbooks.update(workbook.id, {
metadata: {
lastMappingConfig: {
type: "workbook",
operation: "map",
trigger: "manual",
source: job.source,
destination: job.destination,
mode: "foreground",
config: job.config,
},
},
actions: [
{
operation: "backToMappingAction",
mode: "foreground",
label: "Back to Mapping",
tooltip: "Go back to the mapping screen",
confirm: true,
description:
"Are you sure you want to go back to mapping? Any changes to data will be lost.",
},
// If you already have a submit action, you'll want to include this in the update call, otherwise it will be overwritten
{
operation: "submitAction",
mode: "foreground",
label: "Submit",
description: "Submit data to webhook.site",
primary: true,
},
],
});
});
/**
* Handle the back to mapping job by deleting records. Do not complete the job here, pick it up
* after the data deletion job completes.
*/
listener.on(
"job:ready",
{ operation: "backToMappingAction" },
async ({ context }) => {
const { jobId, workbookId } = context;
await api.jobs.ack(jobId, {
info: "One moment...",
progress: 10,
});
const { data: workbook } = await api.workbooks.get(workbookId);
// remove the back action
await api.workbooks.update(context.workbookId, {
actions: workbook.actions!.filter(
(a) => a.operation !== "backToMappingAction"
),
});
const sheets = await api.sheets.list({ workbookId });
await api.jobs.ack(jobId, {
info: "Deleting records...",
progress: 30,
});
// @ts-ignore
await api.jobs.create({
operation: "delete-records",
type: "workbook",
source: workbookId,
trigger: "immediate",
config: {
sheet: sheets.data[0].id,
filter: "all",
},
input: {
proceedMappingAfterComplete: true,
originalJobId: jobId,
},
});
}
);
/**
* After records are deleted, check to see if it was initiated by a back action. If so
* spin up a new mapping job using the most recent configuration and direct the user.
*/
listener.on(
"job:completed",
{ operation: "delete-records" },
async ({ context }) => {
const { data: job } = await api.jobs.get(context.jobId);
const { data: workbook } = await api.workbooks.get(context.workbookId);
if (
job.input!.proceedMappingAfterComplete &&
workbook.metadata.lastMappingConfig
) {
const { data: mappingJob } = await api.jobs.create(
workbook.metadata.lastMappingConfig
);
await api.jobs.complete(job.input!.originalJobId, {
outcome: {
trigger: "automatic_silent",
heading: "Reset complete, ready to redo mapping...",
next: {
type: "id",
id: context.spaceId,
path: `job/${mappingJob.id}`,
label: "Back to Mapping",
},
hideDefaultButton: true,
},
});
}
}
);
Map a New Sheet from a Multi-Sheet File
/**
* When the mapping job completes, let's cache the last config so we can restore it during the back
* operation and let's add the back action.
*/
listener.on("job:completed", { operation: "map" }, async ({ context }) => {
const { data: job } = await api.jobs.get(context.jobId);
// @ts-ignore
const { data: sheet } = await api.sheets.get(job.config.destinationSheetId);
const { data: workbook } = await api.workbooks.get(sheet.workbookId);
await api.workbooks.update(workbook.id, {
metadata: {
lastMappingConfig: {
type: "workbook",
operation: "map",
trigger: "manual",
source: job.source,
destination: job.destination,
mode: "foreground",
config: job.config,
},
},
actions: [
{
operation: "backToMappingAction",
mode: "foreground",
label: "Map another sheet",
tooltip: "Map another sheet in your file",
confirm: true,
},
{
operation: "submitAction",
mode: "foreground",
label: "Submit",
description: "Submit data to webhook.site",
primary: true,
},
],
});
});
/**
* Complete the job we just created and hop back to the first part of the mapping flow using
* the file that initiated the previous mapping job
*/
listener.on(
"job:ready",
{ operation: "backToMappingAction" },
async ({ context }) => {
const { jobId, workbookId, spaceId } = context;
const { data: workbook } = await api.workbooks.get(workbookId);
const { data: files} = await api.files.list({spaceId})
for (let file of files) {
if (file.workbookId === workbook.metadata.lastMappingConfig.source) {
await api.jobs.complete(jobId, {
outcome: {
trigger: "automatic_silent",
next: {
type: "id",
id: context.spaceId,
path: `files/${file.id}/import`,
label: "Back to Mapping",
},
},
});
}
}
}
);