@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.
- package/client/activities/activity-data-collect-edit.ts +105 -0
- package/client/activities/activity-data-collect-view.ts +91 -0
- package/client/activities/activity-data-review-edit.ts +278 -0
- package/client/activities/activity-data-review-view.ts +226 -0
- package/client/activities/activity-ooc-resolve-edit.ts +195 -0
- package/client/activities/activity-ooc-resolve-view.ts +143 -0
- package/client/activities/activity-ooc-review-edit.ts +173 -0
- package/client/activities/activity-ooc-review-view.ts +129 -0
- package/client/bootstrap.ts +35 -0
- package/client/components/data-entry-form.ts +109 -0
- package/client/index.ts +1 -0
- package/client/pages/data-archive/data-archive-list-page.ts +277 -0
- package/client/pages/data-archive/data-archive-request-popup.ts +177 -0
- package/client/pages/data-entry/data-entry-list-page.ts +464 -0
- package/client/pages/data-key-set/data-key-item-list.ts +183 -0
- package/client/pages/data-key-set/data-key-set-importer.ts +89 -0
- package/client/pages/data-key-set/data-key-set-list-page.ts +413 -0
- package/client/pages/data-ooc/data-ooc-list-page.ts +549 -0
- package/client/pages/data-ooc/data-ooc-page.ts +164 -0
- package/client/pages/data-ooc/data-ooc-view.ts +236 -0
- package/client/pages/data-ooc/data-oocs-page.ts +200 -0
- package/client/pages/data-report/data-report-embed-page.ts +108 -0
- package/client/pages/data-report/data-report-list-page.ts +454 -0
- package/client/pages/data-report/data-report-samples-page.ts +174 -0
- package/client/pages/data-report/jasper-report-oocs-page.ts +110 -0
- package/client/pages/data-report/jasper-report-samples-crosstab-page.ts +110 -0
- package/client/pages/data-report/jasper-report-samples-page.ts +110 -0
- package/client/pages/data-sample/data-sample-list-page.ts +442 -0
- package/client/pages/data-sample/data-sample-page.ts +55 -0
- package/client/pages/data-sample/data-sample-search-page.ts +424 -0
- package/client/pages/data-sample/data-sample-view.ts +292 -0
- package/client/pages/data-sample/data-samples-page.ts +249 -0
- package/client/pages/data-sensor/data-sensor-list-page.ts +456 -0
- package/client/pages/data-set/data-item-list.ts +304 -0
- package/client/pages/data-set/data-set-importer.ts +89 -0
- package/client/pages/data-set/data-set-list-page.ts +1078 -0
- package/client/pages/data-summary/data-summary-list-page.ts +363 -0
- package/client/pages/data-summary/data-summary-period-page.ts +439 -0
- package/client/pages/data-summary/data-summary-search-page.ts +426 -0
- package/client/pages/data-summary/data-summary-view.ts +133 -0
- package/client/route.ts +91 -0
- package/client/tsconfig.json +13 -0
- package/dist-client/activities/activity-data-review-edit.js +19 -10
- package/dist-client/activities/activity-data-review-edit.js.map +1 -1
- package/dist-client/activities/activity-data-review-view.js +80 -0
- package/dist-client/activities/activity-data-review-view.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/controllers/create-data-ooc.js +2 -0
- package/dist-server/controllers/create-data-ooc.js.map +1 -1
- package/dist-server/service/data-archive/index.d.ts +1 -1
- package/dist-server/service/data-ooc/index.d.ts +1 -1
- 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/data-sample/index.d.ts +1 -1
- package/dist-server/service/data-set/index.d.ts +1 -1
- package/dist-server/service/index.d.ts +2 -2
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +26 -26
- package/server/activities/activity-data-collect.ts +100 -0
- package/server/activities/activity-data-review.ts +109 -0
- package/server/activities/activity-ooc-resolve.ts +123 -0
- package/server/activities/activity-ooc-review.ts +95 -0
- package/server/activities/index.ts +11 -0
- package/server/controllers/create-data-ooc.ts +80 -0
- package/server/controllers/create-data-sample.ts +323 -0
- package/server/controllers/data-use-case.ts +98 -0
- package/server/controllers/finalize-data-collection.ts +388 -0
- package/server/controllers/index.ts +6 -0
- package/server/controllers/issue-data-collection-task.ts +70 -0
- package/server/controllers/issue-ooc-resolve.ts +58 -0
- package/server/controllers/issue-ooc-review.ts +52 -0
- package/server/controllers/jasper-report.ts +186 -0
- package/server/controllers/query-data-summary-by-period.ts +178 -0
- package/server/controllers/shiny-report.ts +54 -0
- package/server/engine/index.ts +1 -0
- package/server/engine/task/create-data-sample.ts +100 -0
- package/server/engine/task/index.ts +2 -0
- package/server/engine/task/issue-collect-data.ts +45 -0
- package/server/index.ts +8 -0
- package/server/routes.ts +188 -0
- package/server/service/data-archive/data-archive-mutation.ts +273 -0
- package/server/service/data-archive/data-archive-query.ts +58 -0
- package/server/service/data-archive/data-archive-type.ts +48 -0
- package/server/service/data-archive/data-archive.ts +69 -0
- package/server/service/data-archive/index.ts +6 -0
- package/server/service/data-key-set/data-key-item-type.ts +31 -0
- package/server/service/data-key-set/data-key-set-mutation.ts +201 -0
- package/server/service/data-key-set/data-key-set-query.ts +68 -0
- package/server/service/data-key-set/data-key-set-type.ts +70 -0
- package/server/service/data-key-set/data-key-set.ts +86 -0
- package/server/service/data-key-set/index.ts +6 -0
- package/server/service/data-ooc/data-ooc-mutation.ts +154 -0
- package/server/service/data-ooc/data-ooc-query.ts +106 -0
- package/server/service/data-ooc/data-ooc-subscription.ts +48 -0
- package/server/service/data-ooc/data-ooc-type.ts +71 -0
- package/server/service/data-ooc/data-ooc.ts +259 -0
- package/server/service/data-ooc/index.ts +7 -0
- package/server/service/data-sample/data-sample-mutation.ts +18 -0
- package/server/service/data-sample/data-sample-query.ts +215 -0
- package/server/service/data-sample/data-sample-type.ts +47 -0
- package/server/service/data-sample/data-sample.ts +193 -0
- package/server/service/data-sample/index.ts +6 -0
- package/server/service/data-sensor/data-sensor-mutation.ts +116 -0
- package/server/service/data-sensor/data-sensor-query.ts +76 -0
- package/server/service/data-sensor/data-sensor-type.ts +104 -0
- package/server/service/data-sensor/data-sensor.ts +126 -0
- package/server/service/data-sensor/index.ts +6 -0
- package/server/service/data-set/data-item-type.ts +155 -0
- package/server/service/data-set/data-set-mutation.ts +552 -0
- package/server/service/data-set/data-set-query.ts +461 -0
- package/server/service/data-set/data-set-type.ts +204 -0
- package/server/service/data-set/data-set.ts +326 -0
- package/server/service/data-set/index.ts +6 -0
- package/server/service/data-set-history/data-set-history-query.ts +126 -0
- package/server/service/data-set-history/data-set-history-type.ts +12 -0
- package/server/service/data-set-history/data-set-history.ts +217 -0
- package/server/service/data-set-history/event-subscriber.ts +17 -0
- package/server/service/data-set-history/index.ts +7 -0
- package/server/service/data-spec/data-spec-manager.ts +21 -0
- package/server/service/data-spec/data-spec-query.ts +21 -0
- package/server/service/data-spec/data-spec.ts +45 -0
- package/server/service/data-spec/index.ts +5 -0
- package/server/service/data-summary/data-summary-mutation.ts +45 -0
- package/server/service/data-summary/data-summary-query.ts +179 -0
- package/server/service/data-summary/data-summary-type.ts +86 -0
- package/server/service/data-summary/data-summary.ts +170 -0
- package/server/service/data-summary/index.ts +7 -0
- package/server/service/index.ts +57 -0
- package/server/tsconfig.json +10 -0
- package/server/utils/config-resolver.ts +29 -0
- package/server/utils/index.ts +1 -0
|
@@ -0,0 +1,186 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { In } from 'typeorm'
|
|
2
|
+
|
|
3
|
+
import { getQueryBuilderFromListParams, getRepository, getTimesForPeriod, ListParam, Sorting } from '@things-factory/shell'
|
|
4
|
+
|
|
5
|
+
import { DataSet, DataSetSummaryPeriodType } from '../service/data-set/data-set'
|
|
6
|
+
import { DataSummary } from '../service/data-summary/data-summary'
|
|
7
|
+
|
|
8
|
+
const STAT_FUNCTION_MAP = {
|
|
9
|
+
sum: 'sum',
|
|
10
|
+
mean: 'mean',
|
|
11
|
+
stddev: 'standardDeviation',
|
|
12
|
+
variance: 'variance',
|
|
13
|
+
min: 'min',
|
|
14
|
+
max: 'max',
|
|
15
|
+
range: 'range',
|
|
16
|
+
median: 'median',
|
|
17
|
+
mode: 'mode'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function queryDataSummaryByPeriod(
|
|
21
|
+
period: 'today' | 'this month' | '30 days' | 'this year' | '12 months',
|
|
22
|
+
dataSetName: string,
|
|
23
|
+
dataKeys: string[] | null,
|
|
24
|
+
params: ListParam,
|
|
25
|
+
context: ResolverContext
|
|
26
|
+
): Promise<DataSummary[]> {
|
|
27
|
+
const { domain, user, tx } = context.state
|
|
28
|
+
const { t } = context
|
|
29
|
+
|
|
30
|
+
const dataSet = await getRepository(DataSet).findOne({
|
|
31
|
+
where: { domain: { id: In([domain.id, domain.parentId].filter(Boolean)) }, name: dataSetName },
|
|
32
|
+
relations: ['dataKeySet']
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
if (!dataSet) {
|
|
36
|
+
throw new Error(t('error.dataset not found', { dataSetName }))
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// limitations
|
|
40
|
+
const summaryPeriodType = dataSet.summaryPeriod
|
|
41
|
+
if ((summaryPeriodType == DataSetSummaryPeriodType.Day || summaryPeriodType == DataSetSummaryPeriodType.WorkDate) && period == 'today') {
|
|
42
|
+
throw new Error(t('error.summary not supported', { dataSetName, period: t(`label.period-${period}`) }))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// dataKeys 가 설정되지 않았다면, dataSet의 dataKeySet을 그대로 적용한다는 의미임.
|
|
46
|
+
// dataKeys == [] 라면, dataKeySet을 적용하지 않는다는 의미임.
|
|
47
|
+
// dataKeys에는 dataKeySet의 dataKey 값을 따라야 한다.
|
|
48
|
+
const dataKeyItems = !dataSet.dataKeySet?.dataKeyItems
|
|
49
|
+
? []
|
|
50
|
+
: dataKeys
|
|
51
|
+
? dataKeys
|
|
52
|
+
.map(dataKey => {
|
|
53
|
+
return dataSet.dataKeySet.dataKeyItems.find(item => item.dataKey == dataKey)
|
|
54
|
+
})
|
|
55
|
+
.filter(Boolean)
|
|
56
|
+
: dataSet.dataKeySet.dataKeyItems
|
|
57
|
+
|
|
58
|
+
const searchables = dataKeyItems.map((item, index) => `key0${index + 1}`)
|
|
59
|
+
const dataItems = dataSet.dataItems.filter(item => item.agg)
|
|
60
|
+
|
|
61
|
+
const { from, to } = await getTimesForPeriod(period, context)
|
|
62
|
+
|
|
63
|
+
const selectPeriod = queryBuilder => {
|
|
64
|
+
if (period == 'today') {
|
|
65
|
+
queryBuilder.addSelect('summary.date', 'date').addSelect('summary.period', 'period')
|
|
66
|
+
} else if (['this year', '12 months'].includes(period)) {
|
|
67
|
+
queryBuilder.addSelect('SUBSTRING(summary.date, 1, 7) AS month')
|
|
68
|
+
} else {
|
|
69
|
+
queryBuilder.addSelect('summary.date', 'date')
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return queryBuilder
|
|
73
|
+
}
|
|
74
|
+
const selectKeys = dataKeyItems.map((item, index) => {
|
|
75
|
+
const aliasName = `key0${index + 1}`
|
|
76
|
+
const columnName = `summary.${aliasName}`
|
|
77
|
+
|
|
78
|
+
return `${columnName} as ${aliasName}`
|
|
79
|
+
}) as string[]
|
|
80
|
+
const selectData = dataItems.map((item, index) => {
|
|
81
|
+
const aliasName = `data0${index + 1}`
|
|
82
|
+
const columnName = `summary.${aliasName}`
|
|
83
|
+
|
|
84
|
+
switch (item.agg) {
|
|
85
|
+
case STAT_FUNCTION_MAP.sum:
|
|
86
|
+
return `SUM(${columnName}) as ${aliasName}`
|
|
87
|
+
case STAT_FUNCTION_MAP.mean:
|
|
88
|
+
// 일차 mean 된 값들을 다시 계산하므로, 무의미할 수 있다.
|
|
89
|
+
return `AVG(${columnName}) as ${aliasName}`
|
|
90
|
+
case STAT_FUNCTION_MAP.stddev:
|
|
91
|
+
// 일차 stddev 된 값들을 다시 계산하므로, 무의미할 수 있다.
|
|
92
|
+
return `STDDEV(${columnName}) as ${aliasName}`
|
|
93
|
+
case STAT_FUNCTION_MAP.variance:
|
|
94
|
+
// 일차 variance 된 값들을 다시 계산하므로, 무의미할 수 있다.
|
|
95
|
+
return `VARIANCE(${columnName}) as ${aliasName}`
|
|
96
|
+
case STAT_FUNCTION_MAP.min:
|
|
97
|
+
return `MIN(${columnName}) as ${aliasName}`
|
|
98
|
+
case STAT_FUNCTION_MAP.max:
|
|
99
|
+
return `MAX(${columnName}) as ${aliasName}`
|
|
100
|
+
case STAT_FUNCTION_MAP.range:
|
|
101
|
+
// 일차 range 값들을 다시 계산하므로, 무의미할 수 있다.
|
|
102
|
+
return `MAX(${columnName}) - MIN(${columnName}) as ${aliasName}`
|
|
103
|
+
case STAT_FUNCTION_MAP.mode:
|
|
104
|
+
// not guaranteed
|
|
105
|
+
return `MODE() WITHIN GROUP (ORDER BY ${columnName})`
|
|
106
|
+
default:
|
|
107
|
+
return `AVG(${columnName}) as ${aliasName}`
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
const groupByPeriod =
|
|
112
|
+
period == 'today' ? 'summary.date, summary.period' : ['this year', '12 months'].includes(period) ? 'SUBSTRING(summary.date, 1, 7)' : 'summary.date'
|
|
113
|
+
const groupByKeys = dataKeyItems
|
|
114
|
+
.map((item, index) => {
|
|
115
|
+
return `summary.key0${index + 1}`
|
|
116
|
+
})
|
|
117
|
+
.join(', ')
|
|
118
|
+
|
|
119
|
+
const desc = false
|
|
120
|
+
var orderByPeriod: Sorting[] =
|
|
121
|
+
period == 'today'
|
|
122
|
+
? [
|
|
123
|
+
{
|
|
124
|
+
name: 'date',
|
|
125
|
+
desc
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
name: 'period',
|
|
129
|
+
desc
|
|
130
|
+
}
|
|
131
|
+
]
|
|
132
|
+
: ['this year', '12 months'].includes(period)
|
|
133
|
+
? [{ name: 'month', desc }]
|
|
134
|
+
: [{ name: 'date', desc }]
|
|
135
|
+
|
|
136
|
+
if (params?.sortings && params.sortings.length > 0) {
|
|
137
|
+
orderByPeriod = params.sortings
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
var queryBuilder = getQueryBuilderFromListParams({
|
|
141
|
+
repository: getRepository(DataSummary),
|
|
142
|
+
params: {
|
|
143
|
+
...params,
|
|
144
|
+
sortings: []
|
|
145
|
+
},
|
|
146
|
+
domain,
|
|
147
|
+
alias: 'summary',
|
|
148
|
+
searchables: searchables
|
|
149
|
+
})
|
|
150
|
+
.select('summary.dataSet')
|
|
151
|
+
.addSelect(selectData)
|
|
152
|
+
.addSelect('SUM(summary.count)', 'count')
|
|
153
|
+
.addSelect('SUM(summary.countOoc)', 'countOoc')
|
|
154
|
+
.addSelect('SUM(summary.countOos)', 'countOos')
|
|
155
|
+
|
|
156
|
+
queryBuilder = selectPeriod(queryBuilder)
|
|
157
|
+
.innerJoin('summary.dataSet', 'ds', 'ds.id = :dataSetName', {
|
|
158
|
+
dataSetName: dataSet.id
|
|
159
|
+
})
|
|
160
|
+
.andWhere('summary.domain = :domain', { domain: domain.id })
|
|
161
|
+
.andWhere('summary.date >= :from', { from })
|
|
162
|
+
.andWhere('summary.date < :to', { to })
|
|
163
|
+
.addGroupBy('summary.dataSet')
|
|
164
|
+
.addGroupBy(groupByPeriod)
|
|
165
|
+
|
|
166
|
+
if (dataKeyItems.length > 0) {
|
|
167
|
+
queryBuilder.addSelect(selectKeys).addGroupBy(groupByKeys)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
orderByPeriod.map(orderBy => {
|
|
171
|
+
const { name: sort, desc } = orderBy
|
|
172
|
+
queryBuilder.addOrderBy(sort, desc ? 'DESC' : 'ASC')
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
const items = await queryBuilder.getRawMany()
|
|
176
|
+
|
|
177
|
+
return items
|
|
178
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { config } from '@things-factory/env'
|
|
2
|
+
import { getEndpointUrl } from '../utils/config-resolver'
|
|
3
|
+
|
|
4
|
+
import debugLib from 'debug'
|
|
5
|
+
const debug = debugLib('things-factory:dataset:shiny-report')
|
|
6
|
+
const dataReportConfig = config.get('dataReport')
|
|
7
|
+
const {
|
|
8
|
+
shiny: {
|
|
9
|
+
endpoint: ENDPOINT,
|
|
10
|
+
datasource: { database: DATABASE }
|
|
11
|
+
}
|
|
12
|
+
} = Object.assign(
|
|
13
|
+
{
|
|
14
|
+
shiny: {
|
|
15
|
+
endpoint: {},
|
|
16
|
+
datasource: {}
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
dataReportConfig
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
export async function renderShinyReport(context: any) {
|
|
23
|
+
debug('render:context', context)
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const cookies = new URLSearchParams(context.header.cookie.replace(/; /g, '&'))
|
|
27
|
+
const token = cookies.get('access_token')
|
|
28
|
+
const {
|
|
29
|
+
state: { domain },
|
|
30
|
+
query,
|
|
31
|
+
response
|
|
32
|
+
} = context
|
|
33
|
+
|
|
34
|
+
const urlParams = new URLSearchParams({
|
|
35
|
+
token,
|
|
36
|
+
...query,
|
|
37
|
+
database: DATABASE,
|
|
38
|
+
domain: domain?.subdomain
|
|
39
|
+
}).toString()
|
|
40
|
+
|
|
41
|
+
const subpath = query?.reportView || ''
|
|
42
|
+
|
|
43
|
+
if (!subpath) {
|
|
44
|
+
return (context.body = '')
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const url = getEndpointUrl(ENDPOINT, subpath)
|
|
48
|
+
const reportUrl = `${url}/?${urlParams}`
|
|
49
|
+
|
|
50
|
+
response.redirect(reportUrl)
|
|
51
|
+
} catch (ex) {
|
|
52
|
+
context.body = `Error: ${ex.message} || ''`
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import './task'
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { TaskRegistry, InputStep, Context } from '@things-factory/integration-base'
|
|
2
|
+
import { getDataSource } from '@things-factory/shell'
|
|
3
|
+
import { access } from '@things-factory/utils'
|
|
4
|
+
import i18next from 'i18next'
|
|
5
|
+
|
|
6
|
+
import { DataSet } from '../../service/data-set/data-set'
|
|
7
|
+
|
|
8
|
+
import { createDataSample } from '../../controllers/create-data-sample'
|
|
9
|
+
import { TaskHandler } from '@things-factory/integration-base/dist-server/engine/types'
|
|
10
|
+
|
|
11
|
+
async function CreateDataSample(step: InputStep, context: Context) {
|
|
12
|
+
const { data, domain, user, lng } = context
|
|
13
|
+
var {
|
|
14
|
+
params: {
|
|
15
|
+
dataset: dataSetId,
|
|
16
|
+
source: sourceAccessor,
|
|
17
|
+
rawData: rawDataAccessor,
|
|
18
|
+
data: dataAccessor,
|
|
19
|
+
timestamp: timestampAccessor
|
|
20
|
+
}
|
|
21
|
+
} = step
|
|
22
|
+
|
|
23
|
+
if (!dataSetId) {
|
|
24
|
+
throw new Error(`no dataset found`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// make new data-sample
|
|
28
|
+
return await getDataSource().transaction(async tx => {
|
|
29
|
+
const dataSet = await tx.getRepository(DataSet).findOne({
|
|
30
|
+
where: {
|
|
31
|
+
id: dataSetId
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
var source = await access(sourceAccessor, data)
|
|
36
|
+
var extractedData = await access(dataAccessor, data)
|
|
37
|
+
var extractedRawData = rawDataAccessor && (await access(rawDataAccessor, data))
|
|
38
|
+
var timestamp = timestampAccessor && (await access(timestampAccessor, data))
|
|
39
|
+
|
|
40
|
+
const dataSample = await createDataSample(
|
|
41
|
+
{
|
|
42
|
+
dataSet,
|
|
43
|
+
data: extractedData,
|
|
44
|
+
rawData: extractedRawData,
|
|
45
|
+
source,
|
|
46
|
+
collectedAt: new Date(timestamp || new Date())
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
t: i18next.t,
|
|
50
|
+
state: {
|
|
51
|
+
domain,
|
|
52
|
+
user,
|
|
53
|
+
lng,
|
|
54
|
+
tx
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
data: dataSample
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
CreateDataSample.parameterSpec = [
|
|
66
|
+
{
|
|
67
|
+
type: 'entity-selector',
|
|
68
|
+
name: 'dataset',
|
|
69
|
+
label: 'data-set',
|
|
70
|
+
property: {
|
|
71
|
+
queryName: 'dataSets',
|
|
72
|
+
valueKey: 'id'
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
type: 'string',
|
|
77
|
+
name: 'source',
|
|
78
|
+
label: 'source'
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
type: 'string',
|
|
82
|
+
name: 'rawData',
|
|
83
|
+
label: 'raw-data'
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
type: 'string',
|
|
87
|
+
name: 'data',
|
|
88
|
+
label: 'data'
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
type: 'string',
|
|
92
|
+
name: 'timestamp',
|
|
93
|
+
label: 'timestamp'
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
CreateDataSample.connectorFree = true
|
|
98
|
+
CreateDataSample.help = 'dataset/task/create-data-sample'
|
|
99
|
+
|
|
100
|
+
TaskRegistry.registerTaskHandler('create-data-sample', CreateDataSample as TaskHandler)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { TaskRegistry, InputStep, Context } from '@things-factory/integration-base'
|
|
2
|
+
import { issueDataCollectionTask } from '../../controllers/issue-data-collection-task'
|
|
3
|
+
import i18next from 'i18next'
|
|
4
|
+
|
|
5
|
+
async function IssueCollectData(step: InputStep, context: Context) {
|
|
6
|
+
const { logger, data, domain, user, lng } = context
|
|
7
|
+
var {
|
|
8
|
+
params: { dataset: dataSetId }
|
|
9
|
+
} = step
|
|
10
|
+
|
|
11
|
+
if (!dataSetId) {
|
|
12
|
+
throw new Error(`no dataset found`)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const activityInstance = await issueDataCollectionTask(domain.id, dataSetId, {
|
|
16
|
+
t: i18next.t,
|
|
17
|
+
state: {
|
|
18
|
+
domain,
|
|
19
|
+
user,
|
|
20
|
+
lng,
|
|
21
|
+
tx: undefined
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
data: activityInstance
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
IssueCollectData.parameterSpec = [
|
|
31
|
+
{
|
|
32
|
+
type: 'entity-selector',
|
|
33
|
+
name: 'dataset',
|
|
34
|
+
label: 'data-set',
|
|
35
|
+
property: {
|
|
36
|
+
queryName: 'dataSets',
|
|
37
|
+
valueKey: 'id'
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
IssueCollectData.connectorFree = true
|
|
43
|
+
IssueCollectData.help = 'dataset/task/issue-collect-data'
|
|
44
|
+
|
|
45
|
+
TaskRegistry.registerTaskHandler('issue-collect-data', IssueCollectData)
|