Skip to content
Open
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
150 changes: 150 additions & 0 deletions .github/workflows/e2e.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
# Standalone Playwright e2e job, not part of the shared
# glpi-project/plugin-ci-workflows reusable CI (see its README for why: this
# needs a dedicated e2e_testing database/environment and browser install,
# which the shared workflow does not provide).
#
# To reuse this file in another plugin: copy it as-is and update PLUGIN_KEY
# below, plus the specs path if it differs from tests/e2e/specs.
name: "End-to-end tests"

on:
push:
branches:
- "main"
tags:
- "*"
pull_request:
workflow_dispatch:

concurrency:
group: "${{ github.workflow }}-${{ github.ref }}"
cancel-in-progress: true

env:
PLUGIN_KEY: "gantt"

jobs:
e2e:
name: "Playwright"
runs-on: "ubuntu-latest"
container:
image: "ghcr.io/glpi-project/githubactions-glpi-apache:php-8.2-glpi-11.0.x"
# `--user github-actions-runner` is mandatory to prevent rights issues on mounted volume during checkout operation
options: >-
--volume ${{ github.workspace }}:/var/www/glpi/plugins:rw
--user github-actions-runner
services:
db:
image: "ghcr.io/glpi-project/githubactions-mariadb:10.6"
env:
MYSQL_ALLOW_EMPTY_PASSWORD: "yes"
MYSQL_DATABASE: "glpi_e2e"
options: >-
--shm-size=1g
defaults:
run:
# By default, execute commands using the `www-data` user to prevent rights issues on GLPI generated files.
shell: "sudo --set-home --user=www-data --preserve-env bash --noprofile --norc -eo pipefail {0}"
working-directory: "/var/www/glpi"
steps:
- name: "Checkout"
uses: "actions/checkout@v7"
with:
path: "${{ env.PLUGIN_KEY }}"

- name: "Fix plugin directory ownership"
# Use default `bash` shell with `github-actions-runner` user, the only
# one allowed passwordless sudo.
shell: "bash"
run: |
sudo setfacl --recursive --modify u:www-data:rwx "/var/www/glpi/plugins/${{ env.PLUGIN_KEY }}"

- name: "Mark plugin directory as safe for git"
run: |
git config --global --add safe.directory "/var/www/glpi/plugins/${{ env.PLUGIN_KEY }}"

- name: "Restore composer cache"
uses: "actions/cache@v6"
with:
path: "/var/www/glpi/plugins/${{ env.PLUGIN_KEY }}/vendor"
key: "${{ env.PLUGIN_KEY }}-composer-${{ hashFiles(format('{0}/composer.lock', env.PLUGIN_KEY)) }}"

- name: "Install composer dependencies"
working-directory: "/var/www/glpi/plugins/${{ env.PLUGIN_KEY }}"
run: |
composer install --ansi --no-interaction --no-progress --prefer-dist

- name: "Restore npm cache"
uses: "actions/cache@v6"
with:
path: "/var/www/glpi/plugins/${{ env.PLUGIN_KEY }}/node_modules"
key: "${{ env.PLUGIN_KEY }}-npm-${{ hashFiles(format('{0}/package-lock.json', env.PLUGIN_KEY)) }}"

- name: "Build plugin frontend assets"
# `npm install` triggers the `postinstall` script, which runs the webpack
# build; its output (public/lib, public/build) is gitignored.
working-directory: "/var/www/glpi/plugins/${{ env.PLUGIN_KEY }}"
run: |
npm install

- name: "Install e2e database"
# Port 8090 is the vhost bound to GLPI_ENVIRONMENT_TYPE=e2e_testing in
# this image; port 80 serves a different (unconfigured) environment.
run: |
bin/console database:install --ansi --no-interaction --force --reconfigure --no-telemetry \
--db-host=db --db-name=glpi_e2e --db-user=root --db-password="" \
--env=e2e_testing
bin/console config:set url_base "http://localhost:8090" --env=e2e_testing
bin/console config:set url_base_api "http://localhost:8090/apirest.php" --env=e2e_testing

