@things-factory/dataset 5.0.14 → 5.0.15

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 (96) hide show
  1. package/README.md +20 -0
  2. package/assets/data-report.jpg +0 -0
  3. package/assets/glue-table-indices.png +0 -0
  4. package/client/pages/data-archive/data-archive-list-page.js +290 -0
  5. package/client/pages/data-archive/data-archive-request-popup.js +198 -0
  6. package/client/pages/data-key-set/data-key-set-list-page.js +97 -2
  7. package/client/pages/data-report/data-report-embed-page.js +16 -12
  8. package/client/pages/data-report/data-report-list-page.js +16 -13
  9. package/client/pages/data-report/data-report-samples-page.js +186 -0
  10. package/client/pages/data-set/data-set-list-page.js +8 -0
  11. package/client/route.js +20 -12
  12. package/config/config.development.js +21 -0
  13. package/config/config.production.js +32 -11
  14. package/dist-server/controllers/create-data-sample.js +3 -1
  15. package/dist-server/controllers/create-data-sample.js.map +1 -1
  16. package/dist-server/controllers/jasper-report.js +17 -12
  17. package/dist-server/controllers/jasper-report.js.map +1 -1
  18. package/dist-server/controllers/shiny-report.js +44 -0
  19. package/dist-server/controllers/shiny-report.js.map +1 -0
  20. package/dist-server/routes.js +8 -0
  21. package/dist-server/routes.js.map +1 -1
  22. package/dist-server/service/data-archive/data-archive-mutation.js +211 -0
  23. package/dist-server/service/data-archive/data-archive-mutation.js.map +1 -0
  24. package/dist-server/service/data-archive/data-archive-query.js +85 -0
  25. package/dist-server/service/data-archive/data-archive-query.js.map +1 -0
  26. package/dist-server/service/data-archive/data-archive-type.js +74 -0
  27. package/dist-server/service/data-archive/data-archive-type.js.map +1 -0
  28. package/dist-server/service/data-archive/data-archive.js +80 -0
  29. package/dist-server/service/data-archive/data-archive.js.map +1 -0
  30. package/dist-server/service/data-archive/index.js +9 -0
  31. package/dist-server/service/data-archive/index.js.map +1 -0
  32. package/dist-server/service/data-key-set/data-key-item-type.js.map +1 -1
  33. package/dist-server/service/data-key-set/data-key-set-mutation.js.map +1 -1
  34. package/dist-server/service/data-key-set/data-key-set-query.js.map +1 -1
  35. package/dist-server/service/data-key-set/data-key-set-type.js +17 -0
  36. package/dist-server/service/data-key-set/data-key-set-type.js.map +1 -1
  37. package/dist-server/service/data-key-set/data-key-set.js +11 -0
  38. package/dist-server/service/data-key-set/data-key-set.js.map +1 -1
  39. package/dist-server/service/data-ooc/data-ooc-mutation.js.map +1 -1
  40. package/dist-server/service/data-ooc/data-ooc-query.js.map +1 -1
  41. package/dist-server/service/data-ooc/data-ooc-subscription.js.map +1 -1
  42. package/dist-server/service/data-ooc/data-ooc-type.js.map +1 -1
  43. package/dist-server/service/data-ooc/data-ooc.js.map +1 -1
  44. package/dist-server/service/data-sample/data-sample-mutation.js.map +1 -1
  45. package/dist-server/service/data-sample/data-sample-query.js.map +1 -1
  46. package/dist-server/service/data-sample/data-sample-type.js.map +1 -1
  47. package/dist-server/service/data-sample/data-sample.js.map +1 -1
  48. package/dist-server/service/data-sensor/data-sensor-mutation.js.map +1 -1
  49. package/dist-server/service/data-sensor/data-sensor-query.js.map +1 -1
  50. package/dist-server/service/data-sensor/data-sensor-type.js.map +1 -1
  51. package/dist-server/service/data-sensor/data-sensor.js.map +1 -1
  52. package/dist-server/service/data-set/data-item-type.js.map +1 -1
  53. package/dist-server/service/data-set/data-set-mutation.js.map +1 -1
  54. package/dist-server/service/data-set/data-set-query.js.map +1 -1
  55. package/dist-server/service/data-set/data-set-type.js.map +1 -1
  56. package/dist-server/service/data-set/data-set.js +2 -0
  57. package/dist-server/service/data-set/data-set.js.map +1 -1
  58. package/dist-server/service/data-set-history/data-set-history-query.js.map +1 -1
  59. package/dist-server/service/data-set-history/data-set-history-type.js.map +1 -1
  60. package/dist-server/service/data-set-history/data-set-history.js.map +1 -1
  61. package/dist-server/service/data-set-history/event-subscriber.js.map +1 -1
  62. package/dist-server/service/data-spec/data-spec-query.js.map +1 -1
  63. package/dist-server/service/data-spec/data-spec.js.map +1 -1
  64. package/dist-server/service/index.js +5 -2
  65. package/dist-server/service/index.js.map +1 -1
  66. package/dist-server/tsconfig.tsbuildinfo +1 -1
  67. package/helps/dataset/data-archive.md +6 -0
  68. package/package.json +7 -7
  69. package/server/controllers/create-data-sample.ts +4 -0
  70. package/server/controllers/jasper-report.ts +17 -12
  71. package/server/controllers/shiny-report.ts +63 -0
  72. package/server/routes.ts +11 -0
  73. package/server/service/data-archive/data-archive-mutation.ts +234 -0
  74. package/server/service/data-archive/data-archive-query.ts +56 -0
  75. package/server/service/data-archive/data-archive-type.ts +49 -0
  76. package/server/service/data-archive/data-archive.ts +69 -0
  77. package/server/service/data-archive/index.ts +6 -0
  78. package/server/service/data-key-set/data-key-set-type.ts +13 -0
  79. package/server/service/data-key-set/data-key-set.ts +9 -0
  80. package/server/service/data-set/data-set.ts +3 -1
  81. package/server/service/index.ts +5 -2
  82. package/things-factory.config.js +20 -12
  83. package/translations/en.json +15 -1
  84. package/translations/ko.json +14 -1
  85. package/translations/ms.json +2 -0
  86. package/translations/zh.json +2 -0
  87. package/dist-server/service/data-item/data-item-mutation.js +0 -69
  88. package/dist-server/service/data-item/data-item-mutation.js.map +0 -1
  89. package/dist-server/service/data-item/data-item-query.js +0 -100
  90. package/dist-server/service/data-item/data-item-query.js.map +0 -1
  91. package/dist-server/service/data-item/data-item-type.js +0 -80
  92. package/dist-server/service/data-item/data-item-type.js.map +0 -1
  93. package/dist-server/service/data-item/data-item.js +0 -136
  94. package/dist-server/service/data-item/data-item.js.map +0 -1
  95. package/dist-server/service/data-item/index.js +0 -9
  96. package/dist-server/service/data-item/index.js.map +0 -1
