@things-factory/dataset 5.0.0-alpha.3 → 5.0.0-alpha.32

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.
Files changed (99) hide show
  1. package/README.md +13 -0
  2. package/assets/data-samples.jpg +0 -0
  3. package/client/bootstrap.js +16 -1
  4. package/client/pages/data-entry/data-entry-form.js +85 -0
  5. package/client/pages/data-entry/data-entry-list-page.js +464 -0
  6. package/client/pages/data-ooc/data-ooc-list-page.js +488 -0
  7. package/client/pages/data-ooc/data-ooc-view.js +182 -0
  8. package/client/pages/{data-sample.js → data-sample/data-sample-list-page.js} +153 -59
  9. package/client/pages/data-sample/data-sample-view.js +97 -0
  10. package/client/pages/{data-sensor.js → data-sensor/data-sensor-list-page.js} +11 -16
  11. package/client/pages/{data-item-list.js → data-set/data-item-list.js} +70 -16
  12. package/client/pages/{data-set-importer.js → data-set/data-set-importer.js} +0 -0
  13. package/client/pages/{data-set.js → data-set/data-set-list-page.js} +282 -76
  14. package/client/route.js +14 -6
  15. package/dist-server/controllers/create-data-sample.js +133 -0
  16. package/dist-server/controllers/create-data-sample.js.map +1 -0
  17. package/dist-server/controllers/data-use-case.js +57 -0
  18. package/dist-server/controllers/data-use-case.js.map +1 -0
  19. package/dist-server/controllers/index.js +17 -0
  20. package/dist-server/controllers/index.js.map +1 -1
  21. package/dist-server/index.js +2 -0
  22. package/dist-server/index.js.map +1 -1
  23. package/dist-server/routes.js +9 -24
  24. package/dist-server/routes.js.map +1 -1
  25. package/dist-server/service/data-item/data-item-mutation.js +5 -1
  26. package/dist-server/service/data-item/data-item-mutation.js.map +1 -1
  27. package/dist-server/service/data-item/data-item-type.js +18 -6
  28. package/dist-server/service/data-item/data-item-type.js.map +1 -1
  29. package/dist-server/service/data-item/data-item.js +27 -7
  30. package/dist-server/service/data-item/data-item.js.map +1 -1
  31. package/dist-server/service/data-ooc/data-ooc-mutation.js +92 -0
  32. package/dist-server/service/data-ooc/data-ooc-mutation.js.map +1 -0
  33. package/dist-server/service/data-ooc/data-ooc-query.js +115 -0
  34. package/dist-server/service/data-ooc/data-ooc-query.js.map +1 -0
  35. package/dist-server/service/data-ooc/data-ooc-subscription.js +65 -0
  36. package/dist-server/service/data-ooc/data-ooc-subscription.js.map +1 -0
  37. package/dist-server/service/data-ooc/data-ooc-type.js +107 -0
  38. package/dist-server/service/data-ooc/data-ooc-type.js.map +1 -0
  39. package/dist-server/service/data-ooc/data-ooc.js +237 -0
  40. package/dist-server/service/data-ooc/data-ooc.js.map +1 -0
  41. package/dist-server/service/data-ooc/index.js +10 -0
  42. package/dist-server/service/data-ooc/index.js.map +1 -0
  43. package/dist-server/service/data-sample/data-sample-mutation.js +3 -105
  44. package/dist-server/service/data-sample/data-sample-mutation.js.map +1 -1
  45. package/dist-server/service/data-sample/data-sample-type.js +12 -42
  46. package/dist-server/service/data-sample/data-sample-type.js.map +1 -1
  47. package/dist-server/service/data-sample/data-sample.js +34 -3
  48. package/dist-server/service/data-sample/data-sample.js.map +1 -1
  49. package/dist-server/service/data-set/data-set-mutation.js +1 -2
  50. package/dist-server/service/data-set/data-set-mutation.js.map +1 -1
  51. package/dist-server/service/data-set/data-set-query.js +110 -1
  52. package/dist-server/service/data-set/data-set-query.js.map +1 -1
  53. package/dist-server/service/data-set/data-set-type.js +48 -4
  54. package/dist-server/service/data-set/data-set-type.js.map +1 -1
  55. package/dist-server/service/data-set/data-set.js +59 -15
  56. package/dist-server/service/data-set/data-set.js.map +1 -1
  57. package/dist-server/service/data-spec/data-spec-manager.js +20 -0
  58. package/dist-server/service/data-spec/data-spec-manager.js.map +1 -0
  59. package/dist-server/service/data-spec/data-spec-query.js +48 -0
  60. package/dist-server/service/data-spec/data-spec-query.js.map +1 -0
  61. package/dist-server/service/data-spec/data-spec.js +78 -0
  62. package/dist-server/service/data-spec/data-spec.js.map +1 -0
  63. package/dist-server/service/data-spec/index.js +8 -0
  64. package/dist-server/service/data-spec/index.js.map +1 -0
  65. package/dist-server/service/index.js +12 -4
  66. package/dist-server/service/index.js.map +1 -1
  67. package/package.json +18 -12
  68. package/server/controllers/create-data-sample.ts +175 -0
  69. package/server/controllers/data-use-case.ts +85 -0
  70. package/server/controllers/index.ts +1 -0
  71. package/server/index.ts +3 -0
  72. package/server/routes.ts +17 -31
  73. package/server/service/data-item/data-item-mutation.ts +6 -1
  74. package/server/service/data-item/data-item-type.ts +13 -6
  75. package/server/service/data-item/data-item.ts +21 -5
  76. package/server/service/data-ooc/data-ooc-mutation.ts +150 -0
  77. package/server/service/data-ooc/data-ooc-query.ts +63 -0
  78. package/server/service/data-ooc/data-ooc-subscription.ts +51 -0
  79. package/server/service/data-ooc/data-ooc-type.ts +68 -0
  80. package/server/service/data-ooc/data-ooc.ts +204 -0
  81. package/server/service/data-ooc/index.ts +7 -0
  82. package/server/service/data-sample/data-sample-mutation.ts +6 -128
  83. package/server/service/data-sample/data-sample-type.ts +7 -28
  84. package/server/service/data-sample/data-sample.ts +33 -3
  85. package/server/service/data-set/data-set-mutation.ts +1 -4
  86. package/server/service/data-set/data-set-query.ts +87 -2
  87. package/server/service/data-set/data-set-type.ts +37 -4
  88. package/server/service/data-set/data-set.ts +52 -12
  89. package/server/service/data-spec/data-spec-manager.ts +21 -0
  90. package/server/service/data-spec/data-spec-query.ts +21 -0
  91. package/server/service/data-spec/data-spec.ts +44 -0
  92. package/server/service/data-spec/index.ts +5 -0
  93. package/server/service/index.ts +16 -8
  94. package/things-factory.config.js +14 -6
  95. package/translations/en.json +30 -0
  96. package/translations/ko.json +30 -1
  97. package/translations/ms.json +29 -0
  98. package/translations/zh.json +29 -0
  99. package/yarn-error.log +23244 -0
