mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-12 23:14:18 +01:00
N°5305 - CSV import ergonomy PR (#332)
Reworked UI feedbacks on following attributes: - enum - date - external key
This commit is contained in:
@@ -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"> '.sprintf($aDisplayFilters['errors'], $iErrors), '', "1", "show_errors", "checkbox");
|
||||
$oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel('<i class="fas fa-exclamation-triangle" style="color:#A33; background-color: #FFF0F0;"> '.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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user