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

Sign up to get free protection for your applications and to get access to all the features.
Files changed (120) 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/pages/data-entry/data-entry-list-page.js +2 -2
  44. package/dist-client/pages/data-entry/data-entry-list-page.js.map +1 -1
  45. package/dist-client/tsconfig.tsbuildinfo +1 -1
  46. package/dist-server/service/data-sample/data-sample-query.d.ts +1 -1
  47. package/dist-server/service/data-sample/data-sample-query.js +3 -3
  48. package/dist-server/service/data-sample/data-sample-query.js.map +1 -1
  49. package/dist-server/tsconfig.tsbuildinfo +1 -1
  50. package/package.json +26 -26
  51. package/server/activities/activity-data-collect.ts +100 -0
  52. package/server/activities/activity-data-review.ts +82 -0
  53. package/server/activities/activity-ooc-resolve.ts +123 -0
  54. package/server/activities/activity-ooc-review.ts +144 -0
  55. package/server/activities/index.ts +11 -0
  56. package/server/controllers/create-data-sample.ts +426 -0
  57. package/server/controllers/data-use-case.ts +98 -0
  58. package/server/controllers/finalize-data-collection.ts +388 -0
  59. package/server/controllers/index.ts +3 -0
  60. package/server/controllers/issue-data-collection-task.ts +70 -0
  61. package/server/controllers/jasper-report.ts +186 -0
  62. package/server/controllers/query-data-summary-by-period.ts +178 -0
  63. package/server/controllers/shiny-report.ts +54 -0
  64. package/server/engine/index.ts +1 -0
  65. package/server/engine/task/create-data-sample.ts +100 -0
  66. package/server/engine/task/index.ts +2 -0
  67. package/server/engine/task/issue-collect-data.ts +45 -0
  68. package/server/index.ts +8 -0
  69. package/server/routes.ts +188 -0
  70. package/server/service/data-archive/data-archive-mutation.ts +273 -0
  71. package/server/service/data-archive/data-archive-query.ts +58 -0
  72. package/server/service/data-archive/data-archive-type.ts +48 -0
  73. package/server/service/data-archive/data-archive.ts +69 -0
  74. package/server/service/data-archive/index.ts +6 -0
  75. package/server/service/data-key-set/data-key-item-type.ts +31 -0
  76. package/server/service/data-key-set/data-key-set-mutation.ts +201 -0
  77. package/server/service/data-key-set/data-key-set-query.ts +68 -0
  78. package/server/service/data-key-set/data-key-set-type.ts +70 -0
  79. package/server/service/data-key-set/data-key-set.ts +86 -0
  80. package/server/service/data-key-set/index.ts +6 -0
  81. package/server/service/data-ooc/data-ooc-mutation.ts +154 -0
  82. package/server/service/data-ooc/data-ooc-query.ts +106 -0
  83. package/server/service/data-ooc/data-ooc-subscription.ts +48 -0
  84. package/server/service/data-ooc/data-ooc-type.ts +71 -0
  85. package/server/service/data-ooc/data-ooc.ts +259 -0
  86. package/server/service/data-ooc/index.ts +7 -0
  87. package/server/service/data-sample/data-sample-mutation.ts +18 -0
  88. package/server/service/data-sample/data-sample-query.ts +215 -0
  89. package/server/service/data-sample/data-sample-type.ts +47 -0
  90. package/server/service/data-sample/data-sample.ts +193 -0
  91. package/server/service/data-sample/index.ts +6 -0
  92. package/server/service/data-sensor/data-sensor-mutation.ts +116 -0
  93. package/server/service/data-sensor/data-sensor-query.ts +76 -0
  94. package/server/service/data-sensor/data-sensor-type.ts +104 -0
  95. package/server/service/data-sensor/data-sensor.ts +126 -0
  96. package/server/service/data-sensor/index.ts +6 -0
  97. package/server/service/data-set/data-item-type.ts +155 -0
  98. package/server/service/data-set/data-set-mutation.ts +552 -0
  99. package/server/service/data-set/data-set-query.ts +461 -0
  100. package/server/service/data-set/data-set-type.ts +204 -0
  101. package/server/service/data-set/data-set.ts +326 -0
  102. package/server/service/data-set/index.ts +6 -0
  103. package/server/service/data-set-history/data-set-history-query.ts +126 -0
  104. package/server/service/data-set-history/data-set-history-type.ts +12 -0
  105. package/server/service/data-set-history/data-set-history.ts +217 -0
  106. package/server/service/data-set-history/event-subscriber.ts +17 -0
  107. package/server/service/data-set-history/index.ts +7 -0
  108. package/server/service/data-spec/data-spec-manager.ts +21 -0
  109. package/server/service/data-spec/data-spec-query.ts +21 -0
  110. package/server/service/data-spec/data-spec.ts +45 -0
  111. package/server/service/data-spec/index.ts +5 -0
  112. package/server/service/data-summary/data-summary-mutation.ts +45 -0
  113. package/server/service/data-summary/data-summary-query.ts +179 -0
  114. package/server/service/data-summary/data-summary-type.ts +86 -0
  115. package/server/service/data-summary/data-summary.ts +170 -0
  116. package/server/service/data-summary/index.ts +7 -0
  117. package/server/service/index.ts +57 -0
  118. package/server/tsconfig.json +10 -0
  119. package/server/utils/config-resolver.ts +29 -0
  120. package/server/utils/index.ts +1 -0
@@ -0,0 +1,461 @@
1
+ import { Arg, Args, Ctx, Directive, FieldResolver, Query, Resolver, Root } from 'type-graphql'
2
+ import { Brackets, In } from 'typeorm'
3
+
4
+ import { Attachment } from '@things-factory/attachment-base'
5
+ import { Role, User } from '@things-factory/auth-base'
6
+ import { Board } from '@things-factory/board-service'
7
+ import { ApprovalLineItem } from '@things-factory/organization'
8
+ import { Domain, getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'
9
+ import { AssigneeItem } from '@things-factory/worklist'
10
+ import { Scenario } from '@things-factory/integration-base'
11
+
12
+ import { DataKeySet } from '../data-key-set/data-key-set'
13
+ import { DataSample } from '../data-sample/data-sample'
14
+ import { DataItem } from './data-item-type'
15
+ import { DataSet, DataSetState } from './data-set'
16
+ import { DataSetList } from './data-set-type'
17
+
18
+ var parser = require('cron-parser')
19
+
20
+ @Resolver(DataSet)
21
+ export class DataSetQuery {
22
+ // 데이터셋을 ID로 조회
23
+ @Directive('@privilege(category: "data-set", privilege: "query", domainOwnerGranted: true)')
24
+ @Query(returns => DataSet, { description: 'To fetch a DataSet' })
25
+ async dataSet(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<DataSet> {
26
+ const { domain } = context.state
27
+
28
+ return await getRepository(DataSet).findOne({
29
+ where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, id }
30
+ })
31
+ }
32
+
33
+ // 데이터셋을 이름으로 조회
34
+ @Directive('@privilege(category: "data-set", privilege: "query", domainOwnerGranted: true)')
35
+ @Query(returns => DataSet, { description: 'To fetch a DataSet by name' })
36
+ async dataSetByName(@Arg('name') name: string, @Ctx() context: ResolverContext): Promise<DataSet> {
37
+ const { domain } = context.state
38
+
39
+ return await getRepository(DataSet).findOne({
40
+ where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, name }
41
+ })
42
+ }
43
+
44
+ // 여러 데이터셋을 조회
45
+ @Directive('@privilege(category: "data-set", privilege: "query", domainOwnerGranted: true)')
46
+ @Query(returns => DataSetList, { description: 'To fetch multiple DataSets' })
47
+ async dataSets(@Args(type => ListParam) params: ListParam, @Ctx() context: ResolverContext): Promise<DataSetList> {
48
+ const { domain } = context.state
49
+
50
+ const queryBuilder = getQueryBuilderFromListParams({
51
+ repository: getRepository(DataSet),
52
+ params,
53
+ domain,
54
+ alias: 'dataset',
55
+ searchables: ['name', 'description', 'tag', 'supervisoryRole']
56
+ })
57
+
58
+ const [items, total] = await queryBuilder.getManyAndCount()
59
+
60
+ return { items, total }
61
+ }
62
+
63
+ // 데이터 입력을 위한 데이터셋 조회
64
+ @Query(returns => DataSetList, { description: 'To fetch multiple DataSets for data entry manually' })
65
+ async dataSetsForEntry(
66
+ @Args(type => ListParam) params: ListParam,
67
+ @Ctx() context: ResolverContext
68
+ ): Promise<DataSetList> {
69
+ var { domain, user } = context.state
70
+
71
+ // 조회한 사용자가 entry-role을 가진 data-set 리스트만 반환
72
+ user = await getRepository(User).findOne({
73
+ where: { id: user.id },
74
+ relations: ['roles']
75
+ })
76
+
77
+ const roles = user.roles
78
+ .filter(role => role.domainId === domain.id || (domain.parentId && role.domainId === domain.parentId))
79
+ .map(role => role.id)
80
+
81
+ if (!roles.length) {
82
+ return { items: [], total: 0 }
83
+ }
84
+
85
+ const [items, total] = await getQueryBuilderFromListParams({
86
+ repository: getRepository(DataSet),
87
+ params,
88
+ domain,
89
+ alias: 'dataset',
90
+ searchables: ['name', 'description']
91
+ })
92
+ .andWhere('dataset.active = :active', { active: true })
93
+ .andWhere(
94
+ new Brackets(qb => {
95
+ qb.where('dataset.supervisoryRole IN (:...roles)').orWhere('dataset.entryRole IN (:...roles)')
96
+ })
97
+ )
98
+ .setParameter('roles', roles)
99
+ .getManyAndCount()
100
+
101
+ return {
102
+ items,
103
+ total
104
+ }
105
+ }
106
+
107
+ // 데이터 보고를 위한 데이터셋 조회
108
+ @Query(returns => DataSetList, { description: 'To fetch multiple DataSets to see data report' })
109
+ async dataSetsForReport(
110
+ @Args(type => ListParam) params: ListParam,
111
+ @Ctx() context: ResolverContext
112
+ ): Promise<DataSetList> {
113
+ var { domain, user } = context.state
114
+
115
+ // 조회한 사용자가 supervisory 역할을 가진 data-set 리스트만 반환
116
+ user = await getRepository(User).findOne({
117
+ where: { id: user.id },
118
+ relations: ['roles']
119
+ })
120
+ const roles = user.roles.filter(role => role.domainId === domain.id).map(role => role.id)
121
+
122
+ if (!roles.length) {
123
+ return { items: [], total: 0 }
124
+ }
125
+
126
+ const [items, total] = await getQueryBuilderFromListParams({
127
+ repository: getRepository(DataSet),
128
+ params,
129
+ domain,
130
+ alias: 'dataset',
131
+ searchables: ['name', 'description']
132
+ })
133
+ .andWhere(`dataset.active = :active`, { active: true })
134
+ .andWhere('dataset.supervisoryRole IN (:...roles)', { roles })
135
+ .getManyAndCount()
136
+
137
+ return { items, total }
138
+ }
139
+
140
+ // 데이터셋의 상태를 조회
141
+ @FieldResolver(type => DataSetState)
142
+ async status(@Root() dataSet: DataSet): Promise<DataSetState> {
143
+ // 스케줄러 서비스에서 정보 가져오기
144
+ return {
145
+ id: '',
146
+ scheduleId: '',
147
+ state: '',
148
+ startedAt: new Date(),
149
+ nextSchedule: new Date()
150
+ }
151
+ }
152
+
153
+ // 데이터셋의 데이터 항목을 조회
154
+ @FieldResolver(type => [DataItem])
155
+ async dataItems(@Root() dataSet: DataSet): Promise<DataItem[]> {
156
+ if (dataSet.dataItems instanceof Array) {
157
+ return dataSet.dataItems.filter(item => item.name)
158
+ }
159
+
160
+ return []
161
+ }
162
+
163
+ // 데이터셋의 entry 역할을 조회
164
+ @FieldResolver(type => Role)
165
+ async entryRole(@Root() dataSet: DataSet): Promise<Role> {
166
+ return (
167
+ dataSet.entryRole ||
168
+ (dataSet.entryRoleId &&
169
+ (await getRepository(Role).findOneBy({
170
+ id: dataSet.entryRoleId
171
+ })))
172
+ )
173
+ }
174
+
175
+ // 데이터셋의 supervisory 역할을 조회
176
+ @FieldResolver(type => Role, { description: 'Retrieves the supervisory role for the dataset' })
177
+ async supervisoryRole(@Root() dataSet: DataSet): Promise<Role> {
178
+ return (
179
+ dataSet.supervisoryRole ||
180
+ (dataSet.supervisoryRoleId &&
181
+ (await getRepository(Role).findOneBy({
182
+ id: dataSet.supervisoryRoleId
183
+ })))
184
+ )
185
+ }
186
+
187
+ // 데이터셋의 resolver 역할을 조회
188
+ @FieldResolver(type => Role, { description: 'Retrieves the resolver role for the dataset' })
189
+ async resolverRole(@Root() dataSet: DataSet): Promise<Role> {
190
+ return (
191
+ dataSet.resolverRole ||
192
+ (dataSet.resolverRoleId &&
193
+ (await getRepository(Role).findOneBy({
194
+ id: dataSet.resolverRoleId
195
+ })))
196
+ )
197
+ }
198
+
199
+ @FieldResolver(type => [AssigneeItem], { description: 'Retrieves the assignee items for the dataset' })
200
+ async assignees(@Root() dataSet: DataSet, @Ctx() context: ResolverContext): Promise<AssigneeItem[]> {
201
+ const { domain, user } = context.state
202
+ const { assignees } = dataSet
203
+
204
+ if (!assignees || !(assignees instanceof Array)) {
205
+ return null
206
+ }
207
+
208
+ var assigneeItemList = []
209
+
210
+ for (let item of assignees) {
211
+ // "value" deprecated. to be removed
212
+ var { type, assignee, value } = item
213
+ var id = assignee?.id || id
214
+
215
+ switch (type) {
216
+ case 'Employee':
217
+ assignee = id && (await getRepository('Employee').findOneBy({ domain: { id: domain.id }, id }))
218
+ break
219
+ case 'Department':
220
+ assignee = id && (await getRepository('Department').findOneBy({ domain: { id: domain.id }, id }))
221
+ break
222
+ case 'Role':
223
+ assignee = id && (await getRepository('Role').findOneBy({ domain: { id: domain.id }, id }))
224
+ break
225
+ case 'Myself':
226
+ assignee = user
227
+ break
228
+ case 'MyDepartment':
229
+ case 'MySupervisor':
230
+ break
231
+ default:
232
+ }
233
+
234
+ id ? assigneeItemList.push({ type, assignee }) : assigneeItemList.push({ type })
235
+ }
236
+
237
+ return assigneeItemList
238
+ }
239
+
240
+ @FieldResolver(type => [ApprovalLineItem], { description: 'Retrieves the approval line items for the dataset' })
241
+ async approvalLine(@Root() dataSet: DataSet, @Ctx() context: ResolverContext): Promise<ApprovalLineItem[]> {
242
+ const { domain, user } = context.state
243
+ const { reviewApprovalLine } = dataSet
244
+
245
+ if (!reviewApprovalLine || !(reviewApprovalLine instanceof Array)) {
246
+ return null
247
+ }
248
+
249
+ var approvalLineItems = []
250
+
251
+ for (let item of reviewApprovalLine) {
252
+ // "value" deprecated. to be removed
253
+ var { type, approver, value } = item
254
+ var id = approver?.id || value
255
+ approver = null
256
+
257
+ switch (type) {
258
+ case 'Employee':
259
+ approver = id && (await getRepository('Employee').findOneBy({ domain: { id: domain.id }, id }))
260
+ break
261
+ case 'Department':
262
+ approver = id && (await getRepository('Department').findOneBy({ domain: { id: domain.id }, id }))
263
+ break
264
+ case 'Role':
265
+ approver = id && (await getRepository('Role').findOneBy({ domain: { id: domain.id }, id }))
266
+ break
267
+ case 'Myself':
268
+ approver = user
269
+ break
270
+ case 'MyDepartment':
271
+ case 'MySupervisor':
272
+ break
273
+ default:
274
+ }
275
+
276
+ id ? approvalLineItems.push({ type, approver }) : approvalLineItems.push({ type })
277
+ }
278
+
279
+ return approvalLineItems
280
+ }
281
+
282
+ @FieldResolver(type => Board, { nullable: true, description: 'Retrieves the entry board for the dataset' })
283
+ async entryBoard(@Root() dataSet: DataSet): Promise<Board> {
284
+ if (dataSet.entryType == 'board' && dataSet.entryView) {
285
+ return await getRepository(Board).findOneBy({ id: dataSet.entryView })
286
+ }
287
+ }
288
+
289
+ @FieldResolver(type => Board, { nullable: true, description: 'Retrieves the monitor board for the dataset' })
290
+ async monitorBoard(@Root() dataSet: DataSet): Promise<Board> {
291
+ if (dataSet.monitorType == 'board' && dataSet.monitorView) {
292
+ return await getRepository(Board).findOneBy({ id: dataSet.monitorView })
293
+ }
294
+ }
295
+
296
+ @FieldResolver(type => DataKeySet, { nullable: true, description: 'Retrieves the data key set for the dataset' })
297
+ async dataKeySet(@Root() dataSet: DataSet): Promise<DataKeySet> {
298
+ return (
299
+ dataSet.dataKeySet ||
300
+ (dataSet.dataKeySetId &&
301
+ (await getRepository(DataKeySet).findOneBy({
302
+ id: dataSet.dataKeySetId
303
+ })))
304
+ )
305
+ }
306
+
307
+ @FieldResolver(type => Scenario, { description: 'Retrieves the normal scenario for the dataset.' })
308
+ async normalScenario(@Root() dataSet: DataSet): Promise<Scenario> {
309
+ return (
310
+ dataSet.normalScenario ||
311
+ (dataSet.normalScenarioId &&
312
+ (await getRepository(Scenario).findOneBy({
313
+ id: dataSet.normalScenarioId
314
+ })))
315
+ )
316
+ }
317
+
318
+ @FieldResolver(type => Scenario, { description: 'Retrieves the outlier scenario for the dataset.' })
319
+ async outlierScenario(@Root() dataSet: DataSet): Promise<Scenario> {
320
+ return (
321
+ dataSet.outlierScenario ||
322
+ (dataSet.outlierScenarioId &&
323
+ (await getRepository(Scenario).findOneBy({
324
+ id: dataSet.outlierScenarioId
325
+ })))
326
+ )
327
+ }
328
+
329
+ // 데이터셋의 도메인을 조회
330
+ @FieldResolver(type => Domain)
331
+ async domain(@Root() dataSet: DataSet): Promise<Domain> {
332
+ return dataSet.domainId && (await getRepository(Domain).findOneBy({ id: dataSet.domainId }))
333
+ }
334
+
335
+ // 데이터셋의 업데이트한 사용자를 조회
336
+ @FieldResolver(type => User)
337
+ async updater(@Root() dataSet: DataSet): Promise<User> {
338
+ return dataSet.updaterId && (await getRepository(User).findOneBy({ id: dataSet.updaterId }))
339
+ }
340
+
341
+ // 데이터셋의 생성자를 조회
342
+ @FieldResolver(type => User)
343
+ async creator(@Root() dataSet: DataSet): Promise<User> {
344
+ return dataSet.creatorId && (await getRepository(User).findOneBy({ id: dataSet.creatorId }))
345
+ }
346
+
347
+ @FieldResolver(type => Date, { nullable: true, description: 'Retrieves the latest collection time for the dataset.' })
348
+ async latestCollectedAt(@Root() dataSet: DataSet): Promise<Date> {
349
+ const sample = await getRepository(DataSample).findOne({
350
+ select: ['id', 'collectedAt'],
351
+ where: { dataSet: { id: dataSet.id } },
352
+ order: { collectedAt: 'DESC' }
353
+ })
354
+
355
+ return sample?.collectedAt
356
+ }
357
+
358
+ @FieldResolver(type => Date, { nullable: true, description: 'Retrieves the next schedule time for the dataset.' })
359
+ async nextSchedule(@Root() dataSet: DataSet): Promise<Date> {
360
+ const { domainId, schedule, timezone } = dataSet
361
+
362
+ if (!schedule) {
363
+ return
364
+ }
365
+
366
+ var interval = parser.parseExpression(schedule, {
367
+ tz: timezone || ((await getRepository(Domain).findOneBy({ id: domainId })) as Domain).timezone || 'UTC'
368
+ })
369
+
370
+ return interval.next().toDate()
371
+ }
372
+
373
+ @FieldResolver(type => Date, { nullable: true, description: 'Retrieves the previous schedule time for the dataset.' })
374
+ async prevSchedule(@Root() dataSet: DataSet): Promise<Date> {
375
+ const { domainId, schedule, timezone } = dataSet
376
+
377
+ if (!schedule) {
378
+ return
379
+ }
380
+
381
+ var interval = parser.parseExpression(schedule, {
382
+ tz: timezone || ((await getRepository(Domain).findOneBy({ id: domainId })) as Domain).timezone || 'UTC'
383
+ })
384
+
385
+ return interval.prev().toDate()
386
+ }
387
+
388
+ @FieldResolver(type => Date, {
389
+ nullable: true,
390
+ description: 'Retrieves the next summary schedule time for the dataset.'
391
+ })
392
+ async nextSummarySchedule(@Root() dataSet: DataSet): Promise<Date> {
393
+ const { domainId, summarySchedule, timezone } = dataSet
394
+
395
+ if (!summarySchedule) {
396
+ return
397
+ }
398
+
399
+ var interval = parser.parseExpression(summarySchedule, {
400
+ tz: timezone || ((await getRepository(Domain).findOneBy({ id: domainId })) as Domain).timezone || 'UTC'
401
+ })
402
+
403
+ return interval.next().toDate()
404
+
405
+ // FIXME: getSchedules 의 파라미터를 수정해야한다. scheduler 가 없거나 커넥션이 끊겨있어서 오래걸리는 문제는 어떻게 ?? 비동기로 처리한다. 어떻게???
406
+ // client: {
407
+ // application: Application,
408
+ // group: `${domain.id}`,
409
+ // type: 'data-set',
410
+ // key: dataSet.id,
411
+ // operation: 'summary'
412
+ // }
413
+ // const schedule = getSchedules('name', 'group', false)
414
+ // return schedule.nextSchedule
415
+ }
416
+
417
+ @FieldResolver(type => String, { description: 'Retrieves the report template for the dataset.' })
418
+ async reportTemplate(@Root() dataset: DataSet): Promise<string | undefined> {
419
+ const attachment: Attachment = await getRepository(Attachment).findOne({
420
+ where: {
421
+ domain: { id: dataset.domainId },
422
+ refType: `${DataSet.name}-report-template`,
423
+ refBy: dataset.id
424
+ }
425
+ })
426
+
427
+ return attachment?.path
428
+ }
429
+
430
+ @FieldResolver(returns => Boolean, { description: 'Whether the user is a supervisor for this dataset' })
431
+ async isSupervisor(@Root() dataset: DataSet, @Ctx() context: ResolverContext): Promise<boolean> {
432
+ const { user, domain } = context.state
433
+
434
+ const foundUser = await getRepository(User).findOne({
435
+ where: { id: user.id },
436
+ relations: ['roles']
437
+ })
438
+
439
+ const roles = foundUser.roles
440
+ .filter(role => role.domainId === domain.id || (domain.parentId && role.domainId === domain.parentId))
441
+ .map(role => role.id)
442
+
443
+ return roles.includes(dataset.supervisoryRoleId)
444
+ }
445
+
446
+ @FieldResolver(returns => Boolean, { description: 'Whether the user is allowed to enter data for this dataset' })
447
+ async isEntryAllowed(@Root() dataset: DataSet, @Ctx() context: ResolverContext): Promise<boolean> {
448
+ const { user, domain } = context.state
449
+
450
+ const foundUser = await getRepository(User).findOne({
451
+ where: { id: user.id },
452
+ relations: ['roles']
453
+ })
454
+
455
+ const roles = foundUser.roles
456
+ .filter(role => role.domainId === domain.id || (domain.parentId && role.domainId === domain.parentId))
457
+ .map(role => role.id)
458
+
459
+ return roles.includes(dataset.entryRoleId)
460
+ }
461
+ }
@@ -0,0 +1,204 @@
1
+ import type { FileUpload } from 'graphql-upload/GraphQLUpload.js'
2
+ import GraphQLUpload from 'graphql-upload/GraphQLUpload.js'
3
+ import { Field, ID, InputType, Int, ObjectType } from 'type-graphql'
4
+
5
+ import { ApprovalLineItem } from '@things-factory/organization'
6
+ import { ObjectRef, ScalarObject } from '@things-factory/shell'
7
+
8
+ import { DataItemPatch } from './data-item-type'
9
+ import { DataSet, DataSetEntryType, DataSetMonitorType, DataSetReportType, DataSetSummaryPeriodType } from './data-set'
10
+
11
+ @InputType()
12
+ export class NewDataSet {
13
+ @Field()
14
+ name: string
15
+
16
+ @Field({ nullable: true })
17
+ description?: string
18
+
19
+ @Field({ nullable: true, description: 'A tag name of data to be published when a data sample created' })
20
+ tag?: string
21
+
22
+ @Field(type => ObjectRef, { nullable: true })
23
+ dataKeySet?: ObjectRef
24
+
25
+ @Field(type => ObjectRef, { nullable: true })
26
+ entryRole?: ObjectRef
27
+
28
+ @Field(type => ObjectRef, { nullable: true })
29
+ supervisoryRole?: ObjectRef
30
+
31
+ @Field(type => ObjectRef, { nullable: true })
32
+ resolverRole?: ObjectRef
33
+
34
+ @Field(type => ScalarObject, { nullable: true })
35
+ reviewApprovalLine?: ApprovalLineItem[]
36
+
37
+ @Field(type => ScalarObject, { nullable: true })
38
+ outlierApprovalLine?: ApprovalLineItem[]
39
+
40
+ @Field(type => ObjectRef, { nullable: true })
41
+ normalScenario?: ObjectRef
42
+
43
+ @Field(type => ObjectRef, { nullable: true })
44
+ outlierScenario?: ObjectRef
45
+
46
+ @Field({ nullable: true })
47
+ active?: boolean
48
+
49
+ @Field({ nullable: true })
50
+ type?: 'manual' | 'automatic' | string
51
+
52
+ @Field({ nullable: true })
53
+ useCase?: string
54
+
55
+ @Field(type => ScalarObject, { nullable: true })
56
+ partitionKeys?: { [key: string]: any }
57
+
58
+ @Field(type => [DataItemPatch], { nullable: true })
59
+ dataItems?: DataItemPatch[]
60
+
61
+ @Field({ nullable: true })
62
+ schedule?: string
63
+
64
+ @Field({ nullable: true })
65
+ timezone?: string
66
+
67
+ @Field({ nullable: true })
68
+ requiresReview?: boolean
69
+
70
+ @Field({ nullable: true })
71
+ entryType?: DataSetEntryType
72
+
73
+ @Field({ nullable: true })
74
+ entryView?: string
75
+
76
+ @Field({ nullable: true })
77
+ monitorType?: DataSetMonitorType
78
+
79
+ @Field({ nullable: true })
80
+ monitorView?: string
81
+
82
+ @Field({ nullable: true })
83
+ reportType?: DataSetReportType
84
+
85
+ @Field({ nullable: true })
86
+ reportView?: string
87
+
88
+ @Field(type => GraphQLUpload, { nullable: true })
89
+ reportTemplate?: FileUpload
90
+
91
+ @Field({ nullable: true })
92
+ summaryPeriod?: DataSetSummaryPeriodType
93
+
94
+ @Field({
95
+ nullable: true,
96
+ description: 'The maximum allowed time limit (in seconds) for completing the data entry operation'
97
+ })
98
+ timeLimit?: number
99
+ }
100
+
101
+ @InputType()
102
+ export class DataSetPatch {
103
+ @Field(type => ID, { nullable: true })
104
+ id?: string
105
+
106
+ @Field({ nullable: true })
107
+ name?: string
108
+
109
+ @Field({ nullable: true })
110
+ description?: string
111
+
112
+ @Field({ nullable: true, description: 'A tag name of data to be published when a data sample created' })
113
+ tag?: string
114
+
115
+ @Field(type => ObjectRef, { nullable: true })
116
+ dataKeySet?: ObjectRef
117
+
118
+ @Field(type => ObjectRef, { nullable: true })
119
+ supervisoryRole?: ObjectRef
120
+
121
+ @Field(type => ObjectRef, { nullable: true })
122
+ entryRole?: ObjectRef
123
+
124
+ @Field(type => ObjectRef, { nullable: true })
125
+ resolverRole?: ObjectRef
126
+
127
+ @Field(type => ScalarObject, { nullable: true })
128
+ reviewApprovalLine?: ApprovalLineItem[]
129
+
130
+ @Field(type => ScalarObject, { nullable: true })
131
+ outlierApprovalLine?: ApprovalLineItem[]
132
+
133
+ @Field(type => ObjectRef, { nullable: true })
134
+ normalScenario?: ObjectRef
135
+
136
+ @Field(type => ObjectRef, { nullable: true })
137
+ outlierScenario?: ObjectRef
138
+
139
+ @Field({ nullable: true })
140
+ active?: boolean
141
+
142
+ @Field({ nullable: true })
143
+ type?: 'manual' | 'automatic' | string
144
+
145
+ @Field({ nullable: true })
146
+ useCase?: string
147
+
148
+ @Field(type => ScalarObject, { nullable: true })
149
+ partitionKeys?: { [key: string]: any }
150
+
151
+ @Field(type => [DataItemPatch], { nullable: true })
152
+ dataItems?: DataItemPatch[]
153
+
154
+ @Field({ nullable: true })
155
+ schedule?: string
156
+
157
+ @Field({ nullable: true })
158
+ timezone?: string
159
+
160
+ @Field({ nullable: true })
161
+ requiresReview?: boolean
162
+
163
+ @Field({ nullable: true })
164
+ entryType?: DataSetEntryType
165
+
166
+ @Field({ nullable: true })
167
+ entryView?: string
168
+
169
+ @Field({ nullable: true })
170
+ monitorType?: DataSetMonitorType
171
+
172
+ @Field({ nullable: true })
173
+ monitorView?: string
174
+
175
+ @Field({ nullable: true })
176
+ reportType?: DataSetReportType
177
+
178
+ @Field({ nullable: true })
179
+ reportView?: string
180
+
181
+ @Field(type => GraphQLUpload, { nullable: true })
182
+ reportTemplate?: FileUpload
183
+
184
+ @Field({ nullable: true })
185
+ summaryPeriod?: DataSetSummaryPeriodType
186
+
187
+ @Field({
188
+ nullable: true,
189
+ description: 'The maximum allowed time limit (in seconds) for completing the data entry operation'
190
+ })
191
+ timeLimit?: number
192
+
193
+ @Field()
194
+ cuFlag: string
195
+ }
196
+
197
+ @ObjectType()
198
+ export class DataSetList {
199
+ @Field(type => [DataSet])
200
+ items: DataSet[]
201
+
202
+ @Field(type => Int)
203
+ total: number
204
+ }