tapis_job-1.4.1-alpha1/tapis_job.module

tapis_job.module
<?php

/**
 * @file
 * Provides a job entity type.
 */

use Drupal\Core\Cache\RefinableCacheableDependencyInterface;
use Drupal\Core\Entity\Display\EntityViewDisplayInterface;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Link;
use Drupal\Core\Render\Element;
use Drupal\Core\Routing\RouteMatchInterface;
use Drupal\Core\Url;
use Drupal\node\Entity\Node;
use Drupal\node\NodeInterface;
use Drupal\tapis_app\DrupalIds as AppDrupalIds;
use Drupal\tapis_job\Entity\JobAccessLink;
use Drupal\tapis_job\Entity\TapisJob;
use Drupal\Core\Form\FormStateInterface;
use Drupal\tapis_job\TapisJobInterface;
use Drupal\user\UserInterface;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
use Drupal\tapis_system\DrupalIds as SystemDrupalIds;

/**
 * Implements hook_help().
 */
function tapis_job_help($route_name, RouteMatchInterface $route_match) {
  switch ($route_name) {
    // Main module help for the tapis_job module.
    case 'help.page.tapis_job':
      return '<p>' . t('This module integrates Tapis jobs within Drupal using the Tapis job entity. This module lets users launch Tapis apps as jobs via a form in Drupal, and also provides a job details page outlining the job status & other metadata. Furthermore, this module can also be configured to enable access links for web & VNC apps, which will let users share their app sessions with others by creating & managing access links.') . '</p>';
  }
}

/**
 * Implements hook_theme().
 */
function tapis_job_theme(): array {
  return ['tapis_job' => ['render element' => 'elements']];
}

/**
 * Prepares variables for job templates.
 *
 * Default template: tapis-job.html.twig.
 *
 * @param array $variables
 *   An associative array containing:
 *   - elements: An associative array containing the job information and any
 *     fields attached to the entity.
 *   - attributes: HTML attributes for the containing element.
 */
function template_preprocess_tapis_job(array &$variables): void {
  $variables['view_mode'] = $variables['elements']['#view_mode'];
  foreach (Element::children($variables['elements']) as $key) {
    $variables['content'][$key] = $variables['elements'][$key];
  }
}

/**
 *  Implements hook_entity_type_update().
 *
 *  Whenever an app's input type is changed to "flexible_parameters", we need to add our webform handler to the app's webform.
 *
 * @param \Drupal\node\NodeInterface $entity
 *
 * @return void
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 * @throws \Drupal\Core\Entity\EntityStorageException
 * @throws \Drupal\Core\TypedData\Exception\MissingDataException
 */
function tapis_job_node_update(NodeInterface $entity) {
  if ($entity->bundle() !== AppDrupalIds::NODE_BUNDLE_APP) {
    return;
  }

  // Get the app's input type.
  $inputType = $entity->get(AppDrupalIds::APP_INPUT_TYPE)->first()->getValue()['value'];

  // Check if it's equal to "flexible_parameters".
  if ($inputType === "flexible_parameters") {
    // Get the app's web form.
    $webForm_id = $entity->get(AppDrupalIds::APP_WEBFORM)->first()->getValue()['target_id'];
    $webForm = \Drupal::entityTypeManager()->getStorage('webform')->load($webForm_id);

    // Must set original id so that the webform can be resaved.
    $webForm->setOriginalId($webForm->id());

    // Add our handler.
    $handler_manager = \Drupal::service('plugin.manager.webform.handler');

    // Create webform handler.
    $handler_configuration = [
      'id' => 'tapis_job_osp_webform_launch_app',
      'label' => 'Launch an app (OSP)',
      'handler_id' => 'tapis_job_osp_webform_launch_app_',
      'status' => TRUE,
      'weight' => 0,
      'settings' => [],
    ];
    $handler = $handler_manager->createInstance('tapis_job_osp_webform_launch_app', $handler_configuration);

    // Add webform handler which triggers Webform::save().
    $webForm->addWebformHandler($handler);

    // Create dry-run webform handler.
    $handler_configuration = [
      'id' => 'tapis_job_osp_webform_dry_run_app',
      'label' => 'Dry run app - THIS DOES NOT SUBMIT THE JOB (OSP)',
      'handler_id' => 'tapis_job_osp_webform_dry_run_app_',
      'status' => FALSE,
      'weight' => 1,
      'settings' => [],
    ];
    $handler = $handler_manager->createInstance('tapis_job_osp_webform_dry_run_app', $handler_configuration);

    // Add webform handler which triggers Webform::save().
    $webForm->addWebformHandler($handler);

    // Do a final save for the web form.
    $webForm->save();
  }
}

