Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 26 additions & 7 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions plugins/view-mapped/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# @flatfile/plugin-job-handler

## 1.0.1

### Patch Changes

- initial version
1 change: 1 addition & 0 deletions plugins/view-mapped/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# todo
61 changes: 61 additions & 0 deletions plugins/view-mapped/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
{
"name": "@flatfile/plugin-view-mapped",
"version": "1.0.1",
"url": "https://github.com/FlatFilers/flatfile-plugins/tree/main/plugins/view-mapped",
"description": "A plugin for making the view post mapping show only mapped columns.",
"registryMetadata": {
"category": "core"
},
"engines": {
"node": ">= 16"
},
"browser": {
"./dist/index.cjs": "./dist/index.browser.cjs",
"./dist/index.mjs": "./dist/index.browser.mjs"
},
"exports": {
"types": "./dist/index.d.ts",
"node": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
},
"browser": {
"require": "./dist/index.browser.cjs",
"import": "./dist/index.browser.mjs"
},
"default": "./dist/index.mjs"
},
"main": "./dist/index.cjs",
"module": "./dist/index.mjs",
"source": "./src/index.ts",
"types": "./dist/index.d.ts",
"files": [
"dist/**"
],
"scripts": {
"build": "rollup -c",
"build:watch": "rollup -c --watch",
"build:prod": "NODE_ENV=production rollup -c",
"check": "tsc ./**/*.ts --noEmit --esModuleInterop",
"test": "jest ./**/*.spec.ts --config=../../jest.config.js --runInBand"
},
"keywords": [
"flatfile-plugins",
"category-core"
],
"author": "Flatfile, Inc.",
"repository": {
"type": "git",
"url": "https://github.com/FlatFilers/flatfile-plugins.git",
"directory": "plugins/view-mapped"
},
"license": "ISC",
"dependencies": {
"@flatfile/api": "^1.9.7",
"@flatfile/listener": "^1.0.5"
},
"devDependencies": {
"@flatfile/rollup-config": "0.1.1",
"@flatfile/utils-testing": "^0.2.0"
}
}
5 changes: 5 additions & 0 deletions plugins/view-mapped/rollup.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { buildConfig } from '@flatfile/rollup-config'

const config = buildConfig({})

export default config
1 change: 1 addition & 0 deletions plugins/view-mapped/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './view-mapped'
123 changes: 123 additions & 0 deletions plugins/view-mapped/src/view-mapped.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import api from '@flatfile/api'
import type { FlatfileListener } from '@flatfile/listener'

/**
* This plugin allows you to make the post-mapping sheet only display mapped data
*/
export function viewMappedPlugin() {
return (listener: FlatfileListener) => {
// Defining what needs to be done when Flatfile is done mapping columns based on user input during the Mapping stage of the import process
listener.on(
'job:completed',
{ job: 'workbook:map' },
async ({ context: { jobId, workbookId } }) => {
// Creating a custom job that we will use in the next listener to ensure users only see mapped fields in the table
await api.jobs.create({
type: 'workbook',
operation: 'viewMappedFieldsOnly',
source: workbookId,
// This ensures that our custom job will execute automatically when the "job:ready" event of the listener below triggers
trigger: 'immediate',
// This ensures that users are not able to interact with records in the table until it is updated to only show mapped fields
mode: 'foreground',
// This ensures that in the next listener we are able to access the jobId of the mapping job specifically, and not just the jobId of this custom job
input: { mappingJobId: jobId },
})
}
)

// Defining what needs to be done when our custom job triggers. Because we create it when mapping job completes, this is when this job will begin executing
listener
.filter({ job: 'workbook:viewMappedFieldsOnly' })
.on('job:ready', async ({ context: { jobId, workbookId } }) => {
try {
// First, we acknowledge the job
await api.jobs.ack(jobId, {
info: 'Updating the table to only view mapped fields',
progress: 10,
})

// Retrieving the info on the custom job we created in the listener above, and storing that info in its own "customJobInfo" variable
const customJobInfo = await api.jobs.get(jobId)

// From "customJobInfo" variable, retrieving the jobId specifically of the mapping job that completed, and storing it in its own "mappingJobId" variable
const mappingJobId = customJobInfo.data.input.mappingJobId

// Obtaining the mapping job's execution plan to later extract "fieldMapping" out of it, which tells us which fields were mapped in the Matching step
const jobPlan = await api.jobs.getExecutionPlan(mappingJobId)

// Initializing an empty array to store the keys of the mapped fields
const mappedFields = []

// Iterating through all destination fields that are mapped and extracting their field keys. Then, pushing keys of mapped fields to the "mappedFields" variable
for (let i = 0; i < jobPlan.data.plan.fieldMapping.length; i++) {
const destinationFieldKey =
jobPlan.data.plan.fieldMapping[i].destinationField.key

mappedFields.push(destinationFieldKey)
}
// Making an API call to only get the "data" property out of the response, and saving it as its own "fetchedWorkbook" variable
// We need to make this API call and cannot just use what's inside of "workbookOne" because we need data in a specific format
const { data: workbook } = await api.workbooks.get(workbookId)

// Looping through all sheets of the Workbook One. For all fields that are mapped, updating those fields' metadata to "{mapped: true}"
workbook.sheets.forEach((sheet) => {
sheet.config.fields.forEach((field) => {
if (mappedFields.includes(field.key)) {
field.metadata = { mapped: true }
}
})
})

// Looping over each sheet in "workbook" and filtering for fields with metadata "mapped: true". Saving mapped fields per each sheet inside of "filteredWorkbookFields" varibable
const filteredWorkbookFields = workbook.sheets.map((sheet) => {
const fields = sheet.config.fields.filter(
(field) => field.metadata && field.metadata.mapped === true
)
return fields.length > 0 ? fields : null
})

// Updating each sheet in a workbook to only contain fields that a user mapped. This ensures that when the table with data loads, only mapped fields will be displayed
await api.workbooks.update(workbookId, {
// Keeping other non-sheet elements of the workbook untouched (Workbook name, its Submit action, etc)
...workbook,

// Mapping over each sheet to update each to only contain fields that are inside of "filteredWorkbookFields" variable (that have metadata "{mapped: true})"
sheets: workbook.sheets.map((sheet, index) => {
const mappedWorkbookFields = filteredWorkbookFields[index]

// If there are no mapped fields, returning the original sheet structure
if (!mappedWorkbookFields) {
return sheet
}

// If there are mapped fields, returning all properties of the original sheet but updating the "fields" property to the mapped fields
return {
...sheet,
config: {
...sheet.config,
fields: mappedWorkbookFields,
},
}
}),
})

// Completing the job with an appropriate message to the user
await api.jobs.complete(jobId, {
outcome: {
message: 'Table update complete. Please audit the data',
acknowledge: true,
},
})
} catch (error) {
// If something goes wrong while executing the custom job, we fail the job with a message on what next steps to take
await api.jobs.fail(jobId, {
outcome: {
message:
'An error occured while updating the workbook. See Event Logs.',
},
})
}
})
Comment on lines +30 to +121

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Optimize field mapping extraction.

The current implementation uses a loop to extract mapped fields. Consider using array methods like map and reduce for a more functional approach, which can improve readability and performance.

-  for (let i = 0; i < jobPlan.data.plan.fieldMapping.length; i++) {
-    const destinationFieldKey =
-      jobPlan.data.plan.fieldMapping[i].destinationField.key
-    mappedFields.push(destinationFieldKey)
-  }
+  const mappedFields = jobPlan.data.plan.fieldMapping.map(
+    (mapping) => mapping.destinationField.key
+  )
Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
listener
.filter({ job: 'workbook:viewMappedFieldsOnly' })
.on('job:ready', async ({ context: { jobId, workbookId } }) => {
try {
// First, we acknowledge the job
await api.jobs.ack(jobId, {
info: 'Updating the table to only view mapped fields',
progress: 10,
})
// Retrieving the info on the custom job we created in the listener above, and storing that info in its own "customJobInfo" variable
const customJobInfo = await api.jobs.get(jobId)
// From "customJobInfo" variable, retrieving the jobId specifically of the mapping job that completed, and storing it in its own "mappingJobId" variable
const mappingJobId = customJobInfo.data.input.mappingJobId
// Obtaining the mapping job's execution plan to later extract "fieldMapping" out of it, which tells us which fields were mapped in the Matching step
const jobPlan = await api.jobs.getExecutionPlan(mappingJobId)
// Initializing an empty array to store the keys of the mapped fields
const mappedFields = []
// Iterating through all destination fields that are mapped and extracting their field keys. Then, pushing keys of mapped fields to the "mappedFields" variable
for (let i = 0; i < jobPlan.data.plan.fieldMapping.length; i++) {
const destinationFieldKey =
jobPlan.data.plan.fieldMapping[i].destinationField.key
mappedFields.push(destinationFieldKey)
}
// Making an API call to only get the "data" property out of the response, and saving it as its own "fetchedWorkbook" variable
// We need to make this API call and cannot just use what's inside of "workbookOne" because we need data in a specific format
const { data: workbook } = await api.workbooks.get(workbookId)
// Looping through all sheets of the Workbook One. For all fields that are mapped, updating those fields' metadata to "{mapped: true}"
workbook.sheets.forEach((sheet) => {
sheet.config.fields.forEach((field) => {
if (mappedFields.includes(field.key)) {
field.metadata = { mapped: true }
}
})
})
// Looping over each sheet in "workbook" and filtering for fields with metadata "mapped: true". Saving mapped fields per each sheet inside of "filteredWorkbookFields" varibable
const filteredWorkbookFields = workbook.sheets.map((sheet) => {
const fields = sheet.config.fields.filter(
(field) => field.metadata && field.metadata.mapped === true
)
return fields.length > 0 ? fields : null
})
// Updating each sheet in a workbook to only contain fields that a user mapped. This ensures that when the table with data loads, only mapped fields will be displayed
await api.workbooks.update(workbookId, {
// Keeping other non-sheet elements of the workbook untouched (Workbook name, its Submit action, etc)
...workbook,
// Mapping over each sheet to update each to only contain fields that are inside of "filteredWorkbookFields" variable (that have metadata "{mapped: true})"
sheets: workbook.sheets.map((sheet, index) => {
const mappedWorkbookFields = filteredWorkbookFields[index]
// If there are no mapped fields, returning the original sheet structure
if (!mappedWorkbookFields) {
return sheet
}
// If there are mapped fields, returning all properties of the original sheet but updating the "fields" property to the mapped fields
return {
...sheet,
config: {
...sheet.config,
fields: mappedWorkbookFields,
},
}
}),
})
// Completing the job with an appropriate message to the user
await api.jobs.complete(jobId, {
outcome: {
message: 'Table update complete. Please audit the data',
acknowledge: true,
},
})
} catch (error) {
// If something goes wrong while executing the custom job, we fail the job with a message on what next steps to take
await api.jobs.fail(jobId, {
outcome: {
message:
'An error occured while updating the workbook. See Event Logs.',
},
})
}
})
listener
.filter({ job: 'workbook:viewMappedFieldsOnly' })
.on('job:ready', async ({ context: { jobId, workbookId } }) => {
try {
// First, we acknowledge the job
await api.jobs.ack(jobId, {
info: 'Updating the table to only view mapped fields',
progress: 10,
})
// Retrieving the info on the custom job we created in the listener above, and storing that info in its own "customJobInfo" variable
const customJobInfo = await api.jobs.get(jobId)
// From "customJobInfo" variable, retrieving the jobId specifically of the mapping job that completed, and storing it in its own "mappingJobId" variable
const mappingJobId = customJobInfo.data.input.mappingJobId
// Obtaining the mapping job's execution plan to later extract "fieldMapping" out of it, which tells us which fields were mapped in the Matching step
const jobPlan = await api.jobs.getExecutionPlan(mappingJobId)
// Using map to extract the keys of the mapped fields
const mappedFields = jobPlan.data.plan.fieldMapping.map(
(mapping) => mapping.destinationField.key
)
// Making an API call to only get the "data" property out of the response, and saving it as its own "fetchedWorkbook" variable
// We need to make this API call and cannot just use what's inside of "workbookOne" because we need data in a specific format
const { data: workbook } = await api.workbooks.get(workbookId)
// Looping through all sheets of the Workbook One. For all fields that are mapped, updating those fields' metadata to "{mapped: true}"
workbook.sheets.forEach((sheet) => {
sheet.config.fields.forEach((field) => {
if (mappedFields.includes(field.key)) {
field.metadata = { mapped: true }
}
})
})
// Looping over each sheet in "workbook" and filtering for fields with metadata "mapped: true". Saving mapped fields per each sheet inside of "filteredWorkbookFields" varibable
const filteredWorkbookFields = workbook.sheets.map((sheet) => {
const fields = sheet.config.fields.filter(
(field) => field.metadata && field.metadata.mapped === true
)
return fields.length > 0 ? fields : null
})
// Updating each sheet in a workbook to only contain fields that a user mapped. This ensures that when the table with data loads, only mapped fields will be displayed
await api.workbooks.update(workbookId, {
// Keeping other non-sheet elements of the workbook untouched (Workbook name, its Submit action, etc)
...workbook,
// Mapping over each sheet to update each to only contain fields that are inside of "filteredWorkbookFields" variable (that have metadata "{mapped: true})"
sheets: workbook.sheets.map((sheet, index) => {
const mappedWorkbookFields = filteredWorkbookFields[index]
// If there are no mapped fields, returning the original sheet structure
if (!mappedWorkbookFields) {
return sheet
}
// If there are mapped fields, returning all properties of the original sheet but updating the "fields" property to the mapped fields
return {
...sheet,
config: {
...sheet.config,
fields: mappedWorkbookFields,
},
}
}),
})
// Completing the job with an appropriate message to the user
await api.jobs.complete(jobId, {
outcome: {
message: 'Table update complete. Please audit the data',
acknowledge: true,
},
})
} catch (error) {
// If something goes wrong while executing the custom job, we fail the job with a message on what next steps to take
await api.jobs.fail(jobId, {
outcome: {
message:
'An error occured while updating the workbook. See Event Logs.',
},
})
}
})

}
}