N°931: Limit the number of tags in the widget

This commit is contained in:
Eric
2018-09-20 15:15:25 +02:00
parent 38278f3432
commit 398d1aa820
7 changed files with 111 additions and 12 deletions

View File

@@ -2189,6 +2189,11 @@ EOF
$aJson['added'] = array();
$aJson['removed'] = array();
/** @var \AttributeTagSet $oAttDef */
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
$iMaxTags = $oAttDef->GetTagMaxNb();
$aJson['max_tags_allowed'] = $iMaxTags;
return json_encode($aJson);
}

View File

@@ -6869,7 +6869,8 @@ class AttributeTagSet extends AttributeDBFieldVoid
$oTagSet = new ormTagSet(MetaModel::GetAttributeOrigin($this->GetHostClass(), $this->GetCode()), $this->GetCode(), $this->GetTagMaxNb());
if (is_string($proposedValue) && !empty($proposedValue))
{
$aTagCodes = explode(' ', "$proposedValue");
$proposedValue = trim("$proposedValue");
$aTagCodes = explode(' ', $proposedValue);
$oTagSet->SetValue($aTagCodes);
}
elseif ($proposedValue instanceof ormTagSet)

View File

@@ -142,15 +142,13 @@ abstract class TagSetFieldData extends cmdbAbstractObject
parent::DoCheckToDelete($oDeletionPlan);
$sTagCode = $this->Get('code');
$sClass = $this->Get('obj_class');
$sAttCode = $this->Get('obj_attcode');
$oSearch = DBSearch::FromOQL("SELECT $sClass WHERE $sAttCode MATCHES '$sTagCode'");
$oSet = new DBObjectSet($oSearch);
if ($oSet->CountExceeds(0))
if ($this->IsCodeUsed($sTagCode))
{
$this->m_aDeleteIssues[] = Dict::S('Core:TagSetFieldData:ErrorDeleteUsedTag');
}
// Clear cache
$sClass = $this->Get('obj_class');
$sAttCode = $this->Get('obj_attcode');
$sTagDataClass = self::GetTagDataClassName($sClass, $sAttCode);
unset(self::$m_aAllowedValues[$sTagDataClass]);
}
@@ -222,10 +220,43 @@ abstract class TagSetFieldData extends cmdbAbstractObject
$aChanges = $this->ListChanges();
if (array_key_exists('code', $aChanges))
{
throw new CoreException(Dict::S('Core:TagSetFieldData:ErrorCodeUpdateNotAllowed'));
$sTagCode = $this->GetOriginal('code');
if ($this->IsCodeUsed($sTagCode))
{
throw new CoreException(Dict::S('Core:TagSetFieldData:ErrorCodeUpdateNotAllowed'));
}
}
if (array_key_exists('obj_class', $aChanges))
{
throw new CoreException(Dict::S('Core:TagSetFieldData:ErrorClassUpdateNotAllowed'));
}
if (array_key_exists('obj_attcode', $aChanges))
{
throw new CoreException(Dict::S('Core:TagSetFieldData:ErrorAttCodeUpdateNotAllowed'));
}
}
private function IsCodeUsed($sTagCode)
{
try
{
$sClass = $this->Get('obj_class');
$sAttCode = $this->Get('obj_attcode');
$oSearch = DBSearch::FromOQL("SELECT $sClass WHERE $sAttCode MATCHES '$sTagCode'");
$oSet = new DBObjectSet($oSearch);
if ($oSet->CountExceeds(0))
{
return true;
}
}
catch (Exception $e)
{
IssueLog::Warning($e->getMessage());
}
return false;
}
/**
* Display Tag Usage
*

View File

@@ -921,7 +921,9 @@ Dict::Add('EN US', 'English', 'English', array(
'Core:TagSetFieldData:ErrorDuplicateTagCodeOrLabel' => 'Tags codes or labels must be unique',
'Core:TagSetFieldData:ErrorTagCodeSyntax' => 'Tags code should contain between 3 and %1$d alphanumeric characters',
'Core:TagSetFieldData:ErrorTagLabelSyntax' => 'Tags label should not contain \'%1$s\' nor be empty',
'Core:TagSetFieldData:ErrorCodeUpdateNotAllowed' => 'Tags code cannot be changed',
'Core:TagSetFieldData:ErrorCodeUpdateNotAllowed' => 'Tags "Code" cannot be changed',
'Core:TagSetFieldData:ErrorClassUpdateNotAllowed' => 'Tags "Object Class" cannot be changed',
'Core:TagSetFieldData:ErrorAttCodeUpdateNotAllowed' => 'Tags "Attribute Code" cannot be changed',
'Core:TagSetFieldData:WhereIsThisTagTab' => 'Tag usage (%1$d)',
'Core:TagSetFieldData:NoEntryFound' => 'No entry found for this tag',
));

View File

@@ -773,6 +773,8 @@ Opérateurs :<br/>
'Core:TagSetFieldData:ErrorTagCodeSyntax' => 'Le code de l\'étiquette doit contenir entre 3 et %1$d caractères alphanumériques.',
'Core:TagSetFieldData:ErrorTagLabelSyntax' => 'Le nom de l\'étiquette ne doit pas être vide ni contenir le caractère \'%1$s\'',
'Core:TagSetFieldData:ErrorCodeUpdateNotAllowed' => 'Le code de l\'étiquette ne peut pas être changé',
'Core:TagSetFieldData:ErrorClassUpdateNotAllowed' => 'La classe de l\'étiquette ne peut pas être changée',
'Core:TagSetFieldData:ErrorAttCodeUpdateNotAllowed' => 'L\'attribut de l\'étiquette ne peut pas être changé',
'Core:TagSetFieldData:WhereIsThisTagTab' => 'Utilisation (%1$d)',
'Core:TagSetFieldData:NoEntryFound' => 'Pas d\'utilisation de cette étiquette',

View File

@@ -70,7 +70,8 @@ $.widget('itop.tagset_widget',
REMOVED_VAL_KEY: "removed",
STATUS_ADDED: "added",
STATUS_REMOVED: "removed",
STATUS_NEUTRAL: "unchanged",
STATUS_NEUTRAL: "unchanged",
MAX_TAGS_ALLOWED: "max_tags_allowed",
possibleValues: null,
partialValues: null,
@@ -79,6 +80,7 @@ $.widget('itop.tagset_widget',
tagSetCodesStatus: null,
selectizeWidget: null,
maxTagsAllowed: null,
// the constructor
_create: function () {
@@ -100,6 +102,7 @@ $.widget('itop.tagset_widget',
this.possibleValues = dataArray[this.POSSIBLE_VAL_KEY];
this.partialValues = ($.isArray(dataArray[this.PARTIAL_VAL_KEY])) ? dataArray[this.PARTIAL_VAL_KEY] : [];
this.originalValue = dataArray[this.ORIG_VAL_KEY];
this.maxTagsAllowed = dataArray[this.MAX_TAGS_ALLOWED];
this.tagSetCodesStatus = {};
},
@@ -118,7 +121,7 @@ $.widget('itop.tagset_widget',
$inputWidget.selectize({
plugins: ['remove_button'],
delimiter: ' ',
maxItems: null,
maxItems: this.maxTagsAllowed,
hideSelected: true,
valueField: 'code',
labelField: 'label',

View File

@@ -185,16 +185,37 @@ class TagSetFieldDataTest extends ItopDataTestCase
}
/**
* Test that tag code cannot be modified
* @expectedException \CoreException
* Test that tag code cannot be modified if used
*
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \Exception
*/
public function testUpdateCode()
{
$oTagData = $this->CreateTagData(TAG_CLASS, TAG_ATTCODE, 'tag1', 'First');
$oTagData->Set('code', 'tag2');
$oTagData->DBWrite();
//Use it
$oTicket = $this->CreateTicket(1);
$oTicket->Set(TAG_ATTCODE, 'tag2');
$oTicket->DBWrite();
// Try to change the code of the tag, must complain !
try
{
$oTagData->Set('code', 'tag1');
$oTagData->DBWrite();
} catch (\CoreException $e)
{
static::assertTrue(true);
return;
}
// Should not pass here
static::assertFalse(true);
}
/**
@@ -227,4 +248,38 @@ class TagSetFieldDataTest extends ItopDataTestCase
// Failed
static::assertFalse(true);
}
public function testMaxTagsAllowed()
{
/** @var \AttributeTagSet $oAttDef */
$oAttDef = \MetaModel::GetAttributeDef(TAG_CLASS, TAG_ATTCODE);
$iMaxTags = $oAttDef->GetTagMaxNb();
for ($i = 0; $i < $iMaxTags; $i++)
{
$sTagCode = 'MaxTag'.$i;
$this->CreateTagData(TAG_CLASS, TAG_ATTCODE, $sTagCode, $sTagCode);
}
$oTicket = $this->CreateTicket(1);
$this->debug("Max number of tags is $iMaxTags");
$sValue = '';
for ($i = 0; $i < ($iMaxTags + 1); $i++)
{
try
{
$sTagCode = 'MaxTag'.$i;
$sValue .= "$sTagCode ";
$oTicket->Set(TAG_ATTCODE, $sValue);
$oTicket->DBWrite();
} catch (\Exception $e)
{
// Should fail on the last iteration
static::assertEquals($iMaxTags, $i);
$this->debug("Setting (".($i+1).") tag(s) failed");
return;
}
$this->debug("Setting (".($i+1).") tag(s) worked");
}
static::assertFalse(true);
}
}