Skip to content

Commit 01d9102

Browse files
authored
Merge pull request #1868 from firebase/next
2 parents 0d56fe4 + 7da6e53 commit 01d9102

File tree

22 files changed

+545
-13305
lines changed

22 files changed

+545
-13305
lines changed
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
LOCATION=europe-west2
22
IMG_BUCKET=${STORAGE_BUCKET}
3-
IMG_SIZES=200x200
3+
IMG_SIZES=300x300
44
DELETE_ORIGINAL_FILE=true
55
MAKE_PUBLIC=true
66
RESIZED_IMAGES_PATH=thumbnails
77
FAILED_IMAGES_PATH=failed
88
IMAGE_TYPE=webp
99
IS_ANIMATED=true
10-
FUNCTION_MEMORY=1024
10+
DO_BACKFILL=false
11+
SHARP_OPTIONS='{"fit":"cover", "position": "top", "animated": false}'

firestore-bigquery-export/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## Version 0.1.42
2+
3+
fix - correctly extract timestamps from firestore fields to partition columns
4+
15
## Version 0.1.41
26

37
fix - rollback backfill feature

firestore-bigquery-export/README.md

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -146,14 +146,6 @@ To install an extension, your project must be on the [Blaze (pay as you go) plan
146146

147147
* Use new query syntax for snapshots: If enabled, snapshots will be generated with the new query syntax, which should be more performant, and avoid potential resource limitations.
148148

149-
* Import existing Firestore documents into BigQuery?: Do you want to import existing documents from your Firestore collection into BigQuery? These documents will have each have a special changelog with the operation of `IMPORT` and the timestamp of epoch. This ensures that any operation on an imported document supersedes the import record.
150-
151-
* Existing documents collection: What is the path of the the Cloud Firestore Collection you would like to import from? (This may, or may not, be the same Collection for which you plan to mirror changes.) If you want to use a collectionGroup query, provide the collection name value here, and set 'Use Collection Group query' to true.
152-
153-
* Use Collection Group query: Do you want to use a [collection group](https://firebase.google.com/docs/firestore/query-data/queries#collection-group-query) query for importing existing documents? Warning: A collectionGroup query will target every collection in your Firestore project that matches the 'Existing documents collection'. For example, if you have 10,000 documents with a sub-collection named: landmarks, this will query every document in 10,000 landmarks collections.
154-
155-
* Docs per backfill: When importing existing documents, how many should be imported at once? The default value of 200 should be ok for most users. If you are using a transform function or have very large documents, you may need to set this to a lower number. If the lifecycle event function times out, lower this value.
156-
157149
* Cloud KMS key name: Instead of Google managing the key encryption keys that protect your data, you control and manage key encryption keys in Cloud KMS. If this parameter is set, the extension will specify the KMS key name when creating the BQ table. See the PREINSTALL.md for more details.
158150

159151

firestore-bigquery-export/extension.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
# limitations under the License.
1414

1515
name: firestore-bigquery-export
16-
version: 0.1.41
16+
version: 0.1.42
1717
specVersion: v1beta
1818

1919
displayName: Stream Firestore to BigQuery

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

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

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

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,14 @@
55
"url": "github.com/firebase/extensions.git",
66
"directory": "firestore-bigquery-export/firestore-bigquery-change-tracker"
77
},
8-
"version": "1.1.29",
8+
"version": "1.1.30",
99
"description": "Core change-tracker library for Cloud Firestore Collection BigQuery Exports",
1010
"main": "./lib/index.js",
1111
"scripts": {
1212
"build": "npm run clean && npm run compile",
1313
"clean": "rimraf lib",
1414
"compile": "tsc",
15-
"test:local": "firebase ext:dev:emulators:exec ./node_modules/.bin/jest --test-params=./src/__tests__/emulator-params.env --project=extensions-testing --config=./src/__tests__/firebase.json",
15+
"test:local": "jest",
1616
"prepare": "npm run build",
1717
"generate-stresstest-table": "bq query --project_id=extensions-testing --use_legacy_sql=false < ./src/__tests__/fixtures/sql/generateSnapshotStresstestTable.sql"
1818
},
@@ -35,16 +35,16 @@
3535
"traverse": "^0.6.6"
3636
},
3737
"devDependencies": {
38+
"@types/chai": "^4.1.6",
39+
"@types/jest": "^24.0.18",
3840
"@types/node": "14.18.34",
3941
"@types/traverse": "^0.6.32",
40-
"typescript": "^4.9.4",
41-
"rimraf": "^2.6.3",
42-
"nyc": "^14.0.0",
43-
"jest": "^24.9.0",
4442
"chai": "^4.2.0",
45-
"ts-node": "^7.0.1",
43+
"jest": "^24.9.0",
44+
"nyc": "^14.0.0",
45+
"rimraf": "^2.6.3",
4646
"ts-jest": "^24.1.0",
47-
"@types/jest": "^24.0.18",
48-
"@types/chai": "^4.1.6"
47+
"ts-node": "^7.0.1",
48+
"typescript": "^4.9.4"
4949
}
5050
}

