Управление кнопками /добавление новых кнопок

Доброго времени суток!

  1. Какие настройки управляют показом тех или иных кнопок( например, “назначить” “переназначить”)

  2. Хочу нарисовать кнопку “взять в работу”. не смог разобраться, где лежит код уже существующих кнопок.

Настройки жизненного цикла объекта (<lifecycle>), роли и права пользователей.

iTop это своего рода фреймворк. Есть один код, который рисует абстрактные кнопки, есть другой код который вызывает первый с определенными параметрам и получает конкретные кнопки, есть третий код, который использует два предыдущих и размещает кнопки на странице. И всё это ещё зависит от ролей пользователя и жизненного цикла объекта.

Что должна делать кнопка “взять в работу”? То же что и “назначить”, но сразу на того, кто нажимает?

Вот ещё по кастомизации выпадающих меню:

  1. Adding new popup menu items in iTop [iTop Documentation]
  2. Extensions API [iTop Documentation]
  3. iTop extensions » \iPopupMenuExtension

С lifecycle вроде разобрался.
Не могу понять, где лежит код самой кнопки.

Да, кнопка “взять в работу” должна делать тоже что и “назначить”, но сразу на того, кто нажимает.

А можно ли настроить так, чтобы переназначить можно было только через кнопку, а сами поля отображались как “read only”?

Нет кода “самой кнопки”. Есть функции “рисования” кнопок, которые запускаются с разными параметрами для разных кнопок.
Если хочешь изменить надпись, сделай это через перевод.

Думаю, нужно делать новый стимул (что-то типа <stimulus id="ev_assign_to_me" xsi:type="StimulusUserAction"/>), править под него <lifecycle> и делать отдельный <method> для заполнения полей агент и команда. При этом в флагах <state id="assigned"> для всех атрибутов нужно убрать <must_prompt/>.

Дополнительно нужно предусмотреть вариант, когда пользователь не имеет права принимать тикет в работу. Как скрывать от него кнопку стандартными средствами, пока не знаю. Тут нужно проверять, есть ли он и его команда в списке доступных для конкретного тикета, а стандартное разделение прав работает на уровне классов КЕ, а не их содержимого.

Не совсем понял. Доступность полей тикета определяется флагами <flags> в <lifecycle> на уровне статусов <state>.

Владимир, может быть встречались с задачей: нужно написать функцию, которая вызывает страницу создания запроса. Подозреваю, что в itop уже есть такая функция. Как бы найти название этой функции.

Куда вызывает? Кнопку какую-то сделать хотите?

Идея такая- сделать stimuli, который создает дочерний инцидент и добавить свою обработку( например автозаполнение полей).

Кнопку я уже нарисовал. По нажатию кнопки будет вызываться мой метод - тоже сделал. В методе должен вызываться метод отрисовки окна создания дочернего инцидента.

Как на счет такого варианта:
/pages/UI.php?operation=new&class=UserRequest&c[menu]=NewUserRequest&default[org_id]=3&default[parent_request_id]=8 ?

Делаем обычную кнопку в меню Действия, используя iPopupMenuExtension (вот пример). В качестве одного из аргументов URLPopupMenuItem передаём вышеуказанную ссылку. В ссылке через параметры default[код_атрибута]=значение задаём значения по умолчанию для создаваемого тикета. Получаем кнопку, которая открывает форму создания тикета и заполняет нужные поля (в т.ч. parent_request_id). Никаких стимулов и переделки модели данных не требуется.

Владимир, может приходилось работать с параметром $oPage $oP?

Нашел несколько функций, которые отображают различный функционал на странице. Все они в качестве параметров требуют задания текущей страницы( $oPage $oP). Как мне в своей функции(методе) получить в переменную $oPage текущую страницу?

К сожалению, предложенная реализация с ссылкой не получилась:

$sStartPage = “./UI.php?operation=new&class=UserRequest&c[menu]=NewUserRequest&default[org_id]=3?”;
header(“Location: $sStartPage”);

Новый объект создается, а вот сделать привязку к дочернему не получилось(после создания нового родительского нужно обновить дочерний и отследить, что родительский создался)

хочу использовать кусок кода для своей обработки

	// ui.extkeywidget
	case 'objectCreationForm':
	$oPage->SetContentType('text/html');
	$sTargetClass = utils::ReadParam('sTargetClass', '', false, 'class');
	$iInputId = utils::ReadParam('iInputId', '');
	$sAttCode = utils::ReadParam('sAttCode', '');
	$oWidget = new UIExtKeyWidget($sTargetClass, $iInputId, $sAttCode, false);
	$sJson = utils::ReadParam('json', '', false, 'raw_data');
	if (!empty($sJson))
	{
		$oWizardHelper = WizardHelper::FromJSON($sJson);
		$oObj = $oWizardHelper->GetTargetObject();
	}
	else
	{
		// Search form: no current object
		$oObj = null;
	}
	$oWidget->GetObjectCreationForm($oPage, $oObj);
	break;

Так что вы все таки создаёте? Дочерний из родительского или родительский из дочернего?

В интерфейсах iApplicationUIExtension и iPageUIExtension есть методы, позволяющие взаимодействовать со страницей.

Посмотрите методы ResolveChildTickets и UpdateChildRequestLog в модели данных datamodel.itop-incident-mgmt-itil.xml. Оба метода изменяют дочерние тикеты.

От функционала создания дочерних объектов отказались, осталась задача создания родительских объектов из дочерних
Нужно на кнопку ev_my_new_chld_ur повесить функционал создания родительской проблемы(аналогично нажатию “+”) с возможностью автоматического предзаполнения полей.

Сложность в том, что header(“Location: $sStartPage”) перекидывает на новую страницу без возможности отследить был создан родительский объект или нет, а редактирование дочернего объекта прекращается.

о ResolveChildTickets думал, методы выполняются без интерфейса, надо придумать как подставлять id созданной родительской проблемы в дочерний инцидент и возвращаться в редактирование.

…попробую iPageUIExtension

Добрый день.
Требуется переместить в модуле request кнопку “назначить” из меню “другие действия” на место где кнопки: обновить, “новый”, “изменить”.
Подскажите в какую сторону копать, в каком модуле делать изменения?