/**
 *  Implements hook_entity_type_insert().
 *
 *  Whenever a new app's input type is set to "flexible_parameters", we need to add our webform handler to the app's webform.
 *
 * @param \Drupal\node\NodeInterface $entity
 *
 * @return void
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 * @throws \Drupal\Core\Entity\EntityStorageException
 * @throws \Drupal\Core\TypedData\Exception\MissingDataException
 */
function tapis_job_node_insert(NodeInterface $entity): void {
  // we're adding the app's webform handler in this module and not tapis_app,
  // because this module is the one that defines & implements the webform submission handler.
  if ($entity->bundle() !== AppDrupalIds::NODE_BUNDLE_APP) {
    return;
  }

  // Get the app's input type.
  $inputType = $entity->get(AppDrupalIds::APP_INPUT_TYPE)->first()->getValue()['value'];

  // Check if it's equal to "flexible_parameters".
  if ($inputType === "flexible_parameters") {
    // Get the app's web form.
    $webForm_id = $entity->get(AppDrupalIds::APP_WEBFORM)->first()->getValue()['target_id'];
    $webForm = \Drupal::entityTypeManager()->getStorage('webform')->load($webForm_id);
    // Must set original id so that the webform can be resaved.
    $webForm->setOriginalId($webForm->id());

    // Add our handler.
    $handler_manager = \Drupal::service('plugin.manager.webform.handler');

    // Create webform handler.
    $handler_configuration = [
      'id' => 'tapis_job_osp_webform_launch_app',
      'label' => 'Launch an app (OSP)',
      'handler_id' => 'tapis_job_osp_webform_launch_app_',
      'status' => TRUE,
      'weight' => 0,
      'settings' => [],
    ];
    $handler = $handler_manager->createInstance('tapis_job_osp_webform_launch_app', $handler_configuration);
    // Add webform handler which triggers Webform::save().
    $webForm->addWebformHandler($handler);

    // Create dry-run webform handler.
    $handler_configuration = [
      'id' => 'tapis_job_osp_webform_dry_run_app',
      'label' => 'Dry run app - THIS DOES NOT SUBMIT THE JOB (OSP)',
      'handler_id' => 'tapis_job_osp_webform_dry_run_app_',
      'status' => FALSE,
      'weight' => 1,
      'settings' => [],
    ];
    $handler = $handler_manager->createInstance('tapis_job_osp_webform_dry_run_app', $handler_configuration);

    // Add webform handler which triggers Webform::save().
    $webForm->addWebformHandler($handler);

    // Do a final save for the web form.
    $webForm->save();
  }
}

/**
 * Implements hook_user_cancel().
 *
 * When a user is deleted, we need to delete all of their jobs.
 *
 * @param $edit
 * @param \Drupal\user\UserInterface $account
 * @param $method
 *
 * @return void
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 * @throws \Drupal\Core\Entity\EntityStorageException
 */
function tapis_job_user_cancel($edit, UserInterface $account, $method): void {
  switch ($method) {
    case 'user_cancel_reassign':
      // Delete jobs.
      $storage = Drupal::entityTypeManager()->getStorage('tapis_job');
      $tapis_job_ids = $storage->getQuery()->condition('uid', $account->id())->accessCheck(FALSE)->execute();
      foreach ($storage->loadMultiple($tapis_job_ids) as $tapis_job) {
        $tapis_job->delete();
      }
      break;
  }
}

