Add preview step and success step [SCI-9851]

This commit is contained in:
Anton 2024-05-31 21:07:21 +02:00
parent 7d2f6238de
commit 5e8cf6a221
13 changed files with 272 additions and 16 deletions

View file

@ -227,6 +227,11 @@ GEM
mail
case_transform (0.2)
activesupport
caxlsx (4.0.0)
htmlentities (~> 4.3, >= 4.3.4)
marcel (~> 1.0)
nokogiri (~> 1.10, >= 1.10.4)
rubyzip (>= 1.3.0, < 3)
cgi (0.4.1)
childprocess (4.1.0)
chunky_png (1.4.0)
@ -353,6 +358,7 @@ GEM
nokogiri (~> 1.0)
hashdiff (1.0.1)
hashie (5.0.0)
htmlentities (4.3.4)
http (5.1.1)
addressable (~> 2.8)
http-cookie (~> 1.0)
@ -731,13 +737,13 @@ GEM
unf_ext (0.0.8.2)
unicode-display_width (2.4.2)
uniform_notifier (1.16.0)
uri (0.13.0)
validate_email (0.1.6)
activemodel (>= 3.0)
mail (>= 2.2.5)
validate_url (1.0.15)
activemodel (>= 3.0.0)
public_suffix
uri (0.13.0)
version_gem (1.1.3)
view_component (3.9.0)
activesupport (>= 5.2.0, < 8.0)
@ -795,6 +801,7 @@ DEPENDENCIES
capybara
capybara-email
caracal!
caxlsx
cssbundling-rails
cucumber-rails
database_cleaner

View file

