-
Notifications
You must be signed in to change notification settings - Fork 39
Add document transformation capability for indexing #123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
tharropoulos
wants to merge
9
commits into
typesense:master
Choose a base branch
from
tharropoulos:transform-func
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
98c81f2
feat: add transform func config params
tharropoulos eb41fea
feat(transform): add document transformation capability
tharropoulos 958ccf1
feat(indexing): integrate document transformation in indexOnWrite
tharropoulos 3b2c12e
feat(config): add document transformation configuration options
tharropoulos 55ebed9
fix: run util tests on default test script
tharropoulos 906db87
chore: lint
tharropoulos f89ea41
chore: remove logging checks
tharropoulos f5c6b8e
refactor(config): simplify transform function configuration defaults
tharropoulos bbb32a4
fix(test): fix env var in transform test
tharropoulos File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| const config = require("./config.js"); | ||
|
|
||
| const {error, debug} = require("firebase-functions/logger"); | ||
| const mapValue = (value) => { | ||
| const isObject = typeof value === "object"; | ||
| const notNull = value !== null; | ||
|
|
@@ -223,3 +223,50 @@ exports.pathMatchesSelector = function (path, selector) { | |
|
|
||
| return extractedValues; | ||
| }; | ||
|
|
||
| /** | ||
| * Transforms a document by calling a user-defined transform function | ||
| * If no transform function is defined, returns the original document | ||
| * @param {Object} document - The document to transform | ||
| * @return {Object} The transformed document | ||
| */ | ||
| exports.transformDocument = async function (document) { | ||
| const transformFunctionName = config.transformFunctionName; | ||
|
|
||
| if (!transformFunctionName) { | ||
| error("No transform function defined. Returning original document."); | ||
| return document; | ||
| } | ||
|
|
||
| try { | ||
| const projectId = config.transformFunctionProjectId; | ||
| const region = config.transformFunctionRegion; | ||
|
|
||
| debug(`Calling transform function: ${transformFunctionName}`); | ||
|
|
||
| const url = `https://${region}-${projectId}.cloudfunctions.net/${transformFunctionName}`; | ||
|
|
||
| const response = await fetch(url, { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tharropoulos Can we add automatic retries here with exponential backoff?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tharropoulos Looks like this is not addressed yet |
||
| method: "POST", | ||
| body: JSON.stringify({document}), | ||
| headers: {"Content-Type": "application/json", Authorization: `Bearer ${config.transformFunctionSecret}`}, | ||
| }); | ||
|
|
||
| if (!response.ok) { | ||
| throw new Error(`Error calling transform function: ${response.statusText}`); | ||
| } | ||
|
|
||
| const responseData = await response.json(); | ||
|
|
||
| if (responseData) { | ||
| debug(`Transform function succeeded for document ${document.id || "unknown"}`); | ||
| return responseData; | ||
| } | ||
|
|
||
| debug(`Transform function failed for document ${document.id || "unknown"}. Using original document.`); | ||
| return document; | ||
| } catch (err) { | ||
| error(`Error calling transform function: ${err.message}`); | ||
| return document; | ||
| } | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,136 @@ | ||
| const {TestEnvironment} = require("./support/testEnvironment"); | ||
|
|
||
| const mockFetch = jest.fn(); | ||
| global.fetch = (...args) => mockFetch(...args); | ||
|
|
||
| describe("Utils - transformDocument", () => { | ||
| let testEnvironment; | ||
| let utils; | ||
| let config; | ||
|
|
||
| beforeAll((done) => { | ||
| testEnvironment = new TestEnvironment({ | ||
| dotenvConfig: ` | ||
| LOCATION=us-central1 | ||
| FIRESTORE_DATABASE_REGION=nam5 | ||
| FIRESTORE_COLLECTION_PATH=books | ||
| FIRESTORE_COLLECTION_FIELDS=author,title | ||
| TYPESENSE_HOSTS=localhost | ||
| TYPESENSE_PORT=8108 | ||
| TYPESENSE_PROTOCOL=http | ||
| TYPESENSE_COLLECTION_NAME=books_firestore | ||
| TYPESENSE_API_KEY=xyz | ||
| TRANSFORM_FUNCTION_NAME= | ||
| TRANSFORM_FUNCTION_SECRET=test-secret | ||
| GCLOUD_PROJECT=test-project | ||
| `, | ||
| }); | ||
|
|
||
| testEnvironment.setupTestEnvironment(done); | ||
| }); | ||
|
|
||
| beforeEach(() => { | ||
| utils = require("../functions/src/utils.js"); | ||
| config = require("../functions/src/config.js"); | ||
|
|
||
| mockFetch.mockReset(); | ||
|
|
||
| testEnvironment.resetCapturedEmulatorLogs(); | ||
| }); | ||
|
|
||
| afterAll(async () => { | ||
| await testEnvironment.teardownTestEnvironment(); | ||
| }); | ||
|
|
||
| describe("when no transform function is defined", () => { | ||
| it("returns the original document and logs an error", async () => { | ||
| config.transformFunctionName = null; | ||
|
|
||
| const document = {id: "123", title: "Test Document"}; | ||
| const result = await utils.transformDocument(document); | ||
|
|
||
| expect(result).toEqual(document); | ||
|
|
||
| expect(mockFetch).not.toHaveBeenCalled(); | ||
| }); | ||
| }); | ||
|
|
||
| describe("when transform function is defined", () => { | ||
| beforeEach(() => { | ||
| config.transformFunctionName = "test-transform-function"; | ||
| }); | ||
|
|
||
| it("successfully transforms a document", async () => { | ||
| const document = {id: "123", title: "Test Document"}; | ||
| const transformedDocument = {id: "123", title: "Transformed Document"}; | ||
|
|
||
| mockFetch.mockResolvedValueOnce({ | ||
| ok: true, | ||
| json: async () => transformedDocument, | ||
| }); | ||
|
|
||
| const result = await utils.transformDocument(document); | ||
|
|
||
| expect(result).toEqual(transformedDocument); | ||
|
|
||
| expect(mockFetch).toHaveBeenCalledWith("https://us-central1-test-project.cloudfunctions.net/test-transform-function", { | ||
| method: "POST", | ||
| body: JSON.stringify({document}), | ||
| headers: { | ||
| "Content-Type": "application/json", | ||
| Authorization: "Bearer test-secret", | ||
| }, | ||
| }); | ||
| }); | ||
|
|
||
| it("returns original document when transform function returns null/undefined", async () => { | ||
| const document = {id: "123", title: "Test Document"}; | ||
|
|
||
| mockFetch.mockResolvedValueOnce({ | ||
| ok: true, | ||
| json: async () => null, | ||
| }); | ||
|
|
||
| const result = await utils.transformDocument(document); | ||
|
|
||
| expect(result).toEqual(document); | ||
| }); | ||
|
|
||
| it("returns original document when fetch response is not OK", async () => { | ||
| const document = {id: "123", title: "Test Document"}; | ||
|
|
||
| mockFetch.mockResolvedValueOnce({ | ||
| ok: false, | ||
| statusText: "Internal Server Error", | ||
| }); | ||
|
|
||
| const result = await utils.transformDocument(document); | ||
|
|
||
| expect(result).toEqual(document); | ||
| }); | ||
|
|
||
| it("returns original document when fetch throws an exception", async () => { | ||
| const document = {id: "123", title: "Test Document"}; | ||
|
|
||
| mockFetch.mockRejectedValueOnce(new Error("Network error")); | ||
|
|
||
| const result = await utils.transformDocument(document); | ||
|
|
||
| expect(result).toEqual(document); | ||
| }); | ||
|
|
||
| it("handles documents without an ID properly", async () => { | ||
| const document = {title: "Test Document Without ID"}; | ||
| const transformedDocument = {title: "Transformed Document"}; | ||
|
|
||
| mockFetch.mockResolvedValueOnce({ | ||
| ok: true, | ||
| json: async () => transformedDocument, | ||
| }); | ||
|
|
||
| const result = await utils.transformDocument(document); | ||
|
|
||
| expect(result).toEqual(transformedDocument); | ||
| }); | ||
| }); | ||
| }); |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are the above two lines still needed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have to call out to eh region-project id. Both of those are coming from env variables. The gcloud project is automatic and the location is being set from
extension.yaml.