Compare commits

..

1 Commits

Author SHA1 Message Date
v-dumas
6e43d01475 N°3961 - Reorganize Service menus 2026-02-16 13:51:21 +01:00
10 changed files with 248 additions and 475 deletions

View File

@@ -97,77 +97,33 @@ class RestUtils
* @throws Exception
* @api
*/
public static function GetFieldList($sClass, $oData, $sParamName, $bFailIfNotFound = true)
public static function GetFieldList($sClass, $oData, $sParamName)
{
$sFields = self::GetOptionalParam($oData, $sParamName, '*');
return match($sFields) {
'*' => self::GetFieldListForClass($sClass),
'*+' => self::GetFieldListForParentClass($sClass),
default => self::GetLimitedFieldListForClass($sClass, $sFields, $sParamName, $bFailIfNotFound),
};
}
public static function HasRequestedExtendedOutput(string $sFields): bool
{
return match($sFields) {
'*' => false,
'*+' => true,
default => substr_count($sFields, ':') > 1,
};
}
public static function HasRequestedAllOutputFields(string $sFields): bool
{
return match($sFields) {
'*', '*+' => true,
default => false,
};
}
protected static function GetFieldListForClass(string $sClass): array
{
return [$sClass => array_keys(MetaModel::ListAttributeDefs($sClass))];
}
protected static function GetFieldListForParentClass(string $sClass): array
{
$aFieldList = array();
foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sRefClass) {
$aFieldList = array_merge($aFieldList, self::GetFieldListForClass($sRefClass));
}
return $aFieldList;
}
protected static function GetLimitedFieldListForSingleClass(string $sClass, string $sFields, string $sParamName, bool $bFailIfNotFound = true): array
{
$aFieldList = [$sClass => []];
foreach (explode(',', $sFields) as $sAttCode) {
$sAttCode = trim($sAttCode);
if (($sAttCode == 'id') || (MetaModel::IsValidAttCode($sClass, $sAttCode))) {
$aFieldList[$sClass][] = $sAttCode;
} else {
if ($bFailIfNotFound) {
throw new Exception("$sParamName: invalid attribute code '$sAttCode' for class '$sClass'");
$aShowFields = [];
if ($sFields == '*') {
foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) {
$aShowFields[$sClass][] = $sAttCode;
}
} elseif ($sFields == '*+') {
foreach (MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL) as $sRefClass) {
foreach (MetaModel::ListAttributeDefs($sRefClass) as $sAttCode => $oAttDef) {
$aShowFields[$sRefClass][] = $sAttCode;
}
}
}
return $aFieldList;
}
protected static function GetLimitedFieldListForClass(string $sClass, string $sFields, string $sParamName, bool $bFailIfNotFound = true): array
{
if (!str_contains($sFields, ':')) {
return self::GetLimitedFieldListForSingleClass($sClass, $sFields, $sParamName, $bFailIfNotFound);
} else {
foreach (explode(',', $sFields) as $sAttCode) {
$sAttCode = trim($sAttCode);
if (($sAttCode != 'id') && (!MetaModel::IsValidAttCode($sClass, $sAttCode))) {
throw new Exception("$sParamName: invalid attribute code '$sAttCode'");
}
$aShowFields[$sClass][] = $sAttCode;
}
}
$aFieldList = [];
$aFieldListParts = explode(';', $sFields);
foreach ($aFieldListParts as $sClassFields) {
list($sSubClass, $sSubClassFields) = explode(':', $sClassFields);
$aFieldList = array_merge($aFieldList, self::GetLimitedFieldListForSingleClass(trim($sSubClass), trim($sSubClassFields), $sParamName, $bFailIfNotFound));
}
return $aFieldList;
return $aShowFields;
}
/**
* Read and interpret object search criteria from a Rest/Json structure
*

View File

@@ -248,45 +248,6 @@ class RestResultWithObjects extends RestResult
}
}
/**
* @package RESTAPI
* @api
*/
class RestResultWithObjectSets extends RestResultWithObjects
{
private $current_object = null;
public function MakeNewObjectSet()
{
$arr = array();
$this->current_object = &$arr;
$this->objects[] = &$arr;
}
/**
* Report the given object
*
* @api
* @param string $sObjectAlias Name of the subobject, usually the OQL class alias
* @param int $iCode An error code (RestResult::OK is no issue has been found)
* @param string $sMessage Description of the error if any, an empty string otherwise
* @param DBObject $oObject The object being reported
* @param array|null $aFieldSpec An array of class => attribute codes (Cf. RestUtils::GetFieldList). List of the attributes to be reported.
* @param boolean $bExtendedOutput Output all of the link set attributes ?
*
* @return void
* @throws \ArchivedObjectException
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
*/
public function AppendSubObject($sObjectAlias, $iCode, $sMessage, $oObject, $aFieldSpec = null, $bExtendedOutput = false)
{
$oObjRes = ObjectResult::FromDBObject($oObject, $aFieldSpec, $bExtendedOutput, $iCode, $sMessage);
$this->current_object[$sObjectAlias] = $oObjRes;
}
}
/**
* @package RESTAPI
* @api
@@ -539,22 +500,15 @@ class CoreServices implements iRestServiceProvider, iRestInputSanitizer
break;
case 'core/get':
$sClassParam = RestUtils::GetMandatoryParam($aParams, 'class');
$sClass = RestUtils::GetClass($aParams, 'class');
$key = RestUtils::GetMandatoryParam($aParams, 'key');
$sShowFields = RestUtils::GetOptionalParam($aParams, 'output_fields', '*');
$aShowFields = RestUtils::GetFieldList($sClass, $aParams, 'output_fields');
$bExtendedOutput = (RestUtils::GetOptionalParam($aParams, 'output_fields', '*') == '*+');
$iLimit = (int)RestUtils::GetOptionalParam($aParams, 'limit', 0);
$iPage = (int)RestUtils::GetOptionalParam($aParams, 'page', 1);
// Validate the class(es)
$aClass = explode(',', $sClassParam);
foreach ($aClass as $sClass) {
if (!MetaModel::IsValidClass(trim($sClass))) {
throw new Exception("class '$sClass' is not valid");
}
}
$oObjectSet = RestUtils::GetObjectSetFromKey($sClassParam, $key, $iLimit, self::getOffsetFromLimitAndPage($iLimit, $iPage));
$sTargetClass = $oObjectSet->GetFilter()->GetClass();
$oObjectSet = RestUtils::GetObjectSetFromKey($sClass, $key, $iLimit, self::getOffsetFromLimitAndPage($iLimit, $iPage));
$sTargetClass = $oObjectSet->GetFilter()->GetClass();
if (UserRights::IsActionAllowed($sTargetClass, UR_ACTION_READ) != UR_ALLOWED_YES) {
$oResult->code = RestResult::UNAUTHORIZED;
@@ -565,67 +519,19 @@ class CoreServices implements iRestServiceProvider, iRestInputSanitizer
} elseif ($iPage < 1) {
$oResult->code = RestResult::INVALID_PAGE;
$oResult->message = "The request page number is not valid. It must be an integer greater than 0";
} elseif (count($oObjectSet->GetSelectedClasses()) > 1) {
$oResult = new RestResultWithObjectSets();
$aCache = [];
$aShowFields = [];
foreach ($oObjectSet->GetSelectedClasses() as $sSelectedClass) {
$aShowFields = array_merge( $aShowFields, RestUtils::GetFieldList($sSelectedClass, $aParams, 'output_fields', false));
}
while ($oObjects = $oObjectSet->FetchAssoc()) {
$oResult->MakeNewObjectSet();
foreach ($oObjects as $sAlias => $oObject) {
if (!$oObject) {
continue;
}
if (!array_key_exists($sAlias, $aCache)) {
$sClass = get_class($oObject);
$bExtendedOutput = RestUtils::HasRequestedExtendedOutput($sShowFields);
if (!RestUtils::HasRequestedAllOutputFields($sShowFields)) {
$aFields = $aShowFields[$sClass];
//Id is not a valid attribute to optimize
if ($aFields && in_array('id', $aFields)) {
unset($aFields[array_search('id', $aFields)]);
}
$aAttToLoad = [$sAlias => $aFields];
$oObjectSet->OptimizeColumnLoad($aAttToLoad);
}
$aCache[$sAlias] = [
'aShowFields' => $aShowFields,
'bExtendedOutput' => $bExtendedOutput,
];
} else {
$aShowFields = $aCache[$sAlias]['aShowFields'];
$bExtendedOutput = $aCache[$sAlias]['bExtendedOutput'];
}
$oResult->AppendSubObject($sAlias, 0, '', $oObject, $aShowFields, $bExtendedOutput);
}
}
$oResult->message = "Found: ".$oObjectSet->Count();
} else {
$aShowFields =[];
foreach ($aClass as $sSelectedClass) {
$sSelectedClass = trim($sSelectedClass);
$aShowFields = array_merge($aShowFields, RestUtils::GetFieldList($sSelectedClass, $aParams, 'output_fields', false));
}
if (!RestUtils::HasRequestedAllOutputFields($sShowFields) && count($aShowFields) == 1) {
$aFields = $aShowFields[$sClass];
//Id is not a valid attribute to optimize
if (in_array('id', $aFields)) {
unset($aFields[array_search('id', $aFields)]);
}
if (!$bExtendedOutput && RestUtils::GetOptionalParam($aParams, 'output_fields', '*') != '*') {
$aFields = $aShowFields[$sClass];
//Id is not a valid attribute to optimize
if (in_array('id', $aFields)) {
unset($aFields[array_search('id', $aFields)]);
}
$aAttToLoad = [$oObjectSet->GetClassAlias() => $aFields];
$oObjectSet->OptimizeColumnLoad($aAttToLoad);
}
$oObjectSet->OptimizeColumnLoad($aAttToLoad);
}
while ($oObject = $oObjectSet->Fetch()) {
$oResult->AddObject(0, '', $oObject, $aShowFields, RestUtils::HasRequestedExtendedOutput($sShowFields));
$oResult->AddObject(0, '', $oObject, $aShowFields, $bExtendedOutput);
}
$oResult->message = "Found: ".$oObjectSet->Count();
}

View File

@@ -2759,52 +2759,131 @@ public function PrefillSearchForm(&$aContextParam)
</menu>
<menu id="CustomerContract" xsi:type="OQLMenuNode" _delta="define">
<rank>1</rank>
<parent>ServiceManagement</parent>
<parent>Service:Overview</parent>
<oql>SELECT CustomerContract</oql>
<do_search>1</do_search>
</menu>
<menu id="ProviderContract" xsi:type="OQLMenuNode" _delta="define">
<rank>2</rank>
<parent>ServiceManagement</parent>
<parent>Service:Overview</parent>
<oql>SELECT ProviderContract</oql>
<do_search>1</do_search>
</menu>
<menu id="ServiceCatalog" xsi:type="DashboardMenuNode" _delta="define">
<rank>10</rank>
<parent>ServiceManagement</parent>
<definition>
<title>UI-ServiceCatalogMenu-Title</title>
<layout>DashboardLayoutTwoCols</layout>
<cells>
<cell id="1">
<rank>1</rank>
<dashlets>
<dashlet id="ServiceCatalog_RecentRequestByService" xsi:type="DashletGroupByTable">
<rank>0</rank>
<title>UI-ServiceCatalogMenu-RecentRequestByService</title>
<query><![CDATA[SELECT UserRequest AS u WHERE u.start_date < DATE_ADD(NOW(), INTERVAL 90 DAY)]]></query>
<group_by>service_id</group_by>
<style>table</style>
<aggregation_function>count</aggregation_function>
<aggregation_attribute/>
<limit/>
<order_by>attribute</order_by>
<order_direction>desc</order_direction>
</dashlet>
</dashlets>
</cell>
<cell id="2">
<rank>2</rank>
<dashlets>
<dashlet id="0" xsi:type="DashletEmptyCell">
<rank>0</rank>
</dashlet>
</dashlets>
</cell>
</cells>
</definition>
</menu>
<menu id="ServiceFamily" xsi:type="OQLMenuNode" _delta="define">
<rank>2.5</rank>
<parent>ServiceManagement</parent>
<parent>ServiceCatalog</parent>
<oql>SELECT ServiceFamily</oql>
<do_search>1</do_search>
</menu>
<menu id="Service" xsi:type="OQLMenuNode" _delta="define">
<rank>3</rank>
<parent>ServiceManagement</parent>
<parent>ServiceCatalog</parent>
<oql>SELECT Service</oql>
<do_search>1</do_search>
</menu>
<menu id="ServiceSubcategory" xsi:type="OQLMenuNode" _delta="define">
<rank>4</rank>
<parent>ServiceManagement</parent>
<parent>ServiceCatalog</parent>
<oql>SELECT ServiceSubcategory</oql>
<do_search>1</do_search>
</menu>
<menu id="SLA" xsi:type="OQLMenuNode" _delta="define">
<rank>5</rank>
<parent>ServiceManagement</parent>
<parent>ServiceCatalog</parent>
<oql>SELECT SLA</oql>
<do_search>1</do_search>
</menu>
<menu id="SLT" xsi:type="OQLMenuNode" _delta="define">
<rank>6</rank>
<parent>ServiceManagement</parent>
<parent>ServiceCatalog</parent>
<oql>SELECT SLT</oql>
<do_search>1</do_search>
</menu>
<menu id="DeliveryModel" xsi:type="OQLMenuNode" _delta="define">
<rank>7</rank>
<parent>ServiceManagement</parent>
<parent>ServiceCatalog</parent>
<oql>SELECT DeliveryModel</oql>
<do_search>1</do_search>
</menu>
<menu id="RulesAndWorkflow" xsi:type="DashboardMenuNode" _delta="define">
<rank>20</rank>
<parent>ServiceManagement</parent>
<definition>
<title>UI-RulesAndWorkflow-Title</title>
<layout>DashboardLayoutTwoCols</layout>
<cells>
<cell id="0">
<rank>0</rank>
<dashlets>
<dashlet id="RulesAndWorkflow_PlainText" xsi:type="DashletPlainText">
<rank>0</rank>
<text>UI-RulesAndWorkflow-Description</text>
</dashlet>
</dashlets>
</cell>
<cell id="1">
<rank>1</rank>
<dashlets>
<dashlet id="0" xsi:type="DashletEmptyCell">
<rank>0</rank>
</dashlet>
</dashlets>
</cell>
<cell id="2">
<rank>2</rank>
<dashlets>
<dashlet id="0" xsi:type="DashletEmptyCell">
<rank>0</rank>
</dashlet>
</dashlets>
</cell>
<cell id="3">
<rank>3</rank>
<dashlets>
<dashlet id="0" xsi:type="DashletEmptyCell">
<rank>0</rank>
</dashlet>
</dashlets>
</cell>
</cells>
</definition>
</menu>
<menu id="Typology" xsi:type="DashboardMenuNode">
<definition>
<cells>

View File

@@ -62,21 +62,21 @@ Dict::Add('EN US', 'English', 'English', [
'Menu:DeliveryModel+' => 'Delivery models',
'Menu:ServiceFamily' => 'Service families',
'Menu:ServiceFamily+' => 'Service families',
'Menu:ServiceCatalog' => 'Service catalog',
'Menu:ServiceCatalog+' => 'Service catalog',
'UI-ServiceCatalogMenu-Title' => 'Service catalog',
'UI-ServiceCatalogMenu-RecentRequestByService' => 'Recent requests by service',
'Menu:RulesAndWorkflow' => 'Rules and workflow',
'Menu:RulesAndWorkflow+' => 'Automation rules and workflow',
'UI-RulesAndWorkflow-Title' => 'Rules and workflow',
'UI-RulesAndWorkflow-Description' => 'Multiple iTop extensions brings notification rules and workflow automation.
They are included in iTop Products, but not in iTop Community. You may get them on iTop Hub.',
'Contract:baseinfo' => 'General information',
'Contract:moreinfo' => 'Contractual information',
'Contract:cost' => 'Cost information',
]);
/*
'UI:ServiceManagementMenu' => 'Gestion des Services',
'UI:ServiceManagementMenu+' => 'Gestion des Services',
'UI:ServiceManagementMenu:Title' => 'Résumé des services & contrats',
'UI-ServiceManagementMenu-ContractsBySrvLevel' => 'Contrats par niveau de service',
'UI-ServiceManagementMenu-ContractsByStatus' => 'Contrats par état',
'UI-ServiceManagementMenu-ContractsEndingIn30Days' => 'Contrats se terminant dans moins de 30 jours',
*/
//
// Class: Organization
//

View File

@@ -36,19 +36,21 @@ Dict::Add('FR FR', 'French', 'Français', [
'Menu:DeliveryModel+' => 'Modèles de support',
'Menu:ServiceFamily' => 'Familles de service',
'Menu:ServiceFamily+' => 'Familles de service',
'Menu:ServiceCatalog' => 'Catalogue de services',
'Menu:ServiceCatalog+' => '',
'UI-ServiceCatalogMenu-Title' => 'Catalogue de services',
'UI-ServiceCatalogMenu-RecentRequestByService' => 'Demandes récentes groupées par service',
'Menu:RulesAndWorkflow' => 'Règles d\'automatisation',
'Menu:RulesAndWorkflow+' => '',
'UI-RulesAndWorkflow-Title' => 'Règles d\'automatisation',
'UI-RulesAndWorkflow-Description' => 'De nombreuses extensions apportent des règles de notification et d\'automatisation du cycle de vie des tickets.
Elles sont incluses dans les produits Professionels, mais pas dans la version communautaire. Vous pouvez les obtenir sur iTop Hub.',
'Contract:baseinfo' => 'Information générale',
'Contract:moreinfo' => 'Aspects contractuels',
'Contract:cost' => 'Coûts',
]);
/*
'UI:ServiceManagementMenu' => 'Gestion des Services',
'UI:ServiceManagementMenu+' => 'Gestion des Services',
'UI:ServiceManagementMenu:Title' => 'Résumé des services & contrats',
'UI-ServiceManagementMenu-ContractsBySrvLevel' => 'Contrats par niveau de service',
'UI-ServiceManagementMenu-ContractsByStatus' => 'Contrats par état',
'UI-ServiceManagementMenu-ContractsEndingIn30Days' => 'Contrats se terminant dans moins de 30 jours',
*/
//
// Class: Organization

View File

@@ -2693,14 +2693,9 @@ public function PrefillSearchForm(&$aContextParam)
</style>
</menu>
<menu id="WelcomeMenuPage" xsi:type="DashboardMenuNode" _delta="must_exist">
<rank>10</rank>
<parent>WelcomeMenu</parent>
<definition>
<layout>DashboardLayoutOneCol</layout>
<title/>
<cells>
<cell id="0">
<rank>0</rank>
<dashlets>
<dashlet id="6" xsi:type="DashletBadge" _delta="define">
<rank>5</rank>
@@ -2743,52 +2738,130 @@ public function PrefillSearchForm(&$aContextParam)
</menu>
<menu id="CustomerContract" xsi:type="OQLMenuNode" _delta="define">
<rank>1</rank>
<parent>ServiceManagement</parent>
<parent>Service:Overview</parent>
<oql>SELECT CustomerContract</oql>
<do_search>1</do_search>
</menu>
<menu id="ProviderContract" xsi:type="OQLMenuNode" _delta="define">
<rank>2</rank>
<parent>ServiceManagement</parent>
<parent>Service:Overview</parent>
<oql>SELECT ProviderContract</oql>
<do_search>1</do_search>
</menu>
<menu id="ServiceCatalog" xsi:type="DashboardMenuNode" _delta="define">
<rank>10</rank>
<parent>ServiceManagement</parent>
<definition>
<title>UI-ServiceCatalogMenu-Title</title>
<layout>DashboardLayoutTwoCols</layout>
<cells>
<cell id="1">
<rank>1</rank>
<dashlets>
<dashlet id="ServiceCatalog_RecentRequestByService" xsi:type="DashletGroupByTable">
<rank>0</rank>
<title>UI-ServiceCatalogMenu-RecentRequestByService</title>
<query><![CDATA[SELECT UserRequest AS u WHERE u.start_date < DATE_ADD(NOW(), INTERVAL 90 DAY)]]></query>
<group_by>service_id</group_by>
<style>table</style>
<aggregation_function>count</aggregation_function>
<aggregation_attribute/>
<limit/>
<order_by>attribute</order_by>
<order_direction>desc</order_direction>
</dashlet>
</dashlets>
</cell>
<cell id="2">
<rank>2</rank>
<dashlets>
<dashlet id="0" xsi:type="DashletEmptyCell">
<rank>0</rank>
</dashlet>
</dashlets>
</cell>
</cells>
</definition>
</menu>
<menu id="ServiceFamily" xsi:type="OQLMenuNode" _delta="define">
<rank>3</rank>
<parent>ServiceManagement</parent>
<parent>ServiceCatalog</parent>
<oql>SELECT ServiceFamily</oql>
<do_search>1</do_search>
</menu>
<menu id="Service" xsi:type="OQLMenuNode" _delta="define">
<rank>4</rank>
<parent>ServiceManagement</parent>
<parent>ServiceCatalog</parent>
<oql>SELECT Service</oql>
<do_search>1</do_search>
</menu>
<menu id="ServiceSubcategory" xsi:type="OQLMenuNode" _delta="define">
<rank>5</rank>
<parent>ServiceManagement</parent>
<parent>ServiceCatalog</parent>
<oql>SELECT ServiceSubcategory</oql>
<do_search>1</do_search>
</menu>
<menu id="SLA" xsi:type="OQLMenuNode" _delta="define">
<rank>6</rank>
<parent>ServiceManagement</parent>
<parent>ServiceCatalog</parent>
<oql>SELECT SLA</oql>
<do_search>1</do_search>
</menu>
<menu id="SLT" xsi:type="OQLMenuNode" _delta="define">
<rank>7</rank>
<parent>ServiceManagement</parent>
<parent>ServiceCatalog</parent>
<oql>SELECT SLT</oql>
<do_search>1</do_search>
</menu>
<menu id="DeliveryModel" xsi:type="OQLMenuNode" _delta="define">
<rank>8</rank>
<parent>ServiceManagement</parent>
<parent>ServiceCatalog</parent>
<oql>SELECT DeliveryModel</oql>
<do_search>1</do_search>
</menu>
<menu id="RulesAndWorkflow" xsi:type="DashboardMenuNode" _delta="define">
<rank>20</rank>
<parent>ServiceManagement</parent>
<definition>
<title>UI-RulesAndWorkflow-Title</title>
<layout>DashboardLayoutTwoCols</layout>
<cells>
<cell id="0">
<rank>0</rank>
<dashlets>
<dashlet id="RulesAndWorkflow_PlainText" xsi:type="DashletPlainText">
<rank>0</rank>
<text>UI-RulesAndWorkflow-Description</text>
</dashlet>
</dashlets>
</cell>
<cell id="1">
<rank>1</rank>
<dashlets>
<dashlet id="0" xsi:type="DashletEmptyCell">
<rank>0</rank>
</dashlet>
</dashlets>
</cell>
<cell id="2">
<rank>2</rank>
<dashlets>
<dashlet id="0" xsi:type="DashletEmptyCell">
<rank>0</rank>
</dashlet>
</dashlets>
</cell>
<cell id="3">
<rank>3</rank>
<dashlets>
<dashlet id="0" xsi:type="DashletEmptyCell">
<rank>0</rank>
</dashlet>
</dashlets>
</cell>
</cells>
</definition>
</menu>
<menu id="Typology" xsi:type="DashboardMenuNode">
<definition>
<cells>

View File

@@ -60,6 +60,16 @@ Dict::Add('EN US', 'English', 'English', [
'Menu:DeliveryModel+' => 'Delivery models',
'Menu:ServiceFamily' => 'Service families',
'Menu:ServiceFamily+' => 'Service families',
'Menu:ServiceCatalog' => 'Service catalog',
'Menu:ServiceCatalog+' => 'Service catalog',
'UI-ServiceCatalogMenu-Title' => 'Service catalog',
'UI-ServiceCatalogMenu-RecentRequestByService' => 'Recent requests by service',
'Menu:RulesAndWorkflow' => 'Rules and workflow',
'Menu:RulesAndWorkflow+' => 'Automation rules and workflow',
'UI-RulesAndWorkflow-Title' => 'Rules and workflow',
'UI-RulesAndWorkflow-Description' => 'Multiple iTop extensions brings notification rules and workflow automation.
They are included in iTop Products, but not in iTop Community. You may get them on iTop Hub.',
'Menu:Procedure' => 'Procedures catalog',
'Menu:Procedure+' => 'All procedures catalog',

View File

@@ -36,6 +36,16 @@ Dict::Add('FR FR', 'French', 'Français', [
'Menu:DeliveryModel+' => 'Modèles de support',
'Menu:ServiceFamily' => 'Familles de service',
'Menu:ServiceFamily+' => 'Familles de service',
'Menu:ServiceCatalog' => 'Catalogue de services',
'Menu:ServiceCatalog+' => '',
'UI-ServiceCatalogMenu-Title' => 'Catalogue de services',
'UI-ServiceCatalogMenu-RecentRequestByService' => 'Demandes récentes groupées par service',
'Menu:RulesAndWorkflow' => 'Règles d\'automatisation',
'Menu:RulesAndWorkflow+' => '',
'UI-RulesAndWorkflow-Title' => 'Règles d\'automatisation',
'UI-RulesAndWorkflow-Description' => 'De nombreuses extensions apportent des règles de notification et d\'automatisation du cycle de vie des tickets.
Elles sont incluses dans les produits Professionels, mais pas dans la version communautaire. Vous pouvez les obtenir sur iTop Hub.',
'Menu:Procedure' => 'Catalogue des procédures',
'Menu:Procedure+' => 'Catalogue des procédures',
'Contract:baseinfo' => 'Information générale',

View File

@@ -139,158 +139,6 @@ JSON;
$this->assertJsonStringEqualsJsonString($sExpectedJsonOuput, $sJSONOutput);
}
public function testCoreApiGet_Select2SubClasses(){
// Create ticket
$description = date('dmY H:i:s');
$iIdCaller = $this->CreatePerson(1)->GetKey();
$oUserRequest = $this->CreateSampleTicket($description, 'UserRequest', $iIdCaller);
$oChange = $this->CreateSampleTicket($description, 'Change', $iIdCaller);
$iIdUserRequest = $oUserRequest->GetKey();
$iIdChange = $oChange->GetKey();
$sJSONOutput = $this->CallCoreRestApi_Internally(<<<JSON
{
"operation": "core/get",
"class": "UserRequest, Change",
"key": "SELECT UserRequest WHERE id=$iIdUserRequest UNION SELECT Change WHERE id=$iIdChange",
"output_fields": "id, description, outage"
}
JSON);
$sExpectedJsonOuput = <<<JSON
{
"code": 0,
"message": "Found: 2",
"objects": {
"UserRequest::$iIdUserRequest": {
"class": "UserRequest",
"code": 0,
"fields": {
"description": "<p>$description</p>",
"id": "$iIdUserRequest"
},
"key": "$iIdUserRequest",
"message": ""
},
"Change::$iIdChange": {
"class": "Change",
"code": 0,
"fields": {
"description": "<p>$description</p>",
"id": "$iIdChange",
"outage": "no"
},
"key": "$iIdChange",
"message": ""
}
}
}
JSON;
$this->assertJsonStringEqualsJsonString($sExpectedJsonOuput, $sJSONOutput);
}
public function testCoreApiGet_SelectTicketAndPerson(){
// Create ticket
$description = date('dmY H:i:s');
$iIdCaller = $this->CreatePerson(1)->GetKey();
$oUserRequest = $this->CreateSampleTicket($description, 'UserRequest', $iIdCaller);
$iIdUserRequest = $oUserRequest->GetKey();
$sJSONOutput = $this->CallCoreRestApi_Internally(<<<JSON
{
"operation": "core/get",
"class": "UserRequest, Change",
"key": "SELECT UR, P FROM UserRequest AS UR JOIN Person AS P ON UR.caller_id = P.id WHERE UR.id=$iIdUserRequest ",
"output_fields": "id, title, description, name, email"
}
JSON);
$sExpectedJsonOuput = <<<JSON
{
"code": 0,
"message": "Found: 1",
"objects": [{
"UR": {
"class": "UserRequest",
"code": 0,
"fields": {
"description": "<p>$description</p>",
"id": "$iIdUserRequest",
"title": "Houston, got a problem"
},
"key": "$iIdUserRequest",
"message": ""
},
"P": {
"class": "Person",
"code": 0,
"fields": {
"email": "",
"id": "$iIdCaller",
"name": "Person_1"
},
"key": "$iIdCaller",
"message": ""
}
}]
}
JSON;
$this->assertJsonStringEqualsJsonString($sExpectedJsonOuput, $sJSONOutput);
}
public function testCoreApiGetWithUnionAndDifferentOutputFields(){
// Create ticket
$description = date('dmY H:i:s');
$oUserRequest = $this->CreateSampleTicket($description);
$oChange = $this->CreateSampleTicket($description, 'Change');
$iUserRequestId = $oUserRequest->GetKey();
$sUserRequestRef = $oUserRequest->Get('ref');
$iChangeId = $oChange->GetKey();
$sChangeRef = $oChange->Get('ref');
$sJSONOutput = $this->CallCoreRestApi_Internally(<<<JSON
{
"operation": "core/get",
"class": "Ticket",
"key": "SELECT UserRequest WHERE id=$iUserRequestId UNION SELECT Change WHERE id=$iChangeId",
"output_fields": "Ticket:ref;UserRequest:ref,status,origin;Change:ref,status,outage"
}
JSON);
$sExpectedJsonOuput = <<<JSON
{
"code": 0,
"message": "Found: 2",
"objects": {
"Change::$iChangeId": {
"class": "Change",
"code": 0,
"fields": {
"outage": "no",
"ref": "$sChangeRef",
"status": "new"
},
"key": "$iChangeId",
"message": ""
},
"UserRequest::$iUserRequestId": {
"class": "UserRequest",
"code": 0,
"fields": {
"origin": "phone",
"ref": "$sUserRequestRef",
"status": "new"
},
"key": "$iUserRequestId",
"message": ""
}
}
}
JSON;
$this->assertJsonStringEqualsJsonString($sExpectedJsonOuput, $sJSONOutput);
}
public function testCoreApiCreate()
{
// Create ticket
@@ -403,13 +251,12 @@ JSON;
//
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
private function CreateSampleTicket($description, $sType = 'UserRequest', $iIdCaller = null)
private function CreateSampleTicket($description)
{
$oTicket = $this->createObject($sType, [
$oTicket = $this->createObject('UserRequest', [
'org_id' => $this->getTestOrgId(),
"title" => "Houston, got a problem",
"description" => $description,
"caller_id" => $iIdCaller,
]);
return $oTicket;
}

View File

@@ -1,110 +0,0 @@
<?php
namespace Combodo\iTop\Test\UnitTest\Webservices;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use MetaModel;
use RestUtils;
use Ticket;
use UserRequest;
class RestUtilsTest extends ItopDataTestCase
{
public function testGetFieldListForSingleClass(): void
{
$aList = RestUtils::GetFieldList(Ticket::class, (object) ['output_fields' => 'ref,start_date,end_date'], 'output_fields');
$this->assertSame([Ticket::class => ['ref', 'start_date', 'end_date']], $aList);
}
public function testGetFieldListForSingleClassWithInvalidFieldNameFails(): void
{
$this->expectException(\Exception::class);
$this->expectExceptionMessage('output_fields: invalid attribute code \'something\' for class \'Ticket\'');
$aList = RestUtils::GetFieldList(Ticket::class, (object) ['output_fields' => 'ref,something'], 'output_fields');
$this->assertSame([Ticket::class => ['ref', 'start_date', 'end_date']], $aList);
}
public function testGetFieldListWithAsteriskOnParentClass(): void
{
$aList = RestUtils::GetFieldList(Ticket::class, (object) ['output_fields' => '*'], 'output_fields');
$this->assertArrayHasKey(Ticket::class, $aList);
$this->assertContains('operational_status', $aList[Ticket::class]);
$this->assertNotContains('status', $aList[Ticket::class], 'Representation of Class Ticket should not contain status, since it is defined by children');
}
public function testGetFieldListWithAsteriskPlusOnParentClass(): void
{
$aList = RestUtils::GetFieldList(Ticket::class, (object) ['output_fields' => '*+'], 'output_fields');
$this->assertArrayHasKey(Ticket::class, $aList);
$this->assertArrayHasKey(UserRequest::class, $aList);
$this->assertContains('operational_status', $aList[Ticket::class]);
$this->assertContains('status', $aList[UserRequest::class]);
}
public function testGetFieldListForMultipleClasses(): void
{
$aList = RestUtils::GetFieldList(Ticket::class, (object) ['output_fields' => 'Ticket:ref,start_date,end_date;UserRequest:ref,status'], 'output_fields');
$this->assertArrayHasKey(Ticket::class, $aList);
$this->assertArrayHasKey(UserRequest::class, $aList);
$this->assertContains('ref', $aList[Ticket::class]);
$this->assertContains('end_date', $aList[Ticket::class]);
$this->assertNotContains('status', $aList[Ticket::class]);
$this->assertContains('status', $aList[UserRequest::class]);
$this->assertNotContains('end_date', $aList[UserRequest::class]);
}
public function testGetFieldListForMultipleClassesWithInvalidFieldNameFails(): void
{
$this->expectException(\Exception::class);
$this->expectExceptionMessage('output_fields: invalid attribute code \'something\'');
RestUtils::GetFieldList(Ticket::class, (object) ['output_fields' => 'Ticket:ref;UserRequest:ref,something'], 'output_fields');
}
public function testGetFieldListForMultipleClassesWithInvalidFieldName(): void
{
$aList = RestUtils::GetFieldList(Ticket::class, (object) ['output_fields' => 'ref, something'], 'output_fields', false);
$this->assertContains('ref', $aList[Ticket::class]);
$this->assertNotContains('something', $aList[Ticket::class]);
}
/**
* @dataProvider extendedOutputDataProvider
*/
public function testIsExtendedOutputRequest(bool $bExpected, string $sFields): void
{
$this->assertSame($bExpected, RestUtils::HasRequestedExtendedOutput($sFields));
}
/**
* @dataProvider allFieldsOutputDataProvider
*/
public function testIsAllFieldsOutputRequest(bool $bExpected, string $sFields): void
{
$this->assertSame($bExpected, RestUtils::HasRequestedAllOutputFields($sFields));
}
public function extendedOutputDataProvider(): array
{
return [
[false, 'ref,start_date,end_date'],
[false, '*'],
[true, '*+'],
[false, 'Ticket:ref'],
[true, 'Ticket:ref;UserRequest:ref'],
];
}
public function allFieldsOutputDataProvider(): array
{
return [
[false, 'ref,start_date,end_date'],
[true, '*'],
[true, '*+'],
[false, 'Ticket:ref'],
[false, 'Ticket:ref;UserRequest:ref'],
];
}
}