Skip to content

Commit 0034528

Browse files
authored
December 12, 2019 Release
2 parents ab7d229 + ebaf6f0 commit 0034528

38 files changed

+164
-91
lines changed

.github/CODEOWNERS

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# All documentation changes, e.g. changelogs & readmes.
22
*.md @rachelsaunders
3+
*/extension.yaml @rachelsaunders
34

45
# Dependencies, package.json and mono-repo files
56
package.json @Salakar @Ehesp

.github/ISSUE_TEMPLATE/bug.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ other category use one of these other channels:
2424
### [REQUIRED] Step 2: Describe your configuration
2525

2626
- Extension name: **\_** (`storage-resize-images`, `firestore-send-email`, etc)
27+
- Extension version: **\_**
2728
- Configuration values (redact info where appropriate):
2829
- **\_**
2930
- **\_**

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package-lock.json
77
# generated files
88
README.md
99
**/functions/lib/**
10+
**/lib/**
1011
**/dist/**
1112

1213
# extension install md files
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
## Version 0.1.1
2+
3+
fixed - Fixed occasional duplicate rows created in the BigQuery backup table (issue #101).

firestore-bigquery-export/extension.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
name: firestore-bigquery-export
1616
displayName: Export Collections to BigQuery
1717
specVersion: v1beta
18-
version: 0.1.0
18+
version: 0.1.1
1919

2020
description:
2121
Sends realtime, incremental updates from a specified Cloud Firestore collection to BigQuery.

firestore-bigquery-export/firestore-bigquery-change-tracker/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
"url": "github.com/firebase/extensions.git",
66
"directory": "firestore-bigquery-export/firestore-bigquery-change-tracker"
77
},
8-
"version": "1.0.0",
8+
"version": "1.0.1",
99
"description": "Core change-tracker library for Cloud Firestore Collection BigQuery Exports",
1010
"main": "./lib/index.js",
1111
"scripts": {

firestore-bigquery-export/firestore-bigquery-change-tracker/src/bigquery/index.ts

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,29 +51,38 @@ export class FirestoreBigQueryEventHistoryTracker
5151

5252
async record(events: FirestoreDocumentChangeEvent[]) {
5353
await this.initialize();
54-
54+
const options = {
55+
raw: true,
56+
};
5557
const rows = events.map((event) => {
56-
// This must match firestoreToBQTable().
5758
return {
58-
timestamp: event.timestamp,
59-
event_id: event.eventId,
60-
document_name: event.documentName,
61-
operation: ChangeType[event.operation],
62-
data: JSON.stringify(event.data),
59+
insertId: event.eventId,
60+
json: {
61+
timestamp: event.timestamp,
62+
event_id: event.eventId,
63+
document_name: event.documentName,
64+
operation: ChangeType[event.operation],
65+
data: JSON.stringify(event.data),
66+
},
6367
};
6468
});
65-
await this.insertData(rows);
69+
await this.insertData(rows, options);
6670
}
6771

6872
/**
6973
* Inserts rows of data into the BigQuery raw change log table.
7074
*/
71-
private async insertData(rows: bigquery.RowMetadata[]) {
75+
private async insertData(rows: bigquery.RowMetadata[], options: object) {
76+
const payload = {
77+
skipInvalidRows: false,
78+
ignoreUnkownValues: false,
79+
rows: rows,
80+
};
7281
try {
7382
const dataset = this.bq.dataset(this.config.datasetId);
7483
const table = dataset.table(this.rawChangeLogTableName());
7584
logs.dataInserting(rows.length);
76-
await table.insert(rows);
85+
await table.insert(payload, options);
7786
logs.dataInserted(rows.length);
7887
} catch (e) {
7988
// Reinitializing in case the destintation table is modified.

firestore-bigquery-export/firestore-bigquery-change-tracker/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
"target": "es6"
99
},
1010
"compileOnSave": true,
11-
"include": ["src"]
11+
"include": ["src"],
12+
"exclude": ["**/node_modules/@types/jest/**"]
1213
}

firestore-bigquery-export/functions/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"main": "lib/index.js",
1515
"dependencies": {
1616
"firebase-admin": "~7.0.0",
17-
"@firebaseextensions/firestore-bigquery-change-tracker": "^1.0.0",
17+
"@firebaseextensions/firestore-bigquery-change-tracker": "^1.0.1",
1818
"firebase-functions": "^2.3.0",
1919
"sql-formatter": "^2.3.3"
2020
},

firestore-bigquery-export/guides/IMPORT_EXISTING_DOCUMENTS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ Run the import script using [`npx` (the Node Package Runner)](https://www.npmjs.
5959
```
6060
SELECT COUNT(*) FROM
6161
`${PROJECT_ID}.${COLLECTION_PATH}.${COLLECTION_PATH}_raw_changelog`
62-
WHERE operation = "import"
62+
WHERE operation = "IMPORT"
6363
```
6464
6565
The result set will contain the number of documents in your source collection.

firestore-bigquery-export/scripts/gen-schema-view/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,5 +9,6 @@
99
"types": ["node", "mocha", "chai"]
1010
},
1111
"compileOnSave": true,
12-
"include": ["src"]
12+
"include": ["src"],
13+
"exclude": ["**/node_modules/@types/jest/**"]
1314
}

