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);
}