/**
 * Implements hook_entity_type_presave().
 *
 * Whenever a job access link is created, we need to redeem its token in Satellite.
 * Once the token is redeemed in Satellite, the user will be able to access the job's interactive app via the access link.
 *
 * @param \Drupal\tapis_job\Entity\JobAccessLink $jobAccessLink
 */
function tapis_job_job_access_link_presave(JobAccessLink $jobAccessLink): void {
  if ($jobAccessLink->original) {
    // This is NOT a new access link, so ignore it.
    return;
  }

  $proxyId = $jobAccessLink->getProxyId();
  if ($proxyId) {
    $tapisJobProvider = Drupal::service("tapis_job.tapis_job_provider");
    $job = $jobAccessLink->getJob();
    $jobUuid = $job->getTapisUUID();

    $jobProxyId = $job->getProxyId();
    $token = $jobAccessLink->getProxyId();

    $tenantId = $job->getTenantId();

    if ($jobProxyId !== $token) {
      // If the job's proxy ID matches the token, that means that this is the first token that we launched the job with.
      // We don't have to explicitly redeem this token, since the revssh-client will auto-redeem it when its launched.
      // Therefore, we only need to redeem the token if the job's proxy ID does NOT match the token.
      // NOTE: If we change satellite's token format, we'll need to consider cases where the job's original proxy id is deleted and then happens to be recreated with the same value by the user.
      // Get the uid of the job's owner.
      $jobOwnerUid = $job->getOwnerId();

      // Get the owner uid of the job's system.
      $systemOwnerUid = $job->getSystem()->getOwnerId();
      // Redeem the token.
      $tapisJobProvider->redeemSatelliteToken($tenantId, $jobUuid, $token, $jobOwnerUid, $systemOwnerUid);
    }
  }
}

/**
 * Implements hook_entity_type_predelete().
 *
 * Whenever a job access link is deleted, we need to delete its token in Satellite.
 *
 * @param \Drupal\tapis_job\Entity\JobAccessLink $jobAccessLink
 */
function tapis_job_job_access_link_predelete(JobAccessLink $jobAccessLink): void {
  $token = $jobAccessLink->getProxyId();
  $job = $jobAccessLink->getJob();
  $jobUuid = $job->getTapisUUID();
  $tenantId = $job->getTenantId();
  $jobOwnerId = $job->getOwnerId();

  if ($token) {
    $tapisJobProvider = Drupal::service("tapis_job.tapis_job_provider");
    $systemOwnerUid = $job->getSystem()->getOwnerId();
    $tapisJobProvider->deleteSatelliteToken($tenantId, $token, $jobUuid, $jobOwnerId, $systemOwnerUid);
  }
}

/**
 * Implements hook_node_view().
 *
 * This hook is used to add a "Launch app" button to the app's node view page.
 *
 * @param array $build
 * @param \Drupal\Core\Entity\EntityInterface $entity
 * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
 * @param string $view_mode
 */
function tapis_job_node_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode): void {
  if ($entity->bundle() !== AppDrupalIds::NODE_BUNDLE_APP) {
    return;
  }

  // Add a button called "Launch app" that links to the app's launch page.
  if (\Drupal::currentUser()->hasPermission("create job")) {
    $build['launch_app'] = [
      '#type' => 'link',
      '#title' => t('Launch app'),
      '#url' => Url::fromRoute('entity.tapis_job.app_launch_form', ['node' => $entity->id()]),
      '#attributes' => [
        'class' => ['button'],
      ],
      '#weight' => 1000,
    ];
  }
}

/**
 * Implements hook_entity_type_predelete().
 *
 * Whenever a job is deleted, we need to delete its access links and cancel it in Tapis.
 *
 * @param \Drupal\tapis_job\TapisJobInterface $job
 */