- name: "Install and activate plugin"
run: |
bin/console plugin:install --ansi --no-interaction --username=glpi "${{ env.PLUGIN_KEY }}" --env=e2e_testing
bin/console plugin:activate --ansi --no-interaction "${{ env.PLUGIN_KEY }}" --env=e2e_testing

- name: "Run apache"
# Use default `bash` shell with `github-actions-runner` user
shell: "bash"
run: |
sudo service apache2 start

- name: "Read installed Playwright version"
id: "playwright-version"
# Use default `bash` shell with `github-actions-runner` user: only this
# user can write to the runner-owned $GITHUB_OUTPUT file.
shell: "bash"
run: |
echo "version=$(node -p "require('@playwright/test/package.json').version")" >> "$GITHUB_OUTPUT"

- name: "Restore Playwright browser cache"
id: "playwright-cache"
uses: "actions/cache@v6"
with:
path: "/home/www-data/.cache/ms-playwright"
key: "playwright-chromium-${{ steps.playwright-version.outputs.version }}"

- name: "Install Playwright browser"
if: "steps.playwright-cache.outputs.cache-hit != 'true'"
run: |
npx playwright install chromium

- name: "Install Playwright system dependencies"
# Use default `bash` shell with `github-actions-runner` user: this
# command needs passwordless sudo to install system packages as root.
shell: "bash"
run: |
npx playwright install-deps chromium

- name: "Playwright"
env:
E2E_BASE_URL: "http://localhost:8090"
run: |
npx playwright test --project="plugin:${{ env.PLUGIN_KEY }}" --reporter=html

- name: "Upload Playwright report"
if: ${{ !cancelled() }}
uses: "actions/upload-artifact@v7"
with:
name: "playwright-report"
path: "/var/www/glpi/tests/e2e/results"
retention-days: 7
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,16 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]

### Changed

- Upgraded dhtmlx-gantt to v10.

### Added

- Added Playwright e2e tests covering the Gantt view (render, today highlight, drag and drop with rollback).

## [1.3.4] - 2026-08-04

### Added
Expand Down
10 changes: 5 additions & 5 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"license": "GPL-2.0-or-later",
"dependencies": {
"dhtmlx-gantt": "^9.1.4"
"dhtmlx-gantt": "^10.0.1"
},
"scripts": {
"build": "webpack --config .webpack.config.js",
Expand Down
10 changes: 5 additions & 5 deletions public/css/gantt.scss
Original file line number Diff line number Diff line change
Expand Up @@ -185,9 +185,9 @@
}
}

