@things-factory/dataset 5.0.0-alpha.18 → 5.0.0-alpha.20

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/README.md ADDED
@@ -0,0 +1,12 @@
1
+ # dataset
2
+ ## partition keys
3
+ At the early stage, partition keys are desinged for dynamic partitioning for Athena. But It will be required so many AWS Glue crawlers also by each 'data-sets'.
4
+ Now partition keys are fixed with some cases. Default is daily dataset, which has S3 Key like 'domain={domain}/datasetid={datasetid}/year=2022/month=3/day=25'. It will be set default with empty value in 'data-sets'.
5
+
6
+ In another case, it is necessary to divide by hours or minutes and store them. This datetime item must be included in the S3 key. It will be required a new Glue Crawler and a Glue Catalog Table. For example if you need to separate directory hourly, you should add "%H" for 'hour' to partition_keys for this. and also should add a new crawler. Look at the below picture for understanding.
7
+
8
+ ![Architecture for partitioning](./assets/data-samples.jpg)
9
+
10
+ These tasks are related for S3 request limitations.
11
+ > __3,500 PUT/COPY/POST/DELETE or 5,500 GET/HEAD requests per second per prefix in a bucket__
12
+ - https://docs.aws.amazon.com/AmazonS3/latest/userguide/optimizing-performance.html
Binary file
@@ -290,7 +290,9 @@ class DataItemList extends localize(i18next)(LitElement) {
290
290
  }
291
291
  })
292
292
 
293
- if (!response.errors) this.grist.fetch()
293
+ if (!response.errors) {
294
+ this.grist.fetch()
295
+ }
294
296
  }
295
297
  }
296
298
 