function tapis_job_tapis_job_predelete(TapisJobInterface $job): void {
  $tenantId = $job->getTenantId();

  /** @var Drupal\tapis_job\TapisProvider\TapisJobProviderInterface $tapisJobProvider */
  $tapisJobProvider = Drupal::service("tapis_job.tapis_job_provider");
  $tapisJobProvider->deleteAllAccessLinksForJob($job->id());
  $tapisJobProvider->cancelJob($tenantId, $job->getTapisUUID());
}

/**
 * Implements hook_ENTITY_TYPE_predelete() for user entities.
 *
 * @param \Drupal\user\UserInterface $account
 *
 * @return void
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 * @throws \Drupal\Core\Entity\EntityStorageException
 */
function tapis_job_user_predelete(UserInterface $account): void {
  // Delete jobs owned by the user.
  $storage = Drupal::entityTypeManager()->getStorage('tapis_job');
  $tapis_job_ids = $storage->getQuery()->accessCheck(FALSE)->condition('uid', $account->id())->execute();
  $tapis_jobs = $storage->loadMultiple($tapis_job_ids);
  $storage->delete($tapis_jobs);
}

/**
 * Implements hook_ENTITY_TYPE_view().
 *
 * This hook is used to display a message on the job's view page if the job's tenant is in maintenance mode.
 * Otherwise, the job's view page is altered by the TapisJob class, which will display the job's status & other metadata.
 *
 * @param array $build
 * @param \Drupal\Core\Entity\EntityInterface $entity
 * @param \Drupal\Core\Entity\Display\EntityViewDisplayInterface $display
 * @param string $view_mode
 */
function tapis_job_tapis_job_view(array &$build, EntityInterface $entity, EntityViewDisplayInterface $display, $view_mode) {
  /** @var Drupal\tapis_tenant\TapisProvider\TapisSiteTenantProviderInterface $tapisSiteTenantProvider */
  $tapisSiteTenantProvider = \Drupal::service("tapis_tenant.tapis_site_tenant_provider");
  $tapisSiteTenantProvider->setIgnoreMaintenanceMode(TRUE);

  // Get the job's tenant id.
  /** @var \Drupal\tapis_job\TapisJobInterface $entity */
  $tenantId = $entity->getTenantId();

  // Check if the tenant is in maintenance mode.
  if ($tapisSiteTenantProvider->isTenantInMaintenanceMode($tenantId)) {
    \Drupal::messenger()->addMessage(t("This resource is currently in maintenance mode."));
  }

  $currentUserId = Drupal::currentUser()->id();
  $jobOwnerId = $entity->getOwnerId();
  // Get details from Tapis API for this job.
  /** @var \Drupal\tapis_job\TapisProvider\TapisJobProviderInterface $tapisJobProvider */
  $tapisJobProvider = \Drupal::service("tapis_job.tapis_job_provider");
  $tapisJob = $tapisJobProvider->getJob($tenantId, $entity->getTapisUUID(), $jobOwnerId);

  $jobStatus = $tapisJob['status'];
  // Check if cancellation was requested via the state flag.
  // Persist a cancellation flag using the state API.
  $tenantInfo = $tapisSiteTenantProvider->getTenantInfo($tenantId);
  $tapisTenantId = $tenantInfo['tapis_id'];
  $terminatingId = $tapisTenantId . '.' . $currentUserId . '.tapis_job.' . $entity->id() . '.terminating';

  if (\Drupal::state()->get($terminatingId, FALSE)) {
    // If the API hasn't reported a terminal state yet, force "Terminating".
    $terminalStatuses = ['FINISHED', 'CANCELLED', 'FAILED'];
    if (!in_array(strtoupper($jobStatus), $terminalStatuses)) {
      $jobStatus = 'TERMINATING';
    }
    else {
      // Once a terminal status is reached, clear the flag.
      \Drupal::state()->delete($terminatingId);
    }
  }

  $lowercaseJobStatus = strtolower($jobStatus);
  $capitalizedJobStatus = str_replace('_', ' ', ucfirst($lowercaseJobStatus));
  $entity->setStatus($capitalizedJobStatus);
  $entity->save();
  // Clear the cache so that subsequent loads fetch the updated entity.
  \Drupal::entityTypeManager()->getStorage('tapis_job')->resetCache([$entity->id()]);

  if (isset($build['tapisStatus'])) {
    unset($build['tapisStatus']);
  }
  if (isset($build['tapisCondition'])) {
    unset($build['tapisCondition']);
  }

  $terminateJobStatuses = ["FINISHED", "CANCELLED", "FAILED", "TERMINATING"];
  $currentJobStatus = $entity->getStatus();

  if (empty($currentJobStatus) || !in_array(strtoupper($currentJobStatus), $terminateJobStatuses)) {
    $jobCancelURL = Url::fromRoute('entity.tapis_job.cancel_job', ['tapis_job' => $entity->id()]);
    $link = new Link('Terminate Job', $jobCancelURL);
    $build['tapis_job_terminate'] = $link->toRenderable();
    $build['tapis_job_terminate']['#attributes'] = [
      'class' => [
        'button',
        'job--terminate--action',
      ],
      'onclick' => 'return confirm("Would you like to terminate this job?");',
    ];
    $build['tapis_job_terminate']['#weight'] = 100;
  }

  // Get the form builder service.
  $formBuilder = \Drupal::service('form_builder');
  $form = $formBuilder->getForm('Drupal\tapis_job\Form\JobStatusRefreshForm', $entity);

  $build['job_status_refresh_form'] = $form;
  $build['job_status_refresh_form']['#weight'] = 0;

}

/**
 * Implements hook_ENTITY_TYPE_presave() for tapis_job entities.
 *
 * This hook is used to submit a job to Tapis when a new job is created.
 * It will also subscribe to the job's events, so that Tapis sends us webhooks when the job's status changes.
 *
 * @param \Drupal\tapis_job\TapisJobInterface $entity
 */
function tapis_job_tapis_job_presave(EntityInterface $entity): void {
  /** @var Drupal\tapis_job\TapisProvider\TapisJobProviderInterface $tapisJobProvider */
  $tapisJobProvider = Drupal::service("tapis_job.tapis_job_provider");
  $tenantId = $entity->getTenantId();

  if (!$entity->original) {
    // This is a new Tapis job.
    // First, verify that the job owner has view access to the system that the job will run on.
    // @todo Abstract this logic into a separate class/service just for access control checking.
    $jobOwner = $entity->getOwner();

    $system_id = $entity->getSystemId();
    $system = \Drupal::entityTypeManager()->getStorage('node')->load($system_id);
    if (!$system->access("view", $jobOwner)) {
      throw new AccessDeniedHttpException();
    }

    // Update Tapis via API call.
    $tapisDefinition = $entity->getTapisDefinition();
    $tapisJob = $tapisJobProvider->submitJob($tenantId, $tapisDefinition, $jobOwner->id());
    $entity->setTapisId($tapisJob['id']);
    $entity->setTapisUUID($tapisJob['uuid']);

    // Subscribe to the job's events so that we can get webhooks when the job's status changes
    // the reason we have this as a separate call, instead of just including the subscription within the submitJob request,
    // is because the jwt token we inculde within the webhook url query params for security depends on the job's uuid.
    $tapisJobProvider->subscribeToJobEvents($tenantId, $tapisJob['uuid'], $jobOwner->id());
  }
  else {
    // This is an existing Tapis job.
    // We don't do anything here.
  }
}

/**
 * Implements hook_ENTITY_TYPE_insert() for tapis_job entities.
 *
 * This hook is used to create a default initial access link for the job if the job's app is a web or VNC app.
 *
 * @param \Drupal\tapis_job\TapisJobInterface $entity
 */