firestore-bigquery-export/scripts/import/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
"author": "Jan Wyszynski <wyszynski@google.com>",
2525
"license": "Apache-2.0",
2626
"dependencies": {
27-
"@firebaseextensions/firestore-bigquery-change-tracker": "^1.0.0",
27+
"@firebaseextensions/firestore-bigquery-change-tracker": "^1.0.1",
2828
"@google-cloud/bigquery": "^2.1.0",
2929
"firebase-admin": "^7.1.1",
3030
"firebase-functions": "^2.2.1",

firestore-bigquery-export/scripts/import/src/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ const run = async (): Promise<number> => {
163163

164164
let cursorPositionFile =
165165
__dirname +
166-
`/from-${sourceCollectionPath}-to-${projectId}\:${datasetId}\:${rawChangeLogName}`;
166+
`/from-${sourceCollectionPath}-to-${projectId}_${datasetId}_${rawChangeLogName}`;
167167
if (await exists(cursorPositionFile)) {
168168
let cursorDocumentId = (await read(cursorPositionFile)).toString();
169169
cursor = await firebase

firestore-bigquery-export/scripts/import/tsconfig.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@
88
"target": "es6"
99
},
1010
"compileOnSave": true,
11-
"include": ["src"]
11+
"include": ["src"],
12+
"exclude": ["**/node_modules/@types/jest/**"]
1213
}

firestore-counter/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
## Version 0.1.1
2+
3+
changed - Moves the logic for monitoring the extension's workload from the existing HTTP function to a new Pub/Sub controllerCore function. Now, if called, the HTTP function triggers the new `controllerCore` function instead. This change was made to accommodate a [change in the way Google Cloud Functions handles HTTP functions](https://cloud.google.com/functions/docs/securing/managing-access#allowing_unauthenticated_function_invocation).
4+
5+
We recommend that you edit your existing Cloud Scheduler job to instead send a message to the extension's Pub/Sub topic which triggers the new `controllerCore` function (detailed instructions provided in the [POSTINSTALL file](https://github.com/firebase/extensions/blob/master/firestore-counter/POSTINSTALL.md#set-up-a-cloud-scheduler-job). Although it's not recommended, if you leave your Cloud Scheduler job calling the HTTP function, your extension will continue to run as expected.

firestore-counter/POSTINSTALL.md

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
### Post-installation configuration
22

3-
Before you can use this extension, you'll need to update your security rules, set up a scheduled function, and add some code to your JavaScript app.
3+
Before you can use this extension, you'll need to update your security rules, set up a Cloud Scheduler job, and add some code to your JavaScript app.
44

55
#### Update security rules
66

@@ -18,17 +18,23 @@ match /databases/{database}/documents/pages/{page} {
1818
}
1919
```
2020

21-
#### Set up a scheduled function
2221

23-
Review the [scheduled function documentation](https://firebase.google.com/docs/functions/schedule-functions) to set up a call to `${function:controller.url}` every minute. You may need to enable some APIs in your Firebase project to use scheduled functions.
22+
#### Set up a Cloud Scheduler job
2423

25-
As an example, to set up a scheduled function, you can run the following [`gcloud`](https://cloud.google.com/sdk/gcloud/) commands:
24+
**IMPORTANT:** Note the following about v0.1.1 of this extension:
25+
- **If you updated your extension from v0.1.0 to v0.1.1:** We recommend that you edit your Cloud Scheduler job to instead send a message to the extension's Pub/Sub topic, as described in this section. Although it's not recommended, if you leave your Cloud Scheduler job calling `${function:controller.url}`, your extension will continue to run as expected. For more information about the changes for v0.1.1, refer to the [changelog](https://github.com/firebase/extensions/blob/master/firestore-counter/CHANGELOG.md).
26+
- **If you installed this extension for the first time at v0.1.1:** Follow the instructions as described in this section.
27+
28+
Set up a [Cloud Scheduler job](https://cloud.google.com/scheduler/docs/quickstart) to regularly send a message to the extension's Pub/Sub topic (`${param:EXT_INSTANCE_ID}`). This Pub/Sub topic then automatically triggers the controllerCore function (`${function:controllerCore.name}`). This controllerCore function is created by the extension. It works by either aggregating shards itself or scheduling and monitoring workers to aggregate shards.
29+
30+
As an example, to set up the required Cloud Scheduler job, you can run the following `gcloud` commands:
2631

2732
```
28-
gcloud services enable cloudscheduler.googleapis.com
29-
gcloud scheduler jobs create http firestore-sharded-counter-controller --schedule="* * * * *" --uri=${function:controller.url} --project=${param:PROJECT_ID}
33+
gcloud --project=${param:PROJECT_ID} services enable cloudscheduler.googleapis.com
34+
gcloud --project=${param:PROJECT_ID} scheduler jobs create pubsub ${param:EXT_INSTANCE_ID} --schedule="* * * * *" --topic=${param:EXT_INSTANCE_ID} --message-body="{}"
3035
```
3136

37+
3238
#### Specify a document path and increment value in your app
3339

3440
1. Download and copy the [Counter SDK](https://github.com/firebase/extensions/blob/master/firestore-counter/clients/web/dist/sharded-counter.js) into your application project.
@@ -77,7 +83,7 @@ After you complete the post-installation configuration above, the process runs a
7783

7884
1. The client SDK writes to these subcollections to distribute the write load.
7985

80-
1. The scheduled function that you deployed sums the subcollections' values into the single `visits` field (or whichever field you configured in your master document).
86+
1. The controllerCore function sums the subcollections' values into the single `visits` field (or whichever field you configured in your master document).
8187

8288
1. After each summation, the extension deletes the subcollections, leaving only the count in the master document. This is the document field to which you should listen for the count.
8389

firestore-counter/PREINSTALL.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ Before installing this extension, make sure that you've [set up a Cloud Firestor
1818
After installing this extension, you'll need to:
1919

2020
- Update your [database security rules](https://firebase.google.com/docs/rules).
21-
- Set up a [scheduled function](https://firebase.google.com/docs/functions/schedule-functions) to regularly call the controller function, which is created by this extension and monitors the extension's workload.
21+
- Set up a [Cloud Scheduler job](https://cloud.google.com/scheduler/docs/quickstart) to regularly call the controllerCore function, which is created by this extension. It works by either aggregating shards itself or scheduling and monitoring workers to aggregate shards.
2222
- Install the provided [Counter SDK](https://github.com/firebase/extensions/blob/master/firestore-counter/clients/web/src/index.ts) in your app. You can then use this library in your code to specify your document path and increment values.
2323

2424
Detailed information for these post-installation tasks are provided after you install this extension.

firestore-counter/extension.yaml

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
name: firestore-counter
1616
displayName: Distributed Counter
1717
specVersion: v1beta
18-
version: 0.1.0
18+
version: 0.1.1
1919

2020
description:
2121
Records event counters at scale to accommodate high-velocity writes to Cloud Firestore.
@@ -35,14 +35,32 @@ contributors:
3535

3636
roles:
3737
- role: datastore.user
38-
reason: Allows the extension to aggregate Cloud Firestore counter shards.
38+
reason:
39+
Allows the extension to aggregate Cloud Firestore counter shards.
40+
- role: pubsub.publisher
41+
reason:
42+
Allows the HTTPS controller function to publish a message to the extension's Pub/Sub topic,
43+
which triggers the controllerCore function.
3944

4045
resources:
41-
- name: controller
46+
- name: controllerCore
4247
type: firebaseextensions.v1beta.function
4348
description:
4449
Scheduled to run every minute.
4550
This function either aggregates shards itself, or it schedules and monitors workers to aggregate shards.
51+
properties:
52+
sourceDirectory: .
53+
location: ${LOCATION}
54+
maxInstances: 1
55+
eventTrigger:
56+
eventType: google.pubsub.topic.publish
57+
resource: projects/${PROJECT_ID}/topics/${EXT_INSTANCE_ID}
58+
59+
- name: controller
60+
type: firebaseextensions.v1beta.function
61+
description:
62+
Maintained for backwards compatibility.
63+
This function relays a message to the extension's Pub/Sub topic to trigger the controllerCore function.
4664
properties:
4765
sourceDirectory: .
4866
location: ${LOCATION}
@@ -67,7 +85,7 @@ resources:
6785
description:
6886
Monitors a range of shards and aggregates them, as needed.
6987
There may be 0 or more worker functions running at any point in time.
70-
The controller function is responsible for scheduling and monitoring these workers.
88+
The controllerCore function is responsible for scheduling and monitoring these workers.
7189
properties:
7290
sourceDirectory: .
7391
location: ${LOCATION}
@@ -108,4 +126,8 @@ params:
108126
What is the path to the document where the extension can keep its internal state?
109127
default: _firebase_ext_/sharded_counter
110128
example: _firebase_ext_/sharded_counter
129+
validationRegex: "^[^/]+/[^/]+(/[^/]+/[^/]+)*$"
130+
validationErrorMessage:
131+
Enter a document path, not a collection path. The path must have an even number of segments,
132+
for example, `my_collection/doc` or `my_collection/doc/subcollection/doc`, but not `my_collection`.
111133
required: true

firestore-counter/functions/lib/aggregator.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ class NumericUpdate {
2424
/**
2525
* Merges numeric values from an arbitrary deep json into the NumericUpdate object.
2626
* - it ignores non-numeric leaves
27-
* - if there's a type mismatch ('number' vs 'object') current data will be overriden
27+
* - if there's a type mismatch ('number' vs 'object') current data will be overridden
2828
* @param from An object with numeric values to merge from.
2929
*/
3030
mergeFrom(from) {
@@ -33,7 +33,7 @@ class NumericUpdate {
3333
/**
3434
* Subtracts numeric values in an arbitrary deep json from the NumericUpdate object.
3535
* - it ignores non-numeric leaves
36-
* - if there's a type mismatch ('number' vs 'object') current data will be overriden
36+
* - if there's a type mismatch ('number' vs 'object') current data will be overridden
3737
* @param from An object with numeric values to merge from.
3838
*/
3939
subtractFrom(from) {

firestore-counter/functions/lib/common.js

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,6 @@
1414
* See the License for the specific language governing permissions and
1515
* limitations under the License.
1616
*/
17-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
18-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
19-
return new (P || (P = Promise))(function (resolve, reject) {
20-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
21-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
22-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
23-
step((generator = generator.apply(thisArg, _arguments || [])).next());
24-
});
25-
};
2617
Object.defineProperty(exports, "__esModule", { value: true });
2718
function isUpdatedFrequently(shard) {
2819
if (!shard.exists)
@@ -58,9 +49,3 @@ function queryRange(db, collectionId, start, end, limit) {
5849
return query;
5950
}
6051
exports.queryRange = queryRange;
61-
function delay(ms) {
62-
return __awaiter(this, void 0, void 0, function* () {
63-
return new Promise((resolve) => setTimeout(resolve, ms));
64-
});
65-
}
66-
exports.delay = delay;

firestore-counter/functions/lib/controller.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ class ShardedCounterController {
191191
yield t.get(this.controllerDocRef);
192192
}
193193
catch (err) {
194-
console.log("Failed to read controler doc " + this.controllerDocRef.path);
194+
console.log("Failed to read controller doc " + this.controllerDocRef.path);
195195
throw Error("Failed to read controller doc.");
196196
}
197197
// Read all workers' metadata and construct sharding info based on collected stats.
@@ -201,7 +201,7 @@ class ShardedCounterController {
201201
}
202202
catch (err) {
203203
console.log("Failed to read worker docs.", err);
204-
throw Error("Failed to reqad worker docs.");
204+
throw Error("Failed to read worker docs.");
205205
}
206206
let shardingInfo = yield Promise.all(query.docs.map((worker) => __awaiter(this, void 0, void 0, function* () {
207207
const slice = worker.get("slice");

0 commit comments

Comments
 (0)