Skip to content

Commit

Permalink
Function to queue up modified documents.
Browse files Browse the repository at this point in the history
  • Loading branch information
bgoldowsky committed Sep 26, 2024
1 parent bdd53ed commit bf1c923
Show file tree
Hide file tree
Showing 6 changed files with 245 additions and 20 deletions.
18 changes: 14 additions & 4 deletions functions-v2/README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# Firebase functions

The functions are split into two folders `functions-v1` and `functions-v2`. This folder `functions-v2` contains the newer functions. We are hoping to incrementally migrate the legacy functions from `functions-v1` into this folder.

## Available Functions
Expand All @@ -8,8 +9,10 @@ The functions are split into two folders `functions-v1` and `functions-v2`. This
|_updateClassDocNetworksOnUserChange_|Monitors Firestore user documents for changes and updates the Firestore class documents with the networks of all of the teachers in these classes|

Here are the basic development operations you can do after you cd into the `functions-v2` directory:
```

```shell
$ cd functions-v2
$ nvm use 20 # Recent version of node is required for these functions
$ npm install # install local dependencies
$ npm run lint # lint the functions code
$ npm run test # runs jest (unit) tests for the functions code (requires emulator, see below)
Expand All @@ -19,32 +22,39 @@ $ npm run build # build the functions code (transpile TypeScript)
## Testing cloud functions

### Running tests locally (without running functions in the emulator)
```

```shell
$ npm run test:emulator # start the firestore and database emulators
$ npm run test # run all tests in `functions` directory
```

In this approach the functions are running inside of Jest and they connect to the emulated Firestore and Realtime database services.

The tests use `firebase-functions-test`. This package does a little setup of environment variables so when the functions run they will connect to the emulator. This package also provides a way to mock some standard events and wraps the calls to the functions to emulate how they would be called in the cloud. This is a simple and efficient way of testing the basic functionality without loading the function code into the emulator itself. The downside is that the functions are not responding to real events in Firestore or realtime database. If they are http functions they are not receiving the actual request event.

#### Notes

In the tests, the function cannot be imported normally. This is because the `firebase-functions-test`'s initialize function has to be called before the function code calls `initializeApp`. The standard practice for Firebase functions seems to be calling `initializeApp` at the module level not inside of the function body, so it will be called when the module is imported. The work around is to dynamically import the function. The docs for the `firebase-functions-test` use `require` to import the function, but we are trying to stick with the `import` syntax. The dynamic `import` syntax is asynchronous so it requires waiting, which means it can't be at the top level of the module. So the dynamic import of the function is inside of the test body. Typescript is able to track down the types for these dynamic imports. There is info about this approach in the code.

Because the tested functions are not responding to actual changes in the databases, it is necessary for the test to construct an event object that is then passed to the wrapped function. Additionally the database needs to be setup with documents before the test. The test has to make sure the event object is in sync with what is in the database.

`npm run emulator` and `npm run test:emulator` use a project name of `demo-test`. The `demo-` prefix is special and tells the emulator not to allow connections outside of itself. Without this project name being specified the emulator will use the project defined in `.firebaserc`, and will connect to the real version of any service that isn't being emulated.

### Running the functions in the emulator
```

```shell
npm run build
npm run emulator
```

This will load the built function code into the emulator. The only function we have so far is one that monitors Firestore docs for changes. So with the function running in the emulator you can manually change some docs and see if the function responds correctly.

## To deploy firebase functions to production:
```

```shell
$ npm run deploy # deploy all functions
```

## Differences with functions-v1

- in `v2` the firebase-tools are a devDependency: it is not necessary to install them globally
142 changes: 127 additions & 15 deletions functions-v2/package-lock.json

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

3 changes: 2 additions & 1 deletion functions-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,8 @@
"main": "lib/functions-v2/src/index.js",
"dependencies": {
"firebase-admin": "^12.1.0",
"firebase-functions": "^5.1.1"
"firebase-functions": "^5.1.1",
"openai": "^4.64.0"
},
"devDependencies": {
"@jest/globals": "^29.7.0",
Expand Down
1 change: 1 addition & 0 deletions functions-v2/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import * as admin from "firebase-admin";
export {onUserDocWritten} from "./on-user-doc-written";
export {atMidnight} from "./at-midnight";
export {onAnalyzableDocWritten} from "./on-analyzable-doc-written";

admin.initializeApp();
40 changes: 40 additions & 0 deletions functions-v2/src/on-analyzable-doc-written.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {onDocumentWritten} from "firebase-functions/v2/firestore";
import {getDatabase} from "firebase-admin/database";
import * as logger from "firebase-functions/logger";
// import * as admin from "firebase-admin";

// This is one of what will likely be multiple functions for AI analysis of documents:
// 1. (This function) watch for changes to the lastUpdatedAt metadata field and write a queue of docs to process
// 2. Create screenshots of those documents
// 3. Send those screenshots to the AI service for processing, and create document comments with the results

// For now, restrict processing to a particular root for testing.
// TODO later this will be a parameter.
const root = "demo/AI/portals/demo";

// Location of the queue of documents to process, relative to the root
const queuePath = "aiProcessingQueue";

export const onAnalyzableDocWritten =
onDocumentWritten(`${root}/classes/{classId}/users/{userId}/documentMetadata/{docId}/lastUpdatedAt`,
async (event) => {
const {classId, userId, docId} = event.params;
const database = getDatabase();
logger.info("Document update noticed", event.document, classId, userId, docId);

const timestamp = await database.ref(event.document).once("value").then((snap) => {
return snap.val();
},
(error) => {
logger.error("Error reading document", error);
});
getDatabase().ref(`${root}/${queuePath}`).update({
[docId]: {
metadataPath: `classes/${classId}/users/${userId}/documentMetadata/${docId}`,
updated: timestamp,
status: "unanalyzed",
},
});
});


Loading

0 comments on commit bf1c923

Please sign in to comment.