@things-factory/dataset 8.0.0 → 9.0.0-beta.3
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.
- package/dist-client/activities/activity-data-review-edit.d.ts +5 -1
- package/dist-client/activities/activity-data-review-edit.js +143 -5
- package/dist-client/activities/activity-data-review-edit.js.map +1 -1
- package/dist-client/pages/data-entry/data-entry-list-page.js +2 -2
- package/dist-client/pages/data-entry/data-entry-list-page.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/activities/activity-data-review.js +18 -5
- package/dist-server/activities/activity-data-review.js.map +1 -1
- package/dist-server/activities/activity-ooc-review.js +13 -52
- package/dist-server/activities/activity-ooc-review.js.map +1 -1
- package/dist-server/controllers/create-data-ooc.d.ts +4 -0
- package/dist-server/controllers/create-data-ooc.js +65 -0
- package/dist-server/controllers/create-data-ooc.js.map +1 -0
- package/dist-server/controllers/create-data-sample.js +4 -94
- package/dist-server/controllers/create-data-sample.js.map +1 -1
- package/dist-server/controllers/index.d.ts +3 -0
- package/dist-server/controllers/index.js +3 -0
- package/dist-server/controllers/index.js.map +1 -1
- package/dist-server/controllers/issue-ooc-resolve.d.ts +3 -0
- package/dist-server/controllers/issue-ooc-resolve.js +49 -0
- package/dist-server/controllers/issue-ooc-resolve.js.map +1 -0
- package/dist-server/controllers/issue-ooc-review.d.ts +3 -0
- package/dist-server/controllers/issue-ooc-review.js +47 -0
- package/dist-server/controllers/issue-ooc-review.js.map +1 -0
- package/dist-server/service/data-sample/data-sample-query.d.ts +1 -1
- package/dist-server/service/data-sample/data-sample-query.js +3 -3
- package/dist-server/service/data-sample/data-sample-query.js.map +1 -1
- package/dist-server/service/index.d.ts +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +26 -26
- package/translations/en.json +3 -0
- package/translations/ja.json +3 -0
- package/translations/ko.json +3 -0
- package/translations/ms.json +3 -0
- package/translations/zh.json +3 -0
- package/client/activities/activity-data-collect-edit.ts +0 -105
- package/client/activities/activity-data-collect-view.ts +0 -91
- package/client/activities/activity-data-review-edit.ts +0 -133
- package/client/activities/activity-data-review-view.ts +0 -145
- package/client/activities/activity-ooc-resolve-edit.ts +0 -195
- package/client/activities/activity-ooc-resolve-view.ts +0 -143
- package/client/activities/activity-ooc-review-edit.ts +0 -173
- package/client/activities/activity-ooc-review-view.ts +0 -129
- package/client/bootstrap.ts +0 -35
- package/client/components/data-entry-form.ts +0 -109
- package/client/index.ts +0 -1
- package/client/pages/data-archive/data-archive-list-page.ts +0 -277
- package/client/pages/data-archive/data-archive-request-popup.ts +0 -177
- package/client/pages/data-entry/data-entry-list-page.ts +0 -464
- package/client/pages/data-key-set/data-key-item-list.ts +0 -183
- package/client/pages/data-key-set/data-key-set-importer.ts +0 -89
- package/client/pages/data-key-set/data-key-set-list-page.ts +0 -413
- package/client/pages/data-ooc/data-ooc-list-page.ts +0 -549
- package/client/pages/data-ooc/data-ooc-page.ts +0 -164
- package/client/pages/data-ooc/data-ooc-view.ts +0 -236
- package/client/pages/data-ooc/data-oocs-page.ts +0 -200
- package/client/pages/data-report/data-report-embed-page.ts +0 -108
- package/client/pages/data-report/data-report-list-page.ts +0 -454
- package/client/pages/data-report/data-report-samples-page.ts +0 -174
- package/client/pages/data-report/jasper-report-oocs-page.ts +0 -110
- package/client/pages/data-report/jasper-report-samples-crosstab-page.ts +0 -110
- package/client/pages/data-report/jasper-report-samples-page.ts +0 -110
- package/client/pages/data-sample/data-sample-list-page.ts +0 -442
- package/client/pages/data-sample/data-sample-page.ts +0 -55
- package/client/pages/data-sample/data-sample-search-page.ts +0 -424
- package/client/pages/data-sample/data-sample-view.ts +0 -292
- package/client/pages/data-sample/data-samples-page.ts +0 -249
- package/client/pages/data-sensor/data-sensor-list-page.ts +0 -456
- package/client/pages/data-set/data-item-list.ts +0 -304
- package/client/pages/data-set/data-set-importer.ts +0 -89
- package/client/pages/data-set/data-set-list-page.ts +0 -1078
- package/client/pages/data-summary/data-summary-list-page.ts +0 -363
- package/client/pages/data-summary/data-summary-period-page.ts +0 -439
- package/client/pages/data-summary/data-summary-search-page.ts +0 -426
- package/client/pages/data-summary/data-summary-view.ts +0 -133
- package/client/route.ts +0 -91
- package/client/tsconfig.json +0 -13
- package/server/activities/activity-data-collect.ts +0 -100
- package/server/activities/activity-data-review.ts +0 -82
- package/server/activities/activity-ooc-resolve.ts +0 -123
- package/server/activities/activity-ooc-review.ts +0 -144
- package/server/activities/index.ts +0 -11
- package/server/controllers/create-data-sample.ts +0 -426
- package/server/controllers/data-use-case.ts +0 -98
- package/server/controllers/finalize-data-collection.ts +0 -388
- package/server/controllers/index.ts +0 -3
- package/server/controllers/issue-data-collection-task.ts +0 -70
- package/server/controllers/jasper-report.ts +0 -186
- package/server/controllers/query-data-summary-by-period.ts +0 -178
- package/server/controllers/shiny-report.ts +0 -54
- package/server/engine/index.ts +0 -1
- package/server/engine/task/create-data-sample.ts +0 -100
- package/server/engine/task/index.ts +0 -2
- package/server/engine/task/issue-collect-data.ts +0 -45
- package/server/index.ts +0 -8
- package/server/routes.ts +0 -188
- package/server/service/data-archive/data-archive-mutation.ts +0 -273
- package/server/service/data-archive/data-archive-query.ts +0 -58
- package/server/service/data-archive/data-archive-type.ts +0 -48
- package/server/service/data-archive/data-archive.ts +0 -69
- package/server/service/data-archive/index.ts +0 -6
- package/server/service/data-key-set/data-key-item-type.ts +0 -31
- package/server/service/data-key-set/data-key-set-mutation.ts +0 -201
- package/server/service/data-key-set/data-key-set-query.ts +0 -68
- package/server/service/data-key-set/data-key-set-type.ts +0 -70
- package/server/service/data-key-set/data-key-set.ts +0 -86
- package/server/service/data-key-set/index.ts +0 -6
- package/server/service/data-ooc/data-ooc-mutation.ts +0 -154
- package/server/service/data-ooc/data-ooc-query.ts +0 -106
- package/server/service/data-ooc/data-ooc-subscription.ts +0 -48
- package/server/service/data-ooc/data-ooc-type.ts +0 -71
- package/server/service/data-ooc/data-ooc.ts +0 -259
- package/server/service/data-ooc/index.ts +0 -7
- package/server/service/data-sample/data-sample-mutation.ts +0 -18
- package/server/service/data-sample/data-sample-query.ts +0 -215
- package/server/service/data-sample/data-sample-type.ts +0 -47
- package/server/service/data-sample/data-sample.ts +0 -193
- package/server/service/data-sample/index.ts +0 -6
- package/server/service/data-sensor/data-sensor-mutation.ts +0 -116
- package/server/service/data-sensor/data-sensor-query.ts +0 -76
- package/server/service/data-sensor/data-sensor-type.ts +0 -104
- package/server/service/data-sensor/data-sensor.ts +0 -126
- package/server/service/data-sensor/index.ts +0 -6
- package/server/service/data-set/data-item-type.ts +0 -155
- package/server/service/data-set/data-set-mutation.ts +0 -552
- package/server/service/data-set/data-set-query.ts +0 -461
- package/server/service/data-set/data-set-type.ts +0 -204
- package/server/service/data-set/data-set.ts +0 -326
- package/server/service/data-set/index.ts +0 -6
- package/server/service/data-set-history/data-set-history-query.ts +0 -126
- package/server/service/data-set-history/data-set-history-type.ts +0 -12
- package/server/service/data-set-history/data-set-history.ts +0 -217
- package/server/service/data-set-history/event-subscriber.ts +0 -17
- package/server/service/data-set-history/index.ts +0 -7
- package/server/service/data-spec/data-spec-manager.ts +0 -21
- package/server/service/data-spec/data-spec-query.ts +0 -21
- package/server/service/data-spec/data-spec.ts +0 -45
- package/server/service/data-spec/index.ts +0 -5
- package/server/service/data-summary/data-summary-mutation.ts +0 -45
- package/server/service/data-summary/data-summary-query.ts +0 -179
- package/server/service/data-summary/data-summary-type.ts +0 -86
- package/server/service/data-summary/data-summary.ts +0 -170
- package/server/service/data-summary/index.ts +0 -7
- package/server/service/index.ts +0 -57
- package/server/tsconfig.json +0 -10
- package/server/utils/config-resolver.ts +0 -29
- package/server/utils/index.ts +0 -1
|
@@ -1,388 +0,0 @@
|
|
|
1
|
-
const statistics = require('simple-statistics')
|
|
2
|
-
const deepClone = require('lodash/cloneDeep')
|
|
3
|
-
|
|
4
|
-
import moment from 'moment-timezone'
|
|
5
|
-
import { In } from 'typeorm'
|
|
6
|
-
|
|
7
|
-
import { Sorting, getQueryBuilderFromListParams } from '@things-factory/shell'
|
|
8
|
-
import { logger } from '@things-factory/env'
|
|
9
|
-
import {
|
|
10
|
-
getDateRangeForWorkDate,
|
|
11
|
-
getDateRangeForWorkShift,
|
|
12
|
-
getLatestWorkDateAndShift,
|
|
13
|
-
getSummaryScheduleForWorkDate,
|
|
14
|
-
getSummaryScheduleForWorkShift
|
|
15
|
-
} from '@things-factory/work-shift'
|
|
16
|
-
|
|
17
|
-
import { DataSample } from '../service/data-sample/data-sample'
|
|
18
|
-
import { DataSet, DataSetSummaryPeriodType } from '../service/data-set/data-set'
|
|
19
|
-
|
|
20
|
-
import { DataSummary } from '../service/data-summary/data-summary'
|
|
21
|
-
import { DataKeyItem } from '../service/data-key-set/data-key-item-type'
|
|
22
|
-
|
|
23
|
-
import { DataItem } from 'service'
|
|
24
|
-
|
|
25
|
-
const STAT_FUNCTION_MAP = {
|
|
26
|
-
sum: 'sum',
|
|
27
|
-
mean: 'mean',
|
|
28
|
-
stddev: 'standardDeviation',
|
|
29
|
-
variance: 'variance',
|
|
30
|
-
min: 'min',
|
|
31
|
-
max: 'max',
|
|
32
|
-
range: 'range',
|
|
33
|
-
median: 'median',
|
|
34
|
-
mode: 'mode'
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
const compareKeys = (dataKeyItems: DataKeyItem[], summary: Partial<DataSummary>, sample: DataSample): boolean => {
|
|
38
|
-
return dataKeyItems.every((item, index) => {
|
|
39
|
-
const prop = `key0${index + 1}`
|
|
40
|
-
return sample[prop] === summary[prop]
|
|
41
|
-
})
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const buildKeysFromSample = (dataKeyItems: DataKeyItem[], sample: DataSample): Partial<DataSummary> => {
|
|
45
|
-
return dataKeyItems.reduce((sum, item, index) => {
|
|
46
|
-
const prop = `key0${index + 1}`
|
|
47
|
-
sum[prop] = sample[prop]
|
|
48
|
-
|
|
49
|
-
return sum
|
|
50
|
-
}, {} as Partial<DataSummary>)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
const buildKeySortingList = (dataKeyItems: DataKeyItem[]): Sorting[] => {
|
|
54
|
-
return dataKeyItems.reduce((sum, item, index) => {
|
|
55
|
-
const name = `key0${index + 1}`
|
|
56
|
-
sum.push({ name, desc: true })
|
|
57
|
-
return sum
|
|
58
|
-
}, [])
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const calculateSummary = (dataItems: DataItem[], base: { [tag: string]: any[] }) => {
|
|
62
|
-
return dataItems.reduce((summary, item) => {
|
|
63
|
-
const tag = item.tag
|
|
64
|
-
|
|
65
|
-
const data = base[tag]
|
|
66
|
-
.flat(Infinity)
|
|
67
|
-
.map(Number)
|
|
68
|
-
.filter(item => !isNaN(item))
|
|
69
|
-
|
|
70
|
-
if (data.length > 0) {
|
|
71
|
-
try {
|
|
72
|
-
switch (item.stat) {
|
|
73
|
-
case 'range':
|
|
74
|
-
summary[tag] = statistics.max(data) - statistics.min(data)
|
|
75
|
-
break
|
|
76
|
-
|
|
77
|
-
default:
|
|
78
|
-
const functionName = STAT_FUNCTION_MAP[item.stat]
|
|
79
|
-
summary[tag] = (functionName && statistics[functionName](data)) || ''
|
|
80
|
-
}
|
|
81
|
-
} catch (err) {
|
|
82
|
-
summary[tag] = null
|
|
83
|
-
console.error(err)
|
|
84
|
-
}
|
|
85
|
-
} else {
|
|
86
|
-
summary[tag] = null
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
return summary
|
|
90
|
-
}, {})
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const fillSummaryResult = (dataSummary: Partial<DataSummary>, dataItems: DataItem[], base: { [tag: string]: any[] }): void => {
|
|
94
|
-
const summary = calculateSummary(dataItems, base)
|
|
95
|
-
|
|
96
|
-
dataSummary.summary = summary
|
|
97
|
-
dataItems.slice(0, 4).forEach((dataItem, idx) => {
|
|
98
|
-
const value = Number(summary[dataItem.tag])
|
|
99
|
-
dataSummary[`data0${idx + 1}`] = isNaN(value) ? null : value
|
|
100
|
-
})
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
async function getLatestTimesForPeriod(periodType: DataSetSummaryPeriodType, context: ResolverContext): Promise<{ date?: string; period?: string; range: Date[] }> {
|
|
104
|
-
const { domain } = context.state
|
|
105
|
-
const now = moment()
|
|
106
|
-
|
|
107
|
-
if (periodType == DataSetSummaryPeriodType.Hour) {
|
|
108
|
-
const begin = now.clone().subtract(1, 'hour').startOf('hour')
|
|
109
|
-
const end = now.clone().startOf('hour')
|
|
110
|
-
const date = begin.clone().tz(domain.timezone)
|
|
111
|
-
|
|
112
|
-
return {
|
|
113
|
-
date: date.format('YYYY-MM-DD'),
|
|
114
|
-
period: date.format('HH'),
|
|
115
|
-
range: [begin.toDate(), end.toDate()]
|
|
116
|
-
}
|
|
117
|
-
} else if (periodType == DataSetSummaryPeriodType.WorkShift) {
|
|
118
|
-
const { workDate, workShift, shiftRange } = await getLatestWorkDateAndShift(domain, new Date())
|
|
119
|
-
|
|
120
|
-
return { date: workDate, period: workShift, range: shiftRange }
|
|
121
|
-
} else if (periodType == DataSetSummaryPeriodType.WorkDate) {
|
|
122
|
-
const { workDate, dateRange } = await getLatestWorkDateAndShift(domain, new Date())
|
|
123
|
-
|
|
124
|
-
return { date: workDate, range: dateRange }
|
|
125
|
-
} else if (periodType == DataSetSummaryPeriodType.Day) {
|
|
126
|
-
const begin = now.clone().subtract(1, 'day').startOf('day')
|
|
127
|
-
const end = now.clone().startOf('day')
|
|
128
|
-
const date = begin.clone().tz(domain.timezone)
|
|
129
|
-
|
|
130
|
-
return {
|
|
131
|
-
date: date.format('YYYY-MM-DD'),
|
|
132
|
-
range: [begin.toDate(), end.toDate()]
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
async function getTimesForPeriod(
|
|
138
|
-
periodType: DataSetSummaryPeriodType,
|
|
139
|
-
date: string,
|
|
140
|
-
period: string,
|
|
141
|
-
context: ResolverContext
|
|
142
|
-
): Promise<{ date?: string; period?: string; range: Date[] }> {
|
|
143
|
-
const { domain } = context.state
|
|
144
|
-
|
|
145
|
-
if (periodType == DataSetSummaryPeriodType.Hour) {
|
|
146
|
-
const theDate = moment.tz(`${date} ${period}:00:00`, 'YYYY-MM-DD HH:mm:ss', domain.timezone)
|
|
147
|
-
|
|
148
|
-
const begin = theDate.clone().startOf('hour').toDate()
|
|
149
|
-
const end = theDate.clone().add(+1, 'hour').startOf('hour').toDate()
|
|
150
|
-
|
|
151
|
-
return {
|
|
152
|
-
date,
|
|
153
|
-
period,
|
|
154
|
-
range: [begin, end]
|
|
155
|
-
}
|
|
156
|
-
} else if (periodType == DataSetSummaryPeriodType.WorkShift) {
|
|
157
|
-
const range = await getDateRangeForWorkShift(domain, date, period)
|
|
158
|
-
|
|
159
|
-
return { date, period, range }
|
|
160
|
-
} else if (periodType == DataSetSummaryPeriodType.WorkDate) {
|
|
161
|
-
const range = await getDateRangeForWorkDate(domain, date)
|
|
162
|
-
|
|
163
|
-
return { date, range }
|
|
164
|
-
} else if (periodType == DataSetSummaryPeriodType.Day) {
|
|
165
|
-
const theDate = moment.tz(`${date} 00:00:00`, 'YYYY-MM-DD HH:mm:ss', domain.timezone)
|
|
166
|
-
|
|
167
|
-
const begin = theDate.clone().startOf('day').toDate()
|
|
168
|
-
const end = theDate.clone().add(1, 'day').startOf('day').toDate()
|
|
169
|
-
|
|
170
|
-
return {
|
|
171
|
-
date: moment(begin).tz(domain.timezone).format('YYYY-MM-DD'),
|
|
172
|
-
range: [begin, end]
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
export async function getDataFinalizeCrontabSchedule(dataSet: DataSet, context: ResolverContext): Promise<string> {
|
|
178
|
-
const { domain, user, tx } = context.state
|
|
179
|
-
|
|
180
|
-
try {
|
|
181
|
-
const { summaryPeriod } = dataSet
|
|
182
|
-
|
|
183
|
-
if (summaryPeriod == DataSetSummaryPeriodType.Hour) {
|
|
184
|
-
return '0 5 * * * *'
|
|
185
|
-
} else if (summaryPeriod == DataSetSummaryPeriodType.WorkShift) {
|
|
186
|
-
return await getSummaryScheduleForWorkShift(domain)
|
|
187
|
-
} else if (summaryPeriod == DataSetSummaryPeriodType.WorkDate) {
|
|
188
|
-
return await getSummaryScheduleForWorkDate(domain)
|
|
189
|
-
} else if (summaryPeriod == DataSetSummaryPeriodType.Day) {
|
|
190
|
-
return '0 10 0 * * *'
|
|
191
|
-
}
|
|
192
|
-
} catch (err) {
|
|
193
|
-
console.error(err)
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
export async function finalizeLatestDataCollection(dataSetId: string, context: ResolverContext): Promise<boolean> {
|
|
198
|
-
const { domain, user, tx } = context.state
|
|
199
|
-
|
|
200
|
-
try {
|
|
201
|
-
const dataSet = await tx.getRepository(DataSet).findOne({
|
|
202
|
-
where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, id: dataSetId },
|
|
203
|
-
relations: ['dataKeySet']
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
const dataKeyItems = dataSet.dataKeySet?.dataKeyItems || []
|
|
207
|
-
const dataItems = dataSet.dataItems.filter(item => item.stat)
|
|
208
|
-
const initialSummary = dataItems.reduce((sum, item) => {
|
|
209
|
-
sum[item.tag] = []
|
|
210
|
-
return sum
|
|
211
|
-
}, {})
|
|
212
|
-
|
|
213
|
-
const { date, period, range } = await getLatestTimesForPeriod(dataSet.summaryPeriod, context)
|
|
214
|
-
const limit = 100
|
|
215
|
-
var page = 1
|
|
216
|
-
|
|
217
|
-
var summaries: Partial<DataSummary>[] = []
|
|
218
|
-
var summary: Partial<DataSummary>
|
|
219
|
-
|
|
220
|
-
do {
|
|
221
|
-
const samples = await getQueryBuilderFromListParams({
|
|
222
|
-
repository: tx.getRepository(DataSample),
|
|
223
|
-
domain,
|
|
224
|
-
params: {
|
|
225
|
-
filters: [{ name: 'dataSetId', operator: 'eq', value: dataSetId }],
|
|
226
|
-
pagination: { page, limit },
|
|
227
|
-
sortings: [...buildKeySortingList(dataKeyItems), { name: 'collectedAt', desc: true }]
|
|
228
|
-
},
|
|
229
|
-
alias: 'datasample'
|
|
230
|
-
})
|
|
231
|
-
// The 'Between' operator includes the 'to' time in the filtering, making it unsuitable for the desired use case.
|
|
232
|
-
// .andWhere({ collectedAt: Between.apply(null, range) })
|
|
233
|
-
.andWhere('datasample.collectedAt >= :from', { from: range[0] })
|
|
234
|
-
.andWhere('datasample.collectedAt < :to', { to: range[1] })
|
|
235
|
-
.getMany()
|
|
236
|
-
|
|
237
|
-
for (const sample of samples) {
|
|
238
|
-
if (!summary || !compareKeys(dataKeyItems, summary, sample)) {
|
|
239
|
-
if (summary) {
|
|
240
|
-
fillSummaryResult(summary, dataItems, summary.summary)
|
|
241
|
-
summaries.push(summary)
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
summary = {
|
|
245
|
-
domain,
|
|
246
|
-
name: dataSet.name,
|
|
247
|
-
description: dataSet.description,
|
|
248
|
-
date,
|
|
249
|
-
period,
|
|
250
|
-
dataSet,
|
|
251
|
-
...buildKeysFromSample(dataKeyItems, sample),
|
|
252
|
-
count: 0,
|
|
253
|
-
countOoc: 0,
|
|
254
|
-
countOos: 0,
|
|
255
|
-
summary: deepClone(initialSummary),
|
|
256
|
-
updater: user,
|
|
257
|
-
creator: user
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
summary.count++
|
|
262
|
-
sample.ooc && summary.countOoc++
|
|
263
|
-
sample.oos && summary.countOos++
|
|
264
|
-
|
|
265
|
-
dataItems.forEach(item => {
|
|
266
|
-
summary.summary[item.tag].push(sample.data[item.tag])
|
|
267
|
-
})
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
if (samples.length < limit) {
|
|
271
|
-
if (summary) {
|
|
272
|
-
fillSummaryResult(summary, dataItems, summary.summary)
|
|
273
|
-
summaries.push(summary)
|
|
274
|
-
}
|
|
275
|
-
break
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
page++
|
|
279
|
-
} while (true)
|
|
280
|
-
|
|
281
|
-
tx.getRepository(DataSummary).upsert(summaries, ['domain', 'dataSet', 'key01', 'key02', 'key03', 'key04', 'key05', 'date', 'period'])
|
|
282
|
-
|
|
283
|
-
return true
|
|
284
|
-
} catch (e) {
|
|
285
|
-
logger.error(e)
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
return false
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
export async function finalizeDataCollection(dataSetId: string, date: string, period: string, context: ResolverContext): Promise<boolean> {
|
|
292
|
-
const { domain, user, tx } = context.state
|
|
293
|
-
|
|
294
|
-
try {
|
|
295
|
-
const dataSet =
|
|
296
|
-
dataSetId &&
|
|
297
|
-
(await tx.getRepository(DataSet).findOne({
|
|
298
|
-
where: { domain: In([domain.id, domain.parentId].filter(Boolean)), id: dataSetId },
|
|
299
|
-
relations: ['dataKeySet']
|
|
300
|
-
}))
|
|
301
|
-
|
|
302
|
-
const dataKeyItems = dataSet.dataKeySet?.dataKeyItems || []
|
|
303
|
-
const dataItems = dataSet.dataItems.filter(item => item.stat)
|
|
304
|
-
const initialSummary = dataItems.reduce((sum, item) => {
|
|
305
|
-
sum[item.tag] = []
|
|
306
|
-
return sum
|
|
307
|
-
}, {})
|
|
308
|
-
|
|
309
|
-
const times = await getTimesForPeriod(dataSet.summaryPeriod, date, period, context)
|
|
310
|
-
const range = times.range
|
|
311
|
-
period = times.period
|
|
312
|
-
|
|
313
|
-
const limit = 100
|
|
314
|
-
var page = 1
|
|
315
|
-
|
|
316
|
-
var summaries: Partial<DataSummary>[] = []
|
|
317
|
-
var summary: Partial<DataSummary>
|
|
318
|
-
|
|
319
|
-
do {
|
|
320
|
-
const samples = await getQueryBuilderFromListParams({
|
|
321
|
-
repository: tx.getRepository(DataSample),
|
|
322
|
-
params: {
|
|
323
|
-
filters: [{ name: 'dataSetId', operator: 'eq', value: dataSetId }],
|
|
324
|
-
pagination: { page, limit },
|
|
325
|
-
sortings: [...buildKeySortingList(dataKeyItems), { name: 'collectedAt', desc: true }]
|
|
326
|
-
},
|
|
327
|
-
domain,
|
|
328
|
-
alias: 'datasample'
|
|
329
|
-
})
|
|
330
|
-
// The 'Between' operator includes the 'to' time in the filtering, making it unsuitable for the desired use case.
|
|
331
|
-
// .andWhere({ collectedAt: Between.apply(null, range) })
|
|
332
|
-
.andWhere('datasample.collectedAt >= :from', { from: range[0] })
|
|
333
|
-
.andWhere('datasample.collectedAt < :to', { to: range[1] })
|
|
334
|
-
.getMany()
|
|
335
|
-
|
|
336
|
-
for (const sample of samples) {
|
|
337
|
-
if (!summary || !compareKeys(dataKeyItems, summary, sample)) {
|
|
338
|
-
if (summary) {
|
|
339
|
-
fillSummaryResult(summary, dataItems, summary.summary)
|
|
340
|
-
summaries.push(summary)
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
summary = {
|
|
344
|
-
domain,
|
|
345
|
-
name: dataSet.name,
|
|
346
|
-
description: dataSet.description,
|
|
347
|
-
date,
|
|
348
|
-
period,
|
|
349
|
-
dataSet,
|
|
350
|
-
...buildKeysFromSample(dataKeyItems, sample),
|
|
351
|
-
count: 0,
|
|
352
|
-
countOoc: 0,
|
|
353
|
-
countOos: 0,
|
|
354
|
-
summary: deepClone(initialSummary),
|
|
355
|
-
updater: user,
|
|
356
|
-
creator: user
|
|
357
|
-
}
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
summary.count++
|
|
361
|
-
sample.ooc && summary.countOoc++
|
|
362
|
-
sample.oos && summary.countOos++
|
|
363
|
-
|
|
364
|
-
dataItems.forEach(item => {
|
|
365
|
-
summary.summary[item.tag].push(sample.data[item.tag])
|
|
366
|
-
})
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
if (samples.length < limit) {
|
|
370
|
-
if (summary) {
|
|
371
|
-
fillSummaryResult(summary, dataItems, summary.summary)
|
|
372
|
-
summaries.push(summary)
|
|
373
|
-
}
|
|
374
|
-
break
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
page++
|
|
378
|
-
} while (true)
|
|
379
|
-
|
|
380
|
-
tx.getRepository(DataSummary).upsert(summaries, ['domain', 'dataSet', 'key01', 'key02', 'key03', 'key04', 'key05', 'date', 'period'])
|
|
381
|
-
|
|
382
|
-
return true
|
|
383
|
-
} catch (e) {
|
|
384
|
-
logger.error(e)
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
return false
|
|
388
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { In } from 'typeorm'
|
|
2
|
-
|
|
3
|
-
import { Domain, getDataSource } from '@things-factory/shell'
|
|
4
|
-
import { Activity, ActivityInstance, issue } from '@things-factory/worklist'
|
|
5
|
-
|
|
6
|
-
import { DataSet } from '../service/data-set/data-set'
|
|
7
|
-
|
|
8
|
-
export async function issueDataCollectionTask(
|
|
9
|
-
domainId: string,
|
|
10
|
-
dataSetId: string,
|
|
11
|
-
context: ResolverContext
|
|
12
|
-
): Promise<ActivityInstance | void> {
|
|
13
|
-
await getDataSource().transaction(async tx => {
|
|
14
|
-
const domain = await tx.getRepository(Domain).findOneBy({ id: domainId })
|
|
15
|
-
|
|
16
|
-
if (!domain) {
|
|
17
|
-
throw new Error(`domain(${domainId}) not found`)
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
const dataSet = await tx.getRepository(DataSet).findOne({
|
|
21
|
-
where: {
|
|
22
|
-
domain: {
|
|
23
|
-
id: In([domain.id, domain.parentId].filter(Boolean))
|
|
24
|
-
},
|
|
25
|
-
id: dataSetId
|
|
26
|
-
},
|
|
27
|
-
relations: ['entryRole']
|
|
28
|
-
})
|
|
29
|
-
|
|
30
|
-
const activity = (await tx.getRepository(Activity).findOneBy({
|
|
31
|
-
domain: { id: In([domain.id, domain.parentId].filter(Boolean)) },
|
|
32
|
-
name: 'Collect Data'
|
|
33
|
-
})) as Activity
|
|
34
|
-
|
|
35
|
-
if (activity) {
|
|
36
|
-
const { entryRole, timeLimit } = dataSet
|
|
37
|
-
|
|
38
|
-
/* 해당 dataset에 대한 데이타 수집 태스크를 dataset entryRole에게 할당한다. */
|
|
39
|
-
if (entryRole) {
|
|
40
|
-
const activityInstance = {
|
|
41
|
-
name: `[Data 수집] ${dataSet.name}`,
|
|
42
|
-
description: dataSet.description,
|
|
43
|
-
activityId: activity.id,
|
|
44
|
-
dueAt: new Date(Date.now() + (timeLimit || activity.standardTime || 24 * 60 * 60) * 1000),
|
|
45
|
-
input: {
|
|
46
|
-
dataSetId: dataSet.id,
|
|
47
|
-
dataSetName: dataSet.name
|
|
48
|
-
},
|
|
49
|
-
assigneeRole: entryRole,
|
|
50
|
-
threadsMin: 1,
|
|
51
|
-
threadsMax: 1
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
context.state = {
|
|
55
|
-
...context.state,
|
|
56
|
-
domain,
|
|
57
|
-
tx
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return await issue(activityInstance, context)
|
|
61
|
-
} else {
|
|
62
|
-
throw new Error(
|
|
63
|
-
`Data Entry Role not set. So Data Collect Activity for ${dataSet.name}($dataSet.id) could not be issued.`
|
|
64
|
-
)
|
|
65
|
-
}
|
|
66
|
-
} else {
|
|
67
|
-
throw new Error(`Data Collect Activity is not installed.`)
|
|
68
|
-
}
|
|
69
|
-
})
|
|
70
|
-
}
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
import FormData from 'form-data'
|
|
2
|
-
import fetch from 'node-fetch'
|
|
3
|
-
|
|
4
|
-
import { getEndpointUrl } from '../utils/config-resolver'
|
|
5
|
-
|
|
6
|
-
import { STORAGE } from '@things-factory/attachment-base'
|
|
7
|
-
import { AthenaController } from '@things-factory/aws-base'
|
|
8
|
-
import { config } from '@things-factory/env'
|
|
9
|
-
|
|
10
|
-
const dataReportConfig = config.get('dataReport')
|
|
11
|
-
const {
|
|
12
|
-
jasper: {
|
|
13
|
-
endpoint: ENDPOINT,
|
|
14
|
-
datasource: { database: DATABASE }
|
|
15
|
-
}
|
|
16
|
-
} = dataReportConfig || {
|
|
17
|
-
jasper: {
|
|
18
|
-
endpoint: {},
|
|
19
|
-
datasource: {}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function transformValuesToRows(queryResult) {
|
|
24
|
-
var parseData = []
|
|
25
|
-
let index = 1
|
|
26
|
-
for (let i = 0; i < queryResult.Items.length; i++) {
|
|
27
|
-
var j = 0
|
|
28
|
-
const data = JSON.parse(queryResult.Items[i].data)
|
|
29
|
-
const spec = JSON.parse(queryResult.Items[i].spec)
|
|
30
|
-
|
|
31
|
-
for (let key in data) {
|
|
32
|
-
if (Array.isArray(data[key])) {
|
|
33
|
-
for (j = 0; j < data[key].length; j++) {
|
|
34
|
-
for (let specKey in spec) {
|
|
35
|
-
if (key === specKey) {
|
|
36
|
-
parseData.push({
|
|
37
|
-
item: spec[specKey].name,
|
|
38
|
-
index: index + j,
|
|
39
|
-
value: String(data[key][j])
|
|
40
|
-
})
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
} else {
|
|
45
|
-
parseData.push({
|
|
46
|
-
item: key,
|
|
47
|
-
index,
|
|
48
|
-
value: String(data[key])
|
|
49
|
-
})
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
if (j !== 0) {
|
|
53
|
-
index = index + j
|
|
54
|
-
} else {
|
|
55
|
-
index = index + 1
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
/** @todo considering trasformation in lambda, as massive dataset */
|
|
61
|
-
function pivotData(rows) {
|
|
62
|
-
let parsedData = []
|
|
63
|
-
let index = 1
|
|
64
|
-
for (let i = 0; i < rows.length; i++) {
|
|
65
|
-
let j = 0
|
|
66
|
-
const data = JSON.parse(rows[i].data)
|
|
67
|
-
const spec = JSON.parse(rows[i].spec)
|
|
68
|
-
|
|
69
|
-
for (let key in data) {
|
|
70
|
-
/** @todo rule to display or not, about unspecified spec */
|
|
71
|
-
const value = data[key]
|
|
72
|
-
!spec[key]?.hidden &&
|
|
73
|
-
parsedData.push({
|
|
74
|
-
item: spec[key]?.name || key,
|
|
75
|
-
index,
|
|
76
|
-
value: Array.isArray(value) ? value.join(', ') : value
|
|
77
|
-
})
|
|
78
|
-
}
|
|
79
|
-
if (j !== 0) {
|
|
80
|
-
index = index + j
|
|
81
|
-
} else {
|
|
82
|
-
index = index + 1
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return parsedData
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function parseJsonDataField(rows) {
|
|
90
|
-
let parsedData = []
|
|
91
|
-
for (let i = 0; i < rows.length; i++) {
|
|
92
|
-
const row = rows[i]
|
|
93
|
-
const data = JSON.parse(row.data)
|
|
94
|
-
for (let key in data) {
|
|
95
|
-
if (Array.isArray(data[key])) {
|
|
96
|
-
data[key] = data[key].toString()
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
delete row.data
|
|
100
|
-
parsedData.push({ ...row, ...data })
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return parsedData
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const athenaClient = new AthenaController()
|
|
107
|
-
|
|
108
|
-
async function queryAthena(params) {
|
|
109
|
-
const { table, domain, datasetId, startDate, endDate, workShift, timezone } = params
|
|
110
|
-
const queryData = {
|
|
111
|
-
sql: `SELECT ds.name, ds.description, ds.data, dsh.data_items as spec, ds.workdate, ds.workshift,
|
|
112
|
-
DATE_FORMAT(
|
|
113
|
-
FROM_UNIXTIME(collected_at / 1000 / 1000) AT TIME ZONE '${timezone || 'UTC'}',
|
|
114
|
-
'%Y-%m-%d %H:%i:%s'
|
|
115
|
-
) AS dscollected_at
|
|
116
|
-
FROM ${table} ds
|
|
117
|
-
JOIN data_set_histories dsh
|
|
118
|
-
ON (ds.datasetid = dsh.original_id
|
|
119
|
-
and ds.data_set_version = dsh.version)
|
|
120
|
-
WHERE ds.domain='${domain}'
|
|
121
|
-
AND ds.datasetid = '${datasetId}'
|
|
122
|
-
AND ds.workdate >= '${startDate}'
|
|
123
|
-
AND ds.workdate <= '${endDate}'
|
|
124
|
-
${workShift ? "AND ds.workshift = '" + workShift + "'" : ''}
|
|
125
|
-
ORDER BY ds.collected_at`,
|
|
126
|
-
db: DATABASE
|
|
127
|
-
}
|
|
128
|
-
// and json_extract_scalar(data, '$.dauid') = 'A8032AD81730'
|
|
129
|
-
|
|
130
|
-
return await athenaClient.query(queryData)
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export async function renderJasperReport(context: any) {
|
|
134
|
-
const {
|
|
135
|
-
state: { domain },
|
|
136
|
-
query
|
|
137
|
-
} = context
|
|
138
|
-
|
|
139
|
-
const template = await STORAGE.readFile(query['reportTemplate'] || 'dynamic_header_sample.jrxml', 'utf-8')
|
|
140
|
-
let templateType = query['templateType'] || 'crosstab'
|
|
141
|
-
let parsedData = []
|
|
142
|
-
|
|
143
|
-
// @todo: get dataset timezone
|
|
144
|
-
/**
|
|
145
|
-
* const variables = await gql(dataSet(id:${dataSetId}) {
|
|
146
|
-
* name, description, partition_keys, timezone
|
|
147
|
-
* })
|
|
148
|
-
*/
|
|
149
|
-
|
|
150
|
-
query['domain'] = domain?.subdomain
|
|
151
|
-
query['timezone'] = domain?.timezone
|
|
152
|
-
const queryResult = await queryAthena(query)
|
|
153
|
-
const rows = queryResult.Items
|
|
154
|
-
|
|
155
|
-
if (!rows.length) {
|
|
156
|
-
return '<h3>Not found result.</h3>'
|
|
157
|
-
} else {
|
|
158
|
-
const firstRow = rows[0]
|
|
159
|
-
// uses the first row values as data-set has no history data.
|
|
160
|
-
const parameters = {
|
|
161
|
-
name: firstRow.name,
|
|
162
|
-
description: firstRow.description,
|
|
163
|
-
...query
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
if (templateType === 'crosstab') {
|
|
167
|
-
parsedData = pivotData(rows)
|
|
168
|
-
} else {
|
|
169
|
-
parsedData = parseJsonDataField(rows)
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
const formData = new FormData()
|
|
173
|
-
formData.append('template', template)
|
|
174
|
-
formData.append('jsonString', JSON.stringify(parsedData))
|
|
175
|
-
formData.append('parameters', JSON.stringify(parameters))
|
|
176
|
-
|
|
177
|
-
const { reportView } = query
|
|
178
|
-
const url = getEndpointUrl(ENDPOINT, reportView)
|
|
179
|
-
const response = await fetch(url, {
|
|
180
|
-
method: 'POST',
|
|
181
|
-
body: formData
|
|
182
|
-
})
|
|
183
|
-
|
|
184
|
-
return await response.text()
|
|
185
|
-
}
|
|
186
|
-
}
|