package/README.md ADDED
@@ -0,0 +1,13 @@
1
+ # dataset
2
+ ## partition keys
3
+ At the early stage, partition keys are desinged for dynamic partitioning for Athena. But It will be required so many AWS Glue crawlers also by each 'data-sets'.
4
+ Now partition keys are fixed with some cases. Default is daily dataset, which has S3 Key like 'domain={domain}/datasetid={datasetid}/year=2022/month=3/day=25'. It will be set default with empty value in 'data-sets'.
5
+
6
+ In another case, it is necessary to divide by hours or minutes and store them. This datetime item must be included in the S3 key. It will be required a new Glue Crawler and a Glue Catalog Table. For example if you need to separate directory hourly, you should add "%H" for 'hour' to partition_keys for this. and also should add a new crawler. And it would be needed a column to know it is daily or hourly or minutely for reporting client side. It's not developed yet.
7
+ Look at the below picture for understanding.
8
+
9
+ ![Architecture for partitioning](./assets/data-samples.jpg)
10
+
11
+ These tasks are related for S3 request limitations.
12
+ > __3,500 PUT/COPY/POST/DELETE or 5,500 GET/HEAD requests per second per prefix in a bucket__
13
+ - https://docs.aws.amazon.com/AmazonS3/latest/userguide/optimizing-performance.html
Binary file
@@ -1 +1,16 @@
1
- export default function bootstrap() {}
1
+ import {
2
+ OxGristRendererJson5,
3
+ registerEditor as registerGristEditor,
4
+ registerRenderer as registerGristRenderer
5
+ } from '@operato/data-grist'
6
+
7
+ import { OxGristEditorDataItemSpec } from '@operato/dataset/grist-editor'
8
+ import { OxGristEditorPartitionKeys } from '@operato/app/grist-editor/ox-grist-editor-partition-keys.js'
9
+
10
+ export default function bootstrap() {
11
+ registerGristEditor('data-item-spec', OxGristEditorDataItemSpec)
12
+ registerGristRenderer('data-item-spec', OxGristRendererJson5)
13
+
14
+ registerGristEditor('partition-keys', OxGristEditorPartitionKeys)
15
+ registerGristRenderer('partition-keys', OxGristRendererJson5)
16
+ }
@@ -0,0 +1,85 @@
1
+ import '@operato/dataset/ox-data-entry-form.js'
2
+
3
+ import { LitElement, css, html } from 'lit'
4
+ import { i18next, localize } from '@operato/i18n'
5
+
6
+ import { client } from '@operato/graphql'
7
+ import gql from 'graphql-tag'
8
+
9
+ class DataEntryForm extends localize(i18next)(LitElement) {
10
+ static get properties() {
11
+ return {
12
+ dataSet: Object
13
+ }
14
+ }
15
+
16
+ static get styles() {
17
+ return [
18
+ css`
19
+ :host {
20
+ display: flex;
21
+ flex-direction: column;
22
+
23
+ background-color: #fff;
24
+ overflow: auto;
25
+ }
26
+
27
+ ox-data-entry-form {
28
+ flex: 1;
29
+ margin: 10px;
30
+ }
31
+
32
+ .button-container {
33
+ display: flex;
34
+ margin-left: auto;
35
+ padding: var(--padding-default);
36
+ }
37
+ `
38
+ ]
39
+ }
40
+
41
+ get entryForm() {
42
+ return this.renderRoot.querySelector('ox-data-entry-form')
43
+ }
44
+
45
+ render() {
46
+ return html`
47
+ <ox-data-entry-form .dataSet=${this.dataSet}></ox-data-entry-form>
48
+ <div class="button-container">
49
+ <mwc-button raised @click=${this._updateDataItems.bind(this)}>${i18next.t('button.save')}</mwc-button>
50
+ </div>
51
+ `
52
+ }
53
+
54
+ async _updateDataItems() {
55
+ const data = this.entryForm.buildValue()
56
+ const dataSample = {
57
+ dataSet: {
58
+ id: this.dataSet.id
59
+ },
60
+ data
61
+ }
62
+
63
+ const response = await client.mutate({
64
+ mutation: gql`
65
+ mutation ($dataSample: NewDataSample!) {
66
+ createDataSample(dataSample: $dataSample) {
67
+ id
68
+ collectedAt
69
+ }
70
+ }
71
+ `,
72
+ variables: {
73
+ dataSample
74
+ }
75
+ })
76
+
77
+ if (!response.errors) {
78
+ document.dispatchEvent(
79
+ new CustomEvent('notify', { detail: { message: i18next.t('text.data sample created successfully') } })
80
+ )
81
+ }
82
+ }
83
+ }
84
+
85
+ window.customElements.define('data-entry-form', DataEntryForm)
@@ -0,0 +1,464 @@
1
+ import '@operato/data-grist'
2
+ import './data-entry-form'
3
+ import '@operato/board/ox-board-viewer.js'
4
+
5
+ import gql from 'graphql-tag'
6
+ import JSON5 from 'json5'
7
+ import { css, html } from 'lit'
8
+ import { connect } from 'pwa-helpers/connect-mixin'
9
+
10
+ import { getRenderer } from '@operato/data-grist'
11
+ import { OxDataUseCase } from '@operato/dataset'
12
+ import { client } from '@operato/graphql'
13
+ import { i18next, localize } from '@operato/i18n'
14
+ import { openPopup } from '@operato/layout'
15
+ import { navigate, PageView, store } from '@operato/shell'
16
+ import { CommonGristStyles, ScrollbarStyles } from '@operato/styles'
17
+ import { provider } from '@things-factory/board-ui'
18
+
19
+ const USECASE_OPTIONS = () => {
20
+ return ['', ...OxDataUseCase.getUseCaseNames()].map(name => {
21
+ return {
22
+ display: name,
23
+ value: name
24
+ }
25
+ })
26
+ }
27
+
28
+ const showEntryView = async (columns, data, column, record, rowIndex) => {
29
+ const { name, entryType, entryView } = record
30
+ const title = `${name} - ${i18next.t('title.data-entry-form')}`
31
+
32
+ switch (entryType) {
33
+ case 'generated':
34
+ openPopup(html` <data-entry-form .dataSet=${record} style="background-color: white;"></data-entry-form> `, {
35
+ closable: true,
36
+ backdrop: true,
37
+ size: 'large',
38
+ title
39
+ })
40
+ break
41
+
42
+ case 'board':
43
+ const board = {
44
+ id: entryView
45
+ }
46
+ openPopup(
47
+ html`
48
+ <ox-board-viewer
49
+ style="background-color: white;"
50
+ .board=${board}
51
+ .provider=${provider}
52
+ hide-fullscreen
53
+ hide-navigation
54
+ ></ox-board-viewer>
55
+ `,
56
+ {
57
+ closable: true,
58
+ backdrop: true,
59
+ size: 'large',
60
+ title
61
+ }
62
+ )
63
+
64
+ // navigate(`board-viewer/${entryView}?interactive=true&title=${title}`)
65
+ break
66
+
67
+ case 'page':
68
+ navigate(entryView)
69
+ break
70
+
71
+ case 'external':
72
+ window.open(entryView, '_blank')
73
+ break
74
+ }
75
+ }
76
+
77
+ const showMonitorView = (columns, data, column, record, rowIndex) => {
78
+ const { name, monitorType, monitorView } = record
79
+ const title = `${name} - ${i18next.t('title.data-entry-form')}`
80
+
81
+ switch (monitorType) {
82
+ case 'generated':
83
+ openPopup(html` <div style="background-color: white;">Under construction</div> `, {
84
+ backdrop: true,
85
+ size: 'large',
86
+ title
87
+ })
88
+ break
89
+
90
+ case 'board':
91
+ const board = {
92
+ id: monitorView
93
+ }
94
+ openPopup(
95
+ html`
96
+ <ox-board-viewer
97
+ style="background-color: white;"
98
+ .board=${board}
99
+ .provider=${provider}
100
+ hide-fullscreen
101
+ hide-navigation
102
+ ></ox-board-viewer>
103
+ `,
104
+ {
105
+ closable: true,
106
+ backdrop: true,
107
+ size: 'large',
108
+ title
109
+ }
110
+ )
111
+
112
+ // navigate(`board-viewer/${monitorView}?interactive=true&title=${title}`)
113
+ break
114
+
115
+ case 'page':
116
+ navigate(monitorView)
117
+ break
118
+
119
+ case 'external':
120
+ window.open(monitorView, '_blank')
121
+ break
122
+ }
123
+ }
124
+
125
+ export class DataEntryListPage extends connect(store)(localize(i18next)(PageView)) {
126
+ static get properties() {
127
+ return {
128
+ active: String,
129
+ gristConfig: Object,
130
+ filters: Object,
131
+ sorters: Object,
132
+ mode: String
133
+ }
134
+ }
135
+
136
+ static get styles() {
137
+ return [
138
+ ScrollbarStyles,
139
+ CommonGristStyles,
140
+ css`
141
+ :host {
142
+ display: flex;
143
+
144
+ width: 100%;
145
+
146
+ --grid-record-emphasized-background-color: red;
147
+ --grid-record-emphasized-color: yellow;
148
+ }
149
+ `
150
+ ]
151
+ }
152
+
153
+ get context() {
154
+ return {
155
+ title: i18next.t('title.data-entry list'),
156
+ help: 'dataset/data-entry-list'
157
+ }
158
+ }
159
+
160
+ render() {
161
+ const mode = 'CARD'
162
+
163
+ return html`
164
+ <ox-grist
165
+ .mode=${mode}
166
+ .config=${this.gristConfig}
167
+ .filters=${this.filters}
168
+ .orders=${this.orders}
169
+ .fetchHandler=${this.fetchHandler.bind(this)}
170
+ >
171
+ <div slot="headroom" id="filters">
172
+ <div id="filters">
173
+ <ox-filters-form></ox-filters-form>
174
+ </div>
175
+
176
+ <div id="sorters">
177
+ Sort
178
+ <mwc-icon
179
+ @click=${e => {
180
+ const target = e.currentTarget
181
+ this.renderRoot.querySelector('#sorter-control').open({
182
+ right: 0,
183
+ top: target.offsetTop + target.offsetHeight
184
+ })
185
+ }}
186
+ >expand_more</mwc-icon
187
+ >
188
+ <ox-popup id="sorter-control">
189
+ <ox-sorters-control> </ox-sorters-control>
190
+ </ox-popup>
191
+ </div>
192
+ </div>
193
+ </ox-grist>
194
+ `
195
+ }
196
+
197
+ get grist() {
198
+ return this.renderRoot.querySelector('ox-grist')
199
+ }
200
+
201
+ async pageInitialized(lifecycle) {
202
+ this.gristConfig = {
203
+ list: {
204
+ thumbnail: 'entryView',
205
+ fields: ['name', 'description'],
206
+ details: ['schedule', 'type', 'useCase', 'latestCollectedAt', 'prevSchedule', 'nextSchedule']
207
+ },
208
+ columns: [
209
+ {
210
+ type: 'gutter',
211
+ gutterName: 'button',
212
+ icon: 'fact_check',
213
+ handlers: {
214
+ click: showEntryView
215
+ }
216
+ },
217
+ {
218
+ type: 'gutter',
219
+ gutterName: 'button',
220
+ icon: 'analytics',
221
+ handlers: {
222
+ click: showMonitorView
223
+ }
224
+ },
225
+ {
226
+ type: 'string',
227
+ name: 'name',
228
+ header: i18next.t('field.name'),
229
+ record: {
230
+ editable: false
231
+ },
232
+ filter: 'search',
233
+ sortable: true,
234
+ width: 150
235
+ },
236
+ {
237
+ type: 'string',
238
+ name: 'description',
239
+ header: i18next.t('field.description'),
240
+ record: {
241
+ editable: false
242
+ },
243
+ filter: 'search',
244
+ width: 200
245
+ },
246
+ {
247
+ type: 'select',
248
+ name: 'type',
249
+ label: true,
250
+ header: i18next.t('field.type'),
251
+ record: {
252
+ editable: false,
253
+ options: [
254
+ {},
255
+ {
256
+ display: 'Manually Collected',
257
+ value: 'manual'
258
+ },
259
+ {
260
+ display: 'Automatically Collected',
261
+ value: 'automatic'
262
+ }
263
+ ]
264
+ },
265
+ sortable: true,
266
+ filter: true,
267
+ width: 60
268
+ },
269
+ {
270
+ type: 'select',
271
+ name: 'useCase',
272
+ label: true,
273
+ header: i18next.t('field.use-case'),
274
+ record: {
275
+ editable: false,
276
+ options: USECASE_OPTIONS
277
+ },
278
+ sortable: true,
279
+ filter: {
280
+ operator: 'eq',
281
+ options: USECASE_OPTIONS /* in case select options type is a function, filter should have its own options */
282
+ },
283
+ width: 80
284
+ },
285
+ {
286
+ type: 'crontab',
287
+ name: 'schedule',
288
+ label: true,
289
+ header: i18next.t('field.schedule'),
290
+ record: {
291
+ editable: false
292
+ },
293
+ width: 80,
294
+ label: true
295
+ },
296
+ {
297
+ type: 'resource-object',
298
+ name: 'supervisoryRole',
299
+ header: i18next.t('field.supervisory-role'),
300
+ record: {
301
+ editable: false
302
+ },
303
+ width: 120
304
+ },
305
+ {
306
+ type: 'datetime',
307
+ name: 'latestCollectedAt',
308
+ label: true,
309
+ header: i18next.t('field.latest-collected-at'),
310
+ record: {
311
+ editable: false
312
+ },
313
+ width: 180
314
+ },
315
+ {
316
+ type: 'datetime',
317
+ name: 'prevSchedule',
318
+ label: true,
319
+ header: i18next.t('field.prev-schedule'),
320
+ record: {
321
+ editable: false
322
+ },
323
+ width: 180
324
+ },
325
+ {
326
+ type: 'datetime',
327
+ name: 'nextSchedule',
328
+ label: true,
329
+ header: i18next.t('field.next-schedule'),
330
+ record: {
331
+ editable: false
332
+ },
333
+ width: 180
334
+ },
335
+ {
336
+ type: 'string',
337
+ name: 'entryView',
338
+ hidden: true,
339
+ record: {
340
+ editable: false,
341
+ renderer: function (value, column, record, rowIndex, field) {
342
+ const type = record.entryType !== 'board' ? 'string' : 'image'
343
+ value = record.entryType !== 'board' ? value : record.entryBoard?.thumbnail
344
+
345
+ return getRenderer(type)(value, column, record, rowIndex, field)
346
+ }
347
+ }
348
+ }
349
+ ],
350
+ rows: {
351
+ selectable: {
352
+ multiple: false
353
+ },
354
+ handlers: {
355
+ click: showEntryView
356
+ },
357
+ classifier: function (record, rowIndex) {}
358
+ },
359
+ sorters: [
360
+ {
361
+ name: 'name'
362
+ }
363
+ ]
364
+ }
365
+
366
+ await this.updateComplete
367
+
368
+ this.grist.fetch()
369
+ }
370
+
371
+ async pageUpdated(changes, lifecycle) {
372
+ if (this.active) {
373
+ await this.updateComplete
374
+
375
+ var filters = lifecycle.params?.['filters']
376
+ if (filters) {
377
+ try {
378
+ filters = JSON5.parse(filters)
379
+ this.filters = filters
380
+ } catch (e) {
381
+ console.error(`filters parameter parsing error: ${e}`)
382
+ }
383
+ }
384
+
385
+ var sorters = lifecycle.params?.['sorters']
386
+ if (sorters) {
387
+ try {
388
+ sorters = JSON5.parse(sorters)
389
+ this.sorters = sorters
390
+ } catch (e) {
391
+ console.error(`sorters parameter parsing error: ${e}`)
392
+ }
393
+ }
394
+
395
+ this.grist.fetch()
396
+ }
397
+ }
398
+
399
+ async fetchHandler({ page, limit, sortings = [], filters = [] }) {
400
+ const response = await client.query({
401
+ query: gql`
402
+ query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
403
+ responses: dataSetsForEntry(filters: $filters, pagination: $pagination, sortings: $sortings) {
404
+ items {
405
+ id
406
+ name
407
+ description
408
+ partitionKeys
409
+ active
410
+ type
411
+ useCase
412
+ schedule
413
+ timezone
414
+ supervisoryRole {
415
+ id
416
+ name
417
+ }
418
+ entryType
419
+ entryView
420
+ entryBoard {
421
+ thumbnail
422
+ }
423
+ monitorType
424
+ monitorView
425
+ updater {
426
+ id
427
+ name
428
+ }
429
+ updatedAt
430
+ dataItems {
431
+ name
432
+ description
433
+ sequence
434
+ active
435
+ tag
436
+ type
437
+ unit
438
+ options
439
+ quota
440
+ spec
441
+ }
442
+ latestCollectedAt
443
+ nextSchedule
444
+ prevSchedule
445
+ }
446
+ total
447
+ }
448
+ }
449
+ `,
450
+ variables: {
451
+ filters,
452
+ pagination: { page, limit },
453
+ sortings
454
+ }
455
+ })
456
+
457
+ return {
458
+ total: response.data.responses.total || 0,
459
+ records: response.data.responses.items || []
460
+ }
461
+ }
462
+ }
463
+
464
+ window.customElements.define('data-entry-list-page', DataEntryListPage)