firestore-bigquery-export/firestore-bigquery-change-tracker/src/__tests__/bigquery/alternativeProject.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ describe("Using an alternative bigquery project", () => {
2828
bqProjectId = "messaging-test-4395c";
2929
});
3030

31-
test("successfully uses alternative project name when provided", async () => {
31+
xtest("successfully uses alternative project name when provided", async () => {
3232
const event: FirestoreDocumentChangeEvent = changeTrackerEvent({});
3333

3434
await changeTracker({

firestore-bigquery-export/firestore-bigquery-change-tracker/src/__tests__/bigquery/partitioning.test.ts

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ChangeType, FirestoreDocumentChangeEvent } from "../..";
66
import { FirestoreBigQueryEventHistoryTrackerConfig } from "../../bigquery";
77
import { Partitioning } from "../../bigquery/partitioning";
88
import { deleteTable } from "../fixtures/clearTables";
9+
import { changeTracker } from "../fixtures/changeTracker";
910

1011
let bq: BigQuery;
1112
let dataset: Dataset;
@@ -197,6 +198,110 @@ describe("processing partitions on a new table", () => {
197198
expect(value.end_date).toBeDefined();
198199
});
199200

201+
test("returns a value when timePartitioningField and timePartitioningFirestoreField string value has been defined, with a timestamp-like value", async () => {
202+
const config: FirestoreBigQueryEventHistoryTrackerConfig = {
203+
datasetId: "",
204+
tableId: "",
205+
datasetLocation: "",
206+
timePartitioning: "",
207+
timePartitioningField: "end_date",
208+
timePartitioningFieldType: "DATETIME",
209+
timePartitioningFirestoreField: "end_date",
210+
transformFunction: "",
211+
clustering: [],
212+
bqProjectId: null,
213+
};
214+
215+
// a Timestamp-Like object (we lose the instance after serialization)
216+
const end_date = {
217+
_seconds: 1614153600,
218+
_nanoseconds: 0,
219+
};
220+
221+
const event: FirestoreDocumentChangeEvent = {
222+
timestamp: "",
223+
operation: ChangeType.CREATE,
224+
documentName: "",
225+
eventId: "",
226+
documentId: "",
227+
data: { end_date },
228+
};
229+
230+
const partitioning = new Partitioning(config, table);
231+
const value = partitioning.getPartitionValue(event);
232+
233+
expect(value.end_date).toBeDefined();
234+
});
235+
236+
test("returns an empty object when _seconds or _nanoseconds is not a number", async () => {
237+
const config: FirestoreBigQueryEventHistoryTrackerConfig = {
238+
datasetId: "",
239+
tableId: "",
240+
datasetLocation: "",
241+
timePartitioning: "",
242+
timePartitioningField: "end_date",
243+
timePartitioningFieldType: "DATETIME",
244+
timePartitioningFirestoreField: "end_date",
245+
transformFunction: "",
246+
clustering: [],
247+
bqProjectId: null,
248+
};
249+
250+
// a Timestamp-Like object (we lose the instance after serialization)
251+
const end_date = {
252+
_seconds: "not a number",
253+
_nanoseconds: 0,
254+
};
255+
256+
const event: FirestoreDocumentChangeEvent = {
257+
timestamp: "",
258+
operation: ChangeType.CREATE,
259+
documentName: "",
260+
eventId: "",
261+
documentId: "",
262+
data: { end_date },
263+
};
264+
265+
const partitioning = new Partitioning(config, table);
266+
const value = partitioning.getPartitionValue(event);
267+
268+
expect(value).toEqual({});
269+
});
270+
271+
test("returns a value when timePartitioningField and timePartitioningFirestoreField string value has been defined, and is timestamp-like", async () => {
272+
const config: FirestoreBigQueryEventHistoryTrackerConfig = {
273+
datasetId: "",
274+
tableId: "",
275+
datasetLocation: "",
276+
timePartitioning: "",
277+
timePartitioningField: "end_date",
278+
timePartitioningFieldType: "DATETIME",
279+
timePartitioningFirestoreField: "end_date",
280+
transformFunction: "",
281+
clustering: [],
282+
bqProjectId: null,
283+
};
284+
285+
// a Timestamp-Like object (we lose the instance after serialization)
286+
const end_date = JSON.parse(
287+
JSON.stringify(admin.firestore.Timestamp.now())
288+
);
289+
290+
const event: FirestoreDocumentChangeEvent = {
291+
timestamp: "",
292+
operation: ChangeType.CREATE,
293+
documentName: "",
294+
eventId: "",
295+
documentId: "",
296+
data: { end_date },
297+
};
298+
299+
const partitioning = new Partitioning(config, table);
300+
const value = partitioning.getPartitionValue(event);
301+
302+
expect(value.end_date).toBeDefined();
303+
});
304+
200305
test("returns an empty object if timePartitioningFirestoreField has not been provided", async () => {
201306
const config: FirestoreBigQueryEventHistoryTrackerConfig = {
202307
datasetId: "",

firestore-bigquery-export/firestore-bigquery-change-tracker/src/__tests__/bigquery/stresstest.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ let dataset: Dataset;
1818
let table: Table;
1919
let view: Table;
2020
const count = 100;
21-
describe("Stress testing", () => {
21+
describe.skip("Stress testing", () => {
2222
beforeEach(() => {
2323
randomID = (Math.random() + 1).toString(36).substring(7);
2424
datasetId = `dataset_${randomID}`;

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

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,8 @@ export class Partitioning {
5353

5454
private isValidPartitionTypeDate(value) {
5555
/* Check if valid timestamp value from sdk */
56-
if (value instanceof firebase.firestore.Timestamp) return true;
56+
// if (value instanceof firebase.firestore.Timestamp) return true;
57+
if (isTimestampLike(value)) return true;
5758

5859
/* Check if valid date/timstemap, expedted result from production */
5960
if (value && value.toDate && value.toDate()) return true;
@@ -189,7 +190,7 @@ export class Partitioning {
189190
Extracts a valid Partition field from the Document Change Event.
190191
Matches result based on a pre-defined Firestore field matching the event data object.
191192
Return an empty object if no field name or value provided.
192-
Returns empty object if not a string or timestamp
193+
Returns empty object if not a string or timestamp (or result of serializing a timestamp)
193194
Logs warning if not a valid datatype
194195
Delete changes events have no data, return early as cannot partition on empty data.
195196
**/
@@ -210,6 +211,15 @@ export class Partitioning {
210211

211212
if (this.isValidPartitionTypeDate(fieldValue)) {
212213
/* Return converted console value */
214+
if (isTimestampLike(fieldValue)) {
215+
const convertedTimestampFieldValue = convertToTimestamp(fieldValue);
216+
return {
217+
[fieldName]: this.convertDateValue(
218+
convertedTimestampFieldValue.toDate()
219+
),
220+
};
221+
}
222+
213223
if (fieldValue.toDate) {
214224
return { [fieldName]: this.convertDateValue(fieldValue.toDate()) };
215225
}
@@ -309,3 +319,27 @@ export class Partitioning {
309319
}
310320
}
311321
}
322+
323+
type TimestampLike = {
324+
_seconds: number;
325+
_nanoseconds: number;
326+
};
327+
328+
const isTimestampLike = (value: any): value is TimestampLike => {
329+
if (value instanceof firebase.firestore.Timestamp) return true;
330+
return (
331+
typeof value === "object" &&
332+
value !== null &&
333+
"_seconds" in value &&
334+
typeof value["_seconds"] === "number" &&
335+
"_nanoseconds" in value &&
336+
typeof value["_nanoseconds"] === "number"
337+
);
338+
};
339+
340+
const convertToTimestamp = (
341+
value: TimestampLike
342+
): firebase.firestore.Timestamp => {
343+
if (value instanceof firebase.firestore.Timestamp) return value;
344+
return new firebase.firestore.Timestamp(value._seconds, value._nanoseconds);
345+
};

0 commit comments

Comments
 (0)