@ -6,20 +6,31 @@
:uploading="uploading"
@uploadFile="uploadFile"
@generatePreview="generatePreview"
@changeStep="changeStep"
@importRows="importRecords"
/>
</div>
</template>
<script>
/* global HelperModule */
import axios from '../../../../packs/custom_axios';
import InfoModal from '../../../shared/info_modal.vue';
import UploadStep from './upload_step.vue';
import MappingStep from './mapping_step.vue';
import PreviewStep from './preview_step.vue';
import SuccessStep from './success_step.vue';
export default {
name: 'ImportRepositoryModal',
components: { InfoModal, UploadStep, MappingStep },
components: {
InfoModal,
UploadStep,
MappingStep,
PreviewStep,
SuccessStep
},
props: {
repositoryUrl: String,
required: true
@ -70,8 +81,10 @@ export default {
this.params.onlyAddNewItems = onlyAddNewItems;
this.importRecords(true);
},
importRecords(preview = false) {
changeStep(step) {
this.activeStep = step;
},
importRecords(preview) {
const jsonData = {
file_id: this.params.temp_file.id,
mappings: this.params.mapping,
@ -80,7 +93,16 @@ export default {
should_overwrite_with_empty_cells: this.params.updateWithEmptyCells,
can_edit_existing_items: !this.params.onlyAddNewItems
};
axios.post(this.params.attributes.urls.import_records, jsonData);
axios.post(this.params.attributes.urls.import_records, jsonData)
.then((response) => {
if (preview) {
this.params.preview = response.data.changes;
this.params.import_date = response.data.import_date;
this.activeStep = 'PreviewStep';
} else {
this.activeStep = 'SuccessStep';
}
});
}
}
};

View file

@ -52,7 +52,6 @@
:params="params"
:selected="this.selectedItemsIndexes.includes(index)"
@selection:changed="handleChange"
:availableFields="this.availableFields"
:autoMapping="this.autoMapping"
/>
</template>
@ -94,7 +93,7 @@ import modalMixin from '../../../shared/modal_mixin';
export default {
name: 'MappingStep',
emits: ['step:next'],
emits: ['close', 'generatePreview'],
mixins: [modalMixin],
components: {
SelectDropdown,
@ -104,7 +103,7 @@ export default {
params: {
type: Object,
required: true
},
}
},
data() {
return {

View file

@ -27,7 +27,6 @@
v-else
:options="dropdownOptions"
@change="changeSelected"
@isOpen="handleIsOpen"
:clearable="true"
:size="'sm'"
:placeholder="computeMatchNotFound ?

View file

@ -0,0 +1,147 @@
<template>
<div ref="modal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-lg" role="document">
<div class="modal-content grow">
<div class="modal-header gap-4">
<button type="button" class="close" data-dismiss="modal" aria-label="Close" data-e2e="e2e-BT-newInventoryModal-close">
<i class="sn-icon sn-icon-close"></i>
</button>
<h4 class="modal-title truncate !block" id="edit-project-modal-label" data-e2e="e2e-TX-newInventoryModal-title">
{{ i18n.t('repositories.import_records.steps.step3.title') }}
</h4>
</div>
<div class="modal-body">
<p class="text-sn-dark-grey mb-6">
{{ i18n.t('repositories.import_records.steps.step3.subtitle', { inventory: params.attributes.name }) }}
</p>
<div class="flex items-center justify-between text-sn-dark-gray text-sm">
<div>
<div v-html="i18n.t('repositories.import_records.steps.step3.updated_items')"></div>
<hr class="my-1">
<h2 class="m-0 text-sn-alert-green">0</h2>
</div>
<div>
<div v-html="i18n.t('repositories.import_records.steps.step3.new_items')"></div>
<hr class="my-1">
<h2 class="m-0 text-sn-alert-green">0</h2>
</div>
<div>
<div v-html="i18n.t('repositories.import_records.steps.step3.unchanged_items')"></div>
<hr class="my-1">
<h2 class="m-0 ">0</h2>
</div>
<div>
<div v-html="i18n.t('repositories.import_records.steps.step3.duplicated_items')"></div>
<hr class="my-1">
<h2 class="m-0 ">0</h2>
</div>
<div>
<div v-html="i18n.t('repositories.import_records.steps.step3.invalid_items')"></div>
<hr class="my-1">
<h2 class="m-0 text-sn-alert-passion">0</h2>
</div>
<div>
<div v-html="i18n.t('repositories.import_records.steps.step3.invalid_items')"></div>
<hr class="my-1">
<h2 class="m-0 text-sn-alert-passion">0</h2>
</div>
</div>
<div class="my-6">
{{ i18n.t('repositories.import_records.steps.step2.importedFileText') }} {{ params.file_name }}
</div>
<div class="h-80">
<ag-grid-vue
class="ag-theme-alpine w-full flex-grow h-full z-10"
:columnDefs="columnDefs"
:defaultColDef="{
resizable: true,
sortable: false,
suppressMovable: true
}"
:rowData="tableData"
:suppressRowTransform="true"
:suppressRowClickSelection="true"
:enableCellTextSelection="true"
></ag-grid-vue>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" @click="$emit('changeStep', 'MappingStep')">
{{ i18n.t('repositories.import_records.steps.step3.cancel') }}
</button>
<button type="button" class="btn btn-primary" @click="$emit('importRows')">
{{ i18n.t('repositories.import_records.steps.step3.confirm') }}
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { AgGridVue } from 'ag-grid-vue3';
import modalMixin from '../../../shared/modal_mixin';
export default {
name: 'PreviewStep',
mixins: [modalMixin],
props: {
params: {
type: Object,
required: true
}
},
components: {
AgGridVue
},
data() {
return {
};
},
computed: {
columnDefs() {
const columns = [
{
field: 'code',
headerName: this.i18n.t('repositories.import_records.steps.step3.code')
},
{
field: 'name',
headerName: this.i18n.t('repositories.import_records.steps.step3.name')
}
];
this.params.attributes.repository_columns.forEach((col) => {
columns.push({
field: `col_${col[0]}`,
headerName: col[1]
});
});
columns.push({
field: 'status',
headerName: this.i18n.t('repositories.import_records.steps.step3.status'),
pinned: 'right'
});
return columns;
},
tableData() {
const data = this.params.preview.data.map((row) => {
const rowFormated = row.attributes;
row.relationships.repository_cells.data.forEach((c) => {
const cell = this.params.preview.included.find((c1) => c1.id === c.id);
if (cell) {
rowFormated[`col_${cell.attributes.repository_column_id}`] = cell.attributes.formatted_value;
}
});
return rowFormated;
});
return data;
}
},
methods: {
}
};
</script>

View file

@ -0,0 +1,53 @@
<template>
<div ref="modal" class="modal" tabindex="-1" role="dialog">
<div class="modal-dialog modal-sm" role="document">
<div class="modal-content grow">
<div class="modal-header gap-4">
<button type="button" class="close" data-dismiss="modal" aria-label="Close" data-e2e="e2e-BT-newInventoryModal-close">
<i class="sn-icon sn-icon-close"></i>
</button>
<h4 class="modal-title truncate !block" id="edit-project-modal-label" data-e2e="e2e-TX-newInventoryModal-title">
{{ i18n.t('repositories.import_records.steps.step4.title') }}
</h4>
</div>
<div class="modal-body">
<p class="text-sn-dark-grey mb-6">
{{ i18n.t('repositories.import_records.steps.step4.subtitle', { inventory: params.attributes.name }) }}
</p>
<div>
<b>{{ i18n.t('repositories.import_records.steps.step4.import_date') }}</b>
{{ params.import_date }}
</div>
<div>
<b>{{ i18n.t('repositories.import_records.steps.step4.imported_file') }}</b>
{{ params.file_name }}
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" >
{{ i18n.t('repositories.import_records.steps.step4.download_report') }}
</button>
<button type="button" class="btn btn-primary" @click="close">
{{ i18n.t('repositories.import_records.steps.step4.close') }}
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import modalMixin from '../../../shared/modal_mixin';
export default {
name: 'SuccessStep',
mixins: [modalMixin],
props: {
params: {
type: Object,
required: true
}
}
};
</script>

View file

@ -91,7 +91,7 @@ import modalMixin from '../../../shared/modal_mixin';
export default {
name: 'UploadStep',
emits: ['step:next', 'info:hide'],
emits: ['uploadFile'],
components: {
DragAndDropUpload
},
@ -123,6 +123,9 @@ export default {
},
uploadFile(file) {
this.$emit('uploadFile', file);
},
handleError(error) {
this.error = error;
}
}
};

View file

@ -3,7 +3,7 @@
class RepositoryCellSerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
attributes :id, :value, :changes
attributes :id, :value, :changes, :repository_column_id, :formatted_value
def changes
object.value.changes
@ -12,4 +12,8 @@ class RepositoryCellSerializer < ActiveModel::Serializer
def value
object.value
end
def formatted_value
object.value.formatted
end
end

View file

@ -4,5 +4,6 @@ class RepositoryRowSerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
attributes :id, :name, :code
has_many :repository_cells
has_many :repository_cells, serializer: RepositoryCellSerializer
end

View file

@ -3,7 +3,7 @@
class RepositorySerializer < ActiveModel::Serializer
include Rails.application.routes.url_helpers
attributes :urls, :id, :team_id, :repository_columns
attributes :urls, :id, :team_id, :repository_columns, :name
def repository_columns
object.repository_columns.pluck(:id, :name, :data_type)

View file

@ -10,7 +10,7 @@ module ImportRepository
def import!(can_edit_existing_items, should_overwrite_with_empty_cells, preview)
status = run_import_actions(can_edit_existing_items, should_overwrite_with_empty_cells, preview)
@temp_file.destroy
#@temp_file.destroy
status
end

View file

@ -166,9 +166,9 @@ module RepositoryImportParser
imported_rows,
each_serializer: RepositoryRowSerializer,
include: [:repository_cells]
).as_json[:included]
).as_json
{ status: :ok, nr_of_added: @new_rows_added, total_nr: @total_new_rows, changes: changes }
{ status: :ok, nr_of_added: @new_rows_added, total_nr: @total_new_rows, changes: changes, import_date: I18n.l(Date.today, format: :full_date) }
end
def import_batch_to_database(full_row_import_batch, can_edit_existing_items, should_overwrite_with_empty_cells, preview)

View file

@ -2264,6 +2264,27 @@ en:
scinoteColumns: 'SciNote columns'
status: 'Status'
exampleData: 'Example data'
step3:
title: 'Import preview'
subtitle: 'This is a preview of items you are importing/updating to the %{inventory}. The import can still be canceled.'
updated_items: 'Updated<br>items'
new_items: 'New<br>items'
unchanged_items: 'Unchanged<br>items'
duplicated_items: 'Duplicated<br>items'
invalid_items: 'Invalid<br>items'
invalid_cells: 'Invalid<br>cells'
code: 'Code'
name: 'Name'
status: 'Status'
cancel: 'Cancel import'
confirm: 'Confirm'
step4:
title: 'Success report'
subtitle: '%{inventory} was successfully updated.'
import_date: 'Import date:'
imported_file: 'Imported file:'
download_report: 'Download success report'
close: 'Close'
info_sidebar:
title: 'Guide for updating the inventory'
elements: