mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-19 10:24:12 +01:00
Compare commits
1 Commits
feature/re
...
feature/39
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6e43d01475 |
@@ -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
|
||||
*
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
//
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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'],
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user