You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

279 lines
7.1 KiB

3 years ago
3 years ago
3 years ago
  1. <template>
  2. <v-card>
  3. <v-card-title>
  4. {{ $t('dataset.importDataTitle') }}
  5. </v-card-title>
  6. <v-card-text>
  7. <v-overlay :value="isImporting">
  8. <v-progress-circular
  9. indeterminate
  10. size="64"
  11. />
  12. </v-overlay>
  13. <v-select
  14. v-model="selected"
  15. :items="catalog"
  16. item-text="name"
  17. label="File format"
  18. outlined
  19. />
  20. <v-form v-model="valid">
  21. <v-text-field
  22. v-for="(item, key) in textFields"
  23. :key="key"
  24. v-model="option[key]"
  25. :label="item.title"
  26. :rules="requiredRules"
  27. outlined
  28. />
  29. <v-select
  30. v-for="(val, key) in selectFields"
  31. :key="key"
  32. v-model="option[key]"
  33. :items="val.enum"
  34. :label="val.title"
  35. outlined
  36. >
  37. <template #selection="{ item }">
  38. {{ toVisualize(item) }}
  39. </template>
  40. <template #item="{ item }">
  41. {{ toVisualize(item) }}
  42. </template>
  43. </v-select>
  44. </v-form>
  45. <v-sheet
  46. v-if="selected"
  47. :dark="!$vuetify.theme.dark"
  48. :light="$vuetify.theme.dark"
  49. class="mb-5 pa-5"
  50. >
  51. <pre>{{ example }}</pre>
  52. </v-sheet>
  53. <file-pond
  54. v-if="selected && acceptedFileTypes !== '*'"
  55. ref="pond"
  56. chunk-uploads="true"
  57. label-idle="Drop files here..."
  58. :allow-multiple="true"
  59. :accepted-file-types="acceptedFileTypes"
  60. :server="server"
  61. :files="myFiles"
  62. @processfile="handleFilePondProcessFile"
  63. @removefile="handleFilePondRemoveFile"
  64. />
  65. <file-pond
  66. v-if="selected && acceptedFileTypes === '*'"
  67. ref="pond"
  68. chunk-uploads="true"
  69. label-idle="Drop files here..."
  70. :allow-multiple="true"
  71. :server="server"
  72. :files="myFiles"
  73. @processfile="handleFilePondProcessFile"
  74. @removefile="handleFilePondRemoveFile"
  75. />
  76. <v-data-table
  77. v-if="errors.length > 0"
  78. :headers="headers"
  79. :items="errors"
  80. class="elevation-1"
  81. ></v-data-table>
  82. </v-card-text>
  83. <v-card-actions>
  84. <v-btn
  85. class='text-capitalize ms-2 primary'
  86. :disabled="isDisabled"
  87. @click="importDataset"
  88. >
  89. {{ $t('generic.import') }}
  90. </v-btn>
  91. </v-card-actions>
  92. </v-card>
  93. </template>
  94. <script>
  95. import Cookies from 'js-cookie'
  96. import vueFilePond from "vue-filepond"
  97. import "filepond/dist/filepond.min.css"
  98. import FilePondPluginFileValidateType from "filepond-plugin-file-validate-type"
  99. const FilePond = vueFilePond(
  100. FilePondPluginFileValidateType,
  101. )
  102. export default {
  103. components: {
  104. FilePond,
  105. },
  106. layout: 'project',
  107. validate({ params }) {
  108. return /^\d+$/.test(params.id)
  109. },
  110. data() {
  111. return {
  112. catalog: [],
  113. selected: null,
  114. myFiles: [],
  115. option: {'column_data': '', 'column_label': '', 'delimiter': ''},
  116. taskId: null,
  117. polling: null,
  118. errors: [],
  119. headers: [
  120. { text: 'Filename', value: 'filename' },
  121. { text: 'Line', value: 'line' },
  122. { text: 'Message', value: 'message' }
  123. ],
  124. requiredRules: [
  125. v => !!v || 'Field value is required'
  126. ],
  127. server: {
  128. url: '/v1/fp',
  129. headers: {
  130. 'X-CSRFToken': Cookies.get('csrftoken'),
  131. },
  132. process: {
  133. url: '/process/',
  134. method: 'POST',
  135. },
  136. patch: '/patch/',
  137. revert: '/revert/',
  138. restore: '/restore/',
  139. load: '/load/',
  140. fetch: '/fetch/'
  141. },
  142. uploadedFiles: [],
  143. valid: false,
  144. isImporting: false,
  145. }
  146. },
  147. computed: {
  148. isDisabled() {
  149. return this.uploadedFiles.length === 0 || this.taskId !== null || !this.valid
  150. },
  151. properties() {
  152. const item = this.catalog.find(item => item.name === this.selected)
  153. if (item) {
  154. return item.properties
  155. } else {
  156. return {}
  157. }
  158. },
  159. textFields() {
  160. const asArray = Object.entries(this.properties)
  161. const textFields = asArray.filter(([_, value]) => !('enum' in value))
  162. return Object.fromEntries(textFields)
  163. },
  164. selectFields() {
  165. const asArray = Object.entries(this.properties)
  166. const textFields = asArray.filter(([_, value]) => 'enum' in value)
  167. return Object.fromEntries(textFields)
  168. },
  169. acceptedFileTypes() {
  170. const item = this.catalog.find(item => item.name === this.selected)
  171. if (item) {
  172. return item.acceptTypes
  173. } else {
  174. return ''
  175. }
  176. },
  177. example() {
  178. const item = this.catalog.find(item => item.name === this.selected)
  179. if (item) {
  180. const column_data = 'column_data'
  181. const column_label = 'column_label'
  182. if (column_data in this.option && column_label in this.option) {
  183. return item.example.replaceAll(column_data, this.option[column_data])
  184. .replaceAll(column_label, this.option[column_label])
  185. .trim()
  186. } else {
  187. return item.example.trim()
  188. }
  189. } else {
  190. return ''
  191. }
  192. }
  193. },
  194. watch: {
  195. selected() {
  196. const item = this.catalog.find(item => item.name === this.selected)
  197. for (const [key, value] of Object.entries(item.properties)) {
  198. this.option[key] = value.default
  199. }
  200. this.myFiles = []
  201. for (const file of this.uploadedFiles) {
  202. this.$services.parse.revert(file.serverId)
  203. }
  204. this.uploadedFiles = []
  205. this.errors = []
  206. }
  207. },
  208. async created() {
  209. this.catalog = await this.$services.catalog.list(this.$route.params.id)
  210. this.pollData()
  211. },
  212. beforeDestroy() {
  213. clearInterval(this.polling)
  214. },
  215. methods: {
  216. handleFilePondProcessFile(error, file) {
  217. console.log(error)
  218. this.uploadedFiles.push(file)
  219. this.$nextTick()
  220. },
  221. handleFilePondRemoveFile(error, file) {
  222. console.log(error)
  223. const index = this.uploadedFiles.findIndex(item => item.id === file.id)
  224. if (index > -1) {
  225. this.uploadedFiles.splice(index, 1)
  226. this.$nextTick()
  227. }
  228. },
  229. async importDataset() {
  230. this.isImporting = true
  231. this.taskId = await this.$services.parse.analyze(
  232. this.$route.params.id,
  233. this.selected,
  234. this.uploadedFiles.map(item => item.serverId),
  235. this.option
  236. )
  237. },
  238. pollData() {
  239. this.polling = setInterval(async() => {
  240. if (this.taskId) {
  241. const res = await this.$services.taskStatus.get(this.taskId)
  242. if (res.ready) {
  243. this.taskId = null
  244. this.errors = res.result.error
  245. this.myFiles = []
  246. this.uploadedFiles = []
  247. this.isImporting = false
  248. if (this.errors.length === 0) {
  249. this.$router.push(`/projects/${this.$route.params.id}/dataset`)
  250. }
  251. }
  252. }
  253. }, 3000)
  254. },
  255. toVisualize(text) {
  256. if (text === '\t') {
  257. return 'Tab'
  258. } else if (text === ' ') {
  259. return 'Space'
  260. } else if (text === '') {
  261. return 'None'
  262. } else {
  263. return text
  264. }
  265. }
  266. },
  267. };
  268. </script>