Skip to content

Commit 3f21437

Browse files
committed
feat: Implement matrix testing for Dataform versions and update CI configuration
1 parent 76e4281 commit 3f21437

File tree

10 files changed

+192
-20
lines changed

10 files changed

+192
-20
lines changed

.github/workflows/ci.yml

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@ on:
88
jobs:
99
lint-and-test:
1010
runs-on: ubuntu-latest
11+
strategy:
12+
matrix:
13+
dataform-version: ['2.4.2', '3.0.42']
1114

1215
steps:
1316
- name: Checkout code
@@ -20,13 +23,13 @@ jobs:
2023
cache: 'npm'
2124

2225
- name: Install dependencies
23-
run: npm ci && cd test-project && npm ci
26+
run: npm ci
2427

2528
- name: Run linter
2629
run: npm run lint
2730

28-
- name: Run tests
29-
run: npm test
31+
- name: Run tests (Dataform ${{ matrix.dataform-version }})
32+
run: npm test -- ${{ matrix.dataform-version }}
3033

3134

3235
dependabot:

README.md

Lines changed: 96 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,32 @@ This package is designed to optimize BigQuery resource usage by automatically as
1313
* **Automated re-assignement**: Once configured, reservations are applied automatically based on action categorization
1414
* **Flexible configuration**: Easy adjustment of reservation policies through configuration updates
1515

16+
## Compatibility
17+
18+
This package is tested and compatible with:
19+
20+
* **Dataform v2.4.2** (v2.x series)
21+
* **Dataform v3.0.42** (v3.x series)
22+
23+
### Testing Across Versions
24+
25+
To verify compatibility locally across all supported Dataform versions:
26+
27+
```bash
28+
npm test
29+
```
30+
31+
This command runs the matrix test suite which automatically:
32+
1. Iterates through all supported Dataform versions (v2 and v3).
33+
2. Manages configuration file conflicts (e.g., hiding `dataform.json` for v3).
34+
3. Executes unit tests and integration tests.
35+
36+
For faster iteration on the currently installed version in `test-project`, you can run:
37+
38+
```bash
39+
npm run test:single
40+
```
41+
1642
## Getting Started
1743

1844
### Initial Setup
@@ -29,28 +55,56 @@ Add the dependency to your `package.json`:
2955

3056
and click **Install Packages** in Dataform UI.
3157

32-
Then, import the package and create a setter function in your global scope under `/includes` directory:
58+
### Recommended: Automatic Application
59+
60+
The easiest way to integrate this package is to use automatic reservation application. Create a configuration file (e.g., `definitions/_reservations.js`) that will automatically apply reservations to all matching actions:
61+
62+
```javascript
63+
const { applyAutomaticReservations } = require("@masthead-data/dataform-package");
64+
65+
const RESERVATION_CONFIG = [
66+
{
67+
tag: 'production',
68+
reservation: 'projects/{project}/locations/{location}/reservations/{name}',
69+
actions: [
70+
'project.dataset.important_table',
71+
'project.dataset.critical_view'
72+
]
73+
},
74+
{
75+
tag: 'default',
76+
reservation: null,
77+
actions: []
78+
}
79+
];
80+
81+
applyAutomaticReservations(RESERVATION_CONFIG);
82+
```
83+
84+
**Note:** If you have many files in the project we recommend to start the filename with an underscore (e.g., `_reservations.js`) to ensure it runs first in the Dataform queue.
85+
86+
With automatic application, you don't need to add any reservation code to your individual action files — the package handles everything globally.
87+
88+
### Alternative: Manual Application
89+
90+
For more granular control, you can manually apply reservations per file. Create a setter function in your global scope under `/includes` directory:
3391

3492
```javascript
35-
const reservations = require("@masthead-data/dataform-package");
93+
const { createReservationSetter } = require("@masthead-data/dataform-package");
3694

3795
const RESERVATION_CONFIG = [
3896
...
3997
];
4098

41-
// Option 1: Manual Application (per file)
42-
const reservation_setter = reservations.createReservationSetter(RESERVATION_CONFIG);
99+
const reservation_setter = createReservationSetter(RESERVATION_CONFIG);
43100

44101
module.exports = {
45-
...
46102
reservation_setter
47103
}
48-
49-
// Option 2: Automatic Application (Global)
50-
// Place this in a new file, e.g., `definitions/reservations.js`
51-
reservations.applyAutomaticReservations(RESERVATION_CONFIG);
52104
```
53105

106+
Then use `${reservation_setter(ctx)}` in each action file where you want to apply reservations (see usage examples below).
107+
54108
### Configuration Structure
55109

56110
Configuration object defining reservation policies:
@@ -88,7 +142,9 @@ Configuration arguments:
88142
* `null`: Use a default reservation
89143
* **actions**: Array of Dataform action names that are assigned to the reservation
90144

91-
### Usage examples
145+
### Usage Examples (Manual Application)
146+
147+
**Note:** These examples are only needed if you're using the manual application approach. With automatic application via `applyAutomaticReservations()`, reservations are applied automatically and you don't need to add these calls to your action files.
92148

93149
#### `publish` actions
94150

@@ -154,7 +210,36 @@ WHEN NOT MATCHED THEN INSERT (id, value) VALUES (S.id, S.value);
154210
`);
155211
```
156212

