@things-factory/dataset 5.0.0-alpha.1

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 (79) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/LICENSE.md +21 -0
  3. package/client/bootstrap.js +3 -0
  4. package/client/index.js +0 -0
  5. package/client/pages/data-item-list.js +280 -0
  6. package/client/pages/data-sample.js +272 -0
  7. package/client/pages/data-set-importer.js +103 -0
  8. package/client/pages/data-set.js +418 -0
  9. package/client/route.js +11 -0
  10. package/dist-server/controllers/index.js +1 -0
  11. package/dist-server/controllers/index.js.map +1 -0
  12. package/dist-server/index.js +21 -0
  13. package/dist-server/index.js.map +1 -0
  14. package/dist-server/middlewares/index.js +8 -0
  15. package/dist-server/middlewares/index.js.map +1 -0
  16. package/dist-server/migrations/index.js +12 -0
  17. package/dist-server/migrations/index.js.map +1 -0
  18. package/dist-server/routes.js +25 -0
  19. package/dist-server/routes.js.map +1 -0
  20. package/dist-server/service/data-item/data-item-mutation.js +69 -0
  21. package/dist-server/service/data-item/data-item-mutation.js.map +1 -0
  22. package/dist-server/service/data-item/data-item-query.js +100 -0
  23. package/dist-server/service/data-item/data-item-query.js.map +1 -0
  24. package/dist-server/service/data-item/data-item-type.js +80 -0
  25. package/dist-server/service/data-item/data-item-type.js.map +1 -0
  26. package/dist-server/service/data-item/data-item.js +136 -0
  27. package/dist-server/service/data-item/data-item.js.map +1 -0
  28. package/dist-server/service/data-item/index.js +9 -0
  29. package/dist-server/service/data-item/index.js.map +1 -0
  30. package/dist-server/service/data-sample/data-sample-mutation.js +142 -0
  31. package/dist-server/service/data-sample/data-sample-mutation.js.map +1 -0
  32. package/dist-server/service/data-sample/data-sample-query.js +100 -0
  33. package/dist-server/service/data-sample/data-sample-query.js.map +1 -0
  34. package/dist-server/service/data-sample/data-sample-type.js +82 -0
  35. package/dist-server/service/data-sample/data-sample-type.js.map +1 -0
  36. package/dist-server/service/data-sample/data-sample.js +105 -0
  37. package/dist-server/service/data-sample/data-sample.js.map +1 -0
  38. package/dist-server/service/data-sample/index.js +9 -0
  39. package/dist-server/service/data-sample/index.js.map +1 -0
  40. package/dist-server/service/data-set/data-set-mutation.js +216 -0
  41. package/dist-server/service/data-set/data-set-mutation.js.map +1 -0
  42. package/dist-server/service/data-set/data-set-query.js +102 -0
  43. package/dist-server/service/data-set/data-set-query.js.map +1 -0
  44. package/dist-server/service/data-set/data-set-type.js +89 -0
  45. package/dist-server/service/data-set/data-set-type.js.map +1 -0
  46. package/dist-server/service/data-set/data-set.js +117 -0
  47. package/dist-server/service/data-set/data-set.js.map +1 -0
  48. package/dist-server/service/data-set/index.js +9 -0
  49. package/dist-server/service/data-set/index.js.map +1 -0
  50. package/dist-server/service/index.js +40 -0
  51. package/dist-server/service/index.js.map +1 -0
  52. package/package.json +38 -0
  53. package/server/controllers/index.ts +0 -0
  54. package/server/index.ts +5 -0
  55. package/server/middlewares/index.ts +3 -0
  56. package/server/migrations/index.ts +9 -0
  57. package/server/routes.ts +28 -0
  58. package/server/service/data-item/data-item-mutation.ts +56 -0
  59. package/server/service/data-item/data-item-query.ts +53 -0
  60. package/server/service/data-item/data-item-type.ts +50 -0
  61. package/server/service/data-item/data-item.ts +113 -0
  62. package/server/service/data-item/index.ts +6 -0
  63. package/server/service/data-sample/data-sample-mutation.ts +137 -0
  64. package/server/service/data-sample/data-sample-query.ts +53 -0
  65. package/server/service/data-sample/data-sample-type.ts +50 -0
  66. package/server/service/data-sample/data-sample.ts +84 -0
  67. package/server/service/data-sample/index.ts +6 -0
  68. package/server/service/data-set/data-set-mutation.ts +209 -0
  69. package/server/service/data-set/data-set-query.ts +55 -0
  70. package/server/service/data-set/data-set-type.ts +54 -0
  71. package/server/service/data-set/data-set.ts +96 -0
  72. package/server/service/data-set/index.ts +6 -0
  73. package/server/service/index.ts +25 -0
  74. package/things-factory.config.js +17 -0
  75. package/translations/en.json +8 -0
  76. package/translations/ko.json +8 -0
  77. package/translations/ms.json +8 -0
  78. package/translations/zh.json +8 -0
  79. package/tsconfig.json +9 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,8 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
