@things-factory/dataset 6.0.82 → 6.0.84
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/pages/data-summary/data-summary-period-page.ts +436 -0
- package/client/pages/data-summary/data-summary-search-page.ts +0 -12
- package/client/route.ts +4 -0
- package/dist-client/pages/data-summary/data-summary-group-page.d.ts +82 -0
- package/dist-client/pages/data-summary/data-summary-group-page.js +435 -0
- package/dist-client/pages/data-summary/data-summary-group-page.js.map +1 -0
- package/dist-client/pages/data-summary/data-summary-period-page.d.ts +82 -0
- package/dist-client/pages/data-summary/data-summary-period-page.js +436 -0
- package/dist-client/pages/data-summary/data-summary-period-page.js.map +1 -0
- package/dist-client/pages/data-summary/data-summary-search-page.js +0 -12
- package/dist-client/pages/data-summary/data-summary-search-page.js.map +1 -1
- package/dist-client/route.js +3 -0
- package/dist-client/route.js.map +1 -1
- package/dist-client/tsconfig.tsbuildinfo +1 -1
- package/dist-server/controllers/generate-data-summary.js.map +1 -1
- package/dist-server/controllers/query-data-summary-by-period.js +170 -0
- package/dist-server/controllers/query-data-summary-by-period.js.map +1 -0
- package/dist-server/service/data-summary/data-summary-query.js +48 -0
- package/dist-server/service/data-summary/data-summary-query.js.map +1 -1
- package/dist-server/service/data-summary/data-summary-type.js +89 -1
- package/dist-server/service/data-summary/data-summary-type.js.map +1 -1
- package/dist-server/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/server/controllers/generate-data-summary.ts +0 -1
- package/server/controllers/query-data-summary-by-period.ts +200 -0
- package/server/service/data-summary/data-summary-query.ts +48 -2
- package/server/service/data-summary/data-summary-type.ts +57 -0
- package/things-factory.config.js +4 -0
- package/translations/en.json +2 -0
- package/translations/ko.json +2 -0
- package/translations/ms.json +2 -0
- package/translations/zh.json +2 -0
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@things-factory/dataset",
|
3
|
-
"version": "6.0.
|
3
|
+
"version": "6.0.84",
|
4
4
|
"main": "dist-server/index.js",
|
5
5
|
"browser": "dist-client/index.js",
|
6
6
|
"things-factory": true,
|
@@ -50,5 +50,5 @@
|
|
50
50
|
"simple-statistics": "^7.8.3",
|
51
51
|
"statistics": "^3.3.0"
|
52
52
|
},
|
53
|
-
"gitHead": "
|
53
|
+
"gitHead": "b858eba746efe1b59c54bb2e0f4b3b5e67ee8741"
|
54
54
|
}
|
@@ -0,0 +1,200 @@
|
|
1
|
+
import moment from 'moment-timezone'
|
2
|
+
|
3
|
+
import { getQueryBuilderFromListParams, getRepository, ListParam } from '@things-factory/shell'
|
4
|
+
|
5
|
+
import { DataSet, DataSetSummaryPeriodType } from '../service/data-set/data-set'
|
6
|
+
|
7
|
+
import { DataSummary } from '../service/data-summary/data-summary'
|
8
|
+
import { DataSummaryList } from '../service/data-summary/data-summary-type'
|
9
|
+
|
10
|
+
const STAT_FUNCTION_MAP = {
|
11
|
+
sum: 'sum',
|
12
|
+
mean: 'mean',
|
13
|
+
stddev: 'standardDeviation',
|
14
|
+
variance: 'variance',
|
15
|
+
min: 'min',
|
16
|
+
max: 'max',
|
17
|
+
range: 'range',
|
18
|
+
median: 'median',
|
19
|
+
mode: 'mode'
|
20
|
+
}
|
21
|
+
|
22
|
+
async function getTimesForPeriod(
|
23
|
+
period: 'today' | 'this month' | '30 days' | 'this year' | '12 months',
|
24
|
+
context: ResolverContext
|
25
|
+
): Promise<{ from: string; to: string }> {
|
26
|
+
const { domain } = context.state
|
27
|
+
const theDate = moment.tz(new Date(), domain.timezone)
|
28
|
+
|
29
|
+
if (period == 'today') {
|
30
|
+
const from = theDate.clone().format('YYYY-MM-DD')
|
31
|
+
const to = theDate.clone().add(1, 'day').startOf('day').format('YYYY-MM-DD')
|
32
|
+
|
33
|
+
return { from, to }
|
34
|
+
} else if (period == 'this month') {
|
35
|
+
const from = theDate.clone().startOf('month').format('YYYY-MM-DD')
|
36
|
+
const to = theDate.clone().add(1, 'month').startOf('month').format('YYYY-MM-DD')
|
37
|
+
|
38
|
+
return { from, to }
|
39
|
+
} else if (period == '30 days') {
|
40
|
+
const from = theDate.clone().subtract(30, 'day').format('YYYY-MM-DD')
|
41
|
+
const to = theDate.clone().add(1, 'day').startOf('day').format('YYYY-MM-DD')
|
42
|
+
|
43
|
+
return { from, to }
|
44
|
+
} else if (period == 'this year') {
|
45
|
+
const from = theDate.clone().startOf('year').format('YYYY-MM-DD')
|
46
|
+
const to = theDate.clone().add(1, 'year').startOf('year').format('YYYY-MM-DD')
|
47
|
+
|
48
|
+
return { from, to }
|
49
|
+
} else if (period == '12 months') {
|
50
|
+
const from = theDate.clone().subtract(12, 'month').startOf('month').format('YYYY-MM-DD')
|
51
|
+
const to = theDate.clone().add(1, 'month').startOf('month').format('YYYY-MM-DD')
|
52
|
+
|
53
|
+
return { from, to }
|
54
|
+
}
|
55
|
+
}
|
56
|
+
|
57
|
+
export async function queryDataSummaryByPeriod(
|
58
|
+
period: 'today' | 'this month' | '30 days' | 'this year' | '12 months',
|
59
|
+
dataSetName: string,
|
60
|
+
params: ListParam,
|
61
|
+
context: ResolverContext
|
62
|
+
): Promise<DataSummary[]> {
|
63
|
+
const { domain, user, tx } = context.state
|
64
|
+
|
65
|
+
const dataSet = await getRepository(DataSet).findOne({
|
66
|
+
where: { domain: { id: domain.id }, name: dataSetName },
|
67
|
+
relations: ['dataKeySet']
|
68
|
+
})
|
69
|
+
|
70
|
+
if (!dataSet) {
|
71
|
+
throw new Error(`dataSet not found by the given dataSetName(${dataSetName})`)
|
72
|
+
}
|
73
|
+
|
74
|
+
// 주어진 기간에 따라서, date의 범위를 구한다.
|
75
|
+
// day : 시간별 서머리를 제공한다. 'today'
|
76
|
+
// month : 일별 서머리를 제공한다. 'this month', '30 days'
|
77
|
+
// year : 월별 서머리를 제공한다. 'this year', '12 months'
|
78
|
+
|
79
|
+
// limitations
|
80
|
+
const summaryPeriodType = dataSet.summaryPeriod
|
81
|
+
if (
|
82
|
+
(summaryPeriodType == DataSetSummaryPeriodType.Day || summaryPeriodType == DataSetSummaryPeriodType.WorkDate) &&
|
83
|
+
period == 'today'
|
84
|
+
) {
|
85
|
+
throw new Error(`given dataSet(${dataSetName}) is not allowed for getting summary for ${period}`)
|
86
|
+
}
|
87
|
+
|
88
|
+
const dataKeyItems = dataSet.dataKeySet?.dataKeyItems || []
|
89
|
+
const searchables = dataKeyItems.map((item, index) => `key0${index + 1}`)
|
90
|
+
const dataItems = dataSet.dataItems.filter(item => item.stat)
|
91
|
+
|
92
|
+
const { from, to } = await getTimesForPeriod(period, context)
|
93
|
+
|
94
|
+
const selectPeriod =
|
95
|
+
period == 'today'
|
96
|
+
? ['summary.date', 'summary.period']
|
97
|
+
: ['this year', '12 months'].includes(period)
|
98
|
+
? ['SUBSTRING(summary.date, 1, 7) AS month']
|
99
|
+
: ['summary.date']
|
100
|
+
const selectKeys = dataKeyItems.map((item, index) => {
|
101
|
+
const aliasName = `key0${index + 1}`
|
102
|
+
const columnName = `summary.${aliasName}`
|
103
|
+
|
104
|
+
return `${columnName} as ${aliasName}`
|
105
|
+
}) as string[]
|
106
|
+
const selectData = dataItems.map((item, index) => {
|
107
|
+
const aliasName = `data0${index + 1}`
|
108
|
+
const columnName = `summary.${aliasName}`
|
109
|
+
|
110
|
+
switch (item.stat) {
|
111
|
+
case STAT_FUNCTION_MAP.sum:
|
112
|
+
return `SUM(${columnName}) as ${aliasName}`
|
113
|
+
case STAT_FUNCTION_MAP.mean:
|
114
|
+
// 일차 mean 된 값들을 다시 계산하므로, 무의미할 수 있다.
|
115
|
+
return `AVG(${columnName}) as ${aliasName}`
|
116
|
+
case STAT_FUNCTION_MAP.stddev:
|
117
|
+
// 일차 stddev 된 값들을 다시 계산하므로, 무의미할 수 있다.
|
118
|
+
return `STDDEV(${columnName}) as ${aliasName}`
|
119
|
+
case STAT_FUNCTION_MAP.variance:
|
120
|
+
// 일차 variance 된 값들을 다시 계산하므로, 무의미할 수 있다.
|
121
|
+
return `VARIANCE(${columnName}) as ${aliasName}`
|
122
|
+
case STAT_FUNCTION_MAP.min:
|
123
|
+
return `MIN(${columnName}) as ${aliasName}`
|
124
|
+
case STAT_FUNCTION_MAP.max:
|
125
|
+
return `MAX(${columnName}) as ${aliasName}`
|
126
|
+
case STAT_FUNCTION_MAP.range:
|
127
|
+
// 일차 range 값들을 다시 계산하므로, 무의미할 수 있다.
|
128
|
+
return `MAX(${columnName}) - MIN(${columnName}) as ${aliasName}`
|
129
|
+
case STAT_FUNCTION_MAP.mode:
|
130
|
+
// not guaranteed
|
131
|
+
return `MODE() WITHIN GROUP (ORDER BY ${columnName})`
|
132
|
+
default:
|
133
|
+
return `AVG(${columnName}) as ${aliasName}`
|
134
|
+
}
|
135
|
+
})
|
136
|
+
|
137
|
+
const groupByPeriod =
|
138
|
+
period == 'today'
|
139
|
+
? 'summary.date, summary.period'
|
140
|
+
: ['this year', '12 months'].includes(period)
|
141
|
+
? 'month'
|
142
|
+
: 'summary.date'
|
143
|
+
const groupByKeys = dataKeyItems
|
144
|
+
.map((item, index) => {
|
145
|
+
return `summary.key0${index + 1}`
|
146
|
+
})
|
147
|
+
.join(' ')
|
148
|
+
|
149
|
+
const orderByPeriod: { sort: string; order: 'DESC' | 'ASC' }[] =
|
150
|
+
period == 'today'
|
151
|
+
? [
|
152
|
+
{
|
153
|
+
sort: 'summary.date',
|
154
|
+
order: 'DESC'
|
155
|
+
},
|
156
|
+
{
|
157
|
+
sort: 'summary.period',
|
158
|
+
order: 'DESC'
|
159
|
+
}
|
160
|
+
]
|
161
|
+
: ['this year', '12 months'].includes(period)
|
162
|
+
? [{ sort: 'month', order: 'DESC' }]
|
163
|
+
: [{ sort: 'summary.date', order: 'DESC' }]
|
164
|
+
|
165
|
+
const queryBuilder = getQueryBuilderFromListParams({
|
166
|
+
repository: getRepository(DataSummary),
|
167
|
+
params,
|
168
|
+
domain,
|
169
|
+
alias: 'summary',
|
170
|
+
searchables: ['name', 'description'].concat(searchables)
|
171
|
+
})
|
172
|
+
.select('summary.dataSet')
|
173
|
+
.addSelect('ds.name', 'name')
|
174
|
+
.addSelect('ds.description', 'description')
|
175
|
+
.addSelect('summary.date', 'date')
|
176
|
+
.addSelect('summary.period', 'period')
|
177
|
+
.addSelect(selectPeriod)
|
178
|
+
.addSelect(selectKeys)
|
179
|
+
.addSelect(selectData)
|
180
|
+
.addSelect('SUM(summary.count)', 'count')
|
181
|
+
.addSelect('SUM(summary.countOoc)', 'countOoc')
|
182
|
+
.addSelect('SUM(summary.countOos)', 'countOos')
|
183
|
+
.innerJoin('summary.dataSet', 'ds', 'ds.id = :dataSetName', {
|
184
|
+
dataSetName: dataSet.id
|
185
|
+
})
|
186
|
+
.andWhere('summary.domain = :domain', { domain: domain.id })
|
187
|
+
.andWhere('summary.date >= :from', { from })
|
188
|
+
.andWhere('summary.date < :to', { to })
|
189
|
+
.addGroupBy(groupByPeriod)
|
190
|
+
.addGroupBy(groupByKeys)
|
191
|
+
|
192
|
+
orderByPeriod.map(orderBy => {
|
193
|
+
const { sort, order } = orderBy
|
194
|
+
queryBuilder.addOrderBy(sort, order)
|
195
|
+
})
|
196
|
+
|
197
|
+
const items = await queryBuilder.getRawMany()
|
198
|
+
|
199
|
+
return items
|
200
|
+
}
|
@@ -4,9 +4,9 @@ import { User } from '@things-factory/auth-base'
|
|
4
4
|
import { DataKeySet } from '../data-key-set/data-key-set'
|
5
5
|
import { DataItem } from '../data-set/data-item-type'
|
6
6
|
import { DataSet } from '../data-set/data-set'
|
7
|
-
import { DataSetHistory } from '../data-set-history/data-set-history'
|
8
7
|
import { DataSummary } from './data-summary'
|
9
|
-
import { DataSummaryList } from './data-summary-type'
|
8
|
+
import { DataSummaryList, DynamicDataSummary } from './data-summary-type'
|
9
|
+
import { queryDataSummaryByPeriod } from '../../controllers/query-data-summary-by-period'
|
10
10
|
|
11
11
|
@Resolver(DataSummary)
|
12
12
|
export class DataSummaryQuery {
|
@@ -35,6 +35,52 @@ export class DataSummaryQuery {
|
|
35
35
|
return { items, total }
|
36
36
|
}
|
37
37
|
|
38
|
+
@Query(returns => [DynamicDataSummary], { description: 'To fetch multiple dynamically grouped DataSummaries' })
|
39
|
+
async dataSummaryByPeriod(
|
40
|
+
@Arg('period') period: 'today' | 'this month' | '30 days' | 'this year' | '12 months',
|
41
|
+
@Arg('dataSetName') dataSetName: string,
|
42
|
+
@Args() params: ListParam,
|
43
|
+
@Ctx() context: ResolverContext
|
44
|
+
): Promise<DynamicDataSummary[]> {
|
45
|
+
return await queryDataSummaryByPeriod(period, dataSetName, params, context)
|
46
|
+
}
|
47
|
+
|
48
|
+
@Directive('@privilege(category: "data-summary", privilege: "query", domainOwnerGranted: true)')
|
49
|
+
@Query(returns => DataSummaryList, { description: 'To fetch multiple data summaries by data set name' })
|
50
|
+
async dataSummariesByDataSetName(
|
51
|
+
@Arg('dataSetName') dataSetName: string,
|
52
|
+
@Args() params: ListParam,
|
53
|
+
@Ctx() context: ResolverContext
|
54
|
+
): Promise<DataSummaryList> {
|
55
|
+
const { domain } = context.state
|
56
|
+
|
57
|
+
const dataSet = await getRepository(DataSet).findOne({
|
58
|
+
where: { domain: { id: domain.id }, name: dataSetName },
|
59
|
+
relations: ['dataKeySet']
|
60
|
+
})
|
61
|
+
|
62
|
+
if (!dataSet) {
|
63
|
+
throw new Error(`dataSet not found by the given dataSetName(${dataSetName})`)
|
64
|
+
}
|
65
|
+
|
66
|
+
const dataKeyItems = dataSet.dataKeySet?.dataKeyItems || []
|
67
|
+
const searchables = dataKeyItems.map((item, index) => `key0${index + 1}`)
|
68
|
+
|
69
|
+
const queryBuilder = getQueryBuilderFromListParams({
|
70
|
+
repository: getRepository(DataSummary),
|
71
|
+
params,
|
72
|
+
domain,
|
73
|
+
alias: 'summary',
|
74
|
+
searchables: ['name', 'description'].concat(searchables)
|
75
|
+
}).innerJoin('summary.dataSet', 'ds', 'ds.id = :dataSetName', {
|
76
|
+
dataSetName: dataSet.id
|
77
|
+
})
|
78
|
+
|
79
|
+
const [items, total] = await queryBuilder.getManyAndCount()
|
80
|
+
|
81
|
+
return { items, total }
|
82
|
+
}
|
83
|
+
|
38
84
|
@Directive('@privilege(category: "data-summary", privilege: "query", domainOwnerGranted: true)')
|
39
85
|
@Query(returns => DataSummaryList, { description: 'To fetch multiple data summaries by data set' })
|
40
86
|
async dataSummariesByDataSet(
|
@@ -25,6 +25,63 @@ export class DataSummaryPatch {
|
|
25
25
|
cuFlag?: string
|
26
26
|
}
|
27
27
|
|
28
|
+
@ObjectType()
|
29
|
+
export class DynamicDataSummary {
|
30
|
+
@Field({ nullable: true })
|
31
|
+
name?: string
|
32
|
+
|
33
|
+
@Field({ nullable: true })
|
34
|
+
description?: string
|
35
|
+
|
36
|
+
@Field({ nullable: true })
|
37
|
+
month?: string = ''
|
38
|
+
|
39
|
+
@Field({ nullable: true })
|
40
|
+
date?: string = ''
|
41
|
+
|
42
|
+
@Field({ nullable: true })
|
43
|
+
period?: string = ''
|
44
|
+
|
45
|
+
@Field({ nullable: true })
|
46
|
+
count?: number
|
47
|
+
|
48
|
+
@Field({ nullable: true })
|
49
|
+
countOoc?: number
|
50
|
+
|
51
|
+
@Field({ nullable: true })
|
52
|
+
countOos?: number
|
53
|
+
|
54
|
+
@Field({ nullable: true })
|
55
|
+
key01?: string = ''
|
56
|
+
|
57
|
+
@Field({ nullable: true })
|
58
|
+
key02?: string = ''
|
59
|
+
|
60
|
+
@Field({ nullable: true })
|
61
|
+
key03?: string = ''
|
62
|
+
|
63
|
+
@Field({ nullable: true })
|
64
|
+
key04?: string = ''
|
65
|
+
|
66
|
+
@Field({ nullable: true })
|
67
|
+
key05?: string = ''
|
68
|
+
|
69
|
+
@Field({ nullable: true })
|
70
|
+
data01?: number
|
71
|
+
|
72
|
+
@Field({ nullable: true })
|
73
|
+
data02?: number
|
74
|
+
|
75
|
+
@Field({ nullable: true })
|
76
|
+
data03?: number
|
77
|
+
|
78
|
+
@Field({ nullable: true })
|
79
|
+
data04?: number
|
80
|
+
|
81
|
+
@Field({ nullable: true })
|
82
|
+
data05?: number
|
83
|
+
}
|
84
|
+
|
28
85
|
@ObjectType()
|
29
86
|
export class DataSummaryList {
|
30
87
|
@Field(type => [DataSummary])
|
package/things-factory.config.js
CHANGED
package/translations/en.json
CHANGED
@@ -15,6 +15,7 @@
|
|
15
15
|
"field.data-sample": "data sample",
|
16
16
|
"field.data-set": "data set",
|
17
17
|
"field.date": "date",
|
18
|
+
"field.month": "month",
|
18
19
|
"field.device-id": "device id",
|
19
20
|
"field.download-url": "download url",
|
20
21
|
"field.entry-role": "entry role",
|
@@ -104,6 +105,7 @@
|
|
104
105
|
"title.data-sample search": "data sample search",
|
105
106
|
"title.data-sample view": "data sample view",
|
106
107
|
"title.data-summary list": "data summary list",
|
108
|
+
"title.data-summary period": "data summary period",
|
107
109
|
"title.data-summary search": "data summary search",
|
108
110
|
"title.data-summary view": "data summary view",
|
109
111
|
"title.data-sensor list": "data sensor list",
|
package/translations/ko.json
CHANGED
@@ -15,6 +15,7 @@
|
|
15
15
|
"field.data-key": "데이타 키",
|
16
16
|
"field.data-key-set": "데이타 키셋",
|
17
17
|
"field.date": "일자",
|
18
|
+
"field.month": "월",
|
18
19
|
"field.device-id": "디바이스 아이디",
|
19
20
|
"field.download-url": "다운로드 주소",
|
20
21
|
"field.entry-role": "입력담당 역할",
|
@@ -102,6 +103,7 @@
|
|
102
103
|
"title.data-sample search": "데이타 샘플 검색",
|
103
104
|
"title.data-sample view": "데이타 샘플 상세",
|
104
105
|
"title.data-summary list": "데이터 수집 마감 조회",
|
106
|
+
"title.data-summary period": "데이타 수집 마감 기간별 조회",
|
105
107
|
"title.data-summary search": "데이타 수집 마감 검색",
|
106
108
|
"title.data-summary view": "데이타 수집 마감 상세",
|
107
109
|
"title.data-sensor list": "데이타 센서 조회",
|
package/translations/ms.json
CHANGED
@@ -15,6 +15,7 @@
|
|
15
15
|
"field.data-sample": "data sample",
|
16
16
|
"field.data-set": "data set",
|
17
17
|
"field.date": "date",
|
18
|
+
"field.month": "month",
|
18
19
|
"field.device-id": "device id",
|
19
20
|
"field.entry-role": "entry role",
|
20
21
|
"field.entry-type": "entry type",
|
@@ -90,6 +91,7 @@
|
|
90
91
|
"title.data-sample search": "data sample search",
|
91
92
|
"title.data-sample view": "data sample view",
|
92
93
|
"title.data-summary list": "data summary list",
|
94
|
+
"title.data-summary period": "data summary period",
|
93
95
|
"title.data-summary search": "data summary search",
|
94
96
|
"title.data-summary view": "data summary view",
|
95
97
|
"title.data-sensor list": "data sensor list",
|
package/translations/zh.json
CHANGED
@@ -15,6 +15,7 @@
|
|
15
15
|
"field.data-sample": "data sample",
|
16
16
|
"field.data-set": "data set",
|
17
17
|
"field.date": "date",
|
18
|
+
"field.month": "month",
|
18
19
|
"field.device-id": "device id",
|
19
20
|
"field.entry-role": "entry role",
|
20
21
|
"field.entry-type": "entry type",
|
@@ -90,6 +91,7 @@
|
|
90
91
|
"title.data-sample search": "data sample search",
|
91
92
|
"title.data-sample view": "data sample view",
|
92
93
|
"title.data-summary list": "data summary list",
|
94
|
+
"title.data-summary period": "data summary period",
|
93
95
|
"title.data-summary search": "data summary search",
|
94
96
|
"title.data-summary view": "data summary view",
|
95
97
|
"title.data-sensor list": "data sensor list",
|