@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.
Files changed (32) hide show
  1. package/client/pages/data-summary/data-summary-period-page.ts +436 -0
  2. package/client/pages/data-summary/data-summary-search-page.ts +0 -12
  3. package/client/route.ts +4 -0
  4. package/dist-client/pages/data-summary/data-summary-group-page.d.ts +82 -0
  5. package/dist-client/pages/data-summary/data-summary-group-page.js +435 -0
  6. package/dist-client/pages/data-summary/data-summary-group-page.js.map +1 -0
  7. package/dist-client/pages/data-summary/data-summary-period-page.d.ts +82 -0
  8. package/dist-client/pages/data-summary/data-summary-period-page.js +436 -0
  9. package/dist-client/pages/data-summary/data-summary-period-page.js.map +1 -0
  10. package/dist-client/pages/data-summary/data-summary-search-page.js +0 -12
  11. package/dist-client/pages/data-summary/data-summary-search-page.js.map +1 -1
  12. package/dist-client/route.js +3 -0
  13. package/dist-client/route.js.map +1 -1
  14. package/dist-client/tsconfig.tsbuildinfo +1 -1
  15. package/dist-server/controllers/generate-data-summary.js.map +1 -1
  16. package/dist-server/controllers/query-data-summary-by-period.js +170 -0
  17. package/dist-server/controllers/query-data-summary-by-period.js.map +1 -0
  18. package/dist-server/service/data-summary/data-summary-query.js +48 -0
  19. package/dist-server/service/data-summary/data-summary-query.js.map +1 -1
  20. package/dist-server/service/data-summary/data-summary-type.js +89 -1
  21. package/dist-server/service/data-summary/data-summary-type.js.map +1 -1
  22. package/dist-server/tsconfig.tsbuildinfo +1 -1
  23. package/package.json +2 -2
  24. package/server/controllers/generate-data-summary.ts +0 -1
  25. package/server/controllers/query-data-summary-by-period.ts +200 -0
  26. package/server/service/data-summary/data-summary-query.ts +48 -2
  27. package/server/service/data-summary/data-summary-type.ts +57 -0
  28. package/things-factory.config.js +4 -0
  29. package/translations/en.json +2 -0
  30. package/translations/ko.json +2 -0
  31. package/translations/ms.json +2 -0
  32. 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.82",
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": "6cd2a84db2c9ad8d9e75ecbdfcc53df1b20cf79f"
53
+ "gitHead": "b858eba746efe1b59c54bb2e0f4b3b5e67ee8741"
54
54
  }
@@ -1,7 +1,6 @@
1
1
  const statistics = require('simple-statistics')
2
2
  const deepClone = require('lodash/cloneDeep')
3
3
 
4
- import { Between } from 'typeorm'
5
4
  import moment from 'moment-timezone'
6
5
 
7
6
  import { Sorting, getQueryBuilderFromListParams } from '@things-factory/shell'
@@ -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])
@@ -28,6 +28,10 @@ export default {
28
28
  tagname: 'data-summary-search-page',
29
29
  page: 'data-summary-search'
30
30
  },
31
+ {
32
+ tagname: 'data-summary-period-page',
33
+ page: 'data-summary-period'
34
+ },
31
35
  {
32
36
  tagname: 'data-ooc-view-page',
33
37
  page: 'data-ooc'
@@ -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",
@@ -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": "데이타 센서 조회",
@@ -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",
@@ -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",