157-
Example implementation can be found in [https://github.com/HTTPArchive/dataform](https://github.com/HTTPArchive/dataform).
213+
## API Reference
214+
215+
### `applyAutomaticReservations(config)`
216+
217+
**Primary Method** - Automatically applies reservation configurations to all actions in your Dataform project.
218+
219+
* **Parameters:**
220+
* `config` (Array): Array of reservation configuration objects
221+
* **Returns:** `void`
222+
* **Usage:** Call once in a definitions file (e.g., `definitions/_reservations.js`)
223+
* **Behavior:** Automatically intercepts all `publish()`, `operate()`, and `assert()` calls and applies appropriate reservations based on action names
224+
225+
### `createReservationSetter(config)`
226+
227+
**Secondary Method** - Creates a reservation setter function for manual application per action.
228+
229+
* **Parameters:**
230+
* `config` (Array): Array of reservation configuration objects
231+
* **Returns:** `Function` - A setter function that accepts a Dataform context and returns the appropriate `SET @@reservation` SQL statement
232+
* **Usage:** Create in an includes file, then call in individual action files using `${reservation_setter(ctx)}`
233+
* **Use Case:** When you need fine-grained control over which actions get reservations
234+
235+
### `getActionName(ctx)`
236+
237+
**Utility Method** - Extracts the action name from a Dataform context object.
238+
239+
* **Parameters:**
240+
* `ctx` (Object): Dataform context object
241+
* **Returns:** `string|null` - The action name in format `database.schema.name`, or `null` if not found
242+
* **Usage:** Advanced use cases where you need to programmatically determine action names
158243

159244
## Under the Hood
160245

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44
"description": "Masthead Data package for Dataform to optimize BigQuery resource usage",
55
"main": "index.js",
66
"scripts": {
7-
"test": "jest && cd test-project && npx @dataform/cli compile --json > compiled.json && node ../scripts/verify_compilation.js",
7+
"test": "./scripts/test-matrix.sh",
8+
"test:single": "jest && cd test-project && npx @dataform/cli compile --json > compiled.json && node ../scripts/verify_compilation.js",
89
"test:watch": "jest --watch",
910
"lint": "eslint -c .github/linters/eslint.config.mjs .",
1011
"lint:fix": "eslint -c .github/linters/eslint.config.mjs --fix .",

scripts/test-matrix.sh

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# Use provided arguments as versions, or default to the full matrix
5+
if [ $# -gt 0 ]; then
6+
VERSIONS=("$@")
7+
else
8+
VERSIONS=("2.4.2" "3.0.42")
9+
fi
10+
11+
# Cleanup function to restore configuration files
12+
cleanup() {
13+
if [ -f "test-project/dataform.json.bak" ]; then
14+
echo "Restoring dataform.json"
15+
mv "test-project/dataform.json.bak" "test-project/dataform.json"
16+
fi
17+
if [ -f "test-project/workflow_settings.yaml.bak" ]; then
18+
echo "Restoring workflow_settings.yaml"
19+
mv "test-project/workflow_settings.yaml.bak" "test-project/workflow_settings.yaml"
20+
fi
21+
}
22+
23+
# Ensure cleanup runs on exit (including failures)
24+
trap cleanup EXIT INT TERM
25+
26+
echo "Running matrix tests across Dataform versions..."
27+
28+
for VERSION in "${VERSIONS[@]}"; do
29+
echo ""
30+
echo "========================================="
31+
echo "Testing with Dataform v$VERSION"
32+
echo "========================================="
33+
34+
# Configuration management based on version
35+
if [[ $VERSION == 3* ]]; then
36+
if [ -f "test-project/dataform.json" ]; then
37+
echo "Hiding dataform.json for v3 compatibility"
38+
mv test-project/dataform.json test-project/dataform.json.bak
39+
fi
40+
elif [[ $VERSION == 2* ]]; then
41+
if [ -f "test-project/workflow_settings.yaml" ]; then
42+
echo "Hiding workflow_settings.yaml for v2 compatibility"
43+
mv test-project/workflow_settings.yaml test-project/workflow_settings.yaml.bak
44+
fi
45+
fi
46+
47+
# Install specific version
48+
echo "Installing Dataform @$VERSION..."
49+
cd test-project
50+
# Use --no-save to avoid cluttering package.json/package-lock.json during matrix tests
51+
npm install @dataform/cli@$VERSION @dataform/core@$VERSION --no-save
52+
cd ..
53+
54+
# Run tests using the single version command
55+
npm run test:single
56+
57+
# Restore files after the run so the next version has a clean state
58+
cleanup
59+
60+
echo "✓ Dataform v$VERSION tests passed"
61+
done
62+
63+
echo ""
64+
echo "========================================="
65+
echo "All matrix tests passed!"
66+
echo "========================================="
67+

scripts/verify_compilation.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,15 @@ function verify() {
1010
process.exit(1)
1111
}
1212

13-
const compiled = JSON.parse(fs.readFileSync(COMPILED_JSON_PATH, 'utf8'))
13+
let fileContent = fs.readFileSync(COMPILED_JSON_PATH, 'utf8')
14+
15+
// Dataform v2.x outputs a log line before the JSON, skip it
16+
const lines = fileContent.split('\n')
17+
if (lines[0].startsWith('{\"level\":')) {
18+
fileContent = lines.slice(1).join('\n')
19+
}
20+
21+
const compiled = JSON.parse(fileContent)
1422
let errors = []
1523

1624
const checkTable = (name, expectedPreOps) => {

test-project/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
node_modules/
22
compiled.json
3+
snowflake.log

test-project/dataform.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"warehouse": "bigquery",
3+
"defaultDatabase": "masthead-data",
4+
"defaultLocation": "US",
5+
"defaultSchema": "test",
6+
"assertionSchema": "dataform_assertions"
7+
}
Original file line numberDiff line numberDiff line change
@@ -1,2 +1 @@
1-
// Initialize automatic reservations
21
reservations.applyAutomaticReservations(reservations.RESERVATION_CONFIG)

test-project/package-lock.json

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

test/index.test.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const {
22
createReservationSetter,
3+
34
getActionName
45
} = require('../index')
56

0 commit comments

Comments
 (0)