6
+ and adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
7
+
8
+ <!-- ## [Unreleased] -->
package/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) Hatiolab
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,3 @@
1
+ import { store } from '@things-factory/shell'
2
+
3
+ export default function bootstrap() {}
File without changes
@@ -0,0 +1,280 @@
1
+ import gql from 'graphql-tag'
2
+ import { css, html, LitElement } from 'lit'
3
+
4
+ import { client } from '@operato/graphql'
5
+ import { i18next, localize } from '@operato/i18n'
6
+ import { isMobileDevice } from '@operato/utils'
7
+
8
+ class DataItemList extends localize(i18next)(LitElement) {
9
+ static get properties() {
10
+ return {
11
+ dataSet: Object,
12
+ gristConfig: Object
13
+ }
14
+ }
15
+
16
+ static get styles() {
17
+ return [
18
+ css`
19
+ :host {
20
+ display: flex;
21
+ flex-direction: column;
22
+
23
+ background-color: #fff;
24
+ }
25
+
26
+ ox-grist {
27
+ flex: 1;
28
+ }
29
+
30
+ .button-container {
31
+ display: flex;
32
+ margin-left: auto;
33
+ padding: var(--padding-default);
34
+ }
35
+
36
+ form {
37
+ position: relative;
38
+ }
39
+ [danger] {
40
+ --mdc-theme-primary: var(--mdc-danger-button-primary-color);
41
+ }
42
+ mwc-button {
43
+ margin-left: var(--margin-default);
44
+ }
45
+ `
46
+ ]
47
+ }
48
+
49
+ get dataGrist() {
50
+ return this.renderRoot.querySelector('ox-grist')
51
+ }
52
+
53
+ render() {
54
+ return html`
55
+ <ox-grist
56
+ .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
57
+ .config=${this.gristConfig}
58
+ .fetchHandler=${this.fetchHandler.bind(this)}
59
+ ></ox-grist>
60
+ <div class="button-container">
61
+ <mwc-button raised danger @click=${this._deleteDataItems.bind(this)}>${i18next.t('button.delete')}</mwc-button>
62
+ <mwc-button raised @click=${this._updateDataItems.bind(this)}>${i18next.t('button.save')}</mwc-button>
63
+ </div>
64
+ `
65
+ }
66
+
67
+ async updated(changedProps) {}
68
+
69
+ async firstUpdated() {
70
+ this.gristConfig = {
71
+ list: { fields: ['name', 'description', 'active'] },
72
+ columns: [
73
+ { type: 'gutter', gutterName: 'row-selector', multiple: true },
74
+ {
75
+ type: 'gutter',
76
+ gutterName: 'button',
77
+ icon: 'add',
78
+ handlers: {
79
+ click: 'record-copy'
80
+ }
81
+ },
82
+ { type: 'gutter', gutterName: 'sequence' },
83
+ {
84
+ type: 'gutter',
85
+ gutterName: 'button',
86
+ icon: 'arrow_upward',
87
+ handlers: {
88
+ click: 'move-up'
89
+ }
90
+ },
91
+ {
92
+ type: 'gutter',
93
+ gutterName: 'button',
94
+ icon: 'arrow_downward',
95
+ handlers: {
96
+ click: 'move-down'
97
+ }
98
+ },
99
+ {
100
+ type: 'number',
101
+ name: 'sequence',
102
+ hidden: true
103
+ },
104
+ {
105
+ type: 'string',
106
+ name: 'id',
107
+ hidden: true
108
+ },
109
+ {
110
+ type: 'string',
111
+ name: 'name',
112
+ header: i18next.t('field.name'),
113
+ record: {
114
+ editable: true
115
+ },
116
+ width: 140
117
+ },
118
+ {
119
+ type: 'string',
120
+ name: 'description',
121
+ header: i18next.t('field.description'),
122
+ record: {
123
+ editable: true
124
+ },
125
+ width: 180
126
+ },
127
+ {
128
+ type: 'checkbox',
129
+ name: 'active',
130
+ label: true,
131
+ header: i18next.t('field.active'),
132
+ record: {
133
+ editable: true
134
+ },
135
+ sortable: true,
136
+ width: 60
137
+ },
138
+ {
139
+ type: 'select',
140
+ name: 'type',
141
+ header: i18next.t('field.type'),
142
+ record: {
143
+ options: ['number', 'text', 'select', 'boolean'],
144
+ editable: true
145
+ },
146
+ width: 120
147
+ },
148
+ {
149
+ type: 'number',
150
+ name: 'quota',
151
+ header: i18next.t('field.quota'),
152
+ record: {
153
+ editable: true
154
+ },
155
+ width: 60
156
+ },
157
+ {
158
+ type: 'json',
159
+ name: 'spec',
160
+ header: i18next.t('field.spec'),
161
+ record: {
162
+ editable: true
163
+ },
164
+ width: 200
165
+ }
166
+ ],
167
+ rows: {
168
+ selectable: {
169
+ multiple: true
170
+ }
171
+ },
172
+ pagination: {
173
+ infinite: true
174
+ },
175
+ sorters: [
176
+ {
177
+ name: 'sequence'
178
+ }
179
+ ]
180
+ }
181
+ }
182
+
183
+ async fetchHandler({ filters, page, limit, sorters = [] }) {
184
+ const response = await client.query({
185
+ query: gql`
186
+ query {
187
+ dataItems (
188
+ filters: {
189
+ name: "dataSet",
190
+ value: "${this.dataSet.id}",
191
+ operator: "eq"
192
+ },
193
+ sortings: { name: "sequence" }
194
+ ) {
195
+ items {
196
+ id
197
+ name
198
+ description
199
+ sequence
200
+ active
201
+ type
202
+ quota
203
+ spec
204
+ }
205
+ total
206
+ }
207
+ }
208
+ `
209
+ })
210
+
211
+ return {
212
+ total: response.data.dataItems.total || 0,
213
+ records: response.data.dataItems.items || []
214
+ }
215
+ }
216
+
217
+ async _updateDataItems() {
218
+ let patches = this.dataGrist._data.records
219
+ if (patches && patches.length) {
220
+ patches = patches.map(patch => {
221
+ var patchField = {}
222
+ const dirtyFields = patch.__dirtyfields__
223
+ for (let key in dirtyFields) {
224
+ patchField[key] = dirtyFields[key].after
225
+ }
226
+
227
+ return { ...patch.__origin__, ...patchField }
228
+ })
229
+
230
+ const response = await client.mutate({
231
+ mutation: gql`
232
+ mutation ($dataSetId: String!, $patches: [DataItemPatch!]!) {
233
+ updateMultipleDataItem(dataSetId: $dataSetId, patches: $patches) {
234
+ name
235
+ }
236
+ }
237
+ `,
238
+ variables: {
239
+ dataSetId: this.dataSet.id,
240
+ patches
241
+ }
242
+ })
243
+
244
+ if (!response.errors) this.dataGrist.fetch()
245
+ }
246
+ }
247
+
248
+ async _deleteDataItems() {
249
+ if (!confirm(i18next.t('text.sure_to_x', { x: i18next.t('text.delete') }))) return
250
+
251
+ const ids = this.dataGrist.selected.map(record => record.id)
252
+ if (!(ids && ids.length > 0)) return
253
+
254
+ const response = await client.mutate({
255
+ mutation: gql`
256
+ mutation ($ids: [String!]!) {
257
+ deleteDataItems(ids: $ids)
258
+ }
259
+ `,
260
+ variables: {
261
+ ids
262
+ }
263
+ })
264
+
265
+ if (response.errors) return
266
+
267
+ this.dataGrist.fetch()
268
+ await document.dispatchEvent(
269
+ new CustomEvent('notify', {
270
+ detail: {
271
+ message: i18next.t('text.info_x_successfully', {
272
+ x: i18next.t('text.delete')
273
+ })
274
+ }
275
+ })
276
+ )
277
+ }
278
+ }
279
+
280
+ window.customElements.define('data-item-list', DataItemList)
@@ -0,0 +1,272 @@
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 { notify } from '@operato/layout'
10
+ import { PageView, store } from '@operato/shell'
11
+ import { CommonButtonStyles, ScrollbarStyles } from '@operato/styles'
12
+ import { isMobileDevice } from '@operato/utils'
13
+
14
+ export class DataSample extends connect(store)(localize(i18next)(PageView)) {
15
+ static get properties() {
16
+ return {
17
+ active: String,
18
+ gristConfig: Object
19
+ }
20
+ }
21
+
22
+ static get styles() {
23
+ return [
24
+ ScrollbarStyles,
25
+ css`
26
+ :host {
27
+ display: flex;
28
+ flex-direction: column;
29
+
30
+ overflow: hidden;
31
+ }
32
+
33
+ ox-grist {
34
+ overflow-y: auto;
35
+ flex: 1;
36
+ }
37
+
38
+ #filters {
39
+ display: flex;
40
+ flex-direction: row;
41
+ justify-content: space-between;
42
+
43
+ background-color: white;
44
+ }
45
+
46
+ #filters > * {
47
+ padding: var(--padding-default) var(--padding-wide);
48
+ }
49
+ `
50
+ ]
51
+ }
52
+
53
+ get context() {
54
+ return {
55
+ title: i18next.t('title.data-sample list'),
56
+ help: 'integration/ui/data-sample',
57
+ actions: [
58
+ {
59
+ title: i18next.t('button.delete'),
60
+ action: this._deleteDataSample.bind(this),
61
+ ...CommonButtonStyles.delete
62
+ }
63
+ ]
64
+ }
65
+ }
66
+
67
+ render() {
68
+ return html`
69
+ <ox-grist
70
+ .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
71
+ .config=${this.gristConfig}
72
+ .fetchHandler=${this.fetchHandler.bind(this)}
73
+ >
74
+ <div slot="headroom" id="filters">
75
+ <ox-filters-form></ox-filters-form>
76
+ </div>
77
+ </ox-grist>
78
+ `
79
+ }
80
+
81
+ get grist() {
82
+ return this.renderRoot.querySelector('ox-grist')
83
+ }
84
+
85
+ // update with url params value
86
+ _updateSearchConfig(lifecycle) {
87
+ // this.searchConfig = this.searchConfig.map(conf => {
88
+ // if (conf.name in lifecycle.params) {
89
+ // conf.value = lifecycle.params[conf.name]
90
+ // } else {
91
+ // delete conf.value
92
+ // }
93
+ // return conf
94
+ // })
95
+ }
96
+
97
+ // set default field value to record with searchConfig
98
+ _setDefaultFieldsValue(fields) {
99
+ // this.searchConfig.forEach(conf => {
100
+ // if (!fields[conf.name] && conf.value) {
101
+ // fields[conf.name] = conf.value
102
+ // }
103
+ // })
104
+ }
105
+
106
+ async pageInitialized(lifecycle) {
107
+ this._updateSearchConfig(lifecycle)
108
+
109
+ this.gristConfig = {
110
+ list: { fields: ['dataSet', 'data', 'spec', 'updater', 'updatedAt'] },
111
+ columns: [
112
+ { type: 'gutter', gutterName: 'sequence' },
113
+ { type: 'gutter', gutterName: 'row-selector', multiple: true },
114
+ {
115
+ type: 'string',
116
+ name: 'name',
117
+ label: true,
118
+ header: i18next.t('field.name'),
119
+ record: {
120
+ editable: true
121
+ },
122
+ filter: 'search',
123
+ sortable: true,
124
+ width: 150
125
+ },
126
+ {
127
+ type: 'string',
128
+ name: 'description',
129
+ label: true,
130
+ header: i18next.t('field.description'),
131
+ record: {
132
+ editable: true
133
+ },
134
+ filter: 'search',
135
+ width: 200
136
+ },
137
+ {
138
+ type: 'string',
139
+ name: 'dataSetId',
140
+ hidden: true
141
+ },
142
+ {
143
+ type: 'json',
144
+ name: 'data',
145
+ header: i18next.t('field.data'),
146
+ record: {
147
+ editable: false
148
+ },
149
+ width: 200
150
+ },
151
+ {
152
+ type: 'json',
153
+ name: 'spec',
154
+ header: i18next.t('field.spec'),
155
+ record: {
156
+ editable: false
157
+ },
158
+ width: 200
159
+ },
160
+
161
+ {
162
+ type: 'object',
163
+ name: 'updater',
164
+ header: i18next.t('field.updater'),
165
+ sortable: true,
166
+ width: 120
167
+ },
168
+ {
169
+ type: 'datetime',
170
+ name: 'updatedAt',
171
+ header: i18next.t('field.updated_at'),
172
+ sortable: true,
173
+ width: 180
174
+ }
175
+ ],
176
+ rows: {
177
+ appendable: false,
178
+ selectable: {
179
+ multiple: true
180
+ }
181
+ },
182
+ sorters: [
183
+ {
184
+ name: 'name'
185
+ }
186
+ ]
187
+ }
188
+
189
+ await this.updateComplete
190
+
191
+ this.grist.fetch()
192
+ }
193
+
194
+ async pageUpdated(changes, lifecycle) {
195
+ if (this.active) {
196
+ // update with url params value
197
+ this._updateSearchConfig(lifecycle)
198
+ await this.updateComplete
199
+
200
+ this.grist.fetch()
201
+ }
202
+ }
203
+
204
+ async fetchHandler({ page, limit, sortings = [], filters = [] }) {
205
+ const response = await client.query({
206
+ query: gql`
207
+ query ($filters: [Filter!], $pagination: Pagination, $sortings: [Sorting!]) {
208
+ responses: dataSamples(filters: $filters, pagination: $pagination, sortings: $sortings) {
209
+ items {
210
+ id
211
+ name
212
+ description
213
+ dataSetId
214
+ data
215
+ spec
216
+ updater {
217
+ id
218
+ name
219
+ }
220
+ updatedAt
221
+ }
222
+ total
223
+ }
224
+ }
225
+ `,
226
+ variables: {
227
+ filters,
228
+ pagination: { page, limit },
229
+ sortings
230
+ }
231
+ })
232
+
233
+ return {
234
+ total: response.data.responses.total || 0,
235
+ records: response.data.responses.items || []
236
+ }
237
+ }
238
+
239
+ async _deleteDataSample() {
240
+ if (confirm(i18next.t('text.sure_to_x', { x: i18next.t('text.delete') }))) {
241
+ const ids = this.grist.selected.map(record => record.id)
242
+ if (ids && ids.length > 0) {
243
+ const response = await client.mutate({
244
+ mutation: gql`
245
+ mutation ($ids: [String!]!) {
246
+ deleteDataSamples(ids: $ids)
247
+ }
248
+ `,
249
+ variables: {
250
+ ids
251
+ }
252
+ })
253
+
254
+ if (!response.errors) {
255
+ this.grist.fetch()
256
+ notify({
257
+ message: i18next.t('text.info_x_successfully', { x: i18next.t('text.delete') })
258
+ })
259
+ }
260
+ }
261
+ }
262
+ }
263
+
264
+ async stateChanged(state) {
265
+ if (this.active && this._currentPopupName && !state.layout.viewparts[this._currentPopupName]) {
266
+ this.grist.fetch()
267
+ this._currentPopupName = null
268
+ }
269
+ }
270
+ }
271
+
272
+ window.customElements.define('data-sample-page', DataSample)
@@ -0,0 +1,103 @@
1
+ import '@operato/data-grist'
2
+
3
+ import gql from 'graphql-tag'
4
+ import { css, html, LitElement } from 'lit'
5
+
6
+ import { client } from '@operato/graphql'
7
+ import { i18next } from '@operato/i18n'
8
+ import { isMobileDevice } from '@operato/utils'
9
+
10
+ export class DataSetImporter extends LitElement {
11
+ static get properties() {
12
+ return {
13
+ columns: Object,
14
+ scenarios: Array
15
+ }
16
+ }
17
+
18
+ constructor() {
19
+ super()
20
+ this.columns = {
21
+ list: { fields: ['name', 'description'] },
22
+ pagination: { infinite: true },
23
+ columns: [
24
+ {
25
+ type: 'string',
26
+ name: 'name',
27
+ header: i18next.t('field.name'),
28
+ width: 150
29
+ },
30
+ {
31
+ type: 'string',
32
+ name: 'description',
33
+ header: i18next.t('field.description'),
34
+ width: 200
35
+ },
36
+ {
37
+ type: 'checkbox',
38
+ name: 'active',
39
+ header: i18next.t('field.active'),
40
+ width: 60
41
+ }
42
+ ]
43
+ }
44
+ }
45
+
46
+ static get styles() {
47
+ return [
48
+ css`
49
+ :host {
50
+ display: flex;
51
+ flex-direction: column;
52
+
53
+ background-color: #fff;
54
+ }
55
+
56
+ ox-grist {
57
+ flex: 1;
58
+ }
59
+
60
+ .button-container {
61
+ display: flex;
62
+ margin-left: auto;
63
+ padding: var(--padding-default);
64
+ }
65
+
66
+ mwc-button {
67
+ margin-left: var(--margin-default);
68
+ }
69
+ `
70
+ ]
71
+ }
72
+
73
+ render() {
74
+ return html`
75
+ <ox-grist
76
+ .mode=${isMobileDevice() ? 'LIST' : 'GRID'}
77
+ .config=${this.columns}
78
+ .data="${{ records: this.scenarios }}"
79
+ ></ox-grist>
80
+
81
+ <div class="button-container">
82
+ <mwc-button raised @click="${this.save.bind(this)}">${i18next.t('button.save')}</mwc-button>
83
+ </div>
84
+ `
85
+ }
86
+
87
+ async save() {
88
+ const response = await client.mutate({
89
+ mutation: gql`
90
+ mutation importDataSets($dataSets: [DataSetPatch!]!) {
91
+ importDataSets(dataSets: $dataSets)
92
+ }
93
+ `,
94
+ variables: { dataSets: this.dataSets }
95
+ })
96
+
97
+ if (response.error?.length) return
98
+
99
+ this.dispatchEvent(new CustomEvent('imported'))
100
+ }
101
+ }
102
+
103
+ customElements.define('data-set-importer', DataSetImporter)