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

Sign up to get free protection for your applications and to get access to all the features.
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
+ }