N°5305 - CSV import ergonomy PR (#332)

Reworked UI feedbacks on following attributes:
- enum
- date
- external key
This commit is contained in:
odain-cbd
2022-09-20 16:00:33 +02:00
committed by GitHub
parent 2a064fd97d
commit 766c9f0e7e
26 changed files with 1187 additions and 347 deletions

View File

@@ -139,7 +139,7 @@ try {
}
return $aResult;
}
/**
* Return the most frequent (and regularly occuring) character among the given set, in the specified lines
* @param array $aCSVData The input data, one entry per line
@@ -175,7 +175,7 @@ try {
}
$iLine++;
}
$aScores = array();
foreach($aGuesses as $sSep => $aData)
{
@@ -186,7 +186,7 @@ try {
$sSeparator = $aKeys[0]; // Take the first key, the one with the best score
return $sSeparator;
}
/**
* Try to predict the CSV parameters based on the input data
* @param string $sCSVData The input data
@@ -197,10 +197,10 @@ try {
$aData = explode("\n", $sCSVData);
$sSeparator = GuessFromFrequency($aData, array("\t", ',', ';', '|')); // Guess the most frequent (and regular) character on each line
$sQualifier = GuessFromFrequency($aData, array('"', "'")); // Guess the most frequent (and regular) character on each line
return array('separator' => $sSeparator, 'qualifier' => $sQualifier);
}
/**
* Display a banner for the special "synchro" mode
* @param WebPage $oP The Page for the output
@@ -216,6 +216,7 @@ try {
* Add a paragraph to the body of the page
*
* @param string $s_html
* @param ?string $sLinkUrl
*
* @return string
*/
@@ -260,9 +261,9 @@ try {
$sSynchroScope = utils::ReadParam('synchro_scope', '', false, 'raw_data');
$sDateTimeFormat = utils::ReadParam('date_time_format', 'default');
$sCustomDateTimeFormat = utils::ReadParam('custom_date_time_format', (string)AttributeDateTime::GetFormat(), false, 'raw_data');
$sChosenDateFormat = ($sDateTimeFormat == 'default') ? (string)AttributeDateTime::GetFormat() : $sCustomDateTimeFormat;
if (!empty($sSynchroScope))
{
$oSearch = DBObjectSearch::FromOQL($sSynchroScope);
@@ -277,7 +278,7 @@ try {
$sSynchroScope = '';
$aSynchroUpdate = null;
}
// Parse the data set
$oCSVParser = new CSVParser($sCSVData, $sSeparator, $sTextQualifier, MetaModel::GetConfig()->Get('max_execution_time_per_loop'));
$aData = $oCSVParser->ToArray($iSkippedLines);
@@ -287,10 +288,10 @@ try {
$aResult[] = $sTextQualifier.implode($sTextQualifier.$sSeparator.$sTextQualifier, array_shift($aData)).$sTextQualifier; // Remove the first line and store it in case of error
$iRealSkippedLines++;
}
// Format for the line numbers
$sMaxLen = (strlen(''.count($aData)) < 3) ? 3 : strlen(''.count($aData)); // Pad line numbers to the appropriate number of chars, but at least 3
// Compute the list of search/reconciliation criteria
$aSearchKeys = array();
foreach($aSearchFields as $index => $sDummy)
@@ -304,16 +305,16 @@ try {
}
else
{
$aSearchKeys[$sSearchField] = '';
$aSearchKeys[$sSearchField] = '';
}
if (!MetaModel::IsValidFilterCode($sClassName, $sSearchField))
{
// Remove invalid or unmapped search fields
$aSearchFields[$index] = null;
unset($aSearchKeys[$sSearchField]);
unset($aSearchKeys[$sSearchField]);
}
}
// Compute the list of fields and external keys to process
$aExtKeys = array();
$aAttributes = array();
@@ -346,13 +347,13 @@ try {
}
else
{
$aAttributes[$sAttCode] = $iIndex;
$aAttributes[$sAttCode] = $iIndex;
}
}
}
}
}
}
$oMyChange = null;
if (!$bSimulate)
{
@@ -362,8 +363,6 @@ try {
CMDBObject::SetTrackOrigin(CMDBChangeOrigin::CSV_INTERACTIVE);
$oMyChange = CMDBObject::GetCurrentChange();
}
CMDBObject::SetTrackOrigin('csv-interactive');
$oBulk = new BulkChange(
$sClassName,
$aData,
@@ -373,7 +372,7 @@ try {
empty($sSynchroScope) ? null : $sSynchroScope,
$aSynchroUpdate,
$sChosenDateFormat, // date format
true // localize
true // localize
);
$oBulk->SetReportHtml();
@@ -440,7 +439,6 @@ try {
case 'RowStatus_NewObj':
$iCreated++;
$sFinalClass = $aResRow['finalclass'];
$sStatus = '<img src="../images/added.png" title="'.Dict::S('UI:CSVReport-Icon-Created').'">';
$sCSSRowClass = 'ibo-csv-import--row-added';
if ($bSimulate) {
@@ -456,7 +454,7 @@ try {
case 'RowStatus_Issue':
$iErrors++;
$sMessage .= GetDivAlert($oStatus->GetDescription());
$sStatus = '<img src="../images/error.png" title="'.Dict::S('UI:CSVReport-Icon-Error').'">';//translate
$sStatus = '<div class="ibo-csv-import--cell-error"><i class="fas fa-exclamation-triangle" title="'.Dict::S('UI:CSVReport-Icon-Error').'" /></div>';//translate
$sCSSMessageClass = 'ibo-csv-import--cell-error';
$sCSSRowClass = 'ibo-csv-import--row-error';
if (array_key_exists($iLine, $aData)) {
@@ -477,33 +475,36 @@ try {
if (isset($aExternalKeysByColumn[$iNumber - 1])) {
$sExtKeyName = $aExternalKeysByColumn[$iNumber - 1];
$oExtKeyCellStatus = $aResRow[$sExtKeyName];
switch (get_class($oExtKeyCellStatus)) {
case 'CellStatus_Issue':
case 'CellStatus_SearchIssue':
case 'CellStatus_NullIssue':
case 'CellStatus_Ambiguous':
$sCellMessage .= GetDivAlert($oExtKeyCellStatus->GetDescription());
break;
default:
// Do nothing
}
$oCellStatus = $oExtKeyCellStatus;
}
$sHtmlValue = $oCellStatus->GetDisplayableValue();
switch (get_class($oCellStatus)) {
case 'CellStatus_Issue':
case 'CellStatus_NullIssue':
$sCellMessage .= GetDivAlert($oCellStatus->GetDescription());
$aTableRow[$sClassName.'/'.$sAttCode] = '<div class="ibo-csv-import--cell-error">'.Dict::Format('UI:CSVReport-Object-Error', $sHtmlValue).$sCellMessage.'</div>';
break;
case 'CellStatus_SearchIssue':
$sCellMessage .= GetDivAlert($oCellStatus->GetDescription());
$aTableRow[$sClassName.'/'.$sAttCode] = '<div class="ibo-csv-import--cell-error">ERROR: '.$sHtmlValue.$sCellMessage.'</div>';
$aTableRow[$sClassName.'/'.$sAttCode] = sprintf("%s%s%s%s%s%s",
'<a href="',
$oCellStatus->GetSearchLinkUrl(),
'"><div class="ibo-csv-import--cell-error">',
Dict::Format('UI:CSVReport-Object-Error', $sHtmlValue),
GetDivAlert($oCellStatus->GetDescription()),
'<i class="fas fa-search"></i></div><a/>'
);
break;
case 'CellStatus_Ambiguous':
$sCellMessage .= GetDivAlert($oCellStatus->GetDescription());
$aTableRow[$sClassName.'/'.$sAttCode] = '<div class="ibo-csv-import--cell-error" >'.Dict::Format('UI:CSVReport-Object-Ambiguous', $sHtmlValue).$sCellMessage.'</div>';
$aTableRow[$sClassName.'/'.$sAttCode] = sprintf("%s%s%s%s%s%s",
'<a href="',
$oCellStatus->GetSearchLinkUrl(),
'"><i class="fas fa-search"/><div class="ibo-csv-import--cell-error">',
Dict::Format('UI:CSVReport-Object-Ambiguous', $sHtmlValue),
GetDivAlert($oCellStatus->GetDescription()),
'<i class="fas fa-search"></i></div><a/>'
);
break;
case 'CellStatus_Modify':
@@ -592,7 +593,7 @@ try {
$oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oCheckBoxUnchanged));
$oPage->add_ready_script("$('#show_created').on('click', function(){ToggleRows('ibo-csv-import--row-added')})");
$oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel('<img src="../images/error.png">&nbsp;'.sprintf($aDisplayFilters['errors'], $iErrors), '', "1", "show_errors", "checkbox");
$oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel('<i class="fas fa-exclamation-triangle" style="color:#A33; background-color: #FFF0F0;">&nbsp;'.sprintf($aDisplayFilters['errors'], $iErrors).'</i></i>', '', "1", "show_errors", "checkbox");
$oCheckBoxUnchanged->GetInput()->SetIsChecked(true);
$oCheckBoxUnchanged->SetBeforeInput(false);
$oCheckBoxUnchanged->GetInput()->AddCSSClass('ibo-input-checkbox');
@@ -679,7 +680,7 @@ try {
EOF
);
}
$sErrors = json_encode(Dict::Format('UI:CSVImportError_items', $iErrors));
$sCreated = json_encode(Dict::Format('UI:CSVImportCreated_items', $iCreated));
$sModified = json_encode(Dict::Format('UI:CSVImportModified_items', $iModified));
@@ -774,7 +775,7 @@ EOF
{
return null;
}
}
/**
* Perform the actual load of the CSV data and display the results
@@ -798,7 +799,7 @@ EOF
$oField->AddSubBlock($oText);
}
}
/**
* Simulate the load of the CSV data and display the results
* @param WebPage $oPage The web page to display the wizard
@@ -810,7 +811,7 @@ EOF
$oPage->AddSubBlock($oPanel);
ProcessCSVData($oPage, true /* simulate */);
}
/**
* Select the mapping between the CSV column and the fields of the objects
* @param WebPage $oPage The web page to display the wizard
@@ -923,10 +924,10 @@ EOF
$aSearchFields = utils::ReadParam('search_field', array(), false, 'field_name');
$sFieldsMapping = addslashes(json_encode($aFieldsMapping));
$sSearchFields = addslashes(json_encode($aSearchFields));
$oPage->add_ready_script("DoMapping('$sFieldsMapping', '$sSearchFields');"); // There is already a class selected, run the mapping
}
$oPage->add_script(
<<<EOF
var aDefaultKeys = new Array();
@@ -1142,7 +1143,7 @@ EOF
EOF
);
}
/**
* Select the options of the CSV load and check for CSV parsing errors
* @param WebPage $oPage The current web page
@@ -1166,7 +1167,7 @@ EOF
$sCSVData = utils::ReadPostedParam('csvdata', '', 'raw_data');
}
$sEncoding = utils::ReadParam('encoding', 'UTF-8');
// Compute a subset of the data set, now that we know the charset
if ($sEncoding == 'UTF-8')
{
@@ -1183,7 +1184,7 @@ EOF
{
$sUTF8Data = iconv($sEncoding, 'UTF-8//IGNORE//TRANSLIT', $sCSVData);
}
$aGuesses = GuessParameters($sUTF8Data); // Try to predict the parameters, based on the input data
$iSkippedLines = utils::ReadParam('nb_skipped_lines', '');
@@ -1191,7 +1192,7 @@ EOF
$sTextQualifier = utils::ReadParam('text_qualifier', '', false, 'raw_data');
if ($sTextQualifier == '') // May be set to an empty value by the previous page
{
$sTextQualifier = $aGuesses['qualifier'];
$sTextQualifier = $aGuesses['qualifier'];
}
$sOtherTextQualifier = in_array($sTextQualifier, array('"', "'")) ? '' : $sTextQualifier;
$bHeaderLine = utils::ReadParam('header_line', 0);
@@ -1610,7 +1611,7 @@ EOF
null, AjaxTab::ENUM_TAB_PLACEHOLDER_MISC);
}
}
switch($iStep)
{
case 11:
@@ -1618,45 +1619,45 @@ EOF
$oPage = new AjaxPage('');
BulkChange::DisplayImportHistory($oPage);
$oPage->add_ready_script('$("#CSVImportHistory table.listResults").tableHover();');
$oPage->add_ready_script('$("#CSVImportHistory table.listResults").tablesorter( { widgets: ["myZebra", "truncatedList"]} );');
$oPage->add_ready_script('$("#CSVImportHistory table.listResults").tablesorter( { widgets: ["myZebra", "truncatedList"]} );');
break;
case 10:
// Case generated by BulkChange::DisplayImportHistory
$iChange = (int)utils::ReadParam('changeid', 0);
BulkChange::DisplayImportHistoryDetails($oPage, $iChange);
break;
case 5:
LoadData($oPage);
break;
case 4:
Preview($oPage);
break;
case 3:
SelectMapping($oPage);
break;
case 2:
SelectOptions($oPage);
break;
case 1:
case 6: // Loop back here when we are done
default:
Welcome($oPage);
}
$oPage->output();
}
catch(CoreException $e)
{
require_once(APPROOT.'/setup/setuppage.class.inc.php');
$oP = new ErrorPage(Dict::S('UI:PageTitle:FatalError'));
$oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
$oP->error(Dict::Format('UI:Error_Details', $e->getHtmlDesc()));
$oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
$oP->error(Dict::Format('UI:Error_Details', $e->getHtmlDesc()));
$oP->output();
if (MetaModel::IsLogEnabledIssue())
@@ -1684,8 +1685,8 @@ catch(Exception $e)
{
require_once(APPROOT.'/setup/setuppage.class.inc.php');
$oP = new ErrorPage(Dict::S('UI:PageTitle:FatalError'));
$oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
$oP->error(Dict::Format('UI:Error_Details', $e->getMessage()));
$oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
$oP->error(Dict::Format('UI:Error_Details', $e->getMessage()));
$oP->output();
if (MetaModel::IsLogEnabledIssue())
@@ -1705,4 +1706,4 @@ catch(Exception $e)
IssueLog::Error($e->getMessage());
}
}
}