Skip to content

Commit

Permalink
Merge pull request #68 from youvo/lifecycle-history
Browse files Browse the repository at this point in the history
Inscribe lifecycle history with every project transition
  • Loading branch information
simonbaese authored Dec 26, 2024
2 parents f3a1950 + 3541821 commit dbf64fc
Show file tree
Hide file tree
Showing 20 changed files with 348 additions and 39 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies:
- field.field.project.project.field_image
- field.field.project.project.field_image_copyright
- field.field.project.project.field_lifecycle
- field.field.project.project.field_lifecycle_history
- field.field.project.project.field_local
- field.field.project.project.field_material
- field.field.project.project.field_participants
Expand Down Expand Up @@ -95,6 +96,12 @@ content:
region: content
settings: { }
third_party_settings: { }
field_lifecycle_history:
type: null
weight: 121
region: content
settings: { }
third_party_settings: { }
field_local:
type: boolean_checkbox
weight: 16
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ dependencies:
- field.field.project.project.field_image
- field.field.project.project.field_image_copyright
- field.field.project.project.field_lifecycle
- field.field.project.project.field_lifecycle_history
- field.field.project.project.field_local
- field.field.project.project.field_material
- field.field.project.project.field_participants
Expand Down Expand Up @@ -96,6 +97,13 @@ content:
third_party_settings: { }
weight: 8
region: content
field_lifecycle_history:
type: null
label: above
settings: { }
third_party_settings: { }
weight: 18
region: content
field_local:
type: boolean
label: above
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
uuid: 4f801fce-81be-465a-9383-d9a3a9f92137
langcode: de
status: true
dependencies:
config:
- field.storage.project.field_lifecycle_history
module:
- lifecycle
- projects
id: project.project.field_lifecycle_history
field_name: field_lifecycle_history
entity_type: project
bundle: project
label: 'Lifecycle History'
description: ''
required: false
translatable: false
default_value: { }
default_value_callback: ''
settings: { }
field_type: lifecycle_history_item
19 changes: 19 additions & 0 deletions config/sync/field.storage.project.field_lifecycle_history.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
uuid: e80ad92f-3570-49a2-b92c-2e23ae1d74e2
langcode: de
status: true
dependencies:
module:
- lifecycle
- projects
id: project.field_lifecycle_history
field_name: field_lifecycle_history
entity_type: project
type: lifecycle_history_item
settings: { }
module: lifecycle
locked: false
cardinality: -1
translatable: true
indexes: { }
persist_with_no_fields: false
custom_storage: false
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
<?php

declare(strict_types=1);

namespace Drupal\lifecycle\Plugin\Field\FieldType;

use Drupal\Core\Field\FieldItemBase;
use Drupal\Core\Field\FieldStorageDefinitionInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Drupal\Core\TypedData\DataDefinition;

/**
* Provides a lifecycle history field item.
*
* This field type does not provide an UI and is intended to be used in code.
*
* @FieldType(
* id = "lifecycle_history_item",
* label = @Translation("Lifecyle History"),
* description = @Translation("Allows you to store a workflow state."),
* default_formatter = NULL,
* default_widget = NULL,
* )
*
* @property string|null $transition
* @property string|null $from
* @property string $to
* @property int $uid
* @property int $timestamp
*/
class LifecycleHistoryItem extends FieldItemBase {

/**
* {@inheritdoc}
*/
public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition): array {

$properties['transition'] = DataDefinition::create('string')
->setLabel(new TranslatableMarkup('Transition'))
->setDescription(new TranslatableMarkup('The type of the transition.'));

$properties['from'] = DataDefinition::create('string')
->setLabel(new TranslatableMarkup('From'))
->setDescription(new TranslatableMarkup('The state that the transition started from.'));

$properties['to'] = DataDefinition::create('string')
->setLabel(new TranslatableMarkup('To'))
->setDescription(new TranslatableMarkup('The state that the transition went to.'))
->setRequired(TRUE);

$properties['uid'] = DataDefinition::create('integer')
->setLabel(new TranslatableMarkup('Initiator'))
->setDescription(new TranslatableMarkup('The initiator of the transition.'))
->setSetting('unsigned', TRUE)
->setRequired(TRUE);

$properties['timestamp'] = DataDefinition::create('timestamp')
->setLabel(new TranslatableMarkup('Timestamp'))
->setDescription(new TranslatableMarkup('The time of the transition.'))
->setRequired(TRUE);

return $properties;
}