function tapis_job_tapis_job_insert(EntityInterface $entity): void {
  $tenantId = $entity->getTenantId();
  $app = $entity->getApp();
  $proxyId = $entity->getProxyId();
  $jobOwnerId = $entity->getOwnerId();

  $appType = $app->get(AppDrupalIds::APP_TYPE)->getValue()[0]['value'];

  if ($appType === "web" || $appType === "vnc") {
    /** @var Drupal\tapis_job\TapisProvider\TapisJobProviderInterface $tapisJobProvider */
    $tapisJobProvider = Drupal::service("tapis_job.tapis_job_provider");
    /** @var Drupal\tapis_job\Entity\TapisJob $entity */
    $tapisJobProvider->createAccessLink($tenantId, $entity, $proxyId, $jobOwnerId);
  }
}

/**
 * Retrieve the value associated with the specified key in the form.
 *
 * @param array $form
 * @param $key
 *
 * @return mixed|null
 */
function tapis_job_find_webform_element_with_key(array &$form, $key) {
  if (isset($form[$key])) {
    return $form[$key];
  }
  foreach ($form as $elem_key => $elem_value) {
    if ($key === $elem_key) {
      return $elem_value;
    }
    if (is_array($elem_value)) {
      $result = tapis_job_find_webform_element_with_key($elem_value, $key);
      if ($result !== NULL) {
        return $result;
      }
    }
  }
  return NULL;
}

/**
 * Trigger the 'callback' event within the alterElement function of the launch app webform handler
 *  to update the batch logical queue for the tapis system
 *
 * @param $form
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *
 * @return mixed|null
 */
function osp_app_job_webform_refreshBatchLogicalQueue($form, FormStateInterface $form_state): mixed {
  return tapis_job_find_webform_element_with_key($form['elements'], 'osp_app_job_batch_logical_queue_id');
}

/**
 * Trigger the '#after_build' event within the alterElement function of the launch app webform handler
 * to update the batch logical queue.
 *
 * @param array $element
 * @param \Drupal\Core\Form\FormStateInterface $form_state
 *
 * @return array
 * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
 * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
 */
function osp_app_job_batch_logical_queue_id_afterBuild(array $element, FormStateInterface $form_state): array {
  $selected = $form_state->getValues();

  $is_restart_form = $form_state->get("is_restart_form") ?? FALSE;
  $tapis_job_id = $form_state->get("tapis_job_id") ?? NULL;

  if ($is_restart_form && $tapis_job_id) {
    /** @var Drupal\tapis_job\TapisJobInterface $tapisJob */
    $tapisJob = \Drupal::entityTypeManager()->getStorage('tapis_job')->load($tapis_job_id);
    $osp_app_job_system_id = $tapisJob->getSystemId();
  }
  else {
    $osp_app_job_system_id = $selected['osp_app_job_system_id'];
  }

  // Get the node entity with id $osp_app_job_system_id.
  $node = \Drupal::entityTypeManager()->getStorage('node')->load($osp_app_job_system_id);

  // Get the system's batch logical queues (entity reference with cardinality unlimited)
  // $default_system_blq_name = $node->get(SystemDrupalIds::SYSTEM_DEFAULT_BATCH_LOGICAL_QUEUE)->getValue()[0]['value'];
  $default_batch_logical_queue_id = NULL;
  if ($node) {
    if (isset($node->get(SystemDrupalIds::SYSTEM_DEFAULT_BATCH_LOGICAL_QUEUE)
        ->getValue()[0]['value'])) {
      $default_system_blq_name = $node->get(SystemDrupalIds::SYSTEM_DEFAULT_BATCH_LOGICAL_QUEUE)
        ->getValue()[0]['value'];
    }
    else {
      $default_system_blq_name = NULL;
    }
    $batch_logical_queues = $node->get(SystemDrupalIds::SYSTEM_BATCH_LOGICAL_QUEUES)
      ->referencedEntities() ?? [];
    $element['#options'] = [];
    foreach ($batch_logical_queues as $batch_logical_queue) {
      $element['#options'][$batch_logical_queue->id()] = $batch_logical_queue->label();
      if ($batch_logical_queue->label() == $default_system_blq_name) {
        $default_batch_logical_queue_id = $batch_logical_queue->id();
      }
    }
  }

  // Everytime the user changes the webform system option,
  // we set the value of the blq to the default blq for that system
  // note: we set #value and not #default_value because the form has already been rendered
  // and #default_value only applies when rendering the form for the first time.
  $element['#value'] = $default_batch_logical_queue_id;
  return $element;
}

/**
 * Implements hook_menu_local_tasks_alter().
 *
 * This hook is used to disable the app launch form for non-app node bundles.
 * Also, if the app is a batch app or access links are disbaled for all apps, then the job's access links form is disabled.
 *
 * @param array $data
 * @param string $route_name
 * @param \Drupal\Core\Cache\RefinableCacheableDependencyInterface $cacheability
 */
function tapis_job_menu_local_tasks_alter(&$data, $route_name, RefinableCacheableDependencyInterface &$cacheability) {
  // Disable the app launch form for non-app node bundles.
  /** @var \Drupal\node\Entity\Node $node */
  $node = \Drupal::routeMatch()->getParameter('node');
  $tapis_job = \Drupal::routeMatch()->getParameter('tapis_job');

  if ($node !== NULL && !($node instanceof Node)) {
    // Load the node using $node as the nid.
    $node = Node::load($node);
  }

  if ($tapis_job !== NULL && !($tapis_job instanceof TapisJob)) {
    // Load the node using $node as the nid.
    $tapis_job = TapisJob::load($tapis_job);
  }

  // If the node is not an app, then remove the app launch tab.
  if (!($node instanceof NodeInterface) || $node->bundle() !== AppDrupalIds::NODE_BUNDLE_APP) {
    unset($data['tabs'][0]['entity.tapis_job.app_launch_form']);
  }

  // Disable the access links form if access links are disabled OR if the app's type is batch.
  $removedAccessLinksForm = FALSE;

  $config = \Drupal::config('tapis_job.config');
  $enabled_access_links = $config->get("enabled_access_links");
  if (!$enabled_access_links) {
    unset($data['tabs'][0]['entity.tapis_job.job_access_links_form']);
    $removedAccessLinksForm = TRUE;
  }

  if (!$removedAccessLinksForm) {
    // Remove the access links form if the app is a batch app.
    if ($tapis_job !== NULL && $tapis_job->getApp() !== NULL) {
      $appType = $tapis_job->getApp()->get(AppDrupalIds::APP_TYPE)->getValue()[0]['value'];
      if ($appType === "batch") {
        unset($data['tabs'][0]['entity.tapis_job.job_access_links_form']);
      }
    }
  }

  // If the job's app isn't restartable or the job isn't in a terminal state, remove the job restart form.
  if ($tapis_job) {
    $app = $tapis_job->getApp();
    if (!empty($app)) {
      if (isset($app->get(AppDrupalIds::APP_RESTARTABLE)
        ->getValue()[0]['value'])) {
        $is_app_restartable = boolval($app->get(AppDrupalIds::APP_RESTARTABLE)
          ->getValue()[0]['value']);
      }
      else {
        $is_app_restartable = FALSE;
      }
    }
    else {
      $is_app_restartable = FALSE;
    }
    $job_status = $tapis_job->getStatus();
    $is_terminal_job_status = $job_status === "FINISHED" || $job_status === "CANCELLED" || $job_status === "FAILED";
    if (!$is_app_restartable || !$is_terminal_job_status) {
      unset($data['tabs'][0]['entity.tapis_job.restart_form']);
    }
  }

  // Remove the job cancel menu in local task menu.
  unset($data['tabs'][0]['entity.tapis_job.cancel_form']);
}

/**
 * Implements hook_entity_operation().
 *
 * This hook is used to add a "Clone" and "Cancel" operation to the job's entity operations.
 * It is also used to add a "Launch" operation to the app's entity operations.
 *
 * @param Drupal\tapis_job\TapisJobInterface $entity
 */