package/README.md CHANGED
@@ -1,6 +1,12 @@
1
1
  # dataset
2
2
  ## Architecture for data-samples
3
3
  ![Architecture for data-samples](./assets/data-samples.jpg)
4
+ ## data-report process
5
+ ![data-report process](./assets/data-report.jpg)
6
+
7
+ 'shiny' uses the next lambda. But 'jasper' is not using this lambda now. Consider using the same data service.
8
+ - https://github.com/operatochef/serverless/tree/main/func-transform-data-samples
9
+
4
10
  ## Partition Keys
5
11
  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'.
6
12
 
@@ -17,7 +23,21 @@ Now partition keys are fixed
17
23
  - workdate: working date
18
24
  - workshift
19
25
 
26
+ ### Glue Catalog Table Indices
27
+ Partitions can be indices. You can create new indices with combination of partitions.
28
+ It affects query performance and finding partitions.
29
+ ![partitions and indices](./assets/glue-table-indices.png)
30
+ ## Data Samples
31
+ sample data could have an own timestamp if it is from a sensor data.
32
+ 'collected_at' uses this timestamp.
33
+ Manual type of data is used 'Date.now() and new Date()'
34
+ Graphql might affect timezone of server os for Date type field.
35
+ 'TZ=UTC node' can help to solve this when server starts.
36
+ Or use ``` process.env.TZ = 'UTC' ```.
37
+ Now applied in 'create-data-samples.ts'
38
+
20
39
  ## PostgreSQL query for dataset