/**
* {@inheritdoc}
*/
public static function schema(FieldStorageDefinitionInterface $field_definition): array {
return [
'columns' => [
'transition' => [
'description' => 'The type of the transition.',
'type' => 'varchar',
'length' => 64,
],
'from' => [
'description' => 'The state that the transition started from.',
'type' => 'varchar',
'length' => 64,
],
'to' => [
'description' => 'The state that the transition went to.',
'type' => 'varchar',
'length' => 64,
],
'uid' => [
'description' => 'The initiator of the transition.',
'type' => 'int',
'unsigned' => TRUE,
],
'timestamp' => [
'description' => 'The time of the transition.',
'type' => 'int',
'unsigned' => TRUE,
],
],
'indexes' => [
'format' => ['transition'],
],
];
}

/**
* {@inheritdoc}
*/
public function isEmpty(): bool {
return FALSE;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@
use Drupal\workflows\WorkflowInterface;

/**
* Workflow state field item.
* Provides a lifecyle workflow state field item.
*
* @FieldType(
* id = "lifecycle_item",
* label = @Translation("Workflows"),
* description = @Translation("Allows you to store a workflow state."),
* label = @Translation("Lifecycle"),
* description = @Translation("Allows you to store a lifecycle workflow state."),
* constraints = {"LifecycleConstraint" = {}},
* default_formatter = "lifecycle_state_list",
* default_widget = "options_select"
Expand Down
2 changes: 1 addition & 1 deletion web/modules/custom/projects/projects/projects.services.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ services:
project.lifecycle:
class: Drupal\projects\Service\ProjectLifecycle
arguments:
[ '@entity_type.manager' ]
[ '@current_user', '@entity_type.manager', '@datetime.time' ]
logger.channel.projects:
parent: logger.channel_base
arguments: [ 'projects' ]
Expand Down
30 changes: 26 additions & 4 deletions web/modules/custom/projects/projects/src/Entity/Project.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@
use Drupal\Core\Entity\EntityTypeInterface;
use Drupal\Core\Field\BaseFieldDefinition;
use Drupal\Core\Session\AccountInterface;
use Drupal\creatives\Entity\Creative;
use Drupal\organizations\Entity\Organization;
use Drupal\projects\Event\ProjectCreateEvent;
use Drupal\projects\Plugin\Field\UserIsApplicantFieldItemList;
use Drupal\projects\Plugin\Field\UserIsManagerFieldItemList;
use Drupal\projects\Plugin\Field\UserIsParticipantFieldItemList;
use Drupal\projects\ProjectInterface;
use Drupal\projects\ProjectResultInterface;
use Drupal\projects\ProjectState;
use Drupal\projects\Service\ProjectLifecycleInterface;
use Drupal\user\EntityOwnerTrait;
use Drupal\user_types\Utility\Profile;
Expand Down Expand Up @@ -113,6 +115,23 @@ public function delete(): void {
parent::delete();
}

/**
* {@inheritdoc}
*/
public function postCreate(EntityStorageInterface $storage): void {
// Set first item in lifecycle history.
if ($this->hasField('field_lifecycle_history')) {
$this->set('field_lifecycle_history', [
'transition' => NULL,
'from' => NULL,
'to' => ProjectState::DRAFT->value,
'uid' => \Drupal::currentUser()->id(),
'timestamp' => $this->getCreatedTime(),
]);
}
parent::postCreate($storage);
}

/**
* {@inheritdoc}
*/
Expand Down Expand Up @@ -175,11 +194,14 @@ public function access($operation, ?AccountInterface $account = NULL, $return_as
*
* Overwritten method for type hinting.
*/
public function getOwner(): Organization {
public function getOwner(): Organization|Creative {
$key = $this->getEntityType()->getKey('owner');
/** @var \Drupal\organizations\Entity\Organization $organization */
$organization = $this->get($key)->entity;
return $organization;
/** @var \Drupal\organizations\Entity\Organization|\Drupal\creatives\Entity\Creative $owner */
$owner = $this->get($key)->entity;
if (!$owner instanceof Organization && !$owner->hasPermission('administer site')) {
throw new \LogicException('The owner of a project should be an organization');
}
return $owner;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Drupal\Core\Field\FieldItemList;
use Drupal\Core\TypedData\ComputedItemListTrait;
use Drupal\organizations\ManagerInterface;

/**
* AppliedFieldItemList class to generate a computed field.
Expand All @@ -29,8 +30,11 @@ protected function computeValue(): void {
$account = \Drupal::currentUser();

// Set manager status.
$owner = $project->getOwner();
$value = $owner instanceof ManagerInterface ?
$project->getOwner()->isManager($account) : FALSE;
/** @var \Drupal\youvo\Plugin\Field\FieldType\CacheableBooleanItem $item */
$item = $this->createItem(0, $project->getOwner()->isManager($account));
$item = $this->createItem(0, $value);
$item->getValueProperty()->mergeCacheMaxAge(0);
$this->list[] = $item;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use Drupal\Core\Entity\ContentEntityInterface;
use Drupal\Core\Entity\EntityPublishedInterface;
use Drupal\Core\Session\AccountInterface;
use Drupal\creatives\Entity\Creative;
use Drupal\organizations\Entity\Organization;
use Drupal\projects\Service\ProjectLifecycleInterface;
use Drupal\user\EntityOwnerInterface;
Expand Down Expand Up @@ -176,7 +177,9 @@ public function getResult(): ProjectResultInterface;
* Returns the entity owner's user entity.
*
* Overwrite method for type hinting.
*
* Might be a creative user during administrative tasks.
*/
public function getOwner(): Organization;
public function getOwner(): Organization|Creative;

}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

namespace Drupal\projects\Service;

use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Field\FieldItemListInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\lifecycle\Exception\LifecycleTransitionException;
use Drupal\projects\ProjectInterface;
use Drupal\projects\ProjectState;
Expand All @@ -15,6 +18,7 @@ class ProjectLifecycle implements ProjectLifecycleInterface {

const WORKFLOW_ID = 'project_lifecycle';
const LIFECYCLE_FIELD = 'field_lifecycle';
const LIFECYCLE_HISTORY_FIELD = 'field_lifecycle_history';

/**
* The project calling the lifecycle.
Expand All @@ -27,7 +31,9 @@ class ProjectLifecycle implements ProjectLifecycleInterface {
* Constructs a ProjectLifecycle object.
*/
public function __construct(
protected AccountProxyInterface $currentUser,
protected EntityTypeManagerInterface $entityTypeManager,
protected TimeInterface $time,
) {}

/**
Expand Down Expand Up @@ -126,6 +132,13 @@ public function reset(): bool {
return $this->doTransition(ProjectTransition::RESET);
}

/**
* {@inheritdoc}
*/
public function history(): FieldItemListInterface {
return $this->project()->get(static::LIFECYCLE_HISTORY_FIELD);
}

/**
* Abstraction of forward transition flow check.
*/
Expand All @@ -152,9 +165,11 @@ protected function canTransition(ProjectTransition $transition, ProjectState $fr
* Sets new project state for given transition.
*/
protected function doTransition(ProjectTransition $transition): bool {
$old_state = $this->getState();
$new_state = $this->getSuccessorFromTransition($transition);
if ($this->canTransition($transition, $this->getState(), $new_state)) {
if ($this->canTransition($transition, $old_state, $new_state)) {
$this->project()->set(static::LIFECYCLE_FIELD, $new_state->value);
$this->inscribeTransition($transition, $old_state, $new_state);
return TRUE;
}
throw new LifecycleTransitionException($transition->value);
Expand All @@ -174,4 +189,17 @@ protected function getSuccessorFromTransition(ProjectTransition $transition): Pr
};
}

/**
* Inscribes transition in lifecycle history.
*/
protected function inscribeTransition(ProjectTransition $transition, ProjectState $from, ProjectState $to): void {
$this->project()->get(static::LIFECYCLE_HISTORY_FIELD)->appendItem([
'transition' => $transition->value,
'from' => $from->value,
'to' => $to->value,
'uid' => $this->currentUser->id(),
'timestamp' => $this->time->getCurrentTime(),
]);
}

}
Loading

0 comments on commit dbf64fc

Please sign in to comment.