diff --git a/application/displayblock.class.inc.php b/application/displayblock.class.inc.php index e79d94161..6c4fda610 100644 --- a/application/displayblock.class.inc.php +++ b/application/displayblock.class.inc.php @@ -1246,6 +1246,10 @@ JS } else { $oBlock = DashletFactory::MakeForDashletBadge($sClassIconUrl, $sHyperlink, $iCount, $sClassLabel, null, null, $aRefreshParams); } + $sClassDescription = MetaModel::GetClassDescription($sClass); + if (utils::IsNotNullOrEmptyString($sClassDescription)) { + $oBlock->SetClassDescription($sClassDescription); + } return $oBlock; } diff --git a/application/ui.linkswidget.class.inc.php b/application/ui.linkswidget.class.inc.php index b0c4f5255..0354a1a83 100644 --- a/application/ui.linkswidget.class.inc.php +++ b/application/ui.linkswidget.class.inc.php @@ -178,17 +178,18 @@ class UILinksWidget $oDisplayBlock = new DisplayBlock($oFilter, 'search', false); $oBlock->AddSubBlock($oDisplayBlock->GetDisplay($oPage, "SearchFormToAdd_{$sLinkedSetId}", - array( - 'menu' => false, + [ + 'menu' => false, 'result_list_outer_selector' => "SearchResultsToAdd_{$sLinkedSetId}", - 'table_id' => "add_{$sLinkedSetId}", - 'table_inner_id' => "ResultsToAdd_{$sLinkedSetId}", - 'selection_mode' => true, - 'json' => $sJson, - 'cssCount' => '#count_'.$this->m_sAttCode.$this->m_sNameSuffix, - 'query_params' => $oFilter->GetInternalParams(), - 'hidden_criteria' => $sAlreadyLinkedExpression, - ))); + 'table_id' => "add_{$sLinkedSetId}", + 'table_inner_id' => "ResultsToAdd_{$sLinkedSetId}", + 'selection_mode' => true, + 'json' => $sJson, + 'cssCount' => '#count_'.$this->m_sAttCode.$this->m_sNameSuffix, + 'query_params' => $oFilter->GetInternalParams(), + 'hidden_criteria' => $sAlreadyLinkedExpression, + 'submit_on_load' => false, + ])); $oBlock->AddForm(); } diff --git a/core/action.class.inc.php b/core/action.class.inc.php index 756f98a8f..53bf71865 100644 --- a/core/action.class.inc.php +++ b/core/action.class.inc.php @@ -650,6 +650,9 @@ class ActionEmail extends ActionNotification $aMessageContent['subject'] = 'TEST['.$aMessageContent['subject'].']'; $aMessageContent['body'] = $sTestBody; $aMessageContent['to'] = $this->Get('test_recipient'); + // N°6677 Ensure emails in test are never sent to cc'd and bcc'd addresses + $aMessageContent['cc'] = ''; + $aMessageContent['bcc'] = ''; } // Note: N°4849 We pass the "References" identifier instead of the "Message-ID" on purpose as we want notifications emails to group around the triggering iTop object, not just the users' replies to the notification $aMessageContent['in_reply_to'] = $aMessageContent['references']; diff --git a/css/backoffice/components/dashlet/_dashlet-badge.scss b/css/backoffice/components/dashlet/_dashlet-badge.scss index eda128e97..f95a2cc1c 100644 --- a/css/backoffice/components/dashlet/_dashlet-badge.scss +++ b/css/backoffice/components/dashlet/_dashlet-badge.scss @@ -20,6 +20,8 @@ $ibo-dashlet-badge--icon--size: 48px !default; $ibo-dashlet-badge--action-icon--margin-right: $ibo-spacing-300 !default; +$ibo-dashlet-badge--body--tooltip-title--margin-bottom: $ibo-spacing-500 !default; + /* CSS variables (can be changed directly from the browser) */ :root { --ibo-dashlet-badge--min-width: #{$ibo-dashlet-badge--min-width}; @@ -74,18 +76,27 @@ $ibo-dashlet-badge--action-icon--margin-right: $ibo-spacing-300 !default; @extend %ibo-hyperlink-inherited-colors; } } -.ibo-dashlet-badge--action-list-count{ - margin-right: $ibo-dashlet-badge--action-list-count--margin-right; - @extend %ibo-font-ral-bol-450; + +.ibo-dashlet-badge--action-list-count { + margin-right: $ibo-dashlet-badge--action-list-count--margin-right; + @extend %ibo-font-ral-bol-450; } -.ibo-dashlet-badge--action-list-label{ - display: inline-block; - @extend %ibo-text-truncated-with-ellipsis; + +.ibo-dashlet-badge--action-list-label { + display: inline-block; + @extend %ibo-text-truncated-with-ellipsis; } -.ibo-dashlet-badge--action-create{ - @extend %ibo-baseline-centered-content; - @extend %ibo-font-size-150; + +.ibo-dashlet-badge--action-create { + @extend %ibo-baseline-centered-content; + @extend %ibo-font-size-150; } -.ibo-dashlet-badge--action-create-icon{ - margin-right: $ibo-dashlet-badge--action-icon--margin-right; + +.ibo-dashlet-badge--action-create-icon { + margin-right: $ibo-dashlet-badge--action-icon--margin-right; +} + +.ibo-dashlet-badge--body--tooltip-title { + @extend %ibo-font-weight-600; + margin-bottom: $ibo-dashlet-badge--body--tooltip-title--margin-bottom; } diff --git a/js/layouts/activity-panel/activity-panel.js b/js/layouts/activity-panel/activity-panel.js index ed9326589..f3669cadd 100644 --- a/js/layouts/activity-panel/activity-panel.js +++ b/js/layouts/activity-panel/activity-panel.js @@ -117,6 +117,7 @@ $(function() locked_by_someone_else: 'locked_by_someone_else', }, }, + release_lock_promise_resolve: null, // N°4494 - Resolve callback of the Promise used for the action following the log entry send, which must be done only once the lock is released // the constructor _create: function () { @@ -500,7 +501,7 @@ $(function() { // Hide all filters' options only if click wasn't on one of them if(($(oEvent.target).closest(this.js_selectors.activity_filter_options_toggler).length === 0) - && $(oEvent.target).closest(this.js_selectors.activity_filter_options).length === 0) { + && $(oEvent.target).closest(this.js_selectors.activity_filter_options).length === 0) { this._HideAllFiltersOptions(); } }, @@ -947,9 +948,9 @@ $(function() // Send request to server $.post( - GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', - oParams, - 'json' + GetAbsoluteUrlAppRoot()+'pages/ajax.render.php', + oParams, + 'json' ) .fail(function (oXHR, sStatus, sErrorThrown) { CombodoModal.OpenErrorModal(sErrorThrown); @@ -972,12 +973,24 @@ $(function() // For now, we don't hide the forms as the user may want to add something else me.element.find(me.js_selectors.caselog_entry_form).trigger('clear_entry.caselog_entry_form.itop'); - // Redirect to stimulus // - Convert undefined, null and empty string to null sStimulusCode = ((sStimulusCode ?? '') === '') ? null : sStimulusCode; if (null !== sStimulusCode) { - window.location.href = GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=stimulus&class='+me._GetHostObjectClass()+'&id='+me._GetHostObjectID()+'&stimulus='+sStimulusCode; + if (me.options.lock_enabled) { + // Use a Promise to ensure that we redirect to the stimulus page ONLY when the lock is released, otherwise we might lock ourselves + const oPromise = new Promise(function(resolve) { + // Store the resolve callback so we can call it later from outside + me.release_lock_promise_resolve = resolve; + }); + oPromise.then(function () { + window.location.href = GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=stimulus&class='+me._GetHostObjectClass()+'&id='+me._GetHostObjectID()+'&stimulus='+sStimulusCode; + // Resolve callback is reinitialized in case the redirection fails for any reason and we might need to retry + me.release_lock_promise_resolve = null; + }); + } else { + window.location.href = GetAbsoluteUrlAppRoot()+'pages/UI.php?operation=stimulus&class='+me._GetHostObjectClass()+'&id='+me._GetHostObjectID()+'&stimulus='+sStimulusCode; + } } }) .always(function () { @@ -995,7 +1008,7 @@ $(function() _IncreaseTabTogglerMessagesCounter: function(sCaseLogAttCode){ let oTabTogglerCounter = this._GetTabTogglerFromCaseLogAttCode(sCaseLogAttCode).find('[data-role="ibo-activity-panel--tab-title-messages-count"]'); let iNewCounterValue = parseInt(oTabTogglerCounter.attr('data-messages-count')) + 1; - + oTabTogglerCounter.attr('data-messages-count', iNewCounterValue).text(iNewCounterValue); }, /** @@ -1143,11 +1156,10 @@ $(function() else { oParams.operation = 'check_lock_state'; } - $.post( - this.options.lock_endpoint, - oParams, - 'json' + this.options.lock_endpoint, + oParams, + 'json' ) .fail(function (oXHR, sStatus, sErrorThrown) { // In case of HTTP request failure (not lock request), put the details in the JS console @@ -1196,6 +1208,9 @@ $(function() // Tried to release our lock else if ('release_lock' === oParams.operation) { sNewLockStatus = me.enums.lock_status.unknown; + if (me.release_lock_promise_resolve !== null) { + me.release_lock_promise_resolve(); + } } // Just checked if object was locked @@ -1430,9 +1445,9 @@ $(function() limit_results_length: bLimitResultsLength, }; $.post( - this.options.load_more_entries_endpoint, - oParams, - 'json' + this.options.load_more_entries_endpoint, + oParams, + 'json' ) .fail(function (oXHR, sStatus, sErroThrown) { CombodoModal.OpenErrorModal(sErrorThrown); diff --git a/js/links/links_widget.js b/js/links/links_widget.js index 6983e70c0..309918a88 100644 --- a/js/links/links_widget.js +++ b/js/links/links_widget.js @@ -153,16 +153,15 @@ function LinksWidget(id, sClass, sAttCode, iInputId, sSuffix, bDuplicates, oWizH "dataType": "html" }) .done(function (data) { + /* N°6152 - Hide during data loading and before open */ + $('#dlg_'+me.id).hide(); $('#dlg_'+me.id).html(data); window[sPromiseId].then(function () { $('#dlg_'+me.id).dialog('open'); me.UpdateSizes(null, null); - if (me.bDoSearch) - { + if (me.bDoSearch) { me.SearchObjectsToAdd(); - } - else - { + } else { $('#count_'+me.id).change(function () { let c = this.value; me.UpdateButtons(c); diff --git a/pages/UniversalSearch.php b/pages/UniversalSearch.php index ac73a64c4..0597a36f4 100644 --- a/pages/UniversalSearch.php +++ b/pages/UniversalSearch.php @@ -105,7 +105,7 @@ if ($oFilter != null) $aExtraParams['action'] = utils::GetAbsoluteUrlAppRoot().'pages/UniversalSearch.php'; $aExtraParams['table_id'] = '1'; $aExtraParams['search_header_force_dropdown'] = $sSearchHeaderForceDropdown; - //$aExtraParams['class'] = $sClassName; + $aExtraParams['submit_on_load'] = false; $oBlock->Display($oP, 0, $aExtraParams); // Search results diff --git a/pages/tagadmin.php b/pages/tagadmin.php index 33da2ab8f..f52a37df3 100644 --- a/pages/tagadmin.php +++ b/pages/tagadmin.php @@ -109,6 +109,7 @@ try $aExtraParams['action'] = utils::GetAbsoluteUrlAppRoot().'pages/tagadmin.php'; $aExtraParams['table_id'] = '1'; $aExtraParams['search_header_force_dropdown'] = $sSearchHeaderForceDropdown; + $aExtraParams['submit_on_load'] = false; $oBlock->Display($oP, 0, $aExtraParams); // Search results diff --git a/sources/Application/UI/Base/Component/Dashlet/DashletBadge.php b/sources/Application/UI/Base/Component/Dashlet/DashletBadge.php index 12e2722cb..d92fb05f2 100644 --- a/sources/Application/UI/Base/Component/Dashlet/DashletBadge.php +++ b/sources/Application/UI/Base/Component/Dashlet/DashletBadge.php @@ -9,6 +9,7 @@ namespace Combodo\iTop\Application\UI\Base\Component\Dashlet; use Combodo\iTop\Application\UI\Base\tJSRefreshCallback; +use utils; class DashletBadge extends DashletContainer { @@ -29,6 +30,11 @@ class DashletBadge extends DashletContainer protected $iCount; /** @var string */ protected $sClassLabel; + /** + * @var string + * @since 3.1.1 3.2.0 + */ + protected $sClassDescription; /** @var string */ protected $sCreateActionUrl; @@ -62,6 +68,7 @@ class DashletBadge extends DashletContainer $this->sCreateActionUrl = $sCreateActionUrl; $this->sCreateActionLabel = $sCreateActionLabel; $this->aRefreshParams = $aRefreshParams; + $this->sClassDescription = ''; } @@ -185,6 +192,37 @@ class DashletBadge extends DashletContainer return $this; } + /** + * @return string + * @since 3.1.1 3.2.0 + */ + public function GetClassDescription(): string + { + return $this->sClassDescription; + } + + /** + * @param string $sClassDescription + * + * @return DashletBadge + * @since 3.1.1 3.2.0 + */ + public function SetClassDescription(string $sClassDescription) + { + $this->sClassDescription = $sClassDescription; + + return $this; + } + + /** + * @return bool + * @since 3.1.1 + */ + public function HasClassDescription(): bool + { + return utils::IsNotNullOrEmptyString($this->sClassDescription); + } + public function GetJSRefresh(): string { return "$('#".$this->sId."').block(); diff --git a/templates/base/components/dashlet/dashlet-badge.html.twig b/templates/base/components/dashlet/dashlet-badge.html.twig index 74096e9ee..52ea2d2de 100644 --- a/templates/base/components/dashlet/dashlet-badge.html.twig +++ b/templates/base/components/dashlet/dashlet-badge.html.twig @@ -3,7 +3,16 @@ {% apply spaceless %}
+ {% if oUIBlock.HasClassDescription() %} + {# Display both class name and description as the name could be truncated if too long #} + data-tooltip-content="{{ '
'|escape }}{{ oUIBlock.GetClassLabel() }}{{ '
'|escape }}{{ oUIBlock.GetClassDescription() }}{{ '
'|escape }}" + data-tooltip-html-enabled="true" + {% else %} + {# Display only class name as it could be truncated if too long #} + data-tooltip-content="{{ oUIBlock.GetClassLabel() }}" + {% endif %} + {# Delay display to avoid having all tooltips appearing when mouse is just passing through the tabs #} + data-tooltip-show-delay="300">
{# Mind the empty "alt" attribute https://www.w3.org/WAI/tutorials/images/decorative/ #} diff --git a/tests/php-unit-tests/unitary-tests/core/ExpressionEvaluateTest.php b/tests/php-unit-tests/unitary-tests/core/ExpressionEvaluateTest.php index 0a6b49831..8e88ec57f 100644 --- a/tests/php-unit-tests/unitary-tests/core/ExpressionEvaluateTest.php +++ b/tests/php-unit-tests/unitary-tests/core/ExpressionEvaluateTest.php @@ -33,7 +33,7 @@ class ExpressionEvaluateTest extends ItopDataTestCase $aParameters = $oExpression->GetParameters($sParentFilter); sort($aExpectedParameters); sort($aParameters); - static::assertEquals($aExpectedParameters, $aParameters); + $this->assertEquals($aExpectedParameters, $aParameters); } public function GetParametersProvider() @@ -81,7 +81,7 @@ class ExpressionEvaluateTest extends ItopDataTestCase { $oExpression = Expression::FromOQL($sExpression); $value = $oExpression->Evaluate(array()); - static::assertEquals($expectedValue, $value); + $this->assertEquals($expectedValue, $value); } public function VariousExpressionsProvider() @@ -220,7 +220,7 @@ class ExpressionEvaluateTest extends ItopDataTestCase $sNewExpression = "return $sExpression;"; $oExpression = eval($sNewExpression); $res = $oExpression->Evaluate(array()); - static::assertEquals($expectedValue, $res); + $this->assertEquals($expectedValue, $res); } public function NotYetParsableExpressionsProvider() @@ -282,7 +282,7 @@ class ExpressionEvaluateTest extends ItopDataTestCase $value = $aResults[0]["test_$i"]; $expectedValue = $aTest[1]; $this->debug("Test #$i: {$aTests[$i][0]} => ".var_export($value, true)); - static::assertEquals($expectedValue, $value); + $this->assertEquals($expectedValue, $value); } } @@ -305,7 +305,7 @@ class ExpressionEvaluateTest extends ItopDataTestCase $res = $oObject->EvaluateExpression($oExpression); - static::assertEquals($expected, $res); + $this->assertEquals($expected, $res); } public function ExpressionsWithObjectFieldsProvider() @@ -335,12 +335,18 @@ class ExpressionEvaluateTest extends ItopDataTestCase { $oExpression = Expression::FromOQL($sExpression); $res = $oExpression->Evaluate($aParameters); - static::assertEquals($expected, $res); + $this->assertEquals($expected, $res); } public function ExpressionWithParametersProvider() { return array( + ['`DBVariables["analyze_sample_percentage"]` > 10', ['DBVariables["analyze_sample_percentage"]' => 20], true], + ['`DataBase["DBDataSize"]`', ['DataBase["DBDataSize"]' => 4096], 4096], + ['`FileSystem["ItopInstallationIntegrity"]`', ['FileSystem["ItopInstallationIntegrity"]' => 'not_conform'], 'not_conform'], + ['`DBTablesInfo["attachment"].DataSize` > 100', ['DBTablesInfo["attachment"].DataSize' => 200], true], + ['`DBTablesInfo[].DataSize` > 100', ['DBTablesInfo[].DataSize' => 50], false], + ['(`DBTablesInfo[].DataSize` > 100) AND (`DBTablesInfo[].DataFree` * 100 / (`DBTablesInfo[].DataSize` + `DBTablesInfo[].IndexSize` + `DBTablesInfo[].DataFree`) > 10)', ['DBTablesInfo[].DataSize' => 200, 'DBTablesInfo[].DataFree' => 100, 'DBTablesInfo[].IndexSize' => 10], true], array('CONCAT(SUBSTR(name, 4), " cause")', array('name' => 'noble'), 'le cause'), ); } @@ -364,11 +370,11 @@ class ExpressionEvaluateTest extends ItopDataTestCase $res = $oExpression->IsTrue(); if ($bExpectTrue) { - static::assertTrue($res, 'arg: '.$sExpression); + $this->assertTrue($res, 'arg: '.$sExpression); } else { - static::assertFalse($res, 'arg: '.$sExpression); + $this->assertFalse($res, 'arg: '.$sExpression); } } @@ -409,10 +415,10 @@ class ExpressionEvaluateTest extends ItopDataTestCase if ($bProcessed) { $sqlValue = CMDBSource::QueryToScalar("SELECT DATE_FORMAT('$sDate', '%$sFormat')"); - static::assertEquals($sqlValue, $sValueOrException, 'Check test against MySQL'); + $this->assertEquals($sqlValue, $sValueOrException, 'Check test against MySQL'); $res = $oExpression->Evaluate(array()); - static::assertEquals($sValueOrException, $res, 'Check evaluation'); + $this->assertEquals($sValueOrException, $res, 'Check evaluation'); } else { @@ -505,7 +511,7 @@ class ExpressionEvaluateTest extends ItopDataTestCase { $oExpression = new FunctionExpression('DATE_FORMAT', array(new ScalarExpression($sDate), new ScalarExpression("%$sFormat"))); $itopExpressionResult = $oExpression->Evaluate(array()); - static::assertSame($aMysqlDateFormatRsultsForAllFormats[$sFormat], $itopExpressionResult, "Format %$sFormat not matching MySQL for '$sDate'"); + $this->assertSame($aMysqlDateFormatRsultsForAllFormats[$sFormat], $itopExpressionResult, "Format %$sFormat not matching MySQL for '$sDate'"); } } }