@@ -0,0 +1,182 @@
1
+ import '@operato/dataset/ox-data-ooc-view.js'
2
+
3
+ import { LitElement, css, html } from 'lit'
4
+ import { i18next, localize } from '@operato/i18n'
5
+
6
+ import { ScrollbarStyles } from '@operato/styles'
7
+ import { client } from '@operato/graphql'
8
+ import gql from 'graphql-tag'
9
+
10
+ class DataOocView extends localize(i18next)(LitElement) {
11
+ static get styles() {
12
+ return [
13
+ ScrollbarStyles,
14
+ css`
15
+ :host {
16
+ display: flex;
17
+ flex-direction: column;
18
+
19
+ background-color: #fff;
20
+ }
21
+
22
+ div[content] {
23
+ flex: 1;
24
+
25
+ display: flex;
26
+ overflow: auto;
27
+ }
28
+
29
+ ox-data-ooc-view {
30
+ flex: 1;
31
+ padding: var(--padding-wide);
32
+ }
33
+
34
+ label[comment] {
35
+ display: flex;
36
+ flex-direction: column;
37
+
38
+ padding: var(--padding-wide);
39
+ }
40
+
41
+ label[comment] div {
42
+ display: flex;
43
+ }
44
+
45
+ mwc-icon {
46
+ color: var(--status-danger-color);
47
+ }
48
+
49
+ textarea {
50
+ border: var(--input-field-border);
51
+ border-radius: var(--input-border-radius);
52
+ padding: var(--input-field-padding);
53
+ font: var(--input-field-font);
54
+ }
55
+
56
+ .button-container {
57
+ display: flex;
58
+ margin-left: auto;
59
+ padding: var(--padding-default);
60
+ }
61
+ `
62
+ ]
63
+ }
64
+
65
+ static get properties() {
66
+ return {
67
+ dataSet: Object,
68
+ dataOoc: Object
69
+ }
70
+ }
71
+
72
+ get sampleView() {
73
+ return this.renderRoot.querySelector('ox-data-ooc-view')
74
+ }
75
+
76
+ render() {
77
+ const state = this.dataOoc.state
78
+
79
+ return html`
80
+ <div content>
81
+ <ox-data-ooc-view .dataSet=${this.dataSet} .dataOoc=${this.dataOoc}></ox-data-ooc-view>
82
+ </div>
83
+
84
+ ${state === 'CREATED' || state === 'REVIEWED'
85
+ ? html`
86
+ <label comment>
87
+ <div><mwc-icon>build_circle</mwc-icon> <span>correction activity</span></div>
88
+ <textarea placeholder="조치 내용을 입력해주세요."></textarea>
89
+ </label>
90
+ `
91
+ : html``}
92
+
93
+ <div class="button-container">
94
+ ${state === 'CREATED'
95
+ ? html`<mwc-button raised @click=${() => this._processOoc('REVIEWED')}
96
+ >${i18next.t('button.reviewed')}</mwc-button
97
+ >`
98
+ : state === 'REVIEWED'
99
+ ? html`<mwc-button raised @click=${() => this._processOoc('CORRECTED')}
100
+ >${i18next.t('button.corrected')}</mwc-button
101
+ >`
102
+ : html``}
103
+ </div>
104
+ `
105
+ }
106
+
107
+ updated(changes) {
108
+ if (changes.has('dataOoc')) {
109
+ this.fetchDataSet()
110
+ }
111
+ }
112
+
113
+ async fetchDataSet() {
114
+ const id = this.dataOoc?.dataSet?.id
115
+
116
+ if (id) {
117
+ const response = await client.query({
118
+ query: gql`
119
+ query ($id: String!) {
120
+ dataSet(id: $id) {
121
+ id
122
+ name
123
+ description
124
+ useCase
125
+ dataItems {
126
+ id
127
+ name
128
+ description
129
+ active
130
+ unit
131
+ tag
132
+ type
133
+ spec
134
+ }
135
+ }
136
+ }
137
+ `,
138
+ variables: {
139
+ id: this.dataOoc.dataSet.id
140
+ }
141
+ })
142
+
143
+ this.dataSet = response.data.dataSet
144
+ }
145
+ }
146
+
147
+ async _processOoc(state) {
148
+ const commentTextArea = this.renderRoot.querySelector('textarea')
149
+ const comment = commentTextArea && commentTextArea.value
150
+ if (!comment || !comment.trim()) {
151
+ commentTextArea.focus()
152
+ return
153
+ }
154
+
155
+ const patch = {
156
+ state,
157
+ correctiveAction: comment
158
+ }
159
+
160
+ const response = await client.mutate({
161
+ mutation: gql`
162
+ mutation ($id: String!, $patch: DataOocPatch!) {
163
+ updateDataOoc(id: $id, patch: $patch) {
164
+ id
165
+ }
166
+ }
167
+ `,
168
+ variables: {
169
+ id: this.dataOoc.id,
170
+ patch
171
+ }
172
+ })
173
+
174
+ if (!response.errors) {
175
+ document.dispatchEvent(
176
+ new CustomEvent('notify', { detail: { message: i18next.t('text.data ooc updated successfully') } })
177
+ )
178
+ }
179
+ }
180
+ }
181
+
182
+ window.customElements.define('data-ooc-view', DataOocView)
@@ -1,15 +1,16 @@
1
1
  import '@operato/data-grist'
2
+ import './data-ooc-view.js'
2
3
 
3
4
  import { CommonButtonStyles, ScrollbarStyles } from '@operato/styles'
4
5
  import { PageView, store } from '@operato/shell'
5
6
  import { css, html } from 'lit'
6
7
  import { i18next, localize } from '@operato/i18n'
8
+ import { notify, openPopup } from '@operato/layout'
7
9
 
8
10
  import { client } from '@operato/graphql'
9
11
  import { connect } from 'pwa-helpers/connect-mixin'
10
12
  import gql from 'graphql-tag'
11
13
  import { isMobileDevice } from '@operato/utils'
12
- import { notify } from '@operato/layout'
13
14
 