function tapis_job_entity_operation(EntityInterface $entity) {
  if ($entity->getEntityTypeId() === "tapis_job") {
    $operations = [];
    $operations['clone'] = [
      'title' => t('Clone'),
      'url' => Url::fromRoute('entity.tapis_job.clone_form', ['tapis_job' => $entity->id()]),
      'weight' => 0,
    ];
    $operations['cancel'] = [
      'title' => t('Cancel'),
      'url' => Url::fromRoute('entity.tapis_job.cancel_form', ['tapis_job' => $entity->id()]),
      'weight' => 1,
    ];

    // If the job's app isn't restartable or the job isn't in a terminal state, remove the job restart form.
    $app = $entity->getApp();
    if ($app) {
      if (isset($app->get(AppDrupalIds::APP_RESTARTABLE)->getValue()[0]['value'])) {
        $is_app_restartable = boolval($app->get(AppDrupalIds::APP_RESTARTABLE)
          ->getValue()[0]['value']);
      }
      else {
        $is_app_restartable = FALSE;
      }
      $job_status = $entity->getStatus();
      if ($job_status !== NULL) {
        $job_status = strtoupper($job_status);
      }
      $is_terminal_job_status = $job_status === "FINISHED" || $job_status === "CANCELLED" || $job_status === "FAILED";
      if ($is_app_restartable && $is_terminal_job_status) {
        $operations['restart'] = [
          'title' => t('Restart'),
          'url' => Url::fromRoute('entity.tapis_job.restart_form', ['tapis_job' => $entity->id()]),
          'weight' => 1,
        ];
      }
    }

    return $operations;
  }
  elseif ($entity->getEntityTypeId() === "node" && $entity->bundle() === AppDrupalIds::NODE_BUNDLE_APP) {
    $operations = [];
    $operations['launch'] = [
      'title' => t('Launch'),
      'url' => Url::fromRoute('entity.tapis_job.app_launch_form', ['node' => $entity->id()]),
      'weight' => 100,
    ];
    return $operations;
  }
}

/**
 * Implements hook_update_N().
 *
 * This hook is used to update the tapis_job entity type (add remoteStarted, remoteEnded, and tapisStatus fields).
 */
function tapis_job_update_9101() {
  // Add the "remoteStarted", "remoteEnded", and "tapisStatus" fields to the tapis_job entity type.
  $remoteStarted = BaseFieldDefinition::create('datetime')->setLabel(t('Job start timestamp'))->setRequired(FALSE)->setDisplayConfigurable('form', FALSE)->setDisplayOptions('view', ['label' => 'inline', 'type' => 'string'])->setDisplayConfigurable('view', TRUE);
  $remoteEnded = BaseFieldDefinition::create('datetime')->setLabel(t('Job end timestamp'))->setRequired(FALSE)->setDisplayConfigurable('form', FALSE)->setDisplayOptions('view', ['label' => 'inline', 'type' => 'string'])->setDisplayConfigurable('view', TRUE);
  $tapisStatus = BaseFieldDefinition::create('string')->setLabel(t('Status'))->setRequired(FALSE)->setSetting('max_length', 255)->setDisplayConfigurable('form', FALSE)->setDisplayOptions('view', ['label' => 'inline', 'type' => 'string'])->setDisplayConfigurable('view', TRUE);

  \Drupal::entityDefinitionUpdateManager()
    ->installFieldStorageDefinition('remoteStarted', 'tapis_job', 'tapis_job', $remoteStarted);
  \Drupal::entityDefinitionUpdateManager()
    ->installFieldStorageDefinition('remoteEnded', 'tapis_job', 'tapis_job', $remoteEnded);
  \Drupal::entityDefinitionUpdateManager()
    ->installFieldStorageDefinition('tapisStatus', 'tapis_job', 'tapis_job', $tapisStatus);
}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc