@things-factory/dataset 5.0.0-alpha.27 → 5.0.0-alpha.28
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-entry/data-entry-list-page.js +37 -1
- package/client/pages/data-set/data-set-list-page.js +4 -1
- package/dist-server/controllers/create-data-sample.js +14 -9
- package/dist-server/controllers/create-data-sample.js.map +1 -1
- package/dist-server/service/data-set/data-set-query.js +67 -1
- package/dist-server/service/data-set/data-set-query.js.map +1 -1
- package/package.json +17 -16
- package/server/controllers/create-data-sample.ts +21 -13
- package/server/service/data-set/data-set-query.ts +56 -0
- package/translations/en.json +3 -0
- package/translations/ko.json +4 -1
- package/translations/ms.json +4 -1
- package/translations/zh.json +4 -1
@@ -93,7 +93,7 @@ export class DataEntryListPage extends connect(store)(localize(i18next)(PageView
|
|
93
93
|
this.gristConfig = {
|
94
94
|
list: {
|
95
95
|
fields: ['name', 'description'],
|
96
|
-
details: ['schedule', 'active']
|
96
|
+
details: ['schedule', 'active', 'type', 'useCase', 'latestCollectedAt', 'nextSchedule', 'prevSchedule']
|
97
97
|
},
|
98
98
|
columns: [
|
99
99
|
{
|
@@ -208,6 +208,39 @@ export class DataEntryListPage extends connect(store)(localize(i18next)(PageView
|
|
208
208
|
},
|
209
209
|
sortable: true,
|
210
210
|
width: 120
|
211
|
+
},
|
212
|
+
{
|
213
|
+
type: 'datetime',
|
214
|
+
name: 'latestCollectedAt',
|
215
|
+
label: true,
|
216
|
+
header: i18next.t('field.latest-collected-at'),
|
217
|
+
record: {
|
218
|
+
editable: false
|
219
|
+
},
|
220
|
+
sortable: true,
|
221
|
+
width: 180
|
222
|
+
},
|
223
|
+
{
|
224
|
+
type: 'datetime',
|
225
|
+
name: 'prevSchedule',
|
226
|
+
label: true,
|
227
|
+
header: i18next.t('field.prev-schedule'),
|
228
|
+
record: {
|
229
|
+
editable: false
|
230
|
+
},
|
231
|
+
sortable: true,
|
232
|
+
width: 180
|
233
|
+
},
|
234
|
+
{
|
235
|
+
type: 'datetime',
|
236
|
+
name: 'nextSchedule',
|
237
|
+
label: true,
|
238
|
+
header: i18next.t('field.next-schedule'),
|
239
|
+
record: {
|
240
|
+
editable: false
|
241
|
+
},
|
242
|
+
sortable: true,
|
243
|
+
width: 180
|
211
244
|
}
|
212
245
|
],
|
213
246
|
rows: {
|
@@ -302,6 +335,9 @@ export class DataEntryListPage extends connect(store)(localize(i18next)(PageView
|
|
302
335
|
quota
|
303
336
|
spec
|
304
337
|
}
|
338
|
+
latestCollectedAt
|
339
|
+
nextSchedule
|
340
|
+
prevSchedule
|
305
341
|
}
|
306
342
|
total
|
307
343
|
}
|
@@ -17,6 +17,9 @@ import gql from 'graphql-tag'
|
|
17
17
|
import { isMobileDevice } from '@operato/utils'
|
18
18
|
import moment from 'moment-timezone'
|
19
19
|
|
20
|
+
const DEFAULT_TZ = Intl.DateTimeFormat().resolvedOptions().timeZone
|
21
|
+
const TIMEZONE_OPTIONS = ['', DEFAULT_TZ, ...moment.tz.names().filter(tz => tz !== DEFAULT_TZ)]
|
22
|
+
|
20
23
|
export class DataSetListPage extends connect(store)(localize(i18next)(PageView)) {
|
21
24
|
static get properties() {
|
22
25
|
return {
|
@@ -286,7 +289,7 @@ export class DataSetListPage extends connect(store)(localize(i18next)(PageView))
|
|
286
289
|
header: i18next.t('field.timezone'),
|
287
290
|
record: {
|
288
291
|
editable: true,
|
289
|
-
options:
|
292
|
+
options: TIMEZONE_OPTIONS
|
290
293
|
},
|
291
294
|
width: 120
|
292
295
|
},
|
@@ -4,14 +4,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
4
|
};
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
6
6
|
exports.createDataSample = void 0;
|
7
|
-
const
|
7
|
+
const moment_timezone_1 = __importDefault(require("moment-timezone"));
|
8
8
|
const shell_1 = require("@things-factory/shell");
|
9
9
|
const data_item_1 = require("../service/data-item/data-item");
|
10
|
+
const data_ooc_1 = require("../service/data-ooc/data-ooc");
|
10
11
|
const data_sample_1 = require("../service/data-sample/data-sample");
|
11
12
|
const data_set_1 = require("../service/data-set/data-set");
|
12
13
|
const data_use_case_1 = require("./data-use-case");
|
13
14
|
const work_shift_1 = require("@things-factory/work-shift");
|
14
|
-
const moment_1 = __importDefault(require("moment"));
|
15
15
|
const debug = require('debug')('things-factory:dataset:controller/save-data-sample');
|
16
16
|
// parse variable javascript string pattern
|
17
17
|
const replaceVariables = (keys, dic) => {
|
@@ -55,19 +55,24 @@ async function createDataSample(dataSample, context) {
|
|
55
55
|
return spec;
|
56
56
|
}, {});
|
57
57
|
const collectedAt = dataSample.collectedAt || new Date();
|
58
|
-
|
58
|
+
// workDate ex) 2022-04-04
|
59
|
+
const { workDate, workShift } = await (0, work_shift_1.getWorkDateAndShift)(domain, collectedAt);
|
60
|
+
const dateFormat = 'YYYY-MM-DD';
|
61
|
+
// local time
|
62
|
+
const timezone = 'Asia/Seoul';
|
63
|
+
// const collectedAt = dataSample.collectedAt || new Date()
|
64
|
+
const localDateTz = (0, moment_timezone_1.default)(collectedAt).tz(timezone);
|
59
65
|
const defaultPartitionKeys = {
|
60
66
|
domain: domain.subdomain,
|
61
|
-
datasetid: dataSample.dataSet.id
|
62
|
-
|
63
|
-
|
64
|
-
|
67
|
+
datasetid: dataSample.dataSet.id,
|
68
|
+
date: localDateTz.format(dateFormat),
|
69
|
+
workdate: workDate,
|
70
|
+
workshift: workShift
|
65
71
|
};
|
66
72
|
var partitionKeys = Object.assign(Object.assign({}, defaultPartitionKeys), dataSet.partitionKeys);
|
67
|
-
partitionKeys = formatDate(partitionKeys,
|
73
|
+
partitionKeys = formatDate(partitionKeys, localDateTz);
|
68
74
|
partitionKeys = replaceVariables(partitionKeys, Object.assign({}, dataSample.data));
|
69
75
|
const { ooc, oos } = data_use_case_1.DataUseCase.evaluate(dataSet, dataItems, dataSample.data) || {};
|
70
|
-
const { workDate, workShift } = await (0, work_shift_1.getWorkDateAndShift)(domain, collectedAt);
|
71
76
|
const result = await tx.getRepository(data_sample_1.DataSample).save(Object.assign(Object.assign({ name: dataSet.name, description: dataSet.description, useCase: dataSet.useCase }, dataSample), { domain,
|
72
77
|
partitionKeys,
|
73
78
|
spec,
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"create-data-sample.js","sourceRoot":"","sources":["../../server/controllers/create-data-sample.ts"],"names":[],"mappings":";;;;;;AAAA,
|
1
|
+
{"version":3,"file":"create-data-sample.js","sourceRoot":"","sources":["../../server/controllers/create-data-sample.ts"],"names":[],"mappings":"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"}
|
@@ -11,7 +11,7 @@ var __metadata = (this && this.__metadata) || function (k, v) {
|
|
11
11
|
var __param = (this && this.__param) || function (paramIndex, decorator) {
|
12
12
|
return function (target, key) { decorator(target, key, paramIndex); }
|
13
13
|
};
|
14
|
-
var _a;
|
14
|
+
var _a, _b;
|
15
15
|
Object.defineProperty(exports, "__esModule", { value: true });
|
16
16
|
exports.DataSetQuery = void 0;
|
17
17
|
const type_graphql_1 = require("type-graphql");
|
@@ -21,6 +21,8 @@ const shell_1 = require("@things-factory/shell");
|
|
21
21
|
const data_item_1 = require("../data-item/data-item");
|
22
22
|
const data_set_1 = require("./data-set");
|
23
23
|
const data_set_type_1 = require("./data-set-type");
|
24
|
+
const data_sample_1 = require("../data-sample/data-sample");
|
25
|
+
var parser = require('cron-parser');
|
24
26
|
let DataSetQuery = class DataSetQuery {
|
25
27
|
async dataSet(id, context) {
|
26
28
|
const { domain } = context.state;
|
@@ -34,6 +36,13 @@ let DataSetQuery = class DataSetQuery {
|
|
34
36
|
const [items, total] = await (0, typeorm_1.getRepository)(data_set_1.DataSet).findAndCount(convertedParams);
|
35
37
|
return { items, total };
|
36
38
|
}
|
39
|
+
async dataSetsForEntry(params, context) {
|
40
|
+
const { domain } = context.state;
|
41
|
+
/* TODO. 조회한 사용자가 supervisory 역할을 가진 data-set 리스트만 반환 */
|
42
|
+
const convertedParams = (0, shell_1.convertListParams)(params, domain.id);
|
43
|
+
const [items, total] = await (0, typeorm_1.getRepository)(data_set_1.DataSet).findAndCount(convertedParams);
|
44
|
+
return { items, total };
|
45
|
+
}
|
37
46
|
async dataItems(dataSet) {
|
38
47
|
return await (0, typeorm_1.getRepository)(data_item_1.DataItem).find({
|
39
48
|
dataSet
|
@@ -53,6 +62,34 @@ let DataSetQuery = class DataSetQuery {
|
|
53
62
|
async creator(dataSet) {
|
54
63
|
return await (0, typeorm_1.getRepository)(auth_base_1.User).findOne(dataSet.creatorId);
|
55
64
|
}
|
65
|
+
async latestCollectedAt(dataSet) {
|
66
|
+
const sample = await (0, typeorm_1.getRepository)(data_sample_1.DataSample).findOne({
|
67
|
+
select: ['collectedAt'],
|
68
|
+
where: { dataSet },
|
69
|
+
order: { collectedAt: 'DESC' }
|
70
|
+
});
|
71
|
+
return sample === null || sample === void 0 ? void 0 : sample.collectedAt;
|
72
|
+
}
|
73
|
+
async nextSchedule(dataSet) {
|
74
|
+
const { timezone, schedule } = dataSet;
|
75
|
+
if (!schedule) {
|
76
|
+
return;
|
77
|
+
}
|
78
|
+
var interval = parser.parseExpression(schedule, {
|
79
|
+
tz: timezone
|
80
|
+
});
|
81
|
+
return interval.next().toDate();
|
82
|
+
}
|
83
|
+
async prevSchedule(dataSet) {
|
84
|
+
const { timezone, schedule } = dataSet;
|
85
|
+
if (!schedule) {
|
86
|
+
return;
|
87
|
+
}
|
88
|
+
var interval = parser.parseExpression(schedule, {
|
89
|
+
tz: timezone
|
90
|
+
});
|
91
|
+
return interval.prev().toDate();
|
92
|
+
}
|
56
93
|
};
|
57
94
|
__decorate([
|
58
95
|
(0, type_graphql_1.Directive)('@privilege(category: "data-set", privilege: "query", domainOwnerGranted: true)'),
|
@@ -72,6 +109,14 @@ __decorate([
|
|
72
109
|
__metadata("design:paramtypes", [typeof (_a = typeof shell_1.ListParam !== "undefined" && shell_1.ListParam) === "function" ? _a : Object, Object]),
|
73
110
|
__metadata("design:returntype", Promise)
|
74
111
|
], DataSetQuery.prototype, "dataSets", null);
|
112
|
+
__decorate([
|
113
|
+
(0, type_graphql_1.Query)(returns => data_set_type_1.DataSetList, { description: 'To fetch multiple DataSets for data entry manually' }),
|
114
|
+
__param(0, (0, type_graphql_1.Args)()),
|
115
|
+
__param(1, (0, type_graphql_1.Ctx)()),
|
116
|
+
__metadata("design:type", Function),
|
117
|
+
__metadata("design:paramtypes", [typeof (_b = typeof shell_1.ListParam !== "undefined" && shell_1.ListParam) === "function" ? _b : Object, Object]),
|
118
|
+
__metadata("design:returntype", Promise)
|
119
|
+
], DataSetQuery.prototype, "dataSetsForEntry", null);
|
75
120
|
__decorate([
|
76
121
|
(0, type_graphql_1.FieldResolver)(type => [data_item_1.DataItem]),
|
77
122
|
__param(0, (0, type_graphql_1.Root)()),
|
@@ -107,6 +152,27 @@ __decorate([
|
|
107
152
|
__metadata("design:paramtypes", [data_set_1.DataSet]),
|
108
153
|
__metadata("design:returntype", Promise)
|
109
154
|
], DataSetQuery.prototype, "creator", null);
|
155
|
+
__decorate([
|
156
|
+
(0, type_graphql_1.FieldResolver)(type => Date, { nullable: true }),
|
157
|
+
__param(0, (0, type_graphql_1.Root)()),
|
158
|
+
__metadata("design:type", Function),
|
159
|
+
__metadata("design:paramtypes", [data_set_1.DataSet]),
|
160
|
+
__metadata("design:returntype", Promise)
|
161
|
+
], DataSetQuery.prototype, "latestCollectedAt", null);
|
162
|
+
__decorate([
|
163
|
+
(0, type_graphql_1.FieldResolver)(type => Date, { nullable: true }),
|
164
|
+
__param(0, (0, type_graphql_1.Root)()),
|
165
|
+
__metadata("design:type", Function),
|
166
|
+
__metadata("design:paramtypes", [data_set_1.DataSet]),
|
167
|
+
__metadata("design:returntype", Promise)
|
168
|
+
], DataSetQuery.prototype, "nextSchedule", null);
|
169
|
+
__decorate([
|
170
|
+
(0, type_graphql_1.FieldResolver)(type => Date, { nullable: true }),
|
171
|
+
__param(0, (0, type_graphql_1.Root)()),
|
172
|
+
__metadata("design:type", Function),
|
173
|
+
__metadata("design:paramtypes", [data_set_1.DataSet]),
|
174
|
+
__metadata("design:returntype", Promise)
|
175
|
+
], DataSetQuery.prototype, "prevSchedule", null);
|
110
176
|
DataSetQuery = __decorate([
|
111
177
|
(0, type_graphql_1.Resolver)(data_set_1.DataSet)
|
112
178
|
], DataSetQuery);
|
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"data-set-query.js","sourceRoot":"","sources":["../../../server/service/data-set/data-set-query.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,+CAA8F;AAC9F,qCAAuC;AAEvC,yDAAsD;AACtD,iDAA4E;AAE5E,sDAAiD;AACjD,yCAAoC;AACpC,mDAA6C;
|
1
|
+
{"version":3,"file":"data-set-query.js","sourceRoot":"","sources":["../../../server/service/data-set/data-set-query.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@things-factory/dataset",
|
3
|
-
"version": "5.0.0-alpha.
|
3
|
+
"version": "5.0.0-alpha.28",
|
4
4
|
"main": "dist-server/index.js",
|
5
5
|
"browser": "client/index.js",
|
6
6
|
"things-factory": true,
|
@@ -24,20 +24,21 @@
|
|
24
24
|
"migration:create": "node ../../node_modules/typeorm/cli.js migration:create -d ./server/migrations"
|
25
25
|
},
|
26
26
|
"dependencies": {
|
27
|
-
"@operato/app": "1.0.0-alpha.
|
28
|
-
"@operato/data-grist": "1.0.0-alpha.
|
29
|
-
"@operato/dataset": "1.0.0-alpha.
|
30
|
-
"@operato/graphql": "1.0.0-alpha.
|
31
|
-
"@operato/i18n": "1.0.0-alpha.
|
32
|
-
"@operato/layout": "1.0.0-alpha.
|
33
|
-
"@operato/shell": "1.0.0-alpha.
|
34
|
-
"@operato/styles": "1.0.0-alpha.
|
35
|
-
"@operato/utils": "1.0.0-alpha.
|
36
|
-
"@things-factory/auth-base": "^5.0.0-alpha.
|
37
|
-
"@things-factory/env": "^5.0.0-alpha.
|
38
|
-
"@things-factory/shell": "^5.0.0-alpha.
|
39
|
-
"@things-factory/work-shift": "^5.0.0-alpha.
|
40
|
-
"
|
27
|
+
"@operato/app": "1.0.0-alpha.51",
|
28
|
+
"@operato/data-grist": "1.0.0-alpha.51",
|
29
|
+
"@operato/dataset": "1.0.0-alpha.51",
|
30
|
+
"@operato/graphql": "1.0.0-alpha.51",
|
31
|
+
"@operato/i18n": "1.0.0-alpha.51",
|
32
|
+
"@operato/layout": "1.0.0-alpha.51",
|
33
|
+
"@operato/shell": "1.0.0-alpha.51",
|
34
|
+
"@operato/styles": "1.0.0-alpha.51",
|
35
|
+
"@operato/utils": "1.0.0-alpha.51",
|
36
|
+
"@things-factory/auth-base": "^5.0.0-alpha.28",
|
37
|
+
"@things-factory/env": "^5.0.0-alpha.28",
|
38
|
+
"@things-factory/shell": "^5.0.0-alpha.28",
|
39
|
+
"@things-factory/work-shift": "^5.0.0-alpha.28",
|
40
|
+
"cron-parser": "^4.3.0",
|
41
|
+
"moment-timezone": "^0.5.34"
|
41
42
|
},
|
42
|
-
"gitHead": "
|
43
|
+
"gitHead": "9adb6cdfd07c7730800f84a2d532d96f0f436288"
|
43
44
|
}
|
@@ -1,15 +1,17 @@
|
|
1
|
-
import
|
1
|
+
import moment from 'moment-timezone'
|
2
|
+
import { EntityManager } from 'typeorm'
|
3
|
+
|
4
|
+
import { User } from '@things-factory/auth-base'
|
2
5
|
import { Domain, getRedirectSubdomainPath, pubsub } from '@things-factory/shell'
|
3
6
|
|
4
7
|
import { DataItem } from '../service/data-item/data-item'
|
8
|
+
import { DataOoc, DataOocStatus } from '../service/data-ooc/data-ooc'
|
5
9
|
import { DataSample } from '../service/data-sample/data-sample'
|
10
|
+
import { NewDataSample } from '../service/data-sample/data-sample-type'
|
6
11
|
import { DataSet } from '../service/data-set/data-set'
|
7
12
|
import { DataUseCase } from './data-use-case'
|
8
|
-
import { EntityManager } from 'typeorm'
|
9
|
-
import { NewDataSample } from '../service/data-sample/data-sample-type'
|
10
|
-
import { User } from '@things-factory/auth-base'
|
11
13
|
import { getWorkDateAndShift } from '@things-factory/work-shift'
|
12
|
-
|
14
|
+
|
13
15
|
|
14
16
|
const debug = require('debug')('things-factory:dataset:controller/save-data-sample')
|
15
17
|
|
@@ -74,13 +76,21 @@ export async function createDataSample(
|
|
74
76
|
}, {})
|
75
77
|
|
76
78
|
const collectedAt = dataSample.collectedAt || new Date()
|
77
|
-
|
79
|
+
// workDate ex) 2022-04-04
|
80
|
+
const { workDate, workShift } = await getWorkDateAndShift(domain, collectedAt)
|
81
|
+
|
82
|
+
const dateFormat = 'YYYY-MM-DD'
|
83
|
+
// local time
|
84
|
+
const timezone = 'Asia/Seoul'
|
85
|
+
|
86
|
+
// const collectedAt = dataSample.collectedAt || new Date()
|
87
|
+
const localDateTz = moment(collectedAt).tz(timezone)
|
78
88
|
const defaultPartitionKeys = {
|
79
89
|
domain: domain.subdomain,
|
80
|
-
datasetid: dataSample.dataSet.id /* It should not be 'data_set_id' as column name duplicated for Glue
|
81
|
-
|
82
|
-
|
83
|
-
|
90
|
+
datasetid: dataSample.dataSet.id, /* It should not be 'data_set_id' as column name duplicated for Glue */
|
91
|
+
date: localDateTz.format(dateFormat), /* local time date */
|
92
|
+
workdate: workDate, /* working date */
|
93
|
+
workshift: workShift
|
84
94
|
}
|
85
95
|
|
86
96
|
var partitionKeys = {
|
@@ -88,14 +98,12 @@ export async function createDataSample(
|
|
88
98
|
...dataSet.partitionKeys
|
89
99
|
}
|
90
100
|
|
91
|
-
partitionKeys = formatDate(partitionKeys,
|
101
|
+
partitionKeys = formatDate(partitionKeys, localDateTz)
|
92
102
|
partitionKeys = replaceVariables(partitionKeys, {
|
93
103
|
...dataSample.data
|
94
104
|
})
|
95
105
|
|
96
106
|
const { ooc, oos } = DataUseCase.evaluate(dataSet, dataItems, dataSample.data) || {}
|
97
|
-
const { workDate, workShift } = await getWorkDateAndShift(domain, collectedAt)
|
98
|
-
|
99
107
|
const result = await tx.getRepository(DataSample).save({
|
100
108
|
name: dataSet.name,
|
101
109
|
description: dataSet.description,
|
@@ -7,6 +7,9 @@ import { convertListParams, Domain, ListParam } from '@things-factory/shell'
|
|
7
7
|
import { DataItem } from '../data-item/data-item'
|
8
8
|
import { DataSet } from './data-set'
|
9
9
|
import { DataSetList } from './data-set-type'
|
10
|
+
import { DataSample } from '../data-sample/data-sample'
|
11
|
+
|
12
|
+
var parser = require('cron-parser')
|
10
13
|
|
11
14
|
@Resolver(DataSet)
|
12
15
|
export class DataSetQuery {
|
@@ -31,6 +34,18 @@ export class DataSetQuery {
|
|
31
34
|
return { items, total }
|
32
35
|
}
|
33
36
|
|
37
|
+
@Query(returns => DataSetList, { description: 'To fetch multiple DataSets for data entry manually' })
|
38
|
+
async dataSetsForEntry(@Args() params: ListParam, @Ctx() context: any): Promise<DataSetList> {
|
39
|
+
const { domain } = context.state
|
40
|
+
|
41
|
+
/* TODO. 조회한 사용자가 supervisory 역할을 가진 data-set 리스트만 반환 */
|
42
|
+
|
43
|
+
const convertedParams = convertListParams(params, domain.id)
|
44
|
+
const [items, total] = await getRepository(DataSet).findAndCount(convertedParams)
|
45
|
+
|
46
|
+
return { items, total }
|
47
|
+
}
|
48
|
+
|
34
49
|
@FieldResolver(type => [DataItem])
|
35
50
|
async dataItems(@Root() dataSet: DataSet): Promise<DataItem[]> {
|
36
51
|
return await getRepository(DataItem).find({
|
@@ -59,4 +74,45 @@ export class DataSetQuery {
|
|
59
74
|
async creator(@Root() dataSet: DataSet): Promise<User> {
|
60
75
|
return await getRepository(User).findOne(dataSet.creatorId)
|
61
76
|
}
|
77
|
+
|
78
|
+
@FieldResolver(type => Date, { nullable: true })
|
79
|
+
async latestCollectedAt(@Root() dataSet: DataSet): Promise<Date> {
|
80
|
+
const sample = await getRepository(DataSample).findOne({
|
81
|
+
select: ['collectedAt'],
|
82
|
+
where: { dataSet },
|
83
|
+
order: { collectedAt: 'DESC' }
|
84
|
+
})
|
85
|
+
|
86
|
+
return sample?.collectedAt
|
87
|
+
}
|
88
|
+
|
89
|
+
@FieldResolver(type => Date, { nullable: true })
|
90
|
+
async nextSchedule(@Root() dataSet: DataSet): Promise<Date> {
|
91
|
+
const { timezone, schedule } = dataSet
|
92
|
+
|
93
|
+
if (!schedule) {
|
94
|
+
return
|
95
|
+
}
|
96
|
+
|
97
|
+
var interval = parser.parseExpression(schedule, {
|
98
|
+
tz: timezone
|
99
|
+
})
|
100
|
+
|
101
|
+
return interval.next().toDate()
|
102
|
+
}
|
103
|
+
|
104
|
+
@FieldResolver(type => Date, { nullable: true })
|
105
|
+
async prevSchedule(@Root() dataSet: DataSet): Promise<Date> {
|
106
|
+
const { timezone, schedule } = dataSet
|
107
|
+
|
108
|
+
if (!schedule) {
|
109
|
+
return
|
110
|
+
}
|
111
|
+
|
112
|
+
var interval = parser.parseExpression(schedule, {
|
113
|
+
tz: timezone
|
114
|
+
})
|
115
|
+
|
116
|
+
return interval.prev().toDate()
|
117
|
+
}
|
62
118
|
}
|
package/translations/en.json
CHANGED
@@ -10,11 +10,14 @@
|
|
10
10
|
"field.data-sample": "data sample",
|
11
11
|
"field.data-set": "data set",
|
12
12
|
"field.device-id": "device id",
|
13
|
+
"field.latest-collected-at": "latest collected at",
|
13
14
|
"field.netmask": "network mask",
|
15
|
+
"field.next-schedule": "next schedule",
|
14
16
|
"field.oos": "out of critical limit",
|
15
17
|
"field.ooc": "out of control limit",
|
16
18
|
"field.options": "options",
|
17
19
|
"field.partition-keys": "partition keys",
|
20
|
+
"field.prev-schedule": "previous schedule",
|
18
21
|
"field.quota": "sampling #",
|
19
22
|
"field.raw-data": "raw data",
|
20
23
|
"field.ref-by": "ref. by",
|
package/translations/ko.json
CHANGED
@@ -10,11 +10,14 @@
|
|
10
10
|
"field.data-sample": "데이타 샘플",
|
11
11
|
"field.data-set": "데이타셋",
|
12
12
|
"field.device-id": "디바이스 아이디",
|
13
|
+
"field.latest-collected-at": "최근 수집 시간",
|
13
14
|
"field.netmask": "네트워크마스크",
|
15
|
+
"field.next-schedule": "다음 수집계획",
|
14
16
|
"field.oos": "허용한계 이탈여부",
|
15
17
|
"field.ooc": "관리한계 이탈여부",
|
16
18
|
"field.options": "선택옵션",
|
17
19
|
"field.partition-keys": "파티션 키",
|
20
|
+
"field.prev-schedule": "이전 수집계획",
|
18
21
|
"field.quota": "샘플수",
|
19
22
|
"field.raw-data": "데이타 원본",
|
20
23
|
"field.ref-by": "참조아이템",
|
@@ -37,4 +40,4 @@
|
|
37
40
|
"title.data-sample view": "데이타 샘플 상세",
|
38
41
|
"title.data-sensor list": "데이타 센서 조회",
|
39
42
|
"title.data-set list": "데이타 셋 조회"
|
40
|
-
}
|
43
|
+
}
|
package/translations/ms.json
CHANGED
@@ -10,11 +10,14 @@
|
|
10
10
|
"field.data-sample": "data sample",
|
11
11
|
"field.data-set": "data set",
|
12
12
|
"field.device-id": "device id",
|
13
|
+
"field.latest-collected-at": "latest collected at",
|
13
14
|
"field.netmask": "network mask",
|
15
|
+
"field.next-schedule": "next schedule",
|
14
16
|
"field.oos": "out of critical limit",
|
15
17
|
"field.ooc": "out of control limit",
|
16
18
|
"field.options": "options",
|
17
19
|
"field.partition-keys": "partition keys",
|
20
|
+
"field.prev-schedule": "previous schedule",
|
18
21
|
"field.quota": "sampling #",
|
19
22
|
"field.raw-data": "raw data",
|
20
23
|
"field.ref-by": "ref. by",
|
@@ -37,4 +40,4 @@
|
|
37
40
|
"title.data-sample view": "data sample view",
|
38
41
|
"title.data-sensor list": "data sensor list",
|
39
42
|
"title.data-set list": "data set list"
|
40
|
-
}
|
43
|
+
}
|
package/translations/zh.json
CHANGED
@@ -10,11 +10,14 @@
|
|
10
10
|
"field.data-sample": "data sample",
|
11
11
|
"field.data-set": "data set",
|
12
12
|
"field.device-id": "device id",
|
13
|
+
"field.latest-collected-at": "latest collected at",
|
13
14
|
"field.netmask": "network mask",
|
15
|
+
"field.next-schedule": "next schedule",
|
14
16
|
"field.oos": "out of critical limit",
|
15
17
|
"field.ooc": "out of control limit",
|
16
18
|
"field.options": "options",
|
17
19
|
"field.partition-keys": "partition keys",
|
20
|
+
"field.prev-schedule": "previous schedule",
|
18
21
|
"field.quota": "sampling #",
|
19
22
|
"field.raw-data": "raw data",
|
20
23
|
"field.ref-by": "ref. by",
|
@@ -37,4 +40,4 @@
|
|
37
40
|
"title.data-sample view": "data sample view",
|
38
41
|
"title.data-sensor list": "data sensor list",
|
39
42
|
"title.data-set list": "data set list"
|
40
|
-
}
|
43
|
+
}
|