Skip to content
Snippets Groups Projects
Select Git revision
  • ae6600e8a762d34f0c323e0825ccc3b688b94b31
  • master default protected
  • replication_test
  • release-1.10 protected
  • 553-semantic-recommendation
  • dev protected
  • release-1.9 protected
  • 551-init-broker-service-permissions
  • 549-test-oai-pmh
  • 545-saving-multiple-times-breaks-pid-metadata
  • 499-standalone-compute-service-2
  • 539-load-tests
  • hotfix/helm-chart
  • luca_ba_new_interface
  • 534-bug-when-adding-access-to-user-that-is-not-registered-at-dashboard-service
  • release-1.8 protected
  • 533-integrate-semantic-recommendation
  • feature/openshift
  • 518-spark-doesn-t-map-the-headers-correct
  • 485-fixity-checks
  • 530-various-schema-problems-with-subsets
  • v1.10.3 protected
  • v1.10.2 protected
  • v1.10.1 protected
  • v1.10.0-rc13 protected
  • v1.10.0-rc12 protected
  • v1.10.0-rc11 protected
  • v1.10.0-rc10 protected
  • v1.10.0-rc9 protected
  • v1.10.0-rc8 protected
  • v1.10.0-rc7 protected
  • v1.10.0-rc6 protected
  • v1.10.0-rc5 protected
  • v1.10.0-rc4 protected
  • v1.10.0-rc3 protected
  • v1.10.0-rc2 protected
  • v1.10.0rc1 protected
  • v1.10.0rc0 protected
  • v1.10.0 protected
  • v1.9.3 protected
  • v1.9.2 protected
41 results

TableImport.vue

