Skip to content

Commit

Permalink
November 21, 2019 Release
Browse files Browse the repository at this point in the history
November 21, 2019 Release
  • Loading branch information
laurenzlong authored Nov 21, 2019
2 parents 750bb0f + c044297 commit ab7d229
Show file tree
Hide file tree
Showing 31 changed files with 137 additions and 71 deletions.
12 changes: 12 additions & 0 deletions .github/CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# All documentation changes, e.g. changelogs & readmes.
*.md @rachelsaunders

# Dependencies, package.json and mono-repo files
package.json @Salakar @Ehesp
*/package.json @Salakar @Ehesp
lerna.json @Salakar @Ehesp

# Tests
jest.config.js @Salakar @Ehesp
*/jest.config.js @Salakar @Ehesp
__tests__/* @Salakar @Ehesp
1 change: 1 addition & 0 deletions auth-mailchimp-sync/extension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ params:
value: asia-northeast1
default: us-central1
required: true
immutable: true

- param: MAILCHIMP_API_KEY
type: string
Expand Down
1 change: 1 addition & 0 deletions delete-user-data/extension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ params:
value: asia-northeast1
default: us-central1
required: true
immutable: true

- param: FIRESTORE_PATHS
type: string
Expand Down
23 changes: 12 additions & 11 deletions delete-user-data/functions/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,11 @@
* limitations under the License.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Expand All @@ -36,7 +37,7 @@ logs.init();
* Storage, and Firestore. It waits for all deletions to complete, and then
* returns a success message.
*/
exports.clearData = functions.auth.user().onDelete((user) => __awaiter(this, void 0, void 0, function* () {
exports.clearData = functions.auth.user().onDelete((user) => __awaiter(void 0, void 0, void 0, function* () {
logs.start();
const { firestorePaths, rtdbPaths, storagePaths } = config_1.default;
const { uid } = user;
Expand All @@ -62,10 +63,10 @@ exports.clearData = functions.auth.user().onDelete((user) => __awaiter(this, voi
yield Promise.all(promises);
logs.complete(uid);
}));
const clearDatabaseData = (databasePaths, uid) => __awaiter(this, void 0, void 0, function* () {
const clearDatabaseData = (databasePaths, uid) => __awaiter(void 0, void 0, void 0, function* () {
logs.rtdbDeleting();
const paths = extractUserPaths(databasePaths, uid);
const promises = paths.map((path) => __awaiter(this, void 0, void 0, function* () {
const promises = paths.map((path) => __awaiter(void 0, void 0, void 0, function* () {
try {
logs.rtdbPathDeleting(path);
yield admin
Expand All @@ -81,10 +82,10 @@ const clearDatabaseData = (databasePaths, uid) => __awaiter(this, void 0, void 0
yield Promise.all(promises);
logs.rtdbDeleted();
});
const clearStorageData = (storagePaths, uid) => __awaiter(this, void 0, void 0, function* () {
const clearStorageData = (storagePaths, uid) => __awaiter(void 0, void 0, void 0, function* () {
logs.storageDeleting();
const paths = extractUserPaths(storagePaths, uid);
const promises = paths.map((path) => __awaiter(this, void 0, void 0, function* () {
const promises = paths.map((path) => __awaiter(void 0, void 0, void 0, function* () {
const parts = path.split("/");
const bucketName = parts[0];
const bucket = bucketName === "{DEFAULT}"
Expand All @@ -109,20 +110,20 @@ const clearStorageData = (storagePaths, uid) => __awaiter(this, void 0, void 0,
yield Promise.all(promises);
logs.storageDeleted();
});
const clearFirestoreData = (firestorePaths, uid) => __awaiter(this, void 0, void 0, function* () {
const clearFirestoreData = (firestorePaths, uid) => __awaiter(void 0, void 0, void 0, function* () {
logs.firestoreDeleting();
const paths = extractUserPaths(firestorePaths, uid);
const promises = paths.map((path) => __awaiter(this, void 0, void 0, function* () {
const promises = paths.map((path) => __awaiter(void 0, void 0, void 0, function* () {
try {
const isRecursive = config_1.default.firestoreDeleteMode === 'recursive';
const isRecursive = config_1.default.firestoreDeleteMode === "recursive";
if (!isRecursive) {
const firestore = admin.firestore();
logs.firestorePathDeleting(path, false);
// Wrapping in transaction to allow for automatic retries (#48)
yield firestore.runTransaction((transaction => {
yield firestore.runTransaction((transaction) => {
transaction.delete(firestore.doc(path));
return Promise.resolve();
}));
});
logs.firestorePathDeleted(path, false);
}
else {
Expand Down
4 changes: 2 additions & 2 deletions delete-user-data/functions/lib/logs.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@ exports.firestoreNotConfigured = () => {
console.log("Cloud Firestore paths are not configured, skipping");
};
exports.firestorePathDeleted = (path, recursive) => {
console.log(`Deleted: '${path}' from Cloud Firestore ${recursive ? 'with recursive delete' : ''}`);
console.log(`Deleted: '${path}' from Cloud Firestore ${recursive ? "with recursive delete" : ""}`);
};
exports.firestorePathDeleting = (path, recursive) => {
console.log(`Deleting: '${path}' from Cloud Firestore ${recursive ? 'with recursive delete' : ''}`);
console.log(`Deleting: '${path}' from Cloud Firestore ${recursive ? "with recursive delete" : ""}`);
};
exports.firestorePathError = (path, err) => {
console.error(`Error when deleting: '${path}' from Cloud Firestore`, err);
Expand Down
2 changes: 1 addition & 1 deletion firestore-bigquery-export/PREINSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ Learn more about using this script to [backfill your existing collection](https:

This extension uses other Firebase or Google Cloud Platform services which may have associated charges:

+ BigQuery (this extension writes to BigQuery with [streaming inserts](https://cloud.google.com/bigquery/pricing#streaming_pricing))
+ Cloud Firestore
+ BigQuery
+ Cloud Functions

When you use Firebase Extensions, you're only charged for the underlying resources that you use. A paid-tier billing plan is only required if the extension uses a service that requires a paid-tier plan, for example calling to a Google Cloud Platform API or making outbound network requests to non-Google services. All Firebase services offer a free tier of usage. [Learn more about Firebase billing.](https://firebase.google.com/pricing)
2 changes: 1 addition & 1 deletion firestore-bigquery-export/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ Learn more about using this script to [backfill your existing collection](https:

This extension uses other Firebase or Google Cloud Platform services which may have associated charges:

+ BigQuery (this extension writes to BigQuery with [streaming inserts](https://cloud.google.com/bigquery/pricing#streaming_pricing))
+ Cloud Firestore
+ BigQuery
+ Cloud Functions

When you use Firebase Extensions, you're only charged for the underlying resources that you use. A paid-tier billing plan is only required if the extension uses a service that requires a paid-tier plan, for example calling to a Google Cloud Platform API or making outbound network requests to non-Google services. All Firebase services offer a free tier of usage. [Learn more about Firebase billing.](https://firebase.google.com/pricing)
Expand Down
1 change: 1 addition & 0 deletions firestore-bigquery-export/extension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ params:
value: asia-northeast1
default: us-central1
required: true
immutable: true

- param: COLLECTION_PATH
type: string
Expand Down
2 changes: 1 addition & 1 deletion firestore-bigquery-export/functions/lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,5 +20,5 @@ exports.default = {
datasetId: process.env.DATASET_ID,
tableId: process.env.TABLE_ID,
location: process.env.LOCATION,
initialized: false
initialized: false,
};
6 changes: 4 additions & 2 deletions firestore-bigquery-export/functions/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,15 @@ exports.fsexportbigquery = functions.handler.firestore.document.onWrite((change,
logs.start();
try {
const changeType = util_1.getChangeType(change);
yield eventTracker.record([{
yield eventTracker.record([
{
timestamp: context.timestamp,
operation: changeType,
documentName: context.resource.name,
eventId: context.eventId,
data: changeType == firestore_bigquery_change_tracker_1.ChangeType.DELETE ? undefined : change.after.data(),
}]);
},
]);
logs.complete();
}
catch (err) {
Expand Down
31 changes: 23 additions & 8 deletions firestore-bigquery-export/guides/IMPORT_EXISTING_DOCUMENTS.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,42 @@
The `fs-bq-import-collection` script is for use with the official Firebase Extension [**Export Collections to BiqQuery**](https://github.com/firebase/extensions/tree/master/firestore-bigquery-export).

### Overview

The import script (`fs-bq-import-collection`) can read all existing documents in a Cloud Firestore collection and insert them into the raw changelog table created by the Export Collections to BigQuery extension. The script adds a special changelog for each document with the operation of `IMPORT` and the timestamp of epoch. This ensures that any operation on an imported document supersedes the import record.
The import script (`fs-bq-import-collection`) can read all existing documents in a Cloud Firestore collection and insert them into the raw changelog table created by the Export Collections to BigQuery extension. The import script adds a special changelog for each document with the operation of `IMPORT` and the timestamp of epoch. This ensures that any operation on an imported document supersedes the import record.

You may pause and resume the script from the last batch at any point.
You may pause and resume the import script from the last batch at any point.

#### Important notes

- Run the script over the entire collection **_after_** installing the Export Collections to BigQuery extension; otherwise the writes to your database during the import might not be exported to the dataset.
- You must run the import script over the entire collection **_after_** installing the Export Collections to BigQuery extension; otherwise the writes to your database during the import might not be exported to the dataset.

- The import script can take up to _O(collection size)_ time to finish. If your collection is large, you might want to consider [loading data from a Cloud Firestore export into BigQuery](https://cloud.google.com/bigquery/docs/loading-data-cloud-firestore).
- You will see redundant rows in your raw changelog table:

- If document changes occur in the time between installing the extension and running this import script.
- You will see redundant rows in your raw changelog table if either of the following happen:

- If document changes occur in the time between installing the extension and running the import script.
- If you run the import script multiple times over the same collection.

### Install and run the script
### Run the script

This import script uses several values from your installation of the extension:
The import script uses several values from your installation of the extension:

- `${PROJECT_ID}`: the project ID for the Firebase project in which you installed the extension
- `${COLLECTION_PATH}`: the collection path that you specified during extension installation
- `${DATASET_ID}`: the ID that you specified for your dataset during extension installation

1. Run `npx @firebaseextensions/fs-bq-import-collection`.
Run the import script using [`npx` (the Node Package Runner)](https://www.npmjs.com/package/npx) via `npm` (the Node Package Manager).

1. Make sure that you've installed the required tools to run the import script:

- To access the `npm` command tools, you need to install [Node.js](https://www.nodejs.org/).
- If you use `npm` v5.1 or earlier, you need to explicitly install `npx`. Run `npm install --global npx`.

1. Run the import script via `npx` by running the following command:

```
npx @firebaseextensions/fs-bq-import-collection
```

1. When prompted, enter the Cloud Firestore collection path that you specified during extension installation, `${COLLECTION_PATH}`.

Expand Down
1 change: 1 addition & 0 deletions firestore-counter/extension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ params:
value: asia-northeast1
default: us-central1
required: true
immutable: true

- param: INTERNAL_STATE_PATH
label: Document path for internal state
Expand Down
1 change: 1 addition & 0 deletions firestore-send-email/extension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ params:
value: asia-northeast1
default: us-central1
required: true
immutable: true

- param: SMTP_CONNECTION_URI
type: string
Expand Down
16 changes: 8 additions & 8 deletions firestore-send-email/functions/lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ function validateFieldArray(field, array) {
function processCreate(snap) {
return __awaiter(this, void 0, void 0, function* () {
// Wrapping in transaction to allow for automatic retries (#48)
return admin.firestore().runTransaction((transaction => {
return admin.firestore().runTransaction((transaction) => {
transaction.update(snap.ref, {
delivery: {
startTime: admin.firestore.FieldValue.serverTimestamp(),
Expand All @@ -69,7 +69,7 @@ function processCreate(snap) {
},
});
return Promise.resolve();
}));
});
});
}
function preparePayload(payload) {
Expand Down Expand Up @@ -216,10 +216,10 @@ function deliver(payload, ref) {
logs.deliveryError(ref, e);
}
// Wrapping in transaction to allow for automatic retries (#48)
return admin.firestore().runTransaction((transaction => {
return admin.firestore().runTransaction((transaction) => {
transaction.update(ref, update);
return Promise.resolve();
}));
});
});
}
function processWrite(change) {
Expand All @@ -242,25 +242,25 @@ function processWrite(change) {
case "PROCESSING":
if (payload.delivery.leaseExpireTime.toMillis() < Date.now()) {
// Wrapping in transaction to allow for automatic retries (#48)
return admin.firestore().runTransaction((transaction => {
return admin.firestore().runTransaction((transaction) => {
transaction.update(change.after.ref, {
"delivery.state": "ERROR",
error: "Message processing lease expired.",
});
return Promise.resolve();
}));
});
}
return null;
case "PENDING":
case "RETRY":
// Wrapping in transaction to allow for automatic retries (#48)
yield admin.firestore().runTransaction((transaction => {
yield admin.firestore().runTransaction((transaction) => {
transaction.update(change.after.ref, {
"delivery.state": "PROCESSING",
"delivery.leaseExpireTime": admin.firestore.Timestamp.fromMillis(Date.now() + 60000),
});
return Promise.resolve();
}));
});
return deliver(payload, change.after.ref);
}
});
Expand Down
2 changes: 1 addition & 1 deletion firestore-send-email/functions/lib/logs.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,6 @@ function missingDeliveryField(ref) {
}
exports.missingDeliveryField = missingDeliveryField;
function missingUids(uids) {
console.log(`The following uids were provided, however a document does not exist or has no 'email' field: ${uids.join(',')}`);
console.log(`The following uids were provided, however a document does not exist or has no 'email' field: ${uids.join(",")}`);
}
exports.missingUids = missingUids;
2 changes: 1 addition & 1 deletion firestore-send-email/functions/lib/templates.js
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class Templates {
console.log(`loaded template '${doc.id}'`);
});
this.ready = true;
this.waits.forEach(wait => wait());
this.waits.forEach((wait) => wait());
}
render(name, data) {
return __awaiter(this, void 0, void 0, function* () {
Expand Down
2 changes: 2 additions & 0 deletions firestore-shorten-urls-bitly/extension.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ params:
value: asia-northeast1
default: us-central1
required: true
immutable: true

- param: BITLY_ACCESS_TOKEN
label: Bitly access token
description: >
Expand Down
13 changes: 3 additions & 10 deletions firestore-shorten-urls-bitly/functions/lib/abstract-shortener.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,10 @@
* limitations under the License.
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Expand Down Expand Up @@ -70,7 +69,6 @@ class FirestoreUrlShortener {
extractUrl(snapshot) {
return snapshot.get(this.urlFieldName);
}
;
getChangeType(change) {
if (!change.after.exists) {
return ChangeType.DELETE;
Expand All @@ -80,7 +78,6 @@ class FirestoreUrlShortener {
}
return ChangeType.UPDATE;
}
;
handleCreateDocument(snapshot) {
return __awaiter(this, void 0, void 0, function* () {
const url = this.extractUrl(snapshot);
Expand All @@ -93,11 +90,9 @@ class FirestoreUrlShortener {
}
});
}
;
handleDeleteDocument() {
this.logs.documentDeleted();
}
;
handleUpdateDocument(before, after) {
return __awaiter(this, void 0, void 0, function* () {
const urlAfter = this.extractUrl(after);
Expand All @@ -118,18 +113,16 @@ class FirestoreUrlShortener {
}
});
}
;
updateShortUrl(snapshot, url) {
return __awaiter(this, void 0, void 0, function* () {
this.logs.updateDocument(snapshot.ref.path);
// Wrapping in transaction to allow for automatic retries (#48)
yield admin.firestore().runTransaction((transaction => {
yield admin.firestore().runTransaction((transaction) => {
transaction.update(snapshot.ref, this.shortUrlFieldName, url);
return Promise.resolve();
}));
});
this.logs.updateDocumentComplete(snapshot.ref.path);
});
}
;
}
exports.FirestoreUrlShortener = FirestoreUrlShortener;
Loading

0 comments on commit ab7d229

Please sign in to comment.