Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion components/ILIAS/BackgroundTasks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,29 @@ $observer = new MockObserver();
$value = $taskManager->executeTask($task, $observer);
echo $value->getValue(); // will echo 2.
```


Configuring the component
-------------------------

To configure this component you need to add this to your `ilias.ini.php` file:

```ini
[background_tasks]
number_of_concurrent_tasks = <nr>
concurrency = <concurrency>
```

* Replace `<nr>` with a positive integer that defines how many tasks can run in parallel (only for `async` concurrency).
* Replace `<concurrency>` with one of these options:
* `sync`: perform tasks synchronously in the same request
* `async`: perform tasks asynchronously (needs SOAP to be elabled)

Pro-tip for developers
----------------------

Run this database update to enable SOAP quickly:

```sql
update settings set value = 1 where keyword = 'soap_user_administration';
update settings set value = "http://localhost/soap/server.php?wsdl" where keyword = 'soap_internal_wsdl_path';
```
2 changes: 0 additions & 2 deletions components/ILIAS/BackgroundTasks_/BackgroundTasks_.php
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,5 @@ public function init(
array | \ArrayAccess &$pull,
array | \ArrayAccess &$internal,
): void {
$contribute[PublicAsset::class] = fn(): ComponentJS =>
new ComponentJS($this, "background_task_refresh.js");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ public function getNotifications(): array
return [];
}

$this->dic->ui()->mainTemplate()->addJavaScript("assets/js/background_task_refresh.js");
$this->dic->language()->loadLanguageModule('background_tasks');

$id = fn(string $id): IdentificationInterface => $this->if->identifier($id);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,14 +42,18 @@ class ilBTControllerGUI implements ilCtrlBaseClassInterface, ilCtrlSecurityInter
public const CMD_REMOVE = 'abortBucket';
public const CMD_USER_INTERACTION = 'userInteraction';
public const IS_ASYNC = 'bt_task_is_async';
public const CMD_GET_REPLACEMENT_ITEM = "getAsyncReplacementItem";
public const CMD_REFRESH_NOTIFICATION_ITEM = "getAsyncNotificationItemState";
public const CMD_PROGRESS_BAR_STATE = "getAsyncProgressBarState";

public function executeCommand(): void
{
$cmd = $this->ctrl()->getCmd();
switch ($cmd) {
case self::CMD_GET_REPLACEMENT_ITEM:
$this->getAsyncReplacementItem();
case self::CMD_REFRESH_NOTIFICATION_ITEM:
$this->getAsyncNotificationItemReplacement();
break;
case self::CMD_PROGRESS_BAR_STATE:
$this->getAsyncProgressBarState();
break;
case self::CMD_USER_INTERACTION:
$this->userInteraction();
Expand Down Expand Up @@ -116,10 +120,10 @@ protected function abortBucket(): void


/**
* Loads one single aggregate notification item representing a button async
* to replace an existing one.
* Updates the @see ILIAS\UI\Component\Progress\Bar of the requested observer (id)
* on the client asynchronously.
*/
protected function getAsyncReplacementItem(): void
protected function getAsyncProgressBarState(): void
{
$observer_id = $this->retrieveObserverIdFromRequest();
if ($observer_id === null) {
Expand All @@ -132,13 +136,41 @@ protected function getAsyncReplacementItem(): void

$item_source = new ilBTPopOverGUI($this->dic());
$this->dic()->language()->loadLanguageModule('background_tasks');
$item = $item_source->getItemForObserver($bucket);

$this->sendSuccessResponse(
Streams::ofString(
$this->dic()->ui()->renderer()->renderAsync($item)
)
$progress_bar_state = $item_source->getProgressBarState($bucket);
$html = $this->ui()->renderer()->renderAsync($progress_bar_state);

$this->sendHtmlResponse($html);
}

/**
* Updates the @see ILIAS\UI\Component\Item\Notification SURROUNDINGS (not content)
* of the requested observer (id) on the client asynchronously.
*/
protected function getAsyncNotificationItemReplacement(): void
{
$observer_id = (int) $this->http()->request()->getQueryParams()[self::OBSERVER_ID];
$bucket = $this->dic()->backgroundTasks()->persistence()->loadBucket($observer_id);

$item_source = new ilBTPopOverGUI($this->dic());
$this->dic()->language()->loadLanguageModule('background_tasks');

$replacement_notification_item = $item_source->getItemForObserver($bucket);
$html = $this->ui()->renderer()->renderAsync($replacement_notification_item);

$this->sendHtmlResponse($html);
}


protected function sendHtmlResponse(string $html): void
{
$this->http()->saveResponse(
$this->http()->response()
->withHeader('Content-Type', 'text/html; charset=utf-8')
->withBody(Streams::ofString($html))
);
$this->http()->sendResponse();
$this->http()->close();
}


Expand Down
133 changes: 79 additions & 54 deletions components/ILIAS/BackgroundTasks_/classes/class.ilBTPopOverGUI.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,12 @@
use ILIAS\UI\Component\Button\Shy;
use ILIAS\UI\Component\Legacy\Content;
use ILIAS\BackgroundTasks\Task\Job;
use ILIAS\Filesystem\Stream\Streams;
use ILIAS\UI\Component\Progress\Bar;
use ILIAS\UI\Component\Component;
use ILIAS\BackgroundTasks\Task;
use ILIAS\UI\Implementation\Component\Signal;
use ILIAS\UI\Implementation\Component\SignalGenerator;

/**
* Class ilBTPopOverGUI
Expand All @@ -39,12 +45,14 @@ class ilBTPopOverGUI
{
use StateTranslator;

protected ILIAS\Data\Factory $data_factory;

public function __construct(protected Container $dic)
{
// this is bad, we should inject this.
$this->data_factory = new ILIAS\Data\Factory();
}


/**
* Get the Notification Items. DOES NOT DO ANY PERMISSION CHECKS.
*/
Expand Down Expand Up @@ -88,6 +96,8 @@ public function getItemForObserver(Bucket $observer): Notification
$state = $observer->getState();
$current_task = $observer->getCurrentTask();

$progress_bar = $this->getProgressbar($observer);

$icon = $f->symbol()->icon()->standard("bgtk", $this->txt("bg_task"));
$title = $observer->getTitle() . ($state === State::SCHEDULED ? " ({$this->txt('scheduled')})" : "");

Expand All @@ -98,53 +108,34 @@ public function getItemForObserver(Bucket $observer): Notification
$title = $primary_action->withLabel($title);
}
$item = $f->item()->notification($title, $icon);

// $item = $item->withProperties([
// $this->dic->language()->txt('nc_mail_prop_time') => \ilDatePresentation::formatDate(
// new \ilDateTime(time(), IL_CAL_UNIX)
// )
// ]);

$item = $item->withActions($f->dropdown()->standard($actions));
$input = $current_task->getInput();
$message = $current_task->getMessage($input);

if (!empty($message) && $message != null) {
if (!empty($message)) {
$item = $item->withDescription($message);
} else {
$item = $item->withAdditionalContent($this->getProgressbar($observer));
}

$item = $item->withAdditionalContent($this->getUIComponentAsLegacyContent($progress_bar));

return $item->withCloseAction(
$this->getCloseButtonAction($current_task->getRemoveOption(), $redirect_uri, $observer)
);
}

$item = $f->item()->notification($title, $icon);

if ($state === State::RUNNING) {
$url = $this->getRefreshUrl($observer);
//Running Items probably need to refresh themselves, right?
$item = $item->withAdditionalOnLoadCode(fn($id): string => "var notification_item = il.UI.item.notification.getNotificationItemObject($('#$id'));
il.BGTask.refreshItem(notification_item,'$url');");

$expected = $current_task instanceof Job ? $current_task->getExpectedTimeOfTaskInSeconds() : 0;
$possibly_failed = ($observer->getLastHeartbeat() < (time() - $expected));
if ($possibly_failed) {
$item = $item->withDescription($this->txt('task_might_be_failed'));
$item = $item->withCloseAction(
$this->getCloseButtonAction($current_task->getAbortOption(), $redirect_uri, $observer)
);
}
if ($state === State::RUNNING && $this->hasBucketPossiblyFailed($observer)) {
$item = $item->withCloseAction(
$this->getCloseButtonAction($current_task->getAbortOption(), $redirect_uri, $observer)
);
}

return $item->withAdditionalContent($this->getDefaultCardContent($observer));
}

$item = $item->withCloseAction(
$this->getCloseButtonAction($current_task->getAbortOption(), $redirect_uri, $observer)
);

private function getDefaultCardContent(Bucket $observer): Content
{
return $this->getProgressbar($observer);
return $item->withAdditionalContent($this->getUIComponentAsLegacyContent($progress_bar));
}


Expand Down Expand Up @@ -194,32 +185,40 @@ function (Option $option) use ($ctrl, $factory, $observer, $persistence, $redire
);
}

private function getProgressbar(Bucket $observer): Bar
{
$progress_bar = $this->dic->ui()->factory()->progress()->bar(
$this->txt('progress'),
$this->data_factory->uri(ILIAS_HTTP_PATH . '/' . $this->getProgressStateUrl($observer))
);

// immediately start the progress bar after being rendered
$progress_bar = $progress_bar->withAdditionalOnLoadCode(fn($id) => "
il.UI.Progress.Bar.indeterminate(
'{$progress_bar->getUpdateSignal()}',
'{$this->txt('scheduled')}',
);
");

return $progress_bar;
}

private function getProgressbar(Bucket $observer): Content
public function getProgressBarState(Bucket $observer): \ILIAS\UI\Component\Progress\State\Bar\State
{
$percentage = $observer->getOverallPercentage();
$task_not_responding_state = $this->dic->ui()->factory()->progress()->state()->bar()->failure($this->txt('task_might_be_failed'));

switch (true) {
case ($percentage === 100):
$running = "";
$content = $this->dic->language()->txt("completed");
break;
case ($observer->getState() === State::USER_INTERACTION):
$running = "";
$content = $this->dic->language()->txt("waiting");
break;
default:
$running = "active";
$content = "{$percentage}%";
break;
if ($this->hasBucketPossiblyFailed($observer)) {
return $task_not_responding_state;
}

return $this->dic->ui()->factory()->legacy()->content(" <div class='progress'>
<div class='progress-bar progress-bar-striped {$running}' role='progressbar' aria-valuenow='{$percentage}'
aria-valuemin='0' aria-valuemax='100' style='width:{$percentage}%'>
{$content}
</div>
</div> ");
$percentage = $observer->getOverallPercentage();
if (100 > $percentage) {
return $this->dic->ui()->factory()->progress()->state()->bar()->determinate($percentage, $this->txt('waiting'));
}
if (100 <= $percentage) {
return $this->dic->ui()->factory()->progress()->state()->bar()->success($this->txt('completed'));
}
return $task_not_responding_state;
}


Expand Down Expand Up @@ -247,14 +246,40 @@ private function getCloseButtonAction(Option $option, string $redirect_uri, Buck
return $action;
}

protected function getUIComponentAsLegacyContent(Component $component): Content
{
return $this->dic->ui()->factory()->legacy()->content(
$this->dic->ui()->renderer()->render($component),
);
}

private function getRefreshUrl(Bucket $observer): string
protected function hasBucketPossiblyFailed(Bucket $observer): bool
{
$task = $observer->getCurrentTask();
$expected = $task instanceof Job ? $task->getExpectedTimeOfTaskInSeconds() : 0;
return ($observer->getLastHeartbeat() < (time() - $expected));
}

private function getProgressStateUrl(Bucket $observer): string
{
$ctrl = $this->dic->ctrl();
$persistence = $this->dic->backgroundTasks()->persistence();
$ctrl->setParameterByClass(ilBTControllerGUI::class, ilBTControllerGUI::OBSERVER_ID, $persistence->getBucketContainerId($observer));

return $ctrl->getLinkTargetByClass([ilBTControllerGUI::class], ilBTControllerGUI::CMD_GET_REPLACEMENT_ITEM);
return $ctrl->getLinkTargetByClass([ilBTControllerGUI::class], ilBTControllerGUI::CMD_PROGRESS_BAR_STATE);
}

private function getRefreshNotificationItemUrl(Bucket $observer): string
{
$ctrl = $this->dic->ctrl();
$persistence = $this->dic->backgroundTasks()->persistence();
$ctrl->setParameterByClass(
ilBTControllerGUI::class,
ilBTControllerGUI::OBSERVER_ID,
$persistence->getBucketContainerId($observer)
);

return $ctrl->getLinkTargetByClass([ilBTControllerGUI::class], ilBTControllerGUI::CMD_REFRESH_NOTIFICATION_ITEM);
}


Expand Down

This file was deleted.

Loading
Loading