datajunction 0.0.1-a30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/.babelrc ADDED
@@ -0,0 +1,8 @@
1
+ {
2
+ "presets": [
3
+ "@babel/preset-env"
4
+ ],
5
+ "test": [
6
+ "jest"
7
+ ]
8
+ }
package/.eslintignore ADDED
@@ -0,0 +1 @@
1
+ ./dist/*
package/.eslintrc.js ADDED
@@ -0,0 +1,13 @@
1
+ module.exports = {
2
+ env: {
3
+ browser: true,
4
+ es2021: true,
5
+ },
6
+ extends: 'standard',
7
+ overrides: [],
8
+ parserOptions: {
9
+ ecmaVersion: 'latest',
10
+ sourceType: 'module',
11
+ },
12
+ rules: {},
13
+ }
@@ -0,0 +1 @@
1
+ ./dist
package/.prettierrc ADDED
@@ -0,0 +1,6 @@
1
+ {
2
+ "trailingComma": "es5",
3
+ "tabWidth": 4,
4
+ "semi": false,
5
+ "singleQuote": true
6
+ }
package/Makefile ADDED
@@ -0,0 +1,3 @@
1
+ dev-release:
2
+ yarn version --prerelease --preid dev --no-git-tag-version
3
+ npm publish
@@ -0,0 +1 @@
1
+ module.exports = {presets: ['@babel/preset-env']}
package/index.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require('./dist')
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "datajunction",
3
+ "version": "0.0.1a30",
4
+ "description": "A Javascript client for interacting with a DataJunction server",
5
+ "module": "src/index.js",
6
+ "scripts": {
7
+ "test": "jest",
8
+ "test-watch": "jest --watch",
9
+ "build": "rm -rf dist/* && webpack && babel src -d dist",
10
+ "lint": "prettier \"src/**/*.{js,jsx}\"",
11
+ "format": "prettier --write \"src/**/*.{js,jsx}\""
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/DataJunction/dj.git"
16
+ },
17
+ "keywords": [
18
+ "datajunction",
19
+ "metrics",
20
+ "metrics-platform",
21
+ "semantic-layer"
22
+ ],
23
+ "author": "DataJunction Authors",
24
+ "license": "MIT",
25
+ "bugs": {
26
+ "url": "https://github.com/DataJunction/dj/issues"
27
+ },
28
+ "homepage": "https://github.com/DataJunction/dj#readme",
29
+ "devDependencies": {
30
+ "@babel/cli": "^7.0.0",
31
+ "@babel/core": "^7.0.0",
32
+ "@babel/plugin-proposal-object-rest-spread": "^7.0.0",
33
+ "@babel/preset-env": "^7.0.0",
34
+ "babel-core": "^7.0.0-bridge.0",
35
+ "babel-jest": "^23.4.2",
36
+ "eslint": "^8.39.0",
37
+ "eslint-config-standard": "^17.0.0",
38
+ "eslint-plugin-import": "^2.27.5",
39
+ "eslint-plugin-n": "^15.7.0",
40
+ "eslint-plugin-promise": "^6.1.1",
41
+ "jest": "^29.5.0",
42
+ "prettier": "^2.8.8",
43
+ "webpack": "^5.81.0",
44
+ "webpack-cli": "^5.0.2"
45
+ },
46
+ "dependencies": {
47
+ "@babel/core": "^7.22.5",
48
+ "docker-names": "^1.2.1"
49
+ }
50
+ }
@@ -0,0 +1,83 @@
1
+ export default class HttpClient {
2
+ constructor(options = {}) {
3
+ this._baseURL = options.baseURL || ''
4
+ this._headers = options.headers || {}
5
+ }
6
+
7
+ async _fetchJSON(endpoint, options = {}) {
8
+ const res = await fetch(this._baseURL + endpoint, {
9
+ ...options,
10
+ headers: this._headers,
11
+ })
12
+
13
+ if (!res.ok)
14
+ throw new Error(
15
+ res.statusText + ` (${endpoint}, ${JSON.stringify(options)})`
16
+ )
17
+
18
+ if (options.parseResponse !== false && res.status !== 204) {
19
+ return res.json()
20
+ }
21
+
22
+ return undefined
23
+ }
24
+
25
+ setHeader(key, value) {
26
+ this._headers[key] = value
27
+ return this
28
+ }
29
+
30
+ getHeader(key) {
31
+ return this._headers[key]
32
+ }
33
+
34
+ setBasicAuth(username, password) {
35
+ this._headers.Authorization = `Basic ${btoa(`${username}:${password}`)}`
36
+ return this
37
+ }
38
+
39
+ setBearerAuth(token) {
40
+ this._headers.Authorization = `Bearer ${token}`
41
+ return this
42
+ }
43
+
44
+ async get(endpoint, options = {}) {
45
+ return this._fetchJSON(endpoint, {
46
+ ...options,
47
+ method: 'GET',
48
+ })
49
+ }
50
+
51
+ async post(endpoint, body, options = {}) {
52
+ return this._fetchJSON(endpoint, {
53
+ ...options,
54
+ body: body ? JSON.stringify(body) : undefined,
55
+ method: 'POST',
56
+ })
57
+ }
58
+
59
+ async put(endpoint, body, options = {}) {
60
+ return this._fetchJSON(endpoint, {
61
+ ...options,
62
+ body: body ? JSON.stringify(body) : undefined,
63
+ method: 'PUT',
64
+ })
65
+ }
66
+
67
+ async patch(endpoint, operations, options = {}) {
68
+ return this._fetchJSON(endpoint, {
69
+ parseResponse: false,
70
+ ...options,
71
+ body: JSON.stringify(operations),
72
+ method: 'PATCH',
73
+ })
74
+ }
75
+
76
+ async delete(endpoint, options = {}) {
77
+ return this._fetchJSON(endpoint, {
78
+ parseResponse: false,
79
+ ...options,
80
+ method: 'DELETE',
81
+ })
82
+ }
83
+ }
package/src/index.js ADDED
@@ -0,0 +1,298 @@
1
+ import HttpClient from './httpclient.js'
2
+
3
+ export class DJClient extends HttpClient {
4
+ constructor(
5
+ baseURL,
6
+ namespace,
7
+ engineName = null,
8
+ engineVersion = null,
9
+ httpAgent = null
10
+ ) {
11
+ super(
12
+ {
13
+ baseURL,
14
+ },
15
+ httpAgent
16
+ )
17
+ this.namespace = namespace
18
+ this.engineName = engineName
19
+ this.engineVersion = engineVersion
20
+ }
21
+
22
+ get healthcheck() {
23
+ return {
24
+ get: () => this.get('/health/'),
25
+ }
26
+ }
27
+
28
+ get catalogs() {
29
+ return {
30
+ list: () => this.get('/catalogs/'),
31
+ get: (catalog) => this.get(`/catalogs/${catalog}/`),
32
+ create: (catalog) =>
33
+ this.setHeader('Content-Type', 'application/json').post(
34
+ `/catalogs/`,
35
+ catalog
36
+ ),
37
+ addEngine: (catalog, engineName, engineVersion) =>
38
+ this.setHeader('Content-Type', 'application/json').post(
39
+ `/catalogs/${catalog}/engines/`,
40
+ [
41
+ {
42
+ name: engineName,
43
+ version: engineVersion,
44
+ },
45
+ ]
46
+ ),
47
+ }
48
+ }
49
+
50
+ get engines() {
51
+ return {
52
+ list: () => this.get('/engines/'),
53
+ get: (engineName, engineVersion) =>
54
+ this.get(`/engines/${engineName}/${engineVersion}/`),
55
+ create: (engine) => this.post('/engines/', engine),
56
+ }
57
+ }
58
+
59
+ get addEngineToCatalog() {
60
+ return {
61
+ set: (catalogName, engine) =>
62
+ this.setHeader('Content-Type', 'application/json').post(
63
+ `/catalogs/${catalogName}/engines/`,
64
+ [engine]
65
+ ),
66
+ }
67
+ }
68
+
69
+ get namespaces() {
70
+ return {
71
+ list: () => this.get('/namespaces/'),
72
+ nodes: (namespace) => this.get(`/namespaces/${namespace}/`),
73
+ create: (namespace) =>
74
+ this.setHeader('Content-Type', 'application/json').post(
75
+ `/namespaces/${namespace}/`
76
+ ),
77
+ }
78
+ }
79
+
80
+ get commonDimensions() {
81
+ return {
82
+ list: (metrics) => {
83
+ const metricsQuery =
84
+ '?' + metrics.map((m) => `metric=${m}`).join('&')
85
+ return this.get('/metrics/common/dimensions/' + metricsQuery)
86
+ },
87
+ }
88
+ }
89
+
90
+ get nodes() {
91
+ return {
92
+ get: (nodeName) => this.get(`/nodes/${nodeName}/`),
93
+ validate: (nodeDetails) =>
94
+ this.setHeader('Content-Type', 'application/json').post(
95
+ '/nodes/validate/',
96
+ nodeDetails
97
+ ),
98
+ update: (nodeName, nodeDetails) =>
99
+ this.setHeader('Content-Type', 'application/json').patch(
100
+ `/nodes/${nodeName}/`,
101
+ nodeDetails
102
+ ),
103
+ revisions: (nodeName) => this.get(`/nodes/${nodeName}/revisions/`),
104
+ downstream: (nodeName) =>
105
+ this.get(`/nodes/${nodeName}/downstream/`),
106
+ upstream: (nodeName) => this.get(`/nodes/${nodeName}/upstream/`),
107
+ publish: (nodeName) => this.patch(`/nodes/${nodeName}/`, {'mode': 'published'})
108
+ }
109
+ }
110
+
111
+ get sources() {
112
+ return {
113
+ create: (sourceDetails) =>
114
+ this.setHeader('Content-Type', 'application/json').post(
115
+ '/nodes/source/',
116
+ sourceDetails
117
+ ),
118
+ list: () => this.get(`/namespaces/${this.namespace}/?type_=source`),
119
+ }
120
+ }
121
+
122
+ get transforms() {
123
+ return {
124
+ create: (transformDetails) =>
125
+ this.setHeader('Content-Type', 'application/json').post(
126
+ '/nodes/transform/',
127
+ transformDetails
128
+ ),
129
+ list: () =>
130
+ this.get(`/namespaces/${this.namespace}/?type_=transform`),
131
+ }
132
+ }
133
+
134
+ get dimensions() {
135
+ return {
136
+ create: (dimensionDetails) =>
137
+ this.setHeader('Content-Type', 'application/json').post(
138
+ '/nodes/dimension/',
139
+ dimensionDetails
140
+ ),
141
+ list: () =>
142
+ this.get(`/namespaces/${this.namespace}/?type_=dimension`),
143
+ link: (nodeName, nodeColumn, dimension, dimensionColumn) =>
144
+ this.post(
145
+ `/nodes/${nodeName}/columns/${nodeColumn}/?dimension=${dimension}&dimension_column=${dimensionColumn}`
146
+ ),
147
+ }
148
+ }
149
+
150
+ get metrics() {
151
+ return {
152
+ get: (metricName) => this.get(`/metrics/${metricName}/`),
153
+ create: (metricDetails) =>
154
+ this.setHeader('Content-Type', 'application/json').post(
155
+ '/nodes/metric/',
156
+ metricDetails
157
+ ),
158
+ list: () => this.get(`/namespaces/${this.namespace}/?type_=metric`),
159
+ all: () => this.get(`/metrics/`),
160
+ }
161
+ }
162
+
163
+ get cubes() {
164
+ return {
165
+ get: (cubeName) => this.get(`/cubes/${cubeName}/`),
166
+ create: (cubeDetails) =>
167
+ this.setHeader('Content-Type', 'application/json').post(
168
+ '/nodes/cube/',
169
+ cubeDetails
170
+ ),
171
+ }
172
+ }
173
+
174
+ get tags() {
175
+ return {
176
+ list: () => this.get('/tags/'),
177
+ get: (tagName) => this.get(`/tags/${tagName}/`),
178
+ create: (tagData) =>
179
+ this.setHeader('Content-Type', 'application/json').post(
180
+ '/tags/',
181
+ tagData
182
+ ),
183
+ update: (tagName, tagData) =>
184
+ this.setHeader('Content-Type', 'application/json').patch(
185
+ `/tags/${tagName}/`,
186
+ tagData
187
+ ),
188
+ set: (nodeName, tagName) =>
189
+ this.post(`/nodes/${nodeName}/tag/?tag_name=${tagName}`),
190
+ listNodes: (tagName) => this.get(`/tags/${tagName}/nodes/`),
191
+ }
192
+ }
193
+
194
+ get attributes() {
195
+ return {
196
+ list: () => this.get('/attributes/'),
197
+ create: (attributeData) =>
198
+ this.setHeader('Content-Type', 'application/json').post(
199
+ '/attributes/',
200
+ attributeData
201
+ ),
202
+ }
203
+ }
204
+
205
+ get materializationConfigs() {
206
+ return {
207
+ update: (nodeName, materializationDetails) =>
208
+ this.setHeader('Content-Type', 'application/json').post(
209
+ `/nodes/${nodeName}/materialization/`,
210
+ materializationDetails
211
+ ),
212
+ }
213
+ }
214
+
215
+ get columnAttributes() {
216
+ return {
217
+ set: (nodeName, columnAttribute) =>
218
+ this.setHeader('Content-Type', 'application/json').post(
219
+ `/nodes/${nodeName}/attributes/`,
220
+ [columnAttribute]
221
+ ),
222
+ }
223
+ }
224
+
225
+ get availabilityState() {
226
+ return {
227
+ set: (nodeName, availabilityState) =>
228
+ this.setHeader('Content-Type', 'application/json').post(
229
+ `/data/${nodeName}/availability/`,
230
+ availabilityState
231
+ ),
232
+ }
233
+ }
234
+
235
+ get sql() {
236
+ return {
237
+ get: (
238
+ metrics,
239
+ dimensions,
240
+ filters,
241
+ engineName = null,
242
+ engineVersion = null
243
+ ) => {
244
+ const metricsQuery =
245
+ '?' + metrics.map((m) => `metrics=${m}`).join('&')
246
+ const dimensionsQuery = dimensions
247
+ .map((d) => `dimensions=${d}`)
248
+ .join('&')
249
+ const filtersQuery = filters
250
+ .map((f) => `filters=${f}`)
251
+ .join('&')
252
+ const engineNameP = engineName ? `&engine=${engineName}` : ''
253
+ const engineVersionP = engineVersion
254
+ ? `&engine_version=${engineVersion}`
255
+ : ''
256
+ return this.get(
257
+ `/sql/${metricsQuery}&${dimensionsQuery}${filtersQuery}${engineNameP}${engineVersionP}`
258
+ )
259
+ },
260
+ }
261
+ }
262
+
263
+ get data() {
264
+ return {
265
+ get: (
266
+ metrics,
267
+ dimensions,
268
+ filters,
269
+ async_ = false,
270
+ engineName = null,
271
+ engineVersion = null
272
+ ) => {
273
+ const metricsQuery =
274
+ '?' + metrics.map((m) => `metrics=${m}`).join('&')
275
+ const dimensionsQuery = dimensions
276
+ .map((d) => `dimensions=${d}`)
277
+ .join('&')
278
+ const filtersQuery = filters
279
+ .map((f) => `filters=${f}`)
280
+ .join('&')
281
+ const asyncP = async_ ? `&async_=${async_}` : ''
282
+ const engineNameP = engineName ? `&engine=${engineName}` : ''
283
+ const engineVersionP = engineVersion
284
+ ? `&engine_version=${engineVersion}`
285
+ : ''
286
+ const data = this.get(
287
+ `/data/${metricsQuery}&${dimensionsQuery}${filtersQuery}${asyncP}${engineNameP}${engineVersionP}`
288
+ ).then((data) => {
289
+ return {
290
+ columns: data.results[0].columns,
291
+ data: data.results[0].rows,
292
+ }
293
+ })
294
+ return data
295
+ },
296
+ }
297
+ }
298
+ }
@@ -0,0 +1,592 @@
1
+ const { DJClient } = require('./index')
2
+ var dockerNames = require('docker-names')
3
+
4
+ test('should return something', async () => {
5
+ const randomName = dockerNames.getRandomName(true)
6
+ const namespace = `integration.javascript.${randomName}`
7
+ const dj = new DJClient('http://localhost:8000')
8
+
9
+ // Create a namespace
10
+ const namespace_created = await dj.namespaces.create(namespace)
11
+ expect(namespace_created).toEqual({
12
+ message: `Node namespace \`${namespace}\` has been successfully created`,
13
+ })
14
+
15
+ // List namespaces
16
+ const existing_namespaces = await dj.namespaces.list()
17
+ expect(existing_namespaces).toContainEqual({ namespace: namespace })
18
+
19
+ // Create a source
20
+ await dj.sources.create({
21
+ name: `${namespace}.repair_orders`,
22
+ description: 'Repair orders',
23
+ catalog: 'warehouse',
24
+ schema_: 'roads',
25
+ table: 'repair_orders',
26
+ mode: 'published',
27
+ columns: [
28
+ { name: 'repair_order_id', type: 'int' },
29
+ { name: 'municipality_id', type: 'string' },
30
+ { name: 'hard_hat_id', type: 'int' },
31
+ { name: 'order_date', type: 'timestamp' },
32
+ { name: 'required_date', type: 'timestamp' },
33
+ { name: 'dispatched_date', type: 'timestamp' },
34
+ { name: 'dispatcher_id', type: 'int' },
35
+ ],
36
+ })
37
+
38
+ // Get source
39
+ const source1 = await dj.nodes.get(`${namespace}.repair_orders`)
40
+ expect(source1.name).toBe(`${namespace}.repair_orders`)
41
+ expect(source1.status).toBe('valid')
42
+ expect(source1.mode).toBe('published')
43
+
44
+ // Create a transform
45
+ await dj.transforms.create({
46
+ name: `${namespace}.repair_orders_w_dispatchers`,
47
+ description: 'Repair orders that have a dispatcher',
48
+ mode: 'published',
49
+ query: `SELECT
50
+ repair_order_id,
51
+ municipality_id,
52
+ hard_hat_id,
53
+ dispatcher_id
54
+ FROM ${namespace}.repair_orders
55
+ WHERE dispatcher_id IS NOT NULL`,
56
+ })
57
+
58
+ // Get transform
59
+ const transform1 = await dj.nodes.get(
60
+ `${namespace}.repair_orders_w_dispatchers`
61
+ )
62
+ expect(transform1.name).toBe(`${namespace}.repair_orders_w_dispatchers`)
63
+ expect(transform1.status).toBe('valid')
64
+ expect(transform1.mode).toBe('published')
65
+
66
+ // Create a source and dimension node
67
+ const source2 = await dj.sources.create({
68
+ name: `${namespace}.dispatchers`,
69
+ description:
70
+ 'Different third party dispatcher companies that coordinate repairs',
71
+ catalog: 'warehouse',
72
+ schema_: 'roads',
73
+ table: 'dispatchers',
74
+ mode: 'published',
75
+ columns: [
76
+ { name: 'dispatcher_id', type: 'int' },
77
+ { name: 'company_name', type: 'string' },
78
+ { name: 'phone', type: 'string' },
79
+ ],
80
+ })
81
+ expect(source2.name).toBe(`${namespace}.dispatchers`)
82
+ expect(source2.status).toBe('valid')
83
+ expect(source2.mode).toBe('published')
84
+ await dj.dimensions.create({
85
+ name: `${namespace}.all_dispatchers`,
86
+ description: 'All dispatchers',
87
+ primary_key: ['dispatcher_id'],
88
+ mode: 'published',
89
+ query: `SELECT
90
+ dispatcher_id,
91
+ company_name,
92
+ phone
93
+ FROM ${namespace}.dispatchers`,
94
+ })
95
+ const dimension1 = await dj.nodes.get(`${namespace}.all_dispatchers`)
96
+ expect(dimension1.name).toBe(`${namespace}.all_dispatchers`)
97
+ expect(dimension1.status).toBe('valid')
98
+ expect(dimension1.mode).toBe('published')
99
+
100
+ // Create metrics
101
+ await dj.metrics.create({
102
+ name: `${namespace}.num_repair_orders`,
103
+ description: 'Number of repair orders',
104
+ mode: 'published',
105
+ query: `SELECT
106
+ count(repair_order_id)
107
+ FROM ${namespace}.repair_orders`,
108
+ })
109
+ const metric1 = await dj.nodes.get(`${namespace}.num_repair_orders`)
110
+ expect(metric1.name).toBe(`${namespace}.num_repair_orders`)
111
+ expect(metric1.status).toBe('valid')
112
+ expect(metric1.mode).toBe('published')
113
+
114
+ // List metrics
115
+ const list_of_metrics = await dj.metrics.all()
116
+ expect(list_of_metrics).toContain(`${namespace}.num_repair_orders`)
117
+
118
+ // Create a dimension link
119
+ const dimension_linked_message = await dj.dimensions.link(
120
+ `${namespace}.repair_orders`,
121
+ 'dispatcher_id',
122
+ `${namespace}.all_dispatchers`,
123
+ 'dispatcher_id'
124
+ )
125
+ expect(dimension_linked_message).toEqual({
126
+ message: `Dimension node ${namespace}.all_dispatchers has been successfully linked to column dispatcher_id on node ${namespace}.repair_orders`,
127
+ })
128
+
129
+ const common_dimensions = await dj.commonDimensions.list([
130
+ 'default.num_repair_orders',
131
+ 'default.avg_repair_price',
132
+ 'default.total_repair_cost',
133
+ ])
134
+
135
+ expect(common_dimensions).toEqual([
136
+ {
137
+ name: 'default.dispatcher.company_name',
138
+ type: 'string',
139
+ path: [
140
+ 'default.repair_orders.repair_order_id',
141
+ 'default.repair_order.dispatcher_id',
142
+ ],
143
+ },
144
+ {
145
+ name: 'default.dispatcher.dispatcher_id',
146
+ type: 'int',
147
+ path: [
148
+ 'default.repair_orders.repair_order_id',
149
+ 'default.repair_order.dispatcher_id',
150
+ ],
151
+ },
152
+ {
153
+ name: 'default.dispatcher.phone',
154
+ type: 'string',
155
+ path: [
156
+ 'default.repair_orders.repair_order_id',
157
+ 'default.repair_order.dispatcher_id',
158
+ ],
159
+ },
160
+ {
161
+ name: 'default.hard_hat.address',
162
+ type: 'string',
163
+ path: [
164
+ 'default.repair_orders.repair_order_id',
165
+ 'default.repair_order.hard_hat_id',
166
+ ],
167
+ },
168
+ {
169
+ name: 'default.hard_hat.birth_date',
170
+ type: 'date',
171
+ path: [
172
+ 'default.repair_orders.repair_order_id',
173
+ 'default.repair_order.hard_hat_id',
174
+ ],
175
+ },
176
+ {
177
+ name: 'default.hard_hat.city',
178
+ type: 'string',
179
+ path: [
180
+ 'default.repair_orders.repair_order_id',
181
+ 'default.repair_order.hard_hat_id',
182
+ ],
183
+ },
184
+ {
185
+ name: 'default.hard_hat.contractor_id',
186
+ type: 'int',
187
+ path: [
188
+ 'default.repair_orders.repair_order_id',
189
+ 'default.repair_order.hard_hat_id',
190
+ ],
191
+ },
192
+ {
193
+ name: 'default.hard_hat.country',
194
+ type: 'string',
195
+ path: [
196
+ 'default.repair_orders.repair_order_id',
197
+ 'default.repair_order.hard_hat_id',
198
+ ],
199
+ },
200
+ {
201
+ name: 'default.hard_hat.first_name',
202
+ type: 'string',
203
+ path: [
204
+ 'default.repair_orders.repair_order_id',
205
+ 'default.repair_order.hard_hat_id',
206
+ ],
207
+ },
208
+ {
209
+ name: 'default.hard_hat.hard_hat_id',
210
+ type: 'int',
211
+ path: [
212
+ 'default.repair_orders.repair_order_id',
213
+ 'default.repair_order.hard_hat_id',
214
+ ],
215
+ },
216
+ {
217
+ name: 'default.hard_hat.hire_date',
218
+ type: 'date',
219
+ path: [
220
+ 'default.repair_orders.repair_order_id',
221
+ 'default.repair_order.hard_hat_id',
222
+ ],
223
+ },
224
+ {
225
+ name: 'default.hard_hat.last_name',
226
+ type: 'string',
227
+ path: [
228
+ 'default.repair_orders.repair_order_id',
229
+ 'default.repair_order.hard_hat_id',
230
+ ],
231
+ },
232
+ {
233
+ name: 'default.hard_hat.manager',
234
+ type: 'int',
235
+ path: [
236
+ 'default.repair_orders.repair_order_id',
237
+ 'default.repair_order.hard_hat_id',
238
+ ],
239
+ },
240
+ {
241
+ name: 'default.hard_hat.postal_code',
242
+ type: 'string',
243
+ path: [
244
+ 'default.repair_orders.repair_order_id',
245
+ 'default.repair_order.hard_hat_id',
246
+ ],
247
+ },
248
+ {
249
+ name: 'default.hard_hat.state',
250
+ type: 'string',
251
+ path: [
252
+ 'default.repair_orders.repair_order_id',
253
+ 'default.repair_order.hard_hat_id',
254
+ ],
255
+ },
256
+ {
257
+ name: 'default.hard_hat.title',
258
+ type: 'string',
259
+ path: [
260
+ 'default.repair_orders.repair_order_id',
261
+ 'default.repair_order.hard_hat_id',
262
+ ],
263
+ },
264
+ {
265
+ name: 'default.municipality_dim.contact_name',
266
+ type: 'string',
267
+ path: [
268
+ 'default.repair_orders.repair_order_id',
269
+ 'default.repair_order.municipality_id',
270
+ ],
271
+ },
272
+ {
273
+ name: 'default.municipality_dim.contact_title',
274
+ type: 'string',
275
+ path: [
276
+ 'default.repair_orders.repair_order_id',
277
+ 'default.repair_order.municipality_id',
278
+ ],
279
+ },
280
+ {
281
+ name: 'default.municipality_dim.local_region',
282
+ type: 'string',
283
+ path: [
284
+ 'default.repair_orders.repair_order_id',
285
+ 'default.repair_order.municipality_id',
286
+ ],
287
+ },
288
+ {
289
+ name: 'default.municipality_dim.municipality_id',
290
+ type: 'string',
291
+ path: [
292
+ 'default.repair_orders.repair_order_id',
293
+ 'default.repair_order.municipality_id',
294
+ ],
295
+ },
296
+ {
297
+ name: 'default.municipality_dim.municipality_type_desc',
298
+ type: 'string',
299
+ path: [
300
+ 'default.repair_orders.repair_order_id',
301
+ 'default.repair_order.municipality_id',
302
+ ],
303
+ },
304
+ {
305
+ name: 'default.municipality_dim.municipality_type_id',
306
+ type: 'string',
307
+ path: [
308
+ 'default.repair_orders.repair_order_id',
309
+ 'default.repair_order.municipality_id',
310
+ ],
311
+ },
312
+ {
313
+ name: 'default.municipality_dim.state_id',
314
+ type: 'int',
315
+ path: [
316
+ 'default.repair_orders.repair_order_id',
317
+ 'default.repair_order.municipality_id',
318
+ ],
319
+ },
320
+ {
321
+ name: 'default.repair_orders.repair_order_id',
322
+ type: 'int',
323
+ path: [],
324
+ },
325
+ {
326
+ name: 'default.us_state.state_abbr',
327
+ type: 'string',
328
+ path: [
329
+ 'default.repair_orders.repair_order_id',
330
+ 'default.repair_order.hard_hat_id',
331
+ 'default.hard_hat.state',
332
+ ],
333
+ },
334
+ {
335
+ name: 'default.us_state.state_id',
336
+ type: 'int',
337
+ path: [
338
+ 'default.repair_orders.repair_order_id',
339
+ 'default.repair_order.hard_hat_id',
340
+ 'default.hard_hat.state',
341
+ ],
342
+ },
343
+ {
344
+ name: 'default.us_state.state_name',
345
+ type: 'string',
346
+ path: [
347
+ 'default.repair_orders.repair_order_id',
348
+ 'default.repair_order.hard_hat_id',
349
+ 'default.hard_hat.state',
350
+ ],
351
+ },
352
+ {
353
+ name: 'default.us_state.state_region',
354
+ type: 'int',
355
+ path: [
356
+ 'default.repair_orders.repair_order_id',
357
+ 'default.repair_order.hard_hat_id',
358
+ 'default.hard_hat.state',
359
+ ],
360
+ },
361
+ {
362
+ name: 'default.us_state.state_region_description',
363
+ type: 'string',
364
+ path: [
365
+ 'default.repair_orders.repair_order_id',
366
+ 'default.repair_order.hard_hat_id',
367
+ 'default.hard_hat.state',
368
+ ],
369
+ },
370
+ ])
371
+
372
+ const query = await dj.sql.get(
373
+ [
374
+ 'default.num_repair_orders',
375
+ 'default.avg_repair_price',
376
+ 'default.total_repair_cost',
377
+ ],
378
+ [
379
+ 'default.us_state.state_abbr',
380
+ 'default.us_state.state_id',
381
+ 'default.us_state.state_name',
382
+ ],
383
+ []
384
+ )
385
+ const trimmedQuery = query.sql.replace(/\s+/g, '')
386
+ expect(trimmedQuery).toBe(
387
+ `WITH
388
+ m0_default_DOT_num_repair_orders AS (SELECT default_DOT_us_state.state_abbr,
389
+ default_DOT_us_state.state_id,
390
+ default_DOT_us_state.state_name,
391
+ count(default_DOT_repair_orders.repair_order_id) default_DOT_num_repair_orders
392
+ FROM roads.repair_orders AS default_DOT_repair_orders LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id,
393
+ default_DOT_repair_orders.hard_hat_id,
394
+ default_DOT_repair_orders.municipality_id,
395
+ default_DOT_repair_orders.repair_order_id
396
+ FROM roads.repair_orders AS default_DOT_repair_orders)
397
+ AS default_DOT_repair_order ON default_DOT_repair_orders.repair_order_id = default_DOT_repair_order.repair_order_id
398
+ LEFT OUTER JOIN (SELECT default_DOT_hard_hats.birth_date,
399
+ default_DOT_hard_hats.hard_hat_id,
400
+ default_DOT_hard_hats.hire_date,
401
+ default_DOT_hard_hats.state
402
+ FROM roads.hard_hats AS default_DOT_hard_hats)
403
+ AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id
404
+ LEFT OUTER JOIN (SELECT default_DOT_us_states.state_abbr,
405
+ default_DOT_us_states.state_id,
406
+ default_DOT_us_states.state_name
407
+ FROM roads.us_states AS default_DOT_us_states LEFT JOIN roads.us_region AS default_DOT_us_region ON default_DOT_us_states.state_region = default_DOT_us_region.us_region_description)
408
+ AS default_DOT_us_state ON default_DOT_hard_hat.state = default_DOT_us_state.state_abbr
409
+ GROUP BY default_DOT_us_state.state_abbr, default_DOT_us_state.state_id, default_DOT_us_state.state_name
410
+ ),
411
+ m1_default_DOT_avg_repair_price AS (SELECT default_DOT_us_state.state_abbr,
412
+ default_DOT_us_state.state_id,
413
+ default_DOT_us_state.state_name,
414
+ avg(default_DOT_repair_order_details.price) default_DOT_avg_repair_price
415
+ FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id,
416
+ default_DOT_repair_orders.hard_hat_id,
417
+ default_DOT_repair_orders.municipality_id,
418
+ default_DOT_repair_orders.repair_order_id
419
+ FROM roads.repair_orders AS default_DOT_repair_orders)
420
+ AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id
421
+ LEFT OUTER JOIN (SELECT default_DOT_hard_hats.birth_date,
422
+ default_DOT_hard_hats.hard_hat_id,
423
+ default_DOT_hard_hats.hire_date,
424
+ default_DOT_hard_hats.state
425
+ FROM roads.hard_hats AS default_DOT_hard_hats)
426
+ AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id
427
+ LEFT OUTER JOIN (SELECT default_DOT_us_states.state_abbr,
428
+ default_DOT_us_states.state_id,
429
+ default_DOT_us_states.state_name
430
+ FROM roads.us_states AS default_DOT_us_states LEFT JOIN roads.us_region AS default_DOT_us_region ON default_DOT_us_states.state_region = default_DOT_us_region.us_region_description)
431
+ AS default_DOT_us_state ON default_DOT_hard_hat.state = default_DOT_us_state.state_abbr
432
+ GROUP BY default_DOT_us_state.state_abbr, default_DOT_us_state.state_id, default_DOT_us_state.state_name
433
+ ),
434
+ m2_default_DOT_total_repair_cost AS (SELECT default_DOT_us_state.state_abbr,
435
+ default_DOT_us_state.state_id,
436
+ default_DOT_us_state.state_name,
437
+ sum(default_DOT_repair_order_details.price) default_DOT_total_repair_cost
438
+ FROM roads.repair_order_details AS default_DOT_repair_order_details LEFT OUTER JOIN (SELECT default_DOT_repair_orders.dispatcher_id,
439
+ default_DOT_repair_orders.hard_hat_id,
440
+ default_DOT_repair_orders.municipality_id,
441
+ default_DOT_repair_orders.repair_order_id
442
+ FROM roads.repair_orders AS default_DOT_repair_orders)
443
+ AS default_DOT_repair_order ON default_DOT_repair_order_details.repair_order_id = default_DOT_repair_order.repair_order_id
444
+ LEFT OUTER JOIN (SELECT default_DOT_hard_hats.birth_date,
445
+ default_DOT_hard_hats.hard_hat_id,
446
+ default_DOT_hard_hats.hire_date,
447
+ default_DOT_hard_hats.state
448
+ FROM roads.hard_hats AS default_DOT_hard_hats)
449
+ AS default_DOT_hard_hat ON default_DOT_repair_order.hard_hat_id = default_DOT_hard_hat.hard_hat_id
450
+ LEFT OUTER JOIN (SELECT default_DOT_us_states.state_abbr,
451
+ default_DOT_us_states.state_id,
452
+ default_DOT_us_states.state_name
453
+ FROM roads.us_states AS default_DOT_us_states LEFT JOIN roads.us_region AS default_DOT_us_region ON default_DOT_us_states.state_region = default_DOT_us_region.us_region_description)
454
+ AS default_DOT_us_state ON default_DOT_hard_hat.state = default_DOT_us_state.state_abbr
455
+ GROUP BY default_DOT_us_state.state_abbr, default_DOT_us_state.state_id, default_DOT_us_state.state_name
456
+ )SELECT m0_default_DOT_num_repair_orders.default_DOT_num_repair_orders,
457
+ m1_default_DOT_avg_repair_price.default_DOT_avg_repair_price,
458
+ m2_default_DOT_total_repair_cost.default_DOT_total_repair_cost,
459
+ COALESCE(m0_default_DOT_num_repair_orders.state_abbr, m1_default_DOT_avg_repair_price.state_abbr, m2_default_DOT_total_repair_cost.state_abbr) state_abbr,
460
+ COALESCE(m0_default_DOT_num_repair_orders.state_id, m1_default_DOT_avg_repair_price.state_id, m2_default_DOT_total_repair_cost.state_id) state_id,
461
+ COALESCE(m0_default_DOT_num_repair_orders.state_name, m1_default_DOT_avg_repair_price.state_name, m2_default_DOT_total_repair_cost.state_name) state_name
462
+ FROM m0_default_DOT_num_repair_orders FULL OUTER JOIN m1_default_DOT_avg_repair_price ON m0_default_DOT_num_repair_orders.state_abbr = m1_default_DOT_avg_repair_price.state_abbr AND m0_default_DOT_num_repair_orders.state_id = m1_default_DOT_avg_repair_price.state_id AND m0_default_DOT_num_repair_orders.state_name = m1_default_DOT_avg_repair_price.state_name
463
+ FULL OUTER JOIN m2_default_DOT_total_repair_cost ON m0_default_DOT_num_repair_orders.state_abbr = m2_default_DOT_total_repair_cost.state_abbr AND m0_default_DOT_num_repair_orders.state_id = m2_default_DOT_total_repair_cost.state_id AND m0_default_DOT_num_repair_orders.state_name = m2_default_DOT_total_repair_cost.state_name
464
+ `.replace(/\s+/g, '')
465
+ )
466
+
467
+ const data = await dj.data.get(
468
+ [
469
+ 'default.num_repair_orders',
470
+ 'default.avg_repair_price',
471
+ 'default.total_repair_cost',
472
+ ],
473
+ [
474
+ 'default.us_state.state_abbr',
475
+ 'default.us_state.state_id',
476
+ 'default.us_state.state_name',
477
+ ],
478
+ []
479
+ )
480
+
481
+ expect(data).toEqual({
482
+ columns: [
483
+ {
484
+ name: 'default_DOT_num_repair_orders',
485
+ type: 'bigint',
486
+ },
487
+ {
488
+ name: 'default_DOT_avg_repair_price',
489
+ type: 'double',
490
+ },
491
+ {
492
+ name: 'default_DOT_total_repair_cost',
493
+ type: 'double',
494
+ },
495
+ {
496
+ name: 'state_abbr',
497
+ type: 'string',
498
+ },
499
+ {
500
+ name: 'state_id',
501
+ type: 'int',
502
+ },
503
+ {
504
+ name: 'state_name',
505
+ type: 'string',
506
+ },
507
+ ],
508
+ data: [
509
+ [486, 65682.0, 31921452.0, 'AZ', 3, 'Arizona'],
510
+ [486, 39301.5, 19100529.0, 'CT', 7, 'Connecticut'],
511
+ [729, 65595.66666666667, 47819241.0, 'GA', 11, 'Georgia'],
512
+ [729, 76555.33333333333, 55808838.0, 'MA', 22, 'Massachusetts'],
513
+ [1215, 64190.6, 77991579.0, 'MI', 23, 'Michigan'],
514
+ [972, 54672.75, 53141913.0, 'NJ', 31, 'New Jersey'],
515
+ [243, 53374.0, 12969882.0, 'NY', 33, 'New York'],
516
+ [243, 70418.0, 17111574.0, 'OK', 37, 'Oklahoma'],
517
+ [972, 54083.5, 52569162.0, 'PA', 39, 'Pennsylvania'],
518
+ ],
519
+ })
520
+
521
+ // Create a transform 2 downstream from a transform 1
522
+ await dj.transforms.create({
523
+ name: `${namespace}.repair_orders_w_hard_hats`,
524
+ description: 'Repair orders that have a hard hat assigned',
525
+ mode: 'published',
526
+ query: `SELECT
527
+ repair_order_id,
528
+ municipality_id,
529
+ hard_hat_id,
530
+ dispatcher_id
531
+ FROM ${namespace}.repair_orders_w_dispatchers
532
+ WHERE hard_hat_id IS NOT NULL`,
533
+ })
534
+
535
+ // Get transform 2 that's downstream from transform 1 and make sure it's valid
536
+ const transform2 = await dj.nodes.get(
537
+ `${namespace}.repair_orders_w_hard_hats`
538
+ )
539
+ expect(transform2.name).toBe(`${namespace}.repair_orders_w_hard_hats`)
540
+ expect(transform2.status).toBe('valid')
541
+ expect(transform2.mode).toBe('published')
542
+
543
+ // Create a draft transform 4 that's downstream from a not yet created transform 3
544
+ await dj.transforms.create({
545
+ name: `${namespace}.repair_orders_w_repair_order_id`,
546
+ description: 'Repair orders without a null ID',
547
+ mode: 'draft',
548
+ query: `SELECT
549
+ repair_order_id,
550
+ municipality_id,
551
+ hard_hat_id,
552
+ dispatcher_id
553
+ FROM ${namespace}.repair_orders_w_municipalities
554
+ WHERE repair_order_id IS NOT NULL`,
555
+ })
556
+ const transform4 = await dj.nodes.get(
557
+ `${namespace}.repair_orders_w_repair_order_id`
558
+ )
559
+ // Check that transform 4 is invalid because transform 3 does not exist
560
+ expect(transform4.name).toBe(`${namespace}.repair_orders_w_repair_order_id`)
561
+ expect(transform4.status).toBe('invalid')
562
+ expect(transform4.mode).toBe('draft')
563
+
564
+ // Create a draft transform 3 that's downstream from transform 2
565
+ await dj.transforms.create({
566
+ name: `${namespace}.repair_orders_w_municipalities`,
567
+ description: 'Repair orders that have a municipality listed',
568
+ mode: 'draft',
569
+ query: `SELECT
570
+ repair_order_id,
571
+ municipality_id,
572
+ hard_hat_id,
573
+ dispatcher_id
574
+ FROM ${namespace}.repair_orders_w_hard_hats
575
+ WHERE municipality_id IS NOT NULL`,
576
+ })
577
+ const transform3 = await dj.nodes.get(
578
+ `${namespace}.repair_orders_w_municipalities`
579
+ )
580
+ expect(transform3.name).toBe(`${namespace}.repair_orders_w_municipalities`)
581
+ expect(transform3.status).toBe('valid')
582
+ expect(transform3.mode).toBe('draft')
583
+
584
+ // Check that transform 4 is now valid after transform 3 was created
585
+ const transform4_now_valid = await dj.nodes.get(
586
+ `${namespace}.repair_orders_w_repair_order_id`
587
+ )
588
+ expect(transform4_now_valid.status).toBe('valid')
589
+
590
+ // Check that publishing transform 3 works
591
+ await dj.nodes.publish(transform3.name)
592
+ }, 60000)
@@ -0,0 +1,15 @@
1
+ const path = require('path')
2
+
3
+ module.exports = {
4
+ entry: './src/index.js',
5
+ mode: 'production',
6
+ output: {
7
+ path: path.resolve(__dirname, 'dist'),
8
+ filename: 'datajunction.js',
9
+ globalObject: 'this',
10
+ library: {
11
+ name: 'datajunction',
12
+ type: 'umd',
13
+ },
14
+ },
15
+ }