@things-factory/dataset 8.0.0-beta.9 → 8.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (147) hide show
  1. package/client/activities/activity-data-collect-edit.ts +105 -0
  2. package/client/activities/activity-data-collect-view.ts +91 -0
  3. package/client/activities/activity-data-review-edit.ts +133 -0
  4. package/client/activities/activity-data-review-view.ts +145 -0
  5. package/client/activities/activity-ooc-resolve-edit.ts +195 -0
  6. package/client/activities/activity-ooc-resolve-view.ts +143 -0
  7. package/client/activities/activity-ooc-review-edit.ts +173 -0
  8. package/client/activities/activity-ooc-review-view.ts +129 -0
  9. package/client/bootstrap.ts +35 -0
  10. package/client/components/data-entry-form.ts +109 -0
  11. package/client/index.ts +1 -0
  12. package/client/pages/data-archive/data-archive-list-page.ts +277 -0
  13. package/client/pages/data-archive/data-archive-request-popup.ts +177 -0
  14. package/client/pages/data-entry/data-entry-list-page.ts +464 -0
  15. package/client/pages/data-key-set/data-key-item-list.ts +183 -0
  16. package/client/pages/data-key-set/data-key-set-importer.ts +89 -0
  17. package/client/pages/data-key-set/data-key-set-list-page.ts +413 -0
  18. package/client/pages/data-ooc/data-ooc-list-page.ts +549 -0
  19. package/client/pages/data-ooc/data-ooc-page.ts +164 -0
  20. package/client/pages/data-ooc/data-ooc-view.ts +236 -0
  21. package/client/pages/data-ooc/data-oocs-page.ts +200 -0
  22. package/client/pages/data-report/data-report-embed-page.ts +108 -0
  23. package/client/pages/data-report/data-report-list-page.ts +454 -0
  24. package/client/pages/data-report/data-report-samples-page.ts +174 -0
  25. package/client/pages/data-report/jasper-report-oocs-page.ts +110 -0
  26. package/client/pages/data-report/jasper-report-samples-crosstab-page.ts +110 -0
  27. package/client/pages/data-report/jasper-report-samples-page.ts +110 -0
  28. package/client/pages/data-sample/data-sample-list-page.ts +442 -0
  29. package/client/pages/data-sample/data-sample-page.ts +55 -0
  30. package/client/pages/data-sample/data-sample-search-page.ts +424 -0
  31. package/client/pages/data-sample/data-sample-view.ts +292 -0
  32. package/client/pages/data-sample/data-samples-page.ts +249 -0
  33. package/client/pages/data-sensor/data-sensor-list-page.ts +456 -0
  34. package/client/pages/data-set/data-item-list.ts +304 -0
  35. package/client/pages/data-set/data-set-importer.ts +89 -0
  36. package/client/pages/data-set/data-set-list-page.ts +1078 -0
  37. package/client/pages/data-summary/data-summary-list-page.ts +363 -0
  38. package/client/pages/data-summary/data-summary-period-page.ts +439 -0
  39. package/client/pages/data-summary/data-summary-search-page.ts +426 -0
  40. package/client/pages/data-summary/data-summary-view.ts +133 -0
  41. package/client/route.ts +91 -0
  42. package/client/tsconfig.json +13 -0
  43. package/dist-client/activities/activity-data-review-edit.d.ts +1 -5
  44. package/dist-client/activities/activity-data-review-edit.js +5 -143
  45. package/dist-client/activities/activity-data-review-edit.js.map +1 -1
  46. package/dist-client/pages/data-entry/data-entry-list-page.js +2 -2
  47. package/dist-client/pages/data-entry/data-entry-list-page.js.map +1 -1
  48. package/dist-client/tsconfig.tsbuildinfo +1 -1
  49. package/dist-server/activities/activity-data-review.js +5 -18
  50. package/dist-server/activities/activity-data-review.js.map +1 -1
  51. package/dist-server/activities/activity-ooc-review.js +52 -13
  52. package/dist-server/activities/activity-ooc-review.js.map +1 -1
  53. package/dist-server/controllers/create-data-sample.js +94 -4
  54. package/dist-server/controllers/create-data-sample.js.map +1 -1
  55. package/dist-server/controllers/index.d.ts +0 -3
  56. package/dist-server/controllers/index.js +0 -3
  57. package/dist-server/controllers/index.js.map +1 -1
  58. package/dist-server/service/data-sample/data-sample-query.d.ts +1 -1
  59. package/dist-server/service/data-sample/data-sample-query.js +3 -3
  60. package/dist-server/service/data-sample/data-sample-query.js.map +1 -1
  61. package/dist-server/service/index.d.ts +1 -1
  62. package/dist-server/tsconfig.tsbuildinfo +1 -1
  63. package/package.json +26 -26
  64. package/server/activities/activity-data-collect.ts +100 -0
  65. package/server/activities/activity-data-review.ts +82 -0
  66. package/server/activities/activity-ooc-resolve.ts +123 -0
  67. package/server/activities/activity-ooc-review.ts +144 -0
  68. package/server/activities/index.ts +11 -0
  69. package/server/controllers/create-data-sample.ts +426 -0
  70. package/server/controllers/data-use-case.ts +98 -0
  71. package/server/controllers/finalize-data-collection.ts +388 -0
  72. package/server/controllers/index.ts +3 -0
  73. package/server/controllers/issue-data-collection-task.ts +70 -0
  74. package/server/controllers/jasper-report.ts +186 -0
  75. package/server/controllers/query-data-summary-by-period.ts +178 -0
  76. package/server/controllers/shiny-report.ts +54 -0
  77. package/server/engine/index.ts +1 -0
  78. package/server/engine/task/create-data-sample.ts +100 -0
  79. package/server/engine/task/index.ts +2 -0
  80. package/server/engine/task/issue-collect-data.ts +45 -0
  81. package/server/index.ts +8 -0
  82. package/server/routes.ts +188 -0
  83. package/server/service/data-archive/data-archive-mutation.ts +273 -0
  84. package/server/service/data-archive/data-archive-query.ts +58 -0
  85. package/server/service/data-archive/data-archive-type.ts +48 -0
  86. package/server/service/data-archive/data-archive.ts +69 -0
  87. package/server/service/data-archive/index.ts +6 -0
  88. package/server/service/data-key-set/data-key-item-type.ts +31 -0
  89. package/server/service/data-key-set/data-key-set-mutation.ts +201 -0
  90. package/server/service/data-key-set/data-key-set-query.ts +68 -0
  91. package/server/service/data-key-set/data-key-set-type.ts +70 -0
  92. package/server/service/data-key-set/data-key-set.ts +86 -0
  93. package/server/service/data-key-set/index.ts +6 -0
  94. package/server/service/data-ooc/data-ooc-mutation.ts +154 -0
  95. package/server/service/data-ooc/data-ooc-query.ts +106 -0
  96. package/server/service/data-ooc/data-ooc-subscription.ts +48 -0
  97. package/server/service/data-ooc/data-ooc-type.ts +71 -0
  98. package/server/service/data-ooc/data-ooc.ts +259 -0
  99. package/server/service/data-ooc/index.ts +7 -0
  100. package/server/service/data-sample/data-sample-mutation.ts +18 -0
  101. package/server/service/data-sample/data-sample-query.ts +215 -0
  102. package/server/service/data-sample/data-sample-type.ts +47 -0
  103. package/server/service/data-sample/data-sample.ts +193 -0
  104. package/server/service/data-sample/index.ts +6 -0
  105. package/server/service/data-sensor/data-sensor-mutation.ts +116 -0
  106. package/server/service/data-sensor/data-sensor-query.ts +76 -0
  107. package/server/service/data-sensor/data-sensor-type.ts +104 -0
  108. package/server/service/data-sensor/data-sensor.ts +126 -0
  109. package/server/service/data-sensor/index.ts +6 -0
  110. package/server/service/data-set/data-item-type.ts +155 -0
  111. package/server/service/data-set/data-set-mutation.ts +552 -0
  112. package/server/service/data-set/data-set-query.ts +461 -0
  113. package/server/service/data-set/data-set-type.ts +204 -0
  114. package/server/service/data-set/data-set.ts +326 -0
  115. package/server/service/data-set/index.ts +6 -0
  116. package/server/service/data-set-history/data-set-history-query.ts +126 -0
  117. package/server/service/data-set-history/data-set-history-type.ts +12 -0
  118. package/server/service/data-set-history/data-set-history.ts +217 -0
  119. package/server/service/data-set-history/event-subscriber.ts +17 -0
  120. package/server/service/data-set-history/index.ts +7 -0
  121. package/server/service/data-spec/data-spec-manager.ts +21 -0
  122. package/server/service/data-spec/data-spec-query.ts +21 -0
  123. package/server/service/data-spec/data-spec.ts +45 -0
  124. package/server/service/data-spec/index.ts +5 -0
  125. package/server/service/data-summary/data-summary-mutation.ts +45 -0
  126. package/server/service/data-summary/data-summary-query.ts +179 -0
  127. package/server/service/data-summary/data-summary-type.ts +86 -0
  128. package/server/service/data-summary/data-summary.ts +170 -0
  129. package/server/service/data-summary/index.ts +7 -0
  130. package/server/service/index.ts +57 -0
  131. package/server/tsconfig.json +10 -0
  132. package/server/utils/config-resolver.ts +29 -0
  133. package/server/utils/index.ts +1 -0
  134. package/translations/en.json +0 -3
  135. package/translations/ja.json +0 -3
  136. package/translations/ko.json +0 -3
  137. package/translations/ms.json +0 -3
  138. package/translations/zh.json +0 -3
  139. package/dist-server/controllers/create-data-ooc.d.ts +0 -4
  140. package/dist-server/controllers/create-data-ooc.js +0 -65
  141. package/dist-server/controllers/create-data-ooc.js.map +0 -1
  142. package/dist-server/controllers/issue-ooc-resolve.d.ts +0 -3
  143. package/dist-server/controllers/issue-ooc-resolve.js +0 -49
  144. package/dist-server/controllers/issue-ooc-resolve.js.map +0 -1
  145. package/dist-server/controllers/issue-ooc-review.d.ts +0 -3
  146. package/dist-server/controllers/issue-ooc-review.js +0 -47
  147. package/dist-server/controllers/issue-ooc-review.js.map +0 -1
