@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,155 @@
1
+ import { Field, InputType, Int, ObjectType, registerEnumType } from 'type-graphql'
2
+
3
+ import { ScalarObject } from '@things-factory/shell'
4
+
5
+ export enum DataItemType {
6
+ number = 'number',
7
+ text = 'text',
8
+ boolean = 'boolean',
9
+ select = 'select',
10
+ radio = 'radio',
11
+ date = 'date',
12
+ datetime = 'datetime',
13
+ file = 'file',
14
+ signature = 'signature'
15
+ }
16
+
17
+ registerEnumType(DataItemType, {
18
+ name: 'DataItemType',
19
+ description: 'type enumeration of a data-item'
20
+ })
21
+
22
+ export enum DataItemStatType {
23
+ sum = 'sum',
24
+ mean = 'mean',
25
+ stddev = 'stddev',
26
+ variance = 'variance',
27
+ min = 'min',
28
+ max = 'max',
29
+ range = 'range',
30
+ median = 'median',
31
+ mode = 'mode'
32
+ }
33
+
34
+ registerEnumType(DataItemStatType, {
35
+ name: 'DataItemStatType',
36
+ description: 'stat/agg type enumeration of a data-item'
37
+ })
38
+
39
+ @ObjectType({ description: 'Entity for DataItem' })
40
+ export class DataItem {
41
+ @Field({ description: 'The name of the data item' })
42
+ name: string
43
+
44
+ @Field({ nullable: true, description: 'A description of the data item' })
45
+ description?: string
46
+
47
+ @Field({
48
+ nullable: true,
49
+ description:
50
+ 'Specifies a key name to be used as a property in a JSON-like object, representing a subfield of a dataset record.'
51
+ })
52
+ tag?: string
53
+
54
+ @Field({
55
+ nullable: true,
56
+ description:
57
+ 'Specifies a grouping identifier for data items with related content, allowing them to be displayed as subgroups within the overall dataset.'
58
+ })
59
+ group?: string
60
+
61
+ @Field({ nullable: true, description: 'Indicates if the data item is active' })
62
+ active?: boolean
63
+
64
+ @Field({ nullable: true, description: 'Indicates if the data item is hidden' })
65
+ hidden?: boolean
66
+
67
+ @Field({ nullable: true, description: 'The type of the data item' })
68
+ type?: DataItemType
69
+
70
+ @Field(type => ScalarObject, { nullable: true, description: 'Options associated with the data item type' })
71
+ options?: { [option: string]: any }
72
+
73
+ @Field({
74
+ nullable: true,
75
+ description: 'The grouping logic for data finalize in the given field during periodic task deadlines.'
76
+ })
77
+ stat?: DataItemStatType
78
+
79
+ @Field({
80
+ nullable: true,
81
+ description: 'Aggregation functions to be used in data summarizing logic for a given period.'
82
+ })
83
+ agg?: DataItemStatType
84
+
85
+ @Field({ nullable: true, description: 'The unit of measurement for the data item' })
86
+ unit?: string
87
+
88
+ @Field(type => Int, { nullable: true, description: 'The maximum number of data values allowed for this field' })
89
+ quota?: number
90
+
91
+ @Field(type => ScalarObject, {
92
+ nullable: true,
93
+ description: 'Specifies the valid ranges and parameters for this data item.'
94
+ })
95
+ spec?: { [key: string]: any }
96
+ }
97
+
98
+ @InputType()
99
+ export class DataItemPatch {
100
+ @Field({ nullable: true, description: 'The name of the data item' })
101
+ name?: string
102
+
103
+ @Field({ nullable: true, description: 'A description of the data item' })
104
+ description?: string
105
+
106
+ @Field({
107
+ nullable: true,
108
+ description:
109
+ 'Specifies a key name to be used as a property in a JSON-like object, representing a subfield of a dataset record.'
110
+ })
111
+ tag?: string
112
+
113
+ @Field({
114
+ nullable: true,
115
+ description:
116
+ 'Specifies a grouping identifier for data items with related content, allowing them to be displayed as subgroups within the overall dataset.'
117
+ })
118
+ group?: string
119
+
120
+ @Field(type => DataItemType, { nullable: true, description: 'The type of the data item' })
121
+ type?: DataItemType
122
+
123
+ @Field(type => ScalarObject, { nullable: true, description: 'Options associated with the data item type' })
124
+ options?: { [option: string]: any }
125
+
126
+ @Field({
127
+ nullable: true,
128
+ description: 'The grouping logic for data summarization in the given field during periodic task deadlines.'
129
+ })
130
+ stat?: DataItemStatType
131
+
132
+ @Field({
133
+ nullable: true,
134
+ description: 'Aggregation functions to be used in data summarizing logic for a given period.'
135
+ })
136
+ agg?: DataItemStatType
137
+
138
+ @Field({ nullable: true, description: 'The unit of measurement for the data item' })
139
+ unit?: string
140
+
141
+ @Field(type => Int, { nullable: true, description: 'The maximum number of data values allowed for this field' })
142
+ quota?: number
143
+
144
+ @Field({ nullable: true, description: 'Indicates if the data item is active' })
145
+ active?: boolean
146
+
147
+ @Field({ nullable: true, description: 'Indicates if the data item is hidden' })
148
+ hidden?: boolean
149
+
150
+ @Field(type => ScalarObject, {
151
+ nullable: true,
152
+ description: 'Specifies the valid ranges and parameters for this data item.'
153
+ })
154
+ spec?: { [key: string]: any }
155
+ }
@@ -0,0 +1,552 @@
1
+ import { Arg, Ctx, Directive, Mutation, Resolver } from 'type-graphql'
2
+ import { In } from 'typeorm'
3
+
4
+ import { getRepository } from '@things-factory/shell'
5
+ import { User } from '@things-factory/auth-base'
6
+ import { createAttachment, deleteAttachmentsByRef } from '@things-factory/attachment-base'
7
+ import { ApprovalLineItem, OrgMemberTargetType } from '@things-factory/organization'
8
+ import { Application, CallbackBase, registerSchedule, unregisterSchedule } from '@things-factory/scheduler-client'
9
+
10
+ import { getDataFinalizeCrontabSchedule } from '../../controllers/finalize-data-collection'
11
+ import { DataSet } from './data-set'
12
+ import { DataSetPatch, NewDataSet } from './data-set-type'
13
+ import { issueDataCollectionTask } from '../../controllers/issue-data-collection-task'
14
+
15
+ const crypto = require('crypto')
16
+
17
+ function getApprovalLineValue(patch: NewDataSet | DataSetPatch): ApprovalLineItem[] {
18
+ const { reviewApprovalLine } = patch
19
+
20
+ if (!('reviewApprovalLine' in patch)) {
21
+ /* reviewApprovalLine 언급되지 않았다면, 업데이트하지 않는다. */
22
+ return
23
+ }
24
+
25
+ if (!reviewApprovalLine || !(reviewApprovalLine instanceof Array)) {
26
+ /* approvalLine의 값이 없거나 배열이 아니라면, 클리어시킨다. */
27
+ return null
28
+ }
29
+
30
+ return reviewApprovalLine
31
+ .map(m => {
32
+ return {
33
+ type: m.type,
34
+ approver: m.approver
35
+ } as ApprovalLineItem
36
+ })
37
+ .filter(m => m.type)
38
+ .filter(m => {
39
+ switch (m.type) {
40
+ case OrgMemberTargetType.Employee:
41
+ case OrgMemberTargetType.Department:
42
+ case OrgMemberTargetType.Role:
43
+ return !!m.approver?.id
44
+ default:
45
+ return true
46
+ }
47
+ })
48
+ }
49
+
50
+ function getOutlierApprovalLineValue(patch: NewDataSet | DataSetPatch): ApprovalLineItem[] {
51
+ const { outlierApprovalLine } = patch
52
+
53
+ if (!('outlierApprovalLine' in patch)) {
54
+ /* approvalLine이 언급되지 않았다면, 업데이트하지 않는다. */
55
+ return
56
+ }
57
+
58
+ if (!outlierApprovalLine || !(outlierApprovalLine instanceof Array)) {
59
+ /* approvalLine의 값이 없거나 배열이 아니라면, 클리어시킨다. */
60
+ return null
61
+ }
62
+
63
+ return outlierApprovalLine
64
+ .map(m => {
65
+ return {
66
+ type: m.type,
67
+ approver: m.approver
68
+ } as ApprovalLineItem
69
+ })
70
+ .filter(m => m.type)
71
+ .filter(m => {
72
+ switch (m.type) {
73
+ case OrgMemberTargetType.Employee:
74
+ case OrgMemberTargetType.Department:
75
+ case OrgMemberTargetType.Role:
76
+ return !!m.approver?.id
77
+ default:
78
+ return true
79
+ }
80
+ })
81
+ }
82
+
83
+ @Resolver(DataSet)
84
+ export class DataSetMutation {
85
+ @Directive('@privilege(category: "data-set", privilege: "mutation", domainOwnerGranted: true)')
86
+ @Directive('@transaction')
87
+ @Mutation(returns => DataSet, { description: 'To create new DataSet' })
88
+ async createDataSet(@Arg('dataSet') dataSet: NewDataSet, @Ctx() context: ResolverContext): Promise<DataSet> {
89
+ const { domain, user, tx } = context.state
90
+ const dataSetRepo = getRepository(DataSet, tx)
91
+
92
+ const result = await dataSetRepo.save({
93
+ ...dataSet,
94
+ approvalLine: getApprovalLineValue(dataSet),
95
+ outlierApprovalLineValue: getOutlierApprovalLineValue(dataSet),
96
+ version: 1,
97
+ domain,
98
+ creator: user,
99
+ updater: user
100
+ })
101
+
102
+ await this._createAttachment(context, dataSet.reportTemplate, { ref: result, cuFlag: '+' })
103
+
104
+ return result
105
+ }
106
+
107
+ @Directive('@privilege(category: "data-set", privilege: "mutation", domainOwnerGranted: true)')
108
+ @Directive('@transaction')
109
+ @Mutation(returns => DataSet, { description: 'To modify DataSet information' })
110
+ async updateDataSet(
111
+ @Arg('id') id: string,
112
+ @Arg('patch') patch: DataSetPatch,
113
+ @Ctx() context: ResolverContext
114
+ ): Promise<DataSet> {
115
+ const { domain, user, tx } = context.state
116
+ const dataSetRepo = getRepository(DataSet, tx)
117
+
118
+ const dataSet = await dataSetRepo.findOne({
119
+ where: { domain: { id: domain.id }, id },
120
+ relations: ['domain', 'dataKeySet', 'entryRole', 'supervisoryRole', 'creator', 'updater']
121
+ /* history에 항상 반영될 수 있도록 relations가 있어야 함. */
122
+ })
123
+
124
+ const result = await dataSetRepo.save({
125
+ ...dataSet,
126
+ ...patch,
127
+ approvalLine: getApprovalLineValue(patch),
128
+ outlierApprovalLineValue: getOutlierApprovalLineValue(patch),
129
+ updater: user
130
+ })
131
+
132
+ await this._createAttachment(context, dataSet.reportTemplate, { ref: result, cuFlag: 'M' })
133
+
134
+ return result
135
+ }
136
+
137
+ @Directive('@privilege(category: "data-set", privilege: "mutation", domainOwnerGranted: true)')
138
+ @Directive('@transaction')
139
+ @Mutation(returns => [DataSet], { description: "To modify multiple DataSets' information" })
140
+ async updateMultipleDataSet(
141
+ @Arg('patches', type => [DataSetPatch]) patches: DataSetPatch[],
142
+ @Ctx() context: ResolverContext
143
+ ): Promise<DataSet[]> {
144
+ const { domain, user, tx } = context.state
145
+ const dataSetRepo = getRepository(DataSet, tx)
146
+
147
+ let results = []
148
+ const _createRecords = patches.filter((patch: any) => patch.cuFlag.toUpperCase() === '+')
149
+ const _updateRecords = patches.filter((patch: any) => patch.cuFlag.toUpperCase() === 'M')
150
+
151
+ if (_createRecords.length > 0) {
152
+ const cuFlag = '+'
153
+ for (let i = 0; i < _createRecords.length; i++) {
154
+ const newRecord = _createRecords[i]
155
+
156
+ const result = await dataSetRepo.save({
157
+ ...newRecord,
158
+ approvalLine: getApprovalLineValue(newRecord),
159
+ outlierApprovalLineValue: getOutlierApprovalLineValue(newRecord),
160
+ domain,
161
+ creator: user,
162
+ updater: user
163
+ })
164
+
165
+ await this._createAttachment(context, newRecord.reportTemplate, { ref: result, cuFlag })
166
+
167
+ results.push({
168
+ ...result,
169
+ cuFlag
170
+ })
171
+ }
172
+ }
173
+
174
+ if (_updateRecords.length > 0) {
175
+ const cuFlag = 'M'
176
+ for (let i = 0; i < _updateRecords.length; i++) {
177
+ const updateRecord = _updateRecords[i]
178
+ const dataSet = await dataSetRepo.findOne({
179
+ where: { id: updateRecord.id },
180
+ /* history에 항상 반영될 수 있도록 relations가 있어야 함. */
181
+ relations: ['domain', 'dataKeySet', 'entryRole', 'supervisoryRole', 'creator', 'updater']
182
+ })
183
+
184
+ const result = await dataSetRepo.save({
185
+ ...dataSet,
186
+ ...updateRecord,
187
+ approvalLine: getApprovalLineValue(updateRecord),
188
+ outlierApprovalLineValue: getOutlierApprovalLineValue(updateRecord),
189
+ updater: user
190
+ })
191
+
192
+ await this._createAttachment(context, updateRecord.reportTemplate, { ref: result, cuFlag })
193
+
194
+ results.push({
195
+ ...result,
196
+ cuFlag
197
+ })
198
+ }
199
+ }
200
+
201
+ return results
202
+ }
203
+
204
+ @Directive('@privilege(category: "data-set", privilege: "mutation", domainOwnerGranted: true)')
205
+ @Directive('@transaction')
206
+ @Mutation(returns => Boolean, { description: 'To delete DataSet' })
207
+ async deleteDataSet(@Arg('id') id: string, @Ctx() context: ResolverContext): Promise<boolean> {
208
+ const { domain, tx } = context.state
209
+
210
+ await getRepository(DataSet, tx).delete({ domain: { id: domain.id }, id })
211
+ await deleteAttachmentsByRef(null, { refBys: [`report-${id}`] }, context)
212
+
213
+ return true
214
+ }
215
+
216
+ @Directive('@privilege(category: "data-set", privilege: "mutation", domainOwnerGranted: true)')
217
+ @Directive('@transaction')
218
+ @Mutation(returns => Boolean, { description: 'To delete multiple dataSets' })
219
+ async deleteDataSets(@Arg('ids', type => [String]) ids: string[], @Ctx() context: ResolverContext): Promise<boolean> {
220
+ const { domain, tx } = context.state
221
+
222
+ await getRepository(DataSet, tx).delete({
223
+ domain: { id: domain.id },
224
+ id: In(ids)
225
+ })
226
+
227
+ await deleteAttachmentsByRef(null, { refBys: ids.map(id => `report-${id}`) }, context)
228
+
229
+ return true
230
+ }
231
+
232
+ @Directive('@transaction')
233
+ @Mutation(returns => Boolean, { description: 'To issue data-collection task for the given dataset' })
234
+ async issueDataCollection(@Arg('dataSetId') dataSetId: string, @Ctx() context: ResolverContext): Promise<boolean> {
235
+ const { domain, tx, user } = context.state
236
+
237
+ const dataSet = await getRepository(DataSet, tx).findOne({
238
+ where: {
239
+ domain: {
240
+ id: In([domain.id, domain.parentId].filter(Boolean))
241
+ },
242
+ id: dataSetId
243
+ }
244
+ })
245
+
246
+ const { supervisoryRoleId, entryRoleId } = dataSet
247
+ const allowedRoles = [supervisoryRoleId, entryRoleId].filter(Boolean)
248
+
249
+ const me = await getRepository(User, tx).findOne({
250
+ where: { id: user.id },
251
+ relations: ['roles']
252
+ })
253
+
254
+ if (!me.roles.find(role => allowedRoles.includes(role.id))) {
255
+ throw new Error(`You don't have permission to issue data collection task for this dataset.`)
256
+ }
257
+
258
+ const activityInstance = await issueDataCollectionTask(domain.id, dataSet.id, context)
259
+
260
+ return !!activityInstance
261
+ }
262
+
263
+ @Directive('@transaction')
264
+ @Mutation(returns => DataSet, { description: 'To start data collection schedule for the given dataset' })
265
+ async startDataCollectionSchedule(
266
+ @Arg('dataSetId') dataSetId: string,
267
+ @Ctx() context: ResolverContext
268
+ ): Promise<DataSet> {
269
+ const { domain, tx } = context.state
270
+
271
+ var repository = getRepository(DataSet, tx)
272
+ var dataSet = await repository.findOne({
273
+ where: { domain: { id: domain.id }, id: dataSetId }
274
+ })
275
+
276
+ if (!dataSet) {
277
+ throw new Error(
278
+ context.t('error.data-set not found', {
279
+ dataSet: dataSetId
280
+ })
281
+ )
282
+ }
283
+
284
+ try {
285
+ var handle = await registerSchedule({
286
+ name: dataSet.name,
287
+ client: {
288
+ application: Application,
289
+ group: `${domain.id}`,
290
+ type: 'data-set',
291
+ key: dataSet.id,
292
+ operation: 'schedule'
293
+ },
294
+ type: 'cron',
295
+ schedule: dataSet.schedule,
296
+ timezone: dataSet.timezone,
297
+ task: {
298
+ type: 'rest',
299
+ connection: {
300
+ host: `${CallbackBase}/callback-schedule-for-dataset`,
301
+ headers: {
302
+ 'Content-Type': 'application/json',
303
+ accept: '*/*'
304
+ }
305
+ },
306
+ data: {
307
+ domainId: domain.id,
308
+ dataSetId
309
+ },
310
+ history_check: true,
311
+ failed_policy: 'retry_dlq',
312
+ max_retry_count: 3,
313
+ retry_period: 60
314
+ }
315
+ })
316
+
317
+ return await repository.save({
318
+ ...dataSet,
319
+ scheduleId: handle
320
+ })
321
+ } catch (err) {
322
+ console.error('startDataCollectionSchedule', err)
323
+ }
324
+ }
325
+
326
+ @Directive('@transaction')
327
+ @Mutation(returns => DataSet, {
328
+ nullable: true,
329
+ description: 'To stop data collection schedule for the given dataset'
330
+ })
331
+ async stopDataCollectionSchedule(
332
+ @Arg('dataSetId') dataSetId: string,
333
+ @Ctx() context: ResolverContext
334
+ ): Promise<DataSet | undefined> {
335
+ const { domain, tx } = context.state
336
+
337
+ var repository = getRepository(DataSet, tx)
338
+ var dataSet = await repository.findOne({
339
+ where: { domain: { id: domain.id }, id: dataSetId }
340
+ })
341
+
342
+ if (!dataSet) {
343
+ throw new Error(
344
+ context.t('error.data-set not found', {
345
+ dataSet: dataSetId
346
+ })
347
+ )
348
+ }
349
+
350
+ try {
351
+ await unregisterSchedule(dataSet.scheduleId)
352
+
353
+ return await repository.save({
354
+ ...dataSet,
355
+ scheduleId: null
356
+ })
357
+ } catch (err) {
358
+ console.error('stopDataCollectionSchedule', err)
359
+ }
360
+ }
361
+
362
+ @Directive('@transaction')
363
+ @Mutation(returns => DataSet, { description: 'To start data summary schedule for the given dataset' })
364
+ async startDataSummarySchedule(
365
+ @Arg('dataSetId') dataSetId: string,
366
+ @Ctx() context: ResolverContext
367
+ ): Promise<DataSet> {
368
+ const { domain, tx } = context.state
369
+
370
+ var repository = getRepository(DataSet, tx)
371
+ var dataSet = await repository.findOne({
372
+ where: { domain: { id: domain.id }, id: dataSetId }
373
+ })
374
+
375
+ if (!dataSet) {
376
+ throw new Error(
377
+ context.t('error.data-set not found', {
378
+ dataSet: dataSetId
379
+ })
380
+ )
381
+ }
382
+
383
+ if (!dataSet.summaryPeriod) {
384
+ throw new Error(
385
+ context.t('error.data-set summary-period not set', {
386
+ dataSet: dataSetId
387
+ })
388
+ )
389
+ }
390
+
391
+ const schedule = await getDataFinalizeCrontabSchedule(dataSet, context)
392
+
393
+ try {
394
+ var handle = await registerSchedule({
395
+ name: dataSet.name,
396
+ client: {
397
+ application: Application,
398
+ group: `${domain.id}`,
399
+ type: 'data-set',
400
+ key: dataSet.id,
401
+ operation: 'summary'
402
+ },
403
+ type: 'cron',
404
+ schedule,
405
+ timezone: dataSet.timezone,
406
+ task: {
407
+ type: 'rest',
408
+ connection: {
409
+ host: `${CallbackBase}/callback-schedule-for-dataset-summary`,
410
+ headers: {
411
+ 'Content-Type': 'application/json',
412
+ accept: '*/*'
413
+ }
414
+ },
415
+ data: {
416
+ domainId: domain.id,
417
+ dataSetId
418
+ },
419
+ history_check: true,
420
+ failed_policy: 'retry_dlq',
421
+ max_retry_count: 3,
422
+ retry_period: 60
423
+ }
424
+ })
425
+
426
+ return await repository.save({
427
+ ...dataSet,
428
+ summarySchedule: schedule,
429
+ summaryScheduleId: handle
430
+ })
431
+ } catch (err) {
432
+ console.error('startDataSummarySchedule', err)
433
+ }
434
+ }
435
+
436
+ @Directive('@transaction')
437
+ @Mutation(returns => DataSet, {
438
+ nullable: true,
439
+ description: 'To stop data summary schedule for the given dataset'
440
+ })
441
+ async stopDataSummarySchedule(
442
+ @Arg('dataSetId') dataSetId: string,
443
+ @Ctx() context: ResolverContext
444
+ ): Promise<DataSet | undefined> {
445
+ const { domain, tx } = context.state
446
+
447
+ var repository = getRepository(DataSet, tx)
448
+ var dataSet = await repository.findOne({
449
+ where: { domain: { id: domain.id }, id: dataSetId }
450
+ })
451
+
452
+ if (!dataSet) {
453
+ throw new Error(
454
+ context.t('error.data-set not found', {
455
+ dataSet: dataSetId
456
+ })
457
+ )
458
+ }
459
+
460
+ try {
461
+ await unregisterSchedule(dataSet.summaryScheduleId)
462
+
463
+ return await repository.save({
464
+ ...dataSet,
465
+ summarySchedule: null,
466
+ summaryScheduleId: null
467
+ })
468
+ } catch (err) {
469
+ console.error('stopDataSummarySchedule', err)
470
+ }
471
+ }
472
+
473
+ @Directive('@privilege(category: "data-set", privilege: "mutation", domainOwnerGranted: true)')
474
+ @Directive('@transaction')
475
+ @Mutation(returns => Boolean, { description: 'To import multiple data-sets' })
476
+ async importDataSets(
477
+ @Arg('dataSets', type => [DataSetPatch]) dataSets: DataSet[],
478
+ @Ctx() context: ResolverContext
479
+ ): Promise<boolean> {
480
+ const { domain, tx } = context.state
481
+ const dataSetRepo = getRepository(DataSet, tx)
482
+
483
+ await Promise.all(
484
+ dataSets.map(async (dataSet: DataSet) => {
485
+ const createdDataSet: DataSet = await dataSetRepo.save({
486
+ domain,
487
+ ...dataSet
488
+ })
489
+ })
490
+ )
491
+
492
+ return true
493
+ }
494
+
495
+ @Directive('@privilege(category: "data-set", privilege: "mutation", domainOwnerGranted: true)')
496
+ @Directive('@transaction')
497
+ @Mutation(returns => [DataSet], { description: 'To copy multiple data-sets' })
498
+ async copyDataSets(@Arg('ids', type => [String]) ids: string[], @Ctx() context: ResolverContext): Promise<DataSet[]> {
499
+ const { domain, user, tx } = context.state
500
+ const dataSetRepo = getRepository(DataSet, tx)
501
+
502
+ const originals = await dataSetRepo.find({
503
+ where: {
504
+ id: In(ids),
505
+ domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }
506
+ },
507
+ relations: ['domain', 'supervisoryRole', 'entryRole', 'dataKeySet']
508
+ })
509
+
510
+ if (originals.length == 0) {
511
+ return []
512
+ }
513
+
514
+ var newCopys = originals.map(dataSet => {
515
+ let dataSetId = crypto.randomUUID()
516
+
517
+ return {
518
+ ...dataSet,
519
+ id: dataSetId,
520
+ name: dataSet.name + ' (' + dataSetId + ')',
521
+ active: false,
522
+ version: 1,
523
+ domain,
524
+ creator: user,
525
+ updater: user,
526
+ updatedAt: undefined,
527
+ createdAt: undefined
528
+ }
529
+ })
530
+
531
+ var copiedDataSets = await dataSetRepo.save(newCopys)
532
+
533
+ return copiedDataSets
534
+ }
535
+
536
+ async _createAttachment(context, attachment, { ref, cuFlag }) {
537
+ if (attachment) {
538
+ cuFlag == 'M' && (await deleteAttachmentsByRef(null, { refBys: [ref.id] }, context))
539
+ await createAttachment(
540
+ null,
541
+ {
542
+ attachment: {
543
+ file: attachment,
544
+ refType: `${DataSet.name}-report-template`,
545
+ refBy: ref.id
546
+ }
547
+ },
548
+ context
549
+ )
550
+ }
551
+ }
552
+ }