.gantt_today, .gantt_marker.today {
color: var(--dhx-gantt-base-colors-white);
font-size: var(--dhx-gantt-caption-font-size);
line-height: var(--dhx-gantt-caption-line-height);
font-weight: var(--dhx-gantt-caption-line-weight);
// highlights the current day column in the timeline (replaces the dhtmlx-gantt marker plugin, dropped in v10 community edition)
.gantt_task_cell.today {
background-color: rgb(255, 0, 0, 8%);
border-left: 1px solid red;
border-right: 1px solid red;
}
45 changes: 33 additions & 12 deletions public/js/gantt-helper.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const GlpiGantt = (function() {
const url = `${plugin_path }/ajax/gantt.php`;
const parseDateFormat = "%Y-%m-%d %H:%i";
let uiDateFormat = null;
let lastDragState = null;
switch (CFG_GLPI.date_format) {
case 1:
uiDateFormat = '%d-%m-%Y';
Expand Down Expand Up @@ -166,20 +167,21 @@ const GlpiGantt = (function() {
// enable tooltips and fullscreen mode
gantt.plugins({
tooltip: true,
fullscreen: true,
undo: true,
marker: true
fullscreen: true
});

gantt.config.show_marker = true;
gantt.config.current_date = new Date();

const today = new Date();
gantt.addMarker({
start_date: today,
css: "today",
text: __("Today", 'gantt')
});
// highlight the current day column (no marker plugin in dhtmlx-gantt 10 community edition)
gantt.templates.timeline_cell_class = (task, date) => {
const today = new Date();
if (date.getFullYear() === today.getFullYear()
&& date.getMonth() === today.getMonth()
&& date.getDate() === today.getDate()) {
return "today";
}
return "";
};

gantt.templates.tooltip_text = (start, end, task) => {
let text = `<b><span class="capitalize">${
Expand Down Expand Up @@ -324,7 +326,20 @@ const GlpiGantt = (function() {
});

if (!readonly) {
// catch task drag event to update db
// snapshot task state before a drag, to allow rolling back on server error
// (dhtmlx-gantt 10 community edition dropped the undo plugin)
gantt.attachEvent("onBeforeTaskDrag", (id) => {
const task = gantt.getTask(id);
lastDragState = {
id,
start_date: task.start_date,
end_date: task.end_date,
progress: task.progress
};
return true;
});

// catch task drag event to update db
gantt.attachEvent("onAfterTaskDrag", (id) => {
const task = gantt.getTask(id);
const progress = (Math.round(task.progress * 100 / 5) * 5) / 100; // prevent server side exception for wrong stepping
Expand Down Expand Up @@ -765,7 +780,13 @@ const GlpiGantt = (function() {
displayAjaxMessageAfterRedirect();
} else {
gantt.alert(__('Could not update Task[%s]: ', 'gantt').replace('%s', task.text) + json.error);
gantt.undo();
if (lastDragState && lastDragState.id === task.id) {
task.start_date = lastDragState.start_date;
task.end_date = lastDragState.end_date;
task.progress = lastDragState.progress;
gantt.updateTask(task.id);
gantt.render();
}
}
}
});
Expand Down
4 changes: 3 additions & 1 deletion public/js/libs.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
* -------------------------------------------------------------------------
*/

import gantt from 'dhtmlx-gantt/codebase/dhtmlxgantt.js';
// v10's UMD bundle (dhtmlxgantt.js) isn't statically analyzable by webpack,
// so its default export resolves to undefined; the real ESM build works.
import gantt from 'dhtmlx-gantt/codebase/dhtmlxgantt.es.js';
window.gantt = gantt;
import 'dhtmlx-gantt/codebase/dhtmlxgantt.css';
4 changes: 2 additions & 2 deletions templates/view.html.twig
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
#}

<input type="hidden" id="hf_gantt_item_state" value="" />
<div id="gantt-container" class="gantt-block" style="width:100%; height:63vh;"></div>
<div id="gantt-container" class="gantt-block" style="width:100%; height:63vh;" data-testid="gantt-container"></div>
<div class="gantt-block__features">
<ul class="gantt-block__controls mt-3">
<li class="gantt-menu-item gantt-menu-item-right">
Expand Down Expand Up @@ -57,7 +57,7 @@
</span>

<input type="radio" id="scale1" class="btn-check gantt_radio" name="scale" value="day" checked />
<label class="btn btn-outline-secondary" for="scale1">{{ __('Days', 'gantt') }}</label>
<label class="btn btn-outline-secondary" for="scale1" data-testid="gantt-zoom-days">{{ __('Days', 'gantt') }}</label>

<input type="radio" id="scale2" class="btn-check gantt_radio" name="scale" value="week" />
<label class="btn btn-outline-secondary" for="scale2">{{ __('Weeks', 'gantt') }}</label>
Expand Down
30 changes: 30 additions & 0 deletions tests/e2e/fixtures/gantt_fixture.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/**
* -------------------------------------------------------------------------
* gantt plugin for GLPI
* -------------------------------------------------------------------------
*
* LICENSE
*
* This file is part of gantt.
*
* gantt is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* any later version.
*
* gantt is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gantt. If not, see <http://www.gnu.org/licenses/>.
* -------------------------------------------------------------------------
* @copyright Copyright (C) 2013-2023 by gantt plugin team.
* @license GPLv2 https://www.gnu.org/licenses/gpl-2.0.html
* @link https://github.com/pluginsGLPI/gantt
* -------------------------------------------------------------------------
*/

// Re-export GLPI's own worker/profile/api fixtures, no plugin-specific fixture needed yet.
export * from '../../../../../tests/e2e/fixtures/glpi_fixture';
Loading
Loading