@@ -0,0 +1,1078 @@
1
+ import '@material/web/icon/icon.js'
2
+ import '@operato/data-grist'
3
+ import '@operato/context/ox-context-page-toolbar.js'
4
+ import './data-item-list.js'
5
+ import './data-set-importer.js'
6
+ import '../../components/data-entry-form.js'
7
+
8
+ import gql from 'graphql-tag'
9
+ import { css, html } from 'lit'
10
+ import { customElement, query, state } from 'lit/decorators.js'
11
+ import { asyncReplace } from 'lit/directives/async-replace.js'
12
+ import moment from '@operato/moment-timezone-es'
13
+ import { connect } from 'pwa-helpers/connect-mixin'
14
+
15
+ import { DataGrist, FetchOption, getEditor, getRenderer, ImexConfig } from '@operato/data-grist'
16
+ import { OxDataUseCase } from '@operato/dataset'
17
+ import { client } from '@operato/graphql'
18
+ import { i18next, localize } from '@operato/i18n'
19
+ import { notify, openPopup } from '@operato/layout'
20
+ import { PageView, store } from '@operato/shell'
21
+ import { CommonGristStyles, CommonHeaderStyles, ScrollbarStyles } from '@operato/styles'
22
+ import { isMobileDevice, sleep } from '@operato/utils'
23
+
24
+ import { p13n } from '@operato/p13n'
25
+
26
+ const MIN = 60
27
+ const HOUR = 60 * MIN
28
+ const DAY = 24 * HOUR
29
+
30
+ function getSystemTimeZone(): string {
31
+ try {
32
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone
33
+ if (!timeZone) {
34
+ throw new Error('Unable to resolve timeZone')
35
+ }
36
+ return timeZone
37
+ } catch (e) {
38
+ console.warn('Failed to get system timeZone, falling back to UTC.', e)
39
+ return 'UTC'
40
+ }
41
+ }
42
+
43
+ const DEFAULT_TZ = getSystemTimeZone()
44
+ const TIMEZONE_OPTIONS = ['', DEFAULT_TZ, ...moment.tz.names().filter(tz => tz !== DEFAULT_TZ)]
45
+
46
+ const ENTRY_TYPES = [
47
+ { display: '', value: '' },
48
+ { display: 'Generated', value: 'generated' },
49
+ { display: 'Board', value: 'board' },
50
+ { display: 'CustomElement', value: 'custom-element' },
51
+ { display: 'Page', value: 'page' },
52
+ { display: 'External URL', value: 'external' }
53
+ ]
54
+
55
+ const MONITOR_TYPES = [
56
+ { display: '', value: '' },
57
+ { display: 'Generated', value: 'generated' },
58
+ { display: 'Board', value: 'board' },
59
+ { display: 'CustomElement', value: 'custom-element' },
60
+ { display: 'Page', value: 'page' },
61
+ { display: 'External URL', value: 'external' }
62
+ ]
63
+
64
+ const REPORT_TYPES = [
65
+ { display: '', value: '' },
66
+ { display: 'Generated', value: 'generated' },
67
+ { display: 'Embed', value: 'embed' },
68
+ { display: 'CustomElement', value: 'custom-element' },
69
+ { display: 'Page', value: 'page' },
70
+ { display: 'External URL', value: 'external' },
71
+ { display: 'Jasper', value: 'jasper' },
72
+ { display: 'Shiny', value: 'shiny' }
73
+ ]
74
+
75
+ const SUMMARY_PERIOD_TYPES = [
76
+ { display: '', value: '' },
77
+ { display: 'Hour', value: 'hour' },
78
+ { display: 'WorkShift', value: 'work-shift' },
79
+ { display: 'WorkDate', value: 'work-date' },
80
+ { display: 'Day', value: 'day' }
81
+ ]
82
+
83
+ const USECASE_OPTIONS = () => {
84
+ return ['', ...OxDataUseCase.getUseCaseNames()].map(name => {
85
+ return {
86
+ display: name,
87
+ value: name
88
+ }
89
+ })
90
+ }
91
+
92
+ @customElement('data-set-list-page')
93
+ export class DataSetListPage extends connect(store)(p13n(localize(i18next)(PageView))) {
94
+ static styles = [
95
+ ScrollbarStyles,
96
+ CommonGristStyles,
97
+ CommonHeaderStyles,
98
+ css`
99
+ :host {
100
+ display: flex;
101
+
102
+ width: 100%;
103
+
104
+ --grid-record-emphasized-background-color: #8b0000;
105
+ --grid-record-emphasized-color: #ff6b6b;
106
+
107
+ --grid-header-padding: 2px 0 2px 9px;
108
+ }
109
+
110
+ ox-grist {
111
+ overflow-y: auto;
112
+ flex: 1;
113
+ }
114
+
115
+ .header {
116
+ grid-template-areas: 'filters actions';
117
+ }
118
+ `
119
+ ]
120
+
121
+ @state() gristConfig: any
122
+
123
+ @state() mode: 'CARD' | 'GRID' | 'LIST' = isMobileDevice() ? 'CARD' : 'GRID'
124
+
125
+ @query('ox-grist') private grist!: DataGrist
126
+
127
+ get context() {
128
+ return {
129
+ title: i18next.t('title.data-set list'),
130
+ search: {
131
+ handler: (search: string) => {
132
+ this.grist.searchText = search
133
+ },
134
+ value: this.grist?.searchText || ''
135
+ },
136
+ filter: {
137
+ handler: () => {
138
+ this.grist.toggleHeadroom()
139
+ }
140
+ },
141
+ help: 'dataset/data-set',
142
+ exportable: {
143
+ name: i18next.t('title.data-set list'),
144
+ data: this.exportHandler.bind(this)
145
+ },
146
+ importable: {
147
+ handler: this.importHandler.bind(this)
148
+ },
149
+ actions: [
150
+ {
151
+ title: i18next.t('button.save'),
152
+ action: this._updateDataSet.bind(this),
153
+ icon: 'save'
154
+ },
155
+ {
156
+ title: i18next.t('button.copy'),
157
+ action: this._copyDataSet.bind(this),
158
+ icon: 'content_copy'
159
+ },
160
+ {
161
+ title: i18next.t('button.delete'),
162
+ action: this._deleteDataSet.bind(this),
163
+ icon: 'delete',
164
+ emphasis: {
165
+ danger: true
166
+ }
167
+ }
168
+ ],
169
+ toolbar: false
170
+ }
171
+ }
172
+
173
+ render() {
174
+ const mode = this.mode || (isMobileDevice() ? 'CARD' : 'GRID')
175
+
176
+ return html`
177
+ <ox-grist
178
+ .mode=${mode}
179
+ .config=${this.gristConfig}
180
+ .personalConfigProvider=${this.getPagePreferenceProvider('ox-grist')!}
181
+ .fetchHandler=${this.fetchHandler.bind(this)}
182
+ ?url-params-sensitive=${this.active}
183
+ >
184
+ <div slot="headroom" class="header">
185
+ <div class="filters">
186
+ <ox-filters-form autofocus without-search></ox-filters-form>
187
+ </div>
188
+
189
+ <ox-context-page-toolbar class="actions" .context=${this.context}> </ox-context-page-toolbar>
190
+ </div>
191
+
192
+ <ox-grist-personalizer slot="setting"></ox-grist-personalizer>
193
+ </ox-grist>
194
+ `
195
+ }
196
+
197
+ async pageInitialized(lifecycle) {
198
+ this.gristConfig = {
199
+ list: {
200
+ fields: ['name', 'description'],
201
+ details: ['schedule', 'active', 'tag']
202
+ },
203
+ columns: [
204
+ { type: 'gutter', gutterName: 'sequence', fixed: true },
205
+ { type: 'gutter', gutterName: 'row-selector', multiple: true, fixed: true },
206
+ {
207
+ type: 'gutter',
208
+ gutterName: 'button',
209
+ icon: record => (!record ? 'calendar_add_on' : !record.id || !record.scheduleId ? '' : 'event_available'),
210
+ title: i18next.t('button.schedule-task'),
211
+ handlers: {
212
+ click: (columns, data, column, record, rowIndex) => {
213
+ if (!record || !record.id || (!record.scheduleId && !record.schedule)) {
214
+ return
215
+ }
216
+ if (record.scheduleId) {
217
+ this.stopDataCollectionSchedule(record)
218
+ } else {
219
+ this.startDataCollectionSchedule(record)
220
+ }
221
+ }
222
+ }
223
+ },
224
+ {
225
+ type: 'gutter',
226
+ gutterName: 'button',
227
+ title: record =>
228
+ !record ? i18next.t('button.edit-model') : !record.id ? '' : i18next.t('button.edit-model'),
229
+ icon: record => (!record ? 'reorder' : !record.id ? '' : 'reorder'),
230
+ iconOnly: false,
231
+ width: 96,
232
+ fixed: true,
233
+ handlers: {
234
+ click: (columns, data, column, record, rowIndex) => {
235
+ if (!record.id) return
236
+ const popup = openPopup(html` <data-item-list .dataSet=${record}></data-item-list> `, {
237
+ backdrop: true,
238
+ help: 'dataset/ui/data-item-list',
239
+ size: 'large',
240
+ title: i18next.t('title.data-item list')
241
+ })
242
+ popup.onclosed = () => {
243
+ this.grist.fetch()
244
+ }
245
+ }
246
+ }
247
+ },
248
+ {
249
+ type: 'gutter',
250
+ gutterName: 'button',
251
+ icon: record => (!record ? 'fact_check' : !record.id ? '' : 'fact_check'),
252
+ iconOnly: false,
253
+ width: 96,
254
+ fixed: true,
255
+ title: record =>
256
+ !record ? i18next.t('button.enter-data') : !record.id ? '' : i18next.t('button.enter-data'),
257
+ handlers: {
258
+ click: (columns, data, column, record, rowIndex) => {
259
+ openPopup(
260
+ html` <data-entry-form .dataSet=${record} style="background-color: white;"></data-entry-form> `,
261
+ {
262
+ backdrop: true,
263
+ size: 'large',
264
+ title: i18next.t('title.data-entry-form')
265
+ }
266
+ )
267
+ }
268
+ }
269
+ },
270
+ {
271
+ type: 'string',
272
+ name: 'name',
273
+ header: i18next.t('field.name'),
274
+ record: {
275
+ editable: true
276
+ },
277
+ filter: 'search',
278
+ sortable: true,
279
+ fixed: true,
280
+ width: 150,
281
+ imex: {
282
+ width: 25,
283
+ header: i18next.t('field.name'),
284
+ type: 'string',
285
+ key: 'name'
286
+ }
287
+ },
288
+ {
289
+ type: 'string',
290
+ name: 'description',
291
+ header: i18next.t('field.description'),
292
+ record: {
293
+ editable: true
294
+ },
295
+ filter: 'search',
296
+ width: 200,
297
+ imex: {
298
+ width: 33,
299
+ header: i18next.t('field.description'),
300
+ type: 'string',
301
+ key: 'description'
302
+ }
303
+ },
304
+ {
305
+ type: 'varname',
306
+ name: 'tag',
307
+ header: i18next.t('field.tag'),
308
+ record: {
309
+ editable: true
310
+ },
311
+ filter: 'search',
312
+ width: 120,
313
+ imex: {
314
+ width: 25,
315
+ header: i18next.t('field.tag'),
316
+ type: 'string',
317
+ key: 'tag'
318
+ }
319
+ },
320
+ {
321
+ type: 'checkbox',
322
+ name: 'active',
323
+ label: true,
324
+ header: i18next.t('field.active'),
325
+ record: {
326
+ editable: true
327
+ },
328
+ filter: true,
329
+ sortable: true,
330
+ width: 60,
331
+ imex: {
332
+ width: 10,
333
+ header: i18next.t('field.active'),
334
+ type: 'checkbox',
335
+ key: 'active'
336
+ }
337
+ },
338
+ {
339
+ type: 'select',
340
+ name: 'type',
341
+ label: true,
342
+ header: i18next.t('field.type'),
343
+ record: {
344
+ editable: true,
345
+ options: [
346
+ {},
347
+ {
348
+ display: i18next.t('text.manually collected'),
349
+ value: 'manual'
350
+ },
351
+ {
352
+ display: i18next.t('text.automatically collected'),
353
+ value: 'automatic'
354
+ }
355
+ ]
356
+ },
357
+ sortable: true,
358
+ filter: true,
359
+ width: 60,
360
+ imex: {
361
+ width: 10,
362
+ header: i18next.t('field.type'),
363
+ type: 'select',
364
+ key: 'type'
365
+ }
366
+ },
367
+ {
368
+ type: 'select',
369
+ name: 'useCase',
370
+ label: true,
371
+ header: i18next.t('field.use-case'),
372
+ record: {
373
+ editable: true,
374
+ options: USECASE_OPTIONS
375
+ },
376
+ sortable: true,
377
+ filter: true,
378
+ width: 80
379
+ },
380
+ {
381
+ type: 'resource-object',
382
+ name: 'dataKeySet',
383
+ header: i18next.t('field.data-key-set'),
384
+ record: {
385
+ editable: true,
386
+ options: {
387
+ queryName: 'dataKeySets'
388
+ }
389
+ },
390
+ filter: false,
391
+ width: 120
392
+ },
393
+ {
394
+ type: 'partition-keys',
395
+ name: 'partitionKeys',
396
+ header: i18next.t('field.partition-keys'),
397
+ record: {
398
+ editable: true,
399
+ options: {
400
+ objectified: true /* transfered as a object type */
401
+ }
402
+ },
403
+ width: 80
404
+ },
405
+ {
406
+ type: 'select',
407
+ name: 'timezone',
408
+ header: i18next.t('field.timezone'),
409
+ record: {
410
+ editable: true,
411
+ options: TIMEZONE_OPTIONS
412
+ },
413
+ width: 120,
414
+ imex: {
415
+ width: 13,
416
+ header: i18next.t('field.timezone'),
417
+ type: 'select',
418
+ key: 'timezone'
419
+ }
420
+ },
421
+ {
422
+ type: 'crontab',
423
+ name: 'schedule',
424
+ label: true,
425
+ header: i18next.t('field.schedule'),
426
+ record: {
427
+ editable: true,
428
+ options: {
429
+ objectified: true
430
+ }
431
+ },
432
+ width: 80,
433
+ imex: {
434
+ width: 13,
435
+ header: i18next.t('field.schedule'),
436
+ type: 'string',
437
+ key: 'schedule'
438
+ }
439
+ },
440
+ {
441
+ type: 'select',
442
+ name: 'summaryPeriod',
443
+ label: true,
444
+ header: i18next.t('field.summary-period'),
445
+ record: {
446
+ editable: true,
447
+ options: SUMMARY_PERIOD_TYPES
448
+ },
449
+ width: 80
450
+ },
451
+ {
452
+ type: 'string',
453
+ name: 'nextSummarySchedule',
454
+ header: i18next.t('field.next-summary-schedule'),
455
+ record: {
456
+ editable: false,
457
+ renderer: (value, column, record, rowIndex, field) => {
458
+ return html` <span>${asyncReplace(this.summaryScheduleTimer(record.dueAt))}</span>`
459
+ }
460
+ },
461
+ sortable: false,
462
+ width: 140
463
+ },
464
+ {
465
+ type: 'duration',
466
+ name: 'timeLimit',
467
+ header: i18next.t('field.time-limit'),
468
+ record: {
469
+ editable: true
470
+ },
471
+ width: 80
472
+ },
473
+ {
474
+ type: 'resource-object',
475
+ name: 'normalScenario',
476
+ header: {
477
+ renderer: i18next.t('field.normal-scenario'),
478
+ group: i18next.t('text.scenario')
479
+ },
480
+ record: {
481
+ editable: true,
482
+ options: {
483
+ title: i18next.t('title.lookup scenario'),
484
+ queryName: 'scenarios',
485
+ columns: [
486
+ { name: 'id', hidden: true },
487
+ { name: 'name', header: i18next.t('field.name'), filter: 'search' },
488
+ { name: 'description', header: i18next.t('field.description'), filter: 'search' }
489
+ ],
490
+ list: { fields: ['name', 'description'] }
491
+ }
492
+ },
493
+ sortable: true,
494
+ filter: false,
495
+ width: 120
496
+ },
497
+ {
498
+ type: 'resource-object',
499
+ name: 'outlierScenario',
500
+ header: {
501
+ renderer: i18next.t('field.outlier-scenario'),
502
+ group: i18next.t('text.scenario')
503
+ },
504
+ record: {
505
+ editable: true,
506
+ options: {
507
+ title: i18next.t('title.lookup scenario'),
508
+ queryName: 'scenarios',
509
+ columns: [
510
+ { name: 'id', hidden: true },
511
+ { name: 'name', header: i18next.t('field.name'), filter: 'search' },
512
+ { name: 'description', header: i18next.t('field.description'), filter: 'search' }
513
+ ],
514
+ list: { fields: ['name', 'description'] }
515
+ }
516
+ },
517
+ sortable: true,
518
+ filter: false,
519
+ width: 120
520
+ },
521
+ {
522
+ type: 'resource-object',
523
+ name: 'supervisoryRole',
524
+ header: {
525
+ renderer: i18next.t('field.supervisory-role'),
526
+ group: i18next.t('field.role')
527
+ },
528
+ record: {
529
+ editable: true,
530
+ options: {
531
+ title: i18next.t('title.lookup role'),
532
+ queryName: 'roles'
533
+ }
534
+ },
535
+ filter: false,
536
+ width: 120
537
+ },
538
+ {
539
+ type: 'resource-object',
540
+ name: 'entryRole',
541
+ header: {
542
+ renderer: i18next.t('field.entry-role'),
543
+ group: i18next.t('field.role')
544
+ },
545
+ record: {
546
+ editable: true,
547
+ options: {
548
+ title: i18next.t('title.lookup role'),
549
+ queryName: 'roles'
550
+ }
551
+ },
552
+ width: 120
553
+ },
554
+ {
555
+ type: 'resource-object',
556
+ name: 'resolverRole',
557
+ header: {
558
+ renderer: i18next.t('field.resolver-role'),
559
+ group: i18next.t('field.role')
560
+ },
561
+ record: {
562
+ editable: true,
563
+ options: {
564
+ title: i18next.t('title.lookup role'),
565
+ queryName: 'roles'
566
+ }
567
+ },
568
+ filter: false,
569
+ width: 120
570
+ },
571
+ {
572
+ type: 'approval-line',
573
+ name: 'reviewApprovalLine',
574
+ header: {
575
+ renderer: i18next.t('field.review-approval-line'),
576
+ group: i18next.t('field.role')
577
+ },
578
+ record: {
579
+ editable: true
580
+ },
581
+ width: 80
582
+ },
583
+ {
584
+ type: 'approval-line',
585
+ name: 'outlierApprovalLine',
586
+ header: {
587
+ renderer: i18next.t('field.outlier-approval-line'),
588
+ group: i18next.t('field.role')
589
+ },
590
+ record: {
591
+ editable: true
592
+ },
593
+ width: 80
594
+ },
595
+ {
596
+ type: 'checkbox',
597
+ name: 'requiresReview',
598
+ label: true,
599
+ header: {
600
+ renderer: i18next.t('field.requires-review'),
601
+ group: i18next.t('field.role')
602
+ },
603
+ record: {
604
+ editable: true
605
+ },
606
+ filter: true,
607
+ width: 60,
608
+ imex: {
609
+ width: 10,
610
+ header: i18next.t('field.requires-review'),
611
+ type: 'checkbox',
612
+ key: 'requiresReview'
613
+ }
614
+ },
615
+ {
616
+ type: 'select',
617
+ name: 'entryType',
618
+ label: true,
619
+ header: {
620
+ renderer: i18next.t('field.entry-type'),
621
+ group: i18next.t('field.view')
622
+ },
623
+ record: {
624
+ editable: true,
625
+ options: ENTRY_TYPES
626
+ },
627
+ width: 80
628
+ },
629
+ {
630
+ type: 'string',
631
+ name: 'entryView',
632
+ header: {
633
+ renderer: i18next.t('field.entry-view'),
634
+ group: i18next.t('field.view')
635
+ },
636
+ record: {
637
+ editable: true,
638
+ editor: function (value, column, record, rowIndex, field) {
639
+ var type = record.entryType !== 'board' ? 'string' : 'board'
640
+ return getEditor(type)(value, column, record, rowIndex, field)
641
+ },
642
+ renderer: function (value, column, record, rowIndex, field) {
643
+ var type = record.entryType !== 'board' ? 'string' : 'board'
644
+ return getRenderer(type)(value, column, record, rowIndex, field)
645
+ }
646
+ },
647
+ width: 140
648
+ },
649
+ {
650
+ type: 'select',
651
+ name: 'monitorType',
652
+ label: true,
653
+ header: {
654
+ renderer: i18next.t('field.monitor-type'),
655
+ group: i18next.t('field.view')
656
+ },
657
+ record: {
658
+ editable: true,
659
+ options: MONITOR_TYPES
660
+ },
661
+ width: 80
662
+ },
663
+ {
664
+ type: 'string',
665
+ name: 'monitorView',
666
+ header: {
667
+ renderer: i18next.t('field.monitor-view'),
668
+ group: i18next.t('field.view')
669
+ },
670
+ record: {
671
+ editable: true,
672
+ editor: function (value, column, record, rowIndex, field) {
673
+ var type = record.monitorType !== 'board' ? 'string' : 'board'
674
+ return getEditor(type)(value, column, record, rowIndex, field)
675
+ },
676
+ renderer: function (value, column, record, rowIndex, field) {
677
+ var type = record.monitorType !== 'board' ? 'string' : 'board'
678
+ return getRenderer(type)(value, column, record, rowIndex, field)
679
+ }
680
+ },
681
+ width: 140
682
+ },
683
+ {
684
+ type: 'select',
685
+ name: 'reportType',
686
+ label: true,
687
+ header: {
688
+ renderer: i18next.t('field.report-type'),
689
+ group: i18next.t('field.view')
690
+ },
691
+ record: {
692
+ editable: true,
693
+ options: REPORT_TYPES
694
+ },
695
+ width: 80
696
+ },
697
+ {
698
+ type: 'string',
699
+ name: 'reportView',
700
+ header: {
701
+ renderer: i18next.t('field.report-view'),
702
+ group: i18next.t('field.view')
703
+ },
704
+ record: {
705
+ editable: true,
706
+ editor: function (value, column, record, rowIndex, field) {
707
+ var type = record.reportType !== 'custom' ? 'string' : 'script'
708
+ return getEditor(type)(value, column, record, rowIndex, field)
709
+ },
710
+ renderer: function (value, column, record, rowIndex, field) {
711
+ var type = record.reportType !== 'custom' ? 'string' : 'string'
712
+ return getRenderer(type)(value, column, record, rowIndex, field)
713
+ }
714
+ },
715
+ width: 140
716
+ },
717
+ {
718
+ type: 'file',
719
+ name: 'reportTemplate',
720
+ header: {
721
+ renderer: i18next.t('field.report-template'),
722
+ group: i18next.t('field.view')
723
+ },
724
+ record: {
725
+ editable: true
726
+ },
727
+ width: 80
728
+ },
729
+ {
730
+ type: 'resource-object',
731
+ name: 'updater',
732
+ header: i18next.t('field.updater'),
733
+ record: {
734
+ editable: false
735
+ },
736
+ width: 120
737
+ },
738
+ {
739
+ type: 'datetime',
740
+ name: 'updatedAt',
741
+ header: i18next.t('field.updated_at'),
742
+ record: {
743
+ editable: false
744
+ },
745
+ sortable: true,
746
+ width: 180
747
+ }
748
+ ],
749
+ rows: {
750
+ selectable: {
751
+ multiple: true
752
+ }
753
+ },
754
+ sorters: [
755
+ {
756
+ name: 'name'
757
+ }
758
+ ]
759
+ }
760
+ }
761
+
762
+ async fetchHandler({ page, limit, sortings = [], filters = [] }: FetchOption) {
763
+ const response = await client.query({
764
+ query: gql`
765
+ query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
766
+ responses: dataSets(filters: $filters, pagination: $pagination, sortings: $sortings) {
767
+ items {
768
+ id
769
+ name
770
+ description
771
+ tag
772
+ partitionKeys
773
+ active
774
+ type
775
+ useCase
776
+ schedule
777
+ timezone
778
+ scheduleId
779
+ summaryPeriod
780
+ summaryScheduleId
781
+ timeLimit
782
+ dataKeySet {
783
+ id
784
+ name
785
+ }
786
+ entryRole {
787
+ id
788
+ name
789
+ }
790
+ supervisoryRole {
791
+ id
792
+ name
793
+ }
794
+ resolverRole {
795
+ id
796
+ name
797
+ }
798
+ normalScenario {
799
+ id
800
+ name
801
+ }
802
+ outlierScenario {
803
+ id
804
+ name
805
+ }
806
+ reviewApprovalLine {
807
+ type
808
+ value
809
+ approver {
810
+ id
811
+ name
812
+ description
813
+ controlNo
814
+ }
815
+ }
816
+ outlierApprovalLine {
817
+ type
818
+ value
819
+ approver {
820
+ id
821
+ name
822
+ description
823
+ controlNo
824
+ }
825
+ }
826
+ requiresReview
827
+ entryType
828
+ entryView
829
+ monitorType
830
+ monitorView
831
+ reportType
832
+ reportView
833
+ reportTemplate
834
+ updater {
835
+ id
836
+ name
837
+ }
838
+ updatedAt
839
+ dataItems {
840
+ name
841
+ description
842
+ active
843
+ hidden
844
+ tag
845
+ group
846
+ type
847
+ unit
848
+ options
849
+ quota
850
+ spec
851
+ stat
852
+ agg
853
+ }
854
+ }
855
+ total
856
+ }
857
+ }
858
+ `,
859
+ variables: {
860
+ filters,
861
+ pagination: { page, limit },
862
+ sortings
863
+ }
864
+ })
865
+
866
+ return {
867
+ total: response.data.responses.total || 0,
868
+ records: response.data.responses.items || []
869
+ }
870
+ }
871
+
872
+ async _deleteDataSet() {
873
+ if (confirm(i18next.t('text.sure_to_x', { x: i18next.t('text.delete') }))) {
874
+ const ids = this.grist.selected.map(record => record.id)
875
+ if (ids && ids.length > 0) {
876
+ const response = await client.mutate({
877
+ mutation: gql`
878
+ mutation ($ids: [String!]!) {
879
+ deleteDataSets(ids: $ids)
880
+ }
881
+ `,
882
+ variables: {
883
+ ids
884
+ }
885
+ })
886
+
887
+ if (!response.errors) {
888
+ this.grist.fetch()
889
+ notify({
890
+ message: i18next.t('text.info_x_successfully', { x: i18next.t('text.delete') })
891
+ })
892
+ }
893
+ }
894
+ }
895
+ }
896
+
897
+ async _copyDataSet() {
898
+ var selected = this.grist.selected
899
+ if (selected.length == 0) return
900
+
901
+ if (!confirm(i18next.t('text.sure_to_x', { x: i18next.t('text.copy') }))) return
902
+ var response = await client.mutate({
903
+ mutation: gql`
904
+ mutation ($ids: [String!]!) {
905
+ copyDataSets(ids: $ids) {
906
+ id
907
+ }
908
+ }
909
+ `,
910
+ variables: {
911
+ ids: selected.map(r => r.id)
912
+ }
913
+ })
914
+
915
+ if (!response.errors) {
916
+ this.grist.fetch()
917
+ }
918
+ }
919
+
920
+ async _updateDataSet() {
921
+ let patches = this.grist.dirtyRecords
922
+ if (patches && patches.length) {
923
+ patches = patches.map(patch => {
924
+ let patchField: any = patch.id ? { id: patch.id } : {}
925
+ const dirtyFields = patch.__dirtyfields__
926
+ for (let key in dirtyFields) {
927
+ patchField[key] = dirtyFields[key].after
928
+ }
929
+ if (patchField['reportTemplate'] instanceof FileList) {
930
+ patchField['reportTemplate'] = patchField['reportTemplate'][0]
931
+ }
932
+ patchField.cuFlag = patch.__dirty__
933
+
934
+ return patchField
935
+ })
936
+
937
+ const response = await client.mutate({
938
+ mutation: gql`
939
+ mutation ($patches: [DataSetPatch!]!) {
940
+ updateMultipleDataSet(patches: $patches) {
941
+ name
942
+ }
943
+ }
944
+ `,
945
+ variables: {
946
+ patches
947
+ },
948
+ context: {
949
+ hasUpload: true
950
+ }
951
+ })
952
+
953
+ if (!response.errors) {
954
+ this.grist.fetch()
955
+ }
956
+ }
957
+ }
958
+
959
+ async startDataCollectionSchedule(record) {
960
+ var response = await client.mutate({
961
+ mutation: gql`
962
+ mutation ($dataSetId: String!) {
963
+ startDataCollectionSchedule(dataSetId: $dataSetId) {
964
+ scheduleId
965
+ }
966
+ }
967
+ `,
968
+ variables: {
969
+ dataSetId: record.id
970
+ }
971
+ })
972
+
973
+ const scheduleId = response.data.startDataCollectionSchedule.scheduleId
974
+ record.scheduleId = scheduleId
975
+
976
+ notify({
977
+ level: 'info',
978
+ message: `${record.scheduleId ? 'success' : 'fail'} to start data collection schedule : ${record.name}`
979
+ })
980
+
981
+ this.grist.fetch()
982
+ }
983
+
984
+ async stopDataCollectionSchedule(record) {
985
+ var response = await client.mutate({
986
+ mutation: gql`
987
+ mutation ($dataSetId: String!) {
988
+ stopDataCollectionSchedule(dataSetId: $dataSetId) {
989
+ scheduleId
990
+ }
991
+ }
992
+ `,
993
+ variables: {
994
+ dataSetId: record.id
995
+ }
996
+ })
997
+
998
+ if (!response.errors) {
999
+ notify({
1000
+ level: 'info',
1001
+ message: `success to stop data collection schedule : ${record.name}`
1002
+ })
1003
+ } else {
1004
+ notify({
1005
+ level: 'error',
1006
+ message: `${response.errors.map(error => error.message).join('\n')}`
1007
+ })
1008
+ }
1009
+
1010
+ this.grist.fetch()
1011
+ }
1012
+
1013
+ async exportHandler() {
1014
+ var headerSetting = this.grist._config.columns
1015
+ .filter(column => column.type !== 'gutter' && column.record !== undefined && column.imex !== undefined)
1016
+ .map(column => {
1017
+ return column.imex
1018
+ })
1019
+
1020
+ let records = this.grist.data.records
1021
+
1022
+ var data = records.map(item => {
1023
+ return {
1024
+ id: item.id,
1025
+ ...this.grist._config.columns
1026
+ .filter(column => column.type !== 'gutter' && column.record !== undefined && column.imex !== undefined)
1027
+ .reduce((record, column) => {
1028
+ var imexKey = (column.imex as ImexConfig)!.key
1029
+ record[imexKey] = imexKey
1030
+ .split('.')
1031
+ .reduce((obj, key) => (obj && obj[key] !== 'undefined' ? obj[key] : undefined), item)
1032
+ return record
1033
+ }, {})
1034
+ }
1035
+ })
1036
+ return { header: headerSetting, data: data }
1037
+ }
1038
+
1039
+ async importHandler(records) {
1040
+ openPopup(
1041
+ html`
1042
+ <data-set-importer
1043
+ .dataSets=${records}
1044
+ @imported=${() => {
1045
+ history.back()
1046
+ this.grist.fetch()
1047
+ }}
1048
+ ></data-set-importer>
1049
+ `,
1050
+ {
1051
+ backdrop: true,
1052
+ size: 'large',
1053
+ title: i18next.t('title.import data-set')
1054
+ }
1055
+ )
1056
+ }
1057
+
1058
+ async *summaryScheduleTimer(exp) {
1059
+ while (exp && this.active) {
1060
+ var secs = Math.round((Number(exp) - Date.now()) / 1000)
1061
+ var positive = secs >= 0
1062
+
1063
+ secs = Math.abs(secs)
1064
+ const days = Math.floor(secs / DAY)
1065
+ secs -= days * DAY
1066
+ const hours = Math.floor(secs / HOUR)
1067
+ secs -= hours * HOUR
1068
+ const minutes = Math.floor(secs / MIN)
1069
+ const seconds = secs - minutes * MIN
1070
+
1071
+ yield `${positive ? '' : '- '}${days ? `${days}${i18next.t('label.days')} ` : ''}${hours ? `${hours}${i18next.t('label.hours')} ` : ''}${
1072
+ minutes ? `${minutes}${i18next.t('label.minutes')} ` : ''
1073
+ }`
1074
+
1075
+ await sleep(60 * 1000)
1076
+ }
1077
+ }
1078
+ }