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

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 (134) 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 +278 -0
  4. package/client/activities/activity-data-review-view.ts +226 -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.js +19 -10
  44. package/dist-client/activities/activity-data-review-edit.js.map +1 -1
  45. package/dist-client/activities/activity-data-review-view.js +80 -0
  46. package/dist-client/activities/activity-data-review-view.js.map +1 -1
  47. package/dist-client/pages/data-entry/data-entry-list-page.js +2 -2
  48. package/dist-client/pages/data-entry/data-entry-list-page.js.map +1 -1
  49. package/dist-client/tsconfig.tsbuildinfo +1 -1
  50. package/dist-server/controllers/create-data-ooc.js +2 -0
  51. package/dist-server/controllers/create-data-ooc.js.map +1 -1
  52. package/dist-server/service/data-archive/index.d.ts +1 -1
  53. package/dist-server/service/data-ooc/index.d.ts +1 -1
  54. package/dist-server/service/data-sample/data-sample-query.d.ts +1 -1
  55. package/dist-server/service/data-sample/data-sample-query.js +3 -3
  56. package/dist-server/service/data-sample/data-sample-query.js.map +1 -1
  57. package/dist-server/service/data-sample/index.d.ts +1 -1
  58. package/dist-server/service/data-set/index.d.ts +1 -1
  59. package/dist-server/service/index.d.ts +2 -2
  60. package/dist-server/tsconfig.tsbuildinfo +1 -1
  61. package/package.json +26 -26
  62. package/server/activities/activity-data-collect.ts +100 -0
  63. package/server/activities/activity-data-review.ts +109 -0
  64. package/server/activities/activity-ooc-resolve.ts +123 -0
  65. package/server/activities/activity-ooc-review.ts +95 -0
  66. package/server/activities/index.ts +11 -0
  67. package/server/controllers/create-data-ooc.ts +80 -0
  68. package/server/controllers/create-data-sample.ts +323 -0
  69. package/server/controllers/data-use-case.ts +98 -0
  70. package/server/controllers/finalize-data-collection.ts +388 -0
  71. package/server/controllers/index.ts +6 -0
  72. package/server/controllers/issue-data-collection-task.ts +70 -0
  73. package/server/controllers/issue-ooc-resolve.ts +58 -0
  74. package/server/controllers/issue-ooc-review.ts +52 -0
  75. package/server/controllers/jasper-report.ts +186 -0
  76. package/server/controllers/query-data-summary-by-period.ts +178 -0
  77. package/server/controllers/shiny-report.ts +54 -0
  78. package/server/engine/index.ts +1 -0
  79. package/server/engine/task/create-data-sample.ts +100 -0
  80. package/server/engine/task/index.ts +2 -0
  81. package/server/engine/task/issue-collect-data.ts +45 -0
  82. package/server/index.ts +8 -0
  83. package/server/routes.ts +188 -0
  84. package/server/service/data-archive/data-archive-mutation.ts +273 -0
  85. package/server/service/data-archive/data-archive-query.ts +58 -0
  86. package/server/service/data-archive/data-archive-type.ts +48 -0
  87. package/server/service/data-archive/data-archive.ts +69 -0
  88. package/server/service/data-archive/index.ts +6 -0
  89. package/server/service/data-key-set/data-key-item-type.ts +31 -0
  90. package/server/service/data-key-set/data-key-set-mutation.ts +201 -0
  91. package/server/service/data-key-set/data-key-set-query.ts +68 -0
  92. package/server/service/data-key-set/data-key-set-type.ts +70 -0
  93. package/server/service/data-key-set/data-key-set.ts +86 -0
  94. package/server/service/data-key-set/index.ts +6 -0
  95. package/server/service/data-ooc/data-ooc-mutation.ts +154 -0
  96. package/server/service/data-ooc/data-ooc-query.ts +106 -0
  97. package/server/service/data-ooc/data-ooc-subscription.ts +48 -0
  98. package/server/service/data-ooc/data-ooc-type.ts +71 -0
  99. package/server/service/data-ooc/data-ooc.ts +259 -0
  100. package/server/service/data-ooc/index.ts +7 -0
  101. package/server/service/data-sample/data-sample-mutation.ts +18 -0
  102. package/server/service/data-sample/data-sample-query.ts +215 -0
  103. package/server/service/data-sample/data-sample-type.ts +47 -0
  104. package/server/service/data-sample/data-sample.ts +193 -0
  105. package/server/service/data-sample/index.ts +6 -0
  106. package/server/service/data-sensor/data-sensor-mutation.ts +116 -0
  107. package/server/service/data-sensor/data-sensor-query.ts +76 -0
  108. package/server/service/data-sensor/data-sensor-type.ts +104 -0
  109. package/server/service/data-sensor/data-sensor.ts +126 -0
  110. package/server/service/data-sensor/index.ts +6 -0
  111. package/server/service/data-set/data-item-type.ts +155 -0
  112. package/server/service/data-set/data-set-mutation.ts +552 -0
  113. package/server/service/data-set/data-set-query.ts +461 -0
  114. package/server/service/data-set/data-set-type.ts +204 -0
  115. package/server/service/data-set/data-set.ts +326 -0
  116. package/server/service/data-set/index.ts +6 -0
  117. package/server/service/data-set-history/data-set-history-query.ts +126 -0
  118. package/server/service/data-set-history/data-set-history-type.ts +12 -0
  119. package/server/service/data-set-history/data-set-history.ts +217 -0
  120. package/server/service/data-set-history/event-subscriber.ts +17 -0
  121. package/server/service/data-set-history/index.ts +7 -0
  122. package/server/service/data-spec/data-spec-manager.ts +21 -0
  123. package/server/service/data-spec/data-spec-query.ts +21 -0
  124. package/server/service/data-spec/data-spec.ts +45 -0
  125. package/server/service/data-spec/index.ts +5 -0
  126. package/server/service/data-summary/data-summary-mutation.ts +45 -0
  127. package/server/service/data-summary/data-summary-query.ts +179 -0
  128. package/server/service/data-summary/data-summary-type.ts +86 -0
  129. package/server/service/data-summary/data-summary.ts +170 -0
  130. package/server/service/data-summary/index.ts +7 -0
  131. package/server/service/index.ts +57 -0
  132. package/server/tsconfig.json +10 -0
  133. package/server/utils/config-resolver.ts +29 -0
  134. package/server/utils/index.ts +1 -0
@@ -0,0 +1,323 @@
1
+ import moment from 'moment-timezone'
2
+ import { In } from 'typeorm'
3
+
4
+ import { Attachment, createAttachment } from '@things-factory/attachment-base'
5
+ import { Role } from '@things-factory/auth-base'
6
+ import { logger } from '@things-factory/env'
7
+ import { getRedirectSubdomainPath, pubsub } from '@things-factory/shell'
8
+ import { getWorkDateAndShift } from '@things-factory/work-shift'
9
+ import { Scenario, publishData } from '@things-factory/integration-base'
10
+ import { Activity } from '@things-factory/worklist'
11
+ import { issue } from '@things-factory/worklist/dist-server/controllers/activity-instance/issue'
12
+
13
+ import { DataSample } from '../service/data-sample/data-sample'
14
+ import { NewDataSample } from '../service/data-sample/data-sample-type'
15
+ import { DataSet } from '../service/data-set/data-set'
16
+ import { createDataOoc } from './create-data-ooc'
17
+ import { issueOocReview } from './issue-ooc-review'
18
+ import { DataUseCase } from './data-use-case'
19
+
20
+ // See README.md at ## Data Samples
21
+ process.env.TZ = 'UTC'
22
+
23
+ const fillDataKeys = (dataKeySet, data = {}) => {
24
+ const keys = dataKeySet?.dataKeyItems || []
25
+ return keys.reduce((sum, key, index) => {
26
+ const value = data[key.dataKey]
27
+ if (value != null) {
28
+ sum[`key0${index + 1}`] = value instanceof Array ? value[0] : value
29
+ }
30
+ return sum
31
+ }, {})
32
+ }
33
+
34
+ // parse variable javascript string pattern
35
+ const replaceVariables = (keys, dic) => {
36
+ for (const k in keys) {
37
+ const matches = keys[k].match(/\$\{\w*\}/g)
38
+ matches &&
39
+ matches.forEach(m => {
40
+ keys[k] = keys[k].replace(m, dic[m.slice(2, -1)])
41
+ })
42
+ }
43
+ return keys
44
+ }
45
+
46
+ // It is required UTC date for Partitioning File System like AWS S3 from Athena.
47
+ // ex) %YYYY, %MM, %DD
48
+ const formatDate = (keys, _moment) => {
49
+ for (const k in keys) {
50
+ const matches = keys[k].match(/%\w*/g)
51
+ matches &&
52
+ matches.forEach(m => {
53
+ keys[k] = keys[k].replace(m, _moment.format(m.substr(1)))
54
+ })
55
+ }
56
+ return keys
57
+ }
58
+
59
+ export async function createDataSample(newDataSample: NewDataSample, context: ResolverContext): Promise<DataSample> {
60
+ const { domain, user, tx } = context.state
61
+
62
+ const dataSet = await tx.getRepository(DataSet).findOne({
63
+ where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, id: newDataSample.dataSet.id },
64
+ relations: ['dataKeySet']
65
+ })
66
+
67
+ const { dataItems = [], tag: publishTag, normalScenarioId, outlierScenarioId } = dataSet
68
+ const collectedAt = newDataSample.collectedAt || new Date()
69
+
70
+ const timezone = dataSet.timezone || domain.timezone || 'UTC'
71
+ const format = 'YYYY-MM-DD'
72
+
73
+ // workDate ex) 2022-04-04
74
+ const { workDate, workShift } = await getWorkDateAndShift(domain, collectedAt, { timezone, format })
75
+
76
+ // local time dataSet timezone or domain timezone or default 'UTC'
77
+
78
+ const localDateTz = moment(collectedAt).tz(timezone)
79
+ const defaultPartitionKeys = {
80
+ domain: domain.subdomain,
81
+ datasetid: newDataSample.dataSet.id /* It should not be 'data_set_id' as column name duplicated for Glue */,
82
+ date: localDateTz.format(format) /* local time date */,
83
+ workdate: workDate /* working date */,
84
+ workshift: workShift
85
+ }
86
+
87
+ var partitionKeys = {
88
+ ...defaultPartitionKeys,
89
+ ...dataSet.partitionKeys
90
+ }
91
+
92
+ partitionKeys = formatDate(partitionKeys, localDateTz)
93
+ partitionKeys = replaceVariables(partitionKeys, newDataSample.data)
94
+
95
+ const dataKeys = fillDataKeys(dataSet?.dataKeySet, newDataSample.data)
96
+
97
+ const { ooc, oos, judgment } = DataUseCase.evaluate(dataSet, dataItems, newDataSample.data) || {}
98
+
99
+ const old = await tx.getRepository(DataSample).findOne({
100
+ where: {
101
+ domain: { id: domain.id },
102
+ dataSet: { id: dataSet.id },
103
+ collectedAt,
104
+ ...dataKeys
105
+ }
106
+ })
107
+
108
+ /*
109
+ pre-processing for file attachment.
110
+ currently only support type of [FileUpload].
111
+ If [FileUpload[]] type needed, add 'files' type for dataset
112
+ */
113
+
114
+ const data = newDataSample.data
115
+ const attachments = []
116
+
117
+ for (let dataItem of dataItems) {
118
+ if (dataItem.type == 'file') {
119
+ const tag = dataItem.tag
120
+ const filesArray = data[tag]
121
+
122
+ if (tag && filesArray && filesArray.length > 0) {
123
+ let pathsArray = []
124
+
125
+ for (let files of filesArray) {
126
+ let paths = []
127
+
128
+ if (files instanceof Array) {
129
+ for (let file of files) {
130
+ if (file) {
131
+ const attachment = await createAttachment(
132
+ null,
133
+ {
134
+ attachment: {
135
+ file: file.file,
136
+ refType: DataSample.name
137
+ }
138
+ },
139
+ context
140
+ )
141
+
142
+ const fetched = await tx.getRepository(Attachment).findOneBy({ id: attachment.id })
143
+ if (fetched) {
144
+ attachments.push(fetched)
145
+ paths.push({
146
+ id: fetched.id,
147
+ mimetype: fetched.mimetype,
148
+ name: fetched.name,
149
+ fullpath: fetched.fullpath
150
+ })
151
+ } else {
152
+ throw `Failed to save file(${attachment.name})`
153
+ }
154
+ } else {
155
+ paths.push(null)
156
+ }
157
+ }
158
+ }
159
+
160
+ pathsArray.push(paths)
161
+ }
162
+
163
+ data[tag] = pathsArray
164
+ }
165
+ }
166
+ }
167
+
168
+ const dataSample = await tx.getRepository(DataSample).save({
169
+ ...old,
170
+ name: dataSet.name,
171
+ description: dataSet.description,
172
+ useCase: dataSet.useCase,
173
+ type: dataSet.type,
174
+ ...newDataSample,
175
+ ...dataKeys,
176
+ dataSetVersion: dataSet.version,
177
+ domain,
178
+ partitionKeys,
179
+ ooc,
180
+ oos,
181
+ judgment,
182
+ collectedAt,
183
+ workDate,
184
+ workShift,
185
+ creator: user,
186
+ updater: user
187
+ })
188
+
189
+ if (publishTag) {
190
+ publishData(publishTag, data, { domain, user })
191
+ }
192
+
193
+ /* post-process for for file attachment. */
194
+ if (attachments.length > 0) {
195
+ attachments.forEach(attachment => (attachment.refId = dataSample.id))
196
+ tx.getRepository(Attachment).save(attachments)
197
+ }
198
+
199
+ if (ooc || oos) {
200
+ const dataOoc = await createDataOoc(dataSample, dataSet, context)
201
+ await issueOocReview(dataOoc, dataSet, context)
202
+
203
+ try {
204
+ pubsub.publish('notification', {
205
+ notification: {
206
+ domain,
207
+ type: 'error',
208
+ title: `[OOC] ${dataSet.name}`,
209
+ body: `Data OOC occurred on '${dataSet.name}'`,
210
+ url: getRedirectSubdomainPath(context, domain.subdomain, `/data-ooc/${dataOoc.id}`),
211
+ timestamp: collectedAt
212
+ }
213
+ })
214
+ } catch (err) {
215
+ logger.error('Notification', err)
216
+ }
217
+ } else {
218
+ if (normalScenarioId) {
219
+ const scenario = await tx.getRepository(Scenario).findOne({
220
+ where: {
221
+ domain: { id: In([domain.id, domain.parentId].filter(Boolean)) },
222
+ id: normalScenarioId
223
+ },
224
+ relations: ['domain', 'steps', 'updater']
225
+ })
226
+
227
+ if (scenario) {
228
+ scenario.start({
229
+ instanceName: scenario.name + ':' + dataSet.name + ':' + dataSample.id,
230
+ domain,
231
+ user,
232
+ variables: {
233
+ dataSampleId: dataSample.id,
234
+ dataSet: dataSet.id,
235
+ data,
236
+ ooc,
237
+ oos,
238
+ judgment,
239
+ collectedAt,
240
+ workDate,
241
+ workShift,
242
+ domain: {
243
+ id: domain.id,
244
+ subdomain: domain.subdomain,
245
+ name: domain.name
246
+ },
247
+ updator: {
248
+ id: user.id,
249
+ email: user.email,
250
+ name: user.name
251
+ }
252
+ }
253
+ })
254
+ } else {
255
+ console.error(`Cannot find the set normal-scenario for the dataset(${dataSet.name}).`)
256
+ }
257
+ }
258
+
259
+ if (dataSet.requiresReview) {
260
+ const activity = (await tx.getRepository(Activity).findOneBy({
261
+ domain: { id: In([domain.id, domain.parentId].filter(Boolean)) },
262
+ name: 'Data Review'
263
+ })) as Activity
264
+
265
+ if (activity) {
266
+ const assigneeRole =
267
+ dataSet.supervisoryRoleId &&
268
+ (await tx.getRepository(Role).findOneBy({
269
+ domain: { id: In([domain.id, domain.parentId].filter(Boolean)) },
270
+ id: dataSet.supervisoryRoleId
271
+ }))
272
+
273
+ // const assignees = dataSet.supervisoryRoleId
274
+ // ? [{ type: 'Role', value: dataSet.supervisoryRoleId, assigneeRole }]
275
+ // : []
276
+
277
+ /* 해당 dataset의 supervisor로 하여금, data를 리뷰하고 instruction을 작성해서, approvalLine을 이용해서 승인을 한다. */
278
+ if (assigneeRole) {
279
+ dataSample.dataItems = dataItems
280
+ const activityInstance = {
281
+ name: `[Data 검토] ${dataSet.name}`,
282
+ description: dataSet.description,
283
+ activityId: activity.id,
284
+ dueAt: new Date(collectedAt.getTime() + (activity.standardTime || 24 * 60 * 60) * 1000),
285
+ input: {
286
+ dataSampleId: dataSample.id
287
+ },
288
+ assigneeRole,
289
+ threadsMin: 1,
290
+ threadsMax: 1,
291
+ approvalLine: dataSet.reviewApprovalLine
292
+ }
293
+
294
+ dataSample.reviewActivityInstance = await issue(activityInstance as any, context)
295
+ await tx.getRepository(DataSample).save(dataSample)
296
+
297
+ try {
298
+ pubsub.publish('notification', {
299
+ notification: {
300
+ domain,
301
+ type: 'info',
302
+ title: `[Data Review] ${dataSet.name}`,
303
+ body: `Data Review occurred on '${dataSet.name}'`,
304
+ url: getRedirectSubdomainPath(context, domain.subdomain, `/data-sample/${dataSample.id}`),
305
+ timestamp: collectedAt
306
+ }
307
+ })
308
+ } catch (err) {
309
+ logger.error('Notification', err)
310
+ }
311
+ } else {
312
+ console.error(
313
+ `Assignees are not set. So Data Review task for ${dataSet.name}(${dataSet.id}) could not be issued.`
314
+ )
315
+ }
316
+ } else {
317
+ console.error('Data Review Activity not installed.')
318
+ }
319
+ }
320
+ }
321
+
322
+ return dataSample
323
+ }
@@ -0,0 +1,98 @@
1
+ import { DataItem } from '../service/data-set/data-item-type'
2
+ import { DataSet } from '../service/data-set/data-set'
3
+
4
+ export type DataItemSpec = {
5
+ type: string
6
+ label: string
7
+ name: string
8
+ property?: { [option: string]: any }
9
+ }
10
+
11
+ export type DataItemSpecSet = {
12
+ name: string
13
+ description: string
14
+ help?: string
15
+ specs: DataItemSpec[]
16
+ }
17
+
18
+ export type EvaluationResult = {
19
+ oos: boolean
20
+ ooc: boolean
21
+ judgment?: { [tag: string]: { ooc: boolean; oos: boolean } }
22
+ }
23
+
24
+ export abstract class DataUseCase {
25
+ static registry: { [name: string]: DataUseCase } = {}
26
+
27
+ public static registerUseCase(name: string, provider: DataUseCase) {
28
+ DataUseCase.registry[name] = provider
29
+ }
30
+
31
+ public static getUseCaseNames() {
32
+ return Object.keys(DataUseCase.registry)
33
+ }
34
+
35
+ public static getUseCase(name: string): DataUseCase | undefined {
36
+ return DataUseCase.registry[name]
37
+ }
38
+
39
+ public static evaluate(dataSet: DataSet, dataItems: DataItem[], data: any): EvaluationResult {
40
+ var ooc = false
41
+ var oos = false
42
+ var judgment: { [tag: string]: { ooc: boolean; oos: boolean } } = {}
43
+
44
+ if (!dataSet.useCase) {
45
+ return { ooc, oos }
46
+ }
47
+
48
+ const useCaseNames = dataSet.useCase.split(',').map(useCaseName => useCaseName.trim())
49
+ const useCases = useCaseNames.map(useCaseName => DataUseCase.getUseCase(useCaseName)).filter(useCase => !!useCase)
50
+
51
+ for (let i = 0; i < dataItems.length; i++) {
52
+ const dataItem = dataItems[i]
53
+ const { active, tag } = dataItem
54
+ if (!active || !tag) {
55
+ continue
56
+ }
57
+
58
+ let values: any | any[] = data && data[tag]
59
+ if (values == null) {
60
+ continue // TODO what if in case no value ?
61
+ }
62
+
63
+ if (!(values instanceof Array)) {
64
+ values = [values]
65
+ }
66
+
67
+ let oocForTag = false
68
+ let oosForTag = false
69
+
70
+ for (let j = 0; j < useCases.length; j++) {
71
+ const useCase = useCases[j]
72
+
73
+ const specs = dataItem.spec?.[dataSet.useCase]
74
+ if (!specs) {
75
+ continue
76
+ }
77
+
78
+ const result = useCase.evaluate(specs, values)
79
+
80
+ if (result) {
81
+ oocForTag ||= result.ooc
82
+ oosForTag ||= result.oos
83
+ ooc ||= result.ooc
84
+ oos ||= result.oos
85
+ }
86
+ }
87
+
88
+ judgment[tag] = {
89
+ ooc: oocForTag,
90
+ oos: oosForTag
91
+ }
92
+ }
93
+
94
+ return { ooc, oos, judgment }
95
+ }
96
+
97
+ public abstract evaluate(specs: any, values: any[]): EvaluationResult
98
+ }