14
15
  export class DataOoc extends connect(store)(localize(i18next)(PageView)) {
15
16
  static get properties() {
@@ -55,11 +56,11 @@ export class DataOoc extends connect(store)(localize(i18next)(PageView)) {
55
56
  title: i18next.t('title.data-ooc list'),
56
57
  help: 'integration/ui/data-ooc',
57
58
  actions: [
58
- {
59
- title: i18next.t('button.save'),
60
- action: this._updateDataOoc.bind(this),
61
- ...CommonButtonStyles.save
62
- }
59
+ // {
60
+ // title: i18next.t('button.save'),
61
+ // action: this._updateDataOoc.bind(this),
62
+ // ...CommonButtonStyles.save
63
+ // }
63
64
  ],
64
65
  exportable: {
65
66
  name: i18next.t('title.data-ooc list'),
@@ -117,6 +118,26 @@ export class DataOoc extends connect(store)(localize(i18next)(PageView)) {
117
118
  columns: [
118
119
  { type: 'gutter', gutterName: 'sequence' },
119
120
  { type: 'gutter', gutterName: 'row-selector', multiple: true },
121
+ {
122
+ type: 'gutter',
123
+ gutterName: 'button',
124
+ icon: 'assignment_turned_in',
125
+ handlers: {
126
+ click: (columns, data, column, record, rowIndex) => {
127
+ const popup = openPopup(
128
+ html` <data-ooc-view .dataOoc=${record} style="background-color: white;"></data-ooc-view> `,
129
+ {
130
+ backdrop: true,
131
+ size: 'large',
132
+ title: i18next.t('title.data-ooc view')
133
+ }
134
+ )
135
+ popup.onclosed = () => {
136
+ this.grist.fetch()
137
+ }
138
+ }
139
+ }
140
+ },
120
141
  {
121
142
  type: 'string',
122
143
  name: 'name',
@@ -142,6 +163,12 @@ export class DataOoc extends connect(store)(localize(i18next)(PageView)) {
142
163
  width: 150,
143
164
  imex: true
144
165
  },
166
+ {
167
+ type: 'string',
168
+ name: 'history',
169
+ hidden: true,
170
+ imex: true
171
+ },
145
172
  {
146
173
  type: 'checkbox',
147
174
  name: 'ooc',
@@ -161,14 +188,16 @@ export class DataOoc extends connect(store)(localize(i18next)(PageView)) {
161
188
  width: 30
162
189
  },
163
190
  {
164
- type: 'string',
191
+ type: 'select',
165
192
  name: 'state',
166
193
  label: true,
167
194
  header: i18next.t('field.state'),
168
195
  record: {
169
- editable: false
196
+ editable: false,
197
+ options: ['', 'CREATED', 'REVIEWED', 'CORRECTED']
170
198
  },
171
- width: 150,
199
+ width: 100,
200
+ filter: true,
172
201
  imex: true
173
202
  },
174
203
  {
@@ -193,17 +222,6 @@ export class DataOoc extends connect(store)(localize(i18next)(PageView)) {
193
222
  width: 120,
194
223
  imex: true
195
224
  },
196
- {
197
- type: 'resource-object',
198
- name: 'dataSample',
199
- header: i18next.t('field.data-sample'),
200
- record: {
201
- editable: false
202
- },
203
- sortable: true,
204
- width: 120,
205
- imex: true
206
- },
207
225
  {
208
226
  type: 'json5',
209
227
  name: 'partitionKeys',
@@ -320,6 +338,7 @@ export class DataOoc extends connect(store)(localize(i18next)(PageView)) {
320
338
  spec
321
339
  ooc
322
340
  oos
341
+ history
323
342
  correctiveAction
324
343
  correctedAt
325
344
  corrector {
@@ -350,62 +369,62 @@ export class DataOoc extends connect(store)(localize(i18next)(PageView)) {
350
369
  }
351
370
  }
352
371
 
353
- async _updateDataOoc() {
354
- let patches = this.grist.dirtyRecords
355
- if (patches && patches.length) {
356
- patches = patches.map(patch => {
357
- let patchField = patch.id ? { id: patch.id } : {}
358
- const dirtyFields = patch.__dirtyfields__
359
- for (let key in dirtyFields) {
360
- patchField[key] = dirtyFields[key].after
361
- }
362
- this._setDefaultFieldsValue(patchField)
363
- patchField.cuFlag = patch.__dirty__
364
-
365
- return patchField
366
- })
367
-
368
- const response = await client.mutate({
369
- mutation: gql`
370
- mutation ($patches: [DataOocPatch!]!) {
371
- updateMultipleDataOoc(patches: $patches) {
372
- name
373
- }
374
- }
375
- `,
376
- variables: {
377
- patches
378
- }
379
- })
380
-
381
- if (!response.errors) this.grist.fetch()
382
- }
383
- }
384
-
385
- async _deleteDataOoc() {
386
- if (confirm(i18next.t('text.sure_to_x', { x: i18next.t('text.delete') }))) {
387
- const ids = this.grist.selected.map(record => record.id)
388
- if (ids && ids.length > 0) {
389
- const response = await client.mutate({
390
- mutation: gql`
391
- mutation ($ids: [String!]!) {
392
- deleteDataOocs(ids: $ids)
393
- }
394
- `,
395
- variables: {
396
- ids
397
- }
398
- })
399
-
400
- if (!response.errors) {
401
- this.grist.fetch()
402
- notify({
403
- message: i18next.t('text.info_x_successfully', { x: i18next.t('text.delete') })
404
- })
405
- }
406
- }
407
- }
408
- }
372
+ // async _updateDataOoc() {
373
+ // let patches = this.grist.dirtyRecords
374
+ // if (patches && patches.length) {
375
+ // patches = patches.map(patch => {
376
+ // let patchField = patch.id ? { id: patch.id } : {}
377
+ // const dirtyFields = patch.__dirtyfields__
378
+ // for (let key in dirtyFields) {
379
+ // patchField[key] = dirtyFields[key].after
380
+ // }
381
+ // this._setDefaultFieldsValue(patchField)
382
+ // patchField.cuFlag = patch.__dirty__
383
+
384
+ // return patchField
385
+ // })
386
+
387
+ // const response = await client.mutate({
388
+ // mutation: gql`
389
+ // mutation ($patches: [DataOocPatch!]!) {
390
+ // updateMultipleDataOoc(patches: $patches) {
391
+ // name
392
+ // }
393
+ // }
394
+ // `,
395
+ // variables: {
396
+ // patches
397
+ // }
398
+ // })
399
+
400
+ // if (!response.errors) this.grist.fetch()
401
+ // }
402
+ // }
403
+
404
+ // async _deleteDataOoc() {
405
+ // if (confirm(i18next.t('text.sure_to_x', { x: i18next.t('text.delete') }))) {
406
+ // const ids = this.grist.selected.map(record => record.id)
407
+ // if (ids && ids.length > 0) {
408
+ // const response = await client.mutate({
409
+ // mutation: gql`
410
+ // mutation ($ids: [String!]!) {
411
+ // deleteDataOocs(ids: $ids)
412
+ // }
413
+ // `,
414
+ // variables: {
415
+ // ids
416
+ // }
417
+ // })
418
+
419
+ // if (!response.errors) {
420
+ // this.grist.fetch()
421
+ // notify({
422
+ // message: i18next.t('text.info_x_successfully', { x: i18next.t('text.delete') })
423
+ // })
424
+ // }
425
+ // }
426
+ // }
427
+ // }
409
428
 
410
429
  _exportableData() {
411
430
  let records = []
@@ -0,0 +1,97 @@
1
+ import '@operato/dataset/ox-data-sample-view.js'
2
+
3
+ import { LitElement, css, html } from 'lit'
4
+ import { i18next, localize } from '@operato/i18n'
5
+
6
+ import { ScrollbarStyles } from '@operato/styles'
7
+ import { client } from '@operato/graphql'
8
+ import gql from 'graphql-tag'
9
+
10
+ class DataSampleView extends localize(i18next)(LitElement) {
11
+ static get properties() {
12
+ return {
13
+ dataSet: Object,
14
+ dataSample: Object
15
+ }
16
+ }
17
+
18
+ static get styles() {
19
+ return [
20
+ ScrollbarStyles,
21
+ css`
22
+ :host {
23
+ display: flex;
24
+ flex-direction: column;
25
+
26
+ background-color: #fff;
27
+ }
28
+
29
+ div[content] {
30
+ flex: 1;
31
+
32
+ display: flex;
33
+ overflow: auto;
34
+ }
35
+
36
+ ox-data-sample-view {
37
+ flex: 1;
38
+ padding: var(--padding-wide);
39
+ }
40
+ `
41
+ ]
42
+ }
43
+
44
+ get sampleView() {
45
+ return this.renderRoot.querySelector('ox-data-sample-view')
46
+ }
47
+
48
+ render() {
49
+ return html`
50
+ <div content>
51
+ <ox-data-sample-view .dataSet=${this.dataSet} .dataSample=${this.dataSample}></ox-data-sample-view>
52
+ </div>
53
+ `
54
+ }
55
+
56
+ updated(changes) {
57
+ if (changes.has('dataSample')) {
58
+ this.fetchDataSet()
59
+ }
60
+ }
61
+
62
+ async fetchDataSet() {
63
+ const id = this.dataSample?.dataSet?.id
64
+
65
+ if (id) {
66
+ const response = await client.query({
67
+ query: gql`
68
+ query ($id: String!) {
69
+ dataSet(id: $id) {
70
+ id
71
+ name
72
+ description
73
+ useCase
74
+ dataItems {
75
+ id
76
+ name
77
+ description
78
+ active
79
+ unit
80
+ tag
81
+ type
82
+ spec
83
+ }
84
+ }
85
+ }
86
+ `,
87
+ variables: {
88
+ id: this.dataSample.dataSet.id
89
+ }
90
+ })
91
+
92
+ this.dataSet = response.data.dataSet
93
+ }
94
+ }
95
+ }
96
+
97
+ window.customElements.define('data-sample-view', DataSampleView)
@@ -1,15 +1,16 @@
1
1
  import '@operato/data-grist'
2
+ import './data-sample-view.js'
2
3
 
3
4
  import { PageView, store } from '@operato/shell'
4
5
  import { css, html } from 'lit'
5
6
  import { i18next, localize } from '@operato/i18n'
7
+ import { notify, openPopup } from '@operato/layout'
6
8
 
7
9
  import { ScrollbarStyles } from '@operato/styles'
8
10
  import { client } from '@operato/graphql'
9
11
  import { connect } from 'pwa-helpers/connect-mixin'
10
12
  import gql from 'graphql-tag'
11
13
  import { isMobileDevice } from '@operato/utils'
12
- import { notify } from '@operato/layout'
13
14
 
14
15
  export class DataSample extends connect(store)(localize(i18next)(PageView)) {
15
16
  static get properties() {
@@ -109,6 +110,23 @@ export class DataSample extends connect(store)(localize(i18next)(PageView)) {
109
110
  columns: [
110
111
  { type: 'gutter', gutterName: 'sequence' },
111
112
  { type: 'gutter', gutterName: 'row-selector', multiple: true },
113
+ {
114
+ type: 'gutter',
115
+ gutterName: 'button',
116
+ icon: 'assignment',
117
+ handlers: {
118
+ click: (columns, data, column, record, rowIndex) => {
119
+ openPopup(
120
+ html` <data-sample-view .dataSample=${record} style="background-color: white;"></data-sample-view> `,
121
+ {
122
+ backdrop: true,
123
+ size: 'large',
124
+ title: i18next.t('title.data-sample view')
125
+ }
126
+ )
127
+ }
128
+ }
129
+ },
112
130
  {
113
131
  type: 'string',
114
132
  name: 'name',
@@ -153,6 +153,7 @@ export class DataSensor extends connect(store)(localize(i18next)(PageView)) {
153
153
  editable: true
154
154
  },
155
155
  sortable: true,
156
+ filter: true,
156
157
  width: 60
157
158
  },
158
159
  {
@@ -176,6 +177,7 @@ export class DataSensor extends connect(store)(localize(i18next)(PageView)) {
176
177
  editable: true
177
178
  },
178
179
  sortable: true,
180
+ filter: 'search',
179
181
  width: 150
180
182
  },
181
183
  {
@@ -200,6 +200,7 @@ export class DataSet extends connect(store)(localize(i18next)(PageView)) {
200
200
  record: {
201
201
  editable: true
202
202
  },
203
+ filter: true,
203
204
  sortable: true,
204
205
  width: 60
205
206
  },
@@ -223,6 +224,7 @@ export class DataSet extends connect(store)(localize(i18next)(PageView)) {
223
224
  ]
224
225
  },
225
226
  sortable: true,
227
+ filter: true,
226
228
  width: 60
227
229
  },
228
230
  {
@@ -242,6 +244,7 @@ export class DataSet extends connect(store)(localize(i18next)(PageView)) {
242
244
  }
243
245
  },
244
246
  sortable: true,
247
+ filter: true,
245
248
  width: 80
246
249
  },
247
250
  {
@@ -4,13 +4,13 @@ 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 moment_1 = __importDefault(require("moment"));
7
+ const data_ooc_1 = require("../service/data-ooc/data-ooc");
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");
11
10
  const data_sample_1 = require("../service/data-sample/data-sample");
12
11
  const data_set_1 = require("../service/data-set/data-set");
13
12
  const data_use_case_1 = require("./data-use-case");
13
+ const moment_1 = __importDefault(require("moment"));
14
14
  const debug = require('debug')('things-factory:dataset:controller/save-data-sample');
15
15
  // parse variable javascript string pattern
16
16
  const replaceVariables = (keys, dic) => {
@@ -53,12 +53,20 @@ async function createDataSample(dataSample, context) {
53
53
  spec[dataItem.tag] = Object.assign(Object.assign({}, dataItem.spec), { name: dataItem.name /* do we need ? */ });
54
54
  return spec;
55
55
  }, {});
56
- var partitionKeys = Object.assign({}, dataSet.partitionKeys);
57
56
  const collectedAt = dataSample.collectedAt || new Date();
58
- partitionKeys = formatDate(partitionKeys, (0, moment_1.default)(collectedAt).utc());
59
- partitionKeys = replaceVariables(partitionKeys, Object.assign({ domain: domain.subdomain, dataSetId: dataSample.dataSet.id }, dataSample.data));
57
+ const momentUtc = (0, moment_1.default)(collectedAt).utc();
58
+ const defaultPartitionKeys = {
59
+ domain: domain.subdomain,
60
+ dataSetId: dataSample.dataSet.id,
61
+ year: momentUtc.format('Y'),
62
+ month: momentUtc.format('M'),
63
+ day: momentUtc.format('D')
64
+ };
65
+ var partitionKeys = Object.assign(Object.assign({}, defaultPartitionKeys), dataSet.partitionKeys);
66
+ partitionKeys = formatDate(partitionKeys, momentUtc);
67
+ partitionKeys = replaceVariables(partitionKeys, Object.assign({}, dataSample.data));
60
68
  const { ooc, oos } = data_use_case_1.DataUseCase.evaluate(dataSet, dataItems, dataSample.data) || {};
61
- const result = await tx.getRepository(data_sample_1.DataSample).save(Object.assign(Object.assign({ name: dataSet.name, description: dataSet.description }, dataSample), { domain,
69
+ 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,
62
70
  partitionKeys,
63
71
  spec,
64
72
  ooc,
@@ -68,6 +76,7 @@ async function createDataSample(dataSample, context) {
68
76
  const dataOoc = await tx.getRepository(data_ooc_1.DataOoc).save({
69
77
  name: dataSet.name,
70
78
  description: dataSet.description,
79
+ useCase: dataSet.useCase,
71
80
  dataSet,
72
81
  dataSample: result,
73
82
  data: dataSample.data,
@@ -76,6 +85,16 @@ async function createDataSample(dataSample, context) {
76
85
  spec,
77
86
  ooc,
78
87
  oos,
88
+ history: [
89
+ {
90
+ user: {
91
+ id: user.id,
92
+ name: user.name
93
+ },
94
+ state: data_ooc_1.DataOocStatus.CREATED,
95
+ timestamp: Date.now()
96
+ }
97
+ ],
79
98
  state: data_ooc_1.DataOocStatus.CREATED,
80
99
  collectedAt,
81
100
  creator: user,