Blame
  • TableImport.vue 16.94 KiB
    <template>
      <div>
        <v-stepper-header>
          <v-stepper-item
            :title="$t('pages.table.subpages.import.schema.title')"
            :complete="validStep1"
            :value="1"/>
        </v-stepper-header>
        <v-stepper-window
          direction="vertical">
          <v-form
            ref="form"
            v-model="validStep1"
            :disabled="disabled"
            @submit.prevent="submit">
            <v-container>
              <v-row dense>
                <v-col md="8">
                  <v-select
                    v-model="tableImport.separator"
                    :items="separators"
                    item-title="key"
                    item-value="value"
                    required
                    clearable
                    persistent-hint
                    :base-color="suggestedAnalyseSeparator && providedSeparator !== analysedSeparator ? 'warning' : ''"
                    :variant="inputVariant"
                    :hint="$t('pages.table.subpages.import.separator.hint')"
                    :label="$t('pages.table.subpages.import.separator.label')"/>
                </v-col>
              </v-row>
              <v-row dense>
                <v-col md="8">
                  <v-select
                    v-model="tableImport.header"
                    :items="headers"
                    item-title="key"
                    item-value="value"
                    required
                    clearable
                    persistent-hint
                    :variant="inputVariant"
                    :hint="$t('pages.table.subpages.import.skip.hint')"
                    :label="$t('pages.table.subpages.import.skip.label')" />
                </v-col>
              </v-row>
              <v-row dense>
                <v-col md="8">
                  <v-select
                    v-model="tableImport.quote"
                    :items="quotes"
                    item-title="key"
                    item-value="value"
                    clearable
                    persistent-hint
                    :variant="inputVariant"
                    :hint="$t('pages.table.subpages.import.quote.hint')"
                    :label="$t('pages.table.subpages.import.quote.label')"/>
                </v-col>
              </v-row>
              <v-row dense>
                <v-col md="8">
                  <v-select
                    v-model="tableImport.line_termination"
                    :items="lineTerminationItems"
                    item-title="name"
                    item-value="value"
                    clearable
                    persistent-hint
                    :variant="inputVariant"
                    :hint="$t('pages.table.subpages.import.terminator.hint')"
                    :label="$t('pages.table.subpages.import.terminator.label')">
                    <template
                        v-if="suggestedAnalyseLineTerminator && providedTerminator !== analysedTerminator"
                        v-slot:prepend>
                      <v-icon
                        color="warning">
                        mdi-alert-outline
                      </v-icon>
                    </template>
                  </v-select>
                </v-col>
              </v-row>
            </v-container>
          </v-form>
        </v-stepper-window>
        <v-stepper-header>
          <v-stepper-item
            :title="$t('pages.table.subpages.import.file.title')"
            :complete="validStep2"
            :value="2" />
        </v-stepper-header>
        <v-stepper-window
          direction="vertical">
          <v-container>
            <v-row
              v-if="$route.query.location"
              dense>
              <v-col>
                <v-chip
                  prepend-icon="mdi-database-check-outline"
                  size="large"
                  variant="flat"
                  color="tertiary"
                  label>
                  {{ $t('pages.table.subpages.import.storage.text') }}
                </v-chip>
              </v-col>
            </v-row>
            <v-form
              ref="form"
              v-model="validStep2"
              :disabled="disabled"
              @submit.prevent="submit">
                <v-row
                  v-if="step > 1 && suggestedAnalyseSeparator && providedSeparator !== analysedSeparator"
                  dense>
                  <v-col
                    md="8">
                    <v-alert
                      border="start"
                      color="warning">
                      {{ $t('pages.table.subpages.import.separator.warn.prefix') }}
                      <strong>
                        {{ tableImport.separator }}
                      </strong>
                      {{ $t('pages.table.subpages.import.separator.warn.middle') }}
                      <strong>
                        {{ suggestedAnalyseSeparator }}
                      </strong>
                      {{ $t('pages.table.subpages.import.separator.warn.suffix') }}
                    </v-alert>
                  </v-col>
                </v-row>
                <v-row
                  v-if="step > 1 && suggestedAnalyseLineTerminator && providedTerminator !== analysedTerminator"
                  dense>
                  <v-col
                    md="8">
                    <v-alert
                      border="start"
                      color="warning">
                      {{ $t('pages.table.subpages.import.terminator.warn.prefix') }}
                      <strong>{{ JSON.stringify(tableImport.line_termination).replaceAll('"', '') }}</strong>
                      {{ $t('pages.table.subpages.import.terminator.warn.middle') }}
                      <strong>{{ JSON.stringify(suggestedAnalyseLineTerminator).replaceAll('"', '') }}</strong>
                      {{ $t('pages.table.subpages.import.terminator.warn.suffix') }}
                    </v-alert>
                  </v-col>
                </v-row>
                <v-row
                  v-if="!hasCompatibleSchema"
                  dense>
                  <v-col
                    md="8">
                    <v-alert
                      border="start"
                      color="warning"
                      :text="$t('pages.table.subpages.import.dataset.warn')"/>
                  </v-col>
                </v-row>
                <v-row
                  v-if="!$route.query.location"
                  dense>
                  <v-col cols="8">
                    <v-file-input
                      v-model="file"
                      accept=".csv,.tsv"
                      :show-size="1000"
                      counter
                      required
                      :rules="[
                        v => notFile(v) || $t('validation.required'),
                      ]"
                      :prepend-icon="validStep1 ? 'mdi-database-check-outline' : 'mdi-database-arrow-up-outline'"
                      persistent-hint
                      :variant="inputVariant"
                      :hint="$t('pages.table.subpages.import.file.hint')"
                      :label="$t('pages.table.subpages.import.file.label')">
                      <template
                        v-if="uploadProgress"
                        v-slot:append>
                        <span>{{ uploadProgress }}%</span>
                      </template>
                    </v-file-input>
                  </v-col>
                </v-row>
                <v-row
                  dense>
                  <v-col
                    cols="8">
                    <v-btn
                      v-if="create && !$route.query.location"
                      :disabled="!isAnalyseAllowed || !validStep1 || !validStep2 || disabled"
                      :loading="loading"
                      :variant="buttonVariant"
                      color="secondary"
                      size="small"
                      :text="$t('pages.table.subpages.import.analyse.text')"
                      @click="uploadAndAnalyse"/>
                    <v-btn
                      v-if="!create && !$route.query.location"
                      :disabled="!isAnalyseAllowed || !validStep1 || !validStep2 || disabled"
                      :loading="loading || loadingImport"
                      :variant="buttonVariant"
                      color="secondary"
                      size="small"
                      :text="$t('pages.table.subpages.import.upload.text')"
                      @click="uploadAndImport"/>
                    <v-btn
                      v-if="!create && $route.query.location"
                      :disabled="step > 2 || disabled"
                      :loading="loading || loadingImport"
                      :variant="buttonVariant"
                      color="secondary"
                      size="small"
                      class="mt-2"
                      :text="$t('pages.table.subpages.import.text')"
                      @click="importCsv"/>
                  </v-col>
                </v-row>
              </v-form>
            </v-container>
        </v-stepper-window>
        <v-stepper-header
          v-if="!create">
          <v-stepper-item
            :title="$t('pages.table.subpages.import.summary.title')"
            :value="3"/>
        </v-stepper-header>
        <v-stepper-window
          v-if="!create && step === 3"
          direction="vertical">
          <v-container>
            <v-row
              dense>
              <v-col
                md="8">
                <v-alert
                  border="start"
                  color="success">
                  <span>
                    {{ $t(`pages.table.subpages.import.summary.text`)}}
                  </span>
                </v-alert>
              </v-col>
            </v-row>
            <v-row>
              <v-col>
                <v-btn
                  v-if="step === 3"
                  color="secondary"
                  :disabled="step !== 3 || disabled"
                  size="small"
                  variant="flat"
                  :text="$t('navigation.data')"
                  :to="`/database/${$route.params.database_id}/table/${tableId}/data`" />
              </v-col>
            </v-row>
          </v-container>
        </v-stepper-window>
      </div>
    </template>
    
    <script>
    import { useCacheStore } from '@/stores/cache.js'
    
    export default {
      props: {
        tableId: {
          default: () => {
            return null
          }
        },
        create: {
          default: () => {
            return false
          }
        },
        disabled: {
          default: () => {
            return false
          }
        }
      },
      data() {
        return {
          step: 2,
          validStep1: false,
          validStep2: false,
          validStep3: false,
          file: null,
          loading: false,
          loadingImport: false,
          rowCount: null,
          suggestedAnalyseSeparator: null,
          suggestedAnalyseLineTerminator: null,
          columns: [],
          tableImport: {
            location: null,
            quote: '"',
            separator: ',',
            line_termination: '\\n',
            header: true
          },
          separators: [
            {key: ',', value: ','},
            {key: ';', value: ';'},
            {key: '[Tab]', value: '\t'}
          ],
          quotes: [
            {key: 'Double "', value: '"'},
            {key: 'Single \'', value: '\''}
          ],
          headers: [
            {key: 'First line is header', value: true},
            {key: 'Data only', value: false}
          ],
          lineTerminationItems: [
            {name: '\\r\\n (Windows)', value: '\r\n'},
            {name: '\\n (UNIX)', value: '\n'},
            {name: '\\r (pre-OSX)', value: '\r'},
          ],
          cacheStore: useCacheStore()
        }
      },
      mounted() {
        this.cacheStore.setUploadProgress(null)
        this.setQueryParamSafely('location')
        this.setQueryParamSafely('quote')
        this.setQueryParamSafely('separator')
        this.setQueryParamSafely('line_termination')
        this.setQueryParamSafely('header', true)
        if (this.$route.query.location) {
          this.step = 2
          this.validStep2 = true
        }
      },
      computed: {
        table() {
          return this.cacheStore.getTable
        },
        uploadProgress () {
          return this.cacheStore.getUploadProgress
        },
        isAnalyseAllowed () {
          if (!this.file || this.file.length === 0) {
            return false
          }
          return true
        },
        hasCompatibleSchema () {
          if (this.create) {
            return true
          }
          if (!this.columns || !this.table) {
            return false
          }
          const schema = this.table.columns.map(c => c.name)
          let pass = true
          this.columns.forEach(c => {
            if (!schema.includes(c.name)) {
              console.error('Failed to find column', c.name, 'in schema')
              pass = false
            }
          })
          return pass
        },
        providedTerminator() {
          if (this.tableImport.line_termination === null) {
            return null
          }
          return this.tableImport.line_termination.replace(/(\n)/g, function ($0) {
            return $0 === ' ' ? ' ' : '\\n'
          })
        },
        analysedTerminator() {
          if (this.suggestedAnalyseLineTerminator === null) {
            return null
          }
          return this.suggestedAnalyseLineTerminator.replace(/(\n)/g, function ($0) {
            return $0 === ' ' ? ' ' : '\\n'
          })
        },
        providedSeparator() {
          if (this.tableImport.separator === null) {
            return null
          }
          return this.tableImport.separator.replace(/(\n)/g, function ($0) {
            return $0 === ' ' ? ' ' : '\\n'
          })
        },
        analysedSeparator() {
          if (this.suggestedAnalyseSeparator === null) {
            return null
          }
          return this.suggestedAnalyseSeparator.replace(/(\n)/g, function ($0) {
            return $0 === ' ' ? ' ' : '\\n'
          })
        },
        inputVariant () {
          const runtimeConfig = useRuntimeConfig()
          return this.$vuetify.theme.global.name.toLowerCase().endsWith('contrast') ? runtimeConfig.public.variant.input.contrast : runtimeConfig.public.variant.input.normal
        },
        buttonVariant () {
          const runtimeConfig = useRuntimeConfig()
          return this.$vuetify.theme.global.name.toLowerCase().endsWith('contrast') ? runtimeConfig.public.variant.button.contrast : runtimeConfig.public.variant.button.normal
        }
      },
      methods: {
        submit() {
          this.$refs.form.validate()
        },
        setQueryParamSafely(name, parse_bool = false) {
          if (this.$route.query[name]) {
            var value = this.$route.query[name]
            if (parse_bool) {
              value = this.$route.query[name] === 'true'
            }
            this.tableImport[name] = value
          }
        },
        importCsv() {
          this.loadingImport = true
          const tableService = useTableService()
          tableService.importCsv(this.$route.params.database_id, this.tableId, this.tableImport)
            .then(() => {
              const toast = useToastInstance()
              toast.success(this.$t('success.import.dataset'))
              this.cacheStore.reloadDatabase()
              this.step = 3
              this.validStep3 = true
              this.loadingImport = false
            })
            .catch(({code, message}) => {
              this.loadingImport = false
              const toast = useToastInstance()
              if (typeof code !== 'string') {
                return
              }
              toast.error(`${this.$t(code)}: ${message}`)
            })
            .finally(() => {
              this.loadingImport = false
            })
        },
        uploadAndAnalyse() {
          this.upload()
            .then((s3key) => {
              this.analyse(s3key)
            })
        },
        uploadAndImport() {
          this.upload()
            .then((s3key) => {
              this.tableImport.location = s3key
              this.importCsv()
            })
        },
        upload() {
          this.loading = true
          console.debug('upload file', this.file)
          const uploadService = useUploadService()
          return new Promise((resolve, reject) => {
            return uploadService.create(this.file)
              .then((s3key) => {
                const toast = useToastInstance()
                toast.success(this.$t('success.upload.dataset'))
                this.loading = false
                resolve(s3key)
              })
              .catch((error) => {
                console.error('Failed to upload dataset', error)
                const toast = useToastInstance()
                toast.error(this.$t('error.upload.dataset'))
                this.loading = false
                reject(error)
              })
          })
        },
        analyse(filename) {
          const analyseService = useAnalyseService()
          const payload = { filename }
          if (this.tableImport.separator) {
            payload.separator = this.tableImport.separator
          }
          this.loading = true
          analyseService.suggest(payload)
            .then((analysis) => {
              const {columns, separator, line_termination} = analysis
              this.columns = Object.entries(columns)
                .map(([name, analyse]) => {
                  return {
                    name: name.trim(),
                    type: analyse.type,
                    null_allowed: analyse.null_allowed,
                    primary_key: false,
                    size: analyse.size,
                    d: analyse.d,
                    enums: analyse.enums,
                    sets: analyse.sets
                  }
                })
              this.suggestedAnalyseSeparator = separator
              this.suggestedAnalyseLineTerminator = line_termination
              this.tableImport.location = filename
              this.step = 3
              this.cacheStore.setUploadProgress(null)
              const toast = useToastInstance()
              toast.success(this.$t('success.analyse.dataset'))
              this.$emit('analyse', {
                columns: this.columns,
                filename,
                line_termination: line_termination === '\\n' ? '\n' : JSON.stringify(line_termination).replaceAll('"', ''),
                separator: this.tableImport.separator,
                header: this.tableImport.header,
                quote: this.tableImport.quote,
              })
              this.loading = false
            })
            .catch(({code, message}) => {
              this.loading = false
              const toast = useToastInstance()
              if (typeof code !== 'string') {
                /* fallback default error message */
                toast.error(this.$t('error.analyse.invalid'))
                return
              }
              toast.error(`${this.$t(code)}: ${message}`)
            })
        }
      }
    }
    </script>