Introduce migration ListItem & ItemList classes

This commit is contained in:
Yonas Habteab 2023-07-24 13:45:04 +02:00 committed by Johannes Meyer
parent 2daa1447b7
commit 85b63dd067
5 changed files with 445 additions and 1 deletions

View File

@ -80,7 +80,8 @@ class StyleSheet
'css/icinga/modal.less',
'css/icinga/audit.less',
'css/icinga/health.less',
'css/icinga/php-diff.less'
'css/icinga/php-diff.less',
'css/icinga/pending-migration.less',
];
/**

View File

@ -0,0 +1,130 @@
<?php
/* Icinga Web 2 | (c) 2023 Icinga GmbH | GPLv2+ */
namespace Icinga\Web\Widget\ItemList;
use Generator;
use Icinga\Application\Hook\Common\DbMigration;
use Icinga\Application\Hook\MigrationHook;
use Icinga\Application\MigrationManager;
use Icinga\Forms\MigrationForm;
use ipl\I18n\Translation;
use ipl\Web\Common\BaseItemList;
use ipl\Web\Widget\EmptyState;
class MigrationList extends BaseItemList
{
use Translation;
protected $baseAttributes = ['class' => 'item-list'];
/** @var Generator<MigrationHook> */
protected $data;
/** @var ?MigrationForm */
protected $migrationForm;
/** @var bool Whether to render minimal migration list items */
protected $minimal = true;
/**
* Create a new migration list
*
* @param Generator<MigrationHook>|array<DbMigration|MigrationHook> $data
*
* @param ?MigrationForm $form
*/
public function __construct($data, MigrationForm $form = null)
{
parent::__construct($data);
$this->migrationForm = $form;
}
/**
* Set whether to render minimal migration list items
*
* @param bool $minimal
*
* @return $this
*/
public function setMinimal(bool $minimal): self
{
$this->minimal = $minimal;
return $this;
}
/**
* Get whether to render minimal migration list items
*
* @return bool
*/
public function isMinimal(): bool
{
return $this->minimal;
}
protected function getItemClass(): string
{
if ($this->isMinimal()) {
return MigrationListItemMinimal::class;
}
return MigrationListItem::class;
}
protected function assemble(): void
{
$itemClass = $this->getItemClass();
/** @var MigrationHook $data */
foreach ($this->data as $data) {
/** @var MigrationListItem|MigrationListItemMinimal $item */
$item = new $itemClass($data, $this);
if ($item instanceof MigrationListItemMinimal && $this->migrationForm) {
$migrateButton = $this->migrationForm->createElement(
'submit',
sprintf('migrate-%s', $data->getModuleName()),
[
'required' => false,
'label' => $this->translate('Migrate'),
'title' => sprintf(
$this->translatePlural(
'Migrate %d pending migration',
'Migrate all %d pending migrations',
$data->count()
),
$data->count()
)
]
);
$mm = MigrationManager::instance();
if ($data->isModule() && $mm->hasMigrations(MigrationHook::DEFAULT_MODULE)) {
$migrateButton->getAttributes()
->set('disabled', true)
->set(
'title',
$this->translate(
'Please apply all the pending migrations of Icinga Web first or use the apply all'
. ' button instead.'
)
);
}
$this->migrationForm->registerElement($migrateButton);
$item->setMigrateButton($migrateButton);
}
$this->addHtml($item);
}
if ($this->isEmpty()) {
$this->setTag('div');
$this->addHtml(new EmptyState(t('No items found.')));
}
}
}

View File

@ -0,0 +1,85 @@
<?php
/* Icinga Web 2 | (c) 2023 Icinga GmbH | GPLv2+ */
namespace Icinga\Web\Widget\ItemList;
use Icinga\Application\Hook\Common\DbMigration;
use ipl\Html\Attributes;
use ipl\Html\BaseHtmlElement;
use ipl\Html\Html;
use ipl\Html\HtmlElement;
use ipl\Html\HtmlString;
use ipl\Html\Text;
use ipl\I18n\Translation;
use ipl\Web\Common\BaseListItem;
use ipl\Web\Widget\Icon;
class MigrationListItem extends BaseListItem
{
use Translation;
/** @var DbMigration Just for type hint */
protected $item;
protected function assembleVisual(BaseHtmlElement $visual): void
{
if ($this->item->getLastState()) {
$visual->getAttributes()->add('class', 'upgrade-failed');
$visual->addHtml(new Icon('circle-xmark'));
}
}
protected function assembleTitle(BaseHtmlElement $title): void
{
$scriptPath = $this->item->getScriptPath();
/** @var string $parentDirs */
$parentDirs = substr($scriptPath, (int) strpos($scriptPath, 'schema'));
$parentDirs = substr($parentDirs, 0, strrpos($parentDirs, '/') + 1);
$title->addHtml(
new HtmlElement('span', null, Text::create($parentDirs)),
new HtmlElement('strong', null, Text::create($this->item->getVersion() . '.sql'))
);
if ($this->item->getLastState()) {
$title->addHtml(
new HtmlElement(
'span',
Attributes::create(['class' => 'upgrade-failed']),
Text::create($this->translate('Upgrade failed'))
)
);
}
}
protected function assembleHeader(BaseHtmlElement $header): void
{
$header->addHtml($this->createTitle());
}
protected function assembleCaption(BaseHtmlElement $caption): void
{
if ($this->item->getDescription()) {
$caption->addHtml(Text::create($this->item->getDescription()));
}
}
protected function assembleFooter(BaseHtmlElement $footer): void
{
if ($this->item->getLastState()) {
$footer->addHtml(
new HtmlElement(
'section',
Attributes::create(['class' => 'caption']),
new HtmlElement('pre', null, new HtmlString(Html::escape($this->item->getLastState())))
)
);
}
}
protected function assembleMain(BaseHtmlElement $main): void
{
$main->addHtml($this->createHeader(), $this->createCaption());
}
}

View File

@ -0,0 +1,128 @@
<?php
/* Icinga Web 2 | (c) 2023 Icinga GmbH | GPLv2+ */
namespace Icinga\Web\Widget\ItemList;
use Icinga\Application\Hook\Common\DbMigration;
use Icinga\Application\Hook\MigrationHook;
use ipl\Html\Attributes;
use ipl\Html\BaseHtmlElement;
use ipl\Html\Contract\FormElement;
use ipl\Html\FormattedString;
use ipl\Html\Html;
use ipl\Html\HtmlElement;
use ipl\Html\HtmlString;
use ipl\Html\Text;
use ipl\I18n\Translation;
use ipl\Web\Common\BaseListItem;
use ipl\Web\Url;
use ipl\Web\Widget\Icon;
use ipl\Web\Widget\Link;
use LogicException;
class MigrationListItemMinimal extends BaseListItem
{
use Translation;
/** @var ?FormElement */
protected $migrateButton;
/** @var MigrationHook Just for type hint */
protected $item;
/**
* Set a migration form of this list item
*
* @param FormElement $migrateButton
*
* @return $this
*/
public function setMigrateButton(FormElement $migrateButton): self
{
$this->migrateButton = $migrateButton;
return $this;
}
protected function assembleTitle(BaseHtmlElement $title): void
{
$title->addHtml(
FormattedString::create(
t('%s ', '<name>'),
HtmlElement::create('span', ['class' => 'subject'], $this->item->getName())
)
);
}
protected function assembleHeader(BaseHtmlElement $header): void
{
if ($this->migrateButton === null) {
throw new LogicException('Please set the migrate submit button beforehand');
}
$header->addHtml($this->createTitle());
$header->addHtml($this->migrateButton);
}
protected function assembleCaption(BaseHtmlElement $caption): void
{
$migrations = $this->item->getMigrations();
/** @var DbMigration $migration */
$migration = array_shift($migrations);
if ($migration->getLastState()) {
$caption->addHtml(Text::create($migration->getDescription()));
$wrapper = new HtmlElement(
'div',
Attributes::create([
'class' => ['errors-section', 'collapsible'],
'data-visible-height' => 150,
])
);
$wrapper->addHtml(
new Icon('circle-xmark'),
$caption,
new HtmlElement('pre', null, new HtmlString(Html::escape($migration->getLastState())))
);
$caption->prependWrapper($wrapper);
}
}
protected function assembleFooter(BaseHtmlElement $footer): void
{
$footer->addHtml((new MigrationList($this->item->getLatestMigrations(3)))->setMinimal(false));
if ($this->item->count() > 3) {
$footer->addHtml(
new Link(
sprintf($this->translate('Show all %d migrations'), $this->item->count()),
Url::fromPath(
'migrations/migration',
[MigrationHook::MIGRATION_PARAM => $this->item->getModuleName()]
),
[
'data-base-target' => '_next'
]
)
);
}
}
protected function assembleMain(BaseHtmlElement $main): void
{
$this->getAttributes()->add('class', 'minimal');
$main->addHtml($this->createHeader());
$caption = $this->createCaption();
if (! $caption->isEmpty()) {
$main->addHtml($caption);
}
$footer = $this->createFooter();
if ($footer) {
$main->addHtml($footer);
}
}
}

View File

@ -0,0 +1,100 @@
// Style
.migrations {
.migration-details {
> span {
color: @text-color-light;
}
summary {
color: @icinga-blue;
cursor: pointer;
&::-webkit-details-marker {
display: none; // Disable safari default details marker
}
}
}
.migration-form {
input[type="submit"] {
color: @icinga-blue;
background: @low-sat-blue;
border: none;
&:not(:disabled):hover {
background: @icinga-blue;
color: @text-color-on-icinga-blue;
}
&:disabled {
background: @disabled-gray;
color: @default-text-color-inverted;
}
}
}
.modules-header {
input[type="submit"] {
&:not(:disabled) {
background: transparent;
}
}
}
}
// Layout
.pending-migrations-hint {
top: 15%;
right: 50%;
position: fixed;
transform: translate(50%, -50%);
text-align: center;
> p {
font-size: 1.1em;
}
> h2 {
font-size: 2em;
}
}
.migrations {
.list-item {
align-items: baseline;
}
.migration-list-control {
padding-bottom: 1em;
}
.modules-header {
display: inline-flex;
flex-wrap: nowrap;
align-items: baseline;
.migration-form {
margin-left: .5em;
}
}
.migration-details {
font-size: 1.1em;
&[open] summary {
margin-bottom: .5em;
}
}
.migration-form {
input[type="submit"] {
padding: 0.25em 0.5em;
line-height: 1.75;
font-size: 1.1em;
.rounded-corners(3px);
}
}
}