40
+ This is not using now.
21
41
  ```
22
42
  select
23
43
  data::json#>>'{product,0}' as product,
Binary file
Binary file
@@ -0,0 +1,290 @@
1
+ import '@operato/data-grist'
2
+
3
+ import gql from 'graphql-tag'
4
+ import { css, html } from 'lit'
5
+ import { connect } from 'pwa-helpers/connect-mixin'
6
+
7
+ import { client } from '@operato/graphql'
8
+ import { i18next, localize } from '@operato/i18n'
9
+ import { openPopup } from '@operato/layout'
10
+ import { PageView, store } from '@operato/shell'
11
+ import { CommonGristStyles, ScrollbarStyles } from '@operato/styles'
12
+ import { isMobileDevice } from '@operato/utils'
13
+
14
+ import './data-archive-request-popup'
15
+
16
+ export class DataArchiveListPage extends connect(store)(localize(i18next)(PageView)) {
17
+ static get properties() {
18
+ return {
19
+ active: String,
20
+ gristConfig: Object,
21
+ mode: String
22
+ }
23
+ }
24
+
25
+ static get styles() {
26
+ return [
27
+ ScrollbarStyles,
28
+ CommonGristStyles,
29
+ css`
30
+ :host {
31
+ display: flex;
32
+ flex-direction: column;
33
+
34
+ overflow: hidden;
35
+ }
36
+
37
+ ox-grist {
38
+ overflow-y: auto;
39
+ flex: 1;
40
+ }
41
+ `
42
+ ]
43
+ }
44
+
45
+ get context() {
46
+ return {
47
+ title: i18next.t('title.data-archive list'),
48
+ help: 'dataset/data-archive',
49
+ actions: [],
50
+ exportable: {
51
+ name: i18next.t('title.data-archive list'),
52
+ data: this._exportableData.bind(this)
53
+ }
54
+ }
55
+ }
56
+
57
+ render() {
58
+ const mode = this.mode || (isMobileDevice() ? 'LIST' : 'GRID')
59
+
60
+ return html`
61
+ <ox-grist
62
+ .mode=${mode}
63
+ .config=${this.gristConfig}
64
+ .fetchHandler=${this.fetchHandler.bind(this)}
65
+ ?url-params-sensitive=${false /* this.active */}
66
+ >
67
+ <div slot="headroom">
68
+ <div id="filters">
69
+ <ox-filters-form autofocus></ox-filters-form>
70
+ </div>
71
+
72
+ <div id="sorters">
73
+ Sort
74
+ <mwc-icon
75
+ @click=${e => {
76
+ const target = e.currentTarget
77
+ this.renderRoot.querySelector('#sorter-control').open({
78
+ right: 0,
79
+ top: target.offsetTop + target.offsetHeight
80
+ })
81
+ }}
82
+ >expand_more</mwc-icon
83
+ >
84
+ <ox-popup id="sorter-control">
85
+ <ox-sorters-control> </ox-sorters-control>
86
+ </ox-popup>
87
+ </div>
88
+
89
+ <div id="modes">
90
+ <mwc-icon @click=${() => (this.mode = 'GRID')} ?active=${mode == 'GRID'}>grid_on</mwc-icon>
91
+ <mwc-icon @click=${() => (this.mode = 'LIST')} ?active=${mode == 'LIST'}>format_list_bulleted</mwc-icon>
92
+ <mwc-icon @click=${() => (this.mode = 'CARD')} ?active=${mode == 'CARD'}>apps</mwc-icon>
93
+ </div>
94
+
95
+ <mwc-button
96
+ dense
97
+ raised
98
+ label=${i18next.t('button.request')}
99
+ @click=${this.openArchivePopup.bind(this)}
100
+ icon="archive"
101
+ ></mwc-button>
102
+ </div>
103
+ </ox-grist>
104
+ `
105
+ }
106
+
107
+ get grist() {
108
+ return this.renderRoot.querySelector('ox-grist')
109
+ }
110
+
111
+ async pageInitialized(lifecycle) {
112
+ const today = new Date().toISOString().split('T')[0]
113
+
114
+ this.gristConfig = {
115
+ list: { fields: ['updater', 'updatedAt'] },
116
+ columns: [
117
+ { type: 'gutter', gutterName: 'sequence' },
118
+ // { type: 'gutter', gutterName: 'row-selector', multiple: true },
119
+ {
120
+ type: 'resource-object',
121
+ name: 'creator',
122
+ header: i18next.t('field.creator'),
123
+ sortable: true,
124
+ width: 120,
125
+ imex: true
126
+ },
127
+ {
128
+ type: 'datetime',
129
+ name: 'createdAt',
130
+ header: i18next.t('field.created_at'),
131
+ sortable: true,
132
+ width: 180,
133
+ imex: true
134
+ },
135
+ {
136
+ type: 'datetime',
137
+ name: 'updatedAt',
138
+ header: i18next.t('field.updated_at'),
139
+ sortable: true,
140
+ width: 180,
141
+ imex: true
142
+ },
143
+ {
144
+ type: 'json5',
145
+ name: 'requestParams',
146
+ header: i18next.t('field.request-params'),
147
+ record: {
148
+ editable: false
149
+ },
150
+ width: 200,
151
+ imex: true
152
+ },
153
+ {
154
+ type: 'string',
155
+ name: 'downloadUrl',
156
+ label: true,
157
+ header: i18next.t('field.download-url'),
158
+ record: {
159
+ editable: false
160
+ },
161
+ width: 240,
162
+ imex: true
163
+ },
164
+ {
165
+ type: 'string',
166
+ name: 'status',
167
+ label: true,
168
+ header: i18next.t('field.status'),
169
+ record: {
170
+ editable: false
171
+ },
172
+ sortable: true,
173
+ width: 120,
174
+ imex: true
175
+ }
176
+ ],
177
+ rows: {
178
+ appendable: false,
179
+ selectable: {
180
+ multiple: true
181
+ }
182
+ },
183
+ sorters: [
184
+ {
185
+ name: 'createdAt',
186
+ desc: true
187
+ }
188
+ ]
189
+ }
190
+ }
191
+
192
+ closePopupAndRefesh() {
193
+ this.popup && this.popup.close()
194
+ this.grist.fetch()
195
+ }
196
+
197
+ async openArchivePopup() {
198
+ this.popup = openPopup(
199
+ html`
200
+ <data-archive-request-popup
201
+ @requested="${this.closePopupAndRefesh.bind(this)}"
202
+ @created="${this.closePopupAndRefesh.bind(this)}"
203
+ ></data-archive-request-popup>`,
204
+ {
205
+ backdrop: true,
206
+ size: 'small',
207
+ title: i18next.t('title.data-archive request popup')
208
+ })
209
+ }
210
+
211
+ async fetchHandler({ page, limit, sortings = [], filters = [] }) {
212
+ const response = await client.query({
213
+ query: gql`
214
+ query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
215
+ responses: dataArchives(filters: $filters, pagination: $pagination, sortings: $sortings) {
216
+ items {
217
+ id
218
+ type
219
+ requestParams
220
+ downloadUrl
221
+ status
222
+ creator {
223
+ id
224
+ name
225
+ }
226
+ updater {
227
+ id
228
+ name
229
+ }
230
+ updatedAt
231
+ createdAt
232
+ }
233
+ total
234
+ }
235
+ }
236
+ `,
237
+ variables: {
238
+ filters,
239
+ pagination: { page, limit },
240
+ sortings
241
+ }
242
+ })
243
+
244
+ return {
245
+ total: response.data.responses.total || 0,
246
+ records: response.data.responses.items || []
247
+ }
248
+ }
249
+
250
+ _exportableData() {
251
+ let records = []
252
+ if (this.grist.selected && this.grist.selected.length > 0) {
253
+ records = this.grist.selected
254
+ } else {
255
+ records = this.grist.data.records
256
+ }
257
+
258
+ var headerSetting = this.grist.compiledConfig.columns
259
+ .filter(column => column.type !== 'gutter' && column.record !== undefined && column.imex !== undefined)
260
+ .map(column => {
261
+ return column.imex === true
262
+ ? {
263
+ header: column.header.renderer(),
264
+ key: column.name,
265
+ width: column.width,
266
+ type: column.type
267
+ }
268
+ : column.imex
269
+ })
270
+
271
+ var data = records.map(item => {
272
+ return {
273
+ id: item.id,
274
+ ...this.gristConfig.columns
275
+ .filter(column => column.type !== 'gutter' && column.record !== undefined && column.imex !== undefined)
276
+ .reduce((record, column) => {
277
+ const key = column.imex === true ? column.name : column.imex.key
278
+ record[key] = key
279
+ .split('.')
280
+ .reduce((obj, key) => (obj && obj[key] !== 'undefined' ? obj[key] : undefined), item)
281
+ return record
282
+ }, {})
283
+ }
284
+ })
285
+
286
+ return { header: headerSetting, data: data }
287
+ }
288
+ }
289
+
290
+ window.customElements.define('data-archive-list-page', DataArchiveListPage)
@@ -0,0 +1,198 @@
1
+ import { SingleColumnFormStyles } from '@things-factory/form-ui'
2
+ import '@things-factory/grist-ui'
3
+ import { i18next, localize } from '@things-factory/i18n-base'
4
+ import { client, CustomAlert } from '@things-factory/shell'
5
+ import gql from 'graphql-tag'
6
+ import { css, html, LitElement } from 'lit-element'
7
+ import { notify } from '@things-factory/notification'
8
+ import moment from 'moment-timezone'
9
+
10
+ class DataArchiveRequestPopup extends localize(i18next)(LitElement) {
11
+ static get properties() {
12
+ return {
13
+ dataSetTypes: Array
14
+ }
15
+ }
16
+
17
+ static get styles() {
18
+ return [
19
+ SingleColumnFormStyles,
20
+ css`
21
+ :host {
22
+ padding: 10px;
23
+ display: flex;
24
+ flex-direction: column;
25
+ overflow-x: overlay;
26
+ background-color: var(--main-section-background-color);
27
+ }
28
+ .button-container {
29
+ padding: var(--button-container-padding);
30
+ margin: var(--button-container-margin);
31
+ text-align: var(--button-container-align);
32
+ background-color: var(--button-container-background);
33
+ height: var(--button-container-height);
34
+ }
35
+ `
36
+ ]
37
+ }
38
+
39
+ render() {
40
+ return html`
41
+ <form id="input-form" name="generation" class="single-column-form" @submit=${e => this.onSubmit(e)}>
42
+ <fieldset>
43
+ <label>${i18next.t('label.start-date')}</label>
44
+ <input type="month" name="startDate" required/>
45
+ <label>${i18next.t('label.end-date')}</label>
46
+ <input type="month" name="endDate" .value=${moment().format('YYYY-MM')}/>
47
+ <!-- not implemented on server side, yet -->
48
+ <!--
49
+ <label>${i18next.t('label.data-set-type')}</label>
50
+ <select name="type" required>
51
+ ${(this.dataSetTypes || []).map(
52
+ t => html`<option value="${t && t.value}">${t && t.display}</option>`
53
+ )}
54
+ </select>
55
+ -->
56
+ </fieldset>
57
+ </form>
58
+
59
+ <div class="button-container">
60
+ <mwc-button raised @click="${this.requestArchive}" label="${i18next.t('button.submit')}"></mwc-button>
61
+ </div>
62
+ `
63
+ }
64
+
65
+ async firstUpdated() {
66
+
67
+ }
68
+
69
+ async onSubmit(e) {
70
+ e.preventDefault()
71
+ this.requestArchive()
72
+ }
73
+
74
+
75
+ serializeFormData() {
76
+ const obj = {}
77
+
78
+ Array.from(this.shadowRoot.querySelectorAll('form#input-form input')).forEach(field => {
79
+ if (!field.hasAttribute('hidden') && field.value) {
80
+ obj[field.name] = field.type === 'checkbox' ? field.checked : field.value
81
+ }
82
+ })
83
+
84
+ if (obj['startDate']) {
85
+ obj['startDate'] += '-01'
86
+ }
87
+
88
+ if (obj['endDate']) {
89
+ const endDate = moment(obj['endDate']).endOf('month')
90
+ const today = moment()
91
+ const format = 'YYYY-MM-DD'
92
+
93
+ obj['endDate'] = endDate > today ? today.format(format) : endDate.format(format)
94
+ }
95
+
96
+ return obj
97
+ }
98
+
99
+ /** request download url. */
100
+ async requestArchive() {
101
+ // #1 request download url
102
+ const dataArchive = {
103
+ requestParams: this.serializeFormData(),
104
+ status: 'requested'
105
+ }
106
+
107
+ const response = await client.mutate({
108
+ mutation: gql`
109
+ mutation createDataArchive($dataArchive: NewDataArchive!) {
110
+ createDataArchive(dataArchive: $dataArchive) {
111
+ id
112
+ status
113
+ }
114
+ }
115
+ `,
116
+ variables: { dataArchive }
117
+ })
118
+
119
+ if (!response.errors) {
120
+ this.dispatchEvent(new CustomEvent('requested', {}))
121
+
122
+ const { createDataArchive: { id } } = response.data
123
+ dataArchive['id'] = id
124
+
125
+ this._generateArchiveAndDownloadUrl(dataArchive)
126
+
127
+ await CustomAlert({
128
+ type: 'info',
129
+ title: i18next.t('title.ready'),
130
+ text: i18next.t('text.data-archive waits'),
131
+ confirmButton: { text: i18next.t('button.confirm') }
132
+ })
133
+
134
+ } else {
135
+ console.error(response.errors)
136
+ this.showToast(i18next.t('text.failed'))
137
+ }
138
+ }
139
+
140
+ async _generateArchiveAndDownloadUrl(dataArchive) {
141
+ const response = await client.mutate({
142
+ mutation: gql`
143
+ mutation generatePresignedUrl($patch: DataArchivePatch!) {
144
+ generatePresignedUrl(patch: $patch) {
145
+ id
146
+ downloadUrl
147
+ status
148
+ }
149
+ }
150
+ `,
151
+ variables: { patch: dataArchive }
152
+ })
153
+
154
+ if (!response.errors) {
155
+
156
+ const { generatePresignedUrl: { downloadUrl } } = response.data
157
+
158
+ this.showToast(i18next.t('title.data-archive downloads ready'))
159
+
160
+ notify({
161
+ receivers: '',
162
+ title: i18next.t('title.data-archive downloads ready'),
163
+ body: '',
164
+ image: '',
165
+ mode: 'inapp',
166
+ url: downloadUrl
167
+ })
168
+
169
+ this.dispatchEvent(new CustomEvent('created', {}))
170
+
171
+ } else {
172
+ console.error(response.errors)
173
+ this.showToast(i18next.t('text.failed'))
174
+ }
175
+ }
176
+
177
+ showToast(message) {
178
+ document.dispatchEvent(new CustomEvent('notify', { detail: { message } }))
179
+ }
180
+
181
+ constructor() {
182
+ super()
183
+
184
+ this.dataSetTypes = [
185
+ {},
186
+ {
187
+ display: i18next.t('text.manually collected'),
188
+ value: 'manual'
189
+ },
190
+ {
191
+ display: i18next.t('text.automatically collected'),
192
+ value: 'automatic'
193
+ }
194
+ ]
195
+ }
196
+ }
197
+
198
+ window.customElements.define('data-archive-request-popup', DataArchiveRequestPopup)
@@ -12,7 +12,40 @@ import { notify, openPopup } from '@operato/layout'
12
12
  import { navigate, PageView, store } from '@operato/shell'
13
13
  import { CommonButtonStyles, CommonGristStyles, ScrollbarStyles } from '@operato/styles'
14
14
  import { isMobileDevice } from '@operato/utils'
15
+ import { encodeUrlParams } from '@things-factory/utils'
15
16
 
17
+ import { getEditor, getRenderer } from '@operato/data-grist'
18
+
19
+ const REPORT_TYPES = [
20
+ {
21
+ display: '',
22
+ value: ''
23
+ },
24
+ {
25
+ display: 'Generated',
26
+ value: 'generated'
27
+ },
28
+ {
29
+ display: 'Embed',
30
+ value: 'embed'
31
+ },
32
+ {
33
+ display: 'Page',
34
+ value: 'page'
35
+ },
36
+ {
37
+ display: 'External URL',
38
+ value: 'external'
39
+ },
40
+ {
41
+ display: 'Jasper',
42
+ value: 'jasper'
43
+ },
44
+ {
45
+ display: 'Shiny',
46
+ value: 'shiny'
47
+ }
48
+ ]
16
49
  export class DataKeySetListPage extends connect(store)(localize(i18next)(PageView)) {
17
50
  static get properties() {
18
51
  return {
@@ -136,13 +169,45 @@ export class DataKeySetListPage extends connect(store)(localize(i18next)(PageVie
136
169
  {
137
170
  type: 'gutter',
138
171
  gutterName: 'button',
139
- icon: 'newspaper',
172
+ icon: 'checklist',
140
173
  handlers: {
141
174
  click: (columns, data, column, record, rowIndex) => {
142
175
  navigate(`data-sample-search/${record.id}`)
143
176
  }
144
177
  }
145
178
  },
179
+ {
180
+ type: 'gutter',
181
+ gutterName: 'button',
182
+ icon: 'newspaper',
183
+ handlers: {
184
+ click: (columns, data, column, record, rowIndex) => {
185
+ try {
186
+ const { id:dataKeySetId, reportType, reportView } = record
187
+ switch (reportType) {
188
+ case 'generated':
189
+ break
190
+ case 'page':
191
+ break
192
+ case 'embed':
193
+ break
194
+ case 'external':
195
+ reportView && window.open(reportView, '_blank')
196
+ break
197
+ case 'jasper':
198
+ case 'shiny':
199
+ const urlParams = encodeUrlParams({ dataKeySetId, reportType, reportView })
200
+ navigate(`data-report-samples/?${urlParams}`)
201
+ break
202
+ default:
203
+ break
204
+ }
205
+ } catch(ex) {
206
+ console.log(ex)
207
+ }
208
+ }
209
+ }
210
+ },
146
211
  {
147
212
  type: 'string',
148
213
  name: 'name',
@@ -176,6 +241,34 @@ export class DataKeySetListPage extends connect(store)(localize(i18next)(PageVie
176
241
  sortable: true,
177
242
  width: 60
178
243
  },
244
+ {
245
+ type: 'select',
246
+ name: 'reportType',
247
+ label: true,
248
+ header: i18next.t('field.report-type'),
249
+ record: {
250
+ editable: true,
251
+ options: REPORT_TYPES
252
+ },
253
+ width: 80
254
+ },
255
+ {
256
+ type: 'string',
257
+ name: 'reportView',
258
+ header: i18next.t('field.report-view'),
259
+ record: {
260
+ editable: true,
261
+ editor: function (value, column, record, rowIndex, field) {
262
+ var type = record.reportType !== 'custom' ? 'string' : 'script'
263
+ return getEditor(type)(value, column, record, rowIndex, field)
264
+ },
265
+ renderer: function (value, column, record, rowIndex, field) {
266
+ var type = record.reportType !== 'custom' ? 'string' : 'string'
267
+ return getRenderer(type)(value, column, record, rowIndex, field)
268
+ }
269
+ },
270
+ width: 140
271
+ },
179
272
  {
180
273
  type: 'resource-object',
181
274
  name: 'updater',
@@ -226,6 +319,8 @@ export class DataKeySetListPage extends connect(store)(localize(i18next)(PageVie
226
319
  name
227
320
  description
228
321
  active
322
+ reportType
323
+ reportView
229
324
  dataKeyItems {
230
325
  name
231
326
  description
@@ -315,7 +410,7 @@ export class DataKeySetListPage extends connect(store)(localize(i18next)(PageVie
315
410
 
316
411
  async exportHandler() {
317
412
  const exportTargets = this.grist.selected.length ? this.grist.selected : this.grist.dirtyData.records
318
- const targetFieldSet = new Set(['id', 'name', 'description', 'active'])
413
+ const targetFieldSet = new Set(['id', 'name', 'description', 'active', 'reportType', 'reportView'])
319
414
 
320
415
  return exportTargets.map(dataKeySet => {
321
416
  let tempObj = {}