Добрый день.
Т.к. я совсем не программист, но все равно интересует тема с JSON (https://www.itophub.io/wiki/page?id=2_4_0%3Aadvancedtopics%3Arest_json), в документации нет примера как это сделать, поэтому, если у кого найдется время прошу подсказать или привести свой пример.

Не понимаю, как передать в функции данные через json.
Добавил кнопочку (https://www.itophub.io/wiki/page?id=2_4_0%3Acustomization%3Aadd-menu-sample), в service request, кнопка Custom JS Function, при нажатии которой вызывается функция MyCustomJSFunction

// Sample JS function to be called from a custom menu item
function MyCustomJSFunction(sName)
{
 	// nothing fancy here, just popup an alert
	alert(sName);
 }

Как мне в этой функции передать ну вот эти параметры к примеру?

{
   "operation": "core/apply_stimulus",
   "comment": "Synchronization from blah...",
   "class": "UserRequest",
   "key": 15,
   "stimulus": "ev_assign",
   "output_fields": "friendlyname, title, status, contact_list",
   "fields":
   {
      "team_id": 18,
      "agent_id": 57
   }
}

Отвечаю сам себе:
Взял за основу кнопку из примера: https://www.itophub.io/wiki/page?id=2_4_0%3Acustomization%3Aadd-menu-sample и код с примера json отсюда:https://www.itophub.io/wiki/page?id=2_4_0%3Aadvancedtopics%3Arest_json_playground

Получилось сделать кнопку “взять в работу”, но которая назначает на определенного агента.

Кусок кода из
main.assign-to-me.php:

			case iPopupMenuExtension::MENU_OBJDETAILS_ACTIONS:
			// Only for UserRequest
			if ($param instanceof UserRequest)
			{
				// add a separator
				$aResult[] = new SeparatorPopupMenuItem(); // Note: separator does not work in iTop 2.0 due to Trac #698, fixed in 2.0.1
				
				// Add a new menu item that triggers a custom JS function defined in our own javascript file: js/sample.js
				$sModuleDir = basename(dirname(__FILE__));
				$sJSFileUrl = utils::GetAbsoluteUrlModulesRoot().$sModuleDir.'/js/assign-to-me.js';
				$aResult[] = new JSPopupMenuItem('_Custom_JS_', 'Assign to me...', "AssignToMe('".addslashes($param->GetName())."')", array($sJSFileUrl));
			}
			break;

И код скрипта:

function DoXSS(oJSON)
{
	var sURL = "http://<директория itop>/itop-beta/webservices/rest.php?version=1.3";
	var sDataType = 'json';

    $.ajax({
        type: "POST",
        url: sURL,
        dataType: sDataType,
		data: { json_data: JSON.stringify(oJSON) },
    });
  location.reload(); 
	return false;
}

function AssignToMe(sName)
{
	var Ticket = sName;
	var oJSON = {
	 "operation": "core/apply_stimulus",
   "comment": "Assign process...",
   "key": "SELECT UserRequest WHERE ref LIKE \"" + Ticket + "\"",
   "class": "UserRequest",
   "stimulus": "ev_assign",
   "fields":
   {
      "team_id": 728,
      "agent_id": 3
   }

	};

	DoXSS(oJSON);
}

Код может быть кривой или где-то неправильный, но тут как смог.
Теперь вопрос, каким образом определять текущего агента, т.е. agent_id?

А ну да, ведь так работает:

"agent_id": "SELECT Person WHERE id = :current_contact_id"

C team_id можно сделать тоже самое, только тогда агент должен быть только в одной команде…

Т.е. fields будут выглядеть так:

   "fields":
   {
      "team_id": "SELECT Team AS t JOIN lnkPersonToTeam AS lnk ON lnk.team_id = t.id WHERE lnk.person_id = :current_contact_id",
      "agent_id": "SELECT Person WHERE id = :current_contact_id"
   }

Теперь у меня загвостка, как поднять эту кнопку повыше и главное, сделать ее видимой, только, если userrequest в state new?
Или я вобще пошел не по тому пути создания кнопки…

Все никак не могу победить кнопку “взять в работу”, которая бы назначала на текущего агента.
Сделал в xml:

<?xml version="1.0" encoding="UTF-8"?>
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.1">
  <classes>
    <class id="UserRequest" _delta="must_exist">
      <fields>
        <field id="status" xsi:type="AttributeEnum" _delta="redefine">
          <always_load_in_tables>true</always_load_in_tables>
          <values>
            <value id="new">new</value>
            <value id="waiting_for_approval">waiting_for_approval</value>
            <value id="approved">approved</value>
            <value id="rejected">rejected</value>
            <value id="assigned_to_me">assigned_to_me</value>
            <value id="assigned">assigned</value>
            <value id="pending">pending</value>
            <value id="escalated_tto">escalated_tto</value>
            <value id="escalated_ttr">escalated_ttr</value>
            <value id="resolved">resolved</value>
            <value id="closed">closed</value>
          </values>
          <sql>status</sql>
          <default_value>new</default_value>
          <is_null_allowed>false</is_null_allowed>
        </field>
      </fields>
      <lifecycle>
        <stimuli>
          <stimulus id="ev_assign_to_me" xsi:type="StimulusUserAction" _delta="define"/>
        </stimuli>
        <states>
          <state id="new" _delta="must_exist">
            <transitions>
              <transition id="ev_assign_to_me" _delta="define">
                <target>assigned_to_me</target>
               <actions>
                  <action>
                    <verb>AssignMe</verb>
                  </action>
                </actions>
              </transition>
            </transitions>
          </state>



          <state id="assigned_to_me" _delta="define">
            <inherit_flags_from>new</inherit_flags_from>
            <flags>
              <attribute id="team_id">
                <read_only/>
              </attribute>
              <attribute id="agent_id">
                <read_only/>
              </attribute>
            </flags>
            <transitions>
              <transition id="ev_pending">
                <target>pending</target>
                <actions>
                  <action>
                    <verb>SetCurrentDate</verb>
                    <params>
                      <param xsi:type="attcode">last_pending_date</param>
                    </params>
                  </action>
                </actions>
              </transition>
              <transition id="ev_resolve">
                <target>resolved</target>
                <actions>
                  <action>
                    <verb>SetCurrentDate</verb>
                    <params>
                      <param xsi:type="attcode">resolution_date</param>
                    </params>
                  </action>
                  <action>
                    <verb>SetElapsedTime</verb>
                    <params>
                      <param xsi:type="attcode">time_spent</param>
                      <param xsi:type="attcode">start_date</param>
                      <param xsi:type="string">DefaultWorkingTimeComputer</param>
                    </params>
                  </action>
                  <action>
                    <verb>ResolveChildTickets</verb>
                    <params/>
                  </action>
                </actions>
              </transition>
              <transition id="ev_reassign">
                <target>assigned</target>
                <actions/>
				<flags>
					<attribute id="agent_id">
						<must_change/>
					</attribute>
					<attribute id="team_id">
						<must_prompt/>
					</attribute>
				</flags>
              </transition>
              <transition id="ev_timeout">
                <target>escalated_ttr</target>
                <actions/>
              </transition>
              <transition id="ev_autoresolve">
                <target>resolved</target>
                <actions>
                  <action>
                    <verb>SetCurrentDate</verb>
                    <params>
                      <param xsi:type="attcode">resolution_date</param>
                    </params>
                  </action>
                  <action>
                    <verb>SetElapsedTime</verb>
                    <params>
                      <param xsi:type="attcode">time_spent</param>
                      <param xsi:type="attcode">start_date</param>
                      <param xsi:type="string">DefaultWorkingTimeComputer</param>
                    </params>
                  </action>
                  <action>
                    <verb>ResolveChildTickets</verb>
                    <params/>
                  </action>
                </actions>
              </transition>
            </transitions>
          </state>




        </states>
      </lifecycle>
      <methods>
        <method id="AssignMe" _delta="define">
          <comment><![CDATA[/**
	 * To be deprecated: use Reset(agent_id) instead
	 */]]></comment>
          <static>false</static>
          <access>public</access>
          <type>LifecycleAction</type>
          <code><![CDATA[	public function AssignMe($sStimulusCode)
	{     
        $this->set('team_id', 728);
				$this->set('agent_id', 3);
	}]]></code>
        </method>
      </methods>
    </class>
  </classes>
  <menus>
  </menus>
  <user_rights>
    <profiles>
      <profile id="4" _delta="must_exist">
        <groups>
          <group id="UserRequest" _delta="must_exist">
            <actions>
              <action id="stimulus:ev_assign_to_me" _delta="define">allow</action>
            </actions>
          </group>
        </groups>
      </profile>
      <profile id="5" _delta="must_exist">
        <groups>
          <group id="UserRequest" _delta="must_exist">
            <actions>
              <action id="stimulus:ev_assign_to_me" _delta="define">allow</action>
            </actions>
          </group>
        </groups>
      </profile>
    </profiles>
  </user_rights>
</itop_design>

Добавил новый статус, новый стимулус, метод.

  1. Не могу понять в методе, как назначать на текущего агента, пытался запихнуть запрос в переменную (например $agentname = “SELECT Person WHERE id = :current_contact_id”) здесь $this->set(‘agent_id’, $agentname), но ощущение, что полученный $agentname нужно перевести в id, а может и не получается получить текущий контакт, не знаю.
  2. Также приходится дважды нажимать эту новую кнопку, также как и все остальные и как же это обойти?

Нашел, что текущего пользователя в method можно определять так:
$iContactId = UserRights::GetContactId();
И затем указать:
$this->Set('agent_id',$iContactId);

Но вот как определять team_id?

Выбрать все lnkPersonToTeam, где person_id = $iContactId. $lnkPersonToTeam->Get('team